> ## Documentation Index
> Fetch the complete documentation index at: https://developers.stellar.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Build and submit transactions

> Build unsigned transactions with `--build-only`, sign and inspect them offline, then submit with `stellar tx`.

Build a payment, sign it, and send it as three separate steps, so a person or an offline machine can approve it before anything is signed.

## Ask your agent

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
Build a payment from agent-1 to <ADDRESS> for 10 XLM without submitting it, then show me the unsigned XDR.
```

## Steps

1. Build, on a machine with network access:

   ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
   stellar tx new payment --source <SOURCE> --destination <ADDRESS> --amount <AMOUNT> \
     --network <NETWORK> --build-only > unsigned.xdr
   ```

   This writes unsigned base64 XDR to `unsigned.xdr` and exits. It contacts RPC to read the source account's sequence number, but nothing is signed and nothing is submitted.

2. Sign it. This step needs no RPC connection, so it is the one stage you can run offline. Always pass `--network` or `--network-passphrase` on this stage:

   ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
   stellar tx sign --sign-with-key <SOURCE> --network <NETWORK> < unsigned.xdr > signed.xdr
   ```

   A signature commits to the network passphrase. An unqualified `tx sign` falls back to the saved default network, which can silently be the wrong one and produces a valid-looking envelope that fails to submit.

3. Submit the signed envelope, back on a machine with network access:

   ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
   stellar tx send --network <NETWORK> < signed.xdr
   ```

When no one needs to review the envelope, the three stages compose into one pipeline:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx new payment --source <SOURCE> --destination <ADDRESS> --amount <AMOUNT> \
  --network <NETWORK> --build-only \
  | stellar tx sign --sign-with-key <SOURCE> --network <NETWORK> \
  | stellar tx send --network <NETWORK>
```

## What `--build-only` skips

Every `tx new <OPERATION>` accepts `--build-only`, and so do the `contract` commands that submit a transaction (`deploy`, `upload`, `invoke`, `extend`, `restore`, `asset deploy`). It stops before signing and submitting. Read-only commands such as `contract id asset` do not take it. Building still reads the source account's sequence number over RPC, so it needs network access and a funded source account. `token transfer` and `token approve` do not accept `--build-only`, so use `tx new payment --build-only` for a payment handoff. `tx sign`, `tx send`, `tx hash`, `tx simulate`, `tx decode`, and the other envelope commands read XDR from stdin, so build, sign, and send compose into a pipeline. `tx new` builds from flags and `tx fetch` takes `--hash`; neither reads stdin.

## Parsing the result

`tx new <OPERATION>` has no `--output` flag and writes nothing to stdout on success. The transaction hash appears only in the `ℹ️  Signing transaction: <HASH>` line on stderr, and `--quiet` removes that line along with everything else. This is not like `token transfer`, which prints the bare hash as its last stdout line and does support `--output json`.

To get a parseable receipt, let `tx send` produce it rather than trying to scrape an earlier stage. `tx send`, the last stage in step 3 above, returns JSON **on success only**, with top-level keys `status`, `ledger`, `application_order`, `fee_bump`, `tx_hash`, `created_at`, `envelope`, `result`, `result_meta`, and `events`. On success `status` is `SUCCESS`. The hash field is `tx_hash`, not `hash`. It has no `--output` flag, so the shape is always the indented one.

On a submission failure it writes **zero bytes to stdout** and puts the error on stderr. Empty stdout with a non-zero exit is therefore a definite failure, not an ambiguous one, and the exception below is specifically about the case where stdout has content you could not parse.

Never merge stderr into stdout here. `tx send` writes `ℹ️  Transaction hash is <HASH>` to stderr and the JSON receipt to stdout; `2>&1` interleaves them and breaks the parse. Unparseable content on stdout is not evidence of failure. The transaction may have already succeeded. Confirm with `stellar tx fetch result --hash <HASH> --network <NETWORK>` before retrying. Capture stderr to a file rather than discarding it; the hash you need for that check is only there.

## Inspection before signing

Base64 XDR is not human-readable. Showing someone `AAAAAgAAAABfEbrRS/av…` and calling it an approval step is not an approval step: they cannot see the destination, the amount, or whether anything has already signed it. Render the envelope first:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx decode --output json-formatted < unsigned.xdr
```

That prints `source_account`, `fee`, `seq_num`, `cond` (the timebounds, `"none"` if there are none), `memo`, the `operations` array, `ext`, and `signatures: []` on an unsigned envelope. `tx decode` needs no RPC connection. Do not skip `memo` when you review: it is the field that routes a deposit at an exchange, and a payment with the wrong memo reaches the right account and the wrong person.

**None of those fields is the network.** A mainnet payment and a testnet payment decode to an identical field set, so the rendered envelope alone cannot tell a reviewer which network they are approving. Carry the network alongside the envelope out of band: name it in the approval request, and keep testnet and mainnet envelopes in separate, differently named files. The account addresses are the only in-band hint, and they are a weak one, because the same key pair is valid on every network.

`stellar tx hash` then computes the envelope's hash without signing or submitting, also from stdin and also offline. Use it to confirm the thing you approved is the thing you are about to sign:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx hash --network <NETWORK> < unsigned.xdr
```

The hash is unchanged by signing, so the same value should come back after `tx sign` and from the network on submit.

Two `tx hash` calls compared against each other confirm the envelope is the same envelope, and nothing more. Both are computed from the passphrase you passed, so they agree even when `tx sign` used a different one.

**To catch a wrong-network signature before you submit, compare `tx hash` against `tx sign`'s own output.** `tx sign` prints `ℹ️  Signing transaction: <HASH>` to stderr, computed under the passphrase it actually used. Both commands run offline:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx hash --network-passphrase "<INTENDED PASSPHRASE>" < unsigned.xdr
stellar tx sign --sign-with-key <IDENTITY> --network-passphrase "<INTENDED PASSPHRASE>" \
  < unsigned.xdr > signed.xdr
```

Equal hashes mean the signature was made for the network you intended. Unequal means it was not, and the envelope is dead before you spend a submit on it.

Two conditions. Pass `--network-passphrase` explicitly to **both** commands: omit it on both and they resolve the same saved default, agree with each other, and pass on a wrong-network signature. And `--quiet` suppresses the `tx sign` line entirely, so the check and `--quiet` cannot be used together.

Check the destination while you are still at the build stage. A native `payment` cannot create an account, so paying an address that has never been funded fails at submit with `TxFailed` / `OpInner(Payment(NoDestination))` and exit code 1. In this flow that is the worst place to find out, because a human has already reviewed and approved the envelope. `stellar token balance --id native --account <ADDRESS> --output json` answers it for free, and `tx new create-account` is the operation for an account that does not exist yet.

## Air-gapped signing

Only `tx sign` and `tx hash` are genuinely offline. `--build-only` still needs RPC to read the source account's sequence number, so building cannot happen on a machine with no network access. The real split is three stages across as many machines as you want: build on a networked machine, sign on an offline machine, submit from a networked machine. Move only the unsigned XDR to the offline machine, sign it there, then move only the signed XDR back. The signing key never touching a networked machine is the actual security property, not that the whole pipeline avoids the network.

## Soroban paths need a simulation step

A Soroban invocation's resource footprint depends on what the contract does, so `stellar tx simulate --source-account <SOURCE>` runs the transaction against current ledger state and returns the resource fee and footprint it would need, without submitting. `contract invoke` runs this simulation for you automatically. Composing your own `tx new` pipeline for a Soroban call means running `simulate` yourself before `send`.

## 22 operations, one reference page

`stellar tx new <OPERATION>` covers 22 operations, from `payment` and `create-account` to DEX and sponsorship operations. Each shares this same build, sign, send composition and adds its own flags on top. Run `stellar tx new --help` for the full operation list.

## Common pitfalls

A pipeline breaks silently if an earlier stage fails but still writes something to stdout. Check the exit code of each stage, or run the pipeline one stage at a time the first time you compose it.

`tx send` can fail with `TxBadSeq`. The sequence number is fixed at build time, step 1, not at submit time, step 3. Anything else signing for the same source account in between, a concurrent agent or another one of your own commands, makes the envelope stale before it reaches step 3. `stellar tx update sequence-number next` fetches the source account's current sequence, increments it, and rewrites the envelope in place. It is a read-only network call.

**It keeps the signatures the envelope already carried.** Patching an envelope you have already signed and then signing again leaves two signatures on it, and the submit fails `TxBadAuthExtra`. Patch while the envelope is still unsigned, then sign:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx update sequence-number next --network <NETWORK> < unsigned.xdr > fixed.xdr
stellar tx sign --sign-with-key <IDENTITY> --network <NETWORK> < fixed.xdr | stellar tx send --network <NETWORK>
```

If the only copy you have is already signed, rebuild from step 1 instead. Measured on testnet, three steps each: patch-then-sign on an unsigned envelope succeeds, rebuild-then-sign succeeds, and patch-then-re-sign on a signed envelope fails `TxBadAuthExtra` every time.

Elapsed time alone is not the cause: only another transaction from the same source account advances the sequence. A long delay is harmless unless the envelope carries timebounds, which `tx decode` shows as `cond`.

**Two `--build-only` calls against the same source return the same sequence number.** `--build-only` reads the account's current sequence and adds one; it reserves nothing and advances nothing. An agent that batch-builds several envelopes before submitting any of them gets a set that all collide, and only the first can land. Build and submit one at a time, or patch each envelope with the command above before you send it.

## Related pages

* [Send tokens](https://developers.stellar.org/docs/tools/cli/agent-cli/guides/send-tokens.md)
* [Delegate spending](https://developers.stellar.org/docs/tools/cli/agent-cli/guides/delegate-spending.md)
* [Authority model](https://developers.stellar.org/docs/tools/cli/agent-cli/reference/authority-model.md)
* [Quickstart](https://developers.stellar.org/docs/tools/cli/agent-cli/quickstart.md)
