@dfm-fi/agent 0.2.0
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/README.md +326 -0
- package/dist/api-client.d.ts +137 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +215 -0
- package/dist/api-client.js.map +1 -0
- package/dist/config.d.ts +56 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +55 -0
- package/dist/config.js.map +1 -0
- package/dist/create-flow.d.ts +62 -0
- package/dist/create-flow.d.ts.map +1 -0
- package/dist/create-flow.js +88 -0
- package/dist/create-flow.js.map +1 -0
- package/dist/examples/deposit-redeem-loop.d.ts +2 -0
- package/dist/examples/deposit-redeem-loop.d.ts.map +1 -0
- package/dist/examples/deposit-redeem-loop.js +67 -0
- package/dist/examples/deposit-redeem-loop.js.map +1 -0
- package/dist/examples/launch-deposit-redeem.d.ts +2 -0
- package/dist/examples/launch-deposit-redeem.d.ts.map +1 -0
- package/dist/examples/launch-deposit-redeem.js +166 -0
- package/dist/examples/launch-deposit-redeem.js.map +1 -0
- package/dist/examples/list-and-status.d.ts +2 -0
- package/dist/examples/list-and-status.d.ts.map +1 -0
- package/dist/examples/list-and-status.js +67 -0
- package/dist/examples/list-and-status.js.map +1 -0
- package/dist/manager-flow.d.ts +73 -0
- package/dist/manager-flow.d.ts.map +1 -0
- package/dist/manager-flow.js +110 -0
- package/dist/manager-flow.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +529 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/recovery-flow.d.ts +81 -0
- package/dist/recovery-flow.d.ts.map +1 -0
- package/dist/recovery-flow.js +123 -0
- package/dist/recovery-flow.js.map +1 -0
- package/dist/session.d.ts +52 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +107 -0
- package/dist/session.js.map +1 -0
- package/dist/signing.d.ts +30 -0
- package/dist/signing.d.ts.map +1 -0
- package/dist/signing.js +129 -0
- package/dist/signing.js.map +1 -0
- package/dist/submit.d.ts +31 -0
- package/dist/submit.d.ts.map +1 -0
- package/dist/submit.js +165 -0
- package/dist/submit.js.map +1 -0
- package/dist/types.d.ts +191 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/zap-v2-flow.d.ts +76 -0
- package/dist/zap-v2-flow.d.ts.map +1 -0
- package/dist/zap-v2-flow.js +176 -0
- package/dist/zap-v2-flow.js.map +1 -0
- package/package.json +60 -0
- package/skills.md +153 -0
package/README.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# @dfm-fi/agent — DFM v2 Agent Test Harness (MCP)
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that lets an external AI agent drive the
|
|
4
|
+
full DFM v2 **launch → deposit → redeem** DTF flow against the **live API**
|
|
5
|
+
(`https://n2-api.dfm.finance/api/v2`) programmatically — no web-UI clicking.
|
|
6
|
+
|
|
7
|
+
This is a **test harness for the founder + partner**: point your own AI agent
|
|
8
|
+
(Claude Desktop / Claude Code / any MCP client) at the live closed-mainnet API,
|
|
9
|
+
authenticate with your **own test-wallet keypair loaded locally**, and run the
|
|
10
|
+
whole vault lifecycle — create a DTF, deposit USDC, redeem back to USDC — from
|
|
11
|
+
the agent.
|
|
12
|
+
|
|
13
|
+
> ⚠️ This is a **real-money Solana mainnet** system. Read the Security section.
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- **Node 20+** (the repo targets Node; ESM build).
|
|
18
|
+
- A clone of the `dfm-v2` monorepo with its workspace deps installed at the
|
|
19
|
+
repo root (this package uses the hoisted `node_modules` — do **not** run
|
|
20
|
+
`npm install` inside `agent/`).
|
|
21
|
+
- A **dedicated throwaway Solana test wallet** (NOT the protocol wallet), funded
|
|
22
|
+
with a few dollars of **USDC** (mainnet mint `EPjFW…`) + a little **SOL** for
|
|
23
|
+
fees, and **allowlisted** for the closed alpha (send its pubkey to an admin).
|
|
24
|
+
- For the write path only: a **Helius mainnet RPC URL** (`HELIUS_RPC_URL`).
|
|
25
|
+
|
|
26
|
+
## Security model (read first)
|
|
27
|
+
|
|
28
|
+
- The agent authenticates as **its own test wallet**, whose keypair is loaded
|
|
29
|
+
**locally by you** from a file path (`DFM_AGENT_KEYPAIR_PATH`) or an inline env
|
|
30
|
+
(`DFM_AGENT_KEYPAIR_JSON`). The keypair **never crosses the MCP boundary** — it
|
|
31
|
+
is never a tool argument, never in a tool result, never logged. Only the
|
|
32
|
+
**public key** is ever surfaced.
|
|
33
|
+
- **Never** use the protocol wallet (`9GjE…`) here. Use a **dedicated throwaway
|
|
34
|
+
test wallet** funded with a few dollars of USDC. The harness hard-refuses to
|
|
35
|
+
load the known protocol pubkey as a tripwire.
|
|
36
|
+
- The test wallet must be on the **closed-alpha access allowlist** — otherwise
|
|
37
|
+
SIWS sign-in is rejected **server-side** (403), so it can't reach any protected
|
|
38
|
+
endpoint. An admin adds it via the `/ops` Access panel (`POST /access/allowlist`).
|
|
39
|
+
- All **real-money WRITE tools** (deposit / redeem) are **gated behind
|
|
40
|
+
`DFM_AGENT_WRITE_ENABLED=true` and default OFF**. With write off, those tools
|
|
41
|
+
refuse and never touch the chain or load the keypair.
|
|
42
|
+
|
|
43
|
+
## Auth + signing flow (how it works)
|
|
44
|
+
|
|
45
|
+
SIWS (Sign In With Solana), signed locally, exchanged for a Bearer session:
|
|
46
|
+
|
|
47
|
+
1. `POST /auth/nonce { wallet }` → `{ nonce, message }` — the **server builds the
|
|
48
|
+
exact message** (domain-bound to `n2.dfm.finance`, nonce embedded).
|
|
49
|
+
2. Sign **that exact message** locally with the test keypair (Ed25519 → base58).
|
|
50
|
+
3. `POST /auth/siws { wallet, signature, message, client: 'native' }` →
|
|
51
|
+
`{ accessToken, … }`. The server also enforces the access gate here.
|
|
52
|
+
4. Authed calls send `Authorization: Bearer <accessToken>`; a 401 triggers one
|
|
53
|
+
transparent re-auth + retry.
|
|
54
|
+
|
|
55
|
+
The WRITE flows then follow **prepare → sign locally → submit → (confirm/poll)**:
|
|
56
|
+
- **launch**: `/vaults/create/prepare` → one unsigned v0 tx → sign → submit →
|
|
57
|
+
`/vaults/create/confirm`.
|
|
58
|
+
- **deposit / redeem**: `/zap-…-v2/open/prepare` → sign → submit → poll
|
|
59
|
+
`/zap-v2/status` until the backend orchestrator has cranked all swap legs →
|
|
60
|
+
`/zap-…-v2/close/prepare` → sign → submit. The agent signs **only** the two
|
|
61
|
+
user-side boundary transactions; the backend cranks the permissionless legs.
|
|
62
|
+
|
|
63
|
+
## Tools
|
|
64
|
+
|
|
65
|
+
| Tool | Surface | Endpoint | Notes |
|
|
66
|
+
|------|---------|----------|-------|
|
|
67
|
+
| `whoami` | read | `/access/status` | Test-wallet pubkey, cluster, write flag, gate status. **Call first.** |
|
|
68
|
+
| `list_dtfs` | read | `GET /vaults` | Lists vaults (address, symbol, TVL, fees, basket). Public. |
|
|
69
|
+
| `get_vault` | read | `GET /vaults/:address` | Full vault detail + weights + fee structure. Public. |
|
|
70
|
+
| `zap_status` | read (auth) | `GET /vaults/:address/zap-v2/status/:user` | Decoded escrow + per-leg crank progress, or null. |
|
|
71
|
+
| `get_portfolio` | read (auth) | `GET /portfolio` | Session wallet's positions (API reads wallet from JWT). |
|
|
72
|
+
| `launch_vault` | **write (gated)** | `create prepare/confirm` | Create a DTF. Basket sums to 10000 bps, ≤15 assets. |
|
|
73
|
+
| `deposit` | **write (gated)** | `zap-in-v2 open/close` | Full zap-in lifecycle. RAW 6dp USDC. Resumes an open escrow. |
|
|
74
|
+
| `redeem` | **write (gated)** | `zap-out-v2 open/close` | Full zap-out lifecycle. RAW 6dp shares. Resumes an open escrow. |
|
|
75
|
+
| `zap_v2_cancel` | **write (gated)** | `zap-v2/cancel/prepare` | Return a stalled escrow's holdings to the wallet, as-is. |
|
|
76
|
+
| `zap_v2_update_envelope` | **write (gated)** | `zap-v2/update-envelope/prepare` | Loosen the pinned floors (auto-derived from the live escrow) to unstick a crank. |
|
|
77
|
+
| `update_vault_assets` | **write (gated)** | `assets/prepare + confirm` | MANAGER: full-replace a vault's basket (sums to 10000, ≤15, no dupes; manual-mode + 24h timelock). Your own vault only. |
|
|
78
|
+
| `update_management_fee` | **write (gated)** | `PATCH :address/fees` | MANAGER: set the management fee (≤2000 bps). Reverts on immutable-fee vaults (which is what this agent creates). |
|
|
79
|
+
| `transfer_admin` | **write (gated)** | `:address/transfer-admin` | ⚠️ MANAGER, HIGH-SENSITIVITY: initiate handing vault control to another key (the new admin must ACCEPT). Your own vault only. |
|
|
80
|
+
|
|
81
|
+
### Prompt-injection / safety model
|
|
82
|
+
|
|
83
|
+
The harness is **boxed in by design**, so a prompt-injected agent (tricked by malicious text it reads while browsing vaults) is bounded:
|
|
84
|
+
- **No "send funds to an address" tool exists.** `redeem` always pays *your own* wallet; `deposit` pulls from it. There is no way to route money to an attacker.
|
|
85
|
+
- **Every write only touches a vault/wallet the signed-in test wallet OWNS or ADMINS** (the API enforces `vault.admin == caller`), and amounts/slippage are capped by the on-chain guards (≤10% slippage, per-leg `min_outs`, the drain-guard, $10 floor, ≤15 assets, immutable fees).
|
|
86
|
+
- **The keypair never enters the agent's context** (only the pubkey), so injection can't exfiltrate it; the write-gate (`DFM_AGENT_WRITE_ENABLED`) is a hard local kill-switch.
|
|
87
|
+
- So the worst a hijacked agent can do is make *you* mis-spend *your own* money within the contract limits — **never the protocol, never other users, never key theft.** The one action to watch is `transfer_admin` (it gives away control of your own vault) — only invoke it when you explicitly intend it, never inferred from a vault's name/description.
|
|
88
|
+
|
|
89
|
+
## Install
|
|
90
|
+
|
|
91
|
+
### One line — once `@dfm-fi/agent` is published to npm
|
|
92
|
+
|
|
93
|
+
The package is **self-contained** (no monorepo deps) and ships its built `dist/`, so `npx` fetches + runs it — no clone, no build:
|
|
94
|
+
|
|
95
|
+
**Claude Code:**
|
|
96
|
+
```bash
|
|
97
|
+
claude mcp add dfm \
|
|
98
|
+
--env DFM_AGENT_KEYPAIR_PATH=/abs/path/test-wallet.json \
|
|
99
|
+
--env HELIUS_RPC_URL=https://mainnet.helius-rpc.com/?api-key=YOUR_KEY \
|
|
100
|
+
--env DFM_AGENT_WRITE_ENABLED=true \
|
|
101
|
+
-- npx -y @dfm-fi/agent
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Claude Desktop** (`claude_desktop_config.json`):
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"mcpServers": {
|
|
108
|
+
"dfm": {
|
|
109
|
+
"command": "npx",
|
|
110
|
+
"args": ["-y", "@dfm-fi/agent"],
|
|
111
|
+
"env": {
|
|
112
|
+
"DFM_AGENT_KEYPAIR_PATH": "/abs/path/test-wallet.json",
|
|
113
|
+
"HELIUS_RPC_URL": "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY",
|
|
114
|
+
"DFM_AGENT_WRITE_ENABLED": "true"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
(For read-only, drop `HELIUS_RPC_URL` + `DFM_AGENT_WRITE_ENABLED` — the read tools need neither.)
|
|
121
|
+
|
|
122
|
+
> **Publish it (one-time, owner only):** `cd agent && npm login && npm publish --access public`. After that, everyone installs with the one-liner above. (Needs the `@dfm` npm scope — create it free on npmjs.com, or rename the package.)
|
|
123
|
+
|
|
124
|
+
### From source — works today, no publish needed
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
cd dfm-v2/agent && npm run build # tsc -b → dist/
|
|
128
|
+
# then point your MCP client at the built binary (one line):
|
|
129
|
+
claude mcp add dfm -- node /abs/path/dfm-v2/agent/dist/mcp-server.js
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Quick start (read-only — safe)
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Run the MCP server against the live API
|
|
136
|
+
DFM_API_URL=https://n2-api.dfm.finance \
|
|
137
|
+
DFM_AGENT_KEYPAIR_PATH=/abs/path/test-wallet.json \
|
|
138
|
+
npm start # = node dist/mcp-server.js
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Or exercise the read path without MCP:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
DFM_API_URL=https://n2-api.dfm.finance \
|
|
145
|
+
DFM_AGENT_KEYPAIR_PATH=/abs/path/test-wallet.json \
|
|
146
|
+
npx tsx src/examples/list-and-status.ts
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Loading the test keypair
|
|
150
|
+
|
|
151
|
+
Generate a dedicated throwaway wallet and fund it with a little USDC + SOL:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
solana-keygen new --no-bip39-passphrase -o ~/dfm-test-wallet.json
|
|
155
|
+
solana-keygen pubkey ~/dfm-test-wallet.json # send this pubkey to an admin to allowlist
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Then either point the agent at the file (preferred):
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
export DFM_AGENT_KEYPAIR_PATH=$HOME/dfm-test-wallet.json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
…or inline the JSON array (e.g. in a secret manager):
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
export DFM_AGENT_KEYPAIR_JSON="$(cat ~/dfm-test-wallet.json)"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Enabling the real-money launch → deposit → redeem loop
|
|
171
|
+
|
|
172
|
+
Only after the parent has reviewed the write path (`create-flow.ts`,
|
|
173
|
+
`zap-v2-flow.ts`, `recovery-flow.ts`, `signing.ts`, `submit.ts`):
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
export DFM_API_URL=https://n2-api.dfm.finance
|
|
177
|
+
export HELIUS_RPC_URL=https://mainnet.helius-rpc.com/?api-key=YOUR_KEY # required for submit
|
|
178
|
+
export DFM_AGENT_KEYPAIR_PATH=$HOME/dfm-test-wallet.json # allowlisted + funded
|
|
179
|
+
export DFM_AGENT_WRITE_ENABLED=true # the kill-switch
|
|
180
|
+
|
|
181
|
+
# End-to-end example: launch a demo DTF, deposit $12, redeem the full position.
|
|
182
|
+
# (Runs as a SAFE DRY-RUN — printing the calls it would make — when write is OFF.)
|
|
183
|
+
npx tsx src/examples/launch-deposit-redeem.ts
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Copy-paste walkthrough (in an MCP client / agent)
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
1. whoami
|
|
190
|
+
→ confirm the test-wallet pubkey + that accessGate.allowed is true.
|
|
191
|
+
|
|
192
|
+
2. launch_vault
|
|
193
|
+
{
|
|
194
|
+
"name": "My First DTF",
|
|
195
|
+
"symbol": "MYDTF",
|
|
196
|
+
"assets": [
|
|
197
|
+
{ "mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", "symbol": "ETH", "allocationBps": 5000 },
|
|
198
|
+
{ "mint": "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN", "symbol": "JUP", "allocationBps": 5000 }
|
|
199
|
+
],
|
|
200
|
+
"managementFeeBps": 100,
|
|
201
|
+
"exitFeeBps": 100
|
|
202
|
+
}
|
|
203
|
+
→ returns { vault: "<address>", signature, ... }. Note the vault address.
|
|
204
|
+
(These two mints are already registered + Pyth-mapped on mainnet. To use
|
|
205
|
+
others, list candidates with GET /assets and make sure each is registered.)
|
|
206
|
+
|
|
207
|
+
3. deposit
|
|
208
|
+
{ "vault": "<address from step 2>", "usdcAmount": "12000000" } // $12 raw (6dp)
|
|
209
|
+
→ drives open → orchestrator cranks → close; returns when shares are minted.
|
|
210
|
+
|
|
211
|
+
4. get_portfolio
|
|
212
|
+
→ read the resulting raw share balance for that vault.
|
|
213
|
+
|
|
214
|
+
5. redeem
|
|
215
|
+
{ "vault": "<address>", "shares": "<raw shares from step 4>" }
|
|
216
|
+
→ drives open → cranks → close; returns net USDC paid (minus the 1% exit fee).
|
|
217
|
+
|
|
218
|
+
If a deposit/redeem STALLS:
|
|
219
|
+
zap_status { vault, user } → inspect per-leg crank progress + failures
|
|
220
|
+
zap_v2_update_envelope { vault } → loosen the floors 5% and let the crank retry
|
|
221
|
+
zap_v2_cancel { vault } → give up and get the held funds back, as-is
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Notes:
|
|
225
|
+
- **Amounts are RAW 6-decimal**: $12 = `"12000000"`, 1 share = `"1000000"`.
|
|
226
|
+
- The **first deposit** into a vault must clear ~**$12** so it stays above the
|
|
227
|
+
$10 floor after slippage.
|
|
228
|
+
- `allocationBps` MUST sum to exactly **10000** across the basket; **≤15 assets**
|
|
229
|
+
on mainnet.
|
|
230
|
+
- `deposit`/`redeem` are **resumable**: if a prior open landed but its close
|
|
231
|
+
never did, calling the same tool again resumes (poll → close) instead of
|
|
232
|
+
re-opening.
|
|
233
|
+
|
|
234
|
+
## Configure Claude Desktop
|
|
235
|
+
|
|
236
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"mcpServers": {
|
|
241
|
+
"dfm": {
|
|
242
|
+
"command": "node",
|
|
243
|
+
"args": ["/abs/path/dfm-v2/agent/dist/mcp-server.js"],
|
|
244
|
+
"env": {
|
|
245
|
+
"DFM_API_URL": "https://n2-api.dfm.finance",
|
|
246
|
+
"DFM_AGENT_KEYPAIR_PATH": "/abs/path/dfm-test-wallet.json",
|
|
247
|
+
"SOLANA_CLUSTER": "mainnet-beta"
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
(Leave `DFM_AGENT_WRITE_ENABLED` unset for a read-only agent. Add
|
|
255
|
+
`HELIUS_RPC_URL` + `DFM_AGENT_WRITE_ENABLED=true` only when enabling writes.)
|
|
256
|
+
|
|
257
|
+
## Environment variables
|
|
258
|
+
|
|
259
|
+
| Variable | Required | Default | Description |
|
|
260
|
+
|----------|----------|---------|-------------|
|
|
261
|
+
| `DFM_API_URL` | No | `https://n2-api.dfm.finance` | API base (the `/api/v2` prefix is auto-appended). |
|
|
262
|
+
| `DFM_AGENT_KEYPAIR_PATH` | For auth | — | Path to a Solana secret-key JSON file (preferred). The wallet must be **allowlisted** + **funded** (USDC + a little SOL). |
|
|
263
|
+
| `DFM_AGENT_KEYPAIR_JSON` | For auth | — | The secret-key JSON array inline (alternative; e.g. injected from a secret manager). |
|
|
264
|
+
| `DFM_AGENT_WRITE_ENABLED` | No | `false` | Master kill-switch for **all** write tools (launch/deposit/redeem/cancel/update-envelope). |
|
|
265
|
+
| `HELIUS_RPC_URL` / `DFM_RPC_URL` | For write | — | RPC to submit signed txs (mainnet refuses the public RPC fallback). |
|
|
266
|
+
| `SOLANA_CLUSTER` | No | `mainnet-beta` | `mainnet-beta` or `devnet`. |
|
|
267
|
+
|
|
268
|
+
## Architecture
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
Agent / MCP client
|
|
272
|
+
│ stdio (MCP)
|
|
273
|
+
▼
|
|
274
|
+
MCP server (this package)
|
|
275
|
+
├─ read tools ─────────► DFM v2 API (public + authed GET)
|
|
276
|
+
└─ write tools (gated) ─► DFM v2 API /prepare ──► unsigned v0 tx(s)
|
|
277
|
+
│
|
|
278
|
+
sign LOCALLY with test keypair (signing.ts)
|
|
279
|
+
│
|
|
280
|
+
submit via RPC (submit.ts) — rebroadcast in-window,
|
|
281
|
+
│ re-prepare on blockhash expiry
|
|
282
|
+
┌─────────────────────┴─────────────────────┐
|
|
283
|
+
launch: confirm via /create/confirm deposit/redeem: poll /zap-v2/status
|
|
284
|
+
until legs cranked,
|
|
285
|
+
then /close → sign → submit
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The backend orchestrator (server-side) cranks the permissionless Jupiter swap
|
|
289
|
+
legs — the agent only signs the two user-side boundary transactions. The agent
|
|
290
|
+
does **not** crank legs and does **not** hold the protocol key.
|
|
291
|
+
|
|
292
|
+
## Source map
|
|
293
|
+
|
|
294
|
+
| File | Role |
|
|
295
|
+
|------|------|
|
|
296
|
+
| `src/mcp-server.ts` | MCP stdio server; registers the 10 tools + the write gate. |
|
|
297
|
+
| `src/config.ts` | Env → `AgentConfig` (API URL, cluster, RPC, write flag, keypair source). |
|
|
298
|
+
| `src/session.ts` | SIWS sign-in → Bearer session; in-flight dedup; transparent re-auth on 401. |
|
|
299
|
+
| `src/signing.ts` | The signing boundary — loads the local keypair, signs SIWS + v0 txs. Refuses the protocol wallet; never logs key material. |
|
|
300
|
+
| `src/submit.ts` | RPC submit + confirm; rebroadcast in-window; `BlockhashExpiredError` / `OnChainRevertError`. |
|
|
301
|
+
| `src/api-client.ts` | Typed DFM v2 API client (public + authed; create + zap-v2 prepare/confirm). |
|
|
302
|
+
| `src/create-flow.ts` | `launch_vault` driver: prepare → sign → submit → confirm (+ ghost-vault recovery). |
|
|
303
|
+
| `src/zap-v2-flow.ts` | `deposit` / `redeem` lifecycle drivers (resume-aware, close-retry). |
|
|
304
|
+
| `src/recovery-flow.ts` | `zap_v2_cancel` + `zap_v2_update_envelope` (floors auto-derived from the live escrow). |
|
|
305
|
+
| `src/types.ts` | Response shapes mirrored against the live API. |
|
|
306
|
+
|
|
307
|
+
## What remains for the parent to review/enable
|
|
308
|
+
|
|
309
|
+
All write tools are **fully implemented** but gated OFF (`DFM_AGENT_WRITE_ENABLED`
|
|
310
|
+
unset). Before flipping the gate, review the real-money path (search for the
|
|
311
|
+
`// REVIEW: real-money` markers):
|
|
312
|
+
|
|
313
|
+
- **`launch_vault`** (`create-flow.ts`) — pulls the on-chain creation fee from
|
|
314
|
+
the test wallet; the test wallet becomes the vault admin. Confirm the basket /
|
|
315
|
+
fee defaults are what you want.
|
|
316
|
+
- **`deposit` / `redeem`** (`zap-v2-flow.ts`) — move USDC ↔ shares. They are
|
|
317
|
+
resume-aware (won't double-open) and retry the close on blockhash expiry.
|
|
318
|
+
- **`zap_v2_cancel` / `zap_v2_update_envelope`** (`recovery-flow.ts`) — stall
|
|
319
|
+
recovery. `update_envelope` only ever LOOSENS, with the new floors derived from
|
|
320
|
+
the live escrow's pinned `minOuts` (never hand-supplied).
|
|
321
|
+
- **`signing.ts` / `submit.ts`** — the signing boundary + submit robustness.
|
|
322
|
+
Confirm the protocol-wallet tripwire + the "secret never leaves this module"
|
|
323
|
+
invariant.
|
|
324
|
+
|
|
325
|
+
To roll back at any time: unset `DFM_AGENT_WRITE_ENABLED` (the write tools then
|
|
326
|
+
refuse and never touch the chain or load the keypair).
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DFM v2 API client.
|
|
3
|
+
*
|
|
4
|
+
* Two request surfaces:
|
|
5
|
+
* - `publicRequest` — no auth (for `GET /vaults`, `/vaults/:address`,
|
|
6
|
+
* `/access/status`). These are `@Public()` on the API.
|
|
7
|
+
* - `authRequest` — attaches `Authorization: Bearer <token>` from the SIWS
|
|
8
|
+
* session; on a 401 it re-authenticates once and retries. Used for
|
|
9
|
+
* `/portfolio`, `/vaults/:address/zap-v2/status/:user`, and every
|
|
10
|
+
* `/prepare` endpoint.
|
|
11
|
+
*
|
|
12
|
+
* All addresses are validated as base58 before hitting the network. The client
|
|
13
|
+
* NEVER sees the secret key — signing is delegated to `signing.ts` via the
|
|
14
|
+
* write methods, and auth is delegated to `SiwsSession`.
|
|
15
|
+
*/
|
|
16
|
+
import type { AgentConfig } from './config.js';
|
|
17
|
+
import { SiwsSession } from './session.js';
|
|
18
|
+
import type { VaultListResponse, VaultDetailResponse, ZapV2Status, ZapV2PrepareResponse, ZapV2SingleTxResponse, AccessStatus, CreateAssetInput, CreatePrepareResponse, CreateConfirmResponse } from './types.js';
|
|
19
|
+
export declare class DfmApiClient {
|
|
20
|
+
private readonly session;
|
|
21
|
+
private readonly baseUrl;
|
|
22
|
+
constructor(config: AgentConfig, session: SiwsSession);
|
|
23
|
+
private publicRequest;
|
|
24
|
+
private authRequest;
|
|
25
|
+
/** `GET /vaults` (public). Supports the API's documented query params. */
|
|
26
|
+
listVaults(params?: {
|
|
27
|
+
category?: string;
|
|
28
|
+
dtfType?: string;
|
|
29
|
+
status?: string;
|
|
30
|
+
sortBy?: string;
|
|
31
|
+
page?: number;
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): Promise<VaultListResponse>;
|
|
34
|
+
/** `GET /vaults/:address` (public) — wraps the doc under `{ vault, ... }`. */
|
|
35
|
+
getVault(address: string): Promise<VaultDetailResponse>;
|
|
36
|
+
/** `GET /access/status?wallet=` (public) — closed-alpha gate decision. */
|
|
37
|
+
getAccessStatus(wallet: string): Promise<AccessStatus>;
|
|
38
|
+
/**
|
|
39
|
+
* `GET /vaults/:address/zap-v2/status/:user` (auth) — decoded ZapEscrowV2 or
|
|
40
|
+
* null. The session wallet must equal `user` (or be the vault admin), else 403.
|
|
41
|
+
* Returns `null` when the user has no open escrow.
|
|
42
|
+
*/
|
|
43
|
+
getZapV2Status(vaultAddress: string, user: string): Promise<ZapV2Status | null>;
|
|
44
|
+
/**
|
|
45
|
+
* `GET /portfolio` (auth) — the API reads the wallet from the JWT; there is NO
|
|
46
|
+
* portfolio-by-arbitrary-address endpoint. So this returns the SESSION
|
|
47
|
+
* wallet's positions. (To inspect another wallet you'd need that wallet's
|
|
48
|
+
* keypair / session.)
|
|
49
|
+
*/
|
|
50
|
+
getPortfolioPositions(): Promise<unknown>;
|
|
51
|
+
/** `GET /portfolio/summary` (auth) — total value + PnL for the session wallet. */
|
|
52
|
+
getPortfolioSummary(): Promise<unknown>;
|
|
53
|
+
/** `POST /vaults/:address/zap-in-v2/open/prepare` — pull USDC, pin plan. */
|
|
54
|
+
prepareZapInOpenV2(vaultAddress: string, usdcAmountRaw: string, opts?: {
|
|
55
|
+
slippageBps?: number;
|
|
56
|
+
expirySecs?: number;
|
|
57
|
+
}): Promise<ZapV2PrepareResponse>;
|
|
58
|
+
/** `POST /vaults/:address/zap-in-v2/close/prepare` — CPI mint_in_kind_v2. */
|
|
59
|
+
prepareZapInCloseV2(vaultAddress: string): Promise<ZapV2PrepareResponse>;
|
|
60
|
+
/** `POST /vaults/:address/zap-out-v2/open/prepare` — burn shares → escrow. */
|
|
61
|
+
prepareZapOutOpenV2(vaultAddress: string, sharesRaw: string, opts?: {
|
|
62
|
+
slippageBps?: number;
|
|
63
|
+
expirySecs?: number;
|
|
64
|
+
}): Promise<ZapV2PrepareResponse>;
|
|
65
|
+
/** `POST /vaults/:address/zap-out-v2/close/prepare` — exit fee, pay net USDC. */
|
|
66
|
+
prepareZapOutCloseV2(vaultAddress: string): Promise<ZapV2PrepareResponse>;
|
|
67
|
+
/**
|
|
68
|
+
* `POST /vaults/:address/zap-v2/cancel/prepare` — return the escrow's current
|
|
69
|
+
* USDC + per-asset balances to the test wallet as-is (no swaps, no oracle),
|
|
70
|
+
* then close it. 404 server-side when no escrow is open. Single v0 tx.
|
|
71
|
+
*/
|
|
72
|
+
prepareZapCancelV2(vaultAddress: string): Promise<ZapV2SingleTxResponse>;
|
|
73
|
+
/**
|
|
74
|
+
* `POST /vaults/:address/zap-v2/update-envelope/prepare` — LOOSEN the pinned
|
|
75
|
+
* per-leg floors + slippage (and optionally extend expiry) so a stalled escrow
|
|
76
|
+
* can complete. Monotonic loosening is enforced on-chain; the API rejects any
|
|
77
|
+
* non-positive `newMinOuts`. Single v0 tx.
|
|
78
|
+
*/
|
|
79
|
+
prepareUpdateEnvelopeV2(vaultAddress: string, body: {
|
|
80
|
+
newMinOuts: string[];
|
|
81
|
+
newSlippageBps: number;
|
|
82
|
+
newMinUsdcOut?: string;
|
|
83
|
+
extendSecs?: number;
|
|
84
|
+
}): Promise<ZapV2SingleTxResponse>;
|
|
85
|
+
/**
|
|
86
|
+
* `POST /vaults/create/prepare` — build the on-chain `create_vault` tx. The
|
|
87
|
+
* creator is the SESSION wallet (from the JWT), not a body field. Returns a
|
|
88
|
+
* single unsigned v0 tx + the `vaultAddress` PDA + a server-built ALT.
|
|
89
|
+
*/
|
|
90
|
+
prepareCreateVault(params: {
|
|
91
|
+
name: string;
|
|
92
|
+
symbol: string;
|
|
93
|
+
dtfType: string;
|
|
94
|
+
category: number;
|
|
95
|
+
assets: CreateAssetInput[];
|
|
96
|
+
entryFeeBps: number;
|
|
97
|
+
exitFeeBps: number;
|
|
98
|
+
managementFeeBps: number;
|
|
99
|
+
description?: string;
|
|
100
|
+
metadataUri?: string;
|
|
101
|
+
rebalanceType?: number;
|
|
102
|
+
rebalanceThresholdBps?: number;
|
|
103
|
+
tags?: number[];
|
|
104
|
+
immutable?: boolean;
|
|
105
|
+
}): Promise<CreatePrepareResponse>;
|
|
106
|
+
/**
|
|
107
|
+
* `POST /vaults/create/confirm` — record the vault doc after the create tx
|
|
108
|
+
* confirms. Only `signature` + `vaultAddress` are authoritative; the API reads
|
|
109
|
+
* the economic params back from its prepare-cache (so they can't be tampered
|
|
110
|
+
* post-prepare). MUST be called inside the cache TTL (~15 min).
|
|
111
|
+
*/
|
|
112
|
+
confirmCreateVault(signature: string, vaultAddress: string): Promise<CreateConfirmResponse>;
|
|
113
|
+
/**
|
|
114
|
+
* `POST /vaults/:address/assets/prepare` (vault admin) — build an
|
|
115
|
+
* `update_vault_assets` FULL-REPLACE basket tx. The API enforces the same
|
|
116
|
+
* guards as the chain (manual mode, 24h timelock, Σ bps == 10000, no dupes,
|
|
117
|
+
* ≤15 on mainnet) → clean 4xx instead of a chain revert.
|
|
118
|
+
*/
|
|
119
|
+
prepareUpdateAssets(vaultAddress: string, assets: CreateAssetInput[]): Promise<ZapV2SingleTxResponse>;
|
|
120
|
+
/** `POST /vaults/:address/assets/confirm` — record the basket change post-broadcast. */
|
|
121
|
+
confirmUpdateAssets(vaultAddress: string, signature: string): Promise<{
|
|
122
|
+
status: string;
|
|
123
|
+
signature: string;
|
|
124
|
+
}>;
|
|
125
|
+
/**
|
|
126
|
+
* `PATCH /vaults/:address/fees` (vault admin) — build an `update_management_fee`
|
|
127
|
+
* tx (bps ≤2000). Reverts on a fees-immutable vault.
|
|
128
|
+
*/
|
|
129
|
+
prepareUpdateManagementFee(vaultAddress: string, managementFeeBps: number): Promise<ZapV2SingleTxResponse>;
|
|
130
|
+
/**
|
|
131
|
+
* `POST /vaults/:address/transfer-admin` (vault admin) — initiate transferring
|
|
132
|
+
* vault admin to `newAdmin` (the new admin must ACCEPT separately). HIGH
|
|
133
|
+
* sensitivity — gates the agent's `transfer_admin` tool.
|
|
134
|
+
*/
|
|
135
|
+
prepareTransferAdmin(vaultAddress: string, newAdmin: string): Promise<ZapV2SingleTxResponse>;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|