Skip to content

Commit b21932f

Browse files
authored
feat: add new zks_rpc (#42)
1 parent f7d1399 commit b21932f

File tree

6 files changed

+277
-8
lines changed

6 files changed

+277
-8
lines changed

docs/src/overview/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ Supports ETH, Custom Base Token, and ERC-20.
3939
### ZKsync RPC Extensions
4040

4141
* **`getBridgehubAddress`** (`zks_getBridgehubContract`) — resolve the canonical Bridgehub contract address.
42+
* **`getBytecodeSupplierAddress`** (`zks_getBytecodeSupplierContract`) — resolve the Bytecode Supplier contract address.
4243
* **`getL2ToL1LogProof`** (`zks_getL2ToL1LogProof`) — retrieve the log proof for an L2 → L1 transaction.
4344
* **`getReceiptWithL2ToL1`** — returns a standard Ethereum `TransactionReceipt` **augmented** with `l2ToL1Logs`.
45+
* **`getBlockMetadataByNumber`** (`zks_getBlockMetadataByNumber`) — fetch block metadata (pubdata price, native price, execution version).
4446
* **`getGenesis`** (`zks_getGenesis`) - returns Genesis json.
4547

4648
## What You’ll Find Here

docs/src/sdk-reference/core/rpc.md

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# zks_ RPC
22

3-
Public ZKsync `zks_*` RPC methods exposed on the adapters via `client.zks` (Bridgehub address, L2→L1 log proofs, receipts with `l2ToL1Logs`).
3+
Public ZKsync `zks_*` RPC methods exposed on the adapters via `client.zks` (Bridgehub address, Bytecode Supplier address, block metadata, L2→L1 log proofs, receipts with `l2ToL1Logs`).
44

55
## Standard Ethereum RPC (`eth_*`)
66

@@ -13,9 +13,11 @@ For standard Ethereum JSON-RPC (e.g., `eth_call`, `eth_getLogs`, `eth_getBalance
1313
```ts
1414
interface ZksRpc {
1515
getBridgehubAddress(): Promise<Address>;
16+
getBytecodeSupplierAddress(): Promise<Address>;
1617
getL2ToL1LogProof(txHash: Hex, index: number): Promise<ProofNormalized>;
1718
getReceiptWithL2ToL1(txHash: Hex): Promise<ReceiptWithL2ToL1 | null>;
18-
getGenesis(): Promise<GenesisInfo>;
19+
getBlockMetadataByNumber(blockNumber: number): Promise<BlockMetadata | null>;
20+
getGenesis(): Promise<GenesisInput>;
1921
}
2022
```
2123

@@ -33,6 +35,16 @@ const addr = await client.zks.getBridgehubAddress();
3335

3436
---
3537

38+
### `getBytecodeSupplierAddress() → Promise<Address>`
39+
40+
Fetch the on-chain **Bytecode Supplier** contract address.
41+
42+
```ts
43+
const addr = await client.zks.getBytecodeSupplierAddress();
44+
```
45+
46+
---
47+
3648
### `getL2ToL1LogProof(txHash: Hex, index: number) → Promise<ProofNormalized>`
3749

3850
Return a normalized proof for the **L2→L1 log** at `index` in `txHash`.
@@ -72,6 +84,34 @@ console.log(rcpt?.l2ToL1Logs); // always an array
7284

7385
---
7486

87+
### `getBlockMetadataByNumber(blockNumber: number)`
88+
89+
**What it does**
90+
Fetches per-block metadata used by the node (pubdata price, native price, execution version).
91+
Returns `null` if the block metadata is unavailable.
92+
Price fields are returned as `bigint`.
93+
94+
**Example**
95+
96+
```ts
97+
const meta = await client.zks.getBlockMetadataByNumber(123_456);
98+
if (meta) {
99+
console.log(meta.pubdataPricePerByte, meta.nativePrice, meta.executionVersion);
100+
}
101+
```
102+
103+
**Returns**
104+
105+
```ts
106+
type BlockMetadata = {
107+
pubdataPricePerByte: bigint;
108+
nativePrice: bigint;
109+
executionVersion: number;
110+
};
111+
```
112+
113+
---
114+
75115
## Types (overview)
76116

77117
```ts
@@ -85,6 +125,12 @@ type ReceiptWithL2ToL1 = {
85125
// …standard receipt fields…
86126
l2ToL1Logs: unknown[];
87127
};
128+
129+
type BlockMetadata = {
130+
pubdataPricePerByte: bigint;
131+
nativePrice: bigint;
132+
executionVersion: number;
133+
};
88134
```
89135

90136
---

docs/src/sdk-reference/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ const bridgehub = await client.zks.getBridgehubAddress();
9292
| [**Viem · Contracts**](./viem/contracts.md) | Resolved addresses and connected core contracts. |
9393
| [**Viem · Deposits**](./viem/deposits.md) | L1 → L2 flow with quote, prepare, create, status, and wait. |
9494
| [**Viem · Withdrawals**](./viem/withdrawals.md) | L2 → L1 flow with quote, prepare, create, status, wait, and finalize. |
95-
| [**Core · ZK RPC**](./core/rpc.md) | ZKsync-specific RPC: `getBridgehubAddress`, `getL2ToL1LogProof`, enhanced receipts. |
95+
| [**Core · ZK RPC**](./core/rpc.md) | ZKsync-specific RPC: `getBridgehubAddress`, `getBytecodeSupplierAddress`, `getBlockMetadataByNumber`, `getL2ToL1LogProof`. |
9696
| [**Core · Error model**](./core/errors.md) | Typed `ZKsyncError` envelope and `try*` result helpers. |
9797

9898
---
@@ -101,7 +101,7 @@ const bridgehub = await client.zks.getBridgehubAddress();
101101

102102
> [!NOTE]
103103
> **Standard `eth_*` RPC** should always be performed through your chosen base library (**ethers** or **viem**).
104-
> The SDK only adds **ZKsync-specific** RPC methods via `client.zks.*` (e.g. `getBridgehubAddress`, `getL2ToL1LogProof`, `getGenesis`).
104+
> The SDK only adds **ZKsync-specific** RPC methods via `client.zks.*` (e.g. `getBridgehubAddress`, `getBytecodeSupplierAddress`, `getBlockMetadataByNumber`, `getGenesis`).
105105
106106
* Every resource method has a **`try*` variant** (e.g. `tryCreate`) that returns a result object instead of throwing.
107107
When errors occur, the SDK throws **`ZKsyncError`** with a stable, structured envelope (see [Error model](./core/errors.md)).

src/core/rpc/__tests__/zks.test.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
44
/* eslint-disable @typescript-eslint/no-explicit-any */
55
import { describe, it, expect } from 'bun:test';
6-
import { createZksRpc, normalizeProof, normalizeGenesis } from '../zks';
6+
import { createZksRpc, normalizeProof, normalizeGenesis, normalizeBlockMetadata } from '../zks';
77
import type { RpcTransport } from '../types';
88
import { isZKsyncError } from '../../types/errors';
99

@@ -101,6 +101,33 @@ describe('rpc/zks.normalizeGenesis', () => {
101101
});
102102
});
103103

104+
describe('rpc/zks.normalizeBlockMetadata', () => {
105+
const sample = {
106+
pubdata_price_per_byte: '0x7ea8ed4bb',
107+
native_price: '0xf4240',
108+
execution_version: 1,
109+
};
110+
111+
it('normalizes snake-cased fields', () => {
112+
const normalized = normalizeBlockMetadata(sample);
113+
expect(normalized).toEqual({
114+
pubdataPricePerByte: 0x7ea8ed4bbn,
115+
nativePrice: 0xf4240n,
116+
executionVersion: 1,
117+
});
118+
});
119+
120+
it('throws ZKsyncError on malformed response', () => {
121+
try {
122+
normalizeBlockMetadata(null);
123+
throw new Error('expected to throw');
124+
} catch (e) {
125+
expect(isZKsyncError(e)).toBe(true);
126+
expect(String(e)).toContain('Malformed block metadata response');
127+
}
128+
});
129+
});
130+
104131
describe('rpc/zks.getBridgehubAddress', () => {
105132
it('returns a hex address when RPC responds with a 0x-prefixed string', async () => {
106133
const rpc = createZksRpc(
@@ -118,6 +145,25 @@ describe('rpc/zks.getBridgehubAddress', () => {
118145
});
119146
});
120147

148+
describe('rpc/zks.getBytecodeSupplierAddress', () => {
149+
it('returns a hex address when RPC responds with a 0x-prefixed string', async () => {
150+
const rpc = createZksRpc(
151+
fakeTransport({
152+
zks_getBytecodeSupplierContract: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd',
153+
}),
154+
);
155+
const addr = await rpc.getBytecodeSupplierAddress();
156+
expect(addr).toBe('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd');
157+
});
158+
159+
it('wraps unexpected response shape into ZKsyncError', () => {
160+
const rpc = createZksRpc(fakeTransport({ zks_getBytecodeSupplierContract: 123 }));
161+
return expect(rpc.getBytecodeSupplierAddress()).rejects.toThrow(
162+
/Unexpected Bytecode Supplier address response/,
163+
);
164+
});
165+
});
166+
121167
describe('rpc/zks.getL2ToL1LogProof', () => {
122168
it('returns normalized proof on success', async () => {
123169
const proof = {
@@ -197,3 +243,26 @@ describe('rpc/zks.getGenesis', () => {
197243
});
198244
});
199245
});
246+
247+
describe('rpc/zks.getBlockMetadataByNumber', () => {
248+
it('returns null when RPC returns null', async () => {
249+
const rpc = createZksRpc(fakeTransport({ zks_getBlockMetadataByNumber: null }));
250+
const out = await rpc.getBlockMetadataByNumber(123);
251+
expect(out).toBeNull();
252+
});
253+
254+
it('returns normalized block metadata', async () => {
255+
const raw = {
256+
pubdata_price_per_byte: '0x2a',
257+
native_price: '0x2b',
258+
execution_version: 3,
259+
};
260+
const rpc = createZksRpc(fakeTransport({ zks_getBlockMetadataByNumber: raw }));
261+
const out = await rpc.getBlockMetadataByNumber(456);
262+
expect(out).toEqual({
263+
pubdataPricePerByte: 0x2an,
264+
nativePrice: 0x2bn,
265+
executionVersion: 3,
266+
});
267+
});
268+
});

src/core/rpc/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,11 @@ export type GenesisInput = {
4545
genesisRoot: Hex;
4646
};
4747

48+
export type BlockMetadata = {
49+
pubdataPricePerByte: bigint;
50+
nativePrice: bigint;
51+
executionVersion: number;
52+
};
53+
4854
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4955
export type RpcTransport = (method: string, params?: unknown[]) => Promise<any>;

0 commit comments

Comments
 (0)