Action Protocol

Version: 1.0.0
Category: Action
Status: Stable

Overview

The Action Protocol defines how AI agents should perform actions (transactions) in Structs. All actions are submitted as transactions to the consensus network.

Fees

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.

Base Configuration

{
  "baseUrl": "http://localhost:1317",
  "transactionEndpoint": "/cosmos/tx/v1beta1/txs",
  "timeout": 30000,
  "confirmationTimeout": 60000,
  "retryPolicy": {
    "maxRetries": 3,
    "retryDelay": 2000,
    "retryOn": ["timeout", "5xx", "network"]
  }
}

Transaction Flow

Step 1: Get Account Information

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.

Step 2: Build Transaction

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"
    }
  }
}

Step 3: Sign Transaction

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.

Step 4: Submit Transaction

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:

Step 5: Wait for Confirmation

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"
    ]
  }
}

Step 6: Handle Result

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.

Action Patterns

Pattern 1: Simple Action

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"
  ]
}

Pattern 2: Action with Follow-Up

Use Case: Action requires follow-up action

{
  "action": "MsgStructBuild",
  "followUp": {
    "action": "MsgStructBuildComplete",
    "waitFor": "struct in building state",
    "requires": ["proof-of-work"]
  }
}

Example Flow:

  1. Submit MsgStructBuild
  2. Wait for confirmation
  3. Query struct state (wait for building state)
  4. Compute proof-of-work
  5. Submit MsgStructBuildComplete

Pattern 3: Conditional Action

Use 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"
}

Pattern 4: Batch Actions

Use Case: Multiple related actions

{
  "actions": [
    {
      "@type": "/structs.structs.MsgStructBuild",
      ...
    },
    {
      "@type": "/structs.structs.MsgReactorInfuse",
      "structs:deprecated": "MsgReactorAllocate (deprecated - use MsgReactorInfuse)",
      ...
    }
  ],
  "submit": "single transaction"
}

Action Requirements

Common Requirements

{
  "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"
    }
  }
}

Proof-of-Work Requirements

Actions Requiring Proof-of-Work:

Proof-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"
    }
  }
}

Error Handling

Transaction Errors

{
  "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"
  }
}

Error Handling Strategy

{
  "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"
    }
  }
}

Best Practices

  1. Check Preconditions: Always check requirements before submitting
  2. Handle Errors: Always handle transaction errors
  3. Wait for Confirmation: Wait for confirmation before considering action complete
  4. Use Appropriate Broadcast Mode: Use SYNC for most cases, BLOCK for critical actions
  5. Batch When Possible: Batch related actions in single transaction
  6. Monitor Gas: Monitor gas usage and adjust gas limits
  7. Handle Sequence: Track and increment sequence numbers
  8. Proof-of-Work: Compute proof-of-work efficiently

Performance Optimization

Optimization 1: Pre-compute Proof-of-Work

{
  "strategy": "precompute",
  "when": "struct enters building state",
  "action": "start computing proof-of-work",
  "submit": "when ready"
}

Optimization 2: Batch Actions

{
  "strategy": "batch",
  "collect": "related actions",
  "submit": "single transaction",
  "benefit": "lower gas cost, atomic execution"
}

Optimization 3: Async Submission

{
  "strategy": "async",
  "submit": "BROADCAST_MODE_ASYNC",
  "poll": "separate thread",
  "benefit": "non-blocking"
}

Last Updated: December 7, 2025