dfm-agent

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

DFM Agent

DFM Agent

DFM Agent is a fully autonomous AI skill for DTF (DeFi Token Fund) vault management on Solana. The agent researches markets, decides everything — name, symbol, asset allocations, fee structure, policy configuration — and deploys on-chain. No human-in-the-loop confirmation. The policy engine is the guardrail, not a human approval step.
DFM Agent是Solana上用于DTF(DeFi Token Fund,去中心化代币基金)金库管理的完全自主式AI技能。Agent会研究市场,自主决定所有事项——名称、符号、资产配置、费用结构、策略配置——并部署到链上。无需人工介入确认。策略引擎是防护机制,而非人工审批步骤。

Core Philosophy

核心理念

The agent is the creator. It has full authority over what it launches.
+----------------------------------------------------------------+
|                    AUTONOMOUS AGENT FLOW                        |
|                                                                |
|  1. RESEARCH    Agent analyzes markets, trending tokens,       |
|                 liquidity, volume, and macro conditions         |
|                                                                |
|  2. DECIDE      Agent picks vault name, symbol, assets,        |
|                 allocations, fees, and policy rules             |
|                                                                |
|  3. DEPLOY      Two-step vault creation:                       |
|                 a) POST /launch-dtf -> unsigned transaction     |
|                 b) Agent signs tx & submits on-chain            |
|                 c) POST /dtf-create -> policy + DB persist      |
|                                                                |
|  4. MANAGE      Agent monitors, rebalances, distributes fees   |
|                 -- all autonomously via API calls               |
|                                                                |
|  GUARDRAILS:    Policy engine validates constraints.            |
|                 Invalid payloads return errors with specifics.  |
|                 NO human confirmation step.                     |
+----------------------------------------------------------------+
PrincipleDetail
Fully autonomousAgent decides everything: name, symbol, assets, allocations, policy, fees. No confirmation prompts.
Two-step vault creation
POST /launch-dtf
builds the unsigned tx, agent signs & submits, then
POST /dtf-create
creates policy + persists to DB.
Policy engine as guardrailBackend validates the payload and policy config. If valid, it deploys. If not, it returns an error with the specific issue.
Non-custodialAgent Wallet private key never leaves the user's machine. Backend never receives secret keys.
Agent = on-chain authorityThe Agent Wallet becomes the permanent on-chain creator/manager of every vault it deploys.
Agent是创建者,对所启动的金库拥有完全权限。
+----------------------------------------------------------------+
|                    AUTONOMOUS AGENT FLOW                        |
|                                                                |
|  1. RESEARCH    Agent analyzes markets, trending tokens,       |
|                 liquidity, volume, and macro conditions         |
|                                                                |
|  2. DECIDE      Agent picks vault name, symbol, assets,        |
|                 allocations, fees, and policy rules             |
|                                                                |
|  3. DEPLOY      Two-step vault creation:                       |
|                 a) POST /launch-dtf -> unsigned transaction     |
|                 b) Agent signs tx & submits on-chain            |
|                 c) POST /dtf-create -> policy + DB persist      |
|                                                                |
|  4. MANAGE      Agent monitors, rebalances, distributes fees   |
|                 -- all autonomously via API calls               |
|                                                                |
|  GUARDRAILS:    Policy engine validates constraints.            |
|                 Invalid payloads return errors with specifics.  |
|                 NO human confirmation step.                     |
+----------------------------------------------------------------+
原则细节
完全自主Agent决定所有事项:名称、符号、资产、配置、策略、费用。无确认提示。
两步金库创建流程
POST /launch-dtf
构建未签名交易,Agent签名并提交,随后
POST /dtf-create
创建策略并持久化到数据库。
策略引擎作为防护机制后端验证负载和策略配置。若有效则部署,若无效则返回包含具体问题的错误。
非托管Agent Wallet私钥永远不会离开用户设备。后端永远不会接收密钥。
Agent = 链上权限主体Agent Wallet成为其部署的每个金库的永久链上创建者/管理者。

Sensitive Data Rules (MANDATORY -- READ FIRST)

敏感数据规则(强制要求——请先阅读)

NEVER print, echo, log, or display the values of these environment variables in terminal output:
  • DFM_AUTH_TOKEN
    -- JWT auth token
  • DFM_AGENT_KEYPAIR
    -- base58 secret key
  • Any private key, secret key, or auth token
To check if an env var is set, use length check only:
bash
node -e 'console.log(process.env.DFM_AUTH_TOKEN ? "set" : "not set")'
NEVER do any of the following:
bash
undefined
切勿在终端输出中打印、回显、记录或显示以下环境变量的值:
  • DFM_AUTH_TOKEN
    -- JWT认证令牌
  • DFM_AGENT_KEYPAIR
    -- base58格式密钥
  • 任何私钥、密钥或认证令牌
检查环境变量是否设置时,仅使用长度检查:
bash
node -e 'console.log(process.env.DFM_AUTH_TOKEN ? "set" : "not set")'
切勿执行以下任何操作:
bash
undefined

WRONG - exposes token in terminal output

错误 - 在终端输出中暴露令牌

echo $DFM_AUTH_TOKEN export DFM_AUTH_TOKEN="eyJ..." # WRONG - token visible in bash command curl -H "Authorization: Bearer eyJ..." ... # WRONG - token embedded in command curl -H "Authorization: Bearer $DFM_AUTH_TOKEN" ... # WRONG - shell expands and displays it

**ALL API calls MUST use inline `node -e` scripts** that read env vars internally via `process.env`. This prevents sensitive values from appearing in the bash command itself.

**Correct pattern for API calls:**
```bash
node -e '
const http = require("http");
const https = require("https");
const url = new URL(process.env.DFM_API_URL + "/api/v2/agent/dtf/SYMBOL/state");
const client = url.protocol === "https:" ? https : http;
const req = client.get(url, { headers: { "Authorization": "Bearer " + process.env.DFM_AUTH_TOKEN } }, (res) => {
  let data = "";
  res.on("data", (chunk) => data += chunk);
  res.on("end", () => console.log(data));
});
req.on("error", (e) => console.error("Error:", e.message));
'
Do NOT use
curl
for API calls.
Always use
node -e
with
process.env
references so tokens and keys are never visible in the command string.
IMPORTANT: No timeout on API calls. When running Bash commands that make API calls, always set
timeout: 600000
(10 minutes) or use
run_in_background: true
. The default Bash timeout is 2 minutes which is too short for on-chain operations like vault creation, rebalancing, and fee distribution. Never let an API call get killed by a timeout.
echo $DFM_AUTH_TOKEN export DFM_AUTH_TOKEN="eyJ..." # 错误 - 令牌在bash命令中可见 curl -H "Authorization: Bearer eyJ..." ... # 错误 - 令牌嵌入命令中 curl -H "Authorization: Bearer $DFM_AUTH_TOKEN" ... # 错误 - Shell会展开并显示令牌

**所有API调用必须使用内嵌的`node -e`脚本**,通过`process.env`内部读取环境变量。这样可防止敏感值出现在bash命令本身中。

**API调用的正确模式:**
```bash
node -e '
const http = require("http");
const https = require("https");
const url = new URL(process.env.DFM_API_URL + "/api/v2/agent/dtf/SYMBOL/state");
const client = url.protocol === "https:" ? https : http;
const req = client.get(url, { headers: { "Authorization": "Bearer " + process.env.DFM_AUTH_TOKEN } }, (res) => {
  let data = "";
  res.on("data", (chunk) => data += chunk);
  res.on("end", () => console.log(data));
});
req.on("error", (e) => console.error("Error:", e.message));
'
请勿使用
curl
进行API调用。
始终使用带有
process.env
引用的
node -e
,确保令牌和密钥永远不会在命令字符串中可见。
重要提示:API调用无超时限制。 运行发起API调用的Bash命令时,始终设置
timeout: 600000
(10分钟)或使用
run_in_background: true
。默认Bash超时为2分钟,对于金库创建、重新平衡和费用分配等链上操作来说太短。切勿让API调用因超时被终止。

Pre-Flight Auth Check (REQUIRED)

飞行前认证检查(必填)

You MUST complete this check before making any API call. Do not skip this step.
在进行任何API调用前必须完成此检查。请勿跳过此步骤。

Step 1: Ensure
.claude/settings.json
has env vars

步骤1:确保
.claude/settings.json
包含环境变量

Claude Code runs bash in a non-interactive subprocess that does NOT source
~/.zshrc
or
~/.bashrc
. Environment variables set via
export
in the user's terminal are NOT available to Claude Code's bash commands. The only reliable way to pass env vars to Claude Code is via
.claude/settings.json
.
On every pre-flight check, run this script to sync env vars from
~/.zshrc
into
.claude/settings.json
:
bash
node -e '
const fs = require("fs");
const path = require("path");
const os = require("os");

const settingsPath = path.join(process.cwd(), ".claude", "settings.json");
let settings = {};
try { settings = JSON.parse(fs.readFileSync(settingsPath, "utf8")); } catch {}
if (!settings.env) settings.env = {};

// Reject sentinel/placeholder values that should never be honoured.
const isInvalid = (v) => {
  if (!v || typeof v !== "string") return true;
  const t = v.trim();
  if (!t) return true;
  if (t === "+token+" || t === "<token>" || t.startsWith("+")) return true;
  if (t.startsWith("\"") || t.endsWith("\"")) return true; // stray quotes from bad templating
  return false;
};

let zshrc = "";
try { zshrc = fs.readFileSync(path.join(os.homedir(), ".zshrc"), "utf8"); } catch {}

const envVars = ["DFM_API_URL", "DFM_AUTH_TOKEN", "DFM_AGENT_KEYPAIR", "SOLANA_RPC_URL", "AGENT_WALLET_PATH"];
for (const v of envVars) {
  // 1) Keep settings.json value if already valid (it is updated in-process by refresh / launch scripts).
  if (!isInvalid(settings.env[v])) continue;

  // 2) Pull from ~/.zshrc. Use a global regex and take the LAST match — newest export wins
  //    when the file accumulates multiple `export VAR=...` lines.
  const re = new RegExp("export\\s+" + v + "=[\"\\047]?([^\"\\047\\n]+)[\"\\047]?", "g");
  let lastMatch = null, m;
  while ((m = re.exec(zshrc)) !== null) lastMatch = m;
  if (lastMatch && !isInvalid(lastMatch[1])) {
    settings.env[v] = lastMatch[1];
    continue;
  }

  // 3) Final fallback: current process env
  if (!isInvalid(process.env[v])) settings.env[v] = process.env[v];
  else delete settings.env[v]; // ensure no garbage placeholder lingers
}

fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));

for (const v of envVars) {
  console.log(v + "=" + (!isInvalid(settings.env[v]) ? "set" : "NOT SET"));
}
'
If
DFM_AUTH_TOKEN
is SET but any API call returns 401 (Unauthorized / token expired):
  1. Ask the user for their DFM-registered wallet address (only if you don't already know it).
  2. Run the token refresh script below with the wallet address. The script calls
    POST {DFM_API_URL}/api/v2/agent/token/refresh-by-wallet
    , writes the new JWT to
    .claude/settings.json
    , and replaces any existing
    export DFM_AUTH_TOKEN=
    line in
    ~/.zshrc
    (never appends — appending would accumulate stale tokens that the pre-flight may pick up first). The token value is never printed.
  3. After the script reports
    STATUS=success
    , retry the original operation in the same session —
    .claude/settings.json
    is read by Claude Code on the next bash invocation, so no restart is required.
DO NOT improvise the refresh. Earlier improvised attempts have written literal placeholder strings (e.g.
+token+
) into
settings.json
. Always use this exact script.
Write the script once to
.claude/refresh-token.js
, then run it with
node .claude/refresh-token.js <WALLET_ADDRESS>
:
javascript
const http = require("http");
const https = require("https");
const fs = require("fs");
const path = require("path");
const os = require("os");

const apiUrl = process.env.DFM_API_URL;
const walletAddress = process.argv[2];
if (!apiUrl) { console.log("ERROR: DFM_API_URL not set"); process.exit(1); }
if (!walletAddress) { console.log("ERROR: usage: node refresh-token.js <walletAddress>"); process.exit(1); }

const payload = JSON.stringify({ walletAddress });
const url = new URL(apiUrl + "/api/v2/agent/token/refresh-by-wallet");
const client = url.protocol === "https:" ? https : http;

const req = client.request(url, {
  method: "POST",
  headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) }
}, (res) => {
  let data = "";
  res.on("data", (c) => data += c);
  res.on("end", () => {
    try {
      const json = JSON.parse(data);
      // Backend response shape: { status, message, data: { token, expires, expiresPrettyPrint, expiresAt } }
      const token = json.data?.token || json.token;
      if (!token || typeof token !== "string" || token.startsWith("+")) {
        console.log("ERROR: No valid token in response: " + data);
        process.exit(1);
      }

      // (1) Update .claude/settings.json
      const sp = path.join(process.cwd(), ".claude", "settings.json");
      let s = {}; try { s = JSON.parse(fs.readFileSync(sp, "utf8")); } catch {}
      if (!s.env) s.env = {};
      s.env.DFM_AUTH_TOKEN = token;
      fs.mkdirSync(path.dirname(sp), { recursive: true });
      fs.writeFileSync(sp, JSON.stringify(s, null, 2));

      // (2) REPLACE any existing DFM_AUTH_TOKEN export in ~/.zshrc; never append duplicates.
      const zshrcPath = path.join(os.homedir(), ".zshrc");
      let zshrc = "";
      try { zshrc = fs.readFileSync(zshrcPath, "utf8"); } catch {}
      const newLine = "export DFM_AUTH_TOKEN=\"" + token + "\"";
      const lineRe = /^\s*export\s+DFM_AUTH_TOKEN=.*$/gm;
      if (lineRe.test(zshrc)) {
        zshrc = zshrc.replace(lineRe, newLine);
      } else {
        if (zshrc.length && !zshrc.endsWith("\n")) zshrc += "\n";
        zshrc += newLine + "\n";
      }
      fs.writeFileSync(zshrcPath, zshrc);

      // Output ONLY safe info — NEVER the token
      console.log("STATUS=success");
      console.log("DFM_AUTH_TOKEN=set");
    } catch (e) {
      console.log("ERROR: " + e.message);
      process.exit(1);
    }
  });
});
req.on("error", (e) => { console.log("ERROR: " + e.message); process.exit(1); });
req.write(payload);
req.end();
If
DFM_AUTH_TOKEN
is NOT SET after this script
, run the automated agent profile creation flow:
  1. Ask the user for their DFM-registered wallet address (the Solana public key they used to sign up on the DFM Dashboard):
    To set up your DFM Agent, I need the Solana wallet address you registered on the DFM Dashboard (https://qa.dfm.finance). Please paste your wallet public key.
  2. Ensure the agent wallet exists — the profile launch requires the agent's wallet public key.
    • If
      DFM_AGENT_KEYPAIR
      is already set, derive the public key from it (do NOT generate a new one).
    • If a keypair file exists at
      AGENT_WALLET_PATH
      (default
      ~/.dfm/agent-wallet.json
      ), load it and derive the public key.
    • Only if neither exists, generate a new keypair. See "Agent Wallet -- Keypair Generation" section below.
  3. Once the user provides the wallet address and the agent keypair exists, auto-generate the agent profile name and username:
    • Name: generate a creative agent name (e.g. "Alpha Sentinel", "Momentum Agent", "DeFi Navigator")
    • Username: generate a unique username from the name (e.g. "alpha_sentinel", "momentum_agent")
    • Don't worry about pre-checking uniqueness — the
      profile-launch.js
      script auto-retries with a random 4-char hex suffix on any
      409 "Username is already taken"
      from the backend (up to 5 attempts). Just pass a sensible base name/username; the script handles collisions silently.
  4. Create the profile AND save the token in a single script — the API call, token extraction, and env var writing must all happen inside one script so the token is NEVER visible in terminal output. The script also auto-retries on duplicate-username 409s by appending a random suffix to the username (and re-runs up to 5 times) so the agent never has to be re-prompted for a new name. Write a script file and execute it:
    Write a file called
    .claude/profile-launch.js
    with this content, then run it with
    node .claude/profile-launch.js
    :
    javascript
    const http = require("http");
    const https = require("https");
    const fs = require("fs");
    const path = require("path");
    const os = require("os");
    const crypto = require("crypto");
    const { Keypair } = require("@solana/web3.js");
    const bs58 = require("bs58").default || require("bs58");
    
    const apiUrl = process.env.DFM_API_URL;
    const walletAddress = process.argv[2];
    const baseName = process.argv[3];
    const baseUsername = process.argv[4];
    
    if (!apiUrl) { console.log("ERROR: DFM_API_URL not set"); process.exit(1); }
    if (!walletAddress || !baseName || !baseUsername) {
      console.log("ERROR: usage: node profile-launch.js <walletAddress> <name> <username>");
      process.exit(1);
    }
    
    // Derive agent wallet public key from DFM_AGENT_KEYPAIR
    const agentKeypair = Keypair.fromSecretKey(bs58.decode(process.env.DFM_AGENT_KEYPAIR));
    const agentWalletAddress = agentKeypair.publicKey.toBase58();
    
    const MAX_ATTEMPTS = 5;
    const UNAME_RX = /username/i; // disambiguates duplicate-username vs. wallet-already-has-agent
    
    // Sanitize base username to allowed charset (alphanumeric + underscore)
    const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
    const cleanBase = sanitize(baseUsername) || "agent";
    
    // Suffix generator: deterministic-looking but unique. 4 hex chars = 65k combos.
    const suffix = () => crypto.randomBytes(2).toString("hex");
    
    function attempt(usernameToTry, agentNameToTry, n) {
      const payload = JSON.stringify({
        userPublicKey: walletAddress,
        agentWalletAddress: agentWalletAddress,
        name: agentNameToTry,
        username: usernameToTry,
        metadata: [{ key: "created_by", value: "dfm-agent-skill" }]
      });
    
      const url = new URL(apiUrl + "/api/v2/agent/profile-launch");
      const client = url.protocol === "https:" ? https : http;
    
      const req = client.request(url, {
        method: "POST",
        headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) }
      }, (res) => {
        let data = "";
        res.on("data", (chunk) => data += chunk);
        res.on("end", () => {
          let json = null;
          try { json = JSON.parse(data); } catch { /* fall through */ }
          const message = json?.message || json?.error || data;
    
          // 409 + duplicate-username -> regenerate username, retry. Do NOT retry on
          // "agent profile already exists for this wallet" — that is unrecoverable here.
          if (res.statusCode === 409 && UNAME_RX.test(String(message)) && !/wallet/i.test(String(message))) {
            if (n >= MAX_ATTEMPTS) {
              console.log("ERROR: username conflicts after " + MAX_ATTEMPTS + " attempts. Last tried: " + usernameToTry);
              process.exit(1);
            }
            const nextUsername = cleanBase + "_" + suffix();
            const nextName = baseName; // keep display name; only username needs to be unique
            console.log("RETRY=username_taken attempt=" + (n + 1) + " next_username=" + nextUsername);
            return attempt(nextUsername, nextName, n + 1);
          }
    
          if (res.statusCode === 409) {
            console.log("ERROR: 409 Conflict (not username-related): " + message);
            process.exit(1);
          }
    
          if (res.statusCode < 200 || res.statusCode >= 300) {
            console.log("ERROR: HTTP " + res.statusCode + ": " + message);
            process.exit(1);
          }
    
          const token = json?.data?.token?.token;
          if (!token) { console.log("ERROR: No token in response. Response: " + data); process.exit(1); }
    
          // Write to .claude/settings.json
          const sp = path.join(process.cwd(), ".claude", "settings.json");
          let s = {}; try { s = JSON.parse(fs.readFileSync(sp, "utf8")); } catch {}
          if (!s.env) s.env = {};
          s.env.DFM_AUTH_TOKEN = token;
          fs.writeFileSync(sp, JSON.stringify(s, null, 2));
    
          // REPLACE any existing DFM_AUTH_TOKEN export in ~/.zshrc; never append duplicates.
          const zshrcPath = path.join(os.homedir(), ".zshrc");
          let zshrc = "";
          try { zshrc = fs.readFileSync(zshrcPath, "utf8"); } catch {}
          const newLine = "export DFM_AUTH_TOKEN=\"" + token + "\"";
          const lineRe = /^\s*export\s+DFM_AUTH_TOKEN=.*$/gm;
          if (lineRe.test(zshrc)) {
            zshrc = zshrc.replace(lineRe, newLine);
          } else {
            if (zshrc.length && !zshrc.endsWith("\n")) zshrc += "\n";
            zshrc += newLine + "\n";
          }
          fs.writeFileSync(zshrcPath, zshrc);
    
          // Only output safe info — NEVER the token
          const profileName = json.data?.agentProfile?.name || agentNameToTry;
          const profileUsername = json.data?.agentProfile?.username || usernameToTry;
          console.log("STATUS=success");
          console.log("AGENT_NAME=" + profileName);
          console.log("AGENT_USERNAME=" + profileUsername);
          console.log("AGENT_WALLET=" + agentWalletAddress);
          console.log("ATTEMPTS=" + n);
          console.log("DFM_AUTH_TOKEN=set");
        });
      });
      req.on("error", (e) => { console.log("ERROR: " + e.message); process.exit(1); });
      req.write(payload);
      req.end();
    }
    
    attempt(cleanBase, baseName, 1);
    Run it as:
    node .claude/profile-launch.js <WALLET_ADDRESS> "<AGENT_NAME>" "<AGENT_USERNAME>"
    The script derives
    agentWalletAddress
    from
    DFM_AGENT_KEYPAIR
    automatically and includes it in the payload. It outputs ONLY
    STATUS=success
    ,
    AGENT_NAME=...
    ,
    AGENT_USERNAME=...
    ,
    AGENT_WALLET=...
    ,
    ATTEMPTS=<n>
    , and
    DFM_AUTH_TOKEN=set
    . The actual token value is written directly to
    .claude/settings.json
    and
    ~/.zshrc
    — it NEVER appears in terminal output.
    Username conflict retry behavior:
    • On HTTP
      409
      whose message references "username" (e.g.
      "Username is already taken"
      ), the script appends a 4-hex-char suffix to the sanitized base username and re-issues
      POST /profile-launch
      . Up to 5 attempts.
    • During retries the script logs only
      RETRY=username_taken attempt=<n> next_username=<new>
      so the agent (and the human watching) can see progress without leaking secrets.
    • The display
      name
      is preserved across retries — only
      username
      changes.
    • On HTTP
      409
      whose message references "wallet" (e.g.
      "An agent profile already exists for this wallet address"
      ), the script does NOT retry — that's an unrecoverable state for this flow. The agent should call
      /token/refresh-by-wallet
      instead.
    • If all 5 attempts collide, the script exits with a clear
      ERROR:
      line and the agent must surface the conflict to the user.
  5. Tell the user: "Agent profile created! Restart your AI agent to pick up the auth token, then you're ready to go."
If
DFM_AGENT_KEYPAIR
is NOT SET
and the operation requires signing (launch-dtf, distribute-fees), auto-generate a wallet (see "Agent Wallet -- Keypair Generation" section below). Do not ask — just generate it.
Claude Code在非交互式子进程中运行bash,不会读取
~/.zshrc
~/.bashrc
。用户终端中通过
export
设置的环境变量对Claude Code的bash命令不可用。将环境变量传递给Claude Code的唯一可靠方式是通过
.claude/settings.json
每次飞行前检查,运行此脚本将环境变量从
~/.zshrc
同步到
.claude/settings.json
bash
node -e '
const fs = require("fs");
const path = require("path");
const os = require("os");

const settingsPath = path.join(process.cwd(), ".claude", "settings.json");
let settings = {};
try { settings = JSON.parse(fs.readFileSync(settingsPath, "utf8")); } catch {}
if (!settings.env) settings.env = {};

// 拒绝不应被使用的标记/占位值。
const isInvalid = (v) => {
  if (!v || typeof v !== "string") return true;
  const t = v.trim();
  if (!t) return true;
  if (t === "+token+" || t === "<token>" || t.startsWith("+")) return true;
  if (t.startsWith("\"") || t.endsWith("\"")) return true; // 错误模板导致的多余引号
  return false;
};

let zshrc = "";
try { zshrc = fs.readFileSync(path.join(os.homedir(), ".zshrc"), "utf8"); } catch {}

const envVars = ["DFM_API_URL", "DFM_AUTH_TOKEN", "DFM_AGENT_KEYPAIR", "SOLANA_RPC_URL", "AGENT_WALLET_PATH"];
for (const v of envVars) {
  // 1) 如果settings.json中的值已有效则保留(它会被刷新/启动脚本在进程中更新)。
  if (!isInvalid(settings.env[v])) continue;

  // 2) 从~/.zshrc中提取。使用全局正则表达式并取最后一个匹配项——当文件积累多个`export VAR=...`行时,最新的导出生效。
  const re = new RegExp("export\\s+" + v + "=[\"\\047]?([^\"\\047\\n]+)[\"\\047]?", "g");
  let lastMatch = null, m;
  while ((m = re.exec(zshrc)) !== null) lastMatch = m;
  if (lastMatch && !isInvalid(lastMatch[1])) {
    settings.env[v] = lastMatch[1];
    continue;
  }

  // 3) 最终回退:当前进程环境
  if (!isInvalid(process.env[v])) settings.env[v] = process.env[v];
  else delete settings.env[v]; // 确保没有无效占位符残留
}

fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));

for (const v of envVars) {
  console.log(v + "=" + (!isInvalid(settings.env[v]) ? "set" : "NOT SET"));
}
'
如果
DFM_AUTH_TOKEN
已设置但任何API调用返回401(未授权/令牌过期):
  1. 向用户索要其DFM注册钱包地址(如果尚未知晓)。
  2. 使用钱包地址运行下方的令牌刷新脚本。该脚本调用
    POST {DFM_API_URL}/api/v2/agent/token/refresh-by-wallet
    ,将新的JWT写入
    .claude/settings.json
    ,并替换
    ~/.zshrc
    中任何现有的
    export DFM_AUTH_TOKEN=
    行(绝不追加——追加会积累过时令牌,飞行前检查可能会先拾取这些令牌)。令牌值永远不会被打印
  3. 脚本报告
    STATUS=success
    后,在同一会话中重试原操作——Claude Code会在下次bash调用时读取
    .claude/settings.json
    ,因此无需重启。
请勿自行编写刷新逻辑。 之前自行编写的尝试曾将字面占位符字符串(例如
+token+
)写入
settings.json
。请始终使用此精确脚本。
将脚本写入
.claude/refresh-token.js
,然后使用
node .claude/refresh-token.js <WALLET_ADDRESS>
运行:
javascript
const http = require("http");
const https = require("https");
const fs = require("fs");
const path = require("path");
const os = require("os");

const apiUrl = process.env.DFM_API_URL;
const walletAddress = process.argv[2];
if (!apiUrl) { console.log("ERROR: DFM_API_URL not set"); process.exit(1); }
if (!walletAddress) { console.log("ERROR: usage: node refresh-token.js <walletAddress>"); process.exit(1); }

const payload = JSON.stringify({ walletAddress });
const url = new URL(apiUrl + "/api/v2/agent/token/refresh-by-wallet");
const client = url.protocol === "https:" ? https : http;

const req = client.request(url, {
  method: "POST",
  headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) }
}, (res) => {
  let data = "";
  res.on("data", (c) => data += c);
  res.on("end", () => {
    try {
      const json = JSON.parse(data);
      // 后端响应格式:{ status, message, data: { token, expires, expiresPrettyPrint, expiresAt } }
      const token = json.data?.token || json.token;
      if (!token || typeof token !== "string" || token.startsWith("+")) {
        console.log("ERROR: No valid token in response: " + data);
        process.exit(1);
      }

      // (1) 更新.claude/settings.json
      const sp = path.join(process.cwd(), ".claude", "settings.json");
      let s = {}; try { s = JSON.parse(fs.readFileSync(sp, "utf8")); } catch {}
      if (!s.env) s.env = {};
      s.env.DFM_AUTH_TOKEN = token;
      fs.mkdirSync(path.dirname(sp), { recursive: true });
      fs.writeFileSync(sp, JSON.stringify(s, null, 2));

      // (2) 替换~/.zshrc中任何现有的DFM_AUTH_TOKEN导出;绝不追加重复项。
      const zshrcPath = path.join(os.homedir(), ".zshrc");
      let zshrc = "";
      try { zshrc = fs.readFileSync(zshrcPath, "utf8"); } catch {}
      const newLine = "export DFM_AUTH_TOKEN=\"" + token + "\"";
      const lineRe = /^\s*export\s+DFM_AUTH_TOKEN=.*$/gm;
      if (lineRe.test(zshrc)) {
        zshrc = zshrc.replace(lineRe, newLine);
      } else {
        if (zshrc.length && !zshrc.endsWith("\n")) zshrc += "\n";
        zshrc += newLine + "\n";
      }
      fs.writeFileSync(zshrcPath, zshrc);

      // 仅输出安全信息——绝不输出令牌
      console.log("STATUS=success");
      console.log("DFM_AUTH_TOKEN=set");
    } catch (e) {
      console.log("ERROR: " + e.message);
      process.exit(1);
    }
  });
});
req.on("error", (e) => { console.log("ERROR: " + e.message); process.exit(1); });
req.write(payload);
req.end();
如果运行此脚本后
DFM_AUTH_TOKEN
仍未设置
,运行自动化代理配置文件创建流程:
  1. 向用户索要其DFM注册钱包地址(他们在DFM仪表板上注册时使用的Solana公钥):
    要设置您的DFM Agent,我需要您在DFM仪表板(https://qa.dfm.finance)上注册的**Solana钱包地址**。 请粘贴您的钱包公钥。
  2. 确保代理钱包存在——配置文件启动需要代理的钱包公钥。
    • 如果
      DFM_AGENT_KEYPAIR
      已设置,从中派生公钥(请勿生成新的)。
    • 如果
      AGENT_WALLET_PATH
      (默认
      ~/.dfm/agent-wallet.json
      )存在密钥对文件,加载并派生公钥。
    • 仅当两者都不存在时,生成新的密钥对。请参阅下方的“代理钱包——密钥对生成”部分。
  3. 用户提供钱包地址且代理密钥对存在后,自动生成代理配置文件名称和用户名:
    • 名称:生成有创意的代理名称(例如"Alpha Sentinel"、"Momentum Agent"、"DeFi Navigator")
    • 用户名:从名称生成唯一用户名(例如"alpha_sentinel"、"momentum_agent")
    • 无需预先检查唯一性——当后端返回
      409 "Username is already taken"
      时,
      profile-launch.js
      脚本会自动重试,在用户名后追加随机4位十六进制后缀(最多5次尝试)。只需传递合理的基础名称/用户名,脚本会静默处理冲突。
  4. 在单个脚本中创建配置文件并保存令牌——API调用、令牌提取和环境变量写入必须在一个脚本内完成,确保令牌永远不会在终端输出中可见。该脚本还会在用户名重复的409错误时自动重试,在用户名后追加随机后缀(最多重试5次),因此无需重新提示用户输入新名称。编写脚本文件并执行:
    创建名为
    .claude/profile-launch.js
    的文件,内容如下,然后使用
    node .claude/profile-launch.js
    运行:
    javascript
    const http = require("http");
    const https = require("https");
    const fs = require("fs");
    const path = require("path");
    const os = require("os");
    const crypto = require("crypto");
    const { Keypair } = require("@solana/web3.js");
    const bs58 = require("bs58").default || require("bs58");
    
    const apiUrl = process.env.DFM_API_URL;
    const walletAddress = process.argv[2];
    const baseName = process.argv[3];
    const baseUsername = process.argv[4];
    
    if (!apiUrl) { console.log("ERROR: DFM_API_URL not set"); process.exit(1); }
    if (!walletAddress || !baseName || !baseUsername) {
      console.log("ERROR: usage: node profile-launch.js <walletAddress> <name> <username>");
      process.exit(1);
    }
    
    // 从DFM_AGENT_KEYPAIR派生代理钱包公钥
    const agentKeypair = Keypair.fromSecretKey(bs58.decode(process.env.DFM_AGENT_KEYPAIR));
    const agentWalletAddress = agentKeypair.publicKey.toBase58();
    
    const MAX_ATTEMPTS = 5;
    const UNAME_RX = /username/i; // 区分用户名重复与钱包已有代理的情况
    
    // 将基础用户名清理为允许的字符集(字母数字+下划线)
    const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
    const cleanBase = sanitize(baseUsername) || "agent";
    
    // 后缀生成器:看似确定但唯一。4位十六进制字符 = 65k组合。
    const suffix = () => crypto.randomBytes(2).toString("hex");
    
    function attempt(usernameToTry, agentNameToTry, n) {
      const payload = JSON.stringify({
        userPublicKey: walletAddress,
        agentWalletAddress: agentWalletAddress,
        name: agentNameToTry,
        username: usernameToTry,
        metadata: [{ key: "created_by", value: "dfm-agent-skill" }]
      });
    
      const url = new URL(apiUrl + "/api/v2/agent/profile-launch");
      const client = url.protocol === "https:" ? https : http;
    
      const req = client.request(url, {
        method: "POST",
        headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) }
      }, (res) => {
        let data = "";
        res.on("data", (chunk) => data += chunk);
        res.on("end", () => {
          let json = null;
          try { json = JSON.parse(data); } catch { /* 继续执行 */ }
          const message = json?.message || json?.error || data;
    
          // 409 + 用户名重复 -> 重新生成用户名,重试。请勿在"此钱包已存在代理配置文件"时重试——此流程下无法恢复。
          if (res.statusCode === 409 && UNAME_RX.test(String(message)) && !/wallet/i.test(String(message))) {
            if (n >= MAX_ATTEMPTS) {
              console.log("ERROR: username conflicts after " + MAX_ATTEMPTS + " attempts. Last tried: " + usernameToTry);
              process.exit(1);
            }
            const nextUsername = cleanBase + "_" + suffix();
            const nextName = baseName; // 保留显示名称;仅用户名需要唯一
            console.log("RETRY=username_taken attempt=" + (n + 1) + " next_username=" + nextUsername);
            return attempt(nextUsername, nextName, n + 1);
          }
    
          if (res.statusCode === 409) {
            console.log("ERROR: 409 Conflict (not username-related): " + message);
            process.exit(1);
          }
    
          if (res.statusCode < 200 || res.statusCode >= 300) {
            console.log("ERROR: HTTP " + res.statusCode + ": " + message);
            process.exit(1);
          }
    
          const token = json?.data?.token?.token;
          if (!token) { console.log("ERROR: No token in response. Response: " + data); process.exit(1); }
    
          // 写入.claude/settings.json
          const sp = path.join(process.cwd(), ".claude", "settings.json");
          let s = {}; try { s = JSON.parse(fs.readFileSync(sp, "utf8")); } catch {}
          if (!s.env) s.env = {};
          s.env.DFM_AUTH_TOKEN = token;
          fs.writeFileSync(sp, JSON.stringify(s, null, 2));
    
          // 替换~/.zshrc中任何现有的DFM_AUTH_TOKEN导出;绝不追加重复项。
          const zshrcPath = path.join(os.homedir(), ".zshrc");
          let zshrc = "";
          try { zshrc = fs.readFileSync(zshrcPath, "utf8"); } catch {}
          const newLine = "export DFM_AUTH_TOKEN=\"" + token + "\"";
          const lineRe = /^\s*export\s+DFM_AUTH_TOKEN=.*$/gm;
          if (lineRe.test(zshrc)) {
            zshrc = zshrc.replace(lineRe, newLine);
          } else {
            if (zshrc.length && !zshrc.endsWith("\n")) zshrc += "\n";
            zshrc += newLine + "\n";
          }
          fs.writeFileSync(zshrcPath, zshrc);
    
          // 仅输出安全信息——绝不输出令牌
          const profileName = json.data?.agentProfile?.name || agentNameToTry;
          const profileUsername = json.data?.agentProfile?.username || usernameToTry;
          console.log("STATUS=success");
          console.log("AGENT_NAME=" + profileName);
          console.log("AGENT_USERNAME=" + profileUsername);
          console.log("AGENT_WALLET=" + agentWalletAddress);
          console.log("ATTEMPTS=" + n);
          console.log("DFM_AUTH_TOKEN=set");
        });
      });
      req.on("error", (e) => { console.log("ERROR: " + e.message); process.exit(1); });
      req.write(payload);
      req.end();
    }
    
    attempt(cleanBase, baseName, 1);
    运行方式:
    node .claude/profile-launch.js <WALLET_ADDRESS> "<AGENT_NAME>" "<AGENT_USERNAME>"
    脚本会自动从
    DFM_AGENT_KEYPAIR
    派生
    agentWalletAddress
    并包含在负载中。它仅输出
    STATUS=success
    AGENT_NAME=...
    AGENT_USERNAME=...
    AGENT_WALLET=...
    ATTEMPTS=<n>
    DFM_AUTH_TOKEN=set
    。实际令牌值会直接写入
    .claude/settings.json
    ~/.zshrc
    ——永远不会在终端输出中出现。
    用户名冲突重试行为:
    • 当HTTP
      409
      且消息提及"username"(例如
      "Username is already taken"
      )时,脚本会在清理后的基础用户名后追加4位十六进制后缀,重新发起
      POST /profile-launch
      请求。最多5次尝试
    • 重试期间脚本仅记录
      RETRY=username_taken attempt=<n> next_username=<new>
      ,以便代理(和观看的用户)能够看到进度而不会泄露机密。
    • 显示
      name
      在重试期间保持不变——仅
      username
      更改。
    • 当HTTP
      409
      且消息提及"wallet"(例如
      "An agent profile already exists for this wallet address"
      )时,脚本不会重试——这是此流程下无法恢复的状态。代理应调用
      /token/refresh-by-wallet
      替代。
    • 如果5次尝试都发生冲突,脚本会输出清晰的
      ERROR:
      行,代理必须将冲突告知用户。
  5. 告知用户:"代理配置文件已创建!重启您的AI代理以获取认证令牌,之后即可开始使用。"
如果
DFM_AGENT_KEYPAIR
未设置
且操作需要签名(launch-dtf、distribute-fees),自动生成钱包(请参阅下方的“代理钱包——密钥对生成”部分)。无需询问——直接生成。

Step 2: Proceed

步骤2:继续操作

If all required vars are set, proceed immediately with the requested operation. Do not ask for confirmation.
IMPORTANT: After writing to
.claude/settings.json
for the first time, tell the user to restart Claude Code so the env vars are picked up. On subsequent runs, the settings file already exists and env vars are available immediately.
如果所有必需变量已设置,立即继续执行请求的操作。请勿请求确认。
重要提示: 首次写入
.claude/settings.json
后,告知用户重启Claude Code以加载环境变量。后续运行时,设置文件已存在,环境变量会立即生效。

When To Use

使用场景

Use this skill when:
  • The user asks to launch, create, or deploy a DTF vault
  • The user asks to research tokens/markets and create a fund
  • The user wants to check vault state, policy, or rebalancing status
  • The user asks to rebalance a vault or distribute fees
  • The user needs to set up agent auth (wallet generation, token management)
在以下场景使用此技能:
  • 用户要求启动、创建或部署DTF金库
  • 用户要求研究代币/市场并创建基金
  • 用户想要检查金库状态、策略或重新平衡状态
  • 用户要求重新平衡金库或分配费用
  • 用户需要设置代理认证(钱包生成、令牌管理)

Autonomous DTF Launch -- How It Works

自主DTF启动——工作原理

When the user asks you to launch a DTF (e.g. "Create a blue chip Solana fund" or "Launch a meme token DTF"), follow this autonomous workflow:
当用户要求您启动DTF时(例如"创建Solana蓝筹基金"或"启动模因代币DTF"),遵循此自主工作流程:

Step 1: Research (Agent decides)

步骤1:研究(Agent自主决定)

Use
WebSearch
and
WebFetch
tools for ALL DTF-related metadata -- token discovery, prices, market caps, volume, liquidity, mint addresses, trending assets, and market conditions. No exceptions.
Use
WebSearch
to find:
  • Top performing Solana tokens by market cap, volume, and price action
  • Current market conditions, trends, and sentiment
  • Token liquidity and 24h trading volume data
  • For yield DTFs: Solana LSTs (liquid staking tokens) and yield-bearing tokens — mSOL, jitoSOL, bSOL, INF, hSOL, stSOL, and their current APYs, TVLs, and staking yields
Use
WebFetch
to pull data from:
  • Token data aggregators (CoinGecko, CoinMarketCap, Jupiter aggregator, Birdeye, DexScreener)
  • Solana token lists and verified registries for mint addresses
  • For yield DTFs: Staking yield aggregators, LST protocol sites (Marinade, Jito, BlazeStake, Sanctum), and DeFi yield dashboards
Then decide:
  • Identify candidate tokens based on the user's intent or strategy
  • Check token liquidity, 24h volume, market cap
  • For yield DTFs: Prioritize LSTs and yield-bearing assets with highest APY and deepest liquidity
  • Select the best tokens and determine allocations
  • Automatically discover each token's Solana
    mintAddress
    from reliable references (official docs, verified token lists, explorers, major data providers)
  • Cross-check mint addresses across multiple references before including them in
    underlyingAssets
  • Reject unverified, conflicting, or low-confidence mint mappings and replace them with verified assets
使用
WebSearch
WebFetch
工具获取所有DTF相关元数据——代币发现、价格、市值、交易量、流动性、铸币地址、热门资产和市场状况。无例外。
使用
WebSearch
查找:
  • 按市值、交易量和价格表现排名的顶级Solana代币
  • 当前市场状况、趋势和情绪
  • 代币流动性和24小时交易量数据
  • 对于收益型DTF:Solana LST(流动性质押代币)和生息代币——mSOL、jitoSOL、bSOL、INF、hSOL、stSOL,以及它们当前的APY、TVL和质押收益
使用
WebFetch
从以下来源拉取数据:
  • 代币数据聚合器(CoinGecko、CoinMarketCap、Jupiter聚合器、Birdeye、DexScreener)
  • Solana代币列表和已验证注册表以获取铸币地址
  • 对于收益型DTF:质押收益聚合器、LST协议站点(Marinade、Jito、BlazeStake、Sanctum)和DeFi收益仪表板
然后决定:
  • 根据用户意图或策略确定候选代币
  • 检查代币流动性、24小时交易量、市值
  • 对于收益型DTF:优先选择APY最高、流动性最深的LST和生息资产
  • 选择最佳代币并确定配置比例
  • 从可靠来源(官方文档、已验证代币列表、浏览器、主要数据提供商)自动发现每个代币的Solana
    mintAddress
  • 在将代币纳入
    underlyingAssets
    前,跨多个来源交叉验证铸币地址
  • 拒绝未验证、冲突或低置信度的铸币映射,替换为已验证资产

Step 2: Design (Agent decides)

步骤2:设计(Agent自主决定)

Based on your research, autonomously decide:
  • Vault type --
    "DTF"
    for standard diversified token funds,
    "YIELD_DTF"
    for yield-bearing / LST-focused funds. Set in the
    dtf-create
    payload.
  • Vault name -- descriptive, catchy, relevant to the strategy
  • Vault symbol -- short (max 10 chars), unique, memorable
  • Underlying assets -- pass asset
    symbol
    or
    name
    (preferred) with allocation in basis points (must sum to 10000). Backend resolves
    mintAddress
    from
    asset-allocation
    .
    • For DTF: standard tokens (SOL, JUP, Bonk, RAY, etc.)
    • For YIELD_DTF: LSTs and yield-bearing tokens (mSOL, jitoSOL, bSOL, INF, etc.)
  • Management fees -- in basis points (e.g. 200 = 2%)
  • Policy configuration -- asset limits, rebalance frequency, stablecoin minimums, etc.
  • Tags -- searchable categories (include "Yield", "LST", "Staking" for yield DTFs)
  • Description -- strategy summary
  • Launch media fields -- for DTF launch payloads, set
    logoUrl
    ,
    bannerUrl
    , and
    metadataUri
    to empty strings.
基于研究结果,自主决定:
  • 金库类型——标准多元化代币基金使用
    "DTF"
    ,收益型/LST聚焦基金使用
    "YIELD_DTF"
    。在
    dtf-create
    负载中设置。
  • 金库名称——描述性、吸引人、与策略相关
  • 金库符号——简短(最多10个字符)、唯一、易记
  • 底层资产——传递资产
    symbol
    name
    (首选)以及基点形式的配置比例(总和必须为10000)。后端从
    asset-allocation
    解析
    mintAddress
    • DTF:标准代币(SOL、JUP、Bonk、RAY等)
    • YIELD_DTF:LST和生息代币(mSOL、jitoSOL、bSOL、INF等)
  • 管理费——基点形式(例如200 = 2%)
  • 策略配置——资产限制、重新平衡频率、稳定币最低要求等
  • 标签——可搜索的类别(收益型DTF需包含"Yield"、"LST"、"Staking")
  • 描述——策略摘要
  • 启动媒体字段——对于DTF启动负载,将
    logoUrl
    bannerUrl
    metadataUri
    设置为空字符串。

Step 3: Deploy (Two-step flow)

步骤3:部署(两步流程)

3a. Build the unsigned transaction

3a. 构建未签名交易

Send
POST {DFM_API_URL}/api/v2/agent/launch-dtf
with the vault creation payload. The backend builds and returns an unsigned transaction.
json
{
  "signerPublicKey": "<public key derived from DFM_AGENT_KEYPAIR>",
  "vaultName": "Solana Blue Chips",
  "vaultSymbol": "SOLBC",
  "underlyingAssets": [
    { "symbol": "SOL", "mintBps": 4000 },
    { "symbol": "JUP", "mintBps": 3000 },
    { "name": "Bonk", "mintBps": 3000 }
  ],
  "managementFees": 200,
  "category": 0,
  "threshold": 500
}
The response contains:
json
{
  "onChain": {
    "transaction": "base64-encoded-unsigned-versioned-transaction...",
    "vaultIndex": 42,
    "vaultPda": "7Xk...def",
    "vaultMintPda": "9Rm...ghi"
  }
}
发送
POST {DFM_API_URL}/api/v2/agent/launch-dtf
请求,携带金库创建负载。后端构建并返回未签名交易。
json
{
  "signerPublicKey": "<从DFM_AGENT_KEYPAIR派生的公钥>",
  "vaultName": "Solana Blue Chips",
  "vaultSymbol": "SOLBC",
  "underlyingAssets": [
    { "symbol": "SOL", "mintBps": 4000 },
    { "symbol": "JUP", "mintBps": 3000 },
    { "name": "Bonk", "mintBps": 3000 }
  ],
  "managementFees": 200,
  "category": 0,
  "threshold": 500
}
响应包含:
json
{
  "onChain": {
    "transaction": "base64-encoded-unsigned-versioned-transaction...",
    "vaultIndex": 42,
    "vaultPda": "7Xk...def",
    "vaultMintPda": "9Rm...ghi"
  }
}

3b. Sign and submit the transaction on-chain

3b. 签名并提交交易到链上

Use the agent's local keypair to sign the returned transaction and submit it to Solana:
typescript
import { Keypair, VersionedTransaction, Connection } from "@solana/web3.js";
const bs58 = require("bs58").default || require("bs58");

const keypair = Keypair.fromSecretKey(bs58.decode(process.env.DFM_AGENT_KEYPAIR!));
const connection = new Connection(process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com");

// Deserialize the unsigned transaction from the API response
const txBytes = Buffer.from(response.onChain.transaction, "base64");
const tx = VersionedTransaction.deserialize(txBytes);

// Sign with the agent's keypair
tx.sign([keypair]);

// Submit on-chain
const signature = await connection.sendRawTransaction(tx.serialize(), {
  skipPreflight: false,
  preflightCommitment: "confirmed",
});

await connection.confirmTransaction(signature, "confirmed");
使用代理本地密钥对签名返回的交易并提交到Solana:
typescript
import { Keypair, VersionedTransaction, Connection } from "@solana/web3.js";
const bs58 = require("bs58").default || require("bs58");

const keypair = Keypair.fromSecretKey(bs58.decode(process.env.DFM_AGENT_KEYPAIR!));
const connection = new Connection(process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com");

// 从API响应反序列化未签名交易
const txBytes = Buffer.from(response.onChain.transaction, "base64");
const tx = VersionedTransaction.deserialize(txBytes);

// 使用代理密钥对签名
tx.sign([keypair]);

// 提交到链上
const signature = await connection.sendRawTransaction(tx.serialize(), {
  skipPreflight: false,
  preflightCommitment: "confirmed",
});

await connection.confirmTransaction(signature, "confirmed");

3c. Create policy and persist to DB

3c. 创建策略并持久化到数据库

After the on-chain transaction confirms, send
POST {DFM_API_URL}/api/v2/agent/dtf-create
with the transaction signature and policy configuration:
You MUST include ALL policy fields in every
dtf-create
payload.
The agent decides values based on the vault strategy. Missing fields default to 0/disabled which means no policy guardrails.
json
{
  "transactionSignature": "<signature from step 3b>",
  "vaultName": "Solana Blue Chips",
  "vaultSymbol": "SOLBC",
  "vaultType": "DTF",
  "description": "Top-tier Solana ecosystem tokens",
  "tags": ["Blue Chip", "Solana", "DeFi"],
  "logoUrl": "",
  "bannerUrl": "",
  "asset_mode": "OPEN",
  "asset_whitelist": [],
  "asset_blacklist": [],
  "min_amm_liquidity_usd": 100000,
  "min_24h_volume_usd": 500000,
  "min_assets": 3,
  "max_assets": 12,
  "max_asset_pct": 4000,
  "min_asset_pct": 500,
  "min_stablecoin_pct": 0,
  "max_rebalance_pct": 2500,
  "min_rebalance_interval_hours": 4,
  "max_rebalances_per_day": 3,
  "max_rebalances_per_week": 14,
  "launch_blackout_hours": 24,
  "fee_locked": true,
  "notes": "Auto-generated policy for blue chip strategy"
}
Example: WHITELIST mode (for curated LST yield fund):
json
{
  "transactionSignature": "<signature>",
  "vaultName": "Solana LST Yield",
  "vaultSymbol": "SLSTY",
  "vaultType": "YIELD_DTF",
  "description": "Diversified Solana liquid staking token yield fund",
  "tags": ["Yield", "LST", "Staking", "Solana"],
  "logoUrl": "",
  "bannerUrl": "",
  "asset_mode": "WHITELIST_ONLY",
  "asset_whitelist": ["mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn", "bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1"],
  "asset_blacklist": [],
  "min_amm_liquidity_usd": 500000,
  "min_24h_volume_usd": 500000,
  "min_assets": 2,
  "max_assets": 8,
  "max_asset_pct": 5000,
  "min_asset_pct": 1000,
  "min_stablecoin_pct": 0,
  "max_rebalance_pct": 2000,
  "min_rebalance_interval_hours": 12,
  "max_rebalances_per_day": 1,
  "max_rebalances_per_week": 5,
  "launch_blackout_hours": 24,
  "fee_locked": true,
  "notes": "Whitelisted LST-only yield fund — only approved liquid staking tokens allowed"
}
链上交易确认后,发送
POST {DFM_API_URL}/api/v2/agent/dtf-create
请求,携带交易签名和策略配置:
每个
dtf-create
负载必须包含所有策略字段。
Agent根据金库策略决定值。缺失字段默认设为0/禁用,意味着无策略防护机制。
json
{
  "transactionSignature": "<步骤3b中的签名>",
  "vaultName": "Solana Blue Chips",
  "vaultSymbol": "SOLBC",
  "vaultType": "DTF",
  "description": "顶级Solana生态系统代币",
  "tags": ["Blue Chip", "Solana", "DeFi"],
  "logoUrl": "",
  "bannerUrl": "",
  "asset_mode": "OPEN",
  "asset_whitelist": [],
  "asset_blacklist": [],
  "min_amm_liquidity_usd": 100000,
  "min_24h_volume_usd": 500000,
  "min_assets": 3,
  "max_assets": 12,
  "max_asset_pct": 4000,
  "min_asset_pct": 500,
  "min_stablecoin_pct": 0,
  "max_rebalance_pct": 2500,
  "min_rebalance_interval_hours": 4,
  "max_rebalances_per_day": 3,
  "max_rebalances_per_week": 14,
  "launch_blackout_hours": 24,
  "fee_locked": true,
  "notes": "蓝筹策略自动生成的策略"
}
示例:白名单模式(用于精选LST收益基金):
json
{
  "transactionSignature": "<签名>",
  "vaultName": "Solana LST Yield",
  "vaultSymbol": "SLSTY",
  "vaultType": "YIELD_DTF",
  "description": "多元化Solana流动性质押代币收益基金",
  "tags": ["Yield", "LST", "Staking", "Solana"],
  "logoUrl": "",
  "bannerUrl": "",
  "asset_mode": "WHITELIST_ONLY",
  "asset_whitelist": ["mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn", "bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1"],
  "asset_blacklist": [],
  "min_amm_liquidity_usd": 500000,
  "min_24h_volume_usd": 500000,
  "min_assets": 2,
  "max_assets": 8,
  "max_asset_pct": 5000,
  "min_asset_pct": 1000,
  "min_stablecoin_pct": 0,
  "max_rebalance_pct": 2000,
  "min_rebalance_interval_hours": 12,
  "max_rebalances_per_day": 1,
  "max_rebalances_per_week": 5,
  "launch_blackout_hours": 24,
  "fee_locked": true,
  "notes": "白名单LST专属收益基金——仅允许已批准的流动性质押代币"
}

Policy field decision guide

策略字段决策指南

The agent MUST decide ALL policy values based on the vault strategy:
Strategy Type
max_asset_pct
min_asset_pct
min_amm_liquidity_usd
min_24h_volume_usd
max_rebalances_per_day
min_rebalance_interval_hours
Conservative (blue chip, index)3000-4000500-1000500000100000026
Moderate (mixed, ecosystem)4000-500050010000050000034
Aggressive (meme, trending)5000-60003005000010000042
Yield (LSTs, staking, yield)4000-5000500-1000500000500000112
Always set:
  • asset_mode
    : choose based on the vault strategy:
    • "OPEN"
      — any asset can be added. No restrictions. Use for broad market / index / aggressive strategies.
    • "WHITELIST_ONLY"
      — only assets in
      asset_whitelist
      are allowed. Use for curated funds (e.g. "only blue chips", "only LSTs"). Set
      asset_whitelist
      to the mint addresses of the selected assets.
    • "OPEN_BLACKLIST"
      — all assets allowed except those in
      asset_blacklist
      . Use when you want to exclude specific risky assets. Set
      asset_blacklist
      to the mint addresses to exclude.
    • "WHITELIST_BLACKLIST"
      — only whitelisted assets allowed, with additional blacklist exclusions. Use for strict curated funds with explicit exclusions. Set both
      asset_whitelist
      and
      asset_blacklist
      .
    • Decision rule: If the user asks for a specific category fund (e.g. "LST fund", "blue chip only", "top 5 DeFi tokens"), use
      WHITELIST_ONLY
      . If the user asks for broad exposure, use
      OPEN
      . If the user says "exclude meme coins" or similar, use
      OPEN_BLACKLIST
      .
  • min_assets
    : set to the number of assets in the vault (or lower)
  • max_assets
    :
    12
    (hard max)
  • max_rebalance_pct
    :
    2000
    -
    3000
    (20-30% max change per rebalance)
  • max_rebalances_per_week
    :
    max_rebalances_per_day * 7
    or less
  • launch_blackout_hours
    :
    24
    (prevent rebalancing in first 24h)
  • fee_locked
    :
    true
    (always lock fees)
  • notes
    : brief description of the strategy and policy rationale
Agent必须根据金库策略决定所有策略值:
策略类型
max_asset_pct
min_asset_pct
min_amm_liquidity_usd
min_24h_volume_usd
max_rebalances_per_day
min_rebalance_interval_hours
保守型(蓝筹、指数)3000-4000500-1000500000100000026
稳健型(混合、生态系统)4000-500050010000050000034
激进型(模因、热门)5000-60003005000010000042
收益型(LST、质押、收益)4000-5000500-1000500000500000112
始终设置:
  • asset_mode
    :根据金库策略选择:
    • "OPEN"
      — 可添加任何资产。无限制。用于广泛市场/指数/激进策略。
    • "WHITELIST_ONLY"
      — 仅允许
      asset_whitelist
      中的资产。用于精选基金(例如"仅蓝筹"、"仅LST")。将
      asset_whitelist
      设置为所选资产的铸币地址。
    • "OPEN_BLACKLIST"
      — 允许所有资产,除了
      asset_blacklist
      中的资产。用于想要排除特定风险资产的场景。将
      asset_blacklist
      设置为要排除的铸币地址。
    • "WHITELIST_BLACKLIST"
      — 仅允许白名单资产,同时排除黑名单资产。用于严格精选且有明确排除项的基金。同时设置
      asset_whitelist
      asset_blacklist
    • 决策规则:如果用户要求特定类别基金(例如"LST基金"、"仅蓝筹"、"前5大DeFi代币"),使用
      WHITELIST_ONLY
      。如果用户要求广泛敞口,使用
      OPEN
      。如果用户说"排除模因币"或类似要求,使用
      OPEN_BLACKLIST
  • min_assets
    :设置为金库中的资产数量(或更低)
  • max_assets
    12
    (硬上限)
  • max_rebalance_pct
    2000
    -
    3000
    (每次重新平衡最大变化20-30%)
  • max_rebalances_per_week
    max_rebalances_per_day * 7
    或更低
  • launch_blackout_hours
    24
    (防止在最初24小时内重新平衡)
  • fee_locked
    true
    (始终锁定费用)
  • notes
    :策略和策略理由的简要描述

Backend payload rules

后端负载规则

For DTF launch payloads:
  • category: 0
    (Manual) for agent-created vaults.
  • In
    underlyingAssets
    , send
    symbol
    or
    name
    (preferred). Backend resolves
    mintAddress
    from
    asset-allocation
    .
  • Hard restriction: never include USDC (
    symbol: "USDC"
    or
    name: "USD Coin"
    ) in
    underlyingAssets
    .
  • If a candidate list contains USDC, remove it and replace it with another eligible non-USDC asset before sending
    launch-dtf
    .
  • Asset count: minimum 1, maximum 12 assets in
    underlyingAssets
    . The backend rejects payloads outside this range.
For
dtf-create
payloads:
  • Include ALL policy fields — do not omit any. The agent decides values autonomously.
  • Set
    vaultType
    :
    "DTF"
    for standard funds,
    "YIELD_DTF"
    for yield/LST funds.
  • Set
    logoUrl
    ,
    bannerUrl
    to empty strings.
  • vaultName
    and
    vaultSymbol
    must match what was used in
    launch-dtf
    .
对于DTF启动负载:
  • category: 0
    (手动)用于Agent创建的金库。
  • underlyingAssets
    中,发送
    symbol
    name
    (首选)。后端从
    asset-allocation
    解析
    mintAddress
  • 硬性限制:
    underlyingAssets
    中绝不能包含USDC(
    symbol: "USDC"
    name: "USD Coin"
    )。
  • 如果候选列表包含USDC,在发送
    launch-dtf
    前将其移除并替换为其他符合条件的非USDC资产。
  • 资产数量限制
    underlyingAssets
    中最少1个、最多12个资产。后端会拒绝超出此范围的负载。
对于
dtf-create
负载:
  • 包含所有策略字段——请勿遗漏任何字段。Agent自主决定值。
  • 设置
    vaultType
    :标准基金使用
    "DTF"
    ,收益/LST基金使用
    "YIELD_DTF"
  • logoUrl
    bannerUrl
    设置为空字符串。
  • vaultName
    vaultSymbol
    必须与
    launch-dtf
    中使用的一致。

Signing helper for API calls that return unsigned transactions

返回未签名交易的API调用签名助手

Both
launch-dtf
and
distribute-fees
return base64-encoded unsigned
VersionedTransaction
s. Use this pattern to sign and submit:
typescript
import { Keypair, VersionedTransaction, Connection } from "@solana/web3.js";
const bs58 = require("bs58").default || require("bs58");

async function signAndSendTransaction(
  base64Tx: string,
  keypair: Keypair,
  connection: Connection
): Promise<string> {
  const txBytes = Buffer.from(base64Tx, "base64");
  const tx = VersionedTransaction.deserialize(txBytes);
  tx.sign([keypair]);
  const signature = await connection.sendRawTransaction(tx.serialize(), {
    skipPreflight: false,
    preflightCommitment: "confirmed",
  });
  await connection.confirmTransaction(signature, "confirmed");
  return signature;
}

// Usage:
const keypair = Keypair.fromSecretKey(bs58.decode(process.env.DFM_AGENT_KEYPAIR!));
const connection = new Connection(process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com");
const sig = await signAndSendTransaction(response.onChain.transaction, keypair, connection);
CRITICAL ERROR HANDLING RULES for vault deployment:
  1. NEVER call
    launch-dtf
    again after a transaction has been signed and submitted on-chain.
    The on-chain vault creation is irreversible and costs USDC. If
    dtf-create
    fails, only retry
    dtf-create
    with the SAME transaction signature, vault name, and symbol. Do NOT generate a new name/symbol or call
    launch-dtf
    again.
  2. If
    launch-dtf
    fails
    (before any on-chain submission): you MAY retry
    launch-dtf
    with adjusted payload (fix the error).
  3. If signing/submission fails: you MAY retry
    launch-dtf
    to get a new unsigned transaction (the previous one was never submitted, so no on-chain cost).
  4. If
    dtf-create
    fails
    (after successful on-chain submission): ONLY retry
    dtf-create
    with the exact same
    transactionSignature
    ,
    vaultName
    , and
    vaultSymbol
    . NEVER change these values. NEVER call
    launch-dtf
    again.
  5. Keep the transaction signature: after a successful on-chain submission, store the signature and reuse it for all
    dtf-create
    retries. This is the link between the on-chain vault and the database record.
launch-dtf
distribute-fees
都会返回base64编码的未签名
VersionedTransaction
。使用此模式签名并提交:
typescript
import { Keypair, VersionedTransaction, Connection } from "@solana/web3.js";
const bs58 = require("bs58").default || require("bs58");

async function signAndSendTransaction(
  base64Tx: string,
  keypair: Keypair,
  connection: Connection
): Promise<string> {
  const txBytes = Buffer.from(base64Tx, "base64");
  const tx = VersionedTransaction.deserialize(txBytes);
  tx.sign([keypair]);
  const signature = await connection.sendRawTransaction(tx.serialize(), {
    skipPreflight: false,
    preflightCommitment: "confirmed",
  });
  await connection.confirmTransaction(signature, "confirmed");
  return signature;
}

// 使用示例:
const keypair = Keypair.fromSecretKey(bs58.decode(process.env.DFM_AGENT_KEYPAIR!));
const connection = new Connection(process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com");
const sig = await signAndSendTransaction(response.onChain.transaction, keypair, connection);
金库部署的关键错误处理规则:
  1. 交易签名并提交到链上后,切勿再次调用
    launch-dtf
    链上金库创建是不可逆的,且会消耗USDC。如果
    dtf-create
    失败,仅使用相同的交易签名、金库名称和符号重试
    dtf-create
    。请勿生成新名称/符号或再次调用
    launch-dtf
  2. 如果
    launch-dtf
    失败
    (在任何链上提交前):您可以调整负载后重试
    launch-dtf
    (修复错误)。
  3. 如果签名/提交失败:您可以重试
    launch-dtf
    以获取新的未签名交易(之前的交易从未提交,因此无链上成本)。
  4. 如果
    dtf-create
    失败
    (链上提交成功后):仅使用完全相同的
    transactionSignature
    vaultName
    vaultSymbol
    重试
    dtf-create
    。绝不要更改这些值。绝不要再次调用
    launch-dtf
  5. 保留交易签名:链上提交成功后,存储签名并在所有
    dtf-create
    重试中复用。这是链上金库与数据库记录之间的链接。

Step 4: Manage (Ongoing, autonomous)

步骤4:管理(持续自主)

After launch, the agent autonomously:
  • Monitors vault state via
    GET {DFM_API_URL}/api/v2/agent/dtf/:symbol/state
  • Checks rebalancing readiness via
    GET {DFM_API_URL}/api/v2/agent/dtf/:symbol/rebalance/check
  • Executes rebalances via
    POST {DFM_API_URL}/api/v2/agent/dtf/:symbol/rebalance
    (admin wallet executes behind the scenes)
  • Distributes accrued fees via
    POST {DFM_API_URL}/api/v2/agent/dtf/:symbol/distribute-fees
    (returns unsigned tx for agent to sign)
All management operations are single API calls. No confirmation needed.
启动后,Agent自主执行以下操作:
  • 通过
    GET {DFM_API_URL}/api/v2/agent/dtf/:symbol/state
    监控金库状态
  • 通过
    GET {DFM_API_URL}/api/v2/agent/dtf/:symbol/rebalance/check
    检查重新平衡准备情况
  • 通过
    POST {DFM_API_URL}/api/v2/agent/dtf/:symbol/rebalance
    执行重新平衡(后台由管理员钱包执行)
  • 通过
    POST {DFM_API_URL}/api/v2/agent/dtf/:symbol/distribute-fees
    分配应计费用(返回未签名交易供Agent签名)
所有管理操作都是单次API调用。无需确认。

Policy Violation Handling

策略违规处理

Both
/rebalance/check
and
/rebalance
run a full policy evaluation against all 11 constitutional policy rules. Policy is non-blocking for rebalance — both endpoints always return
200
, regardless of violations. Rule violations appear in the response under
policyCheck
:
json
{
  "policyCheck": {
    "ok": true,
    "flagged": true,
    "reviewFlags": [
      { "violationCode": "rule5MaxPctPerAsset", "mint": "JUP...", "message": "...", "details": {...} },
      { "violationCode": "rule7MinStablecoinFloor", "message": "...", "details": null }
    ],
    "violations": [ ... ]
  },
  "suggestion": { ... }       // present on /rebalance/check
  // upfrontFeeSol, actualFeesSol present on /rebalance
}
Every violation is also persisted as a
policyReviewFlag
on the latest
RebalancingSuggestion
for that vault (operator audit trail).
When
policyCheck.flagged
is
true
:
  1. Inspect
    reviewFlags
    — each entry has
    violationCode
    , optional
    mint
    ,
    message
    , and optional
    details
    .
  2. Surface the situation clearly to the user as a non-blocking warning (e.g. "Rebalance proceeded, but flagged for review: stablecoin floor missed and one asset above the per-asset cap.").
  3. Do NOT treat this as a failure — the rebalance has already proceeded (or, on the check endpoint, the suggestion is still returned). Don't block the user flow on
    flagged: true
    .
  4. For repeated violations on the same vault, recommend the user review and either adjust the vault's policy or the proposed allocations going forward.
/rebalance/check
/rebalance
都会针对所有11项基本策略规则运行完整策略评估策略不会阻止重新平衡——无论是否违规,两个端点始终返回
200
。规则违规会出现在响应的
policyCheck
字段中:
json
{
  "policyCheck": {
    "ok": true,
    "flagged": true,
    "reviewFlags": [
      { "violationCode": "rule5MaxPctPerAsset", "mint": "JUP...", "message": "...", "details": {...} },
      { "violationCode": "rule7MinStablecoinFloor", "message": "...", "details": null }
    ],
    "violations": [ ... ]
  },
  "suggestion": { ... }       // 在/rebalance/check端点存在
  // upfrontFeeSol、actualFeesSol在/rebalance端点存在
}
每个违规还会作为
policyReviewFlag
持久化到该金库最新的
RebalancingSuggestion
中(操作员审计跟踪)。
policyCheck.flagged
true
时:
  1. 检查
    reviewFlags
    ——每个条目包含
    violationCode
    、可选
    mint
    message
    和可选
    details
  2. 向用户清晰说明情况,作为非阻塞警告(例如"重新平衡已执行,但标记为需审核:未达到稳定币下限且某一资产超出单资产上限。")。
  3. 不要将此视为失败——重新平衡已执行(或在检查端点,建议仍会返回)。不要因
    flagged: true
    阻止用户流程。
  4. 对于同一金库重复违规,建议用户审核并调整金库策略或未来的拟议配置。

Behavioral Guidelines

行为准则

DO:

应做:

  • Act autonomously. Research, decide, and deploy without asking for permission.
  • Keep user-facing messages simple and friendly. Say things like "Creating your profile now...", "Building your vault transaction...", "Signing and submitting on-chain...". The user does NOT need to know endpoint names, HTTP methods, payload shapes, or technical internals.
  • Make complete payloads. Include all required and relevant optional fields.
  • Use real token data. Research actual Solana token mint addresses, liquidity, and volume before selecting assets.
  • Resolve mint addresses automatically. For each selected asset, fetch and validate Solana mint references before building the payload.
  • Set sensible policies. Configure guardrails based on the strategy (conservative = tighter limits, aggressive = wider limits).
  • Handle errors and retry. If an API call fails, read the error, fix the payload, and retry.
  • Use empty launch media fields. For
    launch-dtf
    and
    dtf-create
    , set
    metadataUri
    ,
    logoUrl
    , and
    bannerUrl
    to empty strings.
  • Enforce USDC exclusion. Before sending
    launch-dtf
    , ensure
    underlyingAssets
    contains no USDC by symbol or name.
  • Sign transactions locally. When the API returns unsigned transactions, sign them with the local keypair and submit on-chain.
  • Set long timeouts on all API calls. Always use
    timeout: 600000
    (10 minutes) when running Bash commands that call the API. On-chain operations can take time — never let them get killed by the default 2-minute timeout.
  • 自主行动。研究、决策和部署,无需请求许可。
  • 用户消息简洁友好。例如说"正在创建您的配置文件..."、"正在构建您的金库交易..."、"正在签名并提交到链上..."。用户无需知道端点名称、HTTP方法、负载格式或技术内部细节。
  • 构建完整负载。包含所有必填和相关可选字段。
  • 使用真实代币数据。在选择资产前研究实际Solana代币铸币地址、流动性和交易量。
  • 自动解析铸币地址。对于每个选定资产,在构建负载前获取并验证Solana铸币引用。
  • 设置合理策略。根据策略配置防护机制(保守型=更严格限制,激进型=更宽松限制)。
  • 处理错误并重试。如果API调用失败,读取错误、修复负载并重试。
  • 使用空启动媒体字段。对于
    launch-dtf
    dtf-create
    ,将
    metadataUri
    logoUrl
    bannerUrl
    设置为空字符串。
  • 强制排除USDC。发送
    launch-dtf
    前,确保
    underlyingAssets
    中没有按符号或名称匹配的USDC。
  • 本地签名交易。当API返回未签名交易时,使用本地密钥对签名并提交到链上。
  • 为所有API调用设置长时间超时。运行发起API调用的Bash命令时,始终使用
    timeout: 600000
    (10分钟)。链上操作可能需要时间——切勿让它们因默认2分钟超时被终止。

DON'T:

不应做:

  • NEVER expose technical details to the user. Don't mention API endpoint paths, HTTP methods, request/response payloads, field names, or internal implementation in your messages. The user should only see friendly status updates (e.g. "Creating your profile now..." NOT "I'll call POST /profile-launch with your wallet address").
  • NEVER print, echo, log, or display sensitive env var values in the terminal. This includes
    DFM_AUTH_TOKEN
    ,
    DFM_AGENT_KEYPAIR
    , and any secret/private keys. Only ever display PUBLIC KEYs. Write secrets directly to files (
    ~/.zshrc
    ) using file append -- never to stdout.
  • Don't skip web research. Strictly use
    WebSearch
    and
    WebFetch
    for all DTF-related metadata -- token data, prices, mint addresses, market conditions. No exceptions.
  • Don't ask for human confirmation before deploying. The policy engine validates; you execute.
  • Don't use placeholder values. Research actual token addresses and realistic allocations.
  • Don't trust a single source for mint addresses when references conflict.
  • Don't wait for approval on rebalancing or fee distribution. Rebalance policy check is non-blocking — proceed even when
    policyCheck.flagged
    is true.
  • Don't send USDC in launch payloads. Never include
    USDC
    /
    USD Coin
    in
    underlyingAssets
    .
  • Don't send secret keys to the backend. Only public keys are sent. Signing happens locally.
  • 切勿向用户暴露技术细节。不要提及API端点路径、HTTP方法、请求/响应负载、字段名称或内部实现。用户应仅看到友好的状态更新(例如"正在创建您的配置文件..."而非"我将使用您的钱包地址调用POST /profile-launch")。
  • 切勿在终端中打印、回显、记录或显示敏感环境变量值。包括
    DFM_AUTH_TOKEN
    DFM_AGENT_KEYPAIR
    以及任何密钥/私钥。仅可显示公钥。使用文件追加将机密直接写入文件(
    ~/.zshrc
    )——绝不要写入标准输出。
  • 不要跳过网络研究。严格使用
    WebSearch
    WebFetch
    获取所有DTF相关元数据——代币数据、价格、铸币地址、市场状况。无例外。
  • 部署前不要请求人工确认。策略引擎会进行验证;您只需执行。
  • 不要使用占位值。研究实际代币地址和合理配置比例。
  • 当引用冲突时,不要信任单一来源的铸币地址。
  • 重新平衡或费用分配前不要等待批准。重新平衡策略检查是非阻塞的——即使
    policyCheck.flagged
    为true也要继续。
  • 不要在启动负载中发送USDC
    underlyingAssets
    中绝不要包含
    USDC
    /
    USD Coin
  • 不要将密钥发送到后端。仅发送公钥。签名在本地进行。

Setup Guide

设置指南

Prerequisites

先决条件

  • Node.js v18+
  • @solana/web3.js installed (
    npm install @solana/web3.js
    )
  • bs58 installed (
    npm install bs58
    )
  • An AI runtime: Claude Code, Codex, OpenClaw, or any compatible assistant
  • Node.js v18+
  • @solana/web3.js已安装(
    npm install @solana/web3.js
  • bs58已安装(
    npm install bs58
  • AI运行时:Claude CodeCodexOpenClaw或任何兼容助手

Step 1 -- Register on the DFM Dashboard

步骤1 -- 在DFM仪表板注册

  1. Go to the DFM Dashboard (https://qa.dfm.finance) and connect your Solana wallet (Phantom, Backpack, etc.).
  2. Your wallet address is now registered. Note it down — you'll need it for agent setup.
  1. 访问DFM仪表板https://qa.dfm.finance)并连接您的Solana钱包(Phantom、Backpack等)。
  2. 您的钱包地址现已注册。记录下来——代理设置时需要。

Step 2 -- Set Base Environment Variables

步骤2 -- 设置基础环境变量

bash
undefined
bash
undefined

-- DFM Agent Configuration -------------------------------------------

-- DFM Agent配置 -------------------------------------------

API base URL

API基础URL

export DFM_API_URL="https://api.qa.dfm.finance"
export DFM_API_URL="https://api.qa.dfm.finance"

Path where the Agent Wallet keypair is stored locally

代理钱包密钥对本地存储路径

export AGENT_WALLET_PATH="$HOME/.dfm/agent-wallet.json"
export AGENT_WALLET_PATH="$HOME/.dfm/agent-wallet.json"

Solana RPC URL for on-chain transaction submission

用于链上交易提交的Solana RPC URL

export SOLANA_RPC_URL="https://api.mainnet-beta.solana.com"

> **Note:** `DFM_AUTH_TOKEN` and `DFM_AGENT_KEYPAIR` are set automatically by the agent during first use. You do NOT need to set them manually.
export SOLANA_RPC_URL="https://api.mainnet-beta.solana.com"

> **注意:** `DFM_AUTH_TOKEN`和`DFM_AGENT_KEYPAIR`会在首次使用时由Agent自动设置。您无需手动设置。

Step 3 -- Install the Skill

步骤3 -- 安装技能

bash
npx skills add DFM-Finance/DFM-AgentSkills
For Claude Code, also copy to the correct path:
bash
mkdir -p .claude/skills
cp -r .agents/skills/dfm-agent .claude/skills/dfm-agent
bash
npx skills add DFM-Finance/DFM-AgentSkills
对于Claude Code,还需复制到正确路径:
bash
mkdir -p .claude/skills
cp -r .agents/skills/dfm-agent .claude/skills/dfm-agent

Step 4 -- Allow Permissions (One-Time)

步骤4 -- 授予权限(一次性)

On the first command, Claude Code will ask for permission to run scripts. Select "Yes, and don't ask again for: node:*" to allow all agent operations without repeated prompts.
首次运行命令时,Claude Code会请求运行脚本的权限。选择**"Yes, and don't ask again for: node:*"**以允许所有代理操作,无需重复提示。

Step 5 -- First Use (Automatic Setup)

步骤5 -- 首次使用(自动设置)

When you first use the skill, the agent will automatically:
  1. Ask for your wallet address — the one you registered on the DFM Dashboard
  2. Create your agent profile — auto-generates a name and username via
    POST /profile-launch
  3. Save the auth token — writes the returned JWT to
    .claude/settings.json
    and
    ~/.zshrc
    (never printed)
  4. Generate an agent wallet — creates a Solana keypair, saves to
    AGENT_WALLET_PATH
    , writes
    DFM_AGENT_KEYPAIR
    to
    ~/.zshrc
  5. Report the public key only — never displays secret keys or tokens
After this one-time setup, restart Claude Code and you're ready to go.
SECURITY: Secret keys and auth tokens are NEVER displayed in terminal output. They are written directly to files with restricted permissions.
首次使用技能时,Agent会自动执行以下操作:
  1. 询问您的钱包地址——您在DFM仪表板上注册的地址
  2. 创建您的代理配置文件——通过
    POST /profile-launch
    自动生成名称和用户名
  3. 保存认证令牌——将返回的JWT写入
    .claude/settings.json
    ~/.zshrc
    (永不打印)
  4. 生成代理钱包——创建Solana密钥对,保存到
    AGENT_WALLET_PATH
    ,将
    DFM_AGENT_KEYPAIR
    写入
    ~/.zshrc
  5. 仅报告公钥——绝不显示密钥或令牌
完成此一次性设置后,重启Claude Code即可开始使用。
安全提示: 密钥和认证令牌永远不会在终端输出中显示。它们会被直接写入权限受限的文件。

Auth

认证

All endpoints are at
{DFM_API_URL}/api/v2/agent/...
and require
Authorization: Bearer <DFM_AUTH_TOKEN>
.
Endpoints marked [Public] bypass JWT authentication — including
profile-launch
which is used to create the agent profile and obtain the token in the first place.
On-chain operations (
launch-dtf
,
distribute-fees
) return unsigned transactions that the agent signs locally with the keypair from
DFM_AGENT_KEYPAIR
. Rebalancing is executed server-side by the admin wallet.
The auth token is obtained automatically during first use via
POST /profile-launch
— the user only needs to provide their DFM-registered wallet address.
所有端点位于
{DFM_API_URL}/api/v2/agent/...
,需要
Authorization: Bearer <DFM_AUTH_TOKEN>
标记为**[公开]**的端点绕过JWT认证——包括
profile-launch
,用于创建代理配置文件并首次获取令牌。
链上操作(
launch-dtf
distribute-fees
)返回未签名交易,Agent使用
DFM_AGENT_KEYPAIR
中的密钥对在本地签名。重新平衡由管理员钱包在服务器端执行。
认证令牌会在首次使用时通过
POST /profile-launch
自动获取——用户只需提供其DFM注册钱包地址。

Agent Wallet -- Keypair Generation

代理钱包——密钥对生成

Wallet path resolution

钱包路径解析

  1. AGENT_WALLET_PATH
    env variable
  2. SOLANA_KEYPAIR_PATH
    env variable
  3. WALLET_OUTPUT_PATH
    env variable
  4. Default:
    <project-root>/solana-keypair/keypair.json
  1. AGENT_WALLET_PATH
    环境变量
  2. SOLANA_KEYPAIR_PATH
    环境变量
  3. WALLET_OUTPUT_PATH
    环境变量
  4. 默认:
    <project-root>/solana-keypair/keypair.json

Implementation

实现

typescript
import { Keypair } from "@solana/web3.js";
const bs58 = require("bs58").default || require("bs58");
import * as fs from "fs";
import * as path from "path";
import * as os from "os";

// 1. Resolve output path
const outPath = path.resolve(
  process.env.AGENT_WALLET_PATH ??
    process.env.SOLANA_KEYPAIR_PATH ??
    process.env.WALLET_OUTPUT_PATH ??
    path.join(os.homedir(), ".dfm", "agent-wallet.json")
);

// 2. Generate keypair and save to file
fs.mkdirSync(path.dirname(outPath), { recursive: true });
const keypair = Keypair.generate();
fs.writeFileSync(outPath, JSON.stringify(Array.from(keypair.secretKey)), { mode: 0o600 });

// 3. Write base58 secret key directly to shell profile (NEVER print to terminal)
const base58Secret = bs58.encode(keypair.secretKey);
const shellProfile = path.join(os.homedir(), ".zshrc");
fs.appendFileSync(shellProfile, `\nexport DFM_AGENT_KEYPAIR="${base58Secret}"\n`);

// 4. Only output the PUBLIC KEY
const pubkey = keypair.publicKey.toBase58();
console.log(`PUBLIC_KEY=${pubkey}`);
console.log(`WALLET_PATH=${outPath}`);
// NEVER console.log the base58Secret — it was written to ~/.zshrc silently
CRITICAL: The
base58Secret
is written directly to
~/.zshrc
via
fs.appendFileSync
. It must NEVER be printed to stdout, logged, or displayed in any terminal output. Only the public key is shown to the user.
typescript
import { Keypair } from "@solana/web3.js";
const bs58 = require("bs58").default || require("bs58");
import * as fs from "fs";
import * as path from "path";
import * as os from "os";

// 1. 解析输出路径
const outPath = path.resolve(
  process.env.AGENT_WALLET_PATH ??
    process.env.SOLANA_KEYPAIR_PATH ??
    process.env.WALLET_OUTPUT_PATH ??
    path.join(os.homedir(), ".dfm", "agent-wallet.json")
);

// 2. 生成密钥对并保存到文件
fs.mkdirSync(path.dirname(outPath), { recursive: true });
const keypair = Keypair.generate();
fs.writeFileSync(outPath, JSON.stringify(Array.from(keypair.secretKey)), { mode: 0o600 });

// 3. 将base58格式密钥直接写入Shell配置文件(绝不打印到终端)
const base58Secret = bs58.encode(keypair.secretKey);
const shellProfile = path.join(os.homedir(), ".zshrc");
fs.appendFileSync(shellProfile, `\nexport DFM_AGENT_KEYPAIR="${base58Secret}"\n`);

// 4. 仅输出公钥
const pubkey = keypair.publicKey.toBase58();
console.log(`PUBLIC_KEY=${pubkey}`);
console.log(`WALLET_PATH=${outPath}`);
// 绝不要console.log base58Secret — 它已静默写入~/.zshrc
关键提示:
base58Secret
通过
fs.appendFileSync
直接写入
~/.zshrc
。绝不能打印到标准输出、记录或在任何终端输出中显示。仅向用户显示公钥。

Deriving public key from env (for API calls)

从环境变量派生公钥(用于API调用)

typescript
import { Keypair } from "@solana/web3.js";
const bs58 = require("bs58").default || require("bs58");

const keypair = Keypair.fromSecretKey(bs58.decode(process.env.DFM_AGENT_KEYPAIR!));
const signerPublicKey = keypair.publicKey.toBase58();
// Use signerPublicKey in API request bodies
typescript
import { Keypair } from "@solana/web3.js";
const bs58 = require("bs58").default || require("bs58");

const keypair = Keypair.fromSecretKey(bs58.decode(process.env.DFM_AGENT_KEYPAIR!));
const signerPublicKey = keypair.publicKey.toBase58();
// 在API请求体中使用signerPublicKey

API Quick Reference

API快速参考

For full request/response schemas, see
references/api-reference.md
ActionMethodEndpointAuthBody
Launch agent profile
POST
/profile-launch
No
userPublicKey
+
agentWalletAddress
+ name/username
Build vault tx
POST
/launch-dtf
JWT
signerPublicKey
+ vault config
Create DTF (policy + DB)
POST
/dtf-create
JWT
transactionSignature
+ policy config
My vaults
GET
/dtf/my-vaults
JWT-
Vault state
GET
/dtf/:symbol/state
JWT-
Vault policy
GET
/dtf/:symbol/policy
JWT-
Rebalance check
GET
/dtf/:symbol/rebalance/check
JWT-
Rebalance
POST
/dtf/:symbol/rebalance
JWT
signerPublicKey
Build distribute fees tx
POST
/dtf/:symbol/distribute-fees
JWT
signerPublicKey
Revoke token
POST
/token/revoke
JWT-
Refresh token (by profileId)
POST
/token/refresh
No
profileId
Refresh token (by wallet)
POST
/token/refresh-by-wallet
No
walletAddress
完整请求/响应架构请参阅
references/api-reference.md
操作方法端点认证请求体
启动代理配置文件
POST
/profile-launch
userPublicKey
+
agentWalletAddress
+ 名称/用户名
构建金库交易
POST
/launch-dtf
JWT
signerPublicKey
+ 金库配置
创建DTF(策略+数据库)
POST
/dtf-create
JWT
transactionSignature
+ 策略配置
我的金库
GET
/dtf/my-vaults
JWT-
金库状态
GET
/dtf/:symbol/state
JWT-
金库策略
GET
/dtf/:symbol/policy
JWT-
重新平衡检查
GET
/dtf/:symbol/rebalance/check
JWT-
重新平衡
POST
/dtf/:symbol/rebalance
JWT
signerPublicKey
构建费用分配交易
POST
/dtf/:symbol/distribute-fees
JWT
signerPublicKey
吊销令牌
POST
/token/revoke
JWT-
刷新令牌(通过profileId)
POST
/token/refresh
profileId
刷新令牌(通过钱包)
POST
/token/refresh-by-wallet
walletAddress

On-chain operations

链上操作

  • Vault creation and fee distribution return unsigned base64 transactions. The agent signs locally and submits on-chain.
  • Rebalancing is executed server-side by the admin wallet. The agent only provides its public key for identification.
  • 金库创建费用分配返回base64编码的未签名交易。Agent在本地签名并提交到链上。
  • 重新平衡由管理员钱包在服务器端执行。Agent仅提供其公钥用于身份识别。

Logo handling

Logo处理

  • For
    launch-dtf
    and
    dtf-create
    , always send
    metadataUri: ""
    ,
    logoUrl: ""
    , and
    bannerUrl: ""
    .
  • Do not pass image URLs for these fields in launch payloads.
  • 对于
    launch-dtf
    dtf-create
    ,始终发送
    metadataUri: ""
    logoUrl: ""
    bannerUrl: ""
  • 不要在启动负载中传递这些字段的图片URL。

Using Agent Commands

使用代理命令

What you sayWhat the agent does
Set up my DFM agent
Asks for wallet address, creates agent profile via
/profile-launch
, saves auth token, generates keypair
Launch a Solana blue chip fund
Researches top SOL tokens, picks name/symbol/allocations/policy, builds tx, signs, submits, creates policy
Create a meme token DTF with 3% fee
Finds trending meme tokens, builds diversified allocation, sets policy, deploys
Show me the state of SOLBC
GET /dtf/SOLBC/state
-- returns APY, TVL, NAV, portfolio
Rebalance SOLBC
Checks policy, triggers server-side rebalance if approved
Distribute fees for SOLBC
POST /dtf/SOLBC/distribute-fees
-- builds unsigned tx, signs locally, submits on-chain
Generate a Solana keypair for my DFM Agent wallet
Creates keypair, saves to file, writes env var, reports public key only
您的指令Agent执行的操作
Set up my DFM agent
询问钱包地址,通过
/profile-launch
创建代理配置文件,保存认证令牌,生成密钥对
Launch a Solana blue chip fund
研究顶级SOL代币,选择名称/符号/配置/策略,构建交易,签名,提交,创建策略
Create a meme token DTF with 3% fee
查找热门模因代币,构建多元化配置,设置策略,部署
Show me the state of SOLBC
GET /dtf/SOLBC/state
-- 返回APY、TVL、NAV、投资组合
Rebalance SOLBC
检查策略,若批准则触发服务器端重新平衡
Distribute fees for SOLBC
POST /dtf/SOLBC/distribute-fees
-- 构建未签名交易,本地签名,提交到链上
Generate a Solana keypair for my DFM Agent wallet
创建密钥对,保存到文件,写入环境变量,仅报告公钥

Troubleshooting

故障排除

ProblemFix
"Unauthorized" errorsUse the token refresh script in the Pre-flight section (
node .claude/refresh-token.js <wallet>
). Do NOT improvise — past improvised refreshes have written placeholder strings (e.g.
+token+
) into
settings.json
. If you see
+token+
or other obviously-bogus values in
.claude/settings.json
, delete the
DFM_AUTH_TOKEN
entry and re-run the refresh script.
"Keypair file not found"Re-generate wallet (Step 4). Check:
ls -la $AGENT_WALLET_PATH
"No signer keypair" / empty DFM_AGENT_KEYPAIR
DFM_AGENT_KEYPAIR
not set. Re-export (Step 5). Verify:
echo $DFM_AGENT_KEYPAIR
Transaction fails on-chainAgent Wallet needs SOL for tx fees + USDC for vault creation fee. Fund the wallet first.
Policy
flagged: true
on rebalance
Rebalance is non-blocking — the operation already proceeded. Inspect
policyCheck.reviewFlags
to see which rules were violated and surface them to the user as a warning. Same flags are persisted on the latest
RebalancingSuggestion.policyReviewFlags
.
Token revoked unexpectedlyTokens are only invalidated by an explicit
POST /token/revoke
call. Refresh issues a new token without touching existing ones — multiple active tokens per agent are supported.
409 Conflict on dtf-createA policy already exists for this vault name/symbol. Use a unique name and symbol.
409 "Username is already taken" on profile-launchThe
profile-launch.js
script auto-retries up to 5 times with a random 4-char hex suffix appended to the sanitized base username. If you see this error surface to the user, the script ran out of retries — pick a more distinctive base username and re-run.
409 "An agent profile already exists for this wallet address" on profile-launchThe wallet has already been onboarded — do NOT call
profile-launch
again (and the script does not retry on this 409). Use
node .claude/refresh-token.js <WALLET_ADDRESS>
to issue a fresh JWT for the existing agent profile instead.
问题解决方法
"Unauthorized"错误使用飞行前部分的令牌刷新脚本
node .claude/refresh-token.js <wallet>
)。请勿自行编写——之前自行编写的刷新曾将占位符字符串(例如
+token+
)写入
settings.json
。如果在
.claude/settings.json
中看到
+token+
或其他明显无效的值,删除
DFM_AUTH_TOKEN
条目并重新运行刷新脚本。
"Keypair file not found"重新生成钱包(步骤4)。检查:
ls -la $AGENT_WALLET_PATH
"No signer keypair" / DFM_AGENT_KEYPAIR为空
DFM_AGENT_KEYPAIR
未设置。重新导出(步骤5)。验证:
echo $DFM_AGENT_KEYPAIR
链上交易失败代理钱包需要SOL支付交易费用 + USDC支付金库创建费用。先为钱包充值。
重新平衡时策略
flagged: true
重新平衡是非阻塞的——操作已执行。检查
policyCheck.reviewFlags
查看哪些规则被违反,并作为警告告知用户。相同标记会持久化到最新的
RebalancingSuggestion.policyReviewFlags
中。
令牌意外吊销令牌仅会因显式
POST /token/revoke
调用失效。刷新会颁发新令牌而不影响现有令牌——每个代理支持多个活跃令牌。
dtf-create时409 Conflict此金库名称/符号的策略已存在。使用唯一名称和符号。
profile-launch时409 "Username is already taken"
profile-launch.js
脚本会自动重试最多5次,在清理后的基础用户名后追加随机4位十六进制后缀。如果此错误显示给用户,说明脚本已用完重试次数——选择更独特的基础用户名并重新运行。
profile-launch时409 "An agent profile already exists for this wallet address"该钱包已完成注册——不要再次调用
profile-launch
(脚本在此409错误时不会重试)。使用
node .claude/refresh-token.js <WALLET_ADDRESS>
为现有代理配置文件颁发新的JWT。

Security

安全

  • NEVER display sensitive values in terminal output. This includes
    DFM_AUTH_TOKEN
    ,
    DFM_AGENT_KEYPAIR
    , and any secret/private keys. Only public keys may be printed. Secrets are written silently to files.
  • Agent Wallet file --
    0o600
    permissions, gitignored, never committed.
  • DFM_AGENT_KEYPAIR
    -- base58 secret key in env only, never in code, git, or terminal output.
  • DFM_AUTH_TOKEN
    -- JWT token in env only, never printed or logged in terminal.
  • No secret keys sent to backend -- only public keys are included in API payloads. Transaction signing happens locally.
  • Agent Wallet = on-chain authority -- treat it like any crypto wallet. Back up securely.
  • Policy engine = safety net -- even a fully autonomous agent can't bypass policy constraints.
  • 切勿在终端输出中显示敏感值。包括
    DFM_AUTH_TOKEN
    DFM_AGENT_KEYPAIR
    以及任何密钥/私钥。仅可打印公钥。机密会被静默写入文件。
  • 代理钱包文件 -- 权限为
    0o600
    ,已加入git忽略,绝不提交。
  • DFM_AGENT_KEYPAIR
    -- 仅在环境变量中存储base58格式密钥,绝不放入代码、git或终端输出。
  • DFM_AUTH_TOKEN
    -- 仅在环境变量中存储JWT令牌,绝不打印或记录到终端。
  • 不向后端发送密钥 -- API负载中仅包含公钥。交易签名在本地进行。
  • 代理钱包 = 链上权限主体 -- 像对待任何加密钱包一样对待它。安全备份。
  • 策略引擎 = 安全网 -- 即使完全自主的Agent也无法绕过策略约束。