> ## 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.

# Delegate spending

> How much can your agent spend without you, and what is the worst case if it goes wrong. A funded account, an allowance, a co-signed vault, a zero-XLM agent, and a self-expiring grant, compared honestly.

Let an agent spend some of your money, with a cap and a deadline the network enforces, without ever holding your key.

## Ask your agent

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
Grant agent-1 an allowance of 25 USDC from treasury on testnet USDC, expiring in about a day, then
show me the allowance.
```

That prompt is [Pattern 2](#pattern-2-an-allowance). `treasury` is the funded account you control, and `agent-1` is your agent's own key from the [Quickstart](https://developers.stellar.org/docs/tools/cli/agent-cli/quickstart.md). The examples use testnet USDC, `USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5`, which has 7 decimals, so 25 USDC is `250000000`.

## Choose a pattern

Pick by two questions: can the agent act without you, and what is the worst case.

| What you want | Do this | Worst case | Human in the loop |
| - | - | - | - |
| Agent spends freely, small stakes | Its own funded account | That balance | Never |
| Agent spends freely, capped, from your money | [An allowance](#pattern-2-an-allowance) | The cap, until the deadline | Never |
| You approve each move | [A co-signed vault](#pattern-1-a-co-signed-vault) | Nothing moves without you | Every transaction |
| You approve each move, agent holds nothing | [A zero-XLM agent](#pattern-3-an-agent-with-no-xlm) | Nothing moves without you | Every transaction |
| Fund it for a limited time | [A self-expiring grant](#pattern-4-a-self-expiring-grant) | What you granted | Once |

Start with the first row if the stakes are small. `stellar keys generate agent-1 --network testnet --fund` is the whole setup.

Only the allowance delegates: the agent moves your money on its own, inside a cap the network enforces. The co-signed vault and the zero-XLM agent are approval workflows, where the agent proposes and you sign every transaction. A self-expiring grant is funding, and once claimed the money is the agent's outright. None of them gives a rolling window, a destination allow-list, or a per-period spend rule. See [What this does not give you](#what-this-does-not-give-you).

## Pattern 1: A co-signed vault

Add the agent's key to a funded account as a signer whose weight sits below the account's payment threshold. The agent can build and sign a transaction from that vault, and nothing moves until your key signs too. This is the only pattern here that bounds *who must agree*, and it covers every asset and every operation rather than one token.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx new set-options --source <VAULT> --signer <AGENT_ADDRESS> --signer-weight 1 \
  --master-weight 2 --low-threshold 2 --med-threshold 2 --high-threshold 2 --network <NETWORK>
```

The agent proposes with `--build-only` and signs with its own key. The result is one base64 string. You sign that same string and submit it:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
# the agent
stellar tx new payment --source <VAULT_ADDRESS> --destination <DESTINATION> \
  --amount <SMALLEST_UNIT> --network <NETWORK> --build-only \
  | stellar tx sign --sign-with-key <AGENT> --network <NETWORK> > proposal.xdr

# you
stellar tx sign --sign-with-key <VAULT> --network <NETWORK> < proposal.xdr \
  | stellar tx send --network <NETWORK>
```

Prove the bound before you trust it. Submit the agent's proposal without your signature:

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

If the agent submits with only its own signature, the network rejects it with `TxBadAuth`. Measured on testnet: no fee was charged and the vault's sequence number was unchanged afterward, so the same envelope can still be co-signed and sent. Co-signed, the identical transaction succeeded.

Match on `TxBadAuth`. It is a single greppable line, and it is a transaction-level signature-weight failure.

Do not match on `TxFailed([OpBadAuth])`. That is a paraphrase, not output: the operation-level failure prints as a multi-line Rust debug dump, so match on `OpBadAuth` alone.

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
❌ error: transaction submission failed: TxFailed(
    VecM(
        [
            OpBadAuth,
        ],
    ),
)
```

You reach the operation-level check only after the transaction-level one passes, which happens when a threshold is at or below the signer's weight. At or below, not below: the default low threshold of 1 against a weight-1 agent is exactly equal, and that is the most likely misconfiguration on this page.

The agent cannot promote itself. Raising a weight or changing a threshold is a high-threshold `set-options`, and it is rejected either way. On a correctly configured vault it fails with `TxBadAuth`; leave the low threshold at 1 and the same attempt fails with the `OpBadAuth` dump instead. The block holds in both cases, only the string you match on changes.

Read the account back rather than assuming the setup took:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar ledger entry fetch account --account <VAULT> --network <NETWORK> --output json
```

`thresholds` comes back as four hex bytes in the order master, low, medium, high. A correctly configured vault reads `02020202`; `02010202` means the low threshold is still 1. Four identical bytes prove nothing about the order, so to see it, set four distinct values: master 5, low 2, medium 3, high 4 reads back `05020304`.

Read `signers` alongside it, and know what it omits. **The master key is not in that array.** Its weight is the first byte of `thresholds`. A correct setup lists only the agent:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
"thresholds":"02020202","signers":[{"key":"G…AGENT","weight":1}]
```

An audit that answers "who can sign this vault" from `signers` alone therefore names the agent and misses the key that can drain it. After revoking, the array is `[]` on an account its owner still fully controls.

**This is not two of two.** Your key alone still moves everything: with master weight 2 against a threshold of 2 you are unilateral, and the agent's weight-1 signature is never required for anything. It is a proposal mechanism, not a co-signature, and losing the agent's key costs you no access to the vault.

Set `--low-threshold 2` deliberately. At the default of 1 an agent with weight 1 can run low-threshold operations alone, which includes `bump-sequence`, `claim-claimable-balance`, and `allow-trust`. Measured: with low threshold 1, a weight-1 agent bumped the vault's sequence by itself. The CLI exposes `allow-trust` as `set-trustline-flags`, so there is no `allow-trust` command to go looking for.

Revoke with one command, effective at the next ledger:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx new set-options --source <VAULT> --signer <AGENT_ADDRESS> --signer-weight 0 --network <NETWORK>
```

Revoking changes the workflow, and the co-signing pipeline above stops working. Once the agent's weight is 0 its signature matches no signer, and an envelope carrying it is rejected with `TxBadAuthExtra` even when you co-sign correctly. Measured on testnet. After revoking, stop routing through `tx sign --sign-with-key <AGENT>`: rebuild the transaction and sign it with your key alone.

This bounds who agrees, not how much. A co-signed payment for the vault's entire balance is still a valid payment, so pair it with an allowance when you need both.

## Pattern 2: An allowance

### Steps

1. Read the current ledger. `--expiration-ledger` is a ledger number, not a duration, so you need this first:

   ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
   stellar ledger latest --network <NETWORK> --output json
   ```

2. Convert your desired duration to ledgers. Both testnet and mainnet close a ledger roughly every 5 seconds, so a day is about 17280 ledgers. There is no `--expires-in` flag. You compute the target ledger yourself: current ledger plus that count.

   Write the number down. `approve` echoes it back as `live_until_ledger` in its event line, which is the cheapest place to read it. That line goes to stderr, so a `$(…)` capture drops it even in text mode, and `--output json` removes it entirely, leaving only `{"tx_hash":"…","result":null}`.

   If you did not record it, see [Reading an allowance back](#reading-an-allowance-back).

3. Confirm the spender exists before you grant anything. This is the only check that catches a typo'd or never-funded spender, and it only works before the `approve` lands:

   ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
   stellar token balance --id native --account <SPENDER> --network <NETWORK> --output json
   ```

   A number, even `0`, means the address exists on this network. `Error(Contract, #6)` means it does not. Stop and ask rather than approving against it.

4. Grant the allowance:

   ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
   stellar token approve --id <TOKEN> --from <OWNER> --spender <SPENDER> \
     --amount <AMOUNT> --expiration-ledger <LEDGER> --network <NETWORK>
   ```

5. Prove the cap binds before you trust it. `token transfer-from` has no simulate-only mode, so probe with `contract invoke` and `--send no`, which simulates without submitting and costs nothing. `contract invoke` needs the token's `C...` contract address, not `CODE:ISSUER`, so resolve it first:

   ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
   SAC=$(stellar contract id asset --asset <CODE:ISSUER|native> --network <NETWORK>)
   stellar contract invoke --id "$SAC" --source <SPENDER> --send no --network <NETWORK> \
     -- transfer_from --spender <SPENDER> --from <OWNER> --to <DEST> --amount <OVER_THE_CAP>
   ```

   It fails at simulation with `Error(Contract, #9)` and data `["not enough allowance to spend", <remaining>, <requested>]`, remaining before requested, and nothing reaches the network. Branch on the message text rather than the code: `#9` is not unique to an over-cap draw.

6. Confirm what is actually granted. Do not trust what you just sent. Read back what the network stored. Note what this step cannot do: `approve` to an address that was never funded succeeds, and every post-hoc check then confirms the typo as though it were correct. `token allowance`, `contract read --durability temporary`, `tx fetch events`, and a Horizon scan all report such a grant live, correctly sized, and correctly dated. Step 3 is the only thing that catches it.

   ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
   stellar token allowance --id <TOKEN> --from <OWNER> --spender <SPENDER> --network <NETWORK>
   ```

### Spending the allowance

The spender needs its own funded account. It is the `--source` of the draw, so it pays that transaction's fee even though the tokens come from the owner.

The spender does **not** need a trustline for the asset. Measured on testnet: a spender with no trustline completed a `transfer_from`. The `--to` destination is what needs one, exactly as it would to receive an ordinary payment, and a missing one there fails with `Error(Contract, #13)`. The two are easy to conflate because the simplest test has the agent draw to itself, which makes it both.

Draw on the allowance with `stellar token transfer-from`. `--id` takes the same token identifier as `approve`, including `CODE:ISSUER` and `native`:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar token transfer-from --id <TOKEN> --spender <SPENDER> --from <OWNER> \
  --to <DESTINATION> --amount <SMALLEST_UNIT> --network <NETWORK>
```

`--spender` must be an identity or secret key you control. It signs, authorizes the draw, and is the transaction's source, so it pays the fee. The allowance is reduced by the amount transferred. Read it back with `stellar token allowance` afterward, the same way you would after granting it. With `--output json` the result is `{"tx_hash":"…","result":null}`.

Do not reach for `stellar token transfer --from <OWNER> --sign-with-key <SPENDER>` instead. It fails with `{"error":{"type":"invoke","message":"...TxBadAuth..."}}`, because a plain transfer is authorized by the owner, not the spender.

Drawing more than the allowance holds fails at simulation with `Error(Contract, #9)` and exit code 1, and nothing is submitted. The diagnostic names both numbers, remaining first and requested second:

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
❌ error: transaction simulation failed: HostError: Error(Contract, #9)
   0: [Diagnostic Event] topics:[error, Error(Contract, #9)], data:["not enough allowance to spend", 210000000, 220000000]
```

That is the cap doing its job. It is enforced by the network, not by the agent's restraint, which is the whole reason to use an allowance instead of trusting a spending limit you wrote into a prompt.

`Error(Contract, #9)` is not unique to an over-cap draw. The same code comes back from `approve` when the expiration ledger is already in the past, with `data:["live_until must be >= ledger sequence", 4000000, 4610092]`. Branch on the message, never on the code alone, or a stale ledger read looks like an agent overspending.

An over-cap draw also has a second, costlier shape. Everything above assumes the draw is simulated before submission, which is what catches it for free. A transaction signed while the allowance was still good and submitted after it was revoked skips that check and fails at consensus instead:

```console theme={"theme":{"light":"github-light","dark":"github-dark"}}
❌ Error event: [{"symbol":"error"},{"error":{"contract":9}}] = {"vec":[{"string":"not enough allowance to spend"},{"i128":"0"},{"i128":"100000000"}]}
fee_charged: 17315, result: TxFailed([OpInner(InvokeHostFunction(Trapped))])
```

None of the strings above appear in that output, and the fee is charged. Match on `Trapped` plus the error event, and treat a fee-charged failure as proof the network refused rather than the client.

`token transfer-from` also has no `--build-only`. When you need the unsigned envelope, as in [Pattern 3](#pattern-3-an-agent-with-no-xlm), call the Stellar Asset Contract's `transfer_from` through `contract invoke`. `contract invoke --id` is stricter than `token --id`: it takes only a `C...` contract address or an alias, not `CODE:ISSUER`, so resolve the address with `stellar contract id asset --asset <CODE:ISSUER|native>` first. That command is a pure read and needs no source account.

### Reading an allowance back

`token allowance` returns the amount alone, but the expiry is not lost. Two ways to read it back, both measured on testnet.

From live state, with no transaction hash. The allowance lives in the token contract's temporary storage under a key of `["Allowance", {from, spender}]`, so encode that key and read it:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
KEY='{"vec":[{"symbol":"Allowance"},{"map":[
  {"key":{"symbol":"from"},"val":{"address":"<OWNER_ADDRESS>"}},
  {"key":{"symbol":"spender"},"val":{"address":"<SPENDER_ADDRESS>"}}]}]}'

stellar contract read --id <SAC> --durability temporary --network <NETWORK> \
  --key-xdr "$(echo "$KEY" | stellar xdr encode --type ScVal --output single-base64)"
```

That returns the amount and the deadline together, for example `{"amount":"80000000","live_until_ledger":4627378}`. Note `--durability temporary`: an allowance is not persistent storage.

That example is the value only. The command prints a four-field CSV row with no `--output` flag: the key, the value as doubled-quote-escaped JSON, the last-modified ledger, and the entry TTL. Passing `--output json` also returns CSV, with the ScVals expanded to multi-line XDR-JSON, so `| jq` fails either way.

**Do not read this as an audit of what the agent can spend.** The entry is not evicted when the deadline passes. `live_until_ledger` is the allowance's own deadline. The fourth column is the ledger entry's own TTL, which is set when the entry is written and has nothing to do with the deadline you chose, so how far it outlives that deadline is not a fixed quantity and no figure for it is worth quoting. Past the deadline this command still returns exit 0 and the original `amount`, for authority the network will not honor for one stroop. After a revoke the two read paths agree instead, showing `amount` 0 and `live_until_ledger` 0.

`stellar token allowance` returns `0` correctly at that same moment. Use it, not `contract read`, to answer what an agent may still spend.

From the grant transaction, if you have or can find its hash:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx fetch events --hash <APPROVE_HASH> --network <NETWORK> --output json
```

The deadline is the second element of the contract event's data vector, `contract_events[0][0].body.v0.data.vec[1].u32`. This works even when the `approve` itself ran with `--output json`.

Do not reach for `ledger entry fetch contract-data`: it ignores `--durability temporary` and hands back the persistent entry instead, so it will never show you an allowance.

## Amount replaces, it does not add

`--amount` on `approve` replaces the existing allowance. It does not add to it. Granting 10 after already granting 25 leaves the allowance at 10, not 35. The safe update pattern is read-then-replace: read the current allowance with `allowance`, decide the new total you want outstanding, then call `approve` with that total.

### Revocation

Revoke by approving zero: `--amount 0 --expiration-ledger 0`. Zero is the one case where the expiration ledger is allowed to be in the past, because a zero allowance has nothing left to expire.

### The spender can be a contract

`--spender` accepts a `C...` contract address as well as a `G...` account. A policy contract that enforces its own rules on top of the allowance can sit in the spender position.

## Pattern 3: An agent with no XLM

An agent account that holds no XLM cannot pay a transaction fee, so it cannot submit anything by itself. Create it with sponsored reserves, give it the asset it will spend or an allowance to draw on, and pay each transaction's fee yourself with a fee bump. Stopping the agent means declining to sign the next one.

Generate the agent's key without `--fund`. The sponsorship transaction below is what creates the account, and friendbot funding it first would defeat the point: the agent must hold no XLM. This is the one place where the [Quickstart](https://developers.stellar.org/docs/tools/cli/agent-cli/quickstart.md)'s `keys generate --fund` is the wrong move.

Create the account with a zero starting balance. The three operations must ride in one transaction, and the sponsored account signs for the `end` operation. `tx op add` does not raise the fee as it adds operations, so set the fee for three operations up front:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx new begin-sponsoring-future-reserves --source <TREASURY> --sponsored-id <AGENT_ADDRESS> \
  --network <NETWORK> --build-only --inclusion-fee 300 \
  | stellar tx op add create-account --source <TREASURY> --destination <AGENT_ADDRESS> --starting-balance 0 --network <NETWORK> \
  | stellar tx op add end-sponsoring-future-reserves --source <TREASURY> --op-source <AGENT_ADDRESS> --network <NETWORK> \
  | stellar tx sign --sign-with-key <TREASURY> --network <NETWORK> \
  | stellar tx sign --sign-with-key <AGENT> --network <NETWORK> \
  | stellar tx send --network <NETWORK>
```

Without `--inclusion-fee 300` this fails with `TxInsufficientFee`.

Prove the account is really sponsored, not merely empty. A zero balance alone says nothing:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar ledger entry fetch account --account <AGENT> --network <NETWORK> --output json
```

`balance` is `0`, and `ext` carries `num_sponsored` for the entries the treasury covers. Horizon reports the same account with a `sponsor` field naming the treasury.

Now give the agent something to spend. It needs a sponsored trustline for the asset, created by the same sandwich with `change-trust` as the middle operation:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx new begin-sponsoring-future-reserves --source <TREASURY> --sponsored-id <AGENT_ADDRESS> \
  --network <NETWORK> --build-only --inclusion-fee 300 \
  | stellar tx op add change-trust --source <TREASURY> --op-source <AGENT_ADDRESS> --line <CODE:ISSUER> --network <NETWORK> \
  | stellar tx op add end-sponsoring-future-reserves --source <TREASURY> --op-source <AGENT_ADDRESS> --network <NETWORK> \
  | stellar tx sign --sign-with-key <TREASURY> --network <NETWORK> \
  | stellar tx sign --sign-with-key <AGENT> --network <NETWORK> \
  | stellar tx send --network <NETWORK>
```

Then either send the agent the asset outright with `token transfer`, or grant it an allowance on the treasury's balance with [Pattern 2](#pattern-2-an-allowance). The allowance is the stronger pairing: the agent holds nothing at all, and each draw needs a fee bump you sign.

The agent builds and signs as usual. Submitting alone fails with `TxInsufficientBalance`, so it signs to a file instead of piping to `tx send`:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx new payment --source <AGENT> --destination <DESTINATION> --asset <CODE:ISSUER> \
  --amount <SMALLEST_UNIT> --network <NETWORK> --build-only \
  | stellar tx sign --sign-with-key <AGENT> --network <NETWORK> > agent-signed.xdr
```

`--asset` is not optional here even though the CLI defaults it. The default is `native`, and a native payment from this agent cannot succeed: it holds no XLM. Your fee bump is still charged, and the inner transaction fails with `Payment(Underfunded)` inside a `TxFeeBumpInnerFailed` dump. The fee bump and sponsorship are working; the fix is the `--asset` flag. A zero-XLM agent spends assets, never XLM.

There is no fee-bump command, so wrap that signed envelope through `tx decode` and `tx encode`. Set the treasury address in a shell variable first: a `<PLACEHOLDER>` written directly after `python3 -` lands where shell redirection goes and fails as a syntax error before Python ever runs.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
TREASURY_ADDRESS=<TREASURY_ADDRESS>

stellar tx decode --output json < agent-signed.xdr > inner.json
python3 - "$TREASURY_ADDRESS" <<'EOF'
import json, sys
inner = json.load(open("inner.json"))
fee = int(inner["tx"]["tx"]["fee"]) + 100
fb = {"tx_fee_bump": {"tx": {"fee_source": sys.argv[1], "fee": str(fee),
      "inner_tx": {"tx": inner["tx"]}, "ext": "v0"}, "signatures": []}}
json.dump(fb, open("bump.json", "w"))
EOF
stellar tx encode --input json < bump.json \
  | stellar tx sign --sign-with-key <TREASURY> --network <NETWORK> \
  | stellar tx send --network <NETWORK>
```

`tx sign` recognizes the fee bump and logs `Signing fee bump transaction`. The result carries `"fee_bump": true`, the inner transaction's `fee_charged` is `0`, and the agent's XLM balance stays at zero. Measured on testnet: a sponsored agent holding `0.0000000` XLM submitted both a classic asset payment and a Soroban `transfer_from` this way, with the treasury as `fee_account` on both. Classic here means a `CODE:ISSUER` asset. Native XLM is the one thing this agent can never send.

Soroban works too, which is how a zero-XLM agent spends an allowance. `token transfer-from` cannot stop before submitting, so build the draw with `contract invoke` instead. `contract invoke --build-only` writes an unsimulated envelope with no resource fee and no authorization entries, so pipe it through `tx simulate` before signing:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar contract invoke --id <SAC> --source <AGENT> --network <NETWORK> --build-only \
  -- transfer_from --spender <AGENT_ADDRESS> --from <OWNER_ADDRESS> --to <DESTINATION> --amount <SMALLEST_UNIT> \
  | stellar tx simulate --source <AGENT> --network <NETWORK> \
  | stellar tx sign --sign-with-key <AGENT> --network <NETWORK> > agent-signed.xdr
```

Do not plan to revoke the sponsorship as a kill switch:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx new revoke-sponsorship --source <TREASURY> --account-id <AGENT_ADDRESS> --network <NETWORK>
```

`--account-id` is required for every form; add `--asset <CODE:ISSUER>` to target the trustline rather than the account entry. `revoke-sponsorship` against an account with no XLM fails, because the account cannot take over a reserve it cannot pay. Measured on testnet, it fails for both the account entry and the trustline. Unlike the other errors in this section, this one is not a single greppable line: it prints a multi-line Rust debug dump with `RevokeSponsorship(` and `LowReserve,` on separate lines, so match on `LowReserve` alone. The fee bump you decline to sign is the control.

## Pattern 4: A self-expiring grant

A claimable balance parks funds the agent may claim until a deadline. Add yourself as a second, unconditional claimant, or an unclaimed balance is stranded once the deadline passes.

The claimant needs a trustline for the asset before it can claim, exactly as it would to receive an ordinary payment. Without one the claim fails with `ClaimClaimableBalance(NoTrust)` after the grant already exists. Native XLM needs none.

Build, sign, and send as a pipeline. `tx new create-claimable-balance` on its own submits the transaction but prints nothing to stdout, and the balance id you need comes back in `tx send`'s result:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx new create-claimable-balance --source <TREASURY> --asset <CODE:ISSUER> --amount <SMALLEST_UNIT> \
  --claimant '<AGENT_ADDRESS>:{"before_absolute_time":"<UNIX_SECONDS>"}' \
  --claimant <TREASURY_ADDRESS> \
  --network <NETWORK> --build-only \
  | stellar tx sign --sign-with-key <TREASURY> --network <NETWORK> \
  | stellar tx send --network <NETWORK>
```

Write the treasury claimant as a bare address, with no predicate. `{"unconditional":true}` is rejected with `Invalid predicate JSON: invalid type: boolean true, expected unit`; the forms that parse are `{"unconditional":null}` and `"unconditional"`.

The id is buried in `tx send`'s result rather than printed on its own line. Pull it out with:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
… | stellar tx send --network <NETWORK> \
  | jq -r '.result.result.tx_success[0].op_inner.create_claimable_balance.success'
```

If you lose that output, `stellar tx fetch result --hash <HASH> --network <NETWORK> --output json` returns the same value from the transaction later.

The id arrives in three incompatible encodings, and the claim command takes only one. `tx send` returns a `B…` strkey. Horizon reports 72 hex characters, which are eight leading zeros followed by the 64 that matter. `claim-claimable-balance` accepts the 64-character hex and nothing else: it rejects the `B…` with `invalid hex for balance-id: B…` and rejects the 72-hex too. `ledger entry fetch claimable-balance --id` has the same restriction, while `clawback-claimable-balance` accepts all three.

`strkey decode` is the bridge from what you were handed to what the claim command wants:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar strkey decode <B_STRKEY> | jq -r '.claimable_balance.v0'
```

That prints the 64-hex. Coming from Horizon instead, drop the first 8 characters of its 72.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar tx new claim-claimable-balance --source <AGENT> --balance-id <64_HEX> --network <NETWORK>
```

This bounds how long the offer stands, not what the agent does with the money afterward. Once claimed, the funds are the agent's outright. Use it to fund an agent for a bounded window, not as a spend cap.

The deadline binds the agent, not you. An unconditional treasury claimant is unconditional in time too, so you can take the grant back seconds after creating it, not only after it expires. Measured on testnet: a grant with a 24-hour window was reclaimed by the treasury immediately.

Nothing happens automatically at the deadline. The entry stays on the ledger, the agent's claim starts failing with `ClaimClaimableBalance(CannotClaim)` and is still charged its fee, and the treasury reclaims with the same command against a different `--source`. Without that second claimant the entry is stranded: it sits there consuming a reserve with nobody able to claim it.

## If you issue the asset

For an asset you issue, the trustline is yours to switch off. Set `--set-required`, `--set-revocable`, and `--set-clawback-enabled` on the issuer once. After that each agent's trustline needs `set-trustline-flags --set-authorize` before it can hold the asset, `--clear-authorize` freezes it, and `clawback` takes the balance back. Measured on testnet: a frozen agent's next payment failed with `Payment(SrcNotAuthorized)`, and a clawback of 50 units of the agent's balance succeeded. All of it works with the agent's key still in the agent's hands, and none of it applies to USDC or any asset someone else issues.

## What this does not give you

An allowance caps one spender's draw on one token from one owner, with a ledger deadline. It gives no rolling time window, no per-counterparty rule beyond that single spender, no allow-list of destinations the spender can pay out to, and no fiat-denominated limit. It has no notion of an agent session either: an allowance outlives however long you intended the agent to run, until its expiration ledger passes or you revoke it.

None of the other three patterns closes those gaps. A co-signed vault makes you approve each spend, which is a stronger control and a slower one; it does not encode a rule you can walk away from. Nothing here gives you a policy engine.

Two things look like controls and are not. **Muxed accounts** (`M…`) are labels on one key, not sub-identities: a payment to or from one works, and the same secret signs for every mux id, so they record which agent a payment was for and bound nothing. The CLI does not round-trip one either: `keys add --public-key <M_ADDRESS>` saves it, and `keys address` reads back the underlying `G…` with the mux id dropped. **Smart wallets** are not a CLI feature today. The CLI can deploy a custom-account contract and send funds to it, but it cannot sign that contract's authorization entry: `contract invoke` stops with `Missing signing key for account C…`, and the `tx sign` path leaves the entry unsigned so the transaction fails onchain inside `__check_auth`. Measured on testnet against a deployed v1 smart wallet holding real balance. Do not move funds into a contract account from the CLI expecting to move them out the same way.

## Common pitfalls

Setting `--expiration-ledger` behind the current ledger, with a positive amount, is rejected. Fetch the current ledger first and add your ledger count to it. Do not guess.

## Related pages

* [Send tokens](https://developers.stellar.org/docs/tools/cli/agent-cli/guides/send-tokens.md)
* [Check balances and metadata](https://developers.stellar.org/docs/tools/cli/agent-cli/guides/check-balances-and-metadata.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)
