277 Commits

Author SHA1 Message Date
Kautuk Kundan
b40d29c82d Merge pull request #400 from web3well/bls-wallet-clients-0.8.0
Pin bls-wallet-clients@0.8.0
2022-11-16 17:14:32 +05:30
Jacob Caban-Tomski
5c6d96f7e5 Pin bls-wallet-clients@0.8.0 2022-11-15 17:48:50 -07:00
Jacob Caban-Tomski
ccc8a59f2f Merge pull request #367 from web3well/contract-updates
Contract Updates & Client Integration
2022-11-15 12:10:10 -07:00
Jacob Caban-Tomski
57e7ec3d24 Merge pull request #397 from web3well/update-client-pins-again
Fix encodedFunction type and update client pins
2022-11-14 10:49:50 -07:00
Andrew Morris
0a00d8606f Update bls-wallet-clients usage to 940dd11 2022-11-14 17:56:47 +11:00
Andrew Morris
940dd11de9 Fix type of ActionData.encodedFunction 2022-11-14 17:46:16 +11:00
Jacob Caban-Tomski
02298b8029 Merge pull request #393 from web3well/update-client-pins
Update clients package (experimental) and pinned versions
2022-11-13 20:04:04 -07:00
Andrew Morris
08c059acb4 Update client pins 2022-11-11 12:33:33 +11:00
Andrew Morris
54e752f503 Deploy to arbitrum goerli 2022-11-11 12:19:53 +11:00
Jacob Caban-Tomski
bc3d1463f1 Merge pull request #391 from web3well/test-prohibit-renounceOwnership
Test prohibit renounce ownership
2022-11-10 15:16:33 -07:00
James Zaki
bc37324932 better handle no admin params 2022-11-10 17:33:47 +00:00
Andrew Morris
a4a0e951a8 Remove redundant disables (#390) 2022-11-10 09:27:55 +00:00
Andrew Morris
f3da0977b8 Use ActionError instead of formatted string, parse Error, Panic in js-land, fallback to reporting bytes 2022-11-10 17:36:28 +11:00
Andrew Morris
b8cefff40e Check selectorId before address (since address is not there for renounceOwnership) 2022-11-10 16:09:46 +11:00
Andrew Morris
59e1a65242 Merge pull request #368 from web3well/bw-160-update-deps
Update dependencies
2022-11-10 12:33:08 +11:00
Blake Duncan
771de1fbab Return txhash from aggregator and display in Quill (#369)
* Add bundle hash and txhash to receipt request

* Save txhash on transaction in local storage

* Fix case issue with filtering in the tx table
2022-11-03 13:37:02 +00:00
Andrew Morris
2b5ad82d5c Don't run aggregator tests for contract-updates 2022-11-02 11:00:26 +11:00
Andrew Morris
c508211f18 Fix eslint issue 2022-11-01 16:42:45 +11:00
Andrew Morris
6e00912981 Fix types 2022-11-01 16:39:05 +11:00
Andrew Morris
d1adf2572a Fix test 2022-11-01 16:13:19 +11:00
Andrew Morris
4888426a4e typechain-types migration 2022-11-01 16:02:02 +11:00
Andrew Morris
cd103faf84 Update deps in clients 2022-11-01 15:52:17 +11:00
Andrew Morris
20fc590cd7 Disable camelcase rule 2022-11-01 15:42:57 +11:00
Andrew Morris
99dbaadda9 Raw update dependencies 2022-11-01 15:42:40 +11:00
Jacob Caban-Tomski
02ffad7f85 Add test cases for new require statements. One still not working. 2022-10-27 17:40:46 -05:00
Jacob Caban-Tomski
84dd0a09d8 Throw exception in client if private key has been recovered from 2022-10-27 16:40:21 -05:00
Jacob Caban-Tomski
e4ab6b53db Move send eth script to hardhat task 2022-10-27 16:24:51 -05:00
Jacob Caban-Tomski
097174b243 Merge branch 'main' of github.com:web3well/bls-wallet into contract-updates 2022-10-27 15:47:54 -05:00
Jacob Caban-Tomski
93c7935dc6 Merge pull request #350 from web3well/contract-audit
Audit fixes 1-4 and BLSWalletWrapper changes
2022-10-27 10:54:26 -05:00
Jacob Caban-Tomski
b06435e3d7 Merge pull request #358 from web3well/docs/add-deployments
Add links to contract deployments to README.
2022-10-26 22:20:46 -05:00
Jacob Caban-Tomski
bb8bd2458e Add links to contract deployments to README.
Co-authored-by: outsider-analytics <outsideranalytics@gmail.com>
2022-10-26 22:19:00 -05:00
Jacob Caban-Tomski
6379006133 Add getOperationResults to cleints
Add getOperationResults to bls-wallet-clients to allow consumers to more easily get at operation errors.
Change existing test case to use getOperationResults.
Add message to require used to prevent ownership changes to proxy admin.
2022-10-25 23:17:06 -05:00
Jacob Caban-Tomski
4bc4701faf Add replay attack test after recovery 2022-10-24 17:23:46 -05:00
Jacob Caban-Tomski
5b7231b980 Merge branch 'main' of github.com:web3well/bls-wallet into contract-audit 2022-10-24 16:09:00 -05:00
Jacob Caban-Tomski
3cfabaa4b1 Merge pull request #353 from web3well/update-input-decode
updated inputDecode to parse params
2022-10-24 15:04:04 -05:00
Jacob Caban-Tomski
b8ae335449 Merge pull request #356 from web3well/existing-seed
Import pre-existing seed phrase during Onboarding
2022-10-24 15:01:23 -05:00
kautukkundan
221b7b94e0 removed stray import and reduced line length 2022-10-23 23:30:33 +05:30
kautukkundan
8f17aa983d added validation to mnemonic 2022-10-23 23:26:32 +05:30
Jacob Caban-Tomski
6a3df55a38 Merge pull request #357 from web3well/docs/agg-env-vars
Add documentation for aggregator env vars
2022-10-20 11:38:28 -05:00
Jacob Caban-Tomski
200718814d Add documentation for aggregator env vars 2022-10-20 11:25:14 -05:00
kautukkundan
0904d8169d lint: fix warnings 2022-10-16 21:05:42 +05:30
kautukkundan
d476a53d0d add existing seed phrase during wallet generation 2022-10-16 21:01:50 +05:30
kautukkundan
5b771c0e8c lint: fixed warnings 2022-10-16 18:41:33 +05:30
kautukkundan
2b16c7367d updated inputDecode to parse params 2022-10-16 18:20:52 +05:30
Jacob Caban-Tomski
0c956188a3 Merge pull request #352 from web3well/update-docs
Update docs
2022-10-07 17:57:28 -05:00
JohnGuilding
6ec971ecac Add ref to scaffold-eth example dapp 2022-10-07 17:55:38 -05:00
JohnGuilding
80e56c1bb5 Add margin to diagram 2022-10-07 17:38:05 -05:00
JohnGuilding
05660dd468 Add proxy aggregator diagrams 2022-10-07 15:50:32 -05:00
JohnGuilding
aa0ba1ad47 Remove "run" section from remote dev doc 2022-10-07 15:47:37 -05:00
JohnGuilding
feaf497c22 Correctly instantiate Web3Provider 2022-10-07 15:46:33 -05:00
JohnGuilding
06a3dd025a Explicit terminal instructions & define HD wallet 2022-10-07 15:46:02 -05:00
JohnGuilding
cac08ec5a6 Update rinkarby instructions to goerli 2022-10-07 15:41:49 -05:00
JohnGuilding
7c93ccea89 Update extension config reference 2022-10-07 15:40:40 -05:00
Jacob Caban-Tomski
647c1bbf4d Fix import string & contract lint 2022-10-05 17:28:41 -06:00
Jacob Caban-Tomski
c2bf4bbc16 Bump bls-wallet-clients version to experimental v0.8.0
Update aggregator BundleService to work with new signature payload which includes wallet address.
2022-10-05 17:21:17 -06:00
Jacob Caban-Tomski
2d28b7b17a Bump bls-wallet-clients to v0.8.0 2022-10-05 17:14:23 -06:00
Jacob Caban-Tomski
92b9b136a7 Add wallet AddressFromPublicKey function 2022-10-05 17:01:38 -06:00
Jacob Caban-Tomski
c3e79a7c8e Remove unneeded arg, expand on any cast. 2022-10-05 16:02:55 -06:00
James Zaki
163af9d49c Remove gateway param to return compatible to server tests 2022-10-05 17:26:34 +01:00
James Zaki
f2a490aaf2 Minor fixes from review 2022-10-05 15:43:51 +01:00
Jacob Caban-Tomski
1481c69dc0 WIP Fix typechain import path 2022-10-04 16:37:08 -06:00
Jacob Caban-Tomski
17052cdaa1 WIP Add build test to client CI process 2022-10-04 16:30:54 -06:00
Jacob Caban-Tomski
def1eaf208 WIP Add back in main restriction for push workflow trigger 2022-10-04 16:23:34 -06:00
Jacob Caban-Tomski
5a33e43945 WIP Remove main branch requirement to run CI steps 2022-10-04 16:16:01 -06:00
Jacob Caban-Tomski
998ee6ba30 WIP Fix client tests, run agg tests on contract changes 2022-10-04 16:06:49 -06:00
James Zaki
f0a3b56a44 Sign wallet address in msg (04) tests 2022-10-04 22:14:40 +01:00
James Zaki
4147726d2a Sign wallet address in msg (04) - WIP 2022-10-04 22:14:40 +01:00
James Zaki
192db97b01 Restrict Owner functions from ProxyAdmin call (03) 2022-10-04 22:14:40 +01:00
James Zaki
e2ee017824 Increment nonce before operation - protect from relpay reentrancy (02) 2022-10-04 22:14:40 +01:00
James Zaki
a485d84364 Sign key hash with chainid and UserOp - protect from rogue key attack (01) 2022-10-04 22:14:40 +01:00
Kautuk Kundan
ae5e1adcc2 Merge pull request #349 from web3well/fix-extension
update config for extension
2022-10-04 01:00:22 +05:30
kautukkundan
1ee24813ed update config for extension 2022-10-03 01:18:13 +05:30
Andrew Morris
4dfa0bdd4a Merge pull request #344 from web3well/bw-330-improve-clients-docs
Improve clients docs
2022-09-29 14:01:27 +10:00
Andrew Morris
de5c288d07 Update docs/use_bls_wallet_clients.md
Co-authored-by: Jacob Caban-Tomski <jacque006@users.noreply.github.com>
2022-09-29 14:01:05 +10:00
Andrew Morris
db0cf3acc8 Merge pull request #282 from web3well/bw-251-currency-switching
Add currency switching and use currency conversions
2022-09-22 10:34:55 +10:00
Jacob Caban-Tomski
035f1872fe Remove unused import 2022-09-21 17:34:56 -06:00
Andrew Morris
60ed167b2d Make example code work 2022-09-21 14:55:33 +10:00
Andrew Morris
1860ecbd18 Remove unnecessary line 2022-09-21 13:59:09 +10:00
Andrew Morris
e830f49714 yarn install -> yarn add 2022-09-21 13:55:24 +10:00
Andrew Morris
8753978719 Just use USD instead of ip-api 2022-09-21 13:43:10 +10:00
Andrew Morris
fec0f12e45 Avoid $ prefix 2022-09-19 15:32:15 +10:00
Jacob Caban-Tomski
dcfd13d8b3 Merge pull request #336 from web3well/contract-operation-logs
Return results in Operation event for visibility of actions
2022-09-16 11:37:41 -06:00
kautukkundan
665ea530fc Update projects to bls-wallet-clients 0.7.3 2022-09-16 22:47:25 +05:30
kautukkundan
b547361d86 Merge branch 'main' of github.com:web3well/bls-wallet into contract-operation-logs 2022-09-16 22:14:57 +05:30
Andrew Morris
b513d9feab Merge remote-tracking branch 'origin/main' into bw-251-currency-switching 2022-09-15 14:13:13 +10:00
Andrew Morris
8d4257aac2 Merge pull request #335 from web3well/clients-0.7.0
Update to bls-wallet-clients 0.7.0
2022-09-15 14:12:29 +10:00
Andrew Morris
e4ad6d317c Update projects to bls-wallet-clients 0.7.1 2022-09-15 13:58:35 +10:00
Andrew Morris
080513ec12 0.7.1 2022-09-15 13:44:16 +10:00
Andrew Morris
3c9490b5a6 Merge commit '1303ffe' into bw-251-currency-switching 2022-09-15 13:33:03 +10:00
Andrew Morris
e7decded4c Merge commit '356b067' into bw-251-currency-switching 2022-09-15 13:32:14 +10:00
Andrew Morris
427d05ce82 Merge remote-tracking branch 'origin/main' into clients-0.7.0 2022-09-15 13:05:25 +10:00
James Zaki
a6e5d6d3d8 Emit action data with Operation event 2022-09-14 14:26:40 +01:00
James Zaki
bcbab0d08a Return index and error message of action in failed Operation 2022-09-14 13:59:18 +01:00
Andrew Morris
1303ffea9b Add network switching (#273)
* Update config system in prep for network switching

* Move builtinNetworks into config

* Move currencyConversionConfig into config

* Select network in ui

* mixtureHasChanged

* Fix issue where ethers Web3Provider assumes network doesn't change, handle addresses changing per network

* Implement per-network information for wallets

* lookupAddress -> pkHashToAddress

* Fix duplication of getting bls network config

* Restore preferred nonce sourcing

* Fix global access of blsNetworksConfig

* Fix global config access

* Fix commented hasChanged

* Fix build failures

* Fix linting issues

* Update extension/config.release.json

Co-authored-by: Jacob Caban-Tomski <jacque006@users.noreply.github.com>

* Update with PR feedback

Switch $preferences to non-$ name.
Add hidden field to networks to hide from end users.
Refactor wallet network data generation. Needs one more pass.

* PR fixes

Fix trailing comma in config json.
Properly inject env vars into config file.

* Move MultiNetowrkConfig to bls-wallet-clients

Add MultiNetworkConfig to clients. Deprecate NetworkConfig.
Update deps in clients.
Add chai-as-promised, ts-node to clients.
Remove need for transpiliation before client tests.
Finish getAndUpdateWalletsNetworkData changes in extension.

* Remove .only from client tests

* Use MultiNetworkConfig from clients lib.

* Fix file misspelling

* Update bls-wallet-clients experimental with main

* Remove empty local.json from CI build

* Update setup script with new extension config

Add troubleshooting section for Deno version

* Update extension & aggregator configs.

Update extension configs to hide all non-deployed networks.
Update aggregator local config to use pks 0 & 1 from main hardhat mnemonic.
Add dangerous command to print private keys from main hardhat mnemonic.

* Default extension network to arbitrum goerli

* Revert changes in aggregator local env

Co-authored-by: Jacob Caban-Tomski <jacque006@users.noreply.github.com>
2022-09-13 20:57:07 -06:00
Jacob Caban-Tomski
356b0674e7 Revert changes in aggregator local env 2022-09-13 20:46:47 -06:00
Jacob Caban-Tomski
90499370b5 Default extension network to arbitrum goerli 2022-09-13 20:15:03 -06:00
Jacob Caban-Tomski
0007a8491d Update extension & aggregator configs.
Update extension configs to hide all non-deployed networks.
Update aggregator local config to use pks 0 & 1 from main hardhat mnemonic.
Add dangerous command to print private keys from main hardhat mnemonic.
2022-09-13 20:01:12 -06:00
Jacob Caban-Tomski
eeb7f506ab Update setup script with new extension config
Add troubleshooting section for Deno version
2022-09-13 18:55:56 -06:00
Jacob Caban-Tomski
8a7187c041 Remove empty local.json from CI build 2022-09-13 18:25:45 -06:00
Jacob Caban-Tomski
68547ca88f Update bls-wallet-clients experimental with main 2022-09-13 18:21:03 -06:00
Jacob Caban-Tomski
576802a79a Merge branch 'main' of github.com:web3well/bls-wallet into bw-252-network-switching 2022-09-13 15:56:54 -06:00
James Zaki
ce8620c978 Return results in Operation event for visibility of failing actions 2022-09-13 22:37:07 +01:00
Andrew Morris
ce9e654815 Use clients 0.7.0 in aggregator 2022-09-13 11:33:27 +10:00
Andrew Morris
19bdc19e8d Use clients 0.7.0 in aggregator-proxy 2022-09-13 11:32:05 +10:00
Andrew Morris
b05525a1b8 Use clients 0.7.0 in extension 2022-09-13 11:30:58 +10:00
Andrew Morris
53e2f518d5 0.7.0 2022-09-13 11:26:23 +10:00
Andrew Morris
9d22092b2c Merge pull request #323 from web3well/default-nitro
updated default env to use arbitrum nitro goerli
2022-09-13 11:15:40 +10:00
Andrew Morris
3936b59e22 Gas adjustment (in test) for new contracts 2022-09-13 10:04:37 +10:00
Andrew Morris
a7d8209664 Use local config when testing aggregator 2022-09-13 09:39:18 +10:00
Andrew Morris
6ed993d951 Remove contracts workaround in aggregator.yml 2022-09-13 09:19:48 +10:00
Andrew Morris
23613b42b8 Merge pull request #333 from web3well/update-contracts
Update contracts
2022-09-13 09:06:44 +10:00
Andrew Morris
92195d1f3e Fix formatting 2022-09-12 16:40:08 +10:00
Andrew Morris
5b2168e262 Revert markdown formatting changes 2022-09-12 15:35:22 +10:00
Andrew Morris
8d5b0867fe Update arbitrum-goerli deployment 2022-09-12 15:02:30 +10:00
Andrew Morris
03ced09fc2 Use updated bls-wallet-clients (contracts compatibility) 2022-09-09 16:54:05 +10:00
Andrew Morris
b267fcd3f3 Merge remote-tracking branch 'origin/default-nitro' 2022-09-09 16:24:04 +10:00
kautukkundan
297fba186b updated network naming 2022-09-09 11:48:13 +05:30
kautukkundan
ebb90e2f9a resolved conflict: pvt_key_agg 2022-09-09 11:44:21 +05:30
Andrew Morris
6608add701 Remove no longer necessary branch switching in setup 2022-09-09 15:54:09 +10:00
Andrew Morris
6d07b754dc Use updated bls-wallet-clients 2022-09-09 15:53:02 +10:00
Andrew Morris
3ac37a0e43 Merge pull request #308 from web3well/new-deployment
New testnet deployments - Arb Rinkeby and Arb Goerli
2022-09-09 15:33:43 +10:00
kautukkundan
b1bac6fa79 updated network name based on chainlist defaults 2022-09-07 21:11:01 +05:30
Andrew Morris
87eb0b53fb Merge pull request #324 from web3well/qr-code
Added QR Code for receive button
2022-09-07 11:25:23 +10:00
kautukkundan
17b7293057 lint fix 2022-09-06 01:59:31 +05:30
kautukkundan
11414de511 Added QR Code for receive button 2022-09-06 01:55:45 +05:30
kautukkundan
6c6972ce5c updated docs 2022-09-06 01:11:20 +05:30
kautukkundan
a8b5e4c9d7 updated extension to use remote aggregator by default 2022-09-06 01:00:01 +05:30
kautukkundan
07c59cd0bd updated default env to use arbitrum nitro goerli 2022-09-06 00:47:27 +05:30
kautukkundan
3512982eff fix: typo for chain Id 2022-09-06 00:39:30 +05:30
Jacob Caban-Tomski
7a37dc89d0 Fix file misspelling 2022-09-03 14:10:14 -06:00
Jacob Caban-Tomski
17654c5042 Use MultiNetworkConfig from clients lib. 2022-09-03 13:59:48 -06:00
Jacob Caban-Tomski
2f62a6da8f Remove .only from client tests 2022-09-03 13:29:59 -06:00
Jacob Caban-Tomski
ee2a533c72 Move MultiNetowrkConfig to bls-wallet-clients
Add MultiNetworkConfig to clients. Deprecate NetworkConfig.
Update deps in clients.
Add chai-as-promised, ts-node to clients.
Remove need for transpiliation before client tests.
Finish getAndUpdateWalletsNetworkData changes in extension.
2022-09-03 13:24:56 -06:00
Andrew Morris
6f45f7942f Merge pull request #313 from web3well/bw-271-fix-aggregator-tests
Switch to supported contracts when testing aggregator
2022-09-02 10:13:25 +10:00
Jacob Caban-Tomski
df6daffcce PR fixes
Fix trailing comma in config json.
Properly inject env vars into config file.
2022-09-01 15:42:26 -06:00
Andrew Morris
287c1c64ee Add comments 2022-09-01 13:52:42 +10:00
Andrew Morris
c13e9800a4 Fetch the commit before checking it out 2022-09-01 13:47:48 +10:00
Andrew Morris
4548bf0a77 Trigger aggregator workflows for yml file 2022-09-01 13:38:57 +10:00
Andrew Morris
e64b637440 Switch to supported contracts when testing aggregator 2022-09-01 13:35:03 +10:00
John Guilding
fd6f60d39c Merge pull request #307 from web3well/update-PRIVATE_KEY_AGG-in–env.example
Update aggregator .env.example private key
2022-08-31 15:41:53 +01:00
kautukkundan
3a519cd6e4 fix: goerli explorer url 2022-08-25 22:51:57 +05:30
kautukkundan
56bdf8a1df created new goerli testnet deployment for git hash 45def92 2022-08-25 22:46:28 +05:30
kautukkundan
600d898fc0 created new testnet deployment for git hash 45def92 2022-08-19 15:07:33 +05:30
Jacob Caban-Tomski
8f5eff131a Merge branch 'main' of github.com:web3well/bls-wallet into bw-252-network-switching 2022-08-18 17:47:06 -06:00
Jacob Caban-Tomski
eebff6d205 Update with PR feedback
Switch $preferences to non-$ name.
Add hidden field to networks to hide from end users.
Refactor wallet network data generation. Needs one more pass.
2022-08-18 17:45:17 -06:00
JohnGuilding
bcf6aef290 Update private key 2022-08-18 13:16:03 +01:00
Jacob Caban-Tomski
ed7bbf5397 Merge pull request #304 from web3well/bw-295-add-note
Add checkout commands to contract deployment and explanation
2022-08-16 14:09:58 +09:00
Andrew Morris
0aacf46234 Add checkout commands to contract deployment and explanation 2022-08-16 14:34:47 +10:00
Andrew Morris
723740348f Merge pull request #296 from web3well/extension-webpack-ignore-node-modules
Ignore watching node_modules during extension dev
2022-08-16 12:04:52 +10:00
Andrew Morris
63d9470cad Merge pull request #298 from web3well/actions-modal
created clickable actions modal
2022-08-16 11:06:49 +10:00
Andrew Morris
c285e8dfaf Merge pull request #293 from web3well/transactions-list
Create Historical Transactions list
2022-08-16 11:00:10 +10:00
kautukkundan
cf46dbf3ff fixed no-unstable-nested-components issue 2022-08-11 22:24:01 +05:30
kautukkundan
427ce33267 change native button to use custom Button 2022-08-11 22:21:54 +05:30
kautukkundan
7a7cc0a9d4 created clickable actions modal 2022-08-11 22:21:54 +05:30
kautukkundan
6d143fcbc8 fixed no-unstable-nested-components issue 2022-08-11 22:20:35 +05:30
Andrew Morris
8c8e840dc6 Update extension/config.release.json
Co-authored-by: Jacob Caban-Tomski <jacque006@users.noreply.github.com>
2022-08-10 22:51:23 +10:00
kautukkundan
d30c692263 lint fix 2022-08-09 20:41:58 +05:30
Jacob Caban-Tomski
014871ee8d Merge pull request #292 from web3well/dapp-guide
Add guide for using BLS Wallet/Quill in a dApp
2022-08-09 19:46:55 +09:00
Jacob Caban-Tomski
20d46b4900 Add guide for using BLS Wallet in a dApp 2022-08-09 19:39:46 +09:00
Jacob Caban-Tomski
268ffe41f8 Ignore watching node_modules during extension dev. 2022-08-09 17:47:35 +09:00
Jacob Caban-Tomski
a0a2ee878b Merge pull request #285 from web3well/bw-284-fix-broken-links
Fix broken links
2022-08-09 17:42:03 +09:00
kautukkundan
3a9c6ce4cd completed tx table ui 2022-08-05 16:34:31 +09:00
kautukkundan
3106e1dd0a crated transactions table 2022-08-05 16:34:31 +09:00
Andrew Morris
8745c185b9 Merge pull request #291 from web3well/bug/extension-release
Switch extension upload to more relevant GH action
2022-08-05 15:00:13 +10:00
Andrew Morris
668362304b Merge branch 'bw-252-network-switching' into bw-251-currency-switching 2022-08-05 13:57:09 +09:00
Andrew Morris
e89a9151e6 Merge remote-tracking branch 'origin/main' into bw-252-network-switching 2022-08-05 13:53:13 +09:00
Jacob Caban-Tomski
c435fe0471 Update presentations list 2022-08-04 15:25:31 +09:00
Jacob Caban-Tomski
956607a479 Switch extension upload to more relevant GH action 2022-08-04 14:51:01 +09:00
Blake Duncan
04af3ae83a Encrypting necessary local storage data (#286)
* First attempt at encrypting the local storage

* Enable encrypted and decrypted storage

* Lint fixes and remove console log

* Update variable naming

* Creating new StorageConfig type

* Removing unused variable
2022-07-29 10:32:45 +01:00
Andrew Morris
27b0864c79 Fix broken links 2022-07-25 07:54:23 +10:00
Andrew Morris
76a7daa0bd Fix linting issues 2022-07-21 16:34:57 +10:00
Andrew Morris
b38e216585 Remove unused import 2022-07-21 15:18:06 +10:00
Andrew Morris
5da15cb16f Styling for eth symbol 2022-07-21 15:08:09 +10:00
Andrew Morris
44b89fd8f3 PreferredCurrencySymbol 2022-07-21 14:59:55 +10:00
Andrew Morris
b8122ced5c Use grid for values in confirm popup 2022-07-21 14:41:57 +10:00
Andrew Morris
7613237f96 Merge remote-tracking branch 'origin/bw-252-network-switching' into bw-251-currency-switching 2022-07-21 11:22:13 +10:00
Andrew Morris
368aa43085 Merge remote-tracking branch 'origin/main' into bw-252-network-switching 2022-07-21 11:20:50 +10:00
Andrew Morris
68b5446a6f Add currency conversion to confirm dialog 2022-07-21 11:17:03 +10:00
Andrew Morris
a0f858dbbb Convert currency in ConnectionSummary and AmountSelector 2022-07-21 10:45:59 +10:00
Andrew Morris
83e30afea1 Put fields into currency conversion, CurrencyDisplay 2022-07-21 10:28:25 +10:00
Andrew Morris
d9616333a4 Add alphabetical option 2022-07-21 08:34:44 +10:00
Jacob Caban-Tomski
0570216fb2 Merge pull request #275 from web3well/one-class-per-line
When wrapping className, use one class per line
2022-07-20 13:32:37 +02:00
Jacob Caban-Tomski
0515caacfc Merge pull request #280 from web3well/bw-279-support-node-18
Support node 18
2022-07-20 13:25:10 +02:00
Andrew Morris
767eafe338 Add TODO 2022-07-20 18:09:36 +10:00
Andrew Morris
d76fb1c227 Add currency selection in general settings 2022-07-20 18:07:03 +10:00
Andrew Morris
ec2ae3eb2d Get default currency from ip api, remove preference identities 2022-07-20 17:40:28 +10:00
Andrew Morris
964b4c7098 Merge branch 'bw-279-support-node-18' into bw-251-currency-switching 2022-07-20 16:56:19 +10:00
Andrew Morris
4259ee5bce Fix linting issues 2022-07-20 08:48:24 +10:00
Andrew Morris
06a1b7318e Specify latest LTS instead of 16 2022-07-20 08:40:27 +10:00
Andrew Morris
3c9cbe6c3e Support node 18 2022-07-20 08:40:17 +10:00
Andrew Morris
6b86b95f3c Merge pull request #276 from web3well/specify-node-version
Specify node version
2022-07-20 08:06:53 +10:00
Blake Duncan
911ffa4865 Update git ignore for webstorm 2022-07-19 10:50:28 +01:00
Blake Duncan
c5f460969f Adding .nvmrc to specify node version 2022-07-19 10:47:50 +01:00
Andrew Morris
a2fb8f7039 When wrapping className, use one class per line 2022-07-19 14:20:55 +10:00
Andrew Morris
87696c3251 Fix build failures 2022-07-19 13:00:06 +10:00
Andrew Morris
a4dbeb00e9 Fix commented hasChanged 2022-07-19 12:05:09 +10:00
Andrew Morris
2a24a15460 Fix global config access 2022-07-19 11:12:56 +10:00
Andrew Morris
9040e90457 Fix global access of blsNetworksConfig 2022-07-19 11:03:56 +10:00
Andrew Morris
3d483d6b76 Restore preferred nonce sourcing 2022-07-19 10:55:28 +10:00
Andrew Morris
7d6aa698ec Fix duplication of getting bls network config 2022-07-19 10:53:44 +10:00
Andrew Morris
7d240daaa5 lookupAddress -> pkHashToAddress 2022-07-19 10:45:12 +10:00
Andrew Morris
5f866991dc Implement per-network information for wallets 2022-07-19 10:13:01 +10:00
Jacob Caban-Tomski
0959db5c4c Merge pull request #269 from web3well/local-testing-fixes
Local testing fixes
2022-07-18 19:24:06 +02:00
Andrew Morris
c6c37ffc47 Merge pull request #267 from web3well/linting-overhaul
Linting overhaul
2022-07-18 09:06:28 +10:00
Kautuk Kundan
1a80eb4a4a Merge pull request #266 from web3well/fix-carousel
Specify deps for carousel correctly
2022-07-17 23:55:13 +05:30
Kautuk Kundan
a7c5de25aa Merge pull request #265 from web3well/bw-145-fix-react-table-typing
Fix react table typing
2022-07-17 23:53:21 +05:30
Kautuk Kundan
a2bd4165b3 Merge pull request #264 from web3well/use-quill-in-confirm
Use QuillContext in Confirm dialog
2022-07-17 23:47:10 +05:30
Andrew Morris
d4bf0b38c4 Fix issue where ethers Web3Provider assumes network doesn't change, handle addresses changing per network 2022-07-15 17:54:09 +10:00
Andrew Morris
64b312e646 mixtureHasChanged 2022-07-15 15:50:04 +10:00
Andrew Morris
1abdcd46df Select network in ui 2022-07-15 15:20:33 +10:00
Andrew Morris
64db0b7eba Move currencyConversionConfig into config 2022-07-15 15:04:57 +10:00
Andrew Morris
b809a22cee Move builtinNetworks into config 2022-07-15 14:48:12 +10:00
Andrew Morris
94ae095acf Update config system in prep for network switching 2022-07-15 13:52:57 +10:00
Andrew Morris
d52d598573 allow env 2022-07-15 11:10:56 +10:00
Andrew Morris
8a4c6e52f1 Fix docker weirdness with localhost by using 127.0.0.1 2022-07-15 11:10:56 +10:00
Andrew Morris
f6473e72af Upgrade deno 2022-07-15 11:10:56 +10:00
Andrew Morris
0f82acf931 Add useful things to the geth dev node 2022-07-15 11:10:56 +10:00
Andrew Morris
bfada65260 Disable rule duplicating prettier and with bad autofix interaction 2022-07-12 14:59:53 +10:00
Andrew Morris
79423ec79f Lints are always warnings 2022-07-12 14:59:53 +10:00
Andrew Morris
5225cb1883 Fix jsxBracketSameLine deprecation warning by upgrading to bracketSameLine 2022-07-12 14:59:53 +10:00
Andrew Morris
f4f5703eb6 Disallow lint warnings in CI 2022-07-12 14:59:53 +10:00
Andrew Morris
36825dbbe6 Enable linting js files, fix .eslintrc.js, tailwind.config.js 2022-07-12 14:59:53 +10:00
Andrew Morris
54e90045fb Render eslint config and re-extend 2022-07-12 14:59:53 +10:00
Andrew Morris
89c8b28d20 Rebuild config for typescript and eslint, lint fixes 2022-07-12 14:59:41 +10:00
Andrew Morris
aa5c2fea6b Specify deps for carousel correctly 2022-07-12 12:55:33 +10:00
Andrew Morris
66c257cacd Fix typing in AssetsTable.tsx 2022-07-12 12:40:39 +10:00
Andrew Morris
501afd7d64 Restore placeholder tables with notice and fix key issues 2022-07-12 12:05:58 +10:00
Andrew Morris
676471b3fe Use QuillContext in Confirm dialog 2022-07-12 11:54:02 +10:00
Andrew Morris
26686cdfe3 Merge pull request #263 from web3well/show-nonce
Show nonce on transactions tab
2022-07-12 09:44:29 +10:00
Kautuk Kundan
7cbd6a7617 Merge pull request #254 from web3well/confirmation-dialog
Confirmation Popup and Transaction Lifecycle
2022-07-11 16:12:43 +05:30
Andrew Morris
1c3b1c76b3 Require confirm dialog for Quill txs, but add todo 2022-07-11 16:08:44 +05:30
Andrew Morris
ed9ab2a103 Restore noUnusedLocals:false 2022-07-11 16:08:44 +05:30
Andrew Morris
b38547126e Restore enum 2022-07-11 16:08:44 +05:30
Andrew Morris
e1aa4de7fb Don't require popup for quill-initiated transactions 2022-07-11 16:08:44 +05:30
Andrew Morris
c0fa1cdb91 Clearer failure when user doesn't approve 2022-07-11 16:08:44 +05:30
Andrew Morris
7f5e5e2f27 Reject invalid transitions, simplify transition table, require valid status in rpc 2022-07-11 16:08:44 +05:30
Andrew Morris
8761d5e7f6 Move transaction typing into QuillStorageCells 2022-07-11 16:08:44 +05:30
Andrew Morris
c62dceb6a9 Improve typing of prompt messaging 2022-07-11 16:08:44 +05:30
Andrew Morris
d8dd145ae8 Use local InternalRpc 2022-07-11 16:08:44 +05:30
Andrew Morris
e43ddbd0fe Prompt user before connecting wallet (since connecting wallet is slow) 2022-07-11 16:08:44 +05:30
Andrew Morris
ce73b22a50 uuid -> RandomId 2022-07-11 16:08:44 +05:30
Andrew Morris
449f5b439a Use <quill> for internal methods 2022-07-11 16:08:44 +05:30
Andrew Morris
3fcddfe2f7 Don't allow unused variables 2022-07-11 16:08:44 +05:30
Andrew Morris
0eee5e34b4 Show nonce on transactions tab 2022-07-11 18:05:20 +10:00
kautukkundan
7fb3a62655 lint fixes 2022-07-10 22:15:44 +05:30
kautukkundan
7c15195aa6 visual changes 2022-07-10 21:49:45 +05:30
kautukkundan
b15c81ed2e added default value and gasPrice to txs 2022-07-10 20:37:14 +05:30
kautukkundan
a2756603bc added formatting for eth and added total actions value 2022-07-10 20:25:25 +05:30
kautukkundan
7fc2b45419 fixed transaction state change bug 2022-07-10 20:06:40 +05:30
kautukkundan
ba83438cf0 completed popup for tx confirmation 2022-07-10 19:52:19 +05:30
kautukkundan
e0d47566db updated UI for confirmation dialog 2022-07-10 17:34:31 +05:30
kautukkundan
39d02cbea0 extension: added transaction controller 2022-07-10 16:42:56 +05:30
Jacob Caban-Tomski
b8ae6e9370 Merge pull request #253 from web3well/bw-230-send-eth
Send eth
2022-07-09 13:38:43 -06:00
Andrew Morris
1b403b9300 Fix routing bug (duplicate route name), fix hiding amount selector 2022-07-08 17:18:10 +10:00
Andrew Morris
6ce54b404d Move selector switching into SendDetailSelectors, move send logic into separate function 2022-07-08 17:12:43 +10:00
Andrew Morris
dbdd56795d Simplify SendDetail cells 2022-07-08 16:52:21 +10:00
Andrew Morris
1358430dbb Use formatCompactAddress (remove shortenAddress duplicate) 2022-07-08 16:38:57 +10:00
Andrew Morris
b200502594 Avoid $ prefix outside of cell definitions 2022-07-08 16:36:36 +10:00
Andrew Morris
5f506f5f14 Basic SendProgress design 2022-07-07 11:33:57 +10:00
Andrew Morris
c982fe7a69 Implement send 2022-07-07 10:33:39 +10:00
Andrew Morris
718be15cac Remove back/cancel when sending 2022-07-07 09:29:17 +10:00
Andrew Morris
ae6db5b3f7 Add AmountSelector 2022-07-07 09:24:52 +10:00
Andrew Morris
d4cfe0e4f6 Add detail to BigSendButton 2022-07-07 08:28:30 +10:00
Andrew Morris
aad71f738f Granular back button 2022-07-07 08:03:38 +10:00
Andrew Morris
1e88e2711f WIP ui for sending eth 2022-07-06 16:34:13 +10:00
Jacob Caban-Tomski
b6e057bed8 Merge pull request #250 from web3well/bw-249-fix-params
Allow omitting params key when empty
2022-07-05 21:39:58 -06:00
Andrew Morris
5c1388e95c Allow omitting params key when empty 2022-07-06 11:50:50 +10:00
Andrew Morris
c3169e93b4 Merge pull request #248 from web3well/bw-232-account-balance
Show account balances
2022-07-06 11:48:26 +10:00
Andrew Morris
847b02d31e Merge branch 'main' into bw-232-account-balance 2022-07-06 11:42:08 +10:00
Andrew Morris
b5c30bedb2 Merge pull request #247 from web3well/bw-229-copy-addresses 2022-07-06 06:22:40 +10:00
Andrew Morris
350a31b66f Show account balances 2022-07-05 16:57:18 +10:00
Andrew Morris
36396a74d9 Make more use of onAction 2022-07-05 15:56:17 +10:00
Andrew Morris
78465e133e Copy address on click 2022-07-05 15:56:02 +10:00
Andrew Morris
363a518acd Fix linting issues 2022-07-05 15:35:22 +10:00
Andrew Morris
565e767d12 Merge pull request #239 from web3well/cells-formula-rename
Use prefixKeys to avoid shadowing
2022-07-05 15:04:59 +10:00
Andrew Morris
49a3661e52 Use prefixKeys to avoid shadowing 2022-07-05 14:59:09 +10:00
Andrew Morris
9cc3db2fce The Big One (#243)
* More stuff wasn't needed

* Quill api is not used

* Remove init and just put that in the constructor (🤯)

* More tidying up

* Cleanup config

* Remove commented location.reload

* Fix address selection

* Replace block tracking

* Remove unused code

* Remove unused code

* Remove unused code

* Delete unused code

* Delete commented code

* Deduplicate currency default config

* Enable strictPropertyInitialization

* Remove commented code

* Remove unused methods

* Remove AccountTrackerController and other unused things

* Remove unnecessary method

* Move CurrencyController setup inside CurrencyController's constructor

* Inline preferences cell

* Move NetworkController initialization inside NetworkController's constructor

* Move block tracking inside NetworkController

* Remove unused interfaces, define defaultCurrencyControllerState inside ICurrencyController

* Controllers set up their own cells

* Fix redundant storage of nativeCurrency

* Remove unnecessary handling of missing config in CurrencyController

* Common definition of QuillCells

* Inline unnecessary ICurrencyController.ts

* Remove unnecessary IKeyringController (but keep docs)

* Remove IPreferencesController (but keep docs)

* Move utils from IPreferencesController into PreferencesController (and delete the empty file)

* Remove redundant call to lookupNetwork

* Remove unused changeProvider method (but capture the idea by watching providerConfig in the networkController), also getSelectedAddress 💥

* currentCurrency -> userCurrency

* nativeCurrency -> networkCurrency

* Replace setDefaultCurrency

* Remove unnecessary methods

* Fix failing assertion

* Use vanilla webextension messaging for private rpc

* Remove no-longer desirable quill_ prefixes (because we're not sharing a namespace anymore)

* Directly implement PrivateRpc in QuillController instead of makePrivateRpc

* Ensure public and private rpc can't overlap

* wip: Replacing public rpc

* Remove commented code

* Don't allow unused vars 😄

* Only pass message to QuillController

* controller -> quillController

* Replace in-page rpc

* Allow public rpc to fallback to the network

* Add debugMe public method

* Add isQuill: true

* Improve settings

* Get settings routes working properly

* Add developer settings

* New setting: breakOnAssertionFailures

* Get breakOnAssertionFailures working

* Don't transpile so heavily

* Make RPC errors expose in useful+safe way

* Combine rpc handlers and simplify using toOkError

* handleRpcMessage -> handleMessage

* Add port handler for quill-events-port

* Move broadcast spec into Rpc.ts, prevent registering unknown events

* Simplify assertion

* Allow broadcasts to be scoped by origin and rename to notifications

* Emit notifications the new way

* QuillInPageProvider -> OldQuillInPageProvider

* QuillProvider -> QuillContextProvider

* QuillProvider

* Fix minor errors/warnings

* Add providerId, move EventsPortInfo into Rpc, handle non-promises in toOkError, simplify decoding EventsPortInfo

* Add assert docs, add softAssert, use softAssert in toOkError

* wip: Connecting events

* Connect events

* RpcResult improvements

* Move eth_requestAccounts into new rpc system

* Simplify implementing rpc

* Stop using the old jrpc-based system

* Add some TODOs

* Move makeEthereumMethods into publicRpc, delete more stuff

* Move TaskQueue into helpers

* Delete more unused code, add more TODOs

* Add note about cells walkthrough

* Remove cells demo page (preserving generic cells components)

* Move contentScript to just a file (not nested in a directory)

* Delete unused code

* Clean up the content script and add some nice docs explaining why it maybe shouldn't exist at all

* Cleanup pageContentScript/index.ts

* Resolve TODO

* Handle async in MemoryCell

* LongPollingCell

* Add some TODOs

* Track provider state using long polling (replacing events)

* Fix infuriating null issue

* Fix FormulaCell bug

* wip

* No more events RPC! 🤩

* Fix emission of disconnect errors

* Add fixme

* Remove unused code

* Replace old eth_chainId middleware

* AggregatorController

* Improve/simplify message passing for PublicRpc

* Remove unused code

* Temporary fix for chain id

* wip: Simplify RPC by consolidating public&private

* Simplify RPC by consolidating public&private

* Remove redundant selectedAddress cell

* Add ICell.update

* Fix redundant update methods in controllers

* Update PreferencesController to use cells properly

* Remove unused method

* Inline simple keyring methods

* Remove unused name fields

* Simplify some things

* Simplify networkController.provider using a cell

* Remove unused code

* Use the internal provider in KeyringController

* Simplify KeyringController

* Inline simple functions

* Fix unnecessary optionality of HDPhrase

* Currency api is not optional

* Simplify CurrencyController

* Don't store currency conversion, userCurrency -> preferredCurrency

* Fix redundant storage of preferred currency

* Replace CurrencyController with a cell

* Remove unused code, simplify getDefaultProviderConfig

* Improve typing of constants

* Move CRYPTO_COMPARE_API_KEY into env.ts

* builtinProviderConfigs

* Awesome docstring for ensureType

* Add TODO

* Temporary fix for 'loading' problem

* Replace unnecessary spaghetti with a simple fetch

* Prefer requestStrict

* Remove unused rpc method

* Delete unused code

* Fix unnecessary directory nesting

* Remove completed TODO

* Add rtti for SendTransactionParams

* Remove unused code, add TODOs

* Add TODOs

* QuillProvider -> QuillEthereumProvider

* pageContentScript/index.ts -> ethereum.ts, and don't remove the tag

* Simplify window.ethereum acquisition in quillPage

* Fix (window as any)

* Remove unused code

* constants -> networks

* Controllers/background.ts -> background/index.ts

* Simplify delegation

* Define keyring rpc

* Add fixme

* Use name of the cell instead of 'state' in controllers

* Move setSelectedAddress into PreferencesController

* Simplify rpc definition

* Use vanilla arguments for createUser

* Inline createHDAccount inside rpc

* Reorganize rpcMap

* Move ethersProvider into QuillController and deprecate it

* Move lookupPrivateKey into rpc

* Move createAccount into rpc

* Move removeAccount into rpc

* Move addAccount inside rpc

* Fix createAccount naming issue

* Add TODO

* Use more entropy in random ids and encode using base58check

* Don't worry about locale for now (just use the browser locale!, also we shouldn't be worried about multiple users right now, just different accounts)

* Move getBuiltinRPCURL into useInputDecode

* Move getDefaultProviderConfig inside QuillCells

* Remove unused code

* utils -> helpers/RandomId

* Fix unnecessary method wrapping in background/index.ts

* Add fixme

* Add MEGAFIX tag

* Fix redundant chainId storage (3 places -> 1 place!)

* Make chainId just a FormulaCell, since we shouldn't be writing it without writing the providerConfig

* Fix duplication of breakOnAssertionFailures cell

* Fix theme cell

* Fix RequestBody typing duplication, expose better type information for request

* forEach.ts

* Make use of forEach

* Implement window.ethereum.rpc

* Provide return type for ethereum.request

* Track breakOnFailures without using window.ethereum

* Capitalize io types

* Output -> Response

* Improve internal rpc calls

* Fix redundant network calls when not finding aggregator receipt

* Track HD indexes instead of using wallets.length

* Simplify network cells

* Reimplement time cell so that it doesn't actively update

* Non-actively updating TimeCell

* QuillCells -> QuillStorageCells

* Cleanup QuillContext cells

* Remove redundant rpc client

* Fix RandomId, isQuillExtensionPage

* Fix line break

* wip: LongPollingController

* Respond 'please-retry' after 5 minutes

* Add longPollCancel

* Fix request id issue

* wip: Lazy long polling cell

* Remove unused dependencies

* Use QuillLongPollingCell for providerState

* Fix incorrect usage of Stoppable

* Remember to break when we have a value

* Show network settings

* Show block number in network settings

* isPermittedOrigin, add origin restriction to longPollingCells

* Add awesome logging

* Improve replayability of logged rpc

* quillPage -> home

* Don't construct QuillEthereumProvider unless window.ethereum is actually accessed

* Only relay rpc if ethereum is accessed

* Fix rpc with home.html

* Improve debug.reset, remove isOnboardingComplete

* Fix onboarding

* Remove unused code

* Improving how loading is handled

* Update TODOs/FIXMEs (descoping + removing completed)

* Improve assertion failures by requiring new Error to be inlined

* Replace all custom throws with asserts

* De-scope disconnect event

* EIP1193 error codes

* GeneralSettings stub (testing currencyConversion cell)

* Test currency polling, implement currency aliases

* Make dev setting defaults good for the team for now (should be configurable)

* Just open the full view

* message -> request

* io.literal('ok') -> io.void

* Docstrings for assertType, assertTypeEcho

* Move debugging and runtime interaction out of QuillController

* Organize background/index.ts, 'quill-rpc-request', deduplicate rtti checking

* Add startup message

* Fix ok:undefined bug

* Post-merge tweaks

* Fix bad substitution in yarn.lock

* Fix double-calculating when using long polling

* Remove unused field

* Use sensible modern targets for babel, upgrade to webpack 5

* Remove unnecessary babel plugins

* Fix sourcemaps

* Update yarn.lock

* Allow /js/*

* Add quillController to Debug.ts

* Fix lint errors

* Fix inaccurate type issue

* Improve error reporting.

Add console.error for button errors so they are not swalloed silently.
Add versions to version mismatch write error.
Duplicate page hash TODO at all component usage sites.

Co-authored-by: Jacob Caban-Tomski <jacque006@users.noreply.github.com>
2022-06-30 17:09:57 -06:00
289 changed files with 45545 additions and 17448 deletions

View File

@@ -27,21 +27,21 @@ runs:
- working-directory: ./extension
shell: bash
run: |
cp .env.release .env
envsubst < config.release.json > config.json
yarn install --frozen-lockfile
- working-directory: ./extension
shell: bash
run: |
CRYPTO_COMPARE_API_KEY=${CRYPTO_COMPARE_API_KEY} \
NETWORK_CONFIG_PATH=../contracts/networks/arbitrum-testnet.json \
NETWORK_CONFIGS_DIR=../contracts/networks \
yarn build:${{ inputs.browser }}
- working-directory: ./extension
shell: bash
run: mv ./extension/${{ inputs.file-name }} ./extension/quill-${{ inputs.file-name }}
- uses: softprops/action-gh-release@v1
- uses: svenstaro/upload-release-action@v2
with:
tag_name: ${{ inputs.tag-name }}
tag: ${{ inputs.tag-name }}
# Note: This path is from repo root
# working-directory is not applied
files: ./extension/extension/quill-${{ inputs.file-name }}
# working-directory is not applied
file: ./extension/extension/quill-${{ inputs.file-name }}
overwrite: true

View File

@@ -7,8 +7,6 @@ on:
paths:
- 'aggregator-proxy/**'
pull_request:
branches:
- 'main'
paths:
- 'aggregator-proxy/**'

View File

@@ -6,11 +6,19 @@ on:
- 'main'
paths:
- 'aggregator/**'
# Check for breaking changes from contracts
- 'contracts/**'
- '.github/workflows/aggregator.yml'
pull_request:
branches:
- 'main'
paths:
- 'aggregator/**'
# Check for breaking changes from contracts
- 'contracts/**'
- '.github/workflows/aggregator.yml'
branches-ignore:
# Changes targeting this branch should be tested+fixed when being merged
# into main
- contract-updates
defaults:
run:
@@ -70,7 +78,7 @@ jobs:
- working-directory: ./
run: docker-compose up -d postgres
- run: cp .env.example .env
- run: cp .env.local.example .env
- run: deno test --allow-net --allow-env --allow-read --unstable
# Cleanup

View File

@@ -7,8 +7,6 @@ on:
paths:
- 'contracts/clients/**'
pull_request:
branches:
- 'main'
paths:
- 'contracts/clients/**'
@@ -17,6 +15,14 @@ defaults:
working-directory: ./contracts/clients
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-contracts-clients
- run: yarn build
test:
runs-on: ubuntu-latest

View File

@@ -8,8 +8,6 @@ on:
- 'contracts/**'
- '!contracts/clients/**'
pull_request:
branches:
- 'main'
paths:
- 'contracts/**'
- '!contracts/clients/**'

View File

@@ -7,8 +7,6 @@ on:
paths:
- 'extension/**'
pull_request:
branches:
- 'main'
paths:
- 'extension/**'
@@ -43,10 +41,7 @@ jobs:
node-version: ${{ env.NODEJS_VERSION }}
cache: yarn
cache-dependency-path: extension/yarn.lock
# Valid network config not needed to test build.
- working-directory: ./contracts/networks
run: echo "{}" > "local.json"
- run: cp .env.example .env
- run: cp config.example.json config.json
- run: yarn install --frozen-lockfile
# For now, just check that chrome builds
- run: yarn build:chrome

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.data
.DS_Store
.idea

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
lts/*

View File

@@ -8,6 +8,7 @@ You can watch a full end-to-end demo of the project [here](https://www.youtube.c
- [See an overview of BLS Wallet & how the components work together](./docs/system_overview.md)
- [Use BLS Wallet in a browser/NodeJS/Deno app](./docs/use_bls_wallet_clients.md)
- [Use BLS Wallet in your L2 dApp for cheaper, multi action transactions](./docs/use_bls_wallet_dapp.md)
- Setup the BLS Wallet components for:
- [Local develeopment](./docs/local_development.md)
- [Remote development](./docs/remote_development.md)
@@ -34,6 +35,14 @@ npm package which provides easy to use constructs to interact with the contracts
Prototype browser extension used to manage BLS Wallets and sign transactions.
## Contract Deployments
See [./contracts/networks](./contracts/networks/) for a list of all contract deployment (network) manifests. Have an L2/rollup testnet you'd like BLS Wallet deployed on? [Open an issue](https://github.com/web3well/bls-wallet/issues/new) or [Deploy it yourself](./docs/remote_development.md)
- [Arbitrum Goerli](./contracts/networks/arbitrum-goerli.json)
- [Arbitrum Rinkby](./contracts/networks/arbitrum-testnet.json) (deprecated, outdated)
## Ways to Contribute
- [Work on an open issue](https://github.com/web3well/bls-wallet/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)

View File

@@ -37,3 +37,13 @@ runAggregatorProxy(
},
);
```
## Instant wallet without dapp-sponsored transaction
![Instant wallet without dapp-sponsored transactions](./../docs/images/system-overview/instant-wallet-without-dapp-sponsored-txs.jpg)
## Instant wallet with dapp-sponsored transaction
![Instant wallet with dapp-sponsored transaction](./../docs/images/system-overview/instant-wallet-with-dapp-sponsored-txs.jpg)
## Example dApp using a proxy aggregator
- https://github.com/JohnGuilding/single-pool-dex

View File

@@ -21,7 +21,7 @@
"@types/koa__cors": "^3.3.0",
"@types/koa__router": "^8.0.11",
"@types/node-fetch": "^2.6.1",
"bls-wallet-clients": "^0.6.0",
"bls-wallet-clients": "0.8.0",
"fp-ts": "^2.12.1",
"io-ts": "^2.2.16",
"io-ts-reporters": "^2.0.1",

View File

@@ -452,9 +452,6 @@
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.5.0.tgz#530f4f608f9ca9d4f89c24ab95db58ab56ab99a0"
integrity sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/rlp@5.6.0", "@ethersproject/rlp@^5.5.0", "@ethersproject/rlp@^5.6.0":
version "5.6.0"
@@ -885,10 +882,10 @@ bech32@1.1.4:
resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9"
integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==
bls-wallet-clients@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/bls-wallet-clients/-/bls-wallet-clients-0.6.0.tgz#9d9b1add69420bbaf807c1442151e487f4ee87a5"
integrity sha512-6EivjMe2uRGIt6Aq5IampqlmsECavLqHGPm6Ki2l3+c+FnwfOQUzNelctVN/vRVxDbDpTX4iAfTIrYYpr1S/vw==
bls-wallet-clients@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/bls-wallet-clients/-/bls-wallet-clients-0.8.0.tgz#7b6510dede672fcbcfd743caa85a038fa26aad11"
integrity sha512-cutglRs+1vRiFPmo5uwMRZRjkeNxr/X3NiyUqsB4VgIRQ+EixCMXvuY7BDok5yby6GWzXox2ZAqJkGTFuHyr2w==
dependencies:
"@thehubbleproject/bls" "^0.5.1"
ethers "5.5.4"

View File

@@ -1,16 +1,16 @@
RPC_URL=http://localhost:8545
RPC_URL=https://goerli-rollup.arbitrum.io/rpc
USE_TEST_NET=false
ORIGIN=http://localhost:3000
PORT=3000
NETWORK_CONFIG_PATH=../contracts/networks/local.json
PRIVATE_KEY_AGG=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
NETWORK_CONFIG_PATH=../contracts/networks/arbitrum-goerli.json
PRIVATE_KEY_AGG=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
PRIVATE_KEY_ADMIN=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
TEST_BLS_WALLETS_SECRET=test-bls-wallets-secret
PG_HOST=localhost
PG_HOST=127.0.0.1
PG_PORT=5432
PG_USER=bls
PG_PASSWORD=generate-a-strong-password

View File

@@ -0,0 +1,32 @@
RPC_URL=http://localhost:8545
USE_TEST_NET=false
ORIGIN=http://localhost:3000
PORT=3000
NETWORK_CONFIG_PATH=../contracts/networks/local.json
PRIVATE_KEY_AGG=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
PRIVATE_KEY_ADMIN=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
TEST_BLS_WALLETS_SECRET=test-bls-wallets-secret
PG_HOST=localhost
PG_PORT=5432
PG_USER=bls
PG_PASSWORD=generate-a-strong-password
PG_DB_NAME=bls_aggregator
BUNDLE_TABLE_NAME=bundles
BUNDLE_QUERY_LIMIT=100
MAX_ELIGIBILITY_DELAY=300
MAX_AGGREGATION_SIZE=12
MAX_AGGREGATION_DELAY_MILLIS=5000
MAX_UNCONFIRMED_AGGREGATIONS=3
LOG_QUERIES=false
TEST_LOGGING=false
FEE_TYPE=ether
FEE_PER_GAS=0
FEE_PER_BYTE=0

View File

@@ -1,4 +1,4 @@
.env*
!.env.example
!.env*.example
cov_profile*
/build

View File

@@ -1,4 +1,4 @@
FROM denoland/deno:1.20.6
FROM denoland/deno:1.23.4
ADD build /app
WORKDIR /app

View File

@@ -29,6 +29,34 @@ you might have:
If you don't have a `.env`, you will need to append `--env <name>` to all
commands.
#### Environment Variables
| Name | Example Value | Description |
| ---------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| RPC_URL | https://localhost:8545 | The RPC endpoint for an EVM node that the BLS Wallet contracts are deployed on |
| USE_TEST_NET | false | Whether to set all transaction's `gasPrice` to 0. Workaround for some networks |
| ORIGIN | http://localhost:3000 | The origin for the aggregator client. Used only in manual tests |
| PORT | 3000 | The port to bind the aggregator to |
| NETWORK_CONFIG_PATH | ../contracts/networks/local.json | Path to the network config file, which contains information on deployed BLS Wallet contracts |
| PRIVATE_KEY_AGG | 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 | Private key for the EOA account used to submit bundles on chain |
| PRIVATE_KEY_ADMIN | 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d | Private key for the admin EOA account. Used only in tests |
| TEST_BLS_WALLETS_SECRET | test-bls-wallets-secret | Secret used to seed BLS Wallet private keys during tests |
| PG_HOST | 127.0.0.1 | Postgres database host |
| PG_PORT | 5432 | Postgres database port |
| PG_USER | bls | Postgres database user |
| PG_PASSWORD | generate-a-strong-password | Postgres database password |
| PG_DB_NAME | bls_aggregator | Postgres database name |
| BUNDLE_TABLE_NAME | bundles | Postgres table name for bundles |
| BUNDLE_QUERY_LIMIT | 100 | Maximum number of bundles returned from Postgres |
| MAX_AGGREGATION_SIZE | 12 | Maximum number of actions from bundles which will be aggregated together for submission on chain |
| MAX_AGGREGATION_DELAY_MILLIS | 5000 | Maximum amount of time in milliseconds aggregator will wait before submitting bundles on chain |
| MAX_UNCONFIRMED_AGGREGATIONS | 3 | Maximum unconfirmed bundle aggregations that will be submitted on chain. Multiplied with `MAX_AGGREGATION_SIZE` to determine maximum of unconfirmed on chain actions |
| LOG_QUERIES | false | Whether to print Postgres queries in event log.`TEST_LOGGING` must be enabled |
| TEST_LOGGING | false | Whether to print aggregator server events to stdout. Useful for debugging & logging. |
| FEE_TYPE | ether OR token:0xabcd...1234 | The fee type the aggregator will accept. Either `ether` for ETH/chains native currency or `token:0xabcd...1234` (token contract address) for an ERC20 token |
| FEE_PER_GAS | 0 | Minimum amount per gas (gasPrice) the aggregator will accept in ETH/chain native currency/ERC20 tokens |
| FEE_PER_BYTE | 0 | Minimum amount per calldata byte the aggregator will accept in ETH/chain native currency/ERC20 tokens (rollup L1 cost) |
### PostgreSQL
#### With docker-compose
@@ -170,10 +198,17 @@ deno run -r --allow-net --allow-env --allow-read --unstable ./programs/aggregato
#### Transaction reverted: function call to a non-contract account
- Is `./contracts/contracts/lib/hubble-contracts/contracts/libs/BLS.sol`'s `COST_ESTIMATOR_ADDRESS` set to the right precompile cost estimator's contract address?
- Is `./contracts/contracts/lib/hubble-contracts/contracts/libs/BLS.sol`'s
`COST_ESTIMATOR_ADDRESS` set to the right precompile cost estimator's contract
address?
- Are the BLS Wallet contracts deployed on the correct network?
- Is `NETWORK_CONFIG_PATH` in `.env` set to the right config?
#### Deno version
Make sure your Deno version is
[up to date.](https://deno.land/manual/getting_started/installation#updating)
### Notable Components
- **src/chain**: Should contain all of the contract interactions, exposing more

View File

@@ -49,7 +49,7 @@ export type {
PublicKey,
Signature,
VerificationGateway,
} from "https://esm.sh/bls-wallet-clients@0.6.0";
} from "https://esm.sh/bls-wallet-clients@0.8.0";
export {
Aggregator as AggregatorClient,
@@ -59,10 +59,10 @@ export {
getConfig,
MockERC20__factory,
VerificationGateway__factory,
} from "https://esm.sh/bls-wallet-clients@0.6.0";
} from "https://esm.sh/bls-wallet-clients@0.8.0";
// Workaround for esbuild's export-star bug
import blsWalletClients from "https://esm.sh/bls-wallet-clients@0.6.0";
import blsWalletClients from "https://esm.sh/bls-wallet-clients@0.8.0";
const {
bundleFromDto,
bundleToDto,

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env -S deno run --unstable --allow-run --allow-read --allow-write
#!/usr/bin/env -S deno run --unstable --allow-run --allow-read --allow-write --allow-env
import { dirname, parseArgs } from "../deps.ts";

View File

@@ -1,6 +1,7 @@
import {
BigNumber,
BlsWalletSigner,
BlsWalletWrapper,
Bundle,
delay,
ethers,
@@ -151,15 +152,22 @@ export default class BundleService {
};
}
const signedCorrectly = this.blsWalletSigner.verify(bundle);
const walletAddresses = await Promise.all(bundle.senderPublicKeys.map(
(pubKey) => BlsWalletWrapper.AddressFromPublicKey(
pubKey, this.ethereumService.verificationGateway
)
));
const failures: TransactionFailure[] = [];
if (signedCorrectly === false) {
failures.push({
for (const walletAddr of walletAddresses) {
const signedCorrectly = this.blsWalletSigner.verify(bundle, walletAddr);
if (!signedCorrectly) {
failures.push({
type: "invalid-signature",
description: "invalid signature",
});
description: `invalid signature for wallet address ${walletAddr}`,
});
}
}
failures.push(...await this.ethereumService.checkNonces(bundle));
@@ -205,6 +213,8 @@ export default class BundleService {
return {
transactionIndex: receipt.transactionIndex,
transactionHash: receipt.transactionHash,
bundleHash: hash,
blockHash: receipt.blockHash,
blockNumber: receipt.blockNumber,
};

View File

@@ -1,7 +1,7 @@
import Range from "../src/helpers/Range.ts";
import {
assertEquals,
assertBundleSucceeds,
assertEquals,
BigNumber,
BlsWalletWrapper,
ethers,
@@ -255,7 +255,7 @@ Fixture.test("submits 9/10 bundles when 7th has insufficient gas-based fee", asy
});
const baseFee = BigNumber.from(1_000_000).mul(1e9); // Note 1
const fee = BigNumber.from(1_950_000).mul(1e9);
const fee = BigNumber.from(1_900_000).mul(1e9);
const [wallet1, wallet2] = await fx.setupWallets(2, {
tokenBalance: fee.mul(10),

View File

@@ -15,7 +15,7 @@ export function assertEquals<L, R extends L>(left: L, right: R) {
export function assertBundleSucceeds(res: AddBundleResponse) {
if ("failures" in res) {
throw new AssertionError("expected bundle to succeed");
throw new AssertionError(`expected bundle to succeed. failures: ${JSON.stringify(res.failures)}`);
}
}

View File

@@ -3,6 +3,7 @@ ETHERSCAN_API_KEY=
ROPSTEN_URL=fill_me_in
RINKEBY_URL=fill_me_in
ARBITRUM_TESTNET_URL=https://rinkeby.arbitrum.io/rpc
ARBITRUM_GOERLI_URL=https://goerli-rollup.arbitrum.io/rpc
ARBITRUM_URL=https://arb1.arbitrum.io/rpc
OPTIMISM_LOCAL_URL=http://localhost:8545
OPTIMISM_TESETNET_URL=https://kovan.optimism.io

View File

@@ -4,7 +4,7 @@
node_modules
coverage
coverage.json
/typechain
/typechain-types
networks/local.json
#Hardhat files

View File

@@ -1,6 +1,6 @@
*
!/dist/src/**/*
!/dist/typechain/**/*
!/dist/typechain-types/**/*
!/src/**/*
!/package.json
!/README.md

View File

@@ -80,6 +80,24 @@ const verificationGateway = VerificationGateway__factory.connect(
await verificationGateway.processBundle(bundle);
```
You can get the results of the operations in a bundle using `getOperationResults`.
```ts
import { getOperationResults } from 'bls-wallet-clients';
...
const txn = await verificationGateway.processBundle(bundle);
const txnReceipt = txn.wait();
const opResults = getOperationResults(txnReceipt);
// Includes data from WalletOperationProcessed event,
// as well as parsed errors with action index
const { error } = opResults[0];
console.log(error?.actionIndex); // ex. 0 (as BigNumber)
console.log(error?.message); // ex. "some require failure message"
```
## Signer
Utilities for signing, aggregating and verifying transaction bundles using the

View File

@@ -1,6 +1,6 @@
{
"name": "bls-wallet-clients",
"version": "0.6.0",
"version": "0.8.0",
"description": "Client libraries for interacting with BLS Wallet components",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@@ -13,10 +13,9 @@
"yarn": ">=1.0.0"
},
"scripts": {
"build": "rm -rf dist && mkdir dist && cp -rH typechain dist/typechain && find ./dist/typechain -type f \\! -name '*.d.ts' -name '*.ts' -delete && tsc",
"build": "rm -rf dist && mkdir dist && cp -rH typechain-types dist/typechain-types && find ./dist/typechain-types -type f \\! -name '*.d.ts' -name '*.ts' -delete && tsc",
"watch": "tsc -w",
"pretest": "yarn build",
"test": "mocha dist/**/*.test.js",
"test": "mocha --require ts-node/register --require source-map-support/register --require ./test/init.ts **/*.test.ts",
"premerge": "yarn test",
"publish-experimental": "node scripts/showVersion.js >.version && npm version $(node scripts/showBaseVersion.js)-$(git rev-parse HEAD | head -c7) --allow-same-version && npm publish --tag experimental && npm version $(cat .version) && rm .version",
"publish-experimental-dry-run": "node scripts/showVersion.js >.version && npm version $(node scripts/showBaseVersion.js)-$(git rev-parse HEAD | head -c7) --allow-same-version && npm publish --tag experimental --dry-run && npm version $(cat .version) && rm .version"
@@ -26,11 +25,14 @@
"ethers": "5.5.4"
},
"devDependencies": {
"@types/chai": "^4.3.0",
"@types/mocha": "^9.1.0",
"@types/chai": "^4.3.3",
"@types/chai-as-promised": "^7.1.5",
"@types/mocha": "^10.0.0",
"chai": "^4.3.6",
"mocha": "^9.2.2",
"chai-as-promised": "^7.1.1",
"mocha": "^10.1.0",
"source-map-support": "^0.5.21",
"typescript": "^4.6.2"
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
}
}

View File

@@ -1,5 +1,7 @@
/* eslint-disable camelcase */
import { ethers, BigNumber } from "ethers";
import { solidityKeccak256 } from "ethers/lib/utils";
import { keccak256, solidityKeccak256, solidityPack } from "ethers/lib/utils";
import {
BlsWalletSigner,
@@ -12,34 +14,63 @@ import {
import {
BLSWallet,
// eslint-disable-next-line camelcase
BLSWallet__factory,
// eslint-disable-next-line camelcase
TransparentUpgradeableProxy__factory,
// eslint-disable-next-line camelcase
VerificationGateway,
VerificationGateway__factory,
} from "../typechain";
} from "../typechain-types";
type SignerOrProvider = ethers.Signer | ethers.providers.Provider;
/**
* Class representing a BLS Wallet
*/
export default class BlsWalletWrapper {
public address: string;
private constructor(
public blsWalletSigner: BlsWalletSigner,
public privateKey: string,
public address: string,
public walletContract: BLSWallet,
) {}
) {
this.address = walletContract.address;
}
/** Get the wallet contract address for the given key, if it exists. */
static async BLSWallet(
privateKey: string,
verificationGateway: VerificationGateway,
): Promise<BLSWallet> {
const contractAddress = await BlsWalletWrapper.Address(
privateKey,
verificationGateway.address,
verificationGateway.provider,
);
return BLSWallet__factory.connect(
contractAddress,
verificationGateway.provider,
);
}
/**
* Gets the address for this wallet.
*
* This could be:
* - The address the wallet is registered to on the VerificationGateway.
* - The expected address if it has not already be created/registered.
* - The original wallet address before it was recovered to another key pair.
*
* Throws an exception if wallet was recovered to a different private key.
*
* @param privateKey private key associated with the wallet
* @param verificationGatewayAddress address of the VerficationGateway contract
* @param signerOrProvider ethers.js Signer or Provider
* @param blsWalletSigner (optional) a BLS Wallet signer
* @returns The wallet's address
*/
static async Address(
privateKey: string,
verificationGatewayAddress: string,
signerOrProvider: SignerOrProvider,
/**
* Internal value associated with the bls-wallet-signer library that can be
* provided as an optimization, otherwise it will be created
* automatically.
*/
blsWalletSigner?: BlsWalletSigner,
): Promise<string> {
blsWalletSigner ??= await this.#BlsWalletSigner(signerOrProvider);
@@ -48,64 +79,90 @@ export default class BlsWalletWrapper {
verificationGatewayAddress,
signerOrProvider,
);
const pubKeyHash = blsWalletSigner.getPublicKeyHash(privateKey);
const [proxyAdminAddress, blsWalletLogicAddress] = await Promise.all([
verificationGateway.walletProxyAdmin(),
verificationGateway.blsWalletLogic(),
]);
const initFunctionParams =
BLSWallet__factory.createInterface().encodeFunctionData("initialize", [
verificationGatewayAddress,
]);
return ethers.utils.getCreate2Address(
verificationGatewayAddress,
blsWalletSigner.getPublicKeyHash(privateKey),
ethers.utils.solidityKeccak256(
["bytes", "bytes"],
[
TransparentUpgradeableProxy__factory.bytecode,
ethers.utils.defaultAbiCoder.encode(
["address", "address", "bytes"],
[blsWalletLogicAddress, proxyAdminAddress, initFunctionParams],
),
],
),
const existingAddress = await verificationGateway.walletFromHash(
pubKeyHash,
);
const hasExistingAddress = !BigNumber.from(existingAddress).isZero();
if (hasExistingAddress) {
return existingAddress;
}
const expectedAddress = await this.ExpectedAddress(
verificationGateway,
pubKeyHash,
);
this.validateWalletNotRecovered(
blsWalletSigner,
verificationGateway,
expectedAddress,
privateKey,
);
return expectedAddress;
}
/** Get the wallet contract address for the given public key */
static async AddressFromPublicKey(
publicKey: PublicKey,
verificationGateway: VerificationGateway,
): Promise<string> {
const pubKeyHash = keccak256(solidityPack(["uint256[4]"], [publicKey]));
const existingAddress = await verificationGateway.walletFromHash(
pubKeyHash,
);
if (!BigNumber.from(existingAddress).isZero()) {
return existingAddress;
}
return this.ExpectedAddress(verificationGateway, pubKeyHash);
}
/**
* Instantiate a `BLSWallet` associated with the provided key if the
* Instantiate a `BLSWallet` associated with the provided private key.
* associated wallet contract already exists.
*
* Throws an exception if wallet was recovered to a different private key.
*
* @param privateKey private key associated with the wallet
* @param verificationGatewayAddress address of the VerficationGateway contract
* @param provider ethers.js Provider
* @returns a BLS Wallet
*/
static async connect(
privateKey: string,
verificationGatewayAddress: string,
provider: ethers.providers.Provider,
): Promise<BlsWalletWrapper> {
const network = await provider.getNetwork();
const blsWalletSigner = await initBlsWalletSigner({
chainId: network.chainId,
});
const contractAddress = await BlsWalletWrapper.Address(
privateKey,
const verificationGateway = VerificationGateway__factory.connect(
verificationGatewayAddress,
provider,
);
const blsWalletSigner = await initBlsWalletSigner({
chainId: (await verificationGateway.provider.getNetwork()).chainId,
});
const walletContract = BLSWallet__factory.connect(
contractAddress,
provider,
);
return new BlsWalletWrapper(
const blsWalletWrapper = new BlsWalletWrapper(
blsWalletSigner,
privateKey,
contractAddress,
walletContract,
await BlsWalletWrapper.BLSWallet(privateKey, verificationGateway),
);
return blsWalletWrapper;
}
async syncWallet(verificationGateway: VerificationGateway) {
this.address = await BlsWalletWrapper.Address(
this.privateKey,
verificationGateway.address,
verificationGateway.provider,
);
this.walletContract = BLSWallet__factory.connect(
this.address,
verificationGateway.provider,
);
}
@@ -114,7 +171,9 @@ export default class BlsWalletWrapper {
* block.
*/
async Nonce(): Promise<BigNumber> {
const code = await this.walletContract.provider.getCode(this.address);
const code = await this.walletContract.provider.getCode(
this.walletContract.address,
);
if (code === "0x") {
// The wallet doesn't exist yet. Wallets are lazily created, so the nonce
@@ -164,7 +223,11 @@ export default class BlsWalletWrapper {
/** Sign an operation, producing a `Bundle` object suitable for use with an aggregator. */
sign(operation: Operation): Bundle {
return this.blsWalletSigner.sign(operation, this.privateKey);
return this.blsWalletSigner.sign(
operation,
this.privateKey,
this.walletContract.address,
);
}
/** Sign a message */
@@ -200,4 +263,57 @@ export default class BlsWalletWrapper {
return await initBlsWalletSigner({ chainId });
}
// Calculates the expected address the wallet will be created at
private static async ExpectedAddress(
verificationGateway: VerificationGateway,
pubKeyHash: string,
): Promise<string> {
const [proxyAdminAddress, blsWalletLogicAddress] = await Promise.all([
verificationGateway.walletProxyAdmin(),
verificationGateway.blsWalletLogic(),
]);
const initFunctionParams =
BLSWallet__factory.createInterface().encodeFunctionData("initialize", [
verificationGateway.address,
]);
return ethers.utils.getCreate2Address(
verificationGateway.address,
pubKeyHash,
ethers.utils.solidityKeccak256(
["bytes", "bytes"],
[
TransparentUpgradeableProxy__factory.bytecode,
ethers.utils.defaultAbiCoder.encode(
["address", "address", "bytes"],
[blsWalletLogicAddress, proxyAdminAddress, initFunctionParams],
),
],
),
);
}
private static async validateWalletNotRecovered(
blsWalletSigner: BlsWalletSigner,
verificationGateway: VerificationGateway,
walletAddress: string,
privateKey: string,
): Promise<void> {
const pubKeyHash = blsWalletSigner.getPublicKeyHash(privateKey);
const existingPubKeyHash = await verificationGateway.hashFromWallet(
walletAddress,
);
const walletIsAlreadyRegistered =
!BigNumber.from(existingPubKeyHash).isZero();
const pubKeyHashesDoNotMatch = pubKeyHash !== existingPubKeyHash;
if (walletIsAlreadyRegistered && pubKeyHashesDoNotMatch) {
throw new Error(
`wallet at ${walletAddress} has been recovered from public key hash ${pubKeyHash} to ${existingPubKeyHash}`,
);
}
}
}

View File

@@ -0,0 +1,61 @@
import { NetworkConfig, validateConfig } from "./NetworkConfig";
/**
* Config representing the deployed state of bls-wallet contracts
* across multiple networks.
*/
export type MultiNetworkConfig = {
[networkKey: string]: NetworkConfig;
};
/**
* Unvalidated MultiNetworkConfig
*/
export type UnvalidatedMultiNetworkConfig = Record<
string,
Record<string, Record<string, unknown>>
>;
type ReadFileFunc = (filePath: string) => Promise<string>;
/**
* Validates and returns a multi-network config.
*
* @param cfg The config object to validate.
*/
export function validateMultiConfig(
cfg: MultiNetworkConfig,
): MultiNetworkConfig {
const isEmpty = !Object.keys(cfg).length;
if (isEmpty) {
throw new Error("config is empty");
}
const multiConfig: MultiNetworkConfig = {};
for (const [networkKey, networkConfig] of Object.entries(cfg)) {
try {
multiConfig[networkKey] = validateConfig(networkConfig);
} catch (err) {
const castErr = err as Error;
const newErr = new Error(`${networkKey}: ${castErr.message}`);
newErr.stack = castErr.stack;
throw newErr;
}
}
return multiConfig;
}
/**
* Retrieves, validates, and returns a multi-network config.
*
* @param networkConfigPath Path to config JSON file.
* @param readFileFunc Callback to retrieve the config. This could be via fetch, fs.readFile, etc.
*/
export async function getMultiConfig(
configPath: string,
readFileFunc: ReadFileFunc,
): Promise<NetworkConfig> {
const cfg = JSON.parse(await readFileFunc(configPath));
validateMultiConfig(cfg);
return cfg;
}

View File

@@ -43,13 +43,14 @@ export type NetworkConfig = {
};
type ReadFileFunc = (filePath: string) => Promise<string>;
type UnvalidatedConfig = Record<string, Record<string, unknown>>;
/**
* Validates and returns a network config.
*
* @param cfg The config object to validate.
*/
export function validateConfig(cfg: any): NetworkConfig {
export function validateConfig(cfg: UnvalidatedConfig): NetworkConfig {
return {
parameters: assertUnknownRecord(cfg.parameters),
addresses: {
@@ -75,6 +76,7 @@ export function validateConfig(cfg: any): NetworkConfig {
/**
* Retrieves, validates, and returns a network config.
* @deprecated Use getMultiConfig instead.
*
* @param networkConfigPath Path to config JSON file.
* @param readFileFunc Callback to retrieve the config. This could be via fetch, fs.readFile, etc.

View File

@@ -0,0 +1,171 @@
import { BigNumber, ContractReceipt, utils } from "ethers";
import assert from "./helpers/assert";
import { ActionData } from "./signer";
const errorSelectors = {
Error: calculateAndCheckSelector("Error(string)", "0x08c379a0"),
Panic: calculateAndCheckSelector("Panic(uint256)", "0x4e487b71"),
ActionError: calculateAndCheckSelector(
"ActionError(uint256,bytes)",
"0x5c667601",
),
};
const actionErrorId = utils
.keccak256(new TextEncoder().encode("ActionError(uint256,bytes)"))
.slice(0, 10);
assert(actionErrorId === "0x5c667601");
type OperationResultError = {
actionIndex?: BigNumber;
message: string;
};
export type OperationResult = {
walletAddress: string;
nonce: BigNumber;
actions: ActionData[];
success: Boolean;
results: string[];
error?: OperationResultError;
};
const getError = (
success: boolean,
results: string[],
): OperationResultError | undefined => {
if (success) {
return undefined;
}
// Single event "WalletOperationProcessed(address indexed wallet, uint256 nonce, bool success, bytes[] results)"
// Get the first (only) result from "results" argument.
const [errorData] = results;
if (!errorData.startsWith(errorSelectors.ActionError)) {
throw new Error(
[
`errorResult does not begin with ActionError selector`,
`(${errorSelectors.ActionError}): ${errorData}`,
].join(" "),
);
}
// remove methodId (4bytes after 0x)
const actionErrorArgBytes = `0x${errorData.slice(10)}`;
let actionIndex: BigNumber | undefined;
let message: string;
try {
const [actionIndexDecoded, actionErrorData] = utils.defaultAbiCoder.decode(
["uint256", "bytes"],
actionErrorArgBytes,
) as [BigNumber, string];
actionIndex = actionIndexDecoded;
const actionErrorDataBody = `0x${actionErrorData.slice(10)}`;
if (actionErrorData.startsWith(errorSelectors.Error)) {
[message] = utils.defaultAbiCoder.decode(["string"], actionErrorDataBody);
} else if (actionErrorData.startsWith(errorSelectors.Panic)) {
const [panicCode] = utils.defaultAbiCoder.decode(
["uint256"],
actionErrorDataBody,
) as [BigNumber];
message = [
`Panic: ${panicCode.toHexString()}`,
"(See Panic(uint256) in the solidity docs:",
"https://docs.soliditylang.org/_/downloads/en/latest/pdf/)",
].join(" ");
} else {
message = `Unexpected action error data: ${actionErrorData}`;
}
} catch (error) {
console.error(error);
message = `Unexpected error data: ${errorData}`;
}
return {
actionIndex,
message,
};
};
export const getOperationResults = (
txnReceipt: ContractReceipt,
): OperationResult[] => {
if (!txnReceipt.events || !txnReceipt.events.length) {
throw new Error(
`no events found in transaction ${txnReceipt.transactionHash}`,
);
}
const walletOpProcessedEvents = txnReceipt.events.filter(
(e) => e.event === "WalletOperationProcessed",
);
if (!walletOpProcessedEvents.length) {
throw new Error(
`no WalletOperationProcessed events found in transaction ${txnReceipt.transactionHash}`,
);
}
return walletOpProcessedEvents.reduce<OperationResult[]>(
(opResults, { args }) => {
if (!args) {
throw new Error("WalletOperationProcessed event missing args");
}
const { wallet, nonce, actions: rawActions, success, results } = args;
const actions = rawActions.map(
({
ethValue,
contractAddress,
encodedFunction,
}: {
ethValue: BigNumber;
contractAddress: string;
encodedFunction: string;
}) => ({
ethValue,
contractAddress,
encodedFunction,
}),
);
const error = getError(success, results);
return [
...opResults,
{
walletAddress: wallet,
nonce,
actions,
success,
results,
error,
},
];
},
[],
);
};
function calculateSelector(signature: string) {
return utils.keccak256(new TextEncoder().encode(signature)).slice(0, 10);
}
function calculateAndCheckSelector(signature: string, expected: string) {
const selector = calculateSelector(signature);
assert(
selector === expected,
`Selector for ${signature} was not ${expected}`,
);
return selector;
}

View File

@@ -2,22 +2,29 @@ import Aggregator from "./Aggregator";
import BlsWalletWrapper from "./BlsWalletWrapper";
// eslint-disable-next-line camelcase
import { VerificationGateway__factory } from "../typechain/factories/VerificationGateway__factory";
import type { VerificationGateway } from "../typechain/VerificationGateway";
import { VerificationGateway__factory } from "../typechain-types/factories/contracts/VerificationGateway__factory";
import type { VerificationGateway } from "../typechain-types/contracts/VerificationGateway";
// eslint-disable-next-line camelcase
import { AggregatorUtilities__factory } from "../typechain/factories/AggregatorUtilities__factory";
import type { AggregatorUtilities } from "../typechain/AggregatorUtilities";
import { AggregatorUtilities__factory } from "../typechain-types/factories/contracts/AggregatorUtilities__factory";
import type { AggregatorUtilities } from "../typechain-types/contracts/AggregatorUtilities";
// eslint-disable-next-line camelcase
import { ERC20__factory } from "../typechain/factories/ERC20__factory";
import type { ERC20 } from "../typechain/ERC20";
import { ERC20__factory } from "../typechain-types/factories/@openzeppelin/contracts/token/ERC20/ERC20__factory";
import type { ERC20 } from "../typechain-types/@openzeppelin/contracts/token/ERC20/ERC20";
// eslint-disable-next-line camelcase
import { MockERC20__factory } from "../typechain/factories/MockERC20__factory";
import type { MockERC20 } from "../typechain/MockERC20";
import { MockERC20__factory } from "../typechain-types/factories/contracts/mock/MockERC20__factory";
import type { MockERC20 } from "../typechain-types/contracts/mock/MockERC20";
import { NetworkConfig, getConfig, validateConfig } from "./NetworkConfig";
import {
MultiNetworkConfig,
getMultiConfig,
validateMultiConfig,
} from "./MultiNetworkConfig";
import { OperationResult, getOperationResults } from "./OperationResults";
export * from "./signer";
@@ -27,6 +34,11 @@ export {
NetworkConfig,
getConfig,
validateConfig,
MultiNetworkConfig,
getMultiConfig,
validateMultiConfig,
OperationResult,
getOperationResults,
// eslint-disable-next-line camelcase
VerificationGateway__factory,
VerificationGateway,

View File

@@ -2,7 +2,7 @@ import { keccak256, solidityPack } from "ethers/lib/utils";
import { Operation } from "./types";
export default (chainId: number) =>
(operation: Operation): string => {
(operation: Operation, walletAddress: string): string => {
let encodedActionData = "0x";
for (const action of operation.actions) {
@@ -18,7 +18,7 @@ export default (chainId: number) =>
}
return solidityPack(
["uint256", "uint256", "bytes32"],
[chainId, operation.nonce, keccak256(encodedActionData)],
["uint256", "address", "uint256", "bytes32"],
[chainId, walletAddress, operation.nonce, keccak256(encodedActionData)],
);
};

View File

@@ -8,10 +8,9 @@ export default (
domain: Uint8Array,
chainId: number,
) =>
(operation: Operation, privateKey: string): Bundle => {
const message = encodeMessageForSigning(chainId)(operation);
(operation: Operation, privateKey: string, walletAddress: string): Bundle => {
const signer = signerFactory.getSigner(domain, privateKey);
const message = encodeMessageForSigning(chainId)(operation, walletAddress);
const signature = signer.sign(message);
return {

View File

@@ -1,18 +1,25 @@
import { BigNumber } from "ethers";
import { VerificationGateway } from "../../typechain";
import { BigNumberish, BytesLike } from "ethers";
export type Bundle = Parameters<VerificationGateway["processBundle"]>[0];
export type Operation = Bundle["operations"][number];
export type ActionData = {
ethValue: BigNumberish;
contractAddress: string;
encodedFunction: BytesLike;
};
export type Operation = {
nonce: BigNumberish;
actions: ActionData[];
};
export type Bundle = {
signature: [BigNumberish, BigNumberish];
senderPublicKeys: [BigNumberish, BigNumberish, BigNumberish, BigNumberish][];
operations: Operation[];
};
export type PublicKey = Bundle["senderPublicKeys"][number];
export type Signature = Bundle["signature"];
export type ActionData = {
ethValue: BigNumber;
contractAddress: string;
encodedFunction: string;
};
export type ActionDataDto = {
ethValue: string;
contractAddress: string;

View File

@@ -6,7 +6,7 @@ import type { Bundle } from "./types";
import isValidEmptyBundle from "./isValidEmptyBundle";
export default (domain: Uint8Array, chainId: number) =>
(bundle: Bundle): boolean => {
(bundle: Bundle, walletAddress: string): boolean => {
// hubbleBls verifier incorrectly rejects empty bundles
if (isValidEmptyBundle(bundle)) {
return true;
@@ -25,6 +25,8 @@ export default (domain: Uint8Array, chainId: number) =>
BigNumber.from(n2).toHexString(),
BigNumber.from(n3).toHexString(),
]),
bundle.operations.map(encodeMessageForSigning(chainId)),
bundle.operations.map((op) =>
encodeMessageForSigning(chainId)(op, walletAddress),
),
);
};

View File

@@ -0,0 +1,88 @@
import { expect } from "chai";
import {
UnvalidatedMultiNetworkConfig,
getMultiConfig,
} from "../src/MultiNetworkConfig";
const getValue = (networkKey: string, propName: string) =>
`${networkKey}-${propName}`;
const getSingleConfig = (networkKey: string) => ({
parameters: {},
addresses: {
create2Deployer: getValue(networkKey, "create2Deployer"),
precompileCostEstimator: getValue(networkKey, "precompileCostEstimator"),
verificationGateway: getValue(networkKey, "verificationGateway"),
blsLibrary: getValue(networkKey, "blsLibrary"),
blsExpander: getValue(networkKey, "blsExpander"),
utilities: getValue(networkKey, "utilities"),
testToken: getValue(networkKey, "testToken"),
},
auxiliary: {
chainid: 123,
domain: getValue(networkKey, "domain"),
genesisBlock: 456,
deployedBy: getValue(networkKey, "deployedBy"),
version: getValue(networkKey, "version"),
},
});
const network1 = "network1";
const network2 = "network2";
describe("MultiNetworkConfig", () => {
let validConfig: UnvalidatedMultiNetworkConfig;
beforeEach(() => {
validConfig = {
[network1]: getSingleConfig(network1),
[network2]: getSingleConfig(network2),
};
});
describe("getMultiConfig", () => {
it("suceeds with valid config", async () => {
await expect(
getMultiConfig("", async () => JSON.stringify(validConfig)),
).to.eventually.deep.equal(validConfig);
});
it("fails if config is not json", async () => {
await expect(getMultiConfig("", async () => "")).to.eventually.be
.rejected;
});
it("fails if config is empty", async () => {
await expect(getMultiConfig("", async () => "{}")).to.eventually.be
.rejected;
});
it(`fails if ${network1}.addresses.verificationGateway is removed`, async () => {
delete validConfig[network1].addresses.verificationGateway;
await expect(getMultiConfig("", async () => JSON.stringify(validConfig)))
.to.eventually.be.rejected;
});
it(`fails if ${network2}.auxiliary is removed`, async () => {
delete validConfig[network1].auxiliary;
await expect(getMultiConfig("", async () => JSON.stringify(validConfig)))
.to.eventually.be.rejected;
});
it(`fails if ${network1}.addresses.blsLibrary is set to a number`, async () => {
validConfig[network1].addresses.blsLibrary = 1337;
await expect(getMultiConfig("", async () => JSON.stringify(validConfig)))
.to.eventually.be.rejected;
});
it(`fails if ${network2}.auxiliary.chainid is set to a string`, async () => {
validConfig[network1].auxiliary.chainid = "off-the-chain";
await expect(getMultiConfig("", async () => JSON.stringify(validConfig)))
.to.eventually.be.rejected;
});
});
});

View File

@@ -1,5 +1,3 @@
import "source-map-support/register";
import { BigNumber } from "ethers";
import { keccak256, arrayify } from "ethers/lib/utils";
import { expect } from "chai";
@@ -14,6 +12,9 @@ const weiPerToken = BigNumber.from(10).pow(18);
const samples = (() => {
const dummy256HexString = "0x" + "0123456789".repeat(10).slice(0, 64);
const contractAddress = dummy256HexString;
// Random addresses
const walletAddress = "0x1337AF0f4b693fd1c36d7059a0798Ff05a60DFFE";
const otherWalletAddress = "0x42C8157D539825daFD6586B119db53761a2a91CD";
const bundleTemplate: Operation = {
nonce: BigNumber.from(123),
@@ -39,6 +40,8 @@ const samples = (() => {
bundleTemplate,
privateKey,
otherPrivateKey,
walletAddress,
otherWalletAddress,
};
})();
@@ -49,23 +52,24 @@ describe("index", () => {
domain,
});
const { bundleTemplate, privateKey, otherPrivateKey } = samples;
const { bundleTemplate, privateKey, otherPrivateKey, walletAddress } =
samples;
const bundle = sign(bundleTemplate, privateKey);
const bundle = sign(bundleTemplate, privateKey, walletAddress);
expect(bundle.signature).to.deep.equal([
"0x0f8af80a400b731f4f2ddcd29816f296cca75e34816d466512a703631de3bb69",
"0x023d76b485531a8dbc087b2d6f25563ad7f6d81d25f5f123186d0ec26da5e2d0",
"0x2c1b0dc6643375e05a6f2ba3d23b1ce941253010b13a127e22f5db647dc37952",
"0x0338f96fc67ce194a74a459791865ac2eb304fc214fd0962775078d12aea5b7e",
]);
expect(verify(bundle)).to.equal(true);
expect(verify(bundle, walletAddress)).to.equal(true);
const bundleBadSig = {
...bundle,
signature: sign(bundleTemplate, otherPrivateKey).signature,
signature: sign(bundleTemplate, otherPrivateKey, walletAddress).signature,
};
expect(verify(bundleBadSig)).to.equal(false);
expect(verify(bundleBadSig, walletAddress)).to.equal(false);
const bundleBadMessage: Bundle = {
senderPublicKeys: bundle.senderPublicKeys,
@@ -85,7 +89,7 @@ describe("index", () => {
signature: bundle.signature,
};
expect(verify(bundleBadMessage)).to.equal(false);
expect(verify(bundleBadMessage, walletAddress)).to.equal(false);
});
it("aggregates transactions", async () => {
@@ -94,38 +98,50 @@ describe("index", () => {
domain,
});
const { bundleTemplate, privateKey } = samples;
const {
bundleTemplate,
privateKey,
otherPrivateKey,
walletAddress,
otherWalletAddress,
} = samples;
const bundle1 = sign(bundleTemplate, privateKey);
const bundle2 = aggregate([bundle1, bundle1]);
const bundle1 = sign(bundleTemplate, privateKey, walletAddress);
const bundle2 = sign(bundleTemplate, otherPrivateKey, otherWalletAddress);
const aggBundle = aggregate([bundle1, bundle2]);
expect(bundle2.signature).to.deep.equal([
"0x0008678ea56953fdca1b007b2685d3ed164b11de015f0a87ee844860c8e6cf30",
"0x2bc51003125b2da84a01e639c3c2be270a9b93ed82498bffbead65c6f07df708",
expect(aggBundle.signature).to.deep.equal([
"0x2319fc81d339dce4678c73429dfd2f11766742ed1e41df5a2ba2bf4863d877b5",
"0x1bb25c15ad1f2f967a80a7a65c7593fcd66b59bf092669707baf2db726e8e714",
]);
expect(verify(bundle2)).to.equal(true);
expect(verify(bundle1, walletAddress)).to.equal(true);
expect(verify(bundle2, otherWalletAddress)).to.equal(true);
const bundle2BadMessage: Bundle = {
...bundle2,
expect(verify(bundle1, otherWalletAddress)).to.equal(false);
expect(verify(bundle2, walletAddress)).to.equal(false);
const aggBundleBadMessage: Bundle = {
...aggBundle,
operations: [
bundle2.operations[0],
aggBundle.operations[0],
{
...bundle2.operations[1],
...aggBundle.operations[1],
// Pretend this client signed to pay a million tokens
actions: [
{
...bundle2.operations[1].actions[0],
...aggBundle.operations[1].actions[0],
ethValue: weiPerToken.mul(1000000),
},
...bundle2.operations[1].actions.slice(1),
...aggBundle.operations[1].actions.slice(1),
],
},
],
};
expect(verify(bundle2BadMessage)).to.equal(false);
expect(verify(aggBundleBadMessage, walletAddress)).to.equal(false);
expect(verify(aggBundleBadMessage, otherWalletAddress)).to.equal(false);
});
it("can aggregate transactions which already have multiple subTransactions", async () => {
@@ -134,7 +150,7 @@ describe("index", () => {
domain,
});
const { bundleTemplate, privateKey } = samples;
const { bundleTemplate, privateKey, walletAddress } = samples;
const bundles = Range(4).map((i) =>
sign(
@@ -148,6 +164,7 @@ describe("index", () => {
],
},
privateKey,
walletAddress,
),
);
@@ -156,7 +173,7 @@ describe("index", () => {
const aggAggBundle = aggregate([aggBundle1, aggBundle2]);
expect(verify(aggAggBundle)).to.equal(true);
expect(verify(aggAggBundle, walletAddress)).to.equal(true);
});
it("generates expected publicKeyStr", async () => {
@@ -196,6 +213,6 @@ describe("index", () => {
const emptyBundle = aggregate([]);
expect(verify(emptyBundle)).to.equal(true);
expect(verify(emptyBundle, samples.walletAddress)).to.equal(true);
});
});

View File

@@ -0,0 +1,4 @@
import chai from "chai";
import chaiAsPromised from "chai-as-promised";
chai.use(chaiAsPromised);

View File

@@ -1 +0,0 @@
../typechain

View File

@@ -0,0 +1 @@
../typechain-types

View File

@@ -2,6 +2,13 @@
# yarn lockfile v1
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
"@ethersproject/abi@5.5.0", "@ethersproject/abi@^5.5.0":
version "5.5.0"
resolved "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.5.0.tgz"
@@ -708,6 +715,24 @@
"@ethersproject/properties" "^5.6.0"
"@ethersproject/strings" "^5.6.0"
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@0.3.9":
version "0.3.9"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@thehubbleproject/bls@^0.5.1":
version "0.5.1"
resolved "https://registry.yarnpkg.com/@thehubbleproject/bls/-/bls-0.5.1.tgz#6b0565f56fc9c8896dcf3c8f0e2214b69a06167f"
@@ -716,20 +741,52 @@
ethers "^5.5.3"
mcl-wasm "^1.0.0"
"@types/chai@^4.3.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.0.tgz#23509ebc1fa32f1b4d50d6a66c4032d5b8eaabdc"
integrity sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==
"@tsconfig/node10@^1.0.7":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==
"@types/mocha@^9.1.0":
version "9.1.0"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.0.tgz#baf17ab2cca3fcce2d322ebc30454bff487efad5"
integrity sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==
"@tsconfig/node12@^1.0.7":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
"@ungap/promise-all-settled@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
"@tsconfig/node14@^1.0.0":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
"@tsconfig/node16@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e"
integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==
"@types/chai-as-promised@^7.1.5":
version "7.1.5"
resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz#6e016811f6c7a64f2eed823191c3a6955094e255"
integrity sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==
dependencies:
"@types/chai" "*"
"@types/chai@*", "@types/chai@^4.3.3":
version "4.3.3"
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07"
integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==
"@types/mocha@^10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.0.tgz#3d9018c575f0e3f7386c1de80ee66cc21fbb7a52"
integrity sha512-rADY+HtTOA52l9VZWtgQfn4p+UDVM2eDVkMZT1I6syp0YKxW2F9v+0pbRZLsvskhQv/vMb6ZfCay81GHbz5SHg==
acorn-walk@^8.1.1:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
acorn@^8.4.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
aes-js@3.0.0:
version "3.0.0"
@@ -761,6 +818,11 @@ anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@@ -799,6 +861,13 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
dependencies:
balanced-match "^1.0.0"
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@@ -826,6 +895,13 @@ camelcase@^6.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e"
integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==
chai-as-promised@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0"
integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==
dependencies:
check-error "^1.0.2"
chai@^4.3.6:
version "4.3.6"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c"
@@ -893,10 +969,15 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
debug@4.3.3:
version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
debug@4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
@@ -917,6 +998,11 @@ diff@5.0.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
elliptic@6.5.4:
version "6.5.4"
resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz"
@@ -1076,11 +1162,6 @@ glob@7.2.0:
once "^1.3.0"
path-is-absolute "^1.0.0"
growl@1.10.5:
version "1.10.5"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
@@ -1160,11 +1241,6 @@ is-unicode-supported@^0.1.0:
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
js-sha3@0.8.0:
version "0.8.0"
resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz"
@@ -1199,6 +1275,11 @@ loupe@^2.3.1:
dependencies:
get-func-name "^2.0.0"
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
mcl-wasm@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mcl-wasm/-/mcl-wasm-1.0.1.tgz#b1f76785be72d33b7ccb2ae6c3f4760949a8ebb7"
@@ -1214,12 +1295,12 @@ minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
minimatch@4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4"
integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==
minimatch@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
dependencies:
brace-expansion "^1.1.7"
brace-expansion "^2.0.1"
minimatch@^3.0.4:
version "3.0.4"
@@ -1228,32 +1309,29 @@ minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
mocha@^9.2.2:
version "9.2.2"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9"
integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==
mocha@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.1.0.tgz#dbf1114b7c3f9d0ca5de3133906aea3dfc89ef7a"
integrity sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==
dependencies:
"@ungap/promise-all-settled" "1.1.2"
ansi-colors "4.1.1"
browser-stdout "1.3.1"
chokidar "3.5.3"
debug "4.3.3"
debug "4.3.4"
diff "5.0.0"
escape-string-regexp "4.0.0"
find-up "5.0.0"
glob "7.2.0"
growl "1.10.5"
he "1.2.0"
js-yaml "4.1.0"
log-symbols "4.1.0"
minimatch "4.2.1"
minimatch "5.0.1"
ms "2.1.3"
nanoid "3.3.1"
nanoid "3.3.3"
serialize-javascript "6.0.0"
strip-json-comments "3.1.1"
supports-color "8.1.1"
which "2.0.2"
workerpool "6.2.0"
workerpool "6.2.1"
yargs "16.2.0"
yargs-parser "20.2.4"
yargs-unparser "2.0.0"
@@ -1268,10 +1346,10 @@ ms@2.1.3:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
nanoid@3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
nanoid@3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
@@ -1410,27 +1488,44 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
ts-node@^10.9.1:
version "10.9.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
dependencies:
"@cspotcode/source-map-support" "^0.8.0"
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
"@tsconfig/node16" "^1.0.2"
acorn "^8.4.1"
acorn-walk "^8.1.1"
arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
type-detect@^4.0.0, type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
typescript@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4"
integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==
typescript@^4.8.4:
version "4.8.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
which@2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
isexe "^2.0.0"
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
workerpool@6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"
integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==
workerpool@6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
wrap-ansi@^7.0.0:
version "7.0.0"
@@ -1489,6 +1584,11 @@ yargs@16.2.0:
y18n "^5.0.5"
yargs-parser "^20.2.2"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"

View File

@@ -148,16 +148,18 @@ contract BLSWallet is Initializable, IWallet
bool success,
bytes[] memory results
) {
incrementNonce(); // before operation to prevent reentrancy
try this._performOperation(op) returns (
bytes[] memory _results
) {
success = true;
results = _results;
}
catch {
catch (bytes memory returnData) {
success = false;
results = new bytes[](1);
results[0] = returnData;
}
incrementNonce(); // regardless of outcome of operation
}
/**
@@ -183,11 +185,23 @@ contract BLSWallet is Initializable, IWallet
else {
(success, result) = address(a.contractAddress).call(a.encodedFunction);
}
require(success);
if (success == false) {
revert IWallet.ActionError(i, result);
}
results[i] = result;
}
}
function stripMethodId(bytes memory encodedFunction) pure private returns(bytes memory) {
bytes memory params = new bytes(encodedFunction.length - 4);
for (uint256 i=0; i<params.length; i++) {
params[i] = encodedFunction[i+4];
}
return params;
}
function clearApprovedProxyAdminFunctionHash() public onlyTrustedGateway {
approvedProxyAdminFunctionHash = 0;
}

View File

@@ -48,7 +48,9 @@ contract VerificationGateway
event WalletOperationProcessed(
address indexed wallet,
uint256 nonce,
bool result
IWallet.ActionData[] actions,
bool success,
bytes[] results
);
event PendingBLSKeySet(
@@ -86,7 +88,27 @@ contract VerificationGateway
for (uint256 i = 0; i<opLength; i++) {
// construct params for signature verification
messages[i] = messagePoint(bundle.operations[i]);
bytes32 keyHash = keccak256(abi.encodePacked(bundle.senderPublicKeys[i]));
address walletAddress = address(walletFromHash[keyHash]);
if (walletAddress == address(0)) {
walletAddress = address(uint160(uint(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
keyHash,
keccak256(abi.encodePacked(
type(TransparentUpgradeableProxy).creationCode,
abi.encode(
address(blsWalletLogic),
address(walletProxyAdmin),
getInitializeData()
)
))
)))));
}
messages[i] = messagePoint(
walletAddress,
bundle.operations[i]
);
}
bool verified = blsLib.verifyMultiple(
@@ -160,11 +182,23 @@ contract VerificationGateway
// ensure first parameter is the calling wallet address
bytes memory encodedAddress = abi.encode(address(wallet));
uint8 selectorOffset = 4;
for (uint256 i=0; i<32; i++) {
require(
(encodedFunction[selectorOffset+i] == encodedAddress[i]),
"VG: first param to proxy admin is not calling wallet"
);
bytes4 selectorId = bytes4(encodedFunction);
// ensure not calling Ownable functions of ProxyAdmin
require((selectorId != Ownable.transferOwnership.selector)
&& (selectorId != Ownable.renounceOwnership.selector),
"VG: cannot change ownership"
);
if (selectorId != Ownable.owner.selector) {
require(encodedFunction.length >= 32, "VG: Expected admin params");
for (uint256 i=0; i<32; i++) {
require(
(encodedFunction[selectorOffset+i] == encodedAddress[i]),
"VG: first param to proxy admin is not calling wallet"
);
}
}
wallet.setAnyPending();
@@ -273,7 +307,9 @@ contract VerificationGateway
emit WalletOperationProcessed(
address(wallet),
bundle.operations[i].nonce,
successes[i]
bundle.operations[i].actions,
successes[i],
results[i]
);
}
}
@@ -358,6 +394,7 @@ contract VerificationGateway
}
function messagePoint(
address walletAddress,
IWallet.Operation memory op
) internal view returns (
uint256[2] memory
@@ -377,6 +414,7 @@ contract VerificationGateway
BLS_DOMAIN,
abi.encodePacked(
block.chainid,
walletAddress,
op.nonce,
keccak256(encodedActionData)
)

View File

@@ -17,6 +17,8 @@ interface IWallet {
bytes encodedFunction;
}
error ActionError(uint256 actionIndex, bytes errorData);
function initialize(address gateway) external;
function nonce() external returns (uint256);

View File

@@ -1,6 +1,6 @@
import * as dotenv from "dotenv";
import { HardhatUserConfig, task } from "hardhat/config";
import { HardhatUserConfig, task, types } from "hardhat/config";
import "@nomiclabs/hardhat-etherscan";
import "@nomiclabs/hardhat-waffle";
import "@typechain/hardhat";
@@ -22,6 +22,28 @@ task("accounts", "Prints the list of accounts", async (_taskArgs, hre) => {
}
});
// Don't run this unless you really need to...
task("privateKeys", "Prints the private keys for accounts")
.addParam("force", "Whether the command should be run", false, types.boolean)
.setAction(async ({ force }: { force: boolean }, hre) => {
if (!force) {
throw new Error("are you sure you want to run this task? (--force true)");
}
const separator = "-".repeat(3);
console.log(separator);
for (let i = 0; i < accounts.count; i++) {
const wallet = hre.ethers.Wallet.fromMnemonic(
accounts.mnemonic,
`m/44'/60'/0'/0/${i}`,
);
console.log(`${i}: ${wallet.address}`);
console.log(wallet.privateKey);
console.log(separator);
}
});
task("fundDeployer", "Sends ETH to create2Deployer contract from first signer")
.addOptionalParam("amount", "Amount of ETH to send", "1.0")
.setAction(async ({ amount }: { amount: string }, hre) => {
@@ -37,6 +59,23 @@ task("fundDeployer", "Sends ETH to create2Deployer contract from first signer")
await txnRes.wait();
});
task("sendEth", "Sends ETH to an address")
.addParam("address", "Address to send ETH to", undefined, types.string)
.addOptionalParam("amount", "Amount of ETH to send", "1.0")
.setAction(
async ({ address, amount }: { address: string; amount: string }, hre) => {
const [account0] = await hre.ethers.getSigners();
console.log(`${account0.address} -> ${address} ${amount} ETH`);
const txnRes = await account0.sendTransaction({
to: address,
value: hre.ethers.utils.parseEther(amount),
});
await txnRes.wait();
},
);
// Do any needed pre-test setup here.
task("test").setAction(async (_taskArgs, _hre, runSuper) => {
chai.use(chaiAsPromised);
@@ -102,6 +141,11 @@ const config: HardhatUserConfig = {
accounts,
gasPrice: 1408857682, // 287938372,
},
arbitrum_goerli: {
// chainId: 421613
url: process.env.ARBITRUM_GOERLI_URL,
accounts,
},
arbitrum: {
// chainId: 42161
url: process.env.ARBITRUM_URL,

View File

@@ -0,0 +1,19 @@
{
"parameters": {},
"addresses": {
"create2Deployer": "0x036d996D6855B83cd80142f2933d8C2617dA5617",
"precompileCostEstimator": "0x22E4a5251C1F02de8369Dd6f192033F6CB7531A4",
"blsLibrary": "0xF8a11BA6eceC43e23c9896b857128a4269290e39",
"verificationGateway": "0xae7DF242c589D479A5cF8fEA681736e0E0Bb1FB9",
"blsExpander": "0x4473e39a5F33A83B81387bb5F816354F04E724a3",
"utilities": "0x76cE3c1F2E6d87c355560fCbd28ccAcAe03f95F6",
"testToken": "0x5081a39b8A5f0E35a8D959395a630b68B74Dd30f"
},
"auxiliary": {
"chainid": 421613,
"domain": "0x0054159611832e24cdd64c6a133e71d373c5f8553dde6c762e6bffe707ad83cc",
"genesisBlock": 1206441,
"deployedBy": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"version": "bc3d1463f163b742026f951a2574016966b5c857"
}
}

View File

@@ -1,19 +1,19 @@
{
"parameters": {},
"addresses": {
"create2Deployer": "0xc1326d37b446bC7df7b36348C963BFcc8eF98Ce3",
"precompileCostEstimator": "0x7a89f10F307Bd51b81eF8D1B2c5fa74c7E2d006D",
"blsLibrary": "0x6F6a92362EA4299B5668dC4A75282bBFd42D4804",
"verificationGateway": "0x697B3E6258B08201d316b31D69805B5F666b62C8",
"blsExpander": "0xaf6E02eAf7855D587ffDE5c424a0991570b56944",
"utilities": "0x5C176B9F019Bfe90cEc3b2492cC5e20f11c97855",
"testToken": "0x09f2C81263B8C079CcE299B4B5b4C32cba0aA0F9"
},
"auxiliary": {
"chainid": 421611,
"domain": "0x0054159611832e24cdd64c6a133e71d373c5f8553dde6c762e6bffe707ad83cc",
"genesisBlock": 11355502,
"deployedBy": "0xcc12Dd5DefC8BCccAbfBA4bFBFECe09B4EDBF263",
"version": "bea30d9171772faf855cdab909f321ce0834a495"
}
}
"parameters": {},
"addresses": {
"create2Deployer": "0x036d996D6855B83cd80142f2933d8C2617dA5617",
"precompileCostEstimator": "0x22E4a5251C1F02de8369Dd6f192033F6CB7531A4",
"blsLibrary": "0x52ED3BAF9F4b60c67D2796e8ED5f35AfA3c4938a",
"verificationGateway": "0xa15954659EFce154a3B45cE88D8158A02bE2049A",
"blsExpander": "0x1a1C1285a5DB87264Ca8a67075e3a27b304d3fBD",
"utilities": "0xeC098e366368fC2269140053cE899F307A2c9209",
"testToken": "0xAfFFb6c8e061904C2cd69F372D6a8293Db7eC0A8"
},
"auxiliary": {
"chainid": 421611,
"domain": "0x0054159611832e24cdd64c6a133e71d373c5f8553dde6c762e6bffe707ad83cc",
"genesisBlock": 14402717,
"deployedBy": "0x6435e511f8908D5C733898C81831a4A3aFE31D07",
"version": "45def922036c441aa1559419470a131de3ce8ae4"
}
}

View File

@@ -12,48 +12,49 @@
"check-ts": "tsc --noEmit",
"lint": "eslint . --ext .ts",
"test": "hardhat test",
"premerge": "rm -rf artifacts cache typechain && hardhat compile && lint && check-ts && yarn --cwd clients premerge && test"
"premerge": "rm -rf artifacts cache typechain-types && hardhat compile && lint && check-ts && yarn --cwd clients premerge && test"
},
"author": "James Zaki",
"license": "MIT",
"dependencies": {
"@openzeppelin/contracts": "^4.3.2"
"@openzeppelin/contracts": "^4.7.3"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^2.1.3",
"@nomiclabs/hardhat-waffle": "^2.0.0",
"@typechain/ethers-v5": "^7.0.1",
"@typechain/hardhat": "^2.3.0",
"@types/chai": "^4.2.21",
"@nomiclabs/hardhat-ethers": "^2.2.1",
"@nomiclabs/hardhat-etherscan": "^3.1.2",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.3",
"@types/chai": "^4.3.3",
"@types/chai-as-promised": "^7.1.5",
"@types/mocha": "^9.0.0",
"@types/node": "^16.4.13",
"@typescript-eslint/eslint-plugin": "^4.29.1",
"@typescript-eslint/parser": "^4.29.1",
"chai": "^4.2.0",
"@types/mocha": "^10.0.0",
"@types/node": "^18.11.8",
"@typescript-eslint/eslint-plugin": "^5.42.0",
"@typescript-eslint/parser": "^5.42.0",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"dotenv": "^10.0.0",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.23.4",
"dotenv": "^16.0.3",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-promise": "^5.1.0",
"ethereum-waffle": "^3.0.0",
"ethers": "5.5.4",
"hardhat": "^2.6.7",
"hardhat-gas-reporter": "^1.0.4",
"mcl-wasm": "^0.8.0",
"prettier": "^2.3.2",
"prettier-plugin-solidity": "^1.0.0-beta.13",
"solhint": "^3.3.6",
"solidity-coverage": "^0.7.16",
"ts-node": "^10.1.0",
"typechain": "^5.1.2",
"typescript": "^4.3.5",
"web3": "^1.7.0",
"web3-utils": "^1.7.0"
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"ethereum-waffle": "^3.4.4",
"ethers": "^5.7.2",
"hardhat": "^2.12.1",
"hardhat-gas-reporter": "^1.0.9",
"mcl-wasm": "^1.0.3",
"prettier": "^2.7.1",
"prettier-plugin-solidity": "^1.0.0-beta.24",
"solhint": "^3.3.7",
"solidity-coverage": "^0.8.2",
"ts-node": "^10.9.1",
"typechain": "^8.1.0",
"typescript": "^4.8.4",
"web3": "^1.8.0",
"web3-utils": "^1.8.0"
}
}

View File

@@ -1,35 +0,0 @@
/**
* Example usage:
*
* yarn hardhat run scripts/test/send_eth.ts --network gethDev
*/
/* eslint-disable no-process-exit */
import { ethers } from "hardhat";
// Change this to the address you want to send eth to.
// We should bring in an npm package at some point to parse cli args.
const receivingAddress = "0x6266142188e26AfA67Caf6FDD581edc1958d7172";
// Change this to the amount of eth you want to send.
const amountEth = "1.0";
async function main() {
const [account0] = await ethers.getSigners();
console.log(`${account0.address} -> ${receivingAddress} ${amountEth} ETH`);
await account0.sendTransaction({
to: receivingAddress,
value: ethers.utils.parseEther(amountEth),
});
console.log("done");
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => {
process.exit(0);
})
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -4,7 +4,7 @@ import "@nomiclabs/hardhat-ethers";
import { ethers } from "hardhat";
import { Wallet, BigNumber, Contract, ContractFactory } from "ethers";
import deployerContract from "./deployDeployer";
import { Create2Deployer } from "../../typechain";
import { Create2Deployer } from "../../typechain-types";
export default class Create2Fixture {
private constructor(public deployerWallet?: Wallet) {}

View File

@@ -19,7 +19,11 @@ import {
import Range from "./Range";
import assert from "./assert";
import Create2Fixture from "./Create2Fixture";
import { VerificationGateway, BLSOpen, ProxyAdmin } from "../../typechain";
import {
VerificationGateway,
BLSOpen,
ProxyAdmin,
} from "../../typechain-types";
export default class Fixture {
static readonly ECDSA_ACCOUNTS_LENGTH = 5;
@@ -119,7 +123,10 @@ export default class Fixture {
// Perform an empty transaction to trigger wallet creation
await (
await verificationGateway.processBundle(
wallet.sign({ nonce: BigNumber.from(0), actions: [] }),
wallet.sign({
nonce: BigNumber.from(0),
actions: [],
}),
)
).wait();

View File

@@ -3,7 +3,7 @@ import { utils, BigNumber, Signer, Contract } from "ethers";
import { BlsWalletWrapper } from "../../clients/src";
import Fixture from "./Fixture";
import { IERC20 } from "../../typechain";
import { IERC20 } from "../../typechain-types";
export default class TokenHelper {
static readonly initialSupply = utils.parseUnits("1000000");

View File

@@ -3,7 +3,7 @@ import "@nomiclabs/hardhat-ethers";
import { ethers } from "hardhat";
import { Wallet } from "ethers";
import { Create2Deployer } from "../../typechain";
import { Create2Deployer } from "../../typechain-types";
import defaultDeployerWalletHardhat from "./defaultDeployerWallet";
dotenv.config();

View File

@@ -6,7 +6,7 @@ import deployerContract, {
defaultDeployerAddress,
defaultDeployerWallet,
} from "../shared/helpers/deployDeployer";
import { Create2Deployer } from "../typechain";
import { Create2Deployer } from "../typechain-types";
describe("Deployer", async function () {
let Create2Deployer: ContractFactory;

View File

@@ -7,7 +7,7 @@ import { BlsWalletWrapper, Signature } from "../clients/src";
import Fixture from "../shared/helpers/Fixture";
import deployAndRunPrecompileCostEstimator from "../shared/helpers/deployAndRunPrecompileCostEstimator";
import { defaultDeployerAddress } from "../shared/helpers/deployDeployer";
import { BLSWallet, VerificationGateway } from "../typechain";
import { BLSWallet, VerificationGateway } from "../typechain-types";
const signWalletAddress = async (
fx: Fixture,
@@ -18,7 +18,7 @@ const signWalletAddress = async (
const wallet = await BlsWalletWrapper.connect(
signerPrivKey,
fx.verificationGateway.address,
fx.provider,
fx.verificationGateway.provider,
);
return wallet.signMessage(addressMessage);
};
@@ -134,7 +134,14 @@ describe("Recovery", async function () {
});
it("should recover before bls key update", async function () {
await fx.call(wallet1, blsWallet, "setRecoveryHash", [recoveryHash], 1);
let recoveredWalletNonce = 1;
await fx.call(
wallet1,
blsWallet,
"setRecoveryHash",
[recoveryHash],
recoveredWalletNonce++,
);
const attackSignature = await signWalletAddress(
fx,
@@ -149,11 +156,27 @@ describe("Recovery", async function () {
vg,
"setBLSKeyForWallet",
[attackSignature, walletAttacker.PublicKey()],
1,
recoveredWalletNonce++,
);
const pendingKey = await Promise.all(
[0, 1, 2, 3].map(async (i) =>
(await vg.pendingBLSPublicKeyFromHash(hash1, i)).toHexString(),
),
);
expect(pendingKey).to.deep.equal(walletAttacker.PublicKey());
await fx.advanceTimeBy(safetyDelaySeconds / 2); // wait half the time
await fx.call(wallet1, vg, "setPendingBLSKeyForWallet", [], 2);
// NB: advancing the time makes an empty tx with lazywallet[1]
// Here this seems to be wallet2, not wallet (wallet being recovered)
// recoveredWalletNonce++
await fx.call(
wallet1,
vg,
"setPendingBLSKeyForWallet",
[],
recoveredWalletNonce++,
);
const addressSignature = await signWalletAddress(
fx,
@@ -173,6 +196,9 @@ describe("Recovery", async function () {
expect(await vg.walletFromHash(hash2)).to.eql(wallet1.address);
await fx.advanceTimeBy(safetyDelaySeconds / 2 + 1); // wait remainder the time
// NB: advancing the time makes an empty tx with lazywallet[1]
// Here this seems to be wallet1, the wallet being recovered
recoveredWalletNonce++;
// check attacker's key not set after waiting full safety delay
await fx.call(
@@ -182,12 +208,23 @@ describe("Recovery", async function () {
[],
await walletAttacker.Nonce(),
);
/**
* TODO (merge-ok)
*
* Event thought typechain types are symlinked between contracts
* and clients, there appears to be a mismatch here passing in
* VerificationGateway. This may be due to differing typescript
* versions between contracts and clients, or something else.
*
* For now cast to 'any'.
*/
await wallet2.syncWallet(vg as any);
await fx.call(
wallet2,
vg,
"setPendingBLSKeyForWallet",
[],
await wallet2.Nonce(),
recoveredWalletNonce++, // await wallet2.Nonce(),
);
expect(await vg.walletFromHash(hash1)).to.not.equal(blsWallet.address);
@@ -199,10 +236,10 @@ describe("Recovery", async function () {
// // verify recovered bls key can successfully call wallet-only function (eg setTrustedGateway)
const res = await fx.callStatic(
wallet2,
fx.verificationGateway,
vg,
"setTrustedBLSGateway",
[hash2, fx.verificationGateway.address],
3,
[hash2, vg.address],
recoveredWalletNonce, // await wallet2.Nonce(),
);
expect(res.successes[0]).to.equal(true);
});
@@ -248,4 +285,52 @@ describe("Recovery", async function () {
.recoverWallet(addressSignature, hashAttacker, salt, wallet1Key),
).to.be.rejectedWith("VG: Signature not verified for wallet address");
});
it("should NOT allow a bundle to be executed on a wallet with the same BLS pubkey but different address (replay attack)", async function () {
// Run empty operation on wallet 2 to align nonces after recovery.
const emptyBundle = wallet2.sign({
nonce: await wallet2.Nonce(),
actions: [],
});
const emptyBundleTxn = await fx.verificationGateway.processBundle(
emptyBundle,
);
await emptyBundleTxn.wait();
// Set wallet 1's pubkey to wallet 2's through recovery
// This will also unregister wallet 2 from VG
await fx.call(wallet1, blsWallet, "setRecoveryHash", [recoveryHash], 1);
const addressSignature = await signWalletAddress(
fx,
wallet1.address,
wallet2.privateKey,
);
const recoveryTxn = await fx.verificationGateway
.connect(recoverySigner)
.recoverWallet(addressSignature, hash1, salt, wallet2.PublicKey());
await recoveryTxn.wait();
const [wallet1PubkeyHash, wallet2PubkeyHash, wallet1Nonce, wallet2Nonce] =
await Promise.all([
vg.hashFromWallet(wallet1.address),
vg.hashFromWallet(wallet2.address),
wallet1.Nonce(),
wallet2.Nonce(),
]);
expect(wallet1PubkeyHash).to.eql(hash2);
expect(wallet2PubkeyHash).to.eql(hash2);
expect(wallet1Nonce.toNumber()).to.eql(wallet2Nonce.toNumber());
// Attempt to run a bundle from wallet 2 through wallet 1
// Signiuture verification should fail as addresses differ
const invalidBundle = wallet2.sign({
nonce: await wallet2.Nonce(),
actions: [],
});
await expect(
fx.verificationGateway.processBundle(invalidBundle),
).to.be.rejectedWith("VG: Sig not verified");
});
});

View File

@@ -1,10 +1,14 @@
import { expect } from "chai";
import { BigNumber } from "ethers";
import { BigNumber, ContractReceipt } from "ethers";
import { solidityPack } from "ethers/lib/utils";
import { ethers, network } from "hardhat";
import { BLSOpen, ProxyAdmin } from "../typechain";
import { ActionData, BlsWalletWrapper } from "../clients/src";
import { BLSOpen, ProxyAdmin } from "../typechain-types";
import {
ActionData,
BlsWalletWrapper,
getOperationResults,
} from "../clients/src";
import Fixture from "../shared/helpers/Fixture";
import deployAndRunPrecompileCostEstimator from "../shared/helpers/deployAndRunPrecompileCostEstimator";
import { defaultDeployerAddress } from "../shared/helpers/deployDeployer";
@@ -14,6 +18,25 @@ import {
} from "../shared/helpers/callProxyAdmin";
import Create2Fixture from "../shared/helpers/Create2Fixture";
const expectOperationsToSucceed = (txnReceipt: ContractReceipt) => {
const opResults = getOperationResults(txnReceipt);
for (const opRes of opResults) {
expect(opRes.success).to.eql(true);
expect(opRes.error).to.eql(undefined);
}
};
const expectOperationFailure = (
txnReceipt: ContractReceipt,
errorMessage: string,
) => {
const opResults = getOperationResults(txnReceipt);
expect(opResults).to.have.lengthOf(1);
expect(opResults[0].success).to.equal(false);
expect(opResults[0].error.actionIndex.toNumber()).to.eql(0);
expect(opResults[0].error.message).to.eql(errorMessage);
};
describe("Upgrade", async function () {
this.beforeAll(async function () {
// deploy the deployer contract for the transient hardhat network
@@ -47,10 +70,11 @@ describe("Upgrade", async function () {
const wallet = await fx.lazyBlsWallets[0]();
// prepare call
await proxyAdminCall(fx, wallet, "upgrade", [
const txnReceipt1 = await proxyAdminCall(fx, wallet, "upgrade", [
wallet.address,
mockWalletUpgraded.address,
]);
expectOperationsToSucceed(txnReceipt1);
// Advance time one week
const latestTimestamp = (await ethers.provider.getBlock("latest"))
@@ -62,10 +86,11 @@ describe("Upgrade", async function () {
]);
// make call
await proxyAdminCall(fx, wallet, "upgrade", [
const txnReceipt2 = await proxyAdminCall(fx, wallet, "upgrade", [
wallet.address,
mockWalletUpgraded.address,
]);
expectOperationsToSucceed(txnReceipt2);
const newBLSWallet = MockWalletUpgraded.attach(wallet.address);
await (await newBLSWallet.setNewData(wallet.address)).wait();
@@ -99,7 +124,7 @@ describe("Upgrade", async function () {
const wallet = await BlsWalletWrapper.connect(
blsSecret,
fx.verificationGateway.address,
fx.provider,
fx.verificationGateway.provider,
);
// Sign simple address message
const addressMessage = solidityPack(["address"], [walletAddress]);
@@ -114,10 +139,13 @@ describe("Upgrade", async function () {
const changeProxyAction = bundle.operations[0].actions[0];
// prepare call
await proxyAdminCall(fx, walletOldVg, "changeProxyAdmin", [
walletAddress,
proxyAdmin2Address,
]);
const txnReceipt = await proxyAdminCall(
fx,
walletOldVg,
"changeProxyAdmin",
[walletAddress, proxyAdmin2Address],
);
expectOperationsToSucceed(txnReceipt);
// Advance time one week
await fx.advanceTimeBy(safetyDelaySeconds + 1);
@@ -284,13 +312,13 @@ describe("Upgrade", async function () {
const wallet1 = await BlsWalletWrapper.connect(
lazyWallet1.privateKey,
vg1.address,
fx.provider,
vg1.provider,
);
const wallet2 = await BlsWalletWrapper.connect(
lazyWallet2.privateKey,
vg1.address,
fx.provider,
vg1.provider,
);
const hash1 = wallet1.blsWalletSigner.getPublicKeyHash(wallet1.privateKey);
@@ -347,4 +375,46 @@ describe("Upgrade", async function () {
expect(await vg1.walletFromHash(hash2)).to.equal(wallet1.address);
expect(await vg1.hashFromWallet(wallet1.address)).to.equal(hash2);
});
it("should NOT allow walletAdminCall where first param is not calling wallet", async function () {
const wallet1 = await fx.lazyBlsWallets[0]();
const wallet2 = await fx.lazyBlsWallets[1]();
const txnReceipt = await proxyAdminCall(fx, wallet1, "upgrade", [
wallet2.address,
wallet2.address,
]);
expectOperationFailure(
txnReceipt,
"VG: first param to proxy admin is not calling wallet",
);
});
it("should NOT allow walletAdminCall to ProxyAdmin.transferOwnership", async function () {
const wallet = await fx.lazyBlsWallets[0]();
const txnReceipt = await proxyAdminCall(fx, wallet, "transferOwnership", [
wallet.address,
]);
expectOperationFailure(txnReceipt, "VG: cannot change ownership");
});
it("should NOT allow walletAdminCall to ProxyAdmin.renounceOwnership", async function () {
const wallet = await fx.lazyBlsWallets[0]();
const txnReceipt = await proxyAdminCall(
fx,
wallet,
"renounceOwnership",
[],
);
expectOperationFailure(txnReceipt, "VG: cannot change ownership");
});
it("call function with no params", async function () {
const wallet = await fx.lazyBlsWallets[0]();
const txnReceipt = await proxyAdminCall(fx, wallet, "owner", []);
expectOperationsToSucceed(txnReceipt);
});
});

View File

@@ -5,11 +5,12 @@ import { ethers, network } from "hardhat";
import Fixture from "../shared/helpers/Fixture";
import TokenHelper from "../shared/helpers/TokenHelper";
import { BigNumber } from "ethers";
import { BigNumber, ContractReceipt } from "ethers";
import { parseEther, solidityPack } from "ethers/lib/utils";
import deployAndRunPrecompileCostEstimator from "../shared/helpers/deployAndRunPrecompileCostEstimator";
// import splitHex256 from "../shared/helpers/splitHex256";
import { defaultDeployerAddress } from "../shared/helpers/deployDeployer";
import { getOperationResults } from "../clients/src";
describe("WalletActions", async function () {
if (`${process.env.DEPLOYER_DEPLOYMENT}` === "true") {
@@ -288,11 +289,21 @@ describe("WalletActions", async function () {
const th = new TokenHelper(fx);
const [sender, recipient] = await th.walletTokenSetup();
await (
const r: ContractReceipt = await (
await fx.verificationGateway.processBundle(
sender.sign({
nonce: await sender.Nonce(),
actions: [
// Send tokens to recipient.
{
ethValue: 0,
contractAddress: th.testToken.address,
encodedFunction: th.testToken.interface.encodeFunctionData(
"transfer",
[recipient.address, th.userStartAmount],
),
},
// Try to send ourselves a lot of tokens from address zero, which
// obviously shouldn't work.
{
@@ -307,21 +318,16 @@ describe("WalletActions", async function () {
],
),
},
// Send tokens to recipient.
{
ethValue: 0,
contractAddress: th.testToken.address,
encodedFunction: th.testToken.interface.encodeFunctionData(
"transfer",
[recipient.address, th.userStartAmount],
),
},
],
}),
)
).wait();
const opResults = getOperationResults(r);
expect(opResults).to.have.lengthOf(1);
expect(opResults[0].error.actionIndex.toNumber()).to.eql(1);
expect(opResults[0].error.message).to.eql("ERC20: insufficient allowance");
const recipientBalance = await th.testToken.balanceOf(recipient.address);
// Should be unchanged because the operation that would have added tokens

View File

@@ -7,6 +7,6 @@
"outDir": "dist",
"declaration": true
},
"include": ["./scripts", "./test", "./typechain"],
"include": ["./scripts", "./test", "./typechain-types"],
"files": ["./hardhat.config.ts"]
}

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,7 @@ services:
command: >
--datadir dev-chain/
--http
--http.api eth,web3,personal,net
--http.addr=0.0.0.0
--http.vhosts='*'
--dev

View File

@@ -1,7 +1,9 @@
## Docs
- [See an overview of BLS Wallet & how the components work together](./docs/system_overview.md)
- [Use BLS Wallet in a browser/NodeJS/Deno app](./docs/use_bls_wallet_clients.md)
- [See an overview of BLS Wallet & how the components work together](./system_overview.md)
- [Use BLS Wallet in a browser/NodeJS/Deno app](./use_bls_wallet_clients.md)
- [Use BLS Wallet in your L2 dApp for cheaper, multi action transactions](./use_bls_wallet_dapp.md)
- Setup the BLS Wallet components for:
- [Local develeopment](./docs/local_development.md)
- [Remote development](./docs/remote_development.md)
- [Local develeopment](./local_development.md)
- [Remote development](./remote_development.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

View File

@@ -1,6 +1,7 @@
# Local Development
These steps will setup this repo on your machine for local development for the majority of the components in this repo.
By default the extension will connect to contracts already deployed on Arbitrum Nitro testnet and a public Aggregator running on https://arbitrum-goerli.blswallet.org/
If you would like to target a remote network instead, add the addtional steps in [Remote Development](./remote_development.md) as well.
## Dependencies
@@ -18,12 +19,17 @@ If you would like to target a remote network instead, add the addtional steps in
## Setup
Install the latest Node 16. If using nvm to manage node versions, run this in the root directory:
```sh
nvm install
```
Run the repo setup script
```sh
./setup.ts
```
Then choose to target either a local Hardhat node or the Arbitrum Testnet.
Then choose to target either a local Hardhat node or the Arbitrum Testnet. If you choose to run on Arbitrum Goerli skip ahead until tests.
### Chain (RPC Node)
@@ -45,7 +51,14 @@ Deploy all `bls-wallet` contracts.
yarn hardhat run scripts/deploy_all.ts --network gethDev
```
## Run
## Aggregator
make these changes in aggregator > .env
RPC_URL=http://localhost:8545
NETWORK_CONFIG_PATH=../contracts/networks/local.json
In a seperate terminal/shell instance
```sh
docker-compose up -d postgres # Or see local postgres instructions in ./aggregator/README.md#PostgreSQL
@@ -59,6 +72,16 @@ cd ./extension
yarn run dev:chrome # or dev:firefox, dev:opera
```
## Extension
make these changes in extension > .env
```
AGGREGATOR_URL=http://localhost:3000/
DEFAULT_CHAIN_ID=31337
NETWORK_CONFIG=./contracts/networks/local.json
```
### Chrome
1. Go to Chrome's [extension page](chrome://extensions).
@@ -85,6 +108,20 @@ cd ../extension
yarn link bls-wallet-clients
```
If you would like live updates to from the clients package to trigger reloads of the extension, be sure to comment out this section of `./extension/weback.config.js`:
```javascript
...
module.exports = {
...
watchOptions: {
// Remove this if you want to watch for changes
// from a linked package, such as bls-wallet-clients.
ignored: /node_modules/,
},
...
};
```
### aggregator
You will need to push up an `@experimental` version to 'bls-wallet-clients' on npm and update the version in `./aggregtor/src/deps.ts` until a local linking solution for deno is found. See https://github.com/alephjs/esm.sh/discussions/216 for details.

View File

@@ -8,7 +8,7 @@ Follow the instructions for [Local Development](./local_development.md), replaci
### Deployer account
BLS Wallet contract deploys use `CREATE2` to maintain consistent addresses across networks. As such, a create2 deployer contract is used and listed in `./contracts/.env` under the environment variables `DEPLOYER_MNEMONIC` & `DEPLOYER_SET_INDEX`. The HD address will need to be funded in order to deploy the contracts.
BLS Wallet contract deploys use `CREATE2` to maintain consistent addresses across networks. As such, a create2 deployer contract is used and listed in `./contracts/.env` under the environment variables `DEPLOYER_MNEMONIC` & `DEPLOYER_SET_INDEX`. The hierarchical deterministic (HD) wallet address will need to be funded in order to deploy the contracts.
If you do not need consistent addresses, for example on a local or testnet network, you can replace the `DEPLOYER_MNEMONIC` with another seed phrase which already has a funded account.
@@ -66,40 +66,27 @@ PRIVATE_KEY_ADMIN=PK1
### Extension
Check the [controller constants file](../extension/source/Controllers/constants.ts) to see if your network is already added. If not, you will need to add chainid & supported networks entries for your network/chain. These changes can be committed.
Check the [`config.json` file](../extension//config.json) to see if your network is already added. If not, you will need to add the relevant properties for your network/chain. These changes can be committed.
Then, update this value in `./extension/.env`.
```
...
## Example: Arbitrum Testnet (Arbitrum Goerli Testnet)
DEFAULT_CHAIN_ID=YOUR_CHAIN_ID
...
```
You will need two ETH addresses with Abitrum Goerli ETH and their private keys (PRIVATE_KEY_AGG & PRIVATE_KEY_ADMIN) for running the aggregator. It is **NOT** recommended that you use any primary wallets with ETH Mainnet assets.
## Run
Follow the remaing instruction in [Local Development](./local_development.md) starting with the `Run` section.
## Example: Arbitrum Testnet (Rinkeby Arbitrum Testnet)
You will need two ETH addresses with Rinkeby ETH and their private keys (PK0 & PK1) for running the aggregator. It is NOT recommended that you use any primary wallets with ETH Mainnet assets.
You can get Rinkeby ETH at https://app.mycrypto.com/faucet, and transfer it into the Arbitrum testnet via https://bridge.arbitrum.io/. Make sure when doing so that your network is set to Rinkeby in your web3 wallet extension, such as MetaMask.
You can get Goerli ETH at https://goerlifaucet.com/ or https://app.mycrypto.com/faucet, and transfer it into the Arbitrum testnet via https://bridge.arbitrum.io/. Make sure when doing so that your network is set to Goerli in your web3 wallet extension, such as MetaMask.
Update these values in `./aggregator/.env`.
```
RPC_URL=https://rinkeby.arbitrum.io/rpc
RPC_URL=https://goerli-rollup.arbitrum.io/rpc
...
NETWORK_CONFIG_PATH=../contracts/networks/arbitrum-testnet.json
NETWORK_CONFIG_PATH=../contracts/networks/arbitrum-goerli.json
PRIVATE_KEY_AGG=PK0
PRIVATE_KEY_ADMIN=PK1
...
```
And then update this value in `./extension/.env`.
```
And then ensure the `defaultNetwork` value in `./extension/config.json` is set to `arbitrum-goerli`.
```json
...
DEFAULT_CHAIN_ID=421611
"defaultNetwork": "arbitrum-goerli",
...
```
```

View File

@@ -1,8 +1,10 @@
# System Overview
## Layer 2 Amsterdam April 2022 Presentation
## Presentations
https://youtu.be/Ke4L_PXIi8M?t=22380
- [Layer 2 Amsterdam April 2022](https://youtu.be/Ke4L_PXIi8M?t=22380)
- [EthPrague June 2022](https://www.youtube.com/watch?v=F4gNVq07CHc)
- [PSE Learn & Share July 20220](https://www.youtube.com/watch?v=FRw_B8bd4VI)
## Overview Diagram

View File

@@ -8,17 +8,15 @@ This walkthrough will show you how to submit an ERC20 transfer to the BLS Wallet
# npm
npm install bls-wallet-clients
# yarn
yarn install bls-wallet-clients
yarn add bls-wallet-clients
# deno in example further below
```
You will also need to have [ethers](https://docs.ethers.io) installed.
## Import
```typescript
import { providers } from "ethers";
import { Aggregator, BLSWalletWrapper, getConfig } from "bls-wallet-clients";
import { Aggregator, BlsWalletWrapper, getConfig } from "bls-wallet-clients";
```
### Deno
@@ -27,7 +25,7 @@ You can use [esm.sh](https://esm.sh/) or a similar service to get Deno compatibl
```typescript
import { providers } from "https://esm.sh/ethers@latest";
import { Aggregator, BLSWalletWrapper, getConfig } from "https://esm.sh/bls-wallet-clients@latest";
import { Aggregator, BlsWalletWrapper, getConfig } from "https://esm.sh/bls-wallet-clients@latest";
```
## Get Deployed Contract Addresses
@@ -39,38 +37,49 @@ If you would like to deploy to a remote network, see [Remote development](./remo
## Send a transaction
```typescript
import { readFile } from "fs/promises";
import { readFile } from 'fs/promises';
// import fetch from 'node-fetch'; // Add this if using nodejs<18
import { ethers, providers } from 'ethers';
import { Aggregator, BlsWalletWrapper, getConfig } from 'bls-wallet-clients';
// globalThis.fetch = fetch; // Add this if using nodejs<18
// Instantiate a provider via browser extension, such as Metamask
const provider = providers.Web3Provider(window.ethereum);
// const provider = new providers.Web3Provider(window.ethereum);
// Or via RPC
const provider = providers.JsonRpcProvider();
const provider = new providers.JsonRpcProvider('https://goerli-rollup.arbitrum.io/rpc');
// See https://docs.ethers.io/v5/getting-started/ for more options
// Get the deployed contract addresses for the network.
// Here, we will get the Arbitrum testnet.
// See local_development.md for deploying locally and
// remote_development.md for deploying to a remote network.
const netCfg = await getConfig("../contracts/networks/arbitrum-testnet.json", async (path) => readFile(path));
const netCfg = await getConfig(
'../contracts/networks/arbitrum-goerli.json',
async (path) => readFile(path),
);
const privateKey = "0x...";
// 32 random bytes
const privateKey = '0x0001020304050607080910111213141516171819202122232425262728293031';
// Note that if a wallet doesn't yet exist, it will be
// lazily created on the first transaction.
const wallet = await BlsWallerWrapper.connect(
const wallet = await BlsWalletWrapper.connect(
privateKey,
netCfg.contracts.verificationGateway,
netCfg.addresses.verificationGateway,
provider
);
const erc20Address = "0x...";
const erc20Address = netCfg.addresses.testToken; // Or some other ERC20 token
const erc20Abi = [
"function transfer(address to, uint amount) returns (bool)",
'function mint(address to, uint amount) returns (bool)',
];
const erc20 = new ethers.Contract(erc20Address, erc20Abi, provider);
const recipientAddress = "0x...";
console.log('Contract wallet:', wallet.address);
console.log('Test token:', erc20.address);
const nonce = await wallet.Nonce();
// All of the actions in a bundle are atomic, if one
// action fails they will all fail.
@@ -78,16 +87,41 @@ const bundle = wallet.sign({
nonce,
actions: [
{
contract: erc20,
method: "transfer",
args: [recipientAddress, ethers.utils.parseUnits("1", 18)],
// Mint ourselves one test token
ethValue: 0,
contractAddress: erc20.address,
encodedFunction: erc20.interface.encodeFunctionData(
'mint',
[wallet.address, ethers.utils.parseUnits('1', 18)],
),
},
],
});
const aggregator = new Aggregator("https://rinkarby.blswallet.org");
await aggregator.add(bundle);
const aggregator = new Aggregator('https://arbitrum-goerli.blswallet.org');
console.log('Sending bundle to the aggregator');
const addResult = await aggregator.add(bundle);
if ('failures' in addResult) {
throw new Error(addResult.failures.join('\n'));
}
console.log('Bundle hash:', addResult.hash);
const checkConfirmation = async () => {
console.log('Checking for confirmation')
const maybeReceipt = await aggregator.lookupReceipt(addResult.hash);
if (maybeReceipt === undefined) {
return;
}
console.log('Confirmed in block', maybeReceipt.blockNumber);
provider.off('block', checkConfirmation);
};
provider.on('block', checkConfirmation);
```
## More

120
docs/use_bls_wallet_dapp.md Normal file
View File

@@ -0,0 +1,120 @@
# Use BLS Wallet In Your L2 dApp
This guide will show you how to use BLS Wallet in your L2 dApp (Layer 2 decentralized application) so you can utilize multi-action transactions.
## Download, Install, & Setup Quill
[Quill](../extension/) is a protoype browser extension wallet which intergrates [bls-wallet-clients](../contracts/clients/) to communicate with the BLS Wallet smart contracts & transaction aggregator. It supports most of the functionality in [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193).
Currently, we have the contracts deployed to the networks/chains listed [here](https://github.com/web3well/bls-wallet/tree/main/contracts/networks). If your desired network isn't there, you can use the [Remote Development](./remote_development.md) contract deployment intrsuctions or request a network deploy by [opening an issue](https://github.com/web3well/bls-wallet/issues/new) or [starting a discussion](https://github.com/web3well/bls-wallet/discussions/new).
Below are the instructions for 2 ways you can add Quill to your browser.
### Prebuilt
Go to the [releases page](https://github.com/web3well/bls-wallet/releases) and scroll down to the latest release. In the `Assets` section, download the extension for either Chrome, Firefox, or Opera. To install, simply drag and drop the file into your browser on the extensions page or follow instructions for installing extensions from a file for your browser.
### From Repo
Follow the instructions in either [Local Development](./local_development.md) or [Remote Development](./remote_development.md) to setup this repo and install Quill.
### Setup Quill
After installing the extension, Quill will auto-open and guide you through the setup process.
## Connect Your dApp to Quill
Next, connect your dApp to Quill just like you would any other extension wallet.
`ethers.js`
```typescript
import { providers } from 'ethers';
const provider = new providers.Web3Provider(window.ethereum);
await window.ethereum.request({ method: "eth_accounts" });
```
Or similarly with [web3modal](https://github.com/WalletConnect/web3modal#usage) or [rainbow-connect](https://rainbowkit.vercel.app/docs/installation#configure)
## Send Multi-Action Transaction
Finally, you can populate & send your multi-action transaction. In the following example, we will do an approve & swap with a DEX (decentralized exchange) in one transaction.
### Check that window.ethereum Supports Multi-Action Transactions
Since any browser extension wallet could be used, make sure that it is Quill before allowing a multi-action transaction.
```typescript
const areMultiActionTransactionSupported = !!window.ethereum.isQuill;
// Branch here depending on the result
if (areMultiActionTransactionSupported) {
...
}
```
### Populate the Transactions (Actions)
First, we will populate the transactions (actions) we want to send.
```typescript
// Get the signer and connect to the contracts.
//
// If you want the contracts to always have write access
// for a specific account, pass the signer in as the
// provider instead and skip calling connect on them.
const signer = provider.getSigner();
const erc20Contract = new ethers.Contract(erc20Address, erc20Abi, provider);
const dexContract = new ethers.Contract(dexAddress, dex20Abi, provider);
// Populate the token approval transaction.
const approveTransaction = await erc20Contract
.connect(signer)
.populateTransaction.approve(dexAddress, amount);
// Populate the token swap transaction.
const swapTransaction = await dexContract
.connect(signer)
.populateTransaction.swap(erc20Address, amount, otherERC20Address);
```
### Send the Transaction
Then, send the populated transactions.
Quill's [eth_sendTransaction](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction) accepts a modified `params` property into which more than one transaction object can be passed in. [Make sure window.ethereum can accept multiple transactions](#check-that-windowethereum-supports-multi-action-transactions) before passing more than one in.
```typescript
const transactionHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [approveTransaction, swapTransaction],
});
const transactionReceipt = await provider.getTransactionReceipt(transactionHash);
// Do anything else you need to with the transaction receipt.
```
You also can still send normal one-off transactions as you normally would, and still get the gas saving benefits of having your transaction aggregated with other transactions.
```typescript
const transferTransaction = await erc20Contract
.connect(signer)
.approve(otherAddress, amount);
await transferTransaction.wait();
```
## How Does This All Work?
See the [System Overview](./system_overview.md) for more details on what's happening behind the scenes.
## Example dApps Which Use BLS Wallet
- https://github.com/kautukkundan/BLSWallet-ERC20-demo
- https://github.com/voltrevo/bls-wallet-billboard
- https://github.com/JohnGuilding/single-pool-dex
## Coming soon
- Gasless transaction example.

View File

@@ -8,10 +8,8 @@
// Do not transform modules to CJS
"modules": false,
"targets": {
"chrome": "49",
"firefox": "52",
"opera": "36",
"edge": "79"
"chrome": "92",
"firefox": "90"
}
}
],
@@ -19,26 +17,7 @@
["@babel/react", { "runtime": "automatic" }]
],
"plugins": [
["@babel/plugin-proposal-class-properties"],
[
"@babel/plugin-transform-destructuring",
{
"useBuiltIns": true
}
],
[
"@babel/plugin-proposal-object-rest-spread",
{
"useBuiltIns": true
}
],
[
// Polyfills the runtime needed for async/await and generators
"@babel/plugin-transform-runtime",
{
"helpers": false,
"regenerator": true
}
]
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-private-methods"
]
}

View File

@@ -1,4 +0,0 @@
PRIVATE_KEY_STORAGE_KEY=default-private-key
AGGREGATOR_URL=http://localhost:3000
DEFAULT_CHAIN_ID=31337
CRYPTO_COMPARE_API_KEY=

View File

@@ -1,4 +0,0 @@
PRIVATE_KEY_STORAGE_KEY=quill-private-key
AGGREGATOR_URL=https://arbitrum-testnet.blswallet.org
DEFAULT_CHAIN_ID=421611
CRYPTO_COMPARE_API_KEY=injected-from-github-actions-environment

1814
extension/.eslintrc.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,65 +0,0 @@
{
"extends": [
"@abhijithvijayan/eslint-config/typescript",
"@abhijithvijayan/eslint-config/node",
"@abhijithvijayan/eslint-config/react"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"sourceType": "module",
"ecmaVersion": 11
},
"rules": {
"@typescript-eslint/no-explicit-any": "warn",
"consistent-return": "off",
"no-console": "off",
"no-continue": "off",
"no-extend-native": "off",
"no-constant-condition": "off",
"no-return-await": "off",
"react/jsx-props-no-spreading": "off",
"react/no-set-state": "off",
"react/destructuring-assignment": "off",
"jsx-a11y/label-has-associated-control": "off",
"class-methods-use-this": "off",
"max-classes-per-file": "off",
"no-param-reassign": "off",
"node/no-missing-import": "off",
"node/no-unpublished-import": "off",
"node/no-unsupported-features/es-syntax": [
"error",
{
"ignores": ["modules"]
}
],
"prettier/prettier": [
"error",
{
"bracketSpacing": true,
"jsxBracketSameLine": false,
"printWidth": 80,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"proseWrap": "always"
}
],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/lines-between-class-members": "off"
},
"env": {
"webextensions": true,
"es2020": true,
"browser": true,
"node": true
},
"settings": {
"node": {
"tryExtensions": [".tsx"] // append tsx to the list as well
}
}
}

View File

@@ -206,3 +206,5 @@ dist/
# Non-template additions
.env*
!.env.example
/build
/config.json

View File

@@ -1,11 +0,0 @@
{
"bracketSpacing": true,
"jsxBracketSameLine": false,
"printWidth": 80,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"proseWrap": "always"
}

View File

@@ -0,0 +1,27 @@
const fs = require('fs');
const path = require('path');
const networksConfigDir =
process.env.NETWORKS_CONFIG_DIR ??
path.join(__dirname, '..', 'contracts', 'networks');
fs.mkdirSync(path.join(__dirname, 'build'), { recursive: true });
const networkFilenames = fs.readdirSync(networksConfigDir);
const multiNetworkConfig = {};
for (const filename of networkFilenames) {
if (!filename.endsWith('.json')) {
throw new Error('Unexpected non-json file');
}
multiNetworkConfig[filename.slice(0, -'.json'.length)] = JSON.parse(
fs.readFileSync(path.join(networksConfigDir, filename), 'utf-8'),
);
}
fs.writeFileSync(
path.join(__dirname, 'build', 'multiNetworkConfig.json'),
JSON.stringify(multiNetworkConfig, null, 2),
);

View File

@@ -0,0 +1,91 @@
{
"defaultNetwork": "arbitrum-goerli",
"builtinNetworks": {
"mainnet": {
"blockExplorerUrl": "http://etherscan.io/",
"chainId": "0x1",
"displayName": "Mainnet",
"logo": "",
"rpcTarget": "https://mainnet.infura.io/v3/d0f317090d6645b6b494ddc6f1cce5ad",
"chainCurrencyName": "Ethereum",
"chainCurrency": "ETH",
"aggregatorUrl": "https://mainnet.blswallet.org",
"networkKey": "mainnet",
"hidden": true
},
"local": {
"blockExplorerUrl": "N/A",
"chainId": "0x7a69",
"displayName": "Local Network",
"logo": "",
"rpcTarget": "http://127.0.0.1:8545",
"chainCurrencyName": "Ethereum",
"chainCurrency": "ETH",
"aggregatorUrl": "http://localhost:3000",
"networkKey": "local"
},
"arbitrum-testnet": {
"blockExplorerUrl": "https://rinkeby-explorer.arbitrum.io",
"chainId": "0x66eeb",
"displayName": "Arbitrum Test Network",
"logo": "",
"rpcTarget": "https://rinkeby.arbitrum.io/rpc",
"chainCurrencyName": "Arbitrum Ether",
"chainCurrency": "ARETH",
"aggregatorUrl": "https://arbitrum-testnet.blswallet.org",
"networkKey": "arbitrum-testnet"
},
"arbitrum-goerli": {
"blockExplorerUrl": "https://goerli-rollup-explorer.arbitrum.io",
"chainId": "0x66EED",
"displayName": "Arbitrum Goerli",
"logo": "",
"rpcTarget": "https://goerli-rollup.arbitrum.io/rpc",
"chainCurrencyName": "Ethereum",
"chainCurrency": "ETH",
"aggregatorUrl": "https://arbitrum-goerli.blswallet.org",
"networkKey": "arbitrum-goerli"
},
"arbitrum": {
"blockExplorerUrl": "https://explorer.arbitrum.io",
"chainId": "0xa4b1",
"displayName": "Arbitrum One",
"logo": "",
"rpcTarget": "https://arb1.arbitrum.io/rpc",
"chainCurrencyName": "Ether",
"chainCurrency": "ETH",
"aggregatorUrl": "https://arbitrum.blswallet.org",
"networkKey": "arbitrum",
"hidden": true
},
"optimism-kovan": {
"blockExplorerUrl": "https://kovan-optimistic.etherscan.io",
"chainId": "0x45",
"displayName": "Optimism Test Network",
"logo": "",
"rpcTarget": "https://kovan.optimism.io",
"chainCurrencyName": "Optimistic Kovan Ether",
"chainCurrency": "KOR",
"aggregatorUrl": "https://optimism-kovan.blswallet.org",
"networkKey": "optimism-kovan",
"hidden": true
},
"optimism": {
"blockExplorerUrl": "https://optimistic.etherscan.io",
"chainId": "0xa",
"displayName": "Optimism",
"logo": "",
"rpcTarget": "https://mainnet.optimism.io",
"chainCurrencyName": "Ether",
"chainCurrency": "ETH",
"aggregatorUrl": "https://optimism.blswallet.org",
"networkKey": "optimism",
"hidden": true
}
},
"currencyConversion": {
"api": "https://min-api.cryptocompare.com/data/price",
"apiKey": "<Add your api key>",
"pollInterval": 30000
}
}

View File

@@ -0,0 +1,91 @@
{
"defaultNetwork": "arbitrum-goerli",
"builtinNetworks": {
"mainnet": {
"blockExplorerUrl": "http://etherscan.io/",
"chainId": "0x1",
"displayName": "Mainnet",
"logo": "",
"rpcTarget": "https://mainnet.infura.io/v3/d0f317090d6645b6b494ddc6f1cce5ad",
"chainCurrencyName": "Ethereum",
"chainCurrency": "ETH",
"aggregatorUrl": "https://mainnet.blswallet.org",
"networkKey": "mainnet",
"hidden": true
},
"local": {
"blockExplorerUrl": "N/A",
"chainId": "0x7a69",
"displayName": "Local Network",
"logo": "",
"rpcTarget": "http://127.0.0.1:8545",
"chainCurrencyName": "Ethereum",
"chainCurrency": "ETH",
"aggregatorUrl": "http://localhost:3000",
"networkKey": "local"
},
"arbitrum-testnet": {
"blockExplorerUrl": "https://rinkeby-explorer.arbitrum.io",
"chainId": "0x66eeb",
"displayName": "Arbitrum Test Network",
"logo": "",
"rpcTarget": "https://rinkeby.arbitrum.io/rpc",
"chainCurrencyName": "Arbitrum Ether",
"chainCurrency": "ARETH",
"aggregatorUrl": "https://arbitrum-testnet.blswallet.org",
"networkKey": "arbitrum-testnet"
},
"arbitrum-goerli": {
"blockExplorerUrl": "https://goerli-rollup-explorer.arbitrum.io",
"chainId": "0x66EED",
"displayName": "Arbitrum Goerli",
"logo": "",
"rpcTarget": "https://goerli-rollup.arbitrum.io/rpc",
"chainCurrencyName": "Ethereum",
"chainCurrency": "ETH",
"aggregatorUrl": "https://arbitrum-goerli.blswallet.org",
"networkKey": "arbitrum-goerli"
},
"arbitrum": {
"blockExplorerUrl": "https://explorer.arbitrum.io",
"chainId": "0xa4b1",
"displayName": "Arbitrum One",
"logo": "",
"rpcTarget": "https://arb1.arbitrum.io/rpc",
"chainCurrencyName": "Ether",
"chainCurrency": "ETH",
"aggregatorUrl": "https://arbitrum.blswallet.org",
"networkKey": "arbitrum",
"hidden": true
},
"optimism-kovan": {
"blockExplorerUrl": "https://kovan-optimistic.etherscan.io",
"chainId": "0x45",
"displayName": "Optimism Test Network",
"logo": "",
"rpcTarget": "https://kovan.optimism.io",
"chainCurrencyName": "Optimistic Kovan Ether",
"chainCurrency": "KOR",
"aggregatorUrl": "https://optimism-kovan.blswallet.org",
"networkKey": "optimism-kovan",
"hidden": true
},
"optimism": {
"blockExplorerUrl": "https://optimistic.etherscan.io",
"chainId": "0xa",
"displayName": "Optimism",
"logo": "",
"rpcTarget": "https://mainnet.optimism.io",
"chainCurrencyName": "Ether",
"chainCurrency": "ETH",
"aggregatorUrl": "https://optimism.blswallet.org",
"networkKey": "optimism",
"hidden": true
}
},
"currencyConversion": {
"api": "https://min-api.cryptocompare.com/data/price",
"apiKey": "${CRYPTO_COMPARE_API_KEY}",
"pollInterval": 30000
}
}

View File

@@ -11,7 +11,6 @@
"url": "https://blswallet.org"
},
"engines": {
"node": ">=16.0.0 <18.0.0",
"yarn": ">=1.0.0"
},
"scripts": {
@@ -22,8 +21,8 @@
"build:firefox": "cross-env NODE_ENV=production cross-env TARGET_BROWSER=firefox webpack",
"build:opera": "cross-env NODE_ENV=production cross-env TARGET_BROWSER=opera webpack",
"build": "yarn run build:chrome && yarn run build:firefox && yarn run build:opera",
"check-ts": "tsc --noEmit",
"lint": "eslint . --ext .ts,.tsx",
"check-ts": "node buildMultiNetworkConfig.js && tsc --noEmit",
"lint": "./scripts/addFileStubsIfNeeded.sh && eslint . --ext .ts,.tsx,.js --max-warnings 0",
"lint:fix": "eslint . --ext .ts,.tsx --fix"
},
"resolutions": {
@@ -32,95 +31,89 @@
"dependencies": {
"@babel/runtime": "^7.16.3",
"@ethereumjs/tx": "^3.5.1",
"@toruslabs/openlogin-jrpc": "^1.7.2",
"@tanstack/react-table": "^8.2.3",
"@types/bs58check": "^2.1.0",
"advanced-css-reset": "^1.2.2",
"async-mutex": "^0.3.2",
"axios": "^0.25.0",
"bls-wallet-clients": "0.6.0",
"dotenv-webpack": "^7.0.3",
"axios": "^0.27.2",
"bls-wallet-clients": "0.8.0",
"browser-passworder": "^2.0.3",
"bs58check": "^2.1.2",
"crypto-browserify": "^3.12.0",
"emoji-log": "^1.0.2",
"end-of-stream": "^1.4.4",
"eth-query": "^2.1.2",
"eth-rpc-errors": "^4.0.3",
"ethers": "5.5.4",
"ethers": "5.6.9",
"fast-deep-equal": "^3.1.3",
"fp-ts": "^2.12.1",
"io-ts": "^2.2.16",
"is-stream": "^3.0.0",
"lodash-es": "^4.17.21",
"phosphor-react": "^1.4.0",
"pump": "^3.0.0",
"qrcode.react": "^3.1.0",
"react": "^17.0.2",
"react-blockies": "^1.4.1",
"react-dom": "^17.0.2",
"react-modal": "^3.15.1",
"react-router-dom": "^6.2.1",
"react-table": "^7.7.0",
"readable-stream": "^3.6.0",
"sass": "^1.44.0",
"typed-emitter": "^1.4.0",
"typescript": "^4.5.3",
"stream-browserify": "^3.0.0",
"typed-emitter": "^2.1.0",
"typescript": "^4.7.4",
"webext-base-css": "^1.3.2",
"webextension-polyfill": "^0.8.0",
"webextension-polyfill": "^0.9.0",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@abhijithvijayan/eslint-config": "^2.6.3",
"@abhijithvijayan/eslint-config-airbnb": "^1.0.2",
"@abhijithvijayan/tsconfig": "^1.3.0",
"@babel/core": "^7.16.0",
"@babel/eslint-parser": "^7.16.3",
"@babel/plugin-proposal-class-properties": "^7.16.0",
"@babel/plugin-proposal-object-rest-spread": "^7.16.0",
"@babel/plugin-transform-destructuring": "^7.16.0",
"@babel/plugin-transform-runtime": "^7.16.4",
"@babel/preset-env": "^7.16.4",
"@babel/preset-react": "^7.16.0",
"@babel/preset-typescript": "^7.16.0",
"@types/axios": "^0.14.0",
"@types/end-of-stream": "^1.4.1",
"@types/lodash-es": "^4.17.5",
"@types/pump": "^1.1.1",
"@types/react": "^17.0.37",
"@types/react-blockies": "^1.4.1",
"@types/react-dom": "^17.0.11",
"@types/react-modal": "^3.13.1",
"@types/react-router-dom": "^5.3.3",
"@types/react-table": "^7.7.9",
"@types/readable-stream": "^2.3.12",
"@types/webextension-polyfill": "^0.8.2",
"@types/webpack": "^4.41.29",
"@types/webextension-polyfill": "^0.9.0",
"@types/webpack": "^5.28.0",
"@types/zxcvbn": "^4.4.1",
"@typescript-eslint/eslint-plugin": "^5.6.0",
"@typescript-eslint/parser": "^5.6.0",
"@typescript-eslint/eslint-plugin": "^5.30.4",
"@typescript-eslint/parser": "^5.30.4",
"autoprefixer": "^10.4.2",
"babel-loader": "^8.2.3",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^6.4.1",
"babel-loader": "^8.2.5",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"cross-env": "^7.0.3",
"css-loader": "^5.2.5",
"eslint": "^7.27.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-import": "^2.23.3",
"eslint-plugin-jsx-a11y": "^6.4.1",
"css-loader": "^6.7.1",
"dotenv-webpack": "^8.0.0",
"eslint": "^8.20.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
"filemanager-webpack-plugin": "^3.1.1",
"fork-ts-checker-webpack-plugin": "^6.5.0",
"html-webpack-plugin": "^4.5.2",
"mini-css-extract-plugin": "^1.6.0",
"optimize-css-assets-webpack-plugin": "^5.0.6",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"filemanager-webpack-plugin": "^7.0.0",
"fork-ts-checker-webpack-plugin": "^7.2.11",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.6.1",
"optimize-css-assets-webpack-plugin": "^6.0.1",
"postcss": "^8.4.5",
"postcss-loader": "^4.3.0",
"postcss-loader": "^7.0.0",
"prettier": "^2.5.1",
"resolve-url-loader": "^3.1.3",
"sass-loader": "^10.2.0",
"resolve-url-loader": "^5.0.0",
"sass-loader": "^13.0.1",
"tailwindcss": "^3.0.13",
"terser-webpack-plugin": "^4.2.3",
"webpack": "^4.46.0",
"webpack-cli": "^4.9.1",
"webpack-extension-reloader": "^1.1.4",
"wext-manifest-loader": "^2.3.0",
"terser-webpack-plugin": "^5.3.3",
"webpack": "~5.73.0",
"webpack-cli": "^4.10.0",
"webpack-ext-reloader": "^1.1.9",
"wext-manifest-loader": "^2.4.1",
"wext-manifest-webpack-plugin": "^1.2.1"
}
}

View File

@@ -0,0 +1,15 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
PROJECT_DIR="$SCRIPT_DIR/.."
if [ ! -f "$PROJECT_DIR/config.json" ]; then
echo {} >"$PROJECT_DIR/config.json"
fi
if [ ! -f "$PROJECT_DIR/build/multiNetworkConfig.json" ]; then
mkdir -p "$PROJECT_DIR/build"
echo {} >"$PROJECT_DIR/build/multiNetworkConfig.json"
fi

View File

@@ -0,0 +1,35 @@
import * as io from 'io-ts';
import configJson from '../config.json';
import { ProviderConfig } from './background/ProviderConfig';
import assertType from './cells/assertType';
import optional from './types/optional';
const Config = io.type({
defaultNetwork: io.string,
currencyConversion: io.type({
api: io.string,
apiKey: io.string,
// Note: We can afford to poll relatively frequently because we only fetch
// currency information when we actually need it, via the magic of cells.
// TODO: Enable even more aggressive polling intervals by tying
// `LongPollingCell`s to user activity (mouse movement, etc). This would
// require some visible indication that the value is not being updated
// though (like a grey filter) so that if you keep the window open on the
// side of your screen you can get an indication that the value isn't
// being kept up to date.
pollInterval: io.number,
}),
builtinNetworks: io.record(io.string, optional(ProviderConfig)),
});
type Config = io.TypeOf<typeof Config>;
export function loadConfig(): Config {
assertType(configJson, Config);
return configJson;
}
export default Config;

View File

@@ -1,67 +1,209 @@
import { ethers } from 'ethers';
import { FunctionComponent, useEffect, useMemo, useState } from 'react';
import { FunctionComponent, useState } from 'react';
import { runtime } from 'webextension-polyfill';
import TaskQueue from '../common/TaskQueue';
// components, styles and UI
import { Check, X, CaretLeft, CaretRight } from 'phosphor-react';
import { ethers } from 'ethers';
import Button from '../components/Button';
import CompactQuillHeading from '../components/CompactQuillHeading';
import { DEFAULT_CHAIN_ID_HEX } from '../env';
import { useInputDecode } from '../hooks/useInputDecode';
import formatCompactAddress from '../Popup/helpers/formatCompactAddress';
import {
PromptMessage,
SendTransactionParams,
TransactionStatus,
} from '../types/Rpc';
import TransactionCard from './TransactionCard';
import onAction from '../helpers/onAction';
import { useQuill } from '../QuillContext';
import useCell from '../cells/useCell';
import Loading from '../components/Loading';
import CurrencyDisplay from '../components/CurrencyDisplay';
import ChainCurrency from '../components/ChainCurrency';
import PreferredCurrency from '../components/PreferredCurrency';
const Confirm: FunctionComponent = () => {
const [id, setId] = useState<string>();
const [to, setTo] = useState<string>('0x');
const [value, setValue] = useState<string>('0');
const [data, setData] = useState<string>('0x');
const quill = useQuill();
// TODO (merge-ok) update component to work across multiple chains/networks
const chainId = DEFAULT_CHAIN_ID_HEX;
const { loading, method } = useInputDecode(data, to, chainId);
const id = new URL(window.location.href).searchParams.get('id');
const transactions = useCell(quill.cells.transactions);
const cleanupTasks = useMemo(() => new TaskQueue(), []);
const [current, setCurrent] = useState<number>(0);
useEffect(() => {
const params = new URL(window.location.href).searchParams;
setId(params.get('id') || '0');
setTo(params.get('to') || '0x');
setValue(params.get('value') || '0');
setData(params.get('data') || '0x');
if (transactions === undefined) {
return <Loading />;
}
return cleanupTasks.run();
}, [cleanupTasks]);
const tx = transactions.outgoing.find((t) => t.id === id);
const respondTx = (result: string) => {
if (tx === undefined) {
return <>Error: Tx not found</>;
}
const respondTx = (result: PromptMessage['result']) => {
runtime.sendMessage(undefined, { id, result });
};
const nextTx = () => {
setCurrent((current + 1) % tx.actions.length);
};
const prevTx = () => {
setCurrent((current - 1) % tx.actions.length);
};
const calculateTotal = (allActions: SendTransactionParams[]) => {
const total = allActions.reduce(
(acc, cur) => acc.add(ethers.BigNumber.from(cur.value)),
ethers.BigNumber.from(0),
);
return ethers.utils.formatEther(total);
};
return (
<div className="confirm">
<div className="section">
<CompactQuillHeading />
<div className="flex flex-col justify-between h-screen bg-grey-200">
<div className="p-4 flex justify-between text-white bg-blue-700">
Transaction request
</div>
<div className="section prompt">
{loading ? (
'loading...'
) : (
<>
<div>{method}</div>
<div>to: {formatCompactAddress(to)}</div>
<div>value: {ethers.utils.formatEther(value)} ETH</div>
<div>
data:
<div className="data">{data}</div>
<div className="flex-grow p-4">
<div className="">
{/* site info */}
<div className="flex gap-4">
<div className="h-10 w-10 bg-grey-400 rounded-full" />
<div className="leading-5">
<div className="">AppName</div>
<div className="text-blue-400">https://app-url.com/</div>
</div>
</div>
</div>
<div className="mt-4">AppName is making requests to your wallet</div>
{tx.actions.length > 1 && (
<div className="mt-4 flex justify-end text-body self-center gap-3">
{current + 1} of {tx.actions?.length}
<div
className={[
'bg-grey-400',
'rounded-md',
'p-1',
'hover:bg-grey-500',
'cursor-pointer',
].join(' ')}
{...onAction(prevTx)}
>
<CaretLeft size={20} className="self-center" />
</div>
<div
className={[
'bg-grey-400',
'rounded-md',
'p-1',
'hover:bg-grey-500',
'cursor-pointer',
].join(' ')}
{...onAction(nextTx)}
>
<CaretRight size={20} className="self-center" />
</div>
</div>
)}
<div className="flex flex-col">
<div className="mt-4">
{tx.actions[current] && (
<TransactionCard {...tx.actions[current]} />
)}
</div>
<div
className={[
'mt-4',
'p-4',
'bg-grey-300',
'rounded-md',
'grid',
'gap-3',
].join(' ')}
style={{
gridTemplateColumns: '1fr auto auto 0.75rem auto auto',
gridTemplateAreas: `
"a1 b1 c1 . d1 e1"
"a2 b2 c2 . d2 e2"
"a3 a3 a3 a3 a3 a3"
"a4 b4 c4 . d4 e4"
`,
}}
>
<div style={{ gridArea: 'a1' }}>Value</div>
<div style={{ gridArea: 'b1' }} className="font-bold text-right">
{calculateTotal(tx.actions)}
</div>
<div style={{ gridArea: 'c1' }}>
<ChainCurrency />
</div>
<div style={{ gridArea: 'd1' }} className="font-bold text-right">
<CurrencyDisplay
chainValue={Number(calculateTotal(tx.actions))}
includeLabel={false}
/>
</div>
<div style={{ gridArea: 'e1' }}>
<PreferredCurrency />
</div>
<Button className="btn-primary" onPress={() => respondTx('Yes')}>
Confirm
</Button>
<Button className="btn-secondary" onPress={() => respondTx('No')}>
Reject
</Button>
</>
)}
<div style={{ gridArea: 'a2' }}>Fee</div>
<div style={{ gridArea: 'b2' }} className="font-bold text-right">
0
</div>
<div style={{ gridArea: 'c2' }}>
<ChainCurrency />
</div>
<div style={{ gridArea: 'd2' }} className="font-bold text-right">
<CurrencyDisplay chainValue={0} includeLabel={false} />
</div>
<div style={{ gridArea: 'e2' }}>
<PreferredCurrency />
</div>
<div
style={{ gridArea: 'a3' }}
className="border-b border-grey-500"
/>
<div style={{ gridArea: 'a4' }}>Total</div>
<div style={{ gridArea: 'b4' }} className="font-bold text-right">
{calculateTotal(tx.actions)}
</div>
<div style={{ gridArea: 'c4' }}>
<ChainCurrency />
</div>
<div style={{ gridArea: 'd4' }} className="font-bold text-right">
<CurrencyDisplay
chainValue={Number(calculateTotal(tx.actions))}
includeLabel={false}
/>
</div>
<div style={{ gridArea: 'e4' }}>
<PreferredCurrency />
</div>
</div>
</div>
</div>
<div className="flex bg-white p-4 justify-between">
<Button
className="btn-secondary"
onPress={() => respondTx(TransactionStatus.REJECTED)}
>
<div className="flex justify-between gap-3">
Reject All <X size={20} className="self-center" />
</div>
</Button>
<Button
className="btn-primary"
onPress={() => respondTx(TransactionStatus.APPROVED)}
>
<div className="flex justify-between gap-3">
Confirm All <Check size={20} className="self-center" />
</div>
</Button>
</div>
</div>
);

View File

@@ -0,0 +1,79 @@
import React from 'react';
import Blockies from 'react-blockies';
import { ArrowRight } from 'phosphor-react';
import { ethers } from 'ethers';
import { SendTransactionParams } from '../types/Rpc';
import formatCompactAddress from '../helpers/formatCompactAddress';
import { useInputDecode } from '../hooks/useInputDecode';
const TransactionCard: React.FC<SendTransactionParams> = ({
data,
from,
to,
value,
gas,
gasPrice,
}) => {
const { loading, method, args } = useInputDecode(data || '0x');
return (
<div className="bg-white rounded-md p-4 border border-blue-400">
<div className="flex gap-4 w-full justify-between">
<Blockies seed={from} className="rounded-md" size={5} scale={8} />
<div className="flex justify-between flex-grow align-middle">
<div className="leading-snug">
<div>from</div>
<div className="font-bold">{formatCompactAddress(from)}</div>
</div>
<ArrowRight size={20} alignmentBaseline="central" />
<div className="leading-snug">
<div>to</div>
<div className="font-bold">{formatCompactAddress(to)}</div>
</div>
</div>
</div>
<div className="mt-4">
<div className="break-all">
details:{' '}
<span className="font-bold">{loading ? 'loading...' : method}</span>
<div className="text-[9pt] mt-2 font-mono">
{loading
? 'loading params...'
: // prettier-ignore
args.map((arg, i) => (
<div key={arg}>
{i + 1}. {arg.toString()}
</div>
))}
</div>
</div>
</div>
<div className="flex mt-6 gap-3">
<div className="w-60 border-r border-grey-400">
<div>Value</div>
<div className="break-all text-[9.5pt] font-bold">
{ethers.utils.formatEther(value || '0x0')} ETH
</div>
</div>
<div className="w-60 border-r border-grey-400">
<div>Gas Price</div>
<div className="break-all text-[9.5pt] font-bold">
{ethers.utils.formatUnits(gasPrice || '0x0', 'gwei')} gwei
</div>
</div>
<div className="w-60">
<div>Gas usage</div>
<div className="break-all text-[9.5pt] font-bold">
{ethers.utils.formatUnits(gas || '0x0', 'wei')} wei
</div>
</div>
</div>
</div>
);
};
export default TransactionCard;

View File

@@ -1,7 +1,22 @@
import ReactDOM from 'react-dom';
import Confirm from './Confirm';
import '../contentScript';
import '../styles/index.scss';
import './styles.scss';
ReactDOM.render(<Confirm />, document.getElementById('confirm-root'));
import ReactDOM from 'react-dom';
import Browser from 'webextension-polyfill';
import QuillEthereumProvider from '../QuillEthereumProvider';
import Confirm from './Confirm';
import { QuillContextProvider } from '../QuillContext';
window.ethereum = new QuillEthereumProvider(true);
window.debug ??= {};
window.debug.Browser = Browser;
ReactDOM.render(
<QuillContextProvider>
<Confirm />
</QuillContextProvider>,
document.getElementById('confirm-root'),
);

View File

@@ -1,116 +0,0 @@
import {
BasePostMessageStream,
ObjectMultiplex,
Stream,
} from '@toruslabs/openlogin-jrpc';
import pump from 'pump';
import { runtime } from 'webextension-polyfill';
import { CONTENT_SCRIPT, INPAGE, PROVIDER } from '../common/constants';
import PortDuplexStream from '../common/PortStream';
function canInjectScript() {
if (window.document.doctype?.name !== 'html') return false;
if (window.location.pathname.endsWith('.pdf')) return false;
if (document.documentElement.nodeName.toLowerCase() !== 'html') return false;
// Can add other checks later
return true;
}
function injectScript() {
try {
const container = document.head || document.documentElement;
const pageContentScriptTag = document.createElement('script');
pageContentScriptTag.src = runtime.getURL('js/pageContentScript.bundle.js');
container.insertBefore(pageContentScriptTag, container.children[0]);
// Can remove after script injection
container.removeChild(pageContentScriptTag);
} catch (error) {
console.error(error, 'Quill script injection failed');
}
}
/**
* Sets up two-way communication streams between the
* browser extension and local per-page browser context.
*
*/
async function setupStreams() {
// the transport-specific streams for communication between inpage and background
const pageStream = new BasePostMessageStream({
name: CONTENT_SCRIPT,
target: INPAGE,
});
const extensionPort = runtime.connect(undefined, {
name: CONTENT_SCRIPT,
});
const extensionStream = new PortDuplexStream(extensionPort);
// create and connect channel muxers
// so we can handle the channels individually
const pageMux = new ObjectMultiplex();
const extensionMux = new ObjectMultiplex();
pump(
pageMux as unknown as Stream,
pageStream as unknown as Stream,
pageMux as unknown as Stream,
(err) => logStreamDisconnectWarning('Quill Inpage Multiplex', err),
);
pump(
extensionMux as unknown as Stream,
extensionStream as unknown as Stream,
extensionMux as unknown as Stream,
(err) => {
logStreamDisconnectWarning('Quill Background Multiplex', err);
window.postMessage(
{
target: INPAGE, // the post-message-stream "target"
data: {
// this object gets passed to obj-multiplex
name: PROVIDER, // the obj-multiplex channel name
data: {
jsonrpc: '2.0',
method: 'QUILL_STREAM_FAILURE',
},
},
},
window.location.origin,
);
},
);
// forward communication across inpage-background for these channels only
forwardTrafficBetweenMuxes(PROVIDER, pageMux, extensionMux);
}
function forwardTrafficBetweenMuxes(
channelName: string,
muxA: ObjectMultiplex,
muxB: ObjectMultiplex,
) {
const channelA = muxA.createStream(channelName);
const channelB = muxB.createStream(channelName);
pump(
channelA as unknown as Stream,
channelB as unknown as Stream,
channelA as unknown as Stream,
(error) =>
console.debug(
`Quill: Muxed traffic for channel "${channelName}" failed.`,
error,
),
);
}
function logStreamDisconnectWarning(remoteLabel: string, error: unknown) {
console.debug(
`Quill: Content script lost connection to "${remoteLabel}".`,
error,
);
}
if (canInjectScript()) {
injectScript();
setupStreams();
}

View File

@@ -1,145 +0,0 @@
import { Mutex } from 'async-mutex';
import EthQuery from '../rpcHelpers/EthQuery';
import BaseController from '../BaseController';
import PollingBlockTracker from '../Block/PollingBlockTracker';
import { SafeEventEmitterProvider } from '../Network/INetworkController';
import NetworkController from '../Network/NetworkController';
import { PreferencesState } from '../Preferences/IPreferencesController';
import {
AccountInformation,
AccountTrackerConfig,
AccountTrackerState,
IAccountTrackerController,
} from './IAccountTrackerController';
/**
* Tracks accounts based on blocks.
* If block tracker provides latest block, we query accounts from it.
* Preferences state changes also retrigger accounts update.
* Network state changes also retrigger accounts update.
*/
class AccountTrackerController
extends BaseController<AccountTrackerConfig, AccountTrackerState>
implements
IAccountTrackerController<AccountTrackerConfig, AccountTrackerState>
{
private provider: SafeEventEmitterProvider;
private blockTracker: PollingBlockTracker;
private mutex = new Mutex();
private ethQuery: EthQuery;
private getIdentities: () => PreferencesState['identities'];
private getCurrentChainId: NetworkController['getNetworkIdentifier'];
constructor({
config,
state,
provider,
blockTracker,
getCurrentChainId,
getIdentities,
onPreferencesStateChange,
}: {
config: AccountTrackerConfig;
state: Partial<AccountTrackerState>;
provider: SafeEventEmitterProvider;
blockTracker: PollingBlockTracker;
getCurrentChainId: NetworkController['getNetworkIdentifier'];
getIdentities: () => PreferencesState['identities'];
onPreferencesStateChange: (
listener: (preferencesState: PreferencesState) => void,
) => void;
}) {
super({ config, state });
this.defaultState = {
accounts: {},
};
this.defaultConfig = {
_currentBlock: '',
};
this.initialize();
this.provider = provider;
this.blockTracker = blockTracker;
this.ethQuery = new EthQuery(provider);
// This starts the blockTracker internal tracking
this.blockTracker.on('latest', (block: string) => {
this.configure({ _currentBlock: block });
this.refresh();
});
this.getIdentities = getIdentities;
this.getCurrentChainId = getCurrentChainId;
onPreferencesStateChange(() => {
this.syncAccounts();
this.refresh();
});
console.log(this.provider, 'eth provider in account tracker');
}
syncAccounts(): void {
const { accounts } = this.state;
const addresses = Object.keys(this.getIdentities());
const existing = Object.keys(accounts);
const newAddresses = addresses.filter(
(address) => existing.indexOf(address) === -1,
);
const oldAddresses = existing.filter(
(address) => addresses.indexOf(address) === -1,
);
newAddresses.forEach((address) => {
accounts[address] = { balance: '0x0' };
});
oldAddresses.forEach((address) => {
delete accounts[address];
});
this.update({ accounts: { ...accounts } });
}
async refresh(): Promise<void> {
const releaseLock = await this.mutex.acquire();
try {
const { accounts } = this.state;
const currentBlock = this.config._currentBlock;
if (!currentBlock) return;
const addresses = Object.keys(accounts);
await Promise.all(
addresses.map((x) => this._updateAccount(x, currentBlock)),
);
} catch (error) {
console.error(error);
} finally {
releaseLock();
}
}
private async _updateAccount(
address: string,
currentBlock: string,
): Promise<void> {
const currentChainId = this.getCurrentChainId();
if (currentChainId === 'loading') {
return;
}
const balance = await this.ethQuery.request({
method: 'eth_getBalance',
params: [address, currentBlock],
});
const result: AccountInformation = {
balance: balance as string,
};
// update accounts state
const { accounts: newAccounts } = this.state;
// only populate if the entry is still present
if (!newAccounts[address]) return;
newAccounts[address] = result;
this.update({ accounts: newAccounts });
}
}
export default AccountTrackerController;

View File

@@ -1,37 +0,0 @@
import { BaseConfig, BaseState, IController } from '../interfaces';
export interface IAccountTrackerController<C, S> extends IController<C, S> {
/**
* Syncs accounts from preferences controller
*/
syncAccounts(): void;
/**
* Refreshes the balances of all accounts
*/
refresh(): Promise<void>;
}
export interface AccountTrackerConfig extends BaseConfig {
_currentBlock?: string;
}
/**
* Account information object
*/
export interface AccountInformation {
/**
* Hex string of an account balance in wei (base unit)
*/
balance: string;
}
/**
* Account tracker controller state
*/
export interface AccountTrackerState extends BaseState {
/**
* Map of addresses to account information
*/
accounts: { [address: string]: AccountInformation }; // address here is public address
}

View File

@@ -1,135 +0,0 @@
import { SafeEventEmitter } from '@toruslabs/openlogin-jrpc';
import { BaseConfig, BaseState, IController } from './interfaces';
/**
* Controller class that provides configuration, state management, and subscriptions
*/
class BaseController<C extends BaseConfig, S extends BaseState>
extends SafeEventEmitter
implements IController<C, S>
{
/**
* Default options used to configure this controller
*/
defaultConfig: C = {} as C;
/**
* Default state set on this controller
*/
defaultState: S = {} as S;
/**
* Determines if listeners are notified of state changes
*/
disabled = false;
/**
* Name of this controller used during composition
*/
name = 'BaseController';
private readonly initialConfig: C;
private readonly initialState: S;
private internalConfig: C = this.defaultConfig;
private internalState: S = this.defaultState;
/**
* Creates a BaseController instance. Both initial state and initial
* configuration options are merged with defaults upon initialization.
*
* @param config - Initial options used to configure this controller
* @param state - Initial state to set on this controller
*/
constructor({
config = {} as C,
state = {} as S,
}: {
config?: Partial<C>;
state?: Partial<S>;
}) {
super();
// Use assign since generics can't be spread: https://git.io/vpRhY
this.initialState = state as S;
this.initialConfig = config as C;
}
/**
* Retrieves current controller configuration options
*
* @returns - Current configuration
*/
get config(): C {
return this.internalConfig;
}
/**
* Retrieves current controller state
*
* @returns - Current state
*/
get state(): S {
return this.internalState;
}
/**
* Updates controller configuration
*
* @param config - New configuration options
* @param overwrite - Overwrite config instead of merging
* @param fullUpdate - Boolean that defines if the update is partial or not
*/
configure(config: Partial<C>, overwrite = false, fullUpdate = true): void {
if (fullUpdate) {
this.internalConfig = overwrite
? (config as C)
: Object.assign(this.internalConfig, config);
Object.keys(this.internalConfig).forEach((key) => {
if (typeof (this.internalConfig as any)[key] !== 'undefined') {
(this as any)[key as string] = (this.internalConfig as any)[key];
}
});
} else {
Object.keys(config).forEach((key) => {
if (typeof (this.internalConfig as any)[key] !== 'undefined') {
(this.internalConfig as any)[key] = (config as any)[key];
(this as any)[key as string] = (config as any)[key];
}
});
}
}
/**
* Updates controller state
*
* @param state - New state
* @param overwrite - Overwrite state instead of merging
*/
update(state: Partial<S>, overwrite = false): void {
this.internalState = overwrite
? { ...(state as S) }
: { ...this.internalState, ...state };
this.emit('store', this.internalState);
}
/**
* Enables the controller. This sets each config option as a member
* variable on this instance and triggers any defined setters. This
* also sets initial state and triggers any listeners.
*
* @returns - This controller instance
*/
protected initialize(): this {
this.internalState = this.defaultState;
this.internalConfig = this.defaultConfig;
this.configure(this.initialConfig);
this.update(this.initialState);
return this;
}
}
export default BaseController;

View File

@@ -1,191 +0,0 @@
import BaseController from '../BaseController';
import {
BaseBlockTrackerConfig,
BaseBlockTrackerState,
} from './IBlockTrackerController';
const sec = 1000;
const calculateSum = (accumulator: number, currentValue: number) =>
accumulator + currentValue;
const blockTrackerEvents: string[] = ['sync', 'latest'];
export class BaseBlockTracker<
C extends BaseBlockTrackerConfig,
S extends BaseBlockTrackerState,
> extends BaseController<C, S> {
name = 'BaseBlockTracker';
private _blockResetTimeout?: number;
constructor({
config = {},
state = {},
}: {
config: Partial<C>;
state: Partial<S>;
}) {
super({ config, state });
// config
this.defaultState = {
_currentBlock: '',
_isRunning: false,
} as S;
this.defaultConfig = {
blockResetDuration: 20 * sec,
} as C;
this.initialize();
// bind functions for internal use
this._onNewListener = this._onNewListener.bind(this);
this._onRemoveListener = this._onRemoveListener.bind(this);
this._resetCurrentBlock = this._resetCurrentBlock.bind(this);
// listen for handler changes
this._setupInternalEvents();
}
isRunning(): boolean | undefined {
return this.state._isRunning;
}
getCurrentBlock(): string | undefined {
return this.state._currentBlock;
}
async getLatestBlock(): Promise<string> {
// return if available
if (this.state._currentBlock) {
return this.state._currentBlock;
}
// wait for a new latest block
const latestBlock = await new Promise((resolve: (state: string) => void) =>
this.once('latest', (newState: BaseBlockTrackerState) => {
if (newState._currentBlock) {
resolve(newState._currentBlock);
}
}),
);
// return newly set current block
return latestBlock;
}
// dont allow module consumer to remove our internal event listeners
removeAllListeners(eventName?: string): this {
if (eventName) {
super.removeAllListeners(eventName);
} else {
super.removeAllListeners();
}
// re-add internal events
this._setupInternalEvents();
// trigger stop check just in case
this._onRemoveListener();
return this;
}
/**
* To be implemented in subclass.
*/
protected _start(): void {
// default behavior is noop
}
/**
* To be implemented in subclass.
*/
protected _end(): void {
// default behavior is noop
}
protected _newPotentialLatest(newBlock: string): void {
const currentBlock = this.state._currentBlock;
// only update if blok number is higher
if (
currentBlock &&
Number.parseInt(newBlock, 16) <= Number.parseInt(currentBlock, 16)
) {
return;
}
this._setCurrentBlock(newBlock);
}
private _setupInternalEvents(): void {
// first remove listeners for idempotency
this.removeListener('newListener', this._onNewListener);
this.removeListener('removeListener', this._onRemoveListener);
// then add them
this.on('removeListener', this._onRemoveListener);
this.on('newListener', this._onNewListener);
}
private _onNewListener(): void {
this._maybeStart();
}
private _onRemoveListener(): void {
// `removeListener` is called *after* the listener is removed
if (this._getBlockTrackerEventCount() > 0) {
return;
}
this._maybeEnd();
}
private _maybeStart(): void {
if (this.state._isRunning) {
return;
}
this.state._isRunning = true;
// cancel setting latest block to stale
this._cancelBlockResetTimeout();
this._start();
}
private _maybeEnd(): void {
if (!this.state._isRunning) {
return;
}
this.state._isRunning = false;
this._setupBlockResetTimeout();
this._end();
}
private _getBlockTrackerEventCount(): number {
return blockTrackerEvents
.map((eventName) => this.listenerCount(eventName))
.reduce(calculateSum);
}
private _setCurrentBlock(newBlock: string): void {
const oldBlock = this.state._currentBlock;
this.update({
_currentBlock: newBlock,
} as S);
this.emit('latest', newBlock);
this.emit('sync', { oldBlock, newBlock });
}
private _setupBlockResetTimeout(): void {
// clear any existing timeout
this._cancelBlockResetTimeout();
// clear latest block when stale
this._blockResetTimeout = window.setTimeout(
this._resetCurrentBlock,
this.config.blockResetDuration,
);
}
private _cancelBlockResetTimeout(): void {
if (this._blockResetTimeout) {
clearTimeout(this._blockResetTimeout);
}
}
private _resetCurrentBlock(): void {
this.update({ _currentBlock: '' } as Partial<S>);
}
}

View File

@@ -1,23 +0,0 @@
import { BaseConfig, BaseState } from '../interfaces';
import { SafeEventEmitterProvider } from '../Network/INetworkController';
export interface BaseBlockTrackerConfig extends BaseConfig {
blockResetDuration?: number;
}
export interface PollingBlockTrackerConfig extends BaseBlockTrackerConfig {
provider: SafeEventEmitterProvider;
pollingInterval: number;
retryTimeout: number;
setSkipCacheFlag: boolean;
}
export interface BaseBlockTrackerState extends BaseState {
/**
* block number in hex string
*/
_currentBlock?: string;
_isRunning?: boolean;
}
export type PollingBlockTrackerState = BaseBlockTrackerState;

View File

@@ -1,98 +0,0 @@
import { BaseBlockTracker } from './BaseBlockTracker';
import { createRandomId, timeout } from '../utils';
import {
PollingBlockTrackerConfig,
PollingBlockTrackerState,
} from './IBlockTrackerController';
import { ExtendedJsonRpcRequest } from '../Network/INetworkController';
const sec = 1000;
class PollingBlockTracker extends BaseBlockTracker<
PollingBlockTrackerConfig,
PollingBlockTrackerState
> {
constructor({
config,
state = {},
}: {
config: Partial<PollingBlockTrackerConfig> &
Pick<PollingBlockTrackerConfig, 'provider'>;
state: Partial<PollingBlockTrackerState>;
}) {
// parse + validate args
if (!config.provider) {
throw new Error('PollingBlockTracker - no provider specified.');
}
super({ config, state });
// config
this.defaultConfig = {
provider: config.provider,
pollingInterval: 20 * sec,
retryTimeout: 2 * sec,
setSkipCacheFlag: false,
};
this.initialize();
}
// trigger block polling
async checkForLatestBlock(): Promise<string> {
await this._updateLatestBlock();
return this.getLatestBlock();
}
protected _start(): void {
this._synchronize().catch((err) => this.emit('error', err));
}
private async _synchronize(): Promise<void> {
while (this.state._isRunning) {
try {
await this._updateLatestBlock();
await timeout(this.config.pollingInterval);
} catch (err: unknown) {
const newErr = new Error(
`PollingBlockTracker - encountered an error while attempting to update latest block:\n${
(err as Error).stack
}`,
);
try {
this.emit('error', newErr);
} catch (emitErr) {
console.error(newErr, emitErr);
}
await timeout(this.config.retryTimeout);
}
}
}
private async _updateLatestBlock(): Promise<void> {
// fetch + set latest block
const latestBlock = await this._fetchLatestBlock();
this._newPotentialLatest(latestBlock);
}
private async _fetchLatestBlock(): Promise<string> {
try {
const req: ExtendedJsonRpcRequest<[]> = {
method: 'eth_blockNumber',
jsonrpc: '2.0',
id: createRandomId(),
params: [],
};
const res = await this.config.provider.sendAsync<[], string>(req);
return res;
} catch (error: unknown) {
throw new Error(
`PollingBlockTracker - encountered error fetching block:\n${
(error as Error).message
}`,
);
}
}
}
export default PollingBlockTracker;

View File

@@ -1,126 +0,0 @@
import CellCollection from '../../cells/CellCollection';
import ICell from '../../cells/ICell';
import { CRYPTO_COMPARE_API_KEY } from '../../env';
import {
CurrencyControllerConfig,
CurrencyControllerState,
} from './ICurrencyController';
// every ten minutes
const POLLING_INTERVAL = 600_000;
const defaultConfig: CurrencyControllerConfig = {
pollInterval: POLLING_INTERVAL,
};
export default class CurrencyController {
private conversionInterval: number;
public config: CurrencyControllerConfig;
public state: ICell<CurrencyControllerState>;
constructor(
config: CurrencyControllerConfig | undefined,
storage: CellCollection,
) {
this.config = config ?? defaultConfig;
this.state = storage.Cell('CurrencyController', CurrencyControllerState, {
currentCurrency: 'usd',
conversionRate: 0,
conversionDate: 'N/A',
nativeCurrency: 'ETH',
});
}
//
// PUBLIC METHODS
//
public async update(stateUpdates: Partial<CurrencyControllerState>) {
await this.state.write({
...(await this.state.read()),
...stateUpdates,
});
}
async updateConversionRate(): Promise<void> {
let state: CurrencyControllerState | undefined;
try {
state = await this.state.read();
const apiUrl = `${
this.config.api
}?fsym=${state.nativeCurrency.toUpperCase()}&tsyms=${state.currentCurrency.toUpperCase()}&api_key=${CRYPTO_COMPARE_API_KEY}`;
let response: Response;
try {
response = await fetch(apiUrl);
} catch (error) {
console.error(
error,
'CurrencyController - Failed to request currency from cryptocompare',
);
return;
}
// parse response
let parsedResponse: { [key: string]: number };
try {
parsedResponse = await response.json();
} catch {
console.error(
new Error(
`CurrencyController - Failed to parse response "${response.status}"`,
),
);
return;
}
// set conversion rate
// if (nativeCurrency === 'ETH') {
// ETH
// this.setConversionRate(Number(parsedResponse.bid))
// this.setConversionDate(Number(parsedResponse.timestamp))
// } else
if (parsedResponse[state.currentCurrency.toUpperCase()]) {
// ETC
this.update({
conversionRate: Number(
parsedResponse[state.currentCurrency.toUpperCase()],
),
conversionDate: (Date.now() / 1000).toString(),
});
} else {
this.update({
conversionRate: 0,
conversionDate: 'N/A',
});
}
} catch (error) {
// reset current conversion rate
console.warn(
'Quill - Failed to query currency conversion:',
state?.nativeCurrency,
state?.currentCurrency,
error,
);
this.update({
conversionRate: 0,
conversionDate: 'N/A',
});
// throw error
console.error(
error,
`CurrencyController - Failed to query rate for currency "${state?.currentCurrency}"`,
);
}
}
public scheduleConversionInterval(): void {
if (this.conversionInterval) {
window.clearInterval(this.conversionInterval);
}
this.conversionInterval = window.setInterval(() => {
this.updateConversionRate();
}, this.config.pollInterval);
}
}

View File

@@ -1,17 +0,0 @@
import * as io from 'io-ts';
import { BaseConfig } from '../interfaces';
export const CurrencyControllerState = io.type({
currentCurrency: io.string,
conversionRate: io.number,
conversionDate: io.string,
nativeCurrency: io.string,
});
export type CurrencyControllerState = io.TypeOf<typeof CurrencyControllerState>;
export interface CurrencyControllerConfig extends BaseConfig {
pollInterval: number;
api?: string;
}

View File

@@ -1,60 +0,0 @@
import { Bundle, Operation } from 'bls-wallet-clients';
import { BaseConfig, BaseState } from '../interfaces';
import { SafeEventEmitterProvider } from '../Network/INetworkController';
export type KeyPair = {
/**
* Hex string without 0x prefix
*/
privateKey: string;
/**
* Address of the deployed contract wallet
*/
address: string;
};
export interface KeyringControllerConfig extends BaseConfig {
provider: SafeEventEmitterProvider;
}
export interface KeyringControllerState extends BaseState {
HDPhrase: string;
wallets: KeyPair[];
chainId: string;
}
export interface IKeyringController {
/**
* Returns the addresses of all stored key pairs
*/
getAccounts(): string[];
/**
* Creates a new key pair
*/
createAccount(): Promise<string>;
/**
* Creates a Deterministic Account based on seed phrase
*/
createHDAccount(): Promise<string>;
/**
* Imports a key pair
* @param privateKey - Hex string without 0x prefix
*/
importAccount(privateKey: string): Promise<string>;
/**
* Removes a key pair
* @param address - Address of the key pair
*/
removeAccount(address: string): void;
/**
* Signs a transaction of Type T
* @param address - account to sign the tx with
* @param tx - Transaction to sign
*/
signTransactions(address: string, tx: Operation): Promise<Bundle>;
}

View File

@@ -1,146 +0,0 @@
import { BlsWalletWrapper, Operation } from 'bls-wallet-clients';
import { ethers } from 'ethers';
import generateRandomHex from '../../helpers/generateRandomHex';
import BaseController from '../BaseController';
import {
IKeyringController,
KeyringControllerConfig,
KeyringControllerState,
} from './IKeyringController';
import { NETWORK_CONFIG } from '../../env';
import { getRPCURL } from '../utils';
export default class KeyringController
extends BaseController<KeyringControllerConfig, KeyringControllerState>
implements IKeyringController
{
name = 'KeyringController';
constructor({
config,
state,
}: {
config: Partial<KeyringControllerConfig>;
state: Partial<KeyringControllerState>;
}) {
super({ config, state });
this.defaultState = {
wallets: state.wallets ?? [],
HDPhrase: state.HDPhrase ?? '',
} as KeyringControllerState;
this.initialize();
}
getAccounts(): string[] {
return this.state.wallets.map((x) => x.address);
}
setHDPhrase(phrase: string) {
this.update({
HDPhrase: phrase,
});
}
isOnboardingComplete = (): boolean => {
return this.state.HDPhrase !== '';
};
async createHDAccount(): Promise<string> {
if (this.state.HDPhrase === '') {
const { phrase } = ethers.Wallet.createRandom().mnemonic;
this.setHDPhrase(phrase);
}
const mnemonic = this.state.HDPhrase;
const node = ethers.utils.HDNode.fromMnemonic(mnemonic);
const partialPath = "m/44'/60'/0'/0/";
const path = partialPath + this.state.wallets.length;
const { privateKey } = node.derivePath(path);
return this._createAccountAndUpdate(privateKey);
}
async createAccount(): Promise<string> {
const privateKey = generateRandomHex(256);
return this._createAccountAndUpdate(privateKey);
}
async importAccount(privateKey: string): Promise<string> {
const existingWallet = this.state.wallets.find(
(x) => x.privateKey.toLowerCase() === privateKey.toLowerCase(),
);
if (existingWallet) return existingWallet.address;
return this._createAccountAndUpdate(privateKey);
}
removeAccount(address: string): void {
const existingWallets = [...this.state.wallets];
const index = this.state.wallets.findIndex((x) => x.address === address);
if (index !== -1) {
existingWallets.splice(index, 1);
this.update({ wallets: existingWallets });
}
}
async signTransactions(address: string, tx: Operation) {
const privKey = this._getPrivateKeyFor(address);
const wallet = await this._getBLSWallet(privKey);
return wallet.sign(tx);
}
async getNonce(address: string) {
const privKey = this._getPrivateKeyFor(address);
const wallet = await this._getBLSWallet(privKey);
return wallet.Nonce();
}
async _createAccountAndUpdate(privateKey: string): Promise<string> {
const address = await this._getContractWalletAddress(privateKey);
if (address) {
this.update({
wallets: [
...this.state.wallets,
{
privateKey,
address,
},
],
});
}
return address;
}
private _getPrivateKeyFor(address: string): string {
const checksummedAddress = ethers.utils.getAddress(address);
const keyPair = this.state.wallets.find(
(x) => x.address === checksummedAddress,
);
if (!keyPair) throw new Error('key does not exist');
return keyPair.privateKey;
}
private _createProvider(): ethers.providers.Provider {
return new ethers.providers.JsonRpcProvider(getRPCURL(this.state.chainId));
}
private _getContractWalletAddress(privateKey: string): Promise<string> {
return BlsWalletWrapper.Address(
privateKey,
NETWORK_CONFIG.addresses.verificationGateway,
this._createProvider(),
);
}
private async _getBLSWallet(privateKey: string): Promise<BlsWalletWrapper> {
return BlsWalletWrapper.connect(
privateKey,
NETWORK_CONFIG.addresses.verificationGateway,
this._createProvider(),
);
}
}

View File

@@ -1,152 +0,0 @@
import {
JRPCEngine,
JRPCMiddleware,
JRPCRequest,
JRPCResponse,
SafeEventEmitter,
} from '@toruslabs/openlogin-jrpc';
import { ProviderConfig } from '../constants';
import { BaseConfig, BaseState, IController } from '../interfaces';
/**
* Custom network properties
* @example isEIP1559Compatible: true etc.
*/
export interface NetworkProperties {
[key: string]: number | string | boolean | unknown;
EIPS: {
// undefined means we have not checked yet. (true or false means property is set)
[key: string | number]: boolean | undefined;
};
}
/**
*
*/
export interface NetworkState extends BaseState {
/**
* Chain Id for the current network
*/
chainId: string;
providerConfig: ProviderConfig;
properties: NetworkProperties;
}
export interface NetworkConfig extends BaseConfig {
providerConfig: ProviderConfig;
}
export interface INetworkController<C, S> extends IController<C, S> {
/**
* Gets the chainId of the network
*/
getNetworkIdentifier(): string;
/**
* Sets provider for the current network controller
* @param providerConfig - Provider config object
*/
setProviderConfig(providerConfig: ProviderConfig): void;
/**
* Connects to the rpcUrl for the current selected provider
*/
lookupNetwork(): Promise<void>;
}
export type BlockData = string | string[];
export type Block = Record<string, BlockData>;
export type SendAsyncCallBack = (
err: Error,
providerRes: JRPCResponse<Block>,
) => void;
export type SendCallBack<U> = (err: any, providerRes: U | undefined) => void;
export type Payload = Partial<JRPCRequest<string[]>>;
export interface SafeEventEmitterProvider extends SafeEventEmitter {
request: <T, U>(req: JRPCRequest<T>) => Promise<U>;
sendAsync: <T, U>(req: JRPCRequest<T>) => Promise<U>;
send: <T, U>(req: JRPCRequest<T>, callback: SendCallBack<U>) => void;
}
export interface ExtendedJsonRpcRequest<T> extends JRPCRequest<T> {
skipCache?: boolean;
}
export function providerFromEngine(
engine: JRPCEngine,
): SafeEventEmitterProvider {
const provider: SafeEventEmitterProvider =
new SafeEventEmitter() as SafeEventEmitterProvider;
// handle both rpc send methods
provider.sendAsync = async <T, U>(req: JRPCRequest<T>) => {
const res = await engine.handle(req);
if (res.error) {
throw new Error(res.error);
}
return res.result as U;
};
provider.request = async <T, U>(req: JRPCRequest<T>) => {
const res = await engine.handle(req);
if (res.error) {
throw new Error(res.error);
}
return res.result as U;
};
provider.send = <T, U>(
req: JRPCRequest<T>,
callback: (error: any, providerRes: U | undefined) => void,
) => {
if (typeof callback !== 'function') {
throw new Error('Must provide callback to "send" method.');
}
engine.handle(req, (err, res) => {
if (err) {
callback(err, undefined);
} else if (res.error) {
callback(new Error(res.error), undefined);
} else {
callback(null, res.result as U);
}
});
};
// forward notifications
if (engine.on) {
engine.on('notification', (message: string) => {
provider.emit('data', null, message);
});
}
return provider;
}
export function providerFromMiddleware(
middleware: JRPCMiddleware<string[], unknown>,
): SafeEventEmitterProvider {
const engine = new JRPCEngine();
engine.push(middleware);
const provider: SafeEventEmitterProvider = providerFromEngine(engine);
return provider;
}
export function providerAsMiddleware(
provider: SafeEventEmitterProvider,
): JRPCMiddleware<unknown, unknown> {
return async (req, res, _next, end) => {
// send request to provider
try {
const providerRes: unknown = await provider.sendAsync<unknown, unknown>(
req,
);
res.result = providerRes;
return end();
} catch (error: unknown) {
return end(error as Error);
}
};
}

View File

@@ -1,252 +0,0 @@
import { providers } from 'ethers';
import { JRPCEngine, JRPCMiddleware } from '@toruslabs/openlogin-jrpc';
import { Mutex } from 'async-mutex';
import EthQuery from '../rpcHelpers/EthQuery';
import BaseController from '../BaseController';
import {
PollingBlockTrackerConfig,
PollingBlockTrackerState,
} from '../Block/IBlockTrackerController';
import PollingBlockTracker from '../Block/PollingBlockTracker';
import { ProviderConfig } from '../constants';
import createEventEmitterProxy from '../createEventEmitterProxy';
import createSwappableProxy from '../createSwappableProxy';
import { getDefaultProviderConfig } from '../utils';
import {
createWalletMiddleware,
IProviderHandlers,
} from './createEthMiddleware';
import { createJsonRpcClient } from './createJsonRpcClient';
import {
INetworkController,
NetworkConfig,
NetworkState,
providerFromEngine,
SafeEventEmitterProvider,
} from './INetworkController';
// use state_get_balance for account balance
export default class NetworkController
extends BaseController<NetworkConfig, NetworkState>
implements INetworkController<NetworkConfig, NetworkState>
{
name = 'NetworkController';
_providerProxy: SafeEventEmitterProvider;
_blockTrackerProxy: PollingBlockTracker;
private mutex = new Mutex();
private _provider: SafeEventEmitterProvider | null = null;
private _blockTracker: PollingBlockTracker | null = null;
/**
* Initialized before our provider is created.
*/
private ethQuery: EthQuery;
private _baseProviderHandlers: IProviderHandlers;
constructor({
config,
state,
}: {
config?: Partial<NetworkConfig>;
state?: Partial<NetworkState>;
}) {
super({ config, state });
this.defaultState = {
chainId: 'loading',
properties: {
EIPS: { 1559: undefined },
},
providerConfig: getDefaultProviderConfig(),
};
this.initialize();
// when a new network is set, we set to loading first and then when connection succeeds, we update the network
}
getNetworkIdentifier(): string {
return this.state.chainId;
}
/**
* Called by orchestrator once while initializing the class
* @param providerHandlers - JRPC handlers for provider
* @returns - provider - Returns the providerProxy
*/
public initializeProvider(
providerHandlers: IProviderHandlers,
): SafeEventEmitterProvider {
this._baseProviderHandlers = providerHandlers;
this.configureProvider();
this.lookupNetwork(); // Not awaiting this, because we don't want to block the initialization
return this._providerProxy;
}
getProvider(): SafeEventEmitterProvider {
return this._providerProxy;
}
setProviderConfig(config: ProviderConfig): void {
this.update({
providerConfig: { ...config },
});
this.refreshNetwork();
}
getProviderConfig(): ProviderConfig {
return this.state.providerConfig;
}
/**
* Refreshes the current network code
*/
async lookupNetwork(): Promise<void> {
const { rpcTarget, chainId } = this.getProviderConfig();
if (!chainId || !rpcTarget || !this._provider) {
this.update({
chainId: 'loading',
properties: { EIPS: { 1559: undefined } },
});
return;
}
const query = this.ethQuery;
if (!query) {
return;
}
const releaseLock = await this.mutex.acquire();
return new Promise((resolve, reject) => {
// info_get_status
query.sendAsync(
{ method: 'net_version' },
(error: Error, network: unknown) => {
releaseLock();
if (error) {
this.update({
chainId: 'loading',
properties: {
EIPS: { 1559: undefined },
},
});
reject(error);
}
this.update({
// Network is returned as a string (base 10)
chainId: `0x${Number.parseInt(network as string, 16).toString()}`,
});
// Don't need to wait for this
this.getEIP1559Compatibility();
this.emit('networkDidChange');
resolve();
},
);
});
}
async getEIP1559Compatibility(): Promise<boolean> {
const { EIPS } = this.state.properties;
// log.info('checking eip 1559 compatibility', EIPS[1559])
if (EIPS[1559] !== undefined) {
return EIPS[1559];
}
const latestBlock = await this.ethQuery.request<providers.Block>({
method: 'eth_getBlockByNumber',
params: ['latest', false],
});
const supportsEIP1559 =
latestBlock && latestBlock.baseFeePerGas !== undefined;
this.update({
properties: {
EIPS: { 1559: supportsEIP1559 },
},
});
return supportsEIP1559;
}
private configureProvider(): void {
const { chainId, rpcTarget, ...rest } = this.getProviderConfig();
if (!chainId || !rpcTarget) {
throw new Error(
'chainId and rpcTarget must be provider in providerConfig',
);
}
this.configureStandardProvider({ chainId, rpcTarget, ...rest });
}
private configureStandardProvider(providerConfig: ProviderConfig): void {
const networkClient = createJsonRpcClient(providerConfig);
this.setNetworkClient(networkClient);
}
private setNetworkClient({
networkMiddleware,
blockTracker,
}: {
networkMiddleware: JRPCMiddleware<unknown, unknown>;
blockTracker: PollingBlockTracker;
}): void {
const walletMiddleware = createWalletMiddleware(this._baseProviderHandlers);
const engine = new JRPCEngine();
engine.push(walletMiddleware);
engine.push(networkMiddleware);
const provider = providerFromEngine(engine);
this.setProvider({ provider, blockTracker });
}
private setProvider({
provider,
blockTracker,
}: {
provider: SafeEventEmitterProvider;
blockTracker: PollingBlockTracker;
}): void {
if (this._providerProxy) {
/* eslint @typescript-eslint/ban-ts-comment: "warn" -- TODO (merge-ok) Fix typing */
// @ts-ignore
this._providerProxy.setTarget(provider);
} else {
this._providerProxy =
createSwappableProxy<SafeEventEmitterProvider>(provider);
}
if (this._blockTrackerProxy) {
/* eslint @typescript-eslint/ban-ts-comment: "warn" -- TODO (merge-ok) Fix typing */
// @ts-ignore
this._blockTrackerProxy.setTarget(blockTracker);
} else {
this._blockTrackerProxy = createEventEmitterProxy<
PollingBlockTrackerConfig,
PollingBlockTrackerState,
PollingBlockTracker
>(blockTracker, {
eventFilter: 'skipInternal',
});
}
// set new provider and blockTracker
this._provider = provider;
provider.setMaxListeners(10);
this._blockTracker = blockTracker;
console.log(
this._blockTracker,
'set block tracker after switching network',
);
this.ethQuery = new EthQuery(provider);
}
private refreshNetwork() {
this.update({
chainId: 'loading',
properties: { EIPS: { 1559: undefined } },
});
this.configureProvider();
this.lookupNetwork();
}
}

View File

@@ -1,50 +0,0 @@
import {
createAsyncMiddleware,
createScaffoldMiddleware,
JRPCMiddleware,
JRPCRequest,
JRPCResponse,
} from '@toruslabs/openlogin-jrpc';
import { BigNumberish, BytesLike } from 'ethers';
import web3_clientVersion from './web3_clientVersion';
type ProviderHandler<Params, Result> = (
req: JRPCRequest<Params>,
) => Promise<Result>;
function toAsyncMiddleware<Params, Result>(
method: ProviderHandler<Params, Result>,
) {
return createAsyncMiddleware(
async (req: JRPCRequest<Params>, res: JRPCResponse<Result>) => {
res.result = await method(req);
},
);
}
export type SendTransactionParams = {
from: string;
to: string;
gas?: BigNumberish;
gasPrice?: BigNumberish;
value?: BigNumberish;
data: BytesLike;
};
export type IProviderHandlers = Record<
string,
ProviderHandler<unknown, unknown>
>;
export function createWalletMiddleware(
handlers: IProviderHandlers,
): JRPCMiddleware<string, unknown> {
const asyncMiddlewares = Object.fromEntries(
Object.keys(handlers).map((method) => [
method,
toAsyncMiddleware(handlers[method]),
]),
);
return createScaffoldMiddleware({ web3_clientVersion, ...asyncMiddlewares });
}

View File

@@ -1,172 +0,0 @@
import {
createAsyncMiddleware,
JRPCEngineNextCallback,
JRPCMiddleware,
JRPCRequest,
JRPCResponse,
} from '@toruslabs/openlogin-jrpc';
import { ethErrors } from 'eth-rpc-errors';
import { Block, Payload } from './INetworkController';
export interface FetchMiddlewareOptions {
rpcTarget: string;
originHttpHeaderKey?: string;
}
export interface PayloadwithOrgin extends Payload {
origin?: string;
}
export interface FetchMiddlewareFromReqOptions extends FetchMiddlewareOptions {
req: PayloadwithOrgin;
}
export interface FetchConfig {
fetchUrl: string;
fetchParams: Record<string, unknown>;
}
const RETRIABLE_ERRORS: string[] = [
// ignore server overload errors
'Gateway timeout',
'ETIMEDOUT',
// ignore server sent html error pages
// or truncated json responses
'failed to parse response body',
// ignore errors where http req failed to establish
'Failed to fetch',
];
function checkForHttpErrors(fetchRes: Response): void {
// check for errors
switch (fetchRes.status) {
case 405:
throw ethErrors.rpc.methodNotFound();
case 418:
throw ethErrors.rpc.internal({
message: `Request is being rate limited.`,
});
case 503:
case 504:
throw ethErrors.rpc.internal({
message:
`Gateway timeout. The request took too long to process.` +
`This can happen when querying over too wide a block range.`,
});
default:
break;
}
}
function timeout(duration: number): Promise<number> {
return new Promise((resolve) => setTimeout(resolve, duration));
}
function parseResponse(fetchRes: Response, body: Record<string, Block>): Block {
// check for error code
if (fetchRes.status !== 200) {
throw ethErrors.rpc.internal({
message: `Non-200 status code: '${fetchRes.status}'`,
data: body,
});
}
// check for rpc error
if (body.error) {
throw ethErrors.rpc.internal({
data: body.error,
});
}
// return successful result
return body.result;
}
export function createFetchConfigFromReq({
req,
rpcTarget,
originHttpHeaderKey,
}: FetchMiddlewareFromReqOptions): FetchConfig {
const parsedUrl: URL = new URL(rpcTarget);
// prepare payload
// copy only canonical json rpc properties
const payload: Payload = {
id: req.id,
jsonrpc: req.jsonrpc,
method: req.method,
params: req.params,
};
// extract 'origin' parameter from request
const originDomain: string | undefined = req.origin;
// serialize request body
const serializedPayload: string = JSON.stringify(payload);
// configure fetch params
const fetchParams = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: serializedPayload,
};
// optional: add request origin as header
if (originHttpHeaderKey && originDomain) {
(fetchParams.headers as Record<string, unknown>)[originHttpHeaderKey] =
originDomain;
}
return { fetchUrl: parsedUrl.href, fetchParams };
}
export function createFetchMiddleware({
rpcTarget,
originHttpHeaderKey,
}: FetchMiddlewareOptions): JRPCMiddleware<string[], Block> {
return createAsyncMiddleware(
async (
req: JRPCRequest<string[]>,
res: JRPCResponse<unknown>,
_next: JRPCEngineNextCallback,
) => {
const { fetchUrl, fetchParams } = createFetchConfigFromReq({
req,
rpcTarget,
originHttpHeaderKey,
});
// attempt request multiple times
const maxAttempts = 5;
const retryInterval = 1000;
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
try {
const fetchRes: Response = await fetch(fetchUrl, fetchParams);
// check for http errrors
checkForHttpErrors(fetchRes);
// parse response body
const fetchBody: Record<string, Block> = await fetchRes.json();
const result: Block = parseResponse(fetchRes, fetchBody);
// set result and exit retry loop
res.result = result;
return;
} catch (err: unknown) {
const errMsg: string = (err as Error).toString();
const isRetriable: boolean = RETRIABLE_ERRORS.some((phrase) =>
errMsg.includes(phrase),
);
// re-throw error if not retriable
if (!isRetriable) {
throw err;
}
}
// delay before retrying
await timeout(retryInterval);
}
},
);
}

Some files were not shown because too many files have changed in this diff Show More