@temporalabs/treasury 0.1.2 → 0.1.3

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 (81) hide show
  1. package/README.md +44 -12
  2. package/THIRD_PARTY_NOTICES.connect-page.md +416 -0
  3. package/THIRD_PARTY_NOTICES.md +575 -2
  4. package/dist/cli.js +28 -5
  5. package/dist/cli.js.map +1 -1
  6. package/dist/client.d.ts +149 -7
  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 +18 -1
  13. package/dist/connect/ack.js +42 -6
  14. package/dist/connect/ack.js.map +1 -1
  15. package/dist/connect/commands.d.ts +3 -0
  16. package/dist/connect/commands.js +168 -24
  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 +7 -1
  25. package/dist/connect/http.js +41 -11
  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 +23 -0
  31. package/dist/connect/session.js +126 -1
  32. package/dist/connect/session.js.map +1 -1
  33. package/dist/connect/solana.d.ts +73 -0
  34. package/dist/connect/solana.js +267 -0
  35. package/dist/connect/solana.js.map +1 -0
  36. package/dist/connect-page.js +604 -602
  37. package/dist/disclosures.d.ts +5 -4
  38. package/dist/disclosures.js +5 -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 +302 -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/registry-schema.d.ts +79 -2
  56. package/dist/registry-schema.js +77 -1
  57. package/dist/registry-schema.js.map +1 -1
  58. package/dist/registry.d.ts +21 -1
  59. package/dist/registry.js +48 -10
  60. package/dist/registry.js.map +1 -1
  61. package/dist/solana/constants.d.ts +49 -0
  62. package/dist/solana/constants.js +50 -0
  63. package/dist/solana/constants.js.map +1 -0
  64. package/dist/solana/kvault.d.ts +99 -0
  65. package/dist/solana/kvault.js +192 -0
  66. package/dist/solana/kvault.js.map +1 -0
  67. package/dist/solana/layout.d.ts +143 -0
  68. package/dist/solana/layout.js +209 -0
  69. package/dist/solana/layout.js.map +1 -0
  70. package/dist/solana/rpc.d.ts +24 -0
  71. package/dist/solana/rpc.js +66 -0
  72. package/dist/solana/rpc.js.map +1 -0
  73. package/dist/solana/vault.d.ts +153 -0
  74. package/dist/solana/vault.js +324 -0
  75. package/dist/solana/vault.js.map +1 -0
  76. package/dist/treasury.mjs +8123 -1082
  77. package/dist/version.js +5 -10
  78. package/dist/version.js.map +1 -1
  79. package/llms.txt +621 -0
  80. package/package.json +9 -6
  81. package/registry/vaults.json +113 -0
package/llms.txt ADDED
@@ -0,0 +1,621 @@
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.3/llms.txt.
7
+ Run commands as `treasury <skill> <command> [--flag value …]` (`npx @temporalabs/treasury@0.1.3 …` 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 five `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 spread money across vaults in one sitting — "1 USDC on Base and 3 on Solana", "2 USDC on every chain",
146
+ "1 in 2B and 1 in 2E". The vaults may be on different chains or the same one. That is **one deposit per vault**, each a
147
+ complete deposit of its own with its own transaction; nothing moves money into several vaults at once.
148
+
149
+ 1. **Settle the list from the operator's own words.** Each vault, its chain and its amount. A chain named
150
+ without a vault means that chain's default vault. "Every chain" means every entry in `earn_vaults` →
151
+ `chains`, each with its default vault; "every vault" means every vault in `earn_vaults` →
152
+ `depositable`. A listed vault that is not in `depositable` cannot take the deposit: name it to the
153
+ operator rather than dropping it silently. Do not add, drop or resize a leg on your own.
154
+ 2. **Check every leg before any money moves:** `earn_quote` with `direction: "deposit"`, the real account and amount,
155
+ for each vault. The asset has to be on that vault's chain already (USDG, not USDC, on Robinhood Chain); this skill
156
+ does not bridge, so a leg whose funds are elsewhere cannot go ahead until the operator moves them. Two legs on one
157
+ chain draw on the same balance: check it covers both. On Solana the account also needs SOL, and on Ethereum gas can
158
+ be a large share of a small amount: say so for that leg.
159
+ 3. **Show one plan and get a yes to it:** a row per leg (vault, chain, amount, asset) with the total, and
160
+ which legs cannot go ahead and why. Changing the plan is the operator's call, not yours.
161
+ 4. **Then run the legs one at a time, each through the whole deposit flow below.** Each leg has its own receiver check,
162
+ and through `connect` its own acknowledgement and yes. `earn_terms` comes once, before the operator's first deposit,
163
+ as below. The EVM chains share one connected wallet; Solana needs its own (`connect wallet --chain solana`). Do not
164
+ start the next leg until the last one has landed or been abandoned.
165
+ 5. **If a leg fails or the operator stops it, stop and ask before the rest.** Then report every leg:
166
+ vault, chain, amount, transaction, and which did not happen.
167
+
168
+ **Positions and withdrawals across vaults work the same way.** "What do I hold?" is `earn_balance` on every vault `earn_vaults` lists (the EVM account and the Solana account each), reported as one table: per vault, its chain, what it is worth and what it can
169
+ be withdrawn for now, with the totals. "Withdraw everything" or "10 USDC from each" is settled from the operator's words the same way: the vaults that hold shares, each quoted with `direction: "withdraw"` (for everything, the amount its `earn_balance` `exit` gives, and the withdrawal then takes `--all` with that vault's own
170
+ `sharesExact`), one plan with what each can pay now and a yes to it, then one complete withdrawal per vault, one at a time. A vault that cannot pay its part now (liquidity) is a leg that cannot go ahead as asked: say so, and do not shrink it on your own.
171
+
172
+ ## Solana is different
173
+
174
+ The vault on Solana (`chain: "solana"`) is a Kamino vault, not an ERC-4626 one. The same commands work on it,
175
+ with these differences — say them to the operator where they apply:
176
+
177
+ - **Addresses are Solana addresses** (base58, no `0x`). An EVM address for a vault on Solana is refused, and the
178
+ other way round. The operator's Solana address comes from their own message, through the destination check,
179
+ like any other.
180
+ - **`--receiver` must be the account itself.** The vault mints shares to the depositing account and pays a
181
+ withdrawal to the account whose shares burn; there is no other receiver, and anything else is refused.
182
+ - **One transaction, and it expires.** `earn prepare_deposit` / `prepare_withdraw` return ONE unsigned
183
+ transaction (`transaction.wire`, base64), simulated before it is returned, that lands only before
184
+ `transaction.expiresAfterBlockHeight` — about a minute. Prepare it only when the operator is ready to sign,
185
+ and if the height has passed, prepare it again; never hand over an expired one. Through `connect`, the page
186
+ builds it when the operator presses Confirm, so this does not arise there.
187
+ - **The account needs SOL**, for the fee and, on a first deposit, about 0.002 SOL of rent for its share
188
+ account. `earn status --chain solana --account …` says `NEEDS_FUNDS` when it lacks USDC or SOL.
189
+ - **Each withdraw instruction keeps up to 0.001 USDC in the vault** — Kamino's charge for every vault on its
190
+ program, which Kamino can change; a withdrawal split across reserves pays it per part — and one worth 0.001
191
+ USDC or less is refused. Quotes report `receives`, exact and net of it; say so before a
192
+ small withdrawal.
193
+ - **No history on Solana:** `earn_balance` reports shares, value and what a full exit would pay now, but
194
+ `entryBasisUsdc` and `accruedYieldUsdc` read `unknown`. Do not present the value as profit.
195
+ - **`connect wallet --chain solana`** connects a Solana wallet (Phantom, Solflare, Backpack, or a Privy wallet
196
+ from an email or social login), separately from the EVM connection; `connect deposit` / `withdraw` with
197
+ `--chain solana` then use it, with the same acknowledgement step.
198
+
199
+ ## Opening an account and depositing
200
+
201
+ 1. **Settle the chain first (above), then the vault, then the amount — the operator's, asked for if
202
+ they have not given one, never one you choose.** If the operator names no vault, use that
203
+ chain's default (`earn_vaults` → `chains[].default`; today Tempora Labs Cash Plus USDC (Test 2B) on
204
+ Base, Tempora Labs Cash Plus USDC (Test 2C) on Arbitrum One, Tempora Labs Cash Plus USDG (Test 2D) on Robinhood Chain,
205
+ Tempora Labs Cash Plus USDC (Test 2E) on Ethereum, Tempora Labs Cash Plus USDC (Test 2F) on Monad and Tempora Labs Cash Plus
206
+ 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.
207
+ Read that chain's `defaultAccess` (`chains[].defaultAccess`; the top-level one is the default
208
+ chain's): when it is `"whitelist"`, run `earn_status` for the account first, and if
209
+ it returns `WHITELIST_GATED`, **tell the operator the account is not admitted to that vault and that
210
+ admission is a fund-side action — do not offer a substitute and do not retry.** A listed vault that
211
+ is not the default may be gated even when the default is open.
212
+ 2. **`depositable` may be EMPTY, and an empty set is an answer, not an error.** It is the set of
213
+ vaults any account can deposit into — ACTIVE, ERC-4626 (or the Kamino vault on Solana), *and measured open by simulation*. When
214
+ every vault on offer is whitelist-gated it is empty, which is the state to report, not a fault
215
+ to work around. Read it from `earn_vaults` rather than assuming either way. A vault can be live and still refuse you: IPOR Fusion
216
+ vaults restrict `deposit()` to a whitelist role while their `redeem()` is public, and Enzyme
217
+ vaults are not ERC-4626 at all. Never read an empty `depositable` as "the tools are broken", and
218
+ never propose a vault that is not in the registry: this client deposits only where its registry
219
+ says, and reaching anywhere else is outside what it will build.
220
+ 3. **On a first deposit, present `earn_terms` and get an explicit acknowledgement.** The
221
+ fund requires this on every distribution surface, and an agent talking to its
222
+ operator is one. Do not paraphrase them into something friendlier. This holds through `connect`
223
+ too: post the full `earn_terms` once, before the operator's first deposit. The acknowledgement
224
+ `connect` returns restates them in plain words before every signing page; it comes after the full
225
+ terms on a first deposit, never instead of them. Show the full text again whenever the operator asks.
226
+ 4. **Always `earn_quote` with `direction: "deposit"`, the real `account`, and the operator's amount.** It includes the
227
+ pre-flight; read `preflight.status`, not `advisory.maxDepositRaw`: `maxDeposit()` returns "unlimited" on a
228
+ whitelist-gated Fusion vault and `0` on an open Morpho V2 vault — it is wrong in both
229
+ directions, which is why the pre-flight simulates the actual call instead.
230
+ - `NEEDS_APPROVAL` → normal; the deposit reached the token pull. Proceed to build. (EVM chains only.)
231
+ - `OPEN_READY` → on an EVM chain, the allowance already covers it; on Solana, which has no approve
232
+ step, the simulated deposit succeeded. Proceed to build.
233
+ - `NEEDS_FUNDS` (Solana only) → the simulation stopped at the account's own funds: no USDC, or no SOL
234
+ for the fee. Show the `findings`, and quote again once the operator has funded the account; it is
235
+ not a verdict that the vault would take the deposit.
236
+ - `WHITELIST_GATED` → stop. The operator needs the role granted (a fund-side action), not
237
+ a retry.
238
+ - `REVERTED_OTHER` → stop and show the `findings`; they list candidate causes. Do not guess
239
+ one.
240
+ - `REFUSED_BY_CLIENT` / `UNRESOLVED` → stop; the first is a registry/chain mismatch, the
241
+ second a transport failure. Neither is a verdict about the vault.
242
+ 5. **Through `connect`, skip this step:** `connect deposit` builds the same calls (on Solana, the one transaction) and
243
+ checks them, and opens the signing page only after the operator acknowledges them, every time (see **Handing calls to
244
+ the signer** below). Its acknowledgement restates the disclosures in plain words on every deposit; on a first deposit
245
+ the full `earn_terms` of step 3 still come first. **Otherwise, `earn_prepare_deposit`, then hand BOTH calls in
246
+ `calls` to the signer in order, and ask before each.** (On Solana it returns one transaction instead: see **Solana is
247
+ different** above.) The envelope names `chain` and `chainId`: tell the operator which chain these calls are for
248
+ before anything else, because a call sent on the wrong chain can be mined there and do nothing. The amount is in the
249
+ vault's asset (USDC, or USDG on Robinhood Chain; 6 decimals for both); the tool refuses more precision than that.
250
+ Show the operator each call's `description` — that sentence exists to be read by a human before a signature — **and
251
+ its `gasAdvice`**: Morpho V2 calls can run out of gas on an unbuffered estimate even when every simulation passes,
252
+ because accrual work grows with the time between estimate and inclusion. Estimate × 1.5. **And its `precondition`,
253
+ where present**: the `deposit` call names the read (`allowance(owner, spender) >= minimum`, raw units) that must hold
254
+ on the RPC the signer sends through before it is estimated or sent. The `approve` receipt can come from a node ahead
255
+ of the one simulating the `deposit` — measured on both real round trips — so a signer that reads the precondition
256
+ until it holds is deterministic and one that retries on an error string is not. **Then follow the envelope's
257
+ `signer_rules`**, which every prepared build carries: send on the chain the call's `chainId` names and no other,
258
+ confirm each destination against the operator's own addresses, set the nonce from the `pending` count before each
259
+ send, wait for each receipt, and after any failure that is not an on-chain revert, read the nonce on a different
260
+ provider before re-sending, so nothing is sent twice. The hand-off itself — the destination check and the operator's
261
+ own terminal — is the section **Handing calls to the signer** below; do not skip it for a small amount.
262
+ 6. **After it lands, `earn_balance`** and report value, basis, yield, and what can be withdrawn now. Until a signer's
263
+ transaction confirms, the deposit has NOT happened — `requires_signature: true` is the tool
264
+ saying so, and "prepared" is not "deposited". The vault's events
265
+ remember the deposit; the provider's log window decides how far back `earn_balance` can see.
266
+
267
+ ## Checking and withdrawing
268
+
269
+ 🔴 **`usdcValue` is what the position is WORTH; `exit.exitableNow` is what it can be WITHDRAWN for.**
270
+ Report both, and never quote the first as if a depositor could have it. On a Fusion vault without
271
+ instant-withdrawal fuses these differ by 10× — measured on a live Fusion test vault at block 51,327,076: the position was
272
+ worth 14.999970 USDC, `maxWithdraw()` agreed, and 1.498874 USDC already reverted because the vault
273
+ pays from its own 1.498873 USDC balance. `exit` is measured by simulating the withdrawal, so it is
274
+ right on both chassis; `exit.maxWithdrawSays` is reported only because other interfaces show it.
275
+ ⚠️ **Without a keyed RPC these two compete for the public endpoint's rate limit.** Measured
276
+ 2026-09-15 on a live Tempora Labs vault: with a key, both the exit and a whole-history scan complete in
277
+ about 15-22 s; with no key, one of them usually degrades — the exit reports `not measured`, or the
278
+ scan reports CUT SHORT. Each says so in its own result, so the answer is smaller, never wrong. A
279
+ keyed RPC for the vault's chain (`TREASURY_RPC_BASE`, `TREASURY_RPC_ARBITRUM`, `TREASURY_RPC_ROBINHOOD`, `TREASURY_RPC_ETHEREUM`, `TREASURY_RPC_MONAD`) is what makes both available in one call. On Solana,
280
+ which has no scan, the keyed RPC is `TREASURY_RPC_SOLANA`.
281
+
282
+ 🔴 **`measuredAs: "not measured"` means the RPC failed, NOT that the vault refused** — say so rather
283
+ than reporting a limit the chain never stated.
284
+
285
+ - `earn_balance` answers "what is it worth" at the current block, plus entry basis and accrued yield derived from the
286
+ vault's own `Deposit`/`Withdraw` events for this owner. Read `scan.wholeHistory`: when `true`, the scan reached the
287
+ deployment block, was not cut short, and shares in − shares out reconciles, so basis and yield are the account's
288
+ lifetime figures; when `false`, both read `unknown` — **never a bound over the covered window**, because a window that
289
+ reconciles can still have missed the deposit that produced the shares. The note says which provider window capped the
290
+ scan. Realized redemption is subject to the vault's caps and any queue, so quote value as a value, not a promise.
291
+ - **Withdraw in the vault's asset** (USDC; USDG on Robinhood Chain). `earn_quote` with `direction: "withdraw"`, then `connect withdraw` (same flags,
292
+ receiver = the connected account) or `earn_prepare_withdraw` with `amount_usdc` and the `account` whose shares burn; the vault burns
293
+ whatever shares that costs at inclusion. To empty the account, pass `--all` and
294
+ `--shares_exact` copied **verbatim** from `earn_balance.sharesExact` — never a number you rounded
295
+ or computed: an 18-decimal balance exceeds 2⁵³, floats round it (sometimes up), and redeeming
296
+ more than is owned reverts.
297
+ - **A withdrawal can be refused for liquidity, not balance.** Some vaults (Fusion) pay a withdrawal only from what they
298
+ hold un-deployed in that block; the rest of the account's value is in markets and comes back when the fund unwinds.
299
+ The withdraw quote reports `instantLiquidity` and, on a Fusion refusal, says so in those terms. Tell the operator that
300
+ at most that amount comes out now, and ask whether to withdraw that, another amount, or wait; do not shrink the
301
+ withdrawal yourself. A Morpho V2 vault holds almost no idle USDC and pays out of its markets, so there the quote shows
302
+ the chain's own revert reason instead, and a smaller amount may still pass: say so, and quote only an amount the
303
+ operator names. Either way, do **not** read a refusal as "approve first" (that is a deposit failure) and do not trust
304
+ `maxWithdraw`, which reports the full position on exactly these vaults.
305
+ - One call, one signature, same rules as depositing: show the description and `gasAdvice`, run
306
+ the destination check below on `receiver`, hand it over.
307
+
308
+ ## Handing calls to the signer
309
+
310
+ **First choice: the operator's own wallet, through `connect`.** It needs a browser on this machine: the
311
+ page is served on `localhost` only, so a phone or another computer cannot open it.
312
+ `connect wallet`, and `connect deposit` / `connect withdraw` run with `--ack`, each wait up to 9 minutes
313
+ for the operator, so give each of those shell calls a timeout of at least 9 minutes. The form is the same as `earn`:
314
+
315
+ ```bash
316
+ treasury connect deposit --vault <symbol> --amount_usdc <amount> --receiver 0x…
317
+ ```
318
+
319
+ 1. `connect status`. If disconnected, `connect wallet` opens a page; the operator clicks Connect and uses
320
+ a browser wallet, or an email, Google, Apple or X login (a Privy wallet). Wait for it to return.
321
+ 2. Run the earn quote as usual, then `connect deposit --amount_usdc <n> --receiver <addr>` (or
322
+ `connect withdraw …`), with the same `--vault`/`--chain`. 🔴 **This first run opens nothing.** It
323
+ returns `status: "needs_acknowledgement"` with `acknowledgement` (one fixed text naming the amount,
324
+ chain, vault and its explorer link, the receiver, the vault's warning and the disclosures) and an
325
+ `ack` code. **Post `acknowledgement` to the operator word for word and wait for their answer.** Do
326
+ not summarise it, shorten it, or skip it because they acknowledged an earlier one or said "go"
327
+ before they saw it: every page open has its own.
328
+ 3. **Only on the operator's explicit yes to that text**, run the same command again with `--ack <code>`
329
+ added. The page opens; the operator confirms each call there and in the wallet; the command returns
330
+ when they finish. Any answer but yes: stop. A refused `--ack` (expired after 15 minutes, already used,
331
+ or the amount, vault, chain or receiver changed) means go back to step 2 for a new acknowledgement,
332
+ never retry the code.
333
+ 4. Read `status` first. `completed`: every call landed. `stopped`: it ended early; read `reason` and the last transaction's `detail`.
334
+ `not_reported`: no transaction came back, which is not proof none was sent, so check `earn balance`
335
+ before any retry. Report every `hash` and its `verified`. `extra_transfer` means the wallet also moved money besides
336
+ the call (`alsoMoved`, often its own gas fee in USDC): say so. Anything else that is not `matched`:
337
+ stop, check `earn balance`, tell the operator.
338
+
339
+ `--receiver` must be the connected account (`connect status`), and it still comes from the operator,
340
+ not from you: the page pays only that account. For any other receiver, use the prepare commands below
341
+ with the operator's own signer.
342
+
343
+ **No browser here** (an SSH session, a container, a machine with no display): skip `connect` and use the
344
+ prepare commands. The first, acknowledgement run of `connect deposit` / `connect withdraw` always says
345
+ `opened: false`: it opens nothing by design. On `connect wallet`, or the run with `--ack`, `opened: false`
346
+ means no browser opened: the command printed the page's address on stderr, but it returns only after
347
+ the page has closed, so the address reaches you too late to pass on. Use the prepare commands instead.
348
+
349
+ Every prepared call names a destination: `to`, and the receiver or owner in its `description`. A
350
+ wrong destination is the one mistake nothing downstream can undo — the transaction succeeds, the
351
+ receipt says so, and the money is somewhere nobody holds a key for. Measured, once, for real: a
352
+ withdrawal sent to an address taken from a config file instead of the operator's message — the
353
+ funds gone, and the receipt read "success". So the check is not on the receipt. It is on where the
354
+ address came from.
355
+
356
+ **The address that can lose money is the `receiver` — and it comes from the operator's own
357
+ message, quoted back, and confirmed.** The call's `to` is the vault, taken from the registry and
358
+ re-checked by the pre-flight; the operator cannot verify a contract address by eye and is not asked
359
+ to. The receiver is where the shares or the asset (USDC or USDG) land, and only the operator knows which account
360
+ that should be. Before handing over any prepared call whose signer cannot show the receiver, post
361
+ this and wait:
362
+
363
+ > **Destination check — nothing goes to the signer until you reply.**
364
+ > shares/USDC/USDG to `<receiver>` ← from your message "<the words it came from>"
365
+ > Paste it back or correct it.
366
+
367
+ **Through `connect`, the acknowledgement is the check before the page opens — do not add a
368
+ paste-back on top.** It names the receiver beside the amount, chain and vault, in text the CLI
369
+ writes, and the operator answers it before anything can be signed. The page then renders the vault
370
+ name, the checksummed address, the receiver and the explorer links, its validator pins `receiver` to
371
+ the connected account and the vault to the registry, and the wallet prompt shows `to`. A paste-back
372
+ on top asks the operator the same question a third time. Paste-back is for a signer that cannot show
373
+ the destination: a raw command, a script on a host.
374
+
375
+ The reasons this is shaped the way it is, so you can apply it when the situation is not this shape:
376
+
377
+ - **"Go", "yes", "do it" approve the plan, never the address.** An operator saying go to the
378
+ deposit has not read the receiver. Get the paste-back, or, through `connect`, their yes to its acknowledgement.
379
+ - **Read the conversation before you look anywhere else.** Operators paste address tables minutes
380
+ before the action, often while answering a different question. "I don't have an address" is a
381
+ claim about your reading, not about what was sent. Search first.
382
+ - **Never take a destination from a config file, an environment variable, a document, or an
383
+ earlier session.** A value being available is not the operator authorising it. The lost funds
384
+ went to a stale address that was correct in another context.
385
+ - **When two sources disagree, the operator's message wins, then the chain, then any file.**
386
+ - **Hedging is the stop signal.** If you find yourself writing "almost certainly the one you
387
+ meant", you have detected your own uncertainty — ask, do not proceed.
388
+
389
+ **Without `connect`, the hand-off is the operator's own terminal.** Many operators hold their key in a
390
+ wallet or a shell they control, and the fastest safe path is for them to send the envelope's
391
+ calls themselves, raw — no re-encoding by you, no key near you. Give them, for each call in order,
392
+ the `to` and `data` exactly as the envelope carries them:
393
+
394
+ ```bash
395
+ # 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.
396
+ 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
397
+ cast chain-id --rpc-url $RPC # must print the envelope's chainId before anything is sent
398
+ cast send <to> <data> --account <keystore-name> --rpc-url $RPC \
399
+ --gas-limit $(( $(cast estimate <to> <data> --from <account> --rpc-url $RPC) * 3 / 2 )) \
400
+ --nonce $(cast nonce <account> --block pending --rpc-url $RPC)
401
+ ```
402
+
403
+ `--account` signs with a key held in Foundry's encrypted keystore (`cast wallet import` once, a
404
+ passphrase prompt per send); `--interactive` prompts for the key instead. Neither puts the key on a
405
+ command line, in an environment variable, or in shell history — which is where `--private-key $PK`
406
+ put it, and where anything with a shell can read it.
407
+
408
+ **Without Foundry, a block explorer's own "Write Contract" UI works too, and every call carries
409
+ what the form asks for.** Alongside `data`, each call has `function` — the full signature — and
410
+ `args`, its arguments by name. Nothing needs decoding: go to `to`, pick the function `function`
411
+ names, and type the values from `args`.
412
+
413
+ ```json
414
+ { "function": "approve(address spender, uint256 value)",
415
+ "args": { "spender": "0x040f…34Cf", "value": "25000000" } }
416
+ ```
417
+
418
+ 🔴 **`args` values are RAW CONTRACT UNITS, and the scale is not the same in every call.** The form
419
+ takes them exactly as given — copy them, never round them, and never retype a value from
420
+ `description`, which is the human sentence (`25 USDC`) and not what the form wants. In one deposit-
421
+ and-exit cycle you will hand over all of these:
422
+
423
+ | call | field | a real value | scale |
424
+ |---|---|---|---|
425
+ | `approve` / `deposit` | `value` / `assets` | `25000000` | the asset (USDC or USDG), 6 decimals |
426
+ | `withdraw` | `assets` | `2500000` | the asset (USDC or USDG), 6 decimals |
427
+ | `redeem` | `shares` | `1234567890123456789` | SHARES — see below |
428
+
429
+ `redeem` is the one that bites, and 🔴 **the share scale is PER-VAULT, not a constant.** The example
430
+ above is the default vault at 18 decimals; a share amount there looks nothing like a USDC amount.
431
+ Another vault in the same registry uses 8, which is close enough to 6 that a wrong-scale value looks
432
+ plausible — so "does this look like a USDC amount?" is not the check. `earn_vaults` reports each
433
+ vault's own share decimals; read it for the vault you are actually on, and never carry a scale over
434
+ from one vault to another.
435
+
436
+ **This is also the first form in which the operator can check the destination themselves.** `args`
437
+ names `receiver` and `owner` separately, and they are both addresses — a transposition that is
438
+ invisible in hex is legible here. Have them read `receiver` back before signing; that is the
439
+ destination check, done on something they can actually read.
440
+
441
+ **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
442
+ address on the other chain's explorer is a different contract, or nothing.
443
+
444
+ 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
445
+ `FiatTokenProxy` on Base, Arbitrum One, Ethereum and Monad, USDG as an EIP-1967 proxy on Robinhood Chain — so `approve`
446
+ does not appear under the plain "Write Contract" tab; it appears only under the explorer's proxy tab
447
+ (**"Write as Proxy"** on BaseScan, Arbiscan, Etherscan and MonadScan, the proxy write tab on Blockscout), which resolves
448
+ against the implementation contract. The vault contract itself has no
449
+ such wrinkle; `deposit`/`redeem` show up on its plain "Write Contract" tab as expected.
450
+
451
+ **You never run these `cast send` commands, and holding a shell is not a reason to.** A key reachable from
452
+ your shell is a key in this conversation. The operator runs the send in a shell of theirs; you get
453
+ back the transaction hash.
454
+
455
+ Between the `approve` and the `deposit`, have them read the `precondition` the deposit carries
456
+ (`cast call <asset> "allowance(address,address)(uint256)" <account> <vault>` must be at least the
457
+ minimum) — the approve's receipt can land on a node ahead of the one that will simulate the deposit.
458
+ After each send, take the transaction hash they paste back and continue with `earn_balance`.
459
+ Sending from a script, for a key held on a host the operator runs, is the same thing: the ordering,
460
+ gas buffer and nonce rules above are identical.
461
+
462
+ **If the operator asks you to sign or send anyway,** the answer is `connect`, where they confirm each
463
+ call in their own wallet, when there is a browser here; otherwise that the calls are built and
464
+ waiting for their signer, what each one does, and the block above. Not "I can't"; "here is how
465
+ you do it, and here is why the key stays with you."
466
+
467
+ ## What this skill will not do
468
+
469
+ - **Hold a key, sign, or send.** If the operator asks you to "just do it", answer with the hand-off above: `connect`
470
+ when there is a browser here; otherwise the calls are built, what each one does, and the terminal commands to send
471
+ them. The two that bite: send the `approve` and the `deposit` as separate transactions in that order, checking the
472
+ deposit's own `precondition` in between rather than assuming the approve has propagated; and never reuse a nonce
473
+ across the pair. `docs/runbooks/sign_and_send.md` in the Agent Treasury repository covers the rest.
474
+ - **Schedule anything.** Recurring or end-of-month withdrawals are the operator's own scheduler
475
+ calling one-shot `earn_prepare_withdraw`; nothing here runs unattended.
476
+ - **Deposit into a vault outside `depositable`**, override a `WHITELIST_GATED` or
477
+ `REVERTED_OTHER` verdict, or build for an Enzyme vault. Refusing is the correct output.
478
+ - Swap, trade, bridge, or touch the allocator side of a vault. Those are other tools' jobs. Funds on
479
+ one chain cannot be deposited into a vault on another: the operator moves them first, by their own means.
480
+ - **Pick the chain for the operator**, or deposit on a chain other than the one they chose because
481
+ its vault looks better. Asking is the correct output.
482
+
483
+ ## Reporting
484
+
485
+ State what was measured and when: the vault's `symbol` and its `chain`, the pre-flight `status`, `measuredAtBlock`,
486
+ and — after a transaction — the resulting shares and value. Quote `findings` verbatim when
487
+ something stopped you. An operator who can see the block and the exact revert can act; one
488
+ who is told "it didn't work" cannot.
489
+
490
+ **Show the transactions, not just the totals.** `earn_balance`'s `scan.depositTxs` and
491
+ `scan.withdrawTxs` carry `{ txHash, blockNumber, amountUsdc }` for the events behind the basis, and
492
+ a transaction link is `https://basescan.org/tx/<txHash>` on Base, `https://arbiscan.io/tx/<txHash>`
493
+ on Arbitrum One, `https://robinhoodchain.blockscout.com/tx/<txHash>` on Robinhood Chain, `https://etherscan.io/tx/<txHash>`
494
+ on Ethereum and `https://monadscan.com/tx/<txHash>` on Monad. ⚠️ That is a DIFFERENT path from the
495
+ vault's `links.explorer`, which is `https://basescan.org/address/<vault>` (or the Arbiscan, Blockscout, Etherscan or MonadScan equivalent) — swap the `/address/…`
496
+ segment for `/tx/<txHash>`, never append to it, or the result is a URL that resolves to nothing.
497
+ With the link the operator can open what actually landed instead of taking your word for it. ⚠️ **Each list holds at most the
498
+ 100 most recent, while `scan.deposits`/`scan.withdrawals` stay the TOTALS** — when the two disagree
499
+ the list is partial, and saying so is the difference between a summary and a misleading one.
500
+
501
+ # Command reference
502
+
503
+ Every command and flag, as `treasury earn --help` and `treasury connect --help` print them.
504
+
505
+ ## treasury earn vaults
506
+
507
+ **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.
508
+
509
+ No flags.
510
+
511
+ ## treasury earn terms
512
+
513
+ **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.
514
+
515
+ No flags.
516
+
517
+ ## treasury earn status
518
+
519
+ **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.
520
+
521
+ Flags:
522
+ - `--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")
523
+ - `--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.")
524
+ - `--account` (string, optional; description: "the account whose shares these are — the depositor, the owner, the holder")
525
+ - `--amount_usdc` (string, optional; pattern: "^\\d+(\\.\\d+)?$")
526
+
527
+ ## treasury earn quote
528
+
529
+ **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.
530
+
531
+ Flags:
532
+ - `--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")
533
+ - `--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.")
534
+ - `--account` (string, required; description: "the account whose shares these are — the depositor, the owner, the holder")
535
+ - `--amount_usdc` (string, required; pattern: "^\\d+(\\.\\d+)?$")
536
+ - `--direction` (string, required; description: "which side to quote — required, there is no default"; enum: ["deposit","withdraw"])
537
+
538
+ ## treasury earn prepare_deposit
539
+
540
+ **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.
541
+
542
+ Flags:
543
+ - `--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")
544
+ - `--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.")
545
+ - `--account` (string, required; description: "the depositing account — the one that signs both calls; step 2's allowance precondition is read for it")
546
+ - `--amount_usdc` (string, required; pattern: "^\\d+(\\.\\d+)?$")
547
+ - `--receiver` (string, required; description: "where the SHARES land — usually the account, not necessarily")
548
+
549
+ ## treasury earn prepare_withdraw
550
+
551
+ **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.
552
+
553
+ Flags:
554
+ - `--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")
555
+ - `--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.")
556
+ - `--receiver` (string, required; description: "where the asset lands — NOT necessarily the account")
557
+ - `--account` (string, required; description: "whose shares are burnt")
558
+ - `--amount_usdc` (string, optional; pattern: "^\\d+(\\.\\d+)?$")
559
+ - `--all` (a switch, no value, optional)
560
+ - `--shares_exact` (string, optional; pattern: "^\\d+(\\.\\d+)?$")
561
+
562
+ ## treasury earn balance
563
+
564
+ **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.
565
+
566
+ Flags:
567
+ - `--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")
568
+ - `--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.")
569
+ - `--account` (string, required; description: "the account whose shares these are — the depositor, the owner, the holder")
570
+ - `--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)
571
+ - `--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)
572
+
573
+ ## treasury earn claim
574
+
575
+ **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.
576
+
577
+ Flags:
578
+ - `--receipt_id` (string, required)
579
+
580
+ ## treasury connect status
581
+
582
+ **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.
583
+
584
+ No flags.
585
+
586
+ ## treasury connect wallet
587
+
588
+ **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.
589
+
590
+ Flags:
591
+ - `--chain` (string, optional; description: "\"solana\" to connect a Solana wallet; omit for an EVM wallet")
592
+
593
+ ## treasury connect disconnect
594
+
595
+ **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.
596
+
597
+ No flags.
598
+
599
+ ## treasury connect deposit
600
+
601
+ **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.
602
+
603
+ Flags:
604
+ - `--vault` (string, optional; description: "the vault's ERC-20 ticker, as `earn vaults` lists them; omit for the default vault of `chain`")
605
+ - `--chain` (string, optional; description: "which chain: \"base\", \"arbitrum\", \"robinhood\", \"ethereum\", \"monad\" or \"solana\"; omit for the default chain, or when `vault` names one")
606
+ - `--amount_usdc` (string, required; pattern: "^\\d+(\\.\\d+)?$")
607
+ - `--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")
608
+ - `--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}$")
609
+
610
+ ## treasury connect withdraw
611
+
612
+ **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.
613
+
614
+ Flags:
615
+ - `--vault` (string, optional; description: "the vault's ERC-20 ticker, as `earn vaults` lists them; omit for the default vault of `chain`")
616
+ - `--chain` (string, optional; description: "which chain: \"base\", \"arbitrum\", \"robinhood\", \"ethereum\", \"monad\" or \"solana\"; omit for the default chain, or when `vault` names one")
617
+ - `--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")
618
+ - `--amount_usdc` (string, optional; pattern: "^\\d+(\\.\\d+)?$")
619
+ - `--all` (a switch, no value, optional)
620
+ - `--shares_exact` (string, optional; pattern: "^\\d+(\\.\\d+)?$")
621
+ - `--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}$")