npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dfm-fi/agent

v0.2.582

Published

DFM v2 MCP server — drive the full DTF launch → deposit → redeem (+ manage) lifecycle from an AI agent against the live API.

Downloads

3,082

Readme

@dfm-fi/agent — DFM v2 Agent Test Harness (MCP)

An MCP (Model Context Protocol) server that lets an external AI agent drive the full DFM v2 launch → deposit → redeem DTF flow against the live API (https://n2-api.dfm.finance/api/v2) programmatically — no web-UI clicking.

This is a test harness for the founder + partner: point your own AI agent (Claude Desktop / Claude Code / any MCP client) at the live closed-mainnet API, authenticate with your own test-wallet keypair loaded locally, and run the whole vault lifecycle — create a DTF, deposit USDC, redeem back to USDC — from the agent.

⚠️ This is a real-money Solana mainnet system. Read the Security section.

Prerequisites

  • Node 20+ (ESM build).
  • To use it: nothing else — npx -y @dfm-fi/agent pulls the published, self-contained package (no monorepo clone, no npm install).
  • To hack on it from source: this package ships its own package-lock.json, so npm ci inside agent/ builds it standalone (that's exactly what the publish CI does).
  • A dedicated throwaway Solana test wallet (NOT the protocol wallet), funded with a few dollars of USDC (mainnet mint EPjFW…) + a little SOL for fees, and allowlisted for the closed alpha (send its pubkey to an admin).
  • For writes: no client RPC is required — the backend broadcasts your signed tx (POST /tx/submit). Set HELIUS_RPC_URL only if you want to submit through your own RPC instead (optional override; power users + the test harness).

Security model (read first)

  • The agent authenticates as its own test wallet, whose keypair is loaded locally by you from a file path (DFM_AGENT_KEYPAIR_PATH) or an inline env (DFM_AGENT_KEYPAIR_JSON). The keypair never crosses the MCP boundary — it is never a tool argument, never in a tool result, never logged. Only the public key is ever surfaced.
  • Never use the protocol wallet (9GjE…) here. Use a dedicated throwaway test wallet funded with a few dollars of USDC. The harness hard-refuses to load the known protocol pubkey as a tripwire.
  • The test wallet must be on the closed-alpha access allowlist — otherwise SIWS sign-in is rejected server-side (403), so it can't reach any protected endpoint. An admin adds it via the /ops Access panel (POST /access/allowlist).
  • All real-money WRITE tools (deposit / redeem) are gated behind DFM_AGENT_WRITE_ENABLED=true and default OFF. With write off, those tools refuse and never touch the chain or load the keypair.

Auth + signing flow (how it works)

SIWS (Sign In With Solana), signed locally, exchanged for a Bearer session:

  1. POST /auth/nonce { wallet }{ nonce, message } — the server builds the exact message (domain-bound to n2.dfm.finance, nonce embedded).
  2. Sign that exact message locally with the test keypair (Ed25519 → base58).
  3. POST /auth/siws { wallet, signature, message, client: 'native' }{ accessToken, … }. The server also enforces the access gate here.
  4. Authed calls send Authorization: Bearer <accessToken>; a 401 triggers one transparent re-auth + retry.

The WRITE flows then follow prepare → sign locally → submit → (confirm/poll):

  • launch: /vaults/create/prepare → one unsigned v0 tx → sign → submit → /vaults/create/confirm.
  • deposit / redeem: /zap-…-v2/open/prepare → sign → submit → poll /zap-v2/status until the backend orchestrator has cranked all swap legs → /zap-…-v2/close/prepare → sign → submit. The agent signs only the two user-side boundary transactions; the backend cranks the permissionless legs.

Tools

| Tool | Surface | Endpoint | Notes | |------|---------|----------|-------| | whoami | read | /access/status | Test-wallet pubkey, cluster, write flag, gate status. Call first. | | list_dtfs | read | GET /vaults | Lists vaults (address, symbol, TVL, fees, basket). Public. | | get_vault | read | GET /vaults/:address | Full vault detail + weights + fee structure. Public. | | zap_status | read (auth) | GET /vaults/:address/zap-v2/status/:user | Decoded escrow + per-leg crank progress, or null. | | get_portfolio | read (auth) | GET /portfolio | Session wallet's positions (API reads wallet from JWT). | | launch_vault | write (gated) | create prepare/confirm | Create a DTF. Basket sums to 10000 bps, ≤15 assets. | | deposit | write (gated) | zap-in-v2 open/close | Full zap-in lifecycle. RAW 6dp USDC. Resumes an open escrow. | | redeem | write (gated) | zap-out-v2 open/close | Full zap-out lifecycle. RAW 6dp shares. Resumes an open escrow. | | zap_v2_cancel | write (gated) | zap-v2/cancel/prepare | Return a stalled escrow's holdings to the wallet, as-is. | | 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. | | 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. | | update_management_fee | write (gated) | POST /fees/:address/update-config | MANAGER: set the management fee (≤2000 bps). Reverts on immutable-fee vaults (which is what this agent creates). | | 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. | | init_rebalancer | write (gated) | POST /rebalance/:address/init/prepare | MANAGER: enable rebalancing (create the vault's RebalancerConfig, creator pays rent). Bundled into launch_vault automatically — only needed for older/opt-out/oversized vaults. Your own vault only. | | rebalance | write (gated) | POST /rebalance/:address/execute/prepare | MANAGER: execute a rebalance swap (sell amountIn raw of inputMintoutputMint, both basket assets) — one-signature approve→execute→revoke. On-chain ≤slippage + Pyth NAV-loss guards. Your own vault only. |

Prompt-injection / safety model

The harness is boxed in by design, so a prompt-injected agent (tricked by malicious text it reads while browsing vaults) is bounded:

  • 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.
  • 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).
  • 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.
  • 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 two actions to watch — only ever invoke them from an explicit human ask, never inferred from a vault's name/description or other untrusted text:
    • transfer_admin — hands away control of your own vault.
    • update_vault_assets — full-replaces your own vault's basket with arbitrary (registered, Pyth-mapped) mints + weights; on the next rebalance the vault's value migrates into them. Bounded (own vault, API-validated mints, ≤15 assets, manual-mode + 24h timelock) so it stays inside your own money, but it IS a basket-redirection an injected agent could trigger.

Install

One command — @dfm-fi/agent is live on npm

Published at @dfm-fi/agent. The package is self-contained (no monorepo deps) and ships its built dist/, so npx fetches + runs it — no clone, no build.

The simplest possible install — one command, auto-registers the MCP server:

npx -y @dfm-fi/agent install

That detects the claude CLI and runs the registration for you (and always prints the manual command + Claude Desktop config as a fallback). Add real-money tools with:

npx -y @dfm-fi/agent install --write --keypair /abs/path/test-wallet.json

(No Solana RPC key needed — the DFM backend broadcasts your signed tx. --dry-run prints what it would do without registering.)

Read-only:

claude mcp add dfm -- npx -y @dfm-fi/agent

Full write-enabled (Claude Code, no RPC key needed):

claude mcp add dfm \
  --env DFM_AGENT_KEYPAIR_PATH=/abs/path/test-wallet.json \
  --env DFM_AGENT_WRITE_ENABLED=true \
  -- npx -y @dfm-fi/agent

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "dfm": {
      "command": "npx",
      "args": ["-y", "@dfm-fi/agent"],
      "env": {
        "DFM_AGENT_KEYPAIR_PATH": "/abs/path/test-wallet.json",
        "DFM_AGENT_WRITE_ENABLED": "true"
      }
    }
  }
}

(For read-only, drop DFM_AGENT_WRITE_ENABLED — the read tools need no keypair. Optional: add --env HELIUS_RPC_URL=https://… to submit through your OWN RPC instead of the backend.)

Already published at @dfm-fi/agent. To cut a new version, see Releasing / updating below — a version bump auto-publishes via GitHub, or run agent/scripts/release.sh from your machine.

From source — works today, no publish needed

cd dfm-v2/agent && npm run build      # tsc -b → dist/
# then point your MCP client at the built binary (one line):
claude mcp add dfm -- node /abs/path/dfm-v2/agent/dist/mcp-server.js

Releasing / updating

The published npm version is what npx -y @dfm-fi/agent installs, so updating users = publishing a new version. There is nothing to bump. The version is derived automatically:

version = <major>.<minor>.<git commit count>      e.g.  0.2.474

where the commit count is the same v.N shown on the web nav and the API /health. So agent v.474@dfm-fi/[email protected] — a 1:1 mapping. (Edit agent/package.json's version only to move the major/minor base line; the patch is always the commit count.)

A) Hands-off — just push, GitHub publishes (preferred)

​.github/workflows/publish-agent.yml watches agent/** on dev/main. Every push that touches agent/** auto-publishes the new 0.2.<commitcount> version — only if it isn't already on npm (idempotent: a dev→main sync of the same commit publishes once; a registry blip fails loud, never a silent skip). It builds isolated from the monorepo with --ignore-scripts (no dependency lifecycle code runs on the VPS runner). So updating the package is just your normal flow:

git add agent/… CHANGELOG.md && git commit -m "feat(agent): …"
git push origin dev && git push origin HEAD:main      # → @dfm-fi/[email protected].<N> auto-publishes

The workflow runs on the self-hosted VPS runner (GitHub-hosted runners are billing-blocked here). Auto-publish on push has no required-reviewer gate by design — frictionless releases for the closed, team-only dev/main (same posture as the auto-deploy). Forks can't trigger it (push events only).

One-time setup: add a granular npm token (permission Read+Write, scoped to only the @dfm-fi/agent package — minimal blast radius) as the repo secret NPM_TOKEN:

gh secret set NPM_TOKEN --repo DFM-Finance/NEXUS2    # paste the token at the prompt (never commit it)

or via Settings ▸ Secrets and variables ▸ Actions ▸ New repository secret. Until it's set, the workflow fails loudly at the token step with that instruction (no publish, no side effects).

B) From your machine — one command

agent/scripts/release.sh computes the same 0.2.<commitcount> version, skips if it's already on npm, builds an isolated --ignore-scripts tarball, and publishes the exact built artifact — directly to npm if a local token is present (env NPM_TOKEN, or ~/.config/dfm/npm-token, chmod 600), otherwise it delivers the tarball to the npm-authed laptop (Taildrop → iMessage fallback) and prints the one command to finish there.

agent/scripts/release.sh

It never takes the token as an argument or echoes it (a chmod-600 temp .npmrc removed by an EXIT trap). The version tracks committed history, so commit your agent changes first, then run it (or just push and let path A do it — idempotent either way).

Quick start (read-only — safe)

# Run the MCP server against the live API
DFM_API_URL=https://n2-api.dfm.finance \
DFM_AGENT_KEYPAIR_PATH=/abs/path/test-wallet.json \
npm start                                   # = node dist/mcp-server.js

Or exercise the read path without MCP:

DFM_API_URL=https://n2-api.dfm.finance \
DFM_AGENT_KEYPAIR_PATH=/abs/path/test-wallet.json \
npx tsx src/examples/list-and-status.ts

Loading the test keypair

Generate a dedicated throwaway wallet and fund it with a little USDC + SOL:

solana-keygen new --no-bip39-passphrase -o ~/dfm-test-wallet.json
solana-keygen pubkey ~/dfm-test-wallet.json   # send this pubkey to an admin to allowlist

Then either point the agent at the file (preferred):

export DFM_AGENT_KEYPAIR_PATH=$HOME/dfm-test-wallet.json

…or inline the JSON array (e.g. in a secret manager):

export DFM_AGENT_KEYPAIR_JSON="$(cat ~/dfm-test-wallet.json)"

Enabling the real-money launch → deposit → redeem loop

Only after the parent has reviewed the write path (create-flow.ts, zap-v2-flow.ts, recovery-flow.ts, signing.ts, submit.ts):

export DFM_API_URL=https://n2-api.dfm.finance
export DFM_AGENT_KEYPAIR_PATH=$HOME/dfm-test-wallet.json                 # allowlisted + funded
export DFM_AGENT_WRITE_ENABLED=true                                     # the kill-switch
# export HELIUS_RPC_URL=https://mainnet.helius-rpc.com/?api-key=YOUR_KEY # OPTIONAL — submit via your own RPC

# End-to-end example: launch a demo DTF, deposit $12, redeem the full position.
# (Runs as a SAFE DRY-RUN — printing the calls it would make — when write is OFF.)
npx tsx src/examples/launch-deposit-redeem.ts

Copy-paste walkthrough (in an MCP client / agent)

1. whoami
   → confirm the test-wallet pubkey + that accessGate.allowed is true.

2. launch_vault
   {
     "name": "My First DTF",
     "symbol": "MYDTF",
     "assets": [
       { "mint": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", "symbol": "ETH", "allocationBps": 5000 },
       { "mint": "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN", "symbol": "JUP", "allocationBps": 5000 }
     ],
     "managementFeeBps": 100,
     "exitFeeBps": 100
   }
   → returns { vault: "<address>", signature, ... }. Note the vault address.
   (These two mints are already registered + Pyth-mapped on mainnet. To use
   others, list candidates with GET /assets and make sure each is registered.)

3. deposit
   { "vault": "<address from step 2>", "usdcAmount": "12000000" }   // $12 raw (6dp)
   → drives open → orchestrator cranks → close; returns when shares are minted.

4. get_portfolio
   → read the resulting raw share balance for that vault.

5. redeem
   { "vault": "<address>", "shares": "<raw shares from step 4>" }
   → drives open → cranks → close; returns net USDC paid (minus the 1% exit fee).

If a deposit/redeem STALLS:
   zap_status { vault, user }      → inspect per-leg crank progress + failures
   zap_v2_update_envelope { vault } → loosen the floors 5% and let the crank retry
   zap_v2_cancel { vault }          → give up and get the held funds back, as-is

Notes:

  • Amounts are RAW 6-decimal: $12 = "12000000", 1 share = "1000000".
  • The first deposit into a vault must clear ~$12 so it stays above the $10 floor after slippage.
  • allocationBps MUST sum to exactly 10000 across the basket; ≤15 assets on mainnet.
  • deposit/redeem are resumable: if a prior open landed but its close never did, calling the same tool again resumes (poll → close) instead of re-opening.

Configure Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "dfm": {
      "command": "node",
      "args": ["/abs/path/dfm-v2/agent/dist/mcp-server.js"],
      "env": {
        "DFM_API_URL": "https://n2-api.dfm.finance",
        "DFM_AGENT_KEYPAIR_PATH": "/abs/path/dfm-test-wallet.json",
        "SOLANA_CLUSTER": "mainnet-beta"
      }
    }
  }
}

(Leave DFM_AGENT_WRITE_ENABLED unset for a read-only agent. Add HELIUS_RPC_URL + DFM_AGENT_WRITE_ENABLED=true only when enabling writes.)

Environment variables

| Variable | Required | Default | Description | |----------|----------|---------|-------------| | DFM_API_URL | No | https://n2-api.dfm.finance | API base (the /api/v2 prefix is auto-appended). | | 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). | | DFM_AGENT_KEYPAIR_JSON | For auth | — | The secret-key JSON array inline (alternative; e.g. injected from a secret manager). | | DFM_AGENT_WRITE_ENABLED | No | false | Master kill-switch for all write tools (launch/deposit/redeem/cancel/update-envelope). | | HELIUS_RPC_URL / DFM_RPC_URL | No (optional) | — | OPTIONAL override — submit through your OWN RPC instead of the backend broadcast (POST /tx/submit). Writes need NO client RPC by default; mainnet never uses the rate-limited public RPC. | | SOLANA_CLUSTER | No | mainnet-beta | mainnet-beta or devnet. |

Architecture

Agent / MCP client
   │  stdio (MCP)
   ▼
MCP server (this package)
   ├─ read tools ─────────► DFM v2 API (public + authed GET)
   └─ write tools (gated) ─► DFM v2 API /prepare ──► unsigned v0 tx(s)
                                  │
                       sign LOCALLY with test keypair (signing.ts)
                                  │
                       submit via RPC (submit.ts) — rebroadcast in-window,
                                  │                  re-prepare on blockhash expiry
            ┌─────────────────────┴─────────────────────┐
   launch:  confirm via /create/confirm     deposit/redeem:  poll /zap-v2/status
                                                              until legs cranked,
                                                              then /close → sign → submit

The backend orchestrator (server-side) cranks the permissionless Jupiter swap legs — the agent only signs the two user-side boundary transactions. The agent does not crank legs and does not hold the protocol key.

Source map

| File | Role | |------|------| | src/mcp-server.ts | MCP stdio server; registers the 13 tools (5 read + 8 write) + the write gate. | | src/config.ts | Env → AgentConfig (API URL, cluster, RPC, write flag, keypair source). | | src/session.ts | SIWS sign-in → Bearer session; in-flight dedup; transparent re-auth on 401. | | src/signing.ts | The signing boundary — loads the local keypair, signs SIWS + v0 txs. Refuses the protocol wallet; never logs key material. | | src/submit.ts | RPC submit + confirm; rebroadcast in-window; BlockhashExpiredError / OnChainRevertError. | | src/api-client.ts | Typed DFM v2 API client (public + authed; create + zap-v2 prepare/confirm). | | src/create-flow.ts | launch_vault driver: prepare → sign → submit → confirm (+ ghost-vault recovery). | | src/zap-v2-flow.ts | deposit / redeem lifecycle drivers (resume-aware, close-retry). | | src/recovery-flow.ts | zap_v2_cancel + zap_v2_update_envelope (floors auto-derived from the live escrow). | | src/types.ts | Response shapes mirrored against the live API. |

What remains for the parent to review/enable

All write tools are fully implemented but gated OFF (DFM_AGENT_WRITE_ENABLED unset). Before flipping the gate, review the real-money path (search for the // REVIEW: real-money markers):

  • launch_vault (create-flow.ts) — pulls the on-chain creation fee from the test wallet; the test wallet becomes the vault admin. Confirm the basket / fee defaults are what you want.
  • deposit / redeem (zap-v2-flow.ts) — move USDC ↔ shares. They are resume-aware (won't double-open) and retry the close on blockhash expiry.
  • zap_v2_cancel / zap_v2_update_envelope (recovery-flow.ts) — stall recovery. update_envelope only ever LOOSENS, with the new floors derived from the live escrow's pinned minOuts (never hand-supplied).
  • signing.ts / submit.ts — the signing boundary + submit robustness. Confirm the protocol-wallet tripwire + the "secret never leaves this module" invariant.

To roll back at any time: unset DFM_AGENT_WRITE_ENABLED (the write tools then refuse and never touch the chain or load the keypair).