Purpose: Explain how the ante handler routes Structs gameplay transactions and Cosmos staking transactions through a free-gas path while everything else continues to pay fees in ualpha.
MsgUpdateParams, MsgGuildCreate and MsgReactorRestart) are free. No fee in ualpha is required, and they execute against a dedicated 20 M free-gas meter that does not consume the block’s normal gas budget.x/staking messages) are also free, against a 40 M free-staking meter, but capped to one listed staking operation per address per transaction/block, including mixed paid transactions.MsgUpdateParams, governance, bank, distribution, etc.) in a single transaction breaks the free path and the whole tx pays normal fees.--gas auto. Gas is metered and rejected on overflow; “free” only means the gas is not paid for, not that it’s unlimited.CheckTx.Cosmos SDK ante decorators run before the message handler executes. Structs’s custom ante chain (app/ante/ante.go) inserts a GasRouterDecorator early in the chain that checks every incoming transaction:
IsFreeTransaction(msgs) – every message URL starts with /structs.structs.Msg AND none is MsgUpdateParams or a member of PricedStructsMessages (MsgGuildCreate, MsgReactorRestart) – the gas meter is replaced with a free meter capped at FreeGasCap (default 20_000_000).IsFreeStakingTransaction(msgs) – every message URL is one of the six entries in FreeStakingMessages – the gas meter is replaced with a free meter capped at FreeStakingGasCap (default 40_000_000), and a per-address per-block throttle gates how many of these can land.ConditionalMempoolFeeDecorator + ConditionalFeeDecorator, which behave like the upstream SDK fee decorators.Both ConditionalMempoolFeeDecorator and ConditionalFeeDecorator short-circuit when the context flag IsFreeTx(ctx) is set, so free transactions are never charged a fee even if the user accidentally attaches one.
--gas auto (or --gas-adjustment 1.5) is still required because the SDK simulator must produce a gas estimate; if the estimate exceeds the meter cap (free or paid) the tx is rejected with out of gas. Free does not mean infinite.
Anything in KnownStructsMessages (defined in app/ante/maps.go) qualifies for the free-gas path, except MsgUpdateParams (governance) and the priced MsgGuildCreate / MsgReactorRestart operations. The list covers:
MsgAddress*, MsgAgreement*, MsgAllocation*, MsgPlayer*, MsgProvider*, MsgReactor*, MsgStruct*, MsgSubstation*, MsgPermission* messages, except MsgReactorRestart.MsgFleetMove, MsgPlanetExplore, MsgPlanetRaidComplete, MsgPlanetUpdateName.MsgGuildCreate: MsgGuildBank*, MsgGuildMembership*, MsgGuildUpdate* (including the UGC messages: MsgGuildUpdateName/Pfp, MsgPlayerUpdateName/Pfp, MsgPlayerUpdatePfpClientRenderAttributes, MsgPlanetUpdateName, MsgSubstationUpdateName/Pfp).Excluded from the free path:
MsgUpdateParams (/structs.structs.MsgUpdateParams) – this is a governance message and must pay normal fees even though it’s in the Structs module.MsgGuildCreate and MsgReactorRestart — ordinary priced gas; including either makes the whole transaction paid. Guild creation also has its separate vault-funded founding cost.If you want to see the canonical, complete list, read KnownStructsMessages in .references/structsd/app/ante/maps.go.
The chain treats staking as a first-class gameplay action because reactor staking is what creates and powers a player. To remove that economic friction the following six messages are free, against a separate 40 M meter:
cosmos.staking.v1beta1.MsgDelegatecosmos.staking.v1beta1.MsgUndelegatecosmos.staking.v1beta1.MsgBeginRedelegatecosmos.staking.v1beta1.MsgCancelUnbondingDelegationcosmos.staking.v1beta1.MsgCreateValidatorcosmos.staking.v1beta1.MsgEditValidatorConstraints:
StakingThrottleDecorator enforces one listed staking operation per address per transaction/block, whether the transaction is free or paid.DelegatorAddress for delegations, ValidatorAddress for validator messages).The staking throttle also rejects two listed staking operations from the same address inside one transaction (structs-ante 2042), and another in the same block (2043). It follows the message even in a mixed paid transaction. Batching or paying gas does not bypass it. Multi-message Structs reactor withdrawal has its own release rules; do not confuse those messages with raw Cosmos staking.
Anything not classified as free runs through the standard fee path:
bank, distribution, gov, slashing, evidence, IBC, etc.MsgUpdateParams, MsgGuildCreate or MsgReactorRestart in any combination.These pay fees in ualpha like any other Cosmos SDK chain. Set --fees or --gas-prices accordingly.
Even when a transaction is free, the rest of the ante chain still runs:
TxSizeDecorator) – rejects oversize transactions before any state read.MsgCountDecorator) – caps the number of messages per tx.CheckTxThrottleDecorator) – rate limits how many txs a single address can cram into the mempool per block (CheckTxAddrCap, default 5; node-local, not consensus; rejection code 2041). A transaction that mixes Structs messages with other modules’ messages counts toward it too. A second check predicts the per-player message cap at admission, so a tx that would push a player past 40 messages in a block is rejected at submission (ErrPlayerMsgCapExceeded, 2040) rather than accepted and failed later.StructsDecorator – looks up the player for the signing address, applies the static permission check from PermissionMap for messages that use it, enforces the per-player message cap (40 per block, shared by all of a player’s addresses and charged whether or not the handler succeeds), and short-circuits same-block lastAction collisions for ChargeMessages. The two recovery messages — MsgAddressRevoke and MsgPlayerUpdatePrimaryAddress — are exempt from the quota when sent from the player’s primary address, so a misbehaving delegate key can burn the block’s budget but can never block its own eviction. The signing address must already be registered as a player — if it resolves to no player, the message is rejected at ante with "address is not registered as a player" (ante error code 2010, app/ante/errors.go). This gate covers every /structs.structs.* game message; a funded-but-unregistered key can pay fees yet still be rejected. Plain Cosmos bank sends (cosmos.bank.v1beta1.MsgSend) are not Structs messages and are exempt, which is how a fresh key can be funded before it becomes a player. Client UIs should pre-check registration (GET /structs/address/{address} → non-1-0 playerId) before offering game actions.ThrottleDecorator – per-object throttles for proof-of-work messages, fleet move, planet explore, address register.StructActivate, StructAttack, StructBuildInitiate, StructTrash, StructDefenseClear/Set, StructMove, StructStealthActivate/Deactivate) – the ante allows one of these per player per block (ErrChargeAlreadyUsedThisBlock, 2021) and never two in one transaction (ErrDuplicateChargeInTx, 2020), so the free path cannot side-step the charge bar. StructBuildInitiate and StructTrash cost 1 charge (every struct type’s buildCharge is 1); StructDeactivate and StructDeactivateBatch cost no charge at all.The DynamicPermissionMessages set (e.g. all UGC messages, address/permission management, guild membership voting flows) skips the ante-level permission check and lets the handler enforce the right permission, since the bits depend on runtime fields. Dynamic permission resolution is separate from fee classification: MsgReactorRestart is dynamic and priced. Evaluate the complete transaction against the fee maps.
--gas auto --gas-adjustment 1.5 as mandatory on every structsd tx structs command. The free meter is tight enough that hand-tuning a low gas value is a footgun.MsgPlayerSend (Structs) and cosmos.bank.v1beta1.MsgSend (bank) in the same tx will pay fees. If you need to do both, send two transactions.uguild.* cannot travel over IBC or to an unregistered address. Eligible destinations are a registered player, a player or guild vault, the structs module account, or an indexed provider pool. Anything else rejects with recipient_not_eligible. Use player-send (or convert) rather than a Cosmos bank send onto IBC.account_sequence. One in-flight tx per address at a time still applies. Wait for the previous block (~6 s) before broadcasting the next.StakingThrottleDecorator (ErrDuplicateStakingInTx 2042 / ErrStakingAlreadySubmittedThisBlock 2043). Wait a block. The throttle now also applies when a staking message is paired with any other message in one tx.reactor-infuse, native staking delegate, migrations, cancelled defusions) are rejected with reactor stake cap exceeded (structs code 1613) when the validator would exceed 30 % of bonded stake while the cap is active (10+ bonded validators). Check structsd query structs reactor-stake-share [reactor-id] first.tx structs that reports insufficient funds while the key’s bank balances look healthy is almost always an empty vault.GasRouterDecorator; application HandlerOptions can configure them. These are not exposed as a guessed gameplay parameter query. Verify the executing release/configuration when diagnosing a cap.protocols/transactions.md – Transaction broadcast/verify flowtroubleshooting/transaction-issues.md – Common rejections and fixesFee/staking-throttle review: official structsd v0.22.0 96cea3a, app/ante/maps.go, gas_router.go, staking_throttle.go. Other gameplay-specific examples retain their own mechanics references.