Skip to content

Commit

Permalink
Merge pull request #7 from argentlabs/develop
Browse files Browse the repository at this point in the history
Release 7.1.0
  • Loading branch information
janek26 authored Mar 6, 2024
2 parents 73a2544 + 683b587 commit a1f4068
Show file tree
Hide file tree
Showing 18 changed files with 240 additions and 411 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Unit tests
on:
push:
branches:
- develop
pull_request:

jobs:
test-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
persist-credentials: false

- uses: oven-sh/setup-bun@v1

- uses: actions/setup-node@v3
with:
node-version: 18

- run: bun install
- run: bun run build
- run: bun run test
env:
TEST_RPC_PROVIDER: ${{ vars.TEST_RPC_PROVIDER }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ dist
# Stores VSCode versions used for testing VSCode extensions

.vscode-test
.vscode

# yarn v2

Expand Down
46 changes: 36 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# @argent/x-multicall

This package provides batch request functionality for Starknet RPC and Sequencer providers. It exports two classes RpcBatchProvider and SequencerBatchProvider for RPC and Sequencer providers respectively. It also exports a default function getBatchProvider which returns an instance of either RpcBatchProvider or SequencerBatchProvider based on the provider type passed to it.
This package provides batch request functionality for Starknet RPC providers.
It exports two classes: RpcBatchProvider and ContractBatchProvider.

## Installation

Expand All @@ -15,11 +16,14 @@ pnpm add @argent/x-multicall
Here is a basic usage example:

```typescript
import { getBatchProvider } from "@argent/x-multicall";
import { RpcBatchProvider } from "@argent/x-multicall";
import { RpcProvider } from "starknet";

const provider = new RpcProvider({ nodeUrl: "your_node_url" });
const batchProvider = getBatchProvider(provider);
const batchProvider = new RpcBatchProvider({
nodeUrl: rpcProvider.nodeUrl,
headers: rpcProvider.headers,
});

// Now you can use batchProvider to make batch requests
const result = batchProvider.callContract({
Expand Down Expand Up @@ -59,13 +63,13 @@ To run tests in watch mode, use:
bun run test:watch
```

## RpcBatchProvider and SequencerBatchProvider
## RpcBatchProvider

RpcBatchProvider and SequencerBatchProvider are classes that extend the RpcProvider and SequencerProvider respectively, adding batch request functionality. They can be used as follows:
RpcBatchProvider is a class that extend the RpcProvider, adding batch request functionality.

```typescript
import { RpcBatchProvider, SequencerBatchProvider } from "@argent/x-multicall";
import { RpcProvider, SequencerProvider } from "starknet";
import { RpcBatchProvider } from "@argent/x-multicall";
import { RpcProvider } from "starknet";

const rpcProvider = new RpcProvider({ nodeUrl: "your_rpc_node_url" });
const rpcBatchProvider = new RpcBatchProvider({
Expand All @@ -75,14 +79,36 @@ const rpcBatchProvider = new RpcBatchProvider({
maxBatchSize: 20,
});

const sequencerProvider = new SequencerProvider({
network: "your_network_name",
const result = rpcBatchProvider.callContract({
contractAddress: "0x0",
entrypoint: "0x0",
calldata: ["0x0"],
});
const sequencerBatchProvider = new SequencerBatchProvider(sequencerProvider);
```

In the above example, batchInterval is the time interval (in ms) at which batch requests are sent and maxBatchSize is the maximum number of requests that can be included in a batch. These options can be adjusted according to your needs.

## ContractBatchProvider

ContractBatchProvider is a class that extend MinimalProviderInterface (requires a provider with `callContract`) and allows contract batching instead of json rpc batching.

```typescript
import { ContractBatchProvider } from "@argent/x-multicall";
import { RpcProvider } from "starknet";

const rpcProvider = new RpcProvider({ nodeUrl: "your_rpc_node_url" });
const contractBatchProvider = new ContractBatchProvider(rpcProvider, {
batchInterval: 200,
maxBatchSize: 20,
});

const result = contractBatchProvider.callContract({
contractAddress: "0x0",
entrypoint: "0x0",
calldata: ["0x0"],
});
```

## License

This project is licensed under the [MIT license](LICENSE).
Binary file modified bun.lockb
Binary file not shown.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@argent/x-multicall",
"version": "7.0.9",
"version": "7.1.0",
"repository": "github:argentlabs/x-multicall.git",
"private": false,
"license": "MIT",
Expand All @@ -26,7 +26,7 @@
"@semantic-release/git": "^10.0.1",
"bun-types": "latest",
"semantic-release": "^22.0.5",
"starknet": "^5.19.5"
"starknet": "6.1.3"
},
"peerDependencies": {
"typescript": "^5.0.0"
Expand Down
71 changes: 0 additions & 71 deletions src/__snapshots__/integration.test.ts.snap

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type { Call, CallContractResponse } from "starknet";
import type { DataLoaderOptions, MinimalProviderInterface } from "../types";
import DataLoader from "dataloader";
import { Call, CallContractResponse } from "starknet";
import type { DataLoaderOptions, MinimalProviderInterface } from "../types";
import { getDataLoader } from "./dataloader";

const DEFAULT_MULTICALL_ADDRESS =
"0x05754af3760f3356da99aea5c3ec39ccac7783d925a19666ebbeca58ff0087f4";
const DEFAULT_MULTICALL_ADDRESS = "0x05754af3760f3356da99aea5c3ec39ccac7783d925a19666ebbeca58ff0087f4";

export class SequencerBatchProvider implements MinimalProviderInterface {
export class ContractBatchProvider implements MinimalProviderInterface {
public readonly dataloader: DataLoader<Call, string[], Call>;

constructor(
Expand All @@ -19,6 +18,6 @@ export class SequencerBatchProvider implements MinimalProviderInterface {

public async callContract(call: Call): Promise<CallContractResponse> {
const result = await this.dataloader.load(call);
return { result };
return result;
}
}
51 changes: 51 additions & 0 deletions src/contractBatch/__snapshots__/integration.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Bun Snapshot v1, https://goo.gl/fbAQLP

exports[`ContractBatchProvider one call fails in a batch 1`] = `
[
{
"status": "fulfilled",
"value": [
"0x0",
"0x0",
],
},
{
"status": "fulfilled",
"value": [
"0x0",
"0x0",
],
},
{
"status": "fulfilled",
"value": [
"0x0",
"0x0",
],
},
{
"status": "fulfilled",
"value": [
"0x0",
"0x0",
],
},
]
`;

exports[`ContractBatchProvider two call fails in a batch 1`] = `
[
{
"status": "rejected",
},
{
"status": "rejected",
},
{
"status": "rejected",
},
{
"status": "rejected",
},
]
`;
37 changes: 8 additions & 29 deletions src/sequencer/aggregate.ts → src/contractBatch/aggregate.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
type Call,
GatewayError,
LibraryError,
num,
transaction,
CallData,
} from "starknet";
import { type Call, GatewayError, LibraryError, num, transaction, CallData } from "starknet";
import type { MinimalProviderInterface } from "../types";

const partitionResponses = (responses: string[]): string[][] => {
Expand All @@ -25,9 +18,7 @@ const extractErrorCallIndex = (e: Error) => {
try {
const errorCallText = e.toString();

const sequencerErrorIndex = errorCallText.match(
/Error message: multicall (\d+) failed/
)?.[1];
const sequencerErrorIndex = errorCallText.match(/Error message: multicall (\d+) failed/)?.[1];
if (sequencerErrorIndex) {
return Number(sequencerErrorIndex);
}
Expand All @@ -37,10 +28,7 @@ const extractErrorCallIndex = (e: Error) => {
}
};

const fallbackAggregate = async (
provider: MinimalProviderInterface,
calls: Call[]
): Promise<(string[] | Error)[]> => {
const fallbackAggregate = async (provider: MinimalProviderInterface, calls: Call[]): Promise<(string[] | Error)[]> => {
const results = await Promise.allSettled(
calls.map((call) =>
provider
Expand All @@ -49,7 +37,7 @@ const fallbackAggregate = async (
entrypoint: call.entrypoint,
calldata: CallData.toCalldata(call.calldata),
})
.then((res) => res.result)
.then((res) => res)
)
);

Expand All @@ -76,7 +64,7 @@ export const aggregate = async (
calldata: transaction.fromCallsToExecuteCalldata([...calls]),
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_blockNumber, _totalLength, ...results] = res.result;
const [_blockNumber, _totalLength, ...results] = res;

return partitionResponses(results);
} catch (e) {
Expand All @@ -86,24 +74,15 @@ export const aggregate = async (

if (
// in case multicall contract is not deployed, fallback to calling each call separately
(e instanceof GatewayError &&
e.errorCode === "StarknetErrorCode.UNINITIALIZED_CONTRACT") || // Sequencer
(e instanceof GatewayError && e.errorCode === "StarknetErrorCode.UNINITIALIZED_CONTRACT") || // Sequencer
(e instanceof LibraryError && e.message === "20: Contract not found") // RPC
) {
return fallbackAggregate(provider, calls);
}

const errorCallIndex = extractErrorCallIndex(e);
const remainingCalls = calls.filter((_, i) => i !== errorCallIndex);
const remainingResults = await aggregate(
provider,
multicallAddress,
remainingCalls
);
return [
...remainingResults.slice(0, errorCallIndex),
e,
...remainingResults.slice(errorCallIndex),
];
const remainingResults = await aggregate(provider, multicallAddress, remainingCalls);
return [...remainingResults.slice(0, errorCallIndex), e, ...remainingResults.slice(errorCallIndex)];
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ export const getDataLoader = (
const dl = new DataLoader(
async (calls: readonly Call[]): Promise<(string[] | Error)[]> => {
dl.clearAll();
const result = await aggregate(
provider,
multicallAddress,
calls as Call[]
);
const result = await aggregate(provider, multicallAddress, calls as Call[]);
return result;
},
{
Expand Down
Loading

0 comments on commit a1f4068

Please sign in to comment.