on master help sign in
Merge pull request #151 from urbit/mino-send-invites

Implementation of invite sending + lots of refactoring work

commit899620ec26d85047e19959ea76b8126e00da13a6 authorFang <Fang-@users.noreply.github.com> dateTue 11 Jun 2019, 16:06 (7 years ago) parentf0e7b2282fb0c8
29 files changed+831−905
package-lock.json+74−181 package.json+4−4 src/bridge/Bridge.js+16−2 src/bridge/components/StatelessTransaction.js+15−38 src/bridge/lib/invite.js+32−168 src/bridge/lib/inviteMail.js+20−4 src/bridge/lib/keys.js+5−19 src/bridge/lib/need.js+62 src/bridge/lib/router.js+5−10 src/bridge/lib/tank.js+81−1 src/bridge/lib/txn.js+8−3 src/bridge/lib/wallet.js+17−5 src/bridge/views/AcceptTransfer.js+7−36 src/bridge/views/CancelTransfer.js+5−29 src/bridge/views/CreateGalaxy.js+5−30 src/bridge/views/GenKeyfile.js+4−15 src/bridge/views/InviteTicket.js+9−13 src/bridge/views/InvitesManage.js+11−27 src/bridge/views/InvitesSend.js+381−120 src/bridge/views/IssueChild.js+11−40 src/bridge/views/Point.js+9−26 src/bridge/views/Point/Actions.js+19−19 src/bridge/views/Points.js+1−3 src/bridge/views/PrivateKey.js+1−3 src/bridge/views/SentTransaction.js+3−5 src/bridge/views/SetKeys.js+9−44 src/bridge/views/SetProxy.js+5−29 src/bridge/views/Transfer.js+5−29 src/walletgen/lib/lib.js+7−2
package-lock.json+74−181
@@ -1,6 +1,6 @@
11{
22 "name": "bridge",
3− "version": "1.4.0",
3+ "version": "1.4.1",
44 "lockfileVersion": 1,
55 "requires": true,
66 "dependencies": {
@@ -1036,28 +1036,6 @@
10361036 "loader-utils": "^1.1.0"
10371037 }
10381038 },
1039− "@trust/keyto": {
1040− "version": "0.3.4",
1041− "resolved": "https://registry.npmjs.org/@trust/keyto/-/keyto-0.3.4.tgz",
1042− "integrity": "sha512-OAqKvuSEPIu2zCnIHzBthvGnV8nKmpv7cBlRMngLzJZzZI9CanyuSfnEI1xC4sH4TwqA0XJR7Mb0oX4bwymXIw==",
1043− "requires": {
1044− "asn1.js": "^4.9.1",
1045− "base64url": "^3.0.0",
1046− "elliptic": "^6.4.0"
1047− }
1048− },
1049− "@trust/webcrypto": {
1050− "version": "0.9.2",
1051− "resolved": "https://registry.npmjs.org/@trust/webcrypto/-/webcrypto-0.9.2.tgz",
1052− "integrity": "sha512-5iMAVcGYKhqLJGjefB1nzuQSqUJTru0nG4CytpBT/GGp1Piz/MVnj2jORdYf4JBYzggCIa8WZUr2rchP2Ngn/w==",
1053− "requires": {
1054− "@trust/keyto": "^0.3.4",
1055− "base64url": "^3.0.0",
1056− "elliptic": "^6.4.0",
1057− "node-rsa": "^0.4.0",
1058− "text-encoding": "^0.6.1"
1059− }
1060− },
10611039 "@types/bn.js": {
10621040 "version": "4.11.5",
10631041 "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.5.tgz",
@@ -1994,40 +1972,18 @@
19941972 }
19951973 },
19961974 "azimuth-js": {
1997− "version": "0.13.2",
1998− "resolved": "https://registry.npmjs.org/azimuth-js/-/azimuth-js-0.13.2.tgz",
1999− "integrity": "sha512-DiteSHZTqst5Ikf5yoOetnU7MIE/El5JIuO9FjyaQHWPYA8ECB4XUaZJCl4VfScnAzzP1YCnORXz/mtVn2D9ng==",
1975+ "version": "0.14.2",
1976+ "resolved": "https://registry.npmjs.org/azimuth-js/-/azimuth-js-0.14.2.tgz",
1977+ "integrity": "sha512-54CJ8K0dheso9U1sBWCHkMvUj7/EIEDGc/UH1HU2uenEkSQpx9Ic4jHq9H6W8baew//PEH4ldYnFFVfsd4Gi4Q==",
20001978 "requires": {
2001− "azimuth-solidity": "1.1.0",
1979+ "azimuth-solidity": "1.2.0",
20021980 "ethereumjs-util": "^5.2.0"
2003−<<<<<<< HEAD
2004−=======
2005− },
2006− "dependencies": {
2007− "azimuth-solidity": {
2008− "version": "1.1.0",
2009− "resolved": "https://registry.npmjs.org/azimuth-solidity/-/azimuth-solidity-1.1.0.tgz",
2010− "integrity": "sha512-YhVWKql1ZZ9Zae96nYA7QLwQlTzGspkNOErq170sl/UJwncQbvAyibSv+heMaS18n4soPDiRSpa1vHFoGoIX1A==",
2011− "requires": {
2012− "babel-polyfill": "^6.26.0",
2013− "babel-register": "^6.26.0",
2014− "openzeppelin-solidity": "1.12.0",
2015− "truffle": "4.1.11"
2016− },
2017− "dependencies": {
2018− "openzeppelin-solidity": {
2019− "version": "1.12.0",
2020− "bundled": true
2021− }
2022− }
2023− }
2024−>>>>>>> master
20251981 }
20261982 },
20271983 "azimuth-solidity": {
2028− "version": "1.1.0",
2029− "resolved": "https://registry.npmjs.org/azimuth-solidity/-/azimuth-solidity-1.1.0.tgz",
2030− "integrity": "sha512-YhVWKql1ZZ9Zae96nYA7QLwQlTzGspkNOErq170sl/UJwncQbvAyibSv+heMaS18n4soPDiRSpa1vHFoGoIX1A==",
1984+ "version": "1.2.0",
1985+ "resolved": "https://registry.npmjs.org/azimuth-solidity/-/azimuth-solidity-1.2.0.tgz",
1986+ "integrity": "sha512-5k50PJtJAXqZFFb0BWH6CxYGfodLNnG9VVJtjAnTxrLl8tO4qQSYWSb1nf39Q0MLT4e+rrUf5JIubZXKckmA3g==",
20311987 "requires": {
20321988 "babel-polyfill": "^6.26.0",
20331989 "babel-register": "^6.26.0",
@@ -2041,22 +1997,6 @@
20411997 }
20421998 }
20431999 },
2044− "b64-lite": {
2045− "version": "1.3.1",
2046− "resolved": "https://registry.npmjs.org/b64-lite/-/b64-lite-1.3.1.tgz",
2047− "integrity": "sha512-A3n/YWszeQeUeKKy+XRfsDW/s/+1cvD110HYA4uXtkNswNLd1MxrNblYt2lgKisUS3UfPPTaXhvFegD+l4YuOA==",
2048− "requires": {
2049− "base-64": "^0.1.0"
2050− }
2051− },
2052− "b64u-lite": {
2053− "version": "1.0.1",
2054− "resolved": "https://registry.npmjs.org/b64u-lite/-/b64u-lite-1.0.1.tgz",
2055− "integrity": "sha512-4E6rqyrCnrAdzayWUS8XwFC/vRTBbmR7fgqb/IuHXRM2z63VH/tNxtzDWZhgCXCORTiwy2mIhCtmHyc9TMBj7Q==",
2056− "requires": {
2057− "b64-lite": "^1.1.1"
2058− }
2059− },
20602000 "babel-code-frame": {
20612001 "version": "6.26.0",
20622002 "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@@ -2528,11 +2468,6 @@
25282468 }
25292469 }
25302470 },
2531− "base-64": {
2532− "version": "0.1.0",
2533− "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
2534− "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
2535− },
25362471 "base-x": {
25372472 "version": "3.0.5",
25382473 "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.5.tgz",
@@ -2546,11 +2481,6 @@
25462481 "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
25472482 "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw=="
25482483 },
2549− "base64url": {
2550− "version": "3.0.1",
2551− "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
2552− "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A=="
2553− },
25542484 "batch": {
25552485 "version": "0.6.1",
25562486 "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
@@ -2692,9 +2622,9 @@
26922622 "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
26932623 },
26942624 "bootstrap": {
2695− "version": "4.1.3",
2696− "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.1.3.tgz",
2697− "integrity": "sha512-rDFIzgXcof0jDyjNosjv4Sno77X4KuPeFxG2XZZv1/Kc8DRVGVADdoQyyOVDwPqL36DDmtCQbrpMCqvpPLJQ0w=="
2625+ "version": "4.3.1",
2626+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz",
2627+ "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag=="
26982628 },
26992629 "boxen": {
27002630 "version": "1.3.0",
@@ -8864,21 +8794,6 @@
88648794 "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
88658795 "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
88668796 },
8867− "isomorphic-webcrypto": {
8868− "version": "1.6.1",
8869− "resolved": "https://registry.npmjs.org/isomorphic-webcrypto/-/isomorphic-webcrypto-1.6.1.tgz",
8870− "integrity": "sha512-ji9vrOdGIQaBguhOg6JZXL+TL8VIEIja/dtcvJ6HtVBX9WygJF6XXjp8+bkZA4EHW3s1mqbG4DIGHBy2FFmOfA==",
8871− "requires": {
8872− "@trust/webcrypto": "^0.9.2",
8873− "b64-lite": "^1.3.1",
8874− "b64u-lite": "^1.0.1",
8875− "mitt": "^1.1.3",
8876− "msrcrypto": "^1.5.1",
8877− "react-native-securerandom": "^0.1.1",
8878− "str2buf": "^1.3.0",
8879− "webcrypto-shim": "^0.1.4"
8880− }
8881− },
88828797 "isstream": {
88838798 "version": "0.1.2",
88848799 "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -10061,6 +9976,11 @@
100619976 "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.4.tgz",
100629977 "integrity": "sha512-PxfGzSs0ztShKrUYPIn5r0MtyAhYcCwmndozzpz8YObbPnD1jFxzlBGbRnX2mIu6Z13xN6+PTu05TQFnZFlzow=="
100639978 },
9979+ "js-sha256": {
9980+ "version": "0.9.0",
9981+ "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
9982+ "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
9983+ },
100649984 "js-sha3": {
100659985 "version": "0.6.1",
100669986 "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.6.1.tgz",
@@ -10823,11 +10743,6 @@
1082310743 }
1082410744 }
1082510745 },
10826− "mitt": {
10827− "version": "1.1.3",
10828− "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.1.3.tgz",
10829− "integrity": "sha512-mUDCnVNsAi+eD6qA0HkRkwYczbLHJ49z17BGe2PYRhZL4wpZUFZGJHU7/5tmvohoma+Hdn0Vh/oJTiPEmgSruA=="
10830− },
1083110746 "mixin-deep": {
1083210747 "version": "1.3.1",
1083310748 "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
@@ -10945,11 +10860,6 @@
1094510860 "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1094610861 "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
1094710862 },
10948− "msrcrypto": {
10949− "version": "1.5.3",
10950− "resolved": "https://registry.npmjs.org/msrcrypto/-/msrcrypto-1.5.3.tgz",
10951− "integrity": "sha512-dEgu9OAAsknOoV3WEMZVyax46JeLqa81D/bHryo02qT3j16b+3a/f2AdKBAIlPDMtc7/ZWtpNNhdxq+csjQBAg=="
10952− },
1095310863 "multicast-dns": {
1095410864 "version": "6.2.3",
1095510865 "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
@@ -11128,21 +11038,6 @@
1112811038 "semver": "^5.3.0"
1112911039 }
1113011040 },
11131− "node-rsa": {
11132− "version": "0.4.2",
11133− "resolved": "http://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz",
11134− "integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA=",
11135− "requires": {
11136− "asn1": "0.2.3"
11137− },
11138− "dependencies": {
11139− "asn1": {
11140− "version": "0.2.3",
11141− "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
11142− "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
11143− }
11144− }
11145− },
1114611041 "normalize-package-data": {
1114711042 "version": "2.4.0",
1114811043 "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@@ -16036,15 +15931,6 @@
1603615931 "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.1.tgz",
1603715932 "integrity": "sha512-gJcOyak5ri9mzhpFObSAkCqNNBCVZE8LaR/mX37agULgSL0dobgX11NVSIetvNzhHMkr6CIqV0xiutvvcjBtkw=="
1603815933 },
16039− "react-native-securerandom": {
16040− "version": "0.1.1",
16041− "resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-0.1.1.tgz",
16042− "integrity": "sha1-8TBiOkEsM4sK+t7bwgTFy7i/IHA=",
16043− "optional": true,
16044− "requires": {
16045− "base64-js": "*"
16046− }
16047− },
1604815934 "react-router": {
1604915935 "version": "4.3.1",
1605015936 "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
@@ -18043,11 +17929,6 @@
1804317929 "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
1804417930 "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
1804517931 },
18046− "str2buf": {
18047− "version": "1.3.0",
18048− "resolved": "https://registry.npmjs.org/str2buf/-/str2buf-1.3.0.tgz",
18049− "integrity": "sha512-xIBmHIUHYZDP4HyoXGHYNVmxlXLXDrtFHYT0eV6IOdEj3VO9ccaF1Ejl9Oq8iFjITllpT8FhaXb4KsNmw+3EuA=="
18050− },
1805117932 "stream-browserify": {
1805217933 "version": "2.0.1",
1805317934 "resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
@@ -18618,11 +18499,6 @@
1861818499 "require-main-filename": "^1.0.1"
1861918500 }
1862018501 },
18621− "text-encoding": {
18622− "version": "0.6.4",
18623− "resolved": "http://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz",
18624− "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk="
18625− },
1862618502 "text-table": {
1862718503 "version": "0.2.0",
1862818504 "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -18849,9 +18725,9 @@
1884918725 }
1885018726 },
1885118727 "tweetnacl": {
18852− "version": "1.0.0",
18853− "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.0.tgz",
18854− "integrity": "sha1-cT2LgY2kIGh0C/aDhtBHnmb8ins="
18728+ "version": "1.0.1",
18729+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.1.tgz",
18730+ "integrity": "sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A=="
1885518731 },
1885618732 "type-check": {
1885718733 "version": "0.3.2",
@@ -19188,18 +19064,29 @@
1918819064 "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
1918919065 },
1919019066 "urbit-key-generation": {
19191− "version": "0.12.1",
19192− "resolved": "https://registry.npmjs.org/urbit-key-generation/-/urbit-key-generation-0.12.1.tgz",
19193− "integrity": "sha512-3/TvWwQjFiYEIS7G7Ffbuj5XbcPCMVXD56sjnPRveZlGbI9B2cv5kwGSGkC8oNAFhp1xkZP6RdrddCealPCTng==",
19067+ "version": "0.16.0",
19068+ "resolved": "https://registry.npmjs.org/urbit-key-generation/-/urbit-key-generation-0.16.0.tgz",
19069+ "integrity": "sha512-3nwiIb2OGg0KdT/k+iJGjJJ5hUjPt64gmUu6wbDkLCfPLAn0xMN0FqaBd9mEc0niqWH1z6v0iJR4N7Vv0xMPVg==",
1919419070 "requires": {
1919519071 "argon2-wasm": "^0.9.0",
1919619072 "bip32": "^1.0.2",
1919719073 "bip39": "^2.5.0",
19198− "isomorphic-webcrypto": "^1.6.0",
19074+ "js-sha256": "^0.9.0",
1919919075 "keccak": "^1.4.0",
1920019076 "secp256k1": "^3.5.2",
1920119077 "tweetnacl": "^1.0.0",
19202− "urbit-ob": "^3.0.1"
19078+ "urbit-ob": "^4.0.0"
19079+ },
19080+ "dependencies": {
19081+ "urbit-ob": {
19082+ "version": "4.0.0",
19083+ "resolved": "https://registry.npmjs.org/urbit-ob/-/urbit-ob-4.0.0.tgz",
19084+ "integrity": "sha512-FsVQx4h2Kuu+ZqUOmAUDAQFlw9r1R+B6r9Bu9TnyNDOraLUlojBw5wG5ONbWiK0g7sgeQkQ3rMVyXqiv41Vrpg==",
19085+ "requires": {
19086+ "bn.js": "^4.11.8",
19087+ "lodash": "^4.17.11"
19088+ }
19089+ }
1920319090 }
1920419091 },
1920519092 "urbit-ob": {
@@ -20161,7 +20048,6 @@
2016120048 "resolved": "https://registry.npmjs.org/web3-providers/-/web3-providers-1.0.0-beta.54.tgz",
2016220049 "integrity": "sha512-P55MQHkfU2VLt3t+3IbqRUVwOziLOMiXxKulVYXzTu9M8p9aogl1tvPjKXpRUGygCY3LsWTpt/UlJR8Aaia+CQ==",
2016320050 "requires": {
20164−<<<<<<< HEAD
2016520051 "@babel/runtime": "^7.3.1",
2016620052 "@types/node": "^10.12.18",
2016720053 "eventemitter3": "3.1.0",
@@ -20172,42 +20058,54 @@
2017220058 },
2017320059 "dependencies": {
2017420060 "@babel/runtime": {
20175− "version": "7.4.4",
20176− "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz",
20177− "integrity": "sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg==",
20061+ "version": "7.4.5",
20062+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz",
20063+ "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==",
2017820064 "requires": {
2017920065 "regenerator-runtime": "^0.13.2"
2018020066 }
2018120067 },
20068+ "bn.js": {
20069+ "version": "4.11.6",
20070+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
20071+ "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU="
20072+ },
2018220073 "regenerator-runtime": {
2018320074 "version": "0.13.2",
2018420075 "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
2018520076 "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
20077+ },
20078+ "web3-core-helpers": {
20079+ "version": "1.0.0-beta.33",
20080+ "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.0.0-beta.33.tgz",
20081+ "integrity": "sha1-Kvcz5QTbBefDZIwdrPV3sOwV3EM=",
20082+ "requires": {
20083+ "web3-eth-iban": "1.0.0-beta.33",
20084+ "web3-utils": "1.0.0-beta.33"
20085+ }
20086+ },
20087+ "web3-eth-iban": {
20088+ "version": "1.0.0-beta.33",
20089+ "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.0.0-beta.33.tgz",
20090+ "integrity": "sha1-HXPQxSiKRWWxdUp1tfs+oLd6Uy8=",
20091+ "requires": {
20092+ "bn.js": "4.11.6",
20093+ "web3-utils": "1.0.0-beta.33"
20094+ }
20095+ },
20096+ "web3-utils": {
20097+ "version": "1.0.0-beta.33",
20098+ "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.0.0-beta.33.tgz",
20099+ "integrity": "sha1-4JG3mU8JtxSwGYpAV9OtLrjL4jg=",
20100+ "requires": {
20101+ "bn.js": "4.11.6",
20102+ "eth-lib": "0.1.27",
20103+ "ethjs-unit": "0.1.6",
20104+ "number-to-bn": "1.7.0",
20105+ "randomhex": "0.1.5",
20106+ "utf8": "2.1.1"
20107+ }
2018620108 }
20187−=======
20188− "web3-core-helpers": "1.0.0-beta.33",
20189− "xhr2": "0.1.4"
20190− }
20191− },
20192− "web3-providers-ipc": {
20193− "version": "1.0.0-beta.33",
20194− "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.0.0-beta.33.tgz",
20195− "integrity": "sha1-Twrcmv6dEsBm5L5cPFNvUHPLB8Y=",
20196− "requires": {
20197− "oboe": "2.1.3",
20198− "underscore": "1.8.3",
20199− "web3-core-helpers": "1.0.0-beta.33"
20200− }
20201− },
20202− "web3-providers-ws": {
20203− "version": "1.0.0-beta.33",
20204− "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.0.0-beta.33.tgz",
20205− "integrity": "sha1-j93qQuGbvyUh7IeVRkV6Yjqdye8=",
20206− "requires": {
20207− "underscore": "1.8.3",
20208− "web3-core-helpers": "1.0.0-beta.33",
20209− "websocket": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible"
20210−>>>>>>> master
2021120109 }
2021220110 },
2021320111 "web3-shh": {
@@ -20309,11 +20207,6 @@
2030920207 }
2031020208 }
2031120209 },
20312− "webcrypto-shim": {
20313− "version": "0.1.4",
20314− "resolved": "https://registry.npmjs.org/webcrypto-shim/-/webcrypto-shim-0.1.4.tgz",
20315− "integrity": "sha512-I2lnL+K2oPNE9ryVHwo42oDnt8XQ9E1KKMGCmcT7OXaAKPmUeCi/G0nUgLR6M6Ztj05ZCxLMGf5bXNaSo+wURg=="
20316− },
2031720210 "webidl-conversions": {
2031820211 "version": "4.0.2",
2031920212 "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
package.json+4−4
@@ -17,13 +17,13 @@
1717 "@ledgerhq/hw-app-eth": "^4.35.0",
1818 "@ledgerhq/hw-transport-u2f": "^4.35.0",
1919 "PaperCollateralRenderer": "github:urbit/PaperCollateralRenderer",
20− "azimuth-js": "^0.13.2",
21− "azimuth-solidity": "^1.1.0",
20+ "azimuth-js": "^0.14.0",
21+ "azimuth-solidity": "^1.2.0",
2222 "babel-polyfill": "^6.26.0",
2323 "bip32": "^1.0.2",
2424 "bip39": "^2.5.0",
2525 "bn.js": "^4.11.8",
26− "bootstrap": "^4.1.3",
26+ "bootstrap": "^4.3.1",
2727 "classnames": "^2.2.6",
2828 "ethereum-blockies-base64": "^1.0.2",
2929 "ethereumjs-tx": "^1.3.7",
@@ -43,7 +43,7 @@
4343 "secp256k1": "^3.5.2",
4444 "sigil-js": "github:urbit/sigil-js#f7f29c5",
4545 "trezor-connect": "^7.0.1",
46− "urbit-key-generation": "0.12.1",
46+ "urbit-key-generation": "^0.16.0",
4747 "urbit-ob": "^3.1.0",
4848 "web3": "1.0.0-beta.54",
4949 "web3-utils": "^1.0.0-beta.52"
src/bridge/Bridge.js+16−2
@@ -12,7 +12,12 @@ import { Container, Row, Col } from './components/Base'
1212
1313import { ROUTE_NAMES, router } from './lib/router'
1414import { NETWORK_NAMES } from './lib/network'
15−import { WALLET_NAMES, DEFAULT_HD_PATH, walletFromMnemonic } from './lib/wallet'
15+import {
16+ WALLET_NAMES,
17+ DEFAULT_HD_PATH,
18+ walletFromMnemonic,
19+ addressFromSecp256k1Public
20+} from './lib/wallet'
1621import { BRIDGE_ERROR } from './lib/error'
1722
1823const initWeb3 = (networkType) => {
@@ -212,7 +217,11 @@ class Bridge extends React.Component {
212217 }
213218
214219 setWallet(wallet) {
215− this.setState({ wallet })
220+ wallet.map(wal => {
221+ wal.address = wal.address || addressFromSecp256k1Public(wal.publicKey);
222+ return wal;
223+ });
224+ this.setState({ wallet });
216225 }
217226
218227 setWalletHdPath(walletHdPath) {
@@ -229,6 +238,10 @@ class Bridge extends React.Component {
229238 DEFAULT_HD_PATH,
230239 urbitWallet.value.meta.passphrase
231240 );
241+ wallet.map(wal => {
242+ wal.address = urbitWallet.value.ownership.keys.address;
243+ return wal;
244+ });
232245 }
233246 this.setState({ urbitWallet, wallet })
234247 }
@@ -342,6 +355,7 @@ class Bridge extends React.Component {
342355 networkRevisionCache={ networkRevisionCache }
343356 setNetworkSeedCache= { this.setNetworkSeedCache }
344357 // txn
358+ onSent={ this.setTxnHashCursor }
345359 setTxnHashCursor={ this.setTxnHashCursor }
346360 txnHashCursor={ txnHashCursor }
347361 setTxnConfirmations={ this.setTxnConfirmations }
src/bridge/components/StatelessTransaction.js+15−38
@@ -1,6 +1,7 @@
11import { Nothing, Just } from 'folktale/maybe'
22import { Ok } from 'folktale/result'
33import React from 'react'
4+import * as need from '../lib/need'
45
56import { Code, H3 } from './Base'
67import { Button } from './Base'
@@ -8,14 +9,12 @@ import { CheckboxButton, Input, InnerLabel,
89 InnerLabelDropdown } from './Base'
910import { Warning } from '../components/Base'
1011
11−import { addressFromSecp256k1Public } from '../lib/wallet'
1212import { BRIDGE_ERROR } from '../lib/error'
1313import { ROUTE_NAMES } from '../lib/router'
1414import * as tank from '../lib/tank'
1515
1616import {
1717 sendSignedTransaction,
18− waitForTransactionConfirm,
1918 fromWei,
2019 toWei,
2120 hexify,
@@ -66,12 +65,7 @@ class StatelessTransaction extends React.Component {
6665 componentDidMount() {
6766 const { props } = this
6867
69− const addr = props.wallet.matchWith({
70− Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
71− Nothing: () => {
72− throw BRIDGE_ERROR.MISSING_WALLET
73− }
74− })
68+ const addr = need.address(props);
7569
7670 props.web3.matchWith({
7771 Nothing: () => {},
@@ -195,7 +189,7 @@ class StatelessTransaction extends React.Component {
195189 try {
196190 this.setState({ txStatus: SUBMISSION_STATES.PREPARING });
197191 const res = await props.beforeSend(stx);
198− if (res === false) throw 'beforeSend disallowed sending';
192+ if (res === false) throw new Error('beforeSend disallowed sending');
199193 } catch (e) {
200194 console.log('beforeSend error', e);
201195 this.setState({
@@ -211,21 +205,15 @@ class StatelessTransaction extends React.Component {
211205 const rawTx = hexify(stx.serialize());
212206 const cost = (state.gasLimit * toWei(state.gasPrice, 'gwei'));
213207
214− //TODO need a lib function or something for address=, it's everywhere
215− const address = props.wallet.matchWith({
216− Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
217− Nothing: () => {
218− throw BRIDGE_ERROR.MISSING_WALLET
219− }
220− });
208+ const address = need.address(props);
221209 let balance = await web3.eth.getBalance(address);
222210 let hasBalance = (balance >= cost);
223211
224212 let usedTank = false;
225213 // if we need to, try and fund the transaction
226214 if (!hasBalance) {
227− hasBalance = await this.ensureFundsFor(web3, address, cost, [rawTx]);
228− usedTank = hasBalance;
215+ usedTank = await this.ensureFundsFor(web3, address, cost, [rawTx]);
216+ hasBalance = usedTank;
229217 }
230218
231219 // if we still don't have sufficient balance, fail and tell the user
@@ -268,26 +256,15 @@ class StatelessTransaction extends React.Component {
268256 }
269257 }
270258
271− //TODO partially copied from InviteTransactions, try to move into tank lib
272− async ensureFundsFor(web3, address, cost, signedTxs) {
273− let balance = await web3.eth.getBalance(address);
274− if (cost > balance) {
275− try {
276−
277− const res = await tank.fundTransactions(signedTxs);
278− if (!res.success) {
279− return false;
280− } else {
281− await waitForTransactionConfirm(web3, res.txHash);
282− return true;
283− }
284−
285− } catch (e) {
286− return false;
287− }
288− } else {
289− return true;
290− }
259+ // uses the gas tank to ensure funds, but if we have to ask the user
260+ // for funding, just resolve instead of waiting
261+ // returns true if the gas tank was used, false otherwise
262+ ensureFundsFor(web3, address, cost, signedTxs) {
263+ return new Promise((resolve, reject) => {
264+ tank.ensureFundsFor(web3, null, address, cost, signedTxs,
265+ () => { resolve(false); })
266+ .then(res => { resolve(res); });
267+ });
291268 }
292269
293270 getChainTitle(chainId) {
src/bridge/lib/invite.js+32−168
@@ -1,32 +1,18 @@
1−import lodash from 'lodash'
21import { Just, Nothing } from 'folktale/maybe'
32import Tx from 'ethereumjs-tx'
43
54import * as azimuth from 'azimuth-js'
6−import * as more from 'more-entropy'
7−import * as ob from 'urbit-ob'
85import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
6+import * as wg from '../../walletgen/lib/lib.js'
97import * as tank from './tank'
10−import { MIN_STAR, MIN_PLANET, SEED_ENTROPY_BITS,
11− GALAXY_ENTROPY_BITS, STAR_ENTROPY_BITS, PLANET_ENTROPY_BITS,
12− } from '../../walletgen/lib/constants'
138
149import JSZip from 'jszip'
1510import saveAs from 'file-saver'
1611
17−import {
18− waitForTransactionConfirm,
19− isTransactionConfirmed
20−} from './txn'
21−import { BRIDGE_ERROR } from './error'
12+import { sendSignedTransaction } from './txn'
13+import { BRIDGE_ERROR } from '../lib/error'
2214import { attemptSeedDerivation } from './keys'
23−import {
24− addressFromSecp256k1Public,
25− addHexPrefix,
26− WALLET_NAMES
27−} from './wallet'
28−
29−const SEED_LENGTH_BYTES = SEED_ENTROPY_BITS / 8
15+import { addHexPrefix, WALLET_NAMES } from './wallet'
3016
3117const INVITE_STAGES = {
3218 INVITE_LOGIN: "invite login",
@@ -79,58 +65,9 @@ const TRANSACTION_STATES = {
7965 },
8066}
8167
82−//TODO should be moved to lib/walletgen
8368async function generateWallet(point) {
84− const makeTicket = point => {
85−
86− const bits = point < MIN_STAR
87− ? GALAXY_ENTROPY_BITS
88− : point < MIN_PLANET
89− ? STAR_ENTROPY_BITS
90− : PLANET_ENTROPY_BITS
91−
92− const bytes = bits / 8
93− const some = new Uint8Array(bytes)
94− window.crypto.getRandomValues(some)
95−
96− const gen = new more.Generator()
97−
98− return new Promise((resolve, reject) => {
99− gen.generate(bits, result => {
100− const chunked = lodash.chunk(result, 2)
101− const desired = chunked.slice(0, bytes) // only take required entropy
102− const more = lodash.flatMap(desired, arr => arr[0] ^ arr[1])
103− const entropy = lodash.zipWith(some, more, (x, y) => x ^ y)
104− const buf = Buffer.from(entropy)
105− const patq = ob.hex2patq(buf.toString('hex'))
106− resolve(patq)
107− reject('Entropy generation failed')
108− })
109− })
110− }
111−
112− const genWallet = async (point, ticket, cb) => {
113−
114− const config = {
115− ticket: ticket,
116− seedSize: SEED_LENGTH_BYTES,
117− ship: point,
118− password: '',
119− revisions: {},
120− boot: false //TODO should this generate networking keys here already?
121− };
122−
123− const wallet = await kg.generateWallet(config);
124−
125− // This is here to notify the anyone who opens console because the thread
126− // hangs, blocking UI updates so this cannot be doen in the UI
127− console.log('Generating Wallet for point address: ', point);
128−
129− return wallet;
130− }
131−
132− const ticket = await makeTicket(point);
133− const wallet = await genWallet(point, ticket);
69+ const ticket = await wg.makeTicket(point);
70+ const wallet = await wg.generateWallet(point, ticket);
13471 return wallet;
13572}
13673
@@ -169,6 +106,13 @@ async function startTransactions(args) {
169106 let { realPointM, web3M, contractsM,
170107 inviteWalletM, realWalletM, setUrbitWallet, updateProgress } = args
171108
109+ const askForFunding = (address, amount, current) => {
110+ updateProgress({
111+ type: "notify",
112+ value: `Please make sure ${address} has at least ${amount} wei, we'll continue once that's true. Current balance: ${current}. Waiting`
113+ });
114+ }
115+
172116 if (Nothing.hasInstance(web3M)) {
173117 throw BRIDGE_ERROR.MISSING_WEB3;
174118 }
@@ -196,7 +140,7 @@ async function startTransactions(args) {
196140 }
197141 const point = realPointM.value;
198142
199− const inviteAddress = addressFromSecp256k1Public(inviteWallet.publicKey);
143+ const inviteAddress = inviteWallet.address;
200144 console.log('working as', inviteAddress);
201145
202146 // transfer from invite wallet to new wallet
@@ -217,7 +161,13 @@ async function startTransactions(args) {
217161 let rawTransferStx = '0x'+transferStx.serialize().toString('hex');
218162
219163 const transferCost = transferTx.gas * transferTx.gasPrice;
220− await ensureFundsFor(web3, point, inviteAddress, transferCost, [rawTransferStx], updateProgress, true);
164+ updateProgress({
165+ type: "progress",
166+ value: TRANSACTION_STATES.FUNDING_INVITE
167+ });
168+ let usedTank = await tank.ensureFundsFor(
169+ web3, point, inviteAddress, transferCost, [rawTransferStx], askForFunding
170+ );
221171
222172 updateProgress({
223173 type: "progress",
@@ -225,7 +175,7 @@ async function startTransactions(args) {
225175 });
226176
227177 // send transaction
228− await sendAndAwaitConfirm(web3, [rawTransferStx]);
178+ await sendAndAwaitConfirm(web3, [rawTransferStx], usedTank);
229179
230180 //
231181 // we're gonna be operating as the new wallet from here on out, so change
@@ -281,14 +231,20 @@ async function startTransactions(args) {
281231 return '0x'+stx.serialize().toString('hex');
282232 });
283233
284− await ensureFundsFor(web3, point, newAddress, totalCost, rawStxs, updateProgress, false);
234+ updateProgress({
235+ type: "progress",
236+ value: TRANSACTION_STATES.FUNDING_RECIPIENT
237+ });
238+ usedTank = await tank.ensureFundsFor(
239+ web3, point, newAddress, totalCost, rawStxs, askForFunding
240+ );
285241
286242 updateProgress({
287243 type: "progress",
288244 value: TRANSACTION_STATES.CONFIGURING
289245 });
290246
291− await sendAndAwaitConfirm(web3, rawStxs);
247+ await sendAndAwaitConfirm(web3, rawStxs, usedTank);
292248
293249 updateProgress({
294250 type: "progress",
@@ -328,106 +284,14 @@ async function startTransactions(args) {
328284 setUrbitWallet(Just(realWallet));
329285}
330286
331−async function ensureFundsFor(web3, point, address, cost, signedTxs, updateProgress, firstTx) {
332− let fundingState = firstTx? TRANSACTION_STATES.FUNDING_INVITE : TRANSACTION_STATES.FUNDING_RECIPIENT
333−
334− updateProgress({
335− type: "progress",
336− value: fundingState
337− });
338−
339− let balance = await web3.eth.getBalance(address);
340−
341− if (cost > balance) {
342−
343− try {
344−
345− const fundsRemaining = await tank.remainingTransactions(point);
346− if (fundsRemaining < signedTxs.length) {
347− throw new Error('request invalid');
348− }
349−
350− const res = await tank.fundTransactions(signedTxs);
351− if (!res.success) {
352− throw new Error('request rejected');
353− } else {
354− await waitForTransactionConfirm(web3, res.txHash);
355− let newBalance = await web3.eth.getBalance(address);
356− console.log('funds have confirmed', balance >= cost, balance, newBalance);
357− }
358−
359− } catch (e) {
360−
361− console.log('funding failed', e);
362− await waitForBalance(web3, address, cost, updateProgress);
363−
364− }
365−
366− } else {
367− console.log('already have sufficient funds');
368− }
369−}
370−
371−// resolves when address has at least minBalance
372−//
373−async function waitForBalance(web3, address, minBalance, updateProgress) {
374− console.log('awaiting balance', address, minBalance);
375− return new Promise((resolve, reject) => {
376− let oldBalance = null;
377− const checkForBalance = async () => {
378− const balance = await web3.eth.getBalance(address);
379− if (balance >= minBalance) {
380− resolve();
381− } else {
382− if (balance !== oldBalance) {
383− askForFunding(address, minBalance, balance, updateProgress);
384− }
385− setTimeout(checkForBalance, 13000);
386− }
387− };
388− checkForBalance();
389− });
390−}
391−
392−async function sendAndAwaitConfirm(web3, signedTxs) {
287+async function sendAndAwaitConfirm(web3, signedTxs, usedTank) {
393288 await Promise.all(signedTxs.map(tx => {
394− console.log('sending...');
395289 return new Promise((resolve, reject) => {
396− web3.eth.sendSignedTransaction(tx).then(res => {
397− console.log('sent, now waiting for confirm!', res.transactionHash);
398− waitForTransactionConfirm(web3, res.transactionHash)
399− .then(resolve);
400− }).catch(async err => {
401− // if there's an error, check if it's because the transaction was
402− // already confirmed prior to sending.
403− // this is really only the case in local dev environments.
404− const txHash = web3.utils.keccak256(tx);
405− console.log(err.message);
406− if (err.message.slice(0,54) ===
407− "Returned error: the tx doesn't have the correct nonce.") {
408− console.log('nonce error, awaiting confirm', err.message.slice(55));
409− //TODO max wait time before assume failure?
410− let res = await waitForTransactionConfirm(web3, txHash);
411− if (res) resolve();
412− else reject(new Error('Unexpected tx failure'));
413− } else {
414− const confirmed = await isTransactionConfirmed(web3, txHash);
415− console.log('error, but maybe confirmed:', confirmed);
416− if (confirmed) resolve();
417− else reject(err);
418− }
419− });
290+ sendSignedTransaction(web3, tx, usedTank, resolve);
420291 });
421292 }));
422293}
423294
424−function askForFunding(address, amount, current, updateProgress) {
425− updateProgress({
426− type: "notify",
427− value: `Please make sure ${address} has at least ${amount} wei, we'll continue once that's true. Current balance: ${current}. Waiting`
428− });
429−}
430−
431295export {
432296 generateWallet,
433297 downloadWallet,
src/bridge/lib/inviteMail.js+20−4
@@ -4,22 +4,38 @@
44// https://stackoverflow.com/a/53011185/1334324
55const baseUrl = 'https://localhost:3002'
66
7−function sendMail(recipient, ticket) {
7+function sendRequest(where, what) {
88 return new Promise((resolve, reject) => {
9− fetch(baseUrl + '/send-ticket', {
9+ fetch(baseUrl + where, {
1010 method: 'POST',
1111 cache: 'no-cache',
1212 headers: {
1313 'Content-Type': 'application/json'
1414 },
15− body: JSON.stringify({recipient,ticket}),
15+ body: JSON.stringify(what)
16+ })
17+ .then(response => {
18+ if (response.ok) {
19+ resolve(response.json());
20+ } else {
21+ reject(response);
22+ }
1623 })
17− .then(response => resolve(response.ok))
1824 .catch(reject);
1925 });
2026}
2127
28+async function hasReceived(recipient) {
29+ const res = await sendRequest('/has-received', {recipient});
30+ return res.hasReceived;
31+}
32+
33+function sendMail(recipient, ticket, tx) {
34+ return sendRequest('/send-ticket', { recipient, ticket, tx });
35+}
36+
2237export {
38+ hasReceived,
2339 sendMail
2440}
2541
src/bridge/lib/keys.js+5−19
@@ -1,5 +1,6 @@
11import BN from 'bn.js'
22import Maybe from 'folktale/maybe'
3+import * as need from '../lib/need'
34
45import * as noun from '../nockjs/noun'
56import * as serial from '../nockjs/serial'
@@ -7,7 +8,6 @@ import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
78
89import { BRIDGE_ERROR } from './error'
910import {
10− addressFromSecp256k1Public,
1111 WALLET_NAMES,
1212 eqAddr
1313 } from './wallet'
@@ -63,24 +63,15 @@ const genKey = (networkSeed, point, revision) => {
6363// 'next' refers to setting the next set of keys
6464// if false, we use revision - 1
6565const attemptSeedDerivation = async (next, args) => {
66− const { walletType, wallet, pointCursor, pointCache } = args
66+ const { walletType } = args
6767 const { urbitWallet, authMnemonic } = args
6868
6969 // NB (jtobin):
7070 //
7171 // following code is intentionally verbose for sake of clarity
7272
73− const point = pointCursor.matchWith({
74− Just: pt => pt.value,
75− Nothing: _ => {
76− throw BRIDGE_ERROR.MISSING_POINT
77− }
78− })
79−
80− const pointDetails =
81− point in pointCache
82− ? pointCache[point]
83− : (() => { throw BRIDGE_ERROR.MISSING_POINT })()
73+ const point = need.pointCursor(args);
74+ const pointDetails = need.fromPointCache(args, point);
8475
8576 const revision =
8677 next === true
@@ -104,12 +95,7 @@ const attemptSeedDerivation = async (next, args) => {
10495
10596 } else if (walletType === WALLET_NAMES.MNEMONIC) {
10697
107− const walProxy = wallet.matchWith({
108− Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
109− Nothing: _ => {
110− throw BRIDGE_ERROR.MISSING_WALLET
111− }
112− })
98+ const walProxy = need.address(args);
11399
114100 const mnemonic = authMnemonic.matchWith({
115101 Just: mnem => mnem.value,
src/bridge/lib/need.jsnew file+62
@@ -0,0 +1,62 @@
1+// need.js: +need but worse
2+//
3+// generally, pass the props object into these functions
4+
5+import { BRIDGE_ERROR } from '../lib/error'
6+
7+function web3(obj) {
8+ return ('web3' in obj === false)
9+ ? (() => { throw BRIDGE_ERROR.MISSING_WEB3 })()
10+ : obj.web3.matchWith({
11+ Nothing: () => { throw BRIDGE_ERROR.MISSING_WEB3 },
12+ Just: w => w.value
13+ });
14+}
15+
16+function contracts(obj) {
17+ return ('contracts' in obj === false)
18+ ? (() => { throw BRIDGE_ERROR.MISSING_CONTRACTS })()
19+ : obj.contracts.matchWith({
20+ Nothing: () => { throw BRIDGE_ERROR.MISSING_CONTRACTS },
21+ Just: c => c.value
22+ });
23+}
24+
25+function wallet(obj) {
26+ return ('wallet' in obj === false)
27+ ? (() => { throw BRIDGE_ERROR.MISSING_WALLET })()
28+ : obj.wallet.matchWith({
29+ Nothing: () => { throw BRIDGE_ERROR.MISSING_WALLET },
30+ Just: w => w.value
31+ });
32+}
33+
34+function address(obj) {
35+ return wallet(obj).address;
36+}
37+
38+function pointCursor(obj) {
39+ return ('pointCursor' in obj === false)
40+ ? (() => { throw BRIDGE_ERROR.MISSING_POINT })()
41+ : obj.pointCursor.matchWith({
42+ Nothing: () => { throw BRIDGE_ERROR.MISSING_POINT },
43+ Just: p => p.value
44+ });
45+}
46+
47+function fromPointCache(obj, point) {
48+ return ('pointCache' in obj === false)
49+ ? (() => { throw BRIDGE_ERROR.MISSING_POINT })()
50+ : point in obj.pointCache
51+ ? obj.pointCache[point]
52+ : (() => { throw BRIDGE_ERROR.MISSING_POINT })()
53+}
54+
55+export {
56+ web3,
57+ contracts,
58+ wallet,
59+ address,
60+ pointCursor,
61+ fromPointCache
62+}
src/bridge/lib/router.js+5−10
@@ -1,4 +1,5 @@
11import * as ob from 'urbit-ob'
2+import * as need from '../lib/need'
23
34import React from 'react';
45
@@ -35,9 +36,8 @@ import Ticket from '../views/Ticket'
3536import Transfer from '../views/Transfer'
3637import Wallet from '../views/Wallet'
3738
38−import { addressFromSecp256k1Public, EthereumWallet } from './wallet'
39+import { EthereumWallet } from './wallet'
3940import { renderNetworkType } from './network'
40−import { BRIDGE_ERROR } from './error'
4141
4242const ROUTE_NAMES = {
4343 DEFAULT: Symbol('DEFAULT'),
@@ -111,7 +111,7 @@ const createRoutes = () => {
111111const ROUTES = createRoutes()
112112
113113const renderRoute = (props, route) => {
114− const { wallet, networkType, pointCursor } = props
114+ const { wallet, networkType } = props
115115 return (
116116 route === ROUTE_NAMES.LANDING
117117 ? 'Bridge'
@@ -137,7 +137,7 @@ const renderRoute = (props, route) => {
137137 Just: (wal) =>
138138 wal.value instanceof EthereumWallet
139139 ? (<span className="text-mono">{wal.value.address}</span>)
140− : (<span className="text-mono">{addressFromSecp256k1Public(wal.value.publicKey)}</span>)
140+ : (<span className="text-mono">{wal.value.address}</span>)
141141 })
142142
143143 : route === ROUTE_NAMES.MNEMONIC
@@ -168,12 +168,7 @@ const renderRoute = (props, route) => {
168168 ? 'View'
169169
170170 : route === ROUTE_NAMES.SHIP
171− ? pointCursor.matchWith({
172− Just: (cursor) => ob.patp(cursor.value),
173− Nothing: () => {
174− throw BRIDGE_ERROR.MISSING_POINT
175− }
176− })
171+ ? ob.patp(need.pointCursor(props))
177172
178173 : route === ROUTE_NAMES.SET_TRANSFER_PROXY
179174 || route === ROUTE_NAMES.SET_MANAGEMENT_PROXY
src/bridge/lib/tank.js+81−1
@@ -1,3 +1,7 @@
1+// /lib/tank: functions for funding transactions
2+
3+import { waitForTransactionConfirm } from './txn'
4+
15//NOTE if accessing this in a localhost configuration fails with "CORS request
26// did not succeed", you might need to visit localhost:3001 or whatever
37// explicitly and tell your browser that's safe to access.
@@ -34,8 +38,84 @@ const fundTransactions = signedTxs => {
3438 return sendRequest('/request', {txs:signedTxs});
3539};
3640
41+// resolves when address has at least cost balance
42+// returns true if gas tank was used, false otherwise
43+// askForFunding: callback that takes (address, requiredBalance, currentBalance)
44+// and tells the user to get that address the required balance
45+// gotFunding: optional callback that stops telling the user to go get funding
46+const ensureFundsFor = async (web3, point, address, cost, signedTxs,
47+ askForFunding, gotFunding) => {
48+ let balance = await web3.eth.getBalance(address);
49+
50+ if (cost > balance) {
51+
52+ try {
53+
54+ if (point !== null) {
55+ const fundsRemaining = await remainingTransactions(point);
56+ if (fundsRemaining < signedTxs.length) {
57+ throw new Error('tank: request invalid');
58+ }
59+ } else {
60+ console.log('tank: skipping remaining-funds check');
61+ //TODO if we can't always (easily) provide a point, and the
62+ // fundTransactions call is gonna fail anyway, should we maybe
63+ // just not bother doing this check in the first place?
64+ }
65+
66+ const res = await fundTransactions(signedTxs);
67+ if (!res.success) {
68+ throw new Error('tank: request rejected');
69+ } else {
70+ await waitForTransactionConfirm(web3, res.txHash);
71+ let newBalance = await web3.eth.getBalance(address);
72+ console.log('tank: funds have confirmed', balance >= cost, balance, newBalance);
73+ return true;
74+ }
75+
76+ } catch (e) {
77+
78+ console.log('tank: funding failed', e);
79+ await waitForBalance(web3, address, cost, askForFunding, gotFunding);
80+
81+ }
82+
83+ } else {
84+ console.log('tank: already have sufficient funds');
85+ }
86+ return false;
87+};
88+
89+// resolves when address has at least minBalance
90+//
91+//TODO should maybe do a "we got it" callback also, so clients can hide msg?
92+async function waitForBalance(web3, address, minBalance,
93+ askForFunding, gotFunding) {
94+ console.log('tank: awaiting balance', address, minBalance);
95+ return new Promise((resolve, reject) => {
96+ let oldBalance = null;
97+ const checkForBalance = async () => {
98+ const balance = await web3.eth.getBalance(address);
99+ if (balance >= minBalance) {
100+ // if we ever asked for funding, retract that request now
101+ if (gotFunding && (oldBalance !== null)) {
102+ gotFunding();
103+ }
104+ resolve();
105+ } else {
106+ if (balance !== oldBalance) {
107+ askForFunding(address, minBalance, balance);
108+ }
109+ setTimeout(checkForBalance, 13000);
110+ }
111+ };
112+ checkForBalance();
113+ });
114+}
115+
37116export {
38117 remainingTransactions,
39− fundTransactions
118+ fundTransactions,
119+ ensureFundsFor
40120}
41121
src/bridge/lib/txn.js+8−3
@@ -1,6 +1,5 @@
11import * as ob from 'urbit-ob'
22import { Just } from 'folktale/maybe'
3−import { Ok, Error } from 'folktale/result'
43import Tx from 'ethereumjs-tx'
54import { toWei, fromWei, toHex } from 'web3-utils'
65
@@ -134,7 +133,8 @@ const signTransaction = async config => {
134133 stx.sign(sec)
135134 }
136135
137− setStx(Just(stx))
136+ setStx(Just(stx));
137+ return stx;
138138}
139139
140140const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
@@ -152,6 +152,10 @@ const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
152152 .on('transactionHash', hash => {
153153 resolve(hash);
154154 })
155+ //TODO do we also reach this if network is slow? web3 only tries a set
156+ // amount of times... should we instead do waitForTransactionConfirm
157+ // in on-transactionHash? we don't care (much) about additional
158+ // confirms anyway.
155159 .on('confirmation', (confirmationNum, txn) => {
156160 confirmationCb(txn.transactionHash, confirmationNum + 1);
157161 resolve(txn.transactionHash);
@@ -160,8 +164,9 @@ const sendSignedTransaction = (web3, stx, doubtNonceError, confirmationCb) => {
160164 // if there's a nonce error, but we used the gas tank, it's likely
161165 // that it's because the tank already submitted our transaction.
162166 // we just wait for first confirmation here.
167+ console.log('sent and got error', err);
163168 if (doubtNonceError &&
164− err.message.includes("the tx doesn't have the correct nonce.")) {
169+ (err.message || '').includes("the tx doesn't have the correct nonce.")) {
165170 console.log('nonce error, likely from gas tank submission, ignoring');
166171 const txHash = web3.utils.keccak256(rawTx);
167172 //TODO can we do does-exists check first?
src/bridge/lib/wallet.js+17−5
@@ -89,7 +89,7 @@ const eqAddr = (addr0, addr1) =>
8989 toChecksumAddress(addr0) === toChecksumAddress(addr1)
9090
9191const urbitWalletFromTicket = async (ticket, point, passphrase) => {
92− if (typeof point === 'string') {
92+ if (typeof point === 'string') { //TODO why not in kg?
9393 point = ob.patp2dec(point);
9494 }
9595 let uhdw = await kg.generateWallet({
@@ -97,12 +97,21 @@ const urbitWalletFromTicket = async (ticket, point, passphrase) => {
9797 ship: point,
9898 passphrase: passphrase
9999 });
100− //TODO remove after keygen-js#61 gets released
101− uhdw.meta.ship = point;
102− uhdw.meta.passphrase = passphrase;
103100 return uhdw;
104101}
105102
103+
104+const ownershipWalletFromTicket = async (ticket, point, passphrase) => {
105+ if (typeof point === 'string') { //TODO why not in kg?
106+ point = ob.patp2dec(point);
107+ }
108+ return await kg.generateOwnershipWallet({
109+ ticket,
110+ ship: point,
111+ passphrase
112+ })
113+}
114+
106115const walletFromMnemonic = (mnemonic, hdpath, passphrase) => {
107116 const seed =
108117 bip39.validateMnemonic(mnemonic)
@@ -113,7 +122,9 @@ const walletFromMnemonic = (mnemonic, hdpath, passphrase) => {
113122 let wal
114123 try {
115124 const hd = bip32.fromSeed(sd)
116− wal = Maybe.Just(hd.derivePath(path))
125+ wal = hd.derivePath(path);
126+ wal.address = addressFromSecp256k1Public(wal.publicKey);
127+ wal = Maybe.Just(wal);
117128 } catch (_) {
118129 wal = Maybe.Nothing()
119130 }
@@ -135,6 +146,7 @@ export {
135146 addressFromSecp256k1Public,
136147 eqAddr,
137148 urbitWalletFromTicket,
149+ ownershipWalletFromTicket,
138150 walletFromMnemonic,
139151 addHexPrefix,
140152 EthereumWallet
src/bridge/views/AcceptTransfer.js+7−36
@@ -4,29 +4,19 @@ import { Row, Col, H1, P } from '../components/Base'
44import { InnerLabel, AddressInput, ShowBlockie, Anchor } from '../components/Base'
55import * as azimuth from 'azimuth-js'
66import * as ob from 'urbit-ob'
7+import * as need from '../lib/need'
78
89import StatelessTransaction from '../components/StatelessTransaction'
9−import { BRIDGE_ERROR } from '../lib/error'
1010import { NETWORK_NAMES } from '../lib/network'
11−import { isValidAddress, addressFromSecp256k1Public } from '../lib/wallet'
11+import { isValidAddress } from '../lib/wallet'
1212
1313class AcceptTransfer extends React.Component {
1414 constructor(props) {
1515 super(props)
1616
17− const receivingAddress = props.wallet.matchWith({
18− Just: (wal) => addressFromSecp256k1Public(wal.value.publicKey),
19− Nothing: () => {
20− throw BRIDGE_ERROR.MISSING_WALLET
21− }
22− })
17+ const receivingAddress = need.address(props);
2318
24− const incomingPoint = props.pointCursor.matchWith({
25− Just: (shp) => shp.value,
26− Nothing: () => {
27− throw BRIDGE_ERROR.MISSING_POINT
28− }
29− })
19+ const incomingPoint = need.pointCursor(props);
3020
3121 this.state = {
3222 receivingAddress: receivingAddress,
@@ -46,19 +36,9 @@ class AcceptTransfer extends React.Component {
4636 createUnsignedTxn() {
4737 const { state, props } = this
4838
49− const validContracts = props.contracts.matchWith({
50− Just: (cs) => cs.value,
51− Nothing: () => {
52− throw BRIDGE_ERROR.MISSING_CONTRACTS
53− }
54− })
39+ const validContracts = need.contracts(props);
5540
56− const validPoint = props.pointCursor.matchWith({
57− Just: (shp) => shp.value,
58− Nothing: () => {
59− throw BRIDGE_ERROR.MISSING_POINT
60− }
61− })
41+ const validPoint = need.pointCursor(props); //TODO state.incomingPoint ?
6242
6343 return Just(azimuth.ecliptic.transferPoint(
6444 validContracts,
@@ -117,16 +97,7 @@ class AcceptTransfer extends React.Component {
11797
11898 <StatelessTransaction
11999 // Upper scope
120− web3={props.web3}
121− contracts={props.contracts}
122− wallet={props.wallet}
123− walletType={props.walletType}
124− walletHdPath={props.walletHdPath}
125− networkType={props.networkType}
126− onSent={props.setTxnHashCursor}
127− setTxnConfirmations={props.setTxnConfirmations}
128− popRoute={props.popRoute}
129− pushRoute={props.pushRoute}
100+ {...props}
130101 // Other
131102 canGenerate={ canGenerate }
132103 createUnsignedTxn={this.createUnsignedTxn}
src/bridge/views/CancelTransfer.js+5−29
@@ -3,21 +3,16 @@ import React from 'react'
33import { Row, Col, H1, P } from '../components/Base'
44import * as azimuth from 'azimuth-js'
55import * as ob from 'urbit-ob'
6+import * as need from '../lib/need'
67
78import StatelessTransaction from '../components/StatelessTransaction'
8−import { BRIDGE_ERROR } from '../lib/error'
99import { ETH_ZERO_ADDR } from '../lib/wallet'
1010
1111class CancelTransfer extends React.Component {
1212 constructor(props) {
1313 super(props)
1414
15− const pointInTransfer = props.pointCursor.matchWith({
16− Just: (pt) => pt.value,
17− Nothing: () => {
18− throw BRIDGE_ERROR.MISSING_POINT
19− }
20− })
15+ const pointInTransfer = need.pointCursor(props);
2116
2217 this.state = {
2318 proxyAddress: '',
@@ -30,19 +25,9 @@ class CancelTransfer extends React.Component {
3025 createUnsignedTxn() {
3126 const { props } = this
3227
33− const validContracts = props.contracts.matchWith({
34− Just: (cs) => cs.value,
35− Nothing: () => {
36− throw BRIDGE_ERROR.MISSING_CONTRACTS
37− }
38− })
28+ const validContracts = need.contracts(props);
3929
40− const validPoint = props.pointCursor.matchWith({
41− Just: (pt) => pt.value,
42− Nothing: () => {
43− throw BRIDGE_ERROR.MISSING_POINT
44− }
45− })
30+ const validPoint = need.pointCursor(props); //TODO this.state.pointInTransfer ?
4631
4732 const txn = azimuth.ecliptic.setTransferProxy(
4833 validContracts,
@@ -80,16 +65,7 @@ class CancelTransfer extends React.Component {
8065 </P>
8166 <StatelessTransaction
8267 // Upper scope
83− web3={props.web3}
84− contracts={props.contracts}
85− wallet={props.wallet}
86− walletType={props.walletType}
87− walletHdPath={props.walletHdPath}
88− networkType={props.networkType}
89− onSent={props.setTxnHashCursor}
90− setTxnConfirmations={props.setTxnConfirmations}
91− popRoute={props.popRoute}
92− pushRoute={props.pushRoute}
68+ {...props}
9369 // Other
9470 canGenerate={ canGenerate }
9571 createUnsignedTxn={this.createUnsignedTxn} />
src/bridge/views/CreateGalaxy.js+5−30
@@ -2,18 +2,17 @@ import React from 'react'
22import Maybe from 'folktale/maybe'
33import * as azimuth from 'azimuth-js'
44import * as ob from 'urbit-ob'
5+import * as need from '../lib/need'
56
67import { Button, } from '../components/Base'
78import { Row, Col, H1, P, Anchor } from '../components/Base'
89import { InnerLabel, GalaxyInput, AddressInput, ValidatedSigil, ShowBlockie } from '../components/Base'
910import StatelessTransaction from '../components/StatelessTransaction'
1011
11−import { BRIDGE_ERROR } from '../lib/error'
1212import { NETWORK_NAMES } from '../lib/network'
1313import { canDecodePatp } from '../lib/txn'
1414
1515import {
16− addressFromSecp256k1Public,
1716 ETH_ZERO_ADDR,
1817 isValidAddress,
1918 eqAddr
@@ -42,12 +41,7 @@ class CreateGalaxy extends React.Component {
4241 constructor(props) {
4342 super(props)
4443
45− const galaxyOwner = props.wallet.matchWith({
46− Just: (wal) => addressFromSecp256k1Public(wal.value.publicKey),
47− Nothing: () => {
48− throw BRIDGE_ERROR.MISSING_WALLET
49− }
50− })
44+ const galaxyOwner = need.address(props);
5145
5246 this.state = {
5347 galaxyOwner: galaxyOwner,
@@ -79,12 +73,7 @@ class CreateGalaxy extends React.Component {
7973 if (canDecodePatp(state.galaxyName) === false) return Maybe.Nothing()
8074 if (isValidGalaxy(state.galaxyName) === false) return Maybe.Nothing()
8175
82− const validContracts = props.contracts.matchWith({
83− Just: (cs) => cs.value,
84− Nothing: () => {
85− throw BRIDGE_ERROR.MISSING_CONTRACTS
86− }
87− })
76+ const validContracts = need.contracts(props);
8877
8978 const galaxyDec = parseInt(ob.patp2dec(state.galaxyName), 10)
9079
@@ -105,12 +94,7 @@ class CreateGalaxy extends React.Component {
10594 return
10695 }
10796
108− const validContracts = props.contracts.matchWith({
109− Just: (cs) => cs.value,
110− Nothing: () => {
111− throw BRIDGE_ERROR.MISSING_CONTRACTS
112− }
113− })
97+ const validContracts = need.contracts(props);
11498
11599 const galaxyDec = ob.patp2dec(state.galaxyName)
116100
@@ -205,16 +189,7 @@ class CreateGalaxy extends React.Component {
205189
206190 <StatelessTransaction
207191 // Upper scope
208− web3={props.web3}
209− contracts={props.contracts}
210− wallet={props.wallet}
211− walletType={props.walletType}
212− walletHdPath={props.walletHdPath}
213− networkType={props.networkType}
214− onSent={props.setTxnHashCursor}
215− setTxnConfirmations={props.setTxnConfirmations}
216− popRoute={props.popRoute}
217− pushRoute={props.pushRoute}
192+ {...props}
218193 // Other
219194 ref={this.statelessRef}
220195 canGenerate={canGenerate}
src/bridge/views/GenKeyfile.js+4−15
@@ -1,5 +1,6 @@
11import React from 'react'
22import Maybe from 'folktale/maybe'
3+import * as need from '../lib/need'
34
45import { Button } from '../components/Base'
56import { Row, Col, H1, P } from '../components/Base'
@@ -9,7 +10,6 @@ import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
910import saveAs from 'file-saver'
1011
1112import { attemptSeedDerivation, genKey } from '../lib/keys'
12−import { BRIDGE_ERROR } from '../lib/error'
1313import {
1414 addHexPrefix
1515 } from '../lib/wallet'
@@ -25,20 +25,9 @@ class GenKeyfile extends React.Component {
2525 }
2626
2727 getPointDetails() {
28− const { pointCache } = this.props
29− const { pointCursor } = this.props
30−
31− const point = pointCursor.matchWith({
32− Just: (pt) => pt.value,
33− Nothing: () => {
34− throw BRIDGE_ERROR.MISSING_POINT
35− }
36− })
37−
38− const pointDetails =
39− point in pointCache
40− ? pointCache[point]
41− : (() => { throw BRIDGE_ERROR.MISSING_POINT })()
28+ const point = need.pointCursor(this.props);
29+
30+ const pointDetails = need.fromPointCache(this.props, point);
4231
4332 // in case we did SetKeys earlier this session, make sure to generate the
4433 // newer keyfile, rather than the one that will expire soon
src/bridge/views/InviteTicket.js+9−13
@@ -3,11 +3,12 @@ import React from 'react'
33import { InnerLabel, Warning, TicketInput,
44 VerifyTicketInput, Button, Row, Col, Passport } from '../components/Base'
55import * as azimuth from 'azimuth-js'
6+import * as need from '../lib/need'
67
78import { randomPatq } from '../lib/lib'
89import { ROUTE_NAMES } from '../lib/router'
9−import { DEFAULT_HD_PATH, urbitWalletFromTicket,
10− walletFromMnemonic, addressFromSecp256k1Public } from '../lib/wallet'
10+import { DEFAULT_HD_PATH, ownershipWalletFromTicket,
11+ walletFromMnemonic } from '../lib/wallet'
1112import { BRIDGE_ERROR } from '../lib/error'
1213import { INVITE_STAGES, WALLET_STATES, TRANSACTION_STATES } from '../lib/invite'
1314import { generateWallet, startTransactions } from '../lib/invite'
@@ -71,12 +72,12 @@ class InviteTicket extends React.Component {
7172 });
7273
7374 //NOTE ~zod because tmp wallet
74− const uhdw = await urbitWalletFromTicket(inviteTicket, '~zod');
75− const mnemonic = uhdw.ownership.seed;
75+ const ownership = await ownershipWalletFromTicket(inviteTicket, 0);
76+ const mnemonic = ownership.seed;
77+ //TODO isn't all this accessible in the ownership object?
7678 const inviteWallet = walletFromMnemonic(
7779 mnemonic,
78− DEFAULT_HD_PATH,
79− uhdw.meta.passphrase
80+ DEFAULT_HD_PATH
8081 ).matchWith({
8182 Just: w => w.value,
8283 Nothing: () => {
@@ -84,14 +85,9 @@ class InviteTicket extends React.Component {
8485 }
8586 });
8687
87− const addr = addressFromSecp256k1Public(inviteWallet.publicKey);
88+ const addr = inviteWallet.address;
8889
89− const ctrcs = this.props.contracts.matchWith({
90− Just: ctrcs => ctrcs.value,
91− Nothing: () => {
92− throw BRIDGE_ERROR.MISSING_CONTRACTS
93− }
94− })
90+ const ctrcs = need.contracts(this.props);
9591
9692 const incoming = await azimuth.azimuth.getTransferringFor(ctrcs, addr)
9793
src/bridge/views/InvitesManage.js+11−27
@@ -2,8 +2,8 @@ import { Just, Nothing } from 'folktale/maybe'
22import React from 'react'
33import * as ob from 'urbit-ob'
44import * as azimuth from 'azimuth-js'
5+import * as need from '../lib/need'
56
6−import { BRIDGE_ERROR } from '../lib/error'
77import { Row, Col, Warning, Input,
88 PointInput, InnerLabel, ValidatedSigil } from '../components/Base'
99import StatelessTransaction from '../components/StatelessTransaction'
@@ -32,18 +32,8 @@ class InvitesManage extends React.Component {
3232 }
3333
3434 componentDidMount() {
35− this.point = this.props.pointCursor.matchWith({
36− Just: (pt) => parseInt(pt.value, 10),
37− Nothing: () => {
38− throw BRIDGE_ERROR.MISSING_POINT
39− }
40− });
41− this.contracts = this.props.contracts.matchWith({
42− Just: cs => cs.value,
43− Nothing: _ => {
44− throw BRIDGE_ERROR.MISSING_CONTRACTS
45− }
46− });
35+ this.point = need.pointCursor(this.props);
36+ this.contracts = need.contracts(this.props);
4737
4838 azimuth.azimuth.isSpawnProxy(
4939 this.contracts,
@@ -86,7 +76,7 @@ class InvitesManage extends React.Component {
8676 } else {
8777 azimuth.delegatedSending.getPool(this.contracts, who)
8878 .then(pool => {
89− azimuth.delegatedSending.invitesInPool(this.contracts, pool)
79+ azimuth.delegatedSending.invitesInPool(this.contracts, pool, this.point)
9080 .then(size => {
9181 this.updatePoolSize(who, size);
9282 });
@@ -96,11 +86,13 @@ class InvitesManage extends React.Component {
9686 }
9787
9888 updatePoolSize(who, size) {
99− this.state.cachedPoolSizes[who] = size;
89+ const cache = this.state.cachedPoolSizes;
90+ cache[who] = size;
91+ this.setState({ cachedPoolSizes: cache });
10092 const target = this.state.targetPlanet;
10193 const match = Just.hasInstance(target) ? (who === target.value) : false;
10294 if (match) {
103− this.setState({currentPoolSize:Just(size)});
95+ this.setState({ currentPoolSize: Just(size) });
10496 }
10597 }
10698
@@ -111,6 +103,7 @@ class InvitesManage extends React.Component {
111103 createUnsignedTxn() {
112104 return Just(azimuth.delegatedSending.setPoolSize(
113105 this.contracts,
106+ this.point,
114107 this.state.targetPlanet.value,
115108 this.state.targetPoolSize
116109 ))
@@ -134,7 +127,7 @@ class InvitesManage extends React.Component {
134127 }
135128
136129 let poolSizeText = this.state.currentPoolSize.matchWith({
137− Just: ps => `(currently ${ps.toString()})`,
130+ Just: ps => `(currently ${ps.value})`,
138131 Nothing: _ => ''
139132 });
140133
@@ -170,16 +163,7 @@ class InvitesManage extends React.Component {
170163
171164 <StatelessTransaction
172165 // Upper scope
173− web3={this.props.web3}
174− contracts={this.props.contracts}
175− wallet={this.props.wallet}
176− walletType={this.props.walletType}
177− walletHdPath={this.props.walletHdPath}
178− networkType={this.props.networkType}
179− onSent={this.props.setTxnHashCursor}
180− setTxnConfirmations={this.props.setTxnConfirmations}
181− popRoute={this.props.popRoute}
182− pushRoute={this.props.pushRoute}
166+ {...this.props}
183167 // Other
184168 canGenerate={this.state.canGenerate}
185169 createUnsignedTxn={this.createUnsignedTxn}
src/bridge/views/InvitesSend.js+381−120
@@ -1,16 +1,45 @@
11import { Just, Nothing } from 'folktale/maybe'
22import React from 'react'
3−import * as ob from 'urbit-ob'
43import * as azimuth from 'azimuth-js'
4+import * as need from '../lib/need'
55
6−import { BRIDGE_ERROR } from '../lib/error'
7−import { sendMail } from '../lib/inviteMail'
8−import { Row, Col, Input, InnerLabel } from '../components/Base'
9−import StatelessTransaction from '../components/StatelessTransaction'
6+import { hasReceived, sendMail } from '../lib/inviteMail'
7+import { Row, Col, Button, Input, H3, Warning } from '../components/Base'
108
119// for wallet generation
1210import * as wg from '../../walletgen/lib/lib'
1311
12+// for transaction generation and signing
13+import {
14+ signTransaction,
15+ sendSignedTransaction,
16+ waitForTransactionConfirm,
17+ hexify,
18+ fromWei,
19+ toWei
20+} from '../lib/txn'
21+import * as tank from '../lib/tank'
22+
23+const GAS_PRICE_GWEI = 20; // we pay the premium for faster ux
24+const GAS_LIMIT = 350000;
25+const INVITE_COST = toWei((GAS_PRICE_GWEI * GAS_LIMIT).toString(), 'gwei');
26+
27+const STATUS = {
28+ INPUT: 'Generate invites',
29+ GENERATING: 'Generating invites',
30+ CAN_SEND: 'Send invites',
31+ FUNDING: 'Funding invites',
32+ SENDING: 'Sending invites',
33+ DONE: 'Done',
34+ FAIL: 'Some failed'
35+}
36+
37+const EMAIL_STATUS = {
38+ BUSY: '⋯',
39+ DONE: '✔',
40+ FAIL: '×'
41+}
42+
1443class InvitesSend extends React.Component {
1544
1645 constructor(props) {
@@ -18,47 +47,39 @@ class InvitesSend extends React.Component {
1847
1948 this.state = {
2049 invitesAvailable: Nothing(),
21− fromPool: Nothing(),
22− haveInvited: Nothing(),
23− randomPlanet: Nothing(),
24− inviteWallet: Nothing(),
25− targetEmail: ''
50+ invited: Nothing(),
51+ targets: [{ email: '', hasReceived: Nothing(), status: Nothing() }],
52+ status: STATUS.INPUT,
53+ errors: Nothing(),
54+ //
55+ fundingMessage: Nothing(),
56+ wipInvites: []
2657 }
2758
2859 this.findInvited = this.findInvited.bind(this);
29− this.findRandomPlanet = this.findRandomPlanet.bind(this);
30− this.generateWallet = this.generateWallet.bind(this);
31− this.canGenerate = this.canGenerate.bind(this);
60+ this.generateInvites = this.generateInvites.bind(this);
61+ this.sendInvites = this.sendInvites.bind(this);
62+ this.canSend = this.canSend.bind(this);
63+ this.addError = this.addError.bind(this);
3264 this.handleEmailInput = this.handleEmailInput.bind(this);
33− this.txnConfirmation = this.txnConfirmation.bind(this);
34− this.createUnsignedTxn = this.createUnsignedTxn.bind(this);
65+ this.addTarget = this.addTarget.bind(this);
66+ this.removeTarget = this.removeTarget.bind(this);
67+ this.setEmailStatus = this.setEmailStatus.bind(this);
68+ this.askForFunding = this.askForFunding.bind(this);
3569 }
3670
3771 componentDidMount() {
38− this.point = this.props.pointCursor.matchWith({
39− Just: (pt) => parseInt(pt.value, 10),
40− Nothing: () => {
41− throw BRIDGE_ERROR.MISSING_POINT
42− }
43− });
44− this.contracts = this.props.contracts.matchWith({
45− Just: cs => cs.value,
46− Nothing: _ => {
47− throw BRIDGE_ERROR.MISSING_CONTRACTS
48− }
49− });
72+ this.point = parseInt(need.pointCursor(this.props), 10);
73+ this.address = need.address(this.props);
74+ this.contracts = need.contracts(this.props);
75+ this.web3 = need.web3(this.props);
5076
51− azimuth.delegatedSending.getPool(this.contracts, this.point)
52− .then(pool => {
53− this.setState({fromPool: Just(pool)});
54− azimuth.delegatedSending.invitesInPool(this.contracts, pool)
55− .then(count => {
56− this.setState({invitesAvailable: Just(count)});
57− });
77+ console.log(azimuth.delegatedSending);
78+ azimuth.delegatedSending.getTotalUsableInvites(this.contracts, this.point)
79+ .then(count => {
80+ this.setState({invitesAvailable: Just(count)});
5881 });
5982 this.findInvited();
60− this.findRandomPlanet();
61− this.generateWallet();
6283 }
6384
6485 componentDidUpdate(prevProps) {
@@ -76,127 +97,367 @@ class InvitesSend extends React.Component {
7697 return res;
7798 });
7899 invited = await Promise.all(invited);
79− this.setState({haveInvited: Just(invited)});
100+ const total = invited.length;
101+ const accepted = invited.filter(i => i.active).length;
102+ this.setState({invited: Just({total, accepted})});
80103 }
81104
82− async findRandomPlanet() {
83− let res = Nothing();
84− const kids = await azimuth.azimuth.getUnspawnedChildren(
85− this.contracts,
86− azimuth.azimuth.getPrefix(this.point)
105+ async getTemporaryWallet() {
106+ const ticket = await wg.makeTicket(0x10000); // planet-sized ticket
107+ const owner = await wg.generateOwnershipWallet(0, ticket);
108+ return {ticket, owner: owner.keys.address};
109+ }
110+
111+ async generateInvites() {
112+ this.setState({ status: STATUS.GENERATING });
113+
114+ const web3 = this.web3;
115+ const targets = this.state.targets;
116+ const planets = await azimuth.delegatedSending.getPlanetsToSend(
117+ this.contracts, this.point, targets.length
87118 );
88− if (kids.length > 0) {
89− const i = Math.floor(Math.random() * kids.length);
90− res = Just(Number(kids[i]));
119+
120+ // account for the race condition where invites got used up while we were
121+ // composing our target list
122+ if (planets.length < targets.length) {
123+ this.addError('Can currently only send '+planets.length+' invites!');
124+ return;
91125 }
92− this.setState({randomPlanet: res});
93− return res;
126+
127+ const nonce = await web3.eth.getTransactionCount(this.address);
128+ const chainId = await web3.eth.net.getId();
129+
130+ // create invite wallets and transactions
131+ let invites = []; // { email, ticket, signedTx, rawTx }
132+ for (let i = 0; i < targets.length; i++) {
133+ console.log('generating email', i, targets[i].email);
134+ this.setEmailStatus(i, EMAIL_STATUS.BUSY);
135+
136+ const email = targets[i].email;
137+ const wallet = await this.getTemporaryWallet();
138+
139+ let inviteTx = azimuth.delegatedSending.sendPoint(
140+ this.contracts, this.point, planets[i], wallet.owner
141+ );
142+ const signedTx = await signTransaction({
143+ ...this.props,
144+ txn: Just(inviteTx),
145+ gasPrice: GAS_PRICE_GWEI.toString(),
146+ gasLimit: GAS_LIMIT.toString(),
147+ nonce: nonce + i,
148+ chainId: chainId,
149+ setStx: () => {}
150+ });
151+ const rawTx = hexify(signedTx.serialize());
152+
153+ invites.push({email, ticket: wallet.ticket, signedTx, rawTx });
154+ this.setEmailStatus(i, EMAIL_STATUS.DONE);
155+ }
156+
157+ this.setState({ wipInvites: invites, status: STATUS.CAN_SEND });
94158 }
95159
96− async generateWallet() {
97− const ticket = await wg.makeTicket(0x10000); // planet-sized ticket
98− const wallet = await wg.generateWallet(0, ticket, false);
99− this.setState({inviteWallet:
100− Just({ticket, owner:wallet.ownership.keys.address})
160+ async sendInvites() {
161+ const invites = this.state.wipInvites;
162+ const web3 = this.web3;
163+
164+ //TODO invite status isn't changing/re-rendering... why?
165+ this.setState({
166+ status: STATUS.FUNDING,
167+ invites: invites.map(i => { return {...i, status: Nothing()}; })
101168 });
169+
170+ const tankWasUsed = await tank.ensureFundsFor(
171+ web3, this.point, this.address,
172+ (INVITE_COST * invites.length),
173+ invites.map(i => i.rawTx), this.askForFunding,
174+ () => { this.setState({ fundingMessage: Nothing() }); }
175+ );
176+
177+ this.setState({ status: STATUS.SENDING });
178+
179+ //TODO nasty loop, probably move into lib... but so many callbacks!
180+ let promises = [];
181+ for (let i = 0; i < invites.length; i++) {
182+ const invite = invites[i];
183+
184+ // send transaction. if it fails, inform the user and skip to next
185+ let txHash;
186+ try {
187+ txHash = await sendSignedTransaction(
188+ web3, Just(invite.signedTx), tankWasUsed, ()=>{}
189+ );
190+ } catch(err) {
191+ console.error('invite tx sending failed', err);
192+ this.addError('Invite transaction not sent for ' + invite.email);
193+ this.setEmailStatus(i, EMAIL_STATUS.FAIL);
194+ continue;
195+ };
196+
197+ // ask the email sender to send this. we can do this prior to tx confirm,
198+ // because the sender service waits for confirm & success for us.
199+ // if this fails, inform the user and skip to next
200+ try {
201+ const mailSuccess = await sendMail(
202+ invite.email, invite.ticket, invite.rawTx
203+ );
204+ if (!mailSuccess) throw new Error();
205+ this.setEmailStatus(i, EMAIL_STATUS.DONE);
206+ } catch(e) {
207+ console.log('email send failed');
208+ //NOTE this assumes that the transaction succeeds, but we don't know
209+ // that for a fact yet...
210+ this.addError(
211+ 'Invite email failed to send for ' + invite.email +
212+ '. Please give them this ticket: ' + invite.ticket
213+ );
214+ this.setEmailStatus(i, EMAIL_STATUS.FAIL);
215+ continue;
216+ }
217+
218+ // update status on transaction confirm, but don't block on it
219+ promises.push(
220+ waitForTransactionConfirm(web3, txHash)
221+ .then(success => {
222+ if (success) {
223+ this.setEmailStatus(i, EMAIL_STATUS.DONE);
224+ } else {
225+ console.log('invite tx rejected');
226+ this.addError('Invite transaction failed for ' + invite.email);
227+ this.setEmailStatus(i, EMAIL_STATUS.FAIL);
228+ }
229+ return success;
230+ })
231+ );
232+
233+ }
234+
235+ const txStatus = await Promise.all(promises);
236+ if (txStatus.every(e => e)) {
237+ this.setState({ status: STATUS.DONE });
238+ } else {
239+ this.setState({ status: STATUS.FAIL });
240+ }
102241 }
103242
104− handleEmailInput(email) {
105− //TODO simple .*@.*\..* email validation
106− this.setState({ targetEmail: email });
243+ setEmailStatus(i, status) {
244+ const targets = this.state.targets;
245+ targets[i].status = Just(status);
246+ this.setState({ targets });
107247 }
108248
109− canGenerate() {
110− //TODO and canSend()
111− let res = this.state.invitesAvailable.matchWith({
112− Nothing: () => false,
113− Just: invites => ((invites.value > 0)
114− && Just.hasInstance(this.state.randomPlanet)
115− && Just.hasInstance(this.state.inviteWallet))
249+ addError(error) {
250+ const newError = this.state.errors.matchWith({
251+ Nothing: () => [error],
252+ Just: errs => { errs.value.push(error); return errs.value; }
116253 });
117− return res;
254+ this.setState({ errors: Just(newError) });
118255 }
119256
120− createUnsignedTxn() {
121− const txn = azimuth.delegatedSending.sendPoint(
122− this.contracts,
123− this.point,
124− this.state.randomPlanet.value,
125− this.state.inviteWallet.value.owner
126− );
127− return Just(txn)
257+ askForFunding(address, minBalance, balance) {
258+ this.setState({ fundingMessage: Just(
259+ `Please make sure ${address} has at least ${fromWei(minBalance)} ETH,` +
260+ `we'll continue once that's true. Current balance: ${fromWei(balance)}.` +
261+ `Waiting...`
262+ ) });
128263 }
129264
130− //TODO find a better way of making the user wait for submission success
131− // and email sending, so that we can catch failure cases and can
132− // always fall back to retrying or "please send them this"
133− txnConfirmation(txHash, confirmations) {
134− this.props.setTxnConfirmations(txHash, confirmations);
135− if (confirmations === 1) {
136− sendMail(this.state.targetEmail, this.state.inviteWallet.value.ticket)
137− .then(console.log);
265+ handleEmailInput(i, email) {
266+ let targets = this.state.targets;
267+ targets[i].email = email;
268+
269+ // if it's a valid address, check if it has received an invite before
270+ if (email.match(/.*@.*\...+/)) {
271+ hasReceived(email)
272+ .then(res => {
273+ targets[i].hasReceived = Just(res);
274+ this.setState({ targets });
275+ });
276+ } else {
277+ targets[i].hasReceived = Nothing();
138278 }
279+
280+ this.setState({ targets });
281+ }
282+
283+ canSend() {
284+ const targets = this.state.targets;
285+
286+ // may not have duplicate targets
287+ if ((new Set(targets.map(t => t.email))).size !== targets.length)
288+ return false;
289+
290+ // none may have received invites already
291+ for (let i = 0; i < targets.length; i++) {
292+ const target = targets[i];
293+ const res = target.hasReceived.matchWith({
294+ Nothing: () => true,
295+ Just: has => has.value
296+ });
297+ if (res) return false;
298+ }
299+
300+ return this.state.invitesAvailable.matchWith({
301+ Nothing: () => false,
302+ Just: invites => (invites.value > 0)
303+ });
304+ }
305+
306+ addTarget() {
307+ let targets = this.state.targets;
308+ targets.push({ email: '', hasReceived: Nothing(), status: Nothing() });
309+ this.setState({ targets });
310+ }
311+
312+ removeTarget() {
313+ let targets = this.state.targets;
314+ targets.length = targets.length - 1;
315+ this.setState({ targets });
139316 }
140317
141318 render() {
142− const inviteStatus = this.state.invitesAvailable.matchWith({
319+
320+ const dif = (this.state.status === STATUS.DONE)
321+ ? this.state.targets.length
322+ : null;
323+
324+ const invitesAvailable = this.state.invitesAvailable.matchWith({
143325 Nothing: () => 'Loading...',
144326 Just: (invites) => {
145− let res = `${invites.value} invites available`;
146− return res + this.state.fromPool.matchWith({
147− Nothing: () => '',
148− Just: (pool) => (pool.value === this.point)
149− ? ''
150− : ` (from ${ob.patp(pool.value)})`
151− });
327+ const dia = dif ? `(-${dif})` : '';
328+ return `You currently have ${invites.value}${dia} invitations left.`;
152329 }
153330 });
154331
332+ const invitesSent = this.state.invited.matchWith({
333+ Nothing: () => (<>{'Loading...'}</>),
334+ Just: (invited) => {
335+ const dit = dif ? `(+${dif})` : '';
336+ return (<>
337+ {'Out of the '}
338+ {invited.value.total + dit}
339+ {' invites you sent, '}
340+ {invited.value.accepted}
341+ {' have been accepted.'}
342+ </>);
343+ }
344+ });
155345
156− const inviteList = this.state.haveInvited.matchWith({
157− Nothing: () => (<li>{'Loading...'}</li>),
158− Just: (invited) => invited.value.map(i => {
159− return (<li>
160− <span class="invitee">{ob.patp(i.point)}</span>:
161− <span class="status">{i.active ? 'accepted' : 'pending'}</span>
162− </li>);
163− })
346+ const error = this.state.errors.matchWith({
347+ Nothing: () => null,
348+ Just: (errors) => (<Warning className={'mt-8'}>
349+ <H3 style={{marginTop: 0, paddingTop: 0}}>
350+ { 'Something went wrong!' }
351+ </H3>
352+ { errors.value.map(err => (<p>{err}</p>)) }
353+ </Warning>)
164354 });
165355
356+ const fundingMessage = this.state.fundingMessage.matchWith({
357+ Nothing: () => null,
358+ Just: msg => (<Warning className={'mt-8'}>{msg.value}</Warning>)
359+ });
360+
361+ let inputs;
362+ if (this.state.status === STATUS.DONE) {
363+ //TODO proper clickable router thing
364+ inputs = '<- Back to Home';
365+ } else {
366+
367+ const inputDisabled = (this.state.status !== STATUS.INPUT);
368+
369+ const lessButton = (
370+ <Button
371+ prop-size={'s narrow'}
372+ className={'mt-8'}
373+ disabled={inputDisabled || this.state.targets.length === 1}
374+ onClick={this.removeTarget}
375+ >
376+ { '-' }
377+ </Button>
378+ );
379+
380+ const moreButton = (
381+ <Button
382+ prop-size={'s narrow'}
383+ className={'mt-8'}
384+ disabled={
385+ inputDisabled ||
386+ this.state.invitesAvailable.matchWith({
387+ Nothing: () => true,
388+ Just: (inv) => (inv.value <= this.state.targets.length)
389+ })
390+ }
391+ onClick={this.addTarget}
392+ >
393+ { '+' }
394+ </Button>
395+ );
396+
397+ let targetInputs = [];
398+ for (let i = 0; i < this.state.targets.length; i++) {
399+ const target = this.state.targets[i];
400+ let progress = target.status.matchWith({
401+ Nothing: () => '',
402+ Just: status => status.value
403+ });
404+ targetInputs.push(<>
405+ <Input
406+ value={ target.email }
407+ onChange={ (value) => { this.handleEmailInput(i, value); } }
408+ placeholder={'Email address'}
409+ disabled={ inputDisabled }>
410+ </Input>
411+ {
412+ target.hasReceived.matchWith({
413+ Nothing: () => null,
414+ Just: (has) => has.value ? <span>{'×'}</span> : <span>{'°'}</span>
415+ })
416+ }
417+ { progress }
418+ </>);
419+ }
420+
421+ const submitButton = (
422+ <Button
423+ prop-size={'xl wide'}
424+ className={'mt-8'}
425+ disabled={ (inputDisabled && (this.state.status !== STATUS.CAN_SEND))
426+ || !this.canSend() }
427+ onClick={ () => {
428+ if (this.state.status === STATUS.INPUT)
429+ this.generateInvites();
430+ if (this.state.status === STATUS.CAN_SEND)
431+ this.sendInvites();
432+ }}
433+ >
434+ { this.state.status }
435+ </Button>
436+ );
437+
438+ inputs = (<>
439+ { lessButton }
440+ { moreButton }
441+ { targetInputs }
442+ { submitButton }
443+ </>);
444+ }
445+
166446 return (
167447 <Row>
168448 <Col>
169449
170450 <p>{ 'send invites here, for planets' }</p>
171451
172− <p>{ inviteStatus }</p>
452+ <p>{ invitesAvailable }</p>
173453
174− <ul>{ inviteList }</ul>
454+ <ul>{ invitesSent }</ul>
175455
176− <Input
177− value={ this.state.targetEmail }
178− onChange={ this.handleEmailInput }>
179− <InnerLabel>
180− { 'Recipient email' }
181− </InnerLabel>
182− </Input>
456+ { error }
457+
458+ { fundingMessage }
183459
184− <StatelessTransaction
185− // Upper scope
186− web3={this.props.web3}
187− contracts={this.props.contracts}
188− wallet={this.props.wallet}
189− walletType={this.props.walletType}
190− walletHdPath={this.props.walletHdPath}
191− networkType={this.props.networkType}
192− setTxnConfirmations={this.txnConfirmation}
193− onSent={this.props.setTxnHashCursor}
194− popRoute={this.props.popRoute}
195− pushRoute={this.props.pushRoute}
196− // Other
197− canGenerate={this.canGenerate()}
198− createUnsignedTxn={this.createUnsignedTxn}
199− ref={this.statelessRef} />
460+ { inputs }
200461
201462 </Col>
202463 </Row>
src/bridge/views/IssueChild.js+11−40
@@ -2,23 +2,21 @@ import { Just, Nothing } from 'folktale/maybe'
22import React from 'react'
33import { azimuth, ecliptic } from 'azimuth-js'
44import * as ob from 'urbit-ob'
5+import * as need from '../lib/need'
56
6−import { Row, Col, H1, P, Anchor } from '../components/Base'
7−import { Button, ShowBlockie, ValidatedSigil } from '../components/Base'
8−import { PointInput, AddressInput, InnerLabel } from '../components/Base'
7+import {
8+ Row, Col, H1, P, Anchor,
9+ ShowBlockie, ValidatedSigil,
10+ PointInput, AddressInput, InnerLabel
11+} from '../components/Base'
912
1013import StatelessTransaction from '../components/StatelessTransaction'
1114
1215import { NETWORK_NAMES } from '../lib/network'
13−import { BRIDGE_ERROR } from '../lib/error'
1416import { getSpawnCandidate } from '../lib/child'
1517import { canDecodePatp } from '../lib/txn'
1618
17−import {
18− ETH_ZERO_ADDR,
19− isValidAddress,
20− eqAddr
21−} from '../lib/wallet'
19+import { isValidAddress } from '../lib/wallet'
2220
2321const setFind = (set, pred) => {
2422 for (const e of set) {
@@ -33,12 +31,7 @@ class IssueChild extends React.Component {
3331 constructor(props) {
3432 super(props)
3533
36− const issuingPoint = props.pointCursor.matchWith({
37− Just: (pt) => parseInt(pt.value, 10),
38− Nothing: () => {
39− throw BRIDGE_ERROR.MISSING_POINT
40− }
41− })
34+ const issuingPoint = parseInt(need.pointCursor(props), 10);
4235
4336 const getCandidate = () => ob.patp(getSpawnCandidate(issuingPoint))
4437
@@ -71,17 +64,9 @@ class IssueChild extends React.Component {
7164 }
7265
7366 componentWillMount() {
74−
75− const { contracts } = this.props
7667 const { issuingPoint } = this.state
7768
78−
79− const validContracts = contracts.matchWith({
80− Just: cs => cs.value,
81− Nothing: _ => {
82− throw BRIDGE_ERROR.MISSING_CONTRACTS
83− }
84− })
69+ const validContracts = need.contracts(this.props);
8570
8671 azimuth.getUnspawnedChildren(validContracts, issuingPoint).then(ps => this.setState({ validChildren: new Set(ps.map(ob.patp)) }))
8772 }
@@ -112,12 +97,7 @@ class IssueChild extends React.Component {
11297 if (state.isAvailable === false) return Nothing()
11398 if (canDecodePatp(state.desiredPoint) === false) return Nothing()
11499
115− const validContracts = props.contracts.matchWith({
116− Just: (cs) => cs.value,
117− Nothing: () => {
118− throw BRIDGE_ERROR.MISSING_CONTRACTS
119− }
120− })
100+ const validContracts = need.contracts(props);
121101
122102 const pointDec = ob.patp2dec(state.desiredPoint)
123103
@@ -260,16 +240,7 @@ class IssueChild extends React.Component {
260240
261241 <StatelessTransaction
262242 // Upper scope
263− web3={props.web3}
264− contracts={props.contracts}
265− wallet={props.wallet}
266− walletType={props.walletType}
267− walletHdPath={props.walletHdPath}
268− networkType={props.networkType}
269− onSent={props.setTxnHashCursor}
270− setTxnConfirmations={props.setTxnConfirmations}
271− popRoute={props.popRoute}
272− pushRoute={props.pushRoute}
243+ {...props}
273244 // Other
274245 canGenerate={canGenerate}
275246 createUnsignedTxn={this.createUnsignedTxn}
src/bridge/views/Point.js+9−26
@@ -3,12 +3,12 @@ import React from 'react'
33import { pour } from 'sigil-js'
44import * as ob from 'urbit-ob'
55import * as azimuth from 'azimuth-js'
6+import * as need from '../lib/need'
67
78import PointList from '../components/PointList'
89import ReactSVGComponents from '../components/ReactSVGComponents'
910import KeysAndMetadata from './Point/KeysAndMetadata'
1011import Actions from './Point/Actions'
11−import { BRIDGE_ERROR } from '../lib/error'
1212import { Row, Col, H1, H3 } from '../components/Base'
1313
1414
@@ -18,7 +18,8 @@ class Point extends React.Component {
1818 super(props)
1919
2020 this.state = {
21− spawned: []
21+ spawned: [],
22+ invites: Nothing()
2223 }
2324 }
2425
@@ -50,9 +51,8 @@ class Point extends React.Component {
5051 pointCursor.chain(point => {
5152 azimuth.azimuth.getPoint(ctrcs, point)
5253 .then(details => addToPointCache({ [point]: details }));
53− const prefix = azimuth.azimuth.getPrefix(point);
54− azimuth.azimuth.getPoint(ctrcs, prefix)
55− .then(details => addToPointCache({ [prefix]: details }));
54+ azimuth.delegatedSending.getTotalUsableInvites(ctrcs, point)
55+ .then(count => this.setState({ invites: Just(count) }));
5656 this.updateSpawned(ctrcs, point);
5757 })));
5858 }
@@ -73,35 +73,19 @@ class Point extends React.Component {
7373 render() {
7474
7575 const {
76− web3, popRoute, pushRoute, wallet, contracts,
77− setPointCursor, pointCursor, pointCache
76+ web3, popRoute, pushRoute, wallet,
77+ setPointCursor, pointCache
7878 } = this.props;
7979
8080 const { spawned } = this.state
8181
82−
83− const point = pointCursor.matchWith({
84− Just: (cursor) => cursor.value,
85− Nothing: () => {
86− throw BRIDGE_ERROR.MISSING_POINT
87− }
88− })
82+ const point = need.pointCursor(this.props);
8983
9084 const pointDetails =
9185 point in pointCache
9286 ? Just(pointCache[point])
9387 : Nothing()
9488
95− const prefix = azimuth.azimuth.getPrefix(point);
96− const prefixDetails =
97− prefix in pointCache
98− ? Just(pointCache[prefix])
99− : Nothing();
100− const delegatedSending = contracts.matchWith({
101− Nothing: () => '0x',
102− Just: (c) => c.value.delegatedSending.address
103− })
104−
10589 const name = ob.patp(point)
10690
10791 const sigil = pour({
@@ -147,10 +131,9 @@ class Point extends React.Component {
147131 pushRoute={ pushRoute }
148132 online={ online }
149133 wallet={ wallet }
150− delegatedSending={ delegatedSending }
151134 point={ point }
152135 pointDetails={ pointDetails }
153− prefixDetails={ prefixDetails } />
136+ invites={ this.state.invites } />
154137 : null
155138 }
156139
src/bridge/views/Point/Actions.js+19−19
@@ -1,11 +1,10 @@
11import React from 'react'
2+import * as need from '../../lib/need'
23import {
34 ETH_ZERO_ADDR,
45 CURVE_ZERO_ADDR,
5− addressFromSecp256k1Public,
66 eqAddr
77} from '../../lib/wallet'
8−import { BRIDGE_ERROR } from '../../lib/error'
98import { Row, Col, H2, P } from '../../components/Base'
109import { Button } from '../../components/Base'
1110import { ROUTE_NAMES } from '../../lib/router'
@@ -18,16 +17,11 @@ const isStar = point =>
1817
1918const Actions = (props) => {
2019 const {
21− pushRoute, online, wallet, delegatedSending,
22− point, pointDetails, prefixDetails
20+ pushRoute, online,
21+ point, pointDetails, invites
2322 } = props
2423
25− const addr = wallet.matchWith({
26− Just: (wal) => addressFromSecp256k1Public(wal.value.publicKey),
27− Nothing: () => {
28− throw BRIDGE_ERROR.MISSING_WALLET
29− }
30− })
24+ const addr = need.address(props);
3125
3226 const isOwner = pointDetails.matchWith({
3327 Nothing: _ => false,
@@ -64,10 +58,10 @@ const Actions = (props) => {
6458 }
6559 })
6660
67− const prefixHasDelegatedSending =
68− prefixDetails.matchWith({
61+ const hasInvites =
62+ invites.matchWith({
6963 Nothing: () => false,
70− Just: (details) => eqAddr(details.value.spawnProxy, delegatedSending)
64+ Just: (count) => (count.value > 0)
7165 });
7266
7367 const planet = isPlanet(point)
@@ -119,23 +113,29 @@ const Actions = (props) => {
119113 }
120114 })
121115
122− let invites = null;
116+ let inviteAction = null;
123117 if (planet) {
124− invites = (
118+ inviteAction = (
125119 <Button
126− disabled={(!isActiveOwner || !online || !prefixHasDelegatedSending)}
120+ disabled={(!isActiveOwner || !online || !hasInvites)}
127121 prop-size={'sm'}
128122 prop-type={'link'}
129123 onClick={ () => {
130124 pushRoute(ROUTE_NAMES.INVITES_SEND);
131125 }}
132126 >
133− { 'Send invites' }
127+ { 'Send invites (' }
128+ { invites.matchWith({
129+ Nothing: () => '?',
130+ Just: (count) => count.value
131+ })
132+ }
133+ { ')' }
134134 </Button>
135135 );
136136 }
137137 if (star) {
138− invites = (
138+ inviteAction = (
139139 <Button
140140 disabled={(!(isActiveOwner && canIssueChild) || !online)}
141141 prop-size={'sm'}
@@ -254,7 +254,7 @@ const Actions = (props) => {
254254 </Col>
255255 <Col className={'flex flex-column items-start col-md-4'}>
256256
257− { invites }
257+ { inviteAction }
258258
259259 </Col>
260260 </Row>
src/bridge/views/Points.js+1−3
@@ -8,7 +8,6 @@ import { NETWORK_NAMES } from '../lib/network'
88import { ROUTE_NAMES } from '../lib/router'
99import {
1010 ETH_ZERO_ADDR,
11− addressFromSecp256k1Public,
1211 eqAddr
1312 } from '../lib/wallet'
1413
@@ -42,8 +41,7 @@ class Points extends React.Component {
4241 web3.chain(_ =>
4342 contracts.chain(ctrcs =>
4443 wallet.chain(async wal => {
45− const pub = wal.publicKey
46− const addr = addressFromSecp256k1Public(pub)
44+ const addr = wal.address
4745
4846 const points =
4947 await azimuth.azimuth.getOwnedPoints(ctrcs, addr)
src/bridge/views/PrivateKey.js+1−3
@@ -7,8 +7,6 @@ import { Row, Col, H1 } from '../components/Base'
77import { ROUTE_NAMES } from '../lib/router'
88import { EthereumWallet } from '../lib/wallet'
99
10−const secre = /[0-9A-Fa-f]{64}/g
11−
1210class PrivateKey extends React.Component {
1311
1412 constructor(props) {
@@ -28,7 +26,7 @@ class PrivateKey extends React.Component {
2826
2927 constructWallet(privateKey) {
3028 const { setWallet } = this.props
31− if (secre.test(privateKey) === true) {
29+ if (/^[0-9A-Fa-f]{64}$/g.test(privateKey) === true) {
3230 const sec = Buffer.from(privateKey, 'hex')
3331 const wallet = new EthereumWallet(sec)
3432 setWallet(Maybe.Just(wallet))
src/bridge/views/SentTransaction.js+3−5
@@ -1,5 +1,6 @@
11import Maybe from 'folktale/maybe'
22import React from 'react'
3+import * as need from '../lib/need'
34import { Row, Col, H1, H3, P, Warning, Anchor } from '../components/Base'
45import { Button } from '../components/Base'
56
@@ -122,15 +123,12 @@ const Failure = (props) =>
122123 </Row>
123124
124125const SentTransaction = (props) => {
125− const { web3, txnHashCursor, networkType, popRoute, pushRoute, txnConfirmations} = props
126+ const { txnHashCursor, networkType, popRoute, pushRoute, txnConfirmations} = props
126127 const { setPointCursor, pointCursor } = props
127128
128129 const promptKeyfile = props.routeData && props.routeData.promptKeyfile
129130
130− const w3 = web3.matchWith({
131− Nothing: _ => { throw BRIDGE_ERROR.MISSING_WEB3 },
132− Just: res => res.value
133− })
131+ const w3 = need.web3(props);
134132
135133 const result = txnHashCursor.matchWith({
136134 Nothing: _ => { throw BRIDGE_ERROR.MISSING_TXN },
src/bridge/views/SetKeys.js+9−44
@@ -2,29 +2,21 @@ import React from 'react'
22import Maybe from 'folktale/maybe'
33import * as azimuth from 'azimuth-js'
44import * as ob from 'urbit-ob'
5+import * as need from '../lib/need'
56import { Row, Col, H1, P, Warning, CheckboxButton } from '../components/Base'
67
78import StatelessTransaction from '../components/StatelessTransaction'
8−import { BRIDGE_ERROR } from '../lib/error'
99import { attemptSeedDerivation } from '../lib/keys'
1010
1111import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
1212
13−import {
14− addressFromSecp256k1Public,
15− addHexPrefix
16−} from '../lib/wallet'
13+import { addHexPrefix } from '../lib/wallet'
1714
1815class SetKeys extends React.Component {
1916 constructor(props) {
2017 super(props)
2118
22− const point = props.pointCursor.matchWith({
23− Just: (shp) => shp.value,
24− Nothing: () => {
25− throw BRIDGE_ERROR.MISSING_POINT
26− }
27− })
19+ const point = need.pointCursor(props);
2820
2921 this.state = {
3022 auth: '',
@@ -51,12 +43,7 @@ class SetKeys extends React.Component {
5143
5244 this.deriveSeed()
5345
54− const addr = props.wallet.matchWith({
55− Just: wal => addressFromSecp256k1Public(wal.value.publicKey),
56− Nothing: () => {
57− throw BRIDGE_ERROR.MISSING_WALLET
58− }
59− })
46+ const addr = need.address(props);
6047
6148 this.determineManagementSeed(props.contracts.value, addr)
6249 }
@@ -101,19 +88,9 @@ class SetKeys extends React.Component {
10188 createUnsignedTxn() {
10289 const { state, props } = this
10390
104− const validContracts = props.contracts.matchWith({
105− Just: (cs) => cs.value,
106− Nothing: () => {
107− throw BRIDGE_ERROR.MISSING_CONTRACTS
108− }
109− })
91+ const validContracts = need.contracts(props);
11092
111− const validPoint = props.pointCursor.matchWith({
112− Just: (shp) => shp.value,
113− Nothing: () => {
114− throw BRIDGE_ERROR.MISSING_POINT
115− }
116− })
93+ const validPoint = need.pointCursor(props);
11794
11895 const hexRegExp = /[0-9A-Fa-f]{64}/g
11996
@@ -146,10 +123,7 @@ class SetKeys extends React.Component {
146123 state.networkSeed.length === 64
147124 && state.networkSeed.length === 64
148125
149− const pointDetails =
150− state.point in props.pointCache
151− ? props.pointCache[state.point]
152− : (() => { throw BRIDGE_ERROR.MISSING_POINT })()
126+ const pointDetails = need.fromPointCache(props, state.point);
153127
154128 return (
155129 <Row>
@@ -175,7 +149,7 @@ class SetKeys extends React.Component {
175149 </Warning>
176150 }
177151
178− { pointDetails.keyRevisionNumber === '0'
152+ { pointDetails.keyRevisionNumber === 0
179153 ? <Warning>
180154 <h3 className={'mb-2'}>{'Warning'}</h3>
181155 {
@@ -196,16 +170,7 @@ class SetKeys extends React.Component {
196170
197171 <StatelessTransaction
198172 // Upper scope
199− web3={props.web3}
200− contracts={props.contracts}
201− wallet={props.wallet}
202− walletType={props.walletType}
203− walletHdPath={props.walletHdPath}
204− networkType={props.networkType}
205− onSent={props.setTxnHashCursor}
206− setTxnConfirmations={props.setTxnConfirmations}
207− popRoute={props.popRoute}
208− pushRoute={props.pushRoute}
173+ {...props}
209174 // Tx
210175 canGenerate={ canGenerate }
211176 createUnsignedTxn={this.createUnsignedTxn}
src/bridge/views/SetProxy.js+5−29
@@ -1,12 +1,12 @@
11import Maybe from 'folktale/maybe'
22import React from 'react'
3+import * as need from '../lib/need'
34import * as azimuth from 'azimuth-js'
45import { Row, Col, H1, P, InnerLabel, ShowBlockie, Anchor, HorizontalSelector } from '../components/Base'
56import { AddressInput } from '../components/Base'
67import * as ob from 'urbit-ob'
78
89import { PROXY_TYPE, renderProxyType } from '../lib/proxy'
9−import { BRIDGE_ERROR } from '../lib/error'
1010
1111import { NETWORK_NAMES } from '../lib/network'
1212
@@ -36,12 +36,7 @@ class SetProxy extends React.Component {
3636 constructor(props) {
3737 super(props)
3838
39− const issuingPoint = props.pointCursor.matchWith({
40− Just: (shp) => shp.value,
41− Nothing: () => {
42− throw BRIDGE_ERROR.MISSING_POINT
43− }
44− })
39+ const issuingPoint = need.pointCursor(props);
4540
4641 this.state = {
4742 proxyAddress: '',
@@ -72,19 +67,9 @@ class SetProxy extends React.Component {
7267 createUnsignedTxn(proxyAddress) {
7368 const { state, props } = this
7469
75− const validContracts = props.contracts.matchWith({
76− Just: (cs) => cs.value,
77− Nothing: () => {
78− throw BRIDGE_ERROR.MISSING_CONTRACTS
79− }
80− })
70+ const validContracts = need.contracts(props);
8171
82− const validPoint = props.pointCursor.matchWith({
83− Just: (shp) => shp.value,
84− Nothing: () => {
85− throw BRIDGE_ERROR.MISSING_POINT
86− }
87− })
72+ const validPoint = need.pointCursor(props);
8873
8974 const txArgs = [
9075 validContracts,
@@ -184,16 +169,7 @@ class SetProxy extends React.Component {
184169
185170 <StatelessTransaction
186171 // Upper scope
187− web3={props.web3}
188− contracts={props.contracts}
189− wallet={props.wallet}
190− walletType={props.walletType}
191− walletHdPath={props.walletHdPath}
192− networkType={props.networkType}
193− onSent={props.setTxnHashCursor}
194− setTxnConfirmations={props.setTxnConfirmations}
195− popRoute={props.popRoute}
196− pushRoute={props.pushRoute}
172+ {...props}
197173 // Other
198174 ref={this.statelessRef}
199175 createUnsignedTxn={this.createUnsignedTxn}
src/bridge/views/Transfer.js+5−29
@@ -2,12 +2,12 @@ import Maybe from 'folktale/maybe'
22import React from 'react'
33import * as azimuth from 'azimuth-js'
44import * as ob from 'urbit-ob'
5+import * as need from '../lib/need'
56
67import { Row, Col, H1, InnerLabel, ShowBlockie, P, Anchor } from '../components/Base'
78import { AddressInput } from '../components/Base'
89import StatelessTransaction from '../components/StatelessTransaction'
910
10−import { BRIDGE_ERROR } from '../lib/error'
1111import { NETWORK_NAMES } from '../lib/network'
1212
1313import {
@@ -18,12 +18,7 @@ class Transfer extends React.Component {
1818 constructor(props) {
1919 super(props)
2020
21− const issuingPoint = props.pointCursor.matchWith({
22− Just: (pt) => pt.value,
23− Nothing: () => {
24− throw BRIDGE_ERROR.MISSING_POINT
25− }
26− })
21+ const issuingPoint = need.pointCursor(props);
2722
2823 this.state = {
2924 receivingAddress: '',
@@ -49,19 +44,9 @@ class Transfer extends React.Component {
4944 createUnsignedTxn() {
5045 const { state, props } = this
5146
52− const validContracts = props.contracts.matchWith({
53− Just: (cs) => cs.value,
54− Nothing: () => {
55− throw BRIDGE_ERROR.MISSING_CONTRACTS
56− }
57− })
47+ const validContracts = need.contracts(props);
5848
59− const validPoint = props.pointCursor.matchWith({
60− Just: (shp) => shp.value,
61− Nothing: () => {
62− throw BRIDGE_ERROR.MISSING_POINTs
63− }
64− })
49+ const validPoint = need.pointCursor(props);
6550
6651 const txn = azimuth.ecliptic.setTransferProxy(
6752 validContracts,
@@ -125,16 +110,7 @@ class Transfer extends React.Component {
125110
126111 <StatelessTransaction
127112 // Upper scope
128− web3={props.web3}
129− contracts={props.contracts}
130− wallet={props.wallet}
131− walletType={props.walletType}
132− walletHdPath={props.walletHdPath}
133− networkType={props.networkType}
134− onSent={props.setTxnHashCursor}
135− setTxnConfirmations={props.setTxnConfirmations}
136− popRoute={props.popRoute}
137− pushRoute={props.pushRoute}
113+ {...props}
138114 // Checks
139115 userApproval={state.userApproval}
140116 canGenerate={ canGenerate }
src/walletgen/lib/lib.js+7−2
@@ -53,13 +53,17 @@ const generateWallet = async (point, ticket, boot) => {
5353
5454 const wallet = await kg.generateWallet(config);
5555
56− // This is here to notify the anyone who opens console because the thread
57− // hangs, blocking UI updates so this cannot be doen in the UI
56+ // This is here to notify anyone who opens console because the thread
57+ // hangs, blocking UI updates so this cannot be done in the UI
5858 console.log('Generating Wallet for point address: ', point);
5959
6060 return wallet;
6161}
6262
63+// returns a promise
64+const generateOwnershipWallet = (ship, ticket) =>
65+ kg.generateOwnershipWallet({ ship, ticket });
66+
6367const seq = num => Array.from(Array(num), (_, i) => i)
6468
6569const compose = (...fs) => fs.reduceRight((pF, nF) => (...args) => nF(pF(...args)), v => v)
@@ -67,6 +71,7 @@ const compose = (...fs) => fs.reduceRight((pF, nF) => (...args) => nF(pF(...args
6771export {
6872 makeTicket,
6973 generateWallet,
74+ generateOwnershipWallet,
7075 compose,
7176 seq,
7277}