genlayer-js 0.18.9 → 0.18.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,15 +3,16 @@ import {GenLayerChain} from "@/types";
3
3
  import {STAKING_ABI} from "@/abi/staking";
4
4
 
5
5
  // chains/localnet.ts
6
- const TESTNET_JSON_RPC_URL = "https://genlayer-testnet.rpc.caldera.xyz/http";
6
+ const TESTNET_JSON_RPC_URL = "https://zksync-os-testnet-genlayer.zksync.dev";
7
+ const TESTNET_WS_URL = "wss://zksync-os-testnet-genlayer.zksync.dev/ws";
7
8
 
8
9
  const STAKING_CONTRACT = {
9
- address: "0x03f410748EBdb4026a6b8299E9B6603A273709D1" as Address,
10
+ address: "0x63Fa5E0bb10fb6fA98F44726C5518223F767687A" as Address,
10
11
  abi: STAKING_ABI,
11
12
  };
12
13
  const EXPLORER_URL = "https://explorer-asimov.genlayer.com/";
13
14
  const CONSENSUS_MAIN_CONTRACT = {
14
- address: "0x67fd4aC71530FB220E0B7F90668BAF977B88fF07" as Address,
15
+ address: "0x6CAFF6769d70824745AD895663409DC70aB5B28E" as Address,
15
16
  abi: [
16
17
  {
17
18
  inputs: [],
@@ -1401,7 +1402,7 @@ const CONSENSUS_MAIN_CONTRACT = {
1401
1402
  };
1402
1403
 
1403
1404
  const CONSENSUS_DATA_CONTRACT = {
1404
- address: "0xB6E1316E57d47d82FDcEa5002028a554754EF243" as Address,
1405
+ address: "0x0D9d1d74d72Fa5eB94bcf746C8FCcb312a722c9B" as Address,
1405
1406
  abi: [
1406
1407
  {
1407
1408
  inputs: [],
@@ -3989,6 +3990,7 @@ export const testnetAsimov: GenLayerChain = defineChain({
3989
3990
  rpcUrls: {
3990
3991
  default: {
3991
3992
  http: [TESTNET_JSON_RPC_URL],
3993
+ webSocket: [TESTNET_WS_URL],
3992
3994
  },
3993
3995
  },
3994
3996
  nativeCurrency: {
@@ -561,8 +561,6 @@ export const stakingActions = (
561
561
  const [
562
562
  epoch,
563
563
  finalized,
564
- validatorMinStake,
565
- delegatorMinStake,
566
564
  activeCount,
567
565
  epochMinDuration,
568
566
  epochZeroMinDuration,
@@ -571,8 +569,6 @@ export const stakingActions = (
571
569
  ] = await Promise.all([
572
570
  contract.read.epoch() as Promise<bigint>,
573
571
  contract.read.finalized() as Promise<bigint>,
574
- contract.read.validatorMinStake() as Promise<bigint>,
575
- contract.read.delegatorMinStake() as Promise<bigint>,
576
572
  contract.read.activeValidatorsCount() as Promise<bigint>,
577
573
  contract.read.epochMinDuration() as Promise<bigint>,
578
574
  contract.read.epochZeroMinDuration() as Promise<bigint>,
@@ -580,8 +576,21 @@ export const stakingActions = (
580
576
  contract.read.epochEven() as Promise<any>,
581
577
  ]);
582
578
 
583
- // Current epoch data for next epoch estimate
584
- const currentEpochData = epoch % 2n === 0n ? epochEven : epochOdd;
579
+ // epochOdd/epochEven return arrays: [start, end, inflation, weight, weightDeposit, weightWithdrawal, vcount, claimed, stakeDeposit, stakeWithdrawal, slashed]
580
+ const raw = epoch % 2n === 0n ? epochEven : epochOdd;
581
+ const currentEpochData = {
582
+ start: raw[0] as bigint,
583
+ end: raw[1] as bigint,
584
+ inflation: raw[2] as bigint,
585
+ weight: raw[3] as bigint,
586
+ weightDeposit: raw[4] as bigint,
587
+ weightWithdrawal: raw[5] as bigint,
588
+ vcount: raw[6] as bigint,
589
+ claimed: raw[7] as bigint,
590
+ stakeDeposit: raw[8] as bigint,
591
+ stakeWithdrawal: raw[9] as bigint,
592
+ slashed: raw[10] as bigint,
593
+ };
585
594
  const currentEpochEnd = currentEpochData.end > 0n;
586
595
 
587
596
  // Estimate next epoch: current start + min duration (if epoch hasn't ended)
@@ -595,10 +604,6 @@ export const stakingActions = (
595
604
  return {
596
605
  currentEpoch: epoch,
597
606
  lastFinalizedEpoch: finalized,
598
- validatorMinStake: formatStakingAmount(validatorMinStake),
599
- validatorMinStakeRaw: validatorMinStake,
600
- delegatorMinStake: formatStakingAmount(delegatorMinStake),
601
- delegatorMinStakeRaw: delegatorMinStake,
602
607
  activeValidatorsCount: activeCount,
603
608
  epochMinDuration,
604
609
  nextEpochEstimate,
@@ -624,20 +629,21 @@ export const stakingActions = (
624
629
  throw new Error(`Epoch ${epochNumber} data no longer available (only current and previous epoch stored)`);
625
630
  }
626
631
 
627
- const epochData = epochNumber % 2n === 0n ? epochEven : epochOdd;
632
+ // epochOdd/epochEven return arrays: [start, end, inflation, weight, weightDeposit, weightWithdrawal, vcount, claimed, stakeDeposit, stakeWithdrawal, slashed]
633
+ const raw = epochNumber % 2n === 0n ? epochEven : epochOdd;
628
634
 
629
635
  return {
630
- start: epochData.start,
631
- end: epochData.end,
632
- inflation: epochData.inflation,
633
- weight: epochData.weight,
634
- weightDeposit: epochData.weightDeposit,
635
- weightWithdrawal: epochData.weightWithdrawal,
636
- vcount: epochData.vcount,
637
- claimed: epochData.claimed,
638
- stakeDeposit: epochData.stakeDeposit,
639
- stakeWithdrawal: epochData.stakeWithdrawal,
640
- slashed: epochData.slashed,
636
+ start: raw[0] as bigint,
637
+ end: raw[1] as bigint,
638
+ inflation: raw[2] as bigint,
639
+ weight: raw[3] as bigint,
640
+ weightDeposit: raw[4] as bigint,
641
+ weightWithdrawal: raw[5] as bigint,
642
+ vcount: raw[6] as bigint,
643
+ claimed: raw[7] as bigint,
644
+ stakeDeposit: raw[8] as bigint,
645
+ stakeWithdrawal: raw[9] as bigint,
646
+ slashed: raw[10] as bigint,
641
647
  };
642
648
  },
643
649
 
@@ -654,7 +660,7 @@ export const stakingActions = (
654
660
 
655
661
  getQuarantinedValidators: async (): Promise<Address[]> => {
656
662
  const contract = getReadOnlyStakingContract();
657
- return contract.read.getQuarantinedValidators() as Promise<Address[]>;
663
+ return contract.read.getValidatorQuarantineList() as Promise<Address[]>;
658
664
  },
659
665
 
660
666
  getBannedValidators: async (startIndex = 0n, size = 100n): Promise<BannedValidatorInfo[]> => {
@@ -677,13 +683,6 @@ export const stakingActions = (
677
683
  }));
678
684
  },
679
685
 
680
- getSlashingAddress: async (): Promise<Address> => {
681
- const contract = getReadOnlyStakingContract();
682
- // contracts() returns tuple: [gen, transactions, idleness, tribunal, slashing, consensus, validatorWalletFactory, nftMinter]
683
- const externalContracts = (await contract.read.contracts()) as readonly ViemAddress[];
684
- return externalContracts[4] as Address; // slashing is at index 4
685
- },
686
-
687
686
  getStakingContract,
688
687
  parseStakingAmount,
689
688
  formatStakingAmount,
@@ -120,10 +120,6 @@ export interface EpochData {
120
120
  export interface EpochInfo {
121
121
  currentEpoch: bigint;
122
122
  lastFinalizedEpoch: bigint;
123
- validatorMinStake: string;
124
- validatorMinStakeRaw: bigint;
125
- delegatorMinStake: string;
126
- delegatorMinStakeRaw: bigint;
127
123
  activeValidatorsCount: bigint;
128
124
  epochMinDuration: bigint;
129
125
  nextEpochEstimate: Date | null;
@@ -218,7 +214,6 @@ export interface StakingActions {
218
214
  getEpochData: (epochNumber: bigint) => Promise<EpochData>;
219
215
  getActiveValidators: () => Promise<Address[]>;
220
216
  getActiveValidatorsCount: () => Promise<bigint>;
221
- getSlashingAddress: () => Promise<Address>;
222
217
  getStakingContract: () => StakingContract;
223
218
  parseStakingAmount: (amount: string | bigint) => bigint;
224
219
  formatStakingAmount: (amount: bigint) => string;
@@ -0,0 +1,291 @@
1
+ // tests/smoke.test.ts
2
+ // Smoke tests against live testnet-asimov to verify ABI compatibility and connectivity.
3
+ // Run with: npm run test:smoke
4
+ // These are excluded from regular `npm test` to avoid CI dependence on testnet availability.
5
+
6
+ import {describe, it, expect, beforeAll} from "vitest";
7
+ import {createPublicClient, http, webSocket, getContract, Address as ViemAddress} from "viem";
8
+ import {testnetAsimov} from "@/chains/testnetAsimov";
9
+ import {createClient} from "@/client/client";
10
+ import {STAKING_ABI} from "@/abi/staking";
11
+ import {Address} from "@/types/accounts";
12
+
13
+ const TIMEOUT = 30_000;
14
+
15
+ // ─── HTTP RPC Connectivity ───────────────────────────────────────────────────
16
+
17
+ describe("Testnet Asimov - HTTP RPC", () => {
18
+ it("should fetch chain ID", async () => {
19
+ const client = createPublicClient({
20
+ chain: testnetAsimov,
21
+ transport: http(testnetAsimov.rpcUrls.default.http[0]),
22
+ });
23
+ const chainId = await client.getChainId();
24
+ expect(chainId).toBe(testnetAsimov.id);
25
+ }, TIMEOUT);
26
+
27
+ it("should fetch latest block number", async () => {
28
+ const client = createPublicClient({
29
+ chain: testnetAsimov,
30
+ transport: http(testnetAsimov.rpcUrls.default.http[0]),
31
+ });
32
+ const blockNumber = await client.getBlockNumber();
33
+ expect(blockNumber).toBeGreaterThan(0n);
34
+ }, TIMEOUT);
35
+ });
36
+
37
+ // ─── WebSocket RPC Connectivity ──────────────────────────────────────────────
38
+
39
+ describe("Testnet Asimov - WebSocket RPC", () => {
40
+ const wsUrl = testnetAsimov.rpcUrls.default.webSocket?.[0];
41
+
42
+ it("should have a WS URL configured", () => {
43
+ expect(wsUrl).toBeDefined();
44
+ expect(wsUrl).toMatch(/^wss?:\/\//);
45
+ });
46
+
47
+ it("should connect and fetch chain ID over WebSocket", async () => {
48
+ if (!wsUrl) return;
49
+ const client = createPublicClient({
50
+ chain: testnetAsimov,
51
+ transport: webSocket(wsUrl),
52
+ });
53
+ const chainId = await client.getChainId();
54
+ // WS endpoint may point to the underlying chain (different ID from GenLayer overlay)
55
+ // The key assertion is that the connection works and returns a valid number
56
+ expect(chainId).toBeTypeOf("number");
57
+ expect(chainId).toBeGreaterThan(0);
58
+ if (chainId !== testnetAsimov.id) {
59
+ console.warn(
60
+ `WS chain ID (${chainId}) differs from HTTP chain ID (${testnetAsimov.id}). ` +
61
+ `WS URL may point to the underlying L1/L2 chain.`
62
+ );
63
+ }
64
+ }, TIMEOUT);
65
+
66
+ it("should fetch latest block number over WebSocket", async () => {
67
+ if (!wsUrl) return;
68
+ const client = createPublicClient({
69
+ chain: testnetAsimov,
70
+ transport: webSocket(wsUrl),
71
+ });
72
+ const blockNumber = await client.getBlockNumber();
73
+ expect(blockNumber).toBeGreaterThan(0n);
74
+ }, TIMEOUT);
75
+ });
76
+
77
+ // ─── Staking Read-Only via WebSocket ─────────────────────────────────────────
78
+
79
+ describe("Testnet Asimov - Staking over WebSocket", () => {
80
+ const wsUrl = testnetAsimov.rpcUrls.default.webSocket?.[0];
81
+ const stakingAddress = testnetAsimov.stakingContract?.address as ViemAddress;
82
+
83
+ // First check if WS points to the same chain — if not, skip staking tests
84
+ let wsMatchesChain = false;
85
+ let wsPub: ReturnType<typeof createPublicClient> | null = null;
86
+
87
+ beforeAll(async () => {
88
+ if (!wsUrl) return;
89
+ wsPub = createPublicClient({chain: testnetAsimov, transport: webSocket(wsUrl)});
90
+ try {
91
+ const chainId = await wsPub.getChainId();
92
+ wsMatchesChain = chainId === testnetAsimov.id;
93
+ if (!wsMatchesChain) {
94
+ console.warn(
95
+ `WS chain ID (${chainId}) differs from testnet (${testnetAsimov.id}). ` +
96
+ `Staking contract calls will be skipped — WS endpoint serves a different chain.`
97
+ );
98
+ }
99
+ } catch {
100
+ console.warn("WS connection failed during setup");
101
+ }
102
+ }, TIMEOUT);
103
+
104
+ it("epoch() via WS", async () => {
105
+ if (!wsMatchesChain || !wsPub) return;
106
+ const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub});
107
+ const epoch = await contract.read.epoch();
108
+ expect(epoch).toBeTypeOf("bigint");
109
+ }, TIMEOUT);
110
+
111
+ it("activeValidatorsCount() via WS", async () => {
112
+ if (!wsMatchesChain || !wsPub) return;
113
+ const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub});
114
+ const count = await contract.read.activeValidatorsCount();
115
+ expect(count).toBeTypeOf("bigint");
116
+ expect(count).toBeGreaterThanOrEqual(0n);
117
+ }, TIMEOUT);
118
+
119
+ it("activeValidators() via WS", async () => {
120
+ if (!wsMatchesChain || !wsPub) return;
121
+ const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub});
122
+ const validators = await contract.read.activeValidators();
123
+ expect(Array.isArray(validators)).toBe(true);
124
+ }, TIMEOUT);
125
+
126
+ it("isValidator() via WS", async () => {
127
+ if (!wsMatchesChain || !wsPub) return;
128
+ const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub});
129
+ const validators = (await contract.read.activeValidators()) as ViemAddress[];
130
+ const nonZero = validators.filter(v => v !== "0x0000000000000000000000000000000000000000");
131
+ if (nonZero.length === 0) return;
132
+
133
+ const result = await contract.read.isValidator([nonZero[0]]);
134
+ expect(result).toBe(true);
135
+ }, TIMEOUT);
136
+
137
+ it("validatorView() via WS", async () => {
138
+ if (!wsMatchesChain || !wsPub) return;
139
+ const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub});
140
+ const validators = (await contract.read.activeValidators()) as ViemAddress[];
141
+ const nonZero = validators.filter(v => v !== "0x0000000000000000000000000000000000000000");
142
+ if (nonZero.length === 0) return;
143
+
144
+ const view = await contract.read.validatorView([nonZero[0]]) as unknown as readonly unknown[];
145
+ expect(Array.isArray(view)).toBe(true);
146
+ // view is [left, right, parent, eBanned, ePrimed, vStake, vShares, dStake, dShares, vDeposit, vWithdrawal, live]
147
+ expect(view.length).toBe(12);
148
+ }, TIMEOUT);
149
+
150
+ it("getValidatorQuarantineList() via WS", async () => {
151
+ if (!wsMatchesChain || !wsPub) return;
152
+ const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub});
153
+ const list = await contract.read.getValidatorQuarantineList();
154
+ expect(Array.isArray(list)).toBe(true);
155
+ }, TIMEOUT);
156
+
157
+ it("epochOdd() / epochEven() via WS", async () => {
158
+ if (!wsMatchesChain || !wsPub) return;
159
+ const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub});
160
+ const odd = await contract.read.epochOdd();
161
+ const even = await contract.read.epochEven();
162
+ expect(Array.isArray(odd)).toBe(true);
163
+ expect(Array.isArray(even)).toBe(true);
164
+ expect(odd.length).toBe(11);
165
+ expect(even.length).toBe(11);
166
+ }, TIMEOUT);
167
+ });
168
+
169
+ // ─── Staking Read-Only Methods ───────────────────────────────────────────────
170
+
171
+ describe("Testnet Asimov - Staking (read-only)", () => {
172
+ let client: ReturnType<typeof createClient>;
173
+
174
+ beforeAll(() => {
175
+ client = createClient({chain: testnetAsimov});
176
+ });
177
+
178
+ it("getEpochInfo", async () => {
179
+ const info = await client.getEpochInfo();
180
+ expect(info.currentEpoch).toBeTypeOf("bigint");
181
+ expect(info.lastFinalizedEpoch).toBeTypeOf("bigint");
182
+ expect(info.activeValidatorsCount).toBeTypeOf("bigint");
183
+ expect(info.epochMinDuration).toBeTypeOf("bigint");
184
+ // nextEpochEstimate is Date | null
185
+ if (info.nextEpochEstimate !== null) {
186
+ expect(info.nextEpochEstimate).toBeInstanceOf(Date);
187
+ }
188
+ }, TIMEOUT);
189
+
190
+ it("getActiveValidatorsCount", async () => {
191
+ const count = await client.getActiveValidatorsCount();
192
+ expect(count).toBeTypeOf("bigint");
193
+ expect(count).toBeGreaterThanOrEqual(0n);
194
+ }, TIMEOUT);
195
+
196
+ it("getActiveValidators", async () => {
197
+ const validators = await client.getActiveValidators();
198
+ expect(Array.isArray(validators)).toBe(true);
199
+ // Each entry should be a hex address
200
+ for (const v of validators) {
201
+ expect(v).toMatch(/^0x[0-9a-fA-F]{40}$/);
202
+ }
203
+ }, TIMEOUT);
204
+
205
+ it("getEpochData for current epoch", async () => {
206
+ const {currentEpoch} = await client.getEpochInfo();
207
+ const data = await client.getEpochData(currentEpoch);
208
+ expect(data.start).toBeTypeOf("bigint");
209
+ expect(data.weight).toBeTypeOf("bigint");
210
+ expect(data.vcount).toBeTypeOf("bigint");
211
+ }, TIMEOUT);
212
+
213
+ it("isValidator returns boolean", async () => {
214
+ const validators = await client.getActiveValidators();
215
+ if (validators.length === 0) return; // nothing to test
216
+
217
+ const result = await client.isValidator(validators[0]);
218
+ expect(result).toBe(true);
219
+
220
+ // zero address should not be a validator
221
+ const fake = await client.isValidator("0x0000000000000000000000000000000000000001" as Address);
222
+ expect(fake).toBe(false);
223
+ }, TIMEOUT);
224
+
225
+ it("getValidatorInfo for an active validator", async () => {
226
+ const validators = await client.getActiveValidators();
227
+ if (validators.length === 0) return;
228
+
229
+ const info = await client.getValidatorInfo(validators[0]);
230
+ expect(info.address).toBe(validators[0]);
231
+ expect(info.owner).toMatch(/^0x[0-9a-fA-F]{40}$/);
232
+ expect(info.operator).toMatch(/^0x[0-9a-fA-F]{40}$/);
233
+ expect(info.vStakeRaw).toBeTypeOf("bigint");
234
+ expect(typeof info.live).toBe("boolean");
235
+ expect(typeof info.banned).toBe("boolean");
236
+ expect(typeof info.needsPriming).toBe("boolean");
237
+ expect(Array.isArray(info.pendingDeposits)).toBe(true);
238
+ expect(Array.isArray(info.pendingWithdrawals)).toBe(true);
239
+ }, TIMEOUT);
240
+
241
+ it("getStakeInfo for validator self-stake", async () => {
242
+ const validators = await client.getActiveValidators();
243
+ if (validators.length === 0) return;
244
+
245
+ const validatorAddr = validators[0];
246
+ // Self-stake: delegator = validator address
247
+ const stakeInfo = await client.getStakeInfo(validatorAddr, validatorAddr);
248
+ expect(stakeInfo.delegator).toBe(validatorAddr);
249
+ expect(stakeInfo.validator).toBe(validatorAddr);
250
+ expect(stakeInfo.shares).toBeTypeOf("bigint");
251
+ expect(stakeInfo.stakeRaw).toBeTypeOf("bigint");
252
+ expect(Array.isArray(stakeInfo.pendingDeposits)).toBe(true);
253
+ expect(Array.isArray(stakeInfo.pendingWithdrawals)).toBe(true);
254
+ }, TIMEOUT);
255
+
256
+ it("getQuarantinedValidators returns array", async () => {
257
+ // This calls getValidatorQuarantineList() — the v0.5 renamed function
258
+ const quarantined = await (client as any).getQuarantinedValidators();
259
+ expect(Array.isArray(quarantined)).toBe(true);
260
+ }, TIMEOUT);
261
+
262
+ it("getBannedValidators returns array", async () => {
263
+ const banned = await (client as any).getBannedValidators();
264
+ expect(Array.isArray(banned)).toBe(true);
265
+ for (const b of banned) {
266
+ expect(b.validator).toMatch(/^0x[0-9a-fA-F]{40}$/);
267
+ expect(b.untilEpoch).toBeTypeOf("bigint");
268
+ expect(typeof b.permanentlyBanned).toBe("boolean");
269
+ }
270
+ }, TIMEOUT);
271
+
272
+ it("getStakingContract returns a contract instance", () => {
273
+ const contract = client.getStakingContract();
274
+ expect(contract).toBeDefined();
275
+ expect(contract.address).toMatch(/^0x[0-9a-fA-F]{40}$/);
276
+ expect(contract.read).toBeDefined();
277
+ });
278
+
279
+ it("parseStakingAmount and formatStakingAmount round-trip", () => {
280
+ // parseStakingAmount treats bare strings as wei; use "gen" suffix for human amounts
281
+ const amount = client.parseStakingAmount("1.5gen");
282
+ expect(amount).toBeTypeOf("bigint");
283
+ expect(amount).toBe(1500000000000000000n);
284
+ const formatted = client.formatStakingAmount(amount);
285
+ expect(formatted).toBe("1.5 GEN");
286
+
287
+ // Raw wei round-trip
288
+ const weiAmount = client.parseStakingAmount("42000000000000000000");
289
+ expect(client.formatStakingAmount(weiAmount)).toBe("42 GEN");
290
+ });
291
+ });
package/vitest.config.ts CHANGED
@@ -5,6 +5,7 @@ export default defineConfig({
5
5
  test: {
6
6
  globals: true,
7
7
  environment: "node",
8
+ exclude: ["node_modules/**", "tests/smoke.test.ts"],
8
9
  coverage: {
9
10
  reporter: ["text", "json", "html"],
10
11
  exclude: ["node_modules/"],
@@ -0,0 +1,16 @@
1
+ import {defineConfig} from "vitest/config";
2
+ import path from "path";
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ globals: true,
7
+ environment: "node",
8
+ include: ["tests/smoke.test.ts"],
9
+ testTimeout: 30_000,
10
+ },
11
+ resolve: {
12
+ alias: {
13
+ "@": path.resolve(__dirname, "./src"),
14
+ },
15
+ },
16
+ });