Compare commits
185 Commits
cells-walk
...
4337-explo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92f6ec8467 | ||
|
|
2e68505b2e | ||
|
|
dcfd13d8b3 | ||
|
|
665ea530fc | ||
|
|
b547361d86 | ||
|
|
8d4257aac2 | ||
|
|
e4ad6d317c | ||
|
|
080513ec12 | ||
|
|
427d05ce82 | ||
|
|
a6e5d6d3d8 | ||
|
|
bcbab0d08a | ||
|
|
1303ffea9b | ||
|
|
ce8620c978 | ||
|
|
ce9e654815 | ||
|
|
19bdc19e8d | ||
|
|
b05525a1b8 | ||
|
|
53e2f518d5 | ||
|
|
9d22092b2c | ||
|
|
3936b59e22 | ||
|
|
a7d8209664 | ||
|
|
6ed993d951 | ||
|
|
23613b42b8 | ||
|
|
92195d1f3e | ||
|
|
5b2168e262 | ||
|
|
8d5b0867fe | ||
|
|
03ced09fc2 | ||
|
|
b267fcd3f3 | ||
|
|
297fba186b | ||
|
|
ebb90e2f9a | ||
|
|
6608add701 | ||
|
|
6d07b754dc | ||
|
|
3ac37a0e43 | ||
|
|
b1bac6fa79 | ||
|
|
87eb0b53fb | ||
|
|
17b7293057 | ||
|
|
11414de511 | ||
|
|
6c6972ce5c | ||
|
|
a8b5e4c9d7 | ||
|
|
07c59cd0bd | ||
|
|
3512982eff | ||
|
|
6f45f7942f | ||
|
|
287c1c64ee | ||
|
|
c13e9800a4 | ||
|
|
4548bf0a77 | ||
|
|
e64b637440 | ||
|
|
fd6f60d39c | ||
|
|
3a519cd6e4 | ||
|
|
56bdf8a1df | ||
|
|
600d898fc0 | ||
|
|
bcf6aef290 | ||
|
|
ed7bbf5397 | ||
|
|
0aacf46234 | ||
|
|
723740348f | ||
|
|
63d9470cad | ||
|
|
c285e8dfaf | ||
|
|
cf46dbf3ff | ||
|
|
427ce33267 | ||
|
|
7a7cc0a9d4 | ||
|
|
6d143fcbc8 | ||
|
|
d30c692263 | ||
|
|
014871ee8d | ||
|
|
20d46b4900 | ||
|
|
268ffe41f8 | ||
|
|
a0a2ee878b | ||
|
|
3a9c6ce4cd | ||
|
|
3106e1dd0a | ||
|
|
8745c185b9 | ||
|
|
c435fe0471 | ||
|
|
956607a479 | ||
|
|
04af3ae83a | ||
|
|
27b0864c79 | ||
|
|
0570216fb2 | ||
|
|
0515caacfc | ||
|
|
4259ee5bce | ||
|
|
06a1b7318e | ||
|
|
3c9cbe6c3e | ||
|
|
6b86b95f3c | ||
|
|
911ffa4865 | ||
|
|
c5f460969f | ||
|
|
a2fb8f7039 | ||
|
|
0959db5c4c | ||
|
|
c6c37ffc47 | ||
|
|
1a80eb4a4a | ||
|
|
a7c5de25aa | ||
|
|
a2bd4165b3 | ||
|
|
d52d598573 | ||
|
|
8a4c6e52f1 | ||
|
|
f6473e72af | ||
|
|
0f82acf931 | ||
|
|
bfada65260 | ||
|
|
79423ec79f | ||
|
|
5225cb1883 | ||
|
|
f4f5703eb6 | ||
|
|
36825dbbe6 | ||
|
|
54e90045fb | ||
|
|
89c8b28d20 | ||
|
|
aa5c2fea6b | ||
|
|
66c257cacd | ||
|
|
501afd7d64 | ||
|
|
676471b3fe | ||
|
|
26686cdfe3 | ||
|
|
7cbd6a7617 | ||
|
|
1c3b1c76b3 | ||
|
|
ed9ab2a103 | ||
|
|
b38547126e | ||
|
|
e1aa4de7fb | ||
|
|
c0fa1cdb91 | ||
|
|
7f5e5e2f27 | ||
|
|
8761d5e7f6 | ||
|
|
c62dceb6a9 | ||
|
|
d8dd145ae8 | ||
|
|
e43ddbd0fe | ||
|
|
ce73b22a50 | ||
|
|
449f5b439a | ||
|
|
3fcddfe2f7 | ||
|
|
0eee5e34b4 | ||
|
|
7fb3a62655 | ||
|
|
7c15195aa6 | ||
|
|
b15c81ed2e | ||
|
|
a2756603bc | ||
|
|
7fc2b45419 | ||
|
|
ba83438cf0 | ||
|
|
e0d47566db | ||
|
|
39d02cbea0 | ||
|
|
b8ae6e9370 | ||
|
|
1b403b9300 | ||
|
|
6ce54b404d | ||
|
|
dbdd56795d | ||
|
|
1358430dbb | ||
|
|
b200502594 | ||
|
|
5f506f5f14 | ||
|
|
c982fe7a69 | ||
|
|
718be15cac | ||
|
|
ae6db5b3f7 | ||
|
|
d4cfe0e4f6 | ||
|
|
aad71f738f | ||
|
|
1e88e2711f | ||
|
|
b6e057bed8 | ||
|
|
5c1388e95c | ||
|
|
c3169e93b4 | ||
|
|
847b02d31e | ||
|
|
b5c30bedb2 | ||
|
|
350a31b66f | ||
|
|
36396a74d9 | ||
|
|
78465e133e | ||
|
|
363a518acd | ||
|
|
565e767d12 | ||
|
|
49a3661e52 | ||
|
|
9cc3db2fce | ||
|
|
17ccc5b00c | ||
|
|
ce7f958fb7 | ||
|
|
4dae24f4ec | ||
|
|
471be6f0e0 | ||
|
|
0ef2799980 | ||
|
|
f31ee249b8 | ||
|
|
935bdf16c3 | ||
|
|
94c9060d57 | ||
|
|
e671e73e0f | ||
|
|
140f5e7094 | ||
|
|
1b5e2eaec4 | ||
|
|
3982007d76 | ||
|
|
b39ed6653f | ||
|
|
cbdb771f33 | ||
|
|
dd944ccf54 | ||
|
|
e58903eb9b | ||
|
|
b538fa70ec | ||
|
|
fdd1c020c6 | ||
|
|
7165a4c2aa | ||
|
|
0cbe1ceaff | ||
|
|
7c8b8383e4 | ||
|
|
d2b18f3f50 | ||
|
|
fafc15c897 | ||
|
|
b52c8e6e90 | ||
|
|
c8e7c7370a | ||
|
|
53c8e62cf6 | ||
|
|
436094914f | ||
|
|
745aed060f | ||
|
|
df53392ce7 | ||
|
|
7d82903f69 | ||
|
|
5e8827b595 | ||
|
|
d2c1e6af1e | ||
|
|
21dd23953d | ||
|
|
0b7580a442 | ||
|
|
074d58b8a8 | ||
|
|
114a5fdf6c |
47
.github/actions/build-upload-extension/action.yml
vendored
Normal 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
|
||||
24
.github/actions/setup-contracts-clients/action.yml
vendored
Normal 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
@@ -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
@@ -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: |
|
||||
## What’s Changed
|
||||
|
||||
$CHANGES
|
||||
34
.github/workflows/aggregator-proxy.yml
vendored
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
2
.github/workflows/labeler.yml
vendored
@@ -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
@@ -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
@@ -1,2 +1,3 @@
|
||||
.data
|
||||
.DS_Store
|
||||
.idea
|
||||
60
CONTRIBUTING.md
Normal 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
@@ -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
@@ -1,173 +1,46 @@
|
||||
# bls-wallet
|
||||

|
||||
|
||||
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/)
|
||||
|
||||

|
||||
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.
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# 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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
32
aggregator/.env.local.example
Normal 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
|
||||
2
aggregator/.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
.env*
|
||||
!.env.example
|
||||
!.env*.example
|
||||
cov_profile*
|
||||
/build
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM denoland/deno:1.20.6
|
||||
FROM denoland/deno:1.23.4
|
||||
|
||||
ADD build /app
|
||||
WORKDIR /app
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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();
|
||||
13
aggregator/programs/helpers/git.ts
Normal 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",
|
||||
),
|
||||
];
|
||||
}
|
||||
22
aggregator/programs/helpers/lint.ts
Normal 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
|
||||
}
|
||||
}
|
||||
25
aggregator/programs/helpers/typescript.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
aggregator/programs/lintTodos.ts
Executable 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
|
||||
@@ -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",
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,5 +35,7 @@ module.exports = {
|
||||
ignores: [],
|
||||
},
|
||||
],
|
||||
// TODO (merge-ok) Remove and fix lint error
|
||||
"node/no-unpublished-import": ["warn"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 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"
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
61
contracts/clients/src/MultiNetworkConfig.ts
Normal 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;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
88
contracts/clients/test/config.test.ts
Normal 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;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,3 @@
|
||||
import "source-map-support/register";
|
||||
|
||||
import { BigNumber } from "ethers";
|
||||
import { keccak256, arrayify } from "ethers/lib/utils";
|
||||
import { expect } from "chai";
|
||||
|
||||
4
contracts/clients/test/init.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import chai from "chai";
|
||||
import chaiAsPromised from "chai-as-promised";
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
_;
|
||||
|
||||
@@ -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> ---
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
16
contracts/contracts/interfaces/UserOperation4337.sol
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
19
contracts/networks/arbitrum-goerli.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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(
|
||||
|
||||
19
contracts/shared/helpers/defaultDeployerWallet.ts
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
24
docs/images/bls-github-banner.svg
Normal 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 |
BIN
docs/images/system-overview/action-0.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/images/system-overview/bundle-2.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
docs/images/system-overview/interaction-3.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
docs/images/system-overview/operation-1.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
139
docs/local_development.md
Normal 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
@@ -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
@@ -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
|
||||
|
||||

|
||||
|
||||
## Actions, Bundles, & Aggregator
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
95
docs/use_bls_wallet_clients.md
Normal 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
@@ -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.
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
2
extension/.gitignore
vendored
@@ -206,3 +206,5 @@ dist/
|
||||
# Non-template additions
|
||||
.env*
|
||||
!.env.example
|
||||
/build
|
||||
/config.json
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all",
|
||||
"useTabs": false,
|
||||
"proseWrap": "always"
|
||||
}
|
||||
27
extension/buildMultiNetworkConfig.js
Normal 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),
|
||||
);
|
||||
91
extension/config.example.json
Normal 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
|
||||
}
|
||||
}
|
||||
91
extension/config.release.json
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
15
extension/scripts/addFileStubsIfNeeded.sh
Executable 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
|
||||
35
extension/source/Config.ts
Normal 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;
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
70
extension/source/Confirm/TransactionCard.tsx
Normal 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;
|
||||
@@ -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'),
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||