mirror of
https://github.com/getwax/bls-wallet.git
synced 2026-01-08 23:28:21 -05:00
Merge branch 'main' of github.com:web3well/bls-wallet into contract-updates
This commit is contained in:
2
.github/workflows/aggregator.yml
vendored
2
.github/workflows/aggregator.yml
vendored
@@ -73,6 +73,8 @@ jobs:
|
|||||||
run: yarn start &
|
run: yarn start &
|
||||||
- working-directory: ./contracts
|
- working-directory: ./contracts
|
||||||
run: ./scripts/wait-for-rpc.sh
|
run: ./scripts/wait-for-rpc.sh
|
||||||
|
- working-directory: ./contracts
|
||||||
|
run: ./scripts/wait-for-contract-deploy.sh
|
||||||
|
|
||||||
- run: cp .env.local.example .env
|
- run: cp .env.local.example .env
|
||||||
- run: deno test --allow-net --allow-env --allow-read
|
- run: deno test --allow-net --allow-env --allow-read
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ You can watch a full end-to-end demo of the project [here](https://www.youtube.c
|
|||||||
- See an [overview](./docs/system_overview.md) of BLS Wallet & how the components work together.
|
- See an [overview](./docs/system_overview.md) of BLS Wallet & how the components work together.
|
||||||
- Use BLS Wallet in [a browser/NodeJS/Deno app](./docs/use_bls_wallet_clients.md).
|
- Use BLS Wallet in [a browser/NodeJS/Deno app](./docs/use_bls_wallet_clients.md).
|
||||||
- Use BLS Wallet in [your L2 dApp](./docs/use_bls_wallet_dapp.md) for cheaper, multi action transactions.
|
- Use BLS Wallet in [your L2 dApp](./docs/use_bls_wallet_dapp.md) for cheaper, multi action transactions.
|
||||||
|
- Use BLS Wallet components and features with an [ethers.js provider and signer](./use_bls_provider.md)
|
||||||
|
|
||||||
### Setup your development environment
|
### Setup your development environment
|
||||||
|
|
||||||
|
|||||||
@@ -88,4 +88,5 @@ type AppEvent =
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default AppEvent;
|
export default AppEvent;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Router } from "../../deps.ts";
|
|||||||
import failRequest from "./helpers/failRequest.ts";
|
import failRequest from "./helpers/failRequest.ts";
|
||||||
import BundleHandler from "./helpers/BundleHandler.ts";
|
import BundleHandler from "./helpers/BundleHandler.ts";
|
||||||
import nil from "../helpers/nil.ts";
|
import nil from "../helpers/nil.ts";
|
||||||
|
|
||||||
import BundleService from "./BundleService.ts";
|
import BundleService from "./BundleService.ts";
|
||||||
|
|
||||||
export default function BundleRouter(bundleService: BundleService) {
|
export default function BundleRouter(bundleService: BundleService) {
|
||||||
|
|||||||
@@ -60,14 +60,14 @@ export type BundleRow = Row;
|
|||||||
function fromRawRow(rawRow: RawRow | sqlite.Row): Row {
|
function fromRawRow(rawRow: RawRow | sqlite.Row): Row {
|
||||||
if (Array.isArray(rawRow)) {
|
if (Array.isArray(rawRow)) {
|
||||||
rawRow = {
|
rawRow = {
|
||||||
id: rawRow[0],
|
id: rawRow[0] as number,
|
||||||
status: rawRow[1],
|
status: rawRow[1] as string,
|
||||||
hash: rawRow[2],
|
hash: rawRow[2] as string,
|
||||||
bundle: rawRow[3],
|
bundle: rawRow[3] as string,
|
||||||
eligibleAfter: rawRow[4],
|
eligibleAfter: rawRow[4] as string,
|
||||||
nextEligibilityDelay: rawRow[5],
|
nextEligibilityDelay: rawRow[5] as string,
|
||||||
submitError: rawRow[6],
|
submitError: rawRow[6] as string | null,
|
||||||
receipt: rawRow[7],
|
receipt: rawRow[7] as string | null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
aggregator/src/app/HealthRouter.ts
Normal file
16
aggregator/src/app/HealthRouter.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Router } from "../../deps.ts";
|
||||||
|
import HealthService from "./HealthService.ts";
|
||||||
|
|
||||||
|
export default function HealthRouter(healthService: HealthService) {
|
||||||
|
const router = new Router({ prefix: "/" });
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"health",
|
||||||
|
async (ctx) => {
|
||||||
|
const healthResults = await healthService.getHealth();
|
||||||
|
console.log(`Status: ${healthResults.status}\n`);
|
||||||
|
ctx.response.status = healthResults.status == 'healthy' ? 200 : 503;
|
||||||
|
ctx.response.body = { status: healthResults.status };
|
||||||
|
});
|
||||||
|
return router;
|
||||||
|
}
|
||||||
11
aggregator/src/app/HealthService.ts
Normal file
11
aggregator/src/app/HealthService.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export type ResourceHealth = 'healthy' | 'unhealthy';
|
||||||
|
|
||||||
|
type HealthCheckResult = {
|
||||||
|
status: ResourceHealth,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class HealthService {
|
||||||
|
getHealth(): Promise<HealthCheckResult> {
|
||||||
|
return Promise.resolve({ status: 'healthy' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ import AppEvent from "./AppEvent.ts";
|
|||||||
import BundleTable from "./BundleTable.ts";
|
import BundleTable from "./BundleTable.ts";
|
||||||
import AggregationStrategy from "./AggregationStrategy.ts";
|
import AggregationStrategy from "./AggregationStrategy.ts";
|
||||||
import AggregationStrategyRouter from "./AggregationStrategyRouter.ts";
|
import AggregationStrategyRouter from "./AggregationStrategyRouter.ts";
|
||||||
|
import HealthService from "./HealthService.ts";
|
||||||
|
import HealthRouter from "./HealthRouter.ts";
|
||||||
|
|
||||||
export default async function app(emit: (evt: AppEvent) => void) {
|
export default async function app(emit: (evt: AppEvent) => void) {
|
||||||
const { addresses } = await getNetworkConfig();
|
const { addresses } = await getNetworkConfig();
|
||||||
@@ -64,10 +66,13 @@ export default async function app(emit: (evt: AppEvent) => void) {
|
|||||||
bundleTable,
|
bundleTable,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const healthService = new HealthService();
|
||||||
|
|
||||||
const routers = [
|
const routers = [
|
||||||
BundleRouter(bundleService),
|
BundleRouter(bundleService),
|
||||||
AdminRouter(adminService),
|
AdminRouter(adminService),
|
||||||
AggregationStrategyRouter(aggregationStrategy),
|
AggregationStrategyRouter(aggregationStrategy),
|
||||||
|
HealthRouter(healthService),
|
||||||
];
|
];
|
||||||
|
|
||||||
const app = new Application();
|
const app = new Application();
|
||||||
|
|||||||
10
aggregator/test/HealthService.test.ts
Normal file
10
aggregator/test/HealthService.test.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { assertEquals } from "./deps.ts";
|
||||||
|
|
||||||
|
import Fixture from "./helpers/Fixture.ts";
|
||||||
|
|
||||||
|
Fixture.test("HealthService returns healthy", async (fx) => {
|
||||||
|
const healthCheckService = fx.createHealthCheckService()
|
||||||
|
const healthStatus = await healthCheckService.getHealth();
|
||||||
|
const expected = {"status":"healthy"};
|
||||||
|
assertEquals(JSON.stringify(healthStatus), JSON.stringify(expected));
|
||||||
|
});
|
||||||
@@ -25,6 +25,7 @@ import BundleTable, { BundleRow } from "../../src/app/BundleTable.ts";
|
|||||||
import AggregationStrategy, {
|
import AggregationStrategy, {
|
||||||
AggregationStrategyConfig,
|
AggregationStrategyConfig,
|
||||||
} from "../../src/app/AggregationStrategy.ts";
|
} from "../../src/app/AggregationStrategy.ts";
|
||||||
|
import HealthService from "../../src/app/HealthService.ts";
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
type ExplicitAny = any;
|
type ExplicitAny = any;
|
||||||
@@ -292,6 +293,12 @@ export default class Fixture {
|
|||||||
|
|
||||||
return wallets;
|
return wallets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createHealthCheckService() {
|
||||||
|
const healthCheckService = new HealthService();
|
||||||
|
|
||||||
|
return healthCheckService;
|
||||||
|
}
|
||||||
|
|
||||||
async cleanup() {
|
async cleanup() {
|
||||||
for (const job of this.cleanupJobs) {
|
for (const job of this.cleanupJobs) {
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ const bundle = wallet.sign({
|
|||||||
|
|
||||||
User bundles must pay fees to compensate the aggregator. Fees can be paid by adding an additional action to the users bundle that pays tx.origin. For more info on how fees work, see [aggregator fees](../../aggregator/README.md#fees).
|
User bundles must pay fees to compensate the aggregator. Fees can be paid by adding an additional action to the users bundle that pays tx.origin. For more info on how fees work, see [aggregator fees](../../aggregator/README.md#fees).
|
||||||
|
|
||||||
Practically, this means you have to first estimate the fee using `aggregator.estimateFee`, and then add an additional action to a user bundle that pays the aggregator with the amount returned from `estimateFee`. When estimating a payment, you should include this additional action with a payment of zero, otherwise the additional action will increase the fee that needs to be paid. Additionally, the `feeRequired` value returned from `estimateFee` is the absolute minimum fee required at the time of estimation, therefore, you should pay slightly extra to ensure the bundle has a good chance of being submitted successfully.
|
Practically, this means you have to first estimate the fee using `aggregator.estimateFee`, and then add an additional action to a user bundle that pays the aggregator with the amount returned from `estimateFee`. When estimating a payment, you should include this additional action with a payment of zero wei, otherwise the additional action will increase the fee that needs to be paid. Additionally, the `feeRequired` value returned from `estimateFee` is the absolute minimum fee required at the time of estimation, therefore, you should pay slightly extra to ensure the bundle has a good chance of being submitted successfully.
|
||||||
|
|
||||||
### Paying aggregator fees with native currency (ETH)
|
### Paying aggregator fees with native currency (ETH)
|
||||||
|
|
||||||
@@ -198,21 +198,6 @@ const bundle = wallet.sign({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Since the aggregator detects that fees have been paid by observing the effect of a bundle on its own balance, you can also tranfer the required fee directly to the aggregator. Following the same example as above, you can add an action that transfers the fee amount to the aggregator address, instead of the action that calls sendEthToTxOrigin. Ensure you modify the estimateFeeBundle to use this action instead.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const bundle = wallet.sign({
|
|
||||||
nonce: await wallet.Nonce(),
|
|
||||||
actions: [
|
|
||||||
...actions, // ... add your user actions here (approve, transfer, etc.)
|
|
||||||
{
|
|
||||||
ethValue: safeFee, // fee amount
|
|
||||||
contractAddress: aggregatorAddress,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Paying aggregator fees with custom currency (ERC20)
|
### Paying aggregator fees with custom currency (ERC20)
|
||||||
|
|
||||||
The aggregator must be set up to accept ERC20 tokens in order for this to work.
|
The aggregator must be set up to accept ERC20 tokens in order for this to work.
|
||||||
@@ -288,25 +273,6 @@ const bundle = wallet.sign({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Since the aggregator detects that fees have been paid by observing the effect of a bundle on its own balance, you can also tranfer the required fee directly to the aggregator. Following the same example as above, you can add an action that transfers the fee amount to the aggregator address, instead of the action that calls sendEthToTxOrigin. Ensure you modify the estimateFeeBundle to use this action instead.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const bundle = wallet.sign({
|
|
||||||
nonce: await wallet.Nonce(),
|
|
||||||
actions: [
|
|
||||||
...actions, // ... add your user actions here (approve, transfer, etc.)
|
|
||||||
{
|
|
||||||
ethValue: 0,
|
|
||||||
contractAddress: tokenContract.address,
|
|
||||||
encodedFunction: tokenContract.interface.encodeFunctionData("transfer", [
|
|
||||||
aggregatorAddress,
|
|
||||||
safeFee, // fee amount
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## VerificationGateway
|
## VerificationGateway
|
||||||
|
|
||||||
Exposes `VerificationGateway` and `VerificationGateway__factory` generated by
|
Exposes `VerificationGateway` and `VerificationGateway__factory` generated by
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import { ActionData, Bundle, PublicKey } from "./signer/types";
|
|||||||
import Aggregator, { BundleReceipt } from "./Aggregator";
|
import Aggregator, { BundleReceipt } from "./Aggregator";
|
||||||
import BlsSigner, {
|
import BlsSigner, {
|
||||||
TransactionBatchResponse,
|
TransactionBatchResponse,
|
||||||
|
// Used for sendTransactionBatch TSdoc comment
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
TransactionBatch,
|
||||||
UncheckedBlsSigner,
|
UncheckedBlsSigner,
|
||||||
_constructorGuard,
|
_constructorGuard,
|
||||||
} from "./BlsSigner";
|
} from "./BlsSigner";
|
||||||
@@ -18,6 +21,7 @@ import {
|
|||||||
} from "../typechain-types";
|
} from "../typechain-types";
|
||||||
import addSafetyPremiumToFee from "./helpers/addSafetyDivisorToFee";
|
import addSafetyPremiumToFee from "./helpers/addSafetyDivisorToFee";
|
||||||
|
|
||||||
|
/** Public key linked to actions parsed from a bundle */
|
||||||
export type PublicKeyLinkedToActions = {
|
export type PublicKeyLinkedToActions = {
|
||||||
publicKey: PublicKey;
|
publicKey: PublicKey;
|
||||||
actions: Array<ActionData>;
|
actions: Array<ActionData>;
|
||||||
@@ -28,6 +32,13 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
|
|||||||
readonly verificationGatewayAddress: string;
|
readonly verificationGatewayAddress: string;
|
||||||
readonly aggregatorUtilitiesAddress: string;
|
readonly aggregatorUtilitiesAddress: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param aggregatorUrl The url for an aggregator instance
|
||||||
|
* @param verificationGatewayAddress Verification gateway contract address
|
||||||
|
* @param aggregatorUtilitiesAddress Aggregator utilities contract address
|
||||||
|
* @param url Rpc url
|
||||||
|
* @param network The network the provider should connect to
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
aggregatorUrl: string,
|
aggregatorUrl: string,
|
||||||
verificationGatewayAddress: string,
|
verificationGatewayAddress: string,
|
||||||
@@ -41,6 +52,10 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
|
|||||||
this.aggregatorUtilitiesAddress = aggregatorUtilitiesAddress;
|
this.aggregatorUtilitiesAddress = aggregatorUtilitiesAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param transaction Transaction request object
|
||||||
|
* @returns An estimate of the amount of gas that would be required to submit the transaction to the network
|
||||||
|
*/
|
||||||
override async estimateGas(
|
override async estimateGas(
|
||||||
transaction: Deferrable<ethers.providers.TransactionRequest>,
|
transaction: Deferrable<ethers.providers.TransactionRequest>,
|
||||||
): Promise<BigNumber> {
|
): Promise<BigNumber> {
|
||||||
@@ -90,6 +105,12 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
|
|||||||
return addSafetyPremiumToFee(feeRequired);
|
return addSafetyPremiumToFee(feeRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends transaction to be executed. Adds the signed bundle to the aggregator
|
||||||
|
*
|
||||||
|
* @param signedTransaction A signed bundle
|
||||||
|
* @returns A transaction response object that can be awaited to get the transaction receipt
|
||||||
|
*/
|
||||||
override async sendTransaction(
|
override async sendTransaction(
|
||||||
signedTransaction: string | Promise<string>,
|
signedTransaction: string | Promise<string>,
|
||||||
): Promise<ethers.providers.TransactionResponse> {
|
): Promise<ethers.providers.TransactionResponse> {
|
||||||
@@ -121,6 +142,10 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param signedTransactionBatch A signed {@link TransactionBatch}
|
||||||
|
* @returns A transaction batch response object that can be awaited to get the transaction receipt
|
||||||
|
*/
|
||||||
async sendTransactionBatch(
|
async sendTransactionBatch(
|
||||||
signedTransactionBatch: string,
|
signedTransactionBatch: string,
|
||||||
): Promise<TransactionBatchResponse> {
|
): Promise<TransactionBatchResponse> {
|
||||||
@@ -149,6 +174,11 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param privateKey Private key for the account the signer represents
|
||||||
|
* @param addressOrIndex (Not Used) address or index of the account, managed by the connected Ethereum node
|
||||||
|
* @returns A new BlsSigner instance
|
||||||
|
*/
|
||||||
override getSigner(
|
override getSigner(
|
||||||
privateKey: string,
|
privateKey: string,
|
||||||
addressOrIndex?: string | number,
|
addressOrIndex?: string | number,
|
||||||
@@ -156,6 +186,11 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
|
|||||||
return new BlsSigner(_constructorGuard, this, privateKey, addressOrIndex);
|
return new BlsSigner(_constructorGuard, this, privateKey, addressOrIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param privateKey Private key for the account the signer represents
|
||||||
|
* @param addressOrIndex (Not Used) address or index of the account, managed by the connected Ethereum node
|
||||||
|
* @returns A new UncheckedBlsSigner instance
|
||||||
|
*/
|
||||||
override getUncheckedSigner(
|
override getUncheckedSigner(
|
||||||
privateKey: string,
|
privateKey: string,
|
||||||
addressOrIndex?: string,
|
addressOrIndex?: string,
|
||||||
@@ -163,6 +198,15 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
|
|||||||
return this.getSigner(privateKey, addressOrIndex).connectUnchecked();
|
return this.getSigner(privateKey, addressOrIndex).connectUnchecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the transaction receipt associated with the transaction (bundle) hash
|
||||||
|
*
|
||||||
|
* @remarks The transaction hash argument corresponds to a bundle hash and cannot be used on a block explorer.
|
||||||
|
* Instead, the transaction hash returned in the transaction receipt from this method can be used in a block explorer.
|
||||||
|
*
|
||||||
|
* @param transactionHash The transaction hash returned from the BlsProvider and BlsSigner sendTransaction methods. This is technically a bundle hash
|
||||||
|
* @returns The transaction receipt that corressponds to the transaction hash (bundle hash)
|
||||||
|
*/
|
||||||
override async getTransactionReceipt(
|
override async getTransactionReceipt(
|
||||||
transactionHash: string | Promise<string>,
|
transactionHash: string | Promise<string>,
|
||||||
): Promise<ethers.providers.TransactionReceipt> {
|
): Promise<ethers.providers.TransactionReceipt> {
|
||||||
@@ -170,6 +214,17 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
|
|||||||
return this._getTransactionReceipt(resolvedTransactionHash, 1, 20);
|
return this._getTransactionReceipt(resolvedTransactionHash, 1, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the transaction receipt associated with the transaction (bundle) hash
|
||||||
|
*
|
||||||
|
* @remarks The transaction hash argument cannot be used on a block explorer. It instead corresponds to a bundle hash.
|
||||||
|
* The transaction hash returned in the transaction receipt from this method can be used in a block explorer.
|
||||||
|
*
|
||||||
|
* @param transactionHash The transaction hash returned from sending a transaction. This is technically a bundle hash
|
||||||
|
* @param confirmations (Not used) the number of confirmations to wait for before returning the transaction receipt
|
||||||
|
* @param retries The number of retries to poll the receipt for
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
override async waitForTransaction(
|
override async waitForTransaction(
|
||||||
transactionHash: string,
|
transactionHash: string,
|
||||||
confirmations?: number,
|
confirmations?: number,
|
||||||
@@ -182,6 +237,11 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param address The address that the method gets the transaction count from
|
||||||
|
* @param blockTag The specific block tag to get the transaction count from
|
||||||
|
* @returns The number of transactions an account has sent
|
||||||
|
*/
|
||||||
override async getTransactionCount(
|
override async getTransactionCount(
|
||||||
address: string | Promise<string>,
|
address: string | Promise<string>,
|
||||||
blockTag?:
|
blockTag?:
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ import { ActionData, bundleToDto } from "./signer";
|
|||||||
export const _constructorGuard = {};
|
export const _constructorGuard = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property gas - (THIS PROPERTY IS NOT USED BY BLS WALLET) transaction gas limit
|
* Based on draft wallet_batchTransactions rpc proposal https://hackmd.io/HFHohGDbRSGgUFI2rk22bA?view
|
||||||
* @property maxPriorityFeePerGas - (THIS PROPERTY IS NOT USED BY BLS WALLET) miner tip aka priority fee
|
*
|
||||||
* @property maxFeePerGas - (THIS PROPERTY IS NOT USED BY BLS WALLET) the maximum total fee per gas the sender is willing to pay (includes the network/base fee and miner/priority fee) in wei
|
* @property gas - (THIS PROPERTY IS NOT USED BY BLS WALLET) Transaction gas limit
|
||||||
* @property nonce - integer of a nonce. This allows overwriting your own pending transactions that use the same nonce
|
* @property maxPriorityFeePerGas - (THIS PROPERTY IS NOT USED BY BLS WALLET) Miner tip aka priority fee
|
||||||
* @property chainId - chain ID that this transaction is valid on
|
* @property maxFeePerGas - (THIS PROPERTY IS NOT USED BY BLS WALLET) The maximum total fee per gas the sender is willing to pay (includes the network/base fee and miner/priority fee) in wei
|
||||||
|
* @property nonce - Integer of a nonce. This allows overwriting your own pending transactions that use the same nonce
|
||||||
|
* @property chainId - Chain ID that this transaction is valid on
|
||||||
* @property accessList - (THIS PROPERTY IS NOT USED BY BLS WALLET) EIP-2930 access list
|
* @property accessList - (THIS PROPERTY IS NOT USED BY BLS WALLET) EIP-2930 access list
|
||||||
*/
|
*/
|
||||||
export type BatchOptions = {
|
export type BatchOptions = {
|
||||||
@@ -33,14 +35,18 @@ export type BatchOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property transactions - an array of transaction objects
|
* @property transactions - An array of Ethers transaction objects
|
||||||
* @property batchOptions - optional batch options taken into account by smart contract wallets
|
* @property batchOptions - Optional batch options taken into account by smart contract wallets. See {@link BatchOptions}
|
||||||
*/
|
*/
|
||||||
export type TransactionBatch = {
|
export type TransactionBatch = {
|
||||||
transactions: Array<ethers.providers.TransactionRequest>;
|
transactions: Array<ethers.providers.TransactionRequest>;
|
||||||
batchOptions?: BatchOptions;
|
batchOptions?: BatchOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property transactions - An array of Ethers transaction response objects
|
||||||
|
* @property awaitBatchReceipt - A function that returns a promise that resolves to a transaction receipt
|
||||||
|
*/
|
||||||
export interface TransactionBatchResponse {
|
export interface TransactionBatchResponse {
|
||||||
transactions: Array<ethers.providers.TransactionResponse>;
|
transactions: Array<ethers.providers.TransactionResponse>;
|
||||||
awaitBatchReceipt: (
|
awaitBatchReceipt: (
|
||||||
@@ -58,6 +64,12 @@ export default class BlsSigner extends Signer {
|
|||||||
|
|
||||||
readonly initPromise: Promise<void>;
|
readonly initPromise: Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param constructorGuard Prevents BlsSigner constructor being called directly
|
||||||
|
* @param provider BlsProvider accociated with this signer
|
||||||
|
* @param privateKey Private key for the account this signer represents
|
||||||
|
* @param addressOrIndex (Not used) Address or index of this account, managed by the connected Ethereum node
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
constructorGuard: Record<string, unknown>,
|
constructorGuard: Record<string, unknown>,
|
||||||
provider: BlsProvider,
|
provider: BlsProvider,
|
||||||
@@ -92,6 +104,7 @@ export default class BlsSigner extends Signer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Instantiates a BLS Wallet and then connects the signer to it */
|
||||||
private async initializeWallet(privateKey: string | Promise<string>) {
|
private async initializeWallet(privateKey: string | Promise<string>) {
|
||||||
const resolvedPrivateKey = await privateKey;
|
const resolvedPrivateKey = await privateKey;
|
||||||
this.wallet = await BlsWalletWrapper.connect(
|
this.wallet = await BlsWalletWrapper.connect(
|
||||||
@@ -101,6 +114,25 @@ export default class BlsSigner extends Signer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns a random BLS private key
|
||||||
|
*/
|
||||||
|
static async getRandomBlsPrivateKey(): Promise<string> {
|
||||||
|
return await BlsWalletWrapper.getRandomBlsPrivateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends transactions to be executed. Converts the TransactionRequest
|
||||||
|
* to a bundle and adds it to the aggregator
|
||||||
|
*
|
||||||
|
* @remarks The transaction hash returned in the transaction response does
|
||||||
|
* NOT correspond to a transaction hash that can be viewed on a block
|
||||||
|
* explorer. It instead represents the bundle hash, which can be used to
|
||||||
|
* get a transaction receipt that has a hash that can be used on a block explorer
|
||||||
|
*
|
||||||
|
* @param transaction Transaction request object
|
||||||
|
* @returns A transaction response object that can be awaited to get the transaction receipt
|
||||||
|
*/
|
||||||
override async sendTransaction(
|
override async sendTransaction(
|
||||||
transaction: Deferrable<ethers.providers.TransactionRequest>,
|
transaction: Deferrable<ethers.providers.TransactionRequest>,
|
||||||
): Promise<ethers.providers.TransactionResponse> {
|
): Promise<ethers.providers.TransactionResponse> {
|
||||||
@@ -148,6 +180,10 @@ export default class BlsSigner extends Signer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param transactionBatch A transaction batch object
|
||||||
|
* @returns A transaction batch response object that can be awaited to get the transaction receipt
|
||||||
|
*/
|
||||||
async sendTransactionBatch(
|
async sendTransactionBatch(
|
||||||
transactionBatch: TransactionBatch,
|
transactionBatch: TransactionBatch,
|
||||||
): Promise<TransactionBatchResponse> {
|
): Promise<TransactionBatchResponse> {
|
||||||
@@ -225,6 +261,9 @@ export default class BlsSigner extends Signer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns The address associated with the BlsSigner
|
||||||
|
*/
|
||||||
async getAddress(): Promise<string> {
|
async getAddress(): Promise<string> {
|
||||||
await this.initPromise;
|
await this.initPromise;
|
||||||
if (this._address) {
|
if (this._address) {
|
||||||
@@ -251,6 +290,12 @@ export default class BlsSigner extends Signer {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @remarks Signs a transaction that can be executed by the BlsProvider
|
||||||
|
*
|
||||||
|
* @param transaction Transaction request object
|
||||||
|
* @returns A signed bundle as a string
|
||||||
|
*/
|
||||||
override async signTransaction(
|
override async signTransaction(
|
||||||
transaction: Deferrable<ethers.providers.TransactionRequest>,
|
transaction: Deferrable<ethers.providers.TransactionRequest>,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
@@ -285,6 +330,12 @@ export default class BlsSigner extends Signer {
|
|||||||
return JSON.stringify(bundleToDto(bundle));
|
return JSON.stringify(bundleToDto(bundle));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs a transaction batch that can be executed by the BlsProvider
|
||||||
|
*
|
||||||
|
* @param transactionBatch A transaction batch object
|
||||||
|
* @returns A signed bundle containing all transactions from the transaction batch as a string
|
||||||
|
*/
|
||||||
async signTransactionBatch(
|
async signTransactionBatch(
|
||||||
transactionBatch: TransactionBatch,
|
transactionBatch: TransactionBatch,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
@@ -342,8 +393,8 @@ export default class BlsSigner extends Signer {
|
|||||||
return JSON.stringify(bundleToDto(bundle));
|
return JSON.stringify(bundleToDto(bundle));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sign a message */
|
/** Signs a message */
|
||||||
// TODO: Come back to this once we support EIP-1271
|
// TODO: bls-wallet #201 Come back to this once we support EIP-1271
|
||||||
override async signMessage(message: Bytes | string): Promise<string> {
|
override async signMessage(message: Bytes | string): Promise<string> {
|
||||||
await this.initPromise;
|
await this.initPromise;
|
||||||
if (isBytes(message)) {
|
if (isBytes(message)) {
|
||||||
@@ -366,6 +417,9 @@ export default class BlsSigner extends Signer {
|
|||||||
throw new Error("_signTypedData() is not implemented");
|
throw new Error("_signTypedData() is not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns A new Signer object which does not perform additional checks when sending a transaction
|
||||||
|
*/
|
||||||
connectUnchecked(): BlsSigner {
|
connectUnchecked(): BlsSigner {
|
||||||
return new UncheckedBlsSigner(
|
return new UncheckedBlsSigner(
|
||||||
_constructorGuard,
|
_constructorGuard,
|
||||||
@@ -379,6 +433,10 @@ export default class BlsSigner extends Signer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param transaction Transaction request object
|
||||||
|
* @returns Transaction hash for the transaction, corresponds to a bundle hash
|
||||||
|
*/
|
||||||
async sendUncheckedTransaction(
|
async sendUncheckedTransaction(
|
||||||
transaction: Deferrable<ethers.providers.TransactionRequest>,
|
transaction: Deferrable<ethers.providers.TransactionRequest>,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
@@ -455,6 +513,12 @@ export default class BlsSigner extends Signer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UncheckedBlsSigner extends BlsSigner {
|
export class UncheckedBlsSigner extends BlsSigner {
|
||||||
|
/**
|
||||||
|
* As with other transaction methods, the transaction hash returned represents the bundle hash, NOT a transaction hash you can use on a block explorer
|
||||||
|
*
|
||||||
|
* @param transaction Transaction request object
|
||||||
|
* @returns The transaction response object with only the transaction hash property populated with a valid value
|
||||||
|
*/
|
||||||
override async sendTransaction(
|
override async sendTransaction(
|
||||||
transaction: Deferrable<ethers.providers.TransactionRequest>,
|
transaction: Deferrable<ethers.providers.TransactionRequest>,
|
||||||
): Promise<ethers.providers.TransactionResponse> {
|
): Promise<ethers.providers.TransactionResponse> {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { expect } from "chai";
|
|||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
import { parseEther } from "ethers/lib/utils";
|
import { parseEther } from "ethers/lib/utils";
|
||||||
|
|
||||||
import { Experimental, BlsWalletWrapper } from "../src";
|
import { Experimental } from "../src";
|
||||||
import BlsSigner, { UncheckedBlsSigner } from "../src/BlsSigner";
|
import BlsSigner, { UncheckedBlsSigner } from "../src/BlsSigner";
|
||||||
|
|
||||||
let aggregatorUrl: string;
|
let aggregatorUrl: string;
|
||||||
@@ -28,7 +28,7 @@ describe("BlsProvider", () => {
|
|||||||
chainId: 0x539, // 1337
|
chainId: 0x539, // 1337
|
||||||
};
|
};
|
||||||
|
|
||||||
privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
|
privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey();
|
||||||
|
|
||||||
blsProvider = new Experimental.BlsProvider(
|
blsProvider = new Experimental.BlsProvider(
|
||||||
aggregatorUrl,
|
aggregatorUrl,
|
||||||
@@ -72,7 +72,7 @@ describe("BlsProvider", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const newPrivateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
|
const newPrivateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey();
|
||||||
|
|
||||||
const newBlsSigner = newBlsProvider.getSigner(newPrivateKey);
|
const newBlsSigner = newBlsProvider.getSigner(newPrivateKey);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
|
|
||||||
import { Experimental, BlsWalletWrapper } from "../src";
|
import { Experimental } from "../src";
|
||||||
import { UncheckedBlsSigner } from "../src/BlsSigner";
|
import { UncheckedBlsSigner } from "../src/BlsSigner";
|
||||||
|
|
||||||
let aggregatorUrl: string;
|
let aggregatorUrl: string;
|
||||||
@@ -25,7 +25,7 @@ describe("BlsSigner", () => {
|
|||||||
chainId: 0x7a69,
|
chainId: 0x7a69,
|
||||||
};
|
};
|
||||||
|
|
||||||
privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
|
privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey();
|
||||||
|
|
||||||
blsProvider = new Experimental.BlsProvider(
|
blsProvider = new Experimental.BlsProvider(
|
||||||
aggregatorUrl,
|
aggregatorUrl,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ describe("BlsProvider", () => {
|
|||||||
chainId: 0x539, // 1337
|
chainId: 0x539, // 1337
|
||||||
};
|
};
|
||||||
|
|
||||||
privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
|
privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey();
|
||||||
|
|
||||||
blsProvider = new Experimental.BlsProvider(
|
blsProvider = new Experimental.BlsProvider(
|
||||||
aggregatorUrl,
|
aggregatorUrl,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { expect } from "chai";
|
|||||||
import { ethers } from "hardhat";
|
import { ethers } from "hardhat";
|
||||||
import { BigNumber, utils, Wallet } from "ethers";
|
import { BigNumber, utils, Wallet } from "ethers";
|
||||||
|
|
||||||
import { Experimental, BlsWalletWrapper } from "../clients/src";
|
import { Experimental } from "../clients/src";
|
||||||
import getNetworkConfig from "../shared/helpers/getNetworkConfig";
|
import getNetworkConfig from "../shared/helpers/getNetworkConfig";
|
||||||
|
|
||||||
describe("Provider tests", function () {
|
describe("Provider tests", function () {
|
||||||
@@ -12,7 +12,7 @@ describe("Provider tests", function () {
|
|||||||
|
|
||||||
this.beforeAll(async () => {
|
this.beforeAll(async () => {
|
||||||
const networkConfig = await getNetworkConfig("local");
|
const networkConfig = await getNetworkConfig("local");
|
||||||
const privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
|
const privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey();
|
||||||
const aggregatorUrl = "http://localhost:3000";
|
const aggregatorUrl = "http://localhost:3000";
|
||||||
const verificationGateway = networkConfig.addresses.verificationGateway;
|
const verificationGateway = networkConfig.addresses.verificationGateway;
|
||||||
const aggregatorUtilities = networkConfig.addresses.utilities;
|
const aggregatorUtilities = networkConfig.addresses.utilities;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ describe("BlsSigner", () => {
|
|||||||
chainId: 0x539, // 1337
|
chainId: 0x539, // 1337
|
||||||
};
|
};
|
||||||
|
|
||||||
privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
|
privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey();
|
||||||
|
|
||||||
blsProvider = new Experimental.BlsProvider(
|
blsProvider = new Experimental.BlsProvider(
|
||||||
aggregatorUrl,
|
aggregatorUrl,
|
||||||
@@ -802,7 +802,7 @@ describe("BlsSigner", () => {
|
|||||||
|
|
||||||
it("should await the init promise when connecting to an unchecked bls signer", async () => {
|
it("should await the init promise when connecting to an unchecked bls signer", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const newPrivateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
|
const newPrivateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey();
|
||||||
const newBlsSigner = blsProvider.getSigner(newPrivateKey);
|
const newBlsSigner = blsProvider.getSigner(newPrivateKey);
|
||||||
const uncheckedBlsSigner = newBlsSigner.connectUnchecked();
|
const uncheckedBlsSigner = newBlsSigner.connectUnchecked();
|
||||||
|
|
||||||
@@ -837,7 +837,7 @@ describe("BlsSigner", () => {
|
|||||||
rpcUrl,
|
rpcUrl,
|
||||||
network,
|
network,
|
||||||
);
|
);
|
||||||
const newPrivateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
|
const newPrivateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey();
|
||||||
const newBlsSigner = newBlsProvider.getSigner(newPrivateKey);
|
const newBlsSigner = newBlsProvider.getSigner(newPrivateKey);
|
||||||
const uncheckedBlsSigner = newBlsSigner.connectUnchecked();
|
const uncheckedBlsSigner = newBlsSigner.connectUnchecked();
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ async function getRandomSigners(
|
|||||||
|
|
||||||
const signers = [];
|
const signers = [];
|
||||||
for (let i = 0; i < numSigners; i++) {
|
for (let i = 0; i < numSigners; i++) {
|
||||||
const privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
|
const privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey();
|
||||||
const blsProvider = new Experimental.BlsProvider(
|
const blsProvider = new Experimental.BlsProvider(
|
||||||
aggregatorUrl,
|
aggregatorUrl,
|
||||||
verificationGateway,
|
verificationGateway,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
- [See an overview of BLS Wallet & how the components work together](./system_overview.md)
|
- [See an overview of BLS Wallet & how the components work together](./system_overview.md)
|
||||||
- [Use BLS Wallet in a browser/NodeJS/Deno app](./use_bls_wallet_clients.md)
|
- [Use BLS Wallet in 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)
|
- [Use BLS Wallet in your L2 dApp for cheaper, multi action transactions](./use_bls_wallet_dapp.md)
|
||||||
|
- [Use BLS Wallet components and features with an ethers.js provider and signer](./use_bls_provider.md)
|
||||||
- Setup the BLS Wallet components for:
|
- Setup the BLS Wallet components for:
|
||||||
- [Local development](./local_development.md)
|
- [Local development](./local_development.md)
|
||||||
- [Remote development](./remote_development.md)
|
- [Remote development](./remote_development.md)
|
||||||
|
|||||||
224
docs/use_bls_provider.md
Normal file
224
docs/use_bls_provider.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# BLS Provider
|
||||||
|
|
||||||
|
This document will show you how to interact with the `BlsProvider` and `BlsSigner` classes.
|
||||||
|
|
||||||
|
The `BlsProvider` and `BlsSigner` are part of the `bls-wallet-clients` npm package, and help you interact with BLS Wallet components in a similar way you would use an Ethers provider and signer to interact with the Ethereum ecosystem. It offers developers a familiar develoment experience, while providing access to BLS Wallet components and features. Essentially it's a Ethers provider-shaped wrapper around `bls-wallet-clients`.
|
||||||
|
|
||||||
|
The `BlsProvider` and `BlsSigner` mimic the behaviour of an Ethers [JsonRpcProvider](https://docs.ethers.org/v5/api/providers/jsonrpc-provider/) and [JsonRpcSigner](https://docs.ethers.org/v5/api/providers/jsonrpc-provider/#JsonRpcSigner) respectively. In this implementation, note that the `BlsSigner` has knowledge of its own private key. For more information on Ethers providers and signers, visit the [Ethers v5 docs](https://docs.ethers.org/v5/).
|
||||||
|
|
||||||
|
The `BlsProvider` and `BlsSigner` are covered by over 100 test cases, including integration tests. If any functionality is not documented here, it will likely be documented by test cases.
|
||||||
|
|
||||||
|
# Creating instances
|
||||||
|
|
||||||
|
## BlsProvider
|
||||||
|
|
||||||
|
### Instantiating a BlsProvider
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Experimental } from "bls-wallet-clients";
|
||||||
|
|
||||||
|
const aggregatorUrl = "http://localhost:3000";
|
||||||
|
const verificationGateway = "0x123";
|
||||||
|
const aggregatorUtilities = "0x321";
|
||||||
|
const rpcUrl = "http://localhost:8545";
|
||||||
|
const network = {
|
||||||
|
name: "localhost",
|
||||||
|
chainId: 0x539, // 1337
|
||||||
|
};
|
||||||
|
|
||||||
|
const provider = new Experimental.BlsProvider(
|
||||||
|
aggregatorUrl,
|
||||||
|
verificationGateway,
|
||||||
|
aggregatorUtilities,
|
||||||
|
rpcUrl,
|
||||||
|
network
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### BlsSigner
|
||||||
|
|
||||||
|
**Important:** Ensure that the BLS wallet you are linking the `BlsSigner` to via the private key is funded. Alternatively, if a wallet doesn't yet exist, it will be lazily created on the first transaction. In this scenario, you can create a random BLS private key with the following helper method and fund that account. It will need to be funded in order to send its first transaction.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Experimental } from "bls-wallet-clients";
|
||||||
|
|
||||||
|
const privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey();
|
||||||
|
|
||||||
|
const signer = provider.getSigner(privateKey);
|
||||||
|
|
||||||
|
// Send funds to this address if the wallet does not exist
|
||||||
|
const address = await signer.getAddress();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Instantiating a BlsSigner
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 32 random bytes
|
||||||
|
const privateKey =
|
||||||
|
"0x0001020304050607080910111213141516171819202122232425262728293031";
|
||||||
|
|
||||||
|
const signer = provider.getSigner(privateKey);
|
||||||
|
```
|
||||||
|
|
||||||
|
# Send ETH
|
||||||
|
|
||||||
|
### Send ETH via BlsSigner
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const transactionResponse = await signer.sendTransaction({
|
||||||
|
to: recipient,
|
||||||
|
value: amountToTransfer,
|
||||||
|
});
|
||||||
|
const transactionReceipt = await transactionResponse.wait();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Send ETH via BlsProvider
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Note the transaction must be signed via the BlsSigner first
|
||||||
|
const signedTransaction = await signer.signTransaction({
|
||||||
|
to: recipient,
|
||||||
|
value: amountToTransfer,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transactionResponse = await provider.sendTransaction(signedTransaction);
|
||||||
|
const transactionReceipt = await transactionResponse.wait();
|
||||||
|
```
|
||||||
|
|
||||||
|
# Get a transaction receipt via a transaction hash
|
||||||
|
|
||||||
|
This will return a transaction receipt that corresponds to a transaction and transaction hash that can be quired on a block explorer.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const transactionReceipt = await provider.getTransactionReceipt(
|
||||||
|
transactionResponse.hash
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
# Multi-action transactions
|
||||||
|
|
||||||
|
### Send multi-action transactions with BlsSigner
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// using BlsSigner
|
||||||
|
const transactionBatchResponse = await signer.sendTransactionBatch(
|
||||||
|
{
|
||||||
|
to: recipient1,
|
||||||
|
value: amountToTransfer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: recipient2,
|
||||||
|
value: amountToTransfer,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const transactionReceipt = await transactionBatchResponse.awaitBatchReceipt();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Send multi-action transactions with BlsProvider
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Note the transaction must be signed via the BlsSigner first
|
||||||
|
const signedTransactionBatch = await signer.signTransactionBatch(
|
||||||
|
{
|
||||||
|
to: recipient1,
|
||||||
|
value: amountToTransfer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: recipient2,
|
||||||
|
value: amountToTransfer,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const transactionBatchResponse = await provider.sendTransactionBatch(
|
||||||
|
signedTransactionBatch
|
||||||
|
);
|
||||||
|
const transactionReceipt = await transactionBatchResponse.awaitBatchReceipt();
|
||||||
|
```
|
||||||
|
|
||||||
|
# Interacting with smart contracts
|
||||||
|
|
||||||
|
### Interacting with a deployed smart contract
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const ERC20 = new ethers.Contract(tokenAddress, tokenInterface, signer);
|
||||||
|
|
||||||
|
const transaction = await ERC20.transfer(recipient, amountToTransfer);
|
||||||
|
await transaction.wait();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interacting with a smart contract that hasn't been deployed
|
||||||
|
|
||||||
|
**Important:** You cannot deploy contracts with a BLS Wallet. Use a funded EOA for deploying
|
||||||
|
contracts instead. Then make sure you connect to the contract instance with your `BlsSigner`.
|
||||||
|
Contract deployments via a BLS Wallet is a feature tabled for our V2 contracts.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Deploying contracts must be done by a non-BLS Wallet account
|
||||||
|
const nonBLSWalletAccount = new ethers.Wallet(fundedWalletPrivateKey, provider);
|
||||||
|
|
||||||
|
const ERC20 = await ethers.getContractFactory("ERC20");
|
||||||
|
const erc20 = await ERC20.connect(nonBLSWalletAccount).deploy(
|
||||||
|
tokenName,
|
||||||
|
tokenSymbol,
|
||||||
|
tokenSupply
|
||||||
|
);
|
||||||
|
await erc20.deployed();
|
||||||
|
|
||||||
|
const signer = provider.getSigner(privateKey);
|
||||||
|
|
||||||
|
const transaction = await erc20
|
||||||
|
.connect(signer)
|
||||||
|
.transfer(recipient, amountToTransfer);
|
||||||
|
await transaction.wait();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-action contract interactions
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Working example of this setup can be found in BlsSignerContractInteraction.test.ts
|
||||||
|
const transactionBatch = {
|
||||||
|
transactions: [
|
||||||
|
{
|
||||||
|
to: ERC20.address,
|
||||||
|
value: 0,
|
||||||
|
data: ERC20.interface.encodeFunctionData("approve", [
|
||||||
|
spender,
|
||||||
|
amountToTransfer,
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: spender,
|
||||||
|
value: 0,
|
||||||
|
data: mockTokenSpender.interface.encodeFunctionData(
|
||||||
|
"TransferERC20ToSelf",
|
||||||
|
[ERC20.address, amountToTransfer]
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await signer.sendTransactionBatch(transactionBatch);
|
||||||
|
await result.awaitBatchReceipt();
|
||||||
|
```
|
||||||
|
|
||||||
|
# Estimating gas for a transaction
|
||||||
|
|
||||||
|
The `BlsProvider` adds a small safety premium to the gas estimate to improve the likelyhood a bundle gets included during aggregation. If you'd like more fined grained control over the fee, you use the [helper method](../contracts//clients/README.md#estimating-and-paying-fees) from the `bls-wallet-clients` package directly instead.
|
||||||
|
|
||||||
|
### Estimating gas with BlsProvider
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const fee = await provider.estimateGas({
|
||||||
|
to: recipient,
|
||||||
|
value: amountToTransfer,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Estimating gas when interacting with a contract
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const feeEstimate = await ERC20.estimateGas.transfer(
|
||||||
|
recipient,
|
||||||
|
amountToTransfer
|
||||||
|
);
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user