Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,35 @@
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

docs-examples:
name: docs-examples 📑
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

- name: Install Foundry (anvil)
uses: foundry-rs/foundry-toolchain@8b0419c685ef46cb79ec93fbdc131174afceb730 # v1.6.0
with:
version: v1.3.4

- name: Run ZKsync OS (L1-L2)
uses: dutterbutter/zksync-server-action@0ccaf62628692bf7dbb6a0832b1f0a9c2a7c08ee # v0.1.0

- name: Setup Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
with:
bun-version: '1.3.5'

- name: Install deps
run: bun install

- name: Build
run: bun run build

- name: Run docs examples tests
env:
L1_RPC: http://localhost:8545
L2_RPC: http://localhost:3050
PRIVATE_KEY: 0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110
run: |
bun test:docs
2 changes: 2 additions & 0 deletions docs/book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ build-dir = "book" # output folder (docs/book)
command = "mdbook-admonish"
assets_version = "3.0.3" # do not edit: managed by `mdbook-admonish install`

[preprocessor.yapp]

[output.html]
additional-css = ["./mdbook-admonish.css"]
default-theme = "light"
Expand Down
144 changes: 144 additions & 0 deletions docs/snippets/core/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// ANCHOR: error-import
import { isZKsyncError } from '../../../src/core/types/errors';
// ANCHOR_END: error-import

import { type ErrorEnvelope as Envelope, Resource, ErrorType } from '../../../src/core/types/errors';
import { Account, createPublicClient, createWalletClient, http, parseEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { createViemClient, createViemSdk, type ViemSdk } from '../../../src/adapters/viem';
import { beforeAll, describe, it } from 'bun:test';
import { l1Chain, l2Chain } from '../viem/chains';
import { ETH_ADDRESS } from '../../../src/core';
import type { Exact } from "./types";

// ANCHOR: envelope-type
export interface ErrorEnvelope {
/** Resource surface that raised the error. */
resource: Resource;
/** SDK operation, e.g. 'withdrawals.finalize' */
operation: string;
/** Broad category */
type: ErrorType;
/** Human-readable, stable message for developers. */
message: string;

/** Optional detail that adapters may enrich (reverts, extra context) */
context?: Record<string, unknown>;

/** If the error is a contract revert, adapters add decoded info here. */
revert?: {
/** 4-byte selector as 0x…8 hex */
selector: `0x${string}`;
/** Decoded error name when available (e.g. 'InvalidProof') */
name?: string;
/** Decoded args (ethers/viem output), when available */
args?: unknown[];
/** Optional adapter-known labels */
contract?: string;
/** Optional adapter-known function name */
fn?: string;
};

/** Original thrown error */
cause?: unknown;
}
// ANCHOR_END: envelope-type

describe('checks rpc docs examples', () => {

let sdk: ViemSdk;
let account: Account;
let params: any;

beforeAll(() => {
account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);

const l1 = createPublicClient({ transport: http(process.env.L1_RPC!) });
const l2 = createPublicClient({ transport: http(process.env.L2_RPC!) });
const l1Wallet = createWalletClient({ chain: l1Chain, account, transport: http(process.env.L1_RPC!) });
const l2Wallet = createWalletClient({ chain: l2Chain, account, transport: http(process.env.L2_RPC!) });

const client = createViemClient({ l1, l2, l1Wallet, l2Wallet });
sdk = createViemSdk(client);

params = {
amount: parseEther('0.01'),
to: account.address,
token: ETH_ADDRESS,
} as const;
})

// this test will always succeed
// but any errors will be highlighted
it('checks to see if the error types are updated', async () => {
const _envelopeType: Exact<ErrorEnvelope, Envelope> = true;
});

it('shows how to handle errors', async () => {
// ANCHOR: zksync-error
try {
const handle = await sdk.deposits.create(params);
} catch (e) {
if (isZKsyncError(e)) {
const err = e; // type-narrowed
const { type, resource, operation, message, context, revert } = err.envelope;

switch (type) {
case 'VALIDATION':
case 'STATE':
// user/action fixable (bad input, not-ready, etc.)
break;
case 'EXECUTION':
case 'RPC':
// network/tx/provider issues
break;
}

console.error(JSON.stringify(err.toJSON())); // structured log
} else {
throw e; // non-SDK error
}
}
// ANCHOR_END: zksync-error
})

it('handles a withdrawal error', async () => {
// ANCHOR: try-create
const res = await sdk.withdrawals.tryCreate(params);
if (!res.ok) {
if (isZKsyncError(res.error)) {
// res.error is a ZKsyncError
console.warn(res.error.envelope.message, res.error.envelope.operation);
} else {
throw new Error("Unkown error");
}
} else {
console.log('l2TxHash', res.value.l2TxHash);
}
// ANCHOR_END: try-create

if(!res.ok) throw new Error("response not ok");
const l2TxHash = res.value.l2TxHash;

// ANCHOR: revert-details
try {
await sdk.withdrawals.finalize(l2TxHash);
} catch (e) {
if (isZKsyncError(e) && e.envelope.revert) {
const { selector, name, args } = e.envelope.revert;
// e.g., name === 'InvalidProof' or 'TransferAmountExceedsBalance'
}
}
// ANCHOR_END: revert-details
})

it('handles a deposit error', async () => {
// ANCHOR: envelope-error
const res = await sdk.deposits.tryCreate(params);
if (!res.ok) {
if (isZKsyncError(res.error)) console.error(res.error.envelope); // structured envelope
}
// ANCHOR_END: envelope-error
})

});
96 changes: 96 additions & 0 deletions docs/snippets/core/rpc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { beforeAll, describe, expect, it } from 'bun:test';

import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { createViemClient, type ViemClient } from '../../../src/adapters/viem';
import { Address, Hex, type ZksRpc as ZksType } from '../../../src/core';
import { GenesisContractDeployment, GenesisInput as GenesisType, GenesisStorageEntry, L2ToL1Log, ProofNormalized as ProofN, ReceiptWithL2ToL1 as RWithLog } from '../../../src/core/rpc/types';

import { l1Chain, l2Chain } from '../viem/chains';
import type { Exact } from "./types";

// ANCHOR: zks-rpc
interface ZksRpc {
getBridgehubAddress(): Promise<Address>;
getL2ToL1LogProof(txHash: Hex, index: number): Promise<ProofNormalized>;
getReceiptWithL2ToL1(txHash: Hex): Promise<ReceiptWithL2ToL1 | null>;
getGenesis(): Promise<GenesisInput>;
}
// ANCHOR_END: zks-rpc

// ANCHOR: proof-receipt-type
type ProofNormalized = {
id: bigint;
batchNumber: bigint;
proof: Hex[];
};

type ReceiptWithL2ToL1 = {
transactionHash?: Hex;
status?: string | number;
blockNumber?: string | number;
logs?: Array<{
address: Address;
topics: Hex[];
data: Hex;
}>;
// ZKsync-specific field
l2ToL1Logs?: L2ToL1Log[];
};
// ANCHOR_END: proof-receipt-type

// ANCHOR: genesis-type
export type GenesisInput = {
initialContracts: GenesisContractDeployment[];
additionalStorage: GenesisStorageEntry[];
executionVersion: number;
genesisRoot: Hex;
};
// ANCHOR_END: genesis-type

describe('checks rpc docs examples', () => {

let client: ViemClient;

beforeAll(() => {
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);

const l1 = createPublicClient({ transport: http(process.env.L1_RPC!) });
const l2 = createPublicClient({ transport: http(process.env.L2_RPC!) });
const l1Wallet = createWalletClient({ chain: l1Chain, account, transport: http(process.env.L1_RPC!) });
const l2Wallet = createWalletClient({ chain: l2Chain, account, transport: http(process.env.L2_RPC!) });

client = createViemClient({ l1, l2, l1Wallet, l2Wallet });
})

// this test will always succeed
// but any errors will be highlighted
it('checks to see if the zks rpc types are updated', async () => {
const _rpcType: Exact<ZksRpc, ZksType> = true;
const _proofType: Exact<ProofNormalized, ProofN> = true;
const _receiptType: Exact<ReceiptWithL2ToL1, RWithLog> = true;
const _genesisType: Exact<GenesisInput, GenesisType> = true;
});

it('tries to get the bridehub address', async () => {
// ANCHOR: bridgehub-address
const addr = await client.zks.getBridgehubAddress();
// ANCHOR_END: bridgehub-address
expect(addr).toContain("0x");
});

it('tries to get the genesis', async () => {
// ANCHOR: genesis-method
const genesis = await client.zks.getGenesis();

for (const contract of genesis.initialContracts) {
console.log('Contract at', contract.address, 'with bytecode', contract.bytecode);
}

console.log('Execution version:', genesis.executionVersion);
console.log('Genesis root:', genesis.genesisRoot);
// ANCHOR_END: genesis-method
expect(genesis.initialContracts).toBeArray();
});

});
8 changes: 8 additions & 0 deletions docs/snippets/core/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type Exact<A, B> =
(<T>() => T extends A ? 1 : 2) extends
(<T>() => T extends B ? 1 : 2)
? (<T>() => T extends B ? 1 : 2) extends
(<T>() => T extends A ? 1 : 2)
? true
: false
: false;
63 changes: 0 additions & 63 deletions docs/snippets/ethers/deposit-erc20.ts

This file was deleted.

Loading