From 4f15ae263f14bafd3eed571b2d8414efad3561f5 Mon Sep 17 00:00:00 2001 From: MaxKuck <45236782+MaxKuck@users.noreply.github.com> Date: Mon, 26 Aug 2019 21:51:36 +0200 Subject: [PATCH 001/102] Update README.md --- README.md | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 74c5bfe79..32e750e36 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# 🌍 Planet A 🌍 +# deora.earth - -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. + +We 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. @@ -25,24 +27,10 @@ $ 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: ``` +------------+------------+------------+-------------+ @@ -51,22 +39,12 @@ Passport data structure: +------------+------------+------------+-------------+ ``` -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 From 4097e0a7d4201cb7ec67e516041a4f8f98e348c1 Mon Sep 17 00:00:00 2001 From: MaxKuck <45236782+MaxKuck@users.noreply.github.com> Date: Mon, 26 Aug 2019 22:00:39 +0200 Subject: [PATCH 002/102] Update README.md --- README.md | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 32e750e36..f2e224d4b 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ -# deora.earth - +# deora.earth deora.earth is a universal white-label voting and community platform. -We +We build an universal decision making/lobby tool for communities, NGOs, political movements and for the people. Together with other blockchain voting initiatives and foundations we want give the voice back to the people. We are using hands-on blockchain solutions for current real offline voting & community organizing challenges. -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. +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 ``` @@ -30,20 +30,10 @@ $ HTTPS=true npm run start ### ERC1948 Tokens - - -``` -+------------+------------+------------+-------------+ -| 20 bytes | 4 bytes | 4 bytes | 4 bytes | -| name str | picId | CO₂ locked | CO₂ emitted | -+------------+------------+------------+-------------+ -``` +` ### 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`. ## License From 271650ba666c1e58c466b665443728ea2ee8205a Mon Sep 17 00:00:00 2001 From: MaxKuck <45236782+MaxKuck@users.noreply.github.com> Date: Mon, 26 Aug 2019 22:01:55 +0200 Subject: [PATCH 003/102] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2e224d4b..13ef61297 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# deora.earth +# deora.earth :earth_africa: deora.earth is a universal white-label voting and community platform. From 46f27c10acd6034d6f68f4115c8beea942c676f4 Mon Sep 17 00:00:00 2001 From: MaxKuck <45236782+MaxKuck@users.noreply.github.com> Date: Mon, 26 Aug 2019 22:02:49 +0200 Subject: [PATCH 004/102] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13ef61297..407c3632d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ deora.earth is a universal white-label voting and community platform. -We build an universal decision making/lobby tool for communities, NGOs, political movements and for the people. Together with other blockchain voting initiatives and foundations we want give the voice back to the people. We are using hands-on blockchain solutions for current real offline voting & community organizing challenges. +We build an universal decision making/lobby tool for communities, NGOs, political movements and for the people. 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. From d48ea3dcd07bfd3d08837e49f20d67e21d815360 Mon Sep 17 00:00:00 2001 From: Onuwa Nnachi Isaac Date: Mon, 26 Aug 2019 14:56:56 +0100 Subject: [PATCH 005/102] Add CI/CD --- .travis.yml | 33 +++++++++++++++++++++++++++++++++ package-lock.json | 47 ++++++++++++++--------------------------------- package.json | 2 +- 3 files changed, 48 insertions(+), 34 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..5ec8d0087 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,33 @@ +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: voting/master + +after_deploy: + - aws configure set preview.cloudfront true + - aws cloudfront create-invalidation --distribution-id $PROD_CLOUDFRONT_DISTRIBUTION --paths "/*" diff --git a/package-lock.json b/package-lock.json index 7a8635803..fc140618b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4790,7 +4790,7 @@ "s3": "^4.4.0", "scrypt": "^6.0.3", "sha3": "^1.2.2", - "solc": "^0.5.10", + "solc": "^0.5.9", "truffle-hdwallet-provider": "0.0.6", "typedarray-to-buffer": "^3.1.5", "web3": "^1.0.0-beta.33", @@ -8191,7 +8191,7 @@ "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "dev": true, "requires": { - "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git", + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb", "ethereumjs-util": "^5.1.1" }, "dependencies": { @@ -9891,8 +9891,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -9910,13 +9909,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9929,18 +9926,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -10043,8 +10037,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -10054,7 +10047,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -10067,20 +10059,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -10097,7 +10086,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -10170,8 +10158,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -10181,7 +10168,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -10257,8 +10243,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -10288,7 +10273,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -10306,7 +10290,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -10345,13 +10328,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -27056,7 +27037,7 @@ "requires": { "underscore": "1.8.3", "web3-core-helpers": "1.0.0-beta.36", - "websocket": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible" + "websocket": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2" } }, "web3-shh": { diff --git a/package.json b/package.json index cf1d6364a..2b7ab1b3f 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "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" From ef6a89ba85898be41e2cd7426e93ded49697f4f4 Mon Sep 17 00:00:00 2001 From: Onuwa Nnachi Isaac Date: Mon, 26 Aug 2019 21:18:55 +0100 Subject: [PATCH 006/102] Update travis branch --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5ec8d0087..7df78c0ff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ deploy: skip_cleanup: true region: "eu-west-1" on: - branch: voting/master + all_branches: true after_deploy: - aws configure set preview.cloudfront true From 486bc33c65be2fdf7b6678bdfce13e3d8b5f57e9 Mon Sep 17 00:00:00 2001 From: Onuwa Nnachi Isaac Date: Mon, 26 Aug 2019 21:24:38 +0100 Subject: [PATCH 007/102] Update travis branch name --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7df78c0ff..5ec8d0087 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ deploy: skip_cleanup: true region: "eu-west-1" on: - all_branches: true + branch: voting/master after_deploy: - aws configure set preview.cloudfront true From 389bf4f32885daf00d4d4df95a94655c127ba175 Mon Sep 17 00:00:00 2001 From: Johann Barbie Date: Tue, 27 Aug 2019 14:35:59 +0200 Subject: [PATCH 008/102] add volt domain --- src/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.js b/src/config.js index f4798a9d9..3acfcdaff 100644 --- a/src/config.js +++ b/src/config.js @@ -42,7 +42,7 @@ const configs = [ } }, { - DOMAINS: ["toco2.leap.rocks", "planeta.leap.rocks"], + DOMAINS: ["volt.deora.earth", "planeta.leap.rocks"], CURRENCY: { EXCHANGE_RATE_QUERY: 5000, CURRENCY_LIST: ["EUR", "USD", "GBP"], From 308bc5861648e605f2c96dc26c140d2854c9dae9 Mon Sep 17 00:00:00 2001 From: Johann Barbie Date: Tue, 27 Aug 2019 15:12:16 +0200 Subject: [PATCH 009/102] travis for master --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5ec8d0087..c3ac245bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ deploy: skip_cleanup: true region: "eu-west-1" on: - branch: voting/master + branch: master after_deploy: - aws configure set preview.cloudfront true From f6106e73e06aea719f547a8682374ee29df5f137 Mon Sep 17 00:00:00 2001 From: Onuwa Nnachi Isaac Date: Mon, 26 Aug 2019 14:56:56 +0100 Subject: [PATCH 010/102] Add CI/CD --- .travis.yml | 33 +++++++++++++++++++++++++++++++++ package-lock.json | 47 ++++++++++++++--------------------------------- package.json | 2 +- 3 files changed, 48 insertions(+), 34 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..7288c586e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,33 @@ +language: node_js + +node_js: + - "8.11" + +before_install: + - pip install --user awscli + - export PATH=$PATH:$HOME/.local/bin + +install: npm install + +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: voting/master + +after_deploy: + - aws configure set preview.cloudfront true + - aws cloudfront create-invalidation --distribution-id $PROD_CLOUDFRONT_DISTRIBUTION --paths "/*" diff --git a/package-lock.json b/package-lock.json index 7a8635803..fc140618b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4790,7 +4790,7 @@ "s3": "^4.4.0", "scrypt": "^6.0.3", "sha3": "^1.2.2", - "solc": "^0.5.10", + "solc": "^0.5.9", "truffle-hdwallet-provider": "0.0.6", "typedarray-to-buffer": "^3.1.5", "web3": "^1.0.0-beta.33", @@ -8191,7 +8191,7 @@ "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "dev": true, "requires": { - "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git", + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb", "ethereumjs-util": "^5.1.1" }, "dependencies": { @@ -9891,8 +9891,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -9910,13 +9909,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9929,18 +9926,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -10043,8 +10037,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -10054,7 +10047,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -10067,20 +10059,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -10097,7 +10086,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -10170,8 +10158,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -10181,7 +10168,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -10257,8 +10243,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -10288,7 +10273,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -10306,7 +10290,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -10345,13 +10328,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -27056,7 +27037,7 @@ "requires": { "underscore": "1.8.3", "web3-core-helpers": "1.0.0-beta.36", - "websocket": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible" + "websocket": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2" } }, "web3-shh": { diff --git a/package.json b/package.json index cf1d6364a..2b7ab1b3f 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "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" From f9c35a7df9caf4f24ab8eea12e893e029a5ac83d Mon Sep 17 00:00:00 2001 From: Onuwa Nnachi Isaac Date: Thu, 29 Aug 2019 09:06:21 +0100 Subject: [PATCH 011/102] Add additional config to after deploy --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6710b2128..d790c1dc1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,5 +29,7 @@ deploy: 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 "/*" From 4c3fcedaf9f3ab4ce24040128bb8d69b0f1cec9e Mon Sep 17 00:00:00 2001 From: MaxKuck <45236782+MaxKuck@users.noreply.github.com> Date: Thu, 29 Aug 2019 12:52:06 +0200 Subject: [PATCH 012/102] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 407c3632d..d17b86627 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ deora.earth is a universal white-label voting and community platform. -We build an universal decision making/lobby tool for communities, NGOs, political movements and for the people. 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. +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. From 27b780303882ef95fc5f3f66f57341d3fb8a5e16 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:51:47 +0300 Subject: [PATCH 013/102] Add jsbi package --- package-lock.json | 13 ++++++++++--- package.json | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a8635803..70a5424e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14313,9 +14313,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 +14323,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": { diff --git a/package.json b/package.json index cf1d6364a..5bf322662 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "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", "qrcode-reader": "^1.0.4", From e18e4df9eefbb54a09ce4d8e4f54a0c264ec4ff1 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:52:12 +0300 Subject: [PATCH 014/102] Change title and add font in header --- public/index.html | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/public/index.html b/public/index.html index a9fa7cc04..e9747a7ed 100644 --- a/public/index.html +++ b/public/index.html @@ -20,29 +20,28 @@ integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous" /> - Planet A - + VOLT + - - + + - + - + - + - - + \ No newline at end of file diff --git a/src/volt/assets/vote-no.png b/src/volt/assets/vote-no.png new file mode 100644 index 0000000000000000000000000000000000000000..c6f9e0a62c6a8d00e35ee3a8c2841065f0152301 GIT binary patch literal 25496 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 literal 0 HcmV?d00001 diff --git a/src/volt/assets/vote-no.svg b/src/volt/assets/vote-no.svg new file mode 100644 index 000000000..866fc1c2c --- /dev/null +++ b/src/volt/assets/vote-no.svg @@ -0,0 +1 @@ +NO \ No newline at end of file diff --git a/src/volt/assets/vote-yes.png b/src/volt/assets/vote-yes.png new file mode 100644 index 0000000000000000000000000000000000000000..14354775328d06bce574d4ed0e615f2c78e243df GIT binary patch literal 29275 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νke6eJP$A7Lmy#KvNjoam{Te>8!u&^!4dbH@n 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 \ No newline at end of file From 3a9ad1557b3a45e22f47d0ac4761d9a10b28863a Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:53:00 +0300 Subject: [PATCH 017/102] Add volt branded colors --- src/theme.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/theme.js b/src/theme.js index 23d5df90e..c9acd2b53 100644 --- a/src/theme.js +++ b/src/theme.js @@ -13,7 +13,7 @@ export default { }, fonts: { serif: 'athelas, georgia, times, serif', - sansSerif: '"Source Sans Pro", -apple-system, sans-serif', + sansSerif: '"Ubuntu", -apple-system, sans-serif', }, space: [ 0, 4, 8, 16, 32, 64, 128, 256 @@ -40,11 +40,19 @@ export default { disabled: 0.4 }, colors: { + voltBrandMain: '#582C83', + voltBrandWhite: '#FFFFFF', + voltBrandGreen: '#41CC8F', + voltBrandGreenBorder: '#0ceba1', + voltBrandRed: '#FF2C14', + voltBrandRedBorder: '#ff6d58', + voltBrandDimPurple: '#8a6ca8', + passportUSA: "#6846c3", passportUSB: "#077154", emitted: '#4C6FED', locked: "#05B62C", - primary: 'black', + primary: '#582C83', blue: '#007ce0', navy: '#004175', copyColor: '#3F3D4B', From 0af79497d6352f267dc4f224d9e3832b2dafa980 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:53:20 +0300 Subject: [PATCH 018/102] Add utility methods used in interactions with contracts --- src/volt/utils.js | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/volt/utils.js diff --git a/src/volt/utils.js b/src/volt/utils.js new file mode 100644 index 000000000..054c640f5 --- /dev/null +++ b/src/volt/utils.js @@ -0,0 +1,47 @@ +import web3 from "web3"; +import { Output, Outpoint } from "leap-core"; +import { voltConfig as VOLT_CONFIG } from "../volt/config"; +import { getId, getData } from "../services/plasma"; + +export const fetchBalanceCard = async (plasma, account) => { + const { BALANCE_CARD_COLOR } = VOLT_CONFIG; + return await plasma.getUnspent(account, BALANCE_CARD_COLOR).then(cards => { + // We assume every user gonna have a single balance card + const firstCard = cards[0]; + const result = { + id: getId(firstCard), + data: getData(firstCard) + }; + console.log(result); + return result; + }); +}; +export const getUTXOs = async (plasma, account, color) => { + const utxos = await plasma.send("plasma_unspent", [account]); + return utxos + .filter(utxo => utxo.output.color === color) + .map(utxo => { + return { + outpoint: Outpoint.fromRaw(utxo.outpoint), + output: utxo.output + }; + }); +}; + +export const pad = a => { + const str = a.toString(); + if (str.length === 3) return a; + if (str.length === 1) return `00${a}`; + return `0${a}`; +}; +export const votesToValue = voteNum => { + const zeroes = "000000000000000000"; // 18 digits for ERC20 token + const voteCredits = `${voteNum}${zeroes}`; + + const hex = web3.utils.toHex(voteCredits).toUpperCase(); + const paddedHex = web3.utils.padLeft(hex, 64); + return { + hex: paddedHex, + string: voteCredits + }; +}; From 8c7d1487bb6712455a57af4443d0a4021a0b4ab6 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:53:31 +0300 Subject: [PATCH 019/102] Add hardcoded proposal --- src/volt/proposals.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/volt/proposals.js diff --git a/src/volt/proposals.js b/src/volt/proposals.js new file mode 100644 index 000000000..d9a50aff8 --- /dev/null +++ b/src/volt/proposals.js @@ -0,0 +1,13 @@ +const proposals = [ + { + proposalId: "prop-12", + title: "bla", + description: "bla bla bla", + topic: ["Smart State", "Social Equality"], + boothAddress: "0x1ba5dca50564df62f60a4930635f7dbbf38378b7", + yesBoxAddress: "0xdea3f5922b9bbde4707d1f73d4d265040c49282b", + noBoxAddress: "0x772e6c04dcb3b42d716db90699de6664f4ba45ba" + } +]; + +export default proposals; From 283e99e75e5e3d2018a89dc34cd81f9e1cf177ce Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:53:58 +0300 Subject: [PATCH 020/102] Add SparseMerkleTree library --- src/volt/lib/SparseMerkleTree.js | 132 +++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/volt/lib/SparseMerkleTree.js diff --git a/src/volt/lib/SparseMerkleTree.js b/src/volt/lib/SparseMerkleTree.js new file mode 100644 index 000000000..09cf21f5a --- /dev/null +++ b/src/volt/lib/SparseMerkleTree.js @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2019-present, Project Democracy + * + * This source code is licensed under the Mozilla Public License, version 2, + * found in the LICENSE file in the root directory of this source tree. + */ + +import { keccak256 } from "ethereumjs-util"; +import JSBI from "jsbi"; + +const ZERO = + "0x0000000000000000000000000000000000000000000000000000000000000000"; +const one = JSBI.BigInt(1); +const two = JSBI.BigInt(2); + +const merkelize = (hash1, hash2) => { + const buffer = Buffer.alloc(64, 0); + if (typeof hash1 === "string" || hash1 instanceof String) { + buffer.write(hash1.replace("0x", ""), "hex"); + } else { + hash1.copy(buffer); + } + if (typeof hash2 === "string" || hash2 instanceof String) { + buffer.write(hash2.replace("0x", ""), 32, "hex"); + } else { + hash2.copy(buffer, 32); + } + return `0x${keccak256(buffer).toString("hex")}`; +}; +const setTrailBit = (trail, pos) => { + const bytePos = trail.length - 1 - Math.floor(pos / 8); + let val = trail.readUInt8(bytePos); + val += 1 << pos % 8; // eslint-disable-line no-bitwise + trail.writeUInt8(val, bytePos); +}; +const setDefaultNodes = depth => { + const defaultNodes = new Array(depth + 1); + defaultNodes[0] = `0x${Buffer.alloc(32, 0).toString("hex")}`; + for (let i = 1; i < depth + 1; i++) { + defaultNodes[i] = `0x${Buffer.alloc(32, 0).toString("hex")}`; + } + return defaultNodes; +}; +const createTree = (orderedLeaves, depth, defaultNodes) => { + const tree = [orderedLeaves]; + let treeLevel = orderedLeaves; + + let nextLevel = {}; + let halfIndex; + let value; + + for (let level = 0; level < depth; level++) { + nextLevel = {}; + for (const index in treeLevel) { + // eslint-disable-line no-restricted-syntax + if (treeLevel.hasOwnProperty(index)) { + // eslint-disable-line no-prototype-builtins + halfIndex = JSBI.divide(JSBI.BigInt(index, 10), two).toString(); + value = treeLevel[index]; + if (JSBI.__absoluteModSmall(JSBI.BigInt(index, 10), two) === 0) { + // eslint-disable-line no-underscore-dangle + const coIndex = JSBI.add(JSBI.BigInt(index, 10), one).toString(); + if (value == ZERO && !treeLevel[coIndex]) { + nextLevel[halfIndex] = ZERO; + } else { + nextLevel[halfIndex] = merkelize( + value, + treeLevel[coIndex] || defaultNodes[level] + ); + } + } else { + const coIndex = JSBI.subtract(JSBI.BigInt(index, 10), one).toString(); + if (treeLevel[coIndex] === undefined) { + if (value == ZERO) { + nextLevel[halfIndex] = ZERO; + } else { + nextLevel[halfIndex] = merkelize(defaultNodes[level], value); + } + } + } + } + } + treeLevel = nextLevel; + tree.push(treeLevel); + } + return tree; +}; + +class SmtLib { + constructor(depth, leaves) { + this.depth = depth; + // Initialize defaults + this.defaultNodes = setDefaultNodes(depth); + // Leaves must be a dictionary with key as the leaf's slot and value the leaf's hash + this.leaves = leaves; + + if (leaves && Object.keys(leaves).length !== 0) { + this.tree = createTree(this.leaves, this.depth, this.defaultNodes); + this.root = this.tree[this.depth]["0"]; + } else { + console.log('The tree is empty!'); + this.tree = []; + this.root = this.defaultNodes[this.depth]; + } + } + + createMerkleProof(uid) { + let index = JSBI.BigInt(uid, 10); + let proof = ""; + const trail = Buffer.alloc(Math.ceil(this.depth / 8), 0); + let siblingIndex; + let siblingHash; + for (let level = 0; level < this.depth; level++) { + siblingIndex = + JSBI.__absoluteModSmall(index, 2) === 0 + ? JSBI.add(index, one) + : JSBI.subtract(index, one); // eslint-disable-line no-underscore-dangle + index = JSBI.divide(index, two); + if (level < this.tree.length) { + siblingHash = this.tree[level][siblingIndex.toString(10)]; + if (siblingHash) { + proof += siblingHash.replace("0x", ""); + setTrailBit(trail, level); + } + } + } + const total = Buffer.concat([trail, Buffer.from(proof, "hex")]); + return `0x${total.toString("hex")}`; + } +} + +export default SmtLib; From 89999f3beeb2ddd4daee31b9f5dc1ff6d8c682ca Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:54:20 +0300 Subject: [PATCH 021/102] Add bytecode and abi for VoteBooth contract --- src/volt/contracts/voteBooth.js | 52 +++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/volt/contracts/voteBooth.js diff --git a/src/volt/contracts/voteBooth.js b/src/volt/contracts/voteBooth.js new file mode 100644 index 000000000..2f3f4a1b5 --- /dev/null +++ b/src/volt/contracts/voteBooth.js @@ -0,0 +1,52 @@ +export const bytecode = + "608060405234801561001057600080fd5b50600436106100395760e060020a60003504636b0f3406811461003e578063f67fcc4c146100f0575b600080fd5b6100ee6004803603608081101561005457600080fd5b81359190810190604081016020820135602060020a81111561007557600080fd5b82018360208201111561008757600080fd5b803590602001918460018302840111602060020a831117156100a857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550508235935050506020013561011c565b005b6100ee6004803603606081101561010657600080fd5b5060ff8135169060208101359060400135610538565b6040805160e060020a6337ebbc0302815260048101869052905173cd1b3a9a7b5f84bc7829bc7e6e23adb1960bee979160009183916337ebbc03916024808301926020929190829003018186803b15801561017657600080fd5b505afa15801561018a573d6000803e3d6000fd5b505050506040513d60208110156101a057600080fd5b505190506101b5846500000000000087610723565b8114610200576040805160e560020a62461bcd02815260206004820152600f6024820152608a60020a6e1c1c9bdbd9881b9bdd081d985b1a5902604482015290519081900360640190fd5b60008085121561027a5784841261025f576040805160e560020a62461bcd0281526020600482015260186024820152604060020a7763616e206e6f74206465637265617365206e6f20766f746502604482015290519081900360640190fd5b5073772e6c04dcb3b42d716db90699de6664f4ba45ba6102e7565b8484136102d0576040805160e560020a62461bcd0281526020600482015260196024820152603860020a7863616e206e6f742064656372656173652079657320766f746502604482015290519081900360640190fd5b5073dea3f5922b9bbde4707d1f73d4d265040c49282b5b6040805160e160020a6331a9108f028152600481018990529051670de0b6b3a7640000878002878002030491738f8fdca55f0601187ca24507d4a1fe1b387db90b9182916323b872dd91600160a060020a03891691636352211e916024808301926020929190829003018186803b15801561036157600080fd5b505afa158015610375573d6000803e3d6000fd5b505050506040513d602081101561038b57600080fd5b50516040805160e060020a63ffffffff8516028152600160a060020a0392831660048201529187166024830152604482018690525160648083019260209291908290030181600087803b1580156103e157600080fd5b505af11580156103f5573d6000803e3d6000fd5b505050506040513d602081101561040b57600080fd5b50733442c197cc858bed2476bdd9c7d4499552780f3d90508063a9059cbb856104358b8b0361089c565b6040518363ffffffff1660e060020a0281526004018083600160a060020a0316600160a060020a0316815260200182815260200192505050602060405180830381600087803b15801561048757600080fd5b505af115801561049b573d6000803e3d6000fd5b505050506040513d60208110156104b157600080fd5b5050600160a060020a03861663a983d43f8b6104d48a650000000000008e610723565b6040518363ffffffff1660e060020a0281526004018083815260200182815260200192505050600060405180830381600087803b15801561051457600080fd5b505af1158015610528573d6000803e3d6000fd5b5050505050505050505050505050565b604080516000815260208082018084526001606060020a0319606060020a300216905260ff861682840152606082018590526080820184905291517378911111111111111111111111111111111117899260019260a080820193601f1981019281900390910190855afa1580156105b3573d6000803e3d6000fd5b50505060206040510351600160a060020a0316141515610618576040805160e560020a62461bcd0281526020600482015260156024820152605b60020a740e6d2cedccae440c8decae640dcdee840dac2e8c6d02604482015290519081900360640190fd5b6040805160e060020a6370a0823102815230600482018190529151733442c197cc858bed2476bdd9c7d4499552780f3d92839263a9059cbb9284916370a08231916024808301926020929190829003018186803b15801561067857600080fd5b505afa15801561068c573d6000803e3d6000fd5b505050506040513d60208110156106a257600080fd5b50516040805160e060020a63ffffffff8616028152600160a060020a03909316600484015260248301919091525160448083019260209291908290030181600087803b1580156106f157600080fd5b505af1158015610705573d6000803e3d6000fd5b505050506040513d602081101561071b57600080fd5b505050505050565b805160009060209060011901061580156107405750610122825111155b1515610790576040805160e560020a62461bcd0281526020600482015260146024820152606260020a731a5b9d985b1a59081c1c9bdbd988199bdc9b585d02604482015290519081900360640190fd5b6020820151600090859060029060f060020a900486845b600981101561088e576001831615156107c3576000955061082a565b6020840193508361ffff16885110151515610823576040805160e560020a62461bcd0281526020600482015260156024820152605b60020a740e0e4dedecc40dcdee840d8dedcce40cadcdeeaced02604482015290519081900360640190fd5b8388015195505b84158015610836575085155b156108445760009450610872565b60018216151561086257846000528560205260406000209450610872565b8560005284602052604060002094505b600261ffff8416049250600261ffff83160491506001016107a7565b509298975050505050505050565b6000808212156108b257816000190290506108b5565b50805b91905056fea165627a7a72305820b46371bc3d392ca51d2277f2b1a2359ee3c081181e21e927105e830b178a7ef60029"; +export const abi = [ + { + constant: false, + inputs: [ + { + name: "balanceCardId", + type: "uint256" + }, + { + name: "proof", + type: "bytes" + }, + { + name: "placedVotes", + type: "int256" + }, + { + name: "newVotes", + type: "int256" + } + ], + name: "castBallot", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function" + }, + { + constant: false, + inputs: [ + { + name: "v", + type: "uint8" + }, + { + name: "r", + type: "bytes32" + }, + { + name: "s", + type: "bytes32" + } + ], + name: "consolidate", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function" + } +]; From 5eb5126b6a3715d43cd158c710020b37c7703929 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:54:34 +0300 Subject: [PATCH 022/102] Create VOLT specific config --- src/volt/config.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/volt/config.js diff --git a/src/volt/config.js b/src/volt/config.js new file mode 100644 index 000000000..3fa4da919 --- /dev/null +++ b/src/volt/config.js @@ -0,0 +1,7 @@ +export const voltConfig = { + CONTRACT_VOICE_CREDITS: "0x8f8FDcA55F0601187ca24507d4A1fE1b387Db90B", + CONTRACT_VOICE_TOKENS: "0x3442c197cc858bED2476BDd9c7d4499552780f3D", + CONTRACT_VOICE_BALANCE_CARD: "0xCD1b3a9a7B5f84BC7829Bc7e6e23adb1960beE97", + BALANCE_CARD_COLOR: "49159", + VOICE_CREDITS_COLOR: "4" +}; From 6c3dc8d6dda04d81630f585cd475814e10f4e4b7 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:55:04 +0300 Subject: [PATCH 023/102] Create components for Vote Controls --- src/volt/components/VoteControls/Choice.js | 51 ++++++ .../components/VoteControls/GridDisplay.js | 105 +++++++++++ src/volt/components/VoteControls/index.js | 163 ++++++++++++++++++ src/volt/components/VoteControls/styles.js | 79 +++++++++ 4 files changed, 398 insertions(+) create mode 100644 src/volt/components/VoteControls/Choice.js create mode 100644 src/volt/components/VoteControls/GridDisplay.js create mode 100644 src/volt/components/VoteControls/index.js create mode 100644 src/volt/components/VoteControls/styles.js diff --git a/src/volt/components/VoteControls/Choice.js b/src/volt/components/VoteControls/Choice.js new file mode 100644 index 000000000..13ad36eeb --- /dev/null +++ b/src/volt/components/VoteControls/Choice.js @@ -0,0 +1,51 @@ +import React from "react"; +import styled from "styled-components"; +import { Flex } from "rimble-ui"; + +const OptionContainer = styled.div` + width: 100%; + display: grid; + grid-template-columns: ${({ num = 1 }) => `repeat(${num}, auto)`}; + grid-column-gap: 16px; + margin-bottom: 16px; +`; + +const Option = styled(Flex).attrs({ + p: 2, + borderRadius: 2, + alignItems: "center", + justifyContent: "center", + fontSize: 2, + color: "voltBrandWhite" +})` + font-weight: bold; + cursor: pointer; + text-transform: uppercase; + border: 3px solid white; + border-color: ${({ selected = false, optionColor, theme }) => + selected ? theme.colors[optionColor] : theme.colors.voltBrandWhite}; + background-color: ${({ selected = false, optionColor, theme }) => + selected ? theme.colors[optionColor] : theme.colors.voltBrandMain}; +`; + +export const Choice = props => { + const { options, selection } = props; + const { onChange } = props; + return ( + + {options.map(option => { + const { value, color } = option; + const selected = value === selection; + return ( + + ); + })} + + ); +}; diff --git a/src/volt/components/VoteControls/GridDisplay.js b/src/volt/components/VoteControls/GridDisplay.js new file mode 100644 index 000000000..99d6dc3ca --- /dev/null +++ b/src/volt/components/VoteControls/GridDisplay.js @@ -0,0 +1,105 @@ +import React from "react"; +import styled from "styled-components"; +import { Flex, Text } from "rimble-ui"; +import { pad } from "../../utils"; + +const GridContainer = styled(Flex).attrs(() => ({}))` + flex-direction: column; + justify-content: center; +`; + +const Label = styled(Text).attrs(() => ({ + color: "white", + fontSize: 2, + mb: 1 +}))` + text-align: center; + text-transform: uppercase; + letter-spacing: 0.6px; +`; + +const ValueContainer = styled.div` + --border-width: 3px; + display: grid; + grid-template-columns: repeat(3, 40px); + border: var(--border-width) solid white; +`; + +const Cell = styled(Flex).attrs(() => ({ + alignItems: "center", + justifyContent: "center", + p: 2, + fontSize: 4 +}))` + border: var(--border-width) solid white; + border-top-width: 0; + border-bottom-width: 0; + &:first-of-type, + &:last-of-type { + border-left-width: 0; + border-right-width: 0; + } +`; + +export const GridDisplay = ({ label, value }) => { + const numbers = pad(value).split(""); + return ( + + + + {numbers.map(number => { + return {number}; + })} + + + ); +}; + +const EquationContainer = styled(Flex).attrs(() => ({ + justifyContent: "space-between", + alignItems: "center", + mb: 3 +}))` + width: 100%; + max-width: 300px; +`; + +const EqualityContainer = styled(Flex).attrs(() => ({ + mx: 2, + flexDirection: "column", + height: "54px", + mt: 4 +}))` + align-items: center; + justify-content: center; +`; + +const Bar = styled(Flex).attrs(() => ({ + width: '20px', + height: "5px", + bg: "white", + mb: "4px" +}))` + &:last-of-type { + margin-bottom: 0; + } +`; + +const Equality = () => { + return ( + + + + + ); +}; + +export const Equation = ({ votes }) => { + return ( + + + + + + ); +}; diff --git a/src/volt/components/VoteControls/index.js b/src/volt/components/VoteControls/index.js new file mode 100644 index 000000000..f937c6ece --- /dev/null +++ b/src/volt/components/VoteControls/index.js @@ -0,0 +1,163 @@ +import React, { Component } from "react"; +import { Tx, Input, Output } from "leap-core"; +import SMT from "../../lib/SparseMerkleTree"; + +import { Equation } from "./GridDisplay"; +import { Choice } from "./Choice"; +import { + SliderLabels, + Container, + Label, + StyledSlider, + ActionButton +} from "./styles"; +import proposals from "../../proposals"; +import { votesToValue, getUTXOs } from "../../utils"; +import { abi, bytecode } from "../../contracts/voteBooth"; +import { voltConfig } from "../../config"; +import {getId} from "../../../services/plasma"; + +class VoteControls extends Component { + constructor(props) { + console.log(props.account); + super(props); + this.state = { + expanded: false, + votes: 0, + choice: "" + }; + this.collapse = this.collapse.bind(this); + this.expand = this.expand.bind(this); + this.setTokenNumber = this.setTokenNumber.bind(this); + this.setChoice = this.setChoice.bind(this); + this.submit = this.submit.bind(this); + } + setTokenNumber(event) { + const { target } = event; + this.setState(state => ({ + ...state, + votes: target.value + })); + } + setChoice({ value }) { + this.setState(state => ({ + ...state, + choice: value + })); + } + + async submit() { + const { account, plasma } = this.props; + const { choice, votes } = this.state; + console.log(`Submit Choice: ${votes} for ${choice} from ${account}`); + const { hex, string } = votesToValue(votes); + const sign = choice === "Yes" ? 1 : -1; + const voiceCredits = sign * votes * 10 ** 18; + + const { boothAddress } = proposals[0]; + const balanceCardColor = voltConfig.BALANCE_CARD_COLOR; + const balanceCardAddress = voltConfig.CONTRACT_VOICE_BALANCE_CARD; + const balanceCards = await plasma.getUnspent(account, balanceCardColor); + console.log({balanceCards}); + const balanceCard = balanceCards[0]; + const balanceCardId = getId(balanceCard); + + const voiceCreditsColor = voltConfig.VOICE_CREDITS_COLOR; + const voiceCreditsUTXOs = await plasma.getUnspent(account, voiceCreditsColor); + console.log({voiceCreditsUTXOs}); + + // TODO: Do we need to get several here? + const gasUTXOs = await plasma.getUnspent(boothAddress, 0); + console.log(gasUTXOs); + const gas = gasUTXOs[0]; + const script = Buffer.from(bytecode, "hex"); + + const condition = Tx.spendCond( + [ + new Input({ + prevout: gas.outpoint, + script + }), + new Input({ + prevout: balanceCard.outpoint + }), + new Input({ + prevout: voiceCreditsUTXOs[1].outpoint // HARDCODED, FIX TO PROPER SOLUTION! + }) + ], + [ + // Empty for now, we can always get them back from check + ] + ); + + // TODO: Read SMT from local storage + const tree = new SMT(9); + const proof = tree.createMerkleProof(0); + + const data = plasma.eth.abi.encodeParameters( + ["uint256", "bytes", "int256", "int256"], + [ balanceCardId, proof, votes, 0 ] // + ); + + condition.inputs[0].setMsgData(data); + + // TODO: Sign transaction here + // TODO: Check Spending Condition + // TODO: Update outputs accordingly + // TODO: Submit transition to blockchain + // TODO: Update local SMT + } + + collapse() { + this.setState(state => { + return { + ...state, + expanded: false + }; + }); + } + expand() { + this.setState(state => { + return { + ...state, + expand: true + }; + }); + } + render() { + const { expanded, votes, choice } = this.state; + const { credits } = this.props; + const max = Math.floor(Math.sqrt(credits)) || 0; + const options = [ + { value: "Yes", color: "voltBrandGreen" }, + { value: "No", color: "voltBrandRed" } + ]; + const disabled = votes < 1 || choice === ""; + return ( + + + + + + + + + + Send Vote + + + ); + } +} + +export default VoteControls; diff --git a/src/volt/components/VoteControls/styles.js b/src/volt/components/VoteControls/styles.js new file mode 100644 index 000000000..87c7a4e06 --- /dev/null +++ b/src/volt/components/VoteControls/styles.js @@ -0,0 +1,79 @@ +import styled from "styled-components"; +import {Flex, Slider, Text} from "rimble-ui"; + +export const Container = styled(Flex).attrs(() => ({ + p: 4, + width: "100%", + bg: "voltBrandMain", + color: "voltBrandWhite", + alignItems: "center", + justifyContent: "center" +}))` + position: fixed; + bottom: 0; + z-index: 2; + flex-direction: column; +`; + +export const SliderLabels = styled(Flex).attrs(() => ({ + mb: 3, + width: "100%", + justifyContent: "space-between", + alignItems: "center" +}))``; + +export const Label = styled(Text).attrs(() => ({ + fontSize: 2, + color: "white" +}))``; + +export const StyledSlider = styled(Slider).attrs(() => ({}))` + background-color: transparent; + user-select: none; + width: 100%; + min-width: auto; + display: flex; + align-items: center; + &:focus, + &::selection { + border: 0; + outline: none; + } + &::-webkit-slider-runnable-track { + background-color: white; + } + &:before { + content: ""; + height: 20px; + width: 4px; + border-radius: 2px; + background-color: white; + display: block; + } + &:after { + content: ""; + height: 20px; + width: 4px; + border-radius: 2px; + background-color: white; + display: block; + } +`; + +export const ActionButton = styled.button.attrs(() => ({}))` + color: white; + font-size: 24px; + font-weight: bold; + cursor: pointer; + align-items: center; + justify-content: center; + padding: 8px; + width: 100%; + border: 3px solid white; + border-radius: 8px; + background: transparent; + text-transform: uppercase; + &:disabled { + opacity: 0.3; + } +`; From 076116a2684cd2743b4286f5eb363ae4ce5164fd Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:55:20 +0300 Subject: [PATCH 024/102] Create components for Receipt view --- src/volt/components/Receipt/index.js | 108 ++++++++++++++++++++++++++ src/volt/components/Receipt/styles.js | 0 2 files changed, 108 insertions(+) create mode 100644 src/volt/components/Receipt/index.js create mode 100644 src/volt/components/Receipt/styles.js diff --git a/src/volt/components/Receipt/index.js b/src/volt/components/Receipt/index.js new file mode 100644 index 000000000..f3e99fae3 --- /dev/null +++ b/src/volt/components/Receipt/index.js @@ -0,0 +1,108 @@ +import React from "react"; +import { FullScreenContainer } from "../Common"; +import { Flex, Text, Image } from "rimble-ui"; +import styled from "styled-components"; +import voteYes from "../../assets/vote-yes.png"; +import voteNo from "../../assets/vote-no.png"; +import voteAccepted from "../../assets/vote-accepted.svg"; +import { ActionClose } from "../Common"; + +const Container = styled(Flex).attrs(() => ({ + bg: "voltBrandMain", + px: "4rem", + pt: "4rem", + pb: "3rem", + flexDirection: "column", + justifyContent: "space-between", + alignItems: "center" +}))` + position: absolute; + width: 100%; + height: 100%; +`; + +const MiddlePart = styled(Flex).attrs(() => ({ + flexDirection: "column", + alignItems: "center", + justifyContent: "center" +}))``; + +const Title = styled(Text).attrs(() => ({ + fontSize: 4 +}))` + color: white; + text-align: center; +`; + +const Graphic = styled(Image).attrs(() => ({}))` + height: auto; + background-color: transparent; +`; + +const ReceiptInfo = styled(Flex).attrs(() => ({ + mb: "3rem", + flexDirection: "column" +}))``; + +const ReceiptFieldContainer = styled(Flex).attrs(() => ({ + mb: 3 +}))` + justify-content: center; +`; + +const Label = styled(Text).attrs(() => ({ + fontSize: 2 +}))` + color: white; + font-weight: normal; + text-transform: uppercase; + text-align: center; + letter-spacing: 3px; + + &:after { + content: ":"; + display: inline-block; + margin-right: 8px; + } +`; + +const Value = styled(Label)` + font-weight: bold; + &:after { + content: ""; + display: none; + } +`; + +const ReceiptField = ({ label, value }) => { + return ( + + + {value} + + ); +}; + +const Receipt = props => { + const { voteType, votes = 0 } = props; + const voteImage = voteType === "yes" ? voteYes : voteNo; + return ( + + + Deine Quittung + + + + + + + + + + Schließen + + + ); +}; + +export default Receipt; diff --git a/src/volt/components/Receipt/styles.js b/src/volt/components/Receipt/styles.js new file mode 100644 index 000000000..e69de29bb From 348425e95e759bda4a57f403083b82c584c584f6 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:55:30 +0300 Subject: [PATCH 025/102] Create Progress component --- src/volt/components/Progress/index.js | 36 +++++++++++ src/volt/components/Progress/styles.js | 84 ++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/volt/components/Progress/index.js create mode 100644 src/volt/components/Progress/styles.js diff --git a/src/volt/components/Progress/index.js b/src/volt/components/Progress/index.js new file mode 100644 index 000000000..dbbc46886 --- /dev/null +++ b/src/volt/components/Progress/index.js @@ -0,0 +1,36 @@ +import React from "react"; +import { + Container, + TopPart, + Message, + InfiniteProgress, + LogoContainer, + Logotype +} from "./styles"; +import { FullScreenContainer } from "../Common"; + +const Logo = () => { + return ( + + Volt + Deora.Earth + + ); +}; + +const Progress = props => { + const { message } = props; + return ( + + + + {message} + + + + + + ); +}; + +export default Progress; diff --git a/src/volt/components/Progress/styles.js b/src/volt/components/Progress/styles.js new file mode 100644 index 000000000..7d1e58ae9 --- /dev/null +++ b/src/volt/components/Progress/styles.js @@ -0,0 +1,84 @@ +import { Flex, Text } from "rimble-ui"; +import styled, { keyframes } from "styled-components"; + +export const Container = styled(Flex).attrs(() => ({ + bg: "voltBrandMain", + px: '4rem', + pt: '8rem', + pb: '3rem', + flexDirection: "column", + justifyContent: "space-between" +}))` + position: absolute; + width: 100%; + height: 100%; +`; + +export const TopPart = styled(Flex).attrs(() => ({ + flexDirection: "column", + justifyContent: "center" +}))``; + +export const Message = styled(Text).attrs(() => ({ + mb: 4, + fontSize: '18px', + color: "voltBrandWhite", + textAlign: "center" +}))` + font-weight: bold; +`; + +const loading = keyframes` + + 0%,10% { + left: 0; + right: 100%; + } + + 50%{ + left: 0; + right: 0; + } + + 90%, 100% { + left: 100%; + right: 0; + } +`; + +export const InfiniteProgress = styled(Flex).attrs(() => ({ + bg: "voltBrandDimPurple", + borderRadius: 3 +}))` + width: 100%; + height: 6px; + position: relative; + overflow: hidden; + &:after { + content: ""; + display: block; + left: 0; + right: 0; + height: 100%; + background-color: white; + position: absolute; + border-radius: 16px; + animation: ${loading} 0.35s linear alternate infinite; + } +`; + +export const LogoContainer = styled(Flex).attrs(() => ({ + flexDirection: "column" +}))` + align-items: center; + justify-content: center; +`; + +export const Logotype = styled(Text).attrs(() => ({ + fontSize: 4, + color: "voltBrandWhite", + mb: 1 +}))` + text-transform: uppercase; + font-weight: bold; +`; From 61f56b8ad0b112dc1ce34cf167a4ccf040cdaffb Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:55:54 +0300 Subject: [PATCH 026/102] Create basic Header component that will consume passed props --- src/volt/components/Header/index.js | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/volt/components/Header/index.js diff --git a/src/volt/components/Header/index.js b/src/volt/components/Header/index.js new file mode 100644 index 000000000..2ba371b1d --- /dev/null +++ b/src/volt/components/Header/index.js @@ -0,0 +1,45 @@ +import React from "react"; +import { Flex, Text } from "rimble-ui"; +import styled from "styled-components"; + +const HeaderContainer = styled(Flex).attrs(() => ({ + p: 2, + flexDirection: "column", + bg: "voltBrandMain", + height: "160px" +}))` + align-items: center; + justify-content: center; +`; + +const BalanceContainer = styled(Flex).attrs(() => ({ + p: 2, + justifyContent: "space-between", + width: "100%" +}))``; + +const Label = styled(Text).attrs(() => ({ + color: "voltBrandWhite" +}))``; + +const Balance = styled(Text).attrs(() => ({ + color: "voltBrandWhite" +}))` + font-weight: bold; +`; + +export const Header = props => { + const { tokens, credits } = props; + return ( + + + + {tokens || "--"} + + + + {credits || "--"} + + + ); +}; From 0980f5974950c1caa4035505ac2fe2bb7f01cfd1 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:56:11 +0300 Subject: [PATCH 027/102] Create some Common components that are use in other components --- src/volt/components/Common/index.js | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/volt/components/Common/index.js diff --git a/src/volt/components/Common/index.js b/src/volt/components/Common/index.js new file mode 100644 index 000000000..6783eb4ed --- /dev/null +++ b/src/volt/components/Common/index.js @@ -0,0 +1,34 @@ +import styled from "styled-components"; +import { Flex } from "rimble-ui"; + +export const MainContainer = styled(Flex).attrs(() => ({ + flexDirection: "column", + bg: "#4d606d" +}))` + position: relative; + height: 100vh; +`; + +export const FullScreenContainer = styled(Flex).attrs(() => ({}))` + position: fixed; + top: 0; + bottom: 0; + width: 100%; + z-index: 5; +`; + +export const ActionClose = styled.button.attrs(() => ({}))` + color: ${({ theme }) => theme.colors.voltBrandMain}; + font-size: 16px; + font-weight: bold; + cursor: pointer; + align-items: center; + justify-content: center; + padding: 5px 10px; + border: 3px solid white; + border-radius: 8px; + background-color: white; + &:disabled { + opacity: 0.3; + } +`; From 6ea8ad4d6e2bd0e8e9f9bc85386b620d7a57ab06 Mon Sep 17 00:00:00 2001 From: Max Daunarovich Date: Thu, 29 Aug 2019 22:58:18 +0300 Subject: [PATCH 028/102] Refactor App.js Remove unused chunks of code. Load balance card after Dapparatus was initialized. Update voice credits and voice tokens with every poll. Prettify. --- src/App.js | 2030 ++++++++++++++++++++-------------------------------- 1 file changed, 771 insertions(+), 1259 deletions(-) diff --git a/src/App.js b/src/App.js index 59fee616a..f9a5396f9 100644 --- a/src/App.js +++ b/src/App.js @@ -1,112 +1,90 @@ -import React, { Component } from 'react'; -import { Tx, Input, Output, Util } from 'leap-core'; +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 { 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 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 GoellarsBalance from "./components/GoellarsBalance"; +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 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 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"; - -let LOADERIMAGE = burnerlogo -let HARDCODEVIEW// = "loader"// = "receipt" +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 VoteControls from "./volt/components/VoteControls"; +import Progress from "./volt/components/Progress"; +import Receipt from "./volt/components/Receipt"; +import { fetchBalanceCard, votesToValue } from "./volt/utils"; +import SMT from "./volt/lib/SparseMerkleTree"; + +let LOADERIMAGE = burnerlogo; +let HARDCODEVIEW; // = "loader"// = "receipt" const CONFIG = getConfig(); -// TODO: Consolidate this with theme.js -let mainStyle = { - width:"100%", - 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+")", - backgroundColor:mainStyle.mainColor, - color:"#FFFFFF", - whiteSpace:"nowrap", - cursor:"pointer", - }, - secondary: { - border:"2px solid "+mainStyle.mainColor, - color:mainStyle.mainColor, - whiteSpace:"nowrap", - cursor:"pointer", - } -} +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 +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 +let interval; export default 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, @@ -116,57 +94,62 @@ 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: '', + possibleNewPrivateKey: "", // NOTE: USD in exchangeRates is undefined, such that any result using this // number becomes NaN intentionally until it's defined. exchangeRates: {} }; 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) + }); + } catch (e) { + console.log(e); + } + }); + } catch (e) { + console.log(e); + } + + this.poll = this.poll.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) { + currencyDisplay(amount, toParts = false, convert = true) { const { account } = this.state; - const locale = getStoredValue('i18nextLng'); - const symbol = getStoredValue('currency', account) || CONFIG.CURRENCY.DEFAULT_CURRENCY; + const locale = getStoredValue("i18nextLng"); + const symbol = + getStoredValue("currency", account) || CONFIG.CURRENCY.DEFAULT_CURRENCY; if (convert) { amount = this.convertCurrency(amount, `${symbol}/USD`); } const formatter = new Intl.NumberFormat(locale, { - style: 'currency', + style: "currency", currency: symbol, maximumFractionDigits: 2 }); @@ -197,298 +180,326 @@ export default class App extends Component { const baseRate = exchangeRates[base]; const counterRate = exchangeRates[counter]; - return baseRate / counterRate * amount; + return (baseRate / counterRate) * amount; } - 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") - 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") + // 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 = 'WEB3'; + contextElement.innerHTML = 'WEB3';*/ } } - }) + }); } - componentDidMount(){ - document.body.style.backgroundColor = mainStyle.backgroundColor - - this.detectContext() + componentDidMount() { + this.detectContext(); - 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 }); + 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; } - 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 }); + 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]; } - 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, 1500); } 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 - - if(this.state.mainnetweb3){ - - try{ - ethBalance = await this.state.mainnetweb3.eth.getBalance(this.state.account) - ethBalance = this.state.mainnetweb3.utils.fromWei(""+ethBalance,'ether') - - 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() - } - } - 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 { + account, + xdaiweb3, + voiceCreditsContract, + voiceTokensContract + } = this.state; + if (account) { + let creditsBalance = 0; + let tokensBalance = 0; + + if (xdaiweb3) { + const { + utils: { fromWei } + } = xdaiweb3; + + const voiceCreditsInWei = await voiceCreditsContract.methods + .balanceOf(account) + .call(); + creditsBalance = fromWei(voiceCreditsInWei, "ether"); + + const voiceTokensInWei = await voiceTokensContract.methods + .balanceOf(account) + .call(); + tokensBalance = fromWei(voiceTokensInWei, "ether"); } - const plasma = this.state.xdaiweb3; - const passports = await fetchAllPassports(plasma, this.state.account); - - this.setState({passports, ethBalance,daiBalance,xdaiBalance,balance:xdaiBalance,hasUpdateOnce:true}) - } - - - } - 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); + // TODO: Fetch Balance Card here - // 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; + // const plasma = this.state.xdaiweb3; + // const passports = await fetchAllPassports(plasma, this.state.account); - this.setState({ exchangeRates: pairs }); + // Update balance card data + const balanceCard = await fetchBalanceCard( + this.state.xdaiweb3, + this.state.account + ); + this.setState(state => ({ + ...state, + balanceCard + })); + + this.setState(state => ({ + ...state, + 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,11 +514,11 @@ 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) => { clearTimeout(this.alertTimeout); this.setState({ alert }); if (alert && hide) { @@ -516,972 +527,460 @@ export default class App extends Component { }, 2000); } }; - goBack(view="main"){ - console.log("GO BACK") - this.changeView(view) - this.setState({scannerOpen: false }) - setTimeout(()=>{window.scrollTo(0,0)},60) + 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) { 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) - }} - /> -
- ) - } - - let eventParser = "" - - - let extraHead = "" - if(this.state.extraHeadroom){ - extraHead = ( -
-
- ) - } - - let totalBalance = parseFloat(this.state.ethBalance) * parseFloat(this.state.ethprice) + parseFloat(this.state.daiBalance) + parseFloat(this.state.xdaiBalance) - let header = ( -
-
- ) - if(web3){ - header = ( -
- ) - } + render() { + const { creditsBalance, tokensBalance, account, balanceCard } = this.state; + const { xdaiweb3 } = this.state; 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') - }} - /> -
- - ) - } - - // NOTE: This view shows specific historical transactions to - // bity.com - if (view.includes("bity_")) { - const orderId = view.replace("bity_","") - return ( -
- - - - - { - this.changeView('main') - }} - /> -
- - ) - } - - if (view.includes("loader_")) { - const network = view.replace("loader_"); - return ( -
-
- - -
- -
- ); - } - - const sendByScan = ( - { - this.changeAlert("danger",error) - }} - /> - ) - - 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': - 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} - - - -
- ); - 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 ( -
-
- - -
- -
- ); - case 'reader': - return ( -
-
- -
- -
+ +
+ + + { + //console.log("DAPPARATUS UPDATE",state) + + 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-- + ) { + //console.log(" ++ Parsing *CURRENT BLOCK* Block "+b+" for transactions...") + 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 + }); + //console.log("~~ DONE PARSING SET ~~") + }); + } + }); + } + }} + />
- ) + ); } } @@ -1490,16 +989,16 @@ export default class App extends Component { // NOTE: This function is used heavily by legacy code. We've reimplemented it's // body though. async function tokenSend(to, value, gasLimit, txData, cb) { - let { account, web3, xdaiweb3, metaAccount } = this.state - if(typeof gasLimit === "function"){ - cb = gasLimit + let { account, web3, xdaiweb3, metaAccount } = this.state; + if (typeof gasLimit === "function") { + cb = gasLimit; } - if(typeof txData === "function"){ - cb = txData + if (typeof txData === "function") { + cb = txData; } - value = xdaiweb3.utils.toWei(""+value, "ether") + value = xdaiweb3.utils.toWei("" + value, "ether"); const color = await xdaiweb3.getColor(CONFIG.SIDECHAIN.DAI_ADDRESS); try { const receipt = await tokenSendV2( @@ -1510,13 +1009,13 @@ async function tokenSend(to, value, gasLimit, txData, cb) { xdaiweb3, web3, metaAccount && metaAccount.privateKey - ) + ); cb(null, receipt); - } catch(err) { + } catch (err) { cb({ error: err, - request: { account, to, value, color }, + request: { account, to, value, color } }); // NOTE: The callback cb of tokenSend is not used correctly in the expected // format cb(error, receipt) throughout the app. We hence cannot send @@ -1528,11 +1027,14 @@ async function tokenSend(to, value, gasLimit, txData, cb) { } async function tokenSendV2(from, to, value, color, xdaiweb3, web3, privateKey) { - const unspent = await xdaiweb3.getUnspent(from, color) + const unspent = await xdaiweb3.getUnspent(from, color); let transaction; if (Util.isNST(color)) { - const { outpoint, output: { data }} = unspent.find( + const { + outpoint, + output: { data } + } = unspent.find( ({ output }) => Number(output.color) === Number(color) && equal(bi(output.value), bi(value)) @@ -1541,10 +1043,12 @@ async function tokenSendV2(from, to, value, color, xdaiweb3, web3, privateKey) { const outputs = [new Output(value, to, color, data)]; transaction = Tx.transfer(inputs, outputs); } else { - transaction = Tx.transferFromUtxos(unspent, from, to, value, color) + transaction = Tx.transferFromUtxos(unspent, from, to, value, color); } - const signedTx = privateKey ? await transaction.signAll(privateKey) : await transaction.signWeb3(web3); + const signedTx = privateKey + ? await transaction.signAll(privateKey) + : await transaction.signWeb3(web3); const rawTx = signedTx.hex(); // NOTE: Leapdao's Plasma implementation currently doesn't return receipts. @@ -1561,15 +1065,23 @@ async function tokenSendV2(from, to, value, color, xdaiweb3, web3, privateKey) { try { // web3 hangs here on invalid txs, trying to get receipt? // await this.web3.eth.sendSignedTransaction(tx.hex()); - await new Promise( - (resolve, reject) => { - xdaiweb3.currentProvider.send( - { jsonrpc: '2.0', id: 42, method: 'eth_sendRawTransaction', 'params': [rawTx] }, - (err, res) => { if (err) { return reject(err); } resolve(res); } - ); - } - ); - } catch(err) { + await new Promise((resolve, reject) => { + xdaiweb3.currentProvider.send( + { + jsonrpc: "2.0", + id: 42, + method: "eth_sendRawTransaction", + params: [rawTx] + }, + (err, res) => { + if (err) { + return reject(err); + } + resolve(res); + } + ); + }); + } catch (err) { // ignore for now console.log(err); // NOTE: Leap's node currently doesn't implement the "newBlockHeaders" @@ -1587,7 +1099,7 @@ async function tokenSendV2(from, to, value, color, xdaiweb3, web3, privateKey) { // } } - let res = await xdaiweb3.eth.getTransaction(signedTx.hash()) + let res = await xdaiweb3.eth.getTransaction(signedTx.hash()); if (res && res.blockHash) { receipt = res; @@ -1595,7 +1107,7 @@ async function tokenSendV2(from, to, value, color, xdaiweb3, web3, privateKey) { } // wait ~100ms - await new Promise((resolve) => setTimeout(() => resolve(), 100)); + await new Promise(resolve => setTimeout(() => resolve(), 100)); } if (receipt) { @@ -1605,28 +1117,28 @@ async function tokenSendV2(from, to, value, color, xdaiweb3, web3, privateKey) { throw new Error("Transaction wasn't included into a block."); } -let sortByBlockNumberDESC = (a,b)=>{ - if(b.blockNumber>a.blockNumber){ - return -1 +let sortByBlockNumberDESC = (a, b) => { + if (b.blockNumber > a.blockNumber) { + return -1; } - if(b.blockNumber{ - if(b.blockNumber { + if (b.blockNumber < a.blockNumber) { + return -1; } - if(b.blockNumber>a.blockNumber){ - return 1 + if (b.blockNumber > a.blockNumber) { + return 1; } - return 0 -} + return 0; +}; // ToDo: do not mutate native prototypes /* eslint-disable no-extend-native */ String.prototype.replaceAll = function(search, replacement) { - var target = this; - return target.replace(new RegExp(search, 'g'), replacement); + var target = this; + return target.replace(new RegExp(search, "g"), replacement); }; From 0f60b4dc4e6b4f1ee11ffae5f5db2cf2f508e648 Mon Sep 17 00:00:00 2001 From: Maksim Daunarovich Date: Mon, 2 Sep 2019 15:39:27 +0300 Subject: [PATCH 029/102] Big initial update (#14) * Parse int when color is passed as string * Use ethers provider builder for plasma rpc * Pass more state params from App to VoteControls * Add ethers package * Add replaceAll, padHEx and toHex utilities * Update proposals json * Add voting booth artifact * Ad new bytecodes and templates * Add BallotBox artifact * Add voice tokens color * Slow down progress animation * Add onClose prop to receipt * Add keys to mapped components * Refactor VoteControls * Add menu and accompany state value * Adjust space scale * Add logos and hamburger icon * Create Menu Component. Implements #5 * Implement Menu screen * Adjust padding of common container * Add bottom margin to allow stacking * Add ballot box and start adjusting vote controls * Add grey colors to list * Add contains utility method * Create proposal list parent and child components * Create SortControls component * Add FilterControls comonent * Add icon assets * Add Star component to Common * Add pointer style to Hamburger icon * Add basic filtering * Start Footer component * Add scrolling to proposals list * Add footer to app layout. Remove unused components --- package-lock.json | 126 +- package.json | 1 + src/App.js | 245 +- src/theme.js | 7 +- src/volt/assets/icn-close.svg | 6 + src/volt/assets/icn-favorites.png | Bin 0 -> 940 bytes src/volt/assets/icn-hamburger.svg | 1 + src/volt/assets/icn-search.svg | 6 + src/volt/assets/icn-sort.svg | 1 + src/volt/assets/logo-deora.png | Bin 0 -> 3180 bytes src/volt/assets/logo-volt-europa.png | Bin 0 -> 2250 bytes src/volt/components/Common/index.js | 11 +- src/volt/components/FilterControls/index.js | 70 + src/volt/components/Footer/History.js | 0 src/volt/components/Footer/index.js | 14 + src/volt/components/Footer/styles.js | 83 + src/volt/components/Header/index.js | 63 +- src/volt/components/Header/styles.js | 130 + src/volt/components/Menu/index.js | 22 + src/volt/components/Menu/styles.js | 28 + src/volt/components/Progress/styles.js | 9 +- .../ProposalsList/SingleProposal/index.js | 36 + .../ProposalsList/SingleProposal/styles.js | 52 + .../ProposalsList/VoteRecord/index.js | 16 + .../ProposalsList/VoteRecord/styles.js | 67 + src/volt/components/ProposalsList/index.js | 27 + src/volt/components/ProposalsList/styles.js | 9 + src/volt/components/Receipt/index.js | 4 +- src/volt/components/SortControls/index.js | 55 + src/volt/components/VoteControls/Choice.js | 1 + .../components/VoteControls/GridDisplay.js | 4 +- src/volt/components/VoteControls/index.js | 349 +- src/volt/components/VoteControls/styles.js | 4 + src/volt/config.js | 3 +- src/volt/contracts/BallotBox.json | 9222 +++++++++++++++++ src/volt/contracts/VotingBooth.json | 7660 ++++++++++++++ src/volt/contracts/ballotBox.js | 129 + src/volt/contracts/voteBooth.js | 15 +- src/volt/proposals.js | 8 +- src/volt/utils.js | 29 +- 40 files changed, 18300 insertions(+), 213 deletions(-) create mode 100644 src/volt/assets/icn-close.svg create mode 100644 src/volt/assets/icn-favorites.png create mode 100644 src/volt/assets/icn-hamburger.svg create mode 100644 src/volt/assets/icn-search.svg create mode 100644 src/volt/assets/icn-sort.svg create mode 100644 src/volt/assets/logo-deora.png create mode 100644 src/volt/assets/logo-volt-europa.png create mode 100644 src/volt/components/FilterControls/index.js create mode 100644 src/volt/components/Footer/History.js create mode 100644 src/volt/components/Footer/index.js create mode 100644 src/volt/components/Footer/styles.js create mode 100644 src/volt/components/Header/styles.js create mode 100644 src/volt/components/Menu/index.js create mode 100644 src/volt/components/Menu/styles.js create mode 100644 src/volt/components/ProposalsList/SingleProposal/index.js create mode 100644 src/volt/components/ProposalsList/SingleProposal/styles.js create mode 100644 src/volt/components/ProposalsList/VoteRecord/index.js create mode 100644 src/volt/components/ProposalsList/VoteRecord/styles.js create mode 100644 src/volt/components/ProposalsList/index.js create mode 100644 src/volt/components/ProposalsList/styles.js create mode 100644 src/volt/components/SortControls/index.js create mode 100644 src/volt/contracts/BallotBox.json create mode 100644 src/volt/contracts/VotingBooth.json create mode 100644 src/volt/contracts/ballotBox.js diff --git a/package-lock.json b/package-lock.json index 000ca11af..4db79665d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4790,7 +4790,7 @@ "s3": "^4.4.0", "scrypt": "^6.0.3", "sha3": "^1.2.2", - "solc": "^0.5.9", + "solc": "^0.5.10", "truffle-hdwallet-provider": "0.0.6", "typedarray-to-buffer": "^3.1.5", "web3": "^1.0.0-beta.33", @@ -8103,6 +8103,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": { @@ -8191,7 +8236,7 @@ "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "dev": true, "requires": { - "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb", + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "ethereumjs-util": "^5.1.1" }, "dependencies": { @@ -8466,22 +8511,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 +8543,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", @@ -9891,7 +9950,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -9909,11 +9969,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9926,15 +9988,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -10037,7 +10102,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -10047,6 +10113,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -10059,17 +10126,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -10086,6 +10156,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -10158,7 +10229,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -10168,6 +10240,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -10243,7 +10316,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -10273,6 +10347,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -10290,6 +10365,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -10328,11 +10404,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, @@ -27044,7 +27122,7 @@ "requires": { "underscore": "1.8.3", "web3-core-helpers": "1.0.0-beta.36", - "websocket": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2" + "websocket": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible" } }, "web3-shh": { diff --git a/package.json b/package.json index f87c6c4e3..37104adc5 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "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", diff --git a/src/App.js b/src/App.js index f9a5396f9..1198d00bc 100644 --- a/src/App.js +++ b/src/App.js @@ -6,30 +6,14 @@ 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 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"; @@ -49,11 +33,16 @@ import { 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 VoteControls from "./volt/components/VoteControls"; import Progress from "./volt/components/Progress"; import Receipt from "./volt/components/Receipt"; -import { fetchBalanceCard, votesToValue } from "./volt/utils"; +import { fetchBalanceCard, votesToValue, contains } from "./volt/utils"; import SMT from "./volt/lib/SparseMerkleTree"; +import ProposalsList from "./volt/components/ProposalsList"; +import SortContols from "./volt/components/SortControls"; +import FilterControls from "./volt/components/FilterControls"; +import Footer from "./volt/components/Footer"; let LOADERIMAGE = burnerlogo; let HARDCODEVIEW; // = "loader"// = "receipt" @@ -86,6 +75,7 @@ export default class App extends Component { } console.log("CACHED VIEW", view); super(props); + this.state = { web3: false, account: false, @@ -102,9 +92,11 @@ export default class App extends Component { 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: {} + isMenuOpen: false, + filterQuery: "", + filteredList: [], + sortedList: [], + favorites: {} }; this.alertTimeout = null; @@ -132,55 +124,12 @@ export default class App extends Component { this.poll = this.poll.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`); - } - - 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.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); } parseAndCleanPath(path) { @@ -257,6 +206,8 @@ export default class App extends Component { componentDidMount() { this.detectContext(); + this.loadProposals(); + console.log( "document.getElementsByClassName('className').style", document.getElementsByClassName(".btn").style @@ -329,7 +280,7 @@ export default class App extends Component { } } - interval = setInterval(this.poll, 1500); + interval = setInterval(this.poll, 2000); } componentWillUnmount() { @@ -819,20 +770,139 @@ export default class App extends Component { } } + // VOLT Methods + openMenu() { + this.setState(state => ({ + ...state, + isMenuOpen: true + })); + } + closeMenu() { + this.setState(state => ({ + ...state, + isMenuOpen: false + })); + } + async loadProposals() { + const endpoint = "https://www.npoint.io/documents/217ecb17f01746799a3b"; + const response = await fetch(endpoint); + const body = await response.json(); + const { + proposals: proposalsList, + voteEndTime, + voteStartTime + } = body.contents; + this.setState(state => ({ + ...state, + proposalsList, + sortedList: proposalsList, + filteredList: proposalsList, + filterQuery: "", + voteStartTime, + voteEndTime + })); + } + + sort(param) { + const { filteredList } = this.state; + return () => { + console.log("Sort by:", param); + }; + } + + filterList(event) { + const query = event.target.value; + const { proposalsList, filteredList, filterQuery } = this.state; + + const lcQuery = query.toLowerCase(); + const searchList = lcQuery.includes(filterQuery) + ? filteredList + : proposalsList; + + const newList = searchList.filter(proposal => { + const inTitle = contains(proposal, "title", lcQuery); + const inDescription = contains(proposal, "description", lcQuery); + const inId = contains(proposal, "proposalId", lcQuery); + return inTitle || inDescription || inId; + }); + + this.setState(state => ({ + ...state, + filterQuery: query, + filteredList: newList + })); + } + + resetFilter() { + const { proposalsList, filteredList, filterQuery } = this.state; + this.setState(state => ({ + ...state, + filterQuery: "", + filteredList: proposalsList + })); + } + + toggleFavorites(id) { + const { account, favorites } = this.state; + const value = !!favorites[id]; + favorites[id] = !value; + + storeValues({ favorites: JSON.stringify(favorites) }, account); + + this.setState(state => ({ + ...state, + favorites + })); + } + render() { - const { creditsBalance, tokensBalance, account, balanceCard } = this.state; - const { xdaiweb3 } = this.state; + const { creditsBalance, tokensBalance } = this.state; + const { xdaiweb3, web3, account, metaAccount } = this.state; + const { + isMenuOpen, + sortedList, + filteredList, + filterQuery, + favorites + } = this.state; + const { voteStartTime, voteEndTime } = this.state; + const web3props = { plasma: xdaiweb3, web3, account, metaAccount }; return ( - -
- + {isMenuOpen && } +
+ + + +