Version: 1.0.0
Category: Action
Status: Stable
The Action Protocol defines how AI agents should perform actions (transactions) in Structs. All actions are submitted as transactions to the consensus network.
As of v0.16.0, transactions composed entirely of structs module messages (excluding MsgUpdateParams) are routed through a free-gas meter and require no ualpha fee. Pure-staking transactions (MsgDelegate, MsgUndelegate, MsgBeginRedelegate, MsgCancelUnbondingDelegation, MsgCreateValidator, MsgEditValidator) are also free, capped at one per signer per block. Mixed transactions and other Cosmos messages still pay fees in ualpha. The transaction examples below intentionally use empty fee.amount arrays for free Structs txs. Always include --gas auto --gas-adjustment 1.5 (or set gas_limit explicitly) so the simulator can size the meter correctly. See knowledge/mechanics/transactions.md for the full ante handler routing rules.
{
"baseUrl": "http://localhost:1317",
"transactionEndpoint": "/cosmos/tx/v1beta1/txs",
"timeout": 30000,
"confirmationTimeout": 60000,
"retryPolicy": {
"maxRetries": 3,
"retryDelay": 2000,
"retryOn": ["timeout", "5xx", "network"]
}
}
Format: Query account to get account_number and sequence
{
"request": {
"method": "GET",
"url": "http://localhost:1317/cosmos/auth/v1beta1/accounts/{address}",
"headers": {
"Accept": "application/json"
}
},
"response": {
"status": 200,
"body": {
"account": {
"@type": "/cosmos.auth.v1beta1.BaseAccount",
"address": "structs1abc...",
"pub_key": {
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "base64_public_key"
},
"account_number": "0",
"sequence": "5"
}
}
}
}
Extract: Store account_number and sequence for transaction signing.
Format: Create transaction body with message(s) and auth_info
{
"body": {
"messages": [
{
"@type": "/structs.structs.MsgStructBuild",
"creator": "structs1abc...",
"structType": "1",
"locationType": 1,
"locationId": "1-1"
}
],
"memo": "",
"timeout_height": "0"
},
"auth_info": {
"signer_infos": [
{
"public_key": {
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "base64_public_key"
},
"mode_info": {
"single": {
"mode": "SIGN_MODE_DIRECT"
}
},
"sequence": "5"
}
],
"fee": {
"amount": [],
"gas_limit": "200000",
"payer": "",
"granter": ""
}
}
}
Multiple Messages: Can include multiple messages in one transaction
{
"body": {
"messages": [
{
"@type": "/structs.structs.MsgStructBuild",
"creator": "structs1abc...",
"structType": "1",
"locationType": 1,
"locationId": "1-1"
},
{
"@type": "/structs.structs.MsgReactorInfuse",
"creator": "structs1abc...",
"reactorId": "3-1",
"allocation": "..."
}
],
"memo": "",
"timeout_height": "0"
},
"auth_info": {
"signer_infos": [
{
"public_key": {
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "base64_public_key"
},
"mode_info": {
"single": {
"mode": "SIGN_MODE_DIRECT"
}
},
"sequence": "5"
}
],
"fee": {
"amount": [],
"gas_limit": "200000"
}
}
}
Format: Sign transaction bytes with player’s private key using secp256k1
{
"signing": {
"method": "secp256k1",
"privateKey": "0x...",
"message": "transaction_bytes",
"chainId": "structs-testnet",
"accountNumber": "0",
"sequence": "5",
"result": "base64_signature"
},
"signedTransaction": {
"body": {
"messages": [
{
"@type": "/structs.structs.MsgStructBuild",
"creator": "structs1abc...",
"structType": "1",
"locationType": 1,
"locationId": "1-1"
}
],
"memo": ""
},
"auth_info": {
"signer_infos": [
{
"public_key": {
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "base64_public_key"
},
"mode_info": {
"single": {
"mode": "SIGN_MODE_DIRECT"
}
},
"sequence": "5"
}
],
"fee": {
"amount": [],
"gas_limit": "200000"
}
},
"signatures": [
"base64_signature"
]
}
}
Note: The transaction must be serialized to bytes before signing. The signature is then added to the signatures array.
Format: POST signed transaction (base64-encoded) to endpoint
{
"request": {
"method": "POST",
"url": "http://localhost:1317/cosmos/tx/v1beta1/txs",
"headers": {
"Content-Type": "application/json"
},
"body": {
"tx_bytes": "base64_encoded_signed_transaction",
"mode": "BROADCAST_MODE_SYNC"
}
},
"response": {
"status": 200,
"body": {
"tx_response": {
"code": 0,
"txhash": "ABC123...",
"height": 12345,
"gas_used": "150000",
"gas_wanted": "200000"
}
}
}
}
Note: tx_bytes must be the complete signed transaction serialized and base64-encoded.
Broadcast Modes:
BROADCAST_MODE_SYNC: Wait for CheckTx (fast, no confirmation)BROADCAST_MODE_ASYNC: Don’t wait (fastest, no confirmation)BROADCAST_MODE_BLOCK: Wait for block inclusion (slowest, confirmed)Format: Poll transaction status using txhash
{
"poll": {
"endpoint": "GET http://localhost:1317/cosmos/tx/v1beta1/txs/{txhash}",
"interval": 1000,
"maxAttempts": 60,
"successCondition": "tx_response.code == 0",
"failureCondition": "tx_response.code != 0"
},
"response": {
"status": 200,
"body": {
"tx_response": {
"code": 0,
"txhash": "ABC123...",
"height": 12345,
"gas_used": "150000",
"gas_wanted": "200000"
}
}
}
}
Sequence Error Handling: If transaction fails with sequence mismatch:
{
"error": {
"code": 4,
"raw_log": "sequence mismatch: expected 5, got 4"
},
"recovery": {
"action": "Get current account sequence",
"steps": [
"1. Query account again: GET /cosmos/auth/v1beta1/accounts/{address}",
"2. Update sequence in transaction",
"3. Re-sign transaction",
"4. Resubmit transaction"
]
}
}
Success Response (code: 0):
{
"tx_response": {
"code": 0,
"txhash": "ABC123...",
"height": 12345,
"gas_used": "150000",
"gas_wanted": "200000"
},
"handling": {
"action": "Transaction accepted",
"next": "Monitor for block inclusion if using BROADCAST_MODE_SYNC"
}
}
Error Response (code != 0):
{
"tx_response": {
"code": 1,
"codespace": "sdk",
"raw_log": "insufficient funds: insufficient account funds",
"txhash": "ABC123..."
},
"handling": {
"action": "Check tx_response.code and raw_log for error details",
"recovery": "Address insufficient funds issue before retrying"
}
}
Related Examples: See ai/examples/auth/consensus-transaction-signing.md for complete working examples.
Use Case: Single action, no dependencies
{
"action": "MsgStructBuild",
"steps": [
"1. Build transaction",
"2. Sign transaction",
"3. Submit transaction",
"4. Wait for confirmation",
"5. Handle result"
]
}
Use Case: Action requires follow-up action
{
"action": "MsgStructBuild",
"followUp": {
"action": "MsgStructBuildComplete",
"waitFor": "struct in building state",
"requires": ["proof-of-work"]
}
}
Example Flow:
MsgStructBuildMsgStructBuildCompleteUse Case: Action depends on game state
{
"action": "MsgStructAttack",
"preconditions": [
{
"check": "GET /structs/struct/{structId}",
"condition": "struct.operatingAmbit >= distance to target",
"ifFalse": "abort"
},
{
"check": "GET /structs/player/{playerId}",
"condition": "player.halted == false",
"ifFalse": "abort"
}
],
"then": "submit action"
}
Use Case: Multiple related actions
{
"actions": [
{
"@type": "/structs.structs.MsgStructBuild",
...
},
{
"@type": "/structs.structs.MsgReactorInfuse",
"structs:deprecated": "MsgReactorAllocate (deprecated - use MsgReactorInfuse)",
...
}
],
"submit": "single transaction"
}
{
"requirements": {
"playerOnline": {
"check": "GET /structs/player/{playerId}",
"condition": "player.halted == false",
"error": "PLAYER_HALTED"
},
"sufficientResources": {
"check": "GET /structs/player/{playerId}",
"condition": "player.playerInventory.resources >= required",
"error": "INSUFFICIENT_RESOURCES"
},
"sufficientCharge": {
"check": "GET /structs/struct/{structId}",
"condition": "struct.charge >= required",
"error": "INSUFFICIENT_CHARGE"
},
"validLocation": {
"check": "GET /structs/planet/{planetId}",
"condition": "planet exists and accessible",
"error": "INVALID_LOCATION"
}
}
}
Actions Requiring Proof-of-Work:
MsgStructBuildCompleteMsgPlanetRaidCompleteProof-of-Work Process:
{
"proofOfWork": {
"difficulty": "from struct type or planet",
"compute": {
"algorithm": "hash",
"input": "structId + nonce",
"target": "difficulty threshold"
},
"submit": {
"hash": "computed hash",
"nonce": "nonce that produces hash"
}
}
}
{
"errorCodes": {
"0": "SUCCESS",
"1": "GENERAL_ERROR",
"2": "INSUFFICIENT_FUNDS",
"3": "INVALID_SIGNATURE",
"4": "INSUFFICIENT_GAS",
"5": "INVALID_MESSAGE",
"6": "PLAYER_HALTED",
"7": "INSUFFICIENT_CHARGE",
"8": "INVALID_LOCATION",
"9": "INVALID_TARGET"
}
}
{
"onError": {
"INSUFFICIENT_FUNDS": {
"action": "log",
"retry": false,
"fallback": "wait for resources"
},
"PLAYER_HALTED": {
"action": "log",
"retry": false,
"fallback": "wait for player online"
},
"INSUFFICIENT_CHARGE": {
"action": "log",
"retry": false,
"fallback": "wait for charge"
},
"NETWORK_ERROR": {
"action": "retry",
"maxRetries": 3,
"backoff": "exponential"
}
}
}
{
"strategy": "precompute",
"when": "struct enters building state",
"action": "start computing proof-of-work",
"submit": "when ready"
}
{
"strategy": "batch",
"collect": "related actions",
"submit": "single transaction",
"benefit": "lower gas cost, atomic execution"
}
{
"strategy": "async",
"submit": "BROADCAST_MODE_ASYNC",
"poll": "separate thread",
"benefit": "non-blocking"
}
Last Updated: December 7, 2025