├── .env
├── .env.example
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── contracts
├── ClaimHolder.sol
├── ClaimVerifier.sol
├── ERC725.sol
├── ERC735.sol
├── Identity.sol
└── KeyHolder.sol
├── data
└── .gitignore
├── dist
├── README
│ ├── Digital Identity ERC-725.webm
│ ├── Digital-Identity-ERC-725-Claim-Checker-Invalid.png
│ ├── Digital-Identity-ERC-725-Claim-Checker-Valid.png
│ ├── Digital-Identity-ERC-725-Claim-Checker.png
│ ├── Digital-Identity-ERC-725-Identities-Claims-1.png
│ ├── Digital-Identity-ERC-725-Identities-Claims-2.png
│ ├── Digital-Identity-ERC-725-Identities-Claims-3.png
│ ├── Digital-Identity-ERC-725-New-Identity.png
│ └── Digital-Identity-ERC-725.png
└── digital-identity.zip
├── index.js
├── issuer-services
├── _facebook.js
├── _github.js
├── _google.js
├── _linkedin.js
├── _simple.js
├── _twitter.js
├── app.js
├── config.json.eg
├── html.js
├── index.js
├── package.json
├── public
│ ├── app.js
│ ├── css
│ │ ├── app.css
│ │ ├── bootstrap.min.css
│ │ └── font-awesome.min.css
│ ├── dev.html
│ ├── favicon.ico
│ ├── fonts
│ │ ├── FontAwesome.otf
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.svg
│ │ ├── fontawesome-webfont.ttf
│ │ ├── fontawesome-webfont.woff
│ │ └── fontawesome-webfont.woff2
│ ├── images
│ │ └── logo.png
│ ├── index.html
│ ├── vendor.js
│ └── vendor
│ │ ├── driver.min.css
│ │ ├── driver.min.js
│ │ └── web3.min.js
└── standalone.js
├── package-lock.json
├── package.json
├── public
├── css
│ ├── bootstrap.min.css
│ └── font-awesome.min.css
├── dev.html
├── favicon.ico
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ └── fontawesome-webfont.woff2
├── images
│ └── logo.png
├── index.html
└── vendor
│ ├── driver.min.css
│ ├── driver.min.js
│ └── web3.min.js
├── scripts
├── accounts.js
└── deploy.js
├── src
├── Store.js
├── actions
│ ├── Identity.js
│ ├── Network.js
│ └── Wallet.js
├── components
│ ├── AccountChooser.js
│ ├── DetailRow.js
│ ├── Dropdown.js
│ ├── FormRow.js
│ ├── Loading.js
│ ├── Modal.js
│ ├── NavItem.js
│ └── NavLink.js
├── constants
│ └── Providers.js
├── contracts
│ ├── ClaimHolder.js
│ ├── ClaimVerifier.js
│ └── Identity.js
├── index.js
├── pages
│ ├── App.js
│ ├── _Init.js
│ ├── _Versions.js
│ ├── _driver.js
│ ├── console
│ │ ├── _Accounts.js
│ │ ├── _IPFS.js
│ │ ├── _Providers.js
│ │ └── index.js
│ ├── identity
│ │ ├── ClaimCheckerDetail.js
│ │ ├── Home.js
│ │ ├── IdentityDetail.js
│ │ ├── _Events.js
│ │ ├── _Summary.js
│ │ ├── _ValidateClaim.js
│ │ ├── _WalletChooser.js
│ │ ├── index.js
│ │ └── modals
│ │ │ ├── ClaimDetail.js
│ │ │ ├── KeyDetail.js
│ │ │ ├── _AddClaim.js
│ │ │ ├── _AddKey.js
│ │ │ ├── _Approve.js
│ │ │ ├── _CheckClaim.js
│ │ │ ├── _NewClaimIssuer.js
│ │ │ ├── _NewIdentity.js
│ │ │ ├── _NewVerifier.js
│ │ │ ├── _RemoveClaim.js
│ │ │ ├── _RemoveIdentity.js
│ │ │ └── _RemoveKey.js
│ └── initIssuer.js
├── reducers
│ ├── Identity.js
│ ├── Network.js
│ └── Wallet.js
└── utils
│ ├── balance.js
│ ├── decodeFn.js
│ ├── ipfsHash.js
│ ├── keyMirror.js
│ └── numberFormat.js
├── test
├── ClaimVerifier.js
├── Identity.js
└── _helper.js
├── webpack.config.js
├── webpack.prod.js
└── yarn.lock
/.env:
--------------------------------------------------------------------------------
1 | LOCAL=true
2 | RESET=true
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | LOCAL=true
2 | RESET=true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | public/app*.js
4 | public/vendor*.js
5 | public/css/app.css
6 | data/db
7 | data/ipfs
8 | data/OfficialIdentities.js
9 | NOTES
10 | src/contracts/deployed.js
11 | issuer-services/package-lock.json
12 | issuer-services/up.json
13 | issuer-services/config.json
14 | # VSCode
15 | .vscode
16 |
17 | # Environment file
18 | #.env
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "lts/*"
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 O2O Protocol
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/contracts/ClaimHolder.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | import './ERC735.sol';
4 | import './KeyHolder.sol';
5 |
6 | contract ClaimHolder is KeyHolder, ERC735 {
7 |
8 | mapping (bytes32 => Claim) claims;
9 | mapping (uint256 => bytes32[]) claimsByType;
10 |
11 | function addClaim(
12 | uint256 _claimType,
13 | uint256 _scheme,
14 | address _issuer,
15 | bytes _signature,
16 | bytes _data,
17 | string _uri
18 | )
19 | public
20 | returns (bytes32 claimRequestId)
21 | {
22 | bytes32 claimId = keccak256(_issuer, _claimType);
23 |
24 | if (msg.sender != address(this)) {
25 | require(keyHasPurpose(keccak256(msg.sender), 3), "Sender does not have claim signer key");
26 | }
27 |
28 | if (claims[claimId].issuer != _issuer) {
29 | claimsByType[_claimType].push(claimId);
30 | }
31 |
32 | claims[claimId].claimType = _claimType;
33 | claims[claimId].scheme = _scheme;
34 | claims[claimId].issuer = _issuer;
35 | claims[claimId].signature = _signature;
36 | claims[claimId].data = _data;
37 | claims[claimId].uri = _uri;
38 |
39 | emit ClaimAdded(
40 | claimId,
41 | _claimType,
42 | _scheme,
43 | _issuer,
44 | _signature,
45 | _data,
46 | _uri
47 | );
48 |
49 | return claimId;
50 | }
51 |
52 | function removeClaim(bytes32 _claimId) public returns (bool success) {
53 | if (msg.sender != address(this)) {
54 | require(keyHasPurpose(keccak256(msg.sender), 1), "Sender does not have management key");
55 | }
56 |
57 | /* uint index; */
58 | /* (index, ) = claimsByType[claims[_claimId].claimType].indexOf(_claimId);
59 | claimsByType[claims[_claimId].claimType].removeByIndex(index); */
60 |
61 | emit ClaimRemoved(
62 | _claimId,
63 | claims[_claimId].claimType,
64 | claims[_claimId].scheme,
65 | claims[_claimId].issuer,
66 | claims[_claimId].signature,
67 | claims[_claimId].data,
68 | claims[_claimId].uri
69 | );
70 |
71 | delete claims[_claimId];
72 | return true;
73 | }
74 |
75 | function getClaim(bytes32 _claimId)
76 | public
77 | constant
78 | returns(
79 | uint256 claimType,
80 | uint256 scheme,
81 | address issuer,
82 | bytes signature,
83 | bytes data,
84 | string uri
85 | )
86 | {
87 | return (
88 | claims[_claimId].claimType,
89 | claims[_claimId].scheme,
90 | claims[_claimId].issuer,
91 | claims[_claimId].signature,
92 | claims[_claimId].data,
93 | claims[_claimId].uri
94 | );
95 | }
96 |
97 | function getClaimIdsByType(uint256 _claimType)
98 | public
99 | constant
100 | returns(bytes32[] claimIds)
101 | {
102 | return claimsByType[_claimType];
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/contracts/ClaimVerifier.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | import './ClaimHolder.sol';
4 |
5 | contract ClaimVerifier {
6 |
7 | event ClaimValid(ClaimHolder _identity, uint256 claimType);
8 | event ClaimInvalid(ClaimHolder _identity, uint256 claimType);
9 |
10 | ClaimHolder public trustedClaimHolder;
11 |
12 | function ClaimVerifier(address _trustedClaimHolder) public {
13 | trustedClaimHolder = ClaimHolder(_trustedClaimHolder);
14 | }
15 |
16 | function checkClaim(ClaimHolder _identity, uint256 claimType)
17 | public
18 | returns (bool claimValid)
19 | {
20 | if (claimIsValid(_identity, claimType)) {
21 | emit ClaimValid(_identity, claimType);
22 | return true;
23 | } else {
24 | emit ClaimInvalid(_identity, claimType);
25 | return false;
26 | }
27 | }
28 |
29 | function claimIsValid(ClaimHolder _identity, uint256 claimType)
30 | public
31 | constant
32 | returns (bool claimValid)
33 | {
34 | uint256 foundClaimType;
35 | uint256 scheme;
36 | address issuer;
37 | bytes memory sig;
38 | bytes memory data;
39 |
40 | // Construct claimId (identifier + claim type)
41 | bytes32 claimId = keccak256(trustedClaimHolder, claimType);
42 |
43 | // Fetch claim from user
44 | ( foundClaimType, scheme, issuer, sig, data, ) = _identity.getClaim(claimId);
45 |
46 | bytes32 dataHash = keccak256(_identity, claimType, data);
47 | bytes32 prefixedHash = keccak256("\x19Ethereum Signed Message:\n32", dataHash);
48 |
49 | // Recover address of data signer
50 | address recovered = getRecoveredAddress(sig, prefixedHash);
51 |
52 | // Take hash of recovered address
53 | bytes32 hashedAddr = keccak256(recovered);
54 |
55 | // Does the trusted identifier have they key which signed the user's claim?
56 | return trustedClaimHolder.keyHasPurpose(hashedAddr, 3);
57 | }
58 |
59 | function getRecoveredAddress(bytes sig, bytes32 dataHash)
60 | public
61 | view
62 | returns (address addr)
63 | {
64 | bytes32 ra;
65 | bytes32 sa;
66 | uint8 va;
67 |
68 | // Check the signature length
69 | if (sig.length != 65) {
70 | return (0);
71 | }
72 |
73 | // Divide the signature in r, s and v variables
74 | assembly {
75 | ra := mload(add(sig, 32))
76 | sa := mload(add(sig, 64))
77 | va := byte(0, mload(add(sig, 96)))
78 | }
79 |
80 | if (va < 27) {
81 | va += 27;
82 | }
83 |
84 | address recoveredAddress = ecrecover(dataHash, va, ra, sa);
85 |
86 | return (recoveredAddress);
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/contracts/ERC725.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | contract ERC725 {
4 |
5 | uint256 constant MANAGEMENT_KEY = 1;
6 | uint256 constant ACTION_KEY = 2;
7 | uint256 constant CLAIM_SIGNER_KEY = 3;
8 | uint256 constant ENCRYPTION_KEY = 4;
9 |
10 | event KeyAdded(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType);
11 | event KeyRemoved(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType);
12 | event ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data);
13 | event Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data);
14 | event Approved(uint256 indexed executionId, bool approved);
15 |
16 | struct Key {
17 | uint256 purpose; //e.g., MANAGEMENT_KEY = 1, ACTION_KEY = 2, etc.
18 | uint256 keyType; // e.g. 1 = ECDSA, 2 = RSA, etc.
19 | bytes32 key;
20 | }
21 |
22 | function getKey(bytes32 _key) public constant returns(uint256 purpose, uint256 keyType, bytes32 key);
23 | function getKeyPurpose(bytes32 _key) public constant returns(uint256 purpose);
24 | function getKeysByPurpose(uint256 _purpose) public constant returns(bytes32[] keys);
25 | function addKey(bytes32 _key, uint256 _purpose, uint256 _keyType) public returns (bool success);
26 | function execute(address _to, uint256 _value, bytes _data) public returns (uint256 executionId);
27 | function approve(uint256 _id, bool _approve) public returns (bool success);
28 | }
29 |
--------------------------------------------------------------------------------
/contracts/ERC735.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | contract ERC735 {
4 |
5 | event ClaimRequested(uint256 indexed claimRequestId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri); event ClaimAdded(bytes32 indexed claimId, uint256 indexed claimType, address indexed issuer, uint256 signatureType, bytes32 signature, bytes claim, string uri);
6 | event ClaimAdded(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
7 | event ClaimRemoved(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
8 | event ClaimChanged(bytes32 indexed claimId, uint256 indexed claimType, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
9 |
10 | struct Claim {
11 | uint256 claimType;
12 | uint256 scheme;
13 | address issuer; // msg.sender
14 | bytes signature; // this.address + claimType + data
15 | bytes data;
16 | string uri;
17 | }
18 |
19 | function getClaim(bytes32 _claimId) public constant returns(uint256 claimType, uint256 scheme, address issuer, bytes signature, bytes data, string uri);
20 | function getClaimIdsByType(uint256 _claimType) public constant returns(bytes32[] claimIds);
21 | function addClaim(uint256 _claimType, uint256 _scheme, address issuer, bytes _signature, bytes _data, string _uri) public returns (bytes32 claimRequestId);
22 | function removeClaim(bytes32 _claimId) public returns (bool success);
23 | }
24 |
--------------------------------------------------------------------------------
/contracts/Identity.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | import './ClaimHolder.sol';
4 |
5 | /**
6 | * NOTE: This contract exists as a convenience for deploying an identity with
7 | * some 'pre-signed' claims. If you don't care about that, just use ClaimHolder
8 | * instead.
9 | */
10 |
11 | contract Identity is ClaimHolder {
12 |
13 | function Identity(
14 | uint256[] _claimType,
15 | uint256[] _scheme,
16 | address[] _issuer,
17 | bytes _signature,
18 | bytes _data,
19 | string _uri,
20 | uint256[] _sigSizes,
21 | uint256[] dataSizes,
22 | uint256[] uriSizes
23 | )
24 | public
25 | {
26 | bytes32 claimId;
27 | uint offset = 0;
28 | uint uoffset = 0;
29 | uint doffset = 0;
30 |
31 | for (uint i = 0; i < _claimType.length; i++) {
32 |
33 | claimId = keccak256(_issuer[i], _claimType[i]);
34 |
35 | claims[claimId] = Claim(
36 | _claimType[i],
37 | _scheme[i],
38 | _issuer[i],
39 | getBytes(_signature, offset, _sigSizes[i]),
40 | getBytes(_data, doffset, dataSizes[i]),
41 | getString(_uri, uoffset, uriSizes[i])
42 | );
43 |
44 | offset += _sigSizes[i];
45 | uoffset += uriSizes[i];
46 | doffset += dataSizes[i];
47 |
48 | emit ClaimAdded(
49 | claimId,
50 | claims[claimId].claimType,
51 | claims[claimId].scheme,
52 | claims[claimId].issuer,
53 | claims[claimId].signature,
54 | claims[claimId].data,
55 | claims[claimId].uri
56 | );
57 | }
58 | }
59 |
60 | function getBytes(bytes _str, uint256 _offset, uint256 _length) constant returns (bytes) {
61 | bytes memory sig = new bytes(_length);
62 | uint256 j = 0;
63 | for (uint256 k = _offset; k< _offset + _length; k++) {
64 | sig[j] = _str[k];
65 | j++;
66 | }
67 | return sig;
68 | }
69 |
70 | function getString(string _str, uint256 _offset, uint256 _length) constant returns (string) {
71 | bytes memory strBytes = bytes(_str);
72 | bytes memory sig = new bytes(_length);
73 | uint256 j = 0;
74 | for (uint256 k = _offset; k< _offset + _length; k++) {
75 | sig[j] = strBytes[k];
76 | j++;
77 | }
78 | return string(sig);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/contracts/KeyHolder.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.22;
2 |
3 | import './ERC725.sol';
4 |
5 | contract KeyHolder is ERC725 {
6 |
7 | uint256 executionNonce;
8 |
9 | struct Execution {
10 | address to;
11 | uint256 value;
12 | bytes data;
13 | bool approved;
14 | bool executed;
15 | }
16 |
17 | mapping (bytes32 => Key) keys;
18 | mapping (uint256 => bytes32[]) keysByPurpose;
19 | mapping (uint256 => Execution) executions;
20 |
21 | event ExecutionFailed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data);
22 |
23 | function KeyHolder() public {
24 | bytes32 _key = keccak256(msg.sender);
25 | keys[_key].key = _key;
26 | keys[_key].purpose = 1;
27 | keys[_key].keyType = 1;
28 | keysByPurpose[1].push(_key);
29 | emit KeyAdded(_key, keys[_key].purpose, 1);
30 | }
31 |
32 | function getKey(bytes32 _key)
33 | public
34 | view
35 | returns(uint256 purpose, uint256 keyType, bytes32 key)
36 | {
37 | return (keys[_key].purpose, keys[_key].keyType, keys[_key].key);
38 | }
39 |
40 | function getKeyPurpose(bytes32 _key)
41 | public
42 | view
43 | returns(uint256 purpose)
44 | {
45 | return (keys[_key].purpose);
46 | }
47 |
48 | function getKeysByPurpose(uint256 _purpose)
49 | public
50 | view
51 | returns(bytes32[] _keys)
52 | {
53 | return keysByPurpose[_purpose];
54 | }
55 |
56 | function addKey(bytes32 _key, uint256 _purpose, uint256 _type)
57 | public
58 | returns (bool success)
59 | {
60 | require(keys[_key].key != _key, "Key already exists"); // Key should not already exist
61 | if (msg.sender != address(this)) {
62 | require(keyHasPurpose(keccak256(msg.sender), 1), "Sender does not have management key"); // Sender has MANAGEMENT_KEY
63 | }
64 |
65 | keys[_key].key = _key;
66 | keys[_key].purpose = _purpose;
67 | keys[_key].keyType = _type;
68 |
69 | keysByPurpose[_purpose].push(_key);
70 |
71 | emit KeyAdded(_key, _purpose, _type);
72 |
73 | return true;
74 | }
75 |
76 | function approve(uint256 _id, bool _approve)
77 | public
78 | returns (bool success)
79 | {
80 | require(keyHasPurpose(keccak256(msg.sender), 2), "Sender does not have action key");
81 |
82 | emit Approved(_id, _approve);
83 |
84 | if (_approve == true) {
85 | executions[_id].approved = true;
86 | success = executions[_id].to.call(executions[_id].data, 0);
87 | if (success) {
88 | executions[_id].executed = true;
89 | emit Executed(
90 | _id,
91 | executions[_id].to,
92 | executions[_id].value,
93 | executions[_id].data
94 | );
95 | return;
96 | } else {
97 | emit ExecutionFailed(
98 | _id,
99 | executions[_id].to,
100 | executions[_id].value,
101 | executions[_id].data
102 | );
103 | return;
104 | }
105 | } else {
106 | executions[_id].approved = false;
107 | }
108 | return true;
109 | }
110 |
111 | function execute(address _to, uint256 _value, bytes _data)
112 | public
113 | returns (uint256 executionId)
114 | {
115 | require(!executions[executionNonce].executed, "Already executed");
116 | executions[executionNonce].to = _to;
117 | executions[executionNonce].value = _value;
118 | executions[executionNonce].data = _data;
119 |
120 | emit ExecutionRequested(executionNonce, _to, _value, _data);
121 |
122 | if (keyHasPurpose(keccak256(msg.sender),1) || keyHasPurpose(keccak256(msg.sender),2)) {
123 | approve(executionNonce, true);
124 | }
125 |
126 | executionNonce++;
127 | return executionNonce-1;
128 | }
129 |
130 | function removeKey(bytes32 _key)
131 | public
132 | returns (bool success)
133 | {
134 | require(keys[_key].key == _key, "No such key");
135 | emit KeyRemoved(keys[_key].key, keys[_key].purpose, keys[_key].keyType);
136 |
137 | /* uint index;
138 | (index,) = keysByPurpose[keys[_key].purpose.indexOf(_key);
139 | keysByPurpose[keys[_key].purpose.removeByIndex(index); */
140 |
141 | delete keys[_key];
142 |
143 | return true;
144 | }
145 |
146 | function keyHasPurpose(bytes32 _key, uint256 _purpose)
147 | public
148 | view
149 | returns(bool result)
150 | {
151 | bool isThere;
152 | if (keys[_key].key == 0) return false;
153 | isThere = keys[_key].purpose <= _purpose;
154 | return isThere;
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/dist/README/Digital Identity ERC-725.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/dist/README/Digital Identity ERC-725.webm
--------------------------------------------------------------------------------
/dist/README/Digital-Identity-ERC-725-Claim-Checker-Invalid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/dist/README/Digital-Identity-ERC-725-Claim-Checker-Invalid.png
--------------------------------------------------------------------------------
/dist/README/Digital-Identity-ERC-725-Claim-Checker-Valid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/dist/README/Digital-Identity-ERC-725-Claim-Checker-Valid.png
--------------------------------------------------------------------------------
/dist/README/Digital-Identity-ERC-725-Claim-Checker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/dist/README/Digital-Identity-ERC-725-Claim-Checker.png
--------------------------------------------------------------------------------
/dist/README/Digital-Identity-ERC-725-Identities-Claims-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/dist/README/Digital-Identity-ERC-725-Identities-Claims-1.png
--------------------------------------------------------------------------------
/dist/README/Digital-Identity-ERC-725-Identities-Claims-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/dist/README/Digital-Identity-ERC-725-Identities-Claims-2.png
--------------------------------------------------------------------------------
/dist/README/Digital-Identity-ERC-725-Identities-Claims-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/dist/README/Digital-Identity-ERC-725-Identities-Claims-3.png
--------------------------------------------------------------------------------
/dist/README/Digital-Identity-ERC-725-New-Identity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/dist/README/Digital-Identity-ERC-725-New-Identity.png
--------------------------------------------------------------------------------
/dist/README/Digital-Identity-ERC-725.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/dist/README/Digital-Identity-ERC-725.png
--------------------------------------------------------------------------------
/dist/digital-identity.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/dist/digital-identity.zip
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import serveStatic from 'serve-static'
3 | import spawn from "cross-spawn"
4 | import Ganache from 'ganache-core'
5 | import opener from 'opener'
6 | import fs from 'fs'
7 | import Web3 from 'web3'
8 | import path from "path"
9 | import dotenv from "dotenv"
10 | import shelljs from "shelljs"
11 |
12 | import simpleIssuer from './issuer-services/_simple'
13 | import { spawnSync } from 'child_process';
14 | import syncReq from "sync-request"
15 |
16 | dotenv.config()
17 | const LOCAL = process.env.LOCAL === "true"
18 | const RESET= process.env.RESET === "true"
19 |
20 | let HOST = "localhost"
21 | if(!LOCAL){
22 | try{
23 | HOST = syncReq("GET", "http://ipinfo.io/ip").getBody('utf8').trim()
24 | }catch(e){
25 | /* Ignore */
26 | }
27 | }
28 |
29 | const app = express()
30 |
31 | app.get('/', (req, res) => {
32 | var html = fs.readFileSync(__dirname + '/public/dev.html').toString()
33 | res.send(html.replace(/\{HOST\}/g, `http://${HOST}:8082/`))
34 | })
35 | app.use(serveStatic('public'))
36 |
37 | try {
38 | var { simpleApp } = require('./issuer-services/config.json')
39 | simpleIssuer(app, { web3: new Web3(), simpleApp })
40 | } catch(e) {
41 | /* Ignore */
42 | }
43 |
44 | const startGanache = () =>
45 | new Promise((resolve, reject) => {
46 | const dataDir = path.join(__dirname, "data", "db")
47 | if(RESET){
48 | console.log("Ganache > Reset")
49 | shelljs.rm("-rf", dataDir)
50 | }
51 | shelljs.mkdir("-p", dataDir)
52 | var server = Ganache.server({
53 | total_accounts: 5,
54 | default_balance_ether: 100,
55 | db_path: 'data/db',
56 | network_id: 999,
57 | seed: 123,
58 | mnemonic: "logic cradle area quality lumber pitch radar sense dove fault capital observe"
59 | // blocktime: 3
60 | })
61 | server.listen(8545, err => {
62 | if (err) {
63 | return reject(err)
64 | }
65 | console.log('Ganache listening. Starting webpack...')
66 | resolve()
67 | })
68 | })
69 |
70 | async function start() {
71 | await startGanache()
72 | const cmd = path.join(__dirname, "node_modules", ".bin", "webpack-dev-server")
73 | const webpackDevServer = spawn(cmd, [
74 | '--info=true',
75 | '--port=8082',
76 | '--host=0.0.0.0'
77 | ])
78 | webpackDevServer.stdout.pipe(process.stdout)
79 | webpackDevServer.stderr.pipe(process.stderr)
80 | process.on('exit', () => webpackDevServer.kill())
81 |
82 | const PORT = process.env.PORT || 3333
83 | app.listen(PORT, () => {
84 | console.log(`\nListening on host ${HOST}, port ${PORT}\n`)
85 | setTimeout(() => {
86 | try{
87 | const url = `http://${HOST}:${PORT}`
88 | console.log(`Opening Browser at ${url}`)
89 | const browser = opener(url)
90 | browser.unref();
91 | }catch(err){
92 | console.log("open browser", err.message);
93 | }
94 | }, 4500)
95 | })
96 | }
97 |
98 | start()
99 |
--------------------------------------------------------------------------------
/issuer-services/_facebook.js:
--------------------------------------------------------------------------------
1 | // https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow
2 |
3 | var superagent = require('superagent')
4 | var HTML = require('./html')
5 |
6 | const ClaimType = 3 // Has Facebook
7 |
8 | module.exports = function facebook(app, { web3, facebookApp, baseUrl }) {
9 | const redirect_uri = `${baseUrl}/fb-auth-response`
10 | app.get('/fb-auth', (req, res) => {
11 | if (!req.query.target) {
12 | res.send('No target identity contract provided')
13 | return
14 | }
15 | if (!req.query.issuer) {
16 | res.send('No issuer identity contract provided')
17 | return
18 | }
19 |
20 | req.session.targetIdentity = req.query.target
21 | req.session.issuer = req.query.issuer
22 | req.session.state = web3.utils.randomHex(8)
23 |
24 | var query = [
25 | `client_id=${facebookApp.client_id}`,
26 | `redirect_uri=${redirect_uri}`,
27 | `state=${req.session.state}`
28 | ]
29 | res.redirect(
30 | `https://www.facebook.com/v2.12/dialog/oauth?${query.join('&')}`
31 | )
32 | })
33 |
34 | app.get(
35 | '/fb-auth-response',
36 | (req, res, next) => {
37 | if (!req.query.code) {
38 | return res.send('No Code specified')
39 | }
40 | if (req.query.state !== req.session.state) {
41 | return res.send('State param does not match')
42 | }
43 | if (!req.session.targetIdentity) {
44 | return res.send('No target identity found')
45 | }
46 | if (!req.session.issuer) {
47 | return res.send('No issuer found')
48 | }
49 |
50 | superagent
51 | .get(`https://graph.facebook.com/v2.12/oauth/access_token`)
52 | .query({
53 | client_id: facebookApp.client_id,
54 | client_secret: facebookApp.secret,
55 | redirect_uri,
56 | code: req.query.code
57 | })
58 | .then(response => {
59 | req.userToken = response.body
60 | next()
61 | })
62 | .catch(() => {
63 | res.send('Error fetching token')
64 | })
65 | },
66 | (req, res, next) => {
67 | superagent
68 | .get(`https://graph.facebook.com/debug_token`)
69 | .query({
70 | input_token: req.userToken.access_token,
71 | access_token: `${facebookApp.client_id}|${facebookApp.secret}`
72 | })
73 | .then(response => {
74 | req.tokenDebug = JSON.parse(response.text).data
75 |
76 | if (req.tokenDebug.app_id !== facebookApp.client_id) {
77 | return res.send("Token's App does not match")
78 | }
79 | if (!req.tokenDebug.is_valid) {
80 | return res.send('Token is invalid')
81 | }
82 | next()
83 | })
84 | .catch((e) => {
85 | console.log(e)
86 | res.send('Error validating token')
87 | })
88 | },
89 | async (req, res) => {
90 | // var data = JSON.stringify({ user_id: req.tokenDebug.user_id })
91 | var rawData = 'Verified OK'
92 | var hexData = web3.utils.asciiToHex(rawData)
93 | var hashed = web3.utils.soliditySha3(req.session.targetIdentity, ClaimType, hexData)
94 | req.signedData = await web3.eth.accounts.sign(hashed, facebookApp.claimSignerKey)
95 |
96 | res.send(HTML(`
97 |
Successfully signed claim:
98 | Issuer: ${req.session.issuer}
99 | Target: ${req.session.targetIdentity}
100 | Data: ${rawData}
101 | Signature: ${req.signedData.signature}
102 | Hash: ${req.signedData.messageHash}
103 |
104 | `
111 | ))
112 | }
113 | )
114 | }
115 |
--------------------------------------------------------------------------------
/issuer-services/_github.js:
--------------------------------------------------------------------------------
1 | // https://github.com/settings/developers
2 |
3 | var OAuth = require('oauth').OAuth2
4 | var HTML = require('./html')
5 | var superagent = require('superagent')
6 |
7 | const ClaimType = 5 // Has GitHub
8 |
9 | module.exports = function github(app, { web3, githubApp, baseUrl }) {
10 | const redirect_uri = `${baseUrl}/github-auth-response`
11 |
12 | var githubOAuth = new OAuth(
13 | githubApp.client_id,
14 | githubApp.secret,
15 | 'https://github.com',
16 | '/login/oauth/authorize',
17 | '/login/oauth/access_token',
18 | null
19 | )
20 |
21 | app.get('/github-auth', (req, res) => {
22 | if (!req.query.target) {
23 | res.send('No target identity contract provided')
24 | return
25 | }
26 | if (!req.query.issuer) {
27 | res.send('No issuer identity contract provided')
28 | return
29 | }
30 |
31 | req.session.targetIdentity = req.query.target
32 | req.session.issuer = req.query.issuer
33 | req.session.state = web3.utils.randomHex(8)
34 |
35 | var authURL = githubOAuth.getAuthorizeUrl({
36 | redirect_uri,
37 | scope: ['user'],
38 | state: req.session.state
39 | })
40 |
41 | res.redirect(authURL)
42 | })
43 |
44 | app.get(
45 | '/github-auth-response',
46 | (req, res, next) => {
47 | githubOAuth.getOAuthAccessToken(
48 | req.query.code,
49 | { redirect_uri },
50 | function(e, access_token, refresh_token, results) {
51 | if (e) {
52 | next(e)
53 | } else if (results.error) {
54 | next(results.error)
55 | } else {
56 | req.access_token = access_token
57 | next()
58 | }
59 | }
60 | )
61 | },
62 | (req, res, next) => {
63 | superagent
64 | .get('https://api.github.com/user')
65 | .set('Authorization', `token ${req.access_token}`)
66 | .accept('json')
67 | .then(response => {
68 | req.githubUser = response.body
69 | next()
70 | })
71 | },
72 | async (req, res) => {
73 | // var data = JSON.stringify({ user_id: req.githubUser.id })
74 | var rawData = 'Verified OK'
75 | var hexData = web3.utils.asciiToHex(rawData)
76 | var hashed = web3.utils.soliditySha3(req.session.targetIdentity, ClaimType, hexData)
77 | req.signedData = await web3.eth.accounts.sign(hashed, githubApp.claimSignerKey)
78 |
79 | res.send(
80 | HTML(`
81 | Successfully signed claim:
82 | Issuer: ${req.session.issuer}
83 | Target: ${req.session.targetIdentity}
84 | Data: ${rawData}
85 | Signature: ${req.signedData.signature}
86 | Hash: ${req.signedData.messageHash}
87 |
88 | `)
95 | )
96 | }
97 | )
98 | }
99 |
--------------------------------------------------------------------------------
/issuer-services/_google.js:
--------------------------------------------------------------------------------
1 | // https://console.developers.google.com/apis/credentials
2 |
3 | var OAuth = require('oauth').OAuth2
4 | var HTML = require('./html')
5 | var superagent = require('superagent')
6 |
7 | const ClaimType = 6 // Has Google
8 |
9 | module.exports = function google(app, { web3, googleApp, baseUrl }) {
10 | const redirect_uri = `${baseUrl}/google-auth-response`
11 |
12 | var googleOAuth = new OAuth(
13 | googleApp.client_id,
14 | googleApp.secret,
15 | 'https://accounts.google.com',
16 | '/o/oauth2/auth',
17 | '/o/oauth2/token'
18 | )
19 |
20 | app.get('/google-auth', (req, res) => {
21 | if (!req.query.target) {
22 | res.send('No target identity contract provided')
23 | return
24 | }
25 | if (!req.query.issuer) {
26 | res.send('No issuer identity contract provided')
27 | return
28 | }
29 |
30 | req.session.targetIdentity = req.query.target
31 | req.session.issuer = req.query.issuer
32 | req.session.state = web3.utils.randomHex(8)
33 |
34 | var authURL = googleOAuth.getAuthorizeUrl({
35 | redirect_uri,
36 | scope: 'https://www.googleapis.com/auth/userinfo.profile',
37 | state: req.session.state,
38 | response_type: 'code'
39 | })
40 |
41 | res.redirect(authURL)
42 | })
43 |
44 | app.get(
45 | '/google-auth-response',
46 | (req, res, next) => {
47 | googleOAuth.getOAuthAccessToken(
48 | req.query.code,
49 | {
50 | redirect_uri,
51 | grant_type: 'authorization_code'
52 | },
53 | function(e, access_token, refresh_token, results) {
54 | if (e) {
55 | next(e)
56 | } else if (results.error) {
57 | next(results.error)
58 | } else {
59 | req.access_token = access_token
60 | next()
61 | }
62 | }
63 | )
64 | },
65 | (req, res, next) => {
66 | superagent
67 | .get('https://www.googleapis.com/oauth2/v1/userinfo')
68 | .query({
69 | alt: 'json',
70 | access_token: req.access_token
71 | })
72 | .then(response => {
73 | req.googleUser = response.body
74 | next()
75 | })
76 | },
77 | async (req, res) => {
78 | // var data = JSON.stringify({ user_id: req.googleUser.id })
79 |
80 | var rawData = 'Verified OK'
81 | var hexData = web3.utils.asciiToHex(rawData)
82 | var hashed = web3.utils.soliditySha3(req.session.targetIdentity, ClaimType, hexData)
83 | req.signedData = await web3.eth.accounts.sign(hashed, googleApp.claimSignerKey)
84 |
85 | res.send(
86 | HTML(`
87 | Successfully signed claim:
88 | Issuer: ${req.session.issuer}
89 | Target: ${req.session.targetIdentity}
90 | Data: ${rawData}
91 | Signature: ${req.signedData.signature}
92 | Hash: ${req.signedData.messageHash}
93 |
94 | `)
101 | )
102 | }
103 | )
104 | }
105 |
--------------------------------------------------------------------------------
/issuer-services/_linkedin.js:
--------------------------------------------------------------------------------
1 | // https://developer.linkedin.com/docs/oauth2
2 |
3 | var OAuth = require('oauth').OAuth2
4 | var HTML = require('./html')
5 | var superagent = require('superagent')
6 |
7 | const ClaimType = 9 // Has LinkedIn
8 |
9 | module.exports = function linkedin(app, { web3, linkedInApp, baseUrl }) {
10 | const redirect_uri = `${baseUrl}/linkedin-auth-response`
11 |
12 | var linkedInOAuth = new OAuth(
13 | linkedInApp.client_id,
14 | linkedInApp.secret,
15 | 'https://www.linkedin.com',
16 | '/oauth/v2/authorization',
17 | '/oauth/v2/accessToken',
18 | null
19 | )
20 |
21 | app.get('/linkedin-auth', (req, res) => {
22 | if (!req.query.target) {
23 | res.send('No target identity contract provided')
24 | return
25 | }
26 | if (!req.query.issuer) {
27 | res.send('No issuer identity contract provided')
28 | return
29 | }
30 |
31 | req.session.targetIdentity = req.query.target
32 | req.session.issuer = req.query.issuer
33 | req.session.state = web3.utils.randomHex(8)
34 |
35 | var authURL = linkedInOAuth.getAuthorizeUrl({
36 | redirect_uri,
37 | scope: ['r_basicprofile', 'r_emailaddress'],
38 | state: req.session.state,
39 | response_type: 'code'
40 | })
41 |
42 | res.redirect(authURL)
43 | })
44 |
45 | app.get(
46 | '/linkedin-auth-response',
47 | (req, res, next) => {
48 | linkedInOAuth.getOAuthAccessToken(
49 | req.query.code,
50 | {
51 | redirect_uri,
52 | grant_type: 'authorization_code'
53 | },
54 | function(e, access_token, refresh_token, results) {
55 | if (e) {
56 | next(e)
57 | } else if (results.error) {
58 | next(results.error)
59 | } else {
60 | req.access_token = access_token
61 | next()
62 | }
63 | }
64 | )
65 | },
66 | (req, res, next) => {
67 | superagent
68 | .get('https://api.linkedin.com/v1/people/~')
69 | .set('Authorization', `Bearer ${req.access_token}`)
70 | .query({ format: 'json' })
71 | .then(response => {
72 | req.linkedInUser = response.body
73 | next()
74 | })
75 | },
76 | async (req, res) => {
77 | // var data = JSON.stringify({ user_id: req.githubUser.id })
78 | var rawData = 'Verified OK'
79 | var hexData = web3.utils.asciiToHex(rawData)
80 | var hashed = web3.utils.soliditySha3(req.session.targetIdentity, ClaimType, hexData)
81 | req.signedData = await web3.eth.accounts.sign(
82 | hashed,
83 | linkedInApp.claimSignerKey
84 | )
85 |
86 | res.send(
87 | HTML(`
88 | Successfully signed claim:
89 | Issuer: ${req.session.issuer}
90 | Target: ${req.session.targetIdentity}
91 | Data: ${rawData}
92 | Signature: ${req.signedData.signature}
93 | Hash: ${req.signedData.messageHash}
94 |
95 | `)
102 | )
103 | }
104 | )
105 | }
106 |
--------------------------------------------------------------------------------
/issuer-services/_simple.js:
--------------------------------------------------------------------------------
1 | var HTML = require('./html')
2 |
3 | const ClaimType = '7';
4 |
5 | module.exports = function dummyService(app, { web3, simpleApp }) {
6 |
7 | app.get('/simple-auth', async (req, res) => {
8 | var issuer = req.query.issuer,
9 | target = req.query.target
10 |
11 | if (!target) {
12 | res.send(HTML('No target identity contract provided'))
13 | return
14 | }
15 | if (!issuer) {
16 | res.send(HTML('No issuer identity contract provided'))
17 | return
18 | }
19 | if (!simpleApp.claimSignerKey) {
20 | res.send(HTML('No private key specified.'))
21 | return
22 | }
23 |
24 | var rawData = 'Verified OK'
25 | var hexData = web3.utils.asciiToHex(rawData)
26 | var hashed = web3.utils.soliditySha3(target, ClaimType, hexData)
27 | var signedData = await web3.eth.accounts.sign(hashed, simpleApp.claimSignerKey)
28 |
29 | res.send(
30 | HTML(
31 | `This example authentication service returns some signed data which can be added to a claim
32 | Issuer: ${issuer}
33 | Target: ${target}
34 | Data: ${rawData}
35 | Signature: ${signedData.signature}
36 | Hash: ${signedData.messageHash}
37 |
38 | `
45 | )
46 | )
47 | })
48 | }
49 |
--------------------------------------------------------------------------------
/issuer-services/_twitter.js:
--------------------------------------------------------------------------------
1 | // https://apps.twitter.com/
2 |
3 | var OAuth = require('oauth').OAuth
4 | var HTML = require('./html')
5 |
6 | const ClaimType = 4 // Has Twitter
7 |
8 | module.exports = function twitter(app, { web3, twitterApp, baseUrl }) {
9 | var twitterOAuth = new OAuth(
10 | 'https://api.twitter.com/oauth/request_token',
11 | 'https://api.twitter.com/oauth/access_token',
12 | twitterApp.client_id,
13 | twitterApp.secret,
14 | '1.0',
15 | `${baseUrl}/twitter-auth-response`,
16 | 'HMAC-SHA1'
17 | )
18 |
19 | app.get('/twitter-auth', (req, res) => {
20 | if (!req.query.target) {
21 | res.send('No target identity contract provided')
22 | return
23 | }
24 | if (!req.query.issuer) {
25 | res.send('No issuer identity contract provided')
26 | return
27 | }
28 |
29 | req.session.targetIdentity = req.query.target
30 | req.session.issuer = req.query.issuer
31 |
32 | twitterOAuth.getOAuthRequestToken(function(
33 | error,
34 | oAuthToken,
35 | oAuthTokenSecret
36 | ) {
37 | req.session.oAuthTokenSecret = oAuthTokenSecret
38 | res.redirect(
39 | `https://twitter.com/oauth/authenticate?oauth_token=${oAuthToken}`
40 | )
41 | })
42 | })
43 |
44 | app.get(
45 | '/twitter-auth-response',
46 | (req, res, next) => {
47 | twitterOAuth.getOAuthAccessToken(
48 | req.query.oauth_token,
49 | req.session.oAuthTokenSecret,
50 | req.query.oauth_verifier,
51 | function(error, oAuthAccessToken, oAuthAccessTokenSecret) {
52 | if (error) {
53 | console.log(error)
54 | res.send('Error')
55 | return
56 | }
57 |
58 | req.oAuthAccessToken = oAuthAccessToken
59 | req.oAuthAccessTokenSecret = oAuthAccessTokenSecret
60 |
61 | next()
62 | }
63 | )
64 | },
65 | (req, res, next) => {
66 | twitterOAuth.get(
67 | 'https://api.twitter.com/1.1/account/verify_credentials.json',
68 | req.oAuthAccessToken,
69 | req.oAuthAccessTokenSecret,
70 | function(error, twitterResponseData) {
71 | if (error) {
72 | res.send('Error')
73 | return
74 | }
75 | try {
76 | req.twitterUser = JSON.parse(twitterResponseData)
77 | next()
78 | } catch (parseError) {
79 | res.send("Error parsing response")
80 | }
81 | }
82 | )
83 | },
84 | async (req, res) => {
85 | // var data = JSON.stringify({ user_id: req.twitterUser.id })
86 |
87 | var rawData = 'Verified OK'
88 | var hexData = web3.utils.asciiToHex(rawData)
89 | var hashed = web3.utils.soliditySha3(req.session.targetIdentity, ClaimType, hexData)
90 | req.signedData = await web3.eth.accounts.sign(hashed, twitterApp.claimSignerKey)
91 |
92 | res.send(
93 | HTML(`
94 | Successfully signed claim:
95 | Issuer: ${req.session.issuer}
96 | Target: ${req.session.targetIdentity}
97 | Data: ${rawData}
98 | Signature: ${req.signedData.signature}
99 | Hash: ${req.signedData.messageHash}
100 |
101 | `)
108 | )
109 | }
110 | )
111 | }
112 |
--------------------------------------------------------------------------------
/issuer-services/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express')
2 | var session = require('express-session')
3 | var Web3 = require('web3')
4 |
5 | var simple = require('./_simple')
6 | var facebook = require('./_facebook')
7 | var twitter = require('./_twitter')
8 | var github = require('./_github')
9 | var google = require('./_google')
10 | var linkedin = require('./_linkedin')
11 |
12 | try {
13 | var Config = require('./config.json')
14 | } catch (e) {
15 | console.log('Please copy config.json.eg to config.json and update it.')
16 | process.exit()
17 | }
18 |
19 | Config.web3 = new Web3(Config.provider)
20 |
21 | const app = express()
22 | app.use(
23 | session({
24 | secret: 'top secret string',
25 | resave: false,
26 | saveUninitialized: true
27 | })
28 | )
29 |
30 | app.get('/', (req, res) => {
31 | res.send('Issuer Services')
32 | })
33 |
34 | simple(app, Config)
35 | facebook(app, Config)
36 | twitter(app, Config)
37 | github(app, Config)
38 | google(app, Config)
39 | linkedin(app, Config)
40 |
41 | const PORT = process.env.PORT || 3001
42 | app.listen(PORT, () => {
43 | console.log(`\nListening on port ${PORT}\n`)
44 | })
45 |
--------------------------------------------------------------------------------
/issuer-services/config.json.eg:
--------------------------------------------------------------------------------
1 | {
2 | "provider": "ws://localhost:8545",
3 | "baseUrl": "http://localhost:3333",
4 | "facebookApp": {
5 | "client_id": "000000000000000",
6 | "secret": "00000000000000000000000000000000",
7 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
8 | },
9 | "twitterApp": {
10 | "client_id": "0000000000000000000000000",
11 | "secret": "00000000000000000000000000000000000000000000000000",
12 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
13 | },
14 | "githubApp": {
15 | "client_id": "00000000000000000000",
16 | "secret": "0000000000000000000000000000000000000000",
17 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
18 | },
19 | "googleApp": {
20 | "client_id": "0000000000000000000000000.apps.googleusercontent.com",
21 | "secret": "000000000000000000000000",
22 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
23 | },
24 | "linkedInApp": {
25 | "client_id": "00000000000000",
26 | "secret": "0000000000000000",
27 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
28 | },
29 | "simpleApp": {
30 | "claimSignerKey": "0x0000000000000000000000000000000000000000000000000000000000000000"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/issuer-services/html.js:
--------------------------------------------------------------------------------
1 | const HTML = content => `
2 |
3 |
4 | ERC-725 Authentication
5 |
6 |
7 |
8 |
9 |
10 |
24 |
25 | ${content}
26 | `
27 |
28 | module.exports = HTML
29 |
--------------------------------------------------------------------------------
/issuer-services/index.js:
--------------------------------------------------------------------------------
1 | var Web3 = require('web3')
2 |
3 | var Config = require('./config.json')
4 | var facebook = require('./_facebook')
5 | var google = require('./_google')
6 | var github = require('./_github')
7 | var simple = require('./_simple')
8 | var twitter = require('./_twitter')
9 | var linkedin = require('./_linkedin')
10 |
11 | module.exports = function (app) {
12 | Config.web3 = new Web3()
13 | facebook(app, Config)
14 | google(app, Config)
15 | github(app, Config)
16 | simple(app, Config)
17 | twitter(app, Config)
18 | linkedin(app, Config)
19 | }
20 |
--------------------------------------------------------------------------------
/issuer-services/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "issuer-services",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "engines": {
7 | "node": ">=8.11.1"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "start": "node standalone.js"
12 | },
13 | "author": "O2OProtocol",
14 | "license": "MIT",
15 | "dependencies": {
16 | "express": "^4.16.3",
17 | "express-session": "^1.15.6",
18 | "oauth": "^0.9.15",
19 | "superagent": "^3.8.2",
20 | "ws": "^5.1.1",
21 | "xhr2": "^0.1.4"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/issuer-services/public/dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Digital Identity ERC-725 Dev
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/issuer-services/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/issuer-services/public/favicon.ico
--------------------------------------------------------------------------------
/issuer-services/public/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/issuer-services/public/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/issuer-services/public/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/issuer-services/public/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/issuer-services/public/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/issuer-services/public/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/issuer-services/public/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/issuer-services/public/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/issuer-services/public/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/issuer-services/public/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/issuer-services/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/issuer-services/public/images/logo.png
--------------------------------------------------------------------------------
/issuer-services/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Digital Identity Blockchain ERC-725
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/issuer-services/public/vendor/driver.min.css:
--------------------------------------------------------------------------------
1 | div#driver-popover-item{display:none;position:absolute;background:#fff;color:#2d2d2d;margin:0;padding:15px;border-radius:5px;min-width:250px;max-width:300px;box-shadow:0 1px 10px rgba(0,0,0,.4);z-index:1000000000}div#driver-popover-item .driver-popover-tip{border:5px solid #fff;content:"";position:absolute}div#driver-popover-item .driver-popover-tip.bottom{bottom:-10px;border-top-color:#fff;border-right-color:transparent;border-bottom-color:transparent;border-left-color:transparent}div#driver-popover-item .driver-popover-tip.left{left:-10px;top:10px;border-top-color:transparent;border-right-color:#fff;border-bottom-color:transparent;border-left-color:transparent}div#driver-popover-item .driver-popover-tip.right{right:-10px;top:10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:transparent;border-left-color:#fff}div#driver-popover-item .driver-popover-tip.top{top:-10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:#fff;border-left-color:transparent}div#driver-popover-item .driver-popover-footer{display:block;clear:both;margin-top:5px}div#driver-popover-item .driver-popover-footer button{display:inline-block;padding:3px 10px;border:1px solid #d4d4d4;text-decoration:none;text-shadow:1px 1px 0 #fff;color:#2d2d2d;font:11px/normal sans-serif;cursor:pointer;outline:0;background-color:#f1f1f1;border-radius:2px;zoom:1;margin:10px 0 0;line-height:1.3}div#driver-popover-item .driver-popover-footer button.driver-disabled{color:gray;cursor:default;pointer-events:none}div#driver-popover-item .driver-popover-footer .driver-close-btn{float:left}div#driver-popover-item .driver-popover-footer .driver-btn-group{float:right}div#driver-popover-item .driver-popover-title{font:19px/normal sans-serif;margin:0 0 5px;font-weight:700;display:block;position:relative;line-height:1.5;zoom:1}div#driver-popover-item .driver-popover-description{margin-bottom:0;font:14px/normal sans-serif;line-height:1.5;color:#2d2d2d;font-weight:400;zoom:1}.driver-no-animation{-webkit-transition:none!important;-moz-transition:none!important;-ms-transition:none!important;-o-transition:none!important;transition:none!important}div#driver-page-overlay{background:#000;position:fixed;bottom:0;right:0;display:block;width:100%;height:100%;zoom:1;filter:alpha(opacity=75);opacity:.75;z-index:100002!important}div#driver-highlighted-element-stage,div#driver-page-overlay{top:0;left:0;-webkit-transition:all .4s;-moz-transition:all .4s;-ms-transition:all .4s;-o-transition:all .4s;transition:all .4s}div#driver-highlighted-element-stage{position:absolute;height:50px;width:300px;background:#fff;z-index:100003!important;display:none}.driver-highlighted-element{z-index:100004!important}.driver-position-relative{position:relative!important}.driver-fix-stacking{z-index:auto!important;opacity:1!important;-webkit-transform:none!important;-moz-transform:none!important;-ms-transform:none!important;-o-transform:none!important;transform:none!important;-webkit-filter:none!important;-moz-filter:none!important;-ms-filter:none!important;-o-filter:none!important;filter:none!important;-webkit-perspective:none!important;-moz-perspective:none!important;-ms-perspective:none!important;-o-perspective:none!important;perspective:none!important}
2 | /*# sourceMappingURL=driver.min.css.map*/
--------------------------------------------------------------------------------
/issuer-services/standalone.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const session = require('express-session')
3 | const crypto = require('crypto')
4 | const WebSocket = require('ws')
5 | const serveStatic = require('serve-static')
6 |
7 | global.XMLHttpRequest = require('xhr2')
8 | global.window = { Promise, WebSocket, crypto }
9 |
10 | var simple = require('./_simple')
11 | var facebook = require('./_facebook')
12 | var google = require('./_google')
13 | var github = require('./_github')
14 | var twitter = require('./_twitter')
15 | var linkedin = require('./_linkedin')
16 |
17 | var Web3 = require('./public/vendor/web3.min')
18 |
19 | let Config = require('./config.json')
20 | Config.web3 = new Web3(Config.provider)
21 |
22 | const app = express()
23 | if (process.env.NODE_ENV === 'production') {
24 | app.use((req, res, next) => {
25 | if (req.header('x-forwarded-proto') !== 'https') {
26 | res.redirect(`https://${req.header('host')}${req.url}`)
27 | } else {
28 | next()
29 | }
30 | })
31 | }
32 | app.use(serveStatic('public'))
33 | app.use(
34 | session({
35 | secret: 'top secret string',
36 | resave: false,
37 | saveUninitialized: true
38 | })
39 | )
40 |
41 | app.get('/', (req, res) => {
42 | res.sendFile(__dirname + '/public/index.html')
43 | })
44 |
45 | simple(app, Config)
46 | facebook(app, Config)
47 | google(app, Config)
48 | github(app, Config)
49 | twitter(app, Config)
50 | linkedin(app, Config)
51 |
52 | const PORT = process.env.PORT || 3001
53 | app.listen(PORT, () => {
54 | console.log(`\nListening on port ${PORT}\n`)
55 | })
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "O2OProtocol",
3 | "name": "digital-identity",
4 | "version": "0.2.0",
5 | "license": "MIT",
6 | "description": "An implementation of ERC-725 & ERC-735 for Digital Identity Blockchain. We uses the Truffle framework and Ganache CLI for testing.",
7 | "main": "index.js",
8 | "engines": {
9 | "node": ">=8.11.1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/o2oprotocol/digital-identity.git"
14 | },
15 | "scripts": {
16 | "test": "mocha -r @babel/register test -t 10000",
17 | "test:watch": "mocha -r @babel/register -w --watch-extensions sol -t 5000 test",
18 | "start": "node -r @babel/register index",
19 | "build:contracts": "BUILD=1 mocha -r @babel/register -t 10000 test",
20 | "build:js": "webpack --config webpack.prod.js --progress",
21 | "build:css": "node -r @babel/register -r ./src/pages/App -e \"console.log(require('react-styl').getCss())\" > public/css/app.css",
22 | "build": "npm run build:js && npm run build:css",
23 | "lint": "eslint .",
24 | "prettier": "find ./src -iname '*.js' | xargs ./node_modules/.bin/prettier --write",
25 | "clean": "rm -rf data/db"
26 | },
27 | "dependencies": {
28 | "@babel/core": "^7.0.0-beta.44",
29 | "@babel/preset-react": "^7.0.0-beta.44",
30 | "@babel/register": "^7.0.0-beta.44",
31 | "ajv": "^6.4.0",
32 | "babel-plugin-module-resolver": "^3.1.1",
33 | "body-parser": "^1.18.2",
34 | "bs58": "^4.0.1",
35 | "cross-spawn": "^6.0.5",
36 | "date-fns": "^1.29.0",
37 | "dotenv": "^5.0.1",
38 | "express": "^4.16.3",
39 | "express-session": "^1.15.6",
40 | "ganache-core": "2.1.0",
41 | "ipfs": "^0.28.2",
42 | "ipfs-api": "^20.0.1",
43 | "oauth": "^0.9.15",
44 | "prettier": "^1.12.1",
45 | "react": "^16.3.2",
46 | "react-dom": "^16.3.2",
47 | "react-redux": "^5.0.7",
48 | "react-styl": "^0.0.3",
49 | "redux": "^3.7.2",
50 | "redux-logger": "^3.0.6",
51 | "redux-thunk": "^2.2.0",
52 | "serve-static": "^1.13.2",
53 | "shelljs": "^0.8.2",
54 | "solc": "^0.4.22",
55 | "superagent": "^3.8.2",
56 | "sync-request": "^6.0.0",
57 | "web3": "^1.0.0-beta.34"
58 | },
59 | "prettier": {
60 | "semi": false,
61 | "singleQuote": true,
62 | "bracketSpacing": true
63 | },
64 | "babel": {
65 | "presets": [
66 | "@babel/preset-env",
67 | "@babel/preset-react"
68 | ],
69 | "plugins": [
70 | [
71 | "module-resolver",
72 | {
73 | "alias": {
74 | "actions": "./src/actions",
75 | "components": "./src/components",
76 | "constants": "./src/constants",
77 | "contracts": "./src/contracts",
78 | "pages": "./src/pages",
79 | "reducers": "./src/reducers",
80 | "utils": "./src/utils"
81 | }
82 | }
83 | ],
84 | "@babel/plugin-transform-runtime",
85 | "@babel/plugin-transform-destructuring",
86 | "@babel/plugin-transform-object-assign",
87 | "@babel/plugin-proposal-object-rest-spread"
88 | ]
89 | },
90 | "devDependencies": {
91 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.44",
92 | "@babel/plugin-syntax-object-rest-spread": "^7.0.0-beta.44",
93 | "@babel/plugin-transform-destructuring": "^7.0.0-beta.44",
94 | "@babel/plugin-transform-object-assign": "^7.0.0-beta.44",
95 | "@babel/plugin-transform-runtime": "^7.0.0-beta.44",
96 | "@babel/preset-env": "^7.0.0-beta.44",
97 | "@babel/runtime": "^7.0.0-beta.44",
98 | "babel-eslint": "^8.2.3",
99 | "babel-loader": "^8.0.0-beta.0",
100 | "clean-webpack-plugin": "^0.1.19",
101 | "eslint": "^4.19.1",
102 | "eslint-plugin-babel": "^5.0.0",
103 | "eslint-plugin-react": "^7.7.0",
104 | "opener": "^1.4.3",
105 | "react-router": "^4.2.0",
106 | "react-router-dom": "^4.2.2",
107 | "webpack": "^4.5.0",
108 | "webpack-cli": "^2.0.14",
109 | "webpack-dev-middleware": "^3.1.2",
110 | "webpack-dev-server": "^v3.1.3"
111 | },
112 | "eslintConfig": {
113 | "parser": "babel-eslint",
114 | "parserOptions": {
115 | "ecmaVersion": 6,
116 | "sourceType": "module",
117 | "ecmaFeatures": {
118 | "jsx": true,
119 | "impliedStrict": true
120 | }
121 | },
122 | "globals": {
123 | "Web3": true,
124 | "web3": true,
125 | "OfficialIdentities": true
126 | },
127 | "env": {
128 | "browser": true,
129 | "node": true,
130 | "es6": true,
131 | "mocha": true
132 | },
133 | "plugins": [
134 | "react"
135 | ],
136 | "extends": [
137 | "eslint:recommended",
138 | "plugin:react/recommended"
139 | ],
140 | "rules": {
141 | "react/prop-types": "off",
142 | "react/no-children-prop": "off",
143 | "no-console": "off"
144 | }
145 | },
146 | "eslintIgnore": [
147 | "node_modules",
148 | "public"
149 | ],
150 | "bugs": {
151 | "url": "https://github.com/o2oprotocol/digital-identity/issues"
152 | },
153 | "homepage": "https://github.com/o2oprotocol/digital-identity#readme",
154 | "keywords": [
155 | "Digital",
156 | "Identity",
157 | "Identity",
158 | "Blockchain",
159 | "ERC-725",
160 | "ERC-735"
161 | ]
162 | }
163 |
--------------------------------------------------------------------------------
/public/dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Digital Identity ERC-725 Dev
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/public/favicon.ico
--------------------------------------------------------------------------------
/public/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/public/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/public/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/public/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/public/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/public/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/public/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/o2oprotocol/digital-identity/fe5e45d041d225d4438982dbc81891ae6c7e097e/public/images/logo.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Digital Identity Blockchain ERC-725
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/vendor/driver.min.css:
--------------------------------------------------------------------------------
1 | div#driver-popover-item{display:none;position:absolute;background:#fff;color:#2d2d2d;margin:0;padding:15px;border-radius:5px;min-width:250px;max-width:300px;box-shadow:0 1px 10px rgba(0,0,0,.4);z-index:1000000000}div#driver-popover-item .driver-popover-tip{border:5px solid #fff;content:"";position:absolute}div#driver-popover-item .driver-popover-tip.bottom{bottom:-10px;border-top-color:#fff;border-right-color:transparent;border-bottom-color:transparent;border-left-color:transparent}div#driver-popover-item .driver-popover-tip.left{left:-10px;top:10px;border-top-color:transparent;border-right-color:#fff;border-bottom-color:transparent;border-left-color:transparent}div#driver-popover-item .driver-popover-tip.right{right:-10px;top:10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:transparent;border-left-color:#fff}div#driver-popover-item .driver-popover-tip.top{top:-10px;border-top-color:transparent;border-right-color:transparent;border-bottom-color:#fff;border-left-color:transparent}div#driver-popover-item .driver-popover-footer{display:block;clear:both;margin-top:5px}div#driver-popover-item .driver-popover-footer button{display:inline-block;padding:3px 10px;border:1px solid #d4d4d4;text-decoration:none;text-shadow:1px 1px 0 #fff;color:#2d2d2d;font:11px/normal sans-serif;cursor:pointer;outline:0;background-color:#f1f1f1;border-radius:2px;zoom:1;margin:10px 0 0;line-height:1.3}div#driver-popover-item .driver-popover-footer button.driver-disabled{color:gray;cursor:default;pointer-events:none}div#driver-popover-item .driver-popover-footer .driver-close-btn{float:left}div#driver-popover-item .driver-popover-footer .driver-btn-group{float:right}div#driver-popover-item .driver-popover-title{font:19px/normal sans-serif;margin:0 0 5px;font-weight:700;display:block;position:relative;line-height:1.5;zoom:1}div#driver-popover-item .driver-popover-description{margin-bottom:0;font:14px/normal sans-serif;line-height:1.5;color:#2d2d2d;font-weight:400;zoom:1}.driver-no-animation{-webkit-transition:none!important;-moz-transition:none!important;-ms-transition:none!important;-o-transition:none!important;transition:none!important}div#driver-page-overlay{background:#000;position:fixed;bottom:0;right:0;display:block;width:100%;height:100%;zoom:1;filter:alpha(opacity=75);opacity:.75;z-index:100002!important}div#driver-highlighted-element-stage,div#driver-page-overlay{top:0;left:0;-webkit-transition:all .4s;-moz-transition:all .4s;-ms-transition:all .4s;-o-transition:all .4s;transition:all .4s}div#driver-highlighted-element-stage{position:absolute;height:50px;width:300px;background:#fff;z-index:100003!important;display:none}.driver-highlighted-element{z-index:100004!important}.driver-position-relative{position:relative!important}.driver-fix-stacking{z-index:auto!important;opacity:1!important;-webkit-transform:none!important;-moz-transform:none!important;-ms-transform:none!important;-o-transform:none!important;transform:none!important;-webkit-filter:none!important;-moz-filter:none!important;-ms-filter:none!important;-o-filter:none!important;filter:none!important;-webkit-perspective:none!important;-moz-perspective:none!important;-ms-perspective:none!important;-o-perspective:none!important;perspective:none!important}
2 | /*# sourceMappingURL=driver.min.css.map*/
--------------------------------------------------------------------------------
/scripts/accounts.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generates a list of accounts and private keys given a mnemonic
3 | */
4 |
5 | var bip39 = require('bip39')
6 | var HDKey = require('hdkey')
7 | var Web3 = require('web3')
8 | var web3 = new Web3()
9 |
10 | const mnemonic = process.argv.slice(2).join(' ')
11 |
12 | if (!mnemonic) {
13 | console.log("\nUsage: node accounts.js [mnemonic]")
14 | // node scripts/accounts.js "logic cradle area quality lumber pitch radar sense dove fault capital observe"
15 | console.log("eg node scripts/accounts.js logic cradle area quality lumber pitch radar sense dove fault capital observe")
16 | process.exit()
17 | }
18 |
19 | console.log(`\nMnemonic: ${mnemonic}\n`)
20 |
21 | for (var offset = 0; offset < 10; offset++) {
22 | var seed = bip39.mnemonicToSeed(mnemonic)
23 | var acct = HDKey.fromMasterSeed(seed).derive("m/44'/60'/0'/0/" + offset)
24 | var account = web3.eth.accounts.privateKeyToAccount(
25 | `0x${acct.privateKey.toString('hex')}`
26 | )
27 | console.log(`${account.address} - ${account.privateKey}`)
28 | }
29 |
30 | console.log()
31 | process.exit()
32 |
--------------------------------------------------------------------------------
/scripts/deploy.js:
--------------------------------------------------------------------------------
1 | import helper from '../test/_helper'
2 |
3 | ;(async () => {
4 | var { accounts, web3, deploy, server } = await helper(
5 | `${__dirname}/identity/`,
6 | 'https://rinkeby.infura.io'
7 | // 'http://localhost:8545'
8 | )
9 |
10 | var account = web3.eth.accounts.wallet.add(
11 | '0xPRIV_KEY'
12 | )
13 |
14 | await deploy('ClaimHolder', { from: account, log: true })
15 |
16 | if (server) {
17 | server.close()
18 | }
19 | })()
20 |
--------------------------------------------------------------------------------
/src/Store.js:
--------------------------------------------------------------------------------
1 | import thunkMiddleware from 'redux-thunk'
2 | import { createStore, applyMiddleware, combineReducers } from 'redux'
3 |
4 | import network from './reducers/Network'
5 | import wallet from './reducers/Wallet'
6 | import identity from './reducers/Identity'
7 |
8 | let middlewares = [thunkMiddleware]
9 |
10 | if (process.env.NODE_ENV !== 'production') {
11 | const { logger } = require(`redux-logger`)
12 | middlewares.push(logger)
13 | }
14 |
15 | export default createStore(
16 | combineReducers({
17 | wallet,
18 | network,
19 | identity
20 | }),
21 | applyMiddleware(...middlewares)
22 | )
23 |
--------------------------------------------------------------------------------
/src/actions/Network.js:
--------------------------------------------------------------------------------
1 | import keyMirror from 'utils/keyMirror'
2 | import balance from 'utils/balance'
3 |
4 | import { loadWallet } from './Wallet'
5 |
6 | export const NetworkConstants = keyMirror(
7 | {
8 | CHANGE: null,
9 | CHANGE_SUCCESS: null,
10 | CHANGE_ERROR: null,
11 |
12 | UPDATE_BALANCE: null,
13 | UPDATE_BALANCE_SUCCESS: null,
14 | UPDATE_BALANCE_ERROR: null,
15 |
16 | FETCH_ACCOUNTS: null,
17 | FETCH_ACCOUNTS_SUCCESS: null,
18 |
19 | FETCH_LOCAL_WALLET: null,
20 | FETCH_LOCAL_WALLET_SUCCESS: null,
21 |
22 | SELECT_ACCOUNT: null,
23 | SET_PROVIDER: null,
24 | SET_IPFS: null,
25 |
26 | SEND_FROM_NODE: null,
27 | SEND_FROM_NODE_SUCCESS: null,
28 | SEND_FROM_NODE_ERROR: null,
29 |
30 | SEND_FROM_ACCOUNT: null,
31 | SEND_FROM_ACCOUNT_SUCCESS: null,
32 | SEND_FROM_ACCOUNT_ERROR: null
33 | },
34 | 'NETWORK'
35 | )
36 |
37 | export function init() {
38 | return async function(dispatch, getState) {
39 | var state = getState()
40 | dispatch({ type: NetworkConstants.CHANGE })
41 | dispatch(loadWallet())
42 |
43 | var accounts = [],
44 | balanceWei
45 | var id = await web3.eth.net.getId().catch(() => {
46 | dispatch({
47 | type: NetworkConstants.CHANGE_ERROR,
48 | error: 'Network unavailable'
49 | })
50 | return
51 | })
52 | if (!id) {
53 | return
54 | }
55 |
56 | var accountsRaw = await web3.eth.getAccounts()
57 |
58 | for (let hash of accountsRaw) {
59 | balanceWei = await web3.eth.getBalance(hash)
60 | accounts.push({
61 | hash,
62 | balanceWei,
63 | balance: balance(balanceWei, state.wallet.exchangeRates)
64 | })
65 | }
66 |
67 | dispatch({
68 | type: NetworkConstants.CHANGE_SUCCESS,
69 | id,
70 | accounts
71 | })
72 | }
73 | }
74 |
75 | export function fetchAccounts() {
76 | return async function(dispatch) {
77 | dispatch({ type: NetworkConstants.FETCH_ACCOUNTS })
78 |
79 | var accounts = []
80 | var accountsRaw = await web3.eth.getAccounts()
81 | for (let hash of accountsRaw) {
82 | var balanceWei = await web3.eth.getBalance(hash)
83 | accounts.push({
84 | hash,
85 | balanceWei,
86 | balance: web3.utils.fromWei(balanceWei, 'ether')
87 | })
88 | }
89 |
90 | dispatch({ type: NetworkConstants.FETCH_ACCOUNTS_SUCCESS, accounts })
91 | }
92 | }
93 |
94 | export function updateBalance(account) {
95 | return async function(dispatch, getState) {
96 | var state = getState()
97 | dispatch({ type: NetworkConstants.UPDATE_BALANCE })
98 |
99 | var balanceWei = await web3.eth.getBalance(account)
100 |
101 | dispatch({
102 | type: NetworkConstants.UPDATE_BALANCE_SUCCESS,
103 | account,
104 | balance: balance(balanceWei, state.wallet.exchangeRates)
105 | })
106 | }
107 | }
108 |
109 | export function sendFromNode(from, to, value) {
110 | return function(dispatch) {
111 | dispatch({ type: NetworkConstants.SEND_FROM_NODE, from, to, value })
112 |
113 | web3.eth
114 | .sendTransaction({
115 | from,
116 | to,
117 | value: web3.utils.toWei(value, 'ether'),
118 | gas: 4612388
119 | })
120 | .on('transactionHash', hash => {
121 | dispatch({ type: 'LOG', message: 'transactionHash', hash })
122 | })
123 | .on('receipt', receipt => {
124 | dispatch({ type: 'LOG', message: 'receipt', receipt })
125 | })
126 | .on('confirmation', function(num, receipt) {
127 | if (num === 1) {
128 | dispatch({ type: NetworkConstants.SEND_FROM_NODE_SUCCESS, receipt })
129 | dispatch(updateBalance(from))
130 | dispatch(updateBalance(to))
131 | }
132 | })
133 | .on('error', error => {
134 | dispatch({ type: NetworkConstants.SEND_FROM_NODE_ERROR, error })
135 | })
136 | }
137 | }
138 |
139 | export function sendFromAccount(from, to, value) {
140 | return async function(dispatch, getState) {
141 | var state = getState()
142 | var chainId = state.network.id
143 | var account = state.wallet.raw[from]
144 | var valEth = value
145 | if (state.wallet.currency !== 'eth') {
146 | valEth = String(
147 | Number(value) / state.wallet.exchangeRates[state.wallet.currency]
148 | )
149 | }
150 |
151 | dispatch({ type: NetworkConstants.SEND_FROM_ACCOUNT, from, to, value })
152 |
153 | var signedTx = await account.signTransaction({
154 | from: account.address,
155 | to,
156 | gas: 4612388,
157 | value: web3.utils.toWei(valEth, 'ether'),
158 | chainId: chainId > 10 ? 1 : chainId
159 | })
160 |
161 | web3.eth
162 | .sendSignedTransaction(signedTx.rawTransaction)
163 | .on('error', error => {
164 | dispatch({
165 | type: NetworkConstants.SEND_FROM_ACCOUNT,
166 | message: error.message
167 | })
168 | })
169 | .on('transactionHash', hash => {
170 | dispatch({ type: 'LOG', message: 'transactionHash', hash })
171 | })
172 | .on('receipt', receipt => {
173 | dispatch({ type: 'LOG', message: 'receipt', receipt })
174 | })
175 | .on('confirmation', num => {
176 | if (num === 1) {
177 | dispatch({ type: NetworkConstants.SEND_FROM_ACCOUNT_SUCCESS })
178 | dispatch(updateBalance(from))
179 | dispatch(updateBalance(to))
180 | }
181 | })
182 | }
183 | }
184 |
185 | export function setProvider(provider) {
186 | return async function(dispatch, getState) {
187 | var state = getState()
188 | if (state.network.provider === provider) {
189 | return
190 | }
191 | web3.eth.setProvider(provider)
192 | dispatch({ type: NetworkConstants.SET_PROVIDER, provider })
193 | dispatch(init())
194 | }
195 | }
196 |
197 | export function selectAccount(hash) {
198 | return { type: NetworkConstants.SELECT_ACCOUNT, hash }
199 | }
200 | export function setIpfs(gateway, api) {
201 | return { type: NetworkConstants.SET_IPFS, gateway, api }
202 | }
203 |
--------------------------------------------------------------------------------
/src/actions/Wallet.js:
--------------------------------------------------------------------------------
1 | import keyMirror from 'utils/keyMirror'
2 | import balance from 'utils/balance'
3 |
4 | export const WalletConstants = keyMirror(
5 | {
6 | LOAD: null,
7 | LOAD_SUCCESS: null,
8 | LOAD_ERROR: null,
9 |
10 | LOAD_EXTERNAL: null,
11 | LOAD_EXTERNAL_SUCCESS: null,
12 | LOAD_EXTERNAL_ERROR: null,
13 |
14 | ADD_ACCOUNT: null,
15 | ADD_ACCOUNT_SUCCESS: null,
16 | ADD_ACCOUNT_ERROR: null,
17 |
18 | SELECT_ACCOUNT: null,
19 | SELECT_ACCOUNT_SUCCESS: null,
20 | SELECT_ACCOUNT_ERROR: null,
21 |
22 | IMPORT_ACCOUNT: null,
23 | IMPORT_ACCOUNT_SUCCESS: null,
24 | IMPORT_ACCOUNT_ERROR: null,
25 |
26 | SAVE: null,
27 | SAVE_SUCCESS: null,
28 | SAVE_ERROR: null,
29 |
30 | REMOVE_ACCOUNT: null,
31 | REMOVE_ACCOUNT_SUCCESS: null,
32 | REMOVE_ACCOUNT_ERROR: null,
33 |
34 | UPDATE_BALANCE: null,
35 | UPDATE_BALANCE_SUCCESS: null,
36 | UPDATE_BALANCE_ERROR: null,
37 |
38 | SET_CURRENCY: null,
39 | LOCK_WALLET: null,
40 | UNLOCK_WALLET: null,
41 | UNLOCKED_WALLET: null
42 | },
43 | 'WALLET'
44 | )
45 |
46 | var watchMetaMaskInterval
47 | function watchMetaMask(dispatch, currentAccount) {
48 | watchMetaMaskInterval = setInterval(async function() {
49 | var accounts = await web3.eth.getAccounts()
50 | if (currentAccount !== accounts[0]) {
51 | dispatch(loadWallet(true))
52 | }
53 | }, 1000)
54 | }
55 |
56 | export function loadWallet(external) {
57 | return async function(dispatch, getState) {
58 | var state = getState()
59 |
60 | dispatch({ type: WalletConstants.LOAD, external })
61 | var wallet = web3.eth.accounts.wallet,
62 | accounts = [],
63 | balanceWei,
64 | balances = {}
65 |
66 | clearInterval(watchMetaMaskInterval)
67 |
68 | try {
69 | if (external) {
70 | web3.setProvider(state.network.browserProvider)
71 | accounts = await web3.eth.getAccounts()
72 |
73 | balanceWei = await web3.eth.getBalance(accounts[0])
74 | balances[accounts[0]] = balance(balanceWei, state.wallet.exchangeRates)
75 |
76 | web3.eth.accounts.wallet.clear()
77 | web3.eth.defaultAccount = accounts[0]
78 | dispatch({
79 | type: WalletConstants.LOAD_EXTERNAL_SUCCESS,
80 | activeAddress: accounts[0],
81 | balances
82 | })
83 | watchMetaMask(dispatch, accounts[0])
84 | return
85 | }
86 |
87 | web3.setProvider(state.network.provider)
88 |
89 | // wallet.load is expensive, so cache private keys in sessionStorage
90 | if (window.sessionStorage.privateKeys) {
91 | JSON.parse(window.sessionStorage.privateKeys).forEach(key =>
92 | web3.eth.accounts.wallet.add(key)
93 | )
94 | } else {
95 | wallet = web3.eth.accounts.wallet.load('', 'o2oprotocol')
96 |
97 | var accountKeys = []
98 | for (var k = 0; k < wallet.length; k++) {
99 | accountKeys.push(wallet[k].privateKey)
100 | }
101 | if (accountKeys.length) {
102 | window.sessionStorage.privateKeys = JSON.stringify(accountKeys)
103 | }
104 | }
105 |
106 | for (var i = 0; i < wallet.length; i++) {
107 | accounts.push(wallet[i].address)
108 | }
109 |
110 | for (let hash of accounts) {
111 | balanceWei = await web3.eth.getBalance(hash)
112 | balances[hash] = balance(balanceWei, state.wallet.exchangeRates)
113 | }
114 |
115 | web3.eth.defaultAccount = accounts[0]
116 |
117 | dispatch({
118 | type: WalletConstants.LOAD_SUCCESS,
119 | wallet,
120 | accounts,
121 | balances
122 | })
123 | } catch (error) {
124 | dispatch({ type: WalletConstants.LOAD_ERROR, error })
125 | }
126 | }
127 | }
128 |
129 | export function selectAccount(address) {
130 | return async function(dispatch) {
131 | dispatch({ type: WalletConstants.SELECT_ACCOUNT, address })
132 |
133 | try {
134 | var account = web3.eth.accounts.wallet[address]
135 | web3.eth.defaultAccount = address
136 |
137 | dispatch({
138 | type: WalletConstants.SELECT_ACCOUNT_SUCCESS,
139 | account,
140 | activeAddress: address
141 | })
142 | } catch (error) {
143 | dispatch({ type: WalletConstants.SELECT_ACCOUNT_ERROR, error })
144 | }
145 | }
146 | }
147 |
148 | export function addAccount() {
149 | return async function(dispatch) {
150 | dispatch({ type: WalletConstants.ADD_ACCOUNT })
151 |
152 | try {
153 | var wallet = web3.eth.accounts.wallet.create(1),
154 | account = wallet[wallet.length - 1]
155 |
156 | dispatch({
157 | type: WalletConstants.ADD_ACCOUNT_SUCCESS,
158 | wallet,
159 | account
160 | })
161 | } catch (error) {
162 | dispatch({ type: WalletConstants.ADD_ACCOUNT_ERROR, error })
163 | }
164 | }
165 | }
166 |
167 | export function importAccountFromKey(privateKey) {
168 | return async function(dispatch, getState) {
169 | var state = getState()
170 | dispatch({ type: WalletConstants.IMPORT_ACCOUNT })
171 |
172 | try {
173 | var account = web3.eth.accounts.wallet.add(privateKey)
174 | var wallet = web3.eth.accounts.wallet
175 |
176 | var balanceWei = await web3.eth.getBalance(account.address)
177 |
178 | dispatch({
179 | type: WalletConstants.IMPORT_ACCOUNT_SUCCESS,
180 | account: wallet[wallet.length - 1],
181 | wallet,
182 | balance: balance(balanceWei, state.wallet.exchangeRates)
183 | })
184 | } catch (error) {
185 | dispatch({ type: WalletConstants.IMPORT_ACCOUNT_ERROR, error })
186 | }
187 | }
188 | }
189 |
190 | export function removeAccount(hash) {
191 | return async function(dispatch) {
192 | dispatch({ type: WalletConstants.REMOVE_ACCOUNT })
193 |
194 | try {
195 | var wallet = web3.eth.accounts.wallet.remove(hash)
196 |
197 | dispatch({
198 | type: WalletConstants.REMOVE_ACCOUNT_SUCCESS,
199 | hash,
200 | wallet
201 | })
202 | } catch (error) {
203 | dispatch({ type: WalletConstants.REMOVE_ACCOUNT_ERROR, error })
204 | }
205 | }
206 | }
207 |
208 | export function saveWallet() {
209 | return async function(dispatch) {
210 | dispatch({ type: WalletConstants.SAVE })
211 |
212 | try {
213 | web3.eth.accounts.wallet.save('', 'o2oprotocol')
214 | dispatch({ type: WalletConstants.SAVE_SUCCESS })
215 | } catch (error) {
216 | dispatch({ type: WalletConstants.SAVE_ERROR, error })
217 | }
218 | }
219 | }
220 |
221 | export function updateBalance() {
222 | return async function(dispatch, getState) {
223 | dispatch({ type: WalletConstants.UPDATE_BALANCE })
224 |
225 | var state = getState()
226 | var account = state.wallet.activeAddress
227 |
228 | var wei = await web3.eth.getBalance(account)
229 |
230 | dispatch({
231 | type: WalletConstants.UPDATE_BALANCE_SUCCESS,
232 | account,
233 | balance: balance(wei, state.wallet.exchangeRates)
234 | })
235 | }
236 | }
237 |
238 | export function setCurrency(currency) {
239 | return {
240 | type: WalletConstants.SET_CURRENCY,
241 | currency
242 | }
243 | }
244 |
245 | export function lockWallet() {
246 | return {
247 | type: WalletConstants.LOCK_WALLET
248 | }
249 | }
250 |
251 | export function unlockWallet() {
252 | return {
253 | type: WalletConstants.UNLOCK_WALLET
254 | }
255 | }
256 |
257 | export function unlockedWallet() {
258 | return {
259 | type: WalletConstants.UNLOCKED_WALLET
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/src/components/AccountChooser.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Dropdown from './Dropdown'
4 | import Modal from './Modal'
5 |
6 | export default class AccountChooser extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {}
10 | }
11 |
12 | render() {
13 | const { wallet, balance, account } = this.props
14 | const currency = `${wallet.currency}Str`
15 | return (
16 |
17 |
22 | {`Wallet ${account.substr(0, 6)}: ${balance[currency]}`}
23 |
24 | >
25 | }
26 | >
27 | {wallet.accounts.map((a, idx) => (
28 | {
35 | e.preventDefault()
36 | this.props.selectAccount(a)
37 | }}
38 | >
39 | {`${this.props.wallet.balances[a][currency]}`}
40 | {`${a.substr(0, 8)}`}
41 |
42 | ))}
43 |
44 |
45 |
46 | Network / Wallet Settings
47 |
48 |
49 |
50 |
51 | Prices in:
52 |
73 |
74 |
75 |
76 | {this.props.wallet.tryUnlock && (
77 |
{
80 | this.props.lockWallet()
81 | this.setState({ unlock: false, correct: false })
82 | }}
83 | onOpen={() => {
84 | this.pw.focus()
85 | this.setState({ correct: false })
86 | }}
87 | shouldClose={this.state.unlock}
88 | submitted={this.state.unlock}
89 | className="p-3"
90 | >
91 | {this.state.correct ? (
92 |
93 |
97 |
98 | ) : (
99 |
100 |
(this.pw = r)}
105 | onKeyDown={e => {
106 | if (e.keyCode === 13) {
107 | this.setState({ correct: true })
108 | setTimeout(() => {
109 | this.setState({ unlock: true })
110 | }, 500)
111 | }
112 | }}
113 | />
114 |
115 |
116 |
117 |
118 |
119 |
120 | )}
121 |
122 | )}
123 |
124 | )
125 | }
126 | }
127 |
128 | require('react-styl')(`
129 | .wallet-unlocked
130 | color: green
131 | `)
132 |
--------------------------------------------------------------------------------
/src/components/DetailRow.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class DetailRow extends Component {
4 | constructor() {
5 | super()
6 | this.state = { expanded: false }
7 | }
8 | render() {
9 | var { label, className } = this.props
10 | return (
11 | this.setState({ mouseDown: +new Date() })}
14 | onMouseUp={() => {
15 | if (this.state.mode === 'info') {
16 | this.setState({ mode: null })
17 | } else if (
18 | this.state.expanded &&
19 | +new Date() - this.state.mouseDown < 250
20 | ) {
21 | this.setState({ expanded: false })
22 | } else if (!this.state.expanded) {
23 | this.setState({ expanded: true })
24 | }
25 | }}
26 | >
27 | {label} |
28 |
29 | {this.state.mode === 'info' ? (
30 | {this.props.info}
31 | ) : (
32 |
33 | {this.props.children}
34 |
35 | )}
36 | |
37 |
38 | {!this.props.info ? null : (
39 | e.preventDefault()}
43 | onMouseUp={e => {
44 | e.stopPropagation()
45 | if (this.state.mode === 'info') {
46 | this.setState({ mode: null, expanded: false })
47 | } else {
48 | this.setState({ mode: 'info', expanded: false })
49 | }
50 | }}
51 | >
52 |
53 |
54 | )}
55 | |
56 |
57 | )
58 | }
59 | }
60 |
61 | export default DetailRow
62 |
--------------------------------------------------------------------------------
/src/components/Dropdown.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | export default class Dropdown extends Component {
4 | constructor(props) {
5 | super(props)
6 | this.state = { open: false }
7 | this.onBlur = this.onBlur.bind(this)
8 | this.onClick = this.onClick.bind(this)
9 | this.onToggle = this.onToggle.bind(this)
10 | }
11 |
12 | componentWillUnmount() {
13 | document.removeEventListener('click', this.onBlur)
14 | }
15 |
16 | render() {
17 | var size = this.props.size,
18 | active = this.state.open ? ' active' : '',
19 | disabled = this.props.disabled ? ' disabled' : ''
20 |
21 | return (
22 |
61 | )
62 | }
63 |
64 | onToggle(e) {
65 | e.preventDefault()
66 | if (this.state.open) {
67 | document.removeEventListener('click', this.onBlur)
68 | } else {
69 | document.addEventListener('click', this.onBlur)
70 | }
71 | this.setState({ open: !this.state.open })
72 | }
73 |
74 | onClick() {
75 | if (this.state.open) {
76 | this.setState({ mouseOver: true, open: false })
77 | document.removeEventListener('click', this.onBlur)
78 | }
79 | }
80 |
81 | onBlur() {
82 | if (!this.state.mouseOver) {
83 | this.setState({ open: false })
84 | }
85 | }
86 | }
87 |
88 | Dropdown.defaultProps = {
89 | size: '',
90 | style: { position: 'relative', display: 'inline-block' }
91 | }
92 |
--------------------------------------------------------------------------------
/src/components/FormRow.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const FormRow = props => (
4 |
5 | {props.label} |
6 |
7 | {props.children}
8 | |
9 |
10 | )
11 |
12 | export default FormRow
13 |
--------------------------------------------------------------------------------
/src/components/Loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Loading = props => {
4 | if (props.show !== true) return null
5 | return (
6 |
7 |
8 | Loading...
9 |
10 |
11 | )
12 | }
13 |
14 | export default Loading
15 |
16 | require('react-styl')(`
17 | .loading-spinner
18 | z-index: 5
19 | position: absolute;
20 | background: rgba(255,255,255,0.9);
21 | top: 0;
22 | left: 0;
23 | right: 0;
24 | bottom: 0;
25 | display: flex;
26 | align-items: center;
27 | justify-content: center;
28 | font-size: 1.5rem;
29 | color: #333;
30 | `)
31 |
--------------------------------------------------------------------------------
/src/components/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | function freezeVp(e) {
5 | e.preventDefault()
6 | }
7 |
8 | export default class Modal extends Component {
9 | constructor(props) {
10 | super(props)
11 | this.state = {
12 | x: 0,
13 | y: 0,
14 | o: 1,
15 | co: 1,
16 | scale: 1,
17 | rotate: 0,
18 | anim: 'is-entering'
19 | }
20 | }
21 |
22 | componentDidMount() {
23 | this.portal = document.createElement('div')
24 | document.body.appendChild(this.portal)
25 | document.body.className += ' pl-modal-open'
26 | document.body.addEventListener('touchmove', freezeVp, false)
27 | this.renderContent(this.props)
28 |
29 | this.onKeyDown = this.onKeyDown.bind(this)
30 | this.onClose = this.onClose.bind(this)
31 | this.doClose = this.doClose.bind(this)
32 |
33 | window.addEventListener('keydown', this.onKeyDown)
34 | setTimeout(() => {
35 | if (this.props.onOpen) {
36 | this.props.onOpen()
37 | }
38 | this.setState({ active: true })
39 | }, 10)
40 | }
41 |
42 | componentWillUnmount() {
43 | document.body.className = document.body.className.replace(
44 | ' pl-modal-open',
45 | ''
46 | )
47 | document.body.removeEventListener('touchmove', freezeVp, false)
48 | window.removeEventListener('keydown', this.onKeyDown)
49 | ReactDOM.unmountComponentAtNode(this.portal)
50 | document.body.removeChild(this.portal)
51 | }
52 |
53 | render() {
54 | return null
55 | }
56 |
57 | componentWillReceiveProps(nextProps) {
58 | if (!this.props.shouldClose && nextProps.shouldClose) {
59 | this.doClose()
60 | }
61 | }
62 |
63 | componentDidUpdate() {
64 | this.renderContent(this.props)
65 | }
66 |
67 | renderContent() {
68 | var content = (
69 | <>
70 |
75 | this.onClose(e)}>
76 |
102 |
103 | >
104 | )
105 |
106 | ReactDOM.render(content, this.portal)
107 | }
108 |
109 | onClose(e) {
110 | if (
111 | this.props.onClose &&
112 | String(e.target.className).indexOf('pl-modal-cell') >= 0
113 | ) {
114 | this.doClose()
115 | }
116 | }
117 |
118 | doClose() {
119 | this.setState({ anim: 'is-leaving' })
120 | setTimeout(() => {
121 | this.setState({
122 | anim: `is-leaving is-${this.props.submitted ? 'submitted' : 'closed'}`
123 | })
124 | }, 10)
125 | this.onCloseTimeout = setTimeout(() => this.props.onClose(), 500)
126 | }
127 |
128 | onKeyDown(e) {
129 | if (e.keyCode === 27) {
130 | // Esc
131 | this.doClose()
132 | }
133 | if (e.keyCode === 13 && this.props.onPressEnter) {
134 | // Enter
135 | this.props.onPressEnter()
136 | }
137 | }
138 | }
139 |
140 | require('react-styl')(`
141 | .pl-modal-open
142 | overflow: hidden
143 | touch-action: none
144 | .pl-modal
145 | position: fixed
146 | z-index: 2000
147 | top: 0
148 | right: 0
149 | bottom: 0
150 | left: 0
151 | overflow-y: auto
152 | -webkit-transform: translate3d(0, 0, 0)
153 | .pl-modal-table
154 | display: table;
155 | table-layout: fixed;
156 | height: 100%;
157 | width: 100%;
158 | .pl-modal-cell
159 | display: table-cell;
160 | height: 100%;
161 | width: 100%;
162 | vertical-align: middle;
163 | padding: 50px;
164 | .pl-modal-content
165 | position: relative;
166 | overflow: hidden;
167 | border-radius: 2px;
168 | margin-left: auto;
169 | margin-right: auto;
170 | max-width: 520px;
171 | background-color: #fff
172 |
173 | .pl-modal-cell
174 | position: relative;
175 | -webkit-transition-property: opacity,-webkit-transform;
176 | transition-property: opacity,-webkit-transform;
177 | transition-property: opacity,transform;
178 | transition-property: opacity,transform,-webkit-transform
179 |
180 | .pl-modal-cell.is-entering>.pl-modal-content
181 | opacity: 0;
182 | -webkit-transform: translateY(50px) scale(.95);
183 | transform: translateY(50px) scale(.95)
184 |
185 | .pl-modal-cell.is-active.is-entering>.pl-modal-content
186 | opacity: 1;
187 | -webkit-transform: translateY(0) scale(1);
188 | transform: translateY(0) scale(1);
189 | -webkit-transition-timing-function: cubic-bezier(.15,1.45,.55,1);
190 | transition-timing-function: cubic-bezier(.15,1.45,.55,1);
191 | -webkit-transition-duration: .4s;
192 | transition-duration: .4s
193 |
194 | .pl-modal-cell.is-leaving.is-closed>.pl-modal-content
195 | opacity: 0;
196 | -webkit-transform: translateY(50px) scale(.95);
197 | transform: translateY(50px) scale(.95);
198 | -webkit-transition-timing-function: ease-in-out;
199 | transition-timing-function: ease-in-out;
200 | -webkit-transition-duration: .2s;
201 | transition-duration: .2s
202 |
203 | .pl-modal-cell.is-leaving.is-submitted>.pl-modal-content
204 | opacity: 0;
205 | -webkit-transform: translateY(-300px) translateZ(-70px) rotateX(10deg);
206 | transform: translateY(-300px) translateZ(-70px) rotateX(10deg);
207 | -webkit-transition-property: opacity,-webkit-transform;
208 | transition-property: opacity,-webkit-transform;
209 | transition-property: opacity,transform;
210 | transition-property: opacity,transform,-webkit-transform;
211 | -webkit-transition-timing-function: cubic-bezier(.5,-.33,1,1);
212 | transition-timing-function: cubic-bezier(.5,-.33,1,1);
213 | -webkit-transition-duration: .2s;
214 | transition-duration: .2s
215 |
216 | .pl-modal
217 | -webkit-transition-property: opacity;
218 | transition-property: opacity
219 |
220 | .pl-modal-bg
221 | position: fixed;
222 | top: 0;
223 | right: 0;
224 | bottom: 0;
225 | left: 0
226 | touch-action: none
227 |
228 | .pl-modal-bg
229 | background: rgba(0,0,0,.6);
230 | z-index: 1
231 |
232 | .pl-modal-bg.is-entering
233 | opacity: 0;
234 | -webkit-transition-duration: .2s;
235 | transition-duration: .2s;
236 | -webkit-transition-timing-function: ease;
237 | transition-timing-function: ease
238 |
239 | .pl-modal-bg.is-active.is-entering
240 | opacity: 1
241 |
242 | .pl-modal-bg.is-leaving
243 | opacity: 1;
244 | -webkit-transition-delay: .2s;
245 | transition-delay: .2s;
246 | -webkit-transition-duration: .2s;
247 | transition-duration: .2s;
248 | -webkit-transition-timing-function: ease-in-out;
249 | transition-timing-function: ease-in-out
250 |
251 | .pl-modal-bg.is-active.is-leaving
252 | opacity: 0
253 |
254 | .pl-modal-content
255 | border-radius: 6px;
256 | background-color: #f5f5f7;
257 | box-shadow: 0 12px 30px 0 rgba(0,0,0,.5),inset 0 1px 0 0 hsla(0,0%,100%,.65);
258 | -webkit-backface-visibility: hidden;
259 | backface-visibility: hidden
260 |
261 | `)
262 |
--------------------------------------------------------------------------------
/src/components/NavItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const NavItem = props => (
4 |
5 | {
9 | e.preventDefault()
10 | props.onClick(props.id)
11 | }}
12 | >
13 | {props.label}
14 |
15 |
16 | )
17 |
18 | export default NavItem
19 |
--------------------------------------------------------------------------------
/src/components/NavLink.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link, Route } from 'react-router-dom'
3 |
4 | const NavLink = ({ to, exact, ...rest }) => (
5 | (
6 |
7 |
8 |
9 | )}/>
10 | )
11 |
12 | export default NavLink
13 |
--------------------------------------------------------------------------------
/src/constants/Providers.js:
--------------------------------------------------------------------------------
1 | const HOST = process.env.HOST || 'localhost'
2 |
3 | export default [
4 | {
5 | name: 'Localhost',
6 | endpoints: [`http://${HOST}:8545`, `ws://${HOST}:8545`]
7 | },
8 | {
9 | name: 'Ropsten',
10 | endpoints: ['https://ropsten.infura.io', 'wss://ropsten.infura.io/ws'],
11 | faucet: 'http://faucet.ropsten.be:3001'
12 | },
13 | {
14 | name: 'Rinkeby',
15 | endpoints: ['https://rinkeby.infura.io', 'wss://rinkeby.infura.io/ws'],
16 | faucet: 'https://faucet.rinkeby.io'
17 | },
18 | {
19 | name: 'Mainnet',
20 | endpoints: ['https://mainnet.infura.io', 'wss://mainnet.infura.io/ws']
21 | },
22 | {
23 | name: 'Kovan',
24 | endpoints: ['https://kovan.infura.io'],
25 | website: 'https://kovan-testnet.github.io/website',
26 | faucet: 'https://github.com/kovan-testnet/faucet'
27 | }
28 | // {
29 | // name: 'Custom'
30 | // }
31 | ]
32 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { Provider } from 'react-redux'
4 | import { Route, HashRouter } from 'react-router-dom'
5 |
6 | import Store from './Store'
7 | import App from './pages/App'
8 |
9 | ReactDOM.render(
10 |
11 |
12 |
13 |
14 | ,
15 | document.getElementById('app')
16 | )
17 |
18 | require('react-styl').addStylesheet()
19 |
--------------------------------------------------------------------------------
/src/pages/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Switch, Route, Link } from 'react-router-dom'
3 | import { connect } from 'react-redux'
4 |
5 | import Console from './console'
6 | import Identity from './identity'
7 | import Versions from './_Versions'
8 | import Init from './_Init'
9 |
10 | import { init } from 'actions/Network'
11 | import AccountChooser from 'components/AccountChooser'
12 |
13 | import { selectAccount, setCurrency, loadWallet } from 'actions/Wallet'
14 |
15 | class App extends Component {
16 | constructor(props) {
17 | super(props)
18 | this.state = {}
19 | }
20 |
21 | componentDidMount() {
22 | this.props.initNetwork()
23 | }
24 |
25 | componentWillUnmount() {
26 | clearTimeout(this.hideNotice)
27 | }
28 |
29 | componentDidUpdate(prevProps) {
30 | if (
31 | window.innerWidth <= 575 &&
32 | this.props.location !== prevProps.location
33 | ) {
34 | window.scrollTo(0, 0)
35 | }
36 | }
37 |
38 | componentWillReceiveProps(nextProps) {
39 | // If no accounts are present, pre-populate for an easier demo experience.
40 | if (
41 | !this.props.wallet.loaded &&
42 | nextProps.wallet.loaded &&
43 | !nextProps.wallet.activeAddress
44 | ) {
45 | window.sessionStorage.privateKeys = JSON.stringify([
46 | // "0x24f3c3b01a0783948380fb683a9712f079e7d249c0461e1f40054b10b1bb0b23", // accounts[0] ClaimSignerKey
47 | "0xd6079ba5123c57b9a8cb3e1fbde9f879c7a18eeca23fa2a965e8181d3ff59f0c", // accounts[1] Identity
48 | "0x20ea25d6c8d99bea5e81918d805b4268d950559b36c5e1cfcbb1cda0197faa08", // accounts[2] Certifier
49 | "0x25acb0da38f5364588f78b4e1f33c4a3981354c9b044d64bf201aad8f38f50ae", // accounts[3] ClaimChecker
50 | ])
51 | this.props.loadWallet()
52 | this.setState({ preloaded: true })
53 | this.hideNotice = setTimeout(
54 | () => this.setState({ preloaded: false }),
55 | 3333
56 | )
57 | }
58 | }
59 |
60 | render() {
61 | return (
62 |
63 |
this.props.history.push('/')} />
64 |
107 |
108 |
109 | {!this.state.preloaded ? null : (
110 |
123 | )}
124 |
125 |
126 |
127 |
128 |
129 |
130 |
149 |
150 |
151 | )
152 | }
153 | }
154 |
155 | const mapStateToProps = state => ({
156 | account: state.wallet.activeAddress,
157 | balance: state.wallet.balances[state.wallet.activeAddress],
158 | wallet: state.wallet,
159 | nodeAccounts: state.network.accounts
160 | })
161 |
162 | const mapDispatchToProps = dispatch => ({
163 | initNetwork: () => {
164 | dispatch(init())
165 | },
166 | loadWallet: () => {
167 | dispatch(loadWallet())
168 | },
169 | selectAccount: hash => dispatch(selectAccount(hash)),
170 | setCurrency: currency => dispatch(setCurrency(currency))
171 | })
172 |
173 | export default connect(mapStateToProps, mapDispatchToProps)(App)
174 |
175 | require('react-styl')(`
176 | table.table
177 | thead tr th
178 | border-top: 0
179 | .btn-sm
180 | padding: 0.125rem 0.375rem
181 | .navbar
182 | border-bottom: 1px solid #E5E9EF;
183 | .navbar-light .navbar-text .dropdown-item.active,
184 | .navbar-light .navbar-text .dropdown-item:active
185 | color: #fff;
186 | .pointer
187 | cursor: pointer
188 | .no-wrap
189 | white-space: nowrap
190 | .footer
191 | display: flex
192 | align-items: center;
193 | color: #999;
194 | margin: 1rem 0;
195 | padding-top: 1rem;
196 | border-top: 1px solid #eee;
197 | font-size: 14px;
198 | a
199 | color: #999;
200 | .middle
201 | flex: 1
202 | text-align: center
203 | .right
204 | flex: 1
205 | text-align: right
206 | .powered-by
207 | flex: 1
208 | font-size: 14px;
209 | letter-spacing: -0.01rem;
210 | img
211 | opacity: .4;
212 | height: 12px;
213 | margin-top: -2px
214 | margin-right: 0.25rem
215 | `)
216 |
--------------------------------------------------------------------------------
/src/pages/_Init.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import Modal from 'components/Modal'
5 |
6 | import { sendFromNode } from 'actions/Network'
7 | import { reset, deployIdentityContract, addKey } from 'actions/Identity'
8 | import { selectAccount } from 'actions/Wallet'
9 | import { callDeploy, callAddKey } from 'pages/initIssuer'
10 | import { setTimeout } from 'timers'
11 |
12 | class Event extends Component {
13 | constructor(props) {
14 | super(props)
15 | this.state = { modal: false, logs: [] }
16 | this.stage = 0
17 | }
18 |
19 | async componentDidMount(){
20 | const blockNo = await web3.eth.getBlockNumber()
21 | console.log("[blockNo]", blockNo)
22 | this.shouldInit = blockNo <= 0
23 | }
24 |
25 | componentWillReceiveProps(nextProps) {
26 | var nodeAccounts = nextProps.network.accounts,
27 | walletAccounts = nextProps.wallet.accounts,
28 | balances = nextProps.wallet.balances;
29 |
30 | const RESET = this.shouldInit
31 | const LOCAL = process.env.LOCAL === "true"
32 |
33 | const broker = LOCAL ? "LocalBroker" : "SellerBuyerBroker"
34 | const uri = LOCAL ? "http://localhost:3001" : "https://digital-identity.o2oprotocol.com"
35 |
36 | if (
37 | this.stage === 0 &&
38 | nextProps.network.status === 'connected' &&
39 | nodeAccounts.length > 2 &&
40 | walletAccounts.length > 2 &&
41 | balances[walletAccounts[0]] &&
42 | RESET
43 | ) {
44 | window.localStorage.clear()
45 | this.props.reset()
46 | this.next('✔ Init Identity Account 1...')
47 | setTimeout(() => {
48 | this.props.sendFromNode(nodeAccounts[0].hash, walletAccounts[0], '5')
49 | }, 500)
50 | } else if (this.stage === 1 && RESET) {
51 | this.next('✔ Init ClaimIssuer Account 2...')
52 | setTimeout(() => {
53 | this.props.sendFromNode(nodeAccounts[0].hash, walletAccounts[1], '15')
54 | }, 500)
55 | } else if (this.stage === 2 && RESET) {
56 | this.next('✔ Init ClaimChecker Account 3...')
57 | setTimeout(() => {
58 | this.props.sendFromNode(nodeAccounts[0].hash, walletAccounts[2], '25')
59 | }, 500)
60 | } else if (this.stage === 3 && RESET) {
61 | this.next(`✔ Add ${broker} certifier...`)
62 | setTimeout(() => {
63 | this.props.selectAccount(walletAccounts[1])
64 | callDeploy(this.props.deployIdentityContract, broker, uri)
65 | }, 500)
66 | } else if (
67 | this.stage === 4 &&
68 | this.props.createIdentityResponse !== 'success' &&
69 | nextProps.createIdentityResponse === 'success'
70 | ) {
71 | this.next('✔ Add Claim Signer key...')
72 | setTimeout(() => {
73 | const identity = this.props.identity.identities.find(i => i.name === broker)
74 | const key =
75 | '0x2d1da9eb632a0a8052bb8c23aac7b482afbc7bd06ef97b8a0a54a943fa68cdfd'
76 | callAddKey(this.props.addKey, key, identity.address)
77 | }, 500)
78 | } else if (
79 | this.stage === 5 &&
80 | this.props.addKeyResponse !== 'success' &&
81 | nextProps.addKeyResponse === 'success'
82 | ) {
83 | this.next('Done!')
84 | this.props.selectAccount(walletAccounts[0])
85 | setTimeout(() => {
86 | this.setState({ shouldClose: true })
87 | }, 1500)
88 | }
89 | }
90 |
91 | next(msg) {
92 | this.stage += 1
93 | this.setState({ modal: true, logs: [...this.state.logs, msg] })
94 | }
95 |
96 | render() {
97 | if (!this.state.modal) {
98 | return null
99 | }
100 | return (
101 | {
103 | this.setState({ modal: false })
104 | if (this.props.onClose) {
105 | this.props.onClose()
106 | }
107 | }}
108 | shouldClose={this.state.shouldClose}
109 | style={{ maxWidth: 375 }}
110 | >
111 |
112 |
Initialize
113 | {this.state.logs.map((log, idx) =>
{log}
)}
114 |
115 |
116 | )
117 | }
118 | }
119 |
120 | const mapStateToProps = state => ({
121 | wallet: state.wallet,
122 | event: state.event,
123 | network: state.network,
124 | identity: state.identity,
125 | createIdentityResponse: state.identity.createIdentityResponse,
126 | addKeyResponse: state.identity.addKeyResponse
127 | })
128 |
129 | const mapDispatchToProps = dispatch => ({
130 | sendFromNode: (...args) => dispatch(sendFromNode(...args)),
131 | deployIdentityContract: (...args) =>
132 | dispatch(deployIdentityContract(...args)),
133 | addKey: (...args) => dispatch(addKey(...args)),
134 | selectAccount: (...args) => dispatch(selectAccount(...args)),
135 | reset: () => dispatch(reset())
136 | })
137 |
138 | export default connect(mapStateToProps, mapDispatchToProps)(Event)
139 |
--------------------------------------------------------------------------------
/src/pages/_Versions.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Dropdown from 'components/Dropdown'
4 |
5 | const latestVersion = '0.2.0'
6 | const oldVersions = [
7 | // { version: '0.2.0', hash: '' }
8 | ]
9 |
10 | const Versions = () => (
11 |
12 |
13 | {latestVersion}
14 |
15 |
16 | {oldVersions.map(({ version, hash }) => (
17 | {}}
22 | >
23 | {version}
24 |
25 |
26 | ))}
27 |
28 | )
29 |
30 | export default Versions
31 |
--------------------------------------------------------------------------------
/src/pages/_driver.js:
--------------------------------------------------------------------------------
1 |
2 | var driver = new Driver();
3 | window.driver = driver;
4 | // Define the steps for introduction
5 | driver.defineSteps([
6 | {
7 | element: '.wallet-chooser',
8 | popover: {
9 | title: 'Wallet Chooser',
10 | description: 'The wallet chooser lets you quickly switch between wallets. Use different wallets for Identities, Certifiers and Protected Contracts.',
11 | position: 'bottom'
12 | }
13 | },
14 | {
15 | element: '.identities-list',
16 | popover: {
17 | title: 'Identities',
18 | description: 'Identities have Claims and',
19 | position: 'bottom'
20 | }
21 | },
22 | {
23 | element: '.certifiers-list',
24 | popover: {
25 | title: 'Title on Popover',
26 | description: 'Body of the popover',
27 | position: 'top'
28 | }
29 | },
30 | {
31 | element: '.protected-list',
32 | popover: {
33 | title: 'Title on Popover',
34 | description: 'Body of the popover',
35 | position: 'right'
36 | }
37 | },
38 | ]);
39 |
--------------------------------------------------------------------------------
/src/pages/console/_IPFS.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import { setIpfs } from 'actions/Network'
5 |
6 | const HOST = process.env.HOST || 'localhost'
7 |
8 | class IPFS extends Component {
9 | render() {
10 | return (
11 |
12 | IPFS:
13 |
14 |
17 | this.props.setIpfs(`http://${HOST}:8081`, `http://${HOST}:5001`)
18 | }
19 | >
20 | Localhost
21 |
22 |
25 | this.props.setIpfs('https://ipfs.o2oprotocol.com', 'https://ipfs.o2oprotocol.com')
26 | }
27 | >
28 | ipfs.o2oprotocol.com
29 |
30 | ipfs.infura.io
31 | gateway.ipfs.io
32 |
33 |
34 | )
35 | }
36 | }
37 |
38 | // const mapStateToProps = state => ({
39 | // })
40 |
41 | const mapDispatchToProps = dispatch => ({
42 | setIpfs: (gateway, api) => dispatch(setIpfs(gateway, api))
43 | })
44 |
45 | export default connect(null, mapDispatchToProps)(IPFS)
46 |
--------------------------------------------------------------------------------
/src/pages/console/_Providers.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import {
5 | fetchAccounts,
6 | init,
7 | setProvider,
8 | sendFromNode
9 | } from 'actions/Network'
10 |
11 | class Console extends Component {
12 | constructor(props) {
13 | super(props)
14 | this.state = {}
15 | }
16 |
17 | render() {
18 | var activeProvider =
19 | this.props.providers.find(
20 | p => (p.endpoints || []).indexOf(this.props.provider) >= 0
21 | ) || this.props.providers[this.props.providers.length - 1]
22 |
23 | return (
24 |
25 |
26 |
27 | {this.props.providers.map((p, idx) => (
28 | this.setProvider(idx)}
34 | >
35 | {p.name}
36 |
37 | ))}
38 |
39 |
40 | {(activeProvider.endpoints || []).map(e => (
41 | this.props.setProvider(e)}
47 | >
48 | {e.split(':')[0].toUpperCase()}
49 |
50 | ))}
51 |
52 |
53 |
54 | {this.renderStatus()}
55 | {`to ${this.props.provider}`}
56 |
57 |
58 | {/*
59 |
Net ID: {this.props.netId}
60 |
61 | {this.props.accounts.length > 0 &&
62 |
63 |
{`~989 ETH in ${
64 | this.props.accounts.length
65 | } node managed accounts`}
66 |
67 | Transfer
68 | {'From: '}
69 |
76 |
77 | To:
78 |
85 |
86 | Amount:
87 | this.amount = ref} type="text" defaultValue="5" />
88 |
89 | {
90 | this.props.sendFromNode(this.from.value, this.to.value, this.amount.value)
91 | }}>Go
92 |
93 |
94 | } */}
95 |
96 | )
97 | }
98 |
99 | setProvider(idx) {
100 | var endpoints = this.props.providers[idx].endpoints || []
101 | var endpoint =
102 | endpoints.find(
103 | e => e.split(':')[0] === this.props.provider.split(':')[0]
104 | ) || endpoints[0]
105 |
106 | if (endpoint) {
107 | this.setState({ customEndpoint: false })
108 | this.props.setProvider(endpoint)
109 | } else {
110 | this.setState({ customEndpoint: true })
111 | }
112 | }
113 |
114 | renderStatus() {
115 | var color = 'green',
116 | text = 'Connected'
117 | if (this.props.status === 'disconnected') {
118 | text = 'Disconnected'
119 | color = 'red'
120 | } else if (this.props.status === 'connecting') {
121 | text = 'Connecting'
122 | color = 'orange'
123 | } else if (this.props.status === 'error') {
124 | text = 'Error'
125 | color = 'red'
126 | }
127 | return (
128 |
129 | {text}
130 |
131 | )
132 | }
133 | }
134 |
135 | const mapStateToProps = state => ({
136 | accounts: state.network.accounts,
137 | account: state.network.account,
138 | providers: state.network.providers,
139 | provider: state.network.provider,
140 | netId: state.network.id,
141 | status: state.network.status,
142 | wallet: state.wallet
143 | })
144 |
145 | const mapDispatchToProps = dispatch => ({
146 | init: () => dispatch(init()),
147 | fetchAccounts: () => dispatch(fetchAccounts()),
148 | setProvider: provider => dispatch(setProvider(provider)),
149 | sendFromNode: (from, to, value) => dispatch(sendFromNode(from, to, value))
150 | })
151 |
152 | export default connect(mapStateToProps, mapDispatchToProps)(Console)
153 |
--------------------------------------------------------------------------------
/src/pages/console/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import { loadWallet } from 'actions/Wallet'
5 |
6 | import Providers from './_Providers'
7 | import Accounts from './_Accounts'
8 |
9 | class Console extends Component {
10 | constructor() {
11 | super()
12 | this.state = {}
13 | }
14 | render() {
15 | return (
16 |
17 |
18 |
19 |
20 |
21 | Wallet Manager:
22 |
23 | this.props.loadWallet(false)}
28 | >
29 | In-Browser
30 |
31 | {
36 | if (this.props.browserHasProvider) {
37 | this.props.loadWallet(true)
38 | } else {
39 | alert('Please unlock MetaMask')
40 | }
41 | }}
42 | >
43 | MetaMask
44 |
45 |
46 |
47 | {this.props.externalProvider ? null : (
48 | <>
49 |
50 |
51 | >
52 | )}
53 |
54 |
55 |
56 | )
57 | }
58 | }
59 |
60 | const mapStateToProps = state => ({
61 | externalProvider: state.wallet.externalProvider,
62 | browserHasProvider: state.network.browserProvider ? true : false
63 | })
64 |
65 | const mapDispatchToProps = dispatch => ({
66 | loadWallet: external => {
67 | dispatch(loadWallet(external))
68 | }
69 | })
70 |
71 | export default connect(mapStateToProps, mapDispatchToProps)(Console)
72 |
--------------------------------------------------------------------------------
/src/pages/identity/ClaimCheckerDetail.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { connect } from 'react-redux'
3 | import { Switch, Route, NavLink } from 'react-router-dom'
4 |
5 | import {
6 | getEvents,
7 | checkClaim,
8 | removeVerifier,
9 | claimType
10 | } from 'actions/Identity'
11 |
12 | import { selectAccount } from 'actions/Wallet'
13 |
14 | import Events from './_Events'
15 | import CheckClaim from './modals/_CheckClaim'
16 |
17 | class ProtectedDetail extends Component {
18 | constructor(props) {
19 | super(props)
20 | this.state = { mode: 'summary' }
21 | this.props.getEvents('ClaimVerifier', props.match.params.id)
22 | }
23 |
24 | componentWillReceiveProps(nextProps) {
25 | if (this.props.match.params.id !== nextProps.match.params.id) {
26 | this.props.getEvents('ClaimVerifier', nextProps.match.params.id)
27 | }
28 | }
29 |
30 | render() {
31 | var identities = this.props.identities.filter(i => i.type !== 'certifier')
32 | var address = this.props.match.params.id
33 |
34 | var verifier = this.props.verifiers.find(i => i.address === address)
35 | if (!verifier) {
36 | return null
37 | }
38 | var isOwner = verifier.owner === this.props.wallet.activeAddress
39 |
40 | return (
41 | <>
42 |
79 |
80 | (
83 | <>
84 | {`Address: ${verifier.address}`}
85 | {`Owner: ${verifier.owner}`}
86 |
87 |
88 |
89 | {
94 | if (isOwner) {
95 | this.props.removeVerifier(verifier.address)
96 | this.setState({ activeIdentity: null })
97 | }
98 | }}
99 | >
100 | Remove Contract
101 |
102 |
103 | >
104 | )}
105 | />
106 | (
109 |
113 | )}
114 | />
115 | (
119 | <>
120 |
121 | this.setState({ checkClaim: true })}
124 | >
125 | {verifier.methodName || 'Check Claim via Contract'}
126 |
127 |
128 | {this.renderAttempts(this.props.events, verifier)}
129 | >
130 | )}
131 | />
132 |
133 |
134 | {this.state.checkClaim && (
135 | this.setState({ checkClaim: false })}
137 | verifier={verifier}
138 | identities={identities}
139 | checkClaim={this.props.checkClaim}
140 | response={this.props.checkClaimResponse}
141 | />
142 | )}
143 | >
144 | )
145 | }
146 |
147 | renderAttempts(events, verifier) {
148 | if (!events || !events.length) {
149 | return null
150 | }
151 |
152 | return (
153 |
154 |
155 |
156 | Block |
157 | Identity |
158 | Claim Type |
159 | Issuer |
160 | Result |
161 |
162 |
163 |
164 | {events.map((evt, idx) => (
165 |
166 | {evt.blockNumber} |
167 |
168 | {this.props.identity.names[evt.returnValues._identity] ||
169 | String(evt.returnValues._identity).substr(0, 8)}
170 | |
171 | {claimType(verifier.claimType)} |
172 |
173 | {this.props.identity.names[verifier.trustedIdentity] ||
174 | String(verifier.trustedIdentity).substr(0, 8)}
175 | |
176 |
177 | {evt.event === 'ClaimValid' ? (
178 | Valid
179 | ) : (
180 | Invalid
181 | )}
182 | |
183 |
184 | ))}
185 |
186 |
187 | )
188 | }
189 | }
190 |
191 | const mapStateToProps = (state, ownProps) => ({
192 | identity: state.identity,
193 | identities: [
194 | ...state.identity.identities,
195 | ...state.identity.officialIdentities
196 | ],
197 | verifiers: state.identity.claimVerifiers,
198 | events: state.identity.events,
199 | eventsResponse: state.identity.eventsResponse,
200 | checkClaimResponse: state.identity.checkClaimResponse,
201 | wallet: state.wallet,
202 | activeIdentity: ownProps.match.params.id
203 | })
204 |
205 | const mapDispatchToProps = dispatch => ({
206 | getEvents: (type, address) => dispatch(getEvents(type, address)),
207 | checkClaim: (...args) => dispatch(checkClaim(...args)),
208 | removeVerifier: (...args) => dispatch(removeVerifier(...args)),
209 | selectAccount: hash => dispatch(selectAccount(hash))
210 | })
211 |
212 | export default connect(mapStateToProps, mapDispatchToProps)(ProtectedDetail)
213 |
--------------------------------------------------------------------------------
/src/pages/identity/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class Home extends Component {
4 |
5 | render() {
6 | return (
7 |
8 |
9 | Select a contract for more
10 | information
11 |
12 |
13 |
14 |
Identity
15 | Controlled by Keys. Has Claims, can add Claims to other identities.
16 |
17 |
18 |
Claim Issuer
19 | Also an Identity. Trusted by Claim Checkers to issue valid claims.
20 |
21 |
22 |
Claim Checker
23 | A contract only allowing interactions from Identites holding Claims
24 | from a Trusted Issuer.
25 |
26 |
27 |
Claim
28 | Some data on one Identity that provably came from another Identity.
29 |
30 |
31 | )
32 | }
33 | }
34 |
35 | export default Home
36 |
--------------------------------------------------------------------------------
/src/pages/identity/_Events.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import ClaimHolder from '../../contracts/ClaimHolder'
4 | import decodeFn from 'utils/decodeFn'
5 |
6 | const Events = props => {
7 | if (props.eventsResponse === null) {
8 | return Loading...
9 | }
10 | return (
11 |
12 | {props.events.length ? null :
No Events
}
13 | {props.events.map((event, idx) => {
14 | var data = null
15 | if (String(event.event).match(/(ExecutionRequested|Executed)/)) {
16 | var decoded = decodeFn(ClaimHolder, event.returnValues.data)
17 | data = (
18 |
19 | {`${decoded.name}\n`}
20 | {displayEvent(decoded.params)}
21 |
22 | )
23 | }
24 |
25 | return (
26 |
27 |
28 | {event.event}
29 | Block {event.blockNumber}
30 |
31 |
{displayEvent(event.returnValues)}
32 | {data}
33 |
34 | )
35 | })}
36 |
37 | )
38 | }
39 |
40 | export function displayEvent(obj) {
41 | if (typeof obj !== 'object') {
42 | return ''
43 | }
44 | var ret = []
45 | Object.keys(obj).forEach(key => {
46 | if (!key.match(/^([0-9]+|__.*)$/)) {
47 | var val = String(obj[key])
48 | val = val.length > 32 ? `${val.substr(0, 32)}...` : val
49 | ret.push(`${key}: ${val}`)
50 | }
51 | })
52 | return ret.join('\n')
53 | }
54 |
55 | export default Events
56 |
--------------------------------------------------------------------------------
/src/pages/identity/_ValidateClaim.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | fromRpcSig,
4 | ecrecover,
5 | toBuffer,
6 | bufferToHex,
7 | pubToAddress
8 | } from 'ethereumjs-util'
9 |
10 | import ClaimHolder from '../../contracts/ClaimHolder'
11 |
12 | class ValidateClaim extends Component {
13 | constructor() {
14 | super()
15 | this.state = { valid: false, icon: 'fa-spinner fa-spin' }
16 | }
17 |
18 | componentDidMount() {
19 | this.validate()
20 | }
21 |
22 | async validate() {
23 | var claim = this.props.claim,
24 | hasKey
25 |
26 | var hashedSignature = web3.utils.soliditySha3(
27 | this.props.subject,
28 | claim.claimType,
29 | claim.data
30 | )
31 | const prefixedMsg = web3.eth.accounts.hashMessage(hashedSignature)
32 |
33 | if (claim.scheme === '4') {
34 | hasKey = true
35 | } else {
36 | var dataBuf = toBuffer(prefixedMsg)
37 | var sig = fromRpcSig(claim.signature)
38 | var recovered = ecrecover(dataBuf, sig.v, sig.r, sig.s)
39 | var recoveredKeyBuf = pubToAddress(recovered)
40 | var recoveredKey = bufferToHex(recoveredKeyBuf)
41 | var hashedRecovered = web3.utils.soliditySha3(recoveredKey)
42 |
43 | var issuer = new web3.eth.Contract(ClaimHolder.abi, claim.issuer)
44 | try {
45 | hasKey = await issuer.methods.keyHasPurpose(hashedRecovered, 3).call()
46 | } catch (e) {
47 | /* Ignore */
48 | }
49 | }
50 |
51 | this.setState({
52 | icon: hasKey ? 'fa-check' : 'fa-times',
53 | className: hasKey ? 'text-success' : 'text-danger',
54 | text: hasKey ? 'Valid' : 'Invalid'
55 | })
56 | }
57 |
58 | render() {
59 | return (
60 |
61 | {this.state.text}
62 |
63 |
64 | )
65 | }
66 | }
67 |
68 | export default ValidateClaim
69 |
--------------------------------------------------------------------------------
/src/pages/identity/_WalletChooser.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class WalletChooser extends Component {
4 | render() {
5 | return (
6 |
7 |
Active wallet:
8 |
9 | {this.props.wallet.accounts.map((a, idx) => (
10 | this.props.selectAccount(a)}
16 | >
17 | {a.substr(0, 6)}
18 |
19 | ))}
20 |
21 |
22 | )
23 | }
24 | }
25 |
26 | export default WalletChooser
27 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/ClaimDetail.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import {
3 | fromRpcSig,
4 | ecrecover,
5 | toBuffer,
6 | bufferToHex,
7 | pubToAddress
8 | } from 'ethereumjs-util'
9 |
10 | import { claimType, scheme } from 'actions/Identity'
11 |
12 | import Identity from 'contracts/ClaimHolder'
13 | import Modal from 'components/Modal'
14 | import Row from 'components/DetailRow'
15 |
16 | class ClaimDetail extends Component {
17 | constructor(props) {
18 | super(props)
19 | this.state = {}
20 | }
21 |
22 | async componentDidMount() {
23 | var { address } = this.props.identity
24 | var identity = new web3.eth.Contract(Identity.abi, address)
25 | var claim = await identity.methods.getClaim(this.props.claimId).call()
26 | if (claim) {
27 | if (claim.scheme === '4') {
28 | this.setState({ claim, hasKey: true })
29 | return
30 | }
31 | var expectedData = web3.utils.soliditySha3('Verified OK')
32 | var hashedSignature = web3.utils.soliditySha3(
33 | address,
34 | claim.claimType,
35 | claim.data
36 | )
37 | const prefixedMsg = web3.eth.accounts.hashMessage(hashedSignature)
38 |
39 | var dataBuf = toBuffer(prefixedMsg)
40 | var sig = fromRpcSig(claim.signature)
41 | var recovered = ecrecover(dataBuf, sig.v, sig.r, sig.s)
42 | var recoveredKeyBuf = pubToAddress(recovered)
43 | var recoveredKey = bufferToHex(recoveredKeyBuf)
44 | var hashedRecovered = web3.utils.soliditySha3(recoveredKey)
45 |
46 | var issuer = new web3.eth.Contract(Identity.abi, claim.issuer)
47 | try {
48 | var hasKey = await issuer.methods
49 | .keyHasPurpose(hashedRecovered, 3)
50 | .call()
51 | } catch (e) {
52 | /*Ignore*/
53 | }
54 |
55 | this.setState({
56 | claim,
57 | recoveredKey,
58 | hasKey,
59 | hashedSignature,
60 | hashedRecovered,
61 | expectedData
62 | })
63 | }
64 | }
65 |
66 | render() {
67 | const { claim, hasKey } = this.state
68 | if (!claim) {
69 | return null
70 | }
71 |
72 | return (
73 | this.props.onClose()}
77 | >
78 | Claim Detail
79 |
80 |
81 |
85 | {this.props.identity.address}
86 |
87 |
91 | {claim.issuer}
92 |
93 |
97 | {this.props.claimId}
98 |
99 | {`${claimType(claim.claimType)} (${claim.claimType})`}
103 | {`${scheme(claim.scheme)} (${claim.scheme})`}
107 |
111 | {web3.utils.hexToAscii(claim.data)}
112 |
113 |
117 | {claim.uri}
118 |
119 |
123 | {this.state.hashedSignature}
124 |
125 |
129 | {claim.signature}
130 |
131 |
136 | {this.state.recoveredKey}
137 |
138 |
142 | {this.state.hashedRecovered}
143 |
144 |
148 |
153 | {hasKey ? 'Yes' : 'No'}
154 |
155 |
156 |
157 |
158 |
159 | )
160 | }
161 | }
162 |
163 | export default ClaimDetail
164 |
165 | require('react-styl')(`
166 | th.label
167 | white-space: no-wrap
168 | td.info a
169 | color: #999
170 | &:hover
171 | color: #000
172 | &.active
173 | color: green
174 | .no-wrap
175 | white-space: nowrap
176 | .bt td, .bt th
177 | border-top-width: 3px
178 | .wb
179 | word-break: break-all
180 | .ellipsis
181 | position: absolute
182 | width: 100%
183 | overflow: hidden
184 | white-space: nowrap
185 | text-overflow: ellipsis
186 | `)
187 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/KeyDetail.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import { keyPurpose, keyType } from 'actions/Identity'
4 |
5 | import Identity from 'contracts/ClaimHolder'
6 | import Modal from 'components/Modal'
7 | import Row from 'components/DetailRow'
8 |
9 | class KeyDetail extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.state = {}
13 | }
14 |
15 | async componentDidMount() {
16 | var identity = new web3.eth.Contract(
17 | Identity.abi,
18 | this.props.identity.address
19 | )
20 | var key = await identity.methods.getKey(this.props.keyId).call()
21 | if (key) {
22 | this.setState({ key })
23 | }
24 | }
25 |
26 | render() {
27 | const { key } = this.state
28 | if (!key) {
29 | return null
30 | }
31 |
32 | return (
33 | this.props.onClose()}
37 | >
38 | Key Detail
39 |
40 |
41 |
45 | {this.props.identity.address}
46 |
47 |
48 | {`${keyPurpose(key.purpose)} (${key.purpose})`}
49 |
50 |
51 | {`${keyType(key.keyType)} (${key.keyType})`}
52 |
53 |
54 | {this.props.keyId}
55 |
56 |
57 |
58 |
59 | )
60 | }
61 | }
62 |
63 | export default KeyDetail
64 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_AddKey.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 | import FormRow from 'components/FormRow'
6 |
7 | import { KeyPurpose, KeyTypes } from 'actions/Identity'
8 |
9 | class AddKey extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.state = {
13 | keyPurpose: '1',
14 | keyType: '1',
15 | key: '',
16 | publicKey: '',
17 | privateKey: ''
18 | }
19 | }
20 |
21 | componentWillReceiveProps(nextProps) {
22 | if (this.props.response !== 'success' && nextProps.response === 'success') {
23 | this.setState({ shouldClose: true, submitted: true })
24 | }
25 | if (
26 | this.props.response !== 'submitted' &&
27 | nextProps.response === 'submitted'
28 | ) {
29 | this.setState({ loading: true })
30 | }
31 | if (this.props.response !== 'error' && nextProps.response === 'error') {
32 | this.setState({ error: true })
33 | }
34 | }
35 |
36 | render() {
37 | return (
38 | this.props.onClose()}
44 | >
45 |
46 | Add a Key to an Identity:
47 | {this.props.wrongOwner && (
48 |
49 | {`Active wallet does not own this identity`}
50 |
51 | )}
52 | {this.state.error && (
53 |
54 | Error! Please try again.
55 |
56 | )}
57 |
133 |
134 | {
137 | this.props.addKey({
138 | purpose: this.state.keyPurpose,
139 | keyType: this.state.keyType,
140 | key: this.state.key,
141 | identity: this.props.identity
142 | })
143 | }}
144 | >
145 | Add Key
146 |
147 |
148 |
149 | )
150 | }
151 |
152 | generateKeys() {
153 | var privateKey = web3.utils.randomHex(32)
154 | for (var i = privateKey.length; i < 66; i++) {
155 | privateKey += '0'
156 | }
157 | var publicKey = web3.eth.accounts.privateKeyToAccount(privateKey).address
158 | var key = web3.utils.sha3(publicKey)
159 |
160 | this.setState({ privateKey, publicKey, key })
161 | }
162 | }
163 |
164 | export default AddKey
165 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_Approve.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 |
6 | class NewIdentity extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {}
10 | }
11 |
12 | componentWillReceiveProps(nextProps) {
13 | if (this.props.response !== 'success' && nextProps.response === 'success') {
14 | this.setState({ shouldClose: true, submitted: true })
15 | }
16 | if (this.props.response !== 'submitted' && nextProps.response === 'submitted') {
17 | this.setState({ loading: true })
18 | }
19 | if (this.props.response !== 'error' && nextProps.response === 'error') {
20 | this.setState({ error: true })
21 | }
22 | }
23 |
24 | render() {
25 | return (
26 | this.props.onClose()}
32 | >
33 |
34 | Are you sure?
35 |
36 | {
39 | this.props.approveExecution(
40 | this.props.identity,
41 | this.props.executionId
42 | )
43 | }}
44 | >
45 | Approve
46 |
47 |
48 |
49 | )
50 | }
51 | }
52 |
53 | export default NewIdentity
54 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_CheckClaim.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 | import FormRow from 'components/FormRow'
6 |
7 | class CheckClaim extends Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {
11 | identity: props.identities[0] ? props.identities[0].address : '',
12 | verifier: props.verifier.address,
13 | submitted: false
14 | }
15 | }
16 |
17 | componentWillReceiveProps(nextProps) {
18 | if (this.props.response !== 'success' && nextProps.response === 'success') {
19 | this.setState({ shouldClose: true, submitted: true })
20 | }
21 | if (
22 | this.props.response !== 'submitted' &&
23 | nextProps.response === 'submitted'
24 | ) {
25 | this.setState({ loading: true })
26 | }
27 | }
28 |
29 | render() {
30 | return (
31 | this.props.onClose()}
36 | >
37 |
38 |
39 |
Check for valid claim:
40 |
41 |
42 |
43 |
58 |
59 |
60 |
61 |
62 | {
65 | this.setState({ submitted: true })
66 | this.props.checkClaim(
67 | this.state.verifier,
68 | this.state.identity,
69 | this.props.verifier.claimType
70 | )
71 | }}
72 | >
73 | Check Claim
74 |
75 |
76 |
77 |
78 | )
79 | }
80 | }
81 |
82 | export default CheckClaim
83 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_NewClaimIssuer.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 | import FormRow from 'components/FormRow'
6 |
7 | import { ClaimTypes, claimType } from 'actions/Identity'
8 |
9 | class NewClaimIssuer extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.state = {
13 | name: '',
14 | uri: '',
15 | preAdd: false,
16 | icon: 'key',
17 | claimType: '3',
18 | stage: 'main',
19 | signerServices: []
20 | }
21 | }
22 |
23 | componentWillReceiveProps(nextProps) {
24 | if (this.props.response !== 'success' && nextProps.response === 'success') {
25 | this.setState({ shouldClose: true, submitted: true })
26 | }
27 | if (
28 | this.props.response !== 'submitted' &&
29 | nextProps.response === 'submitted'
30 | ) {
31 | this.setState({ loading: true })
32 | }
33 | }
34 |
35 | render() {
36 | return (
37 | this.props.onClose()}
44 | onOpen={() => this.nameInput.focus()}
45 | onPressEnter={() => this.onDeploy()}
46 | >
47 |
48 | {this.state.stage === 'main'
49 | ? this.renderMain()
50 | : this.renderSignerService()}
51 |
52 | )
53 | }
54 |
55 | renderMain() {
56 | var { identityType, activeAddress, identities } = this.props
57 | var otherTypeSameOwner = identities.find(
58 | i => i.type !== identityType && i.owner === activeAddress
59 | )
60 | return (
61 | <>
62 |
63 | Deploy a new Claim Issuer contract:
64 |
65 | {otherTypeSameOwner && (
66 |
67 | {`You may want to use a different wallet`}
68 |
69 | )}
70 |
83 | {this.state.signerServices.length === 0 ? null : (
84 |
85 |
86 |
87 | Claim Type |
88 | Icon |
89 | Service URL |
90 |
91 |
92 |
93 | {this.state.signerServices.map((ss, idx) => (
94 |
95 | {claimType(ss.claimType)} |
96 |
97 |
98 | |
99 | {ss.uri} |
100 |
101 | ))}
102 |
103 |
104 | )}
105 | {
108 | e.preventDefault()
109 | this.setState({
110 | stage: 'add-service',
111 | uri: '',
112 | icon: 'key',
113 | claimType: '7'
114 | })
115 | }}
116 | >
117 | Add Claim Signer Service
118 |
119 |
120 | this.onDeploy()}
123 | >
124 | Deploy
125 |
126 |
127 | >
128 | )
129 | }
130 |
131 | renderSignerService() {
132 | const Btn = props => (
133 | {
138 | this.setState({
139 | icon: props.icon === this.state.icon ? null : props.icon
140 | })
141 | }}
142 | >
143 |
144 |
145 | )
146 |
147 | return (
148 | <>
149 |
150 | Add Signer Service to Claim Issuer:
151 |
152 |
194 |
195 | {
198 | this.setState({ stage: 'main' })
199 | }}
200 | >
201 | Cancel
202 |
203 | {
206 | this.setState({
207 | stage: 'main',
208 | signerServices: [
209 | ...this.state.signerServices,
210 | {
211 | uri: this.state.uri,
212 | icon: this.state.icon,
213 | claimType: this.state.claimType
214 | }
215 | ]
216 | })
217 | }}
218 | >
219 | Add
220 |
221 |
222 | >
223 | )
224 | }
225 |
226 | onDeploy() {
227 | this.props.deployIdentityContract(
228 | this.state.name,
229 | this.props.identityType,
230 | this.state.uri,
231 | null,
232 | this.state.icon,
233 | this.state.signerServices
234 | )
235 | }
236 | }
237 |
238 | export default NewClaimIssuer
239 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_NewVerifier.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import FormRow from 'components/FormRow'
5 | import Loading from 'components/Loading'
6 |
7 | import { ClaimTypes } from 'actions/Identity'
8 |
9 | class NewVerifier extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.state = {
13 | name: '',
14 | claimType: '10',
15 | trustedIdentity: props.identities[0] ? props.identities[0].address : '',
16 | methodName: ''
17 | }
18 | }
19 |
20 | componentWillReceiveProps(nextProps) {
21 | if (this.props.response !== 'success' && nextProps.response === 'success') {
22 | this.setState({ shouldClose: true, submitted: true, loading: false })
23 | }
24 | if (!this.props.response && nextProps.response === 'submitted') {
25 | this.setState({ loading: true })
26 | }
27 | }
28 |
29 | render() {
30 | return (
31 | this.props.onClose()}
37 | onOpen={() => this.nameInput.focus()}
38 | onPressEnter={() => this.onDeploy()}
39 | >
40 |
41 |
42 | Deploy a new Claim Checker Contract:
43 |
44 | {!this.props.identities.length && (
45 |
46 | {`Try deploying a Certifier first`}
47 |
48 | )}
49 |
50 |
107 |
108 | this.onDeploy()}>
109 | Deploy
110 |
111 |
112 |
113 | )
114 | }
115 |
116 | onDeploy() {
117 | const { name, trustedIdentity, methodName, claimType } = this.state
118 | this.props.deployClaimVerifier({
119 | name,
120 | trustedIdentity,
121 | methodName,
122 | claimType
123 | })
124 | }
125 | }
126 |
127 | export default NewVerifier
128 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_RemoveClaim.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 |
6 | class RemoveClaim extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {}
10 | }
11 |
12 | componentWillReceiveProps(nextProps) {
13 | if (this.props.response !== 'success' && nextProps.response === 'success') {
14 | this.setState({ shouldClose: true, submitted: true })
15 | }
16 | if (this.props.response !== 'submitted' && nextProps.response === 'submitted') {
17 | this.setState({ loading: true })
18 | }
19 | if (this.props.response !== 'error' && nextProps.response === 'error') {
20 | this.setState({ error: true })
21 | }
22 | }
23 |
24 | render() {
25 | return (
26 | this.props.onClose()}
31 | >
32 |
33 |
34 |
35 |
Are you sure?
36 | {this.props.wrongOwner && (
37 |
38 | {`Active wallet does not own this identity`}
39 |
40 | )}
41 | {this.state.error && (
42 |
Error! Please try again.
43 | )}
44 |
45 | {
48 | this.props.removeClaim()
49 | }}
50 | >
51 | Remove Claim
52 |
53 |
54 |
55 |
56 | )
57 | }
58 | }
59 |
60 | export default RemoveClaim
61 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_RemoveIdentity.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 |
5 | class RemoveIdentity extends Component {
6 | constructor(props) {
7 | super(props)
8 | this.state = {}
9 | }
10 |
11 | render() {
12 | return (
13 | this.props.onClose()}
16 | >
17 |
18 |
Are you sure you wish to remove this identity?
19 |
20 | {
23 | this.props.removeIdentity()
24 | }}
25 | >
26 | Remove
27 |
28 |
29 |
30 |
31 | )
32 | }
33 | }
34 |
35 | export default RemoveIdentity
36 |
--------------------------------------------------------------------------------
/src/pages/identity/modals/_RemoveKey.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Modal from 'components/Modal'
4 | import Loading from 'components/Loading'
5 |
6 | class RemoveKey extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {}
10 | }
11 |
12 | componentWillReceiveProps(nextProps) {
13 | if (this.props.response !== 'success' && nextProps.response === 'success') {
14 | this.setState({ shouldClose: true, submitted: true })
15 | }
16 | if (this.props.response !== 'submitted' && nextProps.response === 'submitted') {
17 | this.setState({ loading: true })
18 | }
19 | if (this.props.response !== 'error' && nextProps.response === 'error') {
20 | this.setState({ error: true })
21 | }
22 | }
23 |
24 | render() {
25 | return (
26 | this.props.onClose()}
31 | >
32 |
33 |
34 |
35 |
Are you sure?
36 | {this.props.wrongOwner && (
37 |
38 | {`Active wallet does not own this identity`}
39 |
40 | )}
41 | {this.state.error && (
42 |
Error! Please try again.
43 | )}
44 |
45 | {
48 | this.props.removeKey()
49 | }}
50 | >
51 | Remove Key
52 |
53 |
54 |
55 |
56 | )
57 | }
58 | }
59 |
60 | export default RemoveKey
61 |
--------------------------------------------------------------------------------
/src/pages/initIssuer.js:
--------------------------------------------------------------------------------
1 | let waitLastDeploy = Promise.resolve()
2 | let waitLastAddKey = Promise.resolve()
3 |
4 | export const callDeploy = (deployIdentityContract, issuerName, oauthLink) => {
5 | waitLastDeploy = waitLastDeploy.then(
6 | () =>
7 | new Promise(resolve => {
8 | const liveServices = [
9 | {
10 | uri: `${oauthLink}/fb-auth`,
11 | icon: 'facebook',
12 | claimType: '3'
13 | },
14 | {
15 | uri: `${oauthLink}/twitter-auth`,
16 | icon: 'twitter',
17 | claimType: '4'
18 | },
19 | {
20 | uri: `${oauthLink}/github-auth`,
21 | icon: 'github',
22 | claimType: '5'
23 | },
24 | {
25 | uri: `${oauthLink}/google-auth`,
26 | icon: 'google',
27 | claimType: '6'
28 | },
29 | {
30 | uri: `${oauthLink}/linkedin-auth`,
31 | icon: 'linkedin',
32 | claimType: '9'
33 | }
34 | ]
35 |
36 | const localServices = [
37 | {
38 | uri: `${oauthLink}/twitter-auth`,
39 | icon: 'twitter',
40 | claimType: '4'
41 | },
42 | {
43 | uri: `${oauthLink}/google-auth`,
44 | icon: 'google',
45 | claimType: '6'
46 | },
47 | {
48 | uri: `${oauthLink}/linkedin-auth`,
49 | icon: 'linkedin',
50 | claimType: '9'
51 | }
52 | ]
53 |
54 | const services = process.env.LOCAL === "true" ? localServices : liveServices
55 |
56 | setTimeout(() => {
57 | deployIdentityContract(
58 | issuerName,
59 | 'certifier',
60 | `${oauthLink}/github-auth`,
61 | false,
62 | 'issuer-icon',
63 | services
64 | )
65 | resolve()
66 | }, 500)
67 | })
68 | )
69 | }
70 |
71 | export const callAddKey = (addKey, key, address) => {
72 | waitLastAddKey = waitLastAddKey.then(
73 | () =>
74 | new Promise(resolve => {
75 | setTimeout(() => {
76 | addKey({
77 | key,
78 | purpose: '3',
79 | keyType: '1',
80 | identity: address
81 | })
82 | resolve()
83 | }, 500)
84 | })
85 | )
86 | }
87 |
--------------------------------------------------------------------------------
/src/reducers/Identity.js:
--------------------------------------------------------------------------------
1 | import { IdentityConstants } from 'actions/Identity'
2 | import { NetworkConstants } from 'actions/Network'
3 |
4 | const officialIdentities =
5 | typeof OfficialIdentities !== 'undefined' ? OfficialIdentities : []
6 |
7 | const initialState = {
8 | contract: null,
9 | officialIdentities: [],
10 | identities: [],
11 | claimVerifiers: [],
12 | keys: [],
13 | claims: [],
14 | createIdentityResponse: undefined,
15 | createVerifierResponse: undefined,
16 | createKeyResponse: undefined,
17 | createClaimResponse: undefined,
18 | removeClaimResponse: undefined,
19 | checkClaimResponse: undefined,
20 | approveExecutionResponse: undefined,
21 | eventsResponse: undefined,
22 | lastDeployedIdentity: undefined,
23 | events: []
24 | }
25 |
26 | function getInitialState() {
27 | var state = {
28 | ...initialState
29 | }
30 | try {
31 | state.identities = JSON.parse(localStorage.identities)
32 | } catch (e) {
33 | /* Ignore */
34 | }
35 | try {
36 | state.claimVerifiers = JSON.parse(localStorage.claimVerifiers)
37 | } catch (e) {
38 | /* Ignore */
39 | }
40 | return applyContractNames(state)
41 | }
42 |
43 | function applyContractNames(state) {
44 | var names = {}
45 | state.identities.forEach(i => (names[i.address] = i.name))
46 | state.claimVerifiers.forEach(i => (names[i.address] = i.name))
47 | return { ...state, names }
48 | }
49 |
50 | export const Purposes = [
51 | { id: '1', name: 'Management' },
52 | { id: '2', name: 'Action' },
53 | { id: '3', name: 'Claim Signer' },
54 | { id: '4', name: 'Encryption' }
55 | ]
56 |
57 | export const KeyTypes = [{ id: '1', name: 'ECDSA' }, { id: '2', name: 'RSA' }]
58 |
59 | export default function Identity(state = getInitialState(), action = {}) {
60 | switch (action.type) {
61 | case NetworkConstants.CHANGE_SUCCESS:
62 | return {
63 | ...state,
64 | officialIdentities: officialIdentities[action.id] || []
65 | }
66 |
67 | case IdentityConstants.RESET:
68 | return getInitialState()
69 |
70 | case IdentityConstants.CHECK_CLAIM:
71 | return { ...state, checkClaimResponse: 'submitted' }
72 |
73 | case IdentityConstants.CHECK_CLAIM_HASH:
74 | return { ...state, checkClaimResponse: 'in-pool' }
75 |
76 | case IdentityConstants.CHECK_CLAIM_SUCCESS:
77 | return { ...state, checkClaimResponse: 'success' }
78 |
79 | case IdentityConstants.CHECK_CLAIM_ERROR:
80 | return { ...state, checkClaimResponse: 'error' }
81 |
82 | case IdentityConstants.ADD_CLAIM:
83 | return { ...state, addClaimResponse: 'submitted' }
84 |
85 | case IdentityConstants.ADD_CLAIM_HASH:
86 | return { ...state, addClaimResponse: 'in-pool' }
87 |
88 | case IdentityConstants.ADD_CLAIM_SUCCESS:
89 | return { ...state, addClaimResponse: 'success' }
90 |
91 | case IdentityConstants.ADD_CLAIM_ERROR:
92 | return { ...state, addClaimResponse: 'error' }
93 |
94 | case IdentityConstants.REMOVE_CLAIM:
95 | return { ...state, removeClaimResponse: 'submitted' }
96 |
97 | case IdentityConstants.REMOVE_CLAIM_HASH:
98 | return { ...state, removeClaimResponse: 'in-pool' }
99 |
100 | case IdentityConstants.REMOVE_CLAIM_SUCCESS:
101 | return { ...state, removeClaimResponse: 'success' }
102 |
103 | case IdentityConstants.REMOVE_CLAIM_ERROR:
104 | return { ...state, removeClaimResponse: 'error' }
105 |
106 | case IdentityConstants.ADD_KEY:
107 | return { ...state, addKeyResponse: 'submitted' }
108 |
109 | case IdentityConstants.ADD_KEY_HASH:
110 | return { ...state, addKeyResponse: 'in-pool' }
111 |
112 | case IdentityConstants.ADD_KEY_SUCCESS:
113 | return { ...state, addKeyResponse: 'success' }
114 |
115 | case IdentityConstants.ADD_KEY_ERROR:
116 | return { ...state, addKeyResponse: 'error' }
117 |
118 | case IdentityConstants.REMOVE_KEY:
119 | return { ...state, removeKeyResponse: 'submitted' }
120 |
121 | case IdentityConstants.REMOVE_KEY_HASH:
122 | return { ...state, removeKeyResponse: 'in-pool' }
123 |
124 | case IdentityConstants.REMOVE_KEY_SUCCESS:
125 | return { ...state, removeKeyResponse: 'success' }
126 |
127 | case IdentityConstants.REMOVE_KEY_ERROR:
128 | return { ...state, removeKeyResponse: 'error' }
129 |
130 | case IdentityConstants.APPROVE_EXECUTION:
131 | return { ...state, approveExecutionResponse: 'submitted' }
132 |
133 | case IdentityConstants.APPROVE_EXECUTION_HASH:
134 | return { ...state, approveExecutionResponse: 'in-pool' }
135 |
136 | case IdentityConstants.APPROVE_EXECUTION_SUCCESS:
137 | return { ...state, approveExecutionResponse: 'success' }
138 |
139 | case IdentityConstants.APPROVE_EXECUTION_ERROR:
140 | return { ...state, approveExecutionResponse: 'error' }
141 |
142 | case IdentityConstants.DEPLOY:
143 | return { ...state, createIdentityResponse: 'submitted' }
144 |
145 | case IdentityConstants.DEPLOY_HASH:
146 | return { ...state, createIdentityResponse: 'in-pool' }
147 |
148 | case IdentityConstants.DEPLOY_RECEIPT: {
149 | let newState = {
150 | ...state,
151 | createIdentityResponse: 'success',
152 | identities: [
153 | ...state.identities,
154 | {
155 | name: action.name,
156 | address: action.receipt.contractAddress,
157 | owner: action.owner,
158 | type: action.identityType,
159 | signerServices: action.signerServices
160 | }
161 | ],
162 | lastDeployedIdentity: action.receipt.contractAddress
163 | }
164 | localStorage.identities = JSON.stringify(newState.identities)
165 | return applyContractNames(newState)
166 | }
167 |
168 | case IdentityConstants.IMPORT: {
169 | let newState = {
170 | ...state,
171 | identities: [
172 | ...state.identities,
173 | {
174 | name: action.name,
175 | address: action.address,
176 | owner: action.owner,
177 | type: action.identityType
178 | }
179 | ]
180 | }
181 | localStorage.identities = JSON.stringify(newState.identities)
182 | return applyContractNames(newState)
183 | }
184 |
185 | case IdentityConstants.DEPLOY_VERIFIER:
186 | return { ...state, createVerifierResponse: 'submitted' }
187 |
188 | case IdentityConstants.DEPLOY_VERIFIER_HASH:
189 | return {
190 | ...state,
191 | contract: action.contract,
192 | createVerifierResponse: 'in-pool'
193 | }
194 |
195 | case IdentityConstants.DEPLOY_VERIFIER_RECEIPT: {
196 | let newState = {
197 | ...state,
198 | createVerifierResponse: 'success',
199 | claimVerifiers: [
200 | ...state.claimVerifiers,
201 | {
202 | name: action.name,
203 | address: action.receipt.contractAddress,
204 | owner: action.owner,
205 | trustedIdentity: action.trustedIdentity,
206 | methodName: action.methodName,
207 | claimType: action.claimType
208 | }
209 | ]
210 | }
211 | localStorage.claimVerifiers = JSON.stringify(newState.claimVerifiers)
212 | return applyContractNames(newState)
213 | }
214 |
215 | case IdentityConstants.GET_EVENTS:
216 | return { ...state, eventsResponse: null }
217 |
218 | case IdentityConstants.GET_EVENTS_SUCCESS:
219 | return { ...state, events: action.events, eventsResponse: 200 }
220 |
221 | case IdentityConstants.REMOVE: {
222 | let newState = {
223 | ...state,
224 | identities: state.identities.filter(i => i.address !== action.address)
225 | }
226 | localStorage.identities = JSON.stringify(newState.identities)
227 | return applyContractNames(newState)
228 | }
229 |
230 | case IdentityConstants.REMOVE_VERIFIER: {
231 | let newState = {
232 | ...state,
233 | claimVerifiers: state.claimVerifiers.filter(
234 | i => i.address !== action.address
235 | )
236 | }
237 | localStorage.claimVerifiers = JSON.stringify(newState.claimVerifiers)
238 | return applyContractNames(newState)
239 | }
240 | }
241 |
242 | return state
243 | }
244 |
--------------------------------------------------------------------------------
/src/reducers/Network.js:
--------------------------------------------------------------------------------
1 | import { NetworkConstants } from 'actions/Network'
2 |
3 | import Providers from 'constants/Providers'
4 |
5 | const HOST = process.env.HOST || 'localhost'
6 | let ipfsGateway = 'https://ipfs.o2oprotocol.com',
7 | ipfsRPC = 'https://ipfs.o2oprotocol.com',
8 | provider = 'https://rinkeby.infura.io',
9 | browserProvider = false
10 |
11 | if (process.env.NODE_ENV !== 'production') {
12 | ipfsGateway = `http://${HOST}:8081`
13 | ipfsRPC = `http://${HOST}:5001`
14 | provider = `http://${HOST}:8545`
15 | }
16 |
17 | if (typeof window !== 'undefined') {
18 | provider = window.sessionStorage.provider || provider
19 | if (window.web3) {
20 | browserProvider = web3.currentProvider
21 | }
22 | window.web3 = new Web3(provider)
23 | }
24 |
25 | const initialState = {
26 | id: null,
27 | contract: null,
28 | accounts: [],
29 | account: null,
30 | status: 'disconnected',
31 |
32 | browserProvider,
33 | providers: Providers,
34 | provider,
35 |
36 | ipfsGateway,
37 | ipfsRPC
38 | }
39 |
40 | export default function Network(state = initialState, action = {}) {
41 | switch (action.type) {
42 | case NetworkConstants.GET_CONTRACT_SUCCESS:
43 | return {
44 | ...state,
45 | contract: action.contract
46 | }
47 |
48 | case NetworkConstants.CHANGE_SUCCESS:
49 | return {
50 | ...state,
51 | status: 'connected',
52 | id: action.id,
53 | contract: action.contract,
54 | accounts: action.accounts,
55 | account: action.accounts[0]
56 | }
57 |
58 | case NetworkConstants.CHANGE_ERROR:
59 | return {
60 | ...state,
61 | status: 'error'
62 | }
63 |
64 | case NetworkConstants.SELECT_ACCOUNT:
65 | return {
66 | ...state,
67 | account: state.accounts.find(a => a.hash === action.hash)
68 | }
69 |
70 | case NetworkConstants.UPDATE_BALANCE_SUCCESS:
71 | var acctIdx = state.accounts.findIndex(a => a.hash === action.account)
72 | if (acctIdx < 0) {
73 | return state
74 | }
75 | var accounts = [...state.accounts],
76 | account = {
77 | hash: action.account,
78 | balance: action.balance
79 | }
80 | accounts[acctIdx] = account
81 | return {
82 | ...state,
83 | accounts,
84 | account
85 | }
86 |
87 | case NetworkConstants.SET_PROVIDER:
88 | window.sessionStorage.provider = action.provider
89 | return {
90 | ...state,
91 | status: 'connecting',
92 | provider: action.provider
93 | }
94 |
95 | case NetworkConstants.SET_IPFS:
96 | return {
97 | ...state,
98 | ipfsGateway: action.gateway,
99 | ipfsRPC: action.api
100 | }
101 | }
102 |
103 | return state
104 | }
105 |
--------------------------------------------------------------------------------
/src/reducers/Wallet.js:
--------------------------------------------------------------------------------
1 | import { WalletConstants } from 'actions/Wallet'
2 | import { NetworkConstants } from 'actions/Network'
3 |
4 | import balance from 'utils/balance'
5 |
6 | const initialState = {
7 | externalProvider: false,
8 |
9 | activeAddress: null,
10 | balances: {},
11 | currency: 'eth',
12 | currencyStr: '$',
13 | exchangeRates: {
14 | usd: 400
15 | },
16 |
17 | unsaved: false,
18 | loaded: false,
19 | raw: {},
20 | accounts: [],
21 | active: null,
22 | locked: false,
23 | tryUnlock: false
24 | }
25 |
26 | export default function Wallet(state = initialState, action = {}) {
27 | switch (action.type) {
28 | case WalletConstants.SELECT_ACCOUNT_SUCCESS:
29 | return {
30 | ...state,
31 | activeAddress: action.activeAddress
32 | }
33 |
34 | case WalletConstants.LOAD_EXTERNAL_SUCCESS:
35 | return {
36 | ...state,
37 | activeAddress: action.activeAddress,
38 | accounts: [],
39 | balances: action.balances,
40 | active: null,
41 | loaded: false
42 | }
43 |
44 | case WalletConstants.LOAD:
45 | return { ...state, externalProvider: action.external }
46 |
47 | case WalletConstants.LOAD_SUCCESS:
48 | return {
49 | ...state,
50 | raw: action.wallet,
51 | accounts: action.accounts,
52 | balances: action.balances,
53 | active: action.wallet[0] ? action.wallet[0] : null,
54 | activeAddress: action.wallet[0] ? action.wallet[0].address : null,
55 | loaded: true
56 | }
57 |
58 | case WalletConstants.ADD_ACCOUNT_SUCCESS:
59 | return {
60 | ...state,
61 | raw: action.wallet,
62 | accounts: [...state.accounts, action.account.address],
63 | balances: {
64 | ...state.balances,
65 | [action.account.address]: balance('0', state.exchangeRates)
66 | },
67 | unsaved: true
68 | }
69 |
70 | case WalletConstants.IMPORT_ACCOUNT_SUCCESS:
71 | return {
72 | ...state,
73 | raw: action.wallet,
74 | accounts: [...state.accounts, action.account.address],
75 | balances: {
76 | ...state.balances,
77 | [action.account.address]: action.balance
78 | },
79 | unsaved: true
80 | }
81 |
82 | case WalletConstants.REMOVE_ACCOUNT_SUCCESS:
83 | return {
84 | ...state,
85 | raw: action.wallet,
86 | accounts: state.accounts.filter(h => h !== action.hash),
87 | unsaved: true
88 | }
89 |
90 | case WalletConstants.SAVE_SUCCESS:
91 | return { ...state, unsaved: false }
92 |
93 | case WalletConstants.SET_CURRENCY:
94 | return {
95 | ...state,
96 | currency: action.currency,
97 | currencyStr: action.currency === 'usd' ? '$' : 'ETH'
98 | }
99 |
100 | case WalletConstants.UPDATE_BALANCE_SUCCESS:
101 | return {
102 | ...state,
103 | balances: {
104 | ...state.balances,
105 | [action.account]: action.balance
106 | }
107 | }
108 |
109 | case NetworkConstants.UPDATE_BALANCE_SUCCESS:
110 | return {
111 | ...state,
112 | balances: {
113 | ...state.balances,
114 | [action.account]: action.balance
115 | }
116 | }
117 |
118 | case WalletConstants.LOCK_WALLET:
119 | return {
120 | ...state,
121 | locked: true,
122 | tryUnlock: false
123 | }
124 |
125 | case WalletConstants.UNLOCK_WALLET:
126 | return {
127 | ...state,
128 | tryUnlock: true
129 | }
130 |
131 | case WalletConstants.UNLOCKED_WALLET:
132 | return {
133 | ...state,
134 | tryUnlock: false,
135 | locked: false
136 | }
137 | }
138 |
139 | return state
140 | }
141 |
--------------------------------------------------------------------------------
/src/utils/balance.js:
--------------------------------------------------------------------------------
1 | import numberFormat from './numberFormat'
2 |
3 | export default function(wei, exchangeRates) {
4 | var eth = web3.utils.fromWei(wei, 'ether')
5 | var balance = {
6 | wei,
7 | eth,
8 | ethStr: `${numberFormat(Number(eth), 4)} ETH`
9 | }
10 | Object.keys(exchangeRates).forEach(currency => {
11 | balance[currency] =
12 | Math.round(exchangeRates[currency] * Number(eth) * 100) / 100
13 | balance[currency + 'Str'] = '$' + numberFormat(balance[currency], 2)
14 | })
15 | return balance
16 | }
17 |
--------------------------------------------------------------------------------
/src/utils/decodeFn.js:
--------------------------------------------------------------------------------
1 | import web3 from 'Web3'
2 |
3 | export default function decodeFn(Contract, data) {
4 | var methodSigs = Contract.abi.filter(a => a.type === 'function').reduce((m, fn) => {
5 | m[web3.eth.abi.encodeFunctionSignature(fn)] = fn
6 | return m
7 | }, {})
8 |
9 | var methodAbi = methodSigs[data.slice(0, 10)]
10 | return { name: methodAbi.name, params: web3.eth.abi.decodeParameters(methodAbi.inputs, data.slice(10)) }
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/ipfsHash.js:
--------------------------------------------------------------------------------
1 | // export function getBytes32FromIpfsHash(hash) {
2 | // return hash;
3 | // }
4 | // export function getIpfsHashFromBytes32(bytes32Hex) {
5 | // return bytes32Hex;
6 | // }
7 | import bs58 from 'bs58'
8 |
9 | export function getBytes32FromIpfsHash(hash) {
10 | return `0x${bs58
11 | .decode(hash)
12 | .slice(2)
13 | .toString('hex')}`
14 | }
15 |
16 | // Return base58 encoded ipfs hash from bytes32 hex string,
17 | // E.g. "0x017dfd85d4f6cb4dcd715a88101f7b1f06cd1e009b2327a0809d01eb9c91f231"
18 | // --> "QmNSUYVKDSvPUnRLKmuxk9diJ6yS96r1TrAXzjTiBcCLAL"
19 | export function getIpfsHashFromBytes32(bytes32Hex) {
20 | // Add our default ipfs values for first 2 bytes:
21 | // function:0x12=sha2, size:0x20=256 bits
22 | // and cut off leading "0x"
23 | const hashHex = '1220' + bytes32Hex.slice(2)
24 | const hashBytes = Buffer.from(hashHex, 'hex')
25 | const hashStr = bs58.encode(hashBytes)
26 | return hashStr
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/keyMirror.js:
--------------------------------------------------------------------------------
1 | export default function(obj, namespace) {
2 | var ret = {},
3 | key
4 |
5 | for (key in obj) {
6 | if (namespace) {
7 | ret[key] = namespace + '_' + key
8 | } else {
9 | ret[key] = key
10 | }
11 | }
12 | return ret
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/numberFormat.js:
--------------------------------------------------------------------------------
1 | export default function numberFormat(number, dec, dsep, tsep) {
2 | if (isNaN(number) || number == null) return ''
3 |
4 | number = number.toFixed(~~dec)
5 | tsep = typeof tsep == 'string' ? tsep : ','
6 |
7 | var parts = number.split('.'),
8 | fnums = parts[0],
9 | decimals = parts[1] ? (dsep || '.') + parts[1] : ''
10 |
11 | return fnums.replace(/(\d)(?=(?:\d{3})+$)/g, '$1' + tsep) + decimals
12 | }
13 |
--------------------------------------------------------------------------------
/test/ClaimVerifier.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import helper from './_helper'
3 |
4 | describe('ClaimVerifier.sol', async function() {
5 | var web3, accounts, deploy, prvSigner, pubSigner
6 | var UserIdentity, ClaimIssuer, ClaimVerifier
7 |
8 | before(async function() {
9 | ({ deploy, accounts, web3 } = await helper(
10 | `${__dirname}/../contracts/`
11 | ))
12 |
13 | prvSigner = web3.utils.randomHex(32)
14 | pubSigner = web3.eth.accounts.privateKeyToAccount(prvSigner).address
15 |
16 | UserIdentity = await deploy('ClaimHolder', { from: accounts[0] })
17 | ClaimIssuer = await deploy('ClaimHolder', { from: accounts[1] })
18 | ClaimVerifier = await deploy('ClaimVerifier', { from: accounts[2], args: [
19 | ClaimIssuer._address
20 | ] })
21 | })
22 |
23 | it('should allow verifier owner to addKey', async function() {
24 | var key = web3.utils.sha3(pubSigner)
25 | var result = await ClaimIssuer.methods.addKey(key, 3, 1).send({ from: accounts[1] })
26 |
27 | assert(result)
28 | })
29 |
30 | it('should not allow new listing without identity claim', async function() {
31 | var res = await ClaimVerifier.methods
32 | .checkClaim(UserIdentity._address, 3)
33 | .send({ from: accounts[0] })
34 | assert(res.events.ClaimInvalid)
35 | })
36 |
37 | it('should allow identity owner to addClaim', async function() {
38 | var data = web3.utils.asciiToHex('Verified OK')
39 | var claimType = 3
40 | var hashed = web3.utils.soliditySha3(UserIdentity._address, claimType, data)
41 | var signed = await web3.eth.accounts.sign(hashed, prvSigner)
42 |
43 | var claimRes = await UserIdentity.methods
44 | .addClaim(
45 | claimType,
46 | 2,
47 | ClaimIssuer._address,
48 | signed.signature,
49 | data,
50 | 'abc.com'
51 | ).send({ from: accounts[0] })
52 |
53 | assert(claimRes.events.ClaimAdded)
54 | })
55 |
56 | it('should not allow new listing without identity claim', async function() {
57 | var res = await ClaimVerifier.methods
58 | .checkClaim(UserIdentity._address, 3)
59 | .send({ from: accounts[0] })
60 | assert(res.events.ClaimValid)
61 | })
62 |
63 | })
64 |
--------------------------------------------------------------------------------
/test/Identity.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 | import helper from './_helper'
3 |
4 | describe('Identity', async function() {
5 | var web3, accounts, deploy, acctSha3, randomHex
6 | var UserIdentity
7 |
8 | before(async function() {
9 | ({ web3, deploy, accounts, web3: { utils: { randomHex } } } = await helper(
10 | `${__dirname}/../contracts/`
11 | ))
12 |
13 | UserIdentity = await deploy('ClaimHolder', { from: accounts[0] })
14 | acctSha3 = web3.utils.keccak256(accounts[0])
15 | })
16 |
17 | describe('Pre-Auth Identity', async function() {
18 | it('should deploy successfully', async function() {
19 | var sig = randomHex(10)
20 | var data = randomHex(10)
21 | var url = "1234567890"
22 | await deploy('Identity', { from: accounts[0], args: [
23 | // [1], [3], [accounts[0]], sig, data, url, [sig.length-2], [data.length-2], [url.length]
24 | [1], [3], [accounts[0]], sig, data, url, [10], [10], [10]
25 | ] })
26 | })
27 | })
28 |
29 | describe('Keys', async function() {
30 | it('should set a default MANAGEMENT_KEY', async function() {
31 | var res = await UserIdentity.methods.getKey(acctSha3).call()
32 | assert.equal(res.purpose, '1')
33 | assert.equal(res.keyType, '1')
34 | assert.equal(res.key, acctSha3)
35 | })
36 |
37 | it('should respond to getKeyPurpose', async function() {
38 | var res = await UserIdentity.methods.getKeyPurpose(acctSha3).call()
39 | assert.equal(res, '1')
40 | })
41 |
42 | it('should respond to getKeysByPurpose', async function() {
43 | var res = await UserIdentity.methods.getKeysByPurpose(1).call()
44 | assert.deepEqual(res, [acctSha3])
45 | })
46 |
47 | it('should implement addKey', async function() {
48 | var newKey = web3.utils.randomHex(32)
49 | var res = await UserIdentity.methods.addKey(newKey, 1, 1).send()
50 | assert(res.events.KeyAdded)
51 |
52 | var getKey = await UserIdentity.methods.getKey(newKey).call()
53 | assert.equal(getKey.key, newKey)
54 | })
55 |
56 | it('should not allow an existing key to be added', async function() {
57 | try {
58 | await UserIdentity.methods.addKey(acctSha3, 1, 1).send()
59 | assert(false)
60 | } catch (e) {
61 | assert(e.message.match(/revert/))
62 | }
63 | })
64 |
65 | it('should not allow sender without MANAGEMENT_KEY to addKey', async function() {
66 | try {
67 | await UserIdentity.methods.addKey(web3.utils.randomHex(32), 1, 1).send({
68 | from: accounts[1]
69 | })
70 | assert(false)
71 | } catch (e) {
72 | assert(e.message.match(/revert/))
73 | }
74 | })
75 | })
76 |
77 | describe('Claims', async function() {
78 |
79 | it('should allow a claim to be added by management account', async function() {
80 | var response = await UserIdentity.methods
81 | .addClaim(1, 2, accounts[0], randomHex(32), randomHex(32), 'abc.com')
82 | .send()
83 | assert(response.events.ClaimAdded)
84 | })
85 |
86 | it('should disallow new claims from unrecognized accounts', async function() {
87 | try {
88 | await UserIdentity.methods
89 | .addClaim(1, 2, accounts[0], randomHex(32), randomHex(32), 'abc.com')
90 | .send({ from: accounts[2] })
91 | assert(false)
92 | } catch (e) {
93 | assert(e.message.match(/revert/))
94 | }
95 | })
96 |
97 | it('should have 1 claim by type', async function() {
98 | var byTypeRes = await UserIdentity.methods.getClaimIdsByType(1).call()
99 | assert.equal(byTypeRes.length, 1)
100 | })
101 |
102 | it('should respond to getClaim', async function() {
103 | var claimId = web3.utils.soliditySha3(accounts[0], 1)
104 | var claim = await UserIdentity.methods.getClaim(claimId).call()
105 | assert.equal(claim.claimType, "1")
106 | })
107 |
108 | // it('should respond to isClaimValid', async function() {
109 | // var claimId = web3.utils.soliditySha3(accounts[0], 1)
110 | // var valid = await UserIdentity.methods.isClaimValid(claimId).call()
111 | // assert(valid)
112 | // })
113 |
114 | it('should allow claim to be removed', async function() {
115 | var claimId = web3.utils.soliditySha3(accounts[0], 1)
116 | var response = await UserIdentity.methods
117 | .removeClaim(claimId)
118 | .send({ from: accounts[0] })
119 | assert(response.events.ClaimRemoved)
120 |
121 | var claim = await UserIdentity.methods.getClaim(claimId).call()
122 | assert.equal(claim.claimType, "0")
123 | })
124 | })
125 |
126 | describe('Executions', async function() {
127 | it('should allow any account to execute actions', async function() {
128 | var addClaimAbi = await UserIdentity.methods
129 | .addClaim(1, 2, accounts[0], randomHex(32), randomHex(32), 'abc.com')
130 | .encodeABI()
131 |
132 | var response = await UserIdentity.methods
133 | .execute(UserIdentity.options.address, 0, addClaimAbi)
134 | .send({
135 | from: accounts[2]
136 | })
137 |
138 | assert(response.events.ExecutionRequested)
139 | assert(!response.events.Approved)
140 | assert(!response.events.Executed)
141 | })
142 |
143 | it('should auto-approve executions from MANAGEMENT_KEYs', async function() {
144 | var addClaimAbi = await UserIdentity.methods
145 | .addClaim(1, 2, accounts[0], randomHex(32), randomHex(32), 'abc.com')
146 | .encodeABI()
147 |
148 | var response = await UserIdentity.methods
149 | .execute(UserIdentity.options.address, 0, addClaimAbi)
150 | .send({
151 | from: accounts[0]
152 | })
153 |
154 | assert(response.events.ExecutionRequested)
155 | assert(response.events.Approved)
156 | assert(response.events.ClaimAdded)
157 | assert(response.events.Executed)
158 | })
159 | })
160 |
161 | describe('Approvals', async function() {
162 | it('should allow MANAGEMENT_KEYs to approve executions', async function() {
163 | var addClaimAbi = await UserIdentity.methods
164 | .addClaim(1, 2, accounts[2], randomHex(32), randomHex(32), 'abc.com')
165 | .encodeABI()
166 |
167 | var response = await UserIdentity.methods
168 | .execute(UserIdentity.options.address, 0, addClaimAbi)
169 | .send({ from: accounts[2] })
170 |
171 | assert(response.events.ExecutionRequested)
172 | assert(!response.events.Approved)
173 |
174 | var id = response.events.ExecutionRequested.returnValues.executionId;
175 |
176 | var approval = await UserIdentity.methods.approve(id, true)
177 | .send({ from: accounts[0] })
178 |
179 | assert(approval.events.Approved)
180 | assert(approval.events.ClaimAdded)
181 | assert(approval.events.Executed)
182 | })
183 |
184 | it('should allow ACTION_KEYs to approve executions')
185 | it('should not allow CLAIM_SIGNER_KEYs to approve executions')
186 | it('should not be able to approve an already executed execution')
187 | it('should not be able to approve a non-existant execution')
188 | })
189 | })
190 |
--------------------------------------------------------------------------------
/test/_helper.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import solc from 'solc'
3 | import linker from 'solc/linker'
4 | import Ganache from 'ganache-core'
5 | import Web3 from 'web3'
6 |
7 | var solcOpts = {
8 | language: 'Solidity',
9 | settings: {
10 | metadata: { useLiteralContent: true },
11 | outputSelection: {
12 | '*': {
13 | '*': ['abi', 'evm.bytecode.object']
14 | }
15 | }
16 | }
17 | }
18 |
19 | // Instantiate a web3 instance. Start a node if one is not already running.
20 | export async function web3Helper(provider = 'ws://localhost:7545') {
21 | var web3 = new Web3(provider)
22 | var instance = await server(web3, provider)
23 | return { web3, server: instance }
24 | }
25 |
26 | function findImportsPath(prefix) {
27 | return function findImports(path) {
28 | try {
29 | return {
30 | contents: fs.readFileSync(prefix + path).toString()
31 | }
32 | } catch (e) {
33 | return { error: 'File not found' }
34 | }
35 | }
36 | }
37 |
38 | export default async function testHelper(contracts, provider) {
39 | const { web3, server } = await web3Helper(provider)
40 | const accounts = await web3.eth.getAccounts()
41 |
42 | async function deploy(contractName, { from, args, log }) {
43 | var sources = {
44 | [contractName]: {
45 | content: fs.readFileSync(`${contracts}/${contractName}.sol`).toString()
46 | }
47 | }
48 | var compileOpts = JSON.stringify({ ...solcOpts, sources })
49 |
50 | // Compile the contract using solc
51 | var rawOutput = solc.compileStandardWrapper(
52 | compileOpts,
53 | findImportsPath(contracts)
54 | )
55 | var output = JSON.parse(rawOutput)
56 |
57 | // If there were any compilation errors, throw them
58 | if (output.errors) {
59 | output.errors.forEach(err => {
60 | if (!err.formattedMessage.match(/Warning:/)) {
61 | throw new SyntaxError(err.formattedMessage)
62 | }
63 | })
64 | }
65 |
66 | var { abi, evm: { bytecode } } = output.contracts[contractName][
67 | contractName
68 | ]
69 |
70 | // Deploy linked libraries
71 | for (let linkedFile in bytecode.linkReferences) {
72 | for (let linkedLib in bytecode.linkReferences[linkedFile]) {
73 | let libObj = output.contracts[linkedFile][linkedLib]
74 | let LibContract = new web3.eth.Contract(libObj.abi)
75 | var libContract = await LibContract.deploy({
76 | data: libObj.evm.bytecode.object
77 | }).send({
78 | from,
79 | gas: 3000000
80 | })
81 |
82 | let libs = { [`${linkedFile}:${linkedLib}`]: libContract._address }
83 |
84 | bytecode.object = linker.linkBytecode(bytecode.object, libs)
85 | }
86 | }
87 |
88 | if (!bytecode.object) {
89 | throw new Error(
90 | 'No Bytecode. Do the method signatures match the interface?'
91 | )
92 | }
93 |
94 | if (process.env.BUILD) {
95 | fs.writeFileSync(
96 | __dirname + '/../src/contracts/' + contractName + '.js',
97 | 'module.exports = ' +
98 | JSON.stringify(
99 | {
100 | abi,
101 | data: bytecode.object
102 | },
103 | null,
104 | 4
105 | )
106 | )
107 | }
108 |
109 | // Instantiate the web3 contract using the abi and bytecode output from solc
110 | var Contract = new web3.eth.Contract(abi)
111 | var contract
112 |
113 | await new Promise(async resolve => {
114 | var chainId = web3.eth.net.getId()
115 |
116 | var data = await Contract.deploy({
117 | data: '0x' + bytecode.object,
118 | arguments: args
119 | }).encodeABI()
120 |
121 | web3.eth
122 | .sendTransaction({
123 | data,
124 | from,
125 | value: 0,
126 | gas: 4612388,
127 | chainId
128 | })
129 | .once('transactionHash', hash => {
130 | if (log) {
131 | console.log('Transaction Hash', hash)
132 | }
133 | })
134 | .once('receipt', receipt => {
135 | if (log) {
136 | console.log(
137 | `Deployed ${contractName} to ${receipt.contractAddress} (${
138 | receipt.cumulativeGasUsed
139 | } gas used)`
140 | )
141 | }
142 | })
143 | .catch('error', err => {
144 | console.log(err)
145 | resolve()
146 | })
147 | .then(instance => {
148 | contract = new web3.eth.Contract(abi, instance.contractAddress)
149 | resolve()
150 | })
151 | })
152 |
153 | if (contract) {
154 | // Set some default options on the contract
155 | contract.options.gas = 1500000
156 | contract.options.from = from
157 | }
158 |
159 | return contract
160 | }
161 |
162 | return { web3, accounts, deploy, server }
163 | }
164 |
165 | // Start the server if it hasn't been already...
166 | async function server(web3, provider) {
167 | try {
168 | // Hack to prevent "connection not open on send" error when using websockets
169 | web3.setProvider(provider.replace(/^ws/, 'http'))
170 | await web3.eth.net.getId()
171 | web3.setProvider(provider)
172 | return
173 | } catch (e) {
174 | /* Ignore */
175 | }
176 |
177 | var port = '7545'
178 | if (String(provider).match(/:([0-9]+)$/)) {
179 | port = provider.match(/:([0-9]+)$/)[1]
180 | }
181 | var server = Ganache.server()
182 | await server.listen(port)
183 | return server
184 | }
185 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const dotenv = require( "dotenv")
4 | const syncReq = require("sync-request")
5 |
6 | dotenv.config()
7 | const publicIp = syncReq("GET", "http://ipinfo.io/ip").getBody('utf8').trim()
8 | const LOCAL = process.env.LOCAL === "true"
9 | if(!LOCAL){
10 | process.env.HOST = publicIp
11 | }
12 |
13 | var OfficialIdentities = []
14 | try {
15 | OfficialIdentities = require('./data/OfficialIdentities')
16 | } catch (e) {
17 | /* Ignore */
18 | }
19 |
20 | var config = {
21 | entry: {
22 | app: './src/index.js'
23 | },
24 | devtool: 'cheap-module-source-map',
25 | output: {
26 | filename: '[name].js',
27 | path: path.resolve(__dirname, 'public'),
28 | hashDigestLength: 8
29 | },
30 | externals: {
31 | Web3: 'web3'
32 | },
33 | module: {
34 | noParse: [/^react$/],
35 | rules: [
36 | {
37 | test: /\.js$/,
38 | exclude: /node_modules/,
39 | loader: 'babel-loader'
40 | }
41 | ]
42 | },
43 | resolve: {
44 | extensions: ['.js', '.json']
45 | },
46 | node: {
47 | fs: 'empty'
48 | },
49 | devServer: {
50 | port: 8082,
51 | headers: {
52 | 'Access-Control-Allow-Origin': '*'
53 | }
54 | },
55 | mode: 'development',
56 | plugins: [
57 | new webpack.EnvironmentPlugin({
58 | HOST: 'localhost',
59 | RESET: true,
60 | LOCAL: true
61 | }),
62 | new webpack.DefinePlugin({
63 | OfficialIdentities: JSON.stringify(OfficialIdentities)
64 | })
65 | ],
66 |
67 | optimization: {
68 | splitChunks: {
69 | cacheGroups: {
70 | vendor: {
71 | chunks: 'initial',
72 | test: path.resolve(__dirname, 'node_modules'),
73 | name: 'vendor',
74 | enforce: true
75 | }
76 | }
77 | }
78 | }
79 | }
80 |
81 | module.exports = config
82 |
--------------------------------------------------------------------------------
/webpack.prod.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 | var devConfig = require('./webpack.config.js')
3 | var path = require('path')
4 |
5 | var prodConfig = {
6 | ...devConfig,
7 | mode: 'production',
8 | devtool: false
9 | }
10 |
11 | prodConfig.plugins.push(new webpack.IgnorePlugin(/redux-logger/))
12 | prodConfig.resolve.alias = {
13 | react: 'react/umd/react.production.min.js',
14 | 'react-dom': 'react-dom/umd/react-dom.production.min.js',
15 | 'react-styl': 'react-styl/prod.js',
16 | web3: path.resolve(__dirname, 'public/web3.min'),
17 | redux: 'redux/dist/redux.min.js',
18 | 'react-redux': 'react-redux/dist/react-redux.min.js',
19 | 'react-router-dom': 'react-router-dom/umd/react-router-dom.min.js'
20 | }
21 | prodConfig.module.noParse = [
22 | /^(react|react-dom|react-styl|redux|react-redux|react-router-dom)$/,
23 | /web3/
24 | ]
25 |
26 | module.exports = prodConfig
27 |
--------------------------------------------------------------------------------