diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..d790c1dc1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,35 @@ +language: node_js + +node_js: + - "8.11" + +before_install: + - pip install --user awscli + - export PATH=$PATH:$HOME/.local/bin + +install: npm i + +script: + - unset CI + - npm run build + +cache: npm + +deploy: + - provider: s3 + cache_control: "max-age=31536000" + access_key_id: $aws_access_key_id + secret_access_key: $aws_secret_access_key + bucket: $prod_s3_bucket + acl: public_read + local_dir: build + skip_cleanup: true + region: "eu-west-1" + on: + branch: master + +after_deploy: + - aws configure set aws_access_key_id $aws_access_key_id + - aws configure set aws_secret_access_key $aws_secret_access_key + - aws configure set preview.cloudfront true + - aws cloudfront create-invalidation --distribution-id $PROD_CLOUDFRONT_DISTRIBUTION --paths "/*" diff --git a/README.md b/README.md index 74c5bfe79..d17b86627 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,18 @@ -# 🌍 Planet A 🌍 +# deora.earth :earth_africa: -Planet A is a "serious social game" taking place during the Berlin Blockchain Week 2019 and CCCamp 2019 involving as many participants as possible. +deora.earth is a universal white-label voting and community platform. -The game will begin as a gimmick incentivizing participants through a financial incentive to interact with each other via a burner wallet. Half way through the game, players will find themselves in a tragedy of the commons, as they find out that their greedy interactions had dangerous emissions that now threaten to collapse the economy of Berlin Blockchain Week. They enter as teams into a competition to educate each other, and prevent an economic tipping points to be reached. The team that manages to achieve the best climate score wins the event. +We build an universal decision making/lobby tool for communities, NGOs, political movements and for everyone who wants to organize themselves. Together with other blockchain voting initiatives and foundations we want to give the voice back to the people. We are using hands-on blockchain solutions for current real offline voting & community organizing challenges. + +For our first version - used for projects of closed events/environments and decision making processes - we are utilizing the burner wallet together with the Leap Network, a Plasma implementation on Ethereum. ## Installation -The Planet A Wallet runs on LeapDAO's test network. Installation should be +The wallet runs on LeapDAO's test network. Installation should be simple and straight forward: ```bash -$ git clone https://github.com/social-dist0rtion-protocol/planet-a.git +$ git $ npm i $ npm run start ``` @@ -25,48 +27,14 @@ $ HTTPS=true npm run start ## Components ### ERC20 Tokens -- LeapToken (LEAP) - - decimals: 18 - - address: [`0xD2D0F8a6ADfF16C2098101087f9548465EC96C98`](https://testnet.leapdao.org/explorer/address/0xD2D0F8a6ADfF16C2098101087f9548465EC96C98) - - purpose: to pay the execution of the smart contract. -- CO2 (CO2) - - decimals: 18 - - unit: Gigaton of CO₂ - - addresss: [`0xF64fFBC4A69631D327590f4151B79816a193a8c6`](https://testnet.leapdao.org/explorer/address/0xF64fFBC4A69631D327590f4151B79816a193a8c6) - - purpose: reserve of CO₂ that is released to `Air`. CO₂ cannot be created out of thin air (eheheh), so the contract needs to be preloaded with a big amount of CO₂. -- Goellars (GOE) - - decimals: 18 - - address: [`0x1f89Fb2199220a350287B162B9D0A330A2D2eFAD`](https://testnet.leapdao.org/explorer/address/0x1f89Fb2199220a350287B162B9D0A330A2D2eFAD) - - purpose: reserve of Göllars to distribute to players on successful handshake. - -### ERC1948 Tokens -Passports are ERC1948 tokens. Each player needs at least one passport to play. -Passport data structure: -``` -+------------+------------+------------+-------------+ -| 20 bytes | 4 bytes | 4 bytes | 4 bytes | -| name str | picId | CO₂ locked | CO₂ emitted | -+------------+------------+------------+-------------+ -``` +### ERC1948 Tokens +` -Note that the CO₂ value in the passport is expressed in Megatons. ### Smart Contracts -The [planet-a-contracts](https://github.com/social-dist0rtion-protocol/planet-a-contracts) repository contains: -- `Earth`: smart contract that handles the handshake function and releases CO₂ to `Air`, and Göllars to the players. -- `Air`: smart contact that accumulates CO₂. It exposes the `plantTree` function to lock CO₂ back to `Earth`. - -#### Earth -`Earth` needs: -- LeapToken -- CO2 -- Goellars - -#### Air -`Air` needs: -- LeapToken + ## License diff --git a/package-lock.json b/package-lock.json index 7a8635803..7c10ee291 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6632,6 +6632,11 @@ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" }, + "dayjs": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.16.tgz", + "integrity": "sha512-XPmqzWz/EJiaRHjBqSJ2s6hE/BUoCIHKgdS2QPtTQtKcS9E4/Qn0WomoH1lXanWCzri+g7zPcuNV4aTZ8PMORQ==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -8103,6 +8108,51 @@ "ethereumjs-util": "6.1.0", "ethers": "3.0.27", "secp256k1": "3.7.0" + }, + "dependencies": { + "elliptic": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.3.3.tgz", + "integrity": "sha1-VILZZG1UvLif19mU/J4ulWiHbj8=", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "inherits": "^2.0.1" + } + }, + "ethers": { + "version": "3.0.27", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-3.0.27.tgz", + "integrity": "sha512-Ymop12NYKLTejQKv3l4a4vwwZNG+V0D2KmBGuSMa0eEguPJYCouNUoJ/8IiDTwqxKsthoSeCRrcXIz5HJDbHqA==", + "requires": { + "aes-js": "3.0.0", + "bn.js": "^4.4.0", + "elliptic": "6.3.3", + "hash.js": "^1.0.0", + "inherits": "2.0.1", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.3", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + } + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" + } } }, "eth-ens-namehash": { @@ -8466,22 +8516,27 @@ } }, "ethers": { - "version": "3.0.27", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-3.0.27.tgz", - "integrity": "sha512-Ymop12NYKLTejQKv3l4a4vwwZNG+V0D2KmBGuSMa0eEguPJYCouNUoJ/8IiDTwqxKsthoSeCRrcXIz5HJDbHqA==", + "version": "4.0.36", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.36.tgz", + "integrity": "sha512-rWdchEhUyXx01GiwexH6Sha97CQ9tJdQwe6FtYKxShC7VEZV41nuKt+lzCQ4OqvQwZK5PcAKaAZv2GDsCH33SA==", "requires": { + "@types/node": "^10.3.2", "aes-js": "3.0.0", "bn.js": "^4.4.0", "elliptic": "6.3.3", - "hash.js": "^1.0.0", - "inherits": "2.0.1", + "hash.js": "1.1.3", "js-sha3": "0.5.7", - "scrypt-js": "2.0.3", + "scrypt-js": "2.0.4", "setimmediate": "1.0.4", "uuid": "2.0.1", "xmlhttprequest": "1.8.0" }, "dependencies": { + "@types/node": { + "version": "10.14.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.16.tgz", + "integrity": "sha512-/opXIbfn0P+VLt+N8DE4l8Mn8rbhiJgabU96ZJ0p9mxOkIks5gh6RUnpHak7Yh0SFkyjO/ODbxsQQPV2bpMmyA==" + }, "elliptic": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.3.3.tgz", @@ -8493,10 +8548,19 @@ "inherits": "^2.0.1" } }, - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==" }, "setimmediate": { "version": "1.0.4", @@ -10612,6 +10676,11 @@ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, + "gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, "gzip-size": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", @@ -10899,6 +10968,19 @@ "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=", "dev": true }, + "history": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", + "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^2.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^0.4.0" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -14313,9 +14395,9 @@ } }, "jsbi": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-2.0.5.tgz", - "integrity": "sha512-TzO/62Hxeb26QMb4IGlI/5X+QLr9Uqp1FPkwp2+KOICW+Q+vSuFj61c8pkT6wAns4WcK56X7CmSHhJeDGWOqxQ==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.0.tgz", + "integrity": "sha512-R3oYDbvvScLbv9y3z1lT7a8ZBVcTh6ehQDX7rjkZdKJeHlSP2mLkB2D66nrk+D1poU/u8vhlMz1RuOUXvSDbGQ==" }, "jsbi-utils": { "version": "1.0.1", @@ -14323,6 +14405,13 @@ "integrity": "sha512-WfKCFGRRBJGjln99Cukvrvp6KJFqXNTOMwlMs4cImf9C7/65K6u8bOFK25rVVTY3piWU6eAb6gjj5568u6SrnA==", "requires": { "jsbi": "^2.0.5" + }, + "dependencies": { + "jsbi": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-2.0.5.tgz", + "integrity": "sha512-TzO/62Hxeb26QMb4IGlI/5X+QLr9Uqp1FPkwp2+KOICW+Q+vSuFj61c8pkT6wAns4WcK56X7CmSHhJeDGWOqxQ==" + } } }, "jsbn": { @@ -14653,9 +14742,9 @@ } }, "leap-core": { - "version": "0.32.4", - "resolved": "https://registry.npmjs.org/leap-core/-/leap-core-0.32.4.tgz", - "integrity": "sha512-LJmUqsx1kbXimiJVq/i3XxNwEP89bnaYJO8/J4slYJvpPatmfMrI031M7RuZrW1fe+TE60a1PK0FKhsNnaPbtA==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/leap-core/-/leap-core-0.35.2.tgz", + "integrity": "sha512-g8N+d+8BoHTJm64/81j1bAxu2kv5bXm6o1rOGMYUNnoBwQvD1kF4Bv03Mc3thZO1JkD3PBvbeIehzVfxACWzpA==", "requires": { "@types/web3": "^1.0.18", "ethereumjs-util": "6.0.0", @@ -14971,6 +15060,11 @@ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, + "lodash.shuffle": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz", + "integrity": "sha1-FFtQU8+HX29cKjP0i26ZSMbse0s=" + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -15399,6 +15493,16 @@ "dom-walk": "^0.1.0" } }, + "mini-create-react-context": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", + "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==", + "requires": { + "@babel/runtime": "^7.4.0", + "gud": "^1.0.0", + "tiny-warning": "^1.0.2" + } + }, "mini-css-extract-plugin": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz", @@ -20936,14 +21040,14 @@ } }, "react": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz", - "integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==", + "version": "16.8.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.0.tgz", + "integrity": "sha512-g+nikW2D48kqgWSPwNo0NH9tIGG3DsQFlrtrQ1kj6W77z5ahyIHG0w8kPpz4Sdj6gyLnz0lEd/xsjOoGge2MYQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.6" + "scheduler": "^0.13.0" } }, "react-app-polyfill": { @@ -21283,14 +21387,14 @@ } }, "react-dom": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz", - "integrity": "sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==", + "version": "16.8.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.0.tgz", + "integrity": "sha512-dBzoAGYZpW9Yggp+CzBPC7q1HmWSeRc93DWrwbskmG1eHJWznZB/p0l/Sm+69leIGUS91AXPB/qB3WcPnKx8Sw==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.6" + "scheduler": "^0.13.0" } }, "react-dom-confetti": { @@ -21398,6 +21502,37 @@ "webrtc-adapter": "^7.2.1" } }, + "react-router": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz", + "integrity": "sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.3.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "react-router-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.1.tgz", + "integrity": "sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.0.1", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, "react-scripts": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-1.1.4.tgz", @@ -23228,6 +23363,11 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=" }, + "resolve-pathname": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", + "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -24623,6 +24763,21 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, + "sourcemapped-stacktrace": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/sourcemapped-stacktrace/-/sourcemapped-stacktrace-1.1.11.tgz", + "integrity": "sha512-O0pcWjJqzQFVsisPlPXuNawJHHg9N9UgpJ/aDmvi9+vnS3x1C0NhwkVFzzZ1VN0Xo+bekyweoqYvBw5ZBKiNnQ==", + "requires": { + "source-map": "0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" + } + } + }, "space-separated-tokens": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz", @@ -25656,6 +25811,16 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, + "tiny-invariant": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", + "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "tlds": { "version": "1.203.1", "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.203.1.tgz", @@ -26521,6 +26686,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "value-equal": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", + "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -28874,6 +29044,11 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, + "ya-bbcode": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/ya-bbcode/-/ya-bbcode-1.0.9.tgz", + "integrity": "sha512-GAlJEeKwR8HxC83Pqevh3arSMbmoDSBxFVGBT8hrJg1utAuXtOt6D/wE86LbfeXeyVQiCJ+uuCVi5ablHIVV/Q==" + }, "yaeti": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", diff --git a/package.json b/package.json index cf1d6364a..2ffb9f3d3 100644 --- a/package.json +++ b/package.json @@ -7,25 +7,29 @@ "base64url": "^3.0.1", "bootstrap": "^4.1.3", "dapparatus": "leapdao/dapparatus#5591152", + "dayjs": "^1.8.16", "eth-crypto": "^1.3.2", "ethereum-mnemonic-privatekey-utils": "^1.0.5", "ethereumjs-util": "^6.0.0", + "ethers": "^4.0.36", "i18next": "^13.1.5", "i18next-browser-languagedetector": "^2.2.4", "iban": "0.0.12", "identity-obj-proxy": "3.0.0", + "jsbi": "^3.1.0", "jsbi-utils": "^1.0.0", - "leap-core": "^0.32.4", + "leap-core": "^0.35.2", + "lodash.shuffle": "^4.2.0", "qrcode-reader": "^1.0.4", "qrcode.react": "^0.8.0", "query-string": "^6.3.0", - "react": "^16.6.0", + "react": "^16.8.0", "react-app-polyfill": "^0.2.2", "react-blockies": "^1.4.0", "react-cookies": "^0.1.0", "react-copy-to-clipboard": "^5.0.1", "react-dev-utils": "^8.0.0", - "react-dom": "^16.6.0", + "react-dom": "^16.8.0", "react-dom-confetti": "^0.1.1", "react-emoji-render": "^0.5.0", "react-file-reader-input": "^2.0.0", @@ -33,18 +37,22 @@ "react-linkify": "^0.2.2", "react-native-webview-messaging": "^1.2.3", "react-qr-reader": "^2.1.1", + "react-router": "^5.0.1", + "react-router-dom": "^5.0.1", "react-scroll": "^1.7.10", "react-sound": "^1.2.0", "rimble-ui": "0.4.2", + "sourcemapped-stacktrace": "^1.1.11", "styled-components": "^4.1.3", - "web3": "1.0.0-beta.36" + "web3": "1.0.0-beta.36", + "ya-bbcode": "^1.0.9" }, "scripts": { "start": "node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js", "lint": "eslint src", - "deploy": "echo 'Re-building, deploying build to S3 and invalidating Cloudfront distribution. To get deployment credentials, ask someone from the team.' && npm run build && AWS_PROFILE=leap aws s3 sync build/ s3://planeta.leap.rocks/ --acl public-read && AWS_PROFILE=leap aws cloudfront create-invalidation --distribution-id EMOOCRPZK4EIL --paths '/*'" + "deploy": "echo 'Re-building, deploying build to S3 and invalidating Cloudfront distribution. To get deployment credentials, ask someone from the team.' && npm run build && AWS_PROFILE=deora-ci aws s3 sync build/ s3://volt.deora.earth/ --acl public-read && AWS_PROFILE=deora-ci aws cloudfront create-invalidation --distribution-id E11AR1A1T4EJQY --paths '/*'" }, "eslintConfig": { "extends": "react-app" diff --git a/public/favicon.ico b/public/favicon.ico index 84e9da822..35eb33eda 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/icons/icon-128x128.png b/public/icons/icon-128x128.png index 227fb5b0c..72790dc52 100644 Binary files a/public/icons/icon-128x128.png and b/public/icons/icon-128x128.png differ diff --git a/public/icons/icon-144x144.png b/public/icons/icon-144x144.png index 78afe2da8..bb8de8478 100644 Binary files a/public/icons/icon-144x144.png and b/public/icons/icon-144x144.png differ diff --git a/public/icons/icon-152x152.png b/public/icons/icon-152x152.png index 8c0135ef3..d19acfd1e 100644 Binary files a/public/icons/icon-152x152.png and b/public/icons/icon-152x152.png differ diff --git a/public/icons/icon-192x192.png b/public/icons/icon-192x192.png index f22bedec0..eed15563f 100644 Binary files a/public/icons/icon-192x192.png and b/public/icons/icon-192x192.png differ diff --git a/public/icons/icon-384x384.png b/public/icons/icon-384x384.png index 623d7f6f4..9de95cf02 100644 Binary files a/public/icons/icon-384x384.png and b/public/icons/icon-384x384.png differ diff --git a/public/icons/icon-512x512.png b/public/icons/icon-512x512.png index abf2fb26d..662361640 100644 Binary files a/public/icons/icon-512x512.png and b/public/icons/icon-512x512.png differ diff --git a/public/icons/icon-72x72.png b/public/icons/icon-72x72.png index c1f16b163..b512161a7 100644 Binary files a/public/icons/icon-72x72.png and b/public/icons/icon-72x72.png differ diff --git a/public/icons/icon-96x96.png b/public/icons/icon-96x96.png index 7e9285152..31b38780c 100644 Binary files a/public/icons/icon-96x96.png and b/public/icons/icon-96x96.png differ diff --git a/public/index.html b/public/index.html index a9fa7cc04..2fcbde13c 100644 --- a/public/index.html +++ b/public/index.html @@ -2,12 +2,13 @@ + - + @@ -20,52 +21,112 @@ integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous" /> - Planet A - + Volt - Digital Voting Platform + - - + + - + - - - - + + + - - + -
+
+
+ +
+
+
- + + + diff --git a/public/logo-deora.png b/public/logo-deora.png new file mode 100644 index 000000000..64716b934 Binary files /dev/null and b/public/logo-deora.png differ diff --git a/public/manifest.json b/public/manifest.json index da9e2c85d..3fbbe62c8 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "Planet A", - "name": "Planet A - A Tragedy of the CO2mmons", + "short_name": "Volt - Digital Voting Platform", + "name": "Volt - Digital Voting Platform", "icons": [ { "src": "favicon.ico", diff --git a/src/App.js b/src/App.js index 59fee616a..fa42ccd4d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,74 +1,70 @@ -import React, { Component } from 'react'; -import { Tx, Input, Output, Util } from 'leap-core'; -import { Dapparatus, Transactions, Gas } from "dapparatus"; -import { equal, bi } from 'jsbi-utils'; -import Web3 from 'web3'; -import { I18nextProvider } from 'react-i18next'; -import i18n from './i18n'; -import './App.scss'; -import Header from './components/Header'; -import NavCard from './components/NavCard'; -import SendByScan from './components/SendByScan'; -import SendToAddress from './components/SendToAddress'; -import Bity from './components/Bity'; -import BityHistory from "./components/BityHistory"; -import WithdrawFromPrivate from './components/WithdrawFromPrivate'; -import RequestFunds from './components/RequestFunds'; -import Receive from './components/Receive' -import Share from './components/Share' -import ShareLink from './components/ShareLink' -import Balance from "./components/Balance"; -import GoellarsBalance from './components/GoellarsBalance'; -import Receipt from "./components/Receipt"; -import MainCard from './components/MainCard'; -import History from './components/History'; -import Advanced from './components/Advanced'; -import RecentTransactions from './components/RecentTransactions'; -import Footer from './components/Footer'; -import Loader from './components/Loader'; -import burnerlogo from './assets/burnerwallet.png'; -import BurnWallet from './components/BurnWallet' -import Bottom from './components/Bottom'; -import Card from './components/StyledCard'; -import { Passports, getDefaultPassport } from './components/Passports'; -import incogDetect from './services/incogDetect.js' -import { ThemeProvider } from 'rimble-ui'; -import theme from "./theme"; +import React, { Component } from "react"; +import { Tx, Input, Output, Util } from "leap-core"; +import { Dapparatus } from "dapparatus"; +import { equal, bi } from "jsbi-utils"; +import Web3 from "web3"; +import { Redirect, Route, withRouter } from 'react-router-dom'; +import i18n from "./i18n"; +import "./App.scss"; + +import burnerlogo from "./assets/burnerwallet.png"; + +import incogDetect from "./services/incogDetect.js"; + import getConfig from "./config"; //https://github.com/lesnitsky/react-native-webview-messaging/blob/v1/examples/react-native/web/index.js -import RNMessageChannel from 'react-native-webview-messaging'; -import eth from './assets/ethereum.png'; +import RNMessageChannel from "react-native-webview-messaging"; + +import base64url from "base64url"; +import EthCrypto from "eth-crypto"; +import { + getStoredValue, + storeValues, + eraseStoredValue +} from "./services/localStorage"; + +// VOLT RELATED IMPORTS +import { voltConfig as VOLT_CONFIG } from "./volt/config"; +import { MainContainer } from "./volt/components/Common"; +import { Header } from "./volt/components/Header"; +import Menu from "./volt/components/Menu"; +import { fetchBalanceCard } from "./volt/utils"; + +import MainPage from "./MainPage"; +import ProposalPage from "./ProposalPage"; +import ResultPage from "./ResultPage"; +import Advanced from "./components/Advanced"; +import BurnWallet from './components/BurnWallet'; +import AlertBox from './volt/components/AlertBox'; +import {ethers} from "ethers"; +import Loader from './volt/components/Loader'; + +const BN = Web3.utils.BN; + +let LOADERIMAGE = burnerlogo; +let HARDCODEVIEW; // = "loader"// = "receipt" -import base64url from 'base64url'; -import EthCrypto from 'eth-crypto'; -import { getStoredValue, storeValues, eraseStoredValue } from "./services/localStorage"; -import { fetchAllPassports } from "./services/plasma"; -import PlanetAMoreButtons from "./planeta/MoreButtons"; -import PlanetAStartHandshake from "./planeta/StartHandshake"; -import PlanetAFinalizeHandshake from "./planeta/FinalizeHandshake"; +const CONFIG = getConfig(); -let LOADERIMAGE = burnerlogo -let HARDCODEVIEW// = "loader"// = "receipt" +let title = i18n.t("app_name"); + +// TODO: Make this part of config.js. Tim didn't do it yet because he doesn't +// understand what these constants do :/ +const BLOCKS_TO_PARSE_PER_BLOCKTIME = 32; +const MAX_BLOCK_TO_LOOK_BACK = 512; //don't look back more than 512 blocks + +let interval; -const CONFIG = getConfig(); -// TODO: Consolidate this with theme.js let mainStyle = { width:"100%", + backgroundColor: "linear-gradient(135deg, red, blue)", height:"100%", - backgroundImage:"linear-gradient(#292929, #191919)", - backgroundColor:"#191919", hotColor:"white", mainColorAlt:"white", mainColor:"white", } -let title = i18n.t('app_name') -let titleImage = ( - -) - -// TODO: Consolidate this with theme.js let buttonStyle = { primary: { backgroundImage:"linear-gradient("+mainStyle.mainColorAlt+","+mainStyle.mainColor+")", @@ -85,29 +81,24 @@ let buttonStyle = { } } -// TODO: Make this part of config.js. Tim didn't do it yet because he doesn't -// understand what these constants do :/ -const BLOCKS_TO_PARSE_PER_BLOCKTIME = 32 -const MAX_BLOCK_TO_LOOK_BACK = 512//don't look back more than 512 blocks - -let interval -let intervalLong - -export default class App extends Component { +class App extends Component { constructor(props) { - - - console.log("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["+title+"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]") - let view = 'main' - let cachedView = getStoredValue("view") - let cachedViewSetAge = Date.now() - getStoredValue("viewSetTime") - if(HARDCODEVIEW){ - view = HARDCODEVIEW - }else if(cachedViewSetAge < 300000 && cachedView&&cachedView!==0){ - view = cachedView + console.log( + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + + title + + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + ); + let view = "main"; + let cachedView = getStoredValue("view"); + let cachedViewSetAge = Date.now() - getStoredValue("viewSetTime"); + if (HARDCODEVIEW) { + view = HARDCODEVIEW; + } else if (cachedViewSetAge < 300000 && cachedView && cachedView !== 0) { + view = cachedView; } - console.log("CACHED VIEW",view) + console.log("CACHED VIEW", view); super(props); + this.state = { web3: false, account: false, @@ -116,379 +107,370 @@ export default class App extends Component { sendLink: "", sendKey: "", alert: null, - loadingTitle:'loading...', + loadingTitle: "loading...", title: title, - extraHeadroom:0, - balance: 0.00, + extraHeadroom: 0, + balance: 0.0, vendors: {}, - ethprice: 0.00, + ethprice: 0.0, hasUpdateOnce: false, - possibleNewPrivateKey: '', - // NOTE: USD in exchangeRates is undefined, such that any result using this - // number becomes NaN intentionally until it's defined. - exchangeRates: {} + possibleNewPrivateKey: "", + isMenuOpen: false, + filterQuery: "", + favorites: {}, + sorting: 'votes', + sortingOrder: 1, + userVotes: {} }; this.alertTimeout = null; - try{ - RNMessageChannel.on('json', update => { - try{ - let safeUpdate = {} - if(update.title) safeUpdate.title = update.title - if(update.extraHeadroom) safeUpdate.extraHeadroom = update.extraHeadroom - if(update.possibleNewPrivateKey) safeUpdate.possibleNewPrivateKey = update.possibleNewPrivateKey - this.setState(safeUpdate,()=>{ - if(this.state.possibleNewPrivateKey){ - this.dealWithPossibleNewPrivateKey() + try { + RNMessageChannel.on("json", update => { + try { + let safeUpdate = {}; + if (update.title) safeUpdate.title = update.title; + if (update.extraHeadroom) + safeUpdate.extraHeadroom = update.extraHeadroom; + if (update.possibleNewPrivateKey) + safeUpdate.possibleNewPrivateKey = update.possibleNewPrivateKey; + this.setState(safeUpdate, () => { + if (this.state.possibleNewPrivateKey) { + this.dealWithPossibleNewPrivateKey(); } - }) - }catch(e){console.log(e)} - }) - }catch(e){console.log(e)} - - this.poll = this.poll.bind(this) - this.longPoll = this.longPoll.bind(this) - this.queryExchangeWithNativeCurrency = this.queryExchangeWithNativeCurrency.bind(this) - this.setPossibleNewPrivateKey = this.setPossibleNewPrivateKey.bind(this) - this.currencyDisplay = this.currencyDisplay.bind(this); - this.convertCurrency = this.convertCurrency.bind(this); - } - - // NOTE: This function is for _displaying_ a currency value to a user. It - // adds a currency unit to the beginning or end of the number! - currencyDisplay(amount, toParts=false, convert=true) { - const { account } = this.state; - const locale = getStoredValue('i18nextLng'); - const symbol = getStoredValue('currency', account) || CONFIG.CURRENCY.DEFAULT_CURRENCY; - - if (convert) { - amount = this.convertCurrency(amount, `${symbol}/USD`); + }); + } catch (e) { + console.log(e); + } + }); + } catch (e) { + console.log(e); } - const formatter = new Intl.NumberFormat(locale, { - style: 'currency', - currency: symbol, - maximumFractionDigits: 2 - }); - return toParts ? formatter.formatToParts(amount) : formatter.format(amount); - } - - /* - * Pair is supposed to be a currency pair according to ISO 4217. Format must - * be BASE/COUNTER. - * - * convertCurrency then ALWAYS converts from COUNTER => BASE. Amount must - * be quoted in the base currency. An example: - * - * convertCurrency(1, "EUR/USD"): - * returns (0.81 / 1) * $1, so essentially converts $1 to 0.81€. - * - * or - * - * convertCurrency(1, "USD/EUR"): - * returns (1 / 0.81) * €1, so essentially converts €1 to 1.23$. - * - * NOTE: This function assumes 1 DAI = 1 USD! - */ - convertCurrency(amount, pair) { - const { exchangeRates } = this.state; - const [base, counter] = pair.split("/"); - - const baseRate = exchangeRates[base]; - const counterRate = exchangeRates[counter]; - - return baseRate / counterRate * amount; + this.poll = this.poll.bind(this); + this.setPossibleNewPrivateKey = this.setPossibleNewPrivateKey.bind(this); + this.openMenu = this.openMenu.bind(this); + this.closeMenu = this.closeMenu.bind(this); + this.sort = this.sort.bind(this); + this.filterList = this.filterList.bind(this); + this.resetFilter = this.resetFilter.bind(this); + this.toggleFavorites = this.toggleFavorites.bind(this); + this.updateVotes = this.updateVotes.bind(this); } - parseAndCleanPath(path){ - let parts = path.split(";") + parseAndCleanPath(path) { + let parts = path.split(";"); //console.log("PARTS",parts) - let state = {} - if(parts.length>0){ - state.toAddress = parts[0].replace("/","") + let state = {}; + if (parts.length > 0) { + state.toAddress = parts[0].replace("/", ""); } - if(parts.length>=2){ - state.amount = parts[1] + if (parts.length >= 2) { + state.amount = parts[1]; } - if(parts.length>2){ - state.message = decodeURI(parts[2]).replaceAll("%23","#").replaceAll("%3B",";").replaceAll("%3A",":").replaceAll("%2F","/") + if (parts.length > 2) { + state.message = decodeURI(parts[2]) + .replaceAll("%23", "#") + .replaceAll("%3B", ";") + .replaceAll("%3A", ":") + .replaceAll("%2F", "/"); } - if(parts.length>3){ - state.extraMessage = decodeURI(parts[3]).replaceAll("%23","#").replaceAll("%3B",";").replaceAll("%3A",":").replaceAll("%2F","/") + if (parts.length > 3) { + state.extraMessage = decodeURI(parts[3]) + .replaceAll("%23", "#") + .replaceAll("%3B", ";") + .replaceAll("%3A", ":") + .replaceAll("%2F", "/"); } //console.log("STATE",state) return state; } - openScanner(returnState){ - this.setState({returnState:returnState, scannerOpen: true}) + openScanner(returnState) { + this.setState({ returnState: returnState, scannerOpen: true }); } - returnToState(scannerState, nextView){ - let updateState = Object.assign({scannerState}, this.state.returnState); - updateState.scannerOpen = false - updateState.returnState = false - console.log("UPDATE FROM RETURN STATE",updateState) + returnToState(scannerState, nextView) { + let updateState = Object.assign({ scannerState }, this.state.returnState); + updateState.scannerOpen = false; + updateState.returnState = false; + console.log("UPDATE FROM RETURN STATE", updateState); if (nextView) { updateState.view = nextView; } - this.setState(updateState) + this.setState(updateState); } updateDimensions() { //force it to rerender when the window is resized to make sure qr fits etc this.forceUpdate(); } - saveKey(update){ - this.setState(update) + saveKey(update) { + this.setState(update); } - detectContext(){ - console.log("DETECTING CONTEXT....") + detectContext() { + console.log("DETECTING CONTEXT...."); //snagged from https://stackoverflow.com/questions/52759238/private-incognito-mode-detection-for-ios-12-safari - incogDetect((result)=>{ - if(result){ - console.log("INCOG") - document.getElementById("main").classList.add("main--incognito") - var contextElement = document.getElementById("context") - contextElement.innerHTML = 'INCOGNITO'; - }else if (typeof web3 !== 'undefined') { - console.log("NOT INCOG",this.state.metaAccount) + incogDetect(result => { + if (result) { + console.log("INCOG"); + // document.getElementById("main").classList.add("main--incognito") + // var contextElement = document.getElementById("context") + // contextElement.innerHTML = 'INCOGNITO'; + } else if (typeof web3 !== "undefined") { + console.log("NOT INCOG", this.state.metaAccount); if (window.web3.currentProvider.isMetaMask === true) { - document.getElementById("main").classList.add("main--metamask") + // document.getElementById("main").classList.add("main--metamask") + // contextElement = document.getElementById("context") + // contextElement.innerHTML = 'METAMASK'; + } else if (this.state.account && !this.state.metaAccount) { + console.log("~~~*** WEB3", this.state.metaAccount, result); + /* document.getElementById("main").classList.add("main--web3") contextElement = document.getElementById("context") - contextElement.innerHTML = 'METAMASK'; - } else if(this.state.account && !this.state.metaAccount) { - console.log("~~~*** WEB3",this.state.metaAccount,result) - document.getElementById("main").classList.add("main--web3") - contextElement = document.getElementById("context") - contextElement.innerHTML = 'WEB3'; + contextElement.innerHTML = 'WEB3';*/ } } - }) + }); } - componentDidMount(){ - document.body.style.backgroundColor = mainStyle.backgroundColor + componentDidMount() { + const { history } = this.props; + console.log({history}); + this.detectContext(); - this.detectContext() + this.loadProposals(); - console.log("document.getElementsByClassName('className').style",document.getElementsByClassName('.btn').style) + console.log( + "document.getElementsByClassName('className').style", + document.getElementsByClassName(".btn").style + ); window.addEventListener("resize", this.updateDimensions.bind(this)); - if(window.location.pathname){ - console.log("PATH",window.location.pathname,window.location.pathname.length,window.location.hash) - if(window.location.pathname.indexOf("/pk")>=0){ + if (window.location.pathname) { + console.log( + "PATH", + window.location.pathname, + window.location.pathname.length, + window.location.hash + ); + if (window.location.pathname.indexOf("/pk") >= 0) { let tempweb3 = new Web3(); - let base64encodedPK = window.location.hash.replace("#","") - let rawPK = tempweb3.utils.bytesToHex(base64url.toBuffer(base64encodedPK)) - this.setState({possibleNewPrivateKey:rawPK}) - window.history.pushState({},"", "/"); - }else if(window.location.pathname.length===43){ - this.changeView('send_to_address') - console.log("CHANGE VIEW") - }else if(window.location.pathname.length===134){ - let parts = window.location.pathname.split(";") - let claimId = parts[0].replace("/","") - let claimKey = parts[1] - console.log("DO CLAIM",claimId,claimKey) - this.setState({claimId,claimKey}) - window.history.pushState({},"", "/"); - }else if( - (window.location.pathname.length>=65&&window.location.pathname.length<=67&&window.location.pathname.indexOf(";")<0) || - (window.location.hash.length>=65 && window.location.hash.length <=67 && window.location.hash.indexOf(";")<0) - ){ - console.log("incoming private key") - let privateKey = window.location.pathname.replace("/","") - if(window.location.hash){ - privateKey = window.location.hash + let base64encodedPK = window.location.hash.replace("#", ""); + let rawPK = tempweb3.utils.bytesToHex( + base64url.toBuffer(base64encodedPK) + ); + this.setState({ possibleNewPrivateKey: rawPK }); + history.push("/"); + } else if (window.location.pathname.length === 43) { + this.changeView("send_to_address"); + console.log("CHANGE VIEW"); + } else if (window.location.pathname.length === 134) { + let parts = window.location.pathname.split(";"); + let claimId = parts[0].replace("/", ""); + let claimKey = parts[1]; + console.log("DO CLAIM", claimId, claimKey); + this.setState({ claimId, claimKey }); + history.push("/"); + } else if ( + (window.location.pathname.length >= 65 && + window.location.pathname.length <= 67 && + window.location.pathname.indexOf(";") < 0) || + (window.location.hash.length >= 65 && + window.location.hash.length <= 67 && + window.location.hash.indexOf(";") < 0) + ) { + console.log("incoming private key"); + let privateKey = window.location.pathname.replace("/", ""); + if (window.location.hash) { + privateKey = window.location.hash; } - privateKey = privateKey.replace("#","") - if(privateKey.indexOf("0x")!==0){ - privateKey="0x"+privateKey + privateKey = privateKey.replace("#", ""); + if (privateKey.indexOf("0x") !== 0) { + privateKey = "0x" + privateKey; } //console.log("!!! possibleNewPrivateKey",privateKey) - this.setState({possibleNewPrivateKey:privateKey}) - window.history.pushState({},"", "/"); - }else if(window.location.pathname.indexOf("/vendors;")===0){ - this.changeView('vendors') - }else{ - let parts = window.location.pathname.split(";") - console.log("PARTS",parts) - if(parts.length>=2){ - let sendToAddress = parts[0].replace("/","") - let sendToAmount = parts[1] - let extraData = "" - if(parts.length>=3){ - extraData = parts[2] + this.setState({ possibleNewPrivateKey: privateKey }); + history.push("/"); + } else if (window.location.pathname.indexOf("/vendors;") === 0) { + this.changeView("vendors"); + } else { + let parts = window.location.pathname.split(";"); + console.log("PARTS", parts); + if (parts.length >= 2) { + let sendToAddress = parts[0].replace("/", ""); + let sendToAmount = parts[1]; + let extraData = ""; + if (parts.length >= 3) { + extraData = parts[2]; } - if((parseFloat(sendToAmount)>0 || extraData) && sendToAddress.length===42){ - this.changeView('send_to_address') + if ( + (parseFloat(sendToAmount) > 0 || extraData) && + sendToAddress.length === 42 + ) { + this.changeView("send_to_address"); } } } } - if (this.state.account){ - let nativeCurrency = getStoredValue('currency', this.state.account) - if (nativeCurrency === null) { - storeValues({currency: CONFIG.CURRENCY.DEFAULT_CURRENCY}, this.state.account) - } - } - interval = setInterval(this.poll,1500) - intervalLong = setInterval(this.longPoll,45000) - // NOTE: We query once before starting the interval to define the value - // for the UI, as it needs to be readily available for the user. - this.queryExchangeWithNativeCurrency(CONFIG.CURRENCY.EXCHANGE_RATE_QUERY); - setTimeout(this.longPoll,150) - - this.connectToRPC() - } - connectToRPC(){ - const mainnetweb3 = new Web3(CONFIG.ROOTCHAIN.RPC); - let daiContract, bridgeContract; - try{ - daiContract = new mainnetweb3.eth.Contract(require("./contracts/StableCoin.abi.js"),CONFIG.ROOTCHAIN.DAI_ADDRESS) - bridgeContract = new mainnetweb3.eth.Contract(require("./contracts/Bridge.abi.js"), CONFIG.SIDECHAIN.BRIDGE_ADDRESS) - }catch(e){ - console.log("ERROR LOADING DAI Stablecoin Contract",e) - } - this.setState({mainnetweb3,daiContract,bridgeContract}) + interval = setInterval(this.poll, 3000); + setInterval(this.loadProposals.bind(this), 300000); // every 5mins } componentWillUnmount() { - clearInterval(interval) - clearInterval(intervalLong) + clearInterval(interval); window.removeEventListener("resize", this.updateDimensions.bind(this)); } - async poll() { - if(this.state.account){ - let ethBalance = 0.00 - let daiBalance = 0.00 - let xdaiBalance = 0.00 + const { + account, + xdaiweb3, + voiceCreditsContract, + voiceTokensContract + } = this.state; + if (account) { + let creditsBalance = new BN(0); + let tokensBalance = new BN(0); - if(this.state.mainnetweb3){ + if (xdaiweb3) { - try{ - ethBalance = await this.state.mainnetweb3.eth.getBalance(this.state.account) - ethBalance = this.state.mainnetweb3.utils.fromWei(""+ethBalance,'ether') + creditsBalance = new BN(await voiceCreditsContract.methods + .balanceOf(account) + .call()); - if(this.state.daiContract){ - daiBalance = await this.state.daiContract.methods.balanceOf(this.state.account).call() - daiBalance = this.state.mainnetweb3.utils.fromWei(""+daiBalance,'ether') - } - }catch(e){ - console.log(e) - this.connectToRPC() - } + tokensBalance = new BN(await voiceTokensContract.methods + .balanceOf(account) + .call()); } - if(this.state.xdaiweb3 && this.state.xdaiContract){ - xdaiBalance = await this.state.xdaiContract.methods.balanceOf(this.state.account).call(); - xdaiBalance = this.state.xdaiweb3.utils.fromWei(""+xdaiBalance,'ether') - } - - const plasma = this.state.xdaiweb3; - const passports = await fetchAllPassports(plasma, this.state.account); - this.setState({passports, ethBalance,daiBalance,xdaiBalance,balance:xdaiBalance,hasUpdateOnce:true}) - } + // TODO: Fetch Balance Card here + // const plasma = this.state.xdaiweb3; + // const passports = await fetchAllPassports(plasma, this.state.account); - } - longPoll() { - fetch("https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd") - .then(r => r.json()) - .then((response)=>{ - const ethprice = response.ethereum.usd; - this.setState({ethprice}) - }) - } - - async queryExchangeWithNativeCurrency() { - const currencies = CONFIG.CURRENCY.CURRENCY_LIST; - currencies.slice(currencies.indexOf("USD"), 1); - - // https://min-api.cryptocompare.com/documentation?key=Price&cat=multipleSymbolsPriceEndpoint - const resp = await fetch(`https://min-api.cryptocompare.com/data/price?fsym=DAI&tsyms=${currencies.join(",")}`) - let pairs = await resp.json(); - // 1 DAI == 1 USD. In numeris veritas! - pairs.USD = 1; + // Update balance card data + const balanceCard = await fetchBalanceCard( + this.state.xdaiweb3, + this.state.account + ); + this.setState({ balanceCard }); - this.setState({ exchangeRates: pairs }); + this.setState({ + creditsBalance, + tokensBalance, + hasUpdateOnce: true + }); + } } - setPossibleNewPrivateKey(value){ + setPossibleNewPrivateKey(value) { this.setState({ possibleNewPrivateKey: value }, async () => { - await this.dealWithPossibleNewPrivateKey() - }) + await this.dealWithPossibleNewPrivateKey(); + }); } - async dealWithPossibleNewPrivateKey(){ + async dealWithPossibleNewPrivateKey() { //this happens as page load and you need to wait until - if(this.state && this.state.hasUpdateOnce){ - if(this.state.metaAccount && this.state.metaAccount.privateKey.replace("0x","") === this.state.possibleNewPrivateKey.replace("0x","")){ - this.setState({possibleNewPrivateKey:false}) + if (this.state && this.state.hasUpdateOnce) { + if ( + this.state.metaAccount && + this.state.metaAccount.privateKey.replace("0x", "") === + this.state.possibleNewPrivateKey.replace("0x", "") + ) { + this.setState({ possibleNewPrivateKey: false }); this.changeAlert({ - type: 'warning', - message: 'Imported identical private key.', + type: "warning", + message: "Imported identical private key." }); - }else{ - - console.log("Checking on pk import...") - console.log("this.state.balance",this.state.balance) - console.log("this.state.metaAccount",this.state.metaAccount) - console.log("this.state.xdaiBalance",this.state.xdaiBalance) - console.log("this.state.daiBalance",this.state.daiBalance) - console.log("this.state.isVendor",this.state.isVendor) - - - console.log(!this.state.metaAccount || this.state.balance>=0.05 || this.state.xdaiBalance>=0.05 || this.state.ethBalance>=0.0005 || this.state.daiBalance>=0.05 || (this.state.isVendor&&this.state.isVendor.isAllowed)) - if(!this.state.metaAccount || this.state.balance>=0.05 || this.state.xdaiBalance>=0.05 || this.state.ethBalance>=0.0005 || this.state.daiBalance>=0.05 || (this.state.isVendor&&this.state.isVendor.isAllowed)){ - this.setState({possibleNewPrivateKey:false,withdrawFromPrivateKey:this.state.possibleNewPrivateKey},()=>{ - this.changeView('withdraw_from_private') - }) - }else{ - this.setState({possibleNewPrivateKey:false,newPrivateKey:this.state.possibleNewPrivateKey}) - storeValues({ - loadedBlocksTop:"", - recentTxs:"", - transactionsByAddress:"" - }, this.state.account); - this.setState({recentTxs:[],transactionsByAddress:{},fullRecentTxs:[],fullTransactionsByAddress:{}}) + } else { + console.log("Checking on pk import..."); + console.log("this.state.metaAccount", this.state.metaAccount); + console.log("this.state.isVendor", this.state.isVendor); + + console.log( + !this.state.metaAccount || + this.state.balance >= 0.05 || + this.state.xdaiBalance >= 0.05 || + this.state.ethBalance >= 0.0005 || + this.state.daiBalance >= 0.05 || + (this.state.isVendor && this.state.isVendor.isAllowed) + ); + if ( + !this.state.metaAccount || + this.state.balance >= 0.05 || + this.state.xdaiBalance >= 0.05 || + this.state.ethBalance >= 0.0005 || + this.state.daiBalance >= 0.05 || + (this.state.isVendor && this.state.isVendor.isAllowed) + ) { + this.setState( + { + possibleNewPrivateKey: false, + withdrawFromPrivateKey: this.state.possibleNewPrivateKey + }, + () => { + this.changeView("withdraw_from_private"); + } + ); + } else { + this.setState({ + possibleNewPrivateKey: false, + newPrivateKey: this.state.possibleNewPrivateKey + }); + storeValues( + { + loadedBlocksTop: "", + recentTxs: "", + transactionsByAddress: "" + }, + this.state.account + ); + this.setState({ + recentTxs: [], + transactionsByAddress: {}, + fullRecentTxs: [], + fullTransactionsByAddress: {} + }); } } - }else{ - setTimeout(this.dealWithPossibleNewPrivateKey.bind(this),500) + } else { + setTimeout(this.dealWithPossibleNewPrivateKey.bind(this), 500); } - - } + componentDidUpdate(prevProps, prevState) { let { network, web3, account } = this.state; if (web3 && network !== prevState.network /*&& !this.checkNetwork()*/) { - console.log("WEB3 DETECTED BUT NOT RIGHT NETWORK",web3, network, prevState.network); + console.log( + "WEB3 DETECTED BUT NOT RIGHT NETWORK", + web3, + network, + prevState.network + ); //this.changeAlert({ // type: 'danger', // message: 'Wrong Network. Please use Custom RPC endpoint: https://dai.poa.network or turn off MetaMask.' //}, false) } - if (prevState.account !== account){ - const currency = getStoredValue('currency'); - if (currency){ - storeValues({currency}, account); - eraseStoredValue('currency'); + if (prevState.account !== account) { + const currency = getStoredValue("currency"); + if (currency) { + storeValues({ currency }, account); + eraseStoredValue("currency"); } } - }; + } checkNetwork() { let { network } = this.state; return network === "xDai" || network === "Unknown"; } - setReceipt = (obj)=>{ - this.setState({receipt:obj}) - } - changeView = (view,cb) => { - if(view==="exchange"||view==="main"/*||view.indexOf("account_")===0*/){ + setReceipt = obj => { + this.setState({ receipt: obj }); + }; + changeView = (view, cb) => { + if ( + view === "exchange" || + view === "main" /*||view.indexOf("account_")===0*/ + ) { storeValues({ viewSetTime: Date.now(), view //some pages should be sticky because of metamask reloads - }) + }); } /*if (view.startsWith('send_with_link')||view.startsWith('send_to_address')) { console.log("This is a send...") @@ -503,1003 +485,730 @@ export default class App extends Component { } } */ - this.changeAlert(null); - console.log("Setting state",view) - this.setState({ view, scannerState:false },cb); + this.changeAlert(null); + console.log("Setting state", view); + this.setState({ view, scannerState: false }, cb); }; - changeAlert = (alert, hide=true) => { + changeAlert = (alert, hide = true) => { + console.log('Alert!!'); clearTimeout(this.alertTimeout); this.setState({ alert }); if (alert && hide) { this.alertTimeout = setTimeout(() => { this.setState({ alert: null }); - }, 2000); + }, 200000); } }; - goBack(view="main"){ - console.log("GO BACK") - this.changeView(view) - this.setState({scannerOpen: false }) - setTimeout(()=>{window.scrollTo(0,0)},60) + + updateVotes = (id, votes) => { + console.log(`Update number of votes for ${id} with ${votes}`); + const { userVotes } = this.state; + userVotes[id] = votes; + this.setState({ userVotes }) + }; + + goBack(view = "main") { + console.log("GO BACK"); + this.changeView(view); + this.setState({ scannerOpen: false }); + setTimeout(() => { + window.scrollTo(0, 0); + }, 60); } - async parseBlocks(parseBlock,recentTxs,transactionsByAddress){ + async parseBlocks(parseBlock, recentTxs, transactionsByAddress) { + // We don't neede this right now, so we simply short fuse it + return false; + let web3; if (this.state.xdaiweb3) { - web3 = this.state.xdaiweb3 + web3 = this.state.xdaiweb3; } else { - web3 = this.state.web3 + web3 = this.state.web3; } - let block = await web3.eth.getBlock(parseBlock) - let updatedTxs = false - if(block){ - let transactions = block.transactions + let block = await web3.eth.getBlock(parseBlock); + let updatedTxs = false; + if (block) { + let transactions = block.transactions; //console.log("transactions",transactions) - for(let t in transactions){ + for (let t in transactions) { //console.log("TX",transactions[t]) - let tx = await web3.eth.getTransaction(transactions[t]) + let tx = await web3.eth.getTransaction(transactions[t]); // NOTE: NST information is encoded in a transaction's values. Hence if // we don't filter out NST transactions, they'll show up as huge // transfers in the UI. - if(tx && tx.to && tx.from && !Util.isNST(tx.color)){ + if (tx && tx.to && tx.from && !Util.isNST(tx.color)) { //console.log("EEETRTTTTERTETETET",tx) let smallerTx = { - hash:tx.hash, - to:tx.to.toLowerCase(), - from:tx.from.toLowerCase(), - value:web3.utils.fromWei(""+tx.value,"ether"), - blockNumber:tx.blockNumber - } - - - if(smallerTx.from===this.state.account || smallerTx.to===this.state.account){ - if(tx.input&&tx.input!=="0x"){ - - let decrypted = await this.decryptInput(tx.input) - - if(decrypted){ - smallerTx.data = decrypted - smallerTx.encrypted = true + hash: tx.hash, + to: tx.to.toLowerCase(), + from: tx.from.toLowerCase(), + value: web3.utils.fromWei("" + tx.value, "ether"), + blockNumber: tx.blockNumber + }; + + if ( + smallerTx.from === this.state.account || + smallerTx.to === this.state.account + ) { + if (tx.input && tx.input !== "0x") { + let decrypted = await this.decryptInput(tx.input); + + if (decrypted) { + smallerTx.data = decrypted; + smallerTx.encrypted = true; } - try{ - smallerTx.data = web3.utils.hexToUtf8(tx.input) - }catch(e){} + try { + smallerTx.data = web3.utils.hexToUtf8(tx.input); + } catch (e) {} //console.log("smallerTx at this point",smallerTx) - if(!smallerTx.data){ - smallerTx.data = " *** unable to decrypt data *** " + if (!smallerTx.data) { + smallerTx.data = " *** unable to decrypt data *** "; } } - updatedTxs = this.addTxIfAccountMatches(recentTxs,transactionsByAddress,smallerTx) || updatedTxs + updatedTxs = + this.addTxIfAccountMatches( + recentTxs, + transactionsByAddress, + smallerTx + ) || updatedTxs; } - } } } - return updatedTxs + return updatedTxs; } - async decryptInput(input){ - let key = input.substring(0,32) + async decryptInput(input) { + let key = input.substring(0, 32); //console.log("looking in memory for key",key) - let cachedEncrypted = this.state[key] - if(!cachedEncrypted){ + let cachedEncrypted = this.state[key]; + if (!cachedEncrypted) { //console.log("nothing found in memory, checking local storage") - cachedEncrypted = getStoredValue(key) + cachedEncrypted = getStoredValue(key); } - if(cachedEncrypted){ - return cachedEncrypted - }else{ - if(this.state.metaAccount){ - try{ - let parsedData = EthCrypto.cipher.parse(input.substring(2)) + if (cachedEncrypted) { + return cachedEncrypted; + } else { + if (this.state.metaAccount) { + try { + let parsedData = EthCrypto.cipher.parse(input.substring(2)); const endMessage = await EthCrypto.decryptWithPrivateKey( this.state.metaAccount.privateKey, // privateKey parsedData // encrypted-data ); - return endMessage - }catch(e){} - }else{ + return endMessage; + } catch (e) {} + } else { //no meta account? maybe try to setup signing keys? //maybe have a contract that tries do decrypt? \ } } - return false + return false; } - initRecentTxs(){ - let recentTxs = [] - if(this.state.recentTx) recentTxs = recentTxs.concat(this.state.recentTxs) - let transactionsByAddress = Object.assign({},this.state.transactionsByAddress) - if(!recentTxs||recentTxs.length<=0){ - recentTxs = getStoredValue("recentTxs", this.state.account) - try{ - recentTxs=JSON.parse(recentTxs) - }catch(e){ - recentTxs=[] + initRecentTxs() { + let recentTxs = []; + if (this.state.recentTx) recentTxs = recentTxs.concat(this.state.recentTxs); + let transactionsByAddress = Object.assign( + {}, + this.state.transactionsByAddress + ); + if (!recentTxs || recentTxs.length <= 0) { + recentTxs = getStoredValue("recentTxs", this.state.account); + try { + recentTxs = JSON.parse(recentTxs); + } catch (e) { + recentTxs = []; } } - if(!recentTxs){ - recentTxs=[] + if (!recentTxs) { + recentTxs = []; } - if(Object.keys(transactionsByAddress).length === 0){ - transactionsByAddress = getStoredValue("transactionsByAddress", this.state.account) - try{ - transactionsByAddress=JSON.parse(transactionsByAddress) - }catch(e){ - transactionsByAddress={} + if (Object.keys(transactionsByAddress).length === 0) { + transactionsByAddress = getStoredValue( + "transactionsByAddress", + this.state.account + ); + try { + transactionsByAddress = JSON.parse(transactionsByAddress); + } catch (e) { + transactionsByAddress = {}; } } - if(!transactionsByAddress){ - transactionsByAddress={} + if (!transactionsByAddress) { + transactionsByAddress = {}; } - return [recentTxs,transactionsByAddress] + return [recentTxs, transactionsByAddress]; } - addTxIfAccountMatches(recentTxs,transactionsByAddress,smallerTx){ - let updatedTxs = false + addTxIfAccountMatches(recentTxs, transactionsByAddress, smallerTx) { + let updatedTxs = false; - let otherAccount = smallerTx.to - if(smallerTx.to===this.state.account){ - otherAccount = smallerTx.from + let otherAccount = smallerTx.to; + if (smallerTx.to === this.state.account) { + otherAccount = smallerTx.from; } - if(!transactionsByAddress[otherAccount]){ - transactionsByAddress[otherAccount] = [] + if (!transactionsByAddress[otherAccount]) { + transactionsByAddress[otherAccount] = []; } - let found = false - if(parseFloat(smallerTx.value)>0.005){ - for(let r in recentTxs){ - if(recentTxs[r].hash===smallerTx.hash/* && (!smallerTx.data || recentTxs[r].data === smallerTx.data)*/){ - found = true - if(!smallerTx.data || recentTxs[r].data === smallerTx.data){ + let found = false; + if (parseFloat(smallerTx.value) > 0.005) { + for (let r in recentTxs) { + if ( + recentTxs[r].hash === + smallerTx.hash /* && (!smallerTx.data || recentTxs[r].data === smallerTx.data)*/ + ) { + found = true; + if (!smallerTx.data || recentTxs[r].data === smallerTx.data) { // do nothing, it exists - }else{ - recentTxs[r].data = smallerTx.data - updatedTxs=true + } else { + recentTxs[r].data = smallerTx.data; + updatedTxs = true; } } } - if(!found){ - updatedTxs=true - recentTxs.push(smallerTx) + if (!found) { + updatedTxs = true; + recentTxs.push(smallerTx); //console.log("recentTxs after push",recentTxs) } } - found = false - for(let t in transactionsByAddress[otherAccount]){ - if(transactionsByAddress[otherAccount][t].hash===smallerTx.hash/* && (!smallerTx.data || recentTxs[r].data === smallerTx.data)*/){ - found = true - if(!smallerTx.data || transactionsByAddress[otherAccount][t].data === smallerTx.data){ + found = false; + for (let t in transactionsByAddress[otherAccount]) { + if ( + transactionsByAddress[otherAccount][t].hash === + smallerTx.hash /* && (!smallerTx.data || recentTxs[r].data === smallerTx.data)*/ + ) { + found = true; + if ( + !smallerTx.data || + transactionsByAddress[otherAccount][t].data === smallerTx.data + ) { // do nothing, it exists - }else{ - transactionsByAddress[otherAccount][t].data = smallerTx.data - if(smallerTx.encrypted) transactionsByAddress[otherAccount][t].encrypted = true - updatedTxs=true + } else { + transactionsByAddress[otherAccount][t].data = smallerTx.data; + if (smallerTx.encrypted) + transactionsByAddress[otherAccount][t].encrypted = true; + updatedTxs = true; } } } - if(!found){ - updatedTxs=true - transactionsByAddress[otherAccount].push(smallerTx) + if (!found) { + updatedTxs = true; + transactionsByAddress[otherAccount].push(smallerTx); } - return updatedTxs + return updatedTxs; } - sortAndSaveTransactions(recentTxs,transactionsByAddress){ - recentTxs.sort(sortByBlockNumber) + sortAndSaveTransactions(recentTxs, transactionsByAddress) { + recentTxs.sort(sortByBlockNumber); - for(let t in transactionsByAddress){ - transactionsByAddress[t].sort(sortByBlockNumberDESC) + for (let t in transactionsByAddress) { + transactionsByAddress[t].sort(sortByBlockNumberDESC); } - recentTxs = recentTxs.slice(0,12) - storeValues({ - recentTxs: JSON.stringify(recentTxs), - transactionsByAddress: JSON.stringify(transactionsByAddress), - }, this.state.account); - this.setState({recentTxs:recentTxs,transactionsByAddress:transactionsByAddress}) + recentTxs = recentTxs.slice(0, 12); + storeValues( + { + recentTxs: JSON.stringify(recentTxs), + transactionsByAddress: JSON.stringify(transactionsByAddress) + }, + this.state.account + ); + this.setState({ + recentTxs: recentTxs, + transactionsByAddress: transactionsByAddress + }); } - async addAllTransactionsFromList(recentTxs,transactionsByAddress,theList){ - let updatedTxs = false - - for(let e in theList){ - let thisEvent = theList[e] - let cleanEvent = Object.assign({},thisEvent) - cleanEvent.to = cleanEvent.to.toLowerCase() - cleanEvent.from = cleanEvent.from.toLowerCase() - cleanEvent.value = this.state.web3.utils.fromWei(""+cleanEvent.value,'ether') - if(cleanEvent.data) { - let decrypted = await this.decryptInput(cleanEvent.data) - if(decrypted){ - cleanEvent.data = decrypted - cleanEvent.encrypted = true - }else{ - try{ - cleanEvent.data = this.state.web3.utils.hexToUtf8(cleanEvent.data) - }catch(e){} + async addAllTransactionsFromList(recentTxs, transactionsByAddress, theList) { + let updatedTxs = false; + + for (let e in theList) { + let thisEvent = theList[e]; + let cleanEvent = Object.assign({}, thisEvent); + cleanEvent.to = cleanEvent.to.toLowerCase(); + cleanEvent.from = cleanEvent.from.toLowerCase(); + cleanEvent.value = this.state.web3.utils.fromWei( + "" + cleanEvent.value, + "ether" + ); + if (cleanEvent.data) { + let decrypted = await this.decryptInput(cleanEvent.data); + if (decrypted) { + cleanEvent.data = decrypted; + cleanEvent.encrypted = true; + } else { + try { + cleanEvent.data = this.state.web3.utils.hexToUtf8(cleanEvent.data); + } catch (e) {} } } - updatedTxs = this.addTxIfAccountMatches(recentTxs,transactionsByAddress,cleanEvent) || updatedTxs + updatedTxs = + this.addTxIfAccountMatches( + recentTxs, + transactionsByAddress, + cleanEvent + ) || updatedTxs; } - return updatedTxs + return updatedTxs; } - syncFullTransactions(){ - let initResult = this.initRecentTxs() - let recentTxs = [] - recentTxs = recentTxs.concat(initResult[0]) - let transactionsByAddress = Object.assign({},initResult[1]) - - let updatedTxs = false - updatedTxs = this.addAllTransactionsFromList(recentTxs,transactionsByAddress,this.state.transferTo) || updatedTxs - updatedTxs = this.addAllTransactionsFromList(recentTxs,transactionsByAddress,this.state.transferFrom) || updatedTxs - updatedTxs = this.addAllTransactionsFromList(recentTxs,transactionsByAddress,this.state.transferToWithData) || updatedTxs - updatedTxs = this.addAllTransactionsFromList(recentTxs,transactionsByAddress,this.state.transferFromWithData) || updatedTxs - - if(updatedTxs||!this.state.fullRecentTxs||!this.state.fullTransactionsByAddress){ - recentTxs.sort(sortByBlockNumber) - for(let t in transactionsByAddress){ - transactionsByAddress[t].sort(sortByBlockNumberDESC) + syncFullTransactions() { + let initResult = this.initRecentTxs(); + let recentTxs = []; + recentTxs = recentTxs.concat(initResult[0]); + let transactionsByAddress = Object.assign({}, initResult[1]); + + let updatedTxs = false; + updatedTxs = + this.addAllTransactionsFromList( + recentTxs, + transactionsByAddress, + this.state.transferTo + ) || updatedTxs; + updatedTxs = + this.addAllTransactionsFromList( + recentTxs, + transactionsByAddress, + this.state.transferFrom + ) || updatedTxs; + updatedTxs = + this.addAllTransactionsFromList( + recentTxs, + transactionsByAddress, + this.state.transferToWithData + ) || updatedTxs; + updatedTxs = + this.addAllTransactionsFromList( + recentTxs, + transactionsByAddress, + this.state.transferFromWithData + ) || updatedTxs; + + if ( + updatedTxs || + !this.state.fullRecentTxs || + !this.state.fullTransactionsByAddress + ) { + recentTxs.sort(sortByBlockNumber); + for (let t in transactionsByAddress) { + transactionsByAddress[t].sort(sortByBlockNumberDESC); } - recentTxs = recentTxs.slice(0,12) + recentTxs = recentTxs.slice(0, 12); //console.log("FULLRECENT",recentTxs) - this.setState({fullRecentTxs:recentTxs,fullTransactionsByAddress:transactionsByAddress}) + this.setState({ + fullRecentTxs: recentTxs, + fullTransactionsByAddress: transactionsByAddress + }); } } - render() { - let { - web3, account, gwei, block, avgBlockTime, etherscan, balance, metaAccount, burnMetaAccount, view, alert, send, passports - } = this.state; - // This makes it easier to debug stuff on the console. Will keep it here for now. - window.myweb3 = web3; - window.myplasma = this.state.xdaiweb3; - const defaultPassport = getDefaultPassport(account, passports); - - let networkOverlay = "" - // if(web3 && !this.checkNetwork() && view!=="exchange"){ - // networkOverlay = ( - //
- // - // - //
- // ) - // } - - - let web3_setup = "" - if(web3){ - web3_setup = ( -
- { - console.log("Transactions component is ready:", state); - state.nativeSend = tokenSend.bind(this) - //delete state.send - state.send = tokenSend.bind(this) - console.log(state) - this.setState(state) - - }} - onReceipt={(transaction, receipt) => { - // this is one way to get the deployed contract address, but instead I'll switch - // to a more straight forward callback system above - console.log("Transaction Receipt", transaction, receipt) - }} - /> -
- ) + // VOLT Methods + openMenu() { + this.setState({ isMenuOpen: true }); + } + closeMenu() { + this.setState({ isMenuOpen: false }); + } + async loadProposals() { + const endpoint = "https://api.npoint.io/217ecb17f01746799a3b"; + const response = await fetch(endpoint); + + const body = await response.json(); + const { + proposals: proposalsList, + voteEndTime, + voteStartTime, + trashAddress + } = body; + + // ToDo: remove second filter when store won't have any duplicate proposalId + const proposals = proposalsList + .map((p,i)=>({...p, id: i })) + .filter(p => p.proposalId) + .filter((p, i, list) => list.findIndex(p2 => p2.proposalId === p.proposalId) === i); + + this.setState(state => ({ + proposalsList: proposals, + filterQuery: "", + voteStartTime, + voteEndTime, + trashBox: trashAddress + })); + } + + sort(param) { + return () => { + this.setState(({ sorting, sortingOrder }) => ({ + sorting: param, + sortingOrder: sorting === param ? sortingOrder * -1 : 1 + })); } + } - let eventParser = "" + filterList(event) { + const query = event.target.value; + this.setState({ + filterQuery: query, + }); + } - let extraHead = "" - if(this.state.extraHeadroom){ - extraHead = ( -
-
- ) - } + resetFilter() { + this.setState({ + filterQuery: "", + }); + } - let totalBalance = parseFloat(this.state.ethBalance) * parseFloat(this.state.ethprice) + parseFloat(this.state.daiBalance) + parseFloat(this.state.xdaiBalance) - let header = ( -
-
- ) - if(web3){ - header = ( -
- ) - } + toggleFavorites(id) { + const { account, favorites } = this.state; + const value = !!favorites[id]; + favorites[id] = !value; - return ( - - -
-
- {extraHead} - {networkOverlay} - {web3_setup} - -
- {header} - - - - {web3 /*&& this.checkNetwork()*/ && (() => { - //console.log("VIEW:",view) - - let defaultBalanceDisplay = ( - - ) - - // NOTE: This view is to show specific historical transactions. - if(view.includes("account_")) { - const targetAddress = view.replace("account_","") - return ( -
- - - {defaultBalanceDisplay} - - - - { - this.changeView('main') - }} - /> -
+ storeValues({ favorites: JSON.stringify(favorites) }, account); - ) - } + this.setState({ favorites }); + } - // NOTE: This view shows specific historical transactions to - // bity.com - if (view.includes("bity_")) { - const orderId = view.replace("bity_","") - return ( -
- - - - - { - this.changeView('main') - }} - /> -
+ render() { + const { creditsBalance, alert } = this.state; + const { xdaiweb3, web3, account, metaAccount, burnMetaAccount } = this.state; + const { + isMenuOpen, + proposalsList, + filterQuery, + favorites + } = this.state; - ) - } + const { userVotes, voteStartTime, voteEndTime, trashBox } = this.state; + const web3Props = { plasma: xdaiweb3, web3, account, metaAccount }; + const filledProposals = proposalsList ? proposalsList.filter(p => p.proposalId) : []; + const maxCredits = filledProposals.length; + return ( + <> + {account ? ( + + {isMenuOpen && } +
- if (view.includes("loader_")) { - const network = view.replace("loader_"); - return ( -
-
- - -
- -
- ); - } + ( + + )} /> - const sendByScan = ( - ( + + )} /> + + ( + { - this.changeAlert("danger",error) + privateKey={metaAccount.privateKey} + changeAlert={this.changeAlert} + currencyDisplay={this.currencyDisplay} + tokenSendV2={tokenSendV2.bind(this)} + metaAccount={this.state.metaAccount} + setPossibleNewPrivateKey={this.setPossibleNewPrivateKey} + history={history} + /> + )} /> + + ( + this.props.history.go(-1) } + currencyDisplay={this.currencyDisplay} + burnWallet={()=>{ + burnMetaAccount(true); + + if(RNMessageChannel){ + RNMessageChannel.send("burn"); + } + + storeValues({ + loadedBlocksTop: "", + metaPrivateKey: "", + recentTxs: "", + transactionsByAddress: "", + }, this.state.account); + + this.setState({ + recentTxs: [], + transactionsByAddress: {} + }); + + window.location.href = 'https://deora.earth/scan'; + return; }} /> - ) - - switch(view) { - case 'planet_a_handshake': - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - - - -
- ); - case 'planet_a_finalize_handshake': - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - - - -
- ); - case 'main': - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - - - - - - - - - - { - this.changeView('advanced') - }} - /> -
- ); - case 'cashout': + )} /> + + { + const proposal = (proposalsList || []).find(p => p.proposalId === proposalId); + if (!proposal) { + return 'Proposal not found'; + } else { + const { voteStartTime, voteEndTime } = this.state; return ( -
- {this.state.scannerOpen ? sendByScan : null} - - -
- -
- -
- -
- ); - case 'advanced': - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - - - -
- ) - case 'withdraw_from_private': - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - {defaultBalanceDisplay} - - - { - this.changeView('main') - }} - /> -
- ); - case 'send_to_address': - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - {defaultBalanceDisplay} - - - -
- ); - case 'receipt': - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - - - -
- ); - case 'receive': - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - {defaultBalanceDisplay} - - - history.replace('/')} + changeAlert={this.changeAlert} + updateVotes={this.updateVotes} + voteEndTime={voteEndTime} + voteStartTime={voteStartTime} + history={history} /> -
- ); - case 'request_funds': - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - {defaultBalanceDisplay} - - - { - this.changeView('main') - }} - /> -
- ); - case 'share': - - let url = window.location.protocol+"//"+window.location.hostname - if(window.location.port&&window.location.port!==80&&window.location.port!==443){ - url = url+":"+window.location.port - } + ) + } + }} /> - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - - - -
- ); - case 'share-link': - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - - - -
- ); - case 'burn-wallet': - return ( -
- {this.state.scannerOpen ? sendByScan : null} - - - {defaultBalanceDisplay} - { - burnMetaAccount() - if(RNMessageChannel){ - RNMessageChannel.send("burn") - } - storeValues({ - loadedBlocksTop: "", - metaPrivateKey: "", - recentTxs: "", - transactionsByAddress: "", - }, this.state.account); - this.setState({recentTxs:[],transactionsByAddress:{}}) - }} - /> - - -
- ); + ( + + )} /> - case 'loader': - return ( -
-
+ { alert && } + + ) : ( + + )} + { + //console.log("DAPPARATUS UPDATE",state) + + const { account, favorites, userVotes } = state; + + console.log('ACCOUNT ADDRESS:', account); + + if (!favorites) { + const storedList = getStoredValue("favorites", account); + const favoritesList = storedList ? JSON.parse(storedList) : {}; + this.setState({ + favorites: favoritesList + }); + } - -
- -
- ); - case 'reader': - return ( -
-
- -
- -
+ if (!userVotes){ + const userVotes = {}; + const localTree = getStoredValue("votes", account); + const parsedTree = JSON.parse(localTree); + + if (parsedTree){ + for (const key in parsedTree){ + if (parsedTree.hasOwnProperty(key)){ + + // Get value from the leaf + const value = parsedTree[key]; + + // Decode it + const decVal = ethers.utils.defaultAbiCoder.decode(["int256"], value); + + // Number stored in the leaf is numOfVotes * (10 ** 18) + const decEthVal = ethers.utils.formatEther(decVal.toString()); + + // Later in list of proposals we will need integer value + userVotes[key] = parseInt(decEthVal); + } + } + } + + this.setState({ + userVotes + }); + } + + if (state.xdaiweb3) { + let voiceCreditsContract; + let voiceTokensContract; + const StableABI = require("./contracts/StableCoin.abi.js"); + try { + voiceCreditsContract = new state.xdaiweb3.eth.Contract( + StableABI, + VOLT_CONFIG.CONTRACT_VOICE_CREDITS ); - case 'claimer': - return ( -
-
- -
- -
+ voiceTokensContract = new state.xdaiweb3.eth.Contract( + StableABI, + VOLT_CONFIG.CONTRACT_VOICE_TOKENS ); - default: - return ( -
unknown view
- ) - } - })()} - { ( false || !web3 /*|| !this.checkNetwork() */) && } - { alert &&
} -
- { - //console.log("DAPPARATUS UPDATE",state) - if (state.xdaiweb3) { - let xdaiContract; - try { - xdaiContract = new state.xdaiweb3.eth.Contract(require("./contracts/StableCoin.abi.js"), CONFIG.SIDECHAIN.DAI_ADDRESS) - } catch(err) { - console.log("Error loading PDAI contract"); - } - this.setState({xdaiContract}); + } catch (err) { + console.log("Error loading contracts"); } - if (state.web3Provider) { - state.web3 = new Web3(state.web3Provider) - this.setState(state,()=>{ - //console.log("state set:",this.state) - if(this.state.possibleNewPrivateKey){ - this.dealWithPossibleNewPrivateKey() - } - if(!this.state.parsingTheChain){ - this.setState({parsingTheChain:true},async ()=>{ - let upperBoundOfSearch = this.state.block - //parse through recent transactions and store in local storage - let initResult = this.initRecentTxs() - let recentTxs = initResult[0] - let transactionsByAddress = initResult[1] - let loadedBlocksTop = this.state.loadedBlocksTop - if (!loadedBlocksTop) { - loadedBlocksTop = getStoredValue("loadedBlocksTop", this.state.account) + this.setState({ + voiceTokensContract, + voiceCreditsContract + }); + } + if (state.web3Provider) { + state.web3 = new Web3(state.web3Provider); + this.setState(state, () => { + //console.log("state set:",this.state) + if (this.state.possibleNewPrivateKey) { + this.dealWithPossibleNewPrivateKey(); + } + if (!this.state.parsingTheChain) { + this.setState({ parsingTheChain: true }, async () => { + let upperBoundOfSearch = this.state.block; + //parse through recent transactions and store in local storage + let initResult = this.initRecentTxs(); + let recentTxs = initResult[0]; + let transactionsByAddress = initResult[1]; + let loadedBlocksTop = this.state.loadedBlocksTop; + if (!loadedBlocksTop) { + loadedBlocksTop = getStoredValue( + "loadedBlocksTop", + this.state.account + ); + } + // Look back through previous blocks since this account + // was last online... this could be bad. We might need a + // central server keeping track of all these and delivering + // a list of recent transactions + let updatedTxs = false; + if ( + !loadedBlocksTop || + loadedBlocksTop < this.state.block + ) { + if (!loadedBlocksTop) + loadedBlocksTop = Math.max(2, this.state.block - 5); + if ( + this.state.block - loadedBlocksTop > + MAX_BLOCK_TO_LOOK_BACK + ) { + loadedBlocksTop = + this.state.block - MAX_BLOCK_TO_LOOK_BACK; } - // Look back through previous blocks since this account - // was last online... this could be bad. We might need a - // central server keeping track of all these and delivering - // a list of recent transactions - let updatedTxs = false - if (!loadedBlocksTop || loadedBlocksTop < this.state.block) { - if (!loadedBlocksTop) loadedBlocksTop = Math.max(2, this.state.block - 5) - if (this.state.block - loadedBlocksTop > MAX_BLOCK_TO_LOOK_BACK) { - loadedBlocksTop = this.state.block - MAX_BLOCK_TO_LOOK_BACK - } - let paddedLoadedBlocks = parseInt(loadedBlocksTop) + BLOCKS_TO_PARSE_PER_BLOCKTIME - //console.log("choosing the min of ",paddedLoadedBlocks,"and",this.state.block) - let parseBlock = Math.min(paddedLoadedBlocks, this.state.block) - //console.log("MIN:",parseBlock) - upperBoundOfSearch = parseBlock - console.log(" +++++++======== Parsing recent blocks ~" + this.state.block) - //first, if we are still back parsing, we need to look at *this* block too - if (upperBoundOfSearch < this.state.block) { - for (let b = this.state.block; b > this.state.block - 6; b--) { - //console.log(" ++ Parsing *CURRENT BLOCK* Block "+b+" for transactions...") - updatedTxs = (await this.parseBlocks(b, recentTxs, transactionsByAddress)) || updatedTxs - } - } - console.log(" +++++++======== Parsing from " + loadedBlocksTop + " to " + upperBoundOfSearch + "....") - while (loadedBlocksTop < parseBlock) { - //console.log(" ++ Parsing Block "+parseBlock+" for transactions...") - updatedTxs = (await this.parseBlocks(parseBlock, recentTxs, transactionsByAddress)) || updatedTxs - parseBlock-- + let paddedLoadedBlocks = + parseInt(loadedBlocksTop) + + BLOCKS_TO_PARSE_PER_BLOCKTIME; + //console.log("choosing the min of ",paddedLoadedBlocks,"and",this.state.block) + let parseBlock = Math.min( + paddedLoadedBlocks, + this.state.block + ); + //console.log("MIN:",parseBlock) + upperBoundOfSearch = parseBlock; + console.log( + " +++++++======== Parsing recent blocks ~" + + this.state.block + ); + //first, if we are still back parsing, we need to look at *this* block too + if (upperBoundOfSearch < this.state.block) { + for ( + let b = this.state.block; + b > this.state.block - 6; + b-- + ) { + updatedTxs = + (await this.parseBlocks( + b, + recentTxs, + transactionsByAddress + )) || updatedTxs; } } - if (updatedTxs || !this.state.recentTxs) { - this.sortAndSaveTransactions(recentTxs, transactionsByAddress) + console.log( + " +++++++======== Parsing from " + + loadedBlocksTop + + " to " + + upperBoundOfSearch + + "...." + ); + while (loadedBlocksTop < parseBlock) { + //console.log(" ++ Parsing Block "+parseBlock+" for transactions...") + updatedTxs = + (await this.parseBlocks( + parseBlock, + recentTxs, + transactionsByAddress + )) || updatedTxs; + parseBlock--; } - storeValues({loadedBlocksTop: upperBoundOfSearch}, this.state.account); - this.setState({parsingTheChain: false, loadedBlocksTop: upperBoundOfSearch}) - //console.log("~~ DONE PARSING SET ~~") - }) - } - }) - } - }} - /> - { - console.log("Gas price update:",state) - const gwei = (state.gwei || this.state.gwei) + 0.1; - console.log("GWEI set:",gwei); - this.setState({ - ...state, - gwei - }) - }} - /> -
- {eventParser} -
-
-
-
- ) + } + if (updatedTxs || !this.state.recentTxs) { + this.sortAndSaveTransactions( + recentTxs, + transactionsByAddress + ); + } + storeValues( + { loadedBlocksTop: upperBoundOfSearch }, + this.state.account + ); + this.setState({ + parsingTheChain: false, + loadedBlocksTop: upperBoundOfSearch + }); + }); + } + }); + } + }} + /> + + ); } } +export default withRouter(App); + //