genlayer-js 0.18.11 → 0.18.12

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.
Files changed (66) hide show
  1. package/package.json +6 -1
  2. package/.eslintignore +0 -3
  3. package/.eslintrc.cjs +0 -59
  4. package/.github/pull_request_template.md +0 -43
  5. package/.github/workflows/publish.yml +0 -98
  6. package/.github/workflows/smoke.yml +0 -27
  7. package/.github/workflows/test.yml +0 -33
  8. package/.prettierignore +0 -19
  9. package/.prettierrc +0 -12
  10. package/.release-it.json +0 -66
  11. package/CHANGELOG.md +0 -306
  12. package/CLAUDE.md +0 -66
  13. package/CONTRIBUTING.md +0 -87
  14. package/renovate.json +0 -20
  15. package/src/abi/calldata/consts.ts +0 -14
  16. package/src/abi/calldata/decoder.ts +0 -86
  17. package/src/abi/calldata/encoder.ts +0 -178
  18. package/src/abi/calldata/index.ts +0 -3
  19. package/src/abi/calldata/string.ts +0 -83
  20. package/src/abi/index.ts +0 -6
  21. package/src/abi/staking.ts +0 -1501
  22. package/src/abi/transactions.ts +0 -11
  23. package/src/accounts/IAccountActions.ts +0 -5
  24. package/src/accounts/account.ts +0 -9
  25. package/src/accounts/actions.ts +0 -34
  26. package/src/chains/actions.ts +0 -40
  27. package/src/chains/index.ts +0 -4
  28. package/src/chains/localnet.ts +0 -4016
  29. package/src/chains/studionet.ts +0 -4017
  30. package/src/chains/testnetAsimov.ts +0 -4013
  31. package/src/client/client.ts +0 -139
  32. package/src/config/snapID.ts +0 -4
  33. package/src/config/transactions.ts +0 -9
  34. package/src/contracts/actions.ts +0 -387
  35. package/src/global.d.ts +0 -9
  36. package/src/index.ts +0 -12
  37. package/src/staking/actions.ts +0 -690
  38. package/src/staking/index.ts +0 -2
  39. package/src/staking/utils.ts +0 -22
  40. package/src/transactions/ITransactionActions.ts +0 -15
  41. package/src/transactions/actions.ts +0 -113
  42. package/src/transactions/decoders.ts +0 -275
  43. package/src/types/accounts.ts +0 -1
  44. package/src/types/calldata.ts +0 -31
  45. package/src/types/chains.ts +0 -22
  46. package/src/types/clients.ts +0 -106
  47. package/src/types/contracts.ts +0 -32
  48. package/src/types/index.ts +0 -9
  49. package/src/types/metamaskClientResult.ts +0 -5
  50. package/src/types/network.ts +0 -1
  51. package/src/types/snapSource.ts +0 -1
  52. package/src/types/staking.ts +0 -220
  53. package/src/types/transactions.ts +0 -312
  54. package/src/utils/async.ts +0 -3
  55. package/src/utils/jsonifier.ts +0 -119
  56. package/src/wallet/actions.ts +0 -10
  57. package/src/wallet/connect.ts +0 -67
  58. package/src/wallet/metamaskClient.ts +0 -50
  59. package/tests/client.test-d.ts +0 -67
  60. package/tests/client.test.ts +0 -197
  61. package/tests/smoke.test.ts +0 -291
  62. package/tests/transactions.test.ts +0 -142
  63. package/tsconfig.json +0 -119
  64. package/tsconfig.vitest-temp.json +0 -41
  65. package/vitest.config.ts +0 -19
  66. package/vitest.smoke.config.ts +0 -16
@@ -1,67 +0,0 @@
1
- import {localnet} from "@/chains/localnet";
2
- import {studionet} from "@/chains/studionet";
3
- import {testnetAsimov} from "@/chains/testnetAsimov";
4
- import {GenLayerClient, GenLayerChain} from "@/types";
5
- import {Network} from "@/types/network";
6
- import {SnapSource} from "@/types/snapSource";
7
- import {snapID} from "@/config/snapID";
8
-
9
- const networks = {
10
- localnet,
11
- studionet,
12
- testnetAsimov,
13
- };
14
-
15
- export const connect = async (
16
- client: GenLayerClient<GenLayerChain>,
17
- network: Network = "studionet",
18
- snapSource: SnapSource = "npm",
19
- ): Promise<void> => {
20
- if (!window.ethereum) {
21
- throw new Error("MetaMask is not installed.");
22
- }
23
- if (network === "mainnet") {
24
- throw new Error(`${network} is not available yet. Please use localnet.`);
25
- }
26
-
27
- const selectedNetwork = networks[network];
28
- if (!selectedNetwork) {
29
- throw new Error(`Network configuration for '${network}' is not available.`);
30
- }
31
-
32
- const chainIdHex = `0x${selectedNetwork.id.toString(16)}`;
33
- const chainParams = {
34
- chainId: chainIdHex,
35
- chainName: selectedNetwork.name,
36
- rpcUrls: selectedNetwork.rpcUrls.default.http,
37
- nativeCurrency: selectedNetwork.nativeCurrency,
38
- blockExplorerUrls: [selectedNetwork.blockExplorers?.default.url],
39
- };
40
-
41
- const currentChainId = await window.ethereum.request({method: "eth_chainId"});
42
- if (currentChainId !== chainIdHex) {
43
- await window.ethereum.request({
44
- method: "wallet_addEthereumChain",
45
- params: [chainParams],
46
- });
47
- await window.ethereum.request({
48
- method: "wallet_switchEthereumChain",
49
- params: [{chainId: chainIdHex}],
50
- });
51
- }
52
-
53
- const id = snapSource === "local" ? snapID.local : snapID.npm;
54
- const installedSnaps: any = await window.ethereum.request({method: "wallet_getSnaps"});
55
- const isGenLayerSnapInstalled = Object.values(installedSnaps).some((snap: any) => snap.id === id);
56
-
57
- if (!isGenLayerSnapInstalled) {
58
- await window.ethereum.request({
59
- method: "wallet_requestSnaps",
60
- params: {
61
- [id]: {},
62
- },
63
- });
64
- }
65
-
66
- client.chain = selectedNetwork;
67
- };
@@ -1,50 +0,0 @@
1
- import { snapID } from "@/config/snapID";
2
- import { SnapSource } from "@/types/snapSource";
3
- import { MetaMaskClientResult } from "@/types/metamaskClientResult";
4
-
5
- export const metamaskClient = async (snapSource: SnapSource = "npm") => {
6
- if (typeof window === "undefined" || !window.ethereum) {
7
- throw new Error("MetaMask is not installed.");
8
- }
9
-
10
- const isFlask = async (): Promise<boolean> => {
11
- try {
12
- const clientVersion = await window.ethereum?.request({
13
- method: "web3_clientVersion",
14
- });
15
-
16
- return (clientVersion as string)?.includes("flask");
17
- } catch (error) {
18
- console.error("Error detecting Flask:", error);
19
- return false;
20
- }
21
- };
22
-
23
- const installedSnaps = async (): Promise<Record<string, any>> => {
24
- try {
25
- return (await window.ethereum?.request({
26
- method: "wallet_getSnaps",
27
- })) as Record<string, any>;
28
- } catch (error) {
29
- console.error("Error getting installed snaps:", error);
30
- return {};
31
- }
32
- };
33
-
34
- const isGenLayerSnapInstalled = async (): Promise<boolean> => {
35
- const id = snapSource === "local" ? snapID.local : snapID.npm;
36
- const snaps = await installedSnaps();
37
-
38
- return Object.values(snaps).some((snap: any) => snap.id === id);
39
- };
40
-
41
- const flaskDetected = await isFlask();
42
- const snapsList = await installedSnaps();
43
- const genLayerSnapInstalled = await isGenLayerSnapInstalled();
44
-
45
- return {
46
- isFlask: flaskDetected,
47
- installedSnaps: snapsList,
48
- isGenLayerSnapInstalled: genLayerSnapInstalled,
49
- } as MetaMaskClientResult;
50
- };
@@ -1,67 +0,0 @@
1
- import {createClient} from "../src/client/client";
2
- import {localnet} from "@/chains/localnet";
3
- import {createAccount, generatePrivateKey} from "../src/accounts/account";
4
- import {TransactionHash, TransactionStatus} from "../src/types/transactions";
5
-
6
- test("type checks", () => {
7
- const client = createClient({
8
- chain: localnet,
9
- account: createAccount(generatePrivateKey()),
10
- });
11
-
12
- const exampleAddress = "0x1234567890123456789012345678901234567890";
13
-
14
- // This should fail type checking - "whatever" is not a valid filter
15
- // @ts-expect-error "whatever" is not a valid filter type
16
- void client.request({
17
- method: "sim_getTransactionsForAddress",
18
- params: [exampleAddress, "whatever"],
19
- });
20
-
21
- // This should pass type checking - "all", "to" and "from" are valid filters
22
- void client.request({
23
- method: "sim_getTransactionsForAddress",
24
- params: [exampleAddress, "all"],
25
- });
26
-
27
- void client.request({
28
- method: "sim_getTransactionsForAddress",
29
- params: [exampleAddress, "to"],
30
- });
31
-
32
- void client.request({
33
- method: "sim_getTransactionsForAddress",
34
- params: [exampleAddress, "from"],
35
- });
36
-
37
- void client.getContractSchema(exampleAddress);
38
-
39
- void client.getContractSchemaForCode("class SomeContract...");
40
-
41
- void client.waitForTransactionReceipt({
42
- hash: "0x1234567890123456789012345678901234567890123456789012345678901234" as TransactionHash,
43
- });
44
-
45
- void client.waitForTransactionReceipt({
46
- hash: "0x1234567890123456789012345678901234567890123456789012345678901234" as TransactionHash,
47
- status: TransactionStatus.FINALIZED,
48
- });
49
-
50
- void client.waitForTransactionReceipt({
51
- hash: "0x1234567890123456789012345678901234567890123456789012345678901234" as TransactionHash,
52
- status: TransactionStatus.FINALIZED,
53
- interval: 1000,
54
- });
55
-
56
- void client.waitForTransactionReceipt({
57
- hash: "0x1234567890123456789012345678901234567890123456789012345678901234" as TransactionHash,
58
- status: TransactionStatus.FINALIZED,
59
- interval: 1000,
60
- retries: 10,
61
- });
62
-
63
- // @ts-expect-error missing hash
64
- void client.waitForTransactionReceipt({
65
- status: TransactionStatus.FINALIZED,
66
- });
67
- });
@@ -1,197 +0,0 @@
1
- // tests/client.test.ts
2
- import {createClient} from "../src/client/client";
3
- import {localnet} from "@/chains/localnet";
4
- import {Address} from "../src/types/accounts";
5
- import {createAccount, generatePrivateKey} from "../src/accounts/account";
6
- import {vi} from "vitest";
7
- import {TransactionHashVariant} from "../src/types/transactions";
8
- import {zeroAddress} from "viem";
9
-
10
- // Setup fetch mock
11
- const mockFetch = vi.fn();
12
- vi.stubGlobal("fetch", mockFetch);
13
-
14
- // Store for gen_call parameters received by mockFetch
15
- let lastGenCallParams: any = null;
16
-
17
- describe("Client Creation", () => {
18
- it("should create a client for the localnet", () => {
19
- const client = createClient({chain: localnet});
20
- expect(client).toBeDefined();
21
- expect(client.chain).toBe(localnet);
22
- });
23
- });
24
-
25
- describe("Client Overrides", () => {
26
- beforeEach(() => {
27
- mockFetch.mockReset();
28
- lastGenCallParams = null; // Reset for each test
29
-
30
- mockFetch.mockImplementation(async (url, options) => {
31
- let body = {};
32
- const bodyString = typeof options?.body === "string" ? options.body : null;
33
-
34
- if (bodyString) {
35
- try {
36
- body = JSON.parse(bodyString);
37
- } catch (e) {
38
- console.error("[TESTS] mockFetch: Failed to parse bodyString:", bodyString, "Error:", e);
39
- // Return a generic error if body parsing fails
40
- return {
41
- ok: false,
42
- status: 500,
43
- json: async () => ({error: {message: "mockFetch body parse error"}}),
44
- };
45
- }
46
- }
47
-
48
- const method = (body as any).method;
49
- // console.log(`[TESTS] mockFetch called: URL=${url}, Method=${method}, Body=`, body); // Optional: keep for debugging
50
-
51
- if (method === "sim_getConsensusContract") {
52
- // console.log("[TESTS] mockFetch: Handling sim_getConsensusContract");
53
- return {
54
- ok: true,
55
- json: async () => ({
56
- result: {
57
- address: "0x0000000000000000000000000000000000000001",
58
- abi: [],
59
- },
60
- }),
61
- };
62
- } else if (method === "gen_call") {
63
- // console.log("[TESTS] mockFetch: Handling gen_call");
64
- lastGenCallParams = (body as any).params; // Store the params for gen_call
65
- return {
66
- ok: true,
67
- json: async () => ({result: "0"}),
68
- };
69
- }
70
-
71
- console.warn(`[TESTS] mockFetch: Unhandled method - URL=${url}, Method=${method}, Body=`, body);
72
- return {
73
- ok: false,
74
- status: 404, // Not Found for unhandled methods
75
- json: async () => ({error: {message: `Unexpected fetch mock call to method: ${method}`}}),
76
- };
77
- });
78
- });
79
-
80
- afterEach(() => {
81
- // Restore any spies if they weren't restored in tests
82
- vi.restoreAllMocks();
83
- });
84
-
85
- it("should default to client account if no account is provided", async () => {
86
- const account = createAccount(generatePrivateKey());
87
- const client = createClient({
88
- chain: localnet,
89
- account: account,
90
- });
91
-
92
- // const requestSpy = vi.spyOn(client, "request"); // Removed spy
93
-
94
- const contractAddress = "0x1234567890123456789012345678901234567890";
95
- await client.readContract({
96
- address: contractAddress as Address,
97
- functionName: "testFunction",
98
- args: ["arg1", "arg2"],
99
- transactionHashVariant: TransactionHashVariant.LATEST_NONFINAL,
100
- });
101
-
102
- expect(lastGenCallParams).toEqual([
103
- {
104
- type: "read",
105
- to: contractAddress,
106
- from: account.address,
107
- data: expect.any(String), // The data is complex, checking type is often sufficient
108
- transaction_hash_variant: TransactionHashVariant.LATEST_NONFINAL,
109
- },
110
- ]);
111
- });
112
-
113
- it("should override client account if account is provided", async () => {
114
- const clientInternalAccount = createAccount(generatePrivateKey()); // Renamed for clarity
115
- const client = createClient({
116
- chain: localnet,
117
- account: clientInternalAccount,
118
- });
119
-
120
- const overrideAccount = createAccount(generatePrivateKey());
121
-
122
- // const requestSpy = vi.spyOn(client, "request"); // Removed spy
123
-
124
- const contractAddress = "0x1234567890123456789012345678901234567890";
125
- await client.readContract({
126
- account: overrideAccount,
127
- address: contractAddress as Address,
128
- functionName: "testFunction",
129
- args: ["arg1", "arg2"],
130
- transactionHashVariant: TransactionHashVariant.LATEST_FINAL,
131
- });
132
-
133
- expect(lastGenCallParams).toEqual([
134
- {
135
- type: "read",
136
- to: contractAddress,
137
- from: overrideAccount.address,
138
- data: expect.any(String),
139
- transaction_hash_variant: TransactionHashVariant.LATEST_FINAL,
140
- },
141
- ]);
142
- });
143
-
144
- it("should use client account if account is an address string and no override", async () => {
145
- // Clarified title
146
- const accountAddressString = "0x65e03a3e916CF1dC92d3C8E8186a89CfAB0D2bc2";
147
- const client = createClient({
148
- chain: localnet,
149
- account: accountAddressString, // Client's account is an address string
150
- });
151
-
152
- // const requestSpy = vi.spyOn(client, "request"); // Removed spy
153
-
154
- const contractAddress = "0x1234567890123456789012345678901234567890";
155
- await client.readContract({
156
- address: contractAddress as Address,
157
- functionName: "testFunction",
158
- args: ["arg1", "arg2"],
159
- // No stateStatus, no account override in this specific call to readContract
160
- });
161
-
162
- expect(lastGenCallParams).toEqual([
163
- {
164
- type: "read",
165
- to: contractAddress,
166
- from: accountAddressString, // Expecting the address string directly
167
- data: expect.any(String),
168
- transaction_hash_variant: TransactionHashVariant.LATEST_NONFINAL,
169
- },
170
- ]);
171
- });
172
-
173
- it("should use zero address when no account is provided anywhere", async () => {
174
- const client = createClient({
175
- chain: localnet,
176
- // No account provided on client
177
- });
178
-
179
- const contractAddress = "0x1234567890123456789012345678901234567890";
180
- await client.readContract({
181
- // No account override either
182
- address: contractAddress as Address,
183
- functionName: "testFunction",
184
- args: ["arg1", "arg2"],
185
- });
186
-
187
- expect(lastGenCallParams).toEqual([
188
- {
189
- type: "read",
190
- to: contractAddress,
191
- from: zeroAddress, // Should default to zero address
192
- data: expect.any(String),
193
- transaction_hash_variant: TransactionHashVariant.LATEST_NONFINAL,
194
- },
195
- ]);
196
- });
197
- });
@@ -1,291 +0,0 @@
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
- });