@dfm-fi/agent 0.2.1942 → 0.2.1960

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 CHANGED
@@ -45,6 +45,17 @@ Claude Desktop (`claude_desktop_config.json`):
45
45
  }
46
46
  ```
47
47
 
48
+ ## Headless agent (no human account)
49
+
50
+ ```bash
51
+ claude mcp add dfm --env DFM_AGENT_WRITE_ENABLED=true -- npx -y @dfm-fi/agent@latest
52
+ ```
53
+
54
+ Then ask the agent to `onboard`: it creates its identity keypair, signs in, sets a profile (a default
55
+ avatar is generated when there is no image), creates an app wallet owned by a local P-256 key, and
56
+ prints the funding step — send USDC and SOL to the identity public key, then `app_wallet_fund {usdc, sol}`.
57
+ `whoami` prints the same funding step while the app wallet is short. No API key is needed for writes.
58
+
48
59
  ## How money moves
49
60
 
50
61
  Fund operations run from your **app wallet**, not from your own wallet. New app wallets are owned
@@ -71,17 +82,27 @@ running. Write tools also require `DFM_AGENT_WRITE_ENABLED=true`.
71
82
  | `trader_profile` | read | A trader's public profile by wallet or @username. |
72
83
  | `xp_status` | read | Your XP and the top of the leaderboard. |
73
84
  | `follow_trader` / `unfollow_trader` | social | Follow or unfollow a trader. Moves no funds. |
85
+ | `report_bug` | support | Send a bug report to the DFM team. Moves no funds. |
86
+ | `onboard` | onboarding | Headless agent: runs the steps below in order, stops at the first missing one and prints the funding step. |
87
+ | `create_identity_wallet` | onboarding | The agent's own Solana identity keypair (SIWS sign-in); reused if present, never a second. |
88
+ | `set_profile` | onboarding | Display name, @username, bio, avatar. No image file? A deterministic default avatar is generated. |
89
+ | `create_app_wallet` | onboarding | An app wallet owned by the agent's local P-256 key. |
90
+ | `create_api_key` | onboarding | Mint a `dfm_` key (read-only unless `DFM_ALLOW_WRITE_KEY_MINT=1`), stored 0600, never shown in chat. |
74
91
  | `launch_vault` | write | Launch a fund (2–10 assets, weights sum to 10,000 bps). |
75
92
  | `deposit` | write | USDC → fund shares (full zap-in; resumes an open escrow). |
76
93
  | `redeem` | write | Fund shares → USDC (full zap-out; resumes an open escrow). |
77
94
  | `swap` | write | Swap one token for another in the app wallet (Jupiter). |
78
95
  | `update_vault_assets` | write | Manager: replace a fund's target basket. |
79
96
  | `rebalance_to_targets` | write | Manager: swap holdings back to the target weights. |
80
- | `init_rebalancer` | write | Manager: enable rebalancing (normally done by `launch_vault`). |
97
+ | `init_rebalancer` | write | Manager: enable rebalancing (normally done by `launch_vault`). Rebalancing is manual: there are no auto-rebalance tools. |
98
+ | `attach_metadata` | write | Retry a fund's share-token metadata (name, symbol, logo) when the launch skipped it or it failed. |
81
99
  | `zap_v2_cancel` / `zap_v2_update_envelope` | write | Recover or unstick a stalled escrow. |
82
100
  | `update_vault_policy` | write | Manager: constitutional limits (explicit human instruction only). |
83
- | `transfer_admin` / `accept_admin_transfer` | write | Hand over / accept fund control (explicit human instruction only). |
84
- | `app_wallet_fund` / `app_wallet_withdraw` / `withdraw_asset` / `app_wallet_rearm` | write | Fund, sweep or re-arm the app wallet; withdraw one token. |
101
+ | `transfer_admin` / `accept_admin_transfer` | write | **Not available yet in this MCP**: `transfer_admin` always refuses; `accept_admin_transfer` only reconciles an acceptance that already landed. |
102
+ | `app_wallet_fund` / `app_wallet_withdraw` / `withdraw_asset` / `app_wallet_rearm` | write | Fund (from the local identity wallet; not in key-only mode), sweep or re-arm the app wallet; withdraw one token. |
103
+ | `close_empty_accounts` | write | Agent-owned app wallet: close empty token accounts and reclaim the rent. |
104
+
105
+ The authoritative list is the server's `tools/list`.
85
106
 
86
107
  There is no fee tool: fees are set by the protocol, not per fund.
87
108
 
@@ -89,12 +110,14 @@ There is no fee tool: fees are set by the protocol, not per fund.
89
110
 
90
111
  | Variable | Default | Description |
91
112
  |----------|---------|-------------|
92
- | `DFM_API_KEY` | — | Recommended auth. A `dfm_` key minted in the app (write access needed for write tools). |
113
+ | `DFM_API_KEY` | — | Auth for an agent operating a human's account: a `dfm_` key the human mints in the app (write access needed for write tools). Not needed by a headless agent. |
93
114
  | `DFM_AGENT_WRITE_ENABLED` | `false` | Master switch for every write tool. |
94
115
  | `DFM_API_URL` | `https://app.dfm.finance` | API base (`/api/v2` is appended). |
95
116
  | `DFM_MCP_CONFIRM_REQUIRED` | unset | `1` = every write needs `confirmPhrase:"<tool_name>"` typed by the human (prompt-injection guard). |
96
117
  | `DFM_MCP_NONINTERACTIVE_WRITES` | unset | `1` = skip the phrase even when required. Only for unattended machines. |
97
- | `DFM_AGENT_KEYPAIR_PATH` / `DFM_AGENT_KEYPAIR_JSON` | — | Legacy: sign in with a local Solana keypair instead of an API key. A leaked keypair is the wallet; prefer a key. |
118
+ | `DFM_ALLOW_WRITE_KEY_MINT` | unset | `1` = `create_api_key` may mint write keys / write caps. Otherwise it mints read-only keys only. |
119
+ | `DFM_AGENT_AVATAR_PATH` | `~/.config/dfm/avatar.{png,jpg,jpeg,webp,gif}` | Operator avatar for `set_profile` (≤ 2 MB). Absent → a generated default. |
120
+ | `DFM_AGENT_KEYPAIR_PATH` / `DFM_AGENT_KEYPAIR_JSON` | `~/.config/dfm/agent-wallet.json` | Identity of a headless agent (SIWS sign-in; `create_identity_wallet` creates it once). It only signs in and funds the app wallet (`app_wallet_fund`); fund operations are signed by the app wallet. Keep it 0600 and backed up. |
98
121
  | `HELIUS_RPC_URL` / `DFM_RPC_URL` | — | Optional: submit through your own RPC instead of the backend. |
99
122
  | `SOLANA_CLUSTER` | `mainnet-beta` | Cluster name. |
100
123
 
@@ -163,9 +163,10 @@ export declare class DfmApiClient {
163
163
  /**
164
164
  * `POST /agent/operating/float/withdraw` — sweep the float back to its OWNER.
165
165
  *
166
- * The destination is pinned server-side to the authenticated wallet, so this
167
- * cannot send anywhere else. Works past a stale rebalancer stamp and a user
168
- * expiry (the sweep carve-outs) — the float must never be trapped.
166
+ * The agent never names a destination: the API resolves the owner's SAVED
167
+ * withdrawal wallet first and falls back to the authenticated wallet, so
168
+ * this cannot send anywhere else. Works past a stale rebalancer stamp and a
169
+ * user expiry (the sweep carve-outs) — the float must never be trapped.
169
170
  */
170
171
  /**
171
172
  * `GET /agent/operating/assets` — every token the app wallet holds.
@@ -187,9 +188,10 @@ export declare class DfmApiClient {
187
188
  /**
188
189
  * `POST /agent/operating/assets/withdraw` — move ONE mint back to the owner.
189
190
  *
190
- * `destination` is deliberately NOT exposed here: the API defaults it to the
191
- * authenticated wallet, and an agent that could name a destination would be
192
- * a drain vector for anything that can put words in its input.
191
+ * `destination` is deliberately NOT exposed here: the API resolves the
192
+ * owner's saved withdrawal wallet first, falling back to the authenticated
193
+ * wallet — an agent that could name a destination would be a drain vector
194
+ * for anything that can put words in its input.
193
195
  */
194
196
  withdrawAppWalletAsset(mint: string, amount?: number | string): Promise<{
195
197
  signature: string;
@@ -251,6 +253,12 @@ export declare class DfmApiClient {
251
253
  * and signs it behind both cap layers plus the tx-assert simulation. Used
252
254
  * when the MCP has an API key but no keypair; see `keyless.ts` for when that
253
255
  * applies and why it never falls back to local signing.
256
+ *
257
+ * The idempotency key is PINNED to the operation itself (tool + canonical
258
+ * args, hashed) rather than a fresh UUID: when the response is lost — a 504
259
+ * the server outlived, a dropped socket — the agent's retry carries the SAME
260
+ * key, and the server returns the already-running/finished job instead of
261
+ * executing a second time (the two-vaults-two-fees bug, A7).
254
262
  */
255
263
  executeViaOperatingWallet(tool: string, args: Record<string, unknown>): Promise<{
256
264
  signature?: string;
@@ -323,6 +331,8 @@ export declare class DfmApiClient {
323
331
  assets: CreateAssetInput[];
324
332
  description?: string;
325
333
  tags?: number[];
334
+ /** Let DFM announce this fund publicly on X/Telegram/Moltbook (default false). */
335
+ announceOptIn?: boolean;
326
336
  }): Promise<CreatePrepareResponse>;
327
337
  /**
328
338
  * `POST /vaults/create/confirm` — record the vault doc after the create tx
@@ -13,13 +13,42 @@
13
13
  * NEVER sees the secret key — signing is delegated to `signing.ts` via the
14
14
  * write methods, and auth is delegated to `SiwsSession`.
15
15
  */
16
- import { randomUUID } from 'node:crypto';
16
+ import { createHash, randomUUID } from 'node:crypto';
17
+ import { dfmClientHeaders } from './client-header.js';
17
18
  const BASE58_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
18
19
  function requireBase58(addr, label) {
19
20
  if (!BASE58_RE.test(addr)) {
20
21
  throw new Error(`Invalid base58 ${label}: ${addr}`);
21
22
  }
22
23
  }
24
+ /**
25
+ * Canonical JSON for an operation id: object keys sorted recursively, so two
26
+ * calls that MEAN the same write hash identically regardless of key order.
27
+ * Money fields travel as strings server-side, so no number-precision games.
28
+ */
29
+ function stableStringify(value) {
30
+ if (value === null || typeof value !== 'object')
31
+ return JSON.stringify(value) ?? 'null';
32
+ if (Array.isArray(value))
33
+ return `[${value.map(stableStringify).join(',')}]`;
34
+ const entries = Object.keys(value)
35
+ .sort()
36
+ .map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`);
37
+ return `{${entries.join(',')}}`;
38
+ }
39
+ /**
40
+ * The deterministic operation id for ONE keyless write (A7): same tool + same
41
+ * args → same id, so a retry after a LOST response names the first attempt
42
+ * instead of executing twice. This is what "the same logical operation" means
43
+ * here — a genuinely NEW write with byte-identical args is deliberately
44
+ * treated as a retry; change an argument to make a new operation.
45
+ */
46
+ function operatingOperationKey(tool, args) {
47
+ const digest = createHash('sha256').update(`${tool}\n${stableStringify(args)}`).digest('hex');
48
+ return `opexec-${digest}`;
49
+ }
50
+ /** Launch-surface label (`launchedVia: 'mcp'` on the API side) — never a credential. */
51
+ const CLIENT_HEADERS = dfmClientHeaders();
23
52
  export class DfmApiClient {
24
53
  session;
25
54
  baseUrl;
@@ -31,7 +60,7 @@ export class DfmApiClient {
31
60
  async publicRequest(path, init) {
32
61
  const res = await fetch(`${this.baseUrl}${path}`, {
33
62
  ...init,
34
- headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}) },
63
+ headers: { 'Content-Type': 'application/json', ...CLIENT_HEADERS, ...(init?.headers ?? {}) },
35
64
  });
36
65
  if (!res.ok) {
37
66
  const body = await res.text().catch(() => '');
@@ -49,6 +78,7 @@ export class DfmApiClient {
49
78
  const idempotencyKey = mutating ? (pinnedKey ?? randomUUID()) : undefined;
50
79
  const common = (extra) => ({
51
80
  'Content-Type': 'application/json',
81
+ ...CLIENT_HEADERS,
52
82
  ...extra,
53
83
  ...(idempotencyKey ? { 'X-Idempotency-Key': idempotencyKey } : {}),
54
84
  ...(reqInit.headers ?? {}),
@@ -58,8 +88,10 @@ export class DfmApiClient {
58
88
  // resolves server-side to its owner wallet plus the credential's caps.
59
89
  // That matters because a leaked keypair IS the wallet, whereas a leaked API
60
90
  // key is revocable and capped (read-only unless explicitly widened).
61
- // There is no re-auth loop here — a key does not expire; a 401 means it was
62
- // revoked, and silently retrying a revoked credential would be wrong.
91
+ // There is no re-auth loop here — a key cannot be refreshed from the MCP
92
+ // side. A 401 means the key was revoked OR expired (the server enforces
93
+ // `expiresAt`); silently retrying a dead credential would be wrong either
94
+ // way, so the caller is told to mint a new one.
63
95
  const apiKey = sessionOnly ? undefined : process.env.DFM_API_KEY?.trim();
64
96
  if (apiKey) {
65
97
  const res = await fetch(`${this.baseUrl}${path}`, {
@@ -288,9 +320,10 @@ export class DfmApiClient {
288
320
  /**
289
321
  * `POST /agent/operating/float/withdraw` — sweep the float back to its OWNER.
290
322
  *
291
- * The destination is pinned server-side to the authenticated wallet, so this
292
- * cannot send anywhere else. Works past a stale rebalancer stamp and a user
293
- * expiry (the sweep carve-outs) — the float must never be trapped.
323
+ * The agent never names a destination: the API resolves the owner's SAVED
324
+ * withdrawal wallet first and falls back to the authenticated wallet, so
325
+ * this cannot send anywhere else. Works past a stale rebalancer stamp and a
326
+ * user expiry (the sweep carve-outs) — the float must never be trapped.
294
327
  */
295
328
  /**
296
329
  * `GET /agent/operating/assets` — every token the app wallet holds.
@@ -304,9 +337,10 @@ export class DfmApiClient {
304
337
  /**
305
338
  * `POST /agent/operating/assets/withdraw` — move ONE mint back to the owner.
306
339
  *
307
- * `destination` is deliberately NOT exposed here: the API defaults it to the
308
- * authenticated wallet, and an agent that could name a destination would be
309
- * a drain vector for anything that can put words in its input.
340
+ * `destination` is deliberately NOT exposed here: the API resolves the
341
+ * owner's saved withdrawal wallet first, falling back to the authenticated
342
+ * wallet — an agent that could name a destination would be a drain vector
343
+ * for anything that can put words in its input.
310
344
  */
311
345
  async withdrawAppWalletAsset(mint, amount) {
312
346
  return this.authRequest('/agent/operating/assets/withdraw', {
@@ -361,11 +395,18 @@ export class DfmApiClient {
361
395
  * and signs it behind both cap layers plus the tx-assert simulation. Used
362
396
  * when the MCP has an API key but no keypair; see `keyless.ts` for when that
363
397
  * applies and why it never falls back to local signing.
398
+ *
399
+ * The idempotency key is PINNED to the operation itself (tool + canonical
400
+ * args, hashed) rather than a fresh UUID: when the response is lost — a 504
401
+ * the server outlived, a dropped socket — the agent's retry carries the SAME
402
+ * key, and the server returns the already-running/finished job instead of
403
+ * executing a second time (the two-vaults-two-fees bug, A7).
364
404
  */
365
405
  async executeViaOperatingWallet(tool, args) {
366
406
  const first = await this.authRequest('/agent/operating/execute', {
367
407
  method: 'POST',
368
408
  body: JSON.stringify({ tool, args }),
409
+ idempotencyKey: operatingOperationKey(tool, args),
369
410
  });
370
411
  // Finished inside the server's inline window — the shape this method has
371
412
  // always returned.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `x-dfm-client: mcp/<version>` on every DFM API call this package makes.
3
+ *
4
+ * The API stamps it onto a launched fund as `launchedVia: 'mcp'` (marketing:
5
+ * how many funds AI agents launch through the MCP). It is a LABEL only — the
6
+ * server never grants, widens or skips anything because of it.
7
+ */
8
+ export declare const DFM_CLIENT_HEADER = "x-dfm-client";
9
+ export declare function readPackageVersion(): string;
10
+ export declare function dfmClientHeaders(version?: string): Record<string, string>;
11
+ //# sourceMappingURL=client-header.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ /**
5
+ * `x-dfm-client: mcp/<version>` on every DFM API call this package makes.
6
+ *
7
+ * The API stamps it onto a launched fund as `launchedVia: 'mcp'` (marketing:
8
+ * how many funds AI agents launch through the MCP). It is a LABEL only — the
9
+ * server never grants, widens or skips anything because of it.
10
+ */
11
+ export const DFM_CLIENT_HEADER = 'x-dfm-client';
12
+ export function readPackageVersion() {
13
+ try {
14
+ // dist/client-header.js → ../package.json (same layout in the published tarball).
15
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
16
+ return JSON.parse(readFileSync(pkgPath, 'utf8')).version ?? '0.0.0';
17
+ }
18
+ catch {
19
+ return '0.0.0';
20
+ }
21
+ }
22
+ export function dfmClientHeaders(version = readPackageVersion()) {
23
+ return { [DFM_CLIENT_HEADER]: `mcp/${version}` };
24
+ }
25
+ //# sourceMappingURL=client-header.js.map
@@ -38,6 +38,8 @@ export interface LaunchVaultParams {
38
38
  /** Category enum (0-7, 255). Defaults to 255 (uncategorized/other). */
39
39
  category?: number;
40
40
  description?: string;
41
+ /** Let DFM announce this fund publicly on X/Telegram/Moltbook (default false). */
42
+ announceOptIn?: boolean;
41
43
  }
42
44
  export interface LaunchVaultResult {
43
45
  ok: boolean;
@@ -93,6 +93,7 @@ export async function runLaunchVault(config, api, params) {
93
93
  category: params.category ?? 255,
94
94
  assets: params.assets,
95
95
  description: params.description,
96
+ ...(params.announceOptIn === true && { announceOptIn: true }),
96
97
  });
97
98
  // 2. SIGN locally + 3. SUBMIT.
98
99
  const signed = signBase64Transaction(config, prepared.transaction);
@@ -0,0 +1,6 @@
1
+ export declare const DEFAULT_AVATAR_SIZE: number;
2
+ /** Which cells are filled: 15 bits for the left 3 columns, mirrored to 5. */
3
+ export declare function identiconCells(seed: string): boolean[][];
4
+ /** A deterministic PNG identicon for `seed` (the identity public key). */
5
+ export declare function defaultAvatarPng(seed: string): Buffer;
6
+ //# sourceMappingURL=default-avatar.d.ts.map
@@ -0,0 +1,101 @@
1
+ /**
2
+ * DEFAULT AVATAR for headless agents (2026-10-20). The API's onboarding gate
3
+ * wants a chosen name AND an avatar before an app wallet can be created; a
4
+ * vanilla headless agent has no operator image, so `set_profile` used to stop
5
+ * dead. This draws a deterministic 5×5 mirrored identicon from the identity
6
+ * public key and encodes it as a PNG with no dependency beyond node:zlib — the
7
+ * API accepts PNG (magic-byte check in profile.controller.ts) and re-encodes it
8
+ * to WebP. Same pubkey → same bytes, so re-running set_profile is idempotent.
9
+ * An operator-provided image always wins (see `avatarPath`).
10
+ */
11
+ import { createHash } from 'node:crypto';
12
+ import { deflateSync } from 'node:zlib';
13
+ const GRID = 5;
14
+ const CELL = 40;
15
+ const MARGIN = 20;
16
+ export const DEFAULT_AVATAR_SIZE = GRID * CELL + 2 * MARGIN; // 240 px
17
+ const CRC_TABLE = (() => {
18
+ const t = new Uint32Array(256);
19
+ for (let n = 0; n < 256; n++) {
20
+ let c = n;
21
+ for (let k = 0; k < 8; k++)
22
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
23
+ t[n] = c >>> 0;
24
+ }
25
+ return t;
26
+ })();
27
+ function crc32(buf) {
28
+ let c = 0xffffffff;
29
+ for (const b of buf)
30
+ c = CRC_TABLE[(c ^ b) & 0xff] ^ (c >>> 8);
31
+ return (c ^ 0xffffffff) >>> 0;
32
+ }
33
+ function chunk(type, data) {
34
+ const len = Buffer.alloc(4);
35
+ len.writeUInt32BE(data.length);
36
+ const td = Buffer.concat([Buffer.from(type, 'latin1'), data]);
37
+ const crc = Buffer.alloc(4);
38
+ crc.writeUInt32BE(crc32(td));
39
+ return Buffer.concat([len, td, crc]);
40
+ }
41
+ /** HSL (h 0-360, s/l 0-1) → 8-bit RGB. */
42
+ function hsl(h, s, l) {
43
+ const a = s * Math.min(l, 1 - l);
44
+ const f = (n) => {
45
+ const k = (n + h / 30) % 12;
46
+ return Math.round(255 * (l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))));
47
+ };
48
+ return [f(0), f(8), f(4)];
49
+ }
50
+ /** Which cells are filled: 15 bits for the left 3 columns, mirrored to 5. */
51
+ export function identiconCells(seed) {
52
+ const h = createHash('sha256').update(seed, 'utf8').digest();
53
+ const cells = [];
54
+ for (let y = 0; y < GRID; y++) {
55
+ const row = [];
56
+ for (let x = 0; x < GRID; x++) {
57
+ const col = x < 3 ? x : GRID - 1 - x;
58
+ row.push((h[y * 3 + col] & 1) === 1);
59
+ }
60
+ cells.push(row);
61
+ }
62
+ return cells;
63
+ }
64
+ /** A deterministic PNG identicon for `seed` (the identity public key). */
65
+ export function defaultAvatarPng(seed) {
66
+ const h = createHash('sha256').update(seed, 'utf8').digest();
67
+ const fg = hsl(((h[20] << 8) | h[21]) % 360, 0.55, 0.5);
68
+ const bg = [242, 242, 242];
69
+ const cells = identiconCells(seed);
70
+ const size = DEFAULT_AVATAR_SIZE;
71
+ const raw = Buffer.alloc(size * (1 + size * 3));
72
+ for (let py = 0; py < size; py++) {
73
+ const rowOff = py * (1 + size * 3);
74
+ raw[rowOff] = 0; // filter: none
75
+ for (let px = 0; px < size; px++) {
76
+ const cx = Math.floor((px - MARGIN) / CELL);
77
+ const cy = Math.floor((py - MARGIN) / CELL);
78
+ const on = px >= MARGIN && py >= MARGIN && cx < GRID && cy < GRID && cells[cy][cx];
79
+ const [r, g, b] = on ? fg : bg;
80
+ const o = rowOff + 1 + px * 3;
81
+ raw[o] = r;
82
+ raw[o + 1] = g;
83
+ raw[o + 2] = b;
84
+ }
85
+ }
86
+ const ihdr = Buffer.alloc(13);
87
+ ihdr.writeUInt32BE(size, 0);
88
+ ihdr.writeUInt32BE(size, 4);
89
+ ihdr[8] = 8; // bit depth
90
+ ihdr[9] = 2; // colour type: truecolour RGB
91
+ ihdr[10] = 0;
92
+ ihdr[11] = 0;
93
+ ihdr[12] = 0;
94
+ return Buffer.concat([
95
+ Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
96
+ chunk('IHDR', ihdr),
97
+ chunk('IDAT', deflateSync(raw)),
98
+ chunk('IEND', Buffer.alloc(0)),
99
+ ]);
100
+ }
101
+ //# sourceMappingURL=default-avatar.js.map
@@ -22,8 +22,8 @@
22
22
  * • zap_v2_cancel — recover a stalled escrow's holdings as-is
23
23
  * • zap_v2_update_envelope — loosen pinned floors to unstick a mid-crank escrow
24
24
  * • update_vault_assets — MANAGER: RECONFIGURE — set new target weights + converge (own vault)
25
- * • transfer_admin — MANAGER, HIGH-SENSITIVITY: hand vault control to a new key
26
- * • accept_admin_transfer — HIGH-SENSITIVITY: complete a pending control handoff
25
+ * • transfer_admin — NOT available yet: always refuses (no app-wallet executor)
26
+ * • accept_admin_transfer — reconcile-only: new acceptances are refused
27
27
  * • update_vault_policy — HIGH-SENSITIVITY: create/amend constitutional limits
28
28
  * • init_rebalancer — MANAGER: enable rebalancing (create RebalancerConfig) for own vault
29
29
  * • rebalance_to_targets — MANAGER: converge holdings to the vault's CONFIG targets (own vault)