185 Commits

Author SHA1 Message Date
Andrew Morris
92f6ec8467 Fix getInitializeData 2022-09-21 13:29:06 +10:00
Andrew Morris
2e68505b2e Draft 2022-09-20 15:54:02 +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
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
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
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
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
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
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
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
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
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
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
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
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
Jacob Caban-Tomski
17ccc5b00c Merge pull request #240 from web3well/extension-releases
Add extension artifacts on releases
2022-06-22 12:32:46 -04:00
James Zaki
ce7f958fb7 Merge pull request #212 from web3well/feat/one-to-one-hash-wallet
Fixes: one-to-one hash wallet 
- Made room in the VerificationGateway by making proxy admin deployment external (requires setting address in constructor as per bls lib param)
- added a delay between wallet's setting of a new blskey hash (using one-to-one reverse mapping)
- removed bls key from wallet
- updated tests
Closes #104 #155
2022-06-20 03:07:24 +10:00
James Zaki
4dae24f4ec Remove bls key from wallet and update tests. Closes #104 #155 2022-06-19 17:52:04 +01:00
James Zaki
471be6f0e0 Set wallet BLS keys in bls verification gateway (wip) 2022-06-19 16:01:07 +01:00
James Zaki
0ef2799980 Reduce VerificationGateway contract bytes (wip) 2022-06-19 15:59:45 +01:00
Jacob Caban-Tomski
f31ee249b8 Add automation label to labeler 2022-06-17 18:05:34 -04:00
Jacob Caban-Tomski
935bdf16c3 Add workflow for uploading extension on releases
Add workflow for uploading extension artifacts for chrome, opera, and firefox on release.
Add action for building and uploading extension assets.
Remove unused extension env vars.
2022-06-17 18:05:31 -04:00
Jacob Caban-Tomski
94c9060d57 Rename CI workflow files 2022-06-17 15:17:58 -04:00
Andrew Morris
e671e73e0f Cells (#221)
* wip StorageManager

* Fixes, add iteration

* Use default to ensure there's always a value

* Define IReadableCell, split out ReadableCellIterator

* FormulaCell

* Remove unused functions

* Rename to cells/ExtensionLocalStorage.ts

* StorageCell -> ExtensionLocalCell

* Split out ICell

* Split out CellIterator

* MemoryCell.ts

* Split out FormulaCell

* ReadableCellIterator -> CellIterator

* Define IAsyncStorage abstraction and use it to generalize ExtensionLocalStorage

* MemoryCellCollection.ts

* Enable removing cells

* useCell.ts, useReadableCell.ts

* Add cells demo

* CellDisplay

* Add counters

* Allow mixing unknowns in CellCollection

* Quill cells

* Allow async formulas

* Use a cell for CurrencyController.state

* Use versionedType when reading

* QuillContext

* Fix window.QuillController() in WalletWrapper

* Replace internal rpc with public+private

* Move eth_accounts into typed rpc

* Replace KeyringController().getAccounts with rpc.public.eth_accounts

* Replace KeyringController().createHdAccount with rpc.private.quill_createHdAccount

* Fix .KeyringController in WalletPage

* Fix CellIterator undefined bug

* useNewCell

* Avoid useCellWithFallback

* Remove useNewCell

* useCellState

* tmp

* Add events to IAsyncStorage, change extensionLocalStorage to singleton

* Use change events in CellCollection

* Allow cleaning up the change handler

* localCellCollection

* Use extensionLocalCellCollection in demo

* Fix demo page

* Make FormulaCell lazy

* Stop iteration more actively

* Fix cleanup of end handler

* Use elcc

* Add blockNumber cell

* Page selection, ethersProvider

* nitpicks

* Balance demo

* More event cleanup fixes

* Improve label

* Add dark-theme

* Remove obsoleted rpc-based cell collection

* Cleanup

* DemoTable

* Add cells readme

* Fix theme persistence

* Remove regex check

* Avoid casting window

* Use ChangeEvent to avoid cast

* Move cells demo into its own page

* Fix linting issues
2022-06-15 20:44:24 -04:00
kautukkundan
140f5e7094 removed unused dependency 2022-06-15 18:02:04 +05:30
Andrew Morris
1b5e2eaec4 Merge pull request #223 from web3well/ci-pipeline
CI Pipeline
2022-06-15 08:13:01 +10:00
Andrew Morris
3982007d76 Merge pull request #238 from web3well/release-drafter
Add release drafter
2022-06-15 08:05:31 +10:00
Andrew Morris
b39ed6653f Merge pull request #234 from web3well/contributing-info
Add contributing recommendations & guidelines
2022-06-15 07:58:48 +10:00
Andrew Morris
cbdb771f33 Merge pull request #236 from web3well/license
Add MIT license
2022-06-15 07:56:30 +10:00
Jacob Caban-Tomski
dd944ccf54 Add release drafter 2022-06-14 16:58:33 -04:00
Jacob Caban-Tomski
e58903eb9b Add MIT license
Add MIT license to extension.
2022-06-14 15:41:27 -04:00
Jacob Caban-Tomski
b538fa70ec Add contributing recommendations & guidelines.
Add ways to contribute to README.
Add contributing guidelines & information.
Move images dir under docs.
2022-06-14 15:14:10 -04:00
kautukkundan
fdd1c020c6 added test for setExternalWallet 1:1 mapping 2022-06-14 23:44:57 +05:30
Jacob Caban-Tomski
7165a4c2aa Update labeler glob patterns 2022-06-09 18:02:56 -04:00
Jacob Caban-Tomski
0cbe1ceaff Add aggregator CI pipeline
Add workflow for linting, typechecking, and running tests for aggregator.
Break up aggregator premerge program into additional typescript check & todo linting programs.
Update default aggregator private keys in .env.example to hardhat node values.
Add fundDeployer hardhat task to simplify local & CI contract deployment.
Remove steps from local development doc.
Default contracts & clients setup action to use NodeJS 16.x.
2022-06-09 17:16:30 -04:00
Jacob Caban-Tomski
7c8b8383e4 Update CI workflows to target PRs, main branch 2022-06-09 11:55:15 -04:00
Jacob Caban-Tomski
d2b18f3f50 Switch extension eslint-disables to warnings
Switch most eslint-disables in extension to warnings.
Update some existing types to better forms.
2022-06-09 11:31:59 -04:00
Jacob Caban-Tomski
fafc15c897 Add CI pipeline for extension
Add workflow file to lint & build extension.
Switch eth-query provider to unknown type.
Supress or fix existing lint errors.
2022-06-09 11:30:29 -04:00
Jacob Caban-Tomski
b52c8e6e90 Add workflow & engine for aggregator-proxy 2022-06-09 11:28:15 -04:00
Jacob Caban-Tomski
c8e7c7370a Add CI pipeline for contracts & clients
Add action which sets up contracts & clients projects.
Add workflows to lint & test contracts & clients.
Update labeler to include aggregator-proxy & documentation.
Add engine fields to contracts & clients.
Update extension engine field.
2022-06-09 11:28:15 -04:00
Jacob Caban-Tomski
53c8e62cf6 Merge pull request #217 from web3well/bw-173-214-215-fix-rpc
Refactor+Update RPC methods and Add Quill context provider
2022-06-09 10:44:22 -04:00
Jacob Caban-Tomski
436094914f Update TODO with issue 2022-06-09 10:43:02 -04:00
Jacob Caban-Tomski
745aed060f Remove quill provider default export 2022-06-07 22:09:25 -04:00
Jacob Caban-Tomski
df53392ce7 Refactor quill context, improve window types.
Remove async init of React root.
Move window.ethereum extraction to quill context provider.
Improve window.ethereum typing.
2022-06-07 19:07:28 -04:00
Jacob Caban-Tomski
7d82903f69 Merge branch 'main' of github.com:web3well/bls-wallet into bw-173-214-215-fix-rpc 2022-06-06 22:21:00 -04:00
Jacob Caban-Tomski
5e8827b595 Merge pull request #158 from web3well/update-docs
Update repo documentation
2022-06-06 22:17:37 -04:00
Jacob Caban-Tomski
d2c1e6af1e Add clients local dev instructions 2022-06-06 14:48:55 -04:00
Jacob Caban-Tomski
21dd23953d Update repo documentation.
Turn main repo README into landing page with relevant sub folder/component links.
Include aggregator-proxy in setup.ts.
Update aggreagator-proxy npm repo.
Clean up all component READMEs. Add more troubleshooting, removing empty sections.
Add docs folder with guides for local & remote setup, system overview, and client usage.
Add image assets for landing & system overview.
2022-06-03 15:12:09 -04:00
kautukkundan
0b7580a442 removed unused comment block 2022-06-02 20:06:08 +05:30
James Zaki
074d58b8a8 Maintain one-to-one bls key-to-wallet relationship. Fixes #104 2022-05-31 15:40:03 +01:00
James Zaki
114a5fdf6c Add/update dependencies 2022-05-31 15:37:30 +01:00
288 changed files with 14016 additions and 13674 deletions

View File

@@ -0,0 +1,47 @@
name: Build & Upload Extension
description: Builds & uploads extension for a broswer to a Github release
inputs:
node-version:
description: 'NodeJS version to use for setup & build'
required: true
browser:
description: 'Which browser to build the extension for'
required: true
file-name:
description: 'The name of the browser asset to upload'
required: true
tag-name:
description: 'Tag name of the release. Commonly github.ref in an on.release workflow'
required: true
runs:
using: composite
steps:
- uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node-version }}
cache: yarn
cache-dependency-path: extension/yarn.lock
- working-directory: ./extension
shell: bash
run: |
envsubst < config.release.json > config.json
yarn install --frozen-lockfile
- working-directory: ./extension
shell: bash
run: |
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: svenstaro/upload-release-action@v2
with:
tag: ${{ inputs.tag-name }}
# Note: This path is from repo root
# working-directory is not applied
file: ./extension/extension/quill-${{ inputs.file-name }}
overwrite: true

View File

@@ -0,0 +1,24 @@
name: Setup Contracts & Clients
description: Sets up contracts & clients
runs:
using: composite
steps:
- uses: actions/setup-node@v3
with:
node-version: 16.x
cache: yarn
cache-dependency-path: |
contracts/yarn.lock
contracts/clients/yarn.lock
- working-directory: ./contracts
shell: bash
run: |
cp .env.example .env
yarn install --frozen-lockfile
yarn build
- working-directory: ./contracts/clients
shell: bash
run: yarn install --frozen-lockfile

22
.github/labeler.yml vendored
View File

@@ -1,9 +1,25 @@
aggregator:
- aggregator/*
- aggregator/**/*
aggregator-proxy:
- aggregator-proxy/*
- aggregator-proxy/**/*
automation:
- .github/*
- .github/**/*
extension:
- extension/*
- extension/**/*
contracts:
# Don't label client only changes.
- any: ['contracts/**/*', '!contracts/clients/**/*']
- contracts/*
# Don't label client only changes.
- any: ['contracts/**/*', '!contracts/clients/**/*']
clients:
- contracts/clients/**/*
- 'contracts/clients/*'
- 'contracts/clients/**/*'
documentation:
- 'docs/*'
- 'docs/**/*'
- '*.md'
- '**/*.md'
- '**/**/*.md'

20
.github/release-drafter.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
categories:
- title: 'aggregator'
label: 'aggregator'
- title: 'aggregator-proxy'
label: 'aggregator-proxy'
- title: 'contracts'
label: 'contracts'
- title: 'clients'
label: 'clients'
- title: 'docs'
label: 'documentation'
- title: 'extension'
label: 'extension'
version-resolver:
default: minor
prerelease: true
template: |
## Whats Changed
$CHANGES

34
.github/workflows/aggregator-proxy.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: aggregator-proxy
on:
push:
branches:
- 'main'
paths:
- 'aggregator-proxy/**'
pull_request:
branches:
- 'main'
paths:
- 'aggregator-proxy/**'
defaults:
run:
working-directory: ./aggregator-proxy
env:
NODEJS_VERSION: 16.x
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODEJS_VERSION }}
cache: yarn
cache-dependency-path: aggregator-proxy/yarn.lock
- run: yarn install --frozen-lockfile
- run: yarn build

80
.github/workflows/aggregator.yml vendored Normal file
View File

@@ -0,0 +1,80 @@
name: aggregator
on:
push:
branches:
- 'main'
paths:
- 'aggregator/**'
- '.github/workflows/aggregator.yml'
pull_request:
branches:
- 'main'
paths:
- 'aggregator/**'
- '.github/workflows/aggregator.yml'
defaults:
run:
working-directory: ./aggregator
env:
DENO_VERSION: 1.x
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v1
with:
deno-version: ${{ env.DENO_VERSION }}
- run: deno lint .
todos-fixmes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v1
with:
deno-version: ${{ env.DENO_VERSION }}
- run: ./programs/lintTodos.ts
typescript:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v1
with:
deno-version: ${{ env.DENO_VERSION }}
- run: ./programs/checkTs.ts
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: denoland/setup-deno@v1
with:
deno-version: ${{ env.DENO_VERSION }}
- uses: ./.github/actions/setup-contracts-clients
# Setup contracts
- working-directory: ./contracts
run: yarn hardhat node &
- working-directory: ./contracts
run: yarn hardhat fundDeployer --network gethDev
- working-directory: ./contracts
run: yarn hardhat run scripts/deploy_all.ts --network gethDev
- working-directory: ./
run: docker-compose up -d postgres
- run: cp .env.local.example .env
- run: deno test --allow-net --allow-env --allow-read --unstable
# Cleanup
- working-directory: ./
run: docker-compose down

26
.github/workflows/clients.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: clients
on:
push:
branches:
- 'main'
paths:
- 'contracts/clients/**'
pull_request:
branches:
- 'main'
paths:
- 'contracts/clients/**'
defaults:
run:
working-directory: ./contracts/clients
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-contracts-clients
- run: yarn test

36
.github/workflows/contracts.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: contracts
on:
push:
branches:
- 'main'
paths:
- 'contracts/**'
- '!contracts/clients/**'
pull_request:
branches:
- 'main'
paths:
- 'contracts/**'
- '!contracts/clients/**'
defaults:
run:
working-directory: ./contracts
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-contracts-clients
- run: yarn lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-contracts-clients
- run: yarn test

52
.github/workflows/extension-release.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: extension-release
on:
release:
types: [published]
defaults:
run:
working-directory: ./extension
env:
NODEJS_VERSION: 16.x
jobs:
chrome:
runs-on: ubuntu-latest
environment: extension-release
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/build-upload-extension
with:
node-version: ${{ env.NODEJS_VERSION }}
browser: chrome
file-name: chrome.zip
tag-name: ${{ github.ref }}
firefox:
runs-on: ubuntu-latest
environment: extension-release
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/build-upload-extension
with:
node-version: ${{ env.NODEJS_VERSION }}
browser: firefox
file-name: firefox.xpi
tag-name: ${{ github.ref }}
opera:
runs-on: ubuntu-latest
environment: extension-release
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/build-upload-extension
with:
node-version: ${{ env.NODEJS_VERSION }}
browser: opera
file-name: opera.crx
tag-name: ${{ github.ref }}

49
.github/workflows/extension.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: extension
on:
push:
branches:
- 'main'
paths:
- 'extension/**'
pull_request:
branches:
- 'main'
paths:
- 'extension/**'
defaults:
run:
working-directory: ./extension
env:
NODEJS_VERSION: 16.x
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODEJS_VERSION }}
cache: yarn
cache-dependency-path: extension/yarn.lock
- run: yarn install --frozen-lockfile
- run: yarn lint
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODEJS_VERSION }}
cache: yarn
cache-dependency-path: extension/yarn.lock
- run: cp config.example.json config.json
- run: yarn install --frozen-lockfile
# For now, just check that chrome builds
- run: yarn build:chrome

View File

@@ -6,6 +6,6 @@ jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@main
- uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

24
.github/workflows/release-drafter.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Release Drafter
on:
push:
branches:
- main
permissions:
contents: read
jobs:
update_release_draft:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: release-drafter/release-drafter@v5
with:
config-name: release-drafter.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

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

1
.nvmrc Normal file
View File

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

60
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,60 @@
# Contribute to BLS Wallet
Thank for taking the time to contribute to BLS Wallet!
In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR.
## Getting started
To get an overview of the project, see [System Overview](docs/system_overview.md)
To setup the repo for local use, see [Local Development](docs/local_development.md)
## Issues
### Create a new issue
First search for an [existing issue](https://github.com/web3well/bls-wallet/issues). If you find one, add any new insight, helpful context, or some reactions. Otherwise, you can [open a new issue](https://github.com/web3well/bls-wallet/issues/new). Be sure to label it with anything relevant.
### Solve an issue
Search for a [existing issue](https://github.com/github/docs/issues) that is unassigned and interests you. If this is your first time contrbuting, you may want to choose a [good first issue](https://github.com/web3well/bls-wallet/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
## Make Changes
1. [Fork the repo](https://github.com/web3well/bls-wallet/fork)
2. Checkout a new branch
3. Make your changes
### Quality Checks
- You should add new/update test cases for new features or bug fixes to ensure that your changes work properly and will not be broken by other future changes.
- Type checking and code linting should all pass.
- For ambiguous Typescript typing, prefer `unknown` over `any`.
## Commit your update
Commit your changes over one or more commits. It is recommend your format your commit messages as follows:
```
A short summary of what you did
A list or paragraph of more specific details
```
## Pull Request
Create a pull request (PR) from your fork's branch to `main`, filling in the descriptions template including [linking to the issue you are resolving](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). Feel free to open a draft PR while you are actively working.
Once ready, a BLS Wallet team member will review the PR.
- When run, all Github Actions workflows should succeed.
- All TODO/FIXME comments in code should be resolved, unless marked `merge-ok` with a description/issue link describing how they can be resolved in future work.
- The author of a comment may mark it as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations) when they are satisified with a requested change or answer to a question. You are not required to resolve all comments as some may provide good historical information.
## Your PR is merged!
Thanks for your hard work! Accept our heartfelt graditiude and revel in your masterful coding and/or documentational skills.
### Thanks
To [github/docs CONTRIBUTING.md](https://github.com/github/docs/blob/main/CONTRIBUTING.md) for being a great contribution template.

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 BLS Wallet
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

181
README.md
View File

@@ -1,173 +1,46 @@
# bls-wallet
![BLS Wallet](./docs/images/bls-github-banner.svg)
An Ethereum Layer 2 smart contract wallet that uses [BLS signatures](https://en.wikipedia.org/wiki/BLS_digital_signature) and aggregated transactions to reduce gas costs.
You can watch a full end-to-end demo of the project [here](https://www.youtube.com/watch?v=MOQ3sCLP56g)
## Getting Started
- [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)
## Components
See each component's directory `README` for more details.
[contracts](./contracts/)
![System Overview](images/system-overview.svg)
Solidity smart contracts for wallets, BLS signature verification, and deployment/testing tools.
### Aggregator
[aggregator](./aggregator/)
Service which aggregates BLS wallet transactions.
Service which accepts BLS signed transactions and bundles them into one for submission.
### Clients
[aggregator-proxy](./aggregator-proxy/)
TS/JS Client libraries for web apps and services.
npm package for proxying to another aggregator instance.
### Contracts
[bls-wallet-clients](./contracts/clients/)
`bls-wallet` Solidity contracts.
npm package which provides easy to use constructs to interact with the contracts and aggregator.
### Extension
[extension](./extension/)
Quill browser extension used to manage BLS Wallets and sign transactions.
Prototype browser extension used to manage BLS Wallets and sign transactions.
### Signer
## Ways to Contribute
TS/JS BLS Signing lib.
- [Work on an open issue](https://github.com/web3well/bls-wallet/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
- [Use BLS Wallet](./docs/use_bls_wallet_clients.md) in your project and [share it with us](https://github.com/web3well/bls-wallet/discussions)
- [Report a bug or request a feature](https://github.com/web3well/bls-wallet/issues/new)
- [Ask a question or answer an existing one](https://github.com/web3well/bls-wallet/discussions)
- [Try or add to our documentation](https://github.com/web3well/bls-wallet/tree/main/docs)
## Dependencies
### Required
- [NodeJS](https://nodejs.org)
- [Yarn](https://yarnpkg.com/getting-started/install) (`npm install -g yarn`)
- [Deno](https://deno.land/#installation)
### Optional (Recomended)
- [nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
- [docker-compose](https://docs.docker.com/compose/install/)
- [MetaMask](https://metamask.io/)
## Setup
Run the repo setup script
```sh
./setup.ts
```
Then choose to target either a local Hardhat node or the Arbitrum Testnet.
### Local
Start a local Hardhat node for RPC use.
```sh
cd ./contracts
yarn hardhat node
```
You can use any two of the private keys displayed (PK0 & PK1) to update these values in `./aggregator/.env`.
```
...
PRIVATE_KEY_AGG=PK0
PRIVATE_KEY_ADMIN=PK1
...
```
Set this value in `./contracts/.env` (This mnemonic is special to hardhat and has funds).
```
...
DEPLOYER_MNEMONIC="test test test test test test test test test test test junk"
...
```
Deploy the PrecompileCostEstimator contract.
```sh
yarn hardhat run scripts/0_deploy_precompile_cost_estimator.ts --network gethDev
```
Copy the address that is output.
Update `./contracts/contracts/lib/hubble-contracts/contracts/libs/BLS.sol`'s `COST_ESTIMATOR_ADDRESS` to the value of that address;
```solidity
...
address private constant COST_ESTIMATOR_ADDRESS = 0x57047C275bbCb44D85DFA50AD562bA968EEba95A;
...
```
Deploy all remaining `bls-wallet` contracts.
```sh
yarn hardhat run scripts/deploy_all.ts --network gethDev
```
### 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 MetaMask.
Update these values in `./aggregator/.env`.
```
RPC_URL=https://rinkeby.arbitrum.io/rpc
...
NETWORK_CONFIG_PATH=../contracts/networks/arbitrum-testnet.json
PRIVATE_KEY_AGG=PK0
PRIVATE_KEY_ADMIN=PK1
...
```
And then update this value in `./extension/.env`.
```
...
DEFAULT_CHAIN_ID=421611
...
```
## Run
```sh
docker-compose up -d postgres # Or see local postgres instructions in ./aggregator/README.md#PostgreSQL
cd ./aggregator
./programs/aggregator.ts
```
In a seperate terminal/shell instance
```sh
cd ./extension
yarn run dev:chrome # or dev:firefox, dev:opera
```
### Chrome
1. Go to Chrome's [extension page](chrome://extensions).
2. Enable `Developer mode`.
3. Either click `Load unpacked extension...` and select `./extension/extension/chrome` or drag that folder into the page.
### Firefox
1. Go to Firefox's [debugging page](about:debugging#/runtime/this-firefox).
2. Click `Load Temporary Add-on...`.
3. Select `./extension/extension/firefox/manifest.json`.
## Testing/using updates to ./clients
### extension
```sh
cd ./contracts/clients
yarn build
yarn link
cd ../extension
yarn link bls-wallet-clients
```
### 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.
In `./contracts/clients` with your changes:
```
yarn publish-experimental
```
Note the `x.y.z-abc1234` version that was output.
Then in `./aggregtor/deps.ts`, change all `from` references for that package.
```typescript
...
} from "https://esm.sh/bls-wallet-clients@x.y.z-abc1234";
...
```
See our [contribution instructions & guidelines](./CONTRIBUTING.md) for more details.

View File

@@ -1,5 +1,16 @@
# Aggregator Proxy
[![npm version](https://img.shields.io/npm/v/bls-wallet-aggregator-proxy)](https://www.npmjs.com/package/bls-wallet-aggregator-proxy)
This package makes it easy to provide an aggregator by proxying another. The primary use-case is to expose a free aggregator based on one that requires payment by augmenting the bundles with transactions that pay `tx.origin`.
## Setup
```sh
npm install bls-wallet-aggregator-proxy
yarn install bls-wallet-aggregator-proxy
```
## Usage
```ts
@@ -9,7 +20,7 @@ import {
// AggregatorProxyCallback,
// ^ Alternatively, for manual control, import AggregatorProxyCallback to
// just generate the req,res callback for use with http.createServer
} from 'aggregator-proxy';
} from 'bls-wallet-aggregator-proxy';
runAggregatorProxy(
'https://arbitrum-testnet.blswallet.org',

View File

@@ -2,10 +2,14 @@
"name": "bls-wallet-aggregator-proxy",
"version": "0.1.1",
"main": "dist/src/index.js",
"repository": "https://github.com/web3well/bls-wallet",
"repository": "https://github.com/web3well/bls-wallet/aggregator-proxy",
"author": "Andrew Morris",
"license": "MIT",
"private": false,
"engines": {
"node": ">=16.0.0",
"yarn": ">=1.0.0"
},
"scripts": {
"build": "rm -rf dist && tsc"
},
@@ -17,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.7.3",
"fp-ts": "^2.12.1",
"io-ts": "^2.2.16",
"io-ts-reporters": "^2.0.1",

View File

@@ -885,10 +885,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.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/bls-wallet-clients/-/bls-wallet-clients-0.7.3.tgz#22fae2434c67b642d172023f259131233e396eb7"
integrity sha512-u2mwyf5+1KHYCyeutfl4jhEDCmsW/C+S54QKbicUkU26MvAGJ76/TxSOOIBiWTjzVDdDUBdJ/zQRoRd35GSGcQ==
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=0x0000000000000000000000000000000000000000000000000000000000000a99
PRIVATE_KEY_ADMIN=
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

@@ -8,7 +8,7 @@ Verification Gateway.
## Installation
Install [Deno](deno.land).
Install [Deno](deno.land)
### Configuration
@@ -168,6 +168,16 @@ You need to reload modules (`-r`):
deno run -r --allow-net --allow-env --allow-read --unstable ./programs/aggregator.ts
```
#### 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?
- 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.7.3";
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.7.3";
// 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.7.3";
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";

5
aggregator/programs/checkTs.ts Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env -S deno run --unstable --allow-run --allow-read --allow-write --allow-env
import { checkTs } from "./helpers/typescript.ts";
await checkTs();

View File

@@ -0,0 +1,13 @@
import * as shell from "./shell.ts";
export async function allFiles() {
return [
...await shell.Lines("git", "ls-files"),
...await shell.Lines(
"git",
"ls-files",
"--others",
"--exclude-standard",
),
];
}

View File

@@ -0,0 +1,22 @@
import * as shell from "./shell.ts";
import { allFiles } from "./git.ts";
// TODO (merge-ok) Consider turning this into a standard eslint rule
export async function lintTodosFixmes(): Promise<void> { // merge-ok
const searchArgs = [
"egrep",
"--color",
"-ni",
"todo|fixme", // merge-ok
...(await allFiles()),
];
const matches = await shell.Lines(...searchArgs);
const notOkMatches = matches.filter((m) => !m.includes("merge-ok"));
if (notOkMatches.length > 0) {
console.error(notOkMatches.join("\n"));
throw new Error(`${notOkMatches.length} todos/fixmes found`); // merge-ok
}
}

View File

@@ -0,0 +1,25 @@
import { allFiles } from "./git.ts";
import * as shell from "./shell.ts";
import nil from "../../src/helpers/nil.ts";
import repoDir from "../../src/helpers/repoDir.ts";
export async function checkTs(): Promise<void> {
let testFilePath: string | nil = nil;
try {
const tsFiles = (await allFiles()).filter((f) => f.endsWith(".ts"));
testFilePath = await Deno.makeTempFile({ suffix: ".ts" });
await Deno.writeTextFile(
testFilePath,
tsFiles.map((f) => `import "${repoDir}/${f}";`).join("\n"),
);
await shell.run("deno", "cache", "--unstable", testFilePath);
} finally {
if (testFilePath !== nil) {
await Deno.remove(testFilePath);
}
}
}

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env -S deno run --unstable --allow-run --allow-read --allow-write --allow-env
// TODO (merge-ok) Consider turning this into a standard eslint rule
import { lintTodosFixmes } from "./helpers/lint.ts"; // merge-ok
await lintTodosFixmes(); // merge-ok

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env -S deno run --unstable --allow-run --allow-read --allow-write --allow-env
import { lintTodosFixmes } from "./helpers/lint.ts"; // merge-ok
import { checkTs } from "./helpers/typescript.ts";
import * as shell from "./helpers/shell.ts";
import repoDir from "../src/helpers/repoDir.ts";
import nil from "../src/helpers/nil.ts";
import { envName } from "../src/helpers/dotEnvPath.ts";
Deno.chdir(repoDir);
@@ -27,44 +28,8 @@ function Checks(): Check[] {
["lint", async () => {
await shell.run("deno", "lint", ".");
}],
["todos and fixmes", async () => { // merge-ok
const searchArgs = [
"egrep",
"--color",
"-ni",
"todo|fixme", // merge-ok
...(await allFiles()),
];
const matches = await shell.Lines(...searchArgs);
const notOkMatches = matches.filter((m) => !m.includes("merge-ok"));
if (notOkMatches.length > 0) {
console.error(notOkMatches.join("\n"));
throw new Error(`${notOkMatches.length} todos/fixmes found`); // merge-ok
}
}],
["typescript", async () => {
let testFilePath: string | nil = nil;
try {
const tsFiles = (await allFiles()).filter((f) => f.endsWith(".ts"));
testFilePath = await Deno.makeTempFile({ suffix: ".ts" });
await Deno.writeTextFile(
testFilePath,
tsFiles.map((f) => `import "${repoDir}/${f}";`).join("\n"),
);
await shell.run("deno", "cache", "--unstable", testFilePath);
} finally {
if (testFilePath !== nil) {
await Deno.remove(testFilePath);
}
}
}],
["todos and fixmes", lintTodosFixmes], // merge-ok
["typescript", checkTs],
["test", async () => {
await shell.run(
"deno",
@@ -85,15 +50,3 @@ function Checks(): Check[] {
}],
];
}
async function allFiles() {
return [
...await shell.Lines("git", "ls-files"),
...await shell.Lines(
"git",
"ls-files",
"--others",
"--exclude-standard",
),
];
}

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

@@ -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

@@ -35,5 +35,7 @@ module.exports = {
ignores: [],
},
],
// TODO (merge-ok) Remove and fix lint error
"node/no-unpublished-import": ["warn"],
},
};

View File

@@ -91,10 +91,6 @@ Proposed solution to make use of [BLS](https://github.com/thehubbleproject/hubbl
For each network, the deployer contract can be deployed with the following script (only needed once)
`DEPLOY_DEPLOYER=true yarn hardhat run scripts/deploy-deployer.ts --network <network-name>`
## Arbitrum
## Optimism's L2 (paused)
- clone https://github.com/ethereum-optimism/optimism
- follow instructions (using latest version of docker)

View File

@@ -1,5 +1,7 @@
# BLS Wallet Clients
[![npm version](https://img.shields.io/npm/v/bls-wallet-clients)](https://www.npmjs.com/package/bls-wallet-clients)
*Client libraries for interacting with BLS Wallet components*
## Network Config
@@ -18,20 +20,20 @@ const netCfg: NetworkConfig = await getConfig(
## Aggregator
Exposes typed functions for interacting with the Aggregator's HTTP api.
Exposes typed functions for interacting with the Aggregator's HTTP API.
```ts
import { Aggregator } from 'bls-wallet-clients';
const aggregator = new Aggregator('https://rinkarby.blswallet.org');
await aggregator.addTransaction(...);
await aggregator.add(...);
```
## BlsWalletWrapper
Wraps a BLS wallet, storing the private key and providing `.sign(...)` to
produce a `Bundle`, that can be used with `aggregator.addTransaction(...)`.
produce a `Bundle`, that can be used with `aggregator.add(...)`.
```ts
import { BlsWalletWrapper } from 'bls-wallet-clients';
@@ -46,16 +48,19 @@ const bundle = wallet.sign({
nonce: await wallet.Nonce(),
actions: [
{
contract: someToken, // An ethers.Contract
method: 'transfer',
args: [recipientAddress, ethers.utils.parseUnits('1', 18)],
ethValue: 0,
contractAddress: someToken.address, // An ethers.Contract
encodedFunction: someToken.interface.encodeFunctionData(
"transfer",
["0x...some address...", ethers.BigNumber.from(1).pow(18)],
),
},
// Additional actions can go here. When using multiple actions, they'll
// either all succeed or all fail.
],
});
await aggregator.addTransaction(bundle);
await aggregator.add(bundle);
```
## VerificationGateway
@@ -118,3 +123,32 @@ import { initBlsWalletSigner } from "bls-wallet-clients";
// Send bundle to an aggregator or use it with VerificationGateway directly.
})();
```
## Local Development
### Setup
```sh
yarn install
```
### Build
```sh
yarn build
```
### Tests
```sh
yarn test
```
### Use in Extension or another project
```sh
yarn build
yarn link
cd other/project/dir
yarn "link bls-wallet-clients"
```

View File

@@ -1,6 +1,6 @@
{
"name": "bls-wallet-clients",
"version": "0.6.0",
"version": "0.7.3",
"description": "Client libraries for interacting with BLS Wallet components",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@@ -8,11 +8,15 @@
"author": "Andrew Morris",
"license": "MIT",
"private": false,
"engines": {
"node": ">=16.0.0",
"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",
"watch": "tsc -w",
"test": "mocha dist/**/*.test.js",
"premerge": "yarn build && yarn test",
"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"
},
@@ -21,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": "^9.1.1",
"chai": "^4.3.6",
"mocha": "^9.2.2",
"chai-as-promised": "^7.1.1",
"mocha": "^10.0.0",
"source-map-support": "^0.5.21",
"typescript": "^4.6.2"
"ts-node": "^10.9.1",
"typescript": "^4.8.2"
}
}

View File

@@ -57,6 +57,7 @@ export default class BlsWalletWrapper {
const initFunctionParams =
BLSWallet__factory.createInterface().encodeFunctionData("initialize", [
verificationGatewayAddress,
ethers.constants.AddressZero, // TODO: 4337 EntryPoint address
]);
return ethers.utils.getCreate2Address(

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

@@ -18,6 +18,11 @@ import { MockERC20__factory } from "../typechain/factories/MockERC20__factory";
import type { MockERC20 } from "../typechain/MockERC20";
import { NetworkConfig, getConfig, validateConfig } from "./NetworkConfig";
import {
MultiNetworkConfig,
getMultiConfig,
validateMultiConfig,
} from "./MultiNetworkConfig";
export * from "./signer";
@@ -27,6 +32,9 @@ export {
NetworkConfig,
getConfig,
validateConfig,
MultiNetworkConfig,
getMultiConfig,
validateMultiConfig,
// eslint-disable-next-line camelcase
VerificationGateway__factory,
VerificationGateway,

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";

View File

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

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,21 +741,58 @@
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==
"@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@^9.1.1":
version "9.1.1"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4"
integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==
"@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==
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"
resolved "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz"
@@ -761,6 +823,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 +866,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 +900,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 +974,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 +1003,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 +1167,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 +1246,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 +1280,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 +1300,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 +1314,30 @@ 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.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9"
integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==
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 +1352,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 +1494,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.2:
version "4.8.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
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 +1590,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

@@ -6,12 +6,12 @@ pragma abicoder v2;
//To avoid constructor params having forbidden evm bytecodes on Optimism
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "./interfaces/IWallet.sol";
import "./interfaces/UserOperation4337.sol";
/** Minimal upgradable smart contract wallet.
Generic calls can only be requested by its trusted gateway.
*/
contract BLSWallet is Initializable, IBLSWallet
contract BLSWallet is Initializable, IWallet
{
uint256 public nonce;
bytes32 public recoveryHash;
@@ -22,19 +22,14 @@ contract BLSWallet is Initializable, IBLSWallet
uint256 pendingPAFunctionTime;
// BLS variables
uint256[4] public blsPublicKey;
uint256[4] pendingBLSPublicKey;
uint256 pendingBLSPublicKeyTime;
address public trustedBLSGateway;
address public trustedEntryPoint;
address pendingBLSGateway;
uint256 pendingGatewayTime;
event PendingRecoveryHashSet(
bytes32 pendingRecoveryHash
);
event PendingBLSKeySet(
uint256[4] pendingBLSKey
);
event PendingGatewaySet(
address pendingGateway
);
@@ -46,10 +41,6 @@ contract BLSWallet is Initializable, IBLSWallet
bytes32 oldHash,
bytes32 newHash
);
event BLSKeySet(
uint256[4] oldBLSKey,
uint256[4] newBLSKey
);
event GatewayUpdated(
address oldGateway,
address newGateway
@@ -59,42 +50,17 @@ contract BLSWallet is Initializable, IBLSWallet
);
function initialize(
address blsGateway
address blsGateway,
address entryPoint
) external initializer {
nonce = 0;
trustedBLSGateway = blsGateway;
pendingGatewayTime = type(uint256).max;
pendingPAFunctionTime = type(uint256).max;
pendingRecoveryHashTime = type(uint256).max;
pendingBLSPublicKeyTime = type(uint256).max;
}
/** */
function latchBLSPublicKey(
uint256[4] memory blsKey
) public onlyTrustedGateway {
require(isZeroBLSKey(blsPublicKey), "BLSWallet: public key already set");
blsPublicKey = blsKey;
}
function isZeroBLSKey(uint256[4] memory blsKey) public pure returns (bool) {
bool isZero = true;
for (uint256 i=0; isZero && i<4; i++) {
isZero = (blsKey[i] == 0);
}
return isZero;
trustedEntryPoint = entryPoint;
}
receive() external payable {}
fallback() external payable {}
/**
BLS public key format, contract can be upgraded for other types
*/
function getBLSPublicKey() external view returns (uint256[4] memory) {
return blsPublicKey;
}
/**
Wallet can update its recovery hash
*/
@@ -111,16 +77,6 @@ contract BLSWallet is Initializable, IBLSWallet
}
}
/**
Wallet can update its BLS key
*/
function setBLSPublicKey(uint256[4] memory blsKey) public onlyThis {
require(isZeroBLSKey(blsKey) == false, "BLSWallet: blsKey must be non-zero");
pendingBLSPublicKey = blsKey;
pendingBLSPublicKeyTime = block.timestamp + 604800; // 1 week from now
emit PendingBLSKeySet(pendingBLSPublicKey);
}
/**
Wallet can migrate to a new gateway, eg additional signature support
*/
@@ -143,51 +99,45 @@ contract BLSWallet is Initializable, IBLSWallet
Set results of any pending set operation if their respective timestamp has elapsed.
*/
function setAnyPending() public {
if (block.timestamp > pendingRecoveryHashTime) {
if (pendingRecoveryHashTime != 0 &&
block.timestamp > pendingRecoveryHashTime
) {
bytes32 previousRecoveryHash = recoveryHash;
recoveryHash = pendingRecoveryHash;
clearPendingRecoveryHash();
emit RecoveryHashUpdated(previousRecoveryHash, recoveryHash);
}
if (block.timestamp > pendingBLSPublicKeyTime) {
uint256[4] memory previousBLSPublicKey = blsPublicKey;
blsPublicKey = pendingBLSPublicKey;
pendingBLSPublicKeyTime = type(uint256).max;
pendingBLSPublicKey = [0,0,0,0];
emit BLSKeySet(previousBLSPublicKey, blsPublicKey);
}
if (block.timestamp > pendingGatewayTime) {
if (pendingGatewayTime != 0 &&
block.timestamp > pendingGatewayTime
) {
address previousGateway = trustedBLSGateway;
trustedBLSGateway = pendingBLSGateway;
pendingGatewayTime = type(uint256).max;
pendingGatewayTime = 0;
pendingBLSGateway = address(0);
emit GatewayUpdated(previousGateway, trustedBLSGateway);
}
if (block.timestamp > pendingPAFunctionTime) {
if (
pendingPAFunctionTime != 0 &&
block.timestamp > pendingPAFunctionTime
) {
approvedProxyAdminFunctionHash = pendingPAFunctionHash;
pendingPAFunctionTime = type(uint256).max;
pendingPAFunctionTime = 0;
pendingPAFunctionHash = 0;
emit ProxyAdminFunctionHashApproved(approvedProxyAdminFunctionHash);
}
}
function clearPendingRecoveryHash() internal {
pendingRecoveryHashTime = type(uint256).max;
pendingRecoveryHashTime = 0;
pendingRecoveryHash = bytes32(0);
}
function recover(
uint256[4] calldata newBLSKey
) public onlyTrustedGateway {
// set new bls key
blsPublicKey = newBLSKey;
function recover() public onlyTrustedGateway {
// clear any pending operations
clearPendingRecoveryHash();
pendingBLSPublicKeyTime = type(uint256).max;
pendingBLSPublicKey = [0,0,0,0];
pendingGatewayTime = type(uint256).max;
pendingGatewayTime = 0;
pendingBLSGateway = address(0);
pendingPAFunctionTime = type(uint256).max;
pendingPAFunctionTime = 0;
pendingPAFunctionHash = 0;
}
@@ -197,7 +147,7 @@ contract BLSWallet is Initializable, IBLSWallet
*/
function performOperation(
IWallet.Operation calldata op
) public payable onlyTrustedGateway thisNonce(op.nonce) returns (
) public payable onlyTrusted thisNonce(op.nonce) returns (
bool success,
bytes[] memory results
) {
@@ -207,8 +157,10 @@ contract BLSWallet is Initializable, IBLSWallet
success = true;
results = _results;
}
catch {
catch (bytes memory returnData) {
success = false;
results = new bytes[](1);
results[0] = returnData;
}
incrementNonce(); // regardless of outcome of operation
}
@@ -236,11 +188,31 @@ contract BLSWallet is Initializable, IBLSWallet
else {
(success, result) = address(a.contractAddress).call(a.encodedFunction);
}
require(success);
if (success == false) {
bytes memory indexByte = new bytes(1);
indexByte[0] = bytes1(uint8(78)); // "N";
if (i < 10) {
indexByte[0] = bytes1(uint8(48 + i)); // "0" - "9"
}
string memory message = string.concat(
string(indexByte),
" - ",
abi.decode(stripMethodId(result), (string)) // remove "Error" methodId, it gets added again on this throw
);
revert(message);
}
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;
}
@@ -252,6 +224,25 @@ contract BLSWallet is Initializable, IBLSWallet
nonce++;
}
// --- <4337> ---
function validateUserOp(
UserOperation4337 calldata userOp,
bytes32 requestId,
address aggregator,
uint256 missingWalletFunds
) external view {
require(aggregator == trustedBLSGateway);
require(userOp.nonce == nonce);
require(missingWalletFunds == 0);
}
function getAggregator() public view returns (address) {
return trustedBLSGateway;
}
// --- </4337> ---
modifier onlyThis() {
require(msg.sender == address(this), "BLSWallet: only callable from this");
_;
@@ -265,6 +256,15 @@ contract BLSWallet is Initializable, IBLSWallet
_;
}
modifier onlyTrusted() {
require(
msg.sender == trustedBLSGateway ||
msg.sender == trustedEntryPoint,
"BLSWallet: only callable from trusted gateway or 4337 entry point"
);
_;
}
modifier thisNonce(uint256 opNonce) {
require(opNonce == nonce, "BLSWallet: only callable with current nonce");
_;

View File

@@ -8,6 +8,7 @@ import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "./interfaces/IWallet.sol";
import "./interfaces/UserOperation4337.sol";
/**
A non-upgradable gateway used to create BLSWallets and call them with
@@ -22,11 +23,16 @@ contract VerificationGateway
bytes32 BLS_DOMAIN = keccak256(abi.encodePacked(uint32(0xfeedbee5)));
uint8 constant BLS_KEY_LEN = 4;
IBLS public blsLib;
IBLS public immutable blsLib;
ProxyAdmin public immutable walletProxyAdmin;
address public blsWalletLogic;
mapping(bytes32 => IWallet) externalWalletsFromHash;
address public immutable blsWalletLogic;
mapping(bytes32 => IWallet) public walletFromHash;
mapping(IWallet => uint256[BLS_KEY_LEN]) public blsKeyFromWallet;
//mapping from an existing wallet's bls key hash to pending variables when setting a new BLS key
mapping(bytes32 => uint256[BLS_KEY_LEN]) public pendingBLSPublicKeyFromHash;
mapping(bytes32 => uint256[2]) public pendingMessageSenderSignatureFromHash;
mapping(bytes32 => uint256) public pendingBLSPublicKeyTimeFromHash;
/** Aggregated signature with corresponding senders + operations */
struct Bundle {
@@ -43,30 +49,41 @@ contract VerificationGateway
event WalletOperationProcessed(
address indexed wallet,
uint256 nonce,
bool result
IWallet.ActionData[] actions,
bool success,
bytes[] results
);
event PendingBLSKeySet(
bytes32 previousHash,
uint256[BLS_KEY_LEN] newBLSKey
);
event BLSKeySetForWallet(
uint256[BLS_KEY_LEN] newBLSKey,
IWallet wallet
);
/**
@param bls verified bls library contract address
*/
constructor(
IBLS bls,
address blsWalletImpl
address blsWalletImpl,
address proxyAdmin
) {
blsLib = bls;
blsWalletLogic = blsWalletImpl;
walletProxyAdmin = new ProxyAdmin();
walletProxyAdmin = ProxyAdmin(proxyAdmin);
}
/** Throw if bundle not valid or signature verification fails */
function verify(
Bundle calldata bundle
Bundle memory bundle
) public view {
uint256 opLength = bundle.operations.length;
require(
opLength == bundle.senderPublicKeys.length,
"VG: Sender and operation length mismatch"
"VG: Sender/op length mismatch"
);
uint256[2][] memory messages = new uint256[2][](opLength);
@@ -81,37 +98,17 @@ contract VerificationGateway
messages
);
require(verified, "VG: All sigs not verified");
require(verified, "VG: Sig not verified");
}
/**
Returns a BLSWallet if deployed from this contract, otherwise 0.
@param hash BLS public key hash used as salt for create2
@return BLSWallet at calculated address (if code exists), otherwise zero address
*/
function walletFromHash(bytes32 hash) public view returns (IWallet) {
//return wallet of hash registered explicitly
if (externalWalletsFromHash[hash] != IWallet(address(0))) {
return externalWalletsFromHash[hash];
function hashFromWallet(IWallet wallet) public view returns (bytes32) {
uint256[BLS_KEY_LEN] memory blsKey = blsKeyFromWallet[wallet];
if (blsLib.isZeroBLSKey(blsKey)) {
return bytes32(0);
}
address walletAddress = address(uint160(uint(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
hash,
keccak256(abi.encodePacked(
type(TransparentUpgradeableProxy).creationCode,
abi.encode(
address(blsWalletLogic),
address(walletProxyAdmin),
getInitializeData()
)
))
)))));
if (!hasCode(walletAddress)) {
walletAddress = address(0);
}
return IWallet(payable(walletAddress));
return keccak256(abi.encodePacked(blsKey));
}
/**
@@ -123,11 +120,42 @@ contract VerificationGateway
@param messageSenderSignature signature of message containing only the calling address
@param publicKey that signed the caller's address
*/
function setExternalWallet(
uint256[2] calldata messageSenderSignature,
uint256[BLS_KEY_LEN] calldata publicKey
function setBLSKeyForWallet(
uint256[2] memory messageSenderSignature,
uint256[BLS_KEY_LEN] memory publicKey
) public {
safeSetWallet(messageSenderSignature, publicKey, msg.sender);
require(blsLib.isZeroBLSKey(publicKey) == false, "VG: publicKey must be non-zero");
IWallet wallet = IWallet(msg.sender);
bytes32 existingHash = hashFromWallet(wallet);
if (existingHash == bytes32(0)) { // wallet does not yet have a bls key registered with this gateway
// set it instantly
safeSetWallet(messageSenderSignature, publicKey, wallet);
}
else { // wallet already has a key registered, set after delay
pendingMessageSenderSignatureFromHash[existingHash] = messageSenderSignature;
pendingBLSPublicKeyFromHash[existingHash] = publicKey;
pendingBLSPublicKeyTimeFromHash[existingHash] = block.timestamp + 604800; // 1 week from now
emit PendingBLSKeySet(existingHash, publicKey);
}
}
function setPendingBLSKeyForWallet() public {
IWallet wallet = IWallet(msg.sender);
bytes32 existingHash = hashFromWallet(wallet);
require(existingHash != bytes32(0), "VG: hash does not exist for caller");
if (
(pendingBLSPublicKeyTimeFromHash[existingHash] != 0) &&
(block.timestamp > pendingBLSPublicKeyTimeFromHash[existingHash])
) {
safeSetWallet(
pendingMessageSenderSignatureFromHash[existingHash],
pendingBLSPublicKeyFromHash[existingHash],
wallet
);
pendingMessageSenderSignatureFromHash[existingHash] = [0,0];
pendingBLSPublicKeyTimeFromHash[existingHash] = 0;
pendingBLSPublicKeyFromHash[existingHash] = [0,0,0,0];
}
}
/**
@@ -138,9 +166,9 @@ contract VerificationGateway
*/
function walletAdminCall(
bytes32 hash,
bytes calldata encodedFunction
bytes memory encodedFunction
) public onlyWallet(hash) {
IWallet wallet = walletFromHash(hash);
IWallet wallet = walletFromHash[hash];
// ensure first parameter is the calling wallet address
bytes memory encodedAddress = abi.encode(address(wallet));
@@ -179,20 +207,18 @@ contract VerificationGateway
@param newBLSKey to set as the wallet's bls public key
*/
function recoverWallet(
uint256[2] calldata walletAddressSignature,
uint256[2] memory walletAddressSignature,
bytes32 blsKeyHash,
bytes32 salt,
uint256[BLS_KEY_LEN] calldata newBLSKey
uint256[BLS_KEY_LEN] memory newBLSKey
) public {
IWallet wallet = walletFromHash(blsKeyHash);
IWallet wallet = walletFromHash[blsKeyHash];
bytes32 recoveryHash = keccak256(
abi.encodePacked(msg.sender, blsKeyHash, salt)
);
if (recoveryHash == wallet.recoveryHash()) {
// override mapping of old key hash (takes precedence over create2 address)
externalWalletsFromHash[blsKeyHash] = IWallet(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF);
safeSetWallet(walletAddressSignature, newBLSKey, address(wallet));
wallet.recover(newBLSKey);
safeSetWallet(walletAddressSignature, newBLSKey, wallet);
wallet.recover();
}
}
@@ -211,7 +237,7 @@ contract VerificationGateway
"BLSWallet: gateway address param not valid"
);
IWallet wallet = walletFromHash(hash);
IWallet wallet = walletFromHash[hash];
require(
VerificationGateway(blsGateway).walletFromHash(hash) == wallet,
@@ -234,7 +260,7 @@ contract VerificationGateway
Can be called with a single operation with no actions.
*/
function processBundle(
Bundle calldata bundle
Bundle memory bundle
) external returns (
bool[] memory successes,
bytes[][] memory results
@@ -260,7 +286,9 @@ contract VerificationGateway
emit WalletOperationProcessed(
address(wallet),
bundle.operations[i].nonce,
successes[i]
bundle.operations[i].actions,
successes[i],
results[i]
);
}
}
@@ -271,18 +299,18 @@ contract VerificationGateway
needed.
*/
function getOrCreateWallet(
uint256[BLS_KEY_LEN] calldata publicKey
uint256[BLS_KEY_LEN] memory publicKey
) private returns (IWallet) {
bytes32 publicKeyHash = keccak256(abi.encodePacked(publicKey));
address blsWallet = address(walletFromHash(publicKeyHash));
// wallet with publicKeyHash doesn't exist at expected create2 address
if (blsWallet == address(0)) {
blsWallet = address(new TransparentUpgradeableProxy{salt: publicKeyHash}(
IWallet blsWallet = walletFromHash[publicKeyHash];
// publicKeyHash does not yet refer to a wallet, create one then update mappings.
if (address(blsWallet) == address(0)) {
blsWallet = IWallet(address(new TransparentUpgradeableProxy{salt: publicKeyHash}(
address(blsWalletLogic),
address(walletProxyAdmin),
getInitializeData()
));
IBLSWallet(payable(blsWallet)).latchBLSPublicKey(publicKey);
)));
updateWalletHashMappings(publicKey, blsWallet);
emit WalletCreated(
address(blsWallet),
publicKey
@@ -298,10 +326,11 @@ contract VerificationGateway
@param wallet address to set
*/
function safeSetWallet(
uint256[2] calldata wallletAddressSignature,
uint256[BLS_KEY_LEN] calldata publicKey,
address wallet
uint256[2] memory wallletAddressSignature,
uint256[BLS_KEY_LEN] memory publicKey,
IWallet wallet
) private {
// verify the given wallet was signed for by the bls key
uint256[2] memory addressMsg = blsLib.hashToPoint(
BLS_DOMAIN,
abi.encodePacked(wallet)
@@ -310,38 +339,43 @@ contract VerificationGateway
blsLib.verifySingle(wallletAddressSignature, publicKey, addressMsg),
"VG: Signature not verified for wallet address."
);
bytes32 publicKeyHash = keccak256(abi.encodePacked(
publicKey
));
externalWalletsFromHash[publicKeyHash] = IWallet(wallet);
emit BLSKeySetForWallet(publicKey, wallet);
updateWalletHashMappings(publicKey, wallet);
}
function hasCode(address a) private view returns (bool) {
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly { size := extcodesize(a) }
return size > 0;
/** @dev Only to be called on wallet creation, and in `safeSetWallet` */
function updateWalletHashMappings(
uint256[BLS_KEY_LEN] memory blsKey,
IWallet wallet
) private {
// remove reference from old hash
bytes32 oldHash = hashFromWallet(wallet);
walletFromHash[oldHash] = IWallet(address(0));
// update new hash / wallet mappings
walletFromHash[keccak256(abi.encodePacked(blsKey))] = wallet;
blsKeyFromWallet[wallet] = blsKey;
}
function getInitializeData() private view returns (bytes memory) {
return abi.encodeWithSignature("initialize(address)", address(this));
return abi.encodeWithSignature("initialize(address,address)", address(this), address(0));
}
modifier onlyWallet(bytes32 hash) {
require(
(msg.sender == address(walletFromHash(hash))),
(IWallet(msg.sender) == walletFromHash[hash]),
"VG: not called from wallet"
);
_;
}
function messagePoint(
IWallet.Operation calldata op
IWallet.Operation memory op
) internal view returns (
uint256[2] memory
) {
bytes memory encodedActionData;
IWallet.ActionData calldata a;
IWallet.ActionData memory a;
for (uint256 i=0; i<op.actions.length; i++) {
a = op.actions[i];
encodedActionData = abi.encodePacked(
@@ -361,4 +395,49 @@ contract VerificationGateway
);
}
}
// --- <4337> ---
// These functions seem to exist to allow clients (aka nodes) to rely on logic defined on-chain
// so that they don't need to implement anything specific to any signature aggregation scheme.
// For our prototype we can do this specific implementation and make this work without these
// functions. Later, it might be necessary to add these for compatibility with clients relying
// on this generic technique.
// function validateUserOpSignature(
// UserOperation4337 calldata userOp,
// bool offChainSigCheck
// ) external view returns (
// bytes memory sigForUserOp,
// bytes memory sigForAggregation,
// bytes memory offChainSigInfo
// ) {}
// function aggregateSignatures(
// bytes[] calldata sigsForAggregation
// ) external view returns (bytes memory aggregatesSignature) {}
function validateSignatures(
UserOperation4337[] calldata userOps,
bytes calldata signature
) view external {
uint256[2][] memory messages = new uint256[2][](userOps.length);
uint256[BLS_KEY_LEN][] memory senderPublicKeys = new uint256[BLS_KEY_LEN][](userOps.length);
for (uint256 i = 0; i < userOps.length; i++) {
messages[i] = blsLib.hashToPoint(
BLS_DOMAIN,
abi.encode(userOps[i])
);
senderPublicKeys[i] = blsKeyFromWallet[IWallet(userOps[i].sender)];
}
bool verified = blsLib.verifyMultiple(
abi.decode(signature, (uint256[2])),
senderPublicKeys,
messages
);
require(verified, "VG: Sig not verified");
}
// --- </4337> ---
}

View File

@@ -17,7 +17,7 @@ interface IWallet {
bytes encodedFunction;
}
function initialize(address gateway) external;
function initialize(address gateway, address entryPoint) external;
function nonce() external returns (uint256);
function performOperation(
@@ -28,7 +28,7 @@ interface IWallet {
);
function recoveryHash() external returns (bytes32);
function recover(uint256[4] calldata newBLSKey) external;
function recover() external;
// prepares gateway to be set (after pending timestamp)
function setTrustedGateway(address gateway) external;
@@ -39,16 +39,3 @@ interface IWallet {
function approvedProxyAdminFunctionHash() external view returns (bytes32);
function clearApprovedProxyAdminFunctionHash() external;
}
/** Interface for bls-specific functions
*/
interface IBLSWallet is IWallet {
// type BLSPublicKey is uint256[4]; // The underlying type for a user defined value type has to be an elementary value type.
function latchBLSPublicKey(
uint256[4] memory blsKey
) external;
function getBLSPublicKey() external view returns (uint256[4] memory);
}

View File

@@ -0,0 +1,16 @@
//SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.4 <0.9.0;
struct UserOperation4337 {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
uint256 callGasLimit;
uint256 verificationGasLimit;
uint256 preVerificationGas;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
bytes paymasterAndData;
bytes signature;
}

View File

@@ -53,4 +53,12 @@ library BLSOpen {
);
}
function isZeroBLSKey(uint256[4] memory blsKey) public pure returns (bool) {
bool isZero = true;
for (uint256 i=0; isZero && i<4; i++) {
isZero = (blsKey[i] == 0);
}
return isZero;
}
}

View File

@@ -20,4 +20,6 @@ interface IBLS {
bytes memory message
) external view returns (uint256[2] memory);
function isZeroBLSKey(uint256[4] memory blsKey) external pure returns (bool);
}

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";
@@ -8,12 +8,13 @@ import * as chai from "chai";
import chaiAsPromised from "chai-as-promised";
import "hardhat-gas-reporter";
import "solidity-coverage";
import defaultDeployerWallets from "./shared/helpers/defaultDeployerWallet";
dotenv.config();
// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
task("accounts", "Prints the list of accounts", async (_taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
@@ -21,6 +22,43 @@ 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) => {
const [account0] = await hre.ethers.getSigners();
const deployerAddress = defaultDeployerWallets(hre.ethers).address;
console.log(`${account0.address} -> ${deployerAddress} ${amount} ETH`);
const txnRes = await account0.sendTransaction({
to: deployerAddress,
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);
@@ -37,11 +75,11 @@ const config: HardhatUserConfig = {
solidity: {
compilers: [
{
version: "0.8.10",
version: "0.8.15",
settings: {
optimizer: {
enabled: true,
runs: 1000,
runs: 1,
},
},
},
@@ -69,6 +107,7 @@ const config: HardhatUserConfig = {
hardhat: {
initialBaseFeePerGas: 0, // workaround from https://github.com/sc-forks/solidity-coverage/issues/652#issuecomment-896330136 . Remove when that issue is closed.
accounts,
blockGasLimit: 200_000_000,
},
gethDev: {
url: `http://localhost:8545`,
@@ -85,6 +124,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": "0xAf96d6e0817Ff8658f0E2a39b641920fA7fF0957",
"blsExpander": "0x376E7c0dA79423F772C2837744F81a7A0ff4bA47",
"utilities": "0x957e58EfEB6cE40F95f3dBFAaCD9465Df5C29E23",
"testToken": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853"
},
"auxiliary": {
"chainid": 421613,
"domain": "0x0054159611832e24cdd64c6a133e71d373c5f8553dde6c762e6bffe707ad83cc",
"genesisBlock": 277661,
"deployedBy": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"version": "03ced09fc2f3b9b8255b05060fcf78c7880a79ca"
}
}

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

@@ -3,10 +3,16 @@
"version": "1.0.0",
"description": "BLS Wallet smart contract",
"main": "index.js",
"engines": {
"node": ">=16.0.0",
"yarn": ">=1.0.0"
},
"scripts": {
"build": "hardhat compile",
"check-ts": "tsc --noEmit",
"lint": "eslint . --ext .ts",
"premerge": "rm -rf artifacts cache typechain && hardhat compile && yarn lint && yarn check-ts && yarn --cwd clients premerge && yarn hardhat test"
"test": "hardhat test",
"premerge": "rm -rf artifacts cache typechain && hardhat compile && lint && check-ts && yarn --cwd clients premerge && test"
},
"author": "James Zaki",
"license": "MIT",

View File

@@ -75,6 +75,8 @@ export default class Create2Fixture {
const initCode = factory.bytecode + constructorParamsBytes.substr(2);
const initCodeHash = ethers.utils.solidityKeccak256(["bytes"], [initCode]);
console.log((initCode.length - 2) / 2, "bytes in contract", contractName);
const contractAddress = ethers.utils.getCreate2Address(
create2Deployer.address,
"0x" + salt.toHexString().substr(2).padStart(64, "0"),

View File

@@ -19,7 +19,7 @@ import {
import Range from "./Range";
import assert from "./assert";
import Create2Fixture from "./Create2Fixture";
import { VerificationGateway, BLSOpen } from "../../typechain";
import { VerificationGateway, BLSOpen, ProxyAdmin } from "../../typechain";
export default class Fixture {
static readonly ECDSA_ACCOUNTS_LENGTH = 5;
@@ -68,14 +68,20 @@ export default class Fixture {
} catch (e) {}
const bls = (await create2Fixture.create2Contract("BLSOpen")) as BLSOpen;
const ProxyAdmin = await ethers.getContractFactory("ProxyAdmin");
const proxyAdmin = (await ProxyAdmin.deploy()) as ProxyAdmin;
await proxyAdmin.deployed();
// deploy Verification Gateway
const verificationGateway = (await create2Fixture.create2Contract(
"VerificationGateway",
ethers.utils.defaultAbiCoder.encode(
["address", "address"],
[bls.address, blsWalletImpl.address],
["address", "address", "address"],
[bls.address, blsWalletImpl.address, proxyAdmin.address],
),
)) as VerificationGateway;
await (
await proxyAdmin.transferOwnership(verificationGateway.address)
).wait();
// deploy BLSExpander Gateway
const blsExpander = await create2Fixture.create2Contract(

View File

@@ -0,0 +1,19 @@
/**
* Note: This file cannot have any direct imports
* of hardhat since it is used in hardhat.config.ts.
*/
import { HardhatEthersHelpers } from "@nomiclabs/hardhat-ethers/types";
import { Wallet } from "ethers";
/**
*
* @returns Wallet constructed from DEPLOYER_ env vars
*/
export default function defaultDeployerWallet(
ethers: HardhatEthersHelpers,
): Wallet {
return Wallet.fromMnemonic(
`${process.env.DEPLOYER_MNEMONIC}`,
`m/44'/60'/0'/0/${process.env.DEPLOYER_SET_INDEX}`,
).connect(ethers.provider);
}

View File

@@ -4,6 +4,7 @@ import "@nomiclabs/hardhat-ethers";
import { ethers } from "hardhat";
import { Wallet } from "ethers";
import { Create2Deployer } from "../../typechain";
import defaultDeployerWalletHardhat from "./defaultDeployerWallet";
dotenv.config();
@@ -11,15 +12,8 @@ export function defaultDeployerAddress(): string {
return defaultDeployerWallet().address;
}
/**
*
* @returns Wallet constructed from DEPLOYER_ env vars
*/
export function defaultDeployerWallet(): Wallet {
return ethers.Wallet.fromMnemonic(
`${process.env.DEPLOYER_MNEMONIC}`,
`m/44'/60'/0'/0/${process.env.DEPLOYER_SET_INDEX}`,
).connect(ethers.provider);
return defaultDeployerWalletHardhat(ethers);
}
/**

View File

@@ -3,11 +3,11 @@ import { BigNumber } from "ethers";
import { solidityPack } from "ethers/lib/utils";
import { ethers, network } from "hardhat";
import { PublicKey, BlsWalletWrapper, Signature } from "../clients/src";
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 } from "../typechain";
import { BLSWallet, VerificationGateway } from "../typechain";
const signWalletAddress = async (
fx: Fixture,
@@ -43,14 +43,18 @@ describe("Recovery", async function () {
const safetyDelaySeconds = 7 * 24 * 60 * 60;
let fx: Fixture;
let wallet1, wallet2, walletAttacker;
let vg: VerificationGateway;
let wallet1: BlsWalletWrapper;
let wallet2: BlsWalletWrapper;
let walletAttacker: BlsWalletWrapper;
let blsWallet: BLSWallet;
let recoverySigner;
let hash1, hash2;
let hash1, hash2, hashAttacker;
let salt;
let recoveryHash;
beforeEach(async function () {
fx = await Fixture.create();
vg = fx.verificationGateway;
wallet1 = await fx.lazyBlsWallets[0]();
wallet2 = await fx.lazyBlsWallets[1]();
@@ -60,6 +64,9 @@ describe("Recovery", async function () {
hash1 = wallet1.blsWalletSigner.getPublicKeyHash(wallet1.privateKey);
hash2 = wallet2.blsWalletSigner.getPublicKeyHash(wallet2.privateKey);
hashAttacker = wallet2.blsWalletSigner.getPublicKeyHash(
walletAttacker.privateKey,
);
salt = "0x1234567812345678123456781234567812345678123456781234567812345678";
recoveryHash = ethers.utils.solidityKeccak256(
["address", "bytes32", "bytes32"],
@@ -68,34 +75,44 @@ describe("Recovery", async function () {
});
it("should update bls key", async function () {
const newKey: PublicKey = [
BigNumber.from(1),
BigNumber.from(2),
BigNumber.from(3),
BigNumber.from(4),
];
const initialKey = await blsWallet.getBLSPublicKey();
expect(await vg.hashFromWallet(wallet1.address)).to.eql(hash1);
await fx.call(wallet1, blsWallet, "setBLSPublicKey", [newKey], 1);
const addressSignature = await signWalletAddress(
fx,
wallet1.address,
wallet2.privateKey,
);
expect(await blsWallet.getBLSPublicKey()).to.eql(initialKey);
await fx.call(
wallet1,
vg,
"setBLSKeyForWallet",
[addressSignature, wallet2.PublicKey()],
1,
);
await fx.advanceTimeBy(safetyDelaySeconds + 1);
await (await blsWallet.setAnyPending()).wait();
await fx.call(wallet1, vg, "setPendingBLSKeyForWallet", [], 2);
expect(await blsWallet.getBLSPublicKey()).to.eql(newKey);
expect(await vg.hashFromWallet(wallet1.address)).to.eql(hash2);
});
it("should NOT override public key after creation", async function () {
const initialKey = await blsWallet.getBLSPublicKey();
it("should NOT override public key hash after creation", async function () {
let walletForHash = await vg.walletFromHash(hash1);
expect(BigNumber.from(walletForHash)).to.not.equal(BigNumber.from(0));
expect(walletForHash).to.equal(wallet1.address);
const ZERO = ethers.BigNumber.from(0);
expect(initialKey).to.not.eql([ZERO, ZERO, ZERO, ZERO]);
let hashFromWallet = await vg.hashFromWallet(wallet1.address);
expect(BigNumber.from(hashFromWallet)).to.not.equal(BigNumber.from(0));
expect(hashFromWallet).to.equal(hash1);
await blsWallet.setAnyPending();
await fx.call(wallet1, vg, "setPendingBLSKeyForWallet", [], 1);
const finalKey = await blsWallet.getBLSPublicKey();
expect(finalKey).to.eql(initialKey);
walletForHash = await vg.walletFromHash(hash1);
expect(walletForHash).to.equal(wallet1.address);
hashFromWallet = await vg.hashFromWallet(wallet1.address);
expect(hashFromWallet).to.equal(hash1);
});
it("should set recovery hash", async function () {
@@ -118,14 +135,25 @@ describe("Recovery", async function () {
it("should recover before bls key update", async function () {
await fx.call(wallet1, blsWallet, "setRecoveryHash", [recoveryHash], 1);
const attackKey = walletAttacker.PublicKey();
// Attacker assumed to have compromised current bls key, and wishes to reset
// the contract's bls key to their own.
await fx.call(wallet1, blsWallet, "setBLSPublicKey", [attackKey], 2);
const attackSignature = await signWalletAddress(
fx,
wallet1.address,
walletAttacker.privateKey,
);
// Attacker assumed to have compromised wallet1 bls key, and wishes to reset
// the gateway wallet's bls key to their own.
await fx.call(
wallet1,
vg,
"setBLSKeyForWallet",
[attackSignature, walletAttacker.PublicKey()],
1,
);
await fx.advanceTimeBy(safetyDelaySeconds / 2); // wait half the time
await (await blsWallet.setAnyPending()).wait();
await fx.call(wallet1, vg, "setPendingBLSKeyForWallet", [], 2);
const addressSignature = await signWalletAddress(
fx,
@@ -141,33 +169,34 @@ describe("Recovery", async function () {
).wait();
// key reset via recovery
expect(await blsWallet.getBLSPublicKey()).to.eql(
safeKey.map(BigNumber.from),
);
expect(await vg.hashFromWallet(wallet1.address)).to.eql(hash2);
expect(await vg.walletFromHash(hash2)).to.eql(wallet1.address);
await fx.advanceTimeBy(safetyDelaySeconds / 2 + 1); // wait remainder the time
// attacker's key not set after waiting full safety delay
expect(await blsWallet.getBLSPublicKey()).to.eql(
safeKey.map(BigNumber.from),
// check attacker's key not set after waiting full safety delay
await fx.call(
walletAttacker,
vg,
"setPendingBLSKeyForWallet",
[],
await walletAttacker.Nonce(),
);
await fx.call(
wallet2,
vg,
"setPendingBLSKeyForWallet",
[],
await wallet2.Nonce(),
);
let walletFromKey = await fx.verificationGateway.walletFromHash(
wallet1.blsWalletSigner.getPublicKeyHash(wallet1.privateKey),
expect(await vg.walletFromHash(hash1)).to.not.equal(blsWallet.address);
expect(await vg.walletFromHash(hashAttacker)).to.not.equal(
blsWallet.address,
);
expect(walletFromKey).to.not.equal(blsWallet.address);
walletFromKey = await fx.verificationGateway.walletFromHash(
walletAttacker.blsWalletSigner.getPublicKeyHash(
walletAttacker.privateKey,
),
);
expect(walletFromKey).to.not.equal(blsWallet.address);
walletFromKey = await fx.verificationGateway.walletFromHash(
wallet2.blsWalletSigner.getPublicKeyHash(wallet2.privateKey),
);
expect(walletFromKey).to.equal(blsWallet.address);
expect(await vg.walletFromHash(hash2)).to.equal(blsWallet.address);
// verify recovered bls key can successfully call wallet-only function (eg setTrustedGateway)
// // verify recovered bls key can successfully call wallet-only function (eg setTrustedGateway)
const res = await fx.callStatic(
wallet2,
fx.verificationGateway,
@@ -184,15 +213,12 @@ describe("Recovery", async function () {
"BLSWallet",
walletAttacker.address,
);
const hashAttacker = walletAttacker.blsWalletSigner.getPublicKeyHash(
walletAttacker.privateKey,
);
// Attacker users recovery signer to set their recovery hash
const attackerRecoveryHash = ethers.utils.solidityKeccak256(
["address", "bytes32", "bytes32"],
[recoverySigner.address, hashAttacker, salt],
);
// Attacker puts their wallet into recovery
await fx.call(
walletAttacker,
attackerWalletContract,
@@ -204,6 +230,9 @@ describe("Recovery", async function () {
// Attacker waits out safety delay
await fx.advanceTimeBy(safetyDelaySeconds + 1);
await (await attackerWalletContract.setAnyPending()).wait();
expect(await attackerWalletContract.recoveryHash()).to.equal(
attackerRecoveryHash,
);
const addressSignature = await signWalletAddress(
fx,
@@ -212,7 +241,7 @@ describe("Recovery", async function () {
);
const wallet1Key = await wallet1.PublicKey();
// Attacker attempts to overwite wallet 1's public key and fails
// Attacker attempts to overwrite wallet 1's hash in the gateway and fails
await expect(
fx.verificationGateway
.connect(recoverySigner)

View File

@@ -3,7 +3,7 @@ import { BigNumber } from "ethers";
import { solidityPack } from "ethers/lib/utils";
import { ethers, network } from "hardhat";
import { BLSOpen } from "../typechain";
import { BLSOpen, ProxyAdmin } from "../typechain";
import { ActionData, BlsWalletWrapper } from "../clients/src";
import Fixture from "../shared/helpers/Fixture";
import deployAndRunPrecompileCostEstimator from "../shared/helpers/deployAndRunPrecompileCostEstimator";
@@ -76,6 +76,10 @@ describe("Upgrade", async function () {
// Deploy new verification gateway
const create2Fixture = Create2Fixture.create();
const bls = (await create2Fixture.create2Contract("BLSOpen")) as BLSOpen;
const ProxyAdmin = await ethers.getContractFactory("ProxyAdmin");
const proxyAdmin2 = (await ProxyAdmin.deploy()) as ProxyAdmin;
await proxyAdmin2.deployed();
const blsWalletImpl = await create2Fixture.create2Contract("BLSWallet");
const VerificationGateway = await ethers.getContractFactory(
"VerificationGateway",
@@ -83,7 +87,9 @@ describe("Upgrade", async function () {
const vg2 = await VerificationGateway.deploy(
bls.address,
blsWalletImpl.address,
proxyAdmin2.address,
);
await (await proxyAdmin2.transferOwnership(vg2.address)).wait();
// Recreate hubble bls signer
const walletOldVg = await fx.lazyBlsWallets[0]();
@@ -123,7 +129,7 @@ describe("Upgrade", async function () {
const setExternalWalletAction: ActionData = {
ethValue: BigNumber.from(0),
contractAddress: vg2.address,
encodedFunction: vg2.interface.encodeFunctionData("setExternalWallet", [
encodedFunction: vg2.interface.encodeFunctionData("setBLSKeyForWallet", [
addressSignature,
walletOldVg.PublicKey(),
]),
@@ -227,6 +233,7 @@ describe("Upgrade", async function () {
// Direct checks corresponding to each action
expect(await vg2.walletFromHash(hash)).to.equal(walletAddress);
expect(await vg2.hashFromWallet(walletAddress)).to.equal(hash);
expect(await proxyAdmin.getProxyAdmin(walletAddress)).to.equal(
proxyAdmin.address,
);
@@ -267,4 +274,77 @@ describe("Upgrade", async function () {
)[0];
expect(walletFromHashAddress).to.equal(walletAddress);
});
it("should change mapping of an address to hash", async function () {
const vg1 = fx.verificationGateway;
const lazyWallet1 = await fx.lazyBlsWallets[0]();
const lazyWallet2 = await fx.lazyBlsWallets[1]();
const wallet1 = await BlsWalletWrapper.connect(
lazyWallet1.privateKey,
vg1.address,
fx.provider,
);
const wallet2 = await BlsWalletWrapper.connect(
lazyWallet2.privateKey,
vg1.address,
fx.provider,
);
const hash1 = wallet1.blsWalletSigner.getPublicKeyHash(wallet1.privateKey);
expect(await vg1.walletFromHash(hash1)).to.equal(wallet1.address);
expect(await vg1.hashFromWallet(wallet1.address)).to.equal(hash1);
// wallet 2 bls key signs message containing address of wallet 1
const addressMessage = solidityPack(["address"], [wallet1.address]);
const addressSignature = wallet2.signMessage(addressMessage);
const setExternalWalletAction: ActionData = {
ethValue: BigNumber.from(0),
contractAddress: vg1.address,
encodedFunction: vg1.interface.encodeFunctionData("setBLSKeyForWallet", [
addressSignature,
wallet2.PublicKey(),
]),
};
// wallet 1 submits a tx
{
const { successes } = await vg1.callStatic.processBundle(
wallet1.sign({
nonce: BigNumber.from(1),
actions: [setExternalWalletAction],
}),
);
expect(successes).to.deep.equal([true]);
}
await (
await fx.verificationGateway.processBundle(
fx.blsWalletSigner.aggregate([
wallet1.sign({
nonce: BigNumber.from(1),
actions: [setExternalWalletAction],
}),
]),
)
).wait();
// wallet 1's hash is pointed to null address
// wallet 2's hash is now pointed to wallet 1's address
const hash2 = wallet2.blsWalletSigner.getPublicKeyHash(wallet2.privateKey);
await fx.advanceTimeBy(safetyDelaySeconds + 1);
await fx.call(wallet1, vg1, "setPendingBLSKeyForWallet", [], 2);
expect(await vg1.walletFromHash(hash1)).to.equal(
ethers.constants.AddressZero,
);
expect(await vg1.walletFromHash(hash2)).to.equal(wallet1.address);
expect(await vg1.hashFromWallet(wallet1.address)).to.equal(hash2);
});
});

View File

@@ -5,7 +5,7 @@ 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";
@@ -118,7 +118,6 @@ describe("WalletActions", async function () {
actions: [
{
ethValue: ethToTransfer,
// TODO: Does wallet contract need to exist?
contractAddress: recvWallet.walletContract.address,
encodedFunction: "0x",
},
@@ -289,11 +288,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.
{
@@ -308,21 +317,21 @@ 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();
// Single event "WalletOperationProcessed(address indexed wallet, uint256 nonce, bool success, bytes[] results)"
// Get the first (only) result from "results" argument.
const result = r.events[0].args.results[0]; // For errors this is "Error(string)"
const errorArgBytesString: string = "0x" + result.substring(10); // remove methodId (4bytes after 0x)
const errorString = ethers.utils.defaultAbiCoder.decode(
["string"],
errorArgBytesString,
)[0]; // decoded bytes is a string of the action index that errored.
expect(errorString).to.equal("1 - ERC20: transfer from the zero address");
const recipientBalance = await th.testToken.balanceOf(recipient.address);
// Should be unchanged because the operation that would have added tokens

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

9
docs/README.md Normal file
View File

@@ -0,0 +1,9 @@
## Docs
- [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](./local_development.md)
- [Remote development](./remote_development.md)

View File

@@ -0,0 +1,24 @@
<svg width="1280" height="640" viewBox="0 0 1280 640" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="1280" height="640" fill="#5A9DED"/>
<path d="M209 379.573V144.717H310.729C328.6 144.717 343.607 147.125 355.75 151.941C367.97 156.758 377.173 163.562 383.359 172.354C389.622 181.146 392.753 191.505 392.753 203.431C392.753 212.07 390.844 219.944 387.025 227.054C383.283 234.164 378.013 240.127 371.216 244.944C364.419 249.684 356.476 252.971 347.387 254.806V257.099C357.469 257.482 366.633 260.043 374.882 264.783C383.13 269.446 389.698 275.906 394.586 284.163C399.474 292.343 401.918 301.976 401.918 313.061C401.918 325.905 398.557 337.334 391.836 347.349C385.192 357.364 375.722 365.239 363.426 370.973C351.13 376.706 336.466 379.573 319.435 379.573H209ZM272.695 328.657H302.48C313.172 328.657 321.192 326.669 326.538 322.694C331.884 318.642 334.557 312.679 334.557 304.804C334.557 299.3 333.297 294.637 330.776 290.814C328.256 286.991 324.667 284.086 320.008 282.099C315.425 280.111 309.888 279.117 303.397 279.117H272.695V328.657ZM272.695 239.669H298.814C304.39 239.669 309.316 238.789 313.592 237.031C317.869 235.273 321.192 232.75 323.559 229.462C326.003 226.099 327.225 222.008 327.225 217.192C327.225 209.929 324.628 204.387 319.435 200.564C314.242 196.665 307.674 194.716 299.731 194.716H272.695V239.669Z" fill="#FCFCFC"/>
<path d="M424.486 379.573V144.717H488.181V328.198H575.049V379.573H424.486Z" fill="#FCFCFC"/>
<path d="M727.27 218.109C726.659 210.464 723.795 204.501 718.678 200.22C713.637 195.939 705.962 193.798 695.651 193.798C689.083 193.798 683.699 194.601 679.498 196.206C675.374 197.735 672.319 199.838 670.334 202.513C668.348 205.189 667.317 208.247 667.241 211.688C667.088 214.516 667.584 217.077 668.73 219.371C669.952 221.588 671.861 223.614 674.458 225.449C677.055 227.207 680.377 228.813 684.425 230.265C688.472 231.718 693.284 233.017 698.859 234.164L718.105 238.292C731.088 241.045 742.201 244.676 751.442 249.187C760.683 253.697 768.244 259.011 774.124 265.127C780.005 271.166 784.32 277.97 787.07 285.539C789.895 293.108 791.346 301.364 791.423 310.309C791.346 325.752 787.49 338.825 779.852 349.528C772.215 360.231 761.294 368.373 747.088 373.954C732.959 379.535 715.966 382.325 696.11 382.325C675.718 382.325 657.923 379.306 642.725 373.266C627.603 367.226 615.842 357.938 607.441 345.4C599.116 332.785 594.916 316.654 594.839 297.007H655.327C655.708 304.193 657.503 310.232 660.711 315.125C663.918 320.018 668.424 323.726 674.229 326.249C680.109 328.772 687.098 330.033 695.193 330.033C701.99 330.033 707.68 329.192 712.262 327.51C716.845 325.828 720.32 323.497 722.687 320.515C725.055 317.534 726.277 314.131 726.353 310.309C726.277 306.716 725.093 303.581 722.802 300.906C720.587 298.153 716.921 295.707 711.804 293.566C706.687 291.349 699.775 289.285 691.069 287.374L667.699 282.328C646.926 277.817 630.544 270.287 618.553 259.737C606.639 249.11 600.72 234.623 600.796 216.275C600.72 201.367 604.691 188.332 612.71 177.17C620.806 165.932 631.995 157.178 646.276 150.909C660.634 144.64 677.093 141.506 695.651 141.506C714.592 141.506 730.974 144.679 744.797 151.024C758.621 157.369 769.275 166.314 776.759 177.858C784.32 189.326 788.139 202.743 788.215 218.109H727.27Z" fill="#FCFCFC"/>
<path d="M1142.29 284.637C1095.4 234.764 1069.33 169.175 1069.33 101.059V89.1151C1069.34 87.6533 1068.9 86.2241 1068.07 85.0142C1067.24 83.8041 1066.06 82.8696 1064.69 82.3324C1009.11 60.7152 947.377 60.3857 891.565 81.4083C890.221 81.9288 889.063 82.83 888.235 83.999C887.407 85.1678 886.946 86.5525 886.91 87.9795L958.557 228.3C962.21 225.844 966.332 224.153 970.67 223.333C975.008 222.512 979.47 222.578 983.78 223.529C988.091 224.479 992.159 226.292 995.735 228.858C999.31 231.423 1002.32 234.684 1004.57 238.444C1006.82 242.202 1008.28 246.377 1008.84 250.712C1009.4 255.046 1009.05 259.448 1007.83 263.647C1006.61 267.845 1004.52 271.75 1001.72 275.124C998.909 278.497 995.433 281.265 991.503 283.259C990.178 283.913 989.06 284.918 988.278 286.162C987.495 287.406 987.077 288.842 987.07 290.308V531.664C987.07 532.603 987.265 533.531 987.644 534.392C988.023 535.254 988.578 536.027 989.274 536.666C989.969 537.304 990.791 537.795 991.686 538.106C992.583 538.416 993.533 538.54 994.479 538.47H994.559C995.922 538.369 997.224 537.873 998.303 537.044C999.383 536.215 1000.19 535.091 1000.63 533.81C1031.19 444.852 1084.54 362.552 1142.52 294.796C1143.75 293.367 1144.4 291.545 1144.36 289.674C1144.32 287.801 1143.58 286.009 1142.29 284.637Z" fill="url(#paint0_linear_912_2145)"/>
<path d="M1064.73 82.3345C1009.14 60.6942 947.382 60.3563 891.548 81.3868C890.205 81.9074 889.047 82.8085 888.219 83.9775C887.391 85.1463 886.93 86.531 886.894 87.958C884.519 160.899 864.991 233.535 811.653 286.393C810.236 287.81 809.441 289.723 809.441 291.716C809.441 293.708 810.236 295.621 811.653 297.038C877.664 363.932 923.267 446.53 953.973 533.631C954.449 534.981 955.313 536.165 956.462 537.032C957.611 537.9 958.991 538.414 960.433 538.51H960.511C961.514 538.577 962.518 538.439 963.464 538.106C964.41 537.773 965.276 537.25 966.01 536.571C966.742 535.893 967.327 535.072 967.727 534.161C968.127 533.25 968.332 532.267 968.332 531.274V295.15C968.545 256.478 977.308 218.323 994.007 183.358C1010.71 148.393 1034.93 117.468 1064.99 92.7512C1066.58 91.4747 1067.99 90.2606 1069.32 89.1172C1069.36 87.6569 1068.93 86.2226 1068.11 85.0091C1067.29 83.7956 1066.11 82.8618 1064.73 82.3345Z" fill="url(#paint1_linear_912_2145)"/>
<path d="M254.22 546.761L212.184 404.604H255.334L274.821 492.064H275.934L299.04 404.604H333.003L356.109 492.342H357.223L376.71 404.604H419.86L377.824 546.761H340.798L316.579 467.353H315.465L291.245 546.761H254.22Z" fill="#FCFCFC"/>
<path d="M452.762 546.761H411.004L458.051 404.604H510.945L557.992 546.761H516.234L485.055 443.752H483.941L452.762 546.761ZM444.967 490.676H523.472V519.551H444.967V490.676Z" fill="#FCFCFC"/>
<path d="M571.72 546.761V404.604H610.416V515.664H668.042V546.761H571.72Z" fill="#FCFCFC"/>
<path d="M683.684 546.761V404.604H722.379V515.664H780.005V546.761H683.684Z" fill="#FCFCFC"/>
<path d="M795.647 546.761V404.604H898.372V435.701H834.343V460.134H893.083V491.231H834.343V515.664H898.093V546.761H795.647Z" fill="#FCFCFC"/>
<path d="M913.666 435.701V404.604H1037.55V435.701H994.676V546.761H956.537V435.701H913.666Z" fill="#FCFCFC"/>
<defs>
<linearGradient id="paint0_linear_912_2145" x1="1015.62" y1="538.493" x2="1015.62" y2="65.885" gradientUnits="userSpaceOnUse">
<stop stop-color="#196DD2"/>
<stop offset="1" stop-color="#0D40A1"/>
</linearGradient>
<linearGradient id="paint1_linear_912_2145" x1="939.368" y1="65.8869" x2="939.368" y2="538.566" gradientUnits="userSpaceOnUse">
<stop stop-color="#1E7EE5"/>
<stop offset="1" stop-color="#196DD2"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

139
docs/local_development.md Normal file
View File

@@ -0,0 +1,139 @@
# 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
### Required
- [NodeJS](https://nodejs.org)
- [Yarn](https://yarnpkg.com/getting-started/install) (`npm install -g yarn`)
- [Deno](https://deno.land/#installation)
### Optional (Recomended)
- [nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
- [docker-compose](https://docs.docker.com/compose/install/)
## 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. If you choose to run on Arbitrum Goerli skip ahead until tests.
### Chain (RPC Node)
Start a local Hardhat node for RPC use.
```sh
cd ./contracts
yarn hardhat node
```
### Contracts
Fund the `create2Deployer`.
```sh
yarn hardhat fundDeployer --network gethDev
```
Deploy all `bls-wallet` contracts.
```sh
yarn hardhat run scripts/deploy_all.ts --network gethDev
```
## Aggregator
make these changes in aggregator > .env
RPC_URL=http://localhost:8545
NETWORK_CONFIG_PATH=../contracts/networks/local.json
```sh
docker-compose up -d postgres # Or see local postgres instructions in ./aggregator/README.md#PostgreSQL
cd ./aggregator
./programs/aggregator.ts
```
In a seperate terminal/shell instance
```sh
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).
2. Enable `Developer mode`.
3. Either click `Load unpacked extension...` and select `./extension/extension/chrome` or drag that folder into the page.
### Firefox
1. Go to Firefox's [debugging page](about:debugging#/runtime/this-firefox).
2. Click `Load Temporary Add-on...`.
3. Select `./extension/extension/firefox/manifest.json`.
### Tests
See each components `README.md` for how to run tests.
## Testing/using updates to ./clients
### extension
```sh
cd ./contracts/clients
yarn build
yarn link
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.
You will need write access to the npmjs project to do this. You can request access or request one of the BLS Wallet project developers push up your client changes in the `Discussions` section of this repo.
In `./contracts/clients` with your changes:
```
yarn publish-experimental
```
Note the `x.y.z-abc1234` version that was output.
Then in `./aggregtor/deps.ts`, change all `from` references for that package.
```typescript
...
} from "https://esm.sh/bls-wallet-clients@x.y.z-abc1234";
...
```

105
docs/remote_development.md Normal file
View File

@@ -0,0 +1,105 @@
# Remote Development
These steps will setup this repo on your machine for targeting a remote chain, such as an EVM compatible L2.
Follow the instructions for [Local Development](./local_development.md), replacing the sections titled `Chain` and `Contracts` with the steps below.
## Deploy Contracts
### 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.
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.
### Update hardhat.config.ts
If your network is not listed in [hardhat.config.ts](../contracts/hardhat.config.ts), you will need to add it.
### Precompile Cost Estimator
If your network does not already have an instance of the [BNPairingPrecompileCostEstimator contract](../contracts/contracts/lib/hubble-contracts/contracts/libs/BNPairingPrecompileCostEstimator.sol), you will need to deploy that.
```sh
cd ./contracts
yarn hardhat run scripts/0_deploy_precompile_cost_estimator.ts --network YOUR_NETWORK
```
Copy the address that is output.
Update `./contracts/contracts/lib/hubble-contracts/contracts/libs/BLS.sol`'s `COST_ESTIMATOR_ADDRESS` to the value of that address if it is different:
```solidity
...
address private constant COST_ESTIMATOR_ADDRESS = YOUR_NETWORKS_PRECOMPILE_COST_ESTIMATOR_ADDRESS;
...
```
### Remaining Contracts
Deploy all remaining `bls-wallet` contracts.
```sh
cd ./contracts # if not already there
yarn hardhat run scripts/deploy_all.ts --network YOUR_NETWORK
```
A network config file will be generated at `./contracts/networks/local.json`. You should rename it to match your network.
```sh
mv ./networks/local.json ./networks/your-network.json
```
This file can be commited so others can use your deployed contracts.
## Remote RPC
### Aggregator
Update these values in `./aggregator/.env`.
PK0 & PK1 are private keys for funded accounts on your network/chain.
```
RPC_URL=https://your.network.rpc
...
NETWORK_CONFIG_PATH=../contracts/networks/your-network.json
PRIVATE_KEY_AGG=PK0
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.
Then, update this value in `./extension/.env`.
```
...
DEFAULT_CHAIN_ID=YOUR_CHAIN_ID
...
```
## 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.
Update these values in `./aggregator/.env`.
```
RPC_URL=https://rinkeby.arbitrum.io/rpc
...
NETWORK_CONFIG_PATH=../contracts/networks/arbitrum-testnet.json
PRIVATE_KEY_AGG=PK0
PRIVATE_KEY_ADMIN=PK1
...
```
And then update this value in `./extension/.env`.
```
...
DEFAULT_CHAIN_ID=421611
...
```

21
docs/system_overview.md Normal file
View File

@@ -0,0 +1,21 @@
# System Overview
## Presentations
- [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
![System Overview](./images/system-overview/system-overview.svg)
## Actions, Bundles, & Aggregator
![Action](./images/system-overview/action-0.jpg)
![Operation](./images/system-overview/operation-1.jpg)
![Bundle](./images/system-overview/bundle-2.jpg)
![Interaction](./images/system-overview/interaction-3.jpg)

View File

@@ -0,0 +1,95 @@
# Use BLS Wallet Client
This walkthrough will show you how to submit an ERC20 transfer to the BLS Wallet Aggregator.
## Add bls-wallet-clients
```sh
# npm
npm install bls-wallet-clients
# yarn
yarn install 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";
```
### Deno
You can use [esm.sh](https://esm.sh/) or a similar service to get Deno compatible modules.
```typescript
import { providers } from "https://esm.sh/ethers@latest";
import { Aggregator, BLSWalletWrapper, getConfig } from "https://esm.sh/bls-wallet-clients@latest";
```
## Get Deployed Contract Addresses
You can find current contract deployments in the [contracts networks folder](../contracts/networks/).
If you would like to deploy locally, see [Local development](./local_development.md).
If you would like to deploy to a remote network, see [Remote development](./remote_development.md).
## Send a transaction
```typescript
import { readFile } from "fs/promises";
// Instantiate a provider via browser extension, such as Metamask
const provider = providers.Web3Provider(window.ethereum);
// Or via RPC
const provider = providers.JsonRpcProvider();
// 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 privateKey = "0x...";
// Note that if a wallet doesn't yet exist, it will be
// lazily created on the first transaction.
const wallet = await BlsWallerWrapper.connect(
privateKey,
netCfg.contracts.verificationGateway,
provider
);
const erc20Address = "0x...";
const erc20Abi = [
"function transfer(address to, uint amount) returns (bool)",
];
const erc20 = new ethers.Contract(erc20Address, erc20Abi, provider);
const recipientAddress = "0x...";
const nonce = await wallet.Nonce();
// All of the actions in a bundle are atomic, if one
// action fails they will all fail.
const bundle = wallet.sign({
nonce,
actions: [
{
contract: erc20,
method: "transfer",
args: [recipientAddress, ethers.utils.parseUnits("1", 18)],
},
],
});
const aggregator = new Aggregator("https://rinkarby.blswallet.org");
await aggregator.add(bundle);
```
## More
See [clients](../contracts/clients/) for additional functionality.

119
docs/use_bls_wallet_dapp.md Normal file
View File

@@ -0,0 +1,119 @@
# 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 = 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
## 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,6 +0,0 @@
PRIVATE_KEY_STORAGE_KEY=default-private-key
AGGREGATOR_URL=http://localhost:3000
DEFAULT_CHAIN_ID=31337
CREATE_TX_URL=
ETHERSCAN_KEY=
CRYPTO_COMPARE_API_KEY=

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://localhost: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://localhost: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

@@ -2,6 +2,7 @@
"name": "bls-wallet-extension",
"version": "0.1.0",
"description": "Web extension for managing a BLS wallet",
"license": "MIT",
"private": true,
"repository": "https://github.com/jzaki/bls-wallet-extension.git",
"author": {
@@ -10,8 +11,7 @@
"url": "https://blswallet.org"
},
"engines": {
"node": ">=16.0.0",
"yarn": ">= 1.0.0"
"yarn": ">=1.0.0"
},
"scripts": {
"dev:chrome": "cross-env NODE_ENV=development cross-env TARGET_BROWSER=chrome webpack --watch",
@@ -21,7 +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",
"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": {
@@ -30,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.7.3",
"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,151 @@
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';
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 });
};
return (
<div className="confirm">
<div className="section">
<CompactQuillHeading />
</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>
const nextTx = () => {
setCurrent((current + 1) % tx.actions.length);
};
const prevTx = () => {
setCurrent((current - 1) % tx.actions.length);
};
<Button className="btn-primary" onPress={() => respondTx('Yes')}>
Confirm
</Button>
<Button className="btn-secondary" onPress={() => respondTx('No')}>
Reject
</Button>
</>
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="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="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',
'flex',
'justify-between',
'h-20',
].join(' ')}
>
<div className="">Total Transaction Fees</div>
<div className="text-right">
<div className="font-bold">USD $0.0</div>
<div className="">{calculateTotal(tx.actions)} ETH</div>
</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,70 @@
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 } = useInputDecode(data || '0x', to);
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-normal">{data}</div>
</div>
</div>
<div className="flex mt-6 gap-3">
<div className="w-60 border-r border-grey-400">
<div>ETH Value</div>
<div className="break-all text-[9.5pt] font-bold">
{ethers.utils.formatEther(value || '0x0')}
</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,21 @@
import ReactDOM from 'react-dom';
import Confirm from './Confirm';
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,127 +0,0 @@
import CellCollection from '../../cells/CellCollection';
import ICell from '../../cells/ICell';
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=${
process.env.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);
}
}

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