@temporalabs/treasury 0.1.1 → 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.
- package/NOTICE +4 -2
- package/README.md +100 -44
- package/THIRD_PARTY_NOTICES.connect-page.md +7836 -0
- package/THIRD_PARTY_NOTICES.md +543 -162
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +243 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +366 -239
- package/dist/client.js +202 -25
- package/dist/client.js.map +1 -1
- package/dist/config/earn.d.ts +48 -12
- package/dist/config/earn.js +48 -12
- package/dist/config/earn.js.map +1 -1
- package/dist/connect/ack.d.ts +76 -0
- package/dist/connect/ack.js +147 -0
- package/dist/connect/ack.js.map +1 -0
- package/dist/connect/commands.d.ts +81 -0
- package/dist/connect/commands.js +555 -0
- package/dist/connect/commands.js.map +1 -0
- package/dist/connect/config.d.ts +24 -0
- package/dist/connect/config.js +35 -0
- package/dist/connect/config.js.map +1 -0
- package/dist/connect/gate-solana.d.ts +8 -0
- package/dist/connect/gate-solana.js +147 -0
- package/dist/connect/gate-solana.js.map +1 -0
- package/dist/connect/gate.d.ts +37 -0
- package/dist/connect/gate.js +115 -0
- package/dist/connect/gate.js.map +1 -0
- package/dist/connect/http.d.ts +60 -0
- package/dist/connect/http.js +235 -0
- package/dist/connect/http.js.map +1 -0
- package/dist/connect/open.d.ts +2 -0
- package/dist/connect/open.js +35 -0
- package/dist/connect/open.js.map +1 -0
- package/dist/connect/page.d.ts +5 -0
- package/dist/connect/page.js +69 -0
- package/dist/connect/page.js.map +1 -0
- package/dist/connect/session.d.ts +39 -0
- package/dist/connect/session.js +198 -0
- package/dist/connect/session.js.map +1 -0
- package/dist/connect/solana.d.ts +73 -0
- package/dist/connect/solana.js +267 -0
- package/dist/connect/solana.js.map +1 -0
- package/dist/connect/verify.d.ts +37 -0
- package/dist/connect/verify.js +89 -0
- package/dist/connect/verify.js.map +1 -0
- package/dist/connect-page.js +5143 -0
- package/dist/disclosures.d.ts +22 -6
- package/dist/disclosures.js +34 -8
- package/dist/disclosures.js.map +1 -1
- package/dist/earn/commands.d.ts +46 -0
- package/dist/earn/commands.js +556 -0
- package/dist/earn/commands.js.map +1 -0
- package/dist/earn/solana.d.ts +278 -0
- package/dist/earn/solana.js +302 -0
- package/dist/earn/solana.js.map +1 -0
- package/dist/guide.d.ts +15 -0
- package/dist/guide.js +50 -0
- package/dist/guide.js.map +1 -0
- package/dist/links.d.ts +2 -0
- package/dist/links.js +40 -14
- package/dist/links.js.map +1 -1
- package/dist/position.d.ts +9 -6
- package/dist/position.js +39 -19
- package/dist/position.js.map +1 -1
- package/dist/preflight.d.ts +9 -0
- package/dist/preflight.js +14 -3
- package/dist/preflight.js.map +1 -1
- package/dist/quote.js +24 -7
- package/dist/quote.js.map +1 -1
- package/dist/redact.d.ts +3 -3
- package/dist/redact.js +3 -3
- package/dist/redact.js.map +1 -1
- package/dist/registry-schema.d.ts +82 -5
- package/dist/registry-schema.js +105 -15
- package/dist/registry-schema.js.map +1 -1
- package/dist/registry.d.ts +50 -7
- package/dist/registry.js +122 -21
- package/dist/registry.js.map +1 -1
- package/dist/solana/constants.d.ts +49 -0
- package/dist/solana/constants.js +50 -0
- package/dist/solana/constants.js.map +1 -0
- package/dist/solana/kvault.d.ts +99 -0
- package/dist/solana/kvault.js +192 -0
- package/dist/solana/kvault.js.map +1 -0
- package/dist/solana/layout.d.ts +143 -0
- package/dist/solana/layout.js +209 -0
- package/dist/solana/layout.js.map +1 -0
- package/dist/solana/rpc.d.ts +24 -0
- package/dist/solana/rpc.js +66 -0
- package/dist/solana/rpc.js.map +1 -0
- package/dist/solana/vault.d.ts +153 -0
- package/dist/solana/vault.js +324 -0
- package/dist/solana/vault.js.map +1 -0
- package/dist/{mcp-server.mjs → treasury.mjs} +41101 -49486
- package/dist/version.js +5 -10
- package/dist/version.js.map +1 -1
- package/llms.txt +621 -0
- package/package.json +12 -9
- package/registry/vaults.json +218 -4
- package/dist/mcp/server.d.ts +0 -8
- package/dist/mcp/server.js +0 -368
- package/dist/mcp/server.js.map +0 -1
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}$")
|