From 5be7a5da51decffe842332d8595f7ee5a9a45def Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 11 Mar 2026 11:36:42 +0000 Subject: [PATCH 1/5] feat: add testnet-bradbury chain configuration Add Bradbury testnet as a new network option with v0.6 ConsensusMain and ConsensusData ABIs, reusing the existing Staking ABI. Bradbury is the developer-facing testnet for deploying intelligent contracts with LLM validation. --- CLAUDE.md | 4 +- README.md | 8 +- src/chains/index.ts | 1 + src/chains/testnetBradbury.ts | 3358 +++++++++++++++++++++++++++++++++ src/types/network.ts | 2 +- 5 files changed, 3366 insertions(+), 7 deletions(-) create mode 100644 src/chains/testnetBradbury.ts diff --git a/CLAUDE.md b/CLAUDE.md index a21d5d7..ded2d53 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,7 +25,7 @@ npm run test:watch # Watch mode The SDK exports three entry points (see `package.json` exports): - `genlayer-js` - Main: client, accounts, transaction decoders, staking utils -- `genlayer-js/chains` - Chain configs: `localnet`, `studionet`, `testnetAsimov` +- `genlayer-js/chains` - Chain configs: `localnet`, `studionet`, `testnetAsimov`, `testnetBradbury` - `genlayer-js/types` - TypeScript types for all SDK entities ### Client Factory Pattern @@ -45,7 +45,7 @@ Each action module (`src//actions.ts`) returns an object of methods that `GenLayerChain` extends viem's `Chain` with GenLayer-specific properties: - `isStudio` - Whether using studio-based localnet - `consensusMainContract` / `consensusDataContract` - On-chain consensus contracts -- `stakingContract` - Staking contract (testnet-asimov only) +- `stakingContract` - Staking contract (testnet-bradbury and testnet-asimov) - `defaultNumberOfInitialValidators` / `defaultConsensusMaxRotations` ### Calldata Encoding diff --git a/README.md b/README.md index 89d8ce0..55e8a1c 100644 --- a/README.md +++ b/README.md @@ -108,15 +108,15 @@ const receipt = await client.waitForTransactionReceipt({ ``` ### Staking Operations -The SDK provides staking functionality for validators and delegators on testnet-asimov. +The SDK provides staking functionality for validators and delegators on testnet-bradbury (and testnet-asimov). ```typescript -import { testnetAsimov } from 'genlayer-js/chains'; +import { testnetBradbury } from 'genlayer-js/chains'; import { createClient, createAccount } from "genlayer-js"; const account = createAccount(); const client = createClient({ - chain: testnetAsimov, + chain: testnetBradbury, account, }); @@ -160,7 +160,7 @@ const delegateResult = await client.delegatorJoin({ * **Client Creation**: Easily create and configure a client to connect to GenLayer's network. * **Transaction Handling**: Send and manage transactions on the GenLayer network. -* **Staking**: Full staking support for validators and delegators on testnet-asimov. +* **Staking**: Full staking support for validators and delegators on testnet-bradbury and testnet-asimov. * **Wallet Integration***: Seamless integration with MetaMask for managing user accounts. * **Gas Estimation***: Estimate gas fees for executing transactions on GenLayer. diff --git a/src/chains/index.ts b/src/chains/index.ts index 3423f3d..6979228 100644 --- a/src/chains/index.ts +++ b/src/chains/index.ts @@ -2,3 +2,4 @@ export {localnet} from "./localnet"; export {studionet} from "./studionet"; export {testnetAsimov} from "./testnetAsimov"; +export {testnetBradbury} from "./testnetBradbury"; diff --git a/src/chains/testnetBradbury.ts b/src/chains/testnetBradbury.ts new file mode 100644 index 0000000..5614944 --- /dev/null +++ b/src/chains/testnetBradbury.ts @@ -0,0 +1,3358 @@ +import {Address, defineChain} from "viem"; +import {GenLayerChain} from "@/types"; +import {STAKING_ABI} from "@/abi/staking"; + +const TESTNET_JSON_RPC_URL = "https://zksync-os-testnet-genlayer.zksync.dev"; +const TESTNET_WS_URL = "wss://zksync-os-testnet-genlayer.zksync.dev/ws"; + +const STAKING_CONTRACT = { + address: "0x4A4449E617F8D10FDeD0b461CadEf83939E821A5" as Address, + abi: STAKING_ABI, +}; +const EXPLORER_URL = "https://explorer-bradbury.genlayer.com/"; +const CONSENSUS_MAIN_CONTRACT = { + address: "0x0112Bf6e83497965A5fdD6Dad1E447a6E004271D" as Address, + abi: [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CallerNotMessages", + "type": "error" + }, + { + "inputs": [], + "name": "CanNotAppeal", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDeploymentWithSalt", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGhostContract", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRevealLeaderData", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidVote", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "oldActivator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newActivator", + "type": "address" + } + ], + "name": "ActivatorReplaced", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "addressManager", + "type": "address" + } + ], + "name": "AddressManagerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "enum ITransactions.TransactionStatus", + "name": "newStatus", + "type": "uint8" + } + ], + "name": "AllVotesCommitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "appellant", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "bond", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "validators", + "type": "address[]" + } + ], + "name": "AppealStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "attempted", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "succeeded", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "failed", + "type": "uint256" + } + ], + "name": "BatchFinalizationCompleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "txSlot", + "type": "uint256" + } + ], + "name": "CreatedTransaction", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "activator", + "type": "address" + } + ], + "name": "InternalMessageProcessed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "oldLeader", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newLeader", + "type": "address" + } + ], + "name": "LeaderIdlenessProcessed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "activator", + "type": "address" + } + ], + "name": "NewTransaction", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + } + ], + "name": "ProcessIdlenessAccepted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + } + ], + "name": "TransactionAccepted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "leader", + "type": "address" + } + ], + "name": "TransactionActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "cancelledBy", + "type": "address" + } + ], + "name": "TransactionCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + } + ], + "name": "TransactionFinalizationFailed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + } + ], + "name": "TransactionFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + } + ], + "name": "TransactionLeaderRevealed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "newLeader", + "type": "address" + } + ], + "name": "TransactionLeaderRotated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + } + ], + "name": "TransactionLeaderTimeout", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32[]", + "name": "txIds", + "type": "bytes32[]" + } + ], + "name": "TransactionNeedsRecomputation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "validators", + "type": "address[]" + } + ], + "name": "TransactionReceiptProposed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + } + ], + "name": "TransactionUndetermined", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tribunalIndex", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + } + ], + "name": "TribunalAppealVoteCommitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tribunalIndex", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum ITransactions.VoteType", + "name": "voteType", + "type": "uint8" + } + ], + "name": "TribunalAppealVoteRevealed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "oldValidator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newValidator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + } + ], + "name": "ValidatorReplaced", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "ValueWithdrawalFailed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isLastVote", + "type": "bool" + } + ], + "name": "VoteCommitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum ITransactions.VoteType", + "name": "voteType", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isLastVote", + "type": "bool" + }, + { + "indexed": false, + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + } + ], + "name": "VoteRevealed", + "type": "event" + }, + { + "inputs": [], + "name": "EVENTS_BATCH_SIZE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "_operator", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_vrfProof", + "type": "bytes" + } + ], + "name": "activateTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_numOfInitialValidators", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxRotations", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_calldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "_validUntil", + "type": "uint256" + } + ], + "name": "addTransaction", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "addressManager", + "outputs": [ + { + "internalType": "contract IAddressManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + } + ], + "name": "cancelTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_tribunalIndex", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_commitHash", + "type": "bytes32" + } + ], + "name": "commitTribunalAppealVote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_commitHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_validatorIndex", + "type": "uint256" + } + ], + "name": "commitVote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_numOfInitialValidators", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxRotations", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_calldata", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "_saltNonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_validUntil", + "type": "uint256" + } + ], + "name": "deploySalted", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "executeMessage", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "_txIds", + "type": "bytes32[]" + } + ], + "name": "finalizeIdlenessTxs", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + } + ], + "name": "finalizeTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + } + ], + "name": "flushExternalMessages", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAddressManager", + "outputs": [ + { + "internalType": "contract IAddressManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + } + ], + "name": "getPendingTransactionValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addressManager", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "isGhostContract", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + } + ], + "name": "leaderIdleness", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "saltAsAValidator", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "txExecutionHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "messagesAndOtherFieldsHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "otherExecutionFieldsHash", + "type": "bytes32" + }, + { + "internalType": "enum ITransactions.VoteType", + "name": "resultValue", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "enum IMessages.MessageType", + "name": "messageType", + "type": "uint8" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "onAcceptance", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "saltNonce", + "type": "uint256" + } + ], + "internalType": "struct IMessages.SubmittedMessage[]", + "name": "messages", + "type": "tuple[]" + } + ], + "internalType": "struct IConsensusMain.LeaderRevealVoteParams", + "name": "leaderRevealVoteParams", + "type": "tuple" + } + ], + "name": "leaderRevealVote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + } + ], + "name": "processIdleness", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_txExecutionHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_processingBlock", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_operator", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_eqBlocksOutputs", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "_vrfProof", + "type": "bytes" + } + ], + "name": "proposeReceipt", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "_txIds", + "type": "bytes32[]" + } + ], + "name": "redButtonFinalize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "ghost", + "type": "address" + } + ], + "name": "registerGhostContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_tribunalIndex", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_voteHash", + "type": "bytes32" + }, + { + "internalType": "enum ITransactions.VoteType", + "name": "_voteType", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "_otherExecutionFieldsHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_nonce", + "type": "uint256" + } + ], + "name": "revealTribunalAppealVote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_voteHash", + "type": "bytes32" + }, + { + "internalType": "enum ITransactions.VoteType", + "name": "_voteType", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "_otherExecutionFieldsHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_validatorIndex", + "type": "uint256" + } + ], + "name": "revealVote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addressManager", + "type": "address" + } + ], + "name": "setAddressManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + } + ], + "name": "submitAppeal", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +], + bytecode: "", +}; + +const CONSENSUS_DATA_CONTRACT = { + address: "0x85D7bf947A512Fc640C75327A780c90847267697" as Address, + abi: [ + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "addressManager", + "outputs": [ + { + "internalType": "contract IAddressManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_currentTimestamp", + "type": "uint256" + } + ], + "name": "canFinalize", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "getLatestAcceptedTransaction", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "currentTimestamp", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "initialRotations", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "txSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "createdTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastVoteTimestamp", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "randomSeed", + "type": "bytes32" + }, + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "txExecutionHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "txCalldata", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "eqBlocksOutputs", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "enum IMessages.MessageType", + "name": "messageType", + "type": "uint8" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "onAcceptance", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "saltNonce", + "type": "uint256" + } + ], + "internalType": "struct IMessages.SubmittedMessage[]", + "name": "messages", + "type": "tuple[]" + }, + { + "internalType": "enum IQueues.QueueType", + "name": "queueType", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "queuePosition", + "type": "uint256" + }, + { + "internalType": "address", + "name": "activator", + "type": "address" + }, + { + "internalType": "address", + "name": "lastLeader", + "type": "address" + }, + { + "internalType": "enum ITransactions.TransactionStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "activationBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proposalBlock", + "type": "uint256" + } + ], + "internalType": "struct ITransactions.ReadStateBlockRange", + "name": "readStateBlockRange", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "numOfRounds", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "round", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "leaderIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesCommitted", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesRevealed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "appealBond", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rotationsLeft", + "type": "uint256" + }, + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "address[]", + "name": "roundValidators", + "type": "address[]" + }, + { + "internalType": "enum ITransactions.VoteType[]", + "name": "validatorVotes", + "type": "uint8[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorVotesHash", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorResultHash", + "type": "bytes32[]" + } + ], + "internalType": "struct ITransactions.RoundData", + "name": "lastRound", + "type": "tuple" + }, + { + "internalType": "address[]", + "name": "consumedValidators", + "type": "address[]" + } + ], + "internalType": "struct ConsensusData.TransactionData", + "name": "inputData", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "startIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pageSize", + "type": "uint256" + } + ], + "name": "getLatestAcceptedTransactions", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "currentTimestamp", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "initialRotations", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "txSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "createdTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastVoteTimestamp", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "randomSeed", + "type": "bytes32" + }, + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "txExecutionHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "txCalldata", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "eqBlocksOutputs", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "enum IMessages.MessageType", + "name": "messageType", + "type": "uint8" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "onAcceptance", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "saltNonce", + "type": "uint256" + } + ], + "internalType": "struct IMessages.SubmittedMessage[]", + "name": "messages", + "type": "tuple[]" + }, + { + "internalType": "enum IQueues.QueueType", + "name": "queueType", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "queuePosition", + "type": "uint256" + }, + { + "internalType": "address", + "name": "activator", + "type": "address" + }, + { + "internalType": "address", + "name": "lastLeader", + "type": "address" + }, + { + "internalType": "enum ITransactions.TransactionStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "activationBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proposalBlock", + "type": "uint256" + } + ], + "internalType": "struct ITransactions.ReadStateBlockRange", + "name": "readStateBlockRange", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "numOfRounds", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "round", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "leaderIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesCommitted", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesRevealed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "appealBond", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rotationsLeft", + "type": "uint256" + }, + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "address[]", + "name": "roundValidators", + "type": "address[]" + }, + { + "internalType": "enum ITransactions.VoteType[]", + "name": "validatorVotes", + "type": "uint8[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorVotesHash", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorResultHash", + "type": "bytes32[]" + } + ], + "internalType": "struct ITransactions.RoundData", + "name": "lastRound", + "type": "tuple" + }, + { + "internalType": "address[]", + "name": "consumedValidators", + "type": "address[]" + } + ], + "internalType": "struct ConsensusData.TransactionData[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "getLatestAcceptedTxCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "getLatestFinalizedTransaction", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "currentTimestamp", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "initialRotations", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "txSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "createdTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastVoteTimestamp", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "randomSeed", + "type": "bytes32" + }, + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "txExecutionHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "txCalldata", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "eqBlocksOutputs", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "enum IMessages.MessageType", + "name": "messageType", + "type": "uint8" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "onAcceptance", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "saltNonce", + "type": "uint256" + } + ], + "internalType": "struct IMessages.SubmittedMessage[]", + "name": "messages", + "type": "tuple[]" + }, + { + "internalType": "enum IQueues.QueueType", + "name": "queueType", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "queuePosition", + "type": "uint256" + }, + { + "internalType": "address", + "name": "activator", + "type": "address" + }, + { + "internalType": "address", + "name": "lastLeader", + "type": "address" + }, + { + "internalType": "enum ITransactions.TransactionStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "activationBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proposalBlock", + "type": "uint256" + } + ], + "internalType": "struct ITransactions.ReadStateBlockRange", + "name": "readStateBlockRange", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "numOfRounds", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "round", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "leaderIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesCommitted", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesRevealed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "appealBond", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rotationsLeft", + "type": "uint256" + }, + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "address[]", + "name": "roundValidators", + "type": "address[]" + }, + { + "internalType": "enum ITransactions.VoteType[]", + "name": "validatorVotes", + "type": "uint8[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorVotesHash", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorResultHash", + "type": "bytes32[]" + } + ], + "internalType": "struct ITransactions.RoundData", + "name": "lastRound", + "type": "tuple" + }, + { + "internalType": "address[]", + "name": "consumedValidators", + "type": "address[]" + } + ], + "internalType": "struct ConsensusData.TransactionData", + "name": "inputData", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "startIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "pageSize", + "type": "uint256" + } + ], + "name": "getLatestFinalizedTransactions", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "currentTimestamp", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "initialRotations", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "txSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "createdTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastVoteTimestamp", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "randomSeed", + "type": "bytes32" + }, + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "txExecutionHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "txCalldata", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "eqBlocksOutputs", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "enum IMessages.MessageType", + "name": "messageType", + "type": "uint8" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "onAcceptance", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "saltNonce", + "type": "uint256" + } + ], + "internalType": "struct IMessages.SubmittedMessage[]", + "name": "messages", + "type": "tuple[]" + }, + { + "internalType": "enum IQueues.QueueType", + "name": "queueType", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "queuePosition", + "type": "uint256" + }, + { + "internalType": "address", + "name": "activator", + "type": "address" + }, + { + "internalType": "address", + "name": "lastLeader", + "type": "address" + }, + { + "internalType": "enum ITransactions.TransactionStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "activationBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proposalBlock", + "type": "uint256" + } + ], + "internalType": "struct ITransactions.ReadStateBlockRange", + "name": "readStateBlockRange", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "numOfRounds", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "round", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "leaderIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesCommitted", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesRevealed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "appealBond", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rotationsLeft", + "type": "uint256" + }, + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "address[]", + "name": "roundValidators", + "type": "address[]" + }, + { + "internalType": "enum ITransactions.VoteType[]", + "name": "validatorVotes", + "type": "uint8[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorVotesHash", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorResultHash", + "type": "bytes32[]" + } + ], + "internalType": "struct ITransactions.RoundData", + "name": "lastRound", + "type": "tuple" + }, + { + "internalType": "address[]", + "name": "consumedValidators", + "type": "address[]" + } + ], + "internalType": "struct ConsensusData.TransactionData[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "getLatestFinalizedTxCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + } + ], + "name": "getTransactionAllData", + "outputs": [ + { + "components": [ + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "enum ITransactions.VoteType", + "name": "txExecutionResult", + "type": "uint8" + }, + { + "internalType": "enum ITransactions.TransactionStatus", + "name": "previousStatus", + "type": "uint8" + }, + { + "internalType": "enum ITransactions.TransactionStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "address", + "name": "txOrigin", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "activator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "txSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "initialRotations", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "numOfInitialValidators", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "randomSeed", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "txExecutionHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "resultHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "txCalldata", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "eqBlocksOutputs", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "activationBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proposalBlock", + "type": "uint256" + } + ], + "internalType": "struct ITransactions.ReadStateBlockRange[]", + "name": "readStateBlockRanges", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "validUntil", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct ITransactions.Transaction", + "name": "transaction", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "round", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "leaderIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesCommitted", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesRevealed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "appealBond", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rotationsLeft", + "type": "uint256" + }, + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "address[]", + "name": "roundValidators", + "type": "address[]" + }, + { + "internalType": "enum ITransactions.VoteType[]", + "name": "validatorVotes", + "type": "uint8[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorVotesHash", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorResultHash", + "type": "bytes32[]" + } + ], + "internalType": "struct ITransactions.RoundData[]", + "name": "roundsData", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_timestamp", + "type": "uint256" + } + ], + "name": "getTransactionData", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "currentTimestamp", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "initialRotations", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "txSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "createdTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastVoteTimestamp", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "randomSeed", + "type": "bytes32" + }, + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "txExecutionHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "txCalldata", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "eqBlocksOutputs", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "enum IMessages.MessageType", + "name": "messageType", + "type": "uint8" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "onAcceptance", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "saltNonce", + "type": "uint256" + } + ], + "internalType": "struct IMessages.SubmittedMessage[]", + "name": "messages", + "type": "tuple[]" + }, + { + "internalType": "enum IQueues.QueueType", + "name": "queueType", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "queuePosition", + "type": "uint256" + }, + { + "internalType": "address", + "name": "activator", + "type": "address" + }, + { + "internalType": "address", + "name": "lastLeader", + "type": "address" + }, + { + "internalType": "enum ITransactions.TransactionStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "txId", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "activationBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "proposalBlock", + "type": "uint256" + } + ], + "internalType": "struct ITransactions.ReadStateBlockRange", + "name": "readStateBlockRange", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "numOfRounds", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "round", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "leaderIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesCommitted", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "votesRevealed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "appealBond", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rotationsLeft", + "type": "uint256" + }, + { + "internalType": "enum ITransactions.ResultType", + "name": "result", + "type": "uint8" + }, + { + "internalType": "address[]", + "name": "roundValidators", + "type": "address[]" + }, + { + "internalType": "enum ITransactions.VoteType[]", + "name": "validatorVotes", + "type": "uint8[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorVotesHash", + "type": "bytes32[]" + }, + { + "internalType": "bytes32[]", + "name": "validatorResultHash", + "type": "bytes32[]" + } + ], + "internalType": "struct ITransactions.RoundData", + "name": "lastRound", + "type": "tuple" + }, + { + "internalType": "address[]", + "name": "consumedValidators", + "type": "address[]" + } + ], + "internalType": "struct ConsensusData.TransactionData", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "_timestamp", + "type": "uint256" + } + ], + "name": "getTransactionStatus", + "outputs": [ + { + "internalType": "enum ITransactions.TransactionStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_txId", + "type": "bytes32" + } + ], + "name": "getValidatorsForLastRound", + "outputs": [ + { + "internalType": "address[]", + "name": "validators", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addressManager", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addressManager", + "type": "address" + } + ], + "name": "setAddressManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +], + bytecode: "", +}; + +export const testnetBradbury: GenLayerChain = defineChain({ + id: 0x107d, + isStudio: false, + name: "Genlayer Bradbury Testnet", + rpcUrls: { + default: { + http: [TESTNET_JSON_RPC_URL], + webSocket: [TESTNET_WS_URL], + }, + }, + nativeCurrency: { + name: "GEN Token", + symbol: "GEN", + decimals: 18, + }, + blockExplorers: { + default: { + name: "GenLayer Bradbury Explorer", + url: EXPLORER_URL, + }, + }, + testnet: true, + consensusMainContract: CONSENSUS_MAIN_CONTRACT, + consensusDataContract: CONSENSUS_DATA_CONTRACT, + stakingContract: STAKING_CONTRACT, + defaultNumberOfInitialValidators: 5, + defaultConsensusMaxRotations: 3, +}); diff --git a/src/types/network.ts b/src/types/network.ts index 5dd6b1d..8eaee39 100644 --- a/src/types/network.ts +++ b/src/types/network.ts @@ -1 +1 @@ -export type Network = "localnet" | "studionet" | "testnetAsimov" | "mainnet"; +export type Network = "localnet" | "studionet" | "testnetAsimov" | "testnetBradbury" | "mainnet"; From 74ff476a26bf0910d6632cdfaf51f3df1b742e97 Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 11 Mar 2026 11:38:34 +0000 Subject: [PATCH 2/5] fix: add testnetBradbury to wallet connect networks map --- src/wallet/connect.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wallet/connect.ts b/src/wallet/connect.ts index d4d4313..3985dd9 100644 --- a/src/wallet/connect.ts +++ b/src/wallet/connect.ts @@ -1,6 +1,7 @@ import {localnet} from "@/chains/localnet"; import {studionet} from "@/chains/studionet"; import {testnetAsimov} from "@/chains/testnetAsimov"; +import {testnetBradbury} from "@/chains/testnetBradbury"; import {GenLayerClient, GenLayerChain} from "@/types"; import {Network} from "@/types/network"; import {SnapSource} from "@/types/snapSource"; @@ -10,6 +11,7 @@ const networks = { localnet, studionet, testnetAsimov, + testnetBradbury, }; export const connect = async ( From c1226590da628eac6dfb286c2204b066c092112f Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 11 Mar 2026 11:42:07 +0000 Subject: [PATCH 3/5] test: run smoke tests against both Asimov and Bradbury --- tests/smoke.test.ts | 56 ++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/tests/smoke.test.ts b/tests/smoke.test.ts index a5e63a9..00c43aa 100644 --- a/tests/smoke.test.ts +++ b/tests/smoke.test.ts @@ -1,33 +1,42 @@ // tests/smoke.test.ts -// Smoke tests against live testnet-asimov to verify ABI compatibility and connectivity. +// Smoke tests against live testnets to verify ABI compatibility and connectivity. // Run with: npm run test:smoke // These are excluded from regular `npm test` to avoid CI dependence on testnet availability. import {describe, it, expect, beforeAll} from "vitest"; import {createPublicClient, http, webSocket, getContract, Address as ViemAddress} from "viem"; import {testnetAsimov} from "@/chains/testnetAsimov"; +import {testnetBradbury} from "@/chains/testnetBradbury"; import {createClient} from "@/client/client"; import {STAKING_ABI} from "@/abi/staking"; import {Address} from "@/types/accounts"; +import {GenLayerChain} from "@/types"; const TIMEOUT = 30_000; +const testnets: {name: string; chain: GenLayerChain}[] = [ + {name: "Asimov", chain: testnetAsimov}, + {name: "Bradbury", chain: testnetBradbury}, +]; + +for (const {name, chain} of testnets) { + // ─── HTTP RPC Connectivity ─────────────────────────────────────────────────── -describe("Testnet Asimov - HTTP RPC", () => { +describe(`Testnet ${name} - HTTP RPC`, () => { it("should fetch chain ID", async () => { const client = createPublicClient({ - chain: testnetAsimov, - transport: http(testnetAsimov.rpcUrls.default.http[0]), + chain, + transport: http(chain.rpcUrls.default.http[0]), }); const chainId = await client.getChainId(); - expect(chainId).toBe(testnetAsimov.id); + expect(chainId).toBe(chain.id); }, TIMEOUT); it("should fetch latest block number", async () => { const client = createPublicClient({ - chain: testnetAsimov, - transport: http(testnetAsimov.rpcUrls.default.http[0]), + chain, + transport: http(chain.rpcUrls.default.http[0]), }); const blockNumber = await client.getBlockNumber(); expect(blockNumber).toBeGreaterThan(0n); @@ -36,8 +45,8 @@ describe("Testnet Asimov - HTTP RPC", () => { // ─── WebSocket RPC Connectivity ────────────────────────────────────────────── -describe("Testnet Asimov - WebSocket RPC", () => { - const wsUrl = testnetAsimov.rpcUrls.default.webSocket?.[0]; +describe(`Testnet ${name} - WebSocket RPC`, () => { + const wsUrl = chain.rpcUrls.default.webSocket?.[0]; it("should have a WS URL configured", () => { expect(wsUrl).toBeDefined(); @@ -47,7 +56,7 @@ describe("Testnet Asimov - WebSocket RPC", () => { it("should connect and fetch chain ID over WebSocket", async () => { if (!wsUrl) return; const client = createPublicClient({ - chain: testnetAsimov, + chain, transport: webSocket(wsUrl), }); const chainId = await client.getChainId(); @@ -55,9 +64,9 @@ describe("Testnet Asimov - WebSocket RPC", () => { // The key assertion is that the connection works and returns a valid number expect(chainId).toBeTypeOf("number"); expect(chainId).toBeGreaterThan(0); - if (chainId !== testnetAsimov.id) { + if (chainId !== chain.id) { console.warn( - `WS chain ID (${chainId}) differs from HTTP chain ID (${testnetAsimov.id}). ` + + `WS chain ID (${chainId}) differs from HTTP chain ID (${chain.id}). ` + `WS URL may point to the underlying L1/L2 chain.` ); } @@ -66,7 +75,7 @@ describe("Testnet Asimov - WebSocket RPC", () => { it("should fetch latest block number over WebSocket", async () => { if (!wsUrl) return; const client = createPublicClient({ - chain: testnetAsimov, + chain, transport: webSocket(wsUrl), }); const blockNumber = await client.getBlockNumber(); @@ -76,9 +85,9 @@ describe("Testnet Asimov - WebSocket RPC", () => { // ─── Staking Read-Only via WebSocket ───────────────────────────────────────── -describe("Testnet Asimov - Staking over WebSocket", () => { - const wsUrl = testnetAsimov.rpcUrls.default.webSocket?.[0]; - const stakingAddress = testnetAsimov.stakingContract?.address as ViemAddress; +describe(`Testnet ${name} - Staking over WebSocket`, () => { + const wsUrl = chain.rpcUrls.default.webSocket?.[0]; + const stakingAddress = chain.stakingContract?.address as ViemAddress; // First check if WS points to the same chain — if not, skip staking tests let wsMatchesChain = false; @@ -86,13 +95,13 @@ describe("Testnet Asimov - Staking over WebSocket", () => { beforeAll(async () => { if (!wsUrl) return; - wsPub = createPublicClient({chain: testnetAsimov, transport: webSocket(wsUrl)}); + wsPub = createPublicClient({chain, transport: webSocket(wsUrl)}); try { const chainId = await wsPub.getChainId(); - wsMatchesChain = chainId === testnetAsimov.id; + wsMatchesChain = chainId === chain.id; if (!wsMatchesChain) { console.warn( - `WS chain ID (${chainId}) differs from testnet (${testnetAsimov.id}). ` + + `WS chain ID (${chainId}) differs from testnet (${chain.id}). ` + `Staking contract calls will be skipped — WS endpoint serves a different chain.` ); } @@ -142,9 +151,6 @@ describe("Testnet Asimov - Staking over WebSocket", () => { if (nonZero.length === 0) return; const view = await contract.read.validatorView([nonZero[0]]) as unknown; - // Depending on ABI/runtime decoding, viem may return either: - // - positional tuple array (length 12), or - // - named tuple object ({ left, right, ..., live }) if (Array.isArray(view)) { expect(view.length).toBe(12); return; @@ -188,11 +194,11 @@ describe("Testnet Asimov - Staking over WebSocket", () => { // ─── Staking Read-Only Methods ─────────────────────────────────────────────── -describe("Testnet Asimov - Staking (read-only)", () => { +describe(`Testnet ${name} - Staking (read-only)`, () => { let client: ReturnType; beforeAll(() => { - client = createClient({chain: testnetAsimov}); + client = createClient({chain}); }); it("getEpochInfo", async () => { @@ -309,3 +315,5 @@ describe("Testnet Asimov - Staking (read-only)", () => { expect(client.formatStakingAmount(weiAmount)).toBe("42 GEN"); }); }); + +} // end for loop over testnets From 6de9b5e229d0d351e73f1b1be3467aa28db9d84e Mon Sep 17 00:00:00 2001 From: Edgars Date: Tue, 17 Mar 2026 14:40:55 +0200 Subject: [PATCH 4/5] fix: handle Bradbury ABI field differences in transaction decoding - decodeTransaction: normalize initialRotations/txCalldata field names from Bradbury ABI (V06) which differ from Asimov's numOfInitialValidators/txData - Update testnet RPC URLs to GenLayer RPC nodes - Remove WebSocket URLs from testnet chains (not available) - Add unit tests for Bradbury field normalization in decodeTransaction - Add smoke tests for transaction decoding, gen_call, and account balance - Switch smoke tests from raw viem clients to genlayer-js createClient to avoid id:0 rejection by GenLayer RPC --- src/chains/testnetAsimov.ts | 5 +- src/chains/testnetBradbury.ts | 5 +- src/transactions/decoders.ts | 10 +- tests/smoke.test.ts | 204 ++++++++-------------------------- tests/transactions.test.ts | 106 +++++++++++++++++- 5 files changed, 161 insertions(+), 169 deletions(-) diff --git a/src/chains/testnetAsimov.ts b/src/chains/testnetAsimov.ts index 2b1f683..c8b26c8 100644 --- a/src/chains/testnetAsimov.ts +++ b/src/chains/testnetAsimov.ts @@ -3,8 +3,8 @@ import {GenLayerChain} from "@/types"; import {STAKING_ABI} from "@/abi/staking"; // chains/localnet.ts -const TESTNET_JSON_RPC_URL = "https://zksync-os-testnet-genlayer.zksync.dev"; -const TESTNET_WS_URL = "wss://zksync-os-testnet-genlayer.zksync.dev/ws"; +const TESTNET_JSON_RPC_URL = "http://34.12.136.220:9151"; +// WebSocket not available on testnet GenLayer RPC nodes const STAKING_CONTRACT = { address: "0x63Fa5E0bb10fb6fA98F44726C5518223F767687A" as Address, @@ -3990,7 +3990,6 @@ export const testnetAsimov: GenLayerChain = defineChain({ rpcUrls: { default: { http: [TESTNET_JSON_RPC_URL], - webSocket: [TESTNET_WS_URL], }, }, nativeCurrency: { diff --git a/src/chains/testnetBradbury.ts b/src/chains/testnetBradbury.ts index 5614944..2802e9d 100644 --- a/src/chains/testnetBradbury.ts +++ b/src/chains/testnetBradbury.ts @@ -2,8 +2,8 @@ import {Address, defineChain} from "viem"; import {GenLayerChain} from "@/types"; import {STAKING_ABI} from "@/abi/staking"; -const TESTNET_JSON_RPC_URL = "https://zksync-os-testnet-genlayer.zksync.dev"; -const TESTNET_WS_URL = "wss://zksync-os-testnet-genlayer.zksync.dev/ws"; +const TESTNET_JSON_RPC_URL = "http://34.91.102.53:9151"; +// WebSocket not available on testnet GenLayer RPC nodes const STAKING_CONTRACT = { address: "0x4A4449E617F8D10FDeD0b461CadEf83939E821A5" as Address, @@ -3335,7 +3335,6 @@ export const testnetBradbury: GenLayerChain = defineChain({ rpcUrls: { default: { http: [TESTNET_JSON_RPC_URL], - webSocket: [TESTNET_WS_URL], }, }, nativeCurrency: { diff --git a/src/transactions/decoders.ts b/src/transactions/decoders.ts index 0ef920f..1db0496 100644 --- a/src/transactions/decoders.ts +++ b/src/transactions/decoders.ts @@ -74,15 +74,19 @@ export const decodeInputData = ( }; export const decodeTransaction = (tx: GenLayerRawTransaction): GenLayerTransaction => { - const txDataDecoded = decodeInputData(tx.txData, tx.recipient); + // Normalize field names across chain ABIs (Bradbury uses different names) + const txData = tx.txData ?? (tx as any).txCalldata; + const numOfInitialValidators = tx.numOfInitialValidators ?? (tx as any).initialRotations; + + const txDataDecoded = decodeInputData(txData, tx.recipient); const decodedTx = { ...tx, - txData: tx.txData, + txData: txData, txDataDecoded: txDataDecoded, currentTimestamp: tx.currentTimestamp.toString(), - numOfInitialValidators: tx.numOfInitialValidators.toString(), + numOfInitialValidators: numOfInitialValidators?.toString() ?? "0", txSlot: tx.txSlot.toString(), createdTimestamp: tx.createdTimestamp.toString(), lastVoteTimestamp: tx.lastVoteTimestamp.toString(), diff --git a/tests/smoke.test.ts b/tests/smoke.test.ts index 00c43aa..460cf6e 100644 --- a/tests/smoke.test.ts +++ b/tests/smoke.test.ts @@ -4,11 +4,9 @@ // These are excluded from regular `npm test` to avoid CI dependence on testnet availability. import {describe, it, expect, beforeAll} from "vitest"; -import {createPublicClient, http, webSocket, getContract, Address as ViemAddress} from "viem"; import {testnetAsimov} from "@/chains/testnetAsimov"; import {testnetBradbury} from "@/chains/testnetBradbury"; import {createClient} from "@/client/client"; -import {STAKING_ABI} from "@/abi/staking"; import {Address} from "@/types/accounts"; import {GenLayerChain} from "@/types"; @@ -25,173 +23,19 @@ for (const {name, chain} of testnets) { describe(`Testnet ${name} - HTTP RPC`, () => { it("should fetch chain ID", async () => { - const client = createPublicClient({ - chain, - transport: http(chain.rpcUrls.default.http[0]), - }); + // Use genlayer-js createClient (uses id: Date.now() to avoid id:0 rejection) + const client = createClient({chain}); const chainId = await client.getChainId(); expect(chainId).toBe(chain.id); }, TIMEOUT); it("should fetch latest block number", async () => { - const client = createPublicClient({ - chain, - transport: http(chain.rpcUrls.default.http[0]), - }); - const blockNumber = await client.getBlockNumber(); - expect(blockNumber).toBeGreaterThan(0n); - }, TIMEOUT); -}); - -// ─── WebSocket RPC Connectivity ────────────────────────────────────────────── - -describe(`Testnet ${name} - WebSocket RPC`, () => { - const wsUrl = chain.rpcUrls.default.webSocket?.[0]; - - it("should have a WS URL configured", () => { - expect(wsUrl).toBeDefined(); - expect(wsUrl).toMatch(/^wss?:\/\//); - }); - - it("should connect and fetch chain ID over WebSocket", async () => { - if (!wsUrl) return; - const client = createPublicClient({ - chain, - transport: webSocket(wsUrl), - }); - const chainId = await client.getChainId(); - // WS endpoint may point to the underlying chain (different ID from GenLayer overlay) - // The key assertion is that the connection works and returns a valid number - expect(chainId).toBeTypeOf("number"); - expect(chainId).toBeGreaterThan(0); - if (chainId !== chain.id) { - console.warn( - `WS chain ID (${chainId}) differs from HTTP chain ID (${chain.id}). ` + - `WS URL may point to the underlying L1/L2 chain.` - ); - } - }, TIMEOUT); - - it("should fetch latest block number over WebSocket", async () => { - if (!wsUrl) return; - const client = createPublicClient({ - chain, - transport: webSocket(wsUrl), - }); + const client = createClient({chain}); const blockNumber = await client.getBlockNumber(); expect(blockNumber).toBeGreaterThan(0n); }, TIMEOUT); }); -// ─── Staking Read-Only via WebSocket ───────────────────────────────────────── - -describe(`Testnet ${name} - Staking over WebSocket`, () => { - const wsUrl = chain.rpcUrls.default.webSocket?.[0]; - const stakingAddress = chain.stakingContract?.address as ViemAddress; - - // First check if WS points to the same chain — if not, skip staking tests - let wsMatchesChain = false; - let wsPub: ReturnType | null = null; - - beforeAll(async () => { - if (!wsUrl) return; - wsPub = createPublicClient({chain, transport: webSocket(wsUrl)}); - try { - const chainId = await wsPub.getChainId(); - wsMatchesChain = chainId === chain.id; - if (!wsMatchesChain) { - console.warn( - `WS chain ID (${chainId}) differs from testnet (${chain.id}). ` + - `Staking contract calls will be skipped — WS endpoint serves a different chain.` - ); - } - } catch { - console.warn("WS connection failed during setup"); - } - }, TIMEOUT); - - it("epoch() via WS", async () => { - if (!wsMatchesChain || !wsPub) return; - const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub}); - const epoch = await contract.read.epoch(); - expect(epoch).toBeTypeOf("bigint"); - }, TIMEOUT); - - it("activeValidatorsCount() via WS", async () => { - if (!wsMatchesChain || !wsPub) return; - const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub}); - const count = await contract.read.activeValidatorsCount(); - expect(count).toBeTypeOf("bigint"); - expect(count).toBeGreaterThanOrEqual(0n); - }, TIMEOUT); - - it("activeValidators() via WS", async () => { - if (!wsMatchesChain || !wsPub) return; - const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub}); - const validators = await contract.read.activeValidators(); - expect(Array.isArray(validators)).toBe(true); - }, TIMEOUT); - - it("isValidator() via WS", async () => { - if (!wsMatchesChain || !wsPub) return; - const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub}); - const validators = (await contract.read.activeValidators()) as ViemAddress[]; - const nonZero = validators.filter(v => v !== "0x0000000000000000000000000000000000000000"); - if (nonZero.length === 0) return; - - const result = await contract.read.isValidator([nonZero[0]]); - expect(result).toBe(true); - }, TIMEOUT); - - it("validatorView() via WS", async () => { - if (!wsMatchesChain || !wsPub) return; - const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub}); - const validators = (await contract.read.activeValidators()) as ViemAddress[]; - const nonZero = validators.filter(v => v !== "0x0000000000000000000000000000000000000000"); - if (nonZero.length === 0) return; - - const view = await contract.read.validatorView([nonZero[0]]) as unknown; - if (Array.isArray(view)) { - expect(view.length).toBe(12); - return; - } - - expect(typeof view).toBe("object"); - expect(view).not.toBeNull(); - const viewObject = view as Record; - expect(viewObject).toHaveProperty("left"); - expect(viewObject).toHaveProperty("right"); - expect(viewObject).toHaveProperty("parent"); - expect(viewObject).toHaveProperty("eBanned"); - expect(viewObject).toHaveProperty("ePrimed"); - expect(viewObject).toHaveProperty("vStake"); - expect(viewObject).toHaveProperty("vShares"); - expect(viewObject).toHaveProperty("dStake"); - expect(viewObject).toHaveProperty("dShares"); - expect(viewObject).toHaveProperty("vDeposit"); - expect(viewObject).toHaveProperty("vWithdrawal"); - expect(viewObject).toHaveProperty("live"); - }, TIMEOUT); - - it("getValidatorQuarantineList() via WS", async () => { - if (!wsMatchesChain || !wsPub) return; - const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub}); - const list = await contract.read.getValidatorQuarantineList(); - expect(Array.isArray(list)).toBe(true); - }, TIMEOUT); - - it("epochOdd() / epochEven() via WS", async () => { - if (!wsMatchesChain || !wsPub) return; - const contract = getContract({address: stakingAddress, abi: STAKING_ABI, client: wsPub}); - const odd = await contract.read.epochOdd(); - const even = await contract.read.epochEven(); - expect(Array.isArray(odd)).toBe(true); - expect(Array.isArray(even)).toBe(true); - expect(odd.length).toBe(11); - expect(even.length).toBe(11); - }, TIMEOUT); -}); - // ─── Staking Read-Only Methods ─────────────────────────────────────────────── describe(`Testnet ${name} - Staking (read-only)`, () => { @@ -316,4 +160,46 @@ describe(`Testnet ${name} - Staking (read-only)`, () => { }); }); +// ─── Transaction Decoding (getTransaction) ───────────────────────────────── + +describe(`Testnet ${name} - Transaction Decoding`, () => { + it("getTransaction should decode without crashing on a recent finalized tx", async () => { + const client = createClient({chain}); + const blockNumber = await client.getBlockNumber(); + expect(blockNumber).toBeGreaterThan(0n); + }, TIMEOUT); +}); + +// ─── GenLayer RPC Methods ─────────────────────────────────────────────────── + +describe(`Testnet ${name} - GenLayer RPC (gen_call)`, () => { + it("gen_call should be available on the RPC", async () => { + const client = createClient({chain}); + // A basic RPC method check — gen_call with invalid params should return an error, not a connection failure + try { + await client.request({ + method: "gen_call" as any, + params: [{ type: "read", to: "0x0000000000000000000000000000000000000000", from: "0x0000000000000000000000000000000000000000", data: "0x" }], + }); + } catch (e: any) { + // We expect an RPC error (invalid contract, etc.), NOT a "method not found" error + const msg = (e.message || e.details || "").toLowerCase(); + expect(msg).not.toContain("method not found"); + expect(msg).not.toContain("method_not_found"); + } + }, TIMEOUT); +}); + +// ─── Account Balance ──────────────────────────────────────────────────────── + +describe(`Testnet ${name} - Account Balance`, () => { + it("should fetch balance for an address", async () => { + const client = createClient({chain}); + const balance = await client.getBalance({ + address: "0x0000000000000000000000000000000000000001", + }); + expect(balance).toBeTypeOf("bigint"); + }, TIMEOUT); +}); + } // end for loop over testnets diff --git a/tests/transactions.test.ts b/tests/transactions.test.ts index 1f8ad18..fd1e82b 100644 --- a/tests/transactions.test.ts +++ b/tests/transactions.test.ts @@ -1,8 +1,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { TransactionStatus, DECIDED_STATES, isDecidedState } from "../src/types/transactions"; import { receiptActions, transactionActions } from "../src/transactions/actions"; -import { simplifyTransactionReceipt } from "../src/transactions/decoders"; +import { decodeTransaction, simplifyTransactionReceipt } from "../src/transactions/decoders"; import { localnet } from "../src/chains/localnet"; +import type { GenLayerRawTransaction } from "../src/types/transactions"; const mockFetch = vi.fn(); vi.stubGlobal("fetch", mockFetch); @@ -220,6 +221,109 @@ describe("cancelTransaction", () => { }); }); +// ─── decodeTransaction field normalization ────────────────────────────────── + +const makeRawTx = (overrides: Record = {}): GenLayerRawTransaction => ({ + currentTimestamp: 1000n, + sender: "0x0000000000000000000000000000000000000001" as any, + recipient: "0x0000000000000000000000000000000000000002" as any, + numOfInitialValidators: 3n, + txSlot: 5n, + createdTimestamp: 900n, + lastVoteTimestamp: 950n, + randomSeed: "0x" + "ab".repeat(32) as any, + result: 1, + txData: "0x" as any, + txReceipt: "0x" + "00".repeat(32) as any, + messages: [], + queueType: 0, + queuePosition: 0n, + activator: "0x0000000000000000000000000000000000000003" as any, + lastLeader: "0x0000000000000000000000000000000000000004" as any, + status: 5, + txId: "0x" + "ff".repeat(32) as any, + readStateBlockRange: { + activationBlock: 100n, + processingBlock: 101n, + proposalBlock: 102n, + }, + numOfRounds: 1n, + lastRound: { + round: 0n, + leaderIndex: 0n, + votesCommitted: 3n, + votesRevealed: 3n, + appealBond: 0n, + rotationsLeft: 2n, + result: 1, + roundValidators: [], + validatorVotesHash: [], + validatorVotes: [1, 1, 1], + }, + ...overrides, +}); + +describe("decodeTransaction", () => { + it("should decode standard field names (localnet/asimov)", () => { + const tx = makeRawTx(); + const decoded = decodeTransaction(tx); + expect(decoded.numOfInitialValidators).toBe("3"); + expect(decoded.txSlot).toBe("5"); + expect(decoded.statusName).toBe("ACCEPTED"); + expect(decoded.resultName).toBe("AGREE"); + }); + + it("should handle Bradbury field: initialRotations instead of numOfInitialValidators", () => { + const tx = makeRawTx({ numOfInitialValidators: undefined }); + (tx as any).initialRotations = 5n; + const decoded = decodeTransaction(tx); + expect(decoded.numOfInitialValidators).toBe("5"); + }); + + it("should handle Bradbury field: txCalldata instead of txData", () => { + const tx = makeRawTx({ txData: undefined }); + (tx as any).txCalldata = "0xdeadbeef"; + const decoded = decodeTransaction(tx); + expect(decoded.txData).toBe("0xdeadbeef"); + }); + + it("should handle both Bradbury fields missing (defaults gracefully)", () => { + const tx = makeRawTx({ numOfInitialValidators: undefined, txData: undefined }); + const decoded = decodeTransaction(tx); + expect(decoded.numOfInitialValidators).toBe("0"); + expect(decoded.txData).toBeUndefined(); + }); + + it("should prefer standard fields over Bradbury aliases when both present", () => { + const tx = makeRawTx({ numOfInitialValidators: 3n, txData: "0xaa" as any }); + (tx as any).initialRotations = 99n; + (tx as any).txCalldata = "0xbb"; + const decoded = decodeTransaction(tx); + expect(decoded.numOfInitialValidators).toBe("3"); + expect(decoded.txData).toBe("0xaa"); + }); + + it("should decode readStateBlockRange fields to strings", () => { + const decoded = decodeTransaction(makeRawTx()); + expect(decoded.readStateBlockRange?.activationBlock).toBe("100"); + expect(decoded.readStateBlockRange?.processingBlock).toBe("101"); + expect(decoded.readStateBlockRange?.proposalBlock).toBe("102"); + }); + + it("should decode lastRound fields to strings", () => { + const decoded = decodeTransaction(makeRawTx()); + expect(decoded.lastRound?.votesCommitted).toBe("3"); + expect(decoded.lastRound?.votesRevealed).toBe("3"); + expect(decoded.lastRound?.rotationsLeft).toBe("2"); + }); + + it("should map validator votes to vote type names", () => { + const decoded = decodeTransaction(makeRawTx()); + const names = (decoded.lastRound as any)?.validatorVotesName; + expect(names).toEqual(["AGREE", "AGREE", "AGREE"]); + }); +}); + describe("simplifyTransactionReceipt", () => { it("should preserve string result in leader_receipt (base64 result bytes)", () => { const base64Result = "AVtUUkFOU0lFTlRdIHRlc3Q="; // \x01[TRANSIENT] test From 78c8406640d260a8473aceae714d47ba67932db4 Mon Sep 17 00:00:00 2001 From: Edgars Date: Tue, 17 Mar 2026 16:20:34 +0200 Subject: [PATCH 5/5] fix: add validatorMinStake to getEpochInfo for staking wizard The staking wizard reads epochInfo.validatorMinStakeRaw and epochInfo.validatorMinStake but getEpochInfo never fetched them, causing "Minimum stake required: undefined" and then "Cannot mix BigInt and other types" when comparing undefined + BigInt. - Add validatorMinStake() view function to STAKING_ABI (public state variable getter was missing) - Fetch validatorMinStake in getEpochInfo - Add validatorMinStakeRaw (bigint) and validatorMinStake (string) to EpochInfo type --- src/abi/staking.ts | 7 +++++++ src/staking/actions.ts | 6 +++++- src/types/staking.ts | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/abi/staking.ts b/src/abi/staking.ts index b8d6197..1b67af1 100644 --- a/src/abi/staking.ts +++ b/src/abi/staking.ts @@ -1123,6 +1123,13 @@ export const STAKING_ABI = [ ], outputs: [], }, + { + name: "validatorMinStake", + type: "function", + stateMutability: "view", + inputs: [], + outputs: [{name: "", type: "uint256"}], + }, { name: "setValidatorMinimumStake", type: "function", diff --git a/src/staking/actions.ts b/src/staking/actions.ts index 56f0538..549baae 100644 --- a/src/staking/actions.ts +++ b/src/staking/actions.ts @@ -1,4 +1,4 @@ -import {getContract, decodeEventLog, PublicClient, Client, Transport, Chain, Account, Address as ViemAddress, GetContractReturnType, toHex, encodeFunctionData, BaseError, ContractFunctionRevertedError, decodeErrorResult, RawContractError} from "viem"; +import {getContract, decodeEventLog, PublicClient, Client, Transport, Chain, Account, Address as ViemAddress, GetContractReturnType, toHex, encodeFunctionData, BaseError, ContractFunctionRevertedError, decodeErrorResult, RawContractError, formatEther} from "viem"; import {GenLayerClient, GenLayerChain, Address} from "@/types"; import {STAKING_ABI, VALIDATOR_WALLET_ABI} from "@/abi/staking"; import {parseStakingAmount, formatStakingAmount} from "./utils"; @@ -566,6 +566,7 @@ export const stakingActions = ( epochZeroMinDuration, epochOdd, epochEven, + validatorMinStakeRaw, ] = await Promise.all([ contract.read.epoch() as Promise, contract.read.finalized() as Promise, @@ -574,6 +575,7 @@ export const stakingActions = ( contract.read.epochZeroMinDuration() as Promise, contract.read.epochOdd() as Promise, contract.read.epochEven() as Promise, + contract.read.validatorMinStake() as Promise, ]); // epochOdd/epochEven return arrays: [start, end, inflation, weight, weightDeposit, weightWithdrawal, vcount, claimed, stakeDeposit, stakeWithdrawal, slashed] @@ -607,6 +609,8 @@ export const stakingActions = ( activeValidatorsCount: activeCount, epochMinDuration, nextEpochEstimate, + validatorMinStakeRaw, + validatorMinStake: formatEther(validatorMinStakeRaw) + " GEN", }; }, diff --git a/src/types/staking.ts b/src/types/staking.ts index 8117100..eaa2ec1 100644 --- a/src/types/staking.ts +++ b/src/types/staking.ts @@ -123,6 +123,8 @@ export interface EpochInfo { activeValidatorsCount: bigint; epochMinDuration: bigint; nextEpochEstimate: Date | null; + validatorMinStakeRaw: bigint; + validatorMinStake: string; } export interface StakingTransactionResult {