# Transactions Constructing, simulating, signing, broadcasting, and recovering from failed Juno transactions. This is the load-bearing reference for anything that signs. ## Anatomy of a tx Every Cosmos transaction is: - **Messages** — one or more typed messages (e.g. `MsgSend`, `MsgExecuteContract`). The chain validates each. - **Signers** — the accounts whose private keys must sign. Usually `info.sender` of the first message. - **Fee** — gas limit × gas price, paid in `ujuno`. - **Memo** — optional human-readable string. Use it to bind intent: `--note 'pay vendor X for invoice #42'`. - **Sequence number** — per-account counter. Mismatch = "account sequence mismatch" error (the #1 retry-failure mode). - **Timeout height** — optional. Tx auto-fails if not included by this height. `junod tx ` wraps all of this. The flags are common across modules. ## The canonical sign-and-broadcast ```bash junod tx bank send \ juno1 juno1 1000000ujuno \ --from \ --keyring-backend test \ --keyring-dir /workspace/.juno-agent \ --chain-id juno-1 \ --node https://juno-rpc.publicnode.com:443 \ --gas auto --gas-adjustment 1.4 \ --gas-prices 0.075ujuno \ --note 'descriptive memo' \ --yes ``` Walk through each flag: | Flag | Why | |---|---| | `--from ` | Which keyring entry signs. Must match a key in `junod keys list`. | | `--keyring-backend` / `--keyring-dir` | Match how the key was created. Defaults will silently look in `~/.juno/keyring-os/`. | | `--chain-id` | Required. Mismatch = "incorrect chain-id" error. Same key signs both `juno-1` and `uni-7`; this flag is the safety. | | `--node` | RPC endpoint. Sticky for a session — switching mid-broadcast can hit sequence-mismatch. | | `--gas auto --gas-adjustment 1.4` | Simulate gas, then add 40% headroom. `1.4` is comfortable for bank/wasm-exec; bump to `1.5` for wasm-store. | | `--gas-prices 0.075ujuno` | Multiplied by gas to compute the fee. The chain rejects below `feemarket params.min_base_gas_price`. | | `--note` (a.k.a. `--memo`) | Recorded on-chain. Useful for audit logs and downstream parsers. | | `--yes` | Skip the y/N confirmation prompt. Suppress only when you're confident. | Alternative fee specification: ```bash --fees 6000ujuno # flat fee instead of gas-prices × gas ``` Use `--fees` when the gas estimate is unstable (e.g., contract execution with variable storage cost) and you want a hard fee ceiling. ## Dry-run first (strongly recommended) `--dry-run` simulates the tx end-to-end on the RPC node and returns the gas estimate without broadcasting: ```bash junod tx bank send 1000000ujuno \ --from --chain-id juno-1 --node $RPC \ --gas auto --gas-adjustment 1.4 --gas-prices 0.075ujuno \ --dry-run # stdout: "gas estimate: 89734" ``` The simulation catches: - Insufficient balance (account doesn't have what it claims to send) - Address validity (bad bech32 → fails here, not on broadcast) - Contract execution errors (wasm-execute will simulate the contract call and return any error) - Gas-too-low scenarios (estimate will be enormous if the tx would fail mid-execution and roll back) It does **not** catch: - Sequence-mismatch errors (simulator uses the latest sequence; broadcast races with other pending txs from the same sender) - Fee-too-low (the simulator doesn't enforce min gas price; broadcast does) - Race conditions on contract state (the simulator uses a recent block; another tx may change state before yours lands) ## Generate-only — full inspection before signing For maximum scrutiny, separate construction from signing: ```bash # 1. Build the unsigned tx junod tx bank send 1000000ujuno \ --from --chain-id juno-1 --node $RPC \ --gas auto --gas-adjustment 1.4 --gas-prices 0.075ujuno \ --generate-only > unsigned.json # 2. Inspect cat unsigned.json | jq # 3. Sign offline junod tx sign unsigned.json \ --from --chain-id juno-1 \ --keyring-backend test --keyring-dir > signed.json # 4. Broadcast junod tx broadcast signed.json --node $RPC ``` Use this pattern for: - Multi-sig flows (each signer signs the same generated tx independently) - Air-gapped signing (sign on an offline machine, transfer signed.json, broadcast on online machine) - High-value txs where you want to manually verify message contents before signing ## Broadcast modes `--broadcast-mode` controls when junod returns: | Mode | When the command returns | Use case | |---|---|---| | `sync` (default) | After the tx is added to the mempool + basic CheckTx | Most common. The txhash is real but you must query for inclusion. | | `async` | Immediately, no mempool feedback | Fire-and-forget. Rare. | | `block` (deprecated, removed in v0.50) | After the tx is included in a block | Was useful, no longer exists. | In SDK v0.50+, `block` mode is gone. The replacement is `sync` + poll for inclusion: ```bash TXHASH= for i in 1 2 3 4 5; do RESULT=$(junod query tx "$TXHASH" --node $RPC -o json 2>/dev/null) if [ -n "$RESULT" ]; then echo "$RESULT" | jq -r '.code, .raw_log' break fi sleep 3 done ``` Block time is ~5–6s, so 2–3 polls usually suffice. ## Reading tx output A successful broadcast returns JSON (with `-o json`) like: ```json { "height": "0", "txhash": "ABC123...", "code": 0, "raw_log": "", "gas_wanted": "200000", "gas_used": "0" } ``` - `code: 0` and `height: 0` immediately after broadcast = "accepted by mempool, not yet in a block." Query the hash to confirm inclusion. - `code: non-zero` = rejected. `raw_log` has the reason. Common `code` values: - `0` — success - `5` — insufficient funds - `11` — out of gas - `13` — insufficient fee - `32` — account sequence mismatch - `19` — tx already in mempool After inclusion, `gas_used` reflects actual consumption and the events array shows what happened (transfers, wasm calls, etc.). ## Recovering from failures ### `account sequence mismatch` The chain expected sequence N but your tx claimed sequence M. Causes: - A prior tx is still in the mempool with the next sequence — wait for it to land, then retry. - The chain-side account has been updated by another tx you didn't issue (rare for a single-key agent; happens if a script and a human share the same key). Fix: ```bash junod query account --node $RPC -o json | jq '.sequence' # explicitly pass --sequence in your next tx, or wait and retry without --sequence (junod queries it fresh) ``` ### `insufficient fees` The chain's min gas price has risen above your `--gas-prices`. Query `feemarket params` (or `globalfee params` on older chains) and adjust. Alternatively, use `--fees` with a larger flat value. ### `out of gas` Your `--gas-adjustment` was too low. Increase to 1.5 or 1.6. For wasm execution with conditional branches that consume more gas, bumping to 1.8–2.0 is reasonable. ### `account not found` The address has never received a tx — the chain has no account entry yet. Send any small inbound tx to the address first (faucet on testnet; a tiny transfer on mainnet), then retry. ### `tx already in mempool` You broadcast the same tx twice. The second is a no-op. Query the hash; if the first is included, you're done. ## Multi-message txs You can compose multiple messages into one tx (atomic — all succeed or all roll back): ```bash # Generate-only first junod tx bank send 100ujuno --generate-only ... > msg1.json junod tx bank send 200ujuno --generate-only ... > msg2.json # Combine junod tx multi-sign-batch msg1.json msg2.json ... (varies by version) ``` In practice, multi-message txs are easier to construct programmatically (via the protobuf SDK in Go/TS/Rust). For CLI workflows, stick to single-message txs. ## Memo discipline Use `--note` (memo) for every tx that has identity behind it. Patterns: ``` "Juno AI: " "agent-id: | reason: " "invoice-42 final payment" ``` Memos are on-chain forever. Don't put anything you wouldn't post publicly. ## Audit log pattern After every signed tx, append to a local `onchain-log.md` (or equivalent). One block per tx: ```markdown ## 2026-05-15 14:32 UTC — bank send (juno-1) - **Tx hash:** ABC123... - **From:** juno1... - **To:** juno1... - **Amount:** 1.0 JUNO (1,000,000 ujuno) - **Fee:** 5685 ujuno (89734 gas × 0.075) - **Memo:** descriptive memo - **Purpose:** one-line context - **Explorer:** https://www.mintscan.io/juno/tx/ABC123... ``` The chain is canonical for state; the log captures intent and context. Reviewing the log is faster than reconstructing intent from on-chain events. ## What this skill teaches; what you decide - **Dry-run before broadcast** is a strong recommendation for any first-time message shape or any tx above trivial value. The skill teaches it; you choose whether to apply. - **Memo every tx with intent** keeps the audit trail readable. Optional but cheap. - **One RPC per session** avoids sequence-race surprises. Switching mid-sequence is the most common foot-gun after a successful tx. Next: contract operations → [`cosmwasm.md`](cosmwasm.md). DAO DAO ops → [`dao-dao.md`](dao-dao.md).