From 9fec8c113abb5aa295405c3ea6aebacb05bd4406 Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 2 Sep 2026 19:58:30 +0100 Subject: [PATCH 1/2] docs: prepare consensus v0.6 release Document fee-funded transactions, safe appeals, Studio preview networks, tooling RC integration, ECVRF selection, tribunals, developer rewards, keeper operations, and migration guidance. Refresh the JavaScript and Python API surfaces from the exact train heads and add a build-time release invariant check over 363 consensus error selectors and critical behavior anchors.\n\nValidated with the complete 231-route production build, protocol-doc checks, release-doc checks, generated API/LLM exports, and whitespace checks. --- data/consensus-v06-error-selectors.json | 1454 +++++++++++++++++ package.json | 3 +- pages/api-references/genlayer-cli.mdx | 11 +- pages/api-references/genlayer-cli/index.mdx | 2 +- .../genlayer-cli/transactions/appeal-bond.mdx | 2 +- .../genlayer-cli/transactions/appeal.mdx | 4 +- pages/api-references/genlayer-js.md | 323 +--- pages/api-references/genlayer-js/contracts.md | 227 ++- pages/api-references/genlayer-js/staking.md | 104 +- .../genlayer-js/transactions.md | 56 +- pages/api-references/genlayer-py.md | 202 +-- pages/api-references/genlayer-py/api.md | 368 ++++- pages/developers/_meta.json | 2 + pages/developers/consensus-v06-migration.mdx | 91 ++ .../decentralized-applications/_meta.json | 5 + .../architecture-overview.mdx | 5 +- .../dapp-development-workflow.mdx | 16 +- .../developer-nft-rewards.mdx | 176 ++ .../fee-outcomes-and-debugging.mdx | 68 + .../fee-profiling-and-estimation.mdx | 334 ++++ .../fees-and-transaction-kit.mdx | 50 + .../querying-a-transaction.mdx | 187 +-- .../reading-data.mdx | 103 +- .../transaction-kit-integration.mdx | 163 ++ .../writing-data.mdx | 291 ++-- pages/developers/error-reference.mdx | 370 +++++ .../intelligent-contracts/deploying.mdx | 56 +- .../deploying/cli-deployment.mdx | 8 +- .../deploying/deploy-scripts.mdx | 312 ++-- .../deploying/network-configuration.mdx | 234 +-- .../features/value-transfers.mdx | 18 +- .../intelligent-contracts/tooling-setup.mdx | 32 +- .../tools/genlayer-studio.mdx | 26 +- .../tools/genlayer-studio/limitations.mdx | 2 +- pages/developers/networks.mdx | 42 +- .../core-concepts/economic-model.mdx | 24 +- .../optimistic-democracy/_meta.json | 2 + .../optimistic-democracy/appeal-process.mdx | 37 +- ...deterministic-violations-and-tribunals.mdx | 85 + .../protocol-randomness.mdx | 76 + .../optimistic-democracy/slashing.mdx | 6 +- pages/validators/_meta.json | 1 + pages/validators/network-keeper-roles.mdx | 210 +++ scripts/check-v06-release-docs.js | 328 ++++ 44 files changed, 4797 insertions(+), 1319 deletions(-) create mode 100644 data/consensus-v06-error-selectors.json create mode 100644 pages/developers/consensus-v06-migration.mdx create mode 100644 pages/developers/decentralized-applications/developer-nft-rewards.mdx create mode 100644 pages/developers/decentralized-applications/fee-outcomes-and-debugging.mdx create mode 100644 pages/developers/decentralized-applications/fee-profiling-and-estimation.mdx create mode 100644 pages/developers/decentralized-applications/fees-and-transaction-kit.mdx create mode 100644 pages/developers/decentralized-applications/transaction-kit-integration.mdx create mode 100644 pages/developers/error-reference.mdx create mode 100644 pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/deterministic-violations-and-tribunals.mdx create mode 100644 pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/protocol-randomness.mdx create mode 100644 pages/validators/network-keeper-roles.mdx create mode 100644 scripts/check-v06-release-docs.js diff --git a/data/consensus-v06-error-selectors.json b/data/consensus-v06-error-selectors.json new file mode 100644 index 00000000..75cc1327 --- /dev/null +++ b/data/consensus-v06-error-selectors.json @@ -0,0 +1,1454 @@ +[ + { + "signature": "NotAppealsContract()", + "selector": "0xa5a4297b" + }, + { + "signature": "NotAValidatorWallet()", + "selector": "0xc1952ed0" + }, + { + "signature": "NotBeneficiary()", + "selector": "0x644d871f" + }, + { + "signature": "NotConsensus()", + "selector": "0x70f64de5" + }, + { + "signature": "NotConsensusOrIdleness()", + "selector": "0xa82180be" + }, + { + "signature": "NotConsensusOrIdlenessOrTransactions()", + "selector": "0x1e3f968f" + }, + { + "signature": "NotConsensusOrTransactions()", + "selector": "0xe6533a27" + }, + { + "signature": "NotCreator()", + "selector": "0x93687c0b" + }, + { + "signature": "NotDaoContract()", + "selector": "0xe6a497af" + }, + { + "signature": "NotEnoughValidators()", + "selector": "0xae575a88" + }, + { + "signature": "NotFunded()", + "selector": "0xd5ef09ba" + }, + { + "signature": "NotGenConsensus()", + "selector": "0xfd9abcdc" + }, + { + "signature": "NotIdleness()", + "selector": "0x878f6816" + }, + { + "signature": "NotOperator()", + "selector": "0x7c214f04" + }, + { + "signature": "NotRevocable()", + "selector": "0x9414820d" + }, + { + "signature": "NotRevoked()", + "selector": "0x73f7ab1e" + }, + { + "signature": "NotRevoker()", + "selector": "0x2ad3d44f" + }, + { + "signature": "NotStaking()", + "selector": "0x890fec52" + }, + { + "signature": "AppealBondTooLow()", + "selector": "0xb44cda7b" + }, + { + "signature": "AppealNotActive()", + "selector": "0x6565b6bf" + }, + { + "signature": "AppealNotAllowed()", + "selector": "0xb94e4c42" + }, + { + "signature": "AppealRoundAlreadyExists()", + "selector": "0x93a19b27" + }, + { + "signature": "AppealRoundNotPermitted()", + "selector": "0x6ecc8d59" + }, + { + "signature": "IndexOutOfBounds()", + "selector": "0x4e23d035" + }, + { + "signature": "BatchSizeExceeded(uint256,uint256)", + "selector": "0xf80a4845" + }, + { + "signature": "BeaconAlreadyDeployed()", + "selector": "0xa1900dfe" + }, + { + "signature": "BeaconNotDeployed()", + "selector": "0x261438ca" + }, + { + "signature": "OperatorTransferNotReady()", + "selector": "0xde4e791a" + }, + { + "signature": "TransferFailed()", + "selector": "0x90b8ec18" + }, + { + "signature": "WalletDeploymentFailed()", + "selector": "0x6e09c9eb" + }, + { + "signature": "NoBurning()", + "selector": "0x96191d45" + }, + { + "signature": "ReductionFactorCannotBeZero()", + "selector": "0x2e980407" + }, + { + "signature": "CallerNotActivator()", + "selector": "0xb56aa94e" + }, + { + "signature": "CallerNotAuthorized()", + "selector": "0xc183bcef" + }, + { + "signature": "CallerNotConsensus()", + "selector": "0x47820187" + }, + { + "signature": "CallerNotGenConsensus()", + "selector": "0xf8beed7d" + }, + { + "signature": "CallerNotGovernance()", + "selector": "0xf2be30fb" + }, + { + "signature": "CallerNotLeader(address,address)", + "selector": "0x3558c9da" + }, + { + "signature": "CallerNotMessages()", + "selector": "0x7e3d1f98" + }, + { + "signature": "CallerNotOwner()", + "selector": "0x5cd83192" + }, + { + "signature": "CallerNotSender()", + "selector": "0xf5963d65" + }, + { + "signature": "CallerNotTransactions()", + "selector": "0xae49b478" + }, + { + "signature": "CanNotAppeal()", + "selector": "0xb39cdfbe" + }, + { + "signature": "UnfinishedTxCounterUnderflow()", + "selector": "0x8894ba94" + }, + { + "signature": "DelegatorBelowMinimumStake()", + "selector": "0x944516fc" + }, + { + "signature": "DelegatorExitExceedsShares()", + "selector": "0x64d3ea58" + }, + { + "signature": "DelegatorExitUnrepresentableShares()", + "selector": "0xac2489dc" + }, + { + "signature": "DelegatorExitWouldBeBelowMinimum()", + "selector": "0x5f9ff6b2" + }, + { + "signature": "DelegatorMayNotExitWithZeroShares()", + "selector": "0x161fa299" + }, + { + "signature": "DelegatorMayNotJoinTwoValidatorsSimultaneously()", + "selector": "0x48a6b5ba" + }, + { + "signature": "DelegatorMayNotJoinWithZeroValue()", + "selector": "0xba5cb6d8" + }, + { + "signature": "DelegatorMustExitAllWhenBelowMinimum()", + "selector": "0x44d4da44" + }, + { + "signature": "EpochAdvanceNotReady()", + "selector": "0xe7295fed" + }, + { + "signature": "EpochAlreadyFinalized()", + "selector": "0x3366263c" + }, + { + "signature": "EpochDurationCannotBeZero()", + "selector": "0xfd06e181" + }, + { + "signature": "EpochDurationOverflow()", + "selector": "0x270bd26d" + }, + { + "signature": "EpochNotFinalized()", + "selector": "0x8cf75707" + }, + { + "signature": "EpochNotFinished()", + "selector": "0xec766557" + }, + { + "signature": "PendingTribunals(uint256)", + "selector": "0x773c68a6" + }, + { + "signature": "PreviousEpochNotFinalizable()", + "selector": "0x93b7eb86" + }, + { + "signature": "AllocationRestrictedTxCannotAcceptMessageFees()", + "selector": "0x35ebdf50" + }, + { + "signature": "FailedFeeTransfer()", + "selector": "0x24a8ac13" + }, + { + "signature": "FeeSettlementNotCompleted()", + "selector": "0x1432a761" + }, + { + "signature": "FeeValueMustBeNonZero(uint256)", + "selector": "0x632be5a1" + }, + { + "signature": "InsufficientFees()", + "selector": "0x8d53e553" + }, + { + "signature": "InsufficientFeesForRound()", + "selector": "0x0c35bf69" + }, + { + "signature": "MessageFeeParamsMismatch()", + "selector": "0x67b02b3b" + }, + { + "signature": "MessageFeesReportMismatch()", + "selector": "0x86515990" + }, + { + "signature": "MessageFeesTotalMustBeNonZero()", + "selector": "0xf79991cb" + }, + { + "signature": "OnlyFeeManager()", + "selector": "0x8f1dbd6c" + }, + { + "signature": "UseBalanceFeeReservationExceeded(bytes32,uint256,uint256)", + "selector": "0xde234cf9" + }, + { + "signature": "OnlyConsensusCanCall()", + "selector": "0x6c6fe28a" + }, + { + "signature": "AccountsArrayEmpty()", + "selector": "0xb1d0b181" + }, + { + "signature": "AmountMustBeGreaterThan0()", + "selector": "0x35f61689" + }, + { + "signature": "InitialMintAlreadyCalled()", + "selector": "0xacf9028d" + }, + { + "signature": "L2BaseGasCostQueryFailed()", + "selector": "0x7dc9c5e2" + }, + { + "signature": "L2MessageAlreadyInvoked()", + "selector": "0x21d83750" + }, + { + "signature": "L2MessageProvenFailed()", + "selector": "0xfd0ae327" + }, + { + "signature": "L2TransactionRequestFailed()", + "selector": "0x1efae811" + }, + { + "signature": "TotalDistributionMustBe100()", + "selector": "0x16904932" + }, + { + "signature": "InvalidAddress()", + "selector": "0xe6c4247b" + }, + { + "signature": "InvalidAmount()", + "selector": "0x2c5211c6" + }, + { + "signature": "InvalidAppealBond()", + "selector": "0xc59a6168" + }, + { + "signature": "InvalidAppealRounds()", + "selector": "0x2b4f0027" + }, + { + "signature": "InvalidAppealRoundState()", + "selector": "0x1a5d319a" + }, + { + "signature": "InvalidAtEpoch()", + "selector": "0x90dce792" + }, + { + "signature": "InvalidBanPeriod()", + "selector": "0xa2fc7bbd" + }, + { + "signature": "InvalidCliffUnlockBps()", + "selector": "0x7f2d8c3d" + }, + { + "signature": "InvalidCommitHash()", + "selector": "0x173d238e" + }, + { + "signature": "InvalidCommittedValidators()", + "selector": "0xcbf18bce" + }, + { + "signature": "InvalidDeploymentWithSalt()", + "selector": "0xaceab4a1" + }, + { + "signature": "InvalidGhostContract()", + "selector": "0x1d41354d" + }, + { + "signature": "InvalidIdleReplacementIndex(address,uint256,uint256)", + "selector": "0x4424217a" + }, + { + "signature": "InvalidInflationThresholds()", + "selector": "0x4774d828" + }, + { + "signature": "InvalidInternalMessageReference()", + "selector": "0x46eee7e3" + }, + { + "signature": "InvalidL2GasParams()", + "selector": "0x21e1b5c9" + }, + { + "signature": "InvalidNonce()", + "selector": "0x756688fe" + }, + { + "signature": "InvalidNumber(uint256)", + "selector": "0xc5d83cde" + }, + { + "signature": "InvalidNumberOfEpochsToClaim()", + "selector": "0xb9a8ce44" + }, + { + "signature": "InvalidNumOfValidators()", + "selector": "0xc4be27c5" + }, + { + "signature": "InvalidOffset()", + "selector": "0x01da1572" + }, + { + "signature": "InvalidOperatorAddress()", + "selector": "0xeb32d3bf" + }, + { + "signature": "InvalidPageSize()", + "selector": "0xe5b7db2e" + }, + { + "signature": "InvalidPeriodDuration()", + "selector": "0x9e11b5e6" + }, + { + "signature": "InvalidPhaseTimeoutBounds()", + "selector": "0x7cee0061" + }, + { + "signature": "InvalidPossessionProof()", + "selector": "0xbabbd7de" + }, + { + "signature": "InvalidPrimingSelfStakeBps()", + "selector": "0xe481a6f8" + }, + { + "signature": "InvalidProcessingBlock()", + "selector": "0xd1ba0787" + }, + { + "signature": "InvalidRecipient()", + "selector": "0x9c8d2cd2" + }, + { + "signature": "InvalidRevealData()", + "selector": "0xbc03c4b4" + }, + { + "signature": "InvalidRevealLeaderData()", + "selector": "0x92c313ee" + }, + { + "signature": "InvalidSender()", + "selector": "0xddb5de5e" + }, + { + "signature": "InvalidSlashDelay()", + "selector": "0x9589cd8b" + }, + { + "signature": "InvalidSlashPercentage()", + "selector": "0x37814740" + }, + { + "signature": "InvalidStakingAddress()", + "selector": "0x7caa6460" + }, + { + "signature": "InvalidStakingContract()", + "selector": "0x3d8c6f98" + }, + { + "signature": "InvalidStopTimestamp()", + "selector": "0xde8f8fd4" + }, + { + "signature": "InvalidTimestamp()", + "selector": "0xb7d09497" + }, + { + "signature": "InvalidTimestampType()", + "selector": "0x099d113d" + }, + { + "signature": "InvalidTransactionInitialization(bytes32)", + "selector": "0x551b05e7" + }, + { + "signature": "InvalidTransactionStatus()", + "selector": "0xf8062102" + }, + { + "signature": "InvalidTribunalAppealStatus()", + "selector": "0x37c1c7d4" + }, + { + "signature": "InvalidTxExecutionHash()", + "selector": "0x22a529c0" + }, + { + "signature": "InvalidValidator()", + "selector": "0x682a6e7c" + }, + { + "signature": "InvalidValidatorsLength()", + "selector": "0x5d67a037" + }, + { + "signature": "InvalidVersion()", + "selector": "0xa9146eeb" + }, + { + "signature": "InvalidVote()", + "selector": "0xd5dd0c66" + }, + { + "signature": "InvalidVoteType()", + "selector": "0x8eed55d1" + }, + { + "signature": "InvalidWeightParams()", + "selector": "0xa17d9e68" + }, + { + "signature": "IdlenessError()", + "selector": "0xc35cc440" + }, + { + "signature": "CanOnlyTriggerInflationMaxEpochsInFuture()", + "selector": "0xdfaaa5b2" + }, + { + "signature": "CanOnlyTriggerInflationMaxTenEpochsInFuture()", + "selector": "0xa9f9ed6c" + }, + { + "signature": "InflationAlreadyInitialized()", + "selector": "0x9ded3b15" + }, + { + "signature": "InflationAlreadyReceived()", + "selector": "0x719a0d39" + }, + { + "signature": "InflationInitialized()", + "selector": "0x067f34cf" + }, + { + "signature": "InflationInvalidAmount()", + "selector": "0x3c1f1f16" + }, + { + "signature": "InflationNotReadyToBeRealized()", + "selector": "0x7d8fa225" + }, + { + "signature": "InflationRequestFailed()", + "selector": "0xced05c45" + }, + { + "signature": "MaxNumberOfValidatorsReached()", + "selector": "0x9ce3911d" + }, + { + "signature": "MaxNumOfIterationsInPendingQueueReached()", + "selector": "0x357bf18b" + }, + { + "signature": "MaxNumOfMessagesExceeded(uint256,uint256)", + "selector": "0x3838b192" + }, + { + "signature": "AddingTransactionToUndeterminedQueueFailed()", + "selector": "0x5bd3cd8c" + }, + { + "signature": "DecisionQueueTimestampMismatch(bytes32,uint256,uint256)", + "selector": "0x2c36b5ad" + }, + { + "signature": "IllegalQueueMigration(bytes32,uint8,uint8)", + "selector": "0xe788332f" + }, + { + "signature": "NumOfMessagesIssuedTooHigh()", + "selector": "0x5013bc2a" + }, + { + "signature": "PendingQueueFull(address,uint256)", + "selector": "0xd48a82a3" + }, + { + "signature": "QueueHeadExceedsTail()", + "selector": "0x698f39ad" + }, + { + "signature": "RemovingTransactionFromPendingQueueFailed()", + "selector": "0x866b818f" + }, + { + "signature": "TransactionCanNotBeAddedToAcceptedQueue()", + "selector": "0xcdcfa366" + }, + { + "signature": "TransactionCanNotBeAddedToPendingQueue()", + "selector": "0x406a3bbb" + }, + { + "signature": "TransactionCanNotBeAddedToUndeterminedQueue()", + "selector": "0x69842b0a" + }, + { + "signature": "TransactionNotAtAcceptedQueueHead()", + "selector": "0x3e714edf" + }, + { + "signature": "TransactionNotAtPendingQueueHead()", + "selector": "0x0844056a" + }, + { + "signature": "TransactionNotAtUndeterminedQueueHead()", + "selector": "0x3d40531f" + }, + { + "signature": "TransactionNotInPendingQueue()", + "selector": "0x7b9ea34f" + }, + { + "signature": "MockZkSyncBridgeCallFailedToL2()", + "selector": "0x9a245636" + }, + { + "signature": "DeveloperAlreadyHasNFT()", + "selector": "0xa74b28d1" + }, + { + "signature": "DeveloperCannotBeZeroAddress()", + "selector": "0x7e3f46bd" + }, + { + "signature": "DeveloperHasNoNFT()", + "selector": "0x70849322" + }, + { + "signature": "DeveloperHasNoRewards()", + "selector": "0xdf6b47b0" + }, + { + "signature": "GhostAlreadyHasNFT()", + "selector": "0x36bd0868" + }, + { + "signature": "GhostCannotBeZeroAddress()", + "selector": "0x4d8d14c5" + }, + { + "signature": "NotNFTOwner()", + "selector": "0x4088c61c" + }, + { + "signature": "AccessControlUnauthorizedAccount(address,bytes32)", + "selector": "0xe2517d3f" + }, + { + "signature": "AllocationCommitmentMissing()", + "selector": "0xbe77b500" + }, + { + "signature": "AllocationDuplicateKey()", + "selector": "0xbb598087" + }, + { + "signature": "AllocationLifecycleBudgetInsufficient()", + "selector": "0x00583b33" + }, + { + "signature": "AllocationSubtreeHashMismatch()", + "selector": "0xdda17044" + }, + { + "signature": "AllocationSubtreeRequired()", + "selector": "0x47db53f3" + }, + { + "signature": "AllocationTreeBudgetInconsistent()", + "selector": "0x328bfb08" + }, + { + "signature": "AllocationTreeMalformed()", + "selector": "0x7fecd5b1" + }, + { + "signature": "AllocationTreeTooDeep()", + "selector": "0x48668d66" + }, + { + "signature": "AlreadyClaimed()", + "selector": "0x646cf558" + }, + { + "signature": "AlreadyFunded()", + "selector": "0x5adf6387" + }, + { + "signature": "AlreadyRevoked()", + "selector": "0x905e7107" + }, + { + "signature": "AlreadySettled()", + "selector": "0x560ff900" + }, + { + "signature": "AlreadyUnlocked()", + "selector": "0x5090d6c6" + }, + { + "signature": "ArrayLengthMismatch()", + "selector": "0xa24a13a6" + }, + { + "signature": "AttemptIdentityMismatch(bytes32,bytes32)", + "selector": "0x6045a383" + }, + { + "signature": "BudgetTooLow()", + "selector": "0x305e533c" + }, + { + "signature": "BurnTransferFailed()", + "selector": "0xaac1169b" + }, + { + "signature": "ContributionTooLarge(uint256,uint256)", + "selector": "0x058baee9" + }, + { + "signature": "DaoAlreadyRegistered()", + "selector": "0x8fd0c2bc" + }, + { + "signature": "DecisionBasisMismatch(bytes32,uint256,uint256)", + "selector": "0x1b98edb6" + }, + { + "signature": "DecisionEffectiveAtInFuture(bytes32,uint256,uint256)", + "selector": "0xe56cb5fe" + }, + { + "signature": "EqOutputsTooLarge(uint256,uint256)", + "selector": "0x6f4a4557" + }, + { + "signature": "ExecutionBudgetExceeded(uint256,uint256)", + "selector": "0x57df8523" + }, + { + "signature": "ExternalBudgetInvalid()", + "selector": "0x8b434eea" + }, + { + "signature": "ExternalGasLimitBelowMinimum()", + "selector": "0xf5963b64" + }, + { + "signature": "ExternalGasLimitOrPriceZero()", + "selector": "0xbf35c24e" + }, + { + "signature": "ExternalMessageFreezeExceeded(bytes32,uint256,uint256)", + "selector": "0x05684868" + }, + { + "signature": "ExternalOnAcceptanceNotSupported()", + "selector": "0xa54ce0b6" + }, + { + "signature": "FinalizedCountExceedsIssued()", + "selector": "0x34bffaee" + }, + { + "signature": "ForcedTransferFault()", + "selector": "0xae656f8c" + }, + { + "signature": "FunctionNotFound(bytes4)", + "selector": "0x5416eb98" + }, + { + "signature": "FundingMismatch()", + "selector": "0xb84a1afb" + }, + { + "signature": "GovernanceInsufficientValue(uint256,uint256)", + "selector": "0xf498db0c" + }, + { + "signature": "GovernanceInvalidDelay()", + "selector": "0xd1132ebc" + }, + { + "signature": "GovernanceOperationExpired(address,bytes4,bytes,uint256,uint256)", + "selector": "0x11515806" + }, + { + "signature": "GovernanceOperationNotFound(address,bytes4,bytes,uint256)", + "selector": "0x8ff7bbe1" + }, + { + "signature": "GovernanceOperationPending(address,bytes4,bytes,uint256)", + "selector": "0xc7eef27f" + }, + { + "signature": "GovernanceTargetIsNotAContract(address)", + "selector": "0x83e02672" + }, + { + "signature": "GovernanceTimelockPending(address,bytes4,bytes,uint256,uint256)", + "selector": "0x2189e680" + }, + { + "signature": "IllegalStatusTransition(bytes32,uint8,uint8)", + "selector": "0xe1081528" + }, + { + "signature": "IncentivePercentageTooHigh()", + "selector": "0xaf188845" + }, + { + "signature": "InsufficientActiveValidators(uint256,uint256)", + "selector": "0xb5e5b936" + }, + { + "signature": "InsufficientAppealFunding(uint256,uint256)", + "selector": "0x96631b91" + }, + { + "signature": "InsufficientAvailablePool(uint256,uint256)", + "selector": "0xdaf0d2c0" + }, + { + "signature": "InsufficientBalance()", + "selector": "0xf4d678b8" + }, + { + "signature": "InsufficientBondCustody()", + "selector": "0xebab1869" + }, + { + "signature": "InsufficientContractBalance()", + "selector": "0x786e0a99" + }, + { + "signature": "InsufficientGasForInternalMessageCreation(uint256,uint256,uint256,address)", + "selector": "0xb2c9bc63" + }, + { + "signature": "InsufficientInflationFunds()", + "selector": "0x58d77d2a" + }, + { + "signature": "InsufficientValue()", + "selector": "0x11011294" + }, + { + "signature": "InternalMessagePayloadUnavailable()", + "selector": "0x69244346" + }, + { + "signature": "InvariantViolation(uint256)", + "selector": "0xa1a97e55" + }, + { + "signature": "JudicialCapacityReservationActive(address)", + "selector": "0x655891ba" + }, + { + "signature": "JudicialCapacityReservationMissing(address)", + "selector": "0x08f16b5f" + }, + { + "signature": "JudicialQuarantineCauseActive(address)", + "selector": "0x1e6f3cc1" + }, + { + "signature": "LegacyProposeReceiptRetired()", + "selector": "0xa4ec1c31" + }, + { + "signature": "Locked()", + "selector": "0x0f2e5b6c" + }, + { + "signature": "ManualUnlockNotRequired()", + "selector": "0x90173285" + }, + { + "signature": "MaxPriceExceeded(uint256,uint256)", + "selector": "0xb4132db3" + }, + { + "signature": "MaxValidatorsCannotBeZero()", + "selector": "0x83c27a2d" + }, + { + "signature": "MaxValidatorsExceedsSafeLimit(uint256,uint256)", + "selector": "0x5449cfab" + }, + { + "signature": "MessageAllocationBudgetInsufficient()", + "selector": "0x67d6a6b3" + }, + { + "signature": "MessageAllocationsNotEqualBudget()", + "selector": "0x9659e275" + }, + { + "signature": "MessageBudgetExceeded(uint256,uint256)", + "selector": "0xd67553bd" + }, + { + "signature": "MessageDeclaredBudgetInsufficient()", + "selector": "0x7f295162" + }, + { + "signature": "MessageEffectCommitLengthMismatch(uint256,uint256)", + "selector": "0x69d6a5d1" + }, + { + "signature": "MessageEffectDescriptorMismatch(bytes32,bytes32,bytes32,bytes32)", + "selector": "0x1d0de3df" + }, + { + "signature": "MessageEmissionPhaseMismatch()", + "selector": "0x5ca1f1d8" + }, + { + "signature": "MessageGenerationSetMismatch(bytes32,uint256,bytes32,bytes32)", + "selector": "0xd1d4bb02" + }, + { + "signature": "MessageNoMatchingAllocation()", + "selector": "0x4e4b3c38" + }, + { + "signature": "MinimumStakeTransitionActive(uint64)", + "selector": "0x06d6cbfc" + }, + { + "signature": "MinimumStakeTransitionConfigLocked()", + "selector": "0x478289fb" + }, + { + "signature": "MinimumStakeTransitionCursorMismatch(uint24,uint24)", + "selector": "0x8b610290" + }, + { + "signature": "MinimumStakeTransitionIdentityMismatch(uint64,uint64,bytes32,bytes32)", + "selector": "0xc2feee5c" + }, + { + "signature": "MinimumStakeTransitionInvalidStatus(MinimumStakeTransitionStatus)", + "selector": "0xfad9cebb" + }, + { + "signature": "MinimumStakeTransitionNotActive()", + "selector": "0x0db97f7e" + }, + { + "signature": "MirrorFieldOverflow()", + "selector": "0xab5f8856" + }, + { + "signature": "NoPendingOperator()", + "selector": "0x9c2af11f" + }, + { + "signature": "NoPendingRefund()", + "selector": "0xfb093898" + }, + { + "signature": "NoPreviousEpoch()", + "selector": "0x9fa56a5b" + }, + { + "signature": "NoValidatorWallet()", + "selector": "0x62b7e683" + }, + { + "signature": "OperatorAlreadyAssigned()", + "selector": "0x5acd21ba" + }, + { + "signature": "OperatorAlreadyUsed()", + "selector": "0x229d6af4" + }, + { + "signature": "OutOfGas()", + "selector": "0x77ebef4d" + }, + { + "signature": "OwnableUnauthorizedAccount(address)", + "selector": "0x118cdaa7" + }, + { + "signature": "PendingIssuedSlotMismatch(address,uint256,uint256)", + "selector": "0xe03bc2f5" + }, + { + "signature": "PercentageOutOfRange(uint256)", + "selector": "0xafd5d0b0" + }, + { + "signature": "PermanentQuarantineNeedsOwnerPardon()", + "selector": "0x7801306b" + }, + { + "signature": "PhaseDeadlineNotInitialized(bytes32,uint8)", + "selector": "0xbd2704b2" + }, + { + "signature": "PhaseTimeoutOutOfBounds(uint256,uint256,uint256)", + "selector": "0xdb0c8dfe" + }, + { + "signature": "PubKeyIsZero()", + "selector": "0x46e80dd3" + }, + { + "signature": "PubKeyMismatch()", + "selector": "0x203f11f1" + }, + { + "signature": "PubKeyNotOnCurve()", + "selector": "0x65858cdf" + }, + { + "signature": "ReadFailure(uint256,bytes)", + "selector": "0x7e5e2d9e" + }, + { + "signature": "RecoverRangeBeyondIssued()", + "selector": "0x82eacf35" + }, + { + "signature": "RecoverRangeInvalid()", + "selector": "0xd1402d7e" + }, + { + "signature": "ReentrancyGuardReentrantCall()", + "selector": "0x3ee5aeb5" + }, + { + "signature": "RegistryFull()", + "selector": "0xd1071567" + }, + { + "signature": "RemedySelectionPlanMismatch(bytes32,bytes32)", + "selector": "0xba21e405" + }, + { + "signature": "RemedySelectionSourceDrift()", + "selector": "0xa73be52b" + }, + { + "signature": "ResolutionBasisMismatch()", + "selector": "0x1e302f9f" + }, + { + "signature": "ResolutionPlanMismatch(uint8,uint8)", + "selector": "0x86edcb1d" + }, + { + "signature": "RevealDeadlineElapsed(bytes32,uint256,uint256)", + "selector": "0x78763ab2" + }, + { + "signature": "RollupBudgetBelowFloor()", + "selector": "0xa70732ee" + }, + { + "signature": "RoundPlanMismatch(bytes32,bytes32)", + "selector": "0xa0835b01" + }, + { + "signature": "RoundSnapshotSealed(bytes32,uint256)", + "selector": "0x2c586f2e" + }, + { + "signature": "SelectionAuthorityPinned(uint256)", + "selector": "0xc8fef2eb" + }, + { + "signature": "SelectionMutationInProgress()", + "selector": "0xf2539721" + }, + { + "signature": "SpendFailed()", + "selector": "0x5435f21a" + }, + { + "signature": "StakingInvariantNotConfigured()", + "selector": "0x6d31b9f3" + }, + { + "signature": "SubmittedMessagesTooLarge(uint256,uint256)", + "selector": "0xdc8b19e1" + }, + { + "signature": "TooManyMessages()", + "selector": "0x1ec0b2f7" + }, + { + "signature": "TopUpCannotExtendSchedule()", + "selector": "0x49449933" + }, + { + "signature": "TransactionAlreadyAccepted()", + "selector": "0x7bdaa2b4" + }, + { + "signature": "TransactionAlreadyExists(bytes32)", + "selector": "0x8ce457d6" + }, + { + "signature": "TransactionInRecomputation()", + "selector": "0x00ebaa7c" + }, + { + "signature": "TransitionCallerEdgeNotAllowed(address,uint8,uint8)", + "selector": "0x8d0b4cad" + }, + { + "signature": "TransitionEndpointNotAllowed(uint8,uint8)", + "selector": "0x78425d91" + }, + { + "signature": "TribunalAlreadyFinalized()", + "selector": "0x6cb27c0f" + }, + { + "signature": "TribunalNotFound()", + "selector": "0x609bbfa8" + }, + { + "signature": "Unauthorized()", + "selector": "0x82b42900" + }, + { + "signature": "UnauthorizedDelegatorClaim()", + "selector": "0xdcc541d1" + }, + { + "signature": "UnauthorizedInflationRequest()", + "selector": "0x7d8f3b9e" + }, + { + "signature": "UnbanCapExceeded(uint256,uint256,uint256)", + "selector": "0x1d49490f" + }, + { + "signature": "UnfinishedTransactions()", + "selector": "0xf082b82d" + }, + { + "signature": "UnknownGenAction()", + "selector": "0xe43351e0" + }, + { + "signature": "UnknownStakingError()", + "selector": "0x109ce524" + }, + { + "signature": "ValidatorDoesNotExist()", + "selector": "0xe51315d2" + }, + { + "signature": "ValidatorMustNotBeDelegator()", + "selector": "0x85d35a02" + }, + { + "signature": "ValidatorWalletAlreadyDeployed()", + "selector": "0xb9ceb484" + }, + { + "signature": "ValueBackingMismatch(uint256,uint256)", + "selector": "0xf23d468d" + }, + { + "signature": "VestingAlreadyExists()", + "selector": "0xe7075d2a" + }, + { + "signature": "VestingAlreadyStopped()", + "selector": "0xd731022d" + }, + { + "signature": "VestingDeploymentFailed()", + "selector": "0x0f5cc9de" + }, + { + "signature": "VestingDoesNotExist()", + "selector": "0x57fd8e07" + }, + { + "signature": "VestingNotStopped()", + "selector": "0x6e32bb06" + }, + { + "signature": "WithdrawExceedsVested()", + "selector": "0x8b6a4865" + }, + { + "signature": "WrongRecomputationTransaction()", + "selector": "0x4d1fe80e" + }, + { + "signature": "ZeroAddress(string)", + "selector": "0xeac0d389" + }, + { + "signature": "ZeroAmount()", + "selector": "0x1f2a2005" + }, + { + "signature": "ZeroTotalWeight()", + "selector": "0x098404de" + }, + { + "signature": "ZeroValue()", + "selector": "0x7c946ed7" + }, + { + "signature": "RandomSeedAlreadySet()", + "selector": "0x7d6b9724" + }, + { + "signature": "SlashGovernanceDelayPassed()", + "selector": "0x06de1175" + }, + { + "signature": "SlashPercentageTooHigh()", + "selector": "0x0edf5154" + }, + { + "signature": "SlashRevokedToRemoveTooHigh(uint256,uint256)", + "selector": "0x217f3b2e" + }, + { + "signature": "OnlyGEN()", + "selector": "0x6a10007b" + }, + { + "signature": "OnlyIdleness()", + "selector": "0xf2c0764c" + }, + { + "signature": "OnlyIdlenessOrTribunal()", + "selector": "0xfcde63e9" + }, + { + "signature": "OnlyStakingContract()", + "selector": "0xd807afce" + }, + { + "signature": "OnlyStakingRouter()", + "selector": "0x25cbc0a7" + }, + { + "signature": "OnlyTransactions()", + "selector": "0x516257a3" + }, + { + "signature": "OnlyTransactionsOrTribunal()", + "selector": "0x6c4db06b" + }, + { + "signature": "OnlyTribunal()", + "selector": "0x811befe9" + }, + { + "signature": "EmptyTransaction()", + "selector": "0x260c9d62" + }, + { + "signature": "FailedTransferCall()", + "selector": "0x3825f587" + }, + { + "signature": "FinalizationNotAllowed()", + "selector": "0xe1b3b3b7" + }, + { + "signature": "FinalizationWindowForRevealingNotOpened()", + "selector": "0xabc07e66" + }, + { + "signature": "TransactionCanNotBeFinalized()", + "selector": "0xd9be37ca" + }, + { + "signature": "TransactionNotAcceptedNorUndetermined()", + "selector": "0x90cb8b61" + }, + { + "signature": "TransactionNotFinalized()", + "selector": "0xe4e81f79" + }, + { + "signature": "TransactionNotFound()", + "selector": "0x31fb878f" + }, + { + "signature": "TransactionNotFound(bytes32)", + "selector": "0x925fea73" + }, + { + "signature": "TransactionNotTerminal()", + "selector": "0x1d3a409a" + }, + { + "signature": "TransactionStillValid()", + "selector": "0xc4afd8fa" + }, + { + "signature": "DeepthoughtCallFailed()", + "selector": "0xbd5ac82f" + }, + { + "signature": "FailedTransfer(address)", + "selector": "0x3f32e1dd" + }, + { + "signature": "NFTMinterCallFailed()", + "selector": "0x64c31e4b" + }, + { + "signature": "NFTMinterNotConfigured()", + "selector": "0xa0c98f30" + }, + { + "signature": "NumberOfValidatorsExceedsAvailable()", + "selector": "0x9c637db9" + }, + { + "signature": "ValidatorsConsumed()", + "selector": "0xeae94a56" + }, + { + "signature": "ValidatorsUnavailable()", + "selector": "0xd0b5c3bb" + }, + { + "signature": "LeaderResultHashAlreadySet()", + "selector": "0x584764ae" + }, + { + "signature": "ValidatorAlreadyCommitted()", + "selector": "0xf8961aee" + }, + { + "signature": "ValidatorAlreadyInTree()", + "selector": "0x45be71b6" + }, + { + "signature": "ValidatorAlreadyJoined()", + "selector": "0x71d16bc6" + }, + { + "signature": "ValidatorAlreadyRevealed()", + "selector": "0x00d6a91b" + }, + { + "signature": "ValidatorAlreadyVoted()", + "selector": "0x6e271ebe" + }, + { + "signature": "VoteAlreadyCommitted()", + "selector": "0xcf1c5b9c" + }, + { + "signature": "VoteAlreadyRevealed()", + "selector": "0x3246ac36" + }, + { + "signature": "AllValidatorsCommitted()", + "selector": "0xb467acd8" + }, + { + "signature": "AllValidatorsConsumed()", + "selector": "0x54055ab9" + }, + { + "signature": "ValidatorSelectionFailed()", + "selector": "0x1f90236d" + }, + { + "signature": "ValidValidatorNotFound()", + "selector": "0x1c177f6c" + }, + { + "signature": "ValidatorBelowMinimumStake()", + "selector": "0x0b294dc3" + }, + { + "signature": "ValidatorExitExceedsShares()", + "selector": "0xfddb7740" + }, + { + "signature": "ValidatorMayNotBeDelegator()", + "selector": "0x359b3ac0" + }, + { + "signature": "ValidatorMayNotDepositZeroValue()", + "selector": "0xffb117c5" + }, + { + "signature": "ValidatorMayNotJoinWithZeroValue()", + "selector": "0xd25ef26f" + }, + { + "signature": "ValidatorNotActive()", + "selector": "0xa6ce15f6" + }, + { + "signature": "ValidatorNotInTree()", + "selector": "0x8ee72f3f" + }, + { + "signature": "ValidatorNotJoined()", + "selector": "0xffc673e8" + }, + { + "signature": "ValidatorWithdrawalExceedsStake()", + "selector": "0xfb7f2a7f" + }, + { + "signature": "NoIdleValidator()", + "selector": "0xede1b7ce" + }, + { + "signature": "NonGenVMContract()", + "selector": "0xc1ba7c94" + }, + { + "signature": "NoRotationsLeft()", + "selector": "0xe0bf2581" + }, + { + "signature": "NoSenderForTransaction()", + "selector": "0xa0b18673" + }, + { + "signature": "NoValidatorsAvailable()", + "selector": "0xc4e41c46" + }, + { + "signature": "NoValidatorsFound()", + "selector": "0x9b7fa1e5" + } +] diff --git a/package.json b/package.json index 793e9d0c..cea35c41 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,12 @@ "description": "GenLayer documentation", "scripts": { "dev": "npm run node-generate-changelog && npm run node-update-setup-guide && npm run node-update-config && npm run node-update-docker-compose && npm run node-update-monitoring-docker-compose && npm run node-update-monitoring-alloy-config && npm run node-update-greybox && npm run node-generate-api-docs && node scripts/generate-full-docs.js && node scripts/check-llm-exports.js && next dev", - "build": "npm run check:protocol-docs && npm run node-generate-changelog && npm run node-update-setup-guide && npm run node-update-config && npm run node-update-docker-compose && npm run node-update-monitoring-docker-compose && npm run node-update-monitoring-alloy-config && npm run node-update-greybox && npm run node-generate-api-docs && node scripts/generate-full-docs.js && node scripts/check-llm-exports.js && next build", + "build": "npm run check:protocol-docs && npm run check:v06-release-docs && npm run node-generate-changelog && npm run node-update-setup-guide && npm run node-update-config && npm run node-update-docker-compose && npm run node-update-monitoring-docker-compose && npm run node-update-monitoring-alloy-config && npm run node-update-greybox && npm run node-generate-api-docs && node scripts/generate-full-docs.js && node scripts/check-llm-exports.js && next build", "start": "next start", "test:e2e": "playwright test", "generate-sitemap": "node scripts/generate-sitemap-xml.js", "check:protocol-docs": "node scripts/check-protocol-docs.js", + "check:v06-release-docs": "node scripts/check-v06-release-docs.js", "node-generate-changelog": "node scripts/generate-changelog.js", "node-generate-api-docs": "node scripts/generate-api-docs.js", "node-update-setup-guide": "node scripts/update-setup-guide-versions.js", diff --git a/pages/api-references/genlayer-cli.mdx b/pages/api-references/genlayer-cli.mdx index 0008f17c..47b84f48 100644 --- a/pages/api-references/genlayer-cli.mdx +++ b/pages/api-references/genlayer-cli.mdx @@ -234,7 +234,7 @@ Large numbers that exceed JavaScript's safe integer range are automatically hand USAGE: genlayer receipt Get transaction receipt genlayer appeal Appeal a transaction - genlayer appeal-bond Show minimum appeal bond required + genlayer appeal-bond Show full appeal charge (bond + induced-work funding) OPTIONS (receipt): --status Status to wait for (default: FINALIZED) @@ -244,17 +244,17 @@ OPTIONS (receipt): --stderr Print only stderr from the receipt OPTIONS (appeal): - --bond Appeal bond amount (e.g. 500gen, 0.5gen). Auto-calculated if omitted + --bond Total appeal charge: bond + induced-work funding. Auto-calculated if omitted --rpc RPC URL override EXAMPLES: - # Check the minimum bond required to appeal + # Check the full charge required to appeal genlayer appeal-bond 0x1234... - # Appeal with auto-calculated bond + # Appeal with an authoritative, auto-calculated charge genlayer appeal 0x1234... - # Appeal with explicit bond + # Appeal with an explicit total charge genlayer appeal 0x1234... --bond 500gen ``` @@ -522,4 +522,3 @@ We welcome contributions to GenLayerJS SDK! Whether it's new features, improved ## License This project is licensed under the ... License - see the [LICENSE](LICENSE) file for details. - diff --git a/pages/api-references/genlayer-cli/index.mdx b/pages/api-references/genlayer-cli/index.mdx index b305231b..d42c5a4c 100644 --- a/pages/api-references/genlayer-cli/index.mdx +++ b/pages/api-references/genlayer-cli/index.mdx @@ -26,7 +26,7 @@ Version: `0.39.1` - `genlayer network` — Network configuration - `genlayer receipt` — Get transaction receipt by hash - `genlayer appeal` — Appeal a transaction by its hash -- `genlayer appeal-bond` — Show minimum appeal bond required for a transaction +- `genlayer appeal-bond` — Show the full appeal charge (bond plus induced-work funding) - `genlayer trace` — Get execution trace for a transaction (return data, stdout, stderr, GenVM logs) - `genlayer finalize` — Finalize a transaction that is ready to be finalized (public call) - `genlayer finalize-batch` — Finalize a batch of idle transactions in a single call (public call) diff --git a/pages/api-references/genlayer-cli/transactions/appeal-bond.mdx b/pages/api-references/genlayer-cli/transactions/appeal-bond.mdx index 882fd3ba..54400859 100644 --- a/pages/api-references/genlayer-cli/transactions/appeal-bond.mdx +++ b/pages/api-references/genlayer-cli/transactions/appeal-bond.mdx @@ -2,7 +2,7 @@ title: appeal-bond --- -Show minimum appeal bond required for a transaction +Show the full appeal charge required for a transaction. The historical command name is retained for compatibility; the returned value includes the bond and induced-work funding. ### Usage diff --git a/pages/api-references/genlayer-cli/transactions/appeal.mdx b/pages/api-references/genlayer-cli/transactions/appeal.mdx index 606c70c1..267be05a 100644 --- a/pages/api-references/genlayer-cli/transactions/appeal.mdx +++ b/pages/api-references/genlayer-cli/transactions/appeal.mdx @@ -2,7 +2,7 @@ title: appeal --- -Appeal a transaction by its hash +Appeal a transaction by its hash. The CLI binds the active decision and uses the schedule-extending appeal path; if `--bond` is omitted it reads the authoritative bond-plus-funding charge. ### Usage @@ -16,6 +16,6 @@ Appeal a transaction by its hash | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | -| | --bond <amount> | Appeal bond amount (e.g. 500gen, 0.5gen). Auto-calculated if omitted | No | | +| | --bond <amount> | Total appeal charge (bond + induced-work funding; e.g. 500gen, 0.5gen). Auto-calculated if omitted | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | | -h | --help | display help for command | No | | diff --git a/pages/api-references/genlayer-js.md b/pages/api-references/genlayer-js.md index 6b8a52b4..f863065b 100644 --- a/pages/api-references/genlayer-js.md +++ b/pages/api-references/genlayer-js.md @@ -1,313 +1,130 @@ -# GenLayerJS - - -## About +--- +description: "Create GenLayer JavaScript clients, estimate and submit fee-funded transactions, wait for outcomes, and appeal decisions." +--- -GenLayerJS SDK is a TypeScript library designed for developers building decentralized applications (Dapps) on the GenLayer protocol. This SDK provides a comprehensive set of tools to interact with the GenLayer network, including client creation, transaction handling, event subscriptions, and more, all while leveraging the power of Viem as the underlying blockchain client. - -## Prerequisites - -Before installing GenLayerJS SDK, ensure you have the following prerequisites installed: +# GenLayerJS -- Node.js (>= 16.x) -- npm (>= 7.x) +`genlayer-js` is the TypeScript client for GenLayer RPC, Intelligent Contracts, transaction lifecycle, fees, appeals, wallets, staking, and developer rewards. It builds on Viem-compatible accounts and EIP-1193 browser providers. -## ️ Installation and Usage +## Install -To install the GenLayerJS SDK, use the following command: ```bash -$ npm install genlayer-js +npm install genlayer-js ``` -Here’s how to initialize the client and connect to the GenLayer Simulator: - -### Reading a Transaction -```typescript -import { localnet } from 'genlayer-js/chains'; -import { createClient } from "genlayer-js"; - -const client = createClient({ - chain: localnet, -}); -const transactionHash = "0x..."; +For the Consensus v0.6 preview, install the explicit v2.0 release candidate published in the release notes. Do not rely on the default npm tag to select a prerelease. -const transaction = await client.getTransaction({ hash: transactionHash }) -``` +## Create a client -### Waiting for Transaction Receipt ```typescript +import { createAccount, createClient } from 'genlayer-js'; import { localnet } from 'genlayer-js/chains'; -import { createClient } from "genlayer-js"; -import { TransactionStatus } from "genlayer-js/types"; +import { TransactionHashVariant } from 'genlayer-js/types'; +const account = createAccount(); const client = createClient({ chain: localnet, -}); - -// Get simplified receipt (default - removes binary data, keeps execution results) -const receipt = await client.waitForTransactionReceipt({ - hash: "0x...", - status: TransactionStatus.FINALIZED, - fullTransaction: false // Default - simplified for readability -}); - -// Get complete receipt with all fields -const fullReceipt = await client.waitForTransactionReceipt({ - hash: "0x...", - status: TransactionStatus.FINALIZED, - fullTransaction: true // Complete receipt with all internal data + account, }); ``` -### Reading a contract -```typescript -import { localnet } from 'genlayer-js/chains'; -import { createClient } from "genlayer-js"; +Use `testnetBradbury`, `testnetAsimov`, or `studionet` for the corresponding hosted network. The v0.123 preview uses the matching RC's `studioDevnet` definition, which binds chain ID 61997, its RPC, and its consensus deployment together. -const client = createClient({ - chain: localnet, -}); - -const result = await client.readContract({ - // account: account, Account is optional when reading from contracts - address: contractAddress, - functionName: 'get_complete_storage', - args: [] - stateStatus: "accepted", -}) -``` +## Read a contract -### Writing a transaction ```typescript -import { localnet } from 'genlayer-js/chains'; -import { createClient, createAccount } from "genlayer-js"; - -const client = createClient({ - network: localnet, -}); - -const account = createAccount(); -const transactionHash = await client.writeContract({ - account: account, // using this account for this transaction +const value = await client.readContract({ address: contractAddress, - functionName: 'account', - args: ['new_storage'], - value: 0, // value is optional, if you want to send some native token to the contract + functionName: 'get_storage', + args: [], + transactionHashVariant: TransactionHashVariant.LATEST_FINAL, }); - -const receipt = await client.waitForTransactionReceipt({ - hash: txHash, - status: TransactionStatus.FINALIZED, // or ACCEPTED - fullTransaction: false // False by default - returns simplified receipt for better readability -}) - ``` -### Checking execution results +## Estimate and submit a write -A transaction can be finalized by consensus but still have a failed execution. Always check `txExecutionResult` before reading contract state: +Build the allocation from a measured contract profile, then let the SDK apply current network prices and caps: ```typescript -import { ExecutionResult, TransactionStatus } from "genlayer-js/types"; - -const receipt = await client.waitForTransactionReceipt({ - hash: txHash, - status: TransactionStatus.FINALIZED, +const estimate = await client.estimateTransactionFees({ + leaderTimeunitsAllocation: 125n, + validatorTimeunitsAllocation: 250n, + executionBudgetPerRound: 786_500n, + totalMessageFees: 0n, + appealRounds: 1n, + rotations: [1n, 1n], }); -if (receipt.txExecutionResultName === ExecutionResult.FINISHED_WITH_RETURN) { - // Execution succeeded — safe to read state - const result = await client.readContract({ - address: contractAddress, - functionName: "get_storage", - args: [], - }); -} else if (receipt.txExecutionResultName === ExecutionResult.FINISHED_WITH_ERROR) { - // Execution failed — contract state was not modified - console.error("Contract execution failed"); -} else { - // NOT_VOTED — execution hasn't completed - console.warn("Execution result not yet available"); -} +const txId = await client.writeContract({ + address: contractAddress, + functionName: 'update_storage', + args: ['new value'], + fees: { + distribution: estimate.distribution, + feeValue: estimate.feeValue, + }, +}); ``` -### Fetching emitted messages and triggered transactions - -Transactions can emit messages to other contracts. These messages create new child transactions when processed: - -```typescript -const tx = await client.getTransaction({ hash: txHash }); - -// Messages emitted by the contract during execution -console.log(tx.messages); -// [{messageType, recipient, value, data, onAcceptance, saltNonce}, ...] +When `rotations` is omitted, the estimator funds the chain's configured maximum for every leader round. Pass an explicit array, including `[0n]`, when the transaction should use a smaller posture. -// Child transaction IDs created from those messages (separate call) -const childTxIds = await client.getTriggeredTransactionIds({ hash: txHash }); -console.log(childTxIds); -// ["0xabc...", "0xdef..."] -``` +For applications, prefer a checked-in [`fee-profile.json`](/developers/decentralized-applications/fee-profiling-and-estimation) or [Transaction Kit](/developers/decentralized-applications/transaction-kit-integration) over hand-maintained allocation constants. -### Debugging transaction execution +## Simulate and derive a preset -Use `debugTraceTransaction` to inspect the full execution trace of a transaction, including return data, errors, and GenVM logs: +Studio can return fee accounting with a write simulation. The one-step helper turns the observed execution and message use into a transaction estimate: ```typescript -const trace = await client.debugTraceTransaction({ - hash: txHash, - round: 0, // optional, defaults to 0 +const recommended = await client.estimateTransactionFeesForWrite({ + address: contractAddress, + functionName: 'update_storage', + args: ['new value'], }); - -console.log(trace.result_code); // 0=success, 1=user error, 2=VM error -console.log(trace.return_data); // hex-encoded contract return data -console.log(trace.stderr); // standard error output -console.log(trace.genvm_log); // detailed GenVM execution logs ``` -### Using with a wallet provider (MetaMask) +Use this during profiling and development, not as a slow simulation before every production click. -When building a browser dApp, create two clients: one for reads (no wallet needed) and one for writes (signed by the wallet). This follows the standard viem pattern and keeps concerns separated. +## Wait for the right outcome ```typescript -import { createClient } from "genlayer-js"; -import { testnetBradbury } from "genlayer-js/chains"; -import { TransactionStatus } from "genlayer-js/types"; - -// Read client — talks directly to GenLayer RPC, no wallet needed -const readClient = createClient({ - chain: testnetBradbury, -}); - -// Write client — signs transactions through the wallet -const writeClient = createClient({ - chain: testnetBradbury, - account: address as `0x${string}`, // from wallet connection - provider: window.ethereum, // or from a wallet SDK -}); +import { isSuccessful } from 'genlayer-js'; -// Use readClient for all reads -const result = await readClient.readContract({ - address: contractAddress, - functionName: "get_storage", - args: [], -}); - -const tx = await readClient.getTransaction({ hash: txHash }); - -// Use writeClient for transactions (MetaMask popup) -const txHash = await writeClient.writeContract({ - address: contractAddress, - functionName: "update_storage", - args: ["new_value"], - value: BigInt(0), -}); - -// Either client can wait for receipts -const receipt = await readClient.waitForTransactionReceipt({ - hash: txHash, - status: TransactionStatus.ACCEPTED, -}); +const receipt = await client.waitForFinalization({ hash: txId }); +if (!isSuccessful(receipt)) { + throw new Error( + `Transaction did not succeed: ${receipt.statusName} / ${receipt.txExecutionResultName}`, + ); +} ``` -### Switching the wallet to the correct network +`waitForDecision` waits for a materialized decision; `waitForFinalization` also waits for final fee settlement and refunds. Advanced protocol consumers can call `client.advanced.getTransactionLifecycle({ hash })` to read the stored and projected status, resolution action, and active decision identity. -When using MetaMask or another browser wallet, the wallet may be connected to a different chain than what your client is configured for. Use `client.connect()` to switch the wallet to the correct GenLayer network before sending transactions: +## Appeal a decision ```typescript -import { createClient } from "genlayer-js"; -import { studionet } from "genlayer-js/chains"; - -const client = createClient({ - chain: studionet, - account: address as `0x${string}`, -}); - -// Switch MetaMask to the correct chain (adds the network if not present) -await client.connect("studionet"); - -// Now transactions will go to the right network -const txHash = await client.writeContract({ - address: contractAddress, - functionName: "create_profile", - args: ["alice", "Hello world"], - value: BigInt(0), -}); +const charge = await client.getAppealCharge({ txId }); +await client.appealTransaction({ txId, value: charge }); ``` -Available networks: `"localnet"`, `"studionet"`, `"testnetAsimov"`, `"testnetBradbury"`. +`getAppealCharge` returns the bond plus induced-work funding. `appealTransaction` binds the current decision and uses `topUpAndSubmitAppeal`, which is safe whether or not the next round was pre-funded. The old `getMinAppealBond` name remains as a deprecated compatibility alias and also returns the complete charge. -> **Note:** If the wallet is on the wrong chain when you call `writeContract`, the SDK will throw a clear error telling you which chain the wallet is on vs. which chain the client expects. Call `client.connect()` to resolve this. - -### Staking Operations - -The SDK provides staking functionality for validators and delegators on testnet-bradbury (and testnet-asimov). +## Browser wallets ```typescript -import { testnetBradbury } from 'genlayer-js/chains'; -import { createClient, createAccount } from "genlayer-js"; - -const account = createAccount(); -const client = createClient({ +const walletClient = createClient({ chain: testnetBradbury, - account, + account: walletAddress, + provider: window.ethereum, }); -// Get epoch info (includes timing estimates and inflation data) -const epochInfo = await client.getEpochInfo(); -// { -// currentEpoch: 2n, -// epochMinDuration: 86400n, // 1 day in seconds -// currentEpochStart: Date, -// currentEpochEnd: Date | null, -// nextEpochEstimate: Date | null, -// validatorMinStake: "0.01 GEN", -// delegatorMinStake: "42 GEN", -// activeValidatorsCount: 6n, -// inflation: "1000 GEN", // Total inflation for current epoch -// inflationRaw: 1000000000000000000000n, -// totalWeight: 500000000000000000000000n, // Total stake weight -// totalClaimed: "500 GEN", // Total claimed rewards -// } - -// Get active validators -const validators = await client.getActiveValidators(); - -// Check if address is a validator -const isValidator = await client.isValidator("0x..."); - -// Get validator info -const validatorInfo = await client.getValidatorInfo("0x..."); - -// Join as validator (requires account with funds) -const result = await client.validatorJoin({ amount: "42000gen" }); - -// Join as delegator -const delegateResult = await client.delegatorJoin({ - validator: "0x...", - amount: "42gen", -}); +await walletClient.connect('testnetBradbury'); ``` -## Key Features - -* **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-bradbury and testnet-asimov. -* **Wallet Integration***: Seamless integration with MetaMask for managing user accounts. -* **Gas Estimation***: Estimate gas fees for executing transactions on GenLayer. - -_* under development_ - -## Documentation - -For detailed information on how to use GenLayerJS SDK, please refer to our [documentation](https://docs.genlayer.com/). - - - -## Contributing - -We welcome contributions to GenLayerJS SDK! Whether it's new features, improved infrastructure, or better documentation, your input is valuable. Please read our [CONTRIBUTING](https://github.com/genlayerlabs/genlayer-js/blob/main/CONTRIBUTING.md) guide for guidelines on how to submit your contributions. +Create an account-free client for reads and a provider-backed client for writes. Before signing, connect the wallet to the exact chain object used by the write client. -## License +## API reference -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +- [Contract, fee, appeal, and developer-reward methods](./genlayer-js/contracts) +- [Transaction and lifecycle methods](./genlayer-js/transactions) +- [Staking methods](./genlayer-js/staking) +- [Consensus v0.6 migration guide](/developers/consensus-v06-migration) diff --git a/pages/api-references/genlayer-js/contracts.md b/pages/api-references/genlayer-js/contracts.md index bbe4fdd9..b889f366 100644 --- a/pages/api-references/genlayer-js/contracts.md +++ b/pages/api-references/genlayer-js/contracts.md @@ -70,10 +70,15 @@ Simulates a state-modifying contract call without executing on-chain. | args | `CalldataEncodable[]` | no | | | kwargs | `Map \| {[key: string]: CalldataEncodable}` | no | | | rawReturn | `RawReturn` | no | | +| includeReceipt | `IncludeReceipt` | no | | +| value | `BigNumberish` | no | | | leaderOnly | `boolean` | no | | +| fees | `TransactionFeeOptions` | no | | | transactionHashVariant | `TransactionHashVariant` | no | | -**Returns:** `RawReturn extends true ? 0x${string} : CalldataEncodable` +**Returns:** `IncludeReceipt extends true + ? SimulateWriteContractResult + : RawReturn extends true ? 0x${string} : CalldataEncodable` --- @@ -88,14 +93,15 @@ Executes a state-modifying function on a contract through consensus. Returns the | functionName | `string` | yes | | | args | `CalldataEncodable[]` | no | | | kwargs | `Map \| {[key: string]: CalldataEncodable}` | no | | -| value | `bigint` | yes | | +| value | `bigint` | no | | | leaderOnly | `boolean` | no | | | consensusMaxRotations | `number` | no | | +| validUntil | `BigNumberish` | no | | +| fees | `TransactionFeeOptions` | no | | **Returns:** `0x${string}` --- - ### deployContract Deploys a new intelligent contract to GenLayer. Returns the transaction hash. @@ -108,12 +114,93 @@ Deploys a new intelligent contract to GenLayer. Returns the transaction hash. | kwargs | `Map \| {[key: string]: CalldataEncodable}` | no | | | leaderOnly | `boolean` | no | | | consensusMaxRotations | `number` | no | | +| validUntil | `BigNumberish` | no | | +| fees | `TransactionFeeOptions` | no | | + +--- + +### getCurrentFeePolicy + +Returns the active fee price policy used to build user-side caps. + +_No parameters._ + +**Returns:** `FeePolicyQuote` + +--- + +### estimateFeesDistribution + +Builds a fee distribution with caps derived from the active fee policy. +Omitted rotations fund the chain's configured consensus maximum. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| args | `FeeEstimateOptions` | no | | + +**Returns:** `FeesDistribution` + +--- + +### estimateTransactionFees + +Builds a complete transaction `fees` object, including feeValue. +Studio has no on-chain FeeManager in the chain definition, so this uses +the same deterministic round-fee math as Studio trusted mode there. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| args | `FeeEstimateOptions` | no | | + +**Returns:** `TransactionFeeEstimate` + +--- + +### estimateTransactionFeesFromSimulation + +Builds a trusted fee preset from a representative Studio simulation. +This turns the returned fee accounting/report into execution and message +budgets while preserving mode-2 message allocations when the simulation +was run with them. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| args | `SimulationFeeEstimateOptions` | yes | | + +**Returns:** `TransactionFeeEstimate` + +--- + +### estimateTransactionFeesForWrite + +Builds a trusted fee preset for a concrete write call in one step. +The method first gives the simulation a baseline fee budget, then uses +the returned Studio/GenVM fee accounting to derive the preset the dapp +should pass with the real transaction. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| args | `WriteFeeEstimateOptions` | yes | | + +**Returns:** `TransactionFeeEstimate` + +--- + +### getAppealCharge + +Returns the full authoritative appeal charge (bond plus appeal funding). + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| txId | ``0x${string}`` | yes | | + +**Returns:** `bigint` --- ### getMinAppealBond -Calculates the minimum bond required to appeal a transaction. +@deprecated Use getAppealCharge. This legacy name also returns bond plus appeal funding. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -168,16 +255,118 @@ Checks if a transaction can be appealed. --- +### getDeveloperNft + +Returns a developer's NFT reward record, or null when no NFT is registered. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| developer | `Address` | yes | | + +**Returns:** `DeveloperNft | null` + +--- + +### getClaimableRewardsFromFees + +Returns claimable developer-NFT rewards accrued from transaction fees. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| nftId | `BigNumberish` | yes | | + +**Returns:** `bigint` + +--- + +### getClaimableRewardsFromInflation + +Returns claimable developer-NFT rewards accrued from inflation. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| nftId | `BigNumberish` | yes | | +| numberOfEpochsToClaim | `BigNumberish` | yes | | + +**Returns:** `bigint` + +--- + +### claimNftRewards + +Claims all currently available rewards for a developer NFT. Returns the EVM transaction hash. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| account | `Account` | no | | +| nftId | `BigNumberish` | yes | | + +**Returns:** `0x${string}` + +--- + +### claimNftEpochs + +Claims a bounded number of reward epochs for a developer NFT. Returns the EVM transaction hash. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| account | `Account` | no | | +| nftId | `BigNumberish` | yes | | +| numberOfEpochsToClaim | `BigNumberish` | yes | | + +**Returns:** `0x${string}` + +--- + ### appealTransaction Appeals a consensus transaction to trigger a new round of validation. +The call is bound to the active decision on both Studio and contract +networks. The schedule-extending entry point is safe for both pre-funded +and unfunded appeals, while submitAppeal rejects an unfunded next round. +When value is omitted, the authoritative appeal charge is used. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| account | `Account` | no | | +| txId | ``0x${string}`` | yes | | +| value | `bigint` | no | | + +--- + +### topUpFees + +Deposits additional fee budget for an existing consensus transaction. +Returns the signed EVM envelope hash on every backend. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | account | `Account` | no | | | txId | ``0x${string}`` | yes | | +| distribution | `FeesDistributionInput` | yes | | +| value | `bigint` | yes | | + +**Returns:** `0x${string}` + +--- + +### topUpAndSubmitAppeal + +Deposits appeal fee budget and submits an appeal in the same consensus call. +Returns the existing GenLayer transaction id, matching appealTransaction. +The call is bound to the active decision on both Studio and contract +networks. When value is omitted, the authoritative appeal charge is used. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| account | `Account` | no | | +| txId | ``0x${string}`` | yes | | +| distribution | `FeesDistributionInput` | yes | | | value | `bigint` | no | | +**Returns:** `0x${string}` + --- ### finalizeTransaction @@ -195,7 +384,35 @@ Finalizes a single GenLayer transaction that is ready to be finalized. Returns t ### finalizeIdlenessTxs -Batch-finalizes idle GenLayer transactions (those stuck without progressing). Returns the EVM transaction hash. +@deprecated The train separates attempt-bound resolution from +decision-bound finalization. Use resolveTransactions or +finalizeDecisions after classifying the lifecycle action. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| account | `Account` | no | | +| txIds | `readonly `0x${string}`[]` | yes | | + +**Returns:** `0x${string}` + +--- + +### resolveTransactions + +Resolves a batch of attempt-bound lifecycle actions. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| account | `Account` | no | | +| txIds | `readonly `0x${string}`[]` | yes | | + +**Returns:** `0x${string}` + +--- + +### finalizeDecisions + +Finalizes a batch of active, decision-bound transactions. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| diff --git a/pages/api-references/genlayer-js/staking.md b/pages/api-references/genlayer-js/staking.md index 06bc6d5b..e61af40c 100644 --- a/pages/api-references/genlayer-js/staking.md +++ b/pages/api-references/genlayer-js/staking.md @@ -9,7 +9,7 @@ Joins as a validator with the specified stake amount. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | amount | `bigint \| string` | yes | | -| operator | `Address` | no | | +| registration | `OperatorRegistrationProof` | yes | | **Returns:** `ValidatorJoinResult` @@ -71,7 +71,7 @@ Primes a validator for participation in the next epoch. ### setOperator -Sets the operator address for a validator wallet. +@deprecated Use initiateOperatorTransfer followed by completeOperatorTransfer. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -82,6 +82,57 @@ Sets the operator address for a validator wallet. --- +### initiateOperatorTransfer + +Starts the two-step operator rotation. The proof is checked against the +wallet-bound context before submission so a registration built for the +wrong registrar fails locally instead of as an opaque on-chain revert. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| options | `InitiateOperatorTransferOptions` | yes | | + +**Returns:** `StakingTransactionResult` + +--- + +### completeOperatorTransfer + +Completes a pending rotation. Callable by the wallet owner or the pending +operator, and only once the factory's operatorTransferDelay has elapsed. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| options | `CompleteOperatorTransferOptions` | yes | | + +**Returns:** `StakingTransactionResult` + +--- + +### cancelOperatorTransfer + +Abandons a pending rotation, leaving the current operator in place. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| options | `CancelOperatorTransferOptions` | yes | | + +**Returns:** `StakingTransactionResult` + +--- + +### getPendingOperator + +Reads the pending operator and when its transfer was initiated. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| validator | `Address` | yes | | + +**Returns:** `PendingOperatorInfo` + +--- + ### setIdentity Sets validator identity information (name, website, social links). @@ -144,7 +195,7 @@ Claims pending delegator withdrawals. ### isValidator -Checks if an address is an active validator. +Checks whether an address is a registered/joined validator wallet. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -166,6 +217,28 @@ Returns comprehensive information about a validator including stake, identity, a --- +### getCurrentEpoch + +Returns the current epoch number. + +_No parameters._ + +**Returns:** `bigint` + +--- + +### isValidatorBelowMin + +Checks whether a validator's self-stake is below the configured validator minimum. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| validator | `Address` | yes | | + +**Returns:** `boolean` + +--- + ### getStakeInfo Returns delegation stake information for a delegator-validator pair. @@ -203,7 +276,7 @@ Returns detailed data for a specific epoch. ### getActiveValidators -Returns addresses of all currently active validators. +Returns validators currently eligible for consensus duties. _No parameters._ @@ -213,7 +286,27 @@ _No parameters._ ### getActiveValidatorsCount -Returns the count of active validators. +Returns the count of validators currently eligible for consensus duties. + +_No parameters._ + +**Returns:** `bigint` + +--- + +### getJoinedValidators + +Returns every validator identity in the append-only joined registry. + +_No parameters._ + +**Returns:** `Address[]` + +--- + +### getJoinedValidatorsCount + +Returns the size of the append-only joined validator registry. _No parameters._ @@ -243,7 +336,6 @@ Returns banned validators with ban duration and permanent ban status. **Returns:** `BannedValidatorInfo[]` --- - ### getQuarantinedValidatorsDetailed Returns detailed quarantine information with pagination. diff --git a/pages/api-references/genlayer-js/transactions.md b/pages/api-references/genlayer-js/transactions.md index 66baa4dc..bc19d37f 100644 --- a/pages/api-references/genlayer-js/transactions.md +++ b/pages/api-references/genlayer-js/transactions.md @@ -9,7 +9,8 @@ Polls until a transaction reaches the specified status. Returns the transaction | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | hash | `TransactionHash` | yes | | -| status | `TransactionStatus` | yes | | +| status | `TransactionStatus` | no | | +| waitUntil | `TransactionReceiptWaitUntil` | no | | | interval | `number` | no | | | retries | `number` | no | | | fullTransaction | `boolean` | no | | @@ -18,9 +19,59 @@ Polls until a transaction reaches the specified status. Returns the transaction --- +### waitForDecision + +Polls until the stored transaction state contains a materialized decision. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| hash | `TransactionHash` | yes | | +| interval | `number` | no | | +| retries | `number` | no | | +| fullTransaction | `boolean` | no | | + +**Returns:** `GenLayerTransaction` + +--- + +### waitForFinalization + +Polls until the stored transaction state is finalized. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| hash | `TransactionHash` | yes | | +| interval | `number` | no | | +| retries | `number` | no | | +| fullTransaction | `boolean` | no | | + +**Returns:** `GenLayerTransaction` + +--- + +### getTransactionLifecycle + +`advanced.getTransactionLifecycle` exposes stored/projected status, +resolution action/source, and active decision identity. Contract networks +use one fixed-block lifecycle read. `Finalize` is an action, not a status +or separate readiness field. +A Studio deployment that does not yet serve `gen_getTransactionLifecycle` +degrades to the stored status its consumer surface does prove, rather +than failing the whole read. + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| hash | `TransactionHash` | yes | | +| timestamp | `number` | no | | + +**Returns:** `TransactionProtocolLifecycle` + +--- + ### getTransaction -Fetches transaction data including status, execution result, and consensus details. +Fetches a transaction with a simple stored lifecycle and split round data. +Use advanced.getTransactionLifecycle for protocol projection/action details. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| @@ -93,4 +144,3 @@ Estimates gas required for a transaction. **Returns:** `bigint` --- - diff --git a/pages/api-references/genlayer-py.md b/pages/api-references/genlayer-py.md index 0df4eae1..13b28c85 100644 --- a/pages/api-references/genlayer-py.md +++ b/pages/api-references/genlayer-py.md @@ -1,190 +1,106 @@ -# GenLayerPY - - - -## About - -GenLayerPY SDK is a python library designed for developers building decentralized applications (Dapps) on the GenLayer protocol. This SDK provides a comprehensive set of tools to interact with the GenLayer network, including client creation, transaction handling, event subscriptions, and more, all while leveraging the power of web3.py as the underlying blockchain client. - -## Prerequisites +--- +description: "Use the GenLayer Python SDK for fee-funded transactions, lifecycle reads, appeals, and contract interaction." +--- -Before installing GenLayerPY SDK, ensure you have the following prerequisites installed: - -- Python (>=3.12) +# GenLayerPY +`genlayer-py` is the Python client for GenLayer RPC, Intelligent Contracts, fee estimation, appeals, and transaction lifecycle. -## ️ Installation and Usage +## Install -To install the GenLayerPY SDK, use the following command: ```bash -$ pip install genlayer-py +pip install genlayer-py ``` -Here’s how to initialize the client and connect to the GenLayer Simulator: - -### Reading a Transaction -```python -from genlayer_py import create_client -from genlayer_py.chains import localnet - -client = create_client( - chain=localnet, -) - -transaction_hash = "0x..." - -transaction = client.get_transaction(hash=transaction_hash) +For the Consensus v0.6 preview, install the explicit v0.19 release candidate published in the release notes. PyPI prerelease versions are not selected by every unconstrained installer, so pin the RC directly. -``` +## Create a client -### Waiting for Transaction Receipt ```python -from genlayer_py import create_client +from genlayer_py import create_account, create_client from genlayer_py.chains import localnet -from genlayer_py.types import TransactionStatus - -client = create_client(chain=localnet) - -# Get simplified receipt (default - removes binary data, keeps execution results) -receipt = client.wait_for_transaction_receipt( - transaction_hash="0x...", - status=TransactionStatus.FINALIZED, - full_transaction=False # Default - simplified for readability -) -# Get complete receipt with all fields -full_receipt = client.wait_for_transaction_receipt( - transaction_hash="0x...", - status=TransactionStatus.FINALIZED, - full_transaction=True # Complete receipt with all internal data -) +account = create_account() +client = create_client(chain=localnet, account=account) ``` -### Reading a contract -```python -from genlayer_py import create_client -from genlayer_py.chains import localnet +Use the matching v0.19 RC's Studio-dev chain definition for the v0.123 preview. Do not substitute stable Studionet: Studio-dev is chain ID 61997 and has its own consensus deployment. -client = create_client( - chain=localnet, -) +## Read a contract -result = client.read_contract( +```python +value = client.read_contract( address=contract_address, - function_name='get_complete_storage', + function_name="get_storage", args=[], - state_status='accepted' ) ``` -### Writing a transaction -```python -from genlayer_py.chains import localnet -from genlayer_py import create_client, create_account +## Estimate and submit a write -client = create_client( - chain=localnet, +```python +estimate = client.estimate_transaction_fees( + { + "leaderTimeunitsAllocation": 125, + "validatorTimeunitsAllocation": 250, + "executionBudgetPerRound": 786_500, + "totalMessageFees": 0, + "appealRounds": 1, + "rotations": [1, 1], + } ) -account = create_account() - -transaction_hash = client.write_contract( - account=account, - transaction=transaction, +tx_id = client.write_contract( address=contract_address, - function_name='account', - args=['new_storage'], - value=0, // value is optional, if you want to send some native token to the contract -) -receipt = client.wait_for_transaction_receipt( - hash=transaction_hash, - status=TransactionStatus.FINALIZED, // or ACCEPTED - full_transaction=False // False by default - returns simplified receipt for better readability + function_name="update_storage", + args=["new value"], + fees={ + "distribution": estimate["distribution"], + "feeValue": estimate["feeValue"], + }, ) ``` -### Checking execution results +For applications, generate those allocation inputs from a checked-in [`fee-profile.json`](/developers/decentralized-applications/fee-profiling-and-estimation). The SDK still reads live prices and caps when it produces the final estimate. -A transaction can be finalized by consensus but still have a failed execution. Always check `tx_execution_result` before reading contract state: +Studio can also simulate one concrete write and derive a recommended preset: ```python -from genlayer_py import create_client, create_account -from genlayer_py.chains import testnet_bradbury -from genlayer_py.types import TransactionStatus, ExecutionResult - -client = create_client(chain=testnet_bradbury, account=create_account()) - -receipt = client.wait_for_transaction_receipt( - transaction_hash=tx_hash, - status=TransactionStatus.FINALIZED, +recommended = client.estimate_transaction_fees_for_write( + address=contract_address, + function_name="update_storage", + args=["new value"], ) - -if receipt.get("tx_execution_result_name") == ExecutionResult.FINISHED_WITH_RETURN.value: - # Execution succeeded — safe to read state - result = client.read_contract( - address=contract_address, - function_name="get_storage", - args=[], - ) -elif receipt.get("tx_execution_result_name") == ExecutionResult.FINISHED_WITH_ERROR.value: - # Execution failed — contract state was not modified - raise RuntimeError("Contract execution failed") -else: - # NOT_VOTED — execution hasn't completed - print("Execution result not yet available") ``` -### Fetching emitted messages and triggered transactions +Use simulation during profiling and development rather than before every production user action. -Transactions can emit messages to other contracts. These messages create new child transactions when processed: +## Wait for the right outcome ```python -tx = client.get_transaction(transaction_hash=tx_hash) +from genlayer_py.transactions import is_successful -# Messages emitted by the contract during execution -print(tx["messages"]) -# [{"messageType": 1, "recipient": "0x...", "value": 0, "data": "0x...", "onAcceptance": True, "saltNonce": 0}, ...] - -# Child transaction IDs created from those messages (separate call) -child_tx_ids = client.get_triggered_transaction_ids(transaction_hash=tx_hash) -print(child_tx_ids) -# ["0xabc...", "0xdef..."] +receipt = client.wait_for_finalization(tx_id) +if not is_successful(receipt): + raise RuntimeError( + f"Transaction did not succeed: " + f"{receipt['status_name']} / {receipt['tx_execution_result_name']}" + ) ``` -### Debugging transaction execution +`wait_for_decision` waits for a stored decision. `wait_for_finalization` also waits for fee settlement and refunds. `get_transaction_lifecycle` exposes the stored/projected state and protocol resolution action when the backend supports the advanced lifecycle RPC. -Use `debug_trace_transaction` to inspect the full execution trace of a transaction, including return data, errors, and GenVM logs: +## Appeal a decision ```python -trace = client.debug_trace_transaction( - transaction_hash=tx_hash, - round=0, # optional, defaults to 0 -) - -print(trace["result_code"]) # 0=success, 1=user error, 2=VM error -print(trace["return_data"]) # hex-encoded contract return data -print(trace["stderr"]) # standard error output -print(trace["genvm_log"]) # detailed GenVM execution logs +charge = client.get_appeal_charge(tx_id) +client.appeal_transaction(tx_id, value=charge) ``` -## Key Features - -* **Client Creation**: Easily create and configure a client to connect to GenLayer’s network. -* **Transaction Handling**: Send and manage transactions on the GenLayer network. -* **Gas Estimation**: Estimate gas fees for executing transactions on GenLayer. - -_* under development_ - - -## Documentation - -For detailed information on how to use GenLayerPY SDK, please refer to our [documentation](https://docs.genlayer.com/api-references/genlayer-py). - - -## Contributing - -We welcome contributions to GenLayerPY SDK! Whether it's new features, improved infrastructure, or better documentation, your input is valuable. Please read our [CONTRIBUTING](https://github.com/genlayerlabs/genlayer-py/blob/main/CONTRIBUTING.md) guide for guidelines on how to submit your contributions. +The charge includes the bond and induced-work funding. `appeal_transaction` resolves and binds the active decision and uses the safe `topUpAndSubmitAppeal` path. `get_min_appeal_bond` is a deprecated compatibility alias that also returns the complete charge. -## License +## API reference -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +- [Client methods and enums](./genlayer-py/api) +- [Fee Profiling & Estimation](/developers/decentralized-applications/fee-profiling-and-estimation) +- [Consensus v0.6 migration guide](/developers/consensus-v06-migration) diff --git a/pages/api-references/genlayer-py/api.md b/pages/api-references/genlayer-py/api.md index a3f957a5..234d8765 100644 --- a/pages/api-references/genlayer-py/api.md +++ b/pages/api-references/genlayer-py/api.md @@ -64,19 +64,19 @@ client.initialize_consensus_smart_contract(force_reset: bool = False) Executes a read-only contract call without modifying state. ```python -client.read_contract(address: Union, function_name: str, args: Optional = None, kwargs: Optional = None, account: Optional = None, raw_return: bool = False, transaction_hash_variant: TransactionHashVariant = , sim_config: Optional = None) +client.read_contract(address: Union, function_name: str, args: Union = None, kwargs: Union = None, account: Union = None, raw_return: bool = False, transaction_hash_variant: TransactionHashVariant = , sim_config: Union = None) ``` | Parameter | Type | Required | Default | |-----------|------|----------|---------| | address | `Union` | yes | | | function_name | `str` | yes | | -| args | `Optional` | no | None | -| kwargs | `Optional` | no | None | -| account | `Optional` | no | None | +| args | `Union` | no | None | +| kwargs | `Union` | no | None | +| account | `Union` | no | None | | raw_return | `bool` | no | False | | transaction_hash_variant | `TransactionHashVariant` | no | | -| sim_config | `Optional` | no | None | +| sim_config | `Union` | no | None | --- @@ -85,20 +85,22 @@ client.read_contract(address: Union, function_name: str, args: Optional = None, Executes a state-modifying function on a contract through consensus. Returns the transaction hash. ```python -client.write_contract(address: Union, function_name: str, account: Optional = None, consensus_max_rotations: Optional = None, value: int = 0, leader_only: bool = False, args: Optional = None, kwargs: Optional = None, sim_config: Optional = None) +client.write_contract(address: Union, function_name: str, account: Union = None, consensus_max_rotations: Union = None, value: int = 0, leader_only: bool = False, args: Union = None, kwargs: Union = None, sim_config: Union = None, valid_until: Union = None, fees: Union = None) ``` | Parameter | Type | Required | Default | |-----------|------|----------|---------| | address | `Union` | yes | | | function_name | `str` | yes | | -| account | `Optional` | no | None | -| consensus_max_rotations | `Optional` | no | None | +| account | `Union` | no | None | +| consensus_max_rotations | `Union` | no | None | | value | `int` | no | 0 | | leader_only | `bool` | no | False | -| args | `Optional` | no | None | -| kwargs | `Optional` | no | None | -| sim_config | `Optional` | no | None | +| args | `Union` | no | None | +| kwargs | `Union` | no | None | +| sim_config | `Union` | no | None | +| valid_until | `Union` | no | None | +| fees | `Union` | no | None | --- @@ -107,17 +109,20 @@ client.write_contract(address: Union, function_name: str, account: Optional = No Simulates a state-modifying contract call without executing on-chain. Localnet only. ```python -client.simulate_write_contract(address: Union, function_name: str, account: Optional = None, args: Optional = None, kwargs: Optional = None, sim_config: Optional = None, transaction_hash_variant: TransactionHashVariant = ) +client.simulate_write_contract(address: Union, function_name: str, account: Union = None, args: Union = None, kwargs: Union = None, value: int = 0, leader_only: bool = False, fees: Union = None, sim_config: Union = None, transaction_hash_variant: TransactionHashVariant = ) ``` | Parameter | Type | Required | Default | |-----------|------|----------|---------| | address | `Union` | yes | | | function_name | `str` | yes | | -| account | `Optional` | no | None | -| args | `Optional` | no | None | -| kwargs | `Optional` | no | None | -| sim_config | `Optional` | no | None | +| account | `Union` | no | None | +| args | `Union` | no | None | +| kwargs | `Union` | no | None | +| value | `int` | no | 0 | +| leader_only | `bool` | no | False | +| fees | `Union` | no | None | +| sim_config | `Union` | no | None | | transaction_hash_variant | `TransactionHashVariant` | no | | --- @@ -127,18 +132,20 @@ client.simulate_write_contract(address: Union, function_name: str, account: Opti Deploys a new intelligent contract to GenLayer. Returns the transaction hash. ```python -client.deploy_contract(code: Union, account: Optional = None, args: Optional = None, kwargs: Optional = None, consensus_max_rotations: Optional = None, leader_only: bool = False, sim_config: Optional = None) +client.deploy_contract(code: Union, account: Union = None, args: Union = None, kwargs: Union = None, consensus_max_rotations: Union = None, leader_only: bool = False, sim_config: Union = None, valid_until: Union = None, fees: Union = None) ``` | Parameter | Type | Required | Default | |-----------|------|----------|---------| | code | `Union` | yes | | -| account | `Optional` | no | None | -| args | `Optional` | no | None | -| kwargs | `Optional` | no | None | -| consensus_max_rotations | `Optional` | no | None | +| account | `Union` | no | None | +| args | `Union` | no | None | +| kwargs | `Union` | no | None | +| consensus_max_rotations | `Union` | no | None | | leader_only | `bool` | no | False | -| sim_config | `Optional` | no | None | +| sim_config | `Union` | no | None | +| valid_until | `Union` | no | None | +| fees | `Union` | no | None | --- @@ -174,35 +181,269 @@ client.get_contract_schema_for_code(contract_code: AnyStr) --- +### get_current_fee_policy + +Returns the active fee price policy used to build user-side caps. + +```python +client.get_current_fee_policy() +``` + +--- + +### estimate_fees_distribution + +Builds a fee distribution with caps derived from the active fee policy. + +```python +client.estimate_fees_distribution(options=None) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| options | `FeeEstimateOptions` | no | None | + +--- + +### estimate_transaction_fees + +Builds a complete transaction fees object, including `feeValue`. + +```python +client.estimate_transaction_fees(options=None) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| options | `FeeEstimateOptions` | no | None | + +--- + +### estimate_transaction_fees_from_simulation + +Builds a complete transaction fees object from a representative Studio simulation. + +```python +client.estimate_transaction_fees_from_simulation(options) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| options | `SimulationFeeEstimateOptions` | yes | | + +--- + +### estimate_transaction_fees_for_write + +Simulates one concrete Studio write and returns a complete transaction fees object. + +```python +client.estimate_transaction_fees_for_write( + address, + function_name, + account=None, + args=None, + kwargs=None, + value=0, + leader_only=False, + options=None, + sim_config=None, + transaction_hash_variant=TransactionHashVariant.LATEST_NONFINAL, +) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| address | `Address \| ChecksumAddress` | yes | | +| function_name | `str` | yes | | +| account | `LocalAccount` | no | None | +| args | `list[CalldataEncodable]` | no | None | +| kwargs | `dict[str, CalldataEncodable]` | no | None | +| value | `int` | no | 0 | +| leader_only | `bool` | no | False | +| options | `FeeEstimateOptions` | no | None | +| sim_config | `SimConfig` | no | None | +| transaction_hash_variant | `TransactionHashVariant` | no | `LATEST_NONFINAL` | + +--- + ### appeal_transaction Appeals a consensus transaction to trigger a new round of validation. Returns the original transaction_id (appeals operate on the same tx). +Missing decision/value inputs are filled from the authoritative quote +on both Studio and deployed Consensus. ```python -client.appeal_transaction(transaction_id: HexStr, account: Optional = None, value: int = 0) +client.appeal_transaction(transaction_id: HexStr, account: Union = None, value: Union = None, expected_decision_id: Union = None) ``` | Parameter | Type | Required | Default | |-----------|------|----------|---------| | transaction_id | `HexStr` | yes | | -| account | `Optional` | no | None | -| value | `int` | no | 0 | +| account | `Union` | no | None | +| value | `Union` | no | None | +| expected_decision_id | `Union` | no | None | + +--- + +### top_up_fees + +Deposits additional fee budget for an existing consensus transaction. + +```python +client.top_up_fees(transaction_id: HexStr, distribution: FeesDistributionInput, value: int, account: Union = None) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| transaction_id | `HexStr` | yes | | +| distribution | `FeesDistributionInput` | yes | | +| value | `int` | yes | | +| account | `Union` | no | None | + +**Returns:** `HexStr` + +--- + +### top_up_and_submit_appeal + +Deposits appeal funding and submits an appeal. + +Omitted decision/value inputs are resolved from the authoritative +appeal quote on both Studio and deployed Consensus. + +```python +client.top_up_and_submit_appeal(transaction_id: HexStr, distribution: FeesDistributionInput, account: Union = None, value: Union = None, expected_decision_id: Union = None) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| transaction_id | `HexStr` | yes | | +| distribution | `FeesDistributionInput` | yes | | +| account | `Union` | no | None | +| value | `Union` | no | None | +| expected_decision_id | `Union` | no | None | + +**Returns:** `HexStr` + +--- + +### can_appeal + +Checks whether the exact active decision can be appealed. + +```python +client.can_appeal(transaction_id: HexStr, expected_decision_id: Union = None) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| transaction_id | `HexStr` | yes | | +| expected_decision_id | `Union` | no | None | + +**Returns:** `bool` + +--- + +### get_appeal_quote + +Returns the latest decision id, appeal charges, and deadline. + +```python +client.get_appeal_quote(transaction_id: HexStr) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| transaction_id | `HexStr` | yes | | + +**Returns:** `Dict` + +--- + +### get_appeal_charge + +Returns the full appeal payment (bond plus induced-work funding). + +```python +client.get_appeal_charge(transaction_id: HexStr) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| transaction_id | `HexStr` | yes | | + +**Returns:** `int` + +--- + +### get_min_appeal_bond + +Deprecated alias for :meth:`get_appeal_charge`. + +```python +client.get_min_appeal_bond(transaction_id: HexStr) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| transaction_id | `HexStr` | yes | | + +**Returns:** `int` + +--- + +### wait_for_decision + +Poll until the stored transaction state is decided or terminal. + +```python +client.wait_for_decision(transaction_hash: Union, interval: int = 3000, retries: int = 10, full_transaction: bool = False) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| transaction_hash | `Union` | yes | | +| interval | `int` | no | 3000 | +| retries | `int` | no | 10 | +| full_transaction | `bool` | no | False | + +**Returns:** `GenLayerTransaction` + +--- + +### wait_for_finalization + +Poll until the stored transaction state is finalized. + +```python +client.wait_for_finalization(transaction_hash: Union, interval: int = 3000, retries: int = 10, full_transaction: bool = False) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| transaction_hash | `Union` | yes | | +| interval | `int` | no | 3000 | +| retries | `int` | no | 10 | +| full_transaction | `bool` | no | False | + +**Returns:** `GenLayerTransaction` --- ### wait_for_transaction_receipt -Polls until a transaction reaches the specified status. Returns the transaction receipt. +Poll for a stored decision (default) or stored finalization. ```python -client.wait_for_transaction_receipt(transaction_hash: Union, status: TransactionStatus = , interval: int = 3000, retries: int = 10, full_transaction: bool = False) +client.wait_for_transaction_receipt(transaction_hash: Union, wait_until: Literal = 'decided', interval: int = 3000, retries: int = 10, full_transaction: bool = False) ``` | Parameter | Type | Required | Default | |-----------|------|----------|---------| | transaction_hash | `Union` | yes | | -| status | `TransactionStatus` | no | | +| wait_until | `Literal` | no | 'decided' | | interval | `int` | no | 3000 | | retries | `int` | no | 10 | | full_transaction | `bool` | no | False | @@ -213,7 +454,12 @@ client.wait_for_transaction_receipt(transaction_hash: Union, status: Transaction ### get_transaction -Fetches transaction data including status, execution result, and consensus details. +Fetch transaction data with a stable stored-state ``lifecycle``. + +The lifecycle's ``state`` is one of processing, decided, finalized, or +canceled. Processing carries ``phase`` and decided carries ``outcome``. +The train exposes ``tx_execution_hash``; legacy receipt bytes are +unavailable, so ``tx_receipt`` is ``None``. ```python client.get_transaction(transaction_hash: Union) @@ -227,6 +473,27 @@ client.get_transaction(transaction_hash: Union) --- +### get_transaction_lifecycle + +Return advanced stored/projected/action protocol lifecycle data. + +If current Studio does not expose the advanced RPC, only its provable +stored status is returned: projection repeats it, resolution is +NoOp/Unspecified, and decision identity is inactive. + +```python +client.get_transaction_lifecycle(transaction_hash: Union, timestamp: Union = None) +``` + +| Parameter | Type | Required | Default | +|-----------|------|----------|---------| +| transaction_hash | `Union` | yes | | +| timestamp | `Union` | no | None | + +**Returns:** `ProtocolTransactionLifecycle` + +--- + ### get_triggered_transaction_ids Returns transaction IDs of child transactions created from emitted messages. @@ -262,29 +529,6 @@ client.debug_trace_transaction(transaction_hash: Union, round: int = 0) ## Types and Enums -### TransactionStatus - -Status of a GenLayer transaction in the consensus lifecycle. - -```python -TransactionStatus.UNINITIALIZED = "UNINITIALIZED" -TransactionStatus.PENDING = "PENDING" -TransactionStatus.PROPOSING = "PROPOSING" -TransactionStatus.COMMITTING = "COMMITTING" -TransactionStatus.REVEALING = "REVEALING" -TransactionStatus.ACCEPTED = "ACCEPTED" -TransactionStatus.UNDETERMINED = "UNDETERMINED" -TransactionStatus.FINALIZED = "FINALIZED" -TransactionStatus.CANCELED = "CANCELED" -TransactionStatus.APPEAL_REVEALING = "APPEAL_REVEALING" -TransactionStatus.APPEAL_COMMITTING = "APPEAL_COMMITTING" -TransactionStatus.READY_TO_FINALIZE = "READY_TO_FINALIZE" -TransactionStatus.VALIDATORS_TIMEOUT = "VALIDATORS_TIMEOUT" -TransactionStatus.LEADER_TIMEOUT = "LEADER_TIMEOUT" -``` - ---- - ### TransactionResult Consensus voting result across validators. @@ -298,6 +542,7 @@ TransactionResult.DETERMINISTIC_VIOLATION = "DETERMINISTIC_VIOLATION" TransactionResult.NO_MAJORITY = "NO_MAJORITY" TransactionResult.MAJORITY_AGREE = "MAJORITY_AGREE" TransactionResult.MAJORITY_DISAGREE = "MAJORITY_DISAGREE" +TransactionResult.MAJORITY_TIMEOUT = "MAJORITY_TIMEOUT" ``` --- @@ -310,28 +555,23 @@ Result of contract execution by the GenVM. ExecutionResult.NOT_VOTED = "NOT_VOTED" ExecutionResult.FINISHED_WITH_RETURN = "FINISHED_WITH_RETURN" ExecutionResult.FINISHED_WITH_ERROR = "FINISHED_WITH_ERROR" +ExecutionResult.TIMEOUT = "TIMEOUT" +ExecutionResult.NONDET_DISAGREE = "NONDET_DISAGREE" +ExecutionResult.DETERMINISTIC_VIOLATION = "DETERMINISTIC_VIOLATION" ``` --- ### VoteType -str(object='') -> str -str(bytes_or_buffer[, encoding[, errors]]) -> str - -Create a new string object from the given object. If encoding or -errors is specified, then the object must expose a data buffer -that will be decoded using the given encoding and error handler. -Otherwise, returns the result of object.__str__() (if defined) -or repr(object). -encoding defaults to 'utf-8'. -errors defaults to 'strict'. +Validator execution vote recorded for a consensus round. ```python VoteType.NOT_VOTED = "NOT_VOTED" -VoteType.AGREE = "AGREE" -VoteType.DISAGREE = "DISAGREE" +VoteType.FINISHED_WITH_RETURN = "FINISHED_WITH_RETURN" +VoteType.FINISHED_WITH_ERROR = "FINISHED_WITH_ERROR" VoteType.TIMEOUT = "TIMEOUT" +VoteType.NONDET_DISAGREE = "NONDET_DISAGREE" VoteType.DETERMINISTIC_VIOLATION = "DETERMINISTIC_VIOLATION" ``` diff --git a/pages/developers/_meta.json b/pages/developers/_meta.json index a289e997..0a1f25fe 100644 --- a/pages/developers/_meta.json +++ b/pages/developers/_meta.json @@ -1,6 +1,8 @@ { "networks": "Networks & RPCs", + "consensus-v06-migration": "Consensus v0.6 Migration", "intelligent-contracts": "Intelligent Contracts", "decentralized-applications": "Frontend & SDK Integration", + "error-reference": "Error & Revert Reference", "staking-guide": "Staking Contract Guide" } diff --git a/pages/developers/consensus-v06-migration.mdx b/pages/developers/consensus-v06-migration.mdx new file mode 100644 index 00000000..689d6415 --- /dev/null +++ b/pages/developers/consensus-v06-migration.mdx @@ -0,0 +1,91 @@ +--- +description: "Migrate applications and tooling to the Consensus v0.6 and Studio v0.123 release family." +--- + +import { Callout } from 'nextra-theme-docs' + +# Consensus v0.6 Migration + +Consensus v0.6 introduces the complete fee-funded transaction lifecycle used by Studio v0.123. Applications should upgrade Studio, node, SDK, CLI, and fee-profile tooling as one compatible release family rather than mixing release-candidate and stable components. + +## Compatible release lines + +| Component | v0.6 release family | +|:--|:--| +| Consensus contracts and GenLayer Node | v0.6 RC | +| GenLayer Studio | v0.123 RC | +| `genlayer-js` | v2.0 RC | +| `genlayer-py` | v0.19 RC | +| GenLayer CLI | v0.40 RC | +| Transaction Kit and `gltest` fee profiles | matching v0.6-compatible prerelease | + +Use the exact versions published in the release notes. Prerelease package tags must be installed explicitly; do not assume an npm or container `latest` tag resolves to the RC. + +## Test on Studio-dev first + +The release-candidate stack is deployed at [studio-dev.genlayer.com](https://studio-dev.genlayer.com), with canonical RPC `https://studio-dev.genlayer.com/api` and chain ID `61997`. It is separate from stable Studionet (`61999`). + +Use the JavaScript `studioDevnet` definition or CLI `studio-dev` alias supplied by the matching RC. Do not point the stable `studionet` chain object at the preview RPC: chain identity and consensus contract addresses must move together. + +## Migrate fee submission + +On a fee-charging deployment, every deploy and write must carry a `FeesDistribution` and its quoted fee value. The recommended path is: + +1. exercise representative contract branches in tests; +2. generate and commit a `fee-profile.json` with `gltest --fee-profile`; +3. convert the selected profile entry into live estimate options; +4. read current network prices and caps through the SDK estimate; and +5. submit the returned `distribution` and `feeValue` unchanged. + +The deposit covers consensus time units, execution, child messages, and the chosen appeal/rotation posture. Unused fee budget is refunded at finalization. A Studio deployment can be gasless; detect that from the estimate result rather than from its network name. + +See [Fees & Transaction Policy](/developers/decentralized-applications/fees-and-transaction-kit) and [Fee Profiling & Estimation](/developers/decentralized-applications/fee-profiling-and-estimation). + +## Migrate appeals + +The authoritative appeal quote contains both the bond and funding for the work induced by the next round. Use the high-level appeal operation and let it bind the active decision: + +```typescript +const charge = await client.getAppealCharge({ txId }); +await client.appealTransaction({ txId, value: charge }); +``` + +```python +charge = client.get_appeal_charge(tx_id) +client.appeal_transaction(tx_id, value=charge) +``` + +Both public operations use `topUpAndSubmitAppeal`. Direct `submitAppeal` is only suitable for explicit low-level conformance against a next round that is already funded; an ordinary unfunded appeal can otherwise revert with `AppealRoundNotPermitted`. + +A successful appeal returns the bond principal plus profit equal to 1.5× the bond, for 2.5× the bond in total. The induced-work funding is not part of the multiplied reward. + +## Read status and execution together + +An accepted or finalized status does not by itself prove successful contract execution. Treat a transaction as successful only when: + +- its status is `ACCEPTED` or `FINALIZED`; and +- its execution result is `FINISHED_WITH_RETURN`. + +Use the SDK's `isSuccessful` helper or Transaction Kit's normalized outcome. Track until finalization when the UI needs exact fee consumption and refunds. + +## Other protocol-visible changes + +- **Randomness:** validator seed advances use ECVRF proofs bound to the registered operator public key and a per-recipient domain-separated seed chain. +- **Tribunals:** the electorate and quorum are frozen at creation, consequence processing is bounded and retryable, and a majority-disagree verdict wires the convicted leader into the staking judicial restriction. +- **Developer rewards:** the first deployment mints one Developer NFT, and later contracts from the same developer are attached to it. One claim processes at most 50 inflation epochs. +- **Error decoding:** the v0.6 snapshot exposes 363 unique custom-error selectors; use the [Error & Revert Reference](/developers/error-reference) with the ABI from the deployment you are calling. + +## Application checklist + +- Install one coherent RC set and lock it in the package and deployment manifests. +- Use `studioDevnet` / `studio-dev` for the preview and `studionet` only for stable Studio. +- Replace hand-built fee arithmetic with an SDK estimate generated from a measured profile. +- Cover every message-emitting and materially expensive branch in profile tests. +- Replace direct public `submitAppeal` calls with `appealTransaction` / `appeal_transaction`. +- Display deposit, consumed fees, and final refund as different values. +- Require both successful status and execution result before showing an application action as complete. +- Re-run profile tests whenever contract code, GenVM, Studio, or the fee policy changes. + + +Studio-dev is a release-candidate environment and may reset. Move durable production-like testing to Bradbury only after the compatible v0.6 stack has been promoted there. + diff --git a/pages/developers/decentralized-applications/_meta.json b/pages/developers/decentralized-applications/_meta.json index 8bc04301..6d3b7685 100644 --- a/pages/developers/decentralized-applications/_meta.json +++ b/pages/developers/decentralized-applications/_meta.json @@ -5,6 +5,11 @@ "querying-a-transaction": "Querying a Transaction", "reading-data": "Reading Data from Intelligent Contracts", "writing-data": "Writing Data to Intelligent Contracts", + "fees-and-transaction-kit": "Fees & Transaction Policy", + "transaction-kit-integration": "Transaction Kit Integration", + "fee-profiling-and-estimation": "Fee Profiling & Estimation", + "fee-outcomes-and-debugging": "Fee Outcomes & Debugging", + "developer-nft-rewards": "Developer NFT Rewards", "testing": "Testing in Studio", "project-boilerplate": "Project Boilerplate" } diff --git a/pages/developers/decentralized-applications/architecture-overview.mdx b/pages/developers/decentralized-applications/architecture-overview.mdx index e10b23c8..0790082b 100644 --- a/pages/developers/decentralized-applications/architecture-overview.mdx +++ b/pages/developers/decentralized-applications/architecture-overview.mdx @@ -14,6 +14,9 @@ The user interface of the DApp is typically built with web technologies like HTM ### GenLayerJS SDK A TypeScript/JavaScript library that abstracts the complexities of blockchain interactions. The SDK provides APIs to read from and write to Intelligent Contracts, manages accounts and queries transactions, and acts as the bridge between the frontend and GenLayer's protocol. +### Transaction policy and fee profile +Fee-charging writes carry an explicit policy for consensus time units, execution, child messages, appeals, rotations, and price ceilings. Applications normally measure that policy in contract tests, commit a `fee-profile.json`, and combine it with current network prices at signing time. [Transaction Kit](./transaction-kit-integration) adds quote review, policy verification, wallet submission, and lifecycle tracking on top of GenLayerJS. + ### Consensus Layer The consensus layer implements GenLayer's Optimistic Democracy mechanism to ensure reliable and secure execution of transactions using a validator-driven commit-reveal scheme. It also handles appeals and transaction finality to maintain integrity and fairness. @@ -42,4 +45,4 @@ flowchart TD end s1 <--> s3 s3 <--> s2 -``` \ No newline at end of file +``` diff --git a/pages/developers/decentralized-applications/dapp-development-workflow.mdx b/pages/developers/decentralized-applications/dapp-development-workflow.mdx index 4d7590ab..0eb98cda 100644 --- a/pages/developers/decentralized-applications/dapp-development-workflow.mdx +++ b/pages/developers/decentralized-applications/dapp-development-workflow.mdx @@ -17,8 +17,8 @@ It provides an interactive environment that serves as a comprehensive sandbox fo The platform runs a simulated network with customizable validators that accurately mirror the GenLayer consensus mechanism. This feature allows developers to test their contracts under conditions that closely resemble the actual blockchain environment, ensuring reliable deployment outcomes. ### Getting Started: -1. **Set Up the Studio**: Developers initialize the Studio using `genlayer cli` command `genlayer init` which configures the environment and spawns a local validator network. GenLayer Studio is also available as a hosted instace at [studio.genlayer.com](https://studio.genlayer.com/). -2. **Write Your First Contract**: Intelligent Contracts in GenLayer are written in Python, utilizing its extensive libraries and GenVM capabilities like LLM calls and web integration. Refer to [Your First Contract](/developers/intelligent-contracts/your-first-contract) guide for more information. +1. **Set Up the Studio**: Run `genlayer init` and `genlayer up` for a local validator network, or use hosted [Studionet](https://studio.genlayer.com/). Use [Studio-dev](https://studio-dev.genlayer.com/) with matching release-candidate tooling when validating the next stack. +2. **Write Your First Contract**: Intelligent Contracts in GenLayer are written in Python and can use GenVM capabilities such as LLM calls and web integration. See [Your First Intelligent Contract](/developers/intelligent-contracts/first-intelligent-contract). 3. **Deploy and Test**: Deploy your Intelligent Contracts through the Studio interface and test them in the simulated network. --- @@ -35,7 +35,7 @@ As projects transforms to real-world applications, developers should migrate to ### Workflow Steps: 1. **Set Up Your Local Environment**: Developers can start with the GenLayer boilerplate project, which includes pre-configured templates for local testing. 2. **Write Tests**: Tests are written in Python, focusing on validating contract functionality and ensuring consensus integrity. The boilerplate includes sample tests to accelerate development. -3. **Simulate Transactions**: Run detailed simulations to observe how contracts behave under various network conditions, ensuring robust performance. +3. **Profile Transaction Paths**: Cover normal, expensive, message-emitting, and failure branches in tests, then generate a `fee-profile.json` for application submissions. --- @@ -59,11 +59,15 @@ Refer to [GenLayerJS](/developers/decentralized-applications/genlayer-js) guide Use the SDK's high-level APIs to interact with deployed contracts. For instance, retrieve user balances or update contract state seamlessly. Refer to [Reading Data](/developers/decentralized-applications/reading-data) and [Writing Data](/developers/decentralized-applications/writing-data) guides for more information. -3. **Monitor Transactions**: +3. **Estimate and Review Fees**: -Developers can subscribe to events or query transaction statuses, ensuring users are kept informed of transaction progress and outcomes. +Use the measured profile plus current network prices to show a quote before signing. [Transaction Kit](/developers/decentralized-applications/transaction-kit-integration) supplies the fee-review and wallet flow for React, Vue, or a headless frontend. -4. **Build the User Interface**: +4. **Monitor Transactions**: + +Track the returned transaction ID to a decision or finalization. Treat the operation as successful only when its status and execution result both indicate success. + +5. **Build the User Interface**: Combine GenLayerJS with popular frontend frameworks (like React or Angular) to create intuitive interfaces. diff --git a/pages/developers/decentralized-applications/developer-nft-rewards.mdx b/pages/developers/decentralized-applications/developer-nft-rewards.mdx new file mode 100644 index 00000000..1c4f60b9 --- /dev/null +++ b/pages/developers/decentralized-applications/developer-nft-rewards.mdx @@ -0,0 +1,176 @@ +--- +description: "Developer NFT Rewards explains how GenLayer credits contract deployers with a share of transaction fees and epoch inflation, and how to read and claim those rewards with GenLayerJS." +--- + +import { Callout } from "nextra-theme-docs"; + +# Developer NFT Rewards + +GenLayer rewards the people who build on it. The first time you deploy an Intelligent Contract, the network mints a **Developer NFT** to your deploying address. That NFT accrues rewards from the activity your contracts generate, and you claim those rewards on-chain whenever you like. + +This page covers what the Developer NFT is, how it earns, and how to read and claim rewards with [GenLayerJS](/developers/decentralized-applications/genlayer-js). + + + The Developer NFT is minted automatically on your first contract deployment — there is no separate registration step. You get **one NFT per developer address**. + + +## What the Developer NFT is + +- It is minted to the **deployer** (the address that sends the deploy transaction) the first time that address deploys a contract. +- There is exactly **one NFT per developer address**. Deploying again from the same address does not mint a second NFT. +- Every later contract deployed from that address is linked to the same NFT, so activity across all of the developer's contracts accrues to one reward account. +- The NFT is the account that accumulates and holds your claimable rewards. Only the NFT owner can claim. + +Each NFT tracks: + +| Field | Meaning | +|-------|---------| +| `nftId` | The on-chain id of your Developer NFT. | +| `developer` | The address that owns the NFT. | +| `claimableRewards` | Fee rewards accumulated so far and not yet claimed. | +| `lastClaimedEpoch` | The last epoch whose inflation rewards you have claimed. | +| `ghosts` | The contract ("ghost") addresses associated with this NFT. | + +## How rewards are earned + +Developer rewards come from two independent sources. + +### 1. Fee share + +The developer reserve is **10% of the transaction's time-unit fee pool**. Storage, receipt, and message-fee buckets are not included in this split. For a recipient contract linked to a Developer NFT, the reserve is credited **when the transaction finalizes** — not at acceptance. + +- Credited per transaction, as soon as that transaction reaches finalization. +- No epoch has to close first — fee rewards become claimable immediately. +- Rewards are attributed to the epoch in which the transaction was **created**, which matters for the inflation share below. + +### 2. Inflation share + +Each epoch, GenLayer sets aside **10% of that epoch's inflation** into a pool for developers. That pool is split across Developer NFTs **pro-rata by the fees each NFT's contracts generated during the epoch**, with one important limit: + + + Your inflation reward for an epoch is **capped at 1× the fees your contracts earned that epoch**. If your contracts earned no fees in an epoch, you earn no inflation for that epoch — the unclaimed portion of the pool is burned. + + +In practice, on a low-traffic network the developer inflation pool is larger than the total fees generated, so each NFT typically receives an inflation reward roughly equal to its own fee earnings for the epoch (that is, up to a **2× effect**: your fee share plus a matching inflation share). + +### When rewards become claimable + +| Reward source | Claimable when | +|---------------|----------------| +| Fee share | Immediately, at each transaction's finalization. | +| Inflation share | Once the epoch is **finalized** (a finalized epoch lags the current epoch by at least one step). | + +The current, unfinalized epoch is never included in claimable inflation. + +## Reading and claiming with GenLayerJS + +All of the following use the standard GenLayerJS client. The NFT contract address is resolved automatically from the network's on-chain address registry — you don't pass it yourself. + +```typescript +import { testnetBradbury } from "genlayer-js/chains"; +import { createClient, createAccount } from "genlayer-js"; + +const account = createAccount(); // or createAccount(privateKey) +const client = createClient({ + chain: testnetBradbury, + account, +}); +``` + +### Look up your Developer NFT + +`getDeveloperNft` returns the full reward record for a developer address, or `null` if that address has never deployed a contract. + +```typescript +const nft = await client.getDeveloperNft({ developer: account.address }); + +if (nft === null) { + console.log("This address has no Developer NFT yet — deploy a contract first."); +} else { + console.log("NFT id:", nft.nftId); + console.log("Unclaimed fee rewards:", nft.claimableRewards); + console.log("Last claimed epoch:", nft.lastClaimedEpoch); + console.log("Contracts (ghosts):", nft.ghosts); +} +``` + +The returned shape is: + +```typescript +interface DeveloperNft { + nftId: bigint; + developer: `0x${string}`; + claimableRewards: bigint; + lastClaimedEpoch: bigint; + ghosts: `0x${string}`[]; +} +``` + +### Check claimable rewards + +Fee rewards and inflation rewards are read separately. + +```typescript +// All-time accumulated fee rewards not yet claimed. +const feeRewards = await client.getClaimableRewardsFromFees({ + nftId: nft.nftId, +}); + +// Inflation rewards over the next `numberOfEpochsToClaim` finalized epochs, +// starting from lastClaimedEpoch + 1. +const inflationRewards = await client.getClaimableRewardsFromInflation({ + nftId: nft.nftId, + numberOfEpochsToClaim: 50n, +}); + +console.log("Claimable from fees:", feeRewards); // bigint (wei) +console.log("Claimable from inflation:", inflationRewards); // bigint (wei) +``` + + + `getClaimableRewardsFromInflation` requires **two** arguments: the `nftId` and how many epochs to look ahead. It only counts epochs that are already finalized; the current epoch is excluded. + + +### Claim rewards + +There are two ways to claim. + +**Claim available rewards** — sweeps all accumulated fee rewards plus at most 50 finalized inflation epochs: + +```typescript +const txHash = await client.claimNftRewards({ + nftId: nft.nftId, +}); +``` + +**Claim a bounded number of epochs** — useful when many epochs have accumulated: + +```typescript +const txHash = await client.claimNftEpochs({ + nftId: nft.nftId, + numberOfEpochsToClaim: 50n, +}); +``` + + + A single claim processes **at most 50 epochs of inflation** at a time. If more than 50 finalized epochs have accumulated since your last claim, call either claim operation repeatedly to clear them in batches. The read view `getClaimableRewardsFromInflation` does **not** enforce this 50-epoch cap, so it can report more than a single claim will actually pay out — size your claim accordingly. + + +Both claim methods return the EVM transaction hash. Only the NFT owner can claim; claiming when there is nothing to claim reverts. + +## Gotchas + + + **One NFT covers all deployments from its developer address.** The first deployment mints the NFT and links its contract; every later deployment links another contract to that same NFT. Use a different developer address only when you intentionally want a separate NFT and reward account. + + +- **Zero-fee epochs earn no inflation.** Because the inflation share is capped at 1× your fee earnings for the epoch, an epoch in which your contracts generated no fees pays no inflation — that share is burned, not carried forward. +- **Claims sweep all fee rewards at once.** Any claim (`claimNftRewards` or `claimNftEpochs`) empties your entire accumulated fee balance, even if you only meant to claim a few epochs of inflation. There is no partial fee claim. +- **Inflation lags fees.** Fee rewards are claimable the moment a transaction finalizes; inflation for an epoch only becomes claimable after that epoch is finalized, which is at least one step behind the current epoch. + +## Related + +- [GenLayer JS](/developers/decentralized-applications/genlayer-js) — client setup and configuration. +- [Reading Data from Intelligent Contracts](/developers/decentralized-applications/reading-data) +- [Writing Data to Intelligent Contracts](/developers/decentralized-applications/writing-data) +- [Staking Contract Guide](/developers/staking-guide) — the sibling reward system for validators and delegators. diff --git a/pages/developers/decentralized-applications/fee-outcomes-and-debugging.mdx b/pages/developers/decentralized-applications/fee-outcomes-and-debugging.mdx new file mode 100644 index 00000000..1c27a909 --- /dev/null +++ b/pages/developers/decentralized-applications/fee-outcomes-and-debugging.mdx @@ -0,0 +1,68 @@ +--- +description: "Interpret GenLayer transaction outcomes, fee refunds, gasless Studio behavior, and fee-related failures." +--- + +# Fee Outcomes and Debugging + +Applications should explain transaction outcomes in terms users or operators can act on. A submitted GenLayer transaction can be accepted by consensus, finalized later, rejected before execution or finish with a contract error. + +## Success rule + +A transaction is successful only when both are true: + +- the transaction status is `ACCEPTED` or `FINALIZED` +- the execution result is `FINISHED_WITH_RETURN` + +Use `isSuccessful(tx)` from `genlayer-js` or the transaction-kit outcome surfaces instead of checking status alone. A transaction can be accepted by validators and still finish with a contract error. + +## User-facing outcomes + +| Status and result | Meaning | Surface it as | +| --- | --- | --- | +| `ACCEPTED` / `FINALIZED` + `FINISHED_WITH_RETURN` | Execution succeeded. | Done. Unused fee budget refunds at finalization. | +| `UNDETERMINED` + any result | Validators could not reach a majority. | Treat as not executed, even if a leader result exists. | +| Canceled at activation because `GENPerTimeUnit` exceeded its submitted ceiling | The transaction entered the queue, but could not lock an allowed time-unit price before work began. | Escrow is refunded. Re-estimate and resubmit with a fresh ceiling. | +| Rejected at submission, such as `InsufficientFees`, `MaxPriceExceeded` for the current storage/receipt price, or an invalid fee shape | The transaction never entered the consensus queue. | Correct the quote or input and retry. | +| `FINISHED_WITH_ERROR` | The contract reverted or errored. | Execution failed. Consensus fees may still be consumed. | + +## Gasless networks + +Some Studio deployments run gasless, with fee accounting disabled and all prices zero. The transaction kit detects this from the estimate: + +```typescript +if (quote.gasless) { + // Show "No fees on this network" and submit without fee params. +} +``` + +The same app code can work on gasless Studio and fee-charging networks. Do not hard-code a network name to decide whether fees are enabled; use the estimate result. + +## Refunds + +The initial deposit is a maximum budget, not the final cost. Unused budget is refunded when the transaction finalizes. Users should understand three numbers: + +- total deposit: what must be available when signing +- spent fees: what the transaction actually consumed +- refund: the unused part returned after finalization + +If an app tracks only until `decided`, it may not yet know the final refund. Use `finalized` tracking for screens or tools that need final fee settlement. + +## Explorer debugging + +Open the transaction in GenLayer Explorer and inspect the Fees section. It should show: + +- allocation versus consumed values +- submitted price ceilings versus activation-locked and current prices +- message fee budget and consumption +- refunds by category +- settlement status + +If a quote looks too large, check whether the transaction used a developer profile or network defaults. `PolicyQuote.source` is `developer` when a matching `fee-profile.json` entry was used and `network-default` otherwise. + +If a transaction failed even though the user accepted the quote, check: + +- whether a price cap was exceeded before execution +- whether the method hit an unmeasured expensive branch +- whether `totalMessageFees` was too low for emitted child transactions +- whether the contract itself returned `FINISHED_WITH_ERROR` +- whether the app tracked only status and ignored execution result diff --git a/pages/developers/decentralized-applications/fee-profiling-and-estimation.mdx b/pages/developers/decentralized-applications/fee-profiling-and-estimation.mdx new file mode 100644 index 00000000..59697db7 --- /dev/null +++ b/pages/developers/decentralized-applications/fee-profiling-and-estimation.mdx @@ -0,0 +1,334 @@ +--- +description: "Generate test-backed fee profiles and use them with GenLayerJS, GenLayerPy, the CLI, and Transaction Kit." +--- + +# Fee Profiling and Estimation + +Fee profiling is how a GenLayer app turns contract tests into reusable fee suggestions. The output is a JSON file, usually `frontend/fee-profile.json` or `fee-profile.json`, that any submission path can use when it estimates a deploy or write transaction. + +The profile is not a live simulation result. It is a checked-in artifact generated from representative tests. Prices and caps are still read live at transaction time. + +`@genlayer/transaction-kit` can consume the profile directly for frontend flows. If you are building a CLI, backend worker, Python script or custom JS app without transaction-kit, use the same profile values as inputs to `estimateTransactionFees` / `estimate_transaction_fees`, then submit the returned `distribution` and `feeValue`. + +## Why profiles exist + +GenLayer transactions can take different paths: + +- a method may or may not emit messages +- a method may make different LLM or web calls depending on state +- a child transaction may terminate early or run through an expensive branch +- appeals and rotations change the amount of consensus work that must be funded +- storage, receipt and rollup writes vary with output size + +Simulating all of that before every user action would be too slow. Instead, the developer measures the important branches during tests, commits the maximum observed requirements and lets the app quote from that profile. + +## How methods are aggregated + +Profiles are keyed by contract method name, not by pytest test name and not by arguments. + +If five tests call `register_and_claim`, all five observations are merged into one `methods.register_and_claim` entry. For each field, the profiler keeps the maximum value observed across the whole run, then applies headroom to fee and time-unit values. + +Example: + +| Test path | Execution budget | Message fees | Rotations | +| --- | ---: | ---: | ---: | +| user already registered, no referral message | 600 | 0 | 0 | +| new user, emits referral message | 200 | 800 | 1 | + +The generated profile uses: + +```json +{ + "methods": { + "register_and_claim": { + "executionBudgetPerRound": "600", + "totalMessageFees": "800", + "rotationsPerRound": "1" + } + } +} +``` + +That combined entry may not describe a single historical transaction. It is intentionally conservative: it is the union of the worst observed components for that method. This is the right default for app safety because a user can usually trigger any branch that the method allows. + +If your app can prove a specific user flow cannot hit the expensive branch, use an explicit override for that flow. Keep the committed profile conservative. + +## What to measure + +Measure scenarios, not just methods. A good profile suite covers: + +- deploy, if the app lets users deploy contracts +- the cheapest successful path +- the most expensive normal path +- every branch that emits a different message set +- every branch that changes storage or receipt size materially +- every branch where child transactions can run different paths +- expected failure/revert paths, if users can trigger them and they consume fees + +For a Rally-style contract, that means tests for paths such as: + +- user already registered, no referral message +- user not registered, referral message emitted +- main transaction passes and emits the shard message +- main transaction fails before the shard message +- shard transaction terminates early +- shard transaction reaches the expensive branch + +The parent method profile should include the worst observed `totalMessageFees` across these paths. If the parent emits child transactions, the message fee budget must be large enough for the worst child path that the parent can create. + +## Generate the profile + +Create a script in the app repository so developers do not have to remember the exact command: + +```jsonc +{ + "scripts": { + "test:fees": "python3 -m pytest tests/integration/test_football_bets.py::test_fee_profile_create_bet --fee-profile frontend/fee-profile.json -v -s" + } +} +``` + +Run it against a fee-reporting Studio or network: + +```bash +npm run test:fees -- --rpc-url http://127.0.0.1:4000/api +``` + +The underlying mechanism is a pytest option provided by `gltest`: + +```bash +python3 -m pytest tests/integration --fee-profile frontend/fee-profile.json +``` + +Use `--fee-profile-headroom` to adjust the multiplier: + +```bash +python3 -m pytest tests/integration \ + --fee-profile frontend/fee-profile.json \ + --fee-profile-headroom 1.5 +``` + +The default headroom is `1.25`. + +## Wait for finalized receipts + +Profile tests should wait for finalized receipts: + +```python +receipt = contract.create_bet(args=["2024-06-20", "Spain", "Italy", "1"]).transact( + fees=transaction_fee_preset(), + wait_until="finalized", +) +``` + +Earlier statuses can prove that validators accepted a transaction before all fee accounting and refunds are settled. Finalized receipts are the reliable source for the profile. + +## Submit the profiling transaction + +The measured transaction itself still needs a fee preset. In local Studio runs, use a trusted developer preset sized from the active fee policy: + +```python +from gltest.clients import get_gl_client + +FEE_ESTIMATE_OPTIONS = { + "leaderTimeunitsAllocation": 100, + "validatorTimeunitsAllocation": 200, + "totalMessageFees": 0, + "rotations": [1], +} + +def transaction_fee_preset(): + estimate = get_gl_client().estimate_transaction_fees(FEE_ESTIMATE_OPTIONS) + return { + "distribution": estimate["distribution"], + "feeValue": estimate["feeValue"], + } +``` + +Use that preset on deploys and writes: + +```python +contract = factory.deploy(fees=transaction_fee_preset(), wait_until="finalized") + +receipt = contract.create_bet(args=["2024-06-20", "Spain", "Italy", "1"]).transact( + fees=transaction_fee_preset(), + wait_until="finalized", +) +``` + +For message-producing scenarios, increase `totalMessageFees` in the profiling preset so the emitted child transactions have enough budget. The generated profile records the observed message budget from the finalized receipt. If no message-producing path is measured, that method will recommend a zero message budget. + +## Profile shape + +A generated profile looks like this: + +```json +{ + "version": 1, + "network": "localnet", + "measuredAt": "2026-06-15T17:26:36Z", + "deploy": { + "leaderTimeunitsAllocation": "125", + "validatorTimeunitsAllocation": "250", + "executionBudgetPerRound": "786434", + "totalMessageFees": "0", + "rotationsPerRound": "1" + }, + "methods": { + "create_bet": { + "leaderTimeunitsAllocation": "125", + "validatorTimeunitsAllocation": "250", + "executionBudgetPerRound": "786297", + "totalMessageFees": "0", + "rotationsPerRound": "1" + } + } +} +``` + +| Field | Meaning | +| --- | --- | +| `leaderTimeunitsAllocation` | Suggested leader time units for the method or deploy. | +| `validatorTimeunitsAllocation` | Suggested validator time units for the method or deploy. | +| `executionBudgetPerRound` | Budget for receipt, storage and rollup writes per leader round. | +| `totalMessageFees` | Budget for child transactions emitted by the contract. | +| `rotationsPerRound` | Suggested rotations per leader round. This is recorded exactly, not multiplied by headroom. | + +The profile intentionally does not contain `feeValue` or live price caps. Those are quoted from the current network fee policy when the user is about to sign. + +## Use the profile in transaction-kit + +For frontend flows, commit the profile next to the frontend and pass it to the kit: + +```typescript +import feeProfile from './fee-profile.json'; + +const kit = createTransactionKit({ + chain, + provider, + account, + suggestions: feeProfile, +}); +``` + +When a transaction matches a profile entry, the quote source is `developer`. Methods without a matching entry fall back to network defaults. + +## Use the profile with genlayer-js + +Without transaction-kit, load the profile yourself, select the deploy or method entry, convert `rotationsPerRound` into the `rotations` array for the appeal posture you want, then estimate from live prices: + +```typescript +import feeProfile from './fee-profile.json'; + +const methodProfile = feeProfile.methods.create_bet; +const appealRounds = 1n; +const rotationsPerRound = BigInt(methodProfile.rotationsPerRound ?? '0'); + +const estimate = await client.estimateTransactionFees({ + leaderTimeunitsAllocation: BigInt(methodProfile.leaderTimeunitsAllocation), + validatorTimeunitsAllocation: BigInt(methodProfile.validatorTimeunitsAllocation), + executionBudgetPerRound: BigInt(methodProfile.executionBudgetPerRound), + totalMessageFees: BigInt(methodProfile.totalMessageFees ?? '0'), + appealRounds, + rotations: Array.from( + { length: Number(appealRounds) + 1 }, + () => rotationsPerRound, + ), +}); + +await client.writeContract({ + address: contract, + functionName: 'create_bet', + args: ['2024-06-20', 'Spain', 'Italy', '1'], + fees: { + distribution: estimate.distribution, + feeValue: estimate.feeValue, + }, +}); +``` + +## Use the profile with genlayer-py + +Python follows the same shape: + +```python +import json + +with open("fee-profile.json", encoding="utf-8") as f: + fee_profile = json.load(f) + +method_profile = fee_profile["methods"]["create_bet"] +appeal_rounds = 1 +rotations_per_round = int(method_profile.get("rotationsPerRound", "0")) + +estimate = client.estimate_transaction_fees( + { + "leaderTimeunitsAllocation": int(method_profile["leaderTimeunitsAllocation"]), + "validatorTimeunitsAllocation": int(method_profile["validatorTimeunitsAllocation"]), + "executionBudgetPerRound": int(method_profile["executionBudgetPerRound"]), + "totalMessageFees": int(method_profile.get("totalMessageFees", "0")), + "appealRounds": appeal_rounds, + "rotations": [rotations_per_round] * (appeal_rounds + 1), + } +) + +tx_hash = client.write_contract( + account=account, + address=contract_address, + function_name="create_bet", + args=["2024-06-20", "Spain", "Italy", "1"], + fees={ + "distribution": estimate["distribution"], + "feeValue": estimate["feeValue"], + }, +) +``` + +## Use the profile with the CLI + +The CLI can consume the generated profile directly: + +```bash +genlayer estimate-fees 0x123456789abcdef create_bet \ + --fee-profile frontend/fee-profile.json \ + --fee-preset standard \ + --json \ + --args "2024-06-20" "Spain" "Italy" "1" +``` + +For writes, pass the same profile path when submitting: + +```bash +genlayer write 0x123456789abcdef create_bet \ + --fee-profile frontend/fee-profile.json \ + --fee-preset standard \ + --args "2024-06-20" "Spain" "Italy" "1" +``` + +For deploys, the CLI uses the profile's `deploy` entry: + +```bash +genlayer deploy \ + --contract contracts/football_bets.py \ + --fee-profile frontend/fee-profile.json +``` + +`write` and targeted `estimate-fees` use `methods[method]`. The CLI converts the measured profile entry into SDK estimate options, reads current prices from the backend, then submits the returned `distribution` and `feeValue`. + +`--fee-preset` controls the appeal posture. The standard preset maps to one appeal round; `low` maps to zero and `high` maps to two. Use `--appeal-rounds` for an explicit override. + +Use `--fees` alongside `--fee-profile` only when a flow needs to override part of the generated profile, for example a custom `messageAllocations` set. Use `--fee-value` only when you need to force the transaction deposit instead of using the SDK estimate. + +This uses the same profile-plus-live-policy model as Transaction Kit, but the named preset sizes are intentionally different. The CLI's developer defaults fund 0/1/2 appeal rounds for `low`/`standard`/`high`; Transaction Kit's user-facing safety postures fund 1/3/5. Use an explicit appeal-round override when an application needs one shared posture across both surfaces. + +## Regenerate policy + +Regenerate and review `fee-profile.json` when you change: + +- contract code that affects LLM calls, web requests, messages, storage or receipt data +- application arguments that select different branches +- child transaction behavior +- intended appeal or rotation posture +- the GenVM or Studio version used for fee accounting + +Treat the file like a performance budget. It should change in the same pull request as the behavior that changed the fee shape. diff --git a/pages/developers/decentralized-applications/fees-and-transaction-kit.mdx b/pages/developers/decentralized-applications/fees-and-transaction-kit.mdx new file mode 100644 index 00000000..f6724445 --- /dev/null +++ b/pages/developers/decentralized-applications/fees-and-transaction-kit.mdx @@ -0,0 +1,50 @@ +--- +description: "Use measured transaction policies to fund consensus, execution, messages, appeals, and rotations safely." +--- + +# Fees and Transaction Policy + +GenLayer apps should treat fees as a measured application policy, not as ad hoc math at submission time. Whether you are building a web frontend, a backend service, a CLI or an automation script, the transaction must include fee parameters that are large enough for the path it can execute. + +The suggested fee parameters come from a developer fee profile generated by the contract test suite. `@genlayer/transaction-kit` is the recommended frontend integration, but the profile itself is not tied to transaction-kit. + +## The model + +Every transaction reserves one protocol-fee deposit up front. The deposit covers: + +- consensus work: leader and validator time units, priced in GEN per time unit +- execution budget: receipt, storage and rollup writes +- message budget: child transactions emitted by the contract + +For a payable call, user value is transferred separately from that fee deposit; the wallet must cover both. Unused fee budget is refunded automatically when the transaction finalizes. The transaction submits user price ceilings and escrows against those ceilings. Storage and receipt ceilings are checked at submission and top-up; their live prices are then locked at activation. The GEN-per-time-unit price is also locked at activation, but if it has risen above its ceiling, activation cancels the transaction before consensus work and refunds the escrow. + +## The pieces + +| Page | Use it for | +| --- | --- | +| [Transaction Kit Integration](./transaction-kit-integration) | Add the fee approval, signing and tracking flow to a React, Vue or headless frontend. | +| [Fee Profiling and Estimation](./fee-profiling-and-estimation) | Measure deploy and method fee requirements from representative contract tests and use `fee-profile.json` from JS, Python, CLI or transaction-kit. | +| [Fee Outcomes and Debugging](./fee-outcomes-and-debugging) | Interpret transaction outcomes, gasless networks, refunds and Explorer fee panels. | + +## Recommended workflow + +1. Write normal contract tests for the app paths users can trigger. +2. Add fee-profile coverage for each economically meaningful branch. +3. Run the profile tests against a fee-reporting Studio or network. +4. Commit the generated `fee-profile.json` where your app or tool can load it. +5. Use the profile entry for the deploy or method you are submitting. +6. Estimate with the current network fee policy and submit the returned `distribution` and `feeValue`. + +This avoids live simulation before every user transaction. GenLayer transactions can include LLM calls, web requests, appeals and emitted messages, so per-click simulation would make the app slow and expensive. The profile gives the app a reproducible, test-backed estimate; the SDK still reads current prices and caps from the network when the user is about to sign. + +## Tooling + +| Tool | Role | +| --- | --- | +| `genlayer-js` v2 | SDK methods for fee estimation, deploy/write submission, receipt waiting and success checks. | +| `genlayer-py` | Python SDK methods for the same estimate and submit flow in scripts, services and CLIs. | +| `@genlayer/transaction-kit` | Core estimate, submit, track and verification flow. | +| `@genlayer/transaction-kit-react` / `-vue` | Drop-in UI for fee receipt, preset selection, price protection, signing and status tracking. | +| GenLayer CLI | `estimate-fees`, `deploy` and `write` commands that accept `fee-profile.json` for terminal workflows. | +| `gltest --fee-profile` | Measures fee usage from contract tests and writes the profile consumed by apps and tools. | +| GenLayer Explorer | Shows transaction fee allocation, consumption, refunds and settlement details. | diff --git a/pages/developers/decentralized-applications/querying-a-transaction.mdx b/pages/developers/decentralized-applications/querying-a-transaction.mdx index 2dcd6bef..3e114fe1 100644 --- a/pages/developers/decentralized-applications/querying-a-transaction.mdx +++ b/pages/developers/decentralized-applications/querying-a-transaction.mdx @@ -1,159 +1,100 @@ --- -description: "Querying a Transaction shows how to fetch GenLayer transaction details by hash with GenLayerJS and monitor status." +description: "Read, track, and interpret GenLayer transaction state with the v2 lifecycle APIs." --- # Querying a Transaction -Querying a transaction in GenLayer means reading a submitted network transaction by its hash to inspect its details. Use transaction queries to monitor transaction status, debug behavior, and verify transaction details. +GenLayerJS exposes a simple stored transaction view for applications, wait helpers for common completion points, and an advanced lifecycle projection for protocol operators. -## Basic Transaction Reading - -Here's the simplest way to read a transaction: +## Read stored state ```typescript -import { simulator } from 'genlayer-js/chains'; -import { createClient } from 'genlayer-js'; - -const client = createClient({ - chain: simulator, -}); +import { createClient, isSuccessful } from 'genlayer-js'; +import { localnet } from 'genlayer-js/chains'; -const transactionHash = "0x..."; +const client = createClient({ chain: localnet }); +const transaction = await client.getTransaction({ hash: txId }); -const transaction = await client.getTransaction({ - hash: transactionHash +console.log({ + status: transaction.statusName, + execution: transaction.txExecutionResultName, + lifecycle: transaction.lifecycle, + queuePosition: transaction.queuePosition, + successful: isSuccessful(transaction), }); ``` -{/* -## Transaction Data Structure -When you read a transaction, you get access to its complete data structure: +`getTransaction` reports the persisted transaction and derives `lifecycle` only from that stored state. It does not silently project timeouts or claim that a transaction has finalized before the corresponding state change is stored. + +Common fields include: + +| Field | Meaning | +|:--|:--| +| `hash` / `txId` | GenLayer transaction identifier. | +| `statusName` | Exact persisted consensus status. | +| `txExecutionResultName` | Contract execution outcome, such as `FINISHED_WITH_RETURN` or `FINISHED_WITH_ERROR`. | +| `lifecycle` | Normalized `processing`, `decided`, `finalized`, or `canceled` view derived from stored state. | +| `queuePosition` | Position while waiting in the recipient contract's queue, when available. | +| `txDataDecoded` | Decoded deploy or call payload. | +| `recipient` | Target contract; for a deployment, the created contract address. | + +## Wait for a decision or finalization ```typescript -interface Transaction { - hash: `0x${string}` // The unique transaction hash - from: `0x${string}` // Address of the sender - to: `0x${string}` | null // Address of the recipient (null for contract deployments) - nonce: number // Transaction sequence number - value: bigint // Amount of native tokens transferred - data: `0x${string}` // Transaction input data - timestamp: number // Block timestamp when transaction was included - status: 'success' | 'failure' | 'pending' // Current transaction status - blockNumber: bigint | null // Block number where transaction was included - blockHash: `0x${string}` | null // Hash of the block - // ... additional fields -} +const decided = await client.waitForDecision({ hash: txId }); +const finalized = await client.waitForFinalization({ hash: txId }); ``` -## Reading Different Transaction Types +Use `waitForDecision` when the UI can continue after the transaction has a materialized decision. Use `waitForFinalization` when you need final fee consumption, refunds, or durable completion. -### Basic Transfer Transaction -```typescript -const transferTx = await client.getTransaction({ - hash: "0x123...", -}); +The generic helper is equivalent: -console.log({ - from: transferTx.from, - to: transferTx.to, - value: transferTx.value, - status: transferTx.status +```typescript +const transaction = await client.waitForTransactionReceipt({ + hash: txId, + waitUntil: 'finalized', + interval: 5_000, + retries: 100, }); ``` -### Contract Interaction Transaction -```typescript -const contractTx = await client.getTransaction({ - hash: "0x456...", -}); +The older `status` option is deprecated. More importantly, reaching an accepted or finalized status does not by itself prove successful execution; call `isSuccessful(transaction)` as well. -// Decode the transaction input data -const decodedInput = client.decodeTransactionInput({ - data: contractTx.data, - abi: contractABI, // You need the contract's ABI -}); +## Read the protocol projection -console.log({ - contractAddress: contractTx.to, - functionName: decodedInput.functionName, - arguments: decodedInput.args -}); -``` +Keepers, debuggers, and other protocol-aware tools can ask for the action implied by the current deadlines and active decision: -### Contract Deployment Transaction ```typescript -const deployTx = await client.getTransaction({ - hash: "0x789...", -}); +const lifecycle = await client.advanced.getTransactionLifecycle({ hash: txId }); console.log({ - deployer: deployTx.from, - contractAddress: deployTx.creates, // Address of deployed contract - deploymentData: deployTx.data + stored: lifecycle.storedStatus, + projected: lifecycle.projectedStatus, + action: lifecycle.resolutionAction, + source: lifecycle.resolutionSource, + decisionId: lifecycle.decisionId, + decisionActive: lifecycle.decisionActive, }); -``` */} +``` + +`resolutionAction === 'Finalize'` is a protocol action, not another transaction status or an independent boolean readiness flag. On an older Studio backend without the lifecycle RPC, the SDK degrades to the stored state it can prove instead of inventing a projected transition. -## Error Handling +## Child transactions and traces ```typescript -async function getTransactionWithRetry( - client: GenLayerClient, - hash: string, - maxAttempts = 3 -): Promise { - for (let attempt = 1; attempt <= maxAttempts; attempt++) { - try { - const tx = await client.getTransaction({ hash }); - if (!tx) throw new Error('Transaction not found'); - return tx; - } catch (error) { - if (attempt === maxAttempts) throw error; - if (error.message.includes('not found')) { - // Wait longer between retries for not found errors - await new Promise(resolve => setTimeout(resolve, 2000 * attempt)); - continue; - } - throw error; // Rethrow other errors immediately - } - } - throw new Error('Failed to fetch transaction after max retries'); -} +const children = await client.getTriggeredTransactionIds({ hash: txId }); +const trace = await client.debugTraceTransaction({ hash: txId, round: 0 }); ``` +Use triggered transaction IDs to follow messages emitted by an Intelligent Contract. Use the trace for return data, stdout, stderr, GenVM logs, and round-level execution debugging. -## Monitoring Transaction Status +## Polling and process restarts -```typescript -async function monitorTransaction( - client: GenLayerClient, - hash: string, - interval = 1000 -): Promise { - return new Promise((resolve, reject) => { - const checkStatus = async () => { - try { - const tx = await client.getTransaction({ hash }); - - if (!tx) { - setTimeout(checkStatus, interval); - return; - } - - if (tx.status === 'pending') { - setTimeout(checkStatus, interval); - return; - } - - resolve(tx); - } catch (error) { - reject(error); - } - }; - - checkStatus(); - }); -} - -// Usage -const finalTx = await monitorTransaction(client, "0x..."); -``` +Persist the transaction ID as soon as submission succeeds. If the application process restarts or a wait times out, resume `getTransaction` / `waitForFinalization` for that ID. Do not infer that the write was never submitted and send it again. + +## Related + +- [Writing to Intelligent Contracts](./writing-data) +- [Fee Outcomes and Debugging](./fee-outcomes-and-debugging) +- [Transaction and lifecycle API reference](/api-references/genlayer-js/transactions) +- [Transaction Statuses](/understand-genlayer-protocol/core-concepts/transactions/transaction-statuses) diff --git a/pages/developers/decentralized-applications/reading-data.mdx b/pages/developers/decentralized-applications/reading-data.mdx index 99cf9b08..f384b437 100644 --- a/pages/developers/decentralized-applications/reading-data.mdx +++ b/pages/developers/decentralized-applications/reading-data.mdx @@ -1,113 +1,84 @@ --- -description: "Reading from Intelligent Contracts uses view functions to query state with GenLayerJS without transactions, gas, or state changes." +description: "Read Intelligent Contract state at the latest final or latest non-final snapshot with GenLayerJS." --- # Reading from Intelligent Contracts -Reading from Intelligent Contracts means querying contract state or executing view functions without modifying blockchain state. These read-only operations are free, require no fees, and provide immediate access to contract data. +Public view methods read Intelligent Contract state without submitting a consensus transaction. They do not need a transaction fee or signing account. -## Understanding View Operations - -In GenLayer, functions marked with the `@gl.public.view` decorator are read-only operations that: -- Don't modify the contract's state -- Can be executed without requiring a transaction -- Return data immediately -- Don't consume gas -- Can be called by any account - -## Basic Contract Reading - -Here's how to read from an Intelligent Contract: +## Basic read ```typescript -import { simulator } from 'genlayer-js/chains'; -import { createClient, createAccount } from 'genlayer-js'; +import { createClient } from 'genlayer-js'; +import { localnet } from 'genlayer-js/chains'; +import { TransactionHashVariant } from 'genlayer-js/types'; -const account = createAccount(); -const client = createClient({ - chain: simulator, - account: account, -}); +const client = createClient({ chain: localnet }); const result = await client.readContract({ address: contractAddress, functionName: 'get_complete_storage', args: [], + transactionHashVariant: TransactionHashVariant.LATEST_FINAL, }); ``` -### Parameters Explained +## Choose the state snapshot -- `address`: The deployed contract's address on the GenLayer network -- `functionName`: The name of the view function you want to call -- `args`: An array of arguments that the function accepts (empty if none required) +| Variant | Use it for | +|:--|:--| +| `LATEST_FINAL` | Durable application state after finalization. This is the conservative default for accounting and irreversible UI decisions. | +| `LATEST_NONFINAL` | The newest available non-final state. Use it for responsive interfaces that can tolerate an appeal or recomputation changing the result. | -## Common View Operations +Omit `transactionHashVariant` when the chain's default is appropriate. Be explicit when an application depends on finality semantics. -Intelligent Contracts typically include several types of view functions: +## Arguments and return formats -### State Queries ```typescript -// Reading a single value -const balance = await client.readContract({ - address: contractAddress, - functionName: 'get_balance', - args: [accountAddress], -}); - -// Reading multiple values const userInfo = await client.readContract({ address: contractAddress, functionName: 'get_user_info', args: [userId], + transactionHashVariant: TransactionHashVariant.LATEST_FINAL, }); ``` -### Computed Values -```typescript -// Getting calculated results -const totalSupply = await client.readContract({ - address: contractAddress, - functionName: 'calculate_total_supply', - args: [], -}); -``` +Use `kwargs` instead of `args` when the contract API is clearer with named arguments. The default result is decoded into GenLayer calldata values. Advanced consumers can request `rawReturn: true` for raw bytes or `jsonSafeReturn: true` where supported by the selected client surface. + +## Reads after a write + +Waiting for a decision and reading the latest non-final state is faster, but that state can still move. Waiting for finalization and reading `LATEST_FINAL` gives a durable pair: -### Validation Checks ```typescript -// Checking permissions -const hasAccess = await client.readContract({ +await client.waitForFinalization({ hash: txId }); + +const value = await client.readContract({ address: contractAddress, - functionName: 'check_user_permission', - args: [userId, 'ADMIN_ROLE'], + functionName: 'get_storage', + args: [], + transactionHashVariant: TransactionHashVariant.LATEST_FINAL, }); ``` -## Error Handling - -When reading from contracts, you should handle potential errors: +## Error handling ```typescript try { - const result = await client.readContract({ + const value = await client.readContract({ address: contractAddress, functionName: 'get_data', args: [], }); - console.log('Data retrieved:', result); + console.log(value); } catch (error) { - if (error.message.includes('Contract not found')) { - console.error('Invalid contract address'); - } else if (error.message.includes('Function not found')) { - console.error('Invalid function name'); - } else { - console.error('Error reading contract:', error); - } + console.error('Contract read failed', error); } ``` -## Best Practices +Do not classify errors only by matching an English message. Preserve the RPC error data so the application can decode a custom error with the deployment's matching ABI and the [Error & Revert Reference](/developers/error-reference). + +## Related -1. **Cache Results**: For frequently accessed data that doesn't change often, consider caching the results. -2. **Batch Readings**: When possible, use functions that return multiple values instead of making multiple separate calls. -3. **Type Safety**: Use TypeScript interfaces to ensure type safety when handling returned data: +- [Writing to Intelligent Contracts](./writing-data) +- [Querying a Transaction](./querying-a-transaction) +- [GenLayerJS contract API](/api-references/genlayer-js/contracts) diff --git a/pages/developers/decentralized-applications/transaction-kit-integration.mdx b/pages/developers/decentralized-applications/transaction-kit-integration.mdx new file mode 100644 index 00000000..98683dee --- /dev/null +++ b/pages/developers/decentralized-applications/transaction-kit-integration.mdx @@ -0,0 +1,163 @@ +--- +description: "Add GenLayer fee review, wallet submission, and transaction tracking to React, Vue, or headless apps." +--- + +# Transaction Kit Integration + +`@genlayer/transaction-kit` is the frontend layer for GenLayer fee approval and transaction tracking. It turns a transaction plus optional developer fee profile into a quote, shows the user what they are funding, submits through an EIP-1193 wallet provider and tracks the result. + +## Install + +```jsonc +{ + "dependencies": { + "@genlayer/transaction-kit": "github:genlayerlabs/genlayer-transaction-kit#pkg/core", + "@genlayer/transaction-kit-react": "github:genlayerlabs/genlayer-transaction-kit#pkg/react" + } +} +``` + +Vue apps use `#pkg/vue`. The adapters are thin UI packages around the same core flow. + +These GitHub references are useful before the first package prerelease exists. Once the release candidate is published, replace them with the exact matching `-rc.N` package versions from the release notes and keep core plus the framework adapter on the same version. Do not depend on the npm `latest` tag for a prerelease. + +## Create the kit + +The kit depends on an injected [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) provider. MetaMask, Privy embedded wallets, external Privy wallets, WalletConnect and similar wallets work as long as they expose `request()`. + +```typescript +import { createTransactionKit } from '@genlayer/transaction-kit'; +import { testnetAsimov } from 'genlayer-js/chains'; +import feeProfile from './fee-profile.json'; + +const kit = createTransactionKit({ + chain: testnetAsimov, + provider: window.ethereum, + account: userAddress, + suggestions: feeProfile, +}); +``` + +`suggestions` is optional. Without it, the kit falls back to network defaults. With it, matching methods are quoted from your measured developer fee profile while prices and caps still come from live network reads. + +## Use the React panel + +```tsx +import { GenLayerTransactionPanel } from '@genlayer/transaction-kit-react'; +import '@genlayer/transaction-kit-react/styles.css'; + + refetchAppState()} +/> +``` + +The panel owns the normal flow: + +1. estimate the fee policy +2. show an itemized fee receipt +3. let the user choose `low`, `standard` or `high` appeal posture +4. show price-protection caps +5. ask the wallet to sign +6. track the transaction +7. surface success, failure or an unresolved outcome + +Deploys use the same panel shape: + +```tsx + +``` + +## Use the headless flow + +Use the headless API when you need a custom UI: + +```typescript +const quote = await kit.estimate( + { preset: 'standard' }, + { kind: 'write', address: contract, method: 'place_bet', args: [42] }, +); + +const { genlayerTxId } = await kit.submit(quote, { + kind: 'write', + address: contract, + method: 'place_bet', + args: [42], +}); + +const final = await kit.track(genlayerTxId, (status) => { + console.log(status.phase, status.statusName, status.executionResultName); +}); +``` + +The same flow is also exposed through framework hooks/composables where available. + +## Verify the live fee policy + +Every quote includes a `verification` result comparing the quoted fee-policy hash with the chain's current policy: + +| Status | Meaning | Default adapter behavior | +|:--|:--|:--| +| `verified` | The quote matches the live fee policy. | Signing is enabled. | +| `mismatch` | The quote is stale or was built against different policy data. | Signing is blocked; re-estimate. | +| `unavailable` | The backend could not provide a comparable live policy. | A warning is shown, but signing remains possible. | + +React and Vue block a known mismatch by default. Keep that fail-closed behavior for ordinary applications. `allowUnverified: true` is an explicit operator override for controlled environments; it should not be the standard way to get a stale quote through the UI. + +```typescript +if (quote.verification.status === 'mismatch') { + throw new Error('Fee policy changed; re-estimate before signing.'); +} +``` + +## Presets, profiles and overrides + +The kit builds fee options in this order: + +1. user preset: `low`, `standard` or `high` +2. developer profile: measured method or deploy suggestions +3. explicit caller overrides + +Use the preset for the user's appeal posture. Use the profile for method-specific allocations, message budget and rotations. Use overrides only when the app deliberately knows more than the default profile for a specific flow. + +| Transaction Kit preset | Funded appeal rounds | +|:--|--:| +| `low` | 1 | +| `standard` | 3 | +| `high` | 5 | + +These are frontend safety postures, not the CLI's smaller developer defaults. Rotations are independent: Transaction Kit reads `rotationsPerRound` from the measured profile and creates one rotation entry for the initial leader round plus each funded appeal round. + +```typescript +await kit.estimate( + { + preset: 'high', + overrides: { + totalMessageFees: 10_000_000_000_000_000n, + }, + }, + { kind: 'write', address: contract, method: 'register_and_claim', args }, +); +``` + +Overrides are useful for advanced apps that can distinguish modes before submission. For example, if a method has a rare expensive branch and the app can prove the current user cannot hit it, the app may override a lower message budget. The default committed profile should still be conservative. + +## Long-running transactions + +Do not force users to stare at a modal for minutes. Transactions that call LLMs, fetch web data or wait through appeals can take time. Use the panel timeline for immediate feedback, but track the transaction in your backend or application state as well. A user should be able to navigate away and come back to the outcome. + +For writes, a quote can include `queue.pendingAhead`, and tracked status can include `queuePosition`. Use those values to explain that the transaction is waiting behind earlier writes to the same contract instead of presenting the wait as a failure. + +The adapters also expose recovery actions when the tracked lifecycle permits them: + +- **Top up fees** adds an explicit distribution and value to a transaction whose budget can still be extended. +- **Cancel** requests cancellation through the current SDK lifecycle path. + +Do not show these actions based only on elapsed wall-clock time. Use the kit's `canTopUp` and `canCancel` state, which is derived from the transaction's current status. diff --git a/pages/developers/decentralized-applications/writing-data.mdx b/pages/developers/decentralized-applications/writing-data.mdx index 8036b6da..0c6a3f3b 100644 --- a/pages/developers/decentralized-applications/writing-data.mdx +++ b/pages/developers/decentralized-applications/writing-data.mdx @@ -1,248 +1,163 @@ --- -description: "Writing to Intelligent Contracts covers GenLayer write transactions, fees, gas, signing, receipts, and finalization." +description: "Estimate, submit, and verify fee-funded writes to Intelligent Contracts with GenLayerJS." --- -# Writing to Intelligent Contracts - -Writing to an Intelligent Contract means sending a transaction that modifies the contract's state. Unlike read operations, write operations require fees and must be processed by the network before taking effect. +import { Callout } from 'nextra-theme-docs' -## Understanding Write Operations +# Writing to Intelligent Contracts -In GenLayer, functions that modify state: -- Require a transaction to be sent to the network -- Consume gas (computational resources) -- Need time to be processed and finalized -- Must be signed by an account with sufficient balance to pay for the transaction fees -- Return a transaction hash immediately, but state changes are not instant +A write changes Intelligent Contract state and enters the GenLayer consensus lifecycle. On a fee-charging deployment, estimate the transaction policy first, submit its `distribution` and `feeValue`, then verify both the consensus status and execution result. -## Basic Contract Writing +## Estimate and submit -Here's how to write to an Intelligent Contract: +For a development-time write, Studio can simulate the concrete call and turn its observed execution and message use into an estimate: ```typescript -import { simulator } from 'genlayer-js/chains'; -import { createClient, createAccount } from 'genlayer-js'; -import type { TransactionStatus } from 'genlayer-js/types'; +import { createAccount, createClient, isSuccessful } from 'genlayer-js'; +import { localnet } from 'genlayer-js/chains'; const account = createAccount(); -const client = createClient({ - chain: simulator, - account: account, -}); +const client = createClient({ chain: localnet, account }); -// Send the transaction -const transactionHash = await client.writeContract({ +const write = { address: contractAddress, functionName: 'update_storage', args: ['new_data'], - value: 0, // Optional: amount of native GEN tokens to send -}); - -// Wait for the transaction to be processed -const receipt = await client.waitForTransactionReceipt({ - hash: transactionHash, - status: TransactionStatus.FINALIZED, // or 'ACCEPTED' +}; + +const estimate = await client.estimateTransactionFeesForWrite(write); +const txId = await client.writeContract({ + ...write, + fees: { + distribution: estimate.distribution, + feeValue: estimate.feeValue, + }, }); -``` - -### Parameters Explained -- `address`: The deployed contract's address -- `functionName`: The name of the function to call -- `args`: Array of arguments for the function -- `value`: Amount of native tokens to send (in wei) - -## Transaction Lifecycle - -1. **Transaction Creation** -```typescript -const transactionHash = await client.writeContract({ - address: contractAddress, - functionName: 'mint_token', - args: [recipient, amount], -}); +const transaction = await client.waitForFinalization({ hash: txId }); +if (!isSuccessful(transaction)) { + throw new Error( + `Write failed: ${transaction.statusName} / ${transaction.txExecutionResultName}`, + ); +} ``` -2. **Transaction Status Monitoring** -```typescript -// Basic waiting -const receipt = await client.waitForTransactionReceipt({ - hash: transactionHash, - status: 'FINALIZED', -}); +`estimateTransactionFeesForWrite` is useful while developing and profiling because it performs a write simulation. For an application user flow, consume a checked-in [`fee-profile.json`](./fee-profiling-and-estimation) or use [Transaction Kit](./transaction-kit-integration) rather than simulating before every click. -// Advanced monitoring with timeout -const receipt = await client.waitForTransactionReceipt({ - hash: transactionHash, - status: 'FINALIZED', - interval: 5_000, // check every 5 seconds - retries: 10, // maximum number of retries -}); -``` +## Write parameters -## Common Write Operations +| Field | Meaning | +|:--|:--| +| `address` | Deployed Intelligent Contract address. | +| `functionName` | Public write method name. | +| `args` / `kwargs` | Positional or named calldata arguments. | +| `value` | Optional GEN intentionally sent to a payable contract method. This is separate from the fee deposit. | +| `fees` | Estimated `distribution` plus `feeValue` on a fee-charging deployment. | +| `validUntil` | Optional latest activation time. It is not a finalization deadline. | -### Calling Payable Methods +## Payable writes -Send GEN with a write call using the `value` parameter: +The user value and fee deposit are different amounts. To send 5 GEN to a payable method, include `value` while keeping the estimate in `fees`: ```typescript -// Send 5 GEN to a payable method -const hash = await client.writeContract({ +const call = { address: contractAddress, functionName: 'tip', args: [], - value: BigInt(5) * BigInt(10 ** 18), // 5 GEN in wei -}); -``` - -The `value` is specified in wei (1 GEN = 10¹⁸ wei). The receiving contract method must be decorated with `@gl.public.write.payable`. - -### Token Transfers -```typescript -// Sending tokens -const hash = await client.writeContract({ - address: tokenContractAddress, - functionName: 'transfer', - args: [recipientAddress, amount], -}); -``` - -### State Updates -```typescript -// Updating user data -const hash = await client.writeContract({ - address: contractAddress, - functionName: 'update_user_profile', - args: [userId, newProfile], + value: 5n * 10n ** 18n, +}; + +const estimate = await client.estimateTransactionFeesForWrite(call); +const txId = await client.writeContract({ + ...call, + fees: { + distribution: estimate.distribution, + feeValue: estimate.feeValue, + }, }); ``` -## Using a Browser Wallet (MetaMask) +The receiving method must be decorated with `@gl.public.write.payable`. The transaction's total wallet requirement is the user value plus the fee deposit. -When building a browser dApp, transactions are signed by the user's wallet instead of a local private key. You need to: +## Browser wallets -1. Create a client with the wallet's address and provider -2. Switch the wallet to the correct GenLayer network -3. Send transactions as usual +Use an EIP-1193 provider for signing and connect it to the same chain definition used by the client: ```typescript -import { createClient } from "genlayer-js"; -import { studionet } from "genlayer-js/chains"; -import { TransactionStatus } from "genlayer-js/types"; +import { createClient } from 'genlayer-js'; +import { studionet } from 'genlayer-js/chains'; -// Create client with wallet address (from your wallet connection flow) const client = createClient({ chain: studionet, account: walletAddress as `0x${string}`, + provider: window.ethereum, }); -// Switch the wallet to the correct network — must be called before writing -await client.connect("studionet"); +await client.connect('studionet'); -// Now write as usual — MetaMask will prompt for signing -const txHash = await client.writeContract({ +const write = { address: contractAddress, - functionName: "create_profile", - args: ["alice", "Hello world"], - value: BigInt(0), -}); - -const receipt = await client.waitForTransactionReceipt({ - hash: txHash, - status: TransactionStatus.ACCEPTED, + functionName: 'create_profile', + args: ['alice', 'Hello world'], +}; +const estimate = await client.estimateTransactionFeesForWrite(write); + +const txId = await client.writeContract({ + ...write, + fees: { + distribution: estimate.distribution, + feeValue: estimate.feeValue, + }, }); ``` -Available networks: `"localnet"`, `"studionet"`, `"testnetAsimov"`, `"testnetBradbury"`. +The matching v2 release candidate also supplies the Studio-dev chain definition for chain ID 61997. Do not reuse `studionet` for the preview; chain identity, RPC, and consensus addresses must move together. + +## Wait for the intended lifecycle point -> **Important:** If the wallet is on the wrong chain, the SDK will throw an error telling you which chain the wallet is on vs. which chain the client expects. Always call `client.connect()` before sending transactions. +- `waitForDecision({ hash })` returns after a materialized decision. Use it for responsive UI that does not need final settlement yet. +- `waitForFinalization({ hash })` waits for final fee settlement and refunds. Use it for accounting and durable completion. +- `waitForTransactionReceipt({ waitUntil: 'decided' | 'finalized' })` is the generic form. Its old `status` option remains only for compatibility. -## Error Handling +An `ACCEPTED` or `FINALIZED` status does not prove that the contract returned successfully. Always use `isSuccessful(transaction)` or require `FINISHED_WITH_RETURN` as well as the accepted/finalized status. + +## Handle failures without duplicating writes + +Separate failures before submission from failures after a transaction ID exists: ```typescript +let txId: `0x${string}` | undefined; + try { - const hash = await client.writeContract({ - address: contractAddress, - functionName: 'update_data', - args: ['new_data'], + const estimate = await client.estimateTransactionFeesForWrite(write); + txId = await client.writeContract({ + ...write, + fees: { + distribution: estimate.distribution, + feeValue: estimate.feeValue, + }, }); - - try { - const receipt = await client.waitForTransactionReceipt({ - hash, - status: 'FINALIZED', - }); - console.log('Transaction successful:', receipt); - } catch (waitError) { - console.error('Transaction failed or timed out:', waitError); - } } catch (error) { - if (error.message.includes('insufficient funds')) { - console.error('Not enough balance to pay for transaction'); - } else if (error.message.includes('user rejected')) { - console.error('User rejected the transaction'); - } else { - console.error('Error sending transaction:', error); - } + // No GenLayer transaction ID was returned. Re-estimate after fixing the + // wallet rejection, balance, stale fee policy, or invalid input. + throw error; } -``` - -## Transaction Status Types -GenLayer transactions can have different status requirements: - -```typescript -enum TransactionStatus { - PENDING = "PENDING", - CANCELED = "CANCELED", - PROPOSING = "PROPOSING", - COMMITTING = "COMMITTING", - REVEALING = "REVEALING", - ACCEPTED = "ACCEPTED", - FINALIZED = "FINALIZED", - UNDETERMINED = "UNDETERMINED", +const transaction = await client.waitForFinalization({ hash: txId }); +if (!isSuccessful(transaction)) { + // Inspect this transaction. Do not blindly submit the same state-changing + // operation again: the first write may already have executed. + console.error(transaction.statusName, transaction.txExecutionResultName); } - -// Wait for just acceptance (faster) -const acceptedReceipt = await client.waitForTransactionReceipt({ - hash: transactionHash, - status: TransactionStatus.ACCEPTED, -}); - -// Wait for full finalization -const finalizedReceipt = await client.waitForTransactionReceipt({ - hash: transactionHash, - status: TransactionStatus.FINALIZED, -}); ``` -## Best Practices + +Once `writeContract` returns a transaction ID, a timeout in your process is not evidence that submission failed. Resume tracking that ID. Blind retries can create duplicate application actions. + -1. **Always Wait for Receipts**: Don't assume a transaction is successful just because you got a hash. -2. **Handle Timeouts**: Set appropriate timeouts for transaction waiting. -3. **Implement Retry Logic**: For important transactions, implement retry mechanisms: +## Related -```typescript -async function sendWithRetry( - client: GenLayerClient, - params: WriteContractParameters, - maxAttempts = 3 -): Promise { - for (let attempt = 1; attempt <= maxAttempts; attempt++) { - try { - const hash = await client.writeContract(params); - return await client.waitForTransactionReceipt({ - hash, - status: 'FINALIZED', - timeout: 30_000 * attempt, // Increase timeout with each attempt - }); - } catch (error) { - if (attempt === maxAttempts) throw error; - console.log(`Attempt ${attempt} failed, retrying...`); - await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); - } - } - throw new Error('Max retry attempts reached'); -} -``` \ No newline at end of file +- [Fee Profiling and Estimation](./fee-profiling-and-estimation) +- [Fee Outcomes and Debugging](./fee-outcomes-and-debugging) +- [Transaction Kit Integration](./transaction-kit-integration) +- [Querying a Transaction](./querying-a-transaction) diff --git a/pages/developers/error-reference.mdx b/pages/developers/error-reference.mdx new file mode 100644 index 00000000..23719911 --- /dev/null +++ b/pages/developers/error-reference.mdx @@ -0,0 +1,370 @@ +--- +description: "A reference catalog of the GenLayer consensus protocol's custom Solidity errors: each error's 4-byte selector, its meaning, and the common cause or fix, organized by area, plus common encoding pitfalls." +--- + +import { Callout } from 'nextra-theme-docs' +import consensusV06Selectors from '../../data/consensus-v06-error-selectors.json' + +# Error & Revert Reference + +When a GenLayer protocol contract rejects a call, it reverts with a **custom error** rather than a plain string. Tooling that does not have the contract ABI shows you only the raw revert data — a 4-byte **selector** such as `0x632be5a1`, sometimes followed by ABI-encoded arguments. This page lets you translate that selector back into a named error, understand what it means, and see the most common cause or fix. + +## How to Look Up a Selector + +The first four bytes of a revert payload identify the error. For example, a revert that begins `0x632be5a1` corresponds to `FeeValueMustBeNonZero(uint256)` — a required fee field was left at zero. + +- If you have the full revert data, take the first four bytes (the leading `0x` plus eight hex characters) and find it in the tables below. +- The selector is derived as the first four bytes of `keccak256("ErrorName(type1,type2,...)")`, using the argument **types only** — no parameter names, no spaces. Any tool that computes function/error selectors (for instance, `cast 4byte-decode` or `cast sig`) uses the same rule. +- Errors that carry arguments (for example `BatchSizeExceeded(uint256,uint256)`) encode those values immediately after the selector; the parenthesized note in the **Meaning** column tells you what each argument is. + + +Selectors are computed from the exact error signature. `FeeValueMustBeNonZero(uint256)` and a hypothetical `FeeValueMustBeNonZero()` are *different* selectors. When computing a selector yourself, always use the canonical, type-only signature and omit parameter names — hashing a signature that still contains parameter names produces a wrong selector. + + +## Common Encoding Pitfalls + +Some reverts you hit while integrating are not protocol errors at all, but encoding mistakes in the request you sent. A few recurring ones: + +- **Write-transaction calldata must be wrapped.** The `data` for a write (state-changing) transaction is not the raw contract calldata. It must be `rlp.encode([calldata, leader_only])` — the calldata together with the leader-only flag. Passing the raw calldata directly typically surfaces as an RLP decoding error like **`RLP string ends with superfluous bytes`**. If you see that message, wrap your calldata as an RLP list rather than sending it bare. +- **Selector present but arguments missing or misaligned.** If a selector matches an error that takes arguments but the payload is only four bytes long (or the wrong length), the sending code likely built the error data by hand. Decode the arguments against the signature shown in the tables. +- **Wrong signature when decoding.** If a selector does not appear in these tables, double-check that you are hashing the type-only signature. A mismatch between your assumed signature and the real one yields a selector that will never resolve here. + +## How This Catalog Was Generated + +The complete index on this page is generated from the consensus repository's canonical list (`scripts/utils/errorSelectors.ts` on the `v0.6-dev` line), at source revision `2f78febfc06866e59e7f12db848664e95ce99e8e`. Each 4-byte selector is computed from the type-only signature with `keccak256`. The checked-in v0.6 snapshot contains 363 unique signatures and 363 unique selectors. CI validates those counts, the known anchor `FeeValueMustBeNonZero(uint256)` = `0x632be5a1`, and the absence of collisions. The shorter tables add explanations for common integration errors. + + +This reference reflects a development snapshot of the consensus contracts and is intended for orientation, not as a compatibility guarantee. Error names, selectors, and especially the exact conditions that trigger them can change between protocol versions. For a definitive decode, always match against the ABI of the specific deployed contract you are calling. Entries noted as "(test mock)" originate from test-only contracts and should not be expected on a production network. + + +## Common Error Tables + +These curated tables are grouped by protocol area. They explain the errors most useful during application, validator, and tooling development. Use the [complete selector index](#complete-v06-selector-index) for exhaustive lookup. + +### Fees + +| Error | Selector | Meaning | Common cause / fix | +|:--|:--|:--|:--| +| `AllocationCommitmentMissing()` | `0xbe77b500` | Expected fee-allocation commitment is not present | Include the commitment | +| `AllocationDuplicateKey()` | `0xbb598087` | Two sibling allocations share (messageType, recipient, callKey) | Deduplicate allocation keys | +| `AllocationLifecycleBudgetInsufficient()` | `0x00583b33` | Budget below minPrimary times (appealRounds+1) for on-acceptance | Raise on-acceptance budget | +| `AllocationRestrictedTxCannotAcceptMessageFees()` | `0x35ebdf50` | A restricted-allocation tx illegally accepted message fees | Do not accept message fees here | +| `AllocationSubtreeHashMismatch()` | `0xdda17044` | Provided subtree hash differs from committed hash | Provide the committed subtree | +| `AllocationSubtreeRequired()` | `0x47db53f3` | Leader must carry subtree under HashCommitments but did not | Include required subtree | +| `AllocationTreeBudgetInconsistent()` | `0x328bfb08` | Allocation-tree node budgets do not sum consistently | Fix tree budget sums | +| `AllocationTreeMalformed()` | `0x7fecd5b1` | Fee allocation tree is structurally invalid | Rebuild a valid tree | +| `AllocationTreeTooDeep()` | `0x48668d66` | Allocation tree exceeds maximum depth | Flatten/reduce tree depth | +| `AlreadyClaimed()` | `0x646cf558` | Fee or reward already claimed | No double claim | +| `AlreadySettled()` | `0x560ff900` | Fees or transaction already settled | No re-settlement | +| `BudgetTooLow()` | `0x305e533c` | Provided budget is below the minimum required | Increase the budget | +| `ExecutionBudgetExceeded(uint256,uint256)` | `0x57df8523` | Execution gas consumption exceeded the tx budget (attempted, budget) | Raise execution budget | +| `ExternalBudgetInvalid()` | `0x8b434eea` | External message budget is invalid | Ensure budget >= gasLimit*price | +| `ExternalGasLimitBelowMinimum()` | `0xf5963b64` | External message gas limit below minimum | Raise external gas limit | +| `ExternalGasLimitOrPriceZero()` | `0xbf35c24e` | External message gas limit or price is zero | Provide non-zero limit and price | +| `ExternalMessageFreezeExceeded(bytes32,uint256,uint256)` | `0x05684868` | External message froze more value than allowed (txId, declaredValue, availableLimit) | Reduce declared value | +| `ExternalOnAcceptanceNotSupported()` | `0xa54ce0b6` | On-acceptance semantics not supported for external messages | Do not use on-acceptance externally | +| `FailedFeeTransfer()` | `0x24a8ac13` | Fee ETH transfer failed | Check recipient/balance | +| `FeeSettlementNotCompleted()` | `0x1432a761` | Operation requires fee settlement to complete first | Settle fees before proceeding | +| `FeeValueMustBeNonZero(uint256)` | `0x632be5a1` | A required fee field was zero (field index) | Provide a non-zero fee value | +| `InsufficientFees()` | `0x8d53e553` | Total fees provided do not cover the operation | Provide more fees | +| `InsufficientFeesForRound()` | `0x0c35bf69` | Fees insufficient to fund a specific consensus round | Increase per-round fees | +| `InsufficientGasForInternalMessageCreation(uint256,uint256,uint256,address)` | `0xb2c9bc63` | Outer tx lacks gas to forward per-child floor under EIP-150 (floor, required, available, recipient) | Raise outer gas limit | +| `InsufficientValue()` | `0x11011294` | msg.value is less than fees owed | Send more value | +| `MaxPriceExceeded(uint256,uint256)` | `0xb4132db3` | A current fee-unit price exceeds the caller's ceiling (`globalPrice`, `userMax`) | Re-estimate; raise the ceiling only if the user accepts it | +| `MessageAllocationBudgetInsufficient()` | `0x67d6a6b3` | Allocation's budget is too low for the message | Increase allocation budget | +| `MessageAllocationsNotEqualBudget()` | `0x9659e275` | Sum of message allocations does not equal declared budget | Make allocations sum to budget | +| `MessageBudgetExceeded(uint256,uint256)` | `0xd67553bd` | Internal message consumed more than its budget (attempted, budget) | Raise message budget | +| `MessageDeclaredBudgetInsufficient()` | `0x7f295162` | Declared message budget is below required | Increase declared budget | +| `MessageEmissionPhaseMismatch()` | `0x5ca1f1d8` | message.onAcceptance differs from allocation.onAcceptance | Align emission phase flags | +| `MessageFeeParamsMismatch()` | `0x67b02b3b` | Message fee parameters do not match allocation/commitment | Align fee params | +| `MessageFeesReportMismatch()` | `0x86515990` | Reported per-message fees differ from expected | Correct the fee report | +| `MessageFeesTotalMustBeNonZero()` | `0xf79991cb` | Sum of message fees is zero when it must be positive | Provide non-zero message fees | +| `MessageNoMatchingAllocation()` | `0x4e4b3c38` | An emitted message has no matching fee allocation | Add a matching allocation | +| `OnlyConsensusCanCall()` | `0x6c6fe28a` | Fee-management function callable only by consensus | Only consensus may call | +| `OnlyFeeManager()` | `0x8f1dbd6c` | Caller is not the fee manager | Only fee manager may call | +| `RollupBudgetBelowFloor()` | `0xa70732ee` | Rollup/L1 gas budget is below the enforced floor | Increase rollup budget | +| `TooManyMessages()` | `0x1ec0b2f7` | Number of messages exceeds the allowed maximum | Emit fewer messages | + +### Staking + +| Error | Selector | Meaning | Common cause / fix | +|:--|:--|:--|:--| +| `AccountsArrayEmpty()` | `0xb1d0b181` | GEN distribution accounts array is empty | Provide at least one account | +| `AllValidatorsConsumed()` | `0x54055ab9` | All validators have been consumed (test mock) | Wait for next selection | +| `AlreadyRevoked()` | `0x905e7107` | Vesting already revoked | No re-revocation | +| `AlreadyUnlocked()` | `0x5090d6c6` | Vesting tokens already unlocked | No re-unlock | +| `AmountMustBeGreaterThan0()` | `0x35f61689` | Amount must be greater than zero | Send a positive amount | +| `BurnTransferFailed()` | `0xaac1169b` | Burn transfer failed | Check burn path/balance | +| `CanOnlyTriggerInflationMaxEpochsInFuture()` | `0xdfaaa5b2` | Inflation trigger is too far in the future | Trigger within the allowed horizon | +| `CanOnlyTriggerInflationMaxTenEpochsInFuture()` | `0xa9f9ed6c` | Inflation trigger limited to at most ten epochs ahead | Trigger within ten epochs | +| `DeepthoughtCallFailed()` | `0xbd5ac82f` | Call to the Deepthought contract failed | Check Deepthought address/target | +| `DelegatorBelowMinimumStake()` | `0x944516fc` | Delegator stake is below the minimum | Increase delegation | +| `DelegatorExitExceedsShares()` | `0x64d3ea58` | Exit amount exceeds the delegator's shares | Exit at most held shares | +| `DelegatorExitWouldBeBelowMinimum()` | `0x5f9ff6b2` | Partial exit would drop stake below minimum | Exit fully or less | +| `DelegatorMayNotExitWithZeroShares()` | `0x161fa299` | Cannot exit with zero shares | Hold shares before exiting | +| `DelegatorMayNotJoinTwoValidatorsSimultaneously()` | `0x48a6b5ba` | Delegator may back only one validator at a time | Exit current validator first | +| `DelegatorMayNotJoinWithZeroValue()` | `0xba5cb6d8` | Delegation requires non-zero value | Send a non-zero amount | +| `DelegatorMustExitAllWhenBelowMinimum()` | `0x44d4da44` | Must fully exit when below minimum stake | Perform a full exit | +| `DeveloperAlreadyHasNFT()` | `0xa74b28d1` | Developer already holds a reward NFT | One NFT per developer | +| `DeveloperCannotBeZeroAddress()` | `0x7e3f46bd` | Developer address is the zero address | Provide a valid developer | +| `DeveloperHasNoNFT()` | `0x70849322` | Developer holds no reward NFT | Mint/assign an NFT first | +| `DeveloperHasNoRewards()` | `0xdf6b47b0` | Developer has no claimable rewards | Nothing to claim | +| `EpochAdvanceNotReady()` | `0xe7295fed` | Conditions to advance the epoch are not met | Wait until advance conditions hold | +| `EpochAlreadyFinalized()` | `0x3366263c` | Epoch is already finalized | No re-finalization | +| `EpochNotFinalized()` | `0x8cf75707` | Epoch is not finalized | Finalize the epoch first | +| `EpochNotFinished()` | `0xec766557` | Epoch has not finished yet | Wait for epoch end | +| `FailedTransfer(address)` | `0x3f32e1dd` | Token/ETH transfer to a validator failed (validator) | Check recipient/balance | +| `FailedTransferCall()` | `0x3825f587` | Reward/transfer call failed | Check recipient/balance | +| `FunctionNotFound(bytes4)` | `0x5416eb98` | No diamond facet implements this selector | Add/register the facet | +| `FundingMismatch()` | `0xb84a1afb` | Funding amount does not match expected | Fund the exact expected amount | +| `GhostAlreadyHasNFT()` | `0x36bd0868` | Ghost contract already has an NFT | One NFT per ghost | +| `GhostCannotBeZeroAddress()` | `0x4d8d14c5` | Ghost contract address is zero | Provide a valid ghost address | +| `IncentivePercentageTooHigh()` | `0xaf188845` | Incentive percentage exceeds the allowed cap | Lower the incentive percentage | +| `InflationAlreadyInitialized()` | `0x9ded3b15` | Inflation already initialized | Initialize once only | +| `InflationAlreadyReceived()` | `0x719a0d39` | Inflation for this period already received | No double receipt | +| `InflationInitialized()` | `0x067f34cf` | Inflation-initialized guard tripped | Operation not allowed post-init | +| `InflationInvalidAmount()` | `0x3c1f1f16` | Inflation amount is invalid | Provide a valid amount | +| `InflationNotReadyToBeRealized()` | `0x7d8fa225` | Inflation is not ready to be realized | Wait until realizable | +| `InflationRequestFailed()` | `0xced05c45` | Cross-layer inflation request failed | Check bridge/L2 path | +| `InitialMintAlreadyCalled()` | `0xacf9028d` | GEN initial mint already executed | Mint once only | +| `InsufficientBondCustody()` | `0xebab1869` | Bond custody balance is insufficient | Ensure sufficient bond custody | +| `InsufficientContractBalance()` | `0x786e0a99` | Vesting contract lacks sufficient balance | Fund the contract | +| `InsufficientInflationFunds()` | `0x58d77d2a` | Not enough funds to pay inflation | Fund inflation reserve | +| `InvalidAtEpoch()` | `0x90dce792` | Operation is invalid at the current epoch | Retry at an allowed epoch | +| `InvalidBanPeriod()` | `0xa2fc7bbd` | Ban period parameter is invalid | Provide a valid ban period | +| `InvalidCliffUnlockBps()` | `0x7f2d8c3d` | Vesting cliff unlock basis points invalid | Use bps within 0..10000 | +| `InvalidInflationThresholds()` | `0x4774d828` | Inflation threshold config is invalid | Fix inflation thresholds | +| `InvalidL2GasParams()` | `0x21e1b5c9` | L2 gas parameters are invalid | Provide valid L2 gas params | +| `InvalidNumberOfEpochsToClaim()` | `0xb9a8ce44` | NFT reward epoch-count to claim is invalid | Use a valid epoch count | +| `InvalidOperatorAddress()` | `0xeb32d3bf` | Operator address is invalid or zero | Provide a valid operator | +| `InvalidPeriodDuration()` | `0x9e11b5e6` | Vesting period duration is invalid | Provide a valid duration | +| `InvalidRecipient()` | `0x9c8d2cd2` | Reward/transfer recipient is invalid | Provide a valid recipient | +| `InvalidSlashPercentage()` | `0x37814740` | Slash percentage is invalid | Use a valid slash percentage | +| `L2BaseGasCostQueryFailed()` | `0x7dc9c5e2` | Failed to query L2 base gas cost | Check L2 gas oracle/bridge | +| `L2MessageAlreadyInvoked()` | `0x21d83750` | L2 message already invoked (replay guard) | Do not re-invoke | +| `L2MessageProvenFailed()` | `0xfd0ae327` | L2 message proof verification failed | Provide a valid proof | +| `L2TransactionRequestFailed()` | `0x1efae811` | L2 transaction request failed | Check L2 bridge/params | +| `ManualUnlockNotRequired()` | `0x90173285` | Manual unlock is not required here | Use standard vesting flow | +| `MaxNumberOfValidatorsReached()` | `0x9ce3911d` | Validator set is at capacity | Cannot add more validators | +| `MaxValidatorsCannotBeZero()` | `0x83c27a2d` | maxValidators config cannot be zero | Set a positive maxValidators | +| `NFTMinterCallFailed()` | `0x64c31e4b` | Call to the NFT minter failed | Check NFT minter address/target | +| `NFTMinterNotConfigured()` | `0xa0c98f30` | NFT minter address is not configured | Configure the NFT minter | +| `NoBurning()` | `0x96191d45` | Burning is not enabled or not applicable | Burning disabled in this config | +| `NoPendingOperator()` | `0x9c2af11f` | No pending operator transfer to finalize | Initiate a transfer first | +| `NoPreviousEpoch()` | `0x9fa56a5b` | No previous epoch exists (test mock) | Only valid after epoch 0 | +| `NotBeneficiary()` | `0x644d871f` | Caller is not the vesting beneficiary | Call from the beneficiary address | +| `NotCreator()` | `0x93687c0b` | Caller is not the vesting schedule creator | Call from the creator address | +| `NotEnoughValidators()` | `0xae575a88` | Not enough validators available (test mock) | Register more validators | +| `NotNFTOwner()` | `0x4088c61c` | Caller does not own the NFT | Call from the NFT owner | +| `NotRevocable()` | `0x9414820d` | Vesting schedule is not revocable | Cannot revoke this schedule | +| `NotRevoked()` | `0x73f7ab1e` | Vesting is not revoked but operation requires revoked state | Revoke first | +| `NotRevoker()` | `0x2ad3d44f` | Caller is not the vesting revoker | Call from the revoker address | +| `NoValidatorsAvailable()` | `0xc4e41c46` | No validators are available (test mock) | Register/activate validators | +| `NumberOfValidatorsExceedsAvailable()` | `0x9c637db9` | Requested count exceeds available validators | Request no more than available | +| `OnlyGEN()` | `0x6a10007b` | Callable only by the GEN token contract | Only GEN may call | +| `OnlyIdleness()` | `0xf2c0764c` | Callable only by the idleness module | Only idleness may call | +| `OnlyIdlenessOrTribunal()` | `0xfcde63e9` | Callable only by idleness or tribunal | Restricted to idleness/tribunal | +| `OnlyStakingContract()` | `0xd807afce` | Callable only by the staking contract | Only staking may call | +| `OnlyTransactions()` | `0x516257a3` | Callable only by the transactions module | Only transactions may call | +| `OnlyTransactionsOrTribunal()` | `0x6c4db06b` | Callable only by transactions or tribunal | Restricted to transactions/tribunal | +| `OnlyTribunal()` | `0x811befe9` | Callable only by the tribunal | Only tribunal may call | +| `OperatorAlreadyAssigned()` | `0x5acd21ba` | Operator address already assigned to a validator | Use a free operator address | +| `OperatorTransferNotReady()` | `0xde4e791a` | Operator transfer timelock not yet elapsed | Wait for the transfer window | +| `PendingTribunals(uint256)` | `0x773c68a6` | Epoch has unresolved tribunals blocking finalization (epoch) | Resolve tribunals first | +| `PreviousEpochNotFinalizable()` | `0x93b7eb86` | Previous epoch cannot be finalized yet | Finalize prior epoch prerequisites | +| `ReductionFactorCannotBeZero()` | `0x2e980407` | Burn/inflation reduction factor is zero | Set a non-zero factor | +| `SlashGovernanceDelayPassed()` | `0x06de1175` | Governance slash delay window has passed | Act within the slash window | +| `SlashPercentageTooHigh()` | `0x0edf5154` | Slash percentage exceeds the cap | Lower the slash percentage | +| `SlashRevokedToRemoveTooHigh(uint256,uint256)` | `0x217f3b2e` | Amount to remove exceeds revoked stake (revoked, toRemove) | Remove at most revoked amount | +| `TotalDistributionMustBe100()` | `0x16904932` | GEN distribution percentages must sum to 100 | Fix distribution to total 100 | +| `TransferFailed()` | `0x90b8ec18` | Token or ETH transfer failed | Check recipient/balance | +| `UnauthorizedDelegatorClaim()` | `0xdcc541d1` | Caller not authorized to claim delegator rewards | Claim from the entitled address | +| `UnauthorizedInflationRequest()` | `0x7d8f3b9e` | Caller not authorized to request inflation | Only authorized caller may request | +| `UnknownGenAction()` | `0xe43351e0` | Unknown GEN cross-layer action code | Use a recognized action code | +| `ValidatorAlreadyInTree()` | `0x45be71b6` | Validator is already in the selection tree | Do not re-insert | +| `ValidatorAlreadyJoined()` | `0x71d16bc6` | Validator has already joined | No re-join | +| `ValidatorBelowMinimumStake()` | `0x0b294dc3` | Validator stake is below the minimum | Top up validator stake | +| `ValidatorDoesNotExist()` | `0xe51315d2` | No such validator | Reference an existing validator | +| `ValidatorExitExceedsShares()` | `0xfddb7740` | Exit amount exceeds the validator's shares | Exit at most held shares | +| `ValidatorMayNotBeDelegator()` | `0x359b3ac0` | A validator may not also be a delegator | Separate the roles | +| `ValidatorMayNotDepositZeroValue()` | `0xffb117c5` | Validator deposit requires non-zero value | Deposit a positive amount | +| `ValidatorMayNotJoinWithZeroValue()` | `0xd25ef26f` | Validator join requires non-zero stake | Join with a positive stake | +| `ValidatorMustNotBeDelegator()` | `0x85d35a02` | An address cannot be both validator and delegator | Separate the roles | +| `ValidatorNotActive()` | `0xa6ce15f6` | Validator exists but is not active this epoch | Wait until validator is active | +| `ValidatorNotInTree()` | `0x8ee72f3f` | Validator is not in the selection tree | Insert validator first | +| `ValidatorNotJoined()` | `0xffc673e8` | Validator has not joined | Join before this action | +| `ValidatorsConsumed()` | `0xeae94a56` | All validators for this round already consumed | Wait for next round/selection | +| `ValidatorsUnavailable()` | `0xd0b5c3bb` | Validators are temporarily unavailable | Retry when validators are available | +| `ValidatorWithdrawalExceedsStake()` | `0xfb7f2a7f` | Withdrawal exceeds the staked amount | Withdraw at most staked | +| `VestingAlreadyExists()` | `0xe7075d2a` | A vesting schedule already exists for the target | Do not recreate the schedule | +| `VestingAlreadyStopped()` | `0xd731022d` | Vesting is already stopped | No re-stop | +| `VestingDeploymentFailed()` | `0x0f5cc9de` | Vesting contract deployment failed | Check beacon/factory params | +| `VestingNotStopped()` | `0x6e32bb06` | Vesting is not stopped but operation requires stopped state | Stop vesting first | +| `WithdrawExceedsVested()` | `0x8b6a4865` | Withdrawal exceeds the vested amount | Withdraw at most vested | +| `ZeroAmount()` | `0x1f2a2005` | Amount is zero | Provide a non-zero amount | + +### Consensus + +| Error | Selector | Meaning | Common cause / fix | +|:--|:--|:--|:--| +| `AddingTransactionToUndeterminedQueueFailed()` | `0x5bd3cd8c` | Failed to add tx to the undetermined queue | Internal queue operation failed | +| `AllValidatorsCommitted()` | `0xb467acd8` | All validators have committed (none left) | No further commits expected | +| `AppealBondTooLow()` | `0xb44cda7b` | Appeal bond is below the required amount | Increase appeal bond | +| `AppealNotActive()` | `0x6565b6bf` | No appeal is currently active | Only valid during an active appeal | +| `AppealNotAllowed()` | `0xb94e4c42` | Appeal not permitted in the current state | Tx/round not appealable now | +| `AppealRoundAlreadyExists()` | `0x93a19b27` | An appeal round already exists | Do not re-create the round | +| `AppealRoundNotPermitted()` | `0x6ecc8d59` | The next appeal round is not currently scheduled/funded | Use the public SDK/CLI appeal path, which calls `topUpAndSubmitAppeal` | +| `BeaconAlreadyDeployed()` | `0xa1900dfe` | Beacon proxy already deployed | Do not redeploy the beacon | +| `BeaconNotDeployed()` | `0x261438ca` | Beacon proxy not deployed yet | Deploy the beacon first | +| `CallerNotActivator()` | `0xb56aa94e` | Caller is not the activator | Call from the activator | +| `CallerNotConsensus()` | `0x47820187` | Caller is not the consensus contract | Only consensus may call | +| `CallerNotGenConsensus()` | `0xf8beed7d` | Caller is not GenConsensus | Only GenConsensus may call | +| `CallerNotLeader(address,address)` | `0x3558c9da` | Caller is not the round leader (leader, caller) | Only the current leader may call | +| `CallerNotMessages()` | `0x7e3d1f98` | Caller is not the messages module | Only messages module may call | +| `CallerNotSender()` | `0xf5963d65` | Caller is not the original tx sender | Only the sender may call | +| `CallerNotTransactions()` | `0xae49b478` | Caller is not the transactions module | Only transactions module may call | +| `CanNotAppeal()` | `0xb39cdfbe` | The transaction/state cannot be appealed | Not eligible for appeal | +| `EmptyTransaction()` | `0x260c9d62` | Transaction payload is empty | Provide a non-empty tx | +| `FinalizationNotAllowed()` | `0xe1b3b3b7` | Finalization is not allowed at this time | Wait until finalization is permitted | +| `FinalizationWindowForRevealingNotOpened()` | `0xabc07e66` | Reveal finalization window is not open | Wait for the reveal window | +| `FinalizedCountExceedsIssued()` | `0x34bffaee` | Finalized count exceeds issued count | Internal accounting invariant broke | +| `IdlenessError()` | `0xc35cc440` | Generic idleness-module error | — | +| `InsufficientActiveValidators(uint256,uint256)` | `0xb5e5b936` | Not enough active validators for the request (numValidators, availableValidators) | Wait for more active validators | +| `InvalidAppealBond()` | `0xc59a6168` | Appeal bond value is invalid | Provide a valid bond | +| `InvalidAppealRounds()` | `0x2b4f0027` | Configured appeal-rounds value is invalid | Set a valid rounds value | +| `InvalidCommitHash()` | `0x173d238e` | Commit hash is invalid | Provide a valid commit hash | +| `InvalidCommittedValidators()` | `0xcbf18bce` | Committed-validators set is invalid | Correct committed validators | +| `InvalidDeploymentWithSalt()` | `0xaceab4a1` | CREATE2 deployment-with-salt is invalid | Fix salt/init params | +| `InvalidGhostContract()` | `0x1d41354d` | Ghost contract is invalid | Target a valid ghost contract | +| `InvalidIdleReplacementIndex(address,uint256,uint256)` | `0x4424217a` | Idle-replacement index mismatch (validator, expected, provided) | Provide the expected index | +| `InvalidNumOfValidators()` | `0xc4be27c5` | Validator count is invalid | Provide a valid count | +| `InvalidPhaseTimeoutBounds()` | `0x7cee0061` | Phase timeout bounds are invalid | Fix min/max bounds | +| `InvalidProcessingBlock()` | `0xd1ba0787` | Processing block is invalid | Use a valid processing block | +| `InvalidRevealData()` | `0xbc03c4b4` | Reveal payload is invalid | Provide valid reveal data | +| `InvalidRevealLeaderData()` | `0x92c313ee` | Leader reveal payload is invalid | Provide valid leader reveal data | +| `InvalidSender()` | `0xddb5de5e` | Sender is invalid | Provide a valid sender | +| `InvalidTimestampType()` | `0x099d113d` | Timestamp type is invalid | Provide a valid timestamp type | +| `InvalidTransactionStatus()` | `0xf8062102` | Tx status is invalid for this operation | Act only in the required status | +| `InvalidTribunalAppealStatus()` | `0x37c1c7d4` | Tribunal appeal status invalid for this action | Act only in the required status | +| `InvalidTxExecutionHash()` | `0x22a529c0` | Tx execution hash is invalid | Provide the correct execution hash | +| `InvalidValidator()` | `0x682a6e7c` | Validator is invalid | Provide a valid validator | +| `InvalidValidatorsLength()` | `0x5d67a037` | Validators array length is invalid | Provide correct array length | +| `InvalidVote()` | `0xd5dd0c66` | Vote value is invalid | Provide a valid vote | +| `InvalidVoteType()` | `0x8eed55d1` | Vote type is invalid | Provide a valid vote type | +| `LeaderResultHashAlreadySet()` | `0x584764ae` | Leader result hash has already been set | Set the leader result once | +| `MaxNumOfIterationsInPendingQueueReached()` | `0x357bf18b` | Pending-queue iteration cap reached | Retry/continue processing later | +| `MaxNumOfMessagesExceeded(uint256,uint256)` | `0x3838b192` | Message count exceeds allocation (numOfMessages, maxAllocatedMessages) | Emit fewer messages | +| `MockZkSyncBridgeCallFailedToL2()` | `0x9a245636` | Mock zkSync L2 bridge call failed (test) | Test/mock harness failure | +| `NoIdleValidator()` | `0xede1b7ce` | No idle validator is available | Wait for an idle validator | +| `NonGenVMContract()` | `0xc1ba7c94` | Target is not a GenVM (ghost) contract | Target a GenVM contract | +| `NoPendingRefund()` | `0xfb093898` | No pending refund to process | Nothing to refund | +| `NoRotationsLeft()` | `0xe0bf2581` | No leader rotations remaining | Rotation budget exhausted | +| `NoSenderForTransaction()` | `0xa0b18673` | Transaction has no sender | Provide a valid sender | +| `NotConsensus()` | `0x70f64de5` | Caller is not the consensus contract | Only consensus may call | +| `NotConsensusOrIdleness()` | `0xa82180be` | Caller is not consensus or idleness module | Restricted to consensus/idleness | +| `NotConsensusOrIdlenessOrTransactions()` | `0x1e3f968f` | Caller is not consensus, idleness, or transactions module | Restricted to those three modules | +| `NotConsensusOrTransactions()` | `0xe6533a27` | Caller is not consensus or transactions module | Restricted to consensus/transactions | +| `NotGenConsensus()` | `0xfd9abcdc` | Caller is not GenConsensus | Only GenConsensus may call | +| `NotIdleness()` | `0x878f6816` | Caller is not the idleness module | Only idleness may call | +| `NoValidatorsFound()` | `0x9b7fa1e5` | No validators were found | Ensure validators exist | +| `NumOfMessagesIssuedTooHigh()` | `0x5013bc2a` | Number of issued messages is too high | Reduce issued messages | +| `OutOfGas()` | `0x77ebef4d` | Out-of-gas surfaced as a typed revert | Increase gas limit | +| `PendingQueueFull(address,uint256)` | `0xd48a82a3` | Recipient's pending message queue is full (recipient, max) | Drain queue or wait | +| `PhaseTimeoutOutOfBounds(uint256,uint256,uint256)` | `0xdb0c8dfe` | Phase timeout is outside allowed bounds (value, minBound, maxBound) | Use a timeout within bounds | +| `QueueHeadExceedsTail()` | `0x698f39ad` | Queue head index exceeds tail (corruption guard) | Internal queue invariant broke | +| `RandomSeedAlreadySet()` | `0x7d6b9724` | Random seed has already been set | Set the seed once only | +| `RecoverRangeBeyondIssued()` | `0x82eacf35` | recoverRecipient range is past the issued count | Use a range within issued | +| `RecoverRangeInvalid()` | `0xd1402d7e` | recoverRecipient range is invalid | Provide a valid range | +| `RemovingTransactionFromPendingQueueFailed()` | `0x866b818f` | Failed to remove tx from the pending queue | Internal queue operation failed | +| `TransactionAlreadyAccepted()` | `0x7bdaa2b4` | Transaction has already been accepted | No re-acceptance | +| `TransactionCanNotBeAddedToAcceptedQueue()` | `0xcdcfa366` | Tx cannot be added to the accepted queue | State disallows enqueue | +| `TransactionCanNotBeAddedToPendingQueue()` | `0x406a3bbb` | Tx cannot be added to the pending queue | State disallows enqueue | +| `TransactionCanNotBeAddedToUndeterminedQueue()` | `0x69842b0a` | Tx cannot be added to the undetermined queue | State disallows enqueue | +| `TransactionCanNotBeFinalized()` | `0xd9be37ca` | Transaction is not eligible for finalization | Not in a finalizable state | +| `TransactionInRecomputation()` | `0x00ebaa7c` | Transaction is currently under recomputation | Wait for recomputation to finish | +| `TransactionNotAcceptedNorUndetermined()` | `0x90cb8b61` | Transaction is neither accepted nor undetermined | Only valid in those states | +| `TransactionNotAtAcceptedQueueHead()` | `0x3e714edf` | Tx is not at the head of the accepted queue | Process the head tx first | +| `TransactionNotAtPendingQueueHead()` | `0x0844056a` | Tx is not at the head of the pending queue | Process the head tx first | +| `TransactionNotAtUndeterminedQueueHead()` | `0x3d40531f` | Tx is not at the head of the undetermined queue | Process the head tx first | +| `TransactionNotFinalized()` | `0xe4e81f79` | Transaction is not finalized | Finalize the tx first | +| `TransactionNotFound()` | `0x31fb878f` | Transaction not found | Reference a valid tx | +| `TransactionNotInPendingQueue()` | `0x7b9ea34f` | Tx is not in the pending queue | Only valid for queued txs | +| `TransactionNotTerminal()` | `0x1d3a409a` | Transaction is not in a terminal state | Only valid for terminal txs | +| `TransactionStillValid()` | `0xc4afd8fa` | Transaction is still valid (cannot treat as expired) | Wait until it is no longer valid | +| `TribunalAlreadyFinalized()` | `0x6cb27c0f` | Tribunal has already been finalized | No re-finalization | +| `TribunalNotFound()` | `0x609bbfa8` | Referenced tribunal does not exist | Reference a valid tribunal | +| `UnfinishedTransactions()` | `0xf082b82d` | Unfinished transactions block the operation | Finish pending txs first | +| `UnfinishedTxCounterUnderflow()` | `0x8894ba94` | Unfinished-transaction counter underflowed | Internal accounting invariant broke | +| `ValidatorAlreadyCommitted()` | `0xf8961aee` | Validator has already committed | Commit once per round | +| `ValidatorAlreadyRevealed()` | `0x00d6a91b` | Validator has already revealed | Reveal once per round | +| `ValidatorAlreadyVoted()` | `0x6e271ebe` | Validator has already voted | Vote once per round | +| `ValidatorSelectionFailed()` | `0x1f90236d` | Validator selection algorithm failed | Check validator set/seed | +| `ValidatorWalletAlreadyDeployed()` | `0xb9ceb484` | Validator wallet already deployed | Do not redeploy the wallet | +| `ValidValidatorNotFound()` | `0x1c177f6c` | No valid validator was found | Ensure eligible validators exist | +| `VoteAlreadyCommitted()` | `0xcf1c5b9c` | Vote has already been committed | Commit vote once | +| `VoteAlreadyRevealed()` | `0x3246ac36` | Vote has already been revealed | Reveal vote once | +| `WalletDeploymentFailed()` | `0x6e09c9eb` | Validator wallet deployment failed | Check factory/CREATE2 params | +| `WrongRecomputationTransaction()` | `0x4d1fe80e` | Recomputation targeted the wrong transaction | Target the correct tx | + +### Governance + +| Error | Selector | Meaning | Common cause / fix | +|:--|:--|:--|:--| +| `CallerNotGovernance()` | `0xf2be30fb` | Caller is not the governance contract | Route via governance | +| `GovernanceInsufficientValue(uint256,uint256)` | `0xf498db0c` | msg.value insufficient for the operation (provided, required) | Send the required value | +| `GovernanceInvalidDelay()` | `0xd1132ebc` | Configured timelock delay is invalid | Set a valid delay | +| `GovernanceOperationExpired(address,bytes4,bytes,uint256,uint256)` | `0x11515806` | Queued operation expired past its window (target, selector, args, value, expiry) | Re-queue the operation | +| `GovernanceOperationNotFound(address,bytes4,bytes,uint256)` | `0x8ff7bbe1` | Operation not found in the queue (target, selector, args, value) | Queue it first | +| `GovernanceOperationPending(address,bytes4,bytes,uint256)` | `0xc7eef27f` | Operation is already queued/pending (target, selector, args, value) | Do not re-queue | +| `GovernanceTargetIsNotAContract(address)` | `0x83e02672` | Governance target address has no code (target) | Target a deployed contract | +| `GovernanceTimelockPending(address,bytes4,bytes,uint256,uint256)` | `0x2189e680` | Timelock not yet elapsed (target, selector, args, value, eta) | Wait until ETA | +| `ZeroValue()` | `0x7c946ed7` | Value or parameter is zero | Provide a non-zero value | + +### Access + +| Error | Selector | Meaning | Common cause / fix | +|:--|:--|:--|:--| +| `CallerNotAuthorized()` | `0xc183bcef` | Caller is not authorized for this action | Use an authorized address | +| `CallerNotOwner()` | `0x5cd83192` | Caller is not the owner | Call from the owner | +| `NotAppealsContract()` | `0xa5a4297b` | Caller is not the appeals contract | Only the appeals contract may call | +| `NotOperator()` | `0x7c214f04` | Caller is not an operator | Call from an operator address | +| `NotStaking()` | `0x890fec52` | Caller is not the staking contract | Only staking may call | +| `Unauthorized()` | `0x82b42900` | Generic unauthorized caller | Use an authorized caller | +| `ZeroAddress(string)` | `0xeac0d389` | A named address is the zero address (key) | Provide a non-zero address | + +### Other + +| Error | Selector | Meaning | Common cause / fix | +|:--|:--|:--|:--| +| `ArrayLengthMismatch()` | `0xa24a13a6` | Two input arrays have different lengths | Match array lengths | +| `BatchSizeExceeded(uint256,uint256)` | `0xf80a4845` | Batch size exceeds the allowed maximum (provided, maximum) | Reduce batch size | +| `IndexOutOfBounds()` | `0x4e23d035` | Array index is out of bounds | Use a valid index | +| `InvalidAddress()` | `0xe6c4247b` | Address parameter is invalid or zero | Provide a valid address | +| `InvalidNonce()` | `0x756688fe` | Nonce is invalid or out of order | Use the correct nonce | +| `InvalidNumber(uint256)` | `0xc5d83cde` | Generic numeric-validation failure (number) | Provide a value in range | +| `InvalidOffset()` | `0x01da1572` | Pagination offset is invalid | Use a valid offset | +| `InvalidPageSize()` | `0xe5b7db2e` | Pagination page size is invalid | Use a valid page size | +| `InvalidVersion()` | `0xa9146eeb` | Version mismatch | Match the expected version | +| `PercentageOutOfRange(uint256)` | `0xafd5d0b0` | Percentage value is out of the allowed range (value) | Provide a valid percentage | +| `ZeroTotalWeight()` | `0x098404de` | Total weight is zero (test mock) | Ensure non-zero total weight | + +## Complete v0.6 Selector Index + +This is the exhaustive selector snapshot for the audited Consensus v0.6 release line. It intentionally includes overloads such as `TransactionNotFound()` and `TransactionNotFound(bytes32)`, because their selectors are different. + + + + + + + + + + {consensusV06Selectors.map(({ signature, selector }) => ( + + + + + ))} + +
Error signatureSelector
{signature}{selector}
diff --git a/pages/developers/intelligent-contracts/deploying.mdx b/pages/developers/intelligent-contracts/deploying.mdx index 7bf8f931..da9bbb47 100644 --- a/pages/developers/intelligent-contracts/deploying.mdx +++ b/pages/developers/intelligent-contracts/deploying.mdx @@ -1,12 +1,12 @@ --- -description: "Deploying Intelligent Contracts on GenLayer: CLI deployment, deploy scripts, network configuration, and workflow guidance." +description: "Deploy Intelligent Contracts with the CLI or GenLayerJS, including v0.6 fee estimation, network selection, and outcome verification." --- import { Callout } from 'nextra-theme-docs' import { Card, Cards } from 'nextra-theme-docs' # Deploying Intelligent Contracts -Deploying Intelligent Contracts on GenLayer means choosing a deployment method and network for moving Python-based contracts from local development toward testnet use. This guide covers local development through testnet deployment, including CLI direct deployment, deploy scripts, network configuration, and how to use each approach effectively. +Deploying an Intelligent Contract creates a consensus transaction. On a Consensus v0.6 deployment, that transaction needs a fee distribution and enough protocol-fee value, just like a write. Choose the network deliberately, estimate from a measured fee profile, then wait for finalization **and verify the execution result** before using the contract address. ## Quick Start @@ -40,25 +40,31 @@ Get started quickly with these essential deployment options: GenLayer offers two primary deployment methods: ### CLI Direct Deployment -Perfect for quick deployments and simple contracts. Deploy with a single command: +The CLI is the shortest path for a single contract. Select and inspect the network first: ```bash +genlayer network set studio-dev +genlayer network info genlayer deploy --contract contracts/my_contract.py --args "Hello World" 42 ``` +The v0.6-compatible CLI resolves the deploy entry in `fee-profile.json`, estimates it against current network prices, and submits the required fee fields. Use the matching release candidate when targeting Studio-dev. + [Learn more about CLI Deployment →](./deploying/cli-deployment) ### Deploy Scripts -Ideal for complex workflows, multi-contract deployments, and testnet environments: +Use ordered scripts for multi-contract deployments and follow-up configuration. The complete guide includes a copyable helper that converts profile quantities into a live estimate: ```typescript -export default async function main(client: GenLayerClient) { - // Deploy and configure multiple contracts - const mainContract = await deployContract(client, "contracts/main.py", []); - const helperContract = await deployContract(client, "contracts/helper.py", [mainContract]); - - await configureContracts(client, mainContract, helperContract); -} +const estimate = await quoteProfile(client, feeProfile.deploy); +const txId = await client.deployContract({ + code, + args: [], + fees: { + distribution: estimate.distribution, + feeValue: estimate.feeValue, + }, +}); ``` [Learn more about Deploy Scripts →](./deploying/deploy-scripts) @@ -69,10 +75,10 @@ Deploy to different networks based on your development stage: | Network | Purpose | When to Use | |---------|---------|-------------| -| **Localnet** | Local development | Development, debugging, initial testing | -| **Studionet** | Hosted development | Team collaboration, quick prototyping | -| **TestnetAsimov** | Infrastructure testing | Stability, scalability, and stress testing | -| **TestnetBradbury** | AI/LLM testing | Real AI workloads and intelligent contract testing | +| **Localnet** | Local development | Fast, resettable testing and fee profiling | +| **Studionet** | Stable hosted Studio | Collaboration and stable hosted examples | +| **Studio-dev** | v0.123 / v0.6 preview | Release-candidate compatibility testing | +| **Bradbury** | Persistent public testnet | Production-like application validation | [Explore Network Configuration →](./deploying/network-configuration) @@ -80,21 +86,23 @@ Deploy to different networks based on your development stage: Follow this recommended progression: -1. **Develop locally** on `localnet` with full control -2. **Test collaboratively** on `studionet` with your team -3. **Validate thoroughly** on `testnetBradbury` in a production-like environment with real AI workloads +1. **Develop and measure locally** on `localnet`. +2. **Commit the generated fee profile** and test its low, standard, and high policies. +3. **Test the release candidate** on `studio-dev` with matching SDK and CLI RCs. +4. **Use Studionet or Bradbury** when you need the corresponding stable hosted environment. - Start with CLI deployment for simple contracts, then graduate to deploy scripts as your projects become more complex. + `studio-next.genlayer.com`, if enabled, is a browser alias for the preview. SDKs, CLI profiles, and wallets should keep using the canonical Studio-dev RPC and chain ID rather than treating it as a separate network. ## What's Next? After deploying your contracts: -1. **Test Contract Functions**: Use `genlayer call` and `genlayer write` to interact with deployed contracts -2. **Monitor Transactions**: Check transaction receipts with `genlayer receipt ` -3. **Build Frontend**: Integrate with your deployed contracts using [GenLayerJS](https://github.com/yeagerai/genlayer-js) -4. **Debug Issues**: Use the [Debugging Guide](./debugging) if you encounter problems +1. **Verify the deployment outcome**: finalization does not by itself mean successful execution. +2. **Record the transaction ID and address**: resume tracking the same transaction after interruption; do not blindly redeploy. +3. **Exercise writes with the same fee profile**: use [Writing to Intelligent Contracts](/developers/decentralized-applications/writing-data). +4. **Integrate the frontend**: use [Transaction Kit](/developers/decentralized-applications/transaction-kit-integration) or the low-level SDK. +5. **Diagnose fee failures**: see [Fee Outcomes and Debugging](/developers/decentralized-applications/fee-outcomes-and-debugging). -Ready to start deploying? Choose your preferred method and dive into the detailed guides! \ No newline at end of file +Ready to start deploying? Choose your preferred method and dive into the detailed guides! diff --git a/pages/developers/intelligent-contracts/deploying/cli-deployment.mdx b/pages/developers/intelligent-contracts/deploying/cli-deployment.mdx index 6cfbef22..628ded40 100644 --- a/pages/developers/intelligent-contracts/deploying/cli-deployment.mdx +++ b/pages/developers/intelligent-contracts/deploying/cli-deployment.mdx @@ -54,7 +54,7 @@ genlayer deploy --rpc https://custom-network.com/api **Alternative: Set default network first** ```bash # Set network then deploy -genlayer network testnet-bradbury +genlayer network set testnet-bradbury genlayer deploy --contract contracts/my_contract.py ``` @@ -112,17 +112,17 @@ Contract Address: 0xabcdef1234567890... ## Quick Deployment Workflow 1. **Prepare your contract**: Ensure your `.py` file is ready -2. **Choose your network**: Use `genlayer network` to set the target +2. **Choose your network**: Use `genlayer network set` to select the target 3. **Deploy**: Run the deploy command with appropriate arguments 4. **Verify**: Note the contract address for future interactions ```bash # Example workflow -genlayer network localnet +genlayer network set localnet genlayer deploy --contract contracts/token.py --args "MyToken" "MTK" 1000000 ``` ## Next Steps - Learn about [Deploy Scripts](./deploy-scripts) for more complex deployments -- Configure networks with [Network Configuration](./network-configuration) \ No newline at end of file +- Configure networks with [Network Configuration](./network-configuration) diff --git a/pages/developers/intelligent-contracts/deploying/deploy-scripts.mdx b/pages/developers/intelligent-contracts/deploying/deploy-scripts.mdx index c415bd4b..ca0bdc6f 100644 --- a/pages/developers/intelligent-contracts/deploying/deploy-scripts.mdx +++ b/pages/developers/intelligent-contracts/deploying/deploy-scripts.mdx @@ -1,257 +1,159 @@ --- -description: "Deploy Scripts guide complex Intelligent Contract deployments with ordered TypeScript or JavaScript scripts." +description: "Create ordered, fee-aware TypeScript deploy scripts for Intelligent Contracts." --- + import { Callout } from 'nextra-theme-docs' # Deploy Scripts -Deploy scripts are TypeScript or JavaScript files used to automate more complex Intelligent Contract deployment workflows. Use deploy scripts when a deployment needs ordered steps, configuration, initialization, or multiple operations beyond a simple deploy command. - -## Script Structure +Deploy scripts automate multi-contract deployments, constructor configuration, and follow-up writes. The CLI loads TypeScript or JavaScript files from `deploy/` in filename order and passes a configured GenLayerJS client to each script. -Create TypeScript or JavaScript files in the `deploy/` directory: +## File order -``` +```text your-project/ ├── deploy/ │ ├── 001_deploy_main_contract.ts -│ ├── 002_setup_configuration.ts -│ └── 003_initialize_data.ts -└── contracts/ - └── my_contract.py +│ ├── 002_deploy_helper_contract.ts +│ └── 003_configure_contracts.ts +├── contracts/ +└── fee-profile.json ``` - - Deploy scripts are executed in alphabetical order. Use numeric prefixes (001_, 002_) to control execution order. - +Use numeric prefixes because scripts execute alphabetically. A later script should not assume an earlier transaction merely returned an ID; the earlier script must wait for and verify its required lifecycle outcome. -## Basic Deploy Script +## Fee-aware deploy helper -Here's a template for a deploy script (`deploy/deployScript.ts`): +On a fee-charging deployment, raw `deployContract` and `writeContract` calls need the estimate produced from the matching profile entry. This helper converts a JSON profile entry into live estimate options: ```typescript -import { readFileSync } from "fs"; -import path from "path"; -import { - TransactionHash, - TransactionStatus, - GenLayerClient, - DecodedDeployData, - GenLayerChain -} from "genlayer-js/types"; -import { testnetBradbury } from "genlayer-js/chains"; - -export default async function main(client: GenLayerClient) { - // Read the contract file - const filePath = path.resolve(process.cwd(), "contracts/my_contract.py"); - const contractCode = new Uint8Array(readFileSync(filePath)); - - // Initialize consensus - await client.initializeConsensusSmartContract(); +import type { GenLayerClient } from 'genlayer-js/types'; - // Deploy the contract - const deployTransaction = await client.deployContract({ - code: contractCode, - args: [], // Constructor arguments - }); - - // Wait for deployment confirmation - const receipt = await client.waitForTransactionReceipt({ - hash: deployTransaction as TransactionHash, - retries: 200, - }); +type ProfileEntry = { + leaderTimeunitsAllocation: string; + validatorTimeunitsAllocation: string; + executionBudgetPerRound: string; + totalMessageFees?: string; + rotationsPerRound?: string; +}; - // Check deployment success - if ( - receipt.statusName !== TransactionStatus.ACCEPTED && - receipt.statusName !== TransactionStatus.FINALIZED - ) { - throw new Error(`Deployment failed. Receipt: ${JSON.stringify(receipt)}`); - } - - // Receipt structure differs between testnet and localnet/studionet - const deployedContractAddress = - (client.chain as GenLayerChain).id !== testnetBradbury.id - ? receipt.data.contract_address - : (receipt.txDataDecoded as DecodedDeployData)?.contractAddress; - - console.log("Contract deployed successfully!", { - "Transaction Hash": deployTransaction, - "Contract Address": deployedContractAddress, +async function quoteProfile( + client: GenLayerClient, + entry: ProfileEntry, + appealRounds = 1n, +) { + const rotationsPerRound = BigInt(entry.rotationsPerRound ?? '0'); + return client.estimateTransactionFees({ + leaderTimeunitsAllocation: BigInt(entry.leaderTimeunitsAllocation), + validatorTimeunitsAllocation: BigInt(entry.validatorTimeunitsAllocation), + executionBudgetPerRound: BigInt(entry.executionBudgetPerRound), + totalMessageFees: BigInt(entry.totalMessageFees ?? '0'), + appealRounds, + rotations: Array.from( + { length: Number(appealRounds) + 1 }, + () => rotationsPerRound, + ), }); - - return deployedContractAddress; } ``` -## Advanced Deploy Script with Configuration +The committed profile supplies measured quantities; `estimateTransactionFees` reads current network prices and ceilings. See [Fee Profiling and Estimation](/developers/decentralized-applications/fee-profiling-and-estimation). + +## Basic deploy script ```typescript -import { readFileSync } from "fs"; -import path from "path"; -import { - TransactionHash, - TransactionStatus, - GenLayerClient, - DecodedDeployData, - GenLayerChain -} from "genlayer-js/types"; -import { testnetBradbury } from "genlayer-js/chains"; +import { readFileSync } from 'node:fs'; +import { isSuccessful } from 'genlayer-js'; +import type { DecodedDeployData, GenLayerClient } from 'genlayer-js/types'; +import feeProfile from '../fee-profile.json'; export default async function main(client: GenLayerClient) { - console.log("🚀 Starting deployment process..."); - - // Deploy main contract - const mainContractAddress = await deployContract( - client, - "contracts/main_contract.py", - ["Initial Config", 1000] + const code = new Uint8Array( + readFileSync(new URL('../contracts/my_contract.py', import.meta.url)), ); - - // Deploy helper contract - const helperContractAddress = await deployContract( - client, - "contracts/helper_contract.py", - [mainContractAddress] - ); - - // Configure the main contract - await configureContract(client, mainContractAddress, helperContractAddress); - - console.log("✅ Deployment completed successfully!"); - console.log({ - mainContract: mainContractAddress, - helperContract: helperContractAddress, + const estimate = await quoteProfile(client, feeProfile.deploy); + + const txId = await client.deployContract({ + code, + args: [], + fees: { + distribution: estimate.distribution, + feeValue: estimate.feeValue, + }, }); -} - -async function deployContract( - client: GenLayerClient, - contractPath: string, - args: any[] = [] -): Promise { - const filePath = path.resolve(process.cwd(), contractPath); - const contractCode = new Uint8Array(readFileSync(filePath)); - await client.initializeConsensusSmartContract(); + const transaction = await client.waitForFinalization({ hash: txId }); + if (!isSuccessful(transaction)) { + throw new Error( + `Deployment failed: ${transaction.statusName} / ${transaction.txExecutionResultName}`, + ); + } - const deployTransaction = await client.deployContract({ - code: contractCode, - args, - }); + const decoded = transaction.txDataDecoded as DecodedDeployData | undefined; + const contractAddress = decoded?.contractAddress ?? transaction.recipient; + if (!contractAddress) throw new Error('Finalized deployment has no contract address'); - const receipt = await client.waitForTransactionReceipt({ - hash: deployTransaction as TransactionHash, - retries: 200, - }); + console.log('Contract deployed', { txId, contractAddress }); + return contractAddress; +} +``` - // Check deployment success - if ( - receipt.statusName !== TransactionStatus.ACCEPTED && - receipt.statusName !== TransactionStatus.FINALIZED - ) { - throw new Error(`Deployment failed for ${contractPath}. Receipt: ${JSON.stringify(receipt)}`); - } + +`initializeConsensusSmartContract()` is deprecated on the v2 client. The consensus deployment comes from the selected chain definition; deploy scripts should not initialize or reset it. + - // Receipt structure differs between testnet and localnet/studionet - const deployedContractAddress = - (client.chain as GenLayerChain).id !== testnetBradbury.id - ? receipt.data.contract_address - : (receipt.txDataDecoded as DecodedDeployData)?.contractAddress; +## Multi-step configuration - return deployedContractAddress; -} +Use the same rule for follow-up writes: select the method profile, estimate against the live policy, submit, and verify the execution result. +```typescript async function configureContract( client: GenLayerClient, - mainAddress: string, - helperAddress: string + mainAddress: `0x${string}`, + helperAddress: `0x${string}`, ) { - // Example configuration call - const hash = await client.writeContract({ - address: mainAddress as any, - functionName: "setHelperContract", - args: [helperAddress], - value: 0n, - }); + const args = [helperAddress]; + const estimate = await quoteProfile( + client, + feeProfile.methods.setHelperContract, + ); - await client.waitForTransactionReceipt({ - hash, - retries: 100, - interval: 5000, + const txId = await client.writeContract({ + address: mainAddress, + functionName: 'setHelperContract', + args, + fees: { + distribution: estimate.distribution, + feeValue: estimate.feeValue, + }, }); + + const transaction = await client.waitForFinalization({ hash: txId }); + if (!isSuccessful(transaction)) { + throw new Error( + `Configuration failed: ${transaction.statusName} / ${transaction.txExecutionResultName}`, + ); + } } ``` -## Running Deploy Scripts +Keep all addresses and transaction IDs in the deployment output or a deployment manifest. If a process stops after submission, resume tracking the recorded transaction ID instead of blindly sending the same deploy or write again. + +## Run scripts -Execute all deploy scripts in order: +Select and verify the network before executing the directory: ```bash +genlayer network set studio-dev +genlayer network info genlayer deploy ``` -The CLI will automatically: -1. Find all `.ts` and `.js` files in the `deploy/` directory -2. Sort them numerically by filename prefix (001_, 002_, etc.) -3. Execute them in order -4. Pass a configured GenLayer client to each script - -## Script Organization - -### Recommended File Naming - -``` -deploy/ -├── 001_core_contracts.ts # Core infrastructure -├── 002_token_contracts.ts # Token-related contracts -├── 003_governance.ts # Governance setup -├── 004_configure_system.ts # System configuration -└── 999_verify_deployment.ts # Post-deployment verification -``` - -## Error Handling - -Always include proper error handling in deploy scripts: - -```typescript -export default async function main(client: GenLayerClient) { - try { - await client.initializeConsensusSmartContract(); - - const deployTransaction = await client.deployContract({ - code: contractCode, - args: [], - }); - - const receipt = await client.waitForTransactionReceipt({ - hash: deployTransaction as TransactionHash, - retries: 200, - }); - - // Check deployment success - if ( - receipt.statusName !== TransactionStatus.ACCEPTED && - receipt.statusName !== TransactionStatus.FINALIZED - ) { - throw new Error(`Deployment failed: ${JSON.stringify(receipt)}`); - } - - // Receipt structure differs between testnet and localnet/studionet - const deployedContractAddress = - (client.chain as GenLayerChain).id !== testnetBradbury.id - ? receipt.data.contract_address - : (receipt.txDataDecoded as DecodedDeployData)?.contractAddress; - - return deployedContractAddress; - } catch (error) { - console.error("Deployment failed:", error); - throw error; - } -} -``` +For stable Studio use `studionet`; for a local stack use `localnet`. Studio-dev requires the matching release-candidate CLI and SDK. See [Network Configuration](./network-configuration). -## Next Steps +## Related -- Learn about different [Network Configuration](./network-configuration) -- Configure CLI deployments with [CLI Deployment](./cli-deployment) \ No newline at end of file +- [CLI Deployment](./cli-deployment) +- [Network Configuration](./network-configuration) +- [Writing to Intelligent Contracts](/developers/decentralized-applications/writing-data) +- [Fee Outcomes and Debugging](/developers/decentralized-applications/fee-outcomes-and-debugging) diff --git a/pages/developers/intelligent-contracts/deploying/network-configuration.mdx b/pages/developers/intelligent-contracts/deploying/network-configuration.mdx index 4a62e945..495d99bb 100644 --- a/pages/developers/intelligent-contracts/deploying/network-configuration.mdx +++ b/pages/developers/intelligent-contracts/deploying/network-configuration.mdx @@ -1,218 +1,112 @@ --- -description: "GenLayer network configuration covers Localnet, Studionet, Testnet Asimov, and Testnet Bradbury for deployments." +description: "Choose and configure Localnet, Studionet, the Studio development preview, Asimov, or Bradbury." --- + import { Callout } from 'nextra-theme-docs' # Network Configuration -GenLayer network configuration defines which network your GenLayer CLI deployments and development workflows use. GenLayer supports multiple networks, each serving specific purposes in the development lifecycle: - -## Localnet - -**Purpose**: Local development and testing -- **When to use**: During development, debugging, and initial testing -- **Setup**: Requires running the GenLayer Studio locally with `genlayer init` and `genlayer up` -- **URL**: `http://localhost:4000/api` (default) -- **Benefits**: - - Full control over validators and network configuration - - No external dependencies - - Fast transaction processing - - Ability to reset database and validators - - Built-in faucet to fund accounts with GEN (💧 button in the account selector) - -### Setting up Localnet - -```bash -# Initialize local network -genlayer init - -# Start the local network -genlayer up -``` - -For complete setup instructions, examples, and video tutorials, see the [GenLayer Studio Guide](../tools/genlayer-studio). - -## Studionet +The GenLayer CLI stores a network **alias**, then resolves the current RPC, chain ID, explorer, and consensus contract addresses from the matching network definition. Keeping the alias explicit prevents a transaction intended for a release-candidate deployment from being signed for the stable Studio chain. -**Purpose**: Hosted development environment -- **When to use**: For development without local setup requirements -- **Setup**: Accessible through [studio.genlayer.com/api](https://studio.genlayer.com) -- **Benefits**: - - No local installation required - - Pre-configured validators - - Shared development environment - - Ideal for quick prototyping - - Built-in faucet to fund accounts with GEN (💧 button in the account selector) +For the complete endpoint and wallet table, see [Networks](/developers/networks). - - Studionet is perfect for getting started quickly or when you need to share your development environment with others. - - -### Using Studionet +## Built-in networks ```bash -# Set network to studionet -genlayer network studionet - -# Deploy to studionet -genlayer deploy --contract contracts/my_contract.py +genlayer network list +genlayer network set localnet +genlayer network info ``` -## TestnetAsimov +The stable built-in aliases are: -**Purpose**: Infrastructure testing and stress testing -- **When to use**: For infrastructure stability testing, scalability testing, and stress tests -- **Setup**: Connect using network configuration -- **Benefits**: - - Stable infrastructure testing environment - - Scalability and stress testing - - Shared testnet for community testing +| Alias | Purpose | +|:--|:--| +| `localnet` | Local Studio or GLSim; full control and resets | +| `studionet` | Stable hosted Studio at `studio.genlayer.com` | +| `testnet-asimov` | Shared infrastructure and stress testing | +| `testnet-bradbury` | Production-like testing with real AI workloads | -### Using TestnetAsimov +The v0.123 / Consensus v0.6 release-candidate tooling adds `studio-dev`, backed by the JavaScript SDK's `studioDevnet` chain definition: ```bash -# Set network to testnet -genlayer network testnet-asimov - -# Deploy to testnet -genlayer deploy --contract contracts/my_contract.py +genlayer network set studio-dev +genlayer network info ``` -## TestnetBradbury - -**Purpose**: Production-like testing environment with real AI/LLM use cases -- **When to use**: For deploying and testing intelligent contracts with real LLM models -- **Setup**: Connect using network configuration -- **Benefits**: - - Real AI workloads using powerful LLM models - - Production-like environment for intelligent contract testing - - Real use cases and contracts deployed - - Shared testnet for community testing +It resolves to `https://studio-dev.genlayer.com/api`, chain ID `61997`. - - Testnet Bradbury configuration details will be available when the network goes live. The faucet and RPC endpoints are shared with Testnet Asimov. + +If `studio-dev` is not listed, the installed CLI/SDK is older than the Studio release candidate. Upgrade to the matching RC; do not substitute `studionet`, which targets chain ID 61999 and a different consensus deployment. -### Getting Testnet Tokens - -Before deploying to TestnetBradbury, you'll need testnet tokens. Use the [faucet](https://testnet-faucet.genlayer.foundation/) to get free GEN tokens. +## Localnet -### Using TestnetBradbury +Initialize and start the local stack: ```bash -# Set network to testnet -genlayer network testnet-bradbury - -# Deploy to testnet -genlayer deploy --contract contracts/my_contract.py +genlayer init +genlayer up +genlayer network set localnet ``` -## Network Comparison +Localnet defaults to `http://localhost:4000/api` and chain ID `61127`. It is the right choice for validator configuration, database resets, custom providers, and fast repeatable development. -| Feature | Localnet | Studionet | TestnetAsimov | TestnetBradbury | -|---------|----------|-----------|---------------|-----------------| -| **Setup Complexity** | Medium | None | Low | Low | -| **Control Level** | Full | Limited | Limited | Limited | -| **Persistence** | Local only | Temporary | Persistent | Persistent | -| **Collaboration** | No | Yes | Yes | Yes | -| **Performance** | Fast | Medium | Production-like | Production-like | -| **Best for** | Development | Prototyping | Infrastructure testing | AI/LLM testing | +## Hosted Studio environments -## Development Workflow - -We recommend following this progression: - -1. **Start with Localnet**: Develop and test your contracts locally with full control -2. **Test on Studionet**: Validate contracts in a shared environment -3. **Deploy to TestnetBradbury**: Final testing in a production-like environment with real AI workloads - -## Managing Network Settings - -### Viewing Current Configuration +Use `studionet` for the stable hosted experience and `studio-dev` only when validating the next release candidate. ```bash -# Show all configuration -genlayer config get +# Stable hosted Studio +genlayer network set studionet +genlayer deploy --contract contracts/my_contract.py -# Show specific network setting -genlayer config get network +# v0.123 / v0.6 release-candidate preview +genlayer network set studio-dev +genlayer deploy --contract contracts/my_contract.py ``` -### Setting Networks +Both have browser faucets. Studio-dev is temporary: expect resets and do not depend on its state for durable deployments. If `studio-next.genlayer.com` points at the same preview, treat it as a web alias and keep the canonical SDK/RPC target on Studio-dev. -The GenLayer CLI allows you to set a default network that will be used for all operations unless overridden. +## Public testnets -**Interactive Network Selection:** -```bash -# Set network interactively -genlayer network -``` - -This command will show you available networks and let you choose: - -``` -? Select a network: -❯ localnet - studionet - testnet-asimov - testnet-bradbury - custom -``` +Use Bradbury for production-like contract testing and Asimov for infrastructure-oriented testing: -**Direct Network Selection:** ```bash -# Set specific network directly -genlayer network localnet -genlayer network studionet -genlayer network testnet-asimov -genlayer network testnet-bradbury -``` - -### Network Switching Workflow - -```bash -# Development workflow -genlayer network localnet -genlayer deploy --contract contracts/my_contract.py - -# Testing workflow -genlayer network studionet -genlayer deploy --contract contracts/my_contract.py - -# Pre-production workflow -genlayer network testnet-bradbury +genlayer network set testnet-bradbury genlayer deploy --contract contracts/my_contract.py ``` -## Network Configuration File +Get test GEN from the [testnet faucet](https://testnet-faucet.genlayer.foundation/). -The CLI stores network configuration in `~/.genlayer/genlayer-config.json`: +## Custom deployment profiles -```json -{ - "network": { - "id": 61999, - "name": "Genlayer Localnet", - "rpcUrls": { - ... - } - } -} -``` +For a deployment that is not bundled with the CLI, create a named profile from a built-in base and override its deployment data. The base supplies the compatible client behavior; explicit overrides identify the actual chain. -### Manual Configuration +```bash +genlayer network add my-preview \ + --base studionet \ + --rpc https://example.net/api \ + --chain-id 62001 \ + --deployment ./deployments.json \ + --explorer https://explorer.example.net + +genlayer network set my-preview +genlayer network info +``` -If needed, you can edit the configuration file directly: +The CLI also accepts explicit consensus-address overrides. Run `genlayer network info` before funding or signing to verify the effective RPC, chain ID, explorer, and contract addresses. -```bash -# Open config file -code ~/.genlayer/genlayer-config.json +## Recommended workflow -# Or edit with any editor -nano ~/.genlayer/genlayer-config.json -``` +1. Develop and profile contract behavior on Localnet. +2. Use stable Studionet for quick hosted collaboration. +3. Use Studio-dev only when the application must validate the release-candidate stack. +4. Deploy to Bradbury for persistent, production-like testnet validation. -## Next Steps +## Next steps -- Start deploying with [CLI Deployment](./cli-deployment) -- Set up automated deployments with [Deploy Scripts](./deploy-scripts) \ No newline at end of file +- [CLI Deployment](./cli-deployment) +- [Deploy Scripts](./deploy-scripts) +- [GenLayer Studio](../tools/genlayer-studio) +- [Fee Profiling and Estimation](/developers/decentralized-applications/fee-profiling-and-estimation) diff --git a/pages/developers/intelligent-contracts/features/value-transfers.mdx b/pages/developers/intelligent-contracts/features/value-transfers.mdx index 7229b04e..ac370663 100644 --- a/pages/developers/intelligent-contracts/features/value-transfers.mdx +++ b/pages/developers/intelligent-contracts/features/value-transfers.mdx @@ -157,6 +157,7 @@ graph TD |---|---| | Studio (Localnet) | Built-in faucet — 💧 button in the account selector | | Studionet | Built-in faucet — 💧 button in the account selector | +| Studio development preview | Built-in faucet — 💧 button in the account selector | | Testnet Asimov | [testnet-faucet.genlayer.foundation](https://testnet-faucet.genlayer.foundation/) | | Testnet Bradbury | [testnet-faucet.genlayer.foundation](https://testnet-faucet.genlayer.foundation/) | @@ -164,7 +165,7 @@ See [Networks](/developers/networks) for full details on each environment. ## Calling Payable Methods from JavaScript -Use the `value` parameter in `writeContract()` to send GEN: +Use the `value` parameter in `writeContract()` to send GEN. On a fee-charging deployment, estimate and submit the protocol fee separately: ```typescript import { createClient, createAccount } from 'genlayer-js'; @@ -175,10 +176,21 @@ const client = createClient({ account: createAccount(), }); -const txHash = await client.writeContract({ +const write = { address: contractAddress, functionName: 'tip', args: [], - value: BigInt(5) * BigInt(10 ** 18), // 5 GEN in wei + value: 5n * 10n ** 18n, // 5 GEN sent to the contract +}; +const estimate = await client.estimateTransactionFeesForWrite(write); + +const txId = await client.writeContract({ + ...write, + fees: { + distribution: estimate.distribution, + feeValue: estimate.feeValue, + }, }); ``` + +The wallet must cover `value + feeValue`. See [Writing to Intelligent Contracts](/developers/decentralized-applications/writing-data) for lifecycle and duplicate-submission guidance. diff --git a/pages/developers/intelligent-contracts/tooling-setup.mdx b/pages/developers/intelligent-contracts/tooling-setup.mdx index 02dfe934..96f38faa 100644 --- a/pages/developers/intelligent-contracts/tooling-setup.mdx +++ b/pages/developers/intelligent-contracts/tooling-setup.mdx @@ -374,10 +374,11 @@ For building a frontend from scratch: npm install genlayer-js ``` -```javascript -import { createClient } from 'genlayer-js'; +```typescript +import { createAccount, createClient } from 'genlayer-js'; +import { localnet } from 'genlayer-js/chains'; -const client = createClient({ endpoint: 'http://localhost:4000/api' }); +const client = createClient({ chain: localnet, account: createAccount() }); // Read from a contract const value = await client.readContract({ @@ -386,14 +387,21 @@ const value = await client.readContract({ args: [], }); -// Write to a contract -const txHash = await client.writeContract({ - address: contractAddress, - functionName: "set_data", - args: [newValue], -}); -const receipt = await client.waitForTransactionReceipt({ - hash: txHash, - status: "FINALIZED", +// Estimate, then write to a fee-charging deployment +const write = { + address: contractAddress, + functionName: 'set_data', + args: [newValue], +}; +const estimate = await client.estimateTransactionFeesForWrite(write); +const txId = await client.writeContract({ + ...write, + fees: { + distribution: estimate.distribution, + feeValue: estimate.feeValue, + }, }); +const transaction = await client.waitForFinalization({ hash: txId }); ``` + +For production application flows, use a measured fee profile instead of simulating every click. See [Writing to Intelligent Contracts](/developers/decentralized-applications/writing-data) and [Fee Profiling and Estimation](/developers/decentralized-applications/fee-profiling-and-estimation). diff --git a/pages/developers/intelligent-contracts/tools/genlayer-studio.mdx b/pages/developers/intelligent-contracts/tools/genlayer-studio.mdx index ab0d3993..f68b27b7 100644 --- a/pages/developers/intelligent-contracts/tools/genlayer-studio.mdx +++ b/pages/developers/intelligent-contracts/tools/genlayer-studio.mdx @@ -1,14 +1,28 @@ --- -description: "GenLayer Studio is a browser-based sandbox for developing and testing Intelligent Contracts in a local replica of the GenLayer network." +description: "Use hosted or local GenLayer Studio to develop, deploy, and inspect Intelligent Contracts." --- import { Card, Cards, Callout, Bleed } from 'nextra-theme-docs' # GenLayer Studio -GenLayer Studio is an interactive sandbox where developers explore the potential of GenLayer's Intelligent Contracts. It replicates the GenLayer network's execution environment and consensus algorithm, but offers a controlled and local environment to test different ideas and behaviors. +GenLayer Studio is an interactive development environment for writing, deploying, and inspecting Intelligent Contracts. It combines a contract editor with accounts, a faucet, validator controls, transaction timelines, state inspection, and node logs. -### What you can do with the GenLayer Studio: +## Choose a Studio environment + +| Environment | Use it for | Web app | Chain ID | +|:--|:--|:--|:--| +| **Studionet** | Stable hosted development | [studio.genlayer.com](https://studio.genlayer.com) | 61999 | +| **Studio development preview** | Release-candidate validation against the next Studio and consensus stack | [studio-dev.genlayer.com](https://studio-dev.genlayer.com) | 61997 | +| **Local Studio** | Full local control, deterministic resets, custom validators and providers | `http://localhost:8080` | 61127 by default | + + +The development preview can be reset or redeployed without preserving state. Use the matching release-candidate SDK or CLI and the explicit Studio-dev network definition. The stable `studionet` preset points to a different chain and must not be reused for the preview. + + +If `studio-next.genlayer.com` is enabled as a second browser entry point for the same preview deployment, it is an alias for people, not a second SDK network. Programmatic clients should keep using the canonical `https://studio-dev.genlayer.com/api` endpoint. A genuinely independent canary would need its own chain ID, deployment, secrets, state, and network preset. + +## What you can do with Studio - **Experiment with Intelligent Contracts:** Intelligent Contracts leverage LLMs, such as GPT-4 or Llama3, to understand natural language and be capable of complex decision making. @@ -16,7 +30,7 @@ GenLayer Studio is an interactive sandbox where developers explore the potential - **Code in Python:** Develop in a familiar, developer-friendly language, where memory and string management are not a big headache. -### Explore the Studio +## Explore Studio Here is a video introduction to help you get started with the GenLayer Studio: @@ -31,8 +45,8 @@ Here is a video introduction to help you get started with the GenLayer Studio: -### Intelligent Contract SDK +## Intelligent Contract SDK - Learn how to write your own Intelligent Contracts at [Intelligent Contracts](/build-with-genlayer/intelligent-contracts) + Learn how to write your own [Intelligent Contracts](/developers/intelligent-contracts/introduction), or review all RPC and wallet details on the [Networks](/developers/networks) page. diff --git a/pages/developers/intelligent-contracts/tools/genlayer-studio/limitations.mdx b/pages/developers/intelligent-contracts/tools/genlayer-studio/limitations.mdx index 07d1e45b..e4da6e15 100644 --- a/pages/developers/intelligent-contracts/tools/genlayer-studio/limitations.mdx +++ b/pages/developers/intelligent-contracts/tools/genlayer-studio/limitations.mdx @@ -8,7 +8,7 @@ GenLayer Studio limitations are Studio-specific caveats for building and testing ## Gas Usage -Studio is gasless for EVM-compatible wallet flows. `eth_gasPrice` returns `0`, `eth_estimateGas` returns a compatibility estimate, and transaction receipts should not be used to benchmark live-network gas usage or fees. +Studio's EVM-compatible wallet layer is gasless: `eth_gasPrice` returns `0`, and `eth_estimateGas` returns a compatibility estimate. Consensus v0.6 protocol fees are a separate layer and can still be enabled for Intelligent Contract transactions. Read those through the GenLayer fee estimate and finalized receipt; do not use `eth_gasPrice` or the outer EVM receipt to decide that a Studio deployment has no protocol fees. ## Chain-Layer and EVM Parity diff --git a/pages/developers/networks.mdx b/pages/developers/networks.mdx index 56723d4c..e8d468da 100644 --- a/pages/developers/networks.mdx +++ b/pages/developers/networks.mdx @@ -63,7 +63,28 @@ Hosted development environment — no local setup required. | **Explorer** | [explorer-studio.genlayer.com](https://explorer-studio.genlayer.com) | | **Faucet** | Built-in — use the 💧 button in the account selector | - + + +--- + +## Studio development preview + +Release-candidate environment for testing the next Studio and consensus stack before it is promoted to Studionet. State and availability are not guaranteed across deployments. + +| Setting | Value | +|:--|:--| +| **Studio web app** | [studio-dev.genlayer.com](https://studio-dev.genlayer.com) | +| **Canonical GenLayer RPC** | `https://studio-dev.genlayer.com/api` | +| **Chain ID** | 61997 | +| **Currency** | GEN | +| **Explorer** | [explorer-studio-dev.genlayer.com](https://explorer-studio-dev.genlayer.com) | +| **Faucet** | Built-in — use the 💧 button in the account selector | + + + + +Use the matching release-candidate SDK or CLI and its explicit `studioDevnet` / `studio-dev` network preset. Do not relabel the stable `studionet` preset: the environments have different chain IDs and deployments. If `studio-next.genlayer.com` is enabled as a browser alias for this same environment, integrations should still use the canonical `studio-dev.genlayer.com/api` RPC. + --- @@ -98,16 +119,17 @@ The underlying zkSync Elastic Chain that GenLayer runs on. You typically don't n ## Network Comparison -| | Bradbury | Asimov | Studionet | Localnet | -|---|---|---|---|---| -| **Purpose** | Production-like testing | Infrastructure testing | Hosted dev | Local dev | -| **Setup** | None — connect and go | None — connect and go | None — browser only | Docker or GLSim | -| **Persistence** | Persistent | Persistent | Temporary | Local only | -| **LLM execution** | Real models | Real models | Real models | Configurable | -| **Faucet** | [Available](https://testnet-faucet.genlayer.foundation) | [Available](https://testnet-faucet.genlayer.foundation) | Built-in (💧 button) | Built-in (💧 button) | +| | Bradbury | Asimov | Studionet | Studio dev | Localnet | +|---|---|---|---|---|---| +| **Purpose** | Production-like testing | Infrastructure testing | Hosted stable dev | Next-stack RC validation | Local dev | +| **Setup** | None | None | None | Matching RC tooling | Docker or GLSim | +| **Persistence** | Persistent | Persistent | Temporary | Temporary; resets expected | Local only | +| **LLM execution** | Real models | Real models | Real models | Real models | Configurable | +| **Faucet** | [Available](https://testnet-faucet.genlayer.foundation) | [Available](https://testnet-faucet.genlayer.foundation) | Built-in | Built-in | Built-in | ## Recommended Flow 1. **Start on Studionet** — zero setup, open [studio.genlayer.com](https://studio.genlayer.com) -2. **Move to Localnet** — when you need full control and fast iteration -3. **Deploy to Bradbury** — when ready for production-like testing with real AI workloads +2. **Use Studio dev only for release-candidate validation** — open [studio-dev.genlayer.com](https://studio-dev.genlayer.com) with the matching RC tooling +3. **Move to Localnet** — when you need full control and fast iteration +4. **Deploy to Bradbury** — when ready for production-like testing with real AI workloads diff --git a/pages/understand-genlayer-protocol/core-concepts/economic-model.mdx b/pages/understand-genlayer-protocol/core-concepts/economic-model.mdx index 2efa2697..fef3191e 100644 --- a/pages/understand-genlayer-protocol/core-concepts/economic-model.mdx +++ b/pages/understand-genlayer-protocol/core-concepts/economic-model.mdx @@ -20,7 +20,7 @@ The budget can cover: The sender also sets price ceilings. Prices lock when the transaction activates so a later governance change cannot silently charge more than the sender authorized. Unused budget is refunded according to the protocol's contributor accounting. -Appeal bonds are separate from the primary transaction budget. The bond funds the additional committee or proposal round and is returned or forfeited according to the appeal result. +Appeal bonds are separate economic stake. The authoritative appeal charge contains both the bond and the induced-work funding needed by the new round. The work funding pays validators and execution costs; the bond is returned with profit or forfeited according to the appeal result. ## Validator selection and stake @@ -41,18 +41,30 @@ These are governance or deployment parameters, not constants applications should ## Reward sources and routing -Rewards come from transaction fees and protocol inflation. Under the current distribution, the combined pool is routed as follows: +Rewards come from transaction fees and protocol inflation, but the two sources do **not** use one common percentage split. -| Recipient | Share | +For the time-unit fee pool, the v0.6 defaults are: + +| Recipient | Share of gross time-unit fees | +| --- | ---: | +| Validator/staking distribution | 85% | +| Intelligent Contract developer | 10% | +| DeepThought DAO treasury | 5% | + +The developer and DAO shares are an overlay on the time-unit work pool. Storage, receipt, and message-fee buckets are not included in that 10%/5% split. Developer fees accrue only when the recipient contract is linked to a Developer NFT; otherwise the unused developer reserve is refunded. + +Protocol inflation uses a different default allocation: + +| Recipient | Share of inflation | | --- | ---: | | Stake pools, including self-stake and delegation | 75% | -| Validator owners for operations | 10% | +| Validator owners for operations | 15% | | Intelligent Contract developers | 10% | -| DeepThought DAO treasury | 5% | +| DeepThought DAO treasury | 0% | Stake-pool rewards are assigned according to selection weight, then shared between a validator's owner and delegators according to their stake in that pool. Stake uses share accounting, so rewards compound by increasing the GEN value represented by each share. -Protocol inflation starts from a configured bootstrap rate and declines toward a configured floor. Treat current rates and splits as protocol parameters when presenting estimates. +Protocol inflation starts from a configured bootstrap rate and declines toward a configured floor. Fee and inflation parameters are governance or deployment values; applications should read or quote them rather than hardcode them. ## Negative incentives diff --git a/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/_meta.json b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/_meta.json index d757be54..23f36f97 100644 --- a/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/_meta.json +++ b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/_meta.json @@ -1,6 +1,8 @@ { "equivalence-principle": "Equivalence Principle", "appeal-process": "Appeals", + "deterministic-violations-and-tribunals": "Deterministic Violations & Tribunals", + "protocol-randomness": "Protocol Randomness", "finality": "Finality", "staking": "Staking", "slashing": "Slashing", diff --git a/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/appeal-process.mdx b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/appeal-process.mdx index 2586afd6..c58a5f9b 100644 --- a/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/appeal-process.mdx +++ b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/appeal-process.mdx @@ -2,9 +2,11 @@ description: "Learn how validator appeals and leader appeals challenge GenLayer consensus decisions." --- +import { Callout } from 'nextra-theme-docs' + # Appeals -An appeal challenges a decided Intelligent Contract transaction before finalization. Anyone can appeal an eligible transaction during its appeal window by posting the bond quoted by the consensus contracts. +An appeal challenges a decided Intelligent Contract transaction before finalization. Anyone can appeal an eligible transaction during its appeal window by paying the exact appeal charge quoted by the consensus contracts. GenLayer has two appeal paths because a disputed committee decision and a round that failed to produce a decision require different remedies. @@ -32,20 +34,41 @@ The new round's outcome determines whether the appeal was economically successfu Normal execution and validator-appeal committees follow interleaved growth schedules. The first normal round currently uses 5 validators, its validator appeal uses 7 fresh validators, and the next expanded normal round uses 11. Later rounds continue growing. These values are protocol parameters and can change through a protocol upgrade. -## Bonds and incentives +## Appeal charge and safe submission + +The authoritative `getAppealCharge` quote has two parts: + +- **bond** — the amount at risk, which discourages frivolous challenges; and +- **funding** — the induced-work reserve needed to schedule the next appeal or execution round. + +The payable charge is at least `bond + funding`. Clients should read the current quote immediately before submitting instead of hardcoding either component. + +The public SDK operations are `appealTransaction` in JavaScript and `appeal_transaction` in Python. The CLI equivalent is `genlayer appeal`. These operations bind the appeal to the transaction's active decision and use the schedule-extending `topUpAndSubmitAppeal` selector, so a caller can fund and start an otherwise unfunded next round atomically. -The appeal bond covers the additional validation work and discourages frivolous challenges. The consensus contracts calculate the required amount from the appeal type and round size. +```typescript +const charge = await client.getAppealCharge({ txId }); +await client.appealTransaction({ txId, value: charge }); +``` -- A successful appellant recovers the bond and receives the configured reward. +```python +charge = client.get_appeal_charge(tx_id) +client.appeal_transaction(tx_id, value=charge) +``` + + +`submitAppeal` is a low-level conformance surface for a next round that is already funded. It can revert with `AppealRoundNotPermitted` when an ordinary caller has only paid the newly quoted charge. Use the public SDK or CLI appeal operation unless you are deliberately testing that pre-funded path. + + +## Bonds and incentives + +- A successful appellant receives the bond principal plus a profit equal to **1.5× the bond**, for **2.5× the bond in total**. The separate induced-work funding is consumed by the scheduled consensus work rather than multiplied as appellant profit. - A failed validator appeal forfeits its bond to the majority-aligned validators in that appeal round. - Leader-appeal bond handling depends on the prior status and the result of the new round. -Clients should request the current bond quote instead of hardcoding an amount. - ## Appeal windows and dependent transactions The appeal-window duration and its reduction after an unsuccessful validator appeal are governance-managed parameters. The window pauses while a validator appeal is voting. A successful appeal gives the recomputed transaction a fresh window. Because transactions for one Intelligent Contract depend on earlier state, a successful appeal can return later non-finalized transactions in that contract's queues for recomputation. Appeals on those dependent transactions are canceled and their bonds refunded when necessary. -Deterministic-violation tribunals are separate. They decide penalties for provable execution fraud but do not change the transaction outcome. See [slashing](/understand-genlayer-protocol/core-concepts/optimistic-democracy/slashing). +Deterministic-violation tribunals are separate. They decide judicial restrictions and penalties for recorded deterministic-result disagreement but do not change the transaction outcome. See [Deterministic Violations & Tribunals](/understand-genlayer-protocol/core-concepts/optimistic-democracy/deterministic-violations-and-tribunals). diff --git a/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/deterministic-violations-and-tribunals.mdx b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/deterministic-violations-and-tribunals.mdx new file mode 100644 index 00000000..9ee55a0f --- /dev/null +++ b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/deterministic-violations-and-tribunals.mdx @@ -0,0 +1,85 @@ +--- +description: "How Consensus v0.6 records deterministic disagreement, freezes a tribunal electorate, and applies judicial restrictions and slashing." +--- + +import { Callout } from 'nextra-theme-docs' + +# Deterministic Violations & Tribunals + +GenLayer permits legitimate differences in non-deterministic work, but deterministic execution should produce the same result hash for every validator. When a consensus round classifies its result as a deterministic violation, the protocol opens a **tribunal** to decide whether the accused leader's result should be upheld or rejected. + +A tribunal is automatic and protocol-funded. It is not a user appeal, does not require an appellant bond, and does not replace the transaction's normal progress. + +## What the Contracts Prove + +Validators compute execution results off-chain and commit and reveal their votes and result hashes. The contracts compare those recorded hashes with the accused leader's result hash. + + +“Deterministic violation” means a recorded deterministic-result disagreement. The EVM contracts do not re-execute the intelligent contract to prove which program output is semantically correct. + + +The tribunal runs in parallel with the transaction. It determines judicial and economic consequences for validator identities; it does not rewrite the transaction outcome that triggered it. + +## Creation-Frozen Electorate + +Tribunal creation pins the transaction, accused leader, relevant round, committee and vote generations, creation epoch, and the staking authority used to construct the electorate. The complete electorate can be materialized in bounded, permissionless pages so a large validator set does not have to be scanned in the transaction that detected the violation. + +The resulting participation target is `quorumSnapshot`, fixed at creation. The accused leader cannot vote. Votes from the original jury are sealed into the tribunal as evidence, and those jurors cannot cast a second tribunal vote. Other validators may participate only if both conditions hold: + +- their registry identity was included in the creation-frozen electorate; and +- they remain live and available when they attempt to vote. + +A validator that becomes eligible only after creation cannot join this tribunal. Conversely, later exits or stake changes do not rewrite who was eligible at creation for abstention accounting. A validator that becomes barred before finalization is recorded separately so a protocol-enforced inability to reveal is not misclassified as a voluntary abstention. + +## Commit, Reveal, and Verdict + +After initialization completes, the tribunal uses commit and reveal windows. Original-jury votes count from their sealed round evidence; newly admitted tribunal voters commit and then reveal an `Agree` or `Disagree` vote relative to the accused leader's result. + +The verdict is calculated against the frozen participation target: + +- **Majority disagree** — strictly more than half of the frozen quorum rejects the leader's result. +- **Majority agree** — strictly more than half supports the leader's result. +- **No majority** — neither side reaches that strict threshold. + +The tribunal may advance early when its frozen quorum has revealed, or advance after the configured commit and reveal deadlines. Live validator-count movement does not dilute or enlarge the saved quorum. + +## Judicial Consequences + +Opening a tribunal immediately creates a judicial restriction for the accused leader. The staking layer preserves the exact capacity disposition so acquittal can restore only what that tribunal actually removed. + +- On **MajorityDisagree**, the protocol calls `convictJudicially`. The leader becomes non-restorable through the ordinary judicial-settlement path, and the permanent restriction remains until an authorized governance pardon clears it. +- On **MajorityAgree**, the tribunal releases its judicial cause. The leader is restored only when no other open or convicted tribunal cause still applies. +- On **NoMajority**, no tribunal slash is created and the non-convicting judicial cause is released through settlement. + +Verdict commitment is final even if the remaining consequence work cannot fit in one transaction. Permissionless, retryable settlement pages finish the bounded electorate and quarantine bookkeeping, while the staking slash cursor waits rather than retiring an incomplete tribunal. + +## Slashing Rules + +The v0.6 defaults are 500 basis points (5%) for the convicted leader and 100 basis points (1%) for a validator fault, subject to the configured maximum slash per validator and epoch. Tribunal slashes are enacted after the configured epoch delay. + +For **MajorityDisagree**: + +- the convicted leader is charged once for the transaction/round/leader identity, even if multiple accusation hashes produce separate tribunals; +- original jurors and tribunal voters recorded as agreeing with the wrong leader are charged; +- eligible abstainers can be charged; and +- an original juror's missing vote is not charged twice when idleness already charged the same omission for that transaction. + +For **MajorityAgree**, only recorded `Disagree` votes against the vindicated leader are charged. Non-members and ordinary abstainers are not turned into accusers. **NoMajority** creates no tribunal slash. + +## Tribunal vs. Appeal + +| | Tribunal | User appeal | +|:--|:--|:--| +| Trigger | Automatic deterministic-violation result | Permissionless challenge to a finalized decision | +| Funding | Protocol consensus flow | Caller pays the quoted bond and induced-work funding | +| Question | Did validators agree or disagree with the accused leader's deterministic result? | Does the next round change the challenged application outcome? | +| Effect | Judicial restriction and validator slashing | Additional appeal and execution rounds, with appellant reward or bond redistribution | +| Transaction outcome | Runs in parallel; does not rewrite it | Can change the finalized decision class | + +## Related Concepts + +- [Appeals Process](/understand-genlayer-protocol/core-concepts/optimistic-democracy/appeal-process) +- [Slashing](/understand-genlayer-protocol/core-concepts/optimistic-democracy/slashing) +- [Staking](/understand-genlayer-protocol/core-concepts/optimistic-democracy/staking) +- [Protocol Randomness](/understand-genlayer-protocol/core-concepts/optimistic-democracy/protocol-randomness) +- [Equivalence Principle](/understand-genlayer-protocol/core-concepts/optimistic-democracy/equivalence-principle) diff --git a/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/protocol-randomness.mdx b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/protocol-randomness.mdx new file mode 100644 index 00000000..f00e3a1a --- /dev/null +++ b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/protocol-randomness.mdx @@ -0,0 +1,76 @@ +--- +description: "How Consensus v0.6 advances per-recipient randomness with ECVRF proofs and uses it for validator selection." +--- + +import { Callout } from 'nextra-theme-docs' + +# Protocol Randomness + +GenLayer uses an evolving random seed when it selects transaction activators, leaders, and validator committees. In Consensus v0.6, each recipient contract has its own seed chain, and ordinary seed advances are backed by an **elliptic-curve verifiable random function (ECVRF)** proof. + +This page describes the deployed contract behavior. It deliberately separates the cryptographic seed advance from the selection policy that consumes the seed. + +## ECVRF Seed Advances + +For a recipient address, `ConsensusManager` stores `recipientRandomSeed[recipient]`. The input for its next advance is: + +```text +keccak256("GenLayer/ConsensusManager/seed-advance/v1" || current recipient seed) +``` + +The validator role responsible for the protocol action supplies an ECVRF proof and its registered operator public key. The contract: + +1. decodes the proof; +2. checks that the proof's public key is the supplied registered operator key; +3. verifies the proof against the domain-separated input; and +4. stores the ECVRF output as the recipient's next seed. + +An ECVRF output is unique for a given key and input. A validator therefore cannot try many valid proofs for the same seed and choose the output that gives it a favorable committee. Its remaining choice is whether to publish the one valid proof or withhold it and enter the protocol's timeout path. + + +The v0.6 seed advance is not an ordinary ECDSA-signature hash chain. Integrations that construct validator actions must produce the ECVRF proof expected by the validator-wallet and consensus interfaces. + + +## Per-Recipient Seed Chains + +Seeds are isolated by recipient contract rather than drawn from one global mutable value. Once a recipient seed has been initialized, the normal consensus path advances it through the ECVRF operation instead of resetting it. + +The domain tag prevents a proof made for seed advancement from being reused as a proof for another purpose under the same key. Including the current stored seed also makes old proofs invalid after the chain advances. + +The transaction records the random seed and the selection authority needed by its consensus flow. Later work on that transaction therefore uses its pinned state instead of silently following an unrelated, newer recipient seed. + +## From a Seed to a Committee + +Randomness supplies the unpredictable input; staking supplies the eligible population and its epoch-effective selection weights. Consensus derives successive draws from the transaction seed and selection context while excluding identities that are unavailable or already consumed by that selection. + +This distinction matters: + +- the ECVRF proves the seed advance and prevents proof grinding; +- staking determines who is eligible and how selection weight is calculated; and +- the transaction snapshot prevents membership and policy changes from rewriting an in-flight selection. + +Appeal committees can be much larger than an ordinary round. Their selection may therefore be prepared and completed through bounded, permissionless steps, but it remains tied to the transaction's pinned randomness and selection authority. + +## Guarantees and Limits + +The v0.6 design provides these practical properties: + +- **Unique proof output.** For one registered key and one seed-advance input, there is only one valid ECVRF output. +- **Public verification.** The contract verifies both the proof and the operator-key binding. +- **Purpose separation.** The domain tag binds the proof to GenLayer seed advancement. +- **Replay resistance.** Advancing the stored seed changes the next proof input. +- **Per-recipient isolation.** Activity on one contract does not directly advance another contract's seed chain. + +It does not turn the whole selection system into a threshold random beacon. In particular: + +- the responsible validator can publish or withhold its unique output, with withholding handled as a liveness failure; and +- a recipient's first seed must be initialized from transaction-creation entropy before ECVRF advances take over. + +Those limits are different from proof grinding: withholding can delay progress, but it does not let the validator choose among multiple valid outputs for the same key and input. + +## Related Concepts + +- [Validators and Validator Roles](/understand-genlayer-protocol/core-concepts/validators-and-validator-roles) +- [Staking](/understand-genlayer-protocol/core-concepts/optimistic-democracy/staking) +- [Appeals Process](/understand-genlayer-protocol/core-concepts/optimistic-democracy/appeal-process) +- [Deterministic Violations & Tribunals](/understand-genlayer-protocol/core-concepts/optimistic-democracy/deterministic-violations-and-tribunals) diff --git a/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/slashing.mdx b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/slashing.mdx index 4482fa99..50480937 100644 --- a/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/slashing.mdx +++ b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/slashing.mdx @@ -14,13 +14,15 @@ GenLayer uses several penalties to protect liveness and punish provable protocol | Repeated idleness or failure to reveal | Add epoch strikes and ban the validator after the configured threshold. | | Failure to reveal a committed vote | Record a percentage-based stake slash and a strike. | | Vote that loses after an appeal | Forfeit the applicable reward or receive a negative fee adjustment; this alone is not a deterministic-violation slash. | -| Provable deterministic execution violation | Quarantine the accused validator and open a tribunal that can impose a larger slash or clear the quarantine. | +| Recorded deterministic-result violation | Apply a judicial selection restriction and open a tribunal that can convict and slash the accused leader or release the restriction. | ## Bans and quarantine A **ban** temporarily excludes a validator after it accumulates the configured number of idleness strikes. Current defaults use three strikes in an epoch and exclude the validator for the current and following epoch. -A **quarantine** immediately excludes a validator selected for a deterministic-violation tribunal. The tribunal uses the active validator network to decide punishment. It runs separately from the transaction's outcome: the transaction continues through rotation or its ordinary appeal path. +A deterministic-violation tribunal immediately applies a **judicial selection restriction** to the accused leader. The tribunal's electorate and participation target are frozen from creation-time validator state; later validators cannot join merely because they become active, and later live-set movement cannot change the saved quorum. The tribunal runs separately from the transaction's outcome: the transaction continues through rotation or its ordinary appeal path. + +Other protocol paths still use time-bounded **quarantine** records. Both quarantine and judicial restrictions prevent new selection, but they have different creation and release rules. See [Deterministic Violations & Tribunals](./deterministic-violations-and-tribunals) for the v0.6 judicial lifecycle. ## Current slash parameters diff --git a/pages/validators/_meta.json b/pages/validators/_meta.json index d1fda8ca..f04164cd 100644 --- a/pages/validators/_meta.json +++ b/pages/validators/_meta.json @@ -1,6 +1,7 @@ { "setup-guide": "Setup Guide", "monitoring": "Monitoring & Telemetry", + "network-keeper-roles": "Network Keeper Roles", "system-requirements": "System Requirements", "genvm-configuration": "GenVM Configuration", "upgrade": "Upgrade Guide", diff --git a/pages/validators/network-keeper-roles.mdx b/pages/validators/network-keeper-roles.mdx new file mode 100644 index 00000000..1c6be363 --- /dev/null +++ b/pages/validators/network-keeper-roles.mdx @@ -0,0 +1,210 @@ +--- +description: "Network keeper roles are the permissionless GenLayer functions anyone can call to keep the network live — inflation relay, epoch advancement, validator priming, appeals, finalization, and cleanup — with their triggers, rewards, and risks." +--- +import { Callout } from "nextra-theme-docs"; + +# Network Keeper Roles + +A **keeper role** is a permissionless smart-contract function that the GenLayer network needs someone to call in order to keep running — advancing epochs, relaying inflation across the L1↔L2 bridge, priming validators, finalizing transactions, and cleaning up expired state. None of these functions require special privileges: **anyone can call them**, from a validator operator to an independent community bot. + +Today the GenLayer Foundation operates a baseline set of these keepers so the network stays live without any community involvement. But because the functions are permissionless, community keepers can run them too — either as a backstop when Foundation services lag, or to earn the rewards attached to some roles. Running keepers is a way to contribute to network liveness and, where a reward exists, to be paid for it. + + + Most keeper work is already automated. Validator nodes finalize transactions and prime validators as part of normal operation, and epoch advancement plus token burning fire automatically inside `epochAdvance()`. The roles below matter most as a **liveness backstop**: they document what can be triggered manually if the automated path stalls, and which triggers carry a reward. + + +## Role Summary + +| Role | Function | Who may call | Reward | Risk if idle | +|---|---|---|---|---| +| **Inflation relayer** | `GEN.executeL2Message()` (L1) | Anyone (permissionless) | `incentivePercentage` × epoch inflation, minted to caller. | **Epoch progression halts network-wide.** L2 cannot advance past `inflationEpoch`. | +| **Epoch advancer** | `Staking.epochAdvance()` (L2) | Anyone (permissionless) | None | Epochs stop advancing; fees/inflation are not distributed. | +| **Validator primer** | `Staking.validatorPrime(validator)` (L2) | Anyone, on behalf of a validator | 1% of any slash that fires (only when a slash is pending and caller ≠ validator). Healthy validator → none. | Validator misses selection; pending slashes go unenforced. | +| **Appellant** | SDK/CLI appeal operation (`topUpAndSubmitAppeal`) | Anyone (permissionless, payable) | Successful appeal returns 2.5× the bond: principal plus 1.5× profit. | Incorrect results are not challenged. | +| **Finalizer** | SDK/CLI finalization operation (`finalizeTransaction(txId, expectedDecisionId)` on-chain) | Anyone (permissionless) | None | Transactions do not settle; fees and appeal payouts are not released. | +| **Stuck-tx healer** | `advanceStuckTransaction(txId)` | Anyone (permissionless) | None | A transaction whose stored status lags its computed status stays stuck. | +| **Burn trigger** | `StakingInflation.burn()` (L2) | Anyone (permissionless) | None | Burnable GEN accumulates instead of leaving supply (only if below the auto-burn threshold). | +| **Quarantine GC** | `StakingBan.cleanupExpiredQuarantines()` (L2) | Anyone (permissionless) | None | Expired quarantine records are not reclaimed (storage bloat only). | + + + **Scope.** This page covers only permissionless keeper functions. Governance-gated actions — unbanning validators (`QUARANTINE_MANAGER_ROLE`), admin/emergency ("red-button") surfaces — are **not** keeper roles and are restricted to authorized operators. + + +--- + +## Inflation Relayer + +**Function:** `GEN.executeL2Message(batchNumber, txIndex, batchIndex, message, proof, l2GasPrice, l2GasLimit)` on the **L1** GEN token contract. + +Each epoch's inflation makes a round trip: the L2 Staking contract requests it, L1 mints and bridges it back, and L2 receives it. The middle step — proving the L2→L1 message on L1 and relaying it — **cannot happen on-chain**, because zkSync L2→L1 messages can only be proved after the L2 batch has finalized on L1 (roughly an hour on real networks). Someone has to call `executeL2Message()` off-chain to complete the round trip. + + + **This role gates the entire network's epoch progression.** The L2 `Staking.epochAdvance()` can only move forward while `epoch + 1 <= inflationEpoch`, and `inflationEpoch` only rises when `executeL2Message()` is successfully relayed on L1. **If no one relays inflation, epoch progression halts network-wide** once the current epoch catches up to `inflationEpoch`. This is the single most important keeper to keep running. + + +**When to call:** whenever an inflation request message is pending relay. Pre-check any candidate message — and estimate the reward — with the L1 view helper: + +```solidity +// Returns (pending, rewards): whether the message still needs relaying, +// and the estimated GEN reward for relaying it. +(bool pending, uint256 rewards) = GEN.isL2MessagePending( + batchNumber, txIndex, batchIndex, message +); +``` + +**Reward:** on a successful INFLATION relay, the caller is paid + +``` +reward = totalInflation × incentivePercentage / 10000 +``` + +minted to `msg.sender` on L1, emitting `IncentiveRewardPaid(caller, reward)`. The reward is **carved out of** that epoch's inflation, not minted on top — total new supply is `fees + totalInflation` regardless of the incentive. + +**Cadence:** relay each pending inflation message shortly after its L2 batch finalizes on L1 (about hourly on testnet, gated by batch finalization). Relaying is idempotent per target epoch — a duplicate request for an already-requested epoch is a safe no-op — so a keeper that occasionally double-submits does no harm. + +--- + +## Epoch Advancer + +**Function:** `Staking.epochAdvance()` on the L2 Staking contract. + +Advances the network from the current epoch to the next. On each advance it distributes the finalized epoch's fees and inflation to validators, developers, and the DAO, requests the next window of inflation from L1 (see [Inflation Relayer](#inflation-relayer)), and — when the burn threshold is met — auto-triggers [token burning](#burn-trigger). + +**When to call:** once the current epoch's minimum duration has elapsed and its transactions are resolved. In normal operation this is driven automatically; a keeper only needs to step in if advancement stalls. + +**Reward:** none. `epochAdvance()` pays no caller incentive. + +**Cadence:** at most once per epoch boundary. Advancement will not run ahead of the inflation bridge — see the halts-chain warning above — so a stalled `inflationEpoch` is diagnosed at the inflation relayer, not here. + +--- + +## Validator Primer + +**Function:** `Staking.validatorPrime(validator)` on the L2 Staking contract, callable by anyone on behalf of any validator. + +Priming rolls a validator's stake forward for the upcoming epoch — committing staged deposits and withdrawals, distributing rewards, and **lazily enforcing any pending slash** against that validator. A validator's own node primes it automatically each epoch; the permissionless surface exists so that a validator with a pending slash cannot escape enforcement simply by not priming itself. + +**When to call:** to enforce a slash that a validator is avoiding, or as a backstop if a validator's node has stopped priming. Priming is also what activates deposits (see the [Staking Contract Guide](/developers/staking-guide#depositing-as-a-validator)). + +**Reward:** when priming applies a pending slash **and the caller is not the validator being primed**, the caller receives + +``` +callerReward = slashedAmount × 100 / 10000 // SLASH_CALLER_INCENTIVE_BPS = 100 = 1% +``` + +deducted from the epoch's slashed pool before the remainder is burned. Priming a **healthy** validator (no pending slash) pays **nothing** — do not expect a reward for routine priming. + +**Cadence:** validators prime themselves each epoch, so a keeper only needs to target validators that (a) have an enactable pending slash and are not priming, or (b) have stopped priming entirely. Related permissionless storage-cleanup helpers — `validatorClean(...)` and `delegatorClean(...)` — reclaim finalized deposit/withdrawal records and carry no reward. + + + Slashing is time-locked and lazy: a recorded penalty becomes enactable only 2 epochs after the event, and is applied at the next priming. A validator that **exits before being primed** can avoid a pending slash; the caller incentive exists partly to pay third parties to prime-and-slash before that happens. + + +--- + +## Appellant + +**Function:** the public SDK/CLI appeal operation, which calls the payable `ConsensusMain.topUpAndSubmitAppeal()` path. + +Anyone may post a bond to appeal a transaction they believe was decided incorrectly, triggering re-evaluation by a fresh validator set. Unlike the other keeper roles, this one is **economically self-motivated**: a correct appeal is profitable, and a frivolous one forfeits the bond. + +**When to call:** when you have evidence that a finalized-pending transaction's result is wrong and are willing to stake a bond on it. Read the current appeal charge first with the CLI: + +```bash +genlayer appeal-bond # bond + induced-work funding +genlayer appeal --bond # exact charge is calculated if omitted +``` + +Despite its historical command name, `appeal-bond` reports the full appeal charge: the bond plus the funding required for the work the appeal induces. The SDK and CLI bind the operation to the active decision and use `topUpAndSubmitAppeal`; direct `submitAppeal` is only for a low-level, already-funded round. + +**Reward:** a **successful** appeal returns the principal plus profit equal to **1.5× the bond**, or **2.5× the bond in total**, settled on finalization. The induced-work funding pays for the scheduled round and is not multiplied as appellant profit. A failed appeal forfeits the bond for protocol distribution. See [Appeal Process](/understand-genlayer-protocol/core-concepts/optimistic-democracy/appeal-process). + +**Cadence:** event-driven — appeal only when you have specific evidence of an incorrect result, never speculatively. + +--- + +## Finalizer + +**Function:** the SDK/CLI finalization operation, which resolves the active decision and calls `finalizeTransaction(txId, expectedDecisionId)` on the consensus contract (`nonReentrant`). + +Settles a transaction once its acceptance window has passed and no successful appeal is outstanding: it releases fees, pays out any successful appellant, and marks the transaction final. + +**When to call:** after a transaction's acceptance window closes. This is normally driven by validator nodes as part of consensus, so a keeper only needs to step in if finalization is lagging. It can also be invoked from the CLI: + +```bash +genlayer finalize +``` + +**Reward:** none. Finalization pays no caller incentive; its purpose is liveness, not profit. + +**Cadence:** as needed to clear transactions whose windows have elapsed but which have not been finalized by validator nodes. + +--- + +## Stuck-Tx Healer + +**Function:** `advanceStuckTransaction(txId)` on the consensus contract's finalization phase — permissionless by design. + +Some transactions can end up with a **stored status that lags their computed status** — for example, a timeout has effectively occurred but the on-chain record has not caught up. `advanceStuckTransaction()` recomputes the current status and writes the catch-up, unblocking the transaction. It has no queue-head precondition (any stuck transaction can be healed, not just the head of the pending queue), is idempotent (a no-op on any transaction that is not in a pending family), and **does not itself finalize** — it is strictly a liveness improvement, not a privilege. + +**When to call:** when a specific transaction appears wedged — its stored state is behind where its timing says it should be. Safe to call speculatively because non-applicable transactions are no-ops. Emits `AdvanceStuckTransactionAttempted`. + +**Reward:** none. + +**Cadence:** on demand, in response to a stuck transaction. Because it is idempotent, a keeper can retry without side effects. + +--- + +## Burn Trigger + +**Function:** `StakingInflation.burn()` on the L2 Staking contract. + +Removes accumulated burnable GEN (unclaimed inflation, the post-incentive slashed pool, and unclaimable developer-inflation surplus) from supply by bridging it out of L2 and burning it on L1. + +**When to call:** almost never manually. Burning fires **automatically inside `epochAdvance()`** whenever the accumulated `burning` balance meets the configured `burnThreshold`. The manual call is a **fallback** for flushing balances that sit **below** the threshold, or when threshold automation is disabled (`burnThreshold = 0`, the default). + + + Completing a burn on L1 requires two further off-chain operator steps (`finalizeWithdrawal` then a BURN-payload `executeL2Message`) that pair with the L2 emission. Those steps do **not** pay the inflation relayer reward. See the staking system specification for the full burn flow. + + +**Reward:** none. + +**Cadence:** only when clearing sub-threshold balances; otherwise leave it to the automatic path. + +--- + +## Quarantine GC + +**Function:** `StakingBan.cleanupExpiredQuarantines(startIndex, maxIterations)` on the L2 Staking contract. + +Paginated garbage collection that reclaims **expired** quarantine records. It only removes quarantines that have already elapsed — it does **not** unban validators or alter any active quarantine (unbanning is a governance action, out of scope for keepers). + +**When to call:** periodically, to keep quarantine storage from bloating after many quarantine cycles. The `startIndex`/`maxIterations` pagination lets a keeper bound gas per call and sweep the set across several transactions. + +**Reward:** none — this is pure storage hygiene. + +**Cadence:** low-frequency housekeeping; there is no liveness deadline. + +--- + +## Running a Keeper + +**Network endpoints and contract addresses.** Keepers need the L2 Staking / consensus contract address and the L1 GEN token address for the target network. The L2 consensus `AddressManager` address and the chain RPC/WebSocket URLs for each testnet live in the [Network-Specific Consensus Configuration](/validators/setup-guide#network-specific-consensus-configuration) section of the setup guide. From the CLI, `genlayer network info` prints the active network's configuration and contract addresses. + +**Reference implementations.** The consensus repository ships reference scripts for the multi-step relay flows — the inflation relayer's proof-fetch-and-execute logic lives at `scripts/consensus_flows/staking/executeL2Message.ts`, alongside companion scripts for the other staking flows. Use these as a starting point for a production keeper: they show how to fetch the L2 receipt, poll `zks_getL2ToL1LogProof` until the batch is finalized, and submit the L1 call. + +**Practical guidance:** + +- **Prioritize the inflation relayer.** It is the only keeper whose absence halts the whole network, and the only one that pays a standing reward. Everything else is a backstop to already-automated behavior. +- **Check for a reward before assuming one.** Only the inflation relayer (via `incentivePercentage`), the validator primer (only on an actually-enforced slash), and the appellant (only on a successful appeal) ever pay. The rest are unpaid liveness/hygiene work. +- **Expect idempotency.** Inflation relays, stuck-tx healing, and quarantine GC are all safe to retry or double-submit — duplicate or non-applicable calls are no-ops, not errors. +- **Watch gas vs. reward.** For the paid roles, compare the estimated reward (e.g. `isL2MessagePending`'s `rewards`, or the pending slash amount) against the transaction cost before submitting. + +## Related Resources + +- [Setup Guide](/validators/setup-guide) — running a validator node and its network configuration +- [Staking Contract Guide](/developers/staking-guide) — direct Solidity interactions, `validatorPrime()`, and the epoch model +- [Staking Concepts](/understand-genlayer-protocol/core-concepts/optimistic-democracy/staking) — epochs, shares vs. stake, reward distribution +- [Slashing](/understand-genlayer-protocol/core-concepts/optimistic-democracy/slashing) — penalties, the 80/20 split, and lazy enforcement +- [Appeal Process](/understand-genlayer-protocol/core-concepts/optimistic-democracy/appeal-process) — how appeals re-evaluate a transaction +- [GenLayer CLI](/api-references/genlayer-cli) — `appeal`, `appeal-bond`, `finalize`, and `network info` commands diff --git a/scripts/check-v06-release-docs.js b/scripts/check-v06-release-docs.js new file mode 100644 index 00000000..66085715 --- /dev/null +++ b/scripts/check-v06-release-docs.js @@ -0,0 +1,328 @@ +const fs = require("fs"); +const path = require("path"); +const { spawnSync } = require("child_process"); +const ts = require("typescript"); + +const ROOT = process.cwd(); +const failures = []; + +const releaseDocs = [ + "pages/developers/consensus-v06-migration.mdx", + "pages/developers/error-reference.mdx", + "pages/developers/networks.mdx", + "pages/developers/decentralized-applications/architecture-overview.mdx", + "pages/developers/decentralized-applications/dapp-development-workflow.mdx", + "pages/developers/intelligent-contracts/deploying/cli-deployment.mdx", + "pages/developers/intelligent-contracts/deploying/deploy-scripts.mdx", + "pages/developers/intelligent-contracts/deploying/network-configuration.mdx", + "pages/developers/intelligent-contracts/deploying.mdx", + "pages/developers/intelligent-contracts/features/value-transfers.mdx", + "pages/developers/intelligent-contracts/tooling-setup.mdx", + "pages/developers/intelligent-contracts/tools/genlayer-studio.mdx", + "pages/developers/intelligent-contracts/tools/genlayer-studio/limitations.mdx", + "pages/developers/decentralized-applications/developer-nft-rewards.mdx", + "pages/developers/decentralized-applications/fee-outcomes-and-debugging.mdx", + "pages/developers/decentralized-applications/fee-profiling-and-estimation.mdx", + "pages/developers/decentralized-applications/fees-and-transaction-kit.mdx", + "pages/developers/decentralized-applications/transaction-kit-integration.mdx", + "pages/developers/decentralized-applications/querying-a-transaction.mdx", + "pages/developers/decentralized-applications/reading-data.mdx", + "pages/developers/decentralized-applications/writing-data.mdx", + "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/appeal-process.mdx", + "pages/understand-genlayer-protocol/core-concepts/economic-model.mdx", + "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/slashing.mdx", + "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/deterministic-violations-and-tribunals.mdx", + "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/protocol-randomness.mdx", + "pages/validators/network-keeper-roles.mdx", + "pages/api-references/genlayer-js.md", + "pages/api-references/genlayer-py.md", +]; + +function read(relativePath) { + return fs.readFileSync(path.join(ROOT, relativePath), "utf8"); +} + +function requireText(relativePath, text, description = text) { + if (!read(relativePath).includes(text)) { + failures.push(`${relativePath}: missing ${description}`); + } +} + +function forbidText(relativePath, text, description = text) { + if (read(relativePath).toLowerCase().includes(text.toLowerCase())) { + failures.push(`${relativePath}: contains stale ${description}`); + } +} + +const appeal = + "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/appeal-process.mdx"; +requireText(appeal, "topUpAndSubmitAppeal", "schedule-extending appeal selector"); +requireText(appeal, "bond + funding", "complete appeal charge formula"); +requireText(appeal, "2.5× the bond in total", "2.5× successful appeal return"); +requireText(appeal, "1.5× the bond", "1.5× appeal profit"); +requireText(appeal, "AppealRoundNotPermitted", "direct-submit failure mode"); +forbidText(appeal, "principal returned plus a 0.5× profit", "old 1.5× total reward"); + +const keepers = "pages/validators/network-keeper-roles.mdx"; +requireText(keepers, "topUpAndSubmitAppeal", "public keeper appeal path"); +requireText(keepers, "2.5× the bond in total", "keeper appeal total return"); +forbidText(keepers, "ConsensusMain.submitAppeal()", "direct submitAppeal keeper guidance"); + +const randomness = + "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/protocol-randomness.mdx"; +requireText(randomness, "ECVRF", "ECVRF seed advance"); +requireText(randomness, "GenLayer/ConsensusManager/seed-advance/v1", "seed-advance domain"); +requireText(randomness, "only one valid ECVRF output", "unique-output guarantee"); +forbidText(randomness, "not a verifiable random function", "pre-v0.6 non-VRF claim"); +forbidText(randomness, "ordinary ECDSA signature over the seed", "pre-v0.6 ECDSA scheme"); + +const tribunals = + "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/deterministic-violations-and-tribunals.mdx"; +requireText(tribunals, "quorumSnapshot", "creation-frozen tribunal quorum"); +requireText(tribunals, "convictJudicially", "wired judicial conviction"); +requireText(tribunals, "500 basis points", "leader slash default"); +requireText(tribunals, "100 basis points", "validator slash default"); +forbidText(tribunals, "not yet wired", "obsolete permanent-ban gap"); +forbidText(tribunals, "implementation and the design intent diverge", "obsolete quorum gap"); + +const nft = "pages/developers/decentralized-applications/developer-nft-rewards.mdx"; +requireText(nft, "Every later contract", "all deployments linked to the developer NFT"); +requireText(nft, "at most 50 finalized inflation epochs", "bounded claim behavior"); +requireText(nft, "time-unit fee pool", "developer fee source"); +forbidText(nft, "only the first contract you deploy", "first-contract-only rewards claim"); + +const networks = "pages/developers/networks.mdx"; +requireText(networks, "https://studio-dev.genlayer.com/api", "canonical Studio-dev RPC"); +requireText(networks, "61997", "Studio-dev chain ID"); +requireText(networks, 'chainId: "0xf22d"', "Studio-dev hexadecimal chain ID"); +requireText(networks, 'chainId: "0xf22f"', "Studionet hexadecimal chain ID"); +forbidText(networks, 'chainId: "0xf23f"', "incorrect Studionet hexadecimal chain ID"); + +const kit = "pages/developers/decentralized-applications/transaction-kit-integration.mdx"; +requireText(kit, "| `low` | 1 |", "Transaction Kit low preset"); +requireText(kit, "| `standard` | 3 |", "Transaction Kit standard preset"); +requireText(kit, "| `high` | 5 |", "Transaction Kit high preset"); +requireText(kit, "Fee policy changed; re-estimate before signing.", "fee-policy mismatch guard"); +requireText(kit, "known mismatch by default", "fail-closed adapter behavior"); + +const economics = "pages/understand-genlayer-protocol/core-concepts/economic-model.mdx"; +requireText(economics, "| Validator/staking distribution | 85% |", "time-unit validator share"); +requireText(economics, "| Validator owners for operations | 15% |", "inflation validator share"); +requireText(economics, "induced-work funding", "separate appeal work funding"); +forbidText(economics, "combined pool is routed", "collapsed fee/inflation allocation"); +forbidText(economics, "The bond funds the additional", "bond-as-work-funding claim"); + +const studioLimitations = + "pages/developers/intelligent-contracts/tools/genlayer-studio/limitations.mdx"; +requireText(studioLimitations, "protocol fees are a separate layer", "EVM/protocol fee distinction"); + +const feeOverview = "pages/developers/decentralized-applications/fees-and-transaction-kit.mdx"; +requireText(feeOverview, "Storage and receipt ceilings are checked at submission", "cap timing"); +requireText(feeOverview, "GEN-per-time-unit price is also locked at activation", "time-unit lock timing"); +requireText(feeOverview, "user value is transferred separately", "user value and fee-deposit separation"); + +const outcomes = "pages/developers/decentralized-applications/fee-outcomes-and-debugging.mdx"; +requireText(outcomes, "Canceled at activation because `GENPerTimeUnit`", "activation cap cancellation"); +requireText(outcomes, "`MaxPriceExceeded` for the current storage/receipt price", "submission cap rejection"); + +const slashing = + "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/slashing.mdx"; +requireText(slashing, "judicial selection restriction", "v0.6 tribunal restriction"); +requireText(slashing, "electorate and participation target are frozen", "frozen tribunal authority"); +forbidText(slashing, "uses the active validator network", "live tribunal electorate claim"); + +const writing = "pages/developers/decentralized-applications/writing-data.mdx"; +requireText(writing, "estimateTransactionFeesForWrite", "fee-aware write example"); +requireText(writing, "isSuccessful(transaction)", "execution-aware success check"); +requireText(writing, "Do not blindly submit", "duplicate-write warning"); +forbidText(writing, "chain: simulator", "obsolete simulator chain import"); +forbidText(writing, "status: TransactionStatus.FINALIZED", "deprecated status waiter"); + +const querying = "pages/developers/decentralized-applications/querying-a-transaction.mdx"; +requireText(querying, "advanced.getTransactionLifecycle", "advanced lifecycle projection"); +requireText(querying, "waitUntil: 'finalized'", "current generic waiter"); +forbidText(querying, "tx.status === 'pending'", "non-canonical status polling"); + +const reading = "pages/developers/decentralized-applications/reading-data.mdx"; +requireText(reading, "TransactionHashVariant.LATEST_FINAL", "explicit final read snapshot"); +forbidText(reading, "chain: simulator", "obsolete simulator chain import"); + +const deployScripts = + "pages/developers/intelligent-contracts/deploying/deploy-scripts.mdx"; +requireText(deployScripts, "estimateTransactionFees", "fee-aware deploy estimate"); +requireText(deployScripts, "isSuccessful(transaction)", "execution-aware deploy result"); +requireText(deployScripts, "is deprecated on the v2 client", "deprecated initializer warning"); +forbidText(deployScripts, "await client.initializeConsensusSmartContract", "deprecated initializer call"); + +const deploying = "pages/developers/intelligent-contracts/deploying.mdx"; +requireText(deploying, "estimate from a measured fee profile", "fee-aware deployment guidance"); +requireText(deploying, "verify the execution result", "execution-aware deployment outcome"); +requireText(deploying, "genlayer network set studio-dev", "Studio-dev network selection"); +forbidText(deploying, "testnetBradbury", "pre-v0.40 Bradbury alias"); + +const valueTransfers = "pages/developers/intelligent-contracts/features/value-transfers.mdx"; +requireText(valueTransfers, "wallet must cover `value + feeValue`", "separate user value and protocol fee funding"); + +const tooling = "pages/developers/intelligent-contracts/tooling-setup.mdx"; +requireText(tooling, "estimateTransactionFeesForWrite", "fee-aware tooling write"); +requireText(tooling, "waitForFinalization", "current finalization waiter"); + +forbidText( + "pages/api-references/genlayer-js.md", + "stateStatus:", + "unsupported GenLayerJS read option", +); + +for (const relativePath of [ + "pages/developers/intelligent-contracts/deploying/cli-deployment.mdx", + "pages/developers/intelligent-contracts/deploying/network-configuration.mdx", +]) { + forbidText(relativePath, "genlayer network localnet", "pre-v0.40 network selection syntax"); + forbidText(relativePath, "genlayer network studionet", "pre-v0.40 network selection syntax"); + forbidText(relativePath, "genlayer network testnet-bradbury", "pre-v0.40 network selection syntax"); +} + +for (const relativePath of releaseDocs) { + const content = read(relativePath); + + const localRoutes = [ + ...Array.from(content.matchAll(/\]\((\/[^)\s#]+)(?:#[^)]*)?\)/g), (match) => match[1]), + ...Array.from(content.matchAll(/\bhref=["'](\/[^"'#?]+)(?:[?#][^"']*)?["']/g), (match) => match[1]), + ]; + for (const localRoute of localRoutes) { + const route = localRoute.replace(/\/$/, ""); + const candidates = [ + path.join(ROOT, "pages", `${route}.mdx`), + path.join(ROOT, "pages", `${route}.md`), + path.join(ROOT, "pages", `${route}.cmdx`), + path.join(ROOT, "pages", route, "index.mdx"), + ]; + if (!candidates.some(fs.existsSync)) { + failures.push(`${relativePath}: unresolved internal link ${localRoute}`); + } + } + + for (const match of content.matchAll(/```(typescript|tsx|python)\s*\n([\s\S]*?)```/g)) { + const [, language, source] = match; + const line = content.slice(0, match.index).split("\n").length; + if (language === "python") { + const result = spawnSync( + "python3", + ["-c", "import ast,sys; ast.parse(sys.stdin.read())"], + { input: source, encoding: "utf8" }, + ); + if (result.status !== 0) { + failures.push(`${relativePath}:${line}: invalid Python example: ${result.stderr.trim()}`); + } + } else { + const result = ts.transpileModule(source, { + compilerOptions: { + jsx: ts.JsxEmit.ReactJSX, + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + }, + fileName: language === "tsx" ? "example.tsx" : "example.ts", + reportDiagnostics: true, + }); + for (const diagnostic of result.diagnostics || []) { + if (diagnostic.category === ts.DiagnosticCategory.Error) { + failures.push( + `${relativePath}:${line}: invalid ${language} example: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, " ")}`, + ); + } + } + } + } +} + +requireText( + "pages/api-references/genlayer-cli/transactions/appeal-bond.mdx", + "bond and induced-work funding", + "complete CLI appeal-charge description", +); +forbidText( + "pages/api-references/genlayer-cli/transactions/appeal-bond.mdx", + "Show minimum appeal bond required", + "minimum-bond-only CLI description", +); + +const jsApi = "pages/api-references/genlayer-js/contracts.md"; +for (const method of [ + "### estimateTransactionFees", + "### estimateTransactionFeesForWrite", + "### getAppealCharge", + "### appealTransaction", + "### getDeveloperNft", + "### resolveTransactions", + "### finalizeDecisions", +]) { + requireText(jsApi, method, `GenLayerJS API method ${method.slice(4)}`); +} +requireText( + "pages/api-references/genlayer-js/transactions.md", + "### getTransactionLifecycle", + "GenLayerJS lifecycle API", +); +forbidText(jsApi, "Calculates the minimum bond required", "minimum-bond-only JS API description"); + +const pyApi = "pages/api-references/genlayer-py/api.md"; +for (const method of [ + "### estimate_transaction_fees", + "### estimate_transaction_fees_for_write", + "### get_appeal_charge", + "### appeal_transaction", + "### get_transaction_lifecycle", + "### wait_for_finalization", +]) { + requireText(pyApi, method, `GenLayerPY API method ${method.slice(4)}`); +} +forbidText(pyApi, "value: int = 0)\n```", "zero-value default for Python appeals"); + +const selectorsPath = "data/consensus-v06-error-selectors.json"; +requireText( + "pages/developers/error-reference.mdx", + "2f78febfc06866e59e7f12db848664e95ce99e8e", + "consensus selector source revision", +); +const selectors = JSON.parse(read(selectorsPath)); +const signatures = new Set(selectors.map(({ signature }) => signature)); +const selectorValues = new Set(selectors.map(({ selector }) => selector)); +if (selectors.length !== 363) { + failures.push(`${selectorsPath}: expected 363 entries, found ${selectors.length}`); +} +if (signatures.size !== selectors.length) { + failures.push(`${selectorsPath}: duplicate error signatures`); +} +if (selectorValues.size !== selectors.length) { + failures.push(`${selectorsPath}: selector collision`); +} + +const anchors = new Map([ + ["FeeValueMustBeNonZero(uint256)", "0x632be5a1"], + ["AppealRoundNotPermitted()", "0x6ecc8d59"], + ["InsufficientAppealFunding(uint256,uint256)", "0x96631b91"], + ["TopUpCannotExtendSchedule()", "0x49449933"], + ["PubKeyMismatch()", "0x203f11f1"], + ["OnlyStakingRouter()", "0x25cbc0a7"], +]); +const actual = new Map(selectors.map(({ signature, selector }) => [signature, selector])); +for (const [signature, selector] of anchors) { + if (actual.get(signature) !== selector) { + failures.push(`${selectorsPath}: ${signature} must map to ${selector}`); + } +} +for (const removed of ["FacetCallFailed(bytes)", "ReconcileBeyondTail()", "ReconcileNotAdvancing()"]) { + if (signatures.has(removed)) { + failures.push(`${selectorsPath}: contains removed signature ${removed}`); + } +} + +if (failures.length) { + console.error("check-v06-release-docs: FAILED"); + for (const failure of failures) console.error(` - ${failure}`); + process.exit(1); +} + +console.log( + `check-v06-release-docs: OK (${selectors.length} unique selectors and release-critical behavior anchors)`, +); From a61e824589046bc52689202cdd307266b195e449 Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 2 Sep 2026 20:10:37 +0100 Subject: [PATCH 2/2] docs: align v0.6 lifecycle interfaces Remove the obsolete ReadyToFinalize status, renumber the terminal phase statuses to the 14-value consensus ontology, document gen_getTransactionLifecycle, and update receipt fields including fee accounting and the absent previousStatus. Strengthen protocol and release checks so the old ordinal model, status timestamp projection, and incomplete vote enum fail before publication.\n\nValidated with the 232-route production build, both documentation checks, generated API and LLM exports, and whitespace checks. --- .../genlayer-cli/transactions/receipt.mdx | 2 +- pages/api-references/genlayer-node.mdx | 195 +++++++++++++++--- .../genlayer-node/gen/_meta.json | 1 + .../gen/gen_getTransactionLifecycle.mdx | 74 +++++++ .../gen/gen_getTransactionReceipt.mdx | 80 ++++++- .../gen/gen_getTransactionStatus.mdx | 40 ++-- .../optimistic-democracy/finality.mdx | 8 +- .../transactions/transaction-execution.mdx | 8 +- .../transactions/transaction-statuses.mdx | 17 +- scripts/check-protocol-docs.js | 39 +++- scripts/check-v06-release-docs.js | 37 ++++ 11 files changed, 432 insertions(+), 69 deletions(-) create mode 100644 pages/api-references/genlayer-node/gen/gen_getTransactionLifecycle.mdx diff --git a/pages/api-references/genlayer-cli/transactions/receipt.mdx b/pages/api-references/genlayer-cli/transactions/receipt.mdx index 03039ed8..c04d3279 100644 --- a/pages/api-references/genlayer-cli/transactions/receipt.mdx +++ b/pages/api-references/genlayer-cli/transactions/receipt.mdx @@ -16,7 +16,7 @@ Get transaction receipt by hash | Short | Long | Description | Required | Default | | --- | --- | --- | :---: | --- | -| | --status <status> | Transaction status to wait for (UNINITIALIZED, PENDING, PROPOSING, COMMITTING, REVEALING, ACCEPTED, UNDETERMINED, FINALIZED, CANCELED, APPEAL_REVEALING, APPEAL_COMMITTING, READY_TO_FINALIZE, VALIDATORS_TIMEOUT, LEADER_TIMEOUT) (default: "FINALIZED") | No | | +| | --status <status> | Transaction status to wait for (UNINITIALIZED, PENDING, PROPOSING, COMMITTING, REVEALING, ACCEPTED, UNDETERMINED, FINALIZED, CANCELED, APPEAL_REVEALING, APPEAL_COMMITTING, VALIDATORS_TIMEOUT, LEADER_TIMEOUT, LEADER_REVEALING) (default: "FINALIZED") | No | | | | --retries <retries> | Number of retries | No | `100` | | | --interval <interval> | Interval between retries in milliseconds (default: 5000) | No | | | | --rpc <rpcUrl> | RPC URL for the network | No | | diff --git a/pages/api-references/genlayer-node.mdx b/pages/api-references/genlayer-node.mdx index 1f682955..aebb731f 100644 --- a/pages/api-references/genlayer-node.mdx +++ b/pages/api-references/genlayer-node.mdx @@ -527,7 +527,8 @@ A `Transaction` object which contains the details of the transaction. The struct - `recipient` (`address`): The destination address of the transaction. - `activator` (`address`): The address of the validator that activated the transaction. - `status` (`uint8`): The current status of the transaction. -- `previousStatus` (`uint8`): The previous status of the transaction. +- `statusName` (`string`): The ABI transaction status name for `status` (`Uninitialized`, `Pending`, `Proposing`, `Committing`, `Revealing`, `Accepted`, `Undetermined`, `Finalized`, `Canceled`, `AppealRevealing`, `AppealCommitting`, `ValidatorsTimeout`, `LeaderTimeout`, or `LeaderRevealing`). +- `previousStatus` (`null`): **Always null.** The contract dropped this field from `ITransactions.Transaction` (26 → 25 tuple components), so the node has nothing to report. It is `null` rather than `0` because `0` decodes as `Uninitialized`, which would be a wrong answer rather than an absent one. The field is removed in a v2 receipt. - `txSlot` (`uint256`): The transaction slot number. - `initialRotations` (`uint256`): The initial number of rotations. - `numOfInitialValidators` (`uint256`): The number of initial validators. @@ -544,7 +545,44 @@ A `Transaction` object which contains the details of the transaction. The struct - `LastVote` (`uint256`): When the last vote was cast. - `AppealSubmitted` (`uint256`): When an appeal was submitted. - `LeaderRevealed` (`uint256`): When the leader was revealed. -- `result` (`uint256`): The result of the transaction execution. +- `result` (`uint8`): The result of the transaction execution. +- `txExecutionResult` (`uint8`): The execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree, `5` DeterministicViolation). +- `txExecutionResultName` (`string`): The ABI vote type name for `txExecutionResult`. +- `epoch` (`uint256`): The epoch the transaction belongs to. +- `resultHash` (`string`): The transaction result hash as a hex string. +- `validUntil` (`uint256`): The timestamp until which the transaction is valid. +- `fees` (`object|null`): Fee accounting echo. `null` when fee accounting is disabled or the FeeManager binding/address is not available. All numeric values in this object are decimal strings. + - `deposit` (`string`): Submission-time fee custody sum, from `transactionFeesBudget(txId) + transactionDeveloperFeesBudget(txId)`. + - `userValue` (`string`): User value carried by the transaction. + - `distribution` (`object`): Effective fee distribution for the transaction: + - `leaderTimeunitsAllocation` (`string`) + - `validatorTimeunitsAllocation` (`string`) + - `appealRounds` (`string`) + - `executionBudgetPerRound` (`string`) + - `totalMessageFees` (`string`) + - `rotations` (`string[]`) + - `maxPriceGenPerTimeUnit` (`string`) + - `storageFeeMaxGasPrice` (`string`) + - `receiptFeeMaxGasPrice` (`string`) + - `locked` (`object|null`): Activation-locked prices, or `null` before activation when no locked prices exist: + - `genPerTimeUnit` (`string`) + - `storageUnitPrice` (`string`) + - `receiptGasPrice` (`string`) + - `consumed` (`object`): On-chain consumption counters: + - `executionConsumed` (`string`) + - `storageFeeUsed` (`string`): Storage fee charged on-chain for the transaction, equal to the storage pages it consumed multiplied by the locked `storageUnitPrice`. + - `messageFeesConsumed` (`string`) + - `messageFeesBudgetTotal` (`string`) + - `hasAllocations` (`boolean`): `true` when the transaction locked a pinned per-message fee allocation tree at submission; `false` for an open bucket (global message-fee bucket) or fee-less transactions. + - `allocations` (`array`): The pinned per-message fee allocation tree, one entry per locked allocation (empty for an open bucket). Budget comes from the on-chain allocation tree; `consumed` is read from the separate `allocationConsumed(txId, key)` mapping, keyed by `keccak256(abi.encode(messageType, recipient, callKey))`. Each entry: + - `messageType` (`number`): `0` = external, `1` = internal. + - `onAcceptance` (`boolean`): Emission phase the allocation is pinned to (`true` = on acceptance, `false` = on finalization). + - `parentIndex` (`string`): Index of the parent allocation in the flat tree, or the root sentinel (`2^256 - 1`) for a root-layer allocation. + - `recipient` (`string`): Target contract address the allocation funds, or the zero address for a recipient wildcard. + - `callKey` (`string`): 32-byte call key (hex) the allocation matches, or the `keccak256("")` call-key wildcard. + - `budget` (`string`): Maximum message-fee budget locked for this allocation. + - `consumed` (`string`): Amount consumed against this allocation so far. + - `feeParams` (`string`): ABI-encoded per-message fee params (hex), decoded per `messageType`. - `readStateBlockRanges` (`array`): Array of block range objects, each containing: - `ActivationBlock` (`uint256`): The activation block number. - `ProcessingBlock` (`uint256`): The processing block number. @@ -589,8 +627,9 @@ A `Transaction` object which contains the details of the transaction. The struct "sender": "0x5b70759760a85d92bf972b67ee8558e8e8da87d4", "recipient": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", "activator": "0xf27b07d36ff5b9dcd37fd3a09811d6456dd4483a", - "status": 14, - "previousStatus": 0, + "status": 13, + "statusName": "LeaderRevealing", + "previousStatus": null, "txSlot": 1, "initialRotations": 1, "numOfInitialValidators": 5, @@ -609,6 +648,39 @@ A `Transaction` object which contains the details of the transaction. The struct "LeaderRevealed": 0 }, "result": 0, + "txExecutionResult": 3, + "txExecutionResultName": "Timeout", + "epoch": 12, + "resultHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "validUntil": 0, + "fees": { + "deposit": "1234500000000000", + "userValue": "0", + "distribution": { + "leaderTimeunitsAllocation": "100", + "validatorTimeunitsAllocation": "200", + "appealRounds": "0", + "executionBudgetPerRound": "20000000000000000", + "totalMessageFees": "0", + "rotations": ["0"], + "maxPriceGenPerTimeUnit": "1200000000000000", + "storageFeeMaxGasPrice": "12", + "receiptFeeMaxGasPrice": "300000000" + }, + "locked": { + "genPerTimeUnit": "0", + "storageUnitPrice": "10", + "receiptGasPrice": "250000000" + }, + "consumed": { + "executionConsumed": "0", + "storageFeeUsed": "250", + "messageFeesConsumed": "0", + "messageFeesBudgetTotal": "0" + }, + "hasAllocations": false, + "allocations": [] + }, "readStateBlockRanges": [ { "ActivationBlock": 1377797, @@ -655,7 +727,7 @@ A `Transaction` object which contains the details of the transaction. The struct ### gen_getTransactionStatus -Returns the current consensus status of a transaction. This is a lightweight endpoint that returns only the status without full receipt data — use it for polling transaction progress. +Returns the transaction's current materialized consensus status. This is the lightweight polling surface: it does not project deadlines or turn protocol actions into synthetic statuses. **Method:** `gen_getTransactionStatus` @@ -663,7 +735,6 @@ Returns the current consensus status of a transaction. This is a lightweight end - `request` (object, required): - `txId` (string, required): The transaction hash (hex-encoded with `0x` prefix) - - `timestamp` (integer, optional): Unix timestamp for the query. If not provided, uses current time **Returns:** Object with status string and numeric code @@ -671,28 +742,27 @@ Returns the current consensus status of a transaction. This is a lightweight end | Field | Type | Description | |-------|------|-------------| -| `status` | string | Human-readable status name (e.g., "FINALIZED", "ACCEPTED", "PENDING") | +| `status` | string | Human-readable status name (e.g., "Finalized", "Accepted", "Pending") | | `statusCode` | uint8 | Numeric status code | **Status Codes:** | Code | Status | |------|--------| -| 0 | UNINITIALIZED | -| 1 | PENDING | -| 2 | PROPOSING | -| 3 | COMMITTING | -| 4 | REVEALING | -| 5 | ACCEPTED | -| 6 | UNDETERMINED | -| 7 | FINALIZED | -| 8 | CANCELED | -| 9 | APPEAL_REVEALING | -| 10 | APPEAL_COMMITTING | -| 11 | READY_TO_FINALIZE | -| 12 | VALIDATORS_TIMEOUT | -| 13 | LEADER_TIMEOUT | -| 14 | LEADER_REVEALING | +| 0 | Uninitialized | +| 1 | Pending | +| 2 | Proposing | +| 3 | Committing | +| 4 | Revealing | +| 5 | Accepted | +| 6 | Undetermined | +| 7 | Finalized | +| 8 | Canceled | +| 9 | AppealRevealing | +| 10 | AppealCommitting | +| 11 | ValidatorsTimeout | +| 12 | LeaderTimeout | +| 13 | LeaderRevealing | **Example Request:** @@ -715,7 +785,7 @@ Returns the current consensus status of a transaction. This is a lightweight end { "jsonrpc": "2.0", "result": { - "status": "FINALIZED", + "status": "Finalized", "statusCode": 7 }, "id": 1 @@ -724,9 +794,84 @@ Returns the current consensus status of a transaction. This is a lightweight end **Notes:** -- This endpoint queries the consensus contract on-chain via `getTransactionData` +- This endpoint reads the stored status directly. A deadline passing does not change this response until the corresponding protocol transition is materialized. - Use this for polling — it's cheaper than `gen_getTransactionReceipt` which returns the full receipt -- The `timestamp` parameter is passed to the on-chain call and affects how the contract reports the status +- `Finalize` is an action, not a status. Explorers and operator tooling that need projected state or recovery actions should use `gen_getTransactionLifecycle`. + +### gen_getTransactionLifecycle + +Returns one advanced protocol lifecycle snapshot for a transaction. This endpoint is intended for explorers, operator tooling, and debugging; ordinary applications should prefer `gen_getTransactionStatus` or their SDK's simplified lifecycle model. + +**Method:** `gen_getTransactionLifecycle` + +**Parameters:** + +- `request` (object, required): + - `txId` (string, required): The transaction hash (hex-encoded with `0x` prefix) + - `timestamp` (integer, optional): Unix timestamp used for projection. Defaults to the node clock. + +**Returns:** The stored state, its projection at `evaluatedAt`, and the resolution kernel's next action from one contract read. + +**Response Fields:** + +| Field | Type | Description | +|-------|------|-------------| +| `storedStatus` | string | Materialized on-chain status | +| `storedStatusCode` | uint8 | Materialized status code | +| `projectedStatus` | string | Status projected at `evaluatedAt`; projection does not write state | +| `projectedStatusCode` | uint8 | Projected status code | +| `resolutionAction` | string | Kernel action available from this snapshot, such as `NoOp`, `ResolveAppeal`, or `Finalize` | +| `resolutionActionCode` | uint8 | Resolution action code | +| `resolutionSource` | string | Resolution-plan source/reason | +| `resolutionSourceCode` | uint8 | Resolution source code | +| `decisionId` | string or null | Decimal decision identity used by decision-scoped protocol commands | +| `decisionActive` | boolean | Whether `decisionId` still identifies the active decision | +| `evaluatedAt` | integer | Unix timestamp used for the projection | + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_getTransactionLifecycle", + "params": [ + { + "txId": "0x563f046c187d711127c51213ca62e2e4fee52009a98f0989a73a0a0382d21890", + "timestamp": 1787868000 + } + ], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "result": { + "storedStatus": "Accepted", + "storedStatusCode": 5, + "projectedStatus": "Accepted", + "projectedStatusCode": 5, + "resolutionAction": "Finalize", + "resolutionActionCode": 6, + "resolutionSource": "FullReveal", + "resolutionSourceCode": 6, + "decisionId": "42", + "decisionActive": true, + "evaluatedAt": 1787868000 + }, + "id": 1 +} +``` + +**Notes:** + +- Every response field comes from one lifecycle read at one pinned head and one timestamp. +- `storedStatus` is the protocol state. `projectedStatus` describes what the kernel computes at `evaluatedAt`; it is not independently materialized. +- `resolutionAction: "Finalize"` is the authoritative finalization capability. There is deliberately no `ReadyToFinalize` status and no redundant `canFinalize` field. +- A client must not submit a decision-scoped command unless `decisionActive` is true and `decisionId` is non-null. ### gen_syncing diff --git a/pages/api-references/genlayer-node/gen/_meta.json b/pages/api-references/genlayer-node/gen/_meta.json index 835928b2..08ada0fc 100644 --- a/pages/api-references/genlayer-node/gen/_meta.json +++ b/pages/api-references/genlayer-node/gen/_meta.json @@ -5,5 +5,6 @@ "gen_getContractCode": "gen_getContractCode", "gen_getTransactionReceipt": "gen_getTransactionReceipt", "gen_getTransactionStatus": "gen_getTransactionStatus", + "gen_getTransactionLifecycle": "gen_getTransactionLifecycle", "gen_syncing": "gen_syncing" } diff --git a/pages/api-references/genlayer-node/gen/gen_getTransactionLifecycle.mdx b/pages/api-references/genlayer-node/gen/gen_getTransactionLifecycle.mdx new file mode 100644 index 00000000..168bef11 --- /dev/null +++ b/pages/api-references/genlayer-node/gen/gen_getTransactionLifecycle.mdx @@ -0,0 +1,74 @@ +### gen_getTransactionLifecycle + +Returns one advanced protocol lifecycle snapshot for a transaction. This endpoint is intended for explorers, operator tooling, and debugging; ordinary applications should prefer `gen_getTransactionStatus` or their SDK's simplified lifecycle model. + +**Method:** `gen_getTransactionLifecycle` + +**Parameters:** + +- `request` (object, required): + - `txId` (string, required): The transaction hash (hex-encoded with `0x` prefix) + - `timestamp` (integer, optional): Unix timestamp used for projection. Defaults to the node clock. + +**Returns:** The stored state, its projection at `evaluatedAt`, and the resolution kernel's next action from one contract read. + +**Response Fields:** + +| Field | Type | Description | +|-------|------|-------------| +| `storedStatus` | string | Materialized on-chain status | +| `storedStatusCode` | uint8 | Materialized status code | +| `projectedStatus` | string | Status projected at `evaluatedAt`; projection does not write state | +| `projectedStatusCode` | uint8 | Projected status code | +| `resolutionAction` | string | Kernel action available from this snapshot, such as `NoOp`, `ResolveAppeal`, or `Finalize` | +| `resolutionActionCode` | uint8 | Resolution action code | +| `resolutionSource` | string | Resolution-plan source/reason | +| `resolutionSourceCode` | uint8 | Resolution source code | +| `decisionId` | string or null | Decimal decision identity used by decision-scoped protocol commands | +| `decisionActive` | boolean | Whether `decisionId` still identifies the active decision | +| `evaluatedAt` | integer | Unix timestamp used for the projection | + +**Example Request:** + +```json +{ + "jsonrpc": "2.0", + "method": "gen_getTransactionLifecycle", + "params": [ + { + "txId": "0x563f046c187d711127c51213ca62e2e4fee52009a98f0989a73a0a0382d21890", + "timestamp": 1787868000 + } + ], + "id": 1 +} +``` + +**Example Response:** + +```json +{ + "jsonrpc": "2.0", + "result": { + "storedStatus": "Accepted", + "storedStatusCode": 5, + "projectedStatus": "Accepted", + "projectedStatusCode": 5, + "resolutionAction": "Finalize", + "resolutionActionCode": 6, + "resolutionSource": "FullReveal", + "resolutionSourceCode": 6, + "decisionId": "42", + "decisionActive": true, + "evaluatedAt": 1787868000 + }, + "id": 1 +} +``` + +**Notes:** + +- Every response field comes from one lifecycle read at one pinned head and one timestamp. +- `storedStatus` is the protocol state. `projectedStatus` describes what the kernel computes at `evaluatedAt`; it is not independently materialized. +- `resolutionAction: "Finalize"` is the authoritative finalization capability. There is deliberately no `ReadyToFinalize` status and no redundant `canFinalize` field. +- A client must not submit a decision-scoped command unless `decisionActive` is true and `decisionId` is non-null. diff --git a/pages/api-references/genlayer-node/gen/gen_getTransactionReceipt.mdx b/pages/api-references/genlayer-node/gen/gen_getTransactionReceipt.mdx index e6d6f8f5..67177b75 100644 --- a/pages/api-references/genlayer-node/gen/gen_getTransactionReceipt.mdx +++ b/pages/api-references/genlayer-node/gen/gen_getTransactionReceipt.mdx @@ -19,7 +19,8 @@ A `Transaction` object which contains the details of the transaction. The struct - `recipient` (`address`): The destination address of the transaction. - `activator` (`address`): The address of the validator that activated the transaction. - `status` (`uint8`): The current status of the transaction. -- `previousStatus` (`uint8`): The previous status of the transaction. +- `statusName` (`string`): The ABI transaction status name for `status` (`Uninitialized`, `Pending`, `Proposing`, `Committing`, `Revealing`, `Accepted`, `Undetermined`, `Finalized`, `Canceled`, `AppealRevealing`, `AppealCommitting`, `ValidatorsTimeout`, `LeaderTimeout`, or `LeaderRevealing`). +- `previousStatus` (`null`): **Always null.** The contract dropped this field from `ITransactions.Transaction` (26 → 25 tuple components), so the node has nothing to report. It is `null` rather than `0` because `0` decodes as `Uninitialized`, which would be a wrong answer rather than an absent one. The field is removed in a v2 receipt. - `txSlot` (`uint256`): The transaction slot number. - `initialRotations` (`uint256`): The initial number of rotations. - `numOfInitialValidators` (`uint256`): The number of initial validators. @@ -36,7 +37,44 @@ A `Transaction` object which contains the details of the transaction. The struct - `LastVote` (`uint256`): When the last vote was cast. - `AppealSubmitted` (`uint256`): When an appeal was submitted. - `LeaderRevealed` (`uint256`): When the leader was revealed. -- `result` (`uint256`): The result of the transaction execution. +- `result` (`uint8`): The result of the transaction execution. +- `txExecutionResult` (`uint8`): The execution vote result (`0` NotVoted, `1` FinishedWithReturn, `2` FinishedWithError, `3` Timeout, `4` NondetDisagree, `5` DeterministicViolation). +- `txExecutionResultName` (`string`): The ABI vote type name for `txExecutionResult`. +- `epoch` (`uint256`): The epoch the transaction belongs to. +- `resultHash` (`string`): The transaction result hash as a hex string. +- `validUntil` (`uint256`): The timestamp until which the transaction is valid. +- `fees` (`object|null`): Fee accounting echo. `null` when fee accounting is disabled or the FeeManager binding/address is not available. All numeric values in this object are decimal strings. + - `deposit` (`string`): Submission-time fee custody sum, from `transactionFeesBudget(txId) + transactionDeveloperFeesBudget(txId)`. + - `userValue` (`string`): User value carried by the transaction. + - `distribution` (`object`): Effective fee distribution for the transaction: + - `leaderTimeunitsAllocation` (`string`) + - `validatorTimeunitsAllocation` (`string`) + - `appealRounds` (`string`) + - `executionBudgetPerRound` (`string`) + - `totalMessageFees` (`string`) + - `rotations` (`string[]`) + - `maxPriceGenPerTimeUnit` (`string`) + - `storageFeeMaxGasPrice` (`string`) + - `receiptFeeMaxGasPrice` (`string`) + - `locked` (`object|null`): Activation-locked prices, or `null` before activation when no locked prices exist: + - `genPerTimeUnit` (`string`) + - `storageUnitPrice` (`string`) + - `receiptGasPrice` (`string`) + - `consumed` (`object`): On-chain consumption counters: + - `executionConsumed` (`string`) + - `storageFeeUsed` (`string`): Storage fee charged on-chain for the transaction, equal to the storage pages it consumed multiplied by the locked `storageUnitPrice`. + - `messageFeesConsumed` (`string`) + - `messageFeesBudgetTotal` (`string`) + - `hasAllocations` (`boolean`): `true` when the transaction locked a pinned per-message fee allocation tree at submission; `false` for an open bucket (global message-fee bucket) or fee-less transactions. + - `allocations` (`array`): The pinned per-message fee allocation tree, one entry per locked allocation (empty for an open bucket). Budget comes from the on-chain allocation tree; `consumed` is read from the separate `allocationConsumed(txId, key)` mapping, keyed by `keccak256(abi.encode(messageType, recipient, callKey))`. Each entry: + - `messageType` (`number`): `0` = external, `1` = internal. + - `onAcceptance` (`boolean`): Emission phase the allocation is pinned to (`true` = on acceptance, `false` = on finalization). + - `parentIndex` (`string`): Index of the parent allocation in the flat tree, or the root sentinel (`2^256 - 1`) for a root-layer allocation. + - `recipient` (`string`): Target contract address the allocation funds, or the zero address for a recipient wildcard. + - `callKey` (`string`): 32-byte call key (hex) the allocation matches, or the `keccak256("")` call-key wildcard. + - `budget` (`string`): Maximum message-fee budget locked for this allocation. + - `consumed` (`string`): Amount consumed against this allocation so far. + - `feeParams` (`string`): ABI-encoded per-message fee params (hex), decoded per `messageType`. - `readStateBlockRanges` (`array`): Array of block range objects, each containing: - `ActivationBlock` (`uint256`): The activation block number. - `ProcessingBlock` (`uint256`): The processing block number. @@ -81,8 +119,9 @@ A `Transaction` object which contains the details of the transaction. The struct "sender": "0x5b70759760a85d92bf972b67ee8558e8e8da87d4", "recipient": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f", "activator": "0xf27b07d36ff5b9dcd37fd3a09811d6456dd4483a", - "status": 14, - "previousStatus": 0, + "status": 13, + "statusName": "LeaderRevealing", + "previousStatus": null, "txSlot": 1, "initialRotations": 1, "numOfInitialValidators": 5, @@ -101,6 +140,39 @@ A `Transaction` object which contains the details of the transaction. The struct "LeaderRevealed": 0 }, "result": 0, + "txExecutionResult": 3, + "txExecutionResultName": "Timeout", + "epoch": 12, + "resultHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "validUntil": 0, + "fees": { + "deposit": "1234500000000000", + "userValue": "0", + "distribution": { + "leaderTimeunitsAllocation": "100", + "validatorTimeunitsAllocation": "200", + "appealRounds": "0", + "executionBudgetPerRound": "20000000000000000", + "totalMessageFees": "0", + "rotations": ["0"], + "maxPriceGenPerTimeUnit": "1200000000000000", + "storageFeeMaxGasPrice": "12", + "receiptFeeMaxGasPrice": "300000000" + }, + "locked": { + "genPerTimeUnit": "0", + "storageUnitPrice": "10", + "receiptGasPrice": "250000000" + }, + "consumed": { + "executionConsumed": "0", + "storageFeeUsed": "250", + "messageFeesConsumed": "0", + "messageFeesBudgetTotal": "0" + }, + "hasAllocations": false, + "allocations": [] + }, "readStateBlockRanges": [ { "ActivationBlock": 1377797, diff --git a/pages/api-references/genlayer-node/gen/gen_getTransactionStatus.mdx b/pages/api-references/genlayer-node/gen/gen_getTransactionStatus.mdx index 1d86211c..bdd3e712 100644 --- a/pages/api-references/genlayer-node/gen/gen_getTransactionStatus.mdx +++ b/pages/api-references/genlayer-node/gen/gen_getTransactionStatus.mdx @@ -1,6 +1,6 @@ ### gen_getTransactionStatus -Returns the current consensus status of a transaction. This is a lightweight endpoint that returns only the status without full receipt data — use it for polling transaction progress. +Returns the transaction's current materialized consensus status. This is the lightweight polling surface: it does not project deadlines or turn protocol actions into synthetic statuses. **Method:** `gen_getTransactionStatus` @@ -8,7 +8,6 @@ Returns the current consensus status of a transaction. This is a lightweight end - `request` (object, required): - `txId` (string, required): The transaction hash (hex-encoded with `0x` prefix) - - `timestamp` (integer, optional): Unix timestamp for the query. If not provided, uses current time **Returns:** Object with status string and numeric code @@ -16,28 +15,27 @@ Returns the current consensus status of a transaction. This is a lightweight end | Field | Type | Description | |-------|------|-------------| -| `status` | string | Human-readable status name (e.g., "FINALIZED", "ACCEPTED", "PENDING") | +| `status` | string | Human-readable status name (e.g., "Finalized", "Accepted", "Pending") | | `statusCode` | uint8 | Numeric status code | **Status Codes:** | Code | Status | |------|--------| -| 0 | UNINITIALIZED | -| 1 | PENDING | -| 2 | PROPOSING | -| 3 | COMMITTING | -| 4 | REVEALING | -| 5 | ACCEPTED | -| 6 | UNDETERMINED | -| 7 | FINALIZED | -| 8 | CANCELED | -| 9 | APPEAL_REVEALING | -| 10 | APPEAL_COMMITTING | -| 11 | READY_TO_FINALIZE | -| 12 | VALIDATORS_TIMEOUT | -| 13 | LEADER_TIMEOUT | -| 14 | LEADER_REVEALING | +| 0 | Uninitialized | +| 1 | Pending | +| 2 | Proposing | +| 3 | Committing | +| 4 | Revealing | +| 5 | Accepted | +| 6 | Undetermined | +| 7 | Finalized | +| 8 | Canceled | +| 9 | AppealRevealing | +| 10 | AppealCommitting | +| 11 | ValidatorsTimeout | +| 12 | LeaderTimeout | +| 13 | LeaderRevealing | **Example Request:** @@ -60,7 +58,7 @@ Returns the current consensus status of a transaction. This is a lightweight end { "jsonrpc": "2.0", "result": { - "status": "FINALIZED", + "status": "Finalized", "statusCode": 7 }, "id": 1 @@ -69,6 +67,6 @@ Returns the current consensus status of a transaction. This is a lightweight end **Notes:** -- This endpoint queries the consensus contract on-chain via `getTransactionData` +- This endpoint reads the stored status directly. A deadline passing does not change this response until the corresponding protocol transition is materialized. - Use this for polling — it's cheaper than `gen_getTransactionReceipt` which returns the full receipt -- The `timestamp` parameter is passed to the on-chain call and affects how the contract reports the status +- `Finalize` is an action, not a status. Explorers and operator tooling that need projected state or recovery actions should use `gen_getTransactionLifecycle`. diff --git a/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/finality.mdx b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/finality.mdx index a78b61e2..74babb36 100644 --- a/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/finality.mdx +++ b/pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/finality.mdx @@ -12,7 +12,7 @@ An Intelligent Contract transaction is final when the consensus decision can no %%{init: {"flowchart": {"curve": "basis", "nodeSpacing": 42, "rankSpacing": 48, "htmlLabels": true}}}%% flowchart TB D["Decided outcome"] --> W["Appeal window"] - W -->|"window elapses"| R["Ready to finalize"] --> F(["Finalized"]) + W -->|"window elapses"| R["Resolution action: Finalize"] --> F(["Finalized"]) W -->|"valid appeal"| A["Appeal processing"] A -->|"confirmed or recomputed"| W @@ -25,9 +25,9 @@ flowchart TB linkStyle default stroke:#7C879C,stroke-width:1.8px; ``` -The appeal window starts after a decided outcome such as Accepted, Undetermined, ValidatorsTimeout, or LeaderTimeout. Its duration is governed by the protocol configuration. Applications should read the effective transaction status rather than assume a fixed number of seconds. +The appeal window starts after a decided outcome such as Accepted, Undetermined, ValidatorsTimeout, or LeaderTimeout. Its duration is governed by the protocol configuration. Applications should read the lifecycle projection rather than assume a fixed number of seconds. -After the window expires, the transaction can report `ReadyToFinalize`. Anyone can then submit the onchain finalization action. `ReadyToFinalize` means the deadline has passed; `Finalized` means the state transition has been recorded. +After the window expires, the advanced lifecycle read reports `resolutionAction: "Finalize"`. Anyone can then submit the decision-bound onchain finalization action. The stored status remains the decided status until finalization is recorded as `Finalized`; finalization readiness is not a transaction status. ## Accepted is not final @@ -48,4 +48,4 @@ The EVM transaction that submitted an Intelligent Contract call can be included - an EVM receipt confirms that the submission was included; and - the GenLayer transaction status confirms the Intelligent Contract outcome. -Use [`gen_getTransactionStatus`](/api-references/genlayer-node/gen/gen_getTransactionStatus) for lightweight polling or [`gen_getTransactionReceipt`](/api-references/genlayer-node/gen/gen_getTransactionReceipt) for the full consensus receipt. +Use [`gen_getTransactionStatus`](/api-references/genlayer-node/gen/gen_getTransactionStatus) for lightweight stored-status polling, [`gen_getTransactionLifecycle`](/api-references/genlayer-node/gen/gen_getTransactionLifecycle) for projection and available resolution actions, or [`gen_getTransactionReceipt`](/api-references/genlayer-node/gen/gen_getTransactionReceipt) for the full consensus receipt. diff --git a/pages/understand-genlayer-protocol/core-concepts/transactions/transaction-execution.mdx b/pages/understand-genlayer-protocol/core-concepts/transactions/transaction-execution.mdx index e1ed9229..81e8dc39 100644 --- a/pages/understand-genlayer-protocol/core-concepts/transactions/transaction-execution.mdx +++ b/pages/understand-genlayer-protocol/core-concepts/transactions/transaction-execution.mdx @@ -13,7 +13,7 @@ flowchart TD Propose -->|"leader submits receipt"| Vote["Committing → LeaderRevealing → Revealing"] Vote --> Decision{"Decided
outcome"} Decision --> Window["Appeal window"] - Window -->|"window elapses"| Ready["ReadyToFinalize"] + Window -->|"window elapses"| Ready["Resolution action: Finalize"] Ready --> Final(["Finalized"]) Pending -. "cancel or expire" .-> Canceled(["Canceled"]) @@ -50,7 +50,7 @@ Accepted, ValidatorsTimeout, Undetermined, and LeaderTimeout are decided outcome %%{init: {"flowchart": {"curve": "basis", "nodeSpacing": 40, "rankSpacing": 46, "htmlLabels": true}}}%% flowchart TB Decision["Decided outcome"] --> Window["Appeal window"] - Window -->|"no appeal"| Ready["Ready to finalize"] + Window -->|"no appeal"| Ready["Resolution action: Finalize"] Window -->|"validator appeal"| Review["Fresh committee review"] Review -->|"confirmed"| Window Review -->|"overturned"| Round["New proposal round"] @@ -67,6 +67,6 @@ flowchart TB ## Finalization -When the effective appeal window has elapsed, the transaction is ready to finalize. The finalization call records the terminal status, settles remaining fees and refunds, emits finalization messages, and advances the recipient's queues. +When the appeal window has elapsed, the advanced lifecycle read exposes the `Finalize` resolution action while retaining the stored decided status. The decision-bound finalization call records the terminal `Finalized` status, settles remaining fees and refunds, emits finalization messages, and advances the recipient's queues. -An EVM submission receipt is not a substitute for this lifecycle. Poll [`gen_getTransactionStatus`](/api-references/genlayer-node/gen/gen_getTransactionStatus) or retrieve the full [`gen_getTransactionReceipt`](/api-references/genlayer-node/gen/gen_getTransactionReceipt). +An EVM submission receipt is not a substitute for this lifecycle. Poll [`gen_getTransactionStatus`](/api-references/genlayer-node/gen/gen_getTransactionStatus), inspect projection and actions with [`gen_getTransactionLifecycle`](/api-references/genlayer-node/gen/gen_getTransactionLifecycle), or retrieve the full [`gen_getTransactionReceipt`](/api-references/genlayer-node/gen/gen_getTransactionReceipt). diff --git a/pages/understand-genlayer-protocol/core-concepts/transactions/transaction-statuses.mdx b/pages/understand-genlayer-protocol/core-concepts/transactions/transaction-statuses.mdx index e4a02017..dc5f126d 100644 --- a/pages/understand-genlayer-protocol/core-concepts/transactions/transaction-statuses.mdx +++ b/pages/understand-genlayer-protocol/core-concepts/transactions/transaction-statuses.mdx @@ -4,7 +4,7 @@ description: "Reference all GenLayer Intelligent Contract transaction statuses a # Transaction statuses -The consensus contracts define 15 numeric status values. APIs can return the stored status or an effective status calculated from the current time, such as `ReadyToFinalize` after an appeal window expires. +The Consensus v0.6 contracts define 14 numeric status values. A transaction has one stored, materialized status. Time-dependent lifecycle projection and the next resolution action are separate fields; finalization readiness is never synthesized as another status. | Code | Status | Meaning | | ---: | --- | --- | @@ -19,10 +19,9 @@ The consensus contracts define 15 numeric status values. APIs can return the sto | 8 | `Canceled` | The transaction was canceled before completing consensus. | | 9 | `AppealRevealing` | A fresh validator-appeal committee is revealing votes. | | 10 | `AppealCommitting` | A fresh validator-appeal committee is committing votes. | -| 11 | `ReadyToFinalize` | The effective appeal window elapsed and the transaction can be finalized. | -| 12 | `ValidatorsTimeout` | A validator majority reported that validation timed out; the appeal window is open. | -| 13 | `LeaderTimeout` | The leader reported an execution timeout; the appeal window is open. | -| 14 | `LeaderRevealing` | The leader must reveal execution data and keys before committee vote reveals. | +| 11 | `ValidatorsTimeout` | A validator majority reported that validation timed out; the appeal window is open. | +| 12 | `LeaderTimeout` | The leader reported an execution timeout; the appeal window is open. | +| 13 | `LeaderRevealing` | The leader must reveal execution data and keys before committee vote reveals. | ## Status is not execution success @@ -30,8 +29,10 @@ The consensus contracts define 15 numeric status values. APIs can return the sto Similarly, `Finalized` means the decided receipt is no longer appealable. It does not convert an error result into a successful contract call. -## Stored and effective status +## Stored status and lifecycle projection -Some view functions accept a timestamp and derive the status that applies after a deadline. A transaction whose stored decision is `Accepted`, for example, can be reported as `ReadyToFinalize` after its appeal window elapses even before a finalization transaction updates storage to `Finalized`. +[`gen_getTransactionStatus`](/api-references/genlayer-node/gen/gen_getTransactionStatus) returns only the exact stored status. A deadline passing does not change that response until a protocol action materializes a transition. -Use the numeric codes for program logic and display the names to users. See [`gen_getTransactionStatus`](/api-references/genlayer-node/gen/gen_getTransactionStatus). +Advanced tooling can use [`gen_getTransactionLifecycle`](/api-references/genlayer-node/gen/gen_getTransactionLifecycle) to read the stored status, its time-dependent projection, the active decision, and the resolution kernel's next action from one snapshot. When an appeal window has elapsed, the stored and projected statuses can both remain `Accepted` while `resolutionAction` is `Finalize`. There is deliberately no `ReadyToFinalize` status and no parallel `canFinalize` boolean. + +Use the numeric codes for program logic and display the names to users. Bind decision-scoped actions only when the lifecycle response reports an active, non-null `decisionId`. diff --git a/scripts/check-protocol-docs.js b/scripts/check-protocol-docs.js index 22625057..347501cf 100644 --- a/scripts/check-protocol-docs.js +++ b/scripts/check-protocol-docs.js @@ -94,7 +94,6 @@ const statuses = [ "Canceled", "AppealRevealing", "AppealCommitting", - "ReadyToFinalize", "ValidatorsTimeout", "LeaderTimeout", "LeaderRevealing", @@ -109,12 +108,48 @@ for (const statusTable of statusTables) { const readableName = status.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase(); const conceptRow = `| ${code} | \`${status}\` |`; const apiRow = `| ${code} | ${readableName} |`; - if (!content.includes(conceptRow) && !content.includes(apiRow)) { + const camelApiRow = `| ${code} | ${status} |`; + if (!content.includes(conceptRow) && !content.includes(apiRow) && !content.includes(camelApiRow)) { failures.push(`${relative(statusTable)}: missing status ${code} ${status}`); } } } +for (const file of [ + path.join(UNDERSTAND, "core-concepts", "transactions", "transaction-statuses.mdx"), + path.join(UNDERSTAND, "core-concepts", "transactions", "transaction-execution.mdx"), + path.join(UNDERSTAND, "core-concepts", "optimistic-democracy", "finality.mdx"), + path.join(ROOT, "pages", "api-references", "genlayer-node", "gen", "gen_getTransactionStatus.mdx"), +]) { + const content = fs.readFileSync(file, "utf8"); + if (content.includes("| 11 | `ReadyToFinalize` |") || content.includes("| 11 | READY_TO_FINALIZE |")) { + failures.push(`${relative(file)}: carries removed ReadyToFinalize status at ordinal 11`); + } +} + +const lifecycleApi = path.join( + ROOT, + "pages", + "api-references", + "genlayer-node", + "gen", + "gen_getTransactionLifecycle.mdx", +); +const lifecycleContent = fs.readFileSync(lifecycleApi, "utf8"); +for (const field of [ + "storedStatus", + "projectedStatus", + "resolutionAction", + "resolutionSource", + "decisionId", + "decisionActive", + "evaluatedAt", +]) { + if (!lifecycleContent.includes(`\`${field}\``)) { + failures.push(`${relative(lifecycleApi)}: missing lifecycle field ${field}`); + } +} + const removedStubs = [ "what-are-intelligent-contracts.mdx", "what-makes-genlayer-different.mdx", diff --git a/scripts/check-v06-release-docs.js b/scripts/check-v06-release-docs.js index 66085715..88de59cc 100644 --- a/scripts/check-v06-release-docs.js +++ b/scripts/check-v06-release-docs.js @@ -30,11 +30,18 @@ const releaseDocs = [ "pages/developers/decentralized-applications/writing-data.mdx", "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/appeal-process.mdx", "pages/understand-genlayer-protocol/core-concepts/economic-model.mdx", + "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/finality.mdx", "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/slashing.mdx", "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/deterministic-violations-and-tribunals.mdx", "pages/understand-genlayer-protocol/core-concepts/optimistic-democracy/protocol-randomness.mdx", + "pages/understand-genlayer-protocol/core-concepts/transactions/transaction-execution.mdx", + "pages/understand-genlayer-protocol/core-concepts/transactions/transaction-statuses.mdx", "pages/validators/network-keeper-roles.mdx", + "pages/api-references/genlayer-cli/transactions/receipt.mdx", "pages/api-references/genlayer-js.md", + "pages/api-references/genlayer-node/gen/gen_getTransactionLifecycle.mdx", + "pages/api-references/genlayer-node/gen/gen_getTransactionReceipt.mdx", + "pages/api-references/genlayer-node/gen/gen_getTransactionStatus.mdx", "pages/api-references/genlayer-py.md", ]; @@ -246,6 +253,36 @@ forbidText( "minimum-bond-only CLI description", ); +const statusConcept = + "pages/understand-genlayer-protocol/core-concepts/transactions/transaction-statuses.mdx"; +requireText(statusConcept, "define 14 numeric status values", "v0.6 status count"); +requireText(statusConcept, "`resolutionAction` is `Finalize`", "action-based finalization readiness"); +forbidText(statusConcept, "| 11 | `ReadyToFinalize` |", "removed ReadyToFinalize status row"); + +const nodeStatus = "pages/api-references/genlayer-node/gen/gen_getTransactionStatus.mdx"; +requireText(nodeStatus, "| 11 | ValidatorsTimeout |", "renumbered ValidatorsTimeout status"); +requireText(nodeStatus, "| 13 | LeaderRevealing |", "renumbered LeaderRevealing status"); +forbidText(nodeStatus, "READY_TO_FINALIZE", "removed ReadyToFinalize API status"); +forbidText(nodeStatus, "`timestamp` (integer, optional)", "removed status timestamp projection"); + +const nodeLifecycle = "pages/api-references/genlayer-node/gen/gen_getTransactionLifecycle.mdx"; +requireText(nodeLifecycle, "`resolutionAction`", "lifecycle resolution action"); +requireText(nodeLifecycle, "`decisionActive`", "lifecycle decision binding"); +requireText(nodeLifecycle, "no `ReadyToFinalize` status", "removed synthetic finalization status"); + +const nodeReceipt = "pages/api-references/genlayer-node/gen/gen_getTransactionReceipt.mdx"; +requireText(nodeReceipt, "`previousStatus` (`null`)", "v0.6 absent previous status"); +requireText(nodeReceipt, "`fees` (`object|null`)", "v0.6 fee receipt echo"); +requireText(nodeReceipt, "`5` DeterministicViolation", "complete execution vote enum"); +requireText(nodeReceipt, '"status": 13', "LeaderRevealing example ordinal"); +forbidText(nodeReceipt, '"status": 14', "pre-train LeaderRevealing ordinal"); + +forbidText( + "pages/api-references/genlayer-cli/transactions/receipt.mdx", + "READY_TO_FINALIZE", + "removed CLI ReadyToFinalize choice", +); + const jsApi = "pages/api-references/genlayer-js/contracts.md"; for (const method of [ "### estimateTransactionFees",