In plain words
Most of what happens on a blockchain is public: anyone can look up who sent how much to whom. H081 is a bank account where your agent's balance, and its payments inside the bank, are private. Only your agent's key can read them.
Some steps still touch the public chain, like putting money in. This page shows exactly what is public and what is not, the one habit that keeps you private (withdraw to a fresh wallet), where your key lives, and the safety limits you can set.
What the public chain sees
Think of the bank as a closed room. Walking in and walking out happen on the street, where anyone can watch. What happens inside the room is hidden.
| What you do | Who sends it | Anyone can see | Stays private |
|---|---|---|---|
| Deposit (wallet → bank) | Your agent's key | Your agent wallet and the amount | What you do with the money afterwards |
| Withdraw through the relayer (the default) | The H081 relayer | The wallet that receives, and the amount it receives | Your agent wallet: it is not shown as the sender |
Withdraw with --direct | Your agent's key | Your agent wallet as the sender, the receiving wallet and the amount | Your remaining balance |
Transfer or payment to an h081-address: code (another H081 account) | Your agent's key | That your agent wallet used the bank | To whom, and how much |
| Payment from a budget to a normal wallet | The H081 relayer | The wallet that receives, and the amount | Which account paid |
| Give a budget (allowance) | Your agent's key | That your agent wallet made a grant | The amount, the rules and who got it |
| Lend | Your agent's key | Your agent wallet as the lender, the amount lent and the borrower's payout account | The borrower's budget and payments |
Relayer: a server run by H081 at https://api.h081.xyz that sends withdrawals for you. It receives a finished proof, never your key, and charges 0.05 USDG or 0.20%, whichever is more.
- You
Can anyone see how much is in my H081 account?
- Claude
uses
h081_bank_balanceNo. Your private balance is 3 USDG, and only your agent's key can read it.
What is public: each deposit (your agent wallet and the amount) and each withdrawal (the receiving wallet and the amount). A withdrawal through the relayer does not show your agent wallet as the sender.
The fresh-wallet rule
Withdraw to a wallet that has never touched your agent. If money goes in from one wallet and comes straight back out to the same wallet, anyone can connect the two, and the privacy is gone.
The mistake, step by step
- Your personal wallet
7Hk…sends 10 USDG to your agent wallet. Public. - Your agent deposits 10 USDG into the bank. Public: agent wallet, 10 USDG.
- Five minutes later you withdraw 10 USDG to
7Hk…. Public:7Hk…received 9.95 USDG from the relayer.
Anyone watching sees 10 USDG leave 7Hk… and almost 10 USDG come back to 7Hk… minutes later. The bank hid nothing.
The same goes for --to self: "self" is your agent wallet, which is the wallet that deposited.
Doing it right
Make a new wallet
In your wallet app, create a new account. It must never have sent to, or received from, your agent wallet or your personal wallet.
Wait a while
Do not withdraw right after depositing. The more time passes, and the more other people use the bank in between, the harder it is to match your withdrawal to your deposit.
Check the fee, then see the plan
Without
--yesnothing is sent. The plan shows the fee, what arrives and what is public.fee quote (nothing is sent) npx -y @h081lll/agent fee 10the plan (nothing is sent) npx -y @h081lll/agent withdraw 10 --to <a wallet that never touched the agent>response (shortened) { "ok": true, "network": "mainnet-beta", "status": "plan", "result": { "op": "withdraw", "usdg": "10", "to": "<fresh wallet>", "fee": "0.05", "payout": "9.95", "publicOnChain": ["recipient", "amount received"], "sentBy": "relayer", "warnings": ["If <fresh wallet> has no USDG account yet, the relayer adds 1 USDG to create it."], "next": "Nothing was sent. ..." }, "requestId": "..." }Send it
send npx -y @h081lll/agent withdraw 10 --to <a wallet that never touched the agent> --yesA fresh wallet often has no USDG account yet. Then the relayer takes 1 USDG more to create one; the plan says so under
warnings.
The same withdrawal from code, plan first:
TOKEN=$(cat ~/.h081/api-token)
# 1 · the plan: no "confirm", so nothing is sent
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"usdg":"10","to":"<fresh wallet>"}' http://127.0.0.1:8081/v1/withdraw
# 2 · send it, with a key that makes a retry safe
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: wd-fresh-1" \
-d '{"usdg":"10","to":"<fresh wallet>","confirm":true}' http://127.0.0.1:8081/v1/withdraw$h = @{ Authorization = "Bearer $(Get-Content $HOME\.h081\api-token)" }
# 1 · the plan: no "confirm", so nothing is sent
Invoke-RestMethod -Method Post -Headers $h -ContentType application/json -Body '{"usdg":"10","to":"<fresh wallet>"}' http://127.0.0.1:8081/v1/withdraw
# 2 · send it, with a key that makes a retry safe
$h["Idempotency-Key"] = "wd-fresh-1"
Invoke-RestMethod -Method Post -Headers $h -ContentType application/json -Body '{"usdg":"10","to":"<fresh wallet>","confirm":true}' http://127.0.0.1:8081/v1/withdrawimport pathlib, uuid, requests
API = "http://127.0.0.1:8081/v1"
s = requests.Session()
s.headers["Authorization"] = "Bearer " + (pathlib.Path.home() / ".h081" / "api-token").read_text().strip()
body = {"usdg": "10", "to": "<fresh wallet>"}
print(s.post(API + "/withdraw", json=body).json()["result"]) # the plan: nothing is sent
key = str(uuid.uuid4()) # keep it: a retry with it never sends twice
r = s.post(API + "/withdraw", json={**body, "confirm": True}, headers={"Idempotency-Key": key}).json()
print(r)import { H081 } from '@h081lll/agent';
const bank = await H081.open();
const to = '<fresh wallet>';
console.log(await bank.withdraw('10', { to })); // the plan: nothing is sent
console.log(await bank.withdraw('10', { to, confirm: true, idempotencyKey: 'wd-fresh-1' }));- You
Withdraw 10 USDG to my fresh wallet 9xQe…. It has never received anything from this agent.
- Claude
uses
h081_bank_withdrawDone. 9.95 USDG is on its way to 9xQe…; the relayer fee was 0.05 USDG.
The relayer sent it, so your agent wallet does not show up as the sender.
Withdrawal and deposit limits
These limits are set by the H081 programs and the relayer, not by you.
| Limit | Amount | If you go over it |
|---|---|---|
| Withdrawal through the relayer, nothing staked | Up to 100 USDG per withdrawal | Error needs_stake. Split it into smaller withdrawals, or stake H081. |
| Withdrawal through the relayer, H081 staked | H081 staked in capacity_stake sets a 30-day limit | See your stake with stake view below |
Withdrawal with --direct | No relayer limit and no fee | Your agent wallet is public as the sender |
| One deposit | At most 1,000 USDG | Error cap_per_deposit |
| The whole bank | 10,000 USDG in total, for now | Error cap_total |
npx -y @h081lll/agent stake viewUnstaking has a 30-day cooldown: you request it first, then unstake once 30 days have passed.
npx -y @h081lll/agent withdraw 150 --to <a wallet that never touched the agent> --directYour key stays on your computer
Your agent has one key. It pays network fees, holds the public USDG going in and out, and opens your private account. Whoever has this key has the money. H081 never has it: it is made on your computer and never sent anywhere.
Where things are kept
File in ~/.h081 | Who can read it | What it is |
|---|---|---|
agent.json | Only you (0600) | The agent key |
config.json | Only you (0600) | Network, RPC URL and port, written by init |
api-token | Only you (0600) | The password for the local API |
ops.jsonl | Only you (0600) | Your local history and retry records (never a key, a note or a token) |
cache/, circuits/ | Public chain data and the proof files |
On macOS and Linux the folder itself is 0700 (only you). On Windows, access is limited to your user with icacls; if that fails you see a warning, and you should check the folder's permissions yourself. H081_HOME moves the folder somewhere else.
npx -y @h081lll/agent infoBack up the key
If your computer dies, the key file dies with it, and so does the money. Make a backup code now. It is the key written as letters and digits, with a checksum and the network built in, so a typo is caught.
npx -y @h081lll/agent backupnpx -y @h081lll/agent restore <your h081-key: code>Restore checks the code first. It refuses to replace a key that is already there unless you add --force.
Spending ceilings
A ceiling is a hard upper limit you set on your own computer. Anything above it is refused before a proof is made or anything is sent, whoever asks: you, a script or Claude.
| Setting | Limits |
|---|---|
H081_MAX_DEPOSIT | Each deposit |
H081_MAX_PAY | Each payment from a budget |
H081_MAX_GRANT | Each budget you give |
H081_MAX_LEND | Each loan you fund |
Values are in base units: 1 USDG = 1000000. None is set by default. There is no ceiling setting for withdrawals; for those, the small balance on a dedicated key is your limit.
# at most 20 USDG per payment and 50 USDG per deposit (base units: 1 USDG = 1000000)
export H081_MAX_PAY=20000000
export H081_MAX_DEPOSIT=50000000
npx -y @h081lll/agent serve# at most 20 USDG per payment and 50 USDG per deposit (base units: 1 USDG = 1000000)
$env:H081_MAX_PAY = "20000000"
$env:H081_MAX_DEPOSIT = "50000000"
npx -y @h081lll/agent serveclaude mcp add --scope user h081 -e H081_SOLANA_KEYPAIR=<absolute path to ~/.h081/agent.json> -e H081_RPC_URL=<your RPC> -e H081_MAX_PAY=20000000 -e H081_MAX_GRANT=20000000 -- npx -y @h081lll/agent mcpOn Windows (not WSL), the end of that line is -- cmd /c npx -y @h081lll/agent mcp.
{ "ok": false, "network": "mainnet-beta",
"error": { "code": "over_operator_ceiling",
"message": "over the operator ceiling H081_MAX_PAY (20 USDG per payment; asked 25 USDG). Nothing was sent.",
"retryable": false },
"requestId": "..." }How the local API protects you
The local API (npx -y @h081lll/agent serve) is a small server on your own computer. It is built so that only programs you run can use it.
- Your computer only. It listens on
127.0.0.1. There is no option to open it to the network. - A password on every call. Every route except
/v1/healthand/v1/openapi.jsonneedsAuthorization: Bearer <token>, with the token from~/.h081/api-token. The check takes the same time for right and wrong tokens, so it cannot be guessed by timing. - Web pages are refused. A request from a browser page (one with an
Originheader) is refused unless you started the server with--allow-origin <that exact URL>. TheHostheader must be127.0.0.1:<port>orlocalhost:<port>, which blocks websites that try to reach your computer through a trick address (DNS rebinding). - JSON only. A POST must be
Content-Type: application/json. - Plan first. A write without
"confirm": trueonly returns the plan. Nothing is proven, signed or sent. - Speed limits. At most 30 sends a minute (
H081_API_MAX_SENDS_PER_MIN), one proof at a time, and at most 8 waiting. - Quiet logs. Keys, tokens, notes and full RPC URLs are never logged; only the RPC host is shown.
- Checked downloads. The proof files come from h081.xyz and are checked against pinned sha256 fingerprints before use.
If you think the token leaked, replace it, then restart serve. Programs using the old one get 401 unauthorized until you give them the new one.
npx -y @h081lll/agent token --rotateRetries are safe
Networks fail. If a payment seems to fail, the natural thing is to try again. The danger is paying twice. H081 prevents that with an idempotency key: a label you attach to one send, like a ticket number.
- Send again with the same label and you get the first answer back. It is never sent twice.
- A new payment gets a new label.
In the API it is the Idempotency-Key header (1 to 128 letters, digits and _ . : -), and a send with "confirm": true must have one. In the SDK it is idempotencyKey. The command line makes a new random one on every run unless you pass --idempotency-key:
npx -y @h081lll/agent withdraw 10 --to <a wallet that never touched the agent> --yes --idempotency-key wd-fresh-1| You send the same key again, and… | You get |
|---|---|
| the first one finished | The stored answer again, with "replayed": true |
| the first one is still running | 409 in_progress; wait 5 seconds (Retry-After) |
| the body is different | 422 idempotency_key_reused; use a new key |
| the first one failed with nothing sent (relayer busy, 429, stale root) | It runs again: the key was released |
the first one failed with an error that is not retryable (for example needs_sol) | The same error again. Fix the cause, then send with a new key |
| no one can tell whether the first one went through | 409 outcome_unknown, every time; it is never sent again |
Retry records are kept in ~/.h081/ops.jsonl for 7 days.
When the outcome is unknown
This happens when the local server stopped in the middle of a send, or when the connection to the relayer or to the Solana RPC broke while it was sending. The money may already be on its way.
Do not send again yet
Retrying with the same key keeps answering
outcome_unknown. That is on purpose.response { "ok": false, "network": "mainnet-beta", "error": { "code": "outcome_unknown", "message": "the gateway stopped while the request with Idempotency-Key wd-fresh-1 was running, so its outcome is unknown: check /v1/balance before retrying with a new key", "retryable": false, "details": { "idempotencyKey": "wd-fresh-1" } }, "requestId": "..." }Check the balance and the history
--freshreads the chain now instead of the 15-second cache.any terminal npx -y @h081lll/agent balance --fresh npx -y @h081lll/agent history --limit 5From the API:
GET /v1/balance?fresh=1andGET /v1/history.Only if it did not go through, send again with a new key
If the balance did not drop and nothing arrived, it was not sent. Send it again with a new idempotency key.
Safety checklist
- Use a dedicated agent key that holds only a few dollars.
- Back up the key with
backupand keep the code offline. - Withdraw to a fresh wallet, and wait a while after depositing.
- Read the plan before adding
--yesor"confirm": true. - Set
H081_MAX_PAYandH081_MAX_GRANTbefore letting an AI spend. - Give every new payment a new idempotency key; reuse a key only to retry that same payment.
- On
outcome_unknown, check the balance before anything else.
- NextErrors and fixesEvery error code, what it means and what to do.
- RelatedAllowancesLet another agent spend some of your money, with limits you set.
New here? The 5-minute quickstart does a first private payment end to end. The package is @h081lll/agent on npm. Questions: @H081CAP on X.