mirror of
https://github.com/getwax/bls-wallet.git
synced 2026-01-10 06:17:55 -05:00
Merge branch 'main' of github.com:web3well/bls-wallet into contract-audit
This commit is contained in:
@@ -37,3 +37,13 @@ runAggregatorProxy(
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## Instant wallet without dapp-sponsored transaction
|
||||

|
||||
|
||||
## Instant wallet with dapp-sponsored transaction
|
||||

|
||||
|
||||
## Example dApp using a proxy aggregator
|
||||
|
||||
- https://github.com/JohnGuilding/single-pool-dex
|
||||
@@ -29,6 +29,34 @@ you might have:
|
||||
If you don't have a `.env`, you will need to append `--env <name>` to all
|
||||
commands.
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
| Name | Example Value | Description |
|
||||
| ---------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| RPC_URL | https://localhost:8545 | The RPC endpoint for an EVM node that the BLS Wallet contracts are deployed on |
|
||||
| USE_TEST_NET | false | Whether to set all transaction's `gasPrice` to 0. Workaround for some networks |
|
||||
| ORIGIN | http://localhost:3000 | The origin for the aggregator client. Used only in manual tests |
|
||||
| PORT | 3000 | The port to bind the aggregator to |
|
||||
| NETWORK_CONFIG_PATH | ../contracts/networks/local.json | Path to the network config file, which contains information on deployed BLS Wallet contracts |
|
||||
| PRIVATE_KEY_AGG | 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 | Private key for the EOA account used to submit bundles on chain |
|
||||
| PRIVATE_KEY_ADMIN | 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d | Private key for the admin EOA account. Used only in tests |
|
||||
| TEST_BLS_WALLETS_SECRET | test-bls-wallets-secret | Secret used to seed BLS Wallet private keys during tests |
|
||||
| PG_HOST | 127.0.0.1 | Postgres database host |
|
||||
| PG_PORT | 5432 | Postgres database port |
|
||||
| PG_USER | bls | Postgres database user |
|
||||
| PG_PASSWORD | generate-a-strong-password | Postgres database password |
|
||||
| PG_DB_NAME | bls_aggregator | Postgres database name |
|
||||
| BUNDLE_TABLE_NAME | bundles | Postgres table name for bundles |
|
||||
| BUNDLE_QUERY_LIMIT | 100 | Maximum number of bundles returned from Postgres |
|
||||
| MAX_AGGREGATION_SIZE | 12 | Maximum number of actions from bundles which will be aggregated together for submission on chain |
|
||||
| MAX_AGGREGATION_DELAY_MILLIS | 5000 | Maximum amount of time in milliseconds aggregator will wait before submitting bundles on chain |
|
||||
| MAX_UNCONFIRMED_AGGREGATIONS | 3 | Maximum unconfirmed bundle aggregations that will be submitted on chain. Multiplied with `MAX_AGGREGATION_SIZE` to determine maximum of unconfirmed on chain actions |
|
||||
| LOG_QUERIES | false | Whether to print Postgres queries in event log.`TEST_LOGGING` must be enabled |
|
||||
| TEST_LOGGING | false | Whether to print aggregator server events to stdout. Useful for debugging & logging. |
|
||||
| FEE_TYPE | ether OR token:0xabcd...1234 | The fee type the aggregator will accept. Either `ether` for ETH/chains native currency or `token:0xabcd...1234` (token contract address) for an ERC20 token |
|
||||
| FEE_PER_GAS | 0 | Minimum amount per gas (gasPrice) the aggregator will accept in ETH/chain native currency/ERC20 tokens |
|
||||
| FEE_PER_BYTE | 0 | Minimum amount per calldata byte the aggregator will accept in ETH/chain native currency/ERC20 tokens (rollup L1 cost) |
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
#### With docker-compose
|
||||
@@ -170,13 +198,16 @@ deno run -r --allow-net --allow-env --allow-read --unstable ./programs/aggregato
|
||||
|
||||
#### Transaction reverted: function call to a non-contract account
|
||||
|
||||
- Is `./contracts/contracts/lib/hubble-contracts/contracts/libs/BLS.sol`'s `COST_ESTIMATOR_ADDRESS` set to the right precompile cost estimator's contract address?
|
||||
- Is `./contracts/contracts/lib/hubble-contracts/contracts/libs/BLS.sol`'s
|
||||
`COST_ESTIMATOR_ADDRESS` set to the right precompile cost estimator's contract
|
||||
address?
|
||||
- Are the BLS Wallet contracts deployed on the correct network?
|
||||
- Is `NETWORK_CONFIG_PATH` in `.env` set to the right config?
|
||||
|
||||
#### Deno version
|
||||
|
||||
Make sure your Deno version is [up to date.](https://deno.land/manual/getting_started/installation#updating)
|
||||
Make sure your Deno version is
|
||||
[up to date.](https://deno.land/manual/getting_started/installation#updating)
|
||||
|
||||
### Notable Components
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 231 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 153 KiB |
@@ -58,6 +58,8 @@ make these changes in aggregator > .env
|
||||
RPC_URL=http://localhost:8545
|
||||
NETWORK_CONFIG_PATH=../contracts/networks/local.json
|
||||
|
||||
In a seperate terminal/shell instance
|
||||
|
||||
```sh
|
||||
docker-compose up -d postgres # Or see local postgres instructions in ./aggregator/README.md#PostgreSQL
|
||||
cd ./aggregator
|
||||
|
||||
@@ -8,7 +8,7 @@ Follow the instructions for [Local Development](./local_development.md), replaci
|
||||
|
||||
### Deployer account
|
||||
|
||||
BLS Wallet contract deploys use `CREATE2` to maintain consistent addresses across networks. As such, a create2 deployer contract is used and listed in `./contracts/.env` under the environment variables `DEPLOYER_MNEMONIC` & `DEPLOYER_SET_INDEX`. The HD address will need to be funded in order to deploy the contracts.
|
||||
BLS Wallet contract deploys use `CREATE2` to maintain consistent addresses across networks. As such, a create2 deployer contract is used and listed in `./contracts/.env` under the environment variables `DEPLOYER_MNEMONIC` & `DEPLOYER_SET_INDEX`. The hierarchical deterministic (HD) wallet address will need to be funded in order to deploy the contracts.
|
||||
|
||||
If you do not need consistent addresses, for example on a local or testnet network, you can replace the `DEPLOYER_MNEMONIC` with another seed phrase which already has a funded account.
|
||||
|
||||
@@ -66,40 +66,27 @@ PRIVATE_KEY_ADMIN=PK1
|
||||
|
||||
### Extension
|
||||
|
||||
Check the [controller constants file](../extension/source/Controllers/constants.ts) to see if your network is already added. If not, you will need to add chainid & supported networks entries for your network/chain. These changes can be committed.
|
||||
Check the [`config.json` file](../extension//config.json) to see if your network is already added. If not, you will need to add the relevant properties for your network/chain. These changes can be committed.
|
||||
|
||||
Then, update this value in `./extension/.env`.
|
||||
```
|
||||
...
|
||||
## Example: Arbitrum Testnet (Arbitrum Goerli Testnet)
|
||||
|
||||
DEFAULT_CHAIN_ID=YOUR_CHAIN_ID
|
||||
...
|
||||
```
|
||||
You will need two ETH addresses with Abitrum Goerli ETH and their private keys (PRIVATE_KEY_AGG & PRIVATE_KEY_ADMIN) for running the aggregator. It is **NOT** recommended that you use any primary wallets with ETH Mainnet assets.
|
||||
|
||||
## Run
|
||||
|
||||
Follow the remaing instruction in [Local Development](./local_development.md) starting with the `Run` section.
|
||||
|
||||
## Example: Arbitrum Testnet (Rinkeby Arbitrum Testnet)
|
||||
|
||||
You will need two ETH addresses with Rinkeby ETH and their private keys (PK0 & PK1) for running the aggregator. It is NOT recommended that you use any primary wallets with ETH Mainnet assets.
|
||||
|
||||
You can get Rinkeby ETH at https://app.mycrypto.com/faucet, and transfer it into the Arbitrum testnet via https://bridge.arbitrum.io/. Make sure when doing so that your network is set to Rinkeby in your web3 wallet extension, such as MetaMask.
|
||||
You can get Goerli ETH at https://goerlifaucet.com/ or https://app.mycrypto.com/faucet, and transfer it into the Arbitrum testnet via https://bridge.arbitrum.io/. Make sure when doing so that your network is set to Goerli in your web3 wallet extension, such as MetaMask.
|
||||
|
||||
Update these values in `./aggregator/.env`.
|
||||
```
|
||||
RPC_URL=https://rinkeby.arbitrum.io/rpc
|
||||
RPC_URL=https://goerli-rollup.arbitrum.io/rpc
|
||||
...
|
||||
NETWORK_CONFIG_PATH=../contracts/networks/arbitrum-testnet.json
|
||||
NETWORK_CONFIG_PATH=../contracts/networks/arbitrum-goerli.json
|
||||
PRIVATE_KEY_AGG=PK0
|
||||
PRIVATE_KEY_ADMIN=PK1
|
||||
...
|
||||
```
|
||||
|
||||
And then update this value in `./extension/.env`.
|
||||
```
|
||||
And then ensure the `defaultNetwork` value in `./extension/config.json` is set to `arbitrum-goerli`.
|
||||
```json
|
||||
...
|
||||
|
||||
DEFAULT_CHAIN_ID=421611
|
||||
"defaultNetwork": "arbitrum-goerli",
|
||||
...
|
||||
```
|
||||
```
|
||||
@@ -30,7 +30,7 @@ Next, connect your dApp to Quill just like you would any other extension wallet.
|
||||
```typescript
|
||||
import { providers } from 'ethers';
|
||||
|
||||
const provider = providers.Web3Provider(window.ethereum);
|
||||
const provider = new providers.Web3Provider(window.ethereum);
|
||||
|
||||
await window.ethereum.request({ method: "eth_accounts" });
|
||||
```
|
||||
@@ -113,6 +113,7 @@ See the [System Overview](./system_overview.md) for more details on what's happe
|
||||
|
||||
- https://github.com/kautukkundan/BLSWallet-ERC20-demo
|
||||
- https://github.com/voltrevo/bls-wallet-billboard
|
||||
- https://github.com/JohnGuilding/single-pool-dex
|
||||
|
||||
## Coming soon
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ const TransactionCard: React.FC<SendTransactionParams> = ({
|
||||
gas,
|
||||
gasPrice,
|
||||
}) => {
|
||||
const { loading, method } = useInputDecode(data || '0x', to);
|
||||
const { loading, method, args } = useInputDecode(data || '0x');
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-md p-4 border border-blue-400">
|
||||
@@ -37,7 +37,16 @@ const TransactionCard: React.FC<SendTransactionParams> = ({
|
||||
<div className="break-all">
|
||||
details:{' '}
|
||||
<span className="font-bold">{loading ? 'loading...' : method}</span>
|
||||
<div className="text-[9pt] mt-2 font-normal">{data}</div>
|
||||
<div className="text-[9pt] mt-2 font-mono">
|
||||
{loading
|
||||
? 'loading params...'
|
||||
: // prettier-ignore
|
||||
args.map((arg, i) => (
|
||||
<div key={arg}>
|
||||
{i + 1}. {arg.toString()}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ethers } from 'ethers';
|
||||
import { FunctionComponent, useEffect, useState } from 'react';
|
||||
import { FilePlus, Cardholder, Warning } from 'phosphor-react';
|
||||
import Button from '../../components/Button';
|
||||
|
||||
import ReviewSecretPhrasePanel from './ReviewSecretPhrasePanel';
|
||||
import ViewSecretPhrasePanel from './ViewSecretPhrasePanel';
|
||||
@@ -8,14 +10,109 @@ const SecretPhrasePanel: FunctionComponent<{
|
||||
secretPhrase?: string[];
|
||||
}> = () => {
|
||||
const [mnemonic, setMnemonic] = useState<string[]>([]);
|
||||
const [mnemonicInput, setMnemonicInput] = useState<string>('');
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (mnemonicInput !== '') {
|
||||
if (mnemonicInput.split(' ').length !== 12) {
|
||||
setError(true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ethers.Wallet.fromMnemonic(mnemonicInput);
|
||||
setError(false);
|
||||
} catch (e) {
|
||||
setError(true);
|
||||
}
|
||||
}
|
||||
}, [mnemonicInput]);
|
||||
|
||||
const createMnemonic = () => {
|
||||
const mnemonicPhrase = ethers.Wallet.createRandom().mnemonic.phrase;
|
||||
setMnemonic(mnemonicPhrase.split(' '));
|
||||
}, []);
|
||||
};
|
||||
|
||||
const validateAndSetMnemonic = (m: string) => {
|
||||
const split = m.split(' ');
|
||||
if (!error) {
|
||||
setMnemonic(split);
|
||||
}
|
||||
};
|
||||
|
||||
const [inReview, setInReview] = useState(false);
|
||||
|
||||
if (mnemonic.length === 0) {
|
||||
return (
|
||||
<div className="">
|
||||
<div className="mb-10">
|
||||
<div className="font-bold">One Last step!</div>
|
||||
<span>
|
||||
You can use an existing seed phrase to generate BLS keypair,
|
||||
Otherwise we will create a new fresh one for you
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-center align-middle gap-3">
|
||||
<Button
|
||||
className="btn-primary"
|
||||
onPress={createMnemonic}
|
||||
iconLeft={<FilePlus size={20} />}
|
||||
>
|
||||
Generate New
|
||||
</Button>
|
||||
|
||||
<div className="text-center text-grey-800"> - OR - </div>
|
||||
|
||||
<div className="">
|
||||
<textarea
|
||||
className={[
|
||||
'mt-2',
|
||||
'bg-opacity-5',
|
||||
'border-opacity-25',
|
||||
'focus:border-opacity-25',
|
||||
].join(' ')}
|
||||
placeholder="existing 12 word Mnemonic (space separated)"
|
||||
onChange={(e) => {
|
||||
setMnemonicInput(e.target.value);
|
||||
}}
|
||||
/>
|
||||
{mnemonicInput !== '' && !error && (
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
onPress={() => validateAndSetMnemonic(mnemonicInput)}
|
||||
iconLeft={<Cardholder size={20} />}
|
||||
>
|
||||
Use Existing Mnemonic
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div
|
||||
className={[
|
||||
'bg-alert-400',
|
||||
'p-4',
|
||||
'mt-4',
|
||||
'text-[10pt]',
|
||||
'rounded-md',
|
||||
'flex',
|
||||
'gap-4',
|
||||
'bg-opacity-20',
|
||||
].join(' ')}
|
||||
>
|
||||
<Warning className="text-alert-500 mt-1" size={64} />
|
||||
<div className="align-text-top">
|
||||
Please enter correct 12 word mnemonic compatible with BIP-39
|
||||
standard, separated by space.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!inReview) {
|
||||
return (
|
||||
<ViewSecretPhrasePanel
|
||||
|
||||
@@ -2,92 +2,44 @@ import { ethers } from 'ethers';
|
||||
import { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import assert from '../helpers/assert';
|
||||
import { useQuill } from '../QuillContext';
|
||||
import { IReadableCell } from '../cells/ICell';
|
||||
|
||||
const getParitySigRegistry = async (
|
||||
provider: IReadableCell<ethers.providers.Provider>,
|
||||
) => {
|
||||
const address = '0x44691B39d1a75dC4E0A0346CBB15E310e6ED1E86';
|
||||
const abi = [
|
||||
{
|
||||
constant: true,
|
||||
inputs: [{ name: '', type: 'bytes4' }],
|
||||
name: 'entries',
|
||||
outputs: [{ name: '', type: 'string' }],
|
||||
payable: false,
|
||||
type: 'function',
|
||||
},
|
||||
];
|
||||
const getMethodFromRegistry = async (data: string) => {
|
||||
if (data === '0x') return { funcSig: 'SENDING ETH', args: [] };
|
||||
|
||||
return new ethers.Contract(address, abi, await provider.read());
|
||||
};
|
||||
const funcSigHex = data.slice(0, 10);
|
||||
|
||||
const getMethodFromOnChainRegistry = async (
|
||||
data: string,
|
||||
provider: IReadableCell<ethers.providers.Provider>,
|
||||
) => {
|
||||
if (data === '0x') return 'SENDING ETH';
|
||||
|
||||
const methodID = ethers.utils.hexDataSlice(data, 0, 4);
|
||||
const registry = await getParitySigRegistry(provider);
|
||||
|
||||
return registry.entries(methodID);
|
||||
};
|
||||
|
||||
const getMethodFromEtherscan = async (to: string, data: string) => {
|
||||
const res = await axios.get(
|
||||
`https://api.etherscan.io/api?module=contract&action=getabi&address=${to}`,
|
||||
`https://sig.eth.samczsun.com/api/v1/signatures?all=true&function=${funcSigHex}`,
|
||||
);
|
||||
const funcSig = res.data.result.function[funcSigHex][0].name;
|
||||
const iface = new ethers.utils.Interface([`function ${funcSig}`]);
|
||||
const { args } = iface.parseTransaction({ data });
|
||||
|
||||
if (res.data.result !== 'Contract source code not verified') {
|
||||
const iface = new ethers.utils.Interface(res.data.result);
|
||||
return iface.parseTransaction({ data, value: 1 }).name;
|
||||
}
|
||||
|
||||
assert(false, () => new Error('Unverified Contract'));
|
||||
return { funcSig, args };
|
||||
};
|
||||
|
||||
const formatMethod = (method: string) =>
|
||||
method
|
||||
.split('(')[0]
|
||||
.replace(/([a-z](?=[A-Z]))/g, '$1 ')
|
||||
.toUpperCase();
|
||||
|
||||
type UseInputDecodeValues = {
|
||||
loading: boolean;
|
||||
method: string;
|
||||
args: ethers.utils.Result;
|
||||
};
|
||||
|
||||
export const useInputDecode = (
|
||||
functionData: string,
|
||||
to: string,
|
||||
): UseInputDecodeValues => {
|
||||
export const useInputDecode = (functionData: string): UseInputDecodeValues => {
|
||||
const quill = useQuill();
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [method, setMethod] = useState<string>('CONTRACT INTERACTION');
|
||||
const [allArgs, setAllArgs] = useState<ethers.utils.Result>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const getMethod = async () => {
|
||||
setLoading(true);
|
||||
|
||||
const data = functionData?.replace(/\s+/g, '');
|
||||
|
||||
try {
|
||||
const registryPromise = getMethodFromOnChainRegistry(
|
||||
data,
|
||||
quill.ethersProvider,
|
||||
);
|
||||
const etherScanPromise = getMethodFromEtherscan(to, data);
|
||||
const rawMethod = (await registryPromise) ?? (await etherScanPromise);
|
||||
if (rawMethod) {
|
||||
setMethod(formatMethod(rawMethod));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log({ error });
|
||||
}
|
||||
const { funcSig, args } = await getMethodFromRegistry(data);
|
||||
setMethod(funcSig);
|
||||
setAllArgs(args);
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
@@ -95,7 +47,7 @@ export const useInputDecode = (
|
||||
if (functionData) {
|
||||
getMethod();
|
||||
}
|
||||
}, [functionData, to, quill]);
|
||||
}, [functionData, quill]);
|
||||
|
||||
return { loading, method };
|
||||
return { loading, method, args: allArgs };
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;300;400;500;600&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Mono&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
@@ -48,6 +48,7 @@ module.exports = {
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Montserrat'],
|
||||
mono: ['Noto Sans Mono'],
|
||||
},
|
||||
fontSize: {
|
||||
disclaimer: '12pt',
|
||||
|
||||
Reference in New Issue
Block a user