genlayer-js 0.18.8 → 0.18.10

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/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  localnet,
6
6
  studionet,
7
7
  testnetAsimov
8
- } from "./chunk-V4ZFI4GV.js";
8
+ } from "./chunk-WZNF2WK4.js";
9
9
  import {
10
10
  CalldataAddress,
11
11
  isDecidedState,
@@ -743,10 +743,44 @@ var _sendTransaction = async ({
743
743
  data: encodedData,
744
744
  value
745
745
  });
746
- } catch (error) {
747
- console.warn("Gas estimation failed, using fallback value:", error);
746
+ } catch (err) {
747
+ console.error("Gas estimation failed, using default 200_000:", err);
748
748
  estimatedGas = 200000n;
749
749
  }
750
+ if (validatedSenderAccount?.type === "local") {
751
+ if (!validatedSenderAccount?.signTransaction) {
752
+ throw new Error("Account does not support signTransaction");
753
+ }
754
+ const gasPriceHex = await client.request({
755
+ method: "eth_gasPrice"
756
+ });
757
+ const transactionRequest2 = {
758
+ account: validatedSenderAccount,
759
+ to: client.chain.consensusMainContract?.address,
760
+ data: encodedData,
761
+ type: "legacy",
762
+ nonce: Number(nonce),
763
+ value,
764
+ gas: estimatedGas,
765
+ gasPrice: BigInt(gasPriceHex),
766
+ chainId: client.chain.id
767
+ };
768
+ const serializedTransaction = await validatedSenderAccount.signTransaction(transactionRequest2);
769
+ const txHash = await client.sendRawTransaction({ serializedTransaction });
770
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
771
+ if (receipt.status === "reverted") {
772
+ throw new Error("Transaction reverted");
773
+ }
774
+ const newTxEvents = parseEventLogs({
775
+ abi: client.chain.consensusMainContract?.abi,
776
+ eventName: "NewTransaction",
777
+ logs: receipt.logs
778
+ });
779
+ if (newTxEvents.length === 0) {
780
+ throw new Error("Transaction not processed by consensus");
781
+ }
782
+ return newTxEvents[0].args["txId"];
783
+ }
750
784
  const transactionRequest = await client.prepareTransactionRequest({
751
785
  account: validatedSenderAccount,
752
786
  to: client.chain.consensusMainContract?.address,
@@ -756,37 +790,17 @@ var _sendTransaction = async ({
756
790
  value,
757
791
  gas: estimatedGas
758
792
  });
759
- if (validatedSenderAccount?.type !== "local") {
760
- const formattedRequest = {
761
- from: transactionRequest.from,
762
- to: transactionRequest.to,
763
- data: encodedData,
764
- value: transactionRequest.value ? `0x${transactionRequest.value.toString(16)}` : "0x0",
765
- gas: transactionRequest.gas ? `0x${transactionRequest.gas.toString(16)}` : "0x5208"
766
- };
767
- return await client.request({
768
- method: "eth_sendTransaction",
769
- params: [formattedRequest]
770
- });
771
- }
772
- if (!validatedSenderAccount?.signTransaction) {
773
- throw new Error("Account does not support signTransaction");
774
- }
775
- const serializedTransaction = await validatedSenderAccount.signTransaction(transactionRequest);
776
- const txHash = await client.sendRawTransaction({ serializedTransaction });
777
- const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
778
- if (receipt.status === "reverted") {
779
- throw new Error("Transaction reverted");
780
- }
781
- const newTxEvents = parseEventLogs({
782
- abi: client.chain.consensusMainContract?.abi,
783
- eventName: "NewTransaction",
784
- logs: receipt.logs
793
+ const formattedRequest = {
794
+ from: transactionRequest.from,
795
+ to: transactionRequest.to,
796
+ data: encodedData,
797
+ value: transactionRequest.value ? `0x${transactionRequest.value.toString(16)}` : "0x0",
798
+ gas: transactionRequest.gas ? `0x${transactionRequest.gas.toString(16)}` : "0x5208"
799
+ };
800
+ return await client.request({
801
+ method: "eth_sendTransaction",
802
+ params: [formattedRequest]
785
803
  });
786
- if (newTxEvents.length === 0) {
787
- throw new Error("Transaction not processed by consensus");
788
- }
789
- return newTxEvents[0].args["txId"];
790
804
  };
791
805
 
792
806
  // src/config/transactions.ts
@@ -1672,6 +1686,7 @@ var stakingActions = (client, publicClient) => {
1672
1686
  const contract = getReadOnlyStakingContract();
1673
1687
  const [
1674
1688
  epoch,
1689
+ finalized,
1675
1690
  validatorMinStake,
1676
1691
  delegatorMinStake,
1677
1692
  activeCount,
@@ -1681,6 +1696,7 @@ var stakingActions = (client, publicClient) => {
1681
1696
  epochEven
1682
1697
  ] = await Promise.all([
1683
1698
  contract.read.epoch(),
1699
+ contract.read.finalized(),
1684
1700
  contract.read.validatorMinStake(),
1685
1701
  contract.read.delegatorMinStake(),
1686
1702
  contract.read.activeValidatorsCount(),
@@ -1690,8 +1706,7 @@ var stakingActions = (client, publicClient) => {
1690
1706
  contract.read.epochEven()
1691
1707
  ]);
1692
1708
  const currentEpochData = epoch % 2n === 0n ? epochEven : epochOdd;
1693
- const currentEpochStart = new Date(Number(currentEpochData.start) * 1e3);
1694
- const currentEpochEnd = currentEpochData.end > 0n ? new Date(Number(currentEpochData.end) * 1e3) : null;
1709
+ const currentEpochEnd = currentEpochData.end > 0n;
1695
1710
  let nextEpochEstimate = null;
1696
1711
  if (!currentEpochEnd) {
1697
1712
  const duration = epoch === 0n ? epochZeroMinDuration : epochMinDuration;
@@ -1700,20 +1715,42 @@ var stakingActions = (client, publicClient) => {
1700
1715
  }
1701
1716
  return {
1702
1717
  currentEpoch: epoch,
1718
+ lastFinalizedEpoch: finalized,
1703
1719
  validatorMinStake: formatStakingAmount(validatorMinStake),
1704
1720
  validatorMinStakeRaw: validatorMinStake,
1705
1721
  delegatorMinStake: formatStakingAmount(delegatorMinStake),
1706
1722
  delegatorMinStakeRaw: delegatorMinStake,
1707
1723
  activeValidatorsCount: activeCount,
1708
1724
  epochMinDuration,
1709
- currentEpochStart,
1710
- currentEpochEnd,
1711
- nextEpochEstimate,
1712
- inflation: formatStakingAmount(currentEpochData.inflation),
1713
- inflationRaw: currentEpochData.inflation,
1714
- totalWeight: currentEpochData.weight,
1715
- totalClaimed: formatStakingAmount(currentEpochData.claimed),
1716
- totalClaimedRaw: currentEpochData.claimed
1725
+ nextEpochEstimate
1726
+ };
1727
+ },
1728
+ getEpochData: async (epochNumber) => {
1729
+ const contract = getReadOnlyStakingContract();
1730
+ const [currentEpoch, epochOdd, epochEven] = await Promise.all([
1731
+ contract.read.epoch(),
1732
+ contract.read.epochOdd(),
1733
+ contract.read.epochEven()
1734
+ ]);
1735
+ if (epochNumber > currentEpoch) {
1736
+ throw new Error(`Epoch ${epochNumber} has not started yet (current: ${currentEpoch})`);
1737
+ }
1738
+ if (epochNumber < currentEpoch - 1n && currentEpoch > 0n) {
1739
+ throw new Error(`Epoch ${epochNumber} data no longer available (only current and previous epoch stored)`);
1740
+ }
1741
+ const epochData = epochNumber % 2n === 0n ? epochEven : epochOdd;
1742
+ return {
1743
+ start: epochData.start,
1744
+ end: epochData.end,
1745
+ inflation: epochData.inflation,
1746
+ weight: epochData.weight,
1747
+ weightDeposit: epochData.weightDeposit,
1748
+ weightWithdrawal: epochData.weightWithdrawal,
1749
+ vcount: epochData.vcount,
1750
+ claimed: epochData.claimed,
1751
+ stakeDeposit: epochData.stakeDeposit,
1752
+ stakeWithdrawal: epochData.stakeWithdrawal,
1753
+ slashed: epochData.slashed
1717
1754
  };
1718
1755
  },
1719
1756
  getActiveValidators: async () => {
@@ -1747,6 +1784,11 @@ var stakingActions = (client, publicClient) => {
1747
1784
  permanentlyBanned: v.permanentlyBanned
1748
1785
  }));
1749
1786
  },
1787
+ getSlashingAddress: async () => {
1788
+ const contract = getReadOnlyStakingContract();
1789
+ const externalContracts = await contract.read.contracts();
1790
+ return externalContracts[4];
1791
+ },
1750
1792
  getStakingContract,
1751
1793
  parseStakingAmount,
1752
1794
  formatStakingAmount
@@ -1785,7 +1827,7 @@ function chainActions(client) {
1785
1827
  }
1786
1828
 
1787
1829
  // src/client/client.ts
1788
- var getCustomTransportConfig = (config) => {
1830
+ var getCustomTransportConfig = (config, chainConfig) => {
1789
1831
  const isAddress = typeof config.account !== "object";
1790
1832
  return {
1791
1833
  async request({ method, params = [] }) {
@@ -1801,11 +1843,8 @@ var getCustomTransportConfig = (config) => {
1801
1843
  }
1802
1844
  }
1803
1845
  {
1804
- if (!config.chain) {
1805
- throw new Error("Chain is not set");
1806
- }
1807
1846
  try {
1808
- const response = await fetch(config.chain.rpcUrls.default.http[0], {
1847
+ const response = await fetch(chainConfig.rpcUrls.default.http[0], {
1809
1848
  method: "POST",
1810
1849
  headers: {
1811
1850
  "Content-Type": "application/json"
@@ -1835,7 +1874,7 @@ var createClient = (config = { chain: localnet }) => {
1835
1874
  if (config.endpoint) {
1836
1875
  chainConfig.rpcUrls.default.http = [config.endpoint];
1837
1876
  }
1838
- const customTransport = custom(getCustomTransportConfig(config), { retryCount: 0, retryDelay: 0 });
1877
+ const customTransport = custom(getCustomTransportConfig(config, chainConfig), { retryCount: 0, retryDelay: 0 });
1839
1878
  const publicClient = createPublicClient(chainConfig, customTransport).extend(
1840
1879
  publicActions
1841
1880
  );
@@ -1,3 +1,3 @@
1
1
  export { Account, Address } from 'viem';
2
- export { I as BannedValidatorInfo, d as CalldataAddress, C as CalldataEncodable, i as ContractMethod, h as ContractMethodBase, f as ContractParamsArraySchemaElement, g as ContractParamsSchema, j as ContractSchema, o as DECIDED_STATES, a as DecodedCallData, D as DecodedDeployData, a3 as DelegatorClaimOptions, a2 as DelegatorExitOptions, a1 as DelegatorJoinOptions, R as DelegatorJoinResult, K as EpochData, L as EpochInfo, G as GenLayerClient, e as GenLayerMethod, b as GenLayerRawTransaction, c as GenLayerTransaction, H as Hash, M as MethodDescription, N as Network, P as PendingDeposit, F as PendingWithdrawal, a0 as SetIdentityOptions, $ as SetOperatorOptions, y as SnapSource, J as StakeInfo, a4 as StakingActions, z as StakingContract, O as StakingTransactionResult, k as TransactionHash, x as TransactionHashVariant, m as TransactionResult, r as TransactionResultNameToNumber, l as TransactionStatus, w as TransactionType, Z as ValidatorClaimOptions, X as ValidatorDepositOptions, Y as ValidatorExitOptions, B as ValidatorIdentity, E as ValidatorInfo, U as ValidatorJoinOptions, Q as ValidatorJoinResult, _ as ValidatorPrimeOptions, A as ValidatorView, s as VoteType, W as WithdrawalCommit, p as isDecidedState, q as transactionResultNumberToName, n as transactionsStatusNameToNumber, t as transactionsStatusNumberToName, u as voteTypeNameToNumber, v as voteTypeNumberToName } from '../index-C3KT8eu_.cjs';
2
+ export { I as BannedValidatorInfo, d as CalldataAddress, C as CalldataEncodable, i as ContractMethod, h as ContractMethodBase, f as ContractParamsArraySchemaElement, g as ContractParamsSchema, j as ContractSchema, o as DECIDED_STATES, a as DecodedCallData, D as DecodedDeployData, a4 as DelegatorClaimOptions, a3 as DelegatorExitOptions, a2 as DelegatorJoinOptions, U as DelegatorJoinResult, K as EpochData, O as EpochInfo, G as GenLayerClient, e as GenLayerMethod, b as GenLayerRawTransaction, c as GenLayerTransaction, H as Hash, L as LeaderReceipt, M as MethodDescription, N as Network, P as PendingDeposit, F as PendingWithdrawal, a1 as SetIdentityOptions, a0 as SetOperatorOptions, y as SnapSource, J as StakeInfo, a5 as StakingActions, z as StakingContract, Q as StakingTransactionResult, k as TransactionHash, x as TransactionHashVariant, m as TransactionResult, r as TransactionResultNameToNumber, l as TransactionStatus, w as TransactionType, _ as ValidatorClaimOptions, Y as ValidatorDepositOptions, Z as ValidatorExitOptions, B as ValidatorIdentity, E as ValidatorInfo, X as ValidatorJoinOptions, R as ValidatorJoinResult, $ as ValidatorPrimeOptions, A as ValidatorView, s as VoteType, W as WithdrawalCommit, p as isDecidedState, q as transactionResultNumberToName, n as transactionsStatusNameToNumber, t as transactionsStatusNumberToName, u as voteTypeNameToNumber, v as voteTypeNumberToName } from '../index-D9ONjYgl.cjs';
3
3
  export { G as GenLayerChain } from '../chains-B7B7UXdn.cjs';
@@ -1,3 +1,3 @@
1
1
  export { Account, Address } from 'viem';
2
- export { I as BannedValidatorInfo, d as CalldataAddress, C as CalldataEncodable, i as ContractMethod, h as ContractMethodBase, f as ContractParamsArraySchemaElement, g as ContractParamsSchema, j as ContractSchema, o as DECIDED_STATES, a as DecodedCallData, D as DecodedDeployData, a3 as DelegatorClaimOptions, a2 as DelegatorExitOptions, a1 as DelegatorJoinOptions, R as DelegatorJoinResult, K as EpochData, L as EpochInfo, G as GenLayerClient, e as GenLayerMethod, b as GenLayerRawTransaction, c as GenLayerTransaction, H as Hash, M as MethodDescription, N as Network, P as PendingDeposit, F as PendingWithdrawal, a0 as SetIdentityOptions, $ as SetOperatorOptions, y as SnapSource, J as StakeInfo, a4 as StakingActions, z as StakingContract, O as StakingTransactionResult, k as TransactionHash, x as TransactionHashVariant, m as TransactionResult, r as TransactionResultNameToNumber, l as TransactionStatus, w as TransactionType, Z as ValidatorClaimOptions, X as ValidatorDepositOptions, Y as ValidatorExitOptions, B as ValidatorIdentity, E as ValidatorInfo, U as ValidatorJoinOptions, Q as ValidatorJoinResult, _ as ValidatorPrimeOptions, A as ValidatorView, s as VoteType, W as WithdrawalCommit, p as isDecidedState, q as transactionResultNumberToName, n as transactionsStatusNameToNumber, t as transactionsStatusNumberToName, u as voteTypeNameToNumber, v as voteTypeNumberToName } from '../index-BNui_XYa.js';
2
+ export { I as BannedValidatorInfo, d as CalldataAddress, C as CalldataEncodable, i as ContractMethod, h as ContractMethodBase, f as ContractParamsArraySchemaElement, g as ContractParamsSchema, j as ContractSchema, o as DECIDED_STATES, a as DecodedCallData, D as DecodedDeployData, a4 as DelegatorClaimOptions, a3 as DelegatorExitOptions, a2 as DelegatorJoinOptions, U as DelegatorJoinResult, K as EpochData, O as EpochInfo, G as GenLayerClient, e as GenLayerMethod, b as GenLayerRawTransaction, c as GenLayerTransaction, H as Hash, L as LeaderReceipt, M as MethodDescription, N as Network, P as PendingDeposit, F as PendingWithdrawal, a1 as SetIdentityOptions, a0 as SetOperatorOptions, y as SnapSource, J as StakeInfo, a5 as StakingActions, z as StakingContract, Q as StakingTransactionResult, k as TransactionHash, x as TransactionHashVariant, m as TransactionResult, r as TransactionResultNameToNumber, l as TransactionStatus, w as TransactionType, _ as ValidatorClaimOptions, Y as ValidatorDepositOptions, Z as ValidatorExitOptions, B as ValidatorIdentity, E as ValidatorInfo, X as ValidatorJoinOptions, R as ValidatorJoinResult, $ as ValidatorPrimeOptions, A as ValidatorView, s as VoteType, W as WithdrawalCommit, p as isDecidedState, q as transactionResultNumberToName, n as transactionsStatusNameToNumber, t as transactionsStatusNumberToName, u as voteTypeNameToNumber, v as voteTypeNumberToName } from '../index-ZDqJWXj0.js';
3
3
  export { G as GenLayerChain } from '../chains-B7B7UXdn.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "genlayer-js",
3
3
  "type": "module",
4
- "version": "0.18.8",
4
+ "version": "0.18.10",
5
5
  "description": "GenLayer JavaScript SDK",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -6,6 +6,9 @@ export const VALIDATOR_WALLET_ABI = [
6
6
  {name: "TransferFailed", type: "error", inputs: []},
7
7
  {name: "OperatorTransferNotReady", type: "error", inputs: []},
8
8
  {name: "NoPendingOperator", type: "error", inputs: []},
9
+ // OpenZeppelin Ownable errors
10
+ {name: "OwnableUnauthorizedAccount", type: "error", inputs: [{name: "account", type: "address"}]},
11
+ {name: "OwnableInvalidOwner", type: "error", inputs: [{name: "owner", type: "address"}]},
9
12
 
10
13
  // Functions
11
14
  {
@@ -311,6 +314,13 @@ export const STAKING_ABI = [
311
314
  inputs: [],
312
315
  outputs: [{name: "", type: "uint256"}],
313
316
  },
317
+ {
318
+ name: "finalized",
319
+ type: "function",
320
+ stateMutability: "view",
321
+ inputs: [],
322
+ outputs: [{name: "", type: "uint256"}],
323
+ },
314
324
  {
315
325
  name: "validatorMinStake",
316
326
  type: "function",
@@ -359,6 +369,7 @@ export const STAKING_ABI = [
359
369
  {name: "claimed", type: "uint256"},
360
370
  {name: "stakeDeposit", type: "uint256"},
361
371
  {name: "stakeWithdrawal", type: "uint256"},
372
+ {name: "slashed", type: "uint256"},
362
373
  ],
363
374
  },
364
375
  ],
@@ -383,6 +394,7 @@ export const STAKING_ABI = [
383
394
  {name: "claimed", type: "uint256"},
384
395
  {name: "stakeDeposit", type: "uint256"},
385
396
  {name: "stakeWithdrawal", type: "uint256"},
397
+ {name: "slashed", type: "uint256"},
386
398
  ],
387
399
  },
388
400
  ],
@@ -623,4 +635,53 @@ export const STAKING_ABI = [
623
635
  {name: "amount", type: "uint256", indexed: false},
624
636
  ],
625
637
  },
638
+ {
639
+ name: "ValidatorPrime",
640
+ type: "event",
641
+ inputs: [
642
+ {name: "validator", type: "address", indexed: false},
643
+ {name: "epoch", type: "uint256", indexed: false},
644
+ {name: "validatorRewards", type: "uint256", indexed: false},
645
+ {name: "delegatorRewards", type: "uint256", indexed: false},
646
+ ],
647
+ },
648
+ // External contracts getter
649
+ {
650
+ name: "contracts",
651
+ type: "function",
652
+ stateMutability: "view",
653
+ inputs: [],
654
+ outputs: [
655
+ {name: "gen", type: "address"},
656
+ {name: "transactions", type: "address"},
657
+ {name: "idleness", type: "address"},
658
+ {name: "tribunal", type: "address"},
659
+ {name: "slashing", type: "address"},
660
+ {name: "consensus", type: "address"},
661
+ {name: "validatorWalletFactory", type: "address"},
662
+ {name: "nftMinter", type: "address"},
663
+ ],
664
+ },
665
+ ] as const;
666
+
667
+ // Slash contract ABI for slashing events
668
+ export const SLASH_ABI = [
669
+ {
670
+ name: "SlashedFromIdleness",
671
+ type: "event",
672
+ inputs: [
673
+ {name: "validator", type: "address", indexed: true},
674
+ {name: "txId", type: "bytes32", indexed: false},
675
+ {name: "epoch", type: "uint256", indexed: false},
676
+ {name: "percentage", type: "uint256", indexed: false},
677
+ ],
678
+ },
679
+ {
680
+ name: "SlashEnacted",
681
+ type: "event",
682
+ inputs: [
683
+ {name: "validator", type: "address", indexed: true},
684
+ {name: "epoch", type: "uint256", indexed: false},
685
+ ],
686
+ },
626
687
  ] as const;
@@ -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-alpha.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: {
@@ -33,7 +33,7 @@ interface ClientConfig {
33
33
  provider?: EthereumProvider; // Custom provider for wallet framework integration
34
34
  }
35
35
 
36
- const getCustomTransportConfig = (config: ClientConfig) => {
36
+ const getCustomTransportConfig = (config: ClientConfig, chainConfig: GenLayerChain) => {
37
37
  const isAddress = typeof config.account !== "object";
38
38
 
39
39
  return {
@@ -51,12 +51,8 @@ const getCustomTransportConfig = (config: ClientConfig) => {
51
51
  }
52
52
 
53
53
  {
54
- if (!config.chain) {
55
- throw new Error("Chain is not set");
56
- }
57
-
58
54
  try {
59
- const response = await fetch(config.chain.rpcUrls.default.http[0], {
55
+ const response = await fetch(chainConfig.rpcUrls.default.http[0], {
60
56
  method: "POST",
61
57
  headers: {
62
58
  "Content-Type": "application/json",
@@ -91,7 +87,7 @@ export const createClient = (config: ClientConfig = {chain: localnet}): GenLayer
91
87
  chainConfig.rpcUrls.default.http = [config.endpoint];
92
88
  }
93
89
 
94
- const customTransport = custom(getCustomTransportConfig(config), {retryCount: 0, retryDelay: 0});
90
+ const customTransport = custom(getCustomTransportConfig(config, chainConfig as GenLayerChain), {retryCount: 0, retryDelay: 0});
95
91
  const publicClient = createPublicClient(chainConfig as GenLayerChain, customTransport).extend(
96
92
  publicActions,
97
93
  );
@@ -301,8 +301,8 @@ const _sendTransaction = async ({
301
301
  }
302
302
 
303
303
  const validatedSenderAccount = validateAccount(senderAccount);
304
-
305
304
  const nonce = await client.getCurrentNonce({address: validatedSenderAccount.address});
305
+
306
306
  let estimatedGas: bigint;
307
307
  try {
308
308
  estimatedGas = await client.estimateTransactionGas({
@@ -311,58 +311,77 @@ const _sendTransaction = async ({
311
311
  data: encodedData,
312
312
  value: value,
313
313
  });
314
- } catch (error) {
315
- console.warn("Gas estimation failed, using fallback value:", error);
314
+ } catch (err) {
315
+ console.error("Gas estimation failed, using default 200_000:", err);
316
316
  estimatedGas = 200_000n;
317
317
  }
318
- const transactionRequest = await client.prepareTransactionRequest({
319
- account: validatedSenderAccount,
320
- to: client.chain.consensusMainContract?.address as Address,
321
- data: encodedData,
322
- type: "legacy",
323
- nonce: Number(nonce),
324
- value: value,
325
- gas: estimatedGas,
326
- });
327
318
 
328
- if (validatedSenderAccount?.type !== "local") {
329
- const formattedRequest = {
330
- from: transactionRequest.from,
331
- to: transactionRequest.to,
319
+ // For local accounts, build transaction request directly to avoid viem's
320
+ // prepareTransactionRequest which calls eth_fillTransaction (unsupported by GenLayer RPC)
321
+ if (validatedSenderAccount?.type === "local") {
322
+ if (!validatedSenderAccount?.signTransaction) {
323
+ throw new Error("Account does not support signTransaction");
324
+ }
325
+
326
+ const gasPriceHex = (await client.request({
327
+ method: "eth_gasPrice",
328
+ })) as string;
329
+
330
+ const transactionRequest = {
331
+ account: validatedSenderAccount,
332
+ to: client.chain.consensusMainContract?.address as Address,
332
333
  data: encodedData,
333
- value: transactionRequest.value ? `0x${transactionRequest.value.toString(16)}` : "0x0",
334
- gas: transactionRequest.gas ? `0x${transactionRequest.gas.toString(16)}` : "0x5208",
334
+ type: "legacy" as const,
335
+ nonce: Number(nonce),
336
+ value: value,
337
+ gas: estimatedGas,
338
+ gasPrice: BigInt(gasPriceHex),
339
+ chainId: client.chain.id,
335
340
  };
336
341
 
337
- return await client.request({
338
- method: "eth_sendTransaction",
339
- params: [formattedRequest as any],
340
- });
341
- }
342
+ const serializedTransaction = await validatedSenderAccount.signTransaction(transactionRequest);
343
+ const txHash = await client.sendRawTransaction({serializedTransaction: serializedTransaction});
344
+ const receipt = await publicClient.waitForTransactionReceipt({hash: txHash});
342
345
 
343
- if (!validatedSenderAccount?.signTransaction) {
344
- throw new Error("Account does not support signTransaction");
345
- }
346
-
347
- const serializedTransaction = await validatedSenderAccount.signTransaction(transactionRequest);
346
+ if (receipt.status === "reverted") {
347
+ throw new Error("Transaction reverted");
348
+ }
348
349
 
349
- const txHash = await client.sendRawTransaction({serializedTransaction: serializedTransaction});
350
+ const newTxEvents = parseEventLogs({
351
+ abi: client.chain.consensusMainContract?.abi as any,
352
+ eventName: "NewTransaction",
353
+ logs: receipt.logs,
354
+ }) as unknown as {args: {txId: `0x${string}`}}[];
350
355
 
351
- const receipt = await publicClient.waitForTransactionReceipt({hash: txHash});
356
+ if (newTxEvents.length === 0) {
357
+ throw new Error("Transaction not processed by consensus");
358
+ }
352
359
 
353
- if (receipt.status === "reverted") {
354
- throw new Error("Transaction reverted");
360
+ return newTxEvents[0].args["txId"];
355
361
  }
356
362
 
357
- const newTxEvents = parseEventLogs({
358
- abi: client.chain.consensusMainContract?.abi as any,
359
- eventName: "NewTransaction",
360
- logs: receipt.logs,
361
- }) as unknown as {args: {txId: `0x${string}`}}[];
363
+ // For external wallets (e.g., MetaMask via AppKit), use prepareTransactionRequest
364
+ // which will route eth_* calls through the provider
365
+ const transactionRequest = await client.prepareTransactionRequest({
366
+ account: validatedSenderAccount,
367
+ to: client.chain.consensusMainContract?.address as Address,
368
+ data: encodedData,
369
+ type: "legacy",
370
+ nonce: Number(nonce),
371
+ value: value,
372
+ gas: estimatedGas,
373
+ });
362
374
 
363
- if (newTxEvents.length === 0) {
364
- throw new Error("Transaction not processed by consensus");
365
- }
375
+ const formattedRequest = {
376
+ from: transactionRequest.from,
377
+ to: transactionRequest.to,
378
+ data: encodedData,
379
+ value: transactionRequest.value ? `0x${transactionRequest.value.toString(16)}` : "0x0",
380
+ gas: transactionRequest.gas ? `0x${transactionRequest.gas.toString(16)}` : "0x5208",
381
+ };
366
382
 
367
- return newTxEvents[0].args["txId"];
383
+ return await client.request({
384
+ method: "eth_sendTransaction",
385
+ params: [formattedRequest as any],
386
+ });
368
387
  };
@@ -8,6 +8,7 @@ import {
8
8
  BannedValidatorInfo,
9
9
  StakeInfo,
10
10
  EpochInfo,
11
+ EpochData,
11
12
  StakingTransactionResult,
12
13
  ValidatorJoinResult,
13
14
  DelegatorJoinResult,
@@ -559,6 +560,7 @@ export const stakingActions = (
559
560
 
560
561
  const [
561
562
  epoch,
563
+ finalized,
562
564
  validatorMinStake,
563
565
  delegatorMinStake,
564
566
  activeCount,
@@ -568,6 +570,7 @@ export const stakingActions = (
568
570
  epochEven,
569
571
  ] = await Promise.all([
570
572
  contract.read.epoch() as Promise<bigint>,
573
+ contract.read.finalized() as Promise<bigint>,
571
574
  contract.read.validatorMinStake() as Promise<bigint>,
572
575
  contract.read.delegatorMinStake() as Promise<bigint>,
573
576
  contract.read.activeValidatorsCount() as Promise<bigint>,
@@ -577,13 +580,11 @@ export const stakingActions = (
577
580
  contract.read.epochEven() as Promise<any>,
578
581
  ]);
579
582
 
580
- // Current epoch data (even epochs use epochEven, odd use epochOdd)
583
+ // Current epoch data for next epoch estimate
581
584
  const currentEpochData = epoch % 2n === 0n ? epochEven : epochOdd;
582
- const currentEpochStart = new Date(Number(currentEpochData.start) * 1000);
583
- const currentEpochEnd = currentEpochData.end > 0n ? new Date(Number(currentEpochData.end) * 1000) : null;
585
+ const currentEpochEnd = currentEpochData.end > 0n;
584
586
 
585
587
  // Estimate next epoch: current start + min duration (if epoch hasn't ended)
586
- // Epoch 0 uses epochZeroMinDuration, all other epochs use epochMinDuration
587
588
  let nextEpochEstimate: Date | null = null;
588
589
  if (!currentEpochEnd) {
589
590
  const duration = epoch === 0n ? epochZeroMinDuration : epochMinDuration;
@@ -593,20 +594,50 @@ export const stakingActions = (
593
594
 
594
595
  return {
595
596
  currentEpoch: epoch,
597
+ lastFinalizedEpoch: finalized,
596
598
  validatorMinStake: formatStakingAmount(validatorMinStake),
597
599
  validatorMinStakeRaw: validatorMinStake,
598
600
  delegatorMinStake: formatStakingAmount(delegatorMinStake),
599
601
  delegatorMinStakeRaw: delegatorMinStake,
600
602
  activeValidatorsCount: activeCount,
601
603
  epochMinDuration,
602
- currentEpochStart,
603
- currentEpochEnd,
604
604
  nextEpochEstimate,
605
- inflation: formatStakingAmount(currentEpochData.inflation),
606
- inflationRaw: currentEpochData.inflation,
607
- totalWeight: currentEpochData.weight,
608
- totalClaimed: formatStakingAmount(currentEpochData.claimed),
609
- totalClaimedRaw: currentEpochData.claimed,
605
+ };
606
+ },
607
+
608
+ getEpochData: async (epochNumber: bigint): Promise<EpochData> => {
609
+ const contract = getReadOnlyStakingContract();
610
+
611
+ const [currentEpoch, epochOdd, epochEven] = await Promise.all([
612
+ contract.read.epoch() as Promise<bigint>,
613
+ contract.read.epochOdd() as Promise<any>,
614
+ contract.read.epochEven() as Promise<any>,
615
+ ]);
616
+
617
+ // Epochs alternate between odd/even storage slots
618
+ // Current epoch N uses: N % 2 === 0 ? epochEven : epochOdd
619
+ // We can only access current epoch and previous epoch (N-1)
620
+ if (epochNumber > currentEpoch) {
621
+ throw new Error(`Epoch ${epochNumber} has not started yet (current: ${currentEpoch})`);
622
+ }
623
+ if (epochNumber < currentEpoch - 1n && currentEpoch > 0n) {
624
+ throw new Error(`Epoch ${epochNumber} data no longer available (only current and previous epoch stored)`);
625
+ }
626
+
627
+ const epochData = epochNumber % 2n === 0n ? epochEven : epochOdd;
628
+
629
+ 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,
610
641
  };
611
642
  },
612
643
 
@@ -646,6 +677,13 @@ export const stakingActions = (
646
677
  }));
647
678
  },
648
679
 
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
+
649
687
  getStakingContract,
650
688
  parseStakingAmount,
651
689
  formatStakingAmount,