├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── future_task.md └── workflows │ ├── pr-any.yml │ └── publish.yml ├── .gitignore ├── .prettierrc.js ├── LICENSE ├── NOTICE ├── README.md ├── eth-truffle ├── .gitignore ├── .travis.yml ├── audit │ └── quantstamp-audit.pdf ├── contracts │ ├── Lock.sol │ ├── Lockdrop.sol │ └── Migrations.sol ├── index.js ├── lib │ └── lockdrop.js ├── migrations │ ├── 1_initial_migration.js │ └── 2_lockdrop_migration.js ├── package-lock.json ├── package.json ├── test │ ├── 1_Lock.test.js │ └── 2_Lockdrop.test.js └── truffle.js ├── ionic.config.json ├── package.json ├── public ├── CNAME ├── assets │ ├── icon │ │ ├── favicon.ico │ │ └── icon.png │ ├── ld_logo_b.png │ └── plasm-logo.png ├── index.html ├── manifest.json └── robots.txt ├── src ├── App.tsx ├── api │ ├── Api.tsx │ └── Web3Api.tsx ├── components │ ├── CopyMessageBox.tsx │ ├── CountdownTimer.tsx │ ├── DropdownOption.tsx │ ├── EthLock │ │ ├── AffiliationList.tsx │ │ ├── CurrentLocks.tsx │ │ ├── EthGlobalLocks.tsx │ │ ├── LockdropCountdownPanel.tsx │ │ ├── LockdropForm.tsx │ │ ├── LockdropResult.tsx │ │ └── LockedEthList.tsx │ ├── Footer.tsx │ ├── LoadingOverlay.tsx │ ├── Navbar.tsx │ ├── NotificationMessage.tsx │ ├── RealtimeLockdrop │ │ ├── ClaimStatus.tsx │ │ └── ClaimableItem.tsx │ ├── SectionCard.tsx │ ├── SideMenu.tsx │ └── TosAgreementModal.tsx ├── config │ └── endpoints.ts ├── contexts │ ├── ApiContext.ts │ └── Web3Context.ts ├── contracts │ ├── Lock.json │ ├── Lockdrop.json │ └── Migrations.json ├── data │ ├── UserAgreement.md │ ├── affiliationProgram.ts │ ├── links.ts │ ├── lockInfo.ts │ └── pages.ts ├── globals.d.ts ├── helpers │ ├── getWeb3.ts │ ├── lockdrop │ │ ├── EthereumLockdrop.ts │ │ ├── PolkadotLockdrop.ts │ │ └── debug.log │ ├── plasmUtils.ts │ └── useChainInfo.ts ├── index.css ├── index.tsx ├── pages │ ├── EthRealTimeLockPage.tsx │ ├── FirstEthLockdropPage.tsx │ ├── LandingPage.tsx │ ├── LockdropCalcPage.tsx │ └── LockdropStatPage.tsx ├── react-app-env.d.ts ├── resources │ ├── dusty-icon.svg │ ├── ethereum_logo.svg │ ├── home-outline.svg │ ├── ld_logo_a.png │ ├── ld_logo_b.png │ ├── ledger_logo.svg │ ├── logo-discord.svg │ ├── logo-github.svg │ ├── logo-telegram.svg │ ├── logo-twitter.svg │ ├── plasm-icon.svg │ ├── plasm-logo.png │ ├── quantstamp-logo.png │ └── trezor_logo.svg ├── serviceWorker.ts ├── setupTests.ts ├── tests │ ├── ethLock.test.ts │ └── plasmLock.test.ts ├── theme │ ├── themes.ts │ └── variables.css └── types │ ├── EtherScanTypes.ts │ ├── LockdropModels.ts │ └── PlasmDrop.ts ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react 5 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 6 | 'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 7 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 8 | ], 9 | parserOptions: { 10 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 11 | sourceType: 'module', // Allows for the use of imports 12 | ecmaFeatures: { 13 | jsx: true, // Allows for the parsing of JSX 14 | }, 15 | }, 16 | rules: { 17 | '@typescript-eslint/explicit-function-return-type': 'off', 18 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 19 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 20 | }, 21 | settings: { 22 | react: { 23 | version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use 24 | }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: fix 6 | assignees: hoonsubin 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/future_task.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Future task issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | --- 8 | ## Required Tasks 9 | - [ ] {task 1} 10 | - [ ] {task 2} 11 | - [ ] ... 12 | ## Description 13 | {Task description, special notes, Dependencies issue or PR, etc.} 14 | ## Estimated dev time 15 | {How long this task will take to be resolved in approximate working days} 16 | -------------------------------------------------------------------------------- /.github/workflows/pr-any.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | on: [pull_request] 3 | 4 | jobs: 5 | lint: 6 | name: Linting 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [14.x] 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Use Node.js ${{ matrix.node-version }} 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | - name: lint 18 | run: | 19 | yarn 20 | yarn lint 21 | working-directory: ./ 22 | run_tests: 23 | name: Unit Tests 24 | runs-on: ubuntu-latest 25 | strategy: 26 | matrix: 27 | node-version: [14.x] 28 | steps: 29 | - uses: actions/checkout@v1 30 | - name: Use Node.js ${{ matrix.node-version }} 31 | uses: actions/setup-node@v1 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | - name: unit_test 35 | run: | 36 | yarn 37 | yarn test 38 | working-directory: ./ 39 | build_code: 40 | name: Build Code 41 | runs-on: ubuntu-latest 42 | strategy: 43 | matrix: 44 | node-version: [14.x] 45 | steps: 46 | - uses: actions/checkout@v1 47 | - name: Use Node.js ${{ matrix.node-version }} 48 | uses: actions/setup-node@v1 49 | with: 50 | node-version: ${{ matrix.node-version }} 51 | - name: build 52 | run: | 53 | yarn 54 | yarn build 55 | working-directory: ./ 56 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: lockdrop-ui publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [14.x] 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v2 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: Install dependencies 21 | run: yarn 22 | working-directory: ./ 23 | - name: applications Build 24 | run: yarn build 25 | working-directory: ./ 26 | - name: Deploy 27 | uses: peaceiris/actions-gh-pages@v3 28 | with: 29 | github_token: ${{ secrets.GITHUB_TOKEN }} 30 | publish_dir: ./build 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .vscode 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 4, 7 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2020 Stake Technologies 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Plasm Ethereum Lockdrop 2 | Copyright 2020 Stake Technologies . 3 | 4 | This product includes software developed at Stake Technologies . 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plasm Network Genesis Lockdrop 2 | 3 | [![Build Status](https://travis-ci.org/staketechnologies/lockdrop-ui.svg?branch=master)](https://travis-ci.org/github/staketechnologies/lockdrop-ui) 4 | 5 | This repository contains smart contracts and DApp helper to make participation a bit easy. 6 | 7 | Native tokens genesis distribution in Plasm Network carry out according to `Lock` events in Ethereum Lockdrop smart contract. 8 | 9 | ## Audit 10 | 11 | 1. By Quantstamp at 11 Feb 2020: [audit report](https://github.com/staketechnologies/ethereum-lockdrop/blob/16a2d495d85f2d311957b9cf366204fbfabadeaa/audit/quantstamp-audit.pdf) 12 | 13 | ## Documentation 14 | 15 | - [Introduction](https://medium.com/stake-technologies/plasm-lockdrop-introduction-54614592a13) 16 | - [Participation Guide](https://medium.com/stake-technologies/plasm-lockdrop-howto-c3cd28e6fed1) 17 | - [Testnet Lockdrop](https://docs.plasmnet.io/PlasmTestnet/Lockdrop.html) 18 | 19 | ## This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and [Ionic Framework](https://ionicframework.com/) 20 | 21 | ## Available Scripts 22 | 23 | In the project directory, you can run: 24 | 25 | ### `yarn start` 26 | 27 | Runs the app in the development mode. 28 | 29 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 30 | 31 | The page will reload if you make edits. 32 | 33 | You will also see any lint errors in the console. 34 | 35 | ### `yarn test` 36 | 37 | Launches the test runner in the interactive watch mode. 38 | 39 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 40 | 41 | ### `yarn run build` 42 | 43 | Builds the app for production to the `build` folder. 44 | 45 | It correctly bundles React in production mode and optimizes the build for the best performance. 46 | 47 | The build is minified and the filenames include the hashes. 48 | 49 | Your app is ready to be deployed! 50 | 51 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 52 | 53 | ### `yarn run eject` 54 | 55 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 56 | 57 | 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. 58 | 59 | 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. 60 | 61 | 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. 62 | 63 | ## Learn More 64 | 65 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 66 | 67 | To learn React, check out the [React documentation](https://reactjs.org/). 68 | 69 | ### Code Splitting 70 | 71 | This section has moved here: 72 | 73 | ### Analyzing the Bundle Size 74 | 75 | This section has moved here: 76 | 77 | ### Making a Progressive Web App 78 | 79 | This section has moved here: 80 | 81 | ### Advanced Configuration 82 | 83 | This section has moved here: 84 | 85 | ### Deployment 86 | 87 | This section has moved here: 88 | 89 | ### `yarn run build` fails to minify 90 | 91 | This section has moved here: 92 | -------------------------------------------------------------------------------- /eth-truffle/.gitignore: -------------------------------------------------------------------------------- 1 | .secret 2 | node_modules/ 3 | .DS_Store -------------------------------------------------------------------------------- /eth-truffle/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | 4 | node_js: 5 | - "10" 6 | 7 | install: 8 | - npm install 9 | 10 | script: 11 | - npm run testrpc >/dev/null & 12 | - sleep 5 13 | - npm test 14 | -------------------------------------------------------------------------------- /eth-truffle/audit/quantstamp-audit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AstarNetwork/lockdrop-ui/44eaaf562ecf1b4a2c6a76411bc221b76dd67c4c/eth-truffle/audit/quantstamp-audit.pdf -------------------------------------------------------------------------------- /eth-truffle/contracts/Lock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.15; 2 | 3 | contract Lock { 4 | // address owner; slot #0 5 | // address unlockTime; slot #1 6 | constructor (address owner, uint256 unlockTime) public payable { 7 | assembly { 8 | sstore(0x00, owner) 9 | sstore(0x01, unlockTime) 10 | } 11 | } 12 | 13 | /** 14 | * @dev Withdraw function once timestamp has passed unlock time 15 | */ 16 | function () external payable { 17 | assembly { 18 | switch gt(timestamp, sload(0x01)) 19 | case 0 { revert(0, 0) } 20 | case 1 { 21 | switch call(gas, sload(0x00), balance(address), 0, 0, 0, 0) 22 | case 0 { revert(0, 0) } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /eth-truffle/contracts/Lockdrop.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.15; 2 | 3 | import "./Lock.sol"; 4 | 5 | contract Lockdrop { 6 | // Time constants 7 | uint256 public constant LOCK_DROP_PERIOD = 30 days; 8 | uint256 public LOCK_START_TIME; 9 | uint256 public LOCK_END_TIME; 10 | 11 | // ETH locking events 12 | event Locked( 13 | uint256 indexed eth, 14 | uint256 indexed duration, 15 | address lock, 16 | address introducer 17 | ); 18 | 19 | constructor(uint256 startTime) public { 20 | LOCK_START_TIME = startTime; 21 | LOCK_END_TIME = startTime + LOCK_DROP_PERIOD; 22 | } 23 | 24 | /** 25 | * @dev Locks up the value sent to contract in a new Lock 26 | * @param _days The length of the lock up 27 | * @param _introducer The introducer of the user. 28 | */ 29 | function lock(uint256 _days, address _introducer) 30 | external 31 | payable 32 | didStart 33 | didNotEnd 34 | { 35 | // Accept External Owned Accounts only 36 | require(msg.sender == tx.origin); 37 | 38 | // Accept only fixed set of durations 39 | require(_days == 30 || _days == 100 || _days == 300 || _days == 1000); 40 | uint256 unlockTime = now + _days * 1 days; 41 | 42 | // Accept non-zero payments only 43 | require(msg.value > 0); 44 | uint256 eth = msg.value; 45 | 46 | // Create ETH lock contract 47 | Lock lockAddr = (new Lock).value(eth)(msg.sender, unlockTime); 48 | 49 | // ensure lock contract has all ETH, or fail 50 | assert(address(lockAddr).balance >= eth); 51 | 52 | emit Locked(eth, _days, address(lockAddr), _introducer); 53 | } 54 | 55 | /** 56 | * @dev Ensures the lockdrop has started 57 | */ 58 | modifier didStart() { 59 | require(now >= LOCK_START_TIME); 60 | _; 61 | } 62 | 63 | /** 64 | * @dev Ensures the lockdrop has not ended 65 | */ 66 | modifier didNotEnd() { 67 | require(now <= LOCK_END_TIME); 68 | _; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /eth-truffle/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.15; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /eth-truffle/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const Web3 = require('web3'); 4 | const lib = require('./lib/lockdrop'); 5 | 6 | require('yargs') 7 | .option('provider', { 8 | alias: 'p', 9 | description: 'Etheruem JSON-RPC provider', 10 | default: 'http://127.0.0.1:8545', 11 | type: 'string', 12 | }) 13 | .command( 14 | 'generate', 15 | 'Generate genesis config from contract state', 16 | yargs => 17 | yargs 18 | .option('contract', { 19 | alias: 'c', 20 | description: 'Lockdrop contract address', 21 | type: 'string', 22 | }) 23 | .option('from', { 24 | alias: 'f', 25 | description: 'Start block number', 26 | type: 'number', 27 | }) 28 | .option('to', { 29 | alias: 't', 30 | description: 'Finish block number', 31 | type: 'number', 32 | }) 33 | .demandOption(['contract', 'from'], 'Please provide contract address and lockdrop start block number'), 34 | argv => { 35 | const web3 = new Web3(argv.provider); 36 | 37 | // Get locks events from contract 38 | lib.getLocks( 39 | argv.contract, 40 | argv.from, 41 | argv.to ? argv.to : 'latest', 42 | )(web3) 43 | .then(locks => { 44 | let rates = lib.getIssueRates(locks); 45 | let balances = lib.getBalances(rates); 46 | console.log( 47 | '"balances": ' + balances.reduce((s, i) => s + '(hex!["' + i[0] + '"], ' + i[1] + '),\n'), 48 | ); 49 | console.log('total: ' + balances.reduce((s, i) => s + parseInt(i[1]), 0) / 10 ** 15); 50 | }) 51 | .catch(console.log); 52 | }, 53 | ) 54 | .help() 55 | .alias('help', 'h').argv; 56 | -------------------------------------------------------------------------------- /eth-truffle/lib/lockdrop.js: -------------------------------------------------------------------------------- 1 | const lockedEventABI = [ 2 | { 3 | anonymous: false, 4 | inputs: [ 5 | { 6 | indexed: true, 7 | name: 'eth', 8 | type: 'uint256', 9 | }, 10 | { 11 | indexed: true, 12 | name: 'duration', 13 | type: 'uint256', 14 | }, 15 | { 16 | indexed: false, 17 | name: 'lock', 18 | type: 'address', 19 | }, 20 | { 21 | indexed: false, 22 | name: 'introducer', 23 | type: 'address', 24 | }, 25 | ], 26 | name: 'Locked', 27 | type: 'event', 28 | }, 29 | ]; 30 | 31 | const BN = require('bn.js'); 32 | const Transaction = require('ethereumjs-tx').Transaction; 33 | 34 | function recover(tx) { 35 | const ethTx = new Transaction( 36 | { 37 | nonce: '0x' + tx.nonce.toString(16), 38 | gasPrice: '0x' + parseInt(tx.gasPrice).toString(16), 39 | gasLimit: '0x' + tx.gas.toString(16), 40 | to: tx.to, 41 | value: '0x' + parseInt(tx.value).toString(16), 42 | data: tx.input, 43 | v: tx.v, 44 | r: tx.r, 45 | s: tx.s, 46 | }, 47 | { chain: 'ropsten' }, 48 | ); 49 | return '0x' + ethTx.getSenderPublicKey().toString('hex'); 50 | } 51 | 52 | function ethToPlm(eth, duration) { 53 | switch (duration) { 54 | case '1': 55 | return eth.mul(new BN('24')); 56 | case '3': 57 | return eth.mul(new BN('100')); 58 | case '7': 59 | return eth.mul(new BN('360')); 60 | } 61 | } 62 | 63 | const TOTAL_ISSUE = new BN('425000000000000000000000'); 64 | 65 | module.exports = { 66 | getLocks: (address, fromBlock, toBlock) => { 67 | if (!fromBlock) { 68 | fromBlock = 0; 69 | } 70 | if (!toBlock) { 71 | toBlock = 'latest'; 72 | } 73 | return web3 => { 74 | const contract = new web3.eth.Contract(lockedEventABI, address); 75 | return contract 76 | .getPastEvents('Locked', { fromBlock, toBlock }) 77 | .then(events => 78 | Promise.all( 79 | events.map(e => Promise.all([web3.eth.getTransaction(e.transactionHash), Promise.resolve(e)])), 80 | ), 81 | ) 82 | .then(events => events.map(e => [recover(e[0]), e[1]])); 83 | }; 84 | }, 85 | getIssueRates: locks => 86 | // Group lock events by sender 87 | Object.entries( 88 | locks.reduce((r, a) => { 89 | const account = a[0]; 90 | const events = a[1]; 91 | const plmBalance = ethToPlm(new BN(events.returnValues.eth), events.returnValues.duration); 92 | r[account] = [...(r[account] || []), plmBalance]; 93 | return r; 94 | }, {}), 95 | ) 96 | // Map lock groups to sender/balance pairs 97 | .map(lock => { 98 | const sender = lock[0]; 99 | const balances = lock[1]; 100 | const totalBalance = balances.reduce((s, i) => s.add(new BN(i)), new BN('0')).div(new BN('1000')); 101 | return [sender.slice(2), totalBalance]; 102 | }), 103 | getBalances: rates => { 104 | const total_rates = rates.reduce((s, i) => s.add(i[1]), new BN('0')); 105 | return rates.map(rate => [rate[0], rate[1].mul(TOTAL_ISSUE).div(total_rates)]); 106 | }, 107 | getTotalEth: locks => locks.reduce((s, i) => s.add(new BN(i[1].returnValues.eth)), new BN('0')), 108 | }; 109 | -------------------------------------------------------------------------------- /eth-truffle/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require('Migrations'); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /eth-truffle/migrations/2_lockdrop_migration.js: -------------------------------------------------------------------------------- 1 | const Lockdrop = artifacts.require('Lockdrop'); 2 | 3 | module.exports = function(deployer, network) { 4 | const currentTime = Math.floor(Date.now() / 1000); 5 | if (network.startsWith('mainnet')) { 6 | const lockdropStart = '1584230400'; // 15th March 7 | deployer.deploy(Lockdrop, lockdropStart); 8 | } else { 9 | deployer.deploy(Lockdrop, currentTime); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /eth-truffle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethereum-lockdrop", 3 | "version": "0.1.0", 4 | "homepage": "https://github.com/stakedtechnologies/ethereum-lockdrop#readme", 5 | "description": "Ethereum lockdrop smart contracts", 6 | "main": "index.js", 7 | "directories": { 8 | "lib": "lib", 9 | "test": "test" 10 | }, 11 | "scripts": { 12 | "test": "truffle test", 13 | "compile": "truffle compile", 14 | "migrate": "truffle migrate", 15 | "console": "truffle console", 16 | "testrpc": "ganache-cli", 17 | "release": "truffle migrate --network mainnet" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/stakedtechnologies/ethereum-lockdrop.git" 22 | }, 23 | "keywords": [ 24 | "ethereum", 25 | "solidity", 26 | "lockdrop" 27 | ], 28 | "dependencies": { 29 | "bn-chai": "^1.0.1", 30 | "chai": "^4.2.0", 31 | "chai-as-promised": "^7.1.1", 32 | "eth-gas-reporter": "^0.1.12", 33 | "ganache-cli": "6.4.2", 34 | "truffle": "5.1.12", 35 | "truffle-flattener": "1.3.0", 36 | "truffle-hdwallet-provider": "^1.0.6", 37 | "web3": "^1.2", 38 | "yargs": "^13.0" 39 | }, 40 | "author": "Aleksandr Krupenkin ", 41 | "license": "BSD-3-Clause" 42 | } 43 | -------------------------------------------------------------------------------- /eth-truffle/test/1_Lock.test.js: -------------------------------------------------------------------------------- 1 | const Lock = artifacts.require('Lock'); 2 | 3 | const chai = require('chai'); 4 | chai.use(require('chai-as-promised')) 5 | chai.use(require('bn-chai')(web3.utils.BN)); 6 | chai.should(); 7 | 8 | contract('Lock', (accounts) => { 9 | describe('Locking funds', () => { 10 | let lock_account = accounts[0]; 11 | let lock_balance = web3.utils.toWei('1', 'ether') 12 | let lock_time = '0'; 13 | let lock = {}; 14 | 15 | it('Locking funds on contract creation', async () => { 16 | lock = await Lock.new(lock_account, lock_time, {value: lock_balance}); 17 | chai.expect(await web3.eth.getBalance(lock.address)).to.eq.BN(lock_balance); 18 | }); 19 | 20 | it('Unlocking funds when time reached', async () => { 21 | await lock.sendTransaction({from: lock_account}).should.be.fulfilled; 22 | chai.expect(await web3.eth.getBalance(lock.address)).to.eq.BN('0'); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /eth-truffle/test/2_Lockdrop.test.js: -------------------------------------------------------------------------------- 1 | const Lockdrop = artifacts.require('Lockdrop'); 2 | const lib = require('../lib/lockdrop'); 3 | 4 | const chai = require('chai'); 5 | chai.use(require('chai-as-promised')) 6 | chai.use(require('bn-chai')(web3.utils.BN)); 7 | chai.should(); 8 | 9 | contract('Lockdrop', (accounts) => { 10 | describe('Smart contract interaction', () => { 11 | it('Locking funds and emit event', async () => { 12 | const lockdrop = await Lockdrop.deployed(); 13 | let tx = await lockdrop.lock(30, accounts[0], {from: accounts[0], value: '1000'}).should.be.fulfilled; 14 | chai.expect(tx.logs[0].event).equal('Locked'); 15 | chai.expect(tx.logs[0].args.eth).to.eq.BN('1000'); 16 | chai.expect(tx.logs[0].args.duration).to.eq.BN('30'); 17 | 18 | tx = await lockdrop.lock(30, accounts[0], {from: accounts[0], value: '500'}).should.be.fulfilled; 19 | chai.expect(tx.logs[0].event).equal('Locked'); 20 | chai.expect(tx.logs[0].args.eth).to.eq.BN('500'); 21 | chai.expect(tx.logs[0].args.duration).to.eq.BN('30'); 22 | 23 | tx = await lockdrop.lock(100, accounts[0], {from: accounts[1], value: '100'}).should.be.fulfilled; 24 | chai.expect(tx.logs[0].event).equal('Locked'); 25 | chai.expect(tx.logs[0].args.eth).to.eq.BN('100'); 26 | chai.expect(tx.logs[0].args.duration).to.eq.BN('100'); 27 | }); 28 | 29 | it('Reject transaction without funds', async () => { 30 | const lockdrop = await Lockdrop.deployed(); 31 | await lockdrop.lock(30, accounts[0]).should.be.rejected; 32 | }); 33 | 34 | it('Reject transaction with wrong duration', async () => { 35 | const lockdrop = await Lockdrop.deployed(); 36 | await lockdrop.lock(0, accounts[0]).should.be.rejected; 37 | await lockdrop.lock(1, accounts[0]).should.be.rejected; 38 | }); 39 | }); 40 | 41 | describe('Event collecting', () => { 42 | it('Collect Locked events', async () => { 43 | const lockdrop = await Lockdrop.deployed(); 44 | const events = await lib.getLocks(lockdrop.address)(web3); 45 | const toAddress = pub => web3.utils.toChecksumAddress('0x'+web3.utils.sha3(pub).slice(-40)); 46 | const senders = events.map(x => toAddress(x[0])); 47 | 48 | chai.expect(senders[0]).to.equal(accounts[0]); 49 | chai.expect(senders[1]).to.equal(accounts[0]); 50 | chai.expect(senders[2]).to.equal(accounts[1]); 51 | }) 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /eth-truffle/truffle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like truffle-hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | 22 | const fs = require('fs'); 23 | const path = require('path'); 24 | const HDWalletProvider = require('truffle-hdwallet-provider'); 25 | 26 | const infuraKey = 'e673f1634f174971890bf13130751704'; 27 | 28 | module.exports = { 29 | contracts_build_directory: path.join(__dirname, '../src/contracts'), 30 | 31 | /** 32 | * Networks define how you connect to your ethereum client and let you set the 33 | * defaults web3 uses to send transactions. If you don't specify one truffle 34 | * will spin up a development blockchain for you on port 9545 when you 35 | * run `develop` or `test`. You can ask a truffle command to use a specific 36 | * network from the command line, e.g 37 | * 38 | * $ truffle test --network 39 | */ 40 | 41 | networks: { 42 | // Useful for testing. The `development` name is special - truffle uses it by default 43 | // if it's defined here and no other network is specified at the command line. 44 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 45 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 46 | // options below to some value. 47 | // 48 | development: { 49 | host: '127.0.0.1', // Localhost (default: none) 50 | port: 8545, // Standard Ethereum port (default: none) 51 | network_id: '*', // Any network (default: none) 52 | }, 53 | 54 | // Another network with more advanced options... 55 | // advanced: { 56 | // port: 8777, // Custom port 57 | // network_id: 1342, // Custom network 58 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 59 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 60 | // from:
, // Account to send txs from (default: accounts[0]) 61 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 62 | // }, 63 | 64 | // Useful for deploying to a public network. 65 | // NB: It's important to wrap the provider as a function. 66 | ropsten: { 67 | provider: () => { 68 | const mnemonic = fs.readFileSync(path.join(__dirname, '.secret')).toString().trim(); 69 | return new HDWalletProvider(mnemonic, 'https://ropsten.infura.io/v3/' + infuraKey) 70 | }, 71 | network_id: 3, // Ropsten's id 72 | gas: 5500000, // Ropsten has a lower block limit than mainnet 73 | confirmations: 2, // # of confs to wait between deployments. (default: 0) 74 | timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 75 | skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 76 | }, 77 | 78 | mainnet: { 79 | provider: () => { 80 | const mnemonic = fs.readFileSync(path.join(__dirname, '.secret')).toString().trim(); 81 | return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/v3/' + infuraKey) 82 | }, 83 | network_id: 1, // Ethereum mainnet id 84 | confirmations: 2, // # of confs to wait between deployments. (default: 0) 85 | timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 86 | gasPrice: 12000000000, 87 | skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 88 | }, 89 | 90 | // Useful for private networks 91 | // private: { 92 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 93 | // network_id: 2111, // This network is yours, in the cloud. 94 | 95 | // Useful for private networks 96 | // private: { 97 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 98 | // network_id: 2111, // This network is yours, in the cloud. 99 | // production: true // Treats this network as if it was a public net. (default: false) 100 | // } 101 | }, 102 | 103 | // Set default mocha options here, use special reporters etc. 104 | mocha: { 105 | // timeout: 100000 106 | reporter: 'eth-gas-reporter', 107 | reporterOptions: { 108 | currency: 'USD', 109 | gasPrice: 10 110 | } 111 | }, 112 | 113 | // Configure your compilers 114 | compilers: { 115 | solc: { 116 | version: '0.5.15', 117 | settings: { 118 | optimizer: { 119 | enabled: true, 120 | runs: 200 121 | }, 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lockdrop-ui", 3 | "integrations": {}, 4 | "type": "react" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lockdrop-ui", 3 | "homepage": "https://lockdrop.astar.network", 4 | "version": "0.0.2", 5 | "private": true, 6 | "dependencies": { 7 | "@ionic/core": "^4.11.0", 8 | "@ionic/react": "^4.11.0", 9 | "@ionic/react-router": "^4.11.0", 10 | "@material-ui/core": "^4.12.3", 11 | "@material-ui/icons": "^4.11.2", 12 | "@material-ui/lab": "^4.0.0-alpha.60", 13 | "@polkadot/api": "^5.4.1", 14 | "@polkadot/types": "^5.4.1", 15 | "@polkadot/util": "^7.1.1", 16 | "@polkadot/util-crypto": "^7.1.1", 17 | "bignumber.js": "^9.0.0", 18 | "eth-crypto": "^1.6.0", 19 | "ethereumjs-util": "^6.2.0", 20 | "fast-sha256": "^1.3.0", 21 | "ionicons": "^4.6.3", 22 | "lodash": "^4.17.21", 23 | "moment": "^2.24.0", 24 | "moment-timezone": "^0.5.31", 25 | "react": "^16.12.0", 26 | "react-countup": "^4.3.3", 27 | "react-dom": "^16.12.0", 28 | "react-dropdown": "^1.7.0", 29 | "react-markdown": "^4.3.1", 30 | "react-router": "^5.1.2", 31 | "react-router-dom": "^5.1.2", 32 | "react-scripts": "^4.0.3", 33 | "react-scroll": "^1.7.16", 34 | "react-swipeable-views": "^0.13.9", 35 | "react-toastify": "^5.5.0", 36 | "react-virtualized": "^9.22.2", 37 | "regtest-client": "^0.2.0", 38 | "rxjs-hooks": "^0.5.2", 39 | "secp256k1": "^4.0.2", 40 | "semantic-ui-react": "^0.88.2", 41 | "styled-components": "^5.0.1", 42 | "styled-media-query": "^2.1.2", 43 | "truffle-contract": "^3.0.1", 44 | "typescript": "^4.3.5", 45 | "varuint-bitcoin": "^1.1.2", 46 | "web3": "^1.3.0", 47 | "web3-utils": "^1.3.0" 48 | }, 49 | "devDependencies": { 50 | "@babel/helper-call-delegate": "^7.10.1", 51 | "@testing-library/dom": "^7.2.2", 52 | "@testing-library/jest-dom": "^4.2.4", 53 | "@testing-library/react": "^9.4.0", 54 | "@testing-library/user-event": "^8.0.3", 55 | "@types/bigi": "^1.4.2", 56 | "@types/bs58": "^4.0.1", 57 | "@types/ecurve": "^1.0.0", 58 | "@types/elliptic": "^6.4.12", 59 | "@types/jest": "^24.0.25", 60 | "@types/lodash": "^4.14.161", 61 | "@types/node": "^12.12.26", 62 | "@types/react": "^16.9.17", 63 | "@types/react-dom": "^16.9.4", 64 | "@types/react-router": "^5.1.4", 65 | "@types/react-router-dom": "^5.1.3", 66 | "@types/react-scroll": "^1.5.4", 67 | "@types/react-swipeable-views": "^0.13.0", 68 | "@types/react-virtualized": "^9.21.10", 69 | "@types/secp256k1": "^4.0.1", 70 | "@types/styled-components": "^5.0.1", 71 | "@typescript-eslint/parser": "^4.29.1", 72 | "eslint": "^7.32.0", 73 | "eslint-config-prettier": "^6.10.0", 74 | "eslint-plugin-prettier": "^3.1.2", 75 | "eslint-plugin-react": "^7.24.0", 76 | "eslint-plugin-react-hooks": "^4.2.0", 77 | "gh-pages": "^2.2.0", 78 | "prettier": "^1.19.1" 79 | }, 80 | "scripts": { 81 | "start": "react-scripts start", 82 | "build": "react-scripts build", 83 | "build:ci": "CI=true react-scripts build", 84 | "test": "react-scripts test", 85 | "eject": "react-scripts eject", 86 | "lint": "eslint src/**/*.{ts,tsx}", 87 | "fix": "eslint --fix src/**/*.{ts,tsx}", 88 | "predeploy": "npm run build", 89 | "deploy": "gh-pages -d build" 90 | }, 91 | "eslintConfig": { 92 | "extends": "react-app" 93 | }, 94 | "browserslist": { 95 | "production": [ 96 | ">0.2%", 97 | "not dead", 98 | "not op_mini all" 99 | ], 100 | "development": [ 101 | "last 1 chrome version", 102 | "last 1 firefox version", 103 | "last 1 safari version" 104 | ] 105 | }, 106 | "jest": { 107 | "transformIgnorePatterns" : ["/node_modules/(?!@polkadot|@babel/runtime/helpers/esm/)"] 108 | }, 109 | "description": "Lockdrop Application for Plasm Network" 110 | } 111 | -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | lockdrop.astar.network -------------------------------------------------------------------------------- /public/assets/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AstarNetwork/lockdrop-ui/44eaaf562ecf1b4a2c6a76411bc221b76dd67c4c/public/assets/icon/favicon.ico -------------------------------------------------------------------------------- /public/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AstarNetwork/lockdrop-ui/44eaaf562ecf1b4a2c6a76411bc221b76dd67c4c/public/assets/icon/icon.png -------------------------------------------------------------------------------- /public/assets/ld_logo_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AstarNetwork/lockdrop-ui/44eaaf562ecf1b4a2c6a76411bc221b76dd67c4c/public/assets/ld_logo_b.png -------------------------------------------------------------------------------- /public/assets/plasm-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AstarNetwork/lockdrop-ui/44eaaf562ecf1b4a2c6a76411bc221b76dd67c4c/public/assets/plasm-logo.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Plasm Lockdrop 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
61 | 62 | 63 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Plasm Lockdrop", 3 | "name": "Plasm Ethereum Lockdrop", 4 | "icons": [{ 5 | "src": "assets/icon/favicon.ico", 6 | "sizes": "64x64 32x32 24x24 16x16", 7 | "type": "image/x-icon" 8 | }, 9 | { 10 | "src": "assets/icon/icon.png", 11 | "type": "image/png", 12 | "sizes": "512x512", 13 | "purpose": "maskable" 14 | } 15 | ], 16 | "start_url": ".", 17 | "display": "standalone", 18 | "theme_color": "#ffffff", 19 | "background_color": "#ffffff" 20 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | 3 | User-agent: Twitterbot 4 | Disallow: 5 | 6 | User-agent: * 7 | Disallow: 8 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import React from 'react'; 3 | import { Route, Redirect } from 'react-router-dom'; 4 | import { IonApp, IonRouterOutlet, IonSplitPane } from '@ionic/react'; 5 | import { IonReactHashRouter } from '@ionic/react-router'; 6 | import LandingPage from './pages/LandingPage'; 7 | import FirstEthLockdropPage from './pages/FirstEthLockdropPage'; 8 | import SideMenu from './components/SideMenu'; 9 | import LockdropCalcPage from './pages/LockdropCalcPage'; 10 | import EthRealTimeLockPage from './pages/EthRealTimeLockPage'; 11 | import * as plasmUtils from './helpers/plasmUtils'; 12 | 13 | /* Core CSS required for Ionic components to work properly */ 14 | import '@ionic/react/css/core.css'; 15 | 16 | /* Basic CSS for apps built with Ionic */ 17 | import '@ionic/react/css/normalize.css'; 18 | import '@ionic/react/css/structure.css'; 19 | import '@ionic/react/css/typography.css'; 20 | 21 | /* Optional CSS utils that can be commented out */ 22 | import '@ionic/react/css/padding.css'; 23 | import '@ionic/react/css/float-elements.css'; 24 | import '@ionic/react/css/text-alignment.css'; 25 | import '@ionic/react/css/text-transformation.css'; 26 | import '@ionic/react/css/flex-utils.css'; 27 | import '@ionic/react/css/display.css'; 28 | 29 | /* Theme variables */ 30 | import './theme/variables.css'; 31 | import LockdropStatPage from './pages/LockdropStatPage'; 32 | import Api from './api/Api'; 33 | import Web3Api from './api/Web3Api'; 34 | 35 | const App: React.FC = () => { 36 | return ( 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | } /> 46 | 47 | ( 50 | 51 | 52 | 53 | )} 54 | /> 55 | ( 58 | 59 | 60 | 61 | )} 62 | /> 63 | 64 | 65 | 66 | 67 | 68 | 69 | ); 70 | }; 71 | 72 | export default App; 73 | -------------------------------------------------------------------------------- /src/api/Api.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useState, useEffect, useContext } from 'react'; 2 | import { ApiPromise, WsProvider } from '@polkadot/api'; 3 | import { ApiContext, ApiProps } from '../contexts/ApiContext'; 4 | import { PlasmNetwork } from '../helpers/plasmUtils'; 5 | import { getNetworkEndpoint } from '../config/endpoints'; 6 | 7 | const DEFAULT_NETWORK = PlasmNetwork.Local; 8 | let api: ApiPromise; 9 | 10 | // Ignore camel case complaints. 11 | /* eslint-disable */ 12 | const types = { 13 | AccountInfo: 'AccountInfoWithRefCount', 14 | AuthorityId: 'AccountId', 15 | AuthorityVote: 'u32', 16 | Claim: { 17 | amount: 'u128', 18 | approve: 'BTreeSet', 19 | complete: 'bool', 20 | decline: 'BTreeSet', 21 | params: 'Lockdrop', 22 | }, 23 | ClaimId: 'H256', 24 | ClaimVote: { 25 | approve: 'bool', 26 | authority: 'u16', 27 | claim_id: 'ClaimId', 28 | }, 29 | DollarRate: 'u128', 30 | Keys: 'SessionKeys2', 31 | Lockdrop: { 32 | duration: 'u64', 33 | public_key: '[u8; 33]', 34 | transaction_hash: 'H256', 35 | type: 'u8', 36 | value: 'u128', 37 | }, 38 | PredicateHash: 'H256', 39 | RefCount: 'u8', 40 | TickerRate: { 41 | authority: 'u16', 42 | btc: 'u128', 43 | eth: 'u128', 44 | }, 45 | }; 46 | /* eslint-enable */ 47 | 48 | /** 49 | * creates ApiPromise for a given network 50 | * @param network end point for the client to connect to 51 | */ 52 | export function getApi(network: PlasmNetwork): ApiPromise { 53 | const url = getNetworkEndpoint(network); 54 | const provider = new WsProvider(url); 55 | return new ApiPromise({ 56 | provider, 57 | types: { 58 | ...types, 59 | LookupSource: 'MultiAddress', 60 | }, 61 | }); 62 | } 63 | 64 | /** 65 | * establishes a connection between the client and the plasm node with the given endpoint. 66 | * this will default to the main net node 67 | * @param network end point for the client to connect to 68 | */ 69 | export async function createPlasmInstance(network?: PlasmNetwork): Promise { 70 | const api = getApi(network || PlasmNetwork.Main); 71 | return await api.isReady; 72 | } 73 | 74 | function Api({ network = DEFAULT_NETWORK, children }: Props): React.ReactElement { 75 | const [isReady, setIsReady] = useState(false); 76 | const value = useMemo(() => ({ api, isReady, network }), [isReady, network]); 77 | 78 | useEffect(() => { 79 | api = getApi(network); 80 | api.on('connected', (): void => { 81 | api.isReady.then((): void => { 82 | setIsReady(true); 83 | }); 84 | }); 85 | 86 | return () => { 87 | api && api.isConnected && api.disconnect(); 88 | }; 89 | // eslint-disable-next-line 90 | }, [network]); 91 | 92 | return {children}; 93 | } 94 | 95 | interface Props { 96 | network?: PlasmNetwork; 97 | children: React.ReactNode; 98 | } 99 | 100 | export default React.memo(Api); 101 | export const useApi = (): ApiProps => ({ ...useContext(ApiContext) }); 102 | -------------------------------------------------------------------------------- /src/api/Web3Api.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useState, useEffect, useContext } from 'react'; 2 | import Web3 from 'web3'; 3 | import { Contract } from 'web3-eth-contract'; 4 | import { Web3Context, Web3ApiProps } from '../contexts/Web3Context'; 5 | import * as ethLockdrop from '../helpers/lockdrop/EthereumLockdrop'; 6 | import { removeWeb3Event } from '../helpers/getWeb3'; 7 | import { LockSeason } from '../types/LockdropModels'; 8 | import { firstLockContract, secondLockContract } from '../data/lockInfo'; 9 | 10 | let web3: Web3; 11 | 12 | export const isMainnet = (currentNetwork: string): boolean => { 13 | return currentNetwork === 'main'; 14 | }; 15 | 16 | export const plasmNetToEthNet = 'Main Network'; 17 | 18 | function Web3Api({ contractAddress, children }: Props): React.ReactElement { 19 | // TODO useReducer, to many state variables 20 | const [currentNetwork, setCurrentNetwork] = useState(''); 21 | const [latestBlock, setLatestBlock] = useState(0); 22 | const [account, setAccount] = useState(''); 23 | const [contract, setContract] = useState(); 24 | const [lockdropStart, setLockdropStart] = useState('0'); 25 | const [lockdropEnd, setLockdropEnd] = useState('0'); 26 | const [error, setError] = useState(); 27 | const [isChangingContract, setIsChangingContract] = useState(false); 28 | const [isWeb3Loading, setIsWeb3Loading] = useState(false); 29 | const [isMainnetLock, setIsMainnetLock] = useState(undefined); 30 | const [_contractAddress, _setContactAddress] = useState(contractAddress); 31 | 32 | const createContract = async (address: string | undefined, isInitial: boolean) => { 33 | console.log('Creating contract with address ', address); 34 | _setContactAddress(address); 35 | try { 36 | if (web3 && address) { 37 | if (!isInitial) { 38 | setIsChangingContract(true); 39 | } 40 | 41 | setError(undefined); 42 | const contract = await ethLockdrop.createContractInstance(web3, address); 43 | 44 | const start = await ethLockdrop.getContractStartDate(contract); 45 | const end = await ethLockdrop.getContractEndDate(contract); 46 | setLockdropStart(start); 47 | setLockdropEnd(end); 48 | setContract(contract); 49 | 50 | if (!isInitial) { 51 | setIsChangingContract(false); 52 | } 53 | } 54 | } catch (err) { 55 | setError(err.message); 56 | } 57 | }; 58 | 59 | const changeLockSeason = async (season: LockSeason, isMainLock: boolean) => { 60 | const contracts = season === LockSeason.First ? firstLockContract : secondLockContract; 61 | const chainType = isMainLock ? 'main' : 'private'; 62 | const contAddr = contracts.find(i => i.type === chainType)?.address; 63 | 64 | if (typeof contAddr !== 'undefined') { 65 | _setContactAddress(contAddr); 66 | } else { 67 | setError('Could not find lockdrop contract'); 68 | } 69 | }; 70 | 71 | const value = useMemo( 72 | () => ({ 73 | web3, 74 | isWeb3Loading, 75 | currentNetwork, 76 | latestBlock, 77 | account, 78 | contract, 79 | lockdropStart, 80 | lockdropEnd, 81 | error, 82 | isChangingContract, 83 | changeContractAddress: address => createContract(address, false), 84 | setLatestBlock: block => setLatestBlock(block), 85 | setAccount: account => setAccount(account), 86 | setIsMainnetLock: value => setIsMainnetLock(value), 87 | setParameters: (isMainnetLock, lockSeason) => { 88 | setIsMainnetLock(isMainnetLock); 89 | changeLockSeason(lockSeason, isMainnetLock); 90 | }, 91 | }), 92 | [ 93 | isWeb3Loading, 94 | currentNetwork, 95 | latestBlock, 96 | account, 97 | contract, 98 | lockdropStart, 99 | lockdropEnd, 100 | error, 101 | isChangingContract, 102 | ], 103 | ); 104 | 105 | useEffect(() => { 106 | const initialize = async () => { 107 | if (typeof isMainnetLock === 'undefined') { 108 | console.log('isMainnetLock is undefined'); 109 | return; 110 | } 111 | 112 | console.log('Initializing web3, isMainnetLock ', isMainnetLock); 113 | setError(undefined); 114 | setIsWeb3Loading(true); 115 | 116 | try { 117 | web3 = await ethLockdrop.connectWeb3(); 118 | 119 | const network = await web3.eth.net.getNetworkType(); 120 | setCurrentNetwork(network); 121 | 122 | if (isMainnet(network) === isMainnetLock) { 123 | const accounts = await ethLockdrop.fetchAllAddresses(web3); 124 | setAccount(accounts[0]); 125 | 126 | const latest = await web3.eth.getBlockNumber(); 127 | setLatestBlock(latest); 128 | 129 | createContract(_contractAddress, true); 130 | } else { 131 | setError('User is not connected to ' + plasmNetToEthNet); 132 | } 133 | } catch (err) { 134 | console.log(err); 135 | setError(err.message); 136 | } finally { 137 | setIsWeb3Loading(false); 138 | } 139 | }; 140 | 141 | initialize(); 142 | 143 | return () => { 144 | removeWeb3Event(); 145 | }; 146 | // eslint-disable-next-line 147 | }, [isMainnetLock]); 148 | 149 | return {children}; 150 | } 151 | 152 | interface Props { 153 | contractAddress?: string; 154 | children: React.ReactNode; 155 | } 156 | 157 | export default React.memo(Web3Api); 158 | export const useEth = (): Web3ApiProps => ({ ...useContext(Web3Context) }); 159 | -------------------------------------------------------------------------------- /src/components/CopyMessageBox.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 3 | import React, { useState } from 'react'; 4 | import { Paper, Typography, makeStyles, createStyles, Tooltip, IconButton } from '@material-ui/core'; 5 | import FileCopyIcon from '@material-ui/icons/FileCopy'; 6 | import { IonToast } from '@ionic/react'; 7 | 8 | interface Props { 9 | message: string; 10 | isCode?: boolean; 11 | header?: string; 12 | componentType?: 13 | | 'inherit' 14 | | 'button' 15 | | 'overline' 16 | | 'caption' 17 | | 'h1' 18 | | 'h2' 19 | | 'h3' 20 | | 'h4' 21 | | 'h5' 22 | | 'h6' 23 | | 'subtitle1' 24 | | 'subtitle2' 25 | | 'body1' 26 | | 'body2' 27 | | 'srOnly'; 28 | } 29 | 30 | const useStyles = makeStyles(theme => 31 | createStyles({ 32 | messageBox: { 33 | padding: theme.spacing(2, 4), 34 | alignItems: 'center', 35 | }, 36 | signMessage: { 37 | alignItems: 'center', 38 | display: 'flex', 39 | justifyContent: 'center', 40 | height: '100%', 41 | }, 42 | message: { 43 | wordBreak: 'break-all', 44 | }, 45 | copyIcon: { 46 | verticalAlign: 'middle', 47 | }, 48 | }), 49 | ); 50 | 51 | const CopyMessageBox: React.FC = ({ header, message, componentType, isCode }) => { 52 | const classes = useStyles(); 53 | const [showCopyToast, setCopyToast] = useState(false); 54 | 55 | const clickCopyMessage = () => { 56 | navigator.clipboard.writeText(message).then( 57 | function() { 58 | setCopyToast(true); 59 | }, 60 | function(err) { 61 | console.error('Async: Could not copy text: ', err); 62 | }, 63 | ); 64 | }; 65 | return ( 66 | <> 67 | 68 | {header && ( 69 | 70 | {header}: 71 | 72 | )} 73 | 74 |
75 | {isCode ? ( 76 | {message} 77 | ) : ( 78 | 83 | {message} 84 | 85 | )} 86 | 87 |
88 | 89 | clickCopyMessage()}> 90 | 91 | 92 | 93 |
94 |
95 |
96 | setCopyToast(false)} 99 | message="Copied message to clipboard" 100 | duration={2000} 101 | /> 102 | 103 | ); 104 | }; 105 | 106 | export default CopyMessageBox; 107 | -------------------------------------------------------------------------------- /src/components/CountdownTimer.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React, { useMemo, useCallback } from 'react'; 3 | import { TimeFormat } from '../types/LockdropModels'; 4 | import moment, { Moment, duration } from 'moment'; 5 | 6 | interface Props { 7 | startTime: Moment; 8 | endTime: Moment; 9 | /**function callback if the countdown is over. This returns a boolean as the parameter */ 10 | onFinish?: (finished: boolean) => void; 11 | } 12 | 13 | const CountdownTimer: React.FC = ({ startTime, endTime, onFinish }) => { 14 | const now = moment().utc(); 15 | const handleCountdownFinish = useCallback( 16 | (didFinish: boolean) => { 17 | if (onFinish) onFinish(didFinish); 18 | }, 19 | [onFinish], 20 | ); 21 | 22 | const timeLeft = useMemo(() => { 23 | const tillStart = moment(startTime).valueOf() - now.valueOf(); 24 | 25 | //let difference = tillStart; 26 | let difference = duration(startTime.diff(now)); 27 | 28 | // if the lockdrop has already started 29 | if (tillStart < 0) { 30 | difference = duration(endTime.diff(now)); 31 | } 32 | 33 | let _timeLeft: TimeFormat = { 34 | days: 0, 35 | hours: 0, 36 | minutes: 0, 37 | seconds: 0, 38 | }; 39 | 40 | const tillEnd = moment(endTime).valueOf() - now.valueOf(); 41 | // check if the duration has ended 42 | if (tillEnd > 0) { 43 | _timeLeft = { 44 | days: difference.days(), 45 | hours: difference.hours(), 46 | minutes: difference.minutes(), 47 | seconds: difference.seconds(), 48 | }; 49 | } 50 | handleCountdownFinish(tillEnd < 0); 51 | return _timeLeft; 52 | }, [now, startTime, endTime, handleCountdownFinish]); 53 | 54 | return ( 55 | <> 56 |

57 | {timeLeft.days} Days {timeLeft.hours} Hours {timeLeft.minutes} Minutes {timeLeft.seconds} Seconds 58 |

59 | 60 | ); 61 | }; 62 | 63 | export default CountdownTimer; 64 | -------------------------------------------------------------------------------- /src/components/DropdownOption.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react'; 2 | import { IonSelect, IonSelectOption } from '@ionic/react'; 3 | import { OptionData } from '../types/LockdropModels'; 4 | 5 | // react function component for making dropdown with the given items 6 | export const DropdownOption: React.FC = ({ dataSets, onChoose }: OptionData): ReactElement => { 7 | const items = dataSets.map(x => { 8 | return ( 9 | 10 | {x.label} 11 | 12 | ); 13 | }); 14 | 15 | return ( 16 | onChoose(e.detail.value)}> 17 | {items} 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/EthLock/AffiliationList.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React, { useState, useEffect } from 'react'; 3 | import { firstEthIntroducer, defaultAddress } from '../../data/affiliationProgram'; 4 | import { LockEvent } from '../../types/LockdropModels'; 5 | import { PlmDrop } from '../../types/PlasmDrop'; 6 | import { calculateTotalPlm } from '../../helpers/lockdrop/EthereumLockdrop'; 7 | import { 8 | List, 9 | ListItemText, 10 | ListSubheader, 11 | Divider, 12 | ListItem, 13 | makeStyles, 14 | createStyles, 15 | Theme, 16 | Typography, 17 | } from '@material-ui/core'; 18 | import SectionCard from '../SectionCard'; 19 | 20 | interface Props { 21 | lockData: LockEvent[]; 22 | } 23 | 24 | const useStyles = makeStyles((theme: Theme) => 25 | createStyles({ 26 | listRoot: { 27 | width: '100%', 28 | maxWidth: 'auto', 29 | backgroundColor: theme.palette.background.paper, 30 | position: 'relative', 31 | overflow: 'auto', 32 | maxHeight: 360, 33 | }, 34 | listSection: { 35 | backgroundColor: 'inherit', 36 | }, 37 | ul: { 38 | backgroundColor: 'inherit', 39 | padding: 0, 40 | }, 41 | title: { 42 | textAlign: 'center', 43 | padding: theme.spacing(4, 2, 0), 44 | }, 45 | }), 46 | ); 47 | 48 | const AffiliationList: React.FC = ({ lockData }) => { 49 | const classes = useStyles(); 50 | const [lockdropResult, setLockdropResult] = useState([]); 51 | 52 | function getAffiliationResults(lockData: LockEvent[]) { 53 | // filter out the 0x00 address from the list 54 | const validAddresses = firstEthIntroducer.filter(address => address !== defaultAddress); 55 | 56 | // get the lockdrop result 57 | const lockResults = validAddresses.map(i => { 58 | return calculateTotalPlm(i, lockData); 59 | }); 60 | 61 | // sort the array by the number of references 62 | return lockResults.sort((a, b) => 63 | a.affiliationRefsBonuses.length > b.affiliationRefsBonuses.length 64 | ? -1 65 | : a.affiliationRefsBonuses.length < b.affiliationRefsBonuses.length 66 | ? 1 67 | : 0, 68 | ); 69 | } 70 | 71 | useEffect(() => { 72 | setLockdropResult(getAffiliationResults(lockData)); 73 | }, [lockData]); 74 | 75 | return ( 76 | <> 77 | 78 | 79 | Affiliation Leaderboard 80 | 81 | }> 82 |
  • 83 |
      84 | There are {firstEthIntroducer.length - 1} introducers 85 | 86 | {lockdropResult.map(i => ( 87 | 88 | ))} 89 |
    90 |
  • 91 |
    92 |
    93 | 94 | ); 95 | }; 96 | 97 | interface IntroducerPlanelProps { 98 | lockResult: PlmDrop; 99 | } 100 | 101 | const IntroducerBonusesItems: React.FC = ({ lockResult }) => { 102 | return ( 103 | <> 104 | 105 | 106 |

    {lockResult.receiver}

    107 |

    {lockResult.affiliationRefsBonuses.length} lock(s) referenced this address

    108 |

    {lockResult.getAffBonus()} PLMs gained from this

    109 |
    110 |
    111 | 112 | 113 | ); 114 | }; 115 | 116 | export default AffiliationList; 117 | -------------------------------------------------------------------------------- /src/components/EthLock/EthGlobalLocks.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable react/prop-types */ 3 | 4 | import React, { useMemo } from 'react'; 5 | import { getTotalLockVal } from '../../helpers/lockdrop/EthereumLockdrop'; 6 | import { LockEvent } from '../../types/LockdropModels'; 7 | import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'; 8 | import ListItem from '@material-ui/core/ListItem'; 9 | import ListItemText from '@material-ui/core/ListItemText'; 10 | import { Divider } from '@material-ui/core'; 11 | import { defaultAddress } from '../../data/affiliationProgram'; 12 | import Web3Utils from 'web3-utils'; 13 | import { List as VirtualizedList, ListRowProps, AutoSizer, CellMeasurerCache, CellMeasurer } from 'react-virtualized'; 14 | 15 | interface LockHistoryProps { 16 | lockData: LockEvent[]; 17 | } 18 | 19 | const useStyles = makeStyles((theme: Theme) => 20 | createStyles({ 21 | listSection: { 22 | backgroundColor: 'inherit', 23 | }, 24 | item: { 25 | display: 'inline-block', 26 | //flexDirection: 'row', 27 | alignItems: 'center', 28 | height: '100%', 29 | //flexWrap: 'wrap', 30 | }, 31 | lockListPage: { 32 | textAlign: 'center', 33 | width: '100%', 34 | height: '100%', 35 | maxWidth: 'auto', 36 | backgroundColor: theme.palette.background.paper, 37 | display: 'flex', 38 | flexDirection: 'column', 39 | flexWrap: 'wrap', 40 | minHeight: 450, 41 | }, 42 | tabMenu: { 43 | backgroundColor: theme.palette.background.paper, 44 | width: 'auto', 45 | }, 46 | autoSizerWrapper: { 47 | flex: '1 1 auto', 48 | }, 49 | }), 50 | ); 51 | 52 | const GlobalLocks: React.FC = ({ lockData }) => { 53 | const classes = useStyles(); 54 | const rowCache = new CellMeasurerCache({ 55 | fixedWidth: true, 56 | defaultHeight: 150, // tune as estimate for unmeasured rows 57 | minHeight: 150, // keep this <= any actual row height 58 | keyMapper: () => 1, 59 | }); 60 | 61 | const totalVal = useMemo(() => { 62 | return getTotalLockVal(lockData, 4); 63 | }, [lockData]); 64 | 65 | const RowRenderer: React.FC = ({ index, key, style, parent }) => { 66 | const eventItem = lockData[index]; 67 | 68 | return ( 69 | 70 | {({ measure, registerChild }) => ( 71 |
    registerChild} onLoad={measure}> 72 | 73 | 74 |

    Lock address: {eventItem.lock}

    75 |
    Locked in block no. {eventItem.blockNo}
    76 |

    77 | Locked {Web3Utils.fromWei(eventItem.eth.toFixed(), 'ether')} ETH for{' '} 78 | {eventItem.duration} days 79 |

    80 | {eventItem.introducer !== defaultAddress ? ( 81 |

    Introducer: {eventItem.introducer}

    82 | ) : ( 83 |

    No introducer

    84 | )} 85 |
    86 |
    87 | 88 |
    89 | )} 90 |
    91 | ); 92 | }; 93 | 94 | return ( 95 |
    96 | {lockData.length > 0 ? ( 97 | <> 98 |
    99 |

    Global Locks

    100 |

    {totalVal} ETH locked

    101 |

    There are {lockData.length} locks

    102 |
    103 | 104 |
    105 | 106 | {({ width, height }) => ( 107 | 116 | )} 117 | 118 |
    119 | 120 | ) : ( 121 | <> 122 |

    No Locks

    123 |

    Please lock some ETH!

    124 | 125 | )} 126 |
    127 | ); 128 | }; 129 | 130 | export default GlobalLocks; 131 | -------------------------------------------------------------------------------- /src/components/EthLock/LockdropCountdownPanel.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React, { useEffect, useState } from 'react'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Container from '@material-ui/core/Container'; 5 | import Typography from '@material-ui/core/Typography'; 6 | import Grid from '@material-ui/core/Grid'; 7 | import { TimeFormat, LockEvent } from '../../types/LockdropModels'; 8 | import moment, { Moment, duration } from 'moment'; 9 | import { getTotalLockVal } from '../../helpers/lockdrop/EthereumLockdrop'; 10 | 11 | interface Props { 12 | startTime: Moment; 13 | endTime: Moment; 14 | lockData: LockEvent[]; 15 | } 16 | 17 | enum LockState { 18 | notStart, 19 | start, 20 | end, 21 | } 22 | 23 | const useStyles = makeStyles(theme => ({ 24 | container: { 25 | padding: theme.spacing(0, 2, 4), 26 | margin: theme.spacing(1), 27 | }, 28 | headerText: { 29 | padding: theme.spacing(1), 30 | }, 31 | })); 32 | 33 | const LockdropCountdownPanel: React.FC = ({ startTime, endTime, lockData }) => { 34 | const now = moment().utc(); 35 | const classes = useStyles(); 36 | 37 | const calculateTimeLeft = (): TimeFormat => { 38 | const tillStart = startTime.valueOf() - now.valueOf(); 39 | 40 | //let difference = tillStart; 41 | let difference = duration(startTime.diff(now)); 42 | 43 | // if the lockdrop has already started 44 | if (tillStart < 0) { 45 | difference = duration(endTime.diff(now)); 46 | } 47 | 48 | let timeLeft: TimeFormat = { 49 | days: 0, 50 | hours: 0, 51 | minutes: 0, 52 | seconds: 0, 53 | }; 54 | 55 | const tillEnd = endTime.valueOf() - now.valueOf(); 56 | // check if the duration has ended 57 | if (tillEnd > 0) { 58 | timeLeft = { 59 | days: difference.days(), 60 | hours: difference.hours(), 61 | minutes: difference.minutes(), 62 | seconds: difference.seconds(), 63 | }; 64 | } 65 | return timeLeft; 66 | }; 67 | 68 | const getLockState = (): LockState => { 69 | const tillStart = startTime.valueOf() - now.valueOf(); 70 | if (tillStart > 0) { 71 | return LockState.notStart; 72 | } else if (tillStart <= 0 && !(endTime.valueOf() - now.valueOf() < 0)) { 73 | return LockState.start; 74 | } else { 75 | return LockState.end; 76 | } 77 | }; 78 | 79 | const [timeLeft, setTimeLeft] = useState(calculateTimeLeft()); 80 | const [lockState, setLockState] = useState(getLockState()); 81 | const [totalLockVal, setTotalLockVal] = useState('0'); 82 | 83 | const getLockValue = async (): Promise => { 84 | try { 85 | const _totalLockVal = getTotalLockVal(lockData); 86 | setTotalLockVal(_totalLockVal); 87 | } catch (err) { 88 | console.error(err); 89 | } 90 | }; 91 | 92 | useEffect(() => { 93 | const interval = setInterval(async () => { 94 | setTimeLeft(calculateTimeLeft()); 95 | setLockState(getLockState()); 96 | await getLockValue(); 97 | }, 1000); 98 | // cleanup hook 99 | return () => { 100 | clearInterval(interval); 101 | }; 102 | }); 103 | 104 | if (lockState !== LockState.end) { 105 | return ( 106 | <> 107 | 108 |
    109 | 110 | Lockdrop {lockState === LockState.notStart ? 'Starting' : 'Ending'} in: 111 | 112 | 113 | 114 |

    {timeLeft.days}

    115 |

    Days

    116 |
    117 | 118 |

    {timeLeft.hours}

    119 |

    Hours

    120 |
    121 | 122 |

    {timeLeft.minutes}

    123 |

    Minutes

    124 |
    125 | 126 |

    {timeLeft.seconds}

    127 |

    Seconds

    128 |
    129 |
    130 |
    131 |
    132 | 133 | ); 134 | } else { 135 | return ( 136 | <> 137 | 138 | 139 | Lockdrop has ended 140 | 141 | 142 | Total Locked Value: {totalLockVal} ETH 143 | 144 | 145 | 146 | ); 147 | } 148 | }; 149 | 150 | export default LockdropCountdownPanel; 151 | 152 | interface PanelWrapperProps { 153 | children: boolean | React.ReactChild | React.ReactFragment | React.ReactPortal; 154 | } 155 | 156 | const PanelWrapper: React.FC = ({ children }) => { 157 | const classes = useStyles(); 158 | 159 | return ( 160 | <> 161 | 162 | {children} 163 | 164 | 165 | ); 166 | }; 167 | -------------------------------------------------------------------------------- /src/components/EthLock/LockdropForm.tsx: -------------------------------------------------------------------------------- 1 | import { IonLabel, IonButton, IonItem, IonInput, IonCard, IonCardContent, IonChip, IonImg } from '@ionic/react'; 2 | import React, { useState } from 'react'; 3 | import { LockInput, OptionItem } from '../../types/LockdropModels'; 4 | import SectionCard from '../SectionCard'; 5 | import { DropdownOption } from '../DropdownOption'; 6 | import Container from '@material-ui/core/Container'; 7 | import { Typography, Link } from '@material-ui/core'; 8 | import quantstampLogo from '../../resources/quantstamp-logo.png'; 9 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; 10 | import { ethDurations } from '../../data/lockInfo'; 11 | 12 | type InputProps = { 13 | onSubmit: (inputs: LockInput) => void; 14 | }; 15 | // the main component function 16 | const LockdropForm: React.FC = ({ onSubmit }: InputProps) => { 17 | // states used in this component 18 | const [lockAmount, setAmount] = useState(''); 19 | const [lockDuration, setDuration] = useState({ label: '', value: 0, rate: 0 }); 20 | const [affAccount, setAff] = useState(''); 21 | 22 | const useStyles = makeStyles((theme: Theme) => 23 | createStyles({ 24 | formRoot: { 25 | padding: theme.spacing(4, 3, 0), 26 | }, 27 | formLabel: { 28 | margin: theme.spacing(2), 29 | }, 30 | quantLogo: { 31 | marginRight: theme.spacing(2), 32 | maxHeight: '100%', 33 | height: 30, 34 | verticalAlign: 'middle', 35 | }, 36 | textBox: { 37 | marginLeft: 'auto', 38 | marginRight: 'auto', 39 | }, 40 | }), 41 | ); 42 | 43 | const classes = useStyles(); 44 | 45 | // the submit button function 46 | function handleSubmit() { 47 | const inputs: LockInput = { 48 | duration: lockDuration.value, 49 | amount: lockAmount, 50 | affiliation: affAccount, 51 | rate: lockDuration.rate, 52 | }; 53 | onSubmit(inputs); 54 | } 55 | 56 | // main render JSX 57 | return ( 58 | <> 59 | 60 |
    61 | 62 | Plasm Main Network Ethereum Lockdrop 63 | 64 |
    65 | 66 | Audited by 67 | 68 | 74 | 75 | 76 |
    77 | 78 | 79 | 80 | This is the lockdrop form for Ethereum. This uses Web3 injection so you must access this 81 | page with a dApp browser or extension installed in order for this to work properly. If you 82 | find any errors or find issues with this form, please contact the Plasm team. Regarding the 83 | audit by Quantstamp, click{' '} 84 | 90 | here 91 | {' '} 92 | for more details 93 | 94 | 95 | 96 | 97 | Number of ETH 98 | e.detail.value && setAmount(e.detail.value)} 101 | > 102 | 103 | Lock Duration 104 | 105 | { 108 | setDuration(ethDurations.filter(i => i.value === parseInt(e))[0]); 109 | }} 110 | > 111 | 112 | 113 | {lockDuration.value 114 | ? 'The rate is ' + lockDuration.rate + 'x' 115 | : 'Please choose the duration'} 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | If you have a friend who is also participating in the lockdrop and is part of our 124 | affiliation program, please input the address. Both parties will be able to receive a 125 | bonus rate of 1% of what the friend is receiving. Checkout this{' '} 126 | 127 | article 128 | {' '} 129 | for details. 130 | 131 | 132 | Affiliation (optional) 133 | 134 | setAff((e.target as HTMLInputElement).value)} 137 | > 138 | 139 | 140 | handleSubmit()}> 141 | Submit Transaction 142 | 143 | 144 |
    145 |
    146 | 147 | ); 148 | }; 149 | 150 | export default LockdropForm; 151 | -------------------------------------------------------------------------------- /src/components/EthLock/LockdropResult.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React, { useEffect, useState } from 'react'; 3 | import { 4 | makeStyles, 5 | createStyles, 6 | Theme, 7 | CircularProgress, 8 | Divider, 9 | Link, 10 | List, 11 | ListItem, 12 | ListItemIcon, 13 | ListItemText, 14 | Collapse, 15 | Typography, 16 | } from '@material-ui/core'; 17 | import { calculateTotalPlm, ethFinalExRate, getPubKey } from '../../helpers/lockdrop/EthereumLockdrop'; 18 | import { PlmDrop } from '../../types/PlasmDrop'; 19 | import BigNumber from 'bignumber.js'; 20 | import CountUp from 'react-countup'; 21 | import { ThemeColors } from '../../theme/themes'; 22 | import { IonPopover, IonList, IonListHeader, IonItem, IonLabel, IonChip, IonButton } from '@ionic/react'; 23 | import { LockEvent } from '../../types/LockdropModels'; 24 | import Web3 from 'web3'; 25 | import SectionCard from '../SectionCard'; 26 | import ExpandLess from '@material-ui/icons/ExpandLess'; 27 | import ExpandMore from '@material-ui/icons/ExpandMore'; 28 | import VpnKeyIcon from '@material-ui/icons/VpnKey'; 29 | import { generatePlmAddress } from '../../helpers/plasmUtils'; 30 | import { useEth } from '../../api/Web3Api'; 31 | import LoadingOverlay from '../LoadingOverlay'; 32 | 33 | const etherScanSearch = 'https://etherscan.io/address/'; 34 | 35 | interface ResultProps { 36 | lockData: LockEvent[]; 37 | } 38 | 39 | const LockdropResult: React.FC = ({ lockData }) => { 40 | const useStyles = makeStyles((theme: Theme) => 41 | createStyles({ 42 | pageContent: { 43 | textAlign: 'center', 44 | padding: theme.spacing(4, 2, 0), 45 | }, 46 | header: { 47 | color: ThemeColors.blue, 48 | }, 49 | claimButton: { 50 | padding: theme.spacing(4, 2, 0), 51 | }, 52 | }), 53 | ); 54 | 55 | const classes = useStyles(); 56 | const [totalPlm, setTotalPlm] = useState(new PlmDrop('', new BigNumber(0), [], [], [])); 57 | const [exRate, setExRate] = useState(0); 58 | const [isLoading, setLoadState] = useState(true); 59 | const [showIntoRefPopover, setShowIntroRefPopover] = useState(false); 60 | const [showIntoPopover, setShowIntroPopover] = useState(false); 61 | const { web3 } = useEth(); 62 | 63 | useEffect(() => { 64 | const interval = setInterval(async () => { 65 | setExRate(ethFinalExRate); 66 | const accounts = await web3.eth.getAccounts(); 67 | const totalIssue = calculateTotalPlm(accounts[0], lockData); 68 | setTotalPlm(totalIssue); 69 | 70 | setLoadState(false); 71 | }, 1000); 72 | // cleanup hook 73 | return () => { 74 | clearInterval(interval); 75 | }; 76 | }); 77 | 78 | const countupTotalPlmVal: JSX.Element = ( 79 | 86 | ); 87 | 88 | return ( 89 |
    90 |

    Lockdrop Result

    91 | {isLoading ? ( 92 | <> 93 | 94 | 95 | ) : totalPlm.locks.length > 0 || totalPlm.affiliationRefsBonuses.length > 0 ? ( 96 | <> 97 |

    {countupTotalPlmVal} PLM in total

    98 |

    You have locked {totalPlm.locks.length} time(s)

    99 |

    100 | ETH exchange rate at the end of the lockdrop: {exRate} USD( 101 | 102 | ref 103 | 104 | ) 105 |

    106 |

    You have received around {totalPlm.basePlm.toFormat(2)} PLM from locking

    107 | 108 |

    Affiliation Program

    109 | setShowIntroRefPopover(true)}> 110 | {totalPlm.affiliationRefsBonuses.length} locks 111 | 112 | referenced your address as a introducer: {totalPlm.getAffBonus()} PLM 113 | 114 | setShowIntroRefPopover(false)}> 115 | 116 | 117 |
    118 | You have referenced 119 | setShowIntroPopover(true)}> 120 | {totalPlm.introducerAndBonuses.length} introducers 121 | 122 | : {totalPlm.getIntroBonus()} PLM 123 | 124 | setShowIntroPopover(false)}> 125 | 126 | 127 |
    128 | 129 | 130 | ) : ( 131 |

    No Locks found for your address!

    132 | )} 133 |
    134 | ); 135 | }; 136 | 137 | export default LockdropResult; 138 | 139 | interface IntroRefProps { 140 | data: PlmDrop; 141 | } 142 | const IntoRefItems: React.FC = ({ data }) => { 143 | return ( 144 | <> 145 | 146 | {data.affiliationRefsBonuses.length > 0 ? ( 147 | <> 148 | References 149 | {data.affiliationRefsBonuses.map((i: [string, BigNumber]) => ( 150 | 151 | {i[0]} 152 | 153 | ))} 154 | 155 | ) : ( 156 | No References 157 | )} 158 | 159 | 160 | ); 161 | }; 162 | 163 | const IntoAffItems: React.FC = ({ data }) => { 164 | return ( 165 | <> 166 | 167 | {data.introducerAndBonuses.length > 0 ? ( 168 | <> 169 | Introducers 170 | {data.introducerAndBonuses.map((i: [string, BigNumber]) => ( 171 | 172 | {i[0]} 173 | 174 | ))} 175 | 176 | ) : ( 177 | No Introducers 178 | )} 179 | 180 | 181 | ); 182 | }; 183 | 184 | interface ClaimProps { 185 | web3: Web3; 186 | } 187 | const ClaimPlm: React.FC = ({ web3 }) => { 188 | const useStyles = makeStyles((theme: Theme) => 189 | createStyles({ 190 | header: { 191 | color: ThemeColors.blue, 192 | }, 193 | claimButton: { 194 | paddingTop: theme.spacing(2), 195 | marginLeft: 'auto', 196 | marginRight: 'auto', 197 | maxWidth: '100%', 198 | }, 199 | addressPanel: { 200 | padding: theme.spacing(3, 3, 0), 201 | }, 202 | root: { 203 | width: '100%', 204 | alignContent: 'center', 205 | backgroundColor: theme.palette.background.paper, 206 | }, 207 | nested: { 208 | paddingLeft: theme.spacing(4), 209 | }, 210 | }), 211 | ); 212 | 213 | const [message, setMessage] = useState(''); 214 | const [plmAddress, setPlmAddress] = useState(''); 215 | const [ethPubkey, setEthPubkey] = useState(''); 216 | const [open, setOpen] = useState(false); 217 | 218 | const getPlasmAddress = async () => { 219 | const pubKey = await getPubKey(web3); 220 | let result = ''; 221 | if (typeof pubKey === 'string') { 222 | setEthPubkey(pubKey); 223 | // remove the 0x prefix before passing the value 224 | const plmAddress = generatePlmAddress(pubKey.replace('0x', '')); 225 | result = plmAddress; 226 | } 227 | setMessage(''); 228 | return result; 229 | }; 230 | const ExpandItem = () => { 231 | setOpen(!open); 232 | }; 233 | 234 | const classes = useStyles(); 235 | 236 | return ( 237 | <> 238 | 239 | { 244 | setMessage('Verifying user...'); 245 | setPlmAddress(await getPlasmAddress()); 246 | }} 247 | > 248 | Get Plasm Address 249 | 250 | {plmAddress ? ( 251 | <> 252 | 253 |
    254 |

    Your Plasm Network address with the lockdrop rewards:

    255 | 261 |

    {plmAddress}

    262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | {open ? : } 270 | 271 | 272 | {ethPubkey} 273 | 274 | 275 |
    276 |
    277 | 278 | ) : null} 279 | 280 | ); 281 | }; 282 | -------------------------------------------------------------------------------- /src/components/EthLock/LockedEthList.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable react/prop-types */ 3 | import React from 'react'; 4 | import SectionCard from '../SectionCard'; 5 | import { LockEvent } from '../../types/LockdropModels'; 6 | import { createStyles, Theme, makeStyles, useTheme } from '@material-ui/core/styles'; 7 | import Typography from '@material-ui/core/Typography'; 8 | import Box from '@material-ui/core/Box'; 9 | import AppBar from '@material-ui/core/AppBar'; 10 | import Tabs from '@material-ui/core/Tabs'; 11 | import Tab from '@material-ui/core/Tab'; 12 | import SwipeableViews from 'react-swipeable-views'; 13 | import CurrentLocks from './CurrentLocks'; 14 | import GlobalLocks from './EthGlobalLocks'; 15 | 16 | interface TabPanelProps { 17 | children?: React.ReactNode; 18 | dir?: string; 19 | index: any; 20 | value: any; 21 | } 22 | 23 | interface LockHistoryProps { 24 | lockData: LockEvent[]; 25 | onClickRefresh?: () => Promise; 26 | } 27 | 28 | const TabPanel = (props: TabPanelProps) => { 29 | const { children, value, index, ...other } = props; 30 | 31 | return ( 32 | 42 | ); 43 | }; 44 | 45 | const a11yProps = (index: any) => { 46 | return { 47 | id: `full-width-tab-${index}`, 48 | 'aria-controls': `full-width-tabpanel-${index}`, 49 | }; 50 | }; 51 | 52 | const useStyles = makeStyles((theme: Theme) => 53 | createStyles({ 54 | tabMenu: { 55 | backgroundColor: theme.palette.background.paper, 56 | width: 'auto', 57 | }, 58 | }), 59 | ); 60 | 61 | // component that displays the number of tokens and the duration for the lock via Web3 62 | const LockedEthList: React.FC = ({ lockData, onClickRefresh }) => { 63 | const classes = useStyles(); 64 | const theme = useTheme(); 65 | const [value, setValue] = React.useState(0); 66 | 67 | const handleChange = (_event: React.ChangeEvent, newValue: number) => { 68 | setValue(newValue); 69 | }; 70 | 71 | const handleChangeIndex = (index: number) => { 72 | setValue(index); 73 | }; 74 | 75 | return ( 76 | <> 77 | 78 |
    79 | 80 | 88 | 89 | 90 | 91 | 92 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 |
    105 |
    106 | 107 | ); 108 | }; 109 | 110 | export default LockedEthList; 111 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Typography from '@material-ui/core/Typography'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Container from '@material-ui/core/Container'; 5 | import Link from '@material-ui/core/Link'; 6 | import List from '@material-ui/core/List'; 7 | import ListItem from '@material-ui/core/ListItem'; 8 | import ListItemText from '@material-ui/core/ListItemText'; 9 | import Grid from '@material-ui/core/Grid'; 10 | import { Company, Products, Collaboration, Blogs, Community } from '../data/links'; 11 | import Divider from '@material-ui/core/Divider'; 12 | import { ThemeColors } from '../theme/themes'; 13 | 14 | const Copyright: React.FC = () => { 15 | return ( 16 | <> 17 | 18 | {'© 2019-' + new Date().getFullYear() + ' '} 19 | 20 | Stake Technologies, Inc. 21 | {' '} 22 | {'All Rights Reserved.'} 23 | 24 | 25 | ); 26 | }; 27 | 28 | const useStyles = makeStyles(theme => ({ 29 | root: { 30 | marginTop: theme.spacing(10), 31 | }, 32 | socialIcon: { 33 | color: 'white', 34 | fontSize: 60, 35 | paddingLeft: theme.spacing(1), 36 | }, 37 | footer: { 38 | padding: theme.spacing(2), 39 | backgroundColor: 'white', 40 | color: 'black', 41 | }, 42 | footerHeader: { 43 | color: ThemeColors.darkBlue, 44 | }, 45 | siteMap: {}, 46 | })); 47 | 48 | const StickyFooter: React.FC = () => { 49 | const classes = useStyles(); 50 | 51 | return ( 52 |
    53 |
    54 | 55 | 56 | 57 | 58 | Company 59 | 60 | 61 | 62 | 63 | {Company.map(company => ( 64 | 71 | 72 | 73 | 74 | 75 | ))} 76 | 77 | 78 | 79 | 80 | Repositories 81 | 82 | 83 | 84 | {Products.map(product => ( 85 | 92 | 93 | 94 | 95 | 96 | ))} 97 | 98 | 99 | 100 | 101 | Collaboration 102 | 103 | 104 | 105 | {Collaboration.map(collaboration => ( 106 | 113 | 114 | 115 | 116 | 117 | ))} 118 | 119 | 120 | 121 | 122 | Blog 123 | 124 | 125 | 126 | {Blogs.map(blog => ( 127 | 134 | 135 | 136 | 137 | 138 | ))} 139 | 140 | 141 | 142 | 143 | Community 144 | 145 | 146 | 147 | {Community.map(community => ( 148 | 155 | 156 | 157 | 158 | 159 | ))} 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 |
    171 |
    172 | ); 173 | }; 174 | 175 | export default StickyFooter; 176 | -------------------------------------------------------------------------------- /src/components/LoadingOverlay.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { IonLoading } from '@ionic/react'; 3 | import { useEth } from '../api/Web3Api'; 4 | 5 | const LoadingOverlay: React.FC = ({ message = '' }: Props) => { 6 | const [isLoading, setLoading] = useState<{ 7 | loading: boolean; 8 | message: string; 9 | }>({ 10 | loading: false, 11 | message: '', 12 | }); 13 | 14 | const { isWeb3Loading, error, isChangingContract } = useEth(); 15 | 16 | useEffect(() => { 17 | setLoading({ 18 | loading: !!message, 19 | message, 20 | }); 21 | }, [message]); 22 | 23 | // Wait for initial web3 API loading 24 | useEffect(() => { 25 | if (isWeb3Loading) { 26 | setLoading({ 27 | loading: true, 28 | message: 'Syncing with Ethereum...', 29 | }); 30 | } else { 31 | setLoading({ loading: false, message: '' }); 32 | } 33 | }, [isWeb3Loading]); 34 | 35 | useEffect(() => { 36 | if (typeof error !== 'undefined') { 37 | setLoading({ loading: false, message: '' }); 38 | } 39 | }, [error]); 40 | 41 | // refresh if contract reloads 42 | useEffect(() => { 43 | if (isChangingContract) { 44 | if (!isWeb3Loading) { 45 | setLoading({ 46 | loading: true, 47 | message: 'Connecting to Web3 instance with new contract...', 48 | }); 49 | } 50 | } else { 51 | if (!isWeb3Loading) { 52 | setLoading({ loading: false, message: '' }); 53 | } 54 | } 55 | 56 | // we disable next line to prevent change on getClaimParams 57 | // eslint-disable-next-line 58 | }, [isChangingContract]); 59 | 60 | return ; 61 | }; 62 | 63 | interface Props { 64 | message?: string; 65 | } 66 | 67 | export default LoadingOverlay; 68 | -------------------------------------------------------------------------------- /src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | // TODOD check app complaining about unused React import 2 | /* eslint-disable */ 3 | // @ts-ignore 4 | import React from 'react'; 5 | import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; 6 | import { IonHeader, IonButtons, IonMenuButton, IonToolbar, IonTitle } from '@ionic/react'; 7 | import plasmLogo from '../resources/plasm-logo.png'; 8 | 9 | const useStyles = makeStyles((theme: Theme) => 10 | createStyles({ 11 | grow: { 12 | flexGrow: 1, 13 | }, 14 | navbar: { 15 | backgroundColor: 'black', 16 | }, 17 | logoIcon: { 18 | margin: theme.spacing(1), 19 | maxHeight: 45, 20 | height: '100%', 21 | verticalAlign: 'middle', 22 | }, 23 | title: { 24 | color: 'white', 25 | }, 26 | inputRoot: { 27 | color: 'inherit', 28 | }, 29 | sectionDesktop: { 30 | display: 'none', 31 | [theme.breakpoints.up('md')]: { 32 | display: 'flex', 33 | }, 34 | }, 35 | sectionMobile: { 36 | display: 'flex', 37 | [theme.breakpoints.up('md')]: { 38 | display: 'none', 39 | }, 40 | }, 41 | listItem: { 42 | width: '100%', 43 | }, 44 | heading: { 45 | fontSize: theme.typography.pxToRem(15), 46 | fontWeight: theme.typography.fontWeightRegular, 47 | }, 48 | }), 49 | ); 50 | 51 | export default function Navbar(): JSX.Element { 52 | const classes = useStyles(); 53 | 54 | return ( 55 | <> 56 | 57 | 58 | 59 | 60 | 61 | 62 | Plasm Network 63 | 64 | 65 | 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/components/NotificationMessage.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | import Button from '@material-ui/core/Button'; 4 | import Typography from '@material-ui/core/Typography'; 5 | import { makeStyles } from '@material-ui/core/styles'; 6 | 7 | const useStyles = makeStyles(() => ({ 8 | btnPrimary: { 9 | background: 'linear-gradient(45deg, #1d417f 30%, #2e8ec0 90%)', 10 | border: 0, 11 | borderRadius: 3, 12 | boxShadow: '0 3px 5px 2px rgba(0, 0, 0, .3)', 13 | color: 'white', 14 | height: 48, 15 | padding: '0 30px', 16 | }, 17 | messageBox: { 18 | display: 'flex', 19 | }, 20 | 21 | messageText: { 22 | color: 'white', 23 | }, 24 | })); 25 | 26 | interface Props { 27 | message: string; 28 | gotoUrl?: string; 29 | btnName: string; 30 | } 31 | 32 | const NotificationMessage: React.FC = ({ message, gotoUrl, btnName }) => { 33 | const classes = useStyles(); 34 | 35 | return ( 36 |
    37 | 38 | {message} 39 | 40 | 41 | {gotoUrl ? ( 42 | 43 | 46 | 47 | ) : null} 48 |
    49 | ); 50 | }; 51 | 52 | export default NotificationMessage; 53 | -------------------------------------------------------------------------------- /src/components/SectionCard.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | import Paper from '@material-ui/core/Paper'; 4 | import { makeStyles } from '@material-ui/core/styles'; 5 | import Container from '@material-ui/core/Container'; 6 | import { ThemeColors } from '../theme/themes'; 7 | 8 | interface Props { 9 | maxWidth: false | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | undefined; 10 | } 11 | 12 | const SectionCard: React.FC = ({ maxWidth, children }) => { 13 | const useStyles = makeStyles(theme => ({ 14 | paper: { 15 | backgroundColor: ThemeColors.white, 16 | padding: theme.spacing(0, 0, 2), 17 | margin: theme.spacing(1), 18 | }, 19 | })); 20 | 21 | const classes = useStyles(); 22 | 23 | return ( 24 | <> 25 | 26 | 27 | {children} 28 | 29 | 30 | 31 | ); 32 | }; 33 | 34 | export default SectionCard; 35 | -------------------------------------------------------------------------------- /src/components/SideMenu.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React from 'react'; 3 | import { IonMenu, IonListHeader, IonContent, IonList, IonItem, IonIcon, IonLabel, IonRouterLink } from '@ionic/react'; 4 | import { Links } from '../data/links'; 5 | import homeIcon from '../resources/home-outline.svg'; 6 | import twitter from '../resources/logo-twitter.svg'; 7 | import discord from '../resources/logo-discord.svg'; 8 | import telegram from '../resources/logo-telegram.svg'; 9 | import github from '../resources/logo-github.svg'; 10 | import { firstLock, secondLock } from '../data/pages'; 11 | import { LockMenu } from '../types/LockdropModels'; 12 | import { calculator, stats } from 'ionicons/icons'; 13 | 14 | interface Props { 15 | headerText: string; 16 | menuItems: LockMenu[]; 17 | } 18 | const MenuSection: React.FC = ({ headerText, menuItems }) => { 19 | return ( 20 | 21 | {headerText} 22 | {menuItems.map((i, index) => ( 23 | 24 | 25 | 26 | {i.title} 27 | 28 | 29 | ))} 30 | 31 | ); 32 | }; 33 | 34 | const SideMenu: React.FC = () => { 35 | return ( 36 | <> 37 | 38 | Sitemap 39 | 40 | 41 | 42 | 43 | 44 | Home 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Lockdrop Utility 54 | 55 | 56 | 57 | Reward Calculator 58 | 59 | 60 | 61 | 62 | 63 | Lockdrop Statistics 64 | 65 | 66 | 67 | 68 | 69 | External Links 70 | 71 | 72 | Discord 73 | 74 | 75 | 76 | Telegram 77 | 78 | 79 | 80 | Twitter 81 | 82 | 83 | 84 | Github 85 | 86 | 87 | 88 | 89 | 90 | ); 91 | }; 92 | 93 | export default SideMenu; 94 | -------------------------------------------------------------------------------- /src/components/TosAgreementModal.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import React, { useState, useEffect } from 'react'; 3 | import { IonModal, IonContent, IonButton, IonLabel, IonHeader, IonToolbar, IonTitle } from '@ionic/react'; 4 | import ReactMarkdown from 'react-markdown'; 5 | import tosContent from '../data/UserAgreement.md'; 6 | import { makeStyles, createStyles } from '@material-ui/core'; 7 | 8 | interface Props { 9 | showModal: boolean; 10 | onAgree?: (agreed: boolean) => void; 11 | } 12 | 13 | const useStyles = makeStyles(theme => 14 | createStyles({ 15 | textBox: { 16 | marginLeft: 'auto', 17 | marginRight: 'auto', 18 | padding: theme.spacing(4), 19 | }, 20 | }), 21 | ); 22 | 23 | const TosAgreementModal: React.FC = ({ showModal, onAgree }) => { 24 | const classes = useStyles(); 25 | 26 | const [toc, setToc] = useState(''); 27 | 28 | // load the markdown content as string on component mount 29 | useEffect(() => { 30 | fetch(tosContent) 31 | .then(data => data.text()) 32 | .then(text => { 33 | setToc(text); 34 | }); 35 | }, []); 36 | 37 | const handleAgreement = (agree: boolean) => { 38 | if (onAgree) onAgree(agree); 39 | }; 40 | 41 | return ( 42 | <> 43 | 44 | 45 | 46 | Lockdrop Terms of Condition 47 | 48 | 49 | 50 | 51 |
    52 | 53 | 54 | 55 | 56 |

    57 | Follow us on{' '} 58 | 59 | Twitter 60 | {' '} 61 | and{' '} 62 | 67 | Telegram{' '} 68 | 69 | for the latest information. 70 |

    71 |
    72 |
    73 | 74 | handleAgreement(true)}> 75 | Agree 76 | 77 |
    78 |
    79 | 80 | ); 81 | }; 82 | 83 | export default TosAgreementModal; 84 | -------------------------------------------------------------------------------- /src/config/endpoints.ts: -------------------------------------------------------------------------------- 1 | import { PlasmNetwork } from '../helpers/plasmUtils'; 2 | 3 | const DEFAULT_ENDPOINT_INDEX = 1; 4 | 5 | interface Endpoint { 6 | network: PlasmNetwork; 7 | address: string; 8 | } 9 | 10 | const endpoints: Endpoint[] = [ 11 | { 12 | network: PlasmNetwork.Local, 13 | address: 'ws://127.0.0.1:9944', 14 | }, 15 | { 16 | network: PlasmNetwork.Main, 17 | address: 'wss://rpc.plasmnet.io', 18 | }, 19 | ]; 20 | 21 | /** 22 | * gets endpoint url for a given network 23 | * @param network the network 24 | */ 25 | export function getNetworkEndpoint(network?: PlasmNetwork): string { 26 | const endpoint = endpoints.find(x => x.network === network) || endpoints[DEFAULT_ENDPOINT_INDEX]; 27 | return endpoint.address; 28 | } 29 | -------------------------------------------------------------------------------- /src/contexts/ApiContext.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ApiPromise } from '@polkadot/api'; 3 | import { PlasmNetwork } from '../helpers/plasmUtils'; 4 | 5 | export interface ApiProps { 6 | api: ApiPromise; 7 | isReady: boolean; 8 | network: PlasmNetwork; 9 | } 10 | 11 | export const ApiContext = React.createContext({} as ApiProps); 12 | -------------------------------------------------------------------------------- /src/contexts/Web3Context.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { LockSeason } from '../types/LockdropModels'; 3 | import Web3 from 'web3'; 4 | import { Contract } from 'web3-eth-contract'; 5 | 6 | export interface Web3ApiProps { 7 | web3: Web3; 8 | isWeb3Loading: boolean; 9 | currentNetwork: string; 10 | latestBlock: number; 11 | account: string; 12 | contract: Contract | undefined; 13 | lockdropStart: string; 14 | lockdropEnd: string; 15 | error: string | undefined; 16 | isChangingContract: boolean; 17 | changeContractAddress: (address: string) => void; 18 | setLatestBlock: (block: number) => void; 19 | setAccount: (account: string) => void; 20 | setIsMainnetLock: (value: boolean) => void; 21 | setParameters: (isMainNetLock: boolean, lockSeason: LockSeason) => void; 22 | } 23 | 24 | export const Web3Context = React.createContext({} as Web3ApiProps); 25 | -------------------------------------------------------------------------------- /src/data/UserAgreement.md: -------------------------------------------------------------------------------- 1 |

    2 |
    Disclaimer
    3 |

    4 | 5 | 1. Stake Technologies, Inc. (hereinafter referred to as the “Company") does NOT warrant 6 | that (i) Lockdrop or the transfer mechanism of token made through Lockdrop, is not 7 | affected by computer viruses, harmful computer programs or other harmful components, 8 | or (ii) users will not suffer damage from these harmful components or the like while 9 | participating in Lockdrop. 10 | 11 | 2. In no event shall the Company be liable for any damages incurred by users or third 12 | parties arising out of changes in the information generated by the Company in 13 | connection with Lockdrop. 14 | 15 | 3. User shall manage its private key to access Lockdrop at its own expense and 16 | responsibility, and the Company does not hold any responsibility or manage the Private 17 | Keys. The Company shall not be liable for any damages incurred by users due to the loss, 18 | theft, unauthorized use by third parties, etc. of their private keys. 19 | 20 | 4. The Company makes NO representation or warranty with respect to the value of PLM 21 | tokens issued on the Plasm Network, and the Company disclaims all warranties with 22 | respect to, including without limitation the generation of the value, listing on the 23 | exchange, increase or decrease of the value, loss of the value and any other matters with 24 | respect to the value of PLM tokens. 25 | 26 | 5. When users transfer or sell PLM tokens, tax may be imposed. Users shall be solely 27 | responsible for all procedures and payments of tax incurred by them, and the Company 28 | shall not be responsible therefor. 29 | 30 | 6. The Company assumes NO responsibility with respect to increase, decrease or loss of 31 | the value of the cryptocurrency/crypto asset or profit that users may have gained or lost 32 | due to increase or decrease of the value of the cryptocurrency/crypto asset during the 33 | users participate in Lockdrop. 34 | 35 | 7. Except as otherwise expressly stated by the Company, the Company does NOT make 36 | any warranty with respect to Lockdrop, and shall NOT be liable for any damages incurred 37 | by users or third parties in connection with Lockdrop. Even if the Company is held liable 38 | for damages against users due to the application of the Japanese Consumer Contract Act 39 | or other reasons, the liability of the Company shall be limited to direct and ordinary 40 | damages actually incurred due to reasons attributable to the Company. 41 | -------------------------------------------------------------------------------- /src/data/links.ts: -------------------------------------------------------------------------------- 1 | export const Links = { 2 | docs: 'https://docs.plasmnet.io', 3 | blog: 'https://medium.com/stake-technologies', 4 | twitter: 'https://twitter.com/Plasm_Network', 5 | github: 'https://github.com/staketechnologies/Plasm', 6 | telegram: 'https://t.me/PlasmOfficial', 7 | discord: 'https://discordapp.com/invite/Dnfn5eT', 8 | email: 'info@stake.co.jp', 9 | home: 'https://stake.co.jp/', 10 | plasmHome: 'https://plasmnet.io/', 11 | }; 12 | 13 | export const SponsorLinks = { 14 | cryptoEconomicsLab: 'https://www.cryptoeconomicslab.com/', 15 | parityTechnologies: 'https://www.parity.io/', 16 | web3Foundation: 'https://web3.foundation/', 17 | longhash: 'https://longhash.co.jp/en/', 18 | universityOfTokyo: 'https://www.u-tokyo.ac.jp/en/index.html', 19 | connect: 'https://www.blockchain.t.u-tokyo.ac.jp/', 20 | parityBuilders: 'https://builders.parity.io/', 21 | acala: 'https://acala.network/', 22 | }; 23 | 24 | export const BlogLinks = { 25 | lockdropIntroduction: 'https://medium.com/stake-technologies/plasm-lockdrop-introduction-54614592a13', 26 | }; 27 | 28 | export const AppLinks = { 29 | plasmnetIo: 'https://apps.plasmnet.io/', 30 | polkadotNetwork: 'https://telemetry.polkadot.io/#list/Plasm%20Testnet%20v3', 31 | joinLockdrop: 'https://lockdrop.astar.network', 32 | inkPlayground: 'https://ink-playground.com/', 33 | }; 34 | 35 | export const Whitepaper = [ 36 | { 37 | description: 'English', 38 | link: 'https://github.com/stakedtechnologies/plasmdocs/blob/master/wp/en.pdf', 39 | icon: `sticky note outline`, 40 | }, 41 | { 42 | description: 'Japanese', 43 | link: 'https://github.com/stakedtechnologies/plasmdocs/blob/master/wp/jp.pdf', 44 | icon: `sticky note outline`, 45 | }, 46 | ]; 47 | 48 | export const Community = [ 49 | { 50 | description: 'Discord', 51 | link: Links.discord, 52 | icon: 'discord', 53 | }, 54 | { 55 | description: 'Telegram', 56 | link: Links.telegram, 57 | icon: 'telegram', 58 | }, 59 | ]; 60 | 61 | export const Company = [ 62 | { 63 | description: 'Home', 64 | link: Links.home, 65 | }, 66 | { 67 | description: 'Contact us', 68 | link: `mailto:${Links.email}`, 69 | }, 70 | ]; 71 | 72 | export const Products = [ 73 | { 74 | description: 'Plasm', 75 | link: 'https://github.com/staketechnologies/Plasm', 76 | }, 77 | { 78 | description: 'Plasm Portal Sites', 79 | link: 'https://github.com/staketechnologies/apps', 80 | }, 81 | { 82 | description: 'Plasm Lockdrop', 83 | link: 'https://github.com/staketechnologies/lockdrop-ui', 84 | }, 85 | { 86 | description: 'Plasm documentations', 87 | link: 'https://github.com/staketechnologies/plasmdocs', 88 | }, 89 | { 90 | description: 'Plasm homepage', 91 | link: 'https://github.com/staketechnologies/plasmnet.io', 92 | }, 93 | ]; 94 | 95 | export const Collaboration = [ 96 | { 97 | description: 'Substrate Builders Program', 98 | link: 'https://builders.parity.io/', 99 | }, 100 | { 101 | description: 'Web3 foundation grants program', 102 | link: 'https://web3.foundation/', 103 | }, 104 | ]; 105 | 106 | export const Blogs = [ 107 | { 108 | description: 'Medium', 109 | link: Links.blog, 110 | }, 111 | ]; 112 | 113 | export const Discussions = [ 114 | { 115 | link: Links.discord, 116 | className: 'discord', 117 | }, 118 | { 119 | link: Links.telegram, 120 | className: 'telegram', 121 | }, 122 | { 123 | link: Links.twitter, 124 | className: 'twitter', 125 | }, 126 | { 127 | link: Links.github, 128 | className: 'github', 129 | }, 130 | ]; 131 | -------------------------------------------------------------------------------- /src/data/lockInfo.ts: -------------------------------------------------------------------------------- 1 | import { OptionItem } from '../types/LockdropModels'; 2 | import moment from 'moment'; 3 | 4 | import Lockdrop from '../contracts/Lockdrop.json'; 5 | 6 | const LOCKDROP_DURATION = 30; // days 7 | 8 | /** 9 | * the time zone is set to UTC as default 10 | * lockdrop starts from 1584230400 epoch time 11 | */ 12 | export const firstLockdropStart = moment.unix(1584230400); 13 | export const firstLockdropEnd = firstLockdropStart.clone().add(LOCKDROP_DURATION, 'days'); 14 | 15 | /** 16 | * the time zone is set to UTC as default 17 | * lockdrop starts from 1598832000 epoch time 18 | */ 19 | export const secondLockdropStart = moment.unix(1598832000); 20 | export const secondLockdropEnd = secondLockdropStart.clone().add(LOCKDROP_DURATION, 'days'); 21 | 22 | export interface LockdropContract { 23 | type: 'main' | 'private'; 24 | address: string; 25 | blockHeight: number; 26 | } 27 | 28 | export const firstLockContract: LockdropContract[] = [ 29 | { 30 | type: 'main', 31 | address: '0x458DaBf1Eff8fCdfbF0896A6Bd1F457c01E2FfD6', 32 | blockHeight: 9662816, 33 | }, 34 | { type: 'private', address: Lockdrop.networks[5777].address, blockHeight: 0 }, 35 | ]; 36 | 37 | export const secondLockContract: LockdropContract[] = [ 38 | { 39 | type: 'main', 40 | address: '0xa4803f17607B7cDC3dC579083d9a14089E87502b', 41 | blockHeight: 10714638, 42 | }, 43 | { type: 'private', address: Lockdrop.networks[5777].address, blockHeight: 0 }, 44 | ]; 45 | 46 | /** 47 | * used to define the content of the dropdown menu 48 | */ 49 | export const ethDurations: OptionItem[] = [ 50 | { label: '30 Days', value: 30, rate: 24 }, 51 | { label: '100 Days', value: 100, rate: 100 }, 52 | { label: '300 Days', value: 300, rate: 360 }, 53 | { label: '1000 Days', value: 1000, rate: 1600 }, 54 | ]; 55 | -------------------------------------------------------------------------------- /src/data/pages.ts: -------------------------------------------------------------------------------- 1 | import ethLogo from '../resources/ethereum_logo.svg'; 2 | import { LockMenu } from '../types/LockdropModels'; 3 | import { firstLockdropStart, firstLockdropEnd, secondLockdropStart, secondLockdropEnd } from './lockInfo'; 4 | 5 | export const firstLock: LockMenu[] = [ 6 | { 7 | title: 'ETH Lock', 8 | uri: '/lock-form/first', 9 | icon: ethLogo, 10 | startDate: firstLockdropStart, 11 | endDate: firstLockdropEnd, 12 | }, 13 | ]; 14 | 15 | export const secondLock: LockMenu[] = [ 16 | { 17 | title: 'ETH Lock', 18 | uri: '/lock-form/second-eth', 19 | icon: ethLogo, 20 | startDate: secondLockdropStart, 21 | endDate: secondLockdropEnd, 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.md'; 2 | -------------------------------------------------------------------------------- /src/helpers/getWeb3.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import Web3 from 'web3'; 3 | 4 | async function web3Listener() { 5 | // Modern dapp browsers... 6 | if ((window as any).ethereum) { 7 | const web3 = new Web3((window as any).ethereum); 8 | try { 9 | // Request account access if needed 10 | //await (window as any).ethereum.enable(); 11 | await (window as any).ethereum.request({ method: 'eth_requestAccounts' }); 12 | console.log('Dapp browser detected'); 13 | 14 | // Accounts now exposed 15 | return web3; 16 | } catch (error) { 17 | return error; 18 | } 19 | } 20 | // Legacy dapp browsers... 21 | else if ((window as any).web3) { 22 | // Use Mist/MetaMask's provider. 23 | const web3 = (window as any).web3; 24 | console.log('Injected web3 detected.'); 25 | return web3; 26 | } 27 | // Fallback to localhost; use dev console port by default... 28 | else { 29 | const provider = new Web3.providers.HttpProvider('http://127.0.0.1:8545'); 30 | const web3 = new Web3(provider); 31 | console.log('No web3 instance injected, using Local web3.'); 32 | return web3; 33 | } 34 | } 35 | 36 | export const removeWeb3Event = (): void => { 37 | new Promise((resolve, reject) => { 38 | try { 39 | window.removeEventListener('load', () => resolve(web3Listener())); 40 | } catch (error) { 41 | reject(error); 42 | } 43 | }); 44 | }; 45 | 46 | const getWeb3 = (): Promise => 47 | new Promise((resolve, reject) => { 48 | // check if the event was already fired 49 | if (document.readyState === 'complete') { 50 | // reload page to reset the event 51 | window.location.reload(); 52 | } 53 | 54 | // Wait for loading completion to avoid race conditions with web3 injection timing. 55 | try { 56 | window.addEventListener('load', () => resolve(web3Listener())); 57 | } catch (error) { 58 | reject(error); 59 | } 60 | }); 61 | 62 | export default getWeb3; 63 | -------------------------------------------------------------------------------- /src/helpers/lockdrop/PolkadotLockdrop.ts: -------------------------------------------------------------------------------- 1 | export function lockDots(): void { 2 | console.log('implement this!'); 3 | } 4 | -------------------------------------------------------------------------------- /src/helpers/lockdrop/debug.log: -------------------------------------------------------------------------------- 1 | [0811/125158.592:ERROR:registration_protocol_win.cc(102)] CreateFile: The system cannot find the file specified. (0x2) 2 | -------------------------------------------------------------------------------- /src/helpers/useChainInfo.ts: -------------------------------------------------------------------------------- 1 | import { ApiPromise } from '@polkadot/api'; 2 | import { useMemo } from 'react'; 3 | import { useApi } from '../api/Api'; 4 | import BN from 'bn.js'; 5 | import { formatBalance } from '@polkadot/util'; 6 | 7 | export interface ChainInfo { 8 | tokenDecimals: number; 9 | formatBalance: (input?: string | number | BN | BigInt | undefined) => string; 10 | } 11 | 12 | const DEFAULT_DECIMALS = 15; 13 | 14 | function createInfo(api: ApiPromise): ChainInfo { 15 | const tokenDecimals = api.registry.chainDecimals[0] || DEFAULT_DECIMALS; 16 | 17 | return { 18 | tokenDecimals, 19 | formatBalance: (input?: string | number | BN | BigInt | undefined) => { 20 | return formatBalance( 21 | input, 22 | { 23 | withSi: true, 24 | withUnit: 'PLM', 25 | }, 26 | tokenDecimals, 27 | ); 28 | }, 29 | }; 30 | } 31 | 32 | export default function useChainInfo(): ChainInfo { 33 | const { api, isReady } = useApi(); 34 | 35 | // eslint-disable-next-line 36 | return useMemo(() => createInfo(api), [api, isReady]); 37 | } 38 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: 'Work Sans', -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 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | // TODOD check app complaining about unused React import 2 | /* eslint-disable */ 3 | // @ts-ignore 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import './index.css'; 7 | import 'react-virtualized/styles.css'; 8 | import App from './App'; 9 | import * as serviceWorker from './serviceWorker'; 10 | import { MuiThemeProvider, createTheme, responsiveFontSizes } from '@material-ui/core/styles'; 11 | import Web3 from 'web3'; 12 | import { Contract } from 'web3-eth-contract'; 13 | import { toast } from 'react-toastify'; 14 | 15 | // define web3 instance as a global variable 16 | declare global { 17 | interface Window { 18 | web3: Web3; 19 | contract: Contract; 20 | } 21 | } 22 | toast.configure({ 23 | position: 'top-right', 24 | autoClose: 5000, 25 | hideProgressBar: false, 26 | closeOnClick: true, 27 | pauseOnHover: true, 28 | draggable: true, 29 | }); 30 | 31 | window.contract = window.contract || {}; 32 | window.web3 = window.web3 || {}; 33 | 34 | let theme = createTheme({ 35 | typography: { 36 | fontFamily: [ 37 | 'Work Sans', 38 | '-apple-system', 39 | 'BlinkMacSystemFont', 40 | 'Segoe UI', 41 | 'Roboto', 42 | 'Oxygen', 43 | 'Ubuntu', 44 | 'Cantarell', 45 | 'Fira Sans', 46 | 'Droid Sans', 47 | 'Helvetica Neue', 48 | ].join(','), 49 | }, 50 | palette: { 51 | primary: { 52 | main: '#4791db', 53 | light: '#1976d2', 54 | dark: '#115293', 55 | }, 56 | }, 57 | }); 58 | 59 | theme = responsiveFontSizes(theme); 60 | 61 | ReactDOM.render( 62 | 63 | 64 | , 65 | document.getElementById('root'), 66 | ); 67 | 68 | // If you want your app to work offline and load faster, you can change 69 | // unregister() to register() below. Note this comes with some pitfalls. 70 | // Learn more about service workers: https://bit.ly/CRA-PWA 71 | serviceWorker.unregister(); 72 | -------------------------------------------------------------------------------- /src/pages/FirstEthLockdropPage.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* eslint-disable react/prop-types */ 3 | import { IonContent, IonPage } from '@ionic/react'; 4 | import React, { useState, useEffect } from 'react'; 5 | import * as ethLockdrop from '../helpers/lockdrop/EthereumLockdrop'; 6 | import Navbar from '../components/Navbar'; 7 | import Footer from '../components/Footer'; 8 | import { LockEvent, LockSeason } from '../types/LockdropModels'; 9 | import LockedEthList from '../components/EthLock/LockedEthList'; 10 | import { toast } from 'react-toastify'; 11 | import 'react-toastify/dist/ReactToastify.css'; 12 | import SectionCard from '../components/SectionCard'; 13 | import { Typography, Divider } from '@material-ui/core'; 14 | import moment from 'moment'; 15 | import LockdropCountdownPanel from '../components/EthLock/LockdropCountdownPanel'; 16 | import { firstLockContract } from '../data/lockInfo'; 17 | import 'react-dropdown/style.css'; 18 | import LockdropResult from '../components/EthLock/LockdropResult'; 19 | import AffiliationList from '../components/EthLock/AffiliationList'; 20 | import { useEth, isMainnet } from '../api/Web3Api'; 21 | import LoadingOverlay from '../components/LoadingOverlay'; 22 | 23 | const FirstEthLockdropPage: React.FC = () => { 24 | const { web3, contract, error, lockdropStart, lockdropEnd, currentNetwork, setParameters } = useEth(); 25 | 26 | const [allLockEvents, setLockEvents] = useState([]); 27 | const lockStoreKey = `id:${firstLockContract.find(i => i.type === 'main')?.address}`; 28 | 29 | // Set network and contract address 30 | useEffect(() => { 31 | setParameters(true, LockSeason.First); 32 | // eslint-disable-next-line 33 | }, []); 34 | 35 | // store all lock events to local storage every time things changes 36 | useEffect(() => { 37 | if (allLockEvents.length > 0 && Array.isArray(allLockEvents)) { 38 | const serializedEvents = ethLockdrop.serializeLockEvents(allLockEvents); 39 | // ensure that the store value are not the same before storing 40 | if (localStorage.getItem(lockStoreKey) !== serializedEvents) { 41 | localStorage.setItem(lockStoreKey, serializedEvents); 42 | } 43 | } 44 | }, [allLockEvents, lockStoreKey]); 45 | 46 | // Display error messages 47 | useEffect(() => { 48 | if (typeof error !== 'undefined') { 49 | toast.error(error); 50 | } 51 | }, [error]); 52 | 53 | // Load lock events 54 | useEffect(() => { 55 | const fetchLockEvents = async () => { 56 | if (typeof contract !== 'undefined') { 57 | const _allLocks = await ethLockdrop.getAllLockEvents(contract); 58 | setLockEvents(_allLocks); 59 | } 60 | }; 61 | 62 | fetchLockEvents(); 63 | }, [contract]); 64 | 65 | return ( 66 | 67 | 68 | 69 | <> 70 | 71 | {!isMainnet(currentNetwork) ? ( 72 | 73 | 74 | Please access this page with a Mainnet wallet 75 | 76 | 77 | ) : ( 78 | <> 79 | 80 | 85 | {web3 && ( 86 | <> 87 | 88 | 89 | 90 | )} 91 | 92 | 93 | 94 | 95 | {web3 && } 96 | 97 | )} 98 | 99 |