From 548301d32d605f47e3247fe5270a50c961e5ce73 Mon Sep 17 00:00:00 2001 From: JohnGuilding Date: Fri, 10 Mar 2023 14:26:21 +0000 Subject: [PATCH 01/10] Add bls provider guide and ts doc comments --- contracts/clients/src/BlsProvider.ts | 49 ++++++ contracts/clients/src/BlsSigner.ts | 43 ++++- docs/use_bls_provider.md | 226 +++++++++++++++++++++++++++ 3 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 docs/use_bls_provider.md diff --git a/contracts/clients/src/BlsProvider.ts b/contracts/clients/src/BlsProvider.ts index 926558e7..0bf8dc32 100644 --- a/contracts/clients/src/BlsProvider.ts +++ b/contracts/clients/src/BlsProvider.ts @@ -28,6 +28,13 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider { readonly verificationGatewayAddress: 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( aggregatorUrl: string, verificationGatewayAddress: string, @@ -41,6 +48,10 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider { this.aggregatorUtilitiesAddress = aggregatorUtilitiesAddress; } + /** + * @param transaction transaction request object + * @returns the amount of gas that would be required to submit the transaction to the network + */ override async estimateGas( transaction: Deferrable, ): Promise { @@ -90,6 +101,13 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider { return addSafetyPremiumToFee(feeRequired); } + /** + * Sends transactions to be executed. Adds the signed + * bundle to the connected aggregator + * + * @param signedTransaction a signed bundle + * @returns a transaction response object that can be awaited to get the transaction receipt + */ override async sendTransaction( signedTransaction: string | Promise, ): Promise { @@ -149,6 +167,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( privateKey: string, addressOrIndex?: string | number, @@ -156,6 +179,11 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider { 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( privateKey: string, addressOrIndex?: string, @@ -163,6 +191,13 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider { return this.getSigner(privateKey, addressOrIndex).connectUnchecked(); } + /** + * @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 + * @returns the transaction receipt that corressponds to the transaction hash (bundle hash) + */ override async getTransactionReceipt( transactionHash: string | Promise, ): Promise { @@ -170,6 +205,15 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider { return this._getTransactionReceipt(resolvedTransactionHash, 1, 20); } + /** + * @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 + * @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( transactionHash: string, confirmations?: number, @@ -182,6 +226,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 the number of transactions an account has sent + */ override async getTransactionCount( address: string | Promise, blockTag?: diff --git a/contracts/clients/src/BlsSigner.ts b/contracts/clients/src/BlsSigner.ts index fee359a2..cef91bd0 100644 --- a/contracts/clients/src/BlsSigner.ts +++ b/contracts/clients/src/BlsSigner.ts @@ -58,6 +58,12 @@ export default class BlsSigner extends Signer { readonly initPromise: Promise; + /** + * @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( constructorGuard: Record, provider: BlsProvider, @@ -92,6 +98,7 @@ export default class BlsSigner extends Signer { } } + /** instantiates a BLS Wallet and then connects the signer to it */ private async initializeWallet(privateKey: string | Promise) { const resolvedPrivateKey = await privateKey; this.wallet = await BlsWalletWrapper.connect( @@ -101,6 +108,18 @@ export default class BlsSigner extends Signer { ); } + /** + * Sends transactions to be executed. Converts the TransactionRequest + * to a bundles and adds it to the connected 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( transaction: Deferrable, ): Promise { @@ -225,6 +244,9 @@ export default class BlsSigner extends Signer { ); } + /** + * @returns the address associated with the BlsSigner + */ async getAddress(): Promise { await this.initPromise; if (this._address) { @@ -251,6 +273,12 @@ export default class BlsSigner extends Signer { ]); } + /** + * 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( transaction: Deferrable, ): Promise { @@ -343,7 +371,7 @@ export default class BlsSigner extends Signer { } /** Sign 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 { await this.initPromise; if (isBytes(message)) { @@ -366,6 +394,9 @@ export default class BlsSigner extends Signer { throw new Error("_signTypedData() is not implemented"); } + /** + * @returns a new Signer object which does not perform additional checks when sending a transaction + */ connectUnchecked(): BlsSigner { return new UncheckedBlsSigner( _constructorGuard, @@ -379,6 +410,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( transaction: Deferrable, ): Promise { @@ -455,6 +490,12 @@ export default class BlsSigner extends Signer { } export class UncheckedBlsSigner extends BlsSigner { + /** + * @remarks as with other transaction methods, the transaction hash returned represents the bundle hash + * + * @param transaction transaction request object + * @returns the transaction response object with only the transaction hash property populated with a valid value + */ override async sendTransaction( transaction: Deferrable, ): Promise { diff --git a/docs/use_bls_provider.md b/docs/use_bls_provider.md new file mode 100644 index 00000000..b43e34ec --- /dev/null +++ b/docs/use_bls_provider.md @@ -0,0 +1,226 @@ +# 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, BlsWalletWrapper } 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 { BlsWalletWrapper } from "bls-wallet-clients"; + +const privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); + +// Send funds to this address if the wallet does not exist +const address = await BlsWalletWrapper.Address( + privateKey, + verificationGatewayAddress, + provider +); +``` + +### 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 +); +``` From 25469d50e4d025fe68876ccc775900e8b4b1794d Mon Sep 17 00:00:00 2001 From: JohnGuilding Date: Fri, 10 Mar 2023 14:27:09 +0000 Subject: [PATCH 02/10] Remove directly paying aggregator fees docs example --- contracts/clients/README.md | 36 +----------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/contracts/clients/README.md b/contracts/clients/README.md index 36bfd723..fa78763c 100644 --- a/contracts/clients/README.md +++ b/contracts/clients/README.md @@ -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). -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) @@ -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) 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 Exposes `VerificationGateway` and `VerificationGateway__factory` generated by From d2c6cff629eca1c54363dcf53320d356f0f8b163 Mon Sep 17 00:00:00 2001 From: JohnGuilding Date: Mon, 27 Mar 2023 12:48:35 +0100 Subject: [PATCH 03/10] Add missing documentation following rebase --- contracts/clients/src/BlsProvider.ts | 65 +++++++++++++++----------- contracts/clients/src/BlsSigner.ts | 70 +++++++++++++++++----------- docs/README.md | 1 + 3 files changed, 82 insertions(+), 54 deletions(-) diff --git a/contracts/clients/src/BlsProvider.ts b/contracts/clients/src/BlsProvider.ts index 0bf8dc32..bdd51cb9 100644 --- a/contracts/clients/src/BlsProvider.ts +++ b/contracts/clients/src/BlsProvider.ts @@ -6,6 +6,9 @@ import { ActionData, Bundle, PublicKey } from "./signer/types"; import Aggregator, { BundleReceipt } from "./Aggregator"; import BlsSigner, { TransactionBatchResponse, + // Used for sendTransactionBatch TSdoc comment + // eslint-disable-next-line no-unused-vars + TransactionBatch, UncheckedBlsSigner, _constructorGuard, } from "./BlsSigner"; @@ -18,6 +21,7 @@ import { } from "../typechain-types"; import addSafetyPremiumToFee from "./helpers/addSafetyDivisorToFee"; +/** Public key linked to actions parsed from a bundle */ export type PublicKeyLinkedToActions = { publicKey: PublicKey; actions: Array; @@ -29,11 +33,11 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider { 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 + * @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( aggregatorUrl: string, @@ -49,8 +53,8 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider { } /** - * @param transaction transaction request object - * @returns the amount of gas that would be required to submit the transaction to the network + * @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( transaction: Deferrable, @@ -102,11 +106,10 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider { } /** - * Sends transactions to be executed. Adds the signed - * bundle to the connected aggregator + * 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 + * @param signedTransaction A signed bundle + * @returns A transaction response object that can be awaited to get the transaction receipt */ override async sendTransaction( signedTransaction: string | Promise, @@ -139,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( signedTransactionBatch: string, ): Promise { @@ -168,9 +175,9 @@ 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 + * @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( privateKey: string, @@ -180,9 +187,9 @@ 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 UncheckedBlsSigner instance + * @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( privateKey: string, @@ -192,11 +199,13 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider { } /** - * @remarks the transaction hash argument corresponds to a bundle hash and cannot be used on a block explorer. + * 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 - * @returns the transaction receipt that corressponds to the transaction hash (bundle hash) + * @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( transactionHash: string | Promise, @@ -206,12 +215,14 @@ export default class BlsProvider extends ethers.providers.JsonRpcProvider { } /** - * @remarks the transaction hash argument cannot be used on a block explorer. It instead corresponds to a bundle hash. + * 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 - * @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 + * @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( @@ -227,9 +238,9 @@ 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 the number of transactions an account has sent + * @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( address: string | Promise, diff --git a/contracts/clients/src/BlsSigner.ts b/contracts/clients/src/BlsSigner.ts index cef91bd0..dfef0768 100644 --- a/contracts/clients/src/BlsSigner.ts +++ b/contracts/clients/src/BlsSigner.ts @@ -16,11 +16,13 @@ import { ActionData, bundleToDto } from "./signer"; export const _constructorGuard = {}; /** - * @property gas - (THIS PROPERTY IS NOT USED BY BLS WALLET) transaction gas limit - * @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 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 + * Based on draft wallet_batchTransactions rpc proposal https://hackmd.io/HFHohGDbRSGgUFI2rk22bA?view + * + * @property gas - (THIS PROPERTY IS NOT USED BY BLS WALLET) Transaction gas limit + * @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 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 */ export type BatchOptions = { @@ -33,14 +35,18 @@ export type BatchOptions = { }; /** - * @property transactions - an array of transaction objects - * @property batchOptions - optional batch options taken into account by smart contract wallets + * @property transactions - An array of Ethers transaction objects + * @property batchOptions - Optional batch options taken into account by smart contract wallets. See {@link BatchOptions} */ export type TransactionBatch = { transactions: Array; 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 { transactions: Array; awaitBatchReceipt: ( @@ -59,10 +65,10 @@ export default class BlsSigner extends Signer { readonly initPromise: Promise; /** - * @param constructorGuard prevents BlsSigner constructor being called directly + * @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 + * @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( constructorGuard: Record, @@ -98,7 +104,7 @@ export default class BlsSigner extends Signer { } } - /** instantiates a BLS Wallet and then connects the signer to it */ + /** Instantiates a BLS Wallet and then connects the signer to it */ private async initializeWallet(privateKey: string | Promise) { const resolvedPrivateKey = await privateKey; this.wallet = await BlsWalletWrapper.connect( @@ -110,15 +116,15 @@ export default class BlsSigner extends Signer { /** * Sends transactions to be executed. Converts the TransactionRequest - * to a bundles and adds it to the connected aggregator + * 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 + * @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 + * @param transaction Transaction request object + * @returns A transaction response object that can be awaited to get the transaction receipt */ override async sendTransaction( transaction: Deferrable, @@ -167,6 +173,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( transactionBatch: TransactionBatch, ): Promise { @@ -245,7 +255,7 @@ export default class BlsSigner extends Signer { } /** - * @returns the address associated with the BlsSigner + * @returns The address associated with the BlsSigner */ async getAddress(): Promise { await this.initPromise; @@ -274,10 +284,10 @@ export default class BlsSigner extends Signer { } /** - * Signs a transaction that can be executed by the BlsProvider + * @remarks Signs a transaction that can be executed by the BlsProvider * - * @param transaction transaction request object - * @returns a signed bundle as a string + * @param transaction Transaction request object + * @returns A signed bundle as a string */ override async signTransaction( transaction: Deferrable, @@ -313,6 +323,12 @@ export default class BlsSigner extends Signer { 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( transactionBatch: TransactionBatch, ): Promise { @@ -370,7 +386,7 @@ export default class BlsSigner extends Signer { return JSON.stringify(bundleToDto(bundle)); } - /** Sign a message */ + /** Signs a message */ // TODO: bls-wallet #201 Come back to this once we support EIP-1271 override async signMessage(message: Bytes | string): Promise { await this.initPromise; @@ -395,7 +411,7 @@ export default class BlsSigner extends Signer { } /** - * @returns a new Signer object which does not perform additional checks when sending a transaction + * @returns A new Signer object which does not perform additional checks when sending a transaction */ connectUnchecked(): BlsSigner { return new UncheckedBlsSigner( @@ -411,8 +427,8 @@ export default class BlsSigner extends Signer { } /** - * @param transaction transaction request object - * @returns transaction hash for the transaction, corresponds to a bundle hash + * @param transaction Transaction request object + * @returns Transaction hash for the transaction, corresponds to a bundle hash */ async sendUncheckedTransaction( transaction: Deferrable, @@ -491,10 +507,10 @@ export default class BlsSigner extends Signer { export class UncheckedBlsSigner extends BlsSigner { /** - * @remarks as with other transaction methods, the transaction hash returned represents the bundle hash + * 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 + * @param transaction Transaction request object + * @returns The transaction response object with only the transaction hash property populated with a valid value */ override async sendTransaction( transaction: Deferrable, diff --git a/docs/README.md b/docs/README.md index a273e3fb..0448cf34 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,6 +3,7 @@ - [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) +- [Use BLS Wallet components and features with an Ethers-like provider](./use_bls_provider.md) - Setup the BLS Wallet components for: - [Local development](./local_development.md) - [Remote development](./remote_development.md) From e734209df029866525dc7897f4ae5e3aeedd0fca Mon Sep 17 00:00:00 2001 From: JohnGuilding Date: Tue, 28 Mar 2023 11:06:50 +0100 Subject: [PATCH 04/10] Remove BlsWalletWrapper in provider docs where possible --- docs/use_bls_provider.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/use_bls_provider.md b/docs/use_bls_provider.md index b43e34ec..9c2cd497 100644 --- a/docs/use_bls_provider.md +++ b/docs/use_bls_provider.md @@ -15,7 +15,7 @@ The `BlsProvider` and `BlsSigner` are covered by over 100 test cases, including ### Instantiating a BlsProvider ```ts -import { Experimental, BlsWalletWrapper } from "bls-wallet-clients"; +import { Experimental } from "bls-wallet-clients"; const aggregatorUrl = "http://localhost:3000"; const verificationGateway = "0x123"; @@ -44,12 +44,10 @@ import { BlsWalletWrapper } from "bls-wallet-clients"; const privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); +const signer = provider.getSigner(privateKey); + // Send funds to this address if the wallet does not exist -const address = await BlsWalletWrapper.Address( - privateKey, - verificationGatewayAddress, - provider -); +const address = await signer.getAddress(); ``` ### Instantiating a BlsSigner From 2dae355817be9e307f7e3498338e69df775d77c8 Mon Sep 17 00:00:00 2001 From: JohnGuilding Date: Tue, 28 Mar 2023 11:07:20 +0100 Subject: [PATCH 05/10] Update root readme to include provider link --- README.md | 1 + docs/README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index acd3ac36..8ba76dc8 100644 --- a/README.md +++ b/README.md @@ -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. - 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 components and features with an [ethers.js provider and signer](./use_bls_provider.md) ### Setup your development environment diff --git a/docs/README.md b/docs/README.md index 0448cf34..874db51b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,7 @@ - [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) -- [Use BLS Wallet components and features with an Ethers-like provider](./use_bls_provider.md) +- [Use BLS Wallet components and features with an ethers.js provider and signer](./use_bls_provider.md) - Setup the BLS Wallet components for: - [Local development](./local_development.md) - [Remote development](./remote_development.md) From 7435d9976ecbb0ec388b0adf914a8745445e9803 Mon Sep 17 00:00:00 2001 From: JohnGuilding Date: Tue, 28 Mar 2023 13:04:58 +0100 Subject: [PATCH 06/10] Fix typescript errors in BundleTable --- aggregator/src/app/BundleTable.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/aggregator/src/app/BundleTable.ts b/aggregator/src/app/BundleTable.ts index f1baa87a..41ae567e 100644 --- a/aggregator/src/app/BundleTable.ts +++ b/aggregator/src/app/BundleTable.ts @@ -60,14 +60,14 @@ export type BundleRow = Row; function fromRawRow(rawRow: RawRow | sqlite.Row): Row { if (Array.isArray(rawRow)) { rawRow = { - id: rawRow[0], - status: rawRow[1], - hash: rawRow[2], - bundle: rawRow[3], - eligibleAfter: rawRow[4], - nextEligibilityDelay: rawRow[5], - submitError: rawRow[6], - receipt: rawRow[7], + id: rawRow[0] as number, + status: rawRow[1] as string, + hash: rawRow[2] as string, + bundle: rawRow[3] as string, + eligibleAfter: rawRow[4] as string, + nextEligibilityDelay: rawRow[5] as string, + submitError: rawRow[6] as string | null, + receipt: rawRow[7] as string | null, }; } From b988fbc92c7518a62609b10953e68449f4b8e1f5 Mon Sep 17 00:00:00 2001 From: JohnGuilding Date: Wed, 29 Mar 2023 11:11:11 +0100 Subject: [PATCH 07/10] Add static get random private key method to bls signer --- contracts/clients/src/BlsSigner.ts | 7 +++++++ contracts/clients/test/BlsProvider.test.ts | 6 +++--- contracts/clients/test/BlsSigner.test.ts | 4 ++-- contracts/test-integration/BlsProvider.test.ts | 2 +- .../BlsProviderContractInteraction.test.ts | 4 ++-- contracts/test-integration/BlsSigner.test.ts | 6 +++--- .../test-integration/BlsSignerContractInteraction.test.ts | 2 +- docs/use_bls_provider.md | 4 ++-- 8 files changed, 21 insertions(+), 14 deletions(-) diff --git a/contracts/clients/src/BlsSigner.ts b/contracts/clients/src/BlsSigner.ts index dfef0768..ea08b0f3 100644 --- a/contracts/clients/src/BlsSigner.ts +++ b/contracts/clients/src/BlsSigner.ts @@ -114,6 +114,13 @@ export default class BlsSigner extends Signer { ); } + /** + * @returns a random BLS private key + */ + static async getRandomBlsPrivateKey(): Promise { + return await BlsWalletWrapper.getRandomBlsPrivateKey(); + } + /** * Sends transactions to be executed. Converts the TransactionRequest * to a bundle and adds it to the aggregator diff --git a/contracts/clients/test/BlsProvider.test.ts b/contracts/clients/test/BlsProvider.test.ts index 7cd24c58..274a8e89 100644 --- a/contracts/clients/test/BlsProvider.test.ts +++ b/contracts/clients/test/BlsProvider.test.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { ethers } from "ethers"; import { parseEther } from "ethers/lib/utils"; -import { Experimental, BlsWalletWrapper } from "../src"; +import { Experimental } from "../src"; import BlsSigner, { UncheckedBlsSigner } from "../src/BlsSigner"; let aggregatorUrl: string; @@ -28,7 +28,7 @@ describe("BlsProvider", () => { chainId: 0x539, // 1337 }; - privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); + privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey(); blsProvider = new Experimental.BlsProvider( aggregatorUrl, @@ -72,7 +72,7 @@ describe("BlsProvider", () => { ); // Act - const newPrivateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); + const newPrivateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey(); const newBlsSigner = newBlsProvider.getSigner(newPrivateKey); diff --git a/contracts/clients/test/BlsSigner.test.ts b/contracts/clients/test/BlsSigner.test.ts index bf9fba18..04ba3b84 100644 --- a/contracts/clients/test/BlsSigner.test.ts +++ b/contracts/clients/test/BlsSigner.test.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { ethers } from "ethers"; -import { Experimental, BlsWalletWrapper } from "../src"; +import { Experimental } from "../src"; import { UncheckedBlsSigner } from "../src/BlsSigner"; let aggregatorUrl: string; @@ -25,7 +25,7 @@ describe("BlsSigner", () => { chainId: 0x7a69, }; - privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); + privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey(); blsProvider = new Experimental.BlsProvider( aggregatorUrl, diff --git a/contracts/test-integration/BlsProvider.test.ts b/contracts/test-integration/BlsProvider.test.ts index de9104dc..8636e4e1 100644 --- a/contracts/test-integration/BlsProvider.test.ts +++ b/contracts/test-integration/BlsProvider.test.ts @@ -39,7 +39,7 @@ describe("BlsProvider", () => { chainId: 0x539, // 1337 }; - privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); + privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey(); blsProvider = new Experimental.BlsProvider( aggregatorUrl, diff --git a/contracts/test-integration/BlsProviderContractInteraction.test.ts b/contracts/test-integration/BlsProviderContractInteraction.test.ts index fafe8ef6..c9edec21 100644 --- a/contracts/test-integration/BlsProviderContractInteraction.test.ts +++ b/contracts/test-integration/BlsProviderContractInteraction.test.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { ethers } from "hardhat"; import { BigNumber, utils, Wallet } from "ethers"; -import { Experimental, BlsWalletWrapper } from "../clients/src"; +import { Experimental } from "../clients/src"; import getNetworkConfig from "../shared/helpers/getNetworkConfig"; describe("Provider tests", function () { @@ -12,7 +12,7 @@ describe("Provider tests", function () { this.beforeAll(async () => { const networkConfig = await getNetworkConfig("local"); - const privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); + const privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey(); const aggregatorUrl = "http://localhost:3000"; const verificationGateway = networkConfig.addresses.verificationGateway; const aggregatorUtilities = networkConfig.addresses.utilities; diff --git a/contracts/test-integration/BlsSigner.test.ts b/contracts/test-integration/BlsSigner.test.ts index 10e7274c..139432a1 100644 --- a/contracts/test-integration/BlsSigner.test.ts +++ b/contracts/test-integration/BlsSigner.test.ts @@ -48,7 +48,7 @@ describe("BlsSigner", () => { chainId: 0x539, // 1337 }; - privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); + privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey(); blsProvider = new Experimental.BlsProvider( aggregatorUrl, @@ -799,7 +799,7 @@ describe("BlsSigner", () => { it("should await the init promise when connecting to an unchecked bls signer", async () => { // Arrange - const newPrivateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); + const newPrivateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey(); const newBlsSigner = blsProvider.getSigner(newPrivateKey); const uncheckedBlsSigner = newBlsSigner.connectUnchecked(); @@ -834,7 +834,7 @@ describe("BlsSigner", () => { rpcUrl, network, ); - const newPrivateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); + const newPrivateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey(); const newBlsSigner = newBlsProvider.getSigner(newPrivateKey); const uncheckedBlsSigner = newBlsSigner.connectUnchecked(); diff --git a/contracts/test-integration/BlsSignerContractInteraction.test.ts b/contracts/test-integration/BlsSignerContractInteraction.test.ts index d56f696c..3465f814 100644 --- a/contracts/test-integration/BlsSignerContractInteraction.test.ts +++ b/contracts/test-integration/BlsSignerContractInteraction.test.ts @@ -22,7 +22,7 @@ async function getRandomSigners( const signers = []; for (let i = 0; i < numSigners; i++) { - const privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); + const privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey(); const blsProvider = new Experimental.BlsProvider( aggregatorUrl, verificationGateway, diff --git a/docs/use_bls_provider.md b/docs/use_bls_provider.md index 9c2cd497..36fb9ee6 100644 --- a/docs/use_bls_provider.md +++ b/docs/use_bls_provider.md @@ -40,9 +40,9 @@ const provider = new Experimental.BlsProvider( **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 { BlsWalletWrapper } from "bls-wallet-clients"; +import { Experimental } from "bls-wallet-clients"; -const privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); +const privateKey = await Experimental.BlsSigner.getRandomBlsPrivateKey(); const signer = provider.getSigner(privateKey); From d86cf097160305a6d57162c3b231eca41c9a059b Mon Sep 17 00:00:00 2001 From: JohnGuilding Date: Thu, 30 Mar 2023 10:33:56 +0100 Subject: [PATCH 08/10] Add wait for contract deploy script to agg workflow --- .github/workflows/aggregator.yml | 2 ++ aggregator/src/app/app.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/aggregator.yml b/.github/workflows/aggregator.yml index aad4ad8f..53e8ae8c 100644 --- a/.github/workflows/aggregator.yml +++ b/.github/workflows/aggregator.yml @@ -73,6 +73,8 @@ jobs: run: yarn start & - working-directory: ./contracts run: ./scripts/wait-for-rpc.sh + - working-directory: ./contracts + run: ./scripts/wait-for-contract-deploy.sh - run: cp .env.local.example .env - run: deno test --allow-net --allow-env --allow-read diff --git a/aggregator/src/app/app.ts b/aggregator/src/app/app.ts index d073f942..88e6c345 100644 --- a/aggregator/src/app/app.ts +++ b/aggregator/src/app/app.ts @@ -16,6 +16,8 @@ import BundleTable from "./BundleTable.ts"; import AggregationStrategy from "./AggregationStrategy.ts"; import AggregationStrategyRouter from "./AggregationStrategyRouter.ts"; +// Temporary comment to test CI + export default async function app(emit: (evt: AppEvent) => void) { const { addresses } = await getNetworkConfig(); From 7978ed0690fe5baedaca5685153744c4c336d65d Mon Sep 17 00:00:00 2001 From: JohnGuilding Date: Thu, 30 Mar 2023 12:19:34 +0100 Subject: [PATCH 09/10] Remove CI test comment --- aggregator/src/app/app.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/aggregator/src/app/app.ts b/aggregator/src/app/app.ts index 88e6c345..d073f942 100644 --- a/aggregator/src/app/app.ts +++ b/aggregator/src/app/app.ts @@ -16,8 +16,6 @@ import BundleTable from "./BundleTable.ts"; import AggregationStrategy from "./AggregationStrategy.ts"; import AggregationStrategyRouter from "./AggregationStrategyRouter.ts"; -// Temporary comment to test CI - export default async function app(emit: (evt: AppEvent) => void) { const { addresses } = await getNetworkConfig(); From 5529e078d9e190a52a636f8fef593905e017a086 Mon Sep 17 00:00:00 2001 From: "Paul-T.C-Yu" <35304302+NOOMA-42@users.noreply.github.com> Date: Mon, 3 Apr 2023 16:19:12 +0800 Subject: [PATCH 10/10] Add /health to aggregator --- aggregator/src/app/AppEvent.ts | 1 + aggregator/src/app/BundleRouter.ts | 1 - aggregator/src/app/HealthRouter.ts | 16 ++++++++++++++++ aggregator/src/app/HealthService.ts | 11 +++++++++++ aggregator/src/app/app.ts | 5 +++++ aggregator/test/HealthService.test.ts | 10 ++++++++++ aggregator/test/helpers/Fixture.ts | 7 +++++++ 7 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 aggregator/src/app/HealthRouter.ts create mode 100644 aggregator/src/app/HealthService.ts create mode 100644 aggregator/test/HealthService.test.ts diff --git a/aggregator/src/app/AppEvent.ts b/aggregator/src/app/AppEvent.ts index 68e1c05d..064eedfe 100644 --- a/aggregator/src/app/AppEvent.ts +++ b/aggregator/src/app/AppEvent.ts @@ -88,4 +88,5 @@ type AppEvent = }; }; + export default AppEvent; diff --git a/aggregator/src/app/BundleRouter.ts b/aggregator/src/app/BundleRouter.ts index 9f01f4fa..90529f38 100644 --- a/aggregator/src/app/BundleRouter.ts +++ b/aggregator/src/app/BundleRouter.ts @@ -2,7 +2,6 @@ import { Router } from "../../deps.ts"; import failRequest from "./helpers/failRequest.ts"; import BundleHandler from "./helpers/BundleHandler.ts"; import nil from "../helpers/nil.ts"; - import BundleService from "./BundleService.ts"; export default function BundleRouter(bundleService: BundleService) { diff --git a/aggregator/src/app/HealthRouter.ts b/aggregator/src/app/HealthRouter.ts new file mode 100644 index 00000000..d016f40a --- /dev/null +++ b/aggregator/src/app/HealthRouter.ts @@ -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; +} \ No newline at end of file diff --git a/aggregator/src/app/HealthService.ts b/aggregator/src/app/HealthService.ts new file mode 100644 index 00000000..21b89587 --- /dev/null +++ b/aggregator/src/app/HealthService.ts @@ -0,0 +1,11 @@ +export type ResourceHealth = 'healthy' | 'unhealthy'; + +type HealthCheckResult = { + status: ResourceHealth, +}; + +export default class HealthService { + getHealth(): Promise { + return Promise.resolve({ status: 'healthy' }); + } +} \ No newline at end of file diff --git a/aggregator/src/app/app.ts b/aggregator/src/app/app.ts index d073f942..d5f2dd54 100644 --- a/aggregator/src/app/app.ts +++ b/aggregator/src/app/app.ts @@ -15,6 +15,8 @@ import AppEvent from "./AppEvent.ts"; import BundleTable from "./BundleTable.ts"; import AggregationStrategy from "./AggregationStrategy.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) { const { addresses } = await getNetworkConfig(); @@ -64,10 +66,13 @@ export default async function app(emit: (evt: AppEvent) => void) { bundleTable, ); + const healthService = new HealthService(); + const routers = [ BundleRouter(bundleService), AdminRouter(adminService), AggregationStrategyRouter(aggregationStrategy), + HealthRouter(healthService), ]; const app = new Application(); diff --git a/aggregator/test/HealthService.test.ts b/aggregator/test/HealthService.test.ts new file mode 100644 index 00000000..03548ab9 --- /dev/null +++ b/aggregator/test/HealthService.test.ts @@ -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)); +}); \ No newline at end of file diff --git a/aggregator/test/helpers/Fixture.ts b/aggregator/test/helpers/Fixture.ts index 6f08fa06..9f8a8198 100644 --- a/aggregator/test/helpers/Fixture.ts +++ b/aggregator/test/helpers/Fixture.ts @@ -25,6 +25,7 @@ import BundleTable, { BundleRow } from "../../src/app/BundleTable.ts"; import AggregationStrategy, { AggregationStrategyConfig, } from "../../src/app/AggregationStrategy.ts"; +import HealthService from "../../src/app/HealthService.ts"; // deno-lint-ignore no-explicit-any type ExplicitAny = any; @@ -292,6 +293,12 @@ export default class Fixture { return wallets; } + + createHealthCheckService() { + const healthCheckService = new HealthService(); + + return healthCheckService; + } async cleanup() { for (const job of this.cleanupJobs) {