generated from defi-wonderland/ts-turborepo-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/dev' into feat/pool-created-handler
- Loading branch information
Showing
39 changed files
with
1,481 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# @grants-stack-indexer/chain-providers | ||
|
||
## Overview | ||
|
||
The `@grants-stack-indexer/chain-providers` package provides wrappers of the `Viem` library to interact with EVM-based blockchains. | ||
|
||
## 📋 Prerequisites | ||
|
||
- Ensure you have `node >= 20.0.0` and `pnpm >= 9.5.0` installed. | ||
|
||
## Installation | ||
|
||
```bash | ||
$ pnpm install | ||
``` | ||
|
||
## Building | ||
|
||
To build the monorepo packages, run: | ||
|
||
```bash | ||
$ pnpm build | ||
``` | ||
|
||
## Test | ||
|
||
```bash | ||
# unit tests | ||
$ pnpm run test | ||
|
||
# test coverage | ||
$ pnpm run test:cov | ||
``` | ||
|
||
## Usage | ||
|
||
### Importing the Package | ||
|
||
You can import the package in your TypeScript or JavaScript files as follows: | ||
|
||
```typescript | ||
import { EvmProvider } from "@grants-stack-indexer/chain-providers"; | ||
``` | ||
|
||
### Example | ||
|
||
```typescript | ||
// EVM-provider | ||
const rpcUrls = [...]; //non-empty | ||
const chain = mainnet; // from viem/chains | ||
|
||
const evmProvider = new EvmProvider(rpcUrls, chain, logger); | ||
|
||
const gasPrice = await evmProvider.getGasPrice(); | ||
|
||
const result = await evmProvider.readContract(address, abi, "myfunction", [arg1, arg2]); | ||
``` | ||
|
||
## API | ||
|
||
### [EvmProvider](./src/providers/evmProvider.ts) | ||
|
||
Available methods | ||
|
||
- `getMulticall3Address()` | ||
- `getBlockNumber()` | ||
- `getBlockByNumber(blockNumber: bigint)` | ||
- `readContract(contractAddress: Address, abi: TAbi functionName: TFunctionName, args?: TArgs)` | ||
- `batchRequest(abi: AbiWithConstructor,bytecode: Hex, args: ContractConstructorArgs<typeof abi>, constructorReturnParams: ReturnType)` | ||
- `multicall(args: MulticallParameters<contracts, allowFailure>)` | ||
|
||
For more details on both providers, refer to their implementations. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"name": "@grants-stack-indexer/chain-providers", | ||
"version": "1.0.0", | ||
"type": "module", | ||
"main": "./dist/src/index.js", | ||
"scripts": { | ||
"build": "tsc -p tsconfig.build.json", | ||
"check-types": "tsc --noEmit -p ./tsconfig.json", | ||
"clean": "rm -rf dist/", | ||
"format": "prettier --check \"{src,test}/**/*.{js,ts,json}\"", | ||
"format:fix": "prettier --write \"{src,test}/**/*.{js,ts,json}\"", | ||
"lint": "eslint \"{src,test}/**/*.{js,ts,json}\"", | ||
"lint:fix": "pnpm lint --fix", | ||
"test": "vitest run --config vitest.config.ts --passWithNoTests", | ||
"test:cov": "vitest run --config vitest.config.ts --coverage" | ||
}, | ||
"dependencies": { | ||
"@grants-stack-indexer/shared": "workspace:*", | ||
"abitype": "1.0.6", | ||
"viem": "2.19.6" | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/chain-providers/src/exceptions/dataDecode.exception.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export class DataDecodeException extends Error { | ||
constructor(message: string) { | ||
super(message); | ||
this.name = "DataDecodeException"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from "./invalidArgument.exception.js"; | ||
export * from "./dataDecode.exception.js"; | ||
export * from "./multicallNotFound.exception.js"; | ||
export * from "./rpcUrlsEmpty.exception.js"; |
6 changes: 6 additions & 0 deletions
6
packages/chain-providers/src/exceptions/invalidArgument.exception.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export class InvalidArgumentException extends Error { | ||
constructor(message: string) { | ||
super(message); | ||
this.name = "InvalidArgumentException"; | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
packages/chain-providers/src/exceptions/multicallNotFound.exception.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export class MulticallNotFound extends Error { | ||
constructor() { | ||
super("Multicall contract address not found"); | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/chain-providers/src/exceptions/rpcUrlsEmpty.exception.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export class RpcUrlsEmpty extends Error { | ||
constructor() { | ||
super("RPC URLs array cannot be empty"); | ||
this.name = "RpcUrlsEmpty"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export { | ||
DataDecodeException, | ||
InvalidArgumentException, | ||
MulticallNotFound, | ||
RpcUrlsEmpty, | ||
} from "./internal.js"; | ||
|
||
export { EvmProvider } from "./internal.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./external.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from "./types/index.js"; | ||
export * from "./exceptions/index.js"; | ||
export * from "./providers/index.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
import { AbiParameter } from "abitype"; | ||
import { | ||
Abi, | ||
Address, | ||
Chain, | ||
ContractConstructorArgs, | ||
ContractFunctionArgs, | ||
ContractFunctionName, | ||
ContractFunctionParameters, | ||
ContractFunctionReturnType, | ||
createPublicClient, | ||
decodeAbiParameters, | ||
DecodeAbiParametersReturnType, | ||
encodeDeployData, | ||
EstimateGasParameters, | ||
fallback, | ||
FallbackTransport, | ||
GetBlockReturnType, | ||
Hex, | ||
http, | ||
HttpTransport, | ||
MulticallParameters, | ||
MulticallReturnType, | ||
toHex, | ||
} from "viem"; | ||
|
||
import { ILogger } from "@grants-stack-indexer/shared"; | ||
|
||
import { | ||
AbiWithConstructor, | ||
DataDecodeException, | ||
InvalidArgumentException, | ||
MulticallNotFound, | ||
RpcUrlsEmpty, | ||
} from "../internal.js"; | ||
|
||
/** | ||
* Acts as a wrapper around Viem library to provide methods to interact with an EVM-based blockchain. | ||
*/ | ||
export class EvmProvider { | ||
private client: ReturnType< | ||
typeof createPublicClient<FallbackTransport<HttpTransport[]>, Chain | undefined> | ||
>; | ||
|
||
constructor( | ||
rpcUrls: string[], | ||
readonly chain: Chain | undefined, | ||
private readonly logger: ILogger, | ||
) { | ||
if (rpcUrls.length === 0) { | ||
throw new RpcUrlsEmpty(); | ||
} | ||
|
||
this.client = createPublicClient({ | ||
chain, | ||
transport: fallback(rpcUrls.map((rpcUrl) => http(rpcUrl))), | ||
}); | ||
} | ||
|
||
/** | ||
* Retrieves the address of the Multicall3 contract. | ||
* @returns {Address | undefined} The address of the Multicall3 contract, or undefined if not found. | ||
*/ | ||
getMulticall3Address(): Address | undefined { | ||
return this.chain?.contracts?.multicall3?.address; | ||
} | ||
|
||
/** | ||
* Retrieves the balance of the specified address. | ||
* @param {Address} address The address for which to retrieve the balance. | ||
* @returns {Promise<bigint>} A Promise that resolves to the balance of the address. | ||
*/ | ||
async getBalance(address: Address): Promise<bigint> { | ||
return this.client.getBalance({ address }); | ||
} | ||
|
||
/** | ||
* Retrieves the current block number. | ||
* @returns {Promise<bigint>} A Promise that resolves to the latest block number. | ||
*/ | ||
async getBlockNumber(): Promise<bigint> { | ||
return this.client.getBlockNumber(); | ||
} | ||
|
||
/** | ||
* Retrieves the current block number. | ||
* @returns {Promise<GetBlockReturnType>} Latest block number. | ||
*/ | ||
async getBlockByNumber(blockNumber: bigint): Promise<GetBlockReturnType> { | ||
return this.client.getBlock({ blockNumber }); | ||
} | ||
|
||
/** | ||
* Retrieves the current estimated gas price on the chain. | ||
* @returns {Promise<bigint>} A Promise that resolves to the current gas price. | ||
*/ | ||
async getGasPrice(): Promise<bigint> { | ||
return this.client.getGasPrice(); | ||
} | ||
|
||
async estimateGas(args: EstimateGasParameters<typeof this.chain>): Promise<bigint> { | ||
return this.client.estimateGas(args); | ||
} | ||
|
||
/** | ||
* Retrieves the value from a storage slot at a given address. | ||
* @param {Address} address The address of the contract. | ||
* @param {number} slot The slot number to read. | ||
* @returns {Promise<Hex>} A Promise that resolves to the value of the storage slot. | ||
* @throws {InvalidArgumentException} If the slot is not a positive integer. | ||
*/ | ||
async getStorageAt(address: Address, slot: number | Hex): Promise<Hex | undefined> { | ||
if (typeof slot === "number" && (slot <= 0 || !Number.isInteger(slot))) { | ||
throw new InvalidArgumentException( | ||
`Slot must be a positive integer number. Received: ${slot}`, | ||
); | ||
} | ||
|
||
return this.client.getStorageAt({ | ||
address, | ||
slot: typeof slot === "string" ? slot : toHex(slot), | ||
}); | ||
} | ||
|
||
/** | ||
* Reads a contract "pure" or "view" function with the specified arguments using readContract from Viem. | ||
* @param {Address} contractAddress - The address of the contract. | ||
* @param {TAbi} abi - The ABI (Application Binary Interface) of the contract. | ||
* @param {TFunctionName} functionName - The name of the function to call. | ||
* @param {TArgs} [args] - The arguments to pass to the function (optional). | ||
* @returns A promise that resolves to the return value of the contract function. | ||
*/ | ||
async readContract< | ||
TAbi extends Abi, | ||
TFunctionName extends ContractFunctionName<TAbi, "pure" | "view"> = ContractFunctionName< | ||
TAbi, | ||
"pure" | "view" | ||
>, | ||
TArgs extends ContractFunctionArgs< | ||
TAbi, | ||
"pure" | "view", | ||
TFunctionName | ||
> = ContractFunctionArgs<TAbi, "pure" | "view", TFunctionName>, | ||
>( | ||
contractAddress: Address, | ||
abi: TAbi, | ||
functionName: TFunctionName, | ||
args?: TArgs, | ||
): Promise<ContractFunctionReturnType<TAbi, "pure" | "view", TFunctionName, TArgs>> { | ||
return this.client.readContract({ | ||
address: contractAddress, | ||
abi, | ||
functionName, | ||
args, | ||
}); | ||
} | ||
|
||
/** | ||
* Executes a batch request to deploy a contract and returns the decoded constructor return parameters. | ||
* @param {AbiWithConstructor} abi - The ABI (Application Binary Interface) of the contract. Must contain a constructor. | ||
* @param {Hex} bytecode - The bytecode of the contract. | ||
* @param {ContractConstructorArgs<typeof abi>} args - The constructor arguments for the contract. | ||
* @param constructorReturnParams - The return parameters of the contract's constructor. | ||
* @returns The decoded constructor return parameters. | ||
* @throws {DataDecodeException} if there is no return data or if the return data does not match the expected type. | ||
*/ | ||
async batchRequest<ReturnType extends readonly AbiParameter[]>( | ||
abi: AbiWithConstructor, | ||
bytecode: Hex, | ||
args: ContractConstructorArgs<typeof abi>, | ||
constructorReturnParams: ReturnType, | ||
): Promise<DecodeAbiParametersReturnType<ReturnType>> { | ||
const deploymentData = args ? encodeDeployData({ abi, bytecode, args }) : bytecode; | ||
|
||
const { data: returnData } = await this.client.call({ | ||
data: deploymentData, | ||
}); | ||
|
||
if (!returnData) { | ||
throw new DataDecodeException("No return data"); | ||
} | ||
|
||
try { | ||
const decoded = decodeAbiParameters(constructorReturnParams, returnData); | ||
return decoded; | ||
} catch (e) { | ||
throw new DataDecodeException("Error decoding return data with given AbiParameters"); | ||
} | ||
} | ||
|
||
/** | ||
* Similar to readContract, but batches up multiple functions | ||
* on a contract in a single RPC call via the multicall3 contract. | ||
* @param {MulticallParameters} args - The parameters for the multicall. | ||
* @returns — An array of results. If allowFailure is true, with accompanying status | ||
* @throws {MulticallNotFound} if the Multicall contract is not found. | ||
*/ | ||
async multicall< | ||
contracts extends readonly unknown[] = readonly ContractFunctionParameters[], | ||
allowFailure extends boolean = true, | ||
>( | ||
args: MulticallParameters<contracts, allowFailure>, | ||
): Promise<MulticallReturnType<contracts, allowFailure>> { | ||
if (!this.chain?.contracts?.multicall3?.address) throw new MulticallNotFound(); | ||
|
||
return this.client.multicall<contracts, allowFailure>(args); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./evmProvider.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./viem.types.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { Abi, AbiConstructor } from "abitype"; | ||
|
||
export type AbiWithConstructor = readonly [AbiConstructor, ...Abi]; |
33 changes: 33 additions & 0 deletions
33
packages/chain-providers/test/fixtures/batchRequest.fixture.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { Hex } from "viem"; | ||
|
||
export const structAbiFixture = { | ||
abi: [ | ||
{ | ||
inputs: [ | ||
{ | ||
internalType: "address[]", | ||
name: "_tokenAddresses", | ||
type: "address[]", | ||
}, | ||
], | ||
stateMutability: "nonpayable", | ||
type: "constructor", | ||
}, | ||
] as const, | ||
bytecode: | ||
`0x608060405234801561001057600080fd5b506040516108aa3803806108aa833981810160405281019061003291906104f2565b60008151905060008167ffffffffffffffff81111561007a577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040519080825280602002602001820160405280156100b357816020015b6100a06103a6565b8152602001906001900390816100985790505b50905060005b828110156103775760008482815181106100fc577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002602001015190508073ffffffffffffffffffffffffffffffffffffffff1663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561014c57600080fd5b505afa158015610160573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101849190610574565b8383815181106101bd577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101516000019060ff16908160ff16815250508073ffffffffffffffffffffffffffffffffffffffff166395d89b416040518163ffffffff1660e01b815260040160006040518083038186803b15801561021b57600080fd5b505afa15801561022f573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906102589190610533565b838381518110610291577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020026020010151602001819052508073ffffffffffffffffffffffffffffffffffffffff166306fdde036040518163ffffffff1660e01b815260040160006040518083038186803b1580156102e657600080fd5b505afa1580156102fa573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906103239190610533565b83838151811061035c577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002602001015160400181905250816001019150506100b9565b5060008160405160200161038b91906106c5565b60405160208183030381529060405290506020810180590381f35b6040518060600160405280600060ff16815260200160608152602001606081525090565b60006103dd6103d884610718565b6106e7565b905080838252602082019050828560208602820111156103fc57600080fd5b60005b8581101561042c57816104128882610474565b8452602084019350602083019250506001810190506103ff565b5050509392505050565b600061044961044484610744565b6106e7565b90508281526020810184848401111561046157600080fd5b61046c848285610808565b509392505050565b6000815190506104838161087b565b92915050565b600082601f83011261049a57600080fd5b81516104aa8482602086016103ca565b91505092915050565b600082601f8301126104c457600080fd5b81516104d4848260208601610436565b91505092915050565b6000815190506104ec81610892565b92915050565b60006020828403121561050457600080fd5b600082015167ffffffffffffffff81111561051e57600080fd5b61052a84828501610489565b91505092915050565b60006020828403121561054557600080fd5b600082015167ffffffffffffffff81111561055f57600080fd5b61056b848285016104b3565b91505092915050565b60006020828403121561058657600080fd5b6000610594848285016104dd565b91505092915050565b60006105a9838361065f565b905092915050565b60006105bc82610784565b6105c681856107a7565b9350836020820285016105d885610774565b8060005b8581101561061457848403895281516105f5858261059d565b94506106008361079a565b925060208a019950506001810190506105dc565b50829750879550505050505092915050565b60006106318261078f565b61063b81856107b8565b935061064b818560208601610808565b6106548161086a565b840191505092915050565b600060608301600083015161067760008601826106b6565b506020830151848203602086015261068f8282610626565b915050604083015184820360408601526106a98282610626565b9150508091505092915050565b6106bf816107fb565b82525050565b600060208201905081810360008301526106df81846105b1565b905092915050565b6000604051905081810181811067ffffffffffffffff8211171561070e5761070d61083b565b5b8060405250919050565b600067ffffffffffffffff8211156107335761073261083b565b5b602082029050602081019050919050565b600067ffffffffffffffff82111561075f5761075e61083b565b5b601f19601f8301169050602081019050919050565b6000819050602082019050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b600082825260208201905092915050565b60006107d4826107db565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600060ff82169050919050565b60005b8381101561082657808201518184015260208101905061080b565b83811115610835576000848401525b50505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b610884816107c9565b811461088f57600080fd5b50565b61089b816107fb565b81146108a657600080fd5b5056fe` as Hex, | ||
args: [ | ||
[ | ||
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", | ||
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", | ||
], | ||
] as const, | ||
returnData: | ||
`0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000045745544800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d57726170706564204574686572000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000045553444300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000855534420436f696e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000` as Hex, | ||
}; | ||
|
||
export const arrayAbiFixture = { | ||
...structAbiFixture, | ||
returnData: | ||
`0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` as Hex, | ||
}; |
Oops, something went wrong.