@temporalabs/treasury 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +45 -13
  2. package/THIRD_PARTY_NOTICES.connect-page.md +494 -24
  3. package/THIRD_PARTY_NOTICES.md +579 -6
  4. package/dist/cli.js +28 -5
  5. package/dist/cli.js.map +1 -1
  6. package/dist/client.d.ts +50 -209
  7. package/dist/client.js +66 -11
  8. package/dist/client.js.map +1 -1
  9. package/dist/config/earn.d.ts +16 -1
  10. package/dist/config/earn.js +16 -1
  11. package/dist/config/earn.js.map +1 -1
  12. package/dist/connect/ack.d.ts +88 -1
  13. package/dist/connect/ack.js +177 -6
  14. package/dist/connect/ack.js.map +1 -1
  15. package/dist/connect/commands.d.ts +30 -0
  16. package/dist/connect/commands.js +846 -88
  17. package/dist/connect/commands.js.map +1 -1
  18. package/dist/connect/config.d.ts +7 -5
  19. package/dist/connect/config.js +8 -6
  20. package/dist/connect/config.js.map +1 -1
  21. package/dist/connect/gate-solana.d.ts +8 -0
  22. package/dist/connect/gate-solana.js +147 -0
  23. package/dist/connect/gate-solana.js.map +1 -0
  24. package/dist/connect/http.d.ts +17 -2
  25. package/dist/connect/http.js +45 -13
  26. package/dist/connect/http.js.map +1 -1
  27. package/dist/connect/page.d.ts +1 -1
  28. package/dist/connect/page.js +2 -2
  29. package/dist/connect/page.js.map +1 -1
  30. package/dist/connect/session.d.ts +40 -1
  31. package/dist/connect/session.js +243 -2
  32. package/dist/connect/session.js.map +1 -1
  33. package/dist/connect/solana.d.ts +112 -0
  34. package/dist/connect/solana.js +370 -0
  35. package/dist/connect/solana.js.map +1 -0
  36. package/dist/connect-page.js +835 -671
  37. package/dist/disclosures.d.ts +7 -4
  38. package/dist/disclosures.js +7 -4
  39. package/dist/disclosures.js.map +1 -1
  40. package/dist/earn/commands.js +127 -54
  41. package/dist/earn/commands.js.map +1 -1
  42. package/dist/earn/solana.d.ts +278 -0
  43. package/dist/earn/solana.js +316 -0
  44. package/dist/earn/solana.js.map +1 -0
  45. package/dist/guide.d.ts +15 -0
  46. package/dist/guide.js +50 -0
  47. package/dist/guide.js.map +1 -0
  48. package/dist/links.js +2 -0
  49. package/dist/links.js.map +1 -1
  50. package/dist/position.d.ts +3 -2
  51. package/dist/position.js +6 -3
  52. package/dist/position.js.map +1 -1
  53. package/dist/quote.js +2 -2
  54. package/dist/quote.js.map +1 -1
  55. package/dist/redact.js +7 -1
  56. package/dist/redact.js.map +1 -1
  57. package/dist/registry-schema.d.ts +79 -2
  58. package/dist/registry-schema.js +77 -1
  59. package/dist/registry-schema.js.map +1 -1
  60. package/dist/registry.d.ts +21 -1
  61. package/dist/registry.js +48 -10
  62. package/dist/registry.js.map +1 -1
  63. package/dist/solana/constants.d.ts +49 -0
  64. package/dist/solana/constants.js +50 -0
  65. package/dist/solana/constants.js.map +1 -0
  66. package/dist/solana/kvault.d.ts +99 -0
  67. package/dist/solana/kvault.js +192 -0
  68. package/dist/solana/kvault.js.map +1 -0
  69. package/dist/solana/layout.d.ts +143 -0
  70. package/dist/solana/layout.js +209 -0
  71. package/dist/solana/layout.js.map +1 -0
  72. package/dist/solana/rpc.d.ts +24 -0
  73. package/dist/solana/rpc.js +66 -0
  74. package/dist/solana/rpc.js.map +1 -0
  75. package/dist/solana/vault.d.ts +153 -0
  76. package/dist/solana/vault.js +322 -0
  77. package/dist/solana/vault.js.map +1 -0
  78. package/dist/treasury.mjs +37410 -27965
  79. package/dist/version.js +5 -10
  80. package/dist/version.js.map +1 -1
  81. package/llms.txt +637 -0
  82. package/package.json +14 -8
  83. package/registry/vaults.json +113 -0
package/llms.txt ADDED
@@ -0,0 +1,637 @@
1
+ # Treasury
2
+
3
+ > Agent Treasury by Tempora Labs — a depositor client for ERC-4626 vaults and a Kamino vault on Solana: registry, pre-flight, and unsigned deposit/withdraw builders. Never signs, never holds a key.
4
+
5
+ This file is generated from the earn skill and from the command descriptions `treasury --help` prints; it is not edited by hand.
6
+ This version's copy is at https://raw.githubusercontent.com/TemporaLabs/treasury/v0.1.4/llms.txt.
7
+ Run commands as `treasury <skill> <command> [--flag value …]` (`npx @temporalabs/treasury@0.1.4 …` runs the same version without installing it). Each prints one JSON document.
8
+
9
+ # The earn skill
10
+
11
+ # Earn — put idle USDC to work in a Tempora Labs vault
12
+
13
+ You are the depositor. A Tempora Labs fund is an ERC-4626 vault on Base, Arbitrum One, Robinhood Chain, Ethereum or Monad, or a Kamino vault on Solana: you put USDC in (USDG on Robinhood Chain), you hold
14
+ shares, the shares' USDC value moves with what the vault earns or loses, you redeem when you need
15
+ cash. What amount is surplus is the operator's decision, not yours — a balance in the wallet is
16
+ not permission to deposit it. **How much to deposit or withdraw is the operator's decision. Ask; do
17
+ not pick.** Not a small test amount, not a round number, not the wallet's balance: if the operator has
18
+ not named an amount, ask for one before you quote. The
19
+ `treasury` CLI gives you eight `earn` commands that read the vault and **prepare unsigned
20
+ calls**, and six `connect` commands that put those calls in front of the operator's own wallet in a
21
+ browser page. Run each one with the shell, exactly like this (nothing stays running between commands):
22
+
23
+ ```bash
24
+ treasury earn <command> --flag value …
25
+ treasury connect <command> --flag value …
26
+ ```
27
+
28
+ Run one plain command per shell call. When unsure of
29
+ a command's flags, run `earn <command> --help` first: the CLI shows only what you ask it for.
30
+
31
+ Below, each command is named by its tool name: `earn_quote` is `earn quote`, and its arguments are its flags
32
+ (`--direction deposit --account 0x… --amount_usdc <amount>`; every amount in this file is a placeholder for the
33
+ operator's own). `earn --help` lists every command and flag; a switch such as `--all` takes no value. A command prints
34
+ one JSON document on stdout. When it cannot run as asked (an unknown or repeated flag, a vault on another chain, a quote
35
+ or balance whose RPC cannot be reached) it exits non-zero with `{ "error": … }` on stderr — report it, never read it as
36
+ a result. A verdict is a result and exits 0, with its fields saying what was found: `WHITELIST_GATED`, or `earn_status`
37
+ reporting `rpc: "unreachable"` or a pre-flight `UNRESOLVED`. Every prepared call comes back inside `{
38
+ requires_signature: true, status: "unsigned", calls }` (on Solana, one `transaction` in place of `calls`) — that
39
+ envelope is the command telling you nothing has been submitted and no money has moved. No `earn` command can sign or
40
+ send, and neither can you through it — the operator's own signer (a wallet, a policy-engine signer, a token-bound
41
+ account) does that. `connect` does not sign either: it opens a page where the operator confirms each call in their own
42
+ wallet. That boundary is the whole design: a skill that supplies judgement must never hold the gate that supplies money.
43
+
44
+ ## When the operator asks what this can do
45
+
46
+ Answer in at most five lines, then ask what they want to do. Name the chains from `earn_vaults`, never from memory. Say that they
47
+ can quote, deposit, check what a position is worth and can be withdrawn for now, and withdraw, in one vault or several in one request
48
+ (below); that money moves only from their own wallet, after their yes there, and that you never sign, bridge or swap; and that there
49
+ are two ways in: through you, or in a browser at `https://app.temporalabs.com`. Quote no yield; this client reads none.
50
+
51
+ ## Setup
52
+
53
+ **Each chain has its own RPC.** The CLI reads Base through `TREASURY_RPC_BASE`, Arbitrum One
54
+ through `TREASURY_RPC_ARBITRUM`, Robinhood Chain through `TREASURY_RPC_ROBINHOOD`, Ethereum through
55
+ `TREASURY_RPC_ETHEREUM`, Monad through `TREASURY_RPC_MONAD` and Solana through `TREASURY_RPC_SOLANA` (and the matching
56
+ `TREASURY_LOGS_RPC_*` variable for the event scans `earn_balance` does), from the shell's environment. A chain never reads
57
+ another chain's variable. Unset, a chain falls back to its public endpoint (`mainnet.base.org`,
58
+ `arb1.arbitrum.io`, `rpc.mainnet.chain.robinhood.com`, `ethereum.reth.rs`, `rpc.monad.xyz`, `api.mainnet-beta.solana.com`), which rate-limits after a handful of calls — an `RPC Request failed …
59
+ over rate limit` error from any tool means set a keyed RPC URL for that chain, not that the vault
60
+ is down. The CLI also accepts `BASE_RPC_URL`, `ARBITRUM_RPC_URL`, `ROBINHOOD_RPC_URL`, `ETHEREUM_RPC_URL`, `MONAD_RPC_URL` and `SOLANA_RPC_URL` as fallbacks. A keyed URL is a
61
+ secret: command output never repeats it — errors are reported without the endpoint.
62
+
63
+ `earn_status` with `chain` checks that chain's RPC. 🔴 **`rpc: "wrong_chain"` means the variable for
64
+ that chain holds an endpoint for a different one** — say so and stop. The same refusal comes back
65
+ from a quote, a pre-flight or a balance (*"the endpoint in … answers for chain N"*): it is a setup
66
+ problem to report, naming the variable, and never a verdict about the vault or the position.
67
+
68
+ `earn_balance` reads history through `eth_getLogs`, starting at the vault's deployment block.
69
+ 🔴 **Read `scan.wholeHistory`, not `scan.complete`.** `complete` only says shares in − shares out
70
+ reconciles, which an empty window does vacuously; `wholeHistory` says the scan actually reached the
71
+ deployment block and was not cut short. Only then are `entryBasisUsdc` and `accruedYieldUsdc` numbers —
72
+ otherwise they read `unknown`, never a partial sum that renders a missed deposit as yield.
73
+
74
+ Providers cap the block range per request (Alchemy free tier 10 blocks, Base public RPC 2,000, Infura on Arbitrum
75
+ 10,000, Monad public RPC 100), and some public endpoints refuse old ranges outright. When the configured RPC cannot
76
+ cover the range, the scan moves to the fallback endpoint and `scan.source` says `"fallback"`; you do not need to retry
77
+ anything. Set `TREASURY_LOGS_FALLBACK` to another endpoint to choose it, or to `off` — or any value that is not a URL —
78
+ to forbid it, for an operator who may not query a third party they did not name. It fails closed: an unrecognised value
79
+ turns the fallback off rather than quietly keeping the default. A URL there names a Base endpoint, so on every other
80
+ chain a set variable means no fallback at all. On Base the fallback reaches `max_log_requests` (default 100) × 2,000
81
+ blocks, about 4.6 days; each scan also stops at a 30-second budget. For an older vault, a whole-history basis needs that
82
+ chain's logs variable (`TREASURY_LOGS_RPC_BASE`, `TREASURY_LOGS_RPC_ARBITRUM`, `TREASURY_LOGS_RPC_ROBINHOOD`,
83
+ `TREASURY_LOGS_RPC_ETHEREUM`, `TREASURY_LOGS_RPC_MONAD`) set to a provider with a wide `eth_getLogs` range; until then
84
+ `scan` says exactly how much was covered. On Monad that is needed from day one: its public RPC serves 100 blocks per
85
+ request and Monad makes about three blocks a second, so a public-RPC scan there covers under an hour.
86
+
87
+ ## The tools
88
+
89
+ | tool | use it to |
90
+ |---|---|
91
+ | `earn_vaults` | see every vault and the chain it is on, `chains` — where a deposit can go, each with its own default vault — the `default`, and `depositable` — the ones you can put money into today |
92
+ | `earn_terms` | show the operator the required disclosures before a first deposit |
93
+ | `earn_status` | **with no `account`**: is the CLI healthy and the chain's RPC reachable (chain, latest block, which RPC variable resolved); pass `chain` to check the chain you are about to use. **With `account`**: the access verdict alone. `mode` tells you which you got |
94
+ | `earn_quote` | with `direction: "deposit"` — expected shares, share price, and the verdict from a **simulated** deposit. No rate is quoted — the vault exposes none and this client calls no yield API. With `direction: "withdraw"` — shares that would burn, a simulated `withdraw` verdict, the vault's `instantLiquidity`, queue depth. `direction` is required and is echoed back on the result |
95
+ | `earn_prepare_deposit` | get the unsigned `approve` + `deposit` calls, enveloped (on Solana, one transaction); `--receiver` (required) is where the shares land |
96
+ | `earn_prepare_withdraw` | get the unsigned `withdraw` call in terms of the vault's asset — or `--all` with `--shares_exact` to empty the account |
97
+ | `earn_balance` | shares (exact), USDC value, entry basis and accrued yield from the vault's own events; `lookback_blocks` / `max_log_requests` set the scan window (see Setup) |
98
+ | `earn_claim` | finalize a queued withdrawal; today no vault queues, and it says so |
99
+
100
+ Everything is USDC in, USDC out — except on Robinhood Chain, where the vault's asset is **USDG** and every
101
+ amount is USDG: say USDG to the operator there, even though the argument is still named `amount_usdc`
102
+ and the result field `usdcValue`. A vault's own asset is `asset.symbol` in `earn_vaults`. Vault shares exist only inside `earn_balance` (as an exact
103
+ string you hand back to `earn_prepare_withdraw` unchanged) — you never compute with them.
104
+
105
+ Every tool that takes `vault` also takes **`chain`** (`"base"`, `"arbitrum"`, `"robinhood"`, `"ethereum"`, `"monad"` or `"solana"`). A vault is on one
106
+ chain, so naming the vault is enough; naming only the chain selects that chain's default vault; naming
107
+ both when they disagree is refused, and the error says which chain the vault is on. Every result that
108
+ names a vault carries `chain` and `chainId`.
109
+
110
+ The address argument is **`account`** on every tool that takes one — whose shares these are.
111
+ **Both prepare commands also require `--receiver`**, and it is a different thing: on
112
+ `earn_prepare_deposit` it is where the new **shares** land, on `earn_prepare_withdraw` where the
113
+ **asset** (USDC, or USDG on Robinhood Chain) lands. They are usually the same address as `account`, and the command will not assume it —
114
+ **neither may you.** The receiver comes from the operator's own message, through the destination check
115
+ below, before you build either call; never fill it in from `account` on your own.
116
+
117
+ ## Choosing the chain
118
+
119
+ A deposit goes to ONE chain, and the vault's asset (USDC; USDG on Robinhood Chain) has to be on that chain already — this skill does not
120
+ bridge. An operator can still put money into several vaults, on one chain or several, in one request: one deposit per vault (below). **Which chain is the operator's decision. Ask; do not pick.**
121
+
122
+ - **Before preparing a deposit, if the operator has not said which chain, show them `earn_vaults` →
123
+ `chains` and ask.** Each entry is a chain with its default vault; the first is the default chain.
124
+ Put it as a choice they can answer in a word: "Base (default), Arbitrum, Robinhood Chain, Ethereum, Monad or Solana?"
125
+ - **Ethereum gas is far higher than on the other chains.** When the operator picks Ethereum for a small amount, say so
126
+ before preparing anything: the approve and the deposit, and later the withdrawal, each pay Ethereum gas in ETH, which
127
+ can be a large share of a small deposit. Offer another chain; the choice stays theirs.
128
+ - **Do not ask again once it is settled.** If they named a chain, named a vault, or said where
129
+ their funds are, that is the answer for the rest of the task.
130
+ - **Never ask for a withdrawal, a balance or a quote on an existing position.** A position is on
131
+ the chain its vault is on. Name the vault and the tools use the right chain.
132
+ - **If no vault was named and you do not already know which vault holds the position, find out
133
+ before you answer.** A tool called with neither `vault` nor `chain` reads the default chain's
134
+ default vault, and "no position there" says nothing about the others. Call `earn_balance` for each
135
+ listed vault first. If more than one holds shares and the request does not say which, ask which position, or whether all of them (below).
136
+ - **"The default" with no chain named means the default chain's default vault** — only when the
137
+ operator says to use the default, not as a way to skip the question.
138
+ - **An account's positions on different chains are separate.** To report everything an account
139
+ holds, call `earn_balance` once per vault; a zero on one chain says nothing about the other.
140
+ - **Say the chain out loud at every money step**: in the quote, when you hand over the calls, and
141
+ when you report the result. The operator's signer must be on that network (below).
142
+
143
+ ### Several vaults in one request
144
+
145
+ An operator can move money in several vaults in one sitting — "1 USDC on Base and 3 on Solana", "2 USDC on every chain",
146
+ "withdraw 1 from 2B and deposit 1 into 2C". Vaults may be on different chains or one, and deposits and withdrawals may
147
+ mix. Each vault is one **leg** with its own transactions; nothing moves money in several vaults at once.
148
+
149
+ 1. **Settle the legs from the operator's own words:** each leg's action, vault, chain and amount. A chain named without a
150
+ vault means its default vault. "Every chain" means every entry in `earn_vaults` → `chains`, each with its default
151
+ vault; "every vault" means every vault in `depositable` (a listed vault outside it cannot take a deposit: name it,
152
+ do not drop it silently). "Withdraw everything" means every vault whose `earn_balance` holds shares, each withdrawn in
153
+ full. Do not add, drop or resize a leg on your own.
154
+ 2. **Check every leg before any money moves:** `earn_quote` with the leg's direction, the real account and amount. The
155
+ asset has to be on that vault's chain already (USDG, not USDC, on Robinhood Chain); this skill does not bridge. Two
156
+ deposits on one chain draw on one balance: check it covers both. On Solana the account also needs SOL, and on
157
+ Ethereum gas can be a large share of a small amount: say so for that leg. A vault that cannot pay its withdrawal now
158
+ (liquidity) is a leg that cannot go ahead as asked: say so, and do not shrink it on your own.
159
+ 3. **Through `connect`, one acknowledgement covers the whole request: `connect plan`.** Pass the legs in order,
160
+ `--legs "deposit <vault> <amount>; withdraw <vault> <amount|all>"`: vault tickers, at most 8 legs; `all` binds the
161
+ share balance read at that moment. Like `connect deposit`, the first run opens nothing and returns one
162
+ `acknowledgement` listing every leg, its receiver and the warnings: post it word for word and wait. Only on the
163
+ operator's explicit yes, run it again with the same `--legs` and `--ack <code>`: that records the yes, opens nothing,
164
+ and returns each leg's `command`. Full `earn_terms` still come first, before the operator's first deposit. A plan
165
+ with legs on EVM chains and on Solana needs both wallets connected.
166
+ 4. **Then run the plan on one signing page: `connect plan --run`**, each shell call with a timeout of at least 9
167
+ minutes. It returns `in_progress` near that limit: run it again at once, and the operator's open page carries on.
168
+ Solana legs run on the same page; on a plan that also has EVM legs, the operator's wallet may ask once, at Start, to
169
+ connect its Solana account, and a wallet without one (Rabby) ends the plan there with nothing sent. **The yes covers every leg:
170
+ do not ask again between them.** Report each leg as in **Handing calls to the signer**, step 4.
171
+ 5. **Stop at the first leg that does not complete**; the CLI refuses the rest. Report every leg (vault, chain, amount,
172
+ transactions, and which did not run; `connect plan --status` lists them). What is left needs a new plan and a new yes.
173
+
174
+ **Without `connect`, the plan is yours to show:** one table, a row per leg with the total, and a yes to it; then one
175
+ complete deposit or withdrawal per vault through the prepare commands, one at a time, each with its own destination check
176
+ (to withdraw everything, `--all` with that vault's own `sharesExact`). **"What do I hold?"** is `earn_balance` on every
177
+ vault `earn_vaults` lists (the EVM and the Solana account each), as one table: per vault, its chain, what it is worth
178
+ and what it can be withdrawn for now, with the totals.
179
+
180
+ ## Solana is different
181
+
182
+ The vault on Solana (`chain: "solana"`) is a Kamino vault, not an ERC-4626 one. The same commands work on it,
183
+ with these differences — say them to the operator where they apply:
184
+
185
+ - **Addresses are Solana addresses** (base58, no `0x`). An EVM address for a vault on Solana is refused, and the other
186
+ way round. The operator's Solana address comes from their own message, through the destination check, like any other.
187
+ - **`--receiver` must be the account itself.** The vault mints shares to the depositing account and pays a
188
+ withdrawal to the account whose shares burn; there is no other receiver, and anything else is refused.
189
+ - **One transaction, and it expires.** `earn prepare_deposit` / `prepare_withdraw` return ONE unsigned
190
+ transaction (`transaction.wire`, base64), simulated before it is returned, that lands only before
191
+ `transaction.expiresAfterBlockHeight` — about a minute. Prepare it only when the operator is ready to sign,
192
+ and if the height has passed, prepare it again; never hand over an expired one. Through `connect`, the page
193
+ builds it when the operator presses Confirm, so this does not arise there.
194
+ - **The account needs SOL**, for the fee and, on a first deposit, about 0.002 SOL of rent for its share
195
+ account. `earn status --chain solana --account …` says `NEEDS_FUNDS` when it lacks USDC or SOL.
196
+ - **Each withdraw instruction keeps up to 0.001 USDC in the vault** — Kamino's charge for every vault on its
197
+ program, which Kamino can change; a withdrawal split across reserves pays it per part — and one worth 0.001
198
+ USDC or less is refused. Quotes report `receives`, exact and net of it; say so before a small withdrawal.
199
+ - **No history on Solana:** `earn_balance` reports shares, value and what a full exit would pay now, but
200
+ `entryBasisUsdc` and `accruedYieldUsdc` read `unknown`. Do not present the value as profit.
201
+ - **`connect wallet --chain solana`** connects a Solana wallet (Phantom, Solflare, Backpack, or a Privy wallet
202
+ from an email or social login), separately from the EVM connection; `connect deposit` / `withdraw` with
203
+ `--chain solana` then use it, with the same acknowledgement step.
204
+
205
+ ## Opening an account and depositing
206
+
207
+ 1. **Settle the chain first (above), then the vault, then the amount — the operator's, asked for if
208
+ they have not given one, never one you choose.** If the operator names no vault, use that
209
+ chain's default (`earn_vaults` → `chains[].default`; today Tempora Labs Cash Plus USDC (Test 2B) on
210
+ Base, Tempora Labs Cash Plus USDC (Test 2C) on Arbitrum One, Tempora Labs Cash Plus USDG (Test 2D) on Robinhood Chain,
211
+ Tempora Labs Cash Plus USDC (Test 2E) on Ethereum, Tempora Labs Cash Plus USDC (Test 2F) on Monad and Tempora Labs Cash Plus
212
+ USDC (Test 2G) on Solana, all open to any account; Test 2 on Base is the demo vault, used only when it is named). Every listed vault is a Tempora Labs vault.
213
+ Read that chain's `defaultAccess` (`chains[].defaultAccess`; the top-level one is the default
214
+ chain's): when it is `"whitelist"`, run `earn_status` for the account first, and if
215
+ it returns `WHITELIST_GATED`, **tell the operator the account is not admitted to that vault and that
216
+ admission is a fund-side action — do not offer a substitute and do not retry.** A listed vault that
217
+ is not the default may be gated even when the default is open.
218
+ 2. **`depositable` may be EMPTY, and an empty set is an answer, not an error.** It is the set of
219
+ vaults any account can deposit into — ACTIVE, ERC-4626 (or the Kamino vault on Solana), *and measured open by simulation*. When
220
+ every vault on offer is whitelist-gated it is empty, which is the state to report, not a fault
221
+ to work around. Read it from `earn_vaults` rather than assuming either way. A vault can be live and still refuse you: IPOR Fusion
222
+ vaults restrict `deposit()` to a whitelist role while their `redeem()` is public, and Enzyme
223
+ vaults are not ERC-4626 at all. Never read an empty `depositable` as "the tools are broken", and
224
+ never propose a vault that is not in the registry: this client deposits only where its registry
225
+ says, and reaching anywhere else is outside what it will build.
226
+ 3. **On a first deposit, present `earn_terms` and get an explicit acknowledgement.** The
227
+ fund requires this on every distribution surface, and an agent talking to its
228
+ operator is one. Do not paraphrase them into something friendlier. This holds through `connect`
229
+ too: post the full `earn_terms` once, before the operator's first deposit. The acknowledgement
230
+ `connect` returns restates them in plain words before every signing page; it comes after the full
231
+ terms on a first deposit, never instead of them. Show the full text again whenever the operator asks.
232
+ 4. **Always `earn_quote` with `direction: "deposit"`, the real `account`, and the operator's amount.** It includes the
233
+ pre-flight; read `preflight.status`, not `advisory.maxDepositRaw`: `maxDeposit()` returns "unlimited" on a
234
+ whitelist-gated Fusion vault and `0` on an open Morpho V2 vault — it is wrong in both
235
+ directions, which is why the pre-flight simulates the actual call instead.
236
+ - `NEEDS_APPROVAL` → normal; the deposit reached the token pull. Proceed to build. (EVM chains only.)
237
+ - `OPEN_READY` → on an EVM chain, the allowance already covers it; on Solana, which has no approve
238
+ step, the simulated deposit succeeded. Proceed to build.
239
+ - `NEEDS_FUNDS` (Solana only) → the simulation stopped at the account's own funds: no USDC, or no SOL
240
+ for the fee. Show the `findings`, and quote again once the operator has funded the account; it is
241
+ not a verdict that the vault would take the deposit.
242
+ - `WHITELIST_GATED` → stop. The operator needs the role granted (a fund-side action), not
243
+ a retry.
244
+ - `REVERTED_OTHER` → stop and show the `findings`; they list candidate causes. Do not guess
245
+ one.
246
+ - `REFUSED_BY_CLIENT` / `UNRESOLVED` → stop; the first is a registry/chain mismatch, the
247
+ second a transport failure. Neither is a verdict about the vault.
248
+ 5. **Through `connect`, skip this step:** `connect deposit` builds the same calls (on Solana, the one transaction) and
249
+ checks them, and opens the signing page only after the operator acknowledges them, every time (see **Handing calls to
250
+ the signer** below). Its acknowledgement restates the disclosures in plain words on every deposit; on a first deposit
251
+ the full `earn_terms` of step 3 still come first. **Otherwise, `earn_prepare_deposit`, then hand BOTH calls in
252
+ `calls` to the signer in order, and ask before each.** (On Solana it returns one transaction instead: see **Solana is
253
+ different** above.) The envelope names `chain` and `chainId`: tell the operator which chain these calls are for
254
+ before anything else, because a call sent on the wrong chain can be mined there and do nothing. The amount is in the
255
+ vault's asset (USDC, or USDG on Robinhood Chain; 6 decimals for both); the tool refuses more precision than that.
256
+ Show the operator each call's `description` — that sentence exists to be read by a human before a signature — **and
257
+ its `gasAdvice`**: Morpho V2 calls can run out of gas on an unbuffered estimate even when every simulation passes,
258
+ because accrual work grows with the time between estimate and inclusion. Estimate × 1.5. **And its `precondition`,
259
+ where present**: the `deposit` call names the read (`allowance(owner, spender) >= minimum`, raw units) that must hold
260
+ on the RPC the signer sends through before it is estimated or sent. The `approve` receipt can come from a node ahead
261
+ of the one simulating the `deposit` — measured on both real round trips — so a signer that reads the precondition
262
+ until it holds is deterministic and one that retries on an error string is not. **Then follow the envelope's
263
+ `signer_rules`**, which every prepared build carries: send on the chain the call's `chainId` names and no other,
264
+ confirm each destination against the operator's own addresses, set the nonce from the `pending` count before each
265
+ send, wait for each receipt, and after any failure that is not an on-chain revert, read the nonce on a different
266
+ provider before re-sending, so nothing is sent twice. The hand-off itself — the destination check and the operator's
267
+ own terminal — is the section **Handing calls to the signer** below; do not skip it for a small amount.
268
+ 6. **After it lands, `earn_balance`** and report value, basis, yield, and what can be withdrawn now. Until a signer's
269
+ transaction confirms, the deposit has NOT happened — `requires_signature: true` is the tool
270
+ saying so, and "prepared" is not "deposited". The vault's events
271
+ remember the deposit; the provider's log window decides how far back `earn_balance` can see.
272
+
273
+ ## Checking and withdrawing
274
+
275
+ 🔴 **`usdcValue` is what the position is WORTH; `exit.exitableNow` is what it can be WITHDRAWN for.**
276
+ Report both, and never quote the first as if a depositor could have it. On a Fusion vault without
277
+ instant-withdrawal fuses these differ by 10× — measured on a live Fusion test vault at block 51,327,076: the position was
278
+ worth 14.999970 USDC, `maxWithdraw()` agreed, and 1.498874 USDC already reverted because the vault
279
+ pays from its own 1.498873 USDC balance. `exit` is measured by simulating the withdrawal, so it is
280
+ right on both chassis; `exit.maxWithdrawSays` is reported only because other interfaces show it.
281
+ ⚠️ **Without a keyed RPC these two compete for the public endpoint's rate limit.** Measured 2026-09-15: with a key,
282
+ the exit and a whole-history scan both complete in about 15-22 s; with none, one usually degrades (the exit `not
283
+ measured`, or the scan CUT SHORT) and says so. The vault's chain's `TREASURY_RPC_*` (Setup) makes both available.
284
+
285
+ 🔴 **`measuredAs: "not measured"` means the RPC failed, NOT that the vault refused** — say so rather
286
+ than reporting a limit the chain never stated.
287
+
288
+ - `earn_balance` answers "what is it worth" at the current block, plus entry basis and accrued yield derived from the
289
+ vault's own `Deposit`/`Withdraw` events for this owner. Read `scan.wholeHistory`: when `true`, the scan reached the
290
+ deployment block, was not cut short, and shares in − shares out reconciles, so basis and yield are the account's
291
+ lifetime figures; when `false`, both read `unknown` — **never a bound over the covered window**, because a window that
292
+ reconciles can still have missed the deposit that produced the shares. The note says which provider window capped the
293
+ scan. Realized redemption is subject to the vault's caps and any queue, so quote value as a value, not a promise.
294
+ - **Withdraw in the vault's asset** (USDC; USDG on Robinhood Chain). `earn_quote` with `direction: "withdraw"`, then `connect withdraw` (same flags,
295
+ receiver = the connected account) or `earn_prepare_withdraw` with `amount_usdc` and the `account` whose shares burn; the vault burns
296
+ whatever shares that costs at inclusion. To empty the account, pass `--all` and
297
+ `--shares_exact` copied **verbatim** from `earn_balance.sharesExact` — never a number you rounded
298
+ or computed: an 18-decimal balance exceeds 2⁵³, floats round it (sometimes up), and redeeming
299
+ more than is owned reverts.
300
+ - **A withdrawal can be refused for liquidity, not balance.** Some vaults (Fusion) pay a withdrawal only from what they
301
+ hold un-deployed in that block; the rest of the account's value is in markets and comes back when the fund unwinds.
302
+ The withdraw quote reports `instantLiquidity` and, on a Fusion refusal, says so in those terms. Tell the operator that
303
+ at most that amount comes out now, and ask whether to withdraw that, another amount, or wait; do not shrink the
304
+ withdrawal yourself. A Morpho V2 vault holds almost no idle USDC and pays out of its markets, so there the quote shows
305
+ the chain's own revert reason instead, and a smaller amount may still pass: say so, and quote only an amount the
306
+ operator names. Either way, do **not** read a refusal as "approve first" (that is a deposit failure) and do not trust
307
+ `maxWithdraw`, which reports the full position on exactly these vaults.
308
+ - One call, one signature, same rules as depositing: show the description and `gasAdvice`, run
309
+ the destination check below on `receiver`, hand it over.
310
+
311
+ ## Handing calls to the signer
312
+
313
+ **First choice: the operator's own wallet, through `connect`.** It needs a browser on this machine: the
314
+ page is served on `localhost` only, so a phone or another computer cannot open it.
315
+ `connect wallet`, and `connect deposit` / `connect withdraw` run with `--ack`, each wait up to 9 minutes
316
+ for the operator, so give each of those shell calls a timeout of at least 9 minutes. The form is the same as `earn`:
317
+
318
+ ```bash
319
+ treasury connect deposit --vault <symbol> --amount_usdc <amount> --receiver 0x…
320
+ ```
321
+
322
+ 1. `connect status`. If disconnected, `connect wallet` opens a page; the operator clicks Connect and uses
323
+ a browser wallet, or an email, Google, Apple or X login (a Privy wallet). Wait for it to return.
324
+ 2. Run the earn quote as usual, then `connect deposit --amount_usdc <n> --receiver <addr>` (or
325
+ `connect withdraw …`), with the same `--vault`/`--chain`. 🔴 **This first run opens nothing.** It
326
+ returns `status: "needs_acknowledgement"` with `acknowledgement` (one fixed text naming the amount,
327
+ chain, vault and its explorer link, the receiver, the vault's warning and the disclosures) and an
328
+ `ack` code. **Post `acknowledgement` to the operator word for word and wait for their answer.** Do
329
+ not summarise it, shorten it, or skip it because they acknowledged an earlier one or said "go"
330
+ before they saw it: every page open has its own, except the legs of one `connect plan` (above).
331
+ 3. **Only on the operator's explicit yes to that text**, run the same command again with `--ack <code>`
332
+ added. The page opens; the operator confirms each call there and in the wallet; the command returns
333
+ when they finish. Any answer but yes: stop. A refused `--ack` (expired after 15 minutes, already used,
334
+ or the amount, vault, chain or receiver changed) means go back to step 2 for a new acknowledgement,
335
+ never retry the code.
336
+ 4. Read `status` first. `completed`: every call landed. `stopped`: it ended early; read `reason` and the last transaction's `detail`.
337
+ `not_reported`: no transaction came back, which is not proof none was sent, so check `earn balance`
338
+ before any retry. Report every `hash` and its `verified`. `extra_transfer` means the wallet also moved money besides
339
+ the call (`alsoMoved`, often its own gas fee in USDC): say so. Anything else that is not `matched`:
340
+ stop, check `earn balance`, tell the operator.
341
+
342
+ `--receiver` must be the connected account (`connect status`), and it still comes from the operator,
343
+ not from you: the page pays only that account. For any other receiver, use the prepare commands below
344
+ with the operator's own signer.
345
+
346
+ **No browser here** (an SSH session, a container, a machine with no display): skip `connect` and use the
347
+ prepare commands. The first, acknowledgement run of `connect deposit` / `connect withdraw` always says
348
+ `opened: false`: it opens nothing by design. On `connect wallet`, or the run with `--ack`, `opened: false`
349
+ means no browser opened: the command printed the page's address on stderr, but it returns only after
350
+ the page has closed, so the address reaches you too late to pass on. Use the prepare commands instead.
351
+
352
+ Every prepared call names a destination: `to`, and the receiver or owner in its `description`. A
353
+ wrong destination is the one mistake nothing downstream can undo — the transaction succeeds, the
354
+ receipt says so, and the money is somewhere nobody holds a key for. Measured, once, for real: a
355
+ withdrawal sent to an address taken from a config file instead of the operator's message — the
356
+ funds gone, and the receipt read "success". So the check is not on the receipt. It is on where the
357
+ address came from.
358
+
359
+ **The address that can lose money is the `receiver` — and it comes from the operator's own
360
+ message, quoted back, and confirmed.** The call's `to` is the vault, taken from the registry and
361
+ re-checked by the pre-flight; the operator cannot verify a contract address by eye and is not asked
362
+ to. The receiver is where the shares or the asset (USDC or USDG) land, and only the operator knows which account
363
+ that should be. Before handing over any prepared call whose signer cannot show the receiver, post
364
+ this and wait:
365
+
366
+ > **Destination check — nothing goes to the signer until you reply.**
367
+ > shares/USDC/USDG to `<receiver>` ← from your message "<the words it came from>"
368
+ > Paste it back or correct it.
369
+
370
+ **Through `connect`, the acknowledgement is the check before the page opens — do not add a
371
+ paste-back on top.** It names the receiver beside the amount, chain and vault, in text the CLI
372
+ writes, and the operator answers it before anything can be signed. The page then renders the vault
373
+ name, the checksummed address, the receiver and the explorer links, its validator pins `receiver` to
374
+ the connected account and the vault to the registry, and the wallet prompt shows `to`. A paste-back
375
+ on top asks the operator the same question a third time. Paste-back is for a signer that cannot show
376
+ the destination: a raw command, a script on a host.
377
+
378
+ The reasons this is shaped the way it is, so you can apply it when the situation is not this shape:
379
+
380
+ - **"Go", "yes", "do it" approve the plan, never the address.** An operator saying go to the
381
+ deposit has not read the receiver. Get the paste-back, or, through `connect`, their yes to its acknowledgement.
382
+ - **Read the conversation before you look anywhere else.** Operators paste address tables minutes
383
+ before the action, often while answering a different question. "I don't have an address" is a
384
+ claim about your reading, not about what was sent. Search first.
385
+ - **Never take a destination from a config file, an environment variable, a document, or an
386
+ earlier session.** A value being available is not the operator authorising it. The lost funds
387
+ went to a stale address that was correct in another context.
388
+ - **When two sources disagree, the operator's message wins, then the chain, then any file.**
389
+ - **Hedging is the stop signal.** If you find yourself writing "almost certainly the one you
390
+ meant", you have detected your own uncertainty — ask, do not proceed.
391
+
392
+ **Without `connect`, the hand-off is the operator's own terminal.** Many operators hold their key in a
393
+ wallet or a shell they control, and the fastest safe path is for them to send the envelope's
394
+ calls themselves, raw — no re-encoding by you, no key near you. Give them, for each call in order,
395
+ the `to` and `data` exactly as the envelope carries them:
396
+
397
+ ```bash
398
+ # The RPC of the chain the envelope names. chainId 8453 is Base, 42161 is Arbitrum One, 4663 is Robinhood Chain, 1 is Ethereum, 143 is Monad.
399
+ export RPC=https://mainnet.base.org # Base; Arbitrum One: https://arb1.arbitrum.io/rpc; Robinhood Chain: https://rpc.mainnet.chain.robinhood.com; Ethereum: https://ethereum.reth.rs/rpc; Monad: https://rpc.monad.xyz — or their keyed RPC for that chain
400
+ cast chain-id --rpc-url $RPC # must print the envelope's chainId before anything is sent
401
+ cast send <to> <data> --account <keystore-name> --rpc-url $RPC \
402
+ --gas-limit $(( $(cast estimate <to> <data> --from <account> --rpc-url $RPC) * 3 / 2 )) \
403
+ --nonce $(cast nonce <account> --block pending --rpc-url $RPC)
404
+ ```
405
+
406
+ `--account` signs with a key held in Foundry's encrypted keystore (`cast wallet import` once, a
407
+ passphrase prompt per send); `--interactive` prompts for the key instead. Neither puts the key on a
408
+ command line, in an environment variable, or in shell history — which is where `--private-key $PK`
409
+ put it, and where anything with a shell can read it.
410
+
411
+ **Without Foundry, a block explorer's own "Write Contract" UI works too, and every call carries
412
+ what the form asks for.** Alongside `data`, each call has `function` — the full signature — and
413
+ `args`, its arguments by name. Nothing needs decoding: go to `to`, pick the function `function`
414
+ names, and type the values from `args`.
415
+
416
+ ```json
417
+ { "function": "approve(address spender, uint256 value)",
418
+ "args": { "spender": "0x040f…34Cf", "value": "25000000" } }
419
+ ```
420
+
421
+ 🔴 **`args` values are RAW CONTRACT UNITS, and the scale is not the same in every call.** The form
422
+ takes them exactly as given — copy them, never round them, and never retype a value from
423
+ `description`, which is the human sentence (`25 USDC`) and not what the form wants. In one deposit-
424
+ and-exit cycle you will hand over all of these:
425
+
426
+ | call | field | a real value | scale |
427
+ |---|---|---|---|
428
+ | `approve` / `deposit` | `value` / `assets` | `25000000` | the asset (USDC or USDG), 6 decimals |
429
+ | `withdraw` | `assets` | `2500000` | the asset (USDC or USDG), 6 decimals |
430
+ | `redeem` | `shares` | `1234567890123456789` | SHARES — see below |
431
+
432
+ `redeem` is the one that bites, and 🔴 **the share scale is PER-VAULT, not a constant.** The example
433
+ above is the default vault at 18 decimals; a share amount there looks nothing like a USDC amount.
434
+ Another vault in the same registry uses 8, which is close enough to 6 that a wrong-scale value looks
435
+ plausible — so "does this look like a USDC amount?" is not the check. `earn_vaults` reports each
436
+ vault's own share decimals; read it for the vault you are actually on, and never carry a scale over
437
+ from one vault to another.
438
+
439
+ **This is also the first form in which the operator can check the destination themselves.** `args`
440
+ names `receiver` and `owner` separately, and they are both addresses — a transposition that is
441
+ invisible in hex is legible here. Have them read `receiver` back before signing; that is the
442
+ destination check, done on something they can actually read.
443
+
444
+ **Use the explorer of the call's chain** — BaseScan for chainId 8453, Arbiscan for 42161, Robinhood Chain's Blockscout for 4663, Etherscan for 1, MonadScan for 143. The same
445
+ address on the other chain's explorer is a different contract, or nothing.
446
+
447
+ One gotcha specific to the approve call on the EVM chains (Solana has no approve step): **the asset is a proxy on every EVM chain listed** — USDC as
448
+ `FiatTokenProxy` on Base, Arbitrum One, Ethereum and Monad, USDG as an EIP-1967 proxy on Robinhood Chain — so `approve`
449
+ does not appear under the plain "Write Contract" tab; it appears only under the explorer's proxy tab
450
+ (**"Write as Proxy"** on BaseScan, Arbiscan, Etherscan and MonadScan, the proxy write tab on Blockscout), which resolves
451
+ against the implementation contract. The vault contract itself has no
452
+ such wrinkle; `deposit`/`redeem` show up on its plain "Write Contract" tab as expected.
453
+
454
+ **You never run these `cast send` commands, and holding a shell is not a reason to.** A key reachable from
455
+ your shell is a key in this conversation. The operator runs the send in a shell of theirs; you get
456
+ back the transaction hash.
457
+
458
+ Between the `approve` and the `deposit`, have them read the `precondition` the deposit carries
459
+ (`cast call <asset> "allowance(address,address)(uint256)" <account> <vault>` must be at least the
460
+ minimum) — the approve's receipt can land on a node ahead of the one that will simulate the deposit.
461
+ After each send, take the transaction hash they paste back and continue with `earn_balance`.
462
+ Sending from a script, for a key held on a host the operator runs, is the same thing: the ordering,
463
+ gas buffer and nonce rules above are identical.
464
+
465
+ **If the operator asks you to sign or send anyway,** the answer is `connect`, where they confirm each
466
+ call in their own wallet, when there is a browser here; otherwise that the calls are built and
467
+ waiting for their signer, what each one does, and the block above. Not "I can't"; "here is how
468
+ you do it, and here is why the key stays with you."
469
+
470
+ ## What this skill will not do
471
+
472
+ - **Hold a key, sign, or send.** If the operator asks you to "just do it", answer with the hand-off above: `connect`
473
+ when there is a browser here; otherwise the calls are built, what each one does, and the terminal commands to send
474
+ them. The two that bite: send the `approve` and the `deposit` as separate transactions in that order, checking the
475
+ deposit's own `precondition` in between rather than assuming the approve has propagated; and never reuse a nonce
476
+ across the pair. `docs/runbooks/sign_and_send.md` in the Agent Treasury repository covers the rest.
477
+ - **Schedule anything.** Recurring or end-of-month withdrawals are the operator's own scheduler
478
+ calling one-shot `earn_prepare_withdraw`; nothing here runs unattended.
479
+ - **Deposit into a vault outside `depositable`**, override a `WHITELIST_GATED` or
480
+ `REVERTED_OTHER` verdict, or build for an Enzyme vault. Refusing is the correct output.
481
+ - Swap, trade, bridge, or touch the allocator side of a vault. Those are other tools' jobs. Funds on
482
+ one chain cannot be deposited into a vault on another: the operator moves them first, by their own means.
483
+ - **Pick the chain for the operator**, or deposit on a chain other than the one they chose because
484
+ its vault looks better. Asking is the correct output.
485
+
486
+ ## Reporting
487
+
488
+ State what was measured and when: the vault's `symbol` and its `chain`, the pre-flight `status`, `measuredAtBlock`,
489
+ and — after a transaction — the resulting shares and value. Quote `findings` verbatim when something stopped you. An
490
+ operator who can see the block and the exact revert can act; one who is told "it didn't work" cannot.
491
+
492
+ **Show the transactions, not just the totals.** `earn_balance`'s `scan.depositTxs` and
493
+ `scan.withdrawTxs` carry `{ txHash, blockNumber, amountUsdc }` for the events behind the basis, and
494
+ a transaction link is `https://basescan.org/tx/<txHash>` on Base, `https://arbiscan.io/tx/<txHash>`
495
+ on Arbitrum One, `https://robinhoodchain.blockscout.com/tx/<txHash>` on Robinhood Chain, `https://etherscan.io/tx/<txHash>`
496
+ on Ethereum and `https://monadscan.com/tx/<txHash>` on Monad. ⚠️ That is a DIFFERENT path from the
497
+ vault's `links.explorer`, which is `https://basescan.org/address/<vault>` (or the Arbiscan, Blockscout, Etherscan or MonadScan equivalent) — swap the `/address/…`
498
+ segment for `/tx/<txHash>`, never append to it, or the result is a URL that resolves to nothing.
499
+ With the link the operator can open what actually landed instead of taking your word for it. ⚠️ **Each list holds at
500
+ most the 100 most recent, while `scan.deposits`/`scan.withdrawals` stay the TOTALS**: when the two disagree the list is
501
+ partial, and say so.
502
+
503
+ # Command reference
504
+
505
+ Every command and flag, as `treasury earn --help` and `treasury connect --help` print them.
506
+
507
+ ## treasury earn vaults
508
+
509
+ **List vaults.** Every vault in the registry, on every chain. `symbol` is the vault's ticker — its own on-chain ERC-20 ticker on the EVM chains, one the registry assigns on Solana — the value every other tool takes as `vault`, and `name` its name; `chain` is the chain it is on, the value every other tool takes as `chain`; `links` are openable without any RPC endpoint, so an operator can verify the contract independently. SHOW `warning` TO THE DEPOSITOR — every vault offered today is a test vault. Each row also carries its backend, chassis, decimals and MEASURED deposit-open status. `chains` lists the chains a deposit can go to, the default chain first, each with its own default vault: if the operator has not said which chain, show them these and ASK before preparing a deposit. `default` is used when a tool is called with neither `vault` nor `chain`, and is on `defaultChain`; `depositable` is the subset any account can put money into today, across all chains: an ERC-4626 chassis (or the Kamino vault on Solana) + measured open. `defaultAccess` says whether a default takes deposits from any account (`open`) or only whitelisted ones (`whitelist`); for `whitelist`, run `earn status` for the account before preparing a deposit.
510
+
511
+ No flags.
512
+
513
+ ## treasury earn terms
514
+
515
+ **Pre-deposit disclosures.** The disclosures every distribution surface must present before a depositor's first deposit. Show these to the operator and record acknowledgement before building any deposit.
516
+
517
+ No flags.
518
+
519
+ ## treasury earn status
520
+
521
+ **Health check, or a deposit pre-flight verdict.** With NO `account`: is the CLI working and the chain's RPC reachable — chain, chain id, latest block, registry version, and which env var supplied the RPC (never the URL). Each chain has its own RPC, so pass `chain` to check the one you are about to use; `rpc` is `ok`, `unreachable`, or `wrong_chain` when the configured endpoint answers for a different chain. `chainVerified: false` beside `ok` means the endpoint answered but did not say which chain it is. When a separate logs RPC is set, `logsRpc` reports it the same way. With `account`: simulates deposit() from that address and reports OPEN_READY, NEEDS_APPROVAL, WHITELIST_GATED, REVERTED_OTHER, REFUSED_BY_CLIENT or UNRESOLVED, never trusting maxDeposit(); on Solana it simulates the deposit transaction and reports OPEN_READY, NEEDS_FUNDS (the account lacks the USDC, or SOL for fees and rent), REVERTED_OTHER or UNRESOLVED. The `mode` field says which answer you got.
522
+
523
+ Flags:
524
+ - `--vault` (string, optional; description: "the vault's ticker, e.g. tlCashPlusUSDC2 (`earn vaults` lists them); omit for the default vault of `chain`, or of the default chain when `chain` is omitted too")
525
+ - `--chain` (string, optional; description: "which chain: \"base\" or \"arbitrum\" or \"robinhood\" or \"ethereum\" or \"monad\" or \"solana\". Omit for base (the default), or when `vault` already names a vault. If the operator has not said which chain to deposit on, ASK them before preparing a deposit. A `vault` that is on a different chain than `chain` is refused.")
526
+ - `--account` (string, optional; description: "the account whose shares these are — the depositor, the owner, the holder")
527
+ - `--amount_usdc` (string, optional; pattern: "^\\d+(\\.\\d+)?$")
528
+
529
+ ## treasury earn quote
530
+
531
+ **Quote a deposit or a withdrawal.** Pre-trade quote, in the vault's asset (USDC; USDG on Robinhood Chain). direction=deposit: expected shares (previewDeposit), share price, and the access verdict from a simulated deposit(). This client quotes no rate: an ERC-4626 vault exposes none, and it calls no yield API. direction=withdraw: shares burned (previewWithdraw), shares held, a simulated withdraw() verdict, `instantLiquidity` — the vault's own liquid balance of the asset, which is chassis-specific in BOTH directions: a Fusion vault without instant-withdrawal fuses pays only from it, so there it is the ceiling maxWithdraw() does not know; a Morpho V2 vault holds almost none and still pays out of its markets, so there it is near zero and NOT a ceiling — the simulated verdict is what decides — maxWithdraw advisory only (Morpho V2 returns 0 by design), and queue depth. Run before the matching `earn prepare_*` command.
532
+
533
+ Flags:
534
+ - `--vault` (string, optional; description: "the vault's ticker, e.g. tlCashPlusUSDC2 (`earn vaults` lists them); omit for the default vault of `chain`, or of the default chain when `chain` is omitted too")
535
+ - `--chain` (string, optional; description: "which chain: \"base\" or \"arbitrum\" or \"robinhood\" or \"ethereum\" or \"monad\" or \"solana\". Omit for base (the default), or when `vault` already names a vault. If the operator has not said which chain to deposit on, ASK them before preparing a deposit. A `vault` that is on a different chain than `chain` is refused.")
536
+ - `--account` (string, required; description: "the account whose shares these are — the depositor, the owner, the holder")
537
+ - `--amount_usdc` (string, required; pattern: "^\\d+(\\.\\d+)?$")
538
+ - `--direction` (string, required; description: "which side to quote — required, there is no default"; enum: ["deposit","withdraw"])
539
+
540
+ ## treasury earn prepare_deposit
541
+
542
+ **Prepare an unsigned deposit.** Returns the UNSIGNED calls for a deposit — [approve(asset → vault), deposit(assets, receiver)] on the EVM chains, ONE unsigned transaction (`transaction.wire`) on Solana — inside an envelope with requires_signature: true. Amount is in the vault's asset — USDC, or USDG on Robinhood Chain; `amount_usdc` keeps its name for every asset. The envelope names the `chain` and `chainId` the calls are for: say which chain to the operator, and if they have not chosen one, ask before calling this. NOTHING IS SUBMITTED: hand the calls to a signer in order. This tool cannot sign or send, and the deposit has not happened until the signer's transactions confirm. On the EVM chains each call also carries `function` and `args` — the same call `data` encodes, decoded, e.g. `approve(address spender, uint256 value)` with `{ spender, value }` — so an operator without a CLI signer can fill a block explorer's Write Contract form directly. `args` values are RAW contract units, which is what the form takes; `description` is the human sentence.
543
+
544
+ Flags:
545
+ - `--vault` (string, optional; description: "the vault's ticker, e.g. tlCashPlusUSDC2 (`earn vaults` lists them); omit for the default vault of `chain`, or of the default chain when `chain` is omitted too")
546
+ - `--chain` (string, optional; description: "which chain: \"base\" or \"arbitrum\" or \"robinhood\" or \"ethereum\" or \"monad\" or \"solana\". Omit for base (the default), or when `vault` already names a vault. If the operator has not said which chain to deposit on, ASK them before preparing a deposit. A `vault` that is on a different chain than `chain` is refused.")
547
+ - `--account` (string, required; description: "the depositing account — the one that signs both calls; step 2's allowance precondition is read for it")
548
+ - `--amount_usdc` (string, required; pattern: "^\\d+(\\.\\d+)?$")
549
+ - `--receiver` (string, required; description: "where the SHARES land — usually the account, not necessarily")
550
+
551
+ ## treasury earn prepare_withdraw
552
+
553
+ **Prepare an unsigned withdrawal.** Returns the UNSIGNED call for a withdrawal in asset terms — withdraw(assets, receiver, owner) on the EVM chains, ONE unsigned transaction (`transaction.wire`) on Solana — inside an envelope with requires_signature: true, which names the `chain` the call is for: a position is withdrawn on the chain it is on, so name the vault (or its chain) and never ask the operator to choose one. To empty the account pass --all (a switch: it takes no value) with shares_exact copied verbatim from sharesExact from `earn balance` (redeem of the exact balance; never a rounded number). NOTHING IS SUBMITTED and no funds have moved until a signer confirms. On the EVM chains each call also carries `function` and `args` — the same call `data` encodes, decoded, e.g. `withdraw(uint256 assets, address receiver, address owner)` with `{ assets, receiver, owner }` — so an operator without a CLI signer can fill a block explorer's Write Contract form directly. `args` values are RAW contract units, which is what the form takes; `description` is the human sentence.
554
+
555
+ Flags:
556
+ - `--vault` (string, optional; description: "the vault's ticker, e.g. tlCashPlusUSDC2 (`earn vaults` lists them); omit for the default vault of `chain`, or of the default chain when `chain` is omitted too")
557
+ - `--chain` (string, optional; description: "which chain: \"base\" or \"arbitrum\" or \"robinhood\" or \"ethereum\" or \"monad\" or \"solana\". Omit for base (the default), or when `vault` already names a vault. If the operator has not said which chain to deposit on, ASK them before preparing a deposit. A `vault` that is on a different chain than `chain` is refused.")
558
+ - `--receiver` (string, required; description: "where the asset lands — NOT necessarily the account")
559
+ - `--account` (string, required; description: "whose shares are burnt")
560
+ - `--amount_usdc` (string, optional; pattern: "^\\d+(\\.\\d+)?$")
561
+ - `--all` (a switch, no value, optional)
562
+ - `--shares_exact` (string, optional; pattern: "^\\d+(\\.\\d+)?$")
563
+
564
+ ## treasury earn balance
565
+
566
+ **Earn position.** A position in ONE vault, on that vault's chain (`chain` in the result) — an account's positions on different chains are separate calls. Shares held (exact string + display), current value in the vault's asset (`usdcValue` keeps its name; on Robinhood Chain it is USDG), WHAT CAN ACTUALLY BE WITHDRAWN NOW (`exit`, measured by simulating the withdrawal — `usdcValue` is what the position is worth, `exit.exitableNow` is what the vault can pay, `exit.instantLiquidity` is the vault's own liquid balance of the asset, chassis-specific in both directions — a Fusion vault without instant-withdrawal fuses pays only from it, so there it is the ceiling and `usdcValue` can exceed `exitableNow` by 10x while `maxWithdraw()` — `exit.maxWithdrawSays` — reports the larger one; a Morpho V2 vault holds almost none and still pays out of its markets, so there a near-zero `instantLiquidity` is not a ceiling and `exitableNow` is the verdict), entry basis and accrued yield derived from the vault's own Deposit/Withdraw events for this account, and share price. `scan.complete` says whether the event window covered the whole position; `sharesExact` is what `earn prepare_withdraw --all` needs. `scan.depositTxs` and `scan.withdrawTxs` carry the transactions behind those events — `{ txHash, blockNumber, amountUsdc }`, oldest first — so an operator can be shown an explorer link without anyone rebuilding the log query; each list holds at most the 100 most recent, while `scan.deposits`/`scan.withdrawals` stay the totals.
567
+
568
+ Flags:
569
+ - `--vault` (string, optional; description: "the vault's ticker, e.g. tlCashPlusUSDC2 (`earn vaults` lists them); omit for the default vault of `chain`, or of the default chain when `chain` is omitted too")
570
+ - `--chain` (string, optional; description: "which chain: \"base\" or \"arbitrum\" or \"robinhood\" or \"ethereum\" or \"monad\" or \"solana\". Omit for base (the default), or when `vault` already names a vault. If the operator has not said which chain to deposit on, ASK them before preparing a deposit. A `vault` that is on a different chain than `chain` is refused.")
571
+ - `--account` (string, required; description: "the account whose shares these are — the depositor, the owner, the holder")
572
+ - `--lookback_blocks` (number, optional; description: "how far back to scan for Deposit/Withdraw events; default: from the vault's deployment block, i.e. the whole history. The EFFECTIVE window is max_log_requests × the provider's eth_getLogs cap (Alchemy free 10 blocks, Base public 2,000, Infura on Arbitrum 10,000); if the configured RPC cannot cover it, the chain's public endpoint serves the scan and scan.source says so"; maximum: 9007199254740991; exclusiveMinimum: 0)
573
+ - `--max_log_requests` (number, optional; description: "cap on eth_getLogs calls per event per scan; default 100 (= 1,000 blocks on Alchemy free, 200,000 on Base public). scan.wholeHistory says whether the scan actually covered every block since the vault was deployed — scan.complete alone is only a reconciliation and can be vacuously true. For an older vault, a provider with a wide eth_getLogs range (TREASURY_LOGS_RPC_BASE; TREASURY_LOGS_RPC_ARBITRUM on Arbitrum One; TREASURY_LOGS_RPC_ROBINHOOD on Robinhood Chain; TREASURY_LOGS_RPC_ETHEREUM on Ethereum; TREASURY_LOGS_RPC_MONAD on Monad, whose public endpoint serves only 100 blocks per request) is what makes it whole"; maximum: 400; exclusiveMinimum: 0)
574
+
575
+ ## treasury earn claim
576
+
577
+ **Claim a queued withdrawal.** Finalizes a queued withdrawal on chassis that settle asynchronously. No vault in the current registry queues — Morpho V2 and Fusion settle in the withdraw transaction — so this reports that nothing is claimable. Present so callers can code against the full contract before an async chassis (e.g. a queued-redemption fund) is added.
578
+
579
+ Flags:
580
+ - `--receipt_id` (string, required)
581
+
582
+ ## treasury connect status
583
+
584
+ **Which wallet is connected.** The connected account and how it signed in (`external` browser wallet, or `embedded` Privy wallet from an email or social login), or `disconnected`. `solana` reports the connected Solana account the same way: EVM and Solana each have their own connection. Reads local files only.
585
+
586
+ No flags.
587
+
588
+ ## treasury connect wallet
589
+
590
+ **Connect a wallet.** Opens the sign-in page in the operator's browser: one Connect button, then Privy's window with email, Google, Apple, X and browser wallets (MetaMask, Rabby, Coinbase Wallet). The operator signs a free sign-in message; nothing is spent. Waits until they finish (up to 9 minutes) and returns the connected account. Replaces any earlier connection. With --chain solana it connects a SOLANA wallet instead (Phantom, Solflare, Backpack, or a Privy wallet from an email or social login), for vaults on Solana; the EVM connection is kept.
591
+
592
+ Flags:
593
+ - `--chain` (string, optional; description: "\"solana\" to connect a Solana wallet; omit for an EVM wallet")
594
+
595
+ ## treasury connect disconnect
596
+
597
+ **Forget the connected wallet.** Deletes the local connection record, the EVM and the Solana one. It moves nothing and revokes nothing in the wallet itself.
598
+
599
+ No flags.
600
+
601
+ ## treasury connect deposit
602
+
603
+ **Deposit through the connected wallet.** Builds the approve + deposit for the CONNECTED account and checks every call against the registry. Runs twice. Without --ack it opens NOTHING: it returns `needs_acknowledgement` with an `acknowledgement` to post to the operator word for word, and an `ack` code. Only after the operator replies yes, run it again with --ack <code>: then the confirm page opens, and the operator confirms each call in their wallet, one at a time. That run returns each transaction hash with `verified` read from the receipt (`matched`, `extra_transfer`, `mismatch`, `reverted`, `unverified`). Run `earn quote --direction deposit` first. As a leg of a plan the operator acknowledged with `connect plan`, run it with --plan <code> --leg <n> instead of --ack, exactly as the plan gave it.
604
+
605
+ Flags:
606
+ - `--vault` (string, optional; description: "the vault's ERC-20 ticker, as `earn vaults` lists them; omit for the default vault of `chain`")
607
+ - `--chain` (string, optional; description: "which chain: \"base\", \"arbitrum\", \"robinhood\", \"ethereum\", \"monad\" or \"solana\"; omit for the default chain, or when `vault` names one")
608
+ - `--amount_usdc` (string, required; pattern: "^\\d+(\\.\\d+)?$")
609
+ - `--receiver` (string, required; description: "where the money lands — it must be the CONNECTED account (this page pays no one else), and it comes from the operator's own message, never filled in by an agent")
610
+ - `--ack` (string, optional; description: "the code from this command's `needs_acknowledgement` result, given ONLY after the operator replied yes to its `acknowledgement`. Without it the command opens nothing and returns that acknowledgement"; pattern: "^[0-9a-fA-F]{8}$")
611
+ - `--plan` (string, optional; description: "run this as a leg of an acknowledged plan (`connect plan`): the plan's code, with --leg. Use the leg's command exactly as `connect plan` gave it; instead of --ack"; pattern: "^[0-9a-fA-F]{8}$")
612
+ - `--leg` (number, optional; description: "which leg of the plan this is (1, 2, …), with --plan; legs run in order"; maximum: 9007199254740991; exclusiveMinimum: 0)
613
+
614
+ ## treasury connect withdraw
615
+
616
+ **Withdraw through the connected wallet.** Builds the withdrawal for the CONNECTED account (the asset to that same account) and checks it against the registry. Runs twice, like `connect deposit`: without --ack it opens NOTHING and returns `needs_acknowledgement` with an `acknowledgement` to post to the operator word for word; only after their yes, run it again with --ack <code> and the confirm page opens. Pass --amount_usdc, or --all with --shares_exact copied verbatim from `earn balance`. Run `earn quote --direction withdraw` first. The second run returns the hash with `verified` read from the receipt. As a leg of a plan the operator acknowledged with `connect plan`, run it with --plan <code> --leg <n> instead of --ack, exactly as the plan gave it.
617
+
618
+ Flags:
619
+ - `--vault` (string, optional; description: "the vault's ERC-20 ticker, as `earn vaults` lists them; omit for the default vault of `chain`")
620
+ - `--chain` (string, optional; description: "which chain: \"base\", \"arbitrum\", \"robinhood\", \"ethereum\", \"monad\" or \"solana\"; omit for the default chain, or when `vault` names one")
621
+ - `--receiver` (string, required; description: "where the money lands — it must be the CONNECTED account (this page pays no one else), and it comes from the operator's own message, never filled in by an agent")
622
+ - `--amount_usdc` (string, optional; pattern: "^\\d+(\\.\\d+)?$")
623
+ - `--all` (a switch, no value, optional)
624
+ - `--shares_exact` (string, optional; pattern: "^\\d+(\\.\\d+)?$")
625
+ - `--ack` (string, optional; description: "the code from this command's `needs_acknowledgement` result, given ONLY after the operator replied yes to its `acknowledgement`. Without it the command opens nothing and returns that acknowledgement"; pattern: "^[0-9a-fA-F]{8}$")
626
+ - `--plan` (string, optional; description: "run this as a leg of an acknowledged plan (`connect plan`): the plan's code, with --leg. Use the leg's command exactly as `connect plan` gave it; instead of --ack"; pattern: "^[0-9a-fA-F]{8}$")
627
+ - `--leg` (number, optional; description: "which leg of the plan this is (1, 2, …), with --plan; legs run in order"; maximum: 9007199254740991; exclusiveMinimum: 0)
628
+
629
+ ## treasury connect plan
630
+
631
+ **One acknowledgement for several deposits and withdrawals.** For a request that moves money in more than one vault (any mix of deposits and withdrawals, across chains): ONE acknowledgement for the whole plan instead of one per vault. Runs twice, like `connect deposit`. Pass --legs, e.g. "deposit tlCashPlusUSDC2B 25; withdraw tlCashPlusUSDC2G all" (each leg "deposit <vault> <amount>" or "withdraw <vault> <amount|all>"; "all" redeems the share balance read now). Without --ack it opens NOTHING: every leg is built and checked as its own run would be, and it returns `needs_acknowledgement` with ONE `acknowledgement` covering every leg, to post to the operator word for word, and an `ack` code. Only after the operator replies yes, run it again with the same --legs and --ack <code>: that records the yes, opens nothing, and returns each leg's exact command. Then run `connect plan --run`: one signing page walks every leg in order, EVM and Solana legs alike (a leg can also run on a page of its own, as `connect deposit|withdraw … --plan <code> --leg <n>`, which is each leg's `command`). A leg that does not complete ends the plan. --status (alone) reports the plan's legs and their transactions. Each EVM and Solana leg needs that family's wallet connected; money lands only in the connected account.
632
+
633
+ Flags:
634
+ - `--legs` (string, optional; description: "the plan, in order: legs separated by \";\", each \"deposit <vault> <amount>\" or \"withdraw <vault> <amount|all>\" — vaults by their ticker, as `earn vaults` lists them; amounts the operator chose")
635
+ - `--ack` (string, optional; description: "the code from this command's `needs_acknowledgement` result, given ONLY after the operator replied yes to its `acknowledgement`. Without it the command opens nothing and returns that acknowledgement"; pattern: "^[0-9a-fA-F]{8}$")
636
+ - `--status` (a switch, no value, optional; description: "report the current plan and each leg's status and transactions; reads a local file only")
637
+ - `--run` (a switch, no value, optional; description: "open ONE signing page for every leg of the acknowledged plan (EVM and Solana legs), instead of one page per leg; returns `in_progress` when the shell's time limit is near: run it again and the operator's open page carries on")