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.
- package/package.json +6 -1
- package/.eslintignore +0 -3
- package/.eslintrc.cjs +0 -59
- package/.github/pull_request_template.md +0 -43
- package/.github/workflows/publish.yml +0 -98
- package/.github/workflows/smoke.yml +0 -27
- package/.github/workflows/test.yml +0 -33
- package/.prettierignore +0 -19
- package/.prettierrc +0 -12
- package/.release-it.json +0 -66
- package/CHANGELOG.md +0 -306
- package/CLAUDE.md +0 -66
- package/CONTRIBUTING.md +0 -87
- package/renovate.json +0 -20
- package/src/abi/calldata/consts.ts +0 -14
- package/src/abi/calldata/decoder.ts +0 -86
- package/src/abi/calldata/encoder.ts +0 -178
- package/src/abi/calldata/index.ts +0 -3
- package/src/abi/calldata/string.ts +0 -83
- package/src/abi/index.ts +0 -6
- package/src/abi/staking.ts +0 -1501
- package/src/abi/transactions.ts +0 -11
- package/src/accounts/IAccountActions.ts +0 -5
- package/src/accounts/account.ts +0 -9
- package/src/accounts/actions.ts +0 -34
- package/src/chains/actions.ts +0 -40
- package/src/chains/index.ts +0 -4
- package/src/chains/localnet.ts +0 -4016
- package/src/chains/studionet.ts +0 -4017
- package/src/chains/testnetAsimov.ts +0 -4013
- package/src/client/client.ts +0 -139
- package/src/config/snapID.ts +0 -4
- package/src/config/transactions.ts +0 -9
- package/src/contracts/actions.ts +0 -387
- package/src/global.d.ts +0 -9
- package/src/index.ts +0 -12
- package/src/staking/actions.ts +0 -690
- package/src/staking/index.ts +0 -2
- package/src/staking/utils.ts +0 -22
- package/src/transactions/ITransactionActions.ts +0 -15
- package/src/transactions/actions.ts +0 -113
- package/src/transactions/decoders.ts +0 -275
- package/src/types/accounts.ts +0 -1
- package/src/types/calldata.ts +0 -31
- package/src/types/chains.ts +0 -22
- package/src/types/clients.ts +0 -106
- package/src/types/contracts.ts +0 -32
- package/src/types/index.ts +0 -9
- package/src/types/metamaskClientResult.ts +0 -5
- package/src/types/network.ts +0 -1
- package/src/types/snapSource.ts +0 -1
- package/src/types/staking.ts +0 -220
- package/src/types/transactions.ts +0 -312
- package/src/utils/async.ts +0 -3
- package/src/utils/jsonifier.ts +0 -119
- package/src/wallet/actions.ts +0 -10
- package/src/wallet/connect.ts +0 -67
- package/src/wallet/metamaskClient.ts +0 -50
- package/tests/client.test-d.ts +0 -67
- package/tests/client.test.ts +0 -197
- package/tests/smoke.test.ts +0 -291
- package/tests/transactions.test.ts +0 -142
- package/tsconfig.json +0 -119
- package/tsconfig.vitest-temp.json +0 -41
- package/vitest.config.ts +0 -19
- package/vitest.smoke.config.ts +0 -16
package/src/wallet/connect.ts
DELETED
|
@@ -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
|
-
};
|
package/tests/client.test-d.ts
DELETED
|
@@ -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
|
-
});
|
package/tests/client.test.ts
DELETED
|
@@ -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
|
-
});
|
package/tests/smoke.test.ts
DELETED
|
@@ -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
|
-
});
|