dfm-agent
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDFM 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. |
+----------------------------------------------------------------+| Principle | Detail |
|---|---|
| Fully autonomous | Agent decides everything: name, symbol, assets, allocations, policy, fees. No confirmation prompts. |
| Two-step vault creation | |
| Policy engine as guardrail | Backend validates the payload and policy config. If valid, it deploys. If not, it returns an error with the specific issue. |
| Non-custodial | Agent Wallet private key never leaves the user's machine. Backend never receives secret keys. |
| Agent = on-chain authority | The 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决定所有事项:名称、符号、资产、配置、策略、费用。无确认提示。 |
| 两步金库创建流程 | |
| 策略引擎作为防护机制 | 后端验证负载和策略配置。若有效则部署,若无效则返回包含具体问题的错误。 |
| 非托管 | 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:
- -- JWT auth token
DFM_AUTH_TOKEN - -- base58 secret key
DFM_AGENT_KEYPAIR - 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切勿在终端输出中打印、回显、记录或显示以下环境变量的值:
- -- JWT认证令牌
DFM_AUTH_TOKEN - -- base58格式密钥
DFM_AGENT_KEYPAIR - 任何私钥、密钥或认证令牌
检查环境变量是否设置时,仅使用长度检查:
bash
node -e 'console.log(process.env.DFM_AUTH_TOKEN ? "set" : "not set")'切勿执行以下任何操作:
bash
undefinedWRONG - 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 for API calls. Always use with references so tokens and keys are never visible in the command string.
curlnode -eprocess.envIMPORTANT: No timeout on API calls. When running Bash commands that make API calls, always set (10 minutes) or use . 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.
timeout: 600000run_in_background: trueecho $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));
'请勿使用进行API调用。 始终使用带有引用的,确保令牌和密钥永远不会在命令字符串中可见。
curlprocess.envnode -e重要提示:API调用无超时限制。 运行发起API调用的Bash命令时,始终设置(10分钟)或使用。默认Bash超时为2分钟,对于金库创建、重新平衡和费用分配等链上操作来说太短。切勿让API调用因超时被终止。
timeout: 600000run_in_background: truePre-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
.claude/settings.json步骤1:确保.claude/settings.json
包含环境变量
.claude/settings.jsonClaude Code runs bash in a non-interactive subprocess that does NOT source or . Environment variables set via 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 .
~/.zshrc~/.bashrcexport.claude/settings.jsonOn every pre-flight check, run this script to sync env vars from into :
~/.zshrc.claude/settings.jsonbash
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 is SET but any API call returns 401 (Unauthorized / token expired):
DFM_AUTH_TOKEN- Ask the user for their DFM-registered wallet address (only if you don't already know it).
- Run the token refresh script below with the wallet address. The script calls , writes the new JWT to
POST {DFM_API_URL}/api/v2/agent/token/refresh-by-wallet, and replaces any existing.claude/settings.jsonline inexport DFM_AUTH_TOKEN=(never appends — appending would accumulate stale tokens that the pre-flight may pick up first). The token value is never printed.~/.zshrc - After the script reports , retry the original operation in the same session —
STATUS=successis read by Claude Code on the next bash invocation, so no restart is required..claude/settings.json
DO NOT improvise the refresh. Earlier improvised attempts have written literal placeholder strings (e.g. ) into . Always use this exact script.
+token+settings.jsonWrite the script once to , then run it with :
.claude/refresh-token.jsnode .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 is NOT SET after this script, run the automated agent profile creation flow:
DFM_AUTH_TOKEN-
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.
-
Ensure the agent wallet exists — the profile launch requires the agent's wallet public key.
- If is already set, derive the public key from it (do NOT generate a new one).
DFM_AGENT_KEYPAIR - If a keypair file exists at (default
AGENT_WALLET_PATH), load it and derive the public key.~/.dfm/agent-wallet.json - Only if neither exists, generate a new keypair. See "Agent Wallet -- Keypair Generation" section below.
- If
-
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 script auto-retries with a random 4-char hex suffix on any
profile-launch.jsfrom the backend (up to 5 attempts). Just pass a sensible base name/username; the script handles collisions silently.409 "Username is already taken"
-
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 calledwith this content, then run it with
.claude/profile-launch.js:node .claude/profile-launch.jsjavascriptconst 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 derivesfromagentWalletAddressautomatically and includes it in the payload. It outputs ONLYDFM_AGENT_KEYPAIR,STATUS=success,AGENT_NAME=...,AGENT_USERNAME=...,AGENT_WALLET=..., andATTEMPTS=<n>. The actual token value is written directly toDFM_AUTH_TOKEN=setand.claude/settings.json— it NEVER appears in terminal output.~/.zshrcUsername conflict retry behavior:- On HTTP whose message references "username" (e.g.
409), the script appends a 4-hex-char suffix to the sanitized base username and re-issues"Username is already taken". Up to 5 attempts.POST /profile-launch - During retries the script logs only so the agent (and the human watching) can see progress without leaking secrets.
RETRY=username_taken attempt=<n> next_username=<new> - The display is preserved across retries — only
namechanges.username - On HTTP whose message references "wallet" (e.g.
409), the script does NOT retry — that's an unrecoverable state for this flow. The agent should call"An agent profile already exists for this wallet address"instead./token/refresh-by-wallet - If all 5 attempts collide, the script exits with a clear line and the agent must surface the conflict to the user.
ERROR:
- On HTTP
-
Tell the user: "Agent profile created! Restart your AI agent to pick up the auth token, then you're ready to go."
If 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.
DFM_AGENT_KEYPAIRClaude Code在非交互式子进程中运行bash,不会读取或。用户终端中通过设置的环境变量对Claude Code的bash命令不可用。将环境变量传递给Claude Code的唯一可靠方式是通过。
~/.zshrc~/.bashrcexport.claude/settings.json每次飞行前检查,运行此脚本将环境变量从同步到:
~/.zshrc.claude/settings.jsonbash
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"));
}
'如果已设置但任何API调用返回401(未授权/令牌过期):
DFM_AUTH_TOKEN- 向用户索要其DFM注册钱包地址(如果尚未知晓)。
- 使用钱包地址运行下方的令牌刷新脚本。该脚本调用,将新的JWT写入
POST {DFM_API_URL}/api/v2/agent/token/refresh-by-wallet,并替换.claude/settings.json中任何现有的~/.zshrc行(绝不追加——追加会积累过时令牌,飞行前检查可能会先拾取这些令牌)。令牌值永远不会被打印。export DFM_AUTH_TOKEN= - 脚本报告后,在同一会话中重试原操作——Claude Code会在下次bash调用时读取
STATUS=success,因此无需重启。.claude/settings.json
请勿自行编写刷新逻辑。 之前自行编写的尝试曾将字面占位符字符串(例如)写入。请始终使用此精确脚本。
+token+settings.json将脚本写入,然后使用运行:
.claude/refresh-token.jsnode .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-
向用户索要其DFM注册钱包地址(他们在DFM仪表板上注册时使用的Solana公钥):要设置您的DFM Agent,我需要您在DFM仪表板(https://qa.dfm.finance)上注册的**Solana钱包地址**。 请粘贴您的钱包公钥。
-
确保代理钱包存在——配置文件启动需要代理的钱包公钥。
- 如果已设置,从中派生公钥(请勿生成新的)。
DFM_AGENT_KEYPAIR - 如果(默认
AGENT_WALLET_PATH)存在密钥对文件,加载并派生公钥。~/.dfm/agent-wallet.json - 仅当两者都不存在时,生成新的密钥对。请参阅下方的“代理钱包——密钥对生成”部分。
- 如果
-
用户提供钱包地址且代理密钥对存在后,自动生成代理配置文件名称和用户名:
- 名称:生成有创意的代理名称(例如"Alpha Sentinel"、"Momentum Agent"、"DeFi Navigator")
- 用户名:从名称生成唯一用户名(例如"alpha_sentinel"、"momentum_agent")
- 无需预先检查唯一性——当后端返回时,
409 "Username is already taken"脚本会自动重试,在用户名后追加随机4位十六进制后缀(最多5次尝试)。只需传递合理的基础名称/用户名,脚本会静默处理冲突。profile-launch.js
-
在单个脚本中创建配置文件并保存令牌——API调用、令牌提取和环境变量写入必须在一个脚本内完成,确保令牌永远不会在终端输出中可见。该脚本还会在用户名重复的409错误时自动重试,在用户名后追加随机后缀(最多重试5次),因此无需重新提示用户输入新名称。编写脚本文件并执行:创建名为的文件,内容如下,然后使用
.claude/profile-launch.js运行:node .claude/profile-launch.jsjavascriptconst 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 且消息提及"username"(例如
409)时,脚本会在清理后的基础用户名后追加4位十六进制后缀,重新发起"Username is already taken"请求。最多5次尝试。POST /profile-launch - 重试期间脚本仅记录,以便代理(和观看的用户)能够看到进度而不会泄露机密。
RETRY=username_taken attempt=<n> next_username=<new> - 显示在重试期间保持不变——仅
name更改。username - 当HTTP 且消息提及"wallet"(例如
409)时,脚本不会重试——这是此流程下无法恢复的状态。代理应调用"An agent profile already exists for this wallet address"替代。/token/refresh-by-wallet - 如果5次尝试都发生冲突,脚本会输出清晰的行,代理必须将冲突告知用户。
ERROR:
- 当HTTP
-
告知用户:"代理配置文件已创建!重启您的AI代理以获取认证令牌,之后即可开始使用。"
如果未设置且操作需要签名(launch-dtf、distribute-fees),自动生成钱包(请参阅下方的“代理钱包——密钥对生成”部分)。无需询问——直接生成。
DFM_AGENT_KEYPAIRStep 2: Proceed
步骤2:继续操作
If all required vars are set, proceed immediately with the requested operation. Do not ask for confirmation.
IMPORTANT: After writing to 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以加载环境变量。后续运行时,设置文件已存在,环境变量会立即生效。
.claude/settings.jsonWhen 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 and tools for ALL DTF-related metadata -- token discovery, prices, market caps, volume, liquidity, mint addresses, trending assets, and market conditions. No exceptions.
WebSearchWebFetchUse to find:
WebSearch- 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 to pull data from:
WebFetch- 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 from reliable references (official docs, verified token lists, explorers, major data providers)
mintAddress - 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
使用和工具获取所有DTF相关元数据——代币发现、价格、市值、交易量、流动性、铸币地址、热门资产和市场状况。无例外。
WebSearchWebFetch使用查找:
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 -- for standard diversified token funds,
"DTF"for yield-bearing / LST-focused funds. Set in the"YIELD_DTF"payload.dtf-create - Vault name -- descriptive, catchy, relevant to the strategy
- Vault symbol -- short (max 10 chars), unique, memorable
- Underlying assets -- pass asset or
symbol(preferred) with allocation in basis points (must sum to 10000). Backend resolvesnamefrommintAddress.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, andbannerUrlto empty strings.metadataUri
基于研究结果,自主决定:
- 金库类型——标准多元化代币基金使用,收益型/LST聚焦基金使用
"DTF"。在"YIELD_DTF"负载中设置。dtf-create - 金库名称——描述性、吸引人、与策略相关
- 金库符号——简短(最多10个字符)、唯一、易记
- 底层资产——传递资产或
symbol(首选)以及基点形式的配置比例(总和必须为10000)。后端从name解析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 with the vault creation payload. The backend builds and returns an unsigned transaction.
POST {DFM_API_URL}/api/v2/agent/launch-dtfjson
{
"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-dtfjson
{
"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 with the transaction signature and policy configuration:
POST {DFM_API_URL}/api/v2/agent/dtf-createYou MUST include ALL policy fields in every payload. The agent decides values based on the vault strategy. Missing fields default to 0/disabled which means no policy guardrails.
dtf-createjson
{
"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每个负载必须包含所有策略字段。 Agent根据金库策略决定值。缺失字段默认设为0/禁用,意味着无策略防护机制。
dtf-createjson
{
"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 | | | | | | |
|---|---|---|---|---|---|---|
| Conservative (blue chip, index) | 3000-4000 | 500-1000 | 500000 | 1000000 | 2 | 6 |
| Moderate (mixed, ecosystem) | 4000-5000 | 500 | 100000 | 500000 | 3 | 4 |
| Aggressive (meme, trending) | 5000-6000 | 300 | 50000 | 100000 | 4 | 2 |
| Yield (LSTs, staking, yield) | 4000-5000 | 500-1000 | 500000 | 500000 | 1 | 12 |
Always set:
- : choose based on the vault strategy:
asset_mode- — any asset can be added. No restrictions. Use for broad market / index / aggressive strategies.
"OPEN" - — only assets in
"WHITELIST_ONLY"are allowed. Use for curated funds (e.g. "only blue chips", "only LSTs"). Setasset_whitelistto the mint addresses of the selected assets.asset_whitelist - — all assets allowed except those in
"OPEN_BLACKLIST". Use when you want to exclude specific risky assets. Setasset_blacklistto the mint addresses to exclude.asset_blacklist - — only whitelisted assets allowed, with additional blacklist exclusions. Use for strict curated funds with explicit exclusions. Set both
"WHITELIST_BLACKLIST"andasset_whitelist.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 . If the user asks for broad exposure, use
WHITELIST_ONLY. If the user says "exclude meme coins" or similar, useOPEN.OPEN_BLACKLIST
- : set to the number of assets in the vault (or lower)
min_assets - :
max_assets(hard max)12 - :
max_rebalance_pct-2000(20-30% max change per rebalance)3000 - :
max_rebalances_per_weekor lessmax_rebalances_per_day * 7 - :
launch_blackout_hours(prevent rebalancing in first 24h)24 - :
fee_locked(always lock fees)true - : brief description of the strategy and policy rationale
notes
Agent必须根据金库策略决定所有策略值:
| 策略类型 | | | | | | |
|---|---|---|---|---|---|---|
| 保守型(蓝筹、指数) | 3000-4000 | 500-1000 | 500000 | 1000000 | 2 | 6 |
| 稳健型(混合、生态系统) | 4000-5000 | 500 | 100000 | 500000 | 3 | 4 |
| 激进型(模因、热门) | 5000-6000 | 300 | 50000 | 100000 | 4 | 2 |
| 收益型(LST、质押、收益) | 4000-5000 | 500-1000 | 500000 | 500000 | 1 | 12 |
始终设置:
- :根据金库策略选择:
asset_mode- — 可添加任何资产。无限制。用于广泛市场/指数/激进策略。
"OPEN" - — 仅允许
"WHITELIST_ONLY"中的资产。用于精选基金(例如"仅蓝筹"、"仅LST")。将asset_whitelist设置为所选资产的铸币地址。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(每次重新平衡最大变化20-30%)3000 - :
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:
- (Manual) for agent-created vaults.
category: 0 - In , send
underlyingAssetsorsymbol(preferred). Backend resolvesnamefrommintAddress.asset-allocation - Hard restriction: never include USDC (or
symbol: "USDC") inname: "USD Coin".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 . The backend rejects payloads outside this range.
underlyingAssets
For payloads:
dtf-create- Include ALL policy fields — do not omit any. The agent decides values autonomously.
- Set :
vaultTypefor standard funds,"DTF"for yield/LST funds."YIELD_DTF" - Set ,
logoUrlto empty strings.bannerUrl - and
vaultNamemust match what was used invaultSymbol.launch-dtf
对于DTF启动负载:
- (手动)用于Agent创建的金库。
category: 0 - 在中,发送
underlyingAssets或symbol(首选)。后端从name解析asset-allocation。mintAddress - 硬性限制:中绝不能包含USDC(
underlyingAssets或symbol: "USDC")。name: "USD Coin" - 如果候选列表包含USDC,在发送前将其移除并替换为其他符合条件的非USDC资产。
launch-dtf - 资产数量限制:中最少1个、最多12个资产。后端会拒绝超出此范围的负载。
underlyingAssets
对于负载:
dtf-create- 包含所有策略字段——请勿遗漏任何字段。Agent自主决定值。
- 设置:标准基金使用
vaultType,收益/LST基金使用"DTF"。"YIELD_DTF" - 将、
logoUrl设置为空字符串。bannerUrl - 和
vaultName必须与vaultSymbol中使用的一致。launch-dtf
Signing helper for API calls that return unsigned transactions
返回未签名交易的API调用签名助手
Both and return base64-encoded unsigned s. Use this pattern to sign and submit:
launch-dtfdistribute-feesVersionedTransactiontypescript
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:
-
NEVER callagain after a transaction has been signed and submitted on-chain. The on-chain vault creation is irreversible and costs USDC. If
launch-dtffails, only retrydtf-createwith the SAME transaction signature, vault name, and symbol. Do NOT generate a new name/symbol or calldtf-createagain.launch-dtf -
Iffails (before any on-chain submission): you MAY retry
launch-dtfwith adjusted payload (fix the error).launch-dtf -
If signing/submission fails: you MAY retryto get a new unsigned transaction (the previous one was never submitted, so no on-chain cost).
launch-dtf -
Iffails (after successful on-chain submission): ONLY retry
dtf-createwith the exact samedtf-create,transactionSignature, andvaultName. NEVER change these values. NEVER callvaultSymbolagain.launch-dtf -
Keep the transaction signature: after a successful on-chain submission, store the signature and reuse it for allretries. This is the link between the on-chain vault and the database record.
dtf-create
launch-dtfdistribute-feesVersionedTransactiontypescript
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);金库部署的关键错误处理规则:
-
交易签名并提交到链上后,切勿再次调用。 链上金库创建是不可逆的,且会消耗USDC。如果
launch-dtf失败,仅使用相同的交易签名、金库名称和符号重试dtf-create。请勿生成新名称/符号或再次调用dtf-create。launch-dtf -
如果失败(在任何链上提交前):您可以调整负载后重试
launch-dtf(修复错误)。launch-dtf -
如果签名/提交失败:您可以重试以获取新的未签名交易(之前的交易从未提交,因此无链上成本)。
launch-dtf -
如果失败(链上提交成功后):仅使用完全相同的
dtf-create、transactionSignature和vaultName重试vaultSymbol。绝不要更改这些值。绝不要再次调用dtf-create。launch-dtf -
保留交易签名:链上提交成功后,存储签名并在所有重试中复用。这是链上金库与数据库记录之间的链接。
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 (admin wallet executes behind the scenes)
POST {DFM_API_URL}/api/v2/agent/dtf/:symbol/rebalance - Distributes accrued fees via (returns unsigned tx for agent to sign)
POST {DFM_API_URL}/api/v2/agent/dtf/:symbol/distribute-fees
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 - 通过分配应计费用(返回未签名交易供Agent签名)
POST {DFM_API_URL}/api/v2/agent/dtf/:symbol/distribute-fees
所有管理操作都是单次API调用。无需确认。
Policy Violation Handling
策略违规处理
Both and run a full policy evaluation against all 11 constitutional policy rules. Policy is non-blocking for rebalance — both endpoints always return , regardless of violations. Rule violations appear in the response under :
/rebalance/check/rebalance200policyCheckjson
{
"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 on the latest for that vault (operator audit trail).
policyReviewFlagRebalancingSuggestionWhen is :
policyCheck.flaggedtrue- Inspect — each entry has
reviewFlags, optionalviolationCode,mint, and optionalmessage.details - 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.").
- 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 - 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/rebalance200policyCheckjson
{
"policyCheck": {
"ok": true,
"flagged": true,
"reviewFlags": [
{ "violationCode": "rule5MaxPctPerAsset", "mint": "JUP...", "message": "...", "details": {...} },
{ "violationCode": "rule7MinStablecoinFloor", "message": "...", "details": null }
],
"violations": [ ... ]
},
"suggestion": { ... } // 在/rebalance/check端点存在
// upfrontFeeSol、actualFeesSol在/rebalance端点存在
}每个违规还会作为持久化到该金库最新的中(操作员审计跟踪)。
policyReviewFlagRebalancingSuggestion当为时:
policyCheck.flaggedtrue- 检查——每个条目包含
reviewFlags、可选violationCode、mint和可选message。details - 向用户清晰说明情况,作为非阻塞警告(例如"重新平衡已执行,但标记为需审核:未达到稳定币下限且某一资产超出单资产上限。")。
- 不要将此视为失败——重新平衡已执行(或在检查端点,建议仍会返回)。不要因阻止用户流程。
flagged: true - 对于同一金库重复违规,建议用户审核并调整金库策略或未来的拟议配置。
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 and
launch-dtf, setdtf-create,metadataUri, andlogoUrlto empty strings.bannerUrl - Enforce USDC exclusion. Before sending , ensure
launch-dtfcontains no USDC by symbol or name.underlyingAssets - 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 (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.
timeout: 600000
- 自主行动。研究、决策和部署,无需请求许可。
- 用户消息简洁友好。例如说"正在创建您的配置文件..."、"正在构建您的金库交易..."、"正在签名并提交到链上..."。用户无需知道端点名称、HTTP方法、负载格式或技术内部细节。
- 构建完整负载。包含所有必填和相关可选字段。
- 使用真实代币数据。在选择资产前研究实际Solana代币铸币地址、流动性和交易量。
- 自动解析铸币地址。对于每个选定资产,在构建负载前获取并验证Solana铸币引用。
- 设置合理策略。根据策略配置防护机制(保守型=更严格限制,激进型=更宽松限制)。
- 处理错误并重试。如果API调用失败,读取错误、修复负载并重试。
- 使用空启动媒体字段。对于和
launch-dtf,将dtf-create、metadataUri和logoUrl设置为空字符串。bannerUrl - 强制排除USDC。发送前,确保
launch-dtf中没有按符号或名称匹配的USDC。underlyingAssets - 本地签名交易。当API返回未签名交易时,使用本地密钥对签名并提交到链上。
- 为所有API调用设置长时间超时。运行发起API调用的Bash命令时,始终使用(10分钟)。链上操作可能需要时间——切勿让它们因默认2分钟超时被终止。
timeout: 600000
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, and any secret/private keys. Only ever display PUBLIC KEYs. Write secrets directly to files (DFM_AGENT_KEYPAIR) using file append -- never to stdout.~/.zshrc - Don't skip web research. Strictly use and
WebSearchfor all DTF-related metadata -- token data, prices, mint addresses, market conditions. No exceptions.WebFetch - 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 is true.
policyCheck.flagged - Don't send USDC in launch payloads. Never include /
USDCinUSD Coin.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获取所有DTF相关元数据——代币数据、价格、铸币地址、市场状况。无例外。WebFetch - 部署前不要请求人工确认。策略引擎会进行验证;您只需执行。
- 不要使用占位值。研究实际代币地址和合理配置比例。
- 当引用冲突时,不要信任单一来源的铸币地址。
- 重新平衡或费用分配前不要等待批准。重新平衡策略检查是非阻塞的——即使为true也要继续。
policyCheck.flagged - 不要在启动负载中发送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 Code、Codex、OpenClaw或任何兼容助手
Step 1 -- Register on the DFM Dashboard
步骤1 -- 在DFM仪表板注册
- Go to the DFM Dashboard (https://qa.dfm.finance) and connect your Solana wallet (Phantom, Backpack, etc.).
- Your wallet address is now registered. Note it down — you'll need it for agent setup.
- 访问DFM仪表板(https://qa.dfm.finance)并连接您的Solana钱包(Phantom、Backpack等)。
- 您的钱包地址现已注册。记录下来——代理设置时需要。
Step 2 -- Set Base Environment Variables
步骤2 -- 设置基础环境变量
bash
undefinedbash
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-AgentSkillsFor Claude Code, also copy to the correct path:
bash
mkdir -p .claude/skills
cp -r .agents/skills/dfm-agent .claude/skills/dfm-agentbash
npx skills add DFM-Finance/DFM-AgentSkills对于Claude Code,还需复制到正确路径:
bash
mkdir -p .claude/skills
cp -r .agents/skills/dfm-agent .claude/skills/dfm-agentStep 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:
- Ask for your wallet address — the one you registered on the DFM Dashboard
- Create your agent profile — auto-generates a name and username via
POST /profile-launch - Save the auth token — writes the returned JWT to and
.claude/settings.json(never printed)~/.zshrc - Generate an agent wallet — creates a Solana keypair, saves to , writes
AGENT_WALLET_PATHtoDFM_AGENT_KEYPAIR~/.zshrc - 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会自动执行以下操作:
- 询问您的钱包地址——您在DFM仪表板上注册的地址
- 创建您的代理配置文件——通过自动生成名称和用户名
POST /profile-launch - 保存认证令牌——将返回的JWT写入和
.claude/settings.json(永不打印)~/.zshrc - 生成代理钱包——创建Solana密钥对,保存到,将
AGENT_WALLET_PATH写入DFM_AGENT_KEYPAIR~/.zshrc - 仅报告公钥——绝不显示密钥或令牌
完成此一次性设置后,重启Claude Code即可开始使用。
安全提示: 密钥和认证令牌永远不会在终端输出中显示。它们会被直接写入权限受限的文件。
Auth
认证
All endpoints are at and require .
{DFM_API_URL}/api/v2/agent/...Authorization: Bearer <DFM_AUTH_TOKEN>Endpoints marked [Public] bypass JWT authentication — including which is used to create the agent profile and obtain the token in the first place.
profile-launchOn-chain operations (, ) return unsigned transactions that the agent signs locally with the keypair from . Rebalancing is executed server-side by the admin wallet.
launch-dtfdistribute-feesDFM_AGENT_KEYPAIRThe auth token is obtained automatically during first use via — the user only needs to provide their DFM-registered wallet address.
POST /profile-launch所有端点位于,需要。
{DFM_API_URL}/api/v2/agent/...Authorization: Bearer <DFM_AUTH_TOKEN>标记为**[公开]**的端点绕过JWT认证——包括,用于创建代理配置文件并首次获取令牌。
profile-launch链上操作(、)返回未签名交易,Agent使用中的密钥对在本地签名。重新平衡由管理员钱包在服务器端执行。
launch-dtfdistribute-feesDFM_AGENT_KEYPAIR认证令牌会在首次使用时通过自动获取——用户只需提供其DFM注册钱包地址。
POST /profile-launchAgent Wallet -- Keypair Generation
代理钱包——密钥对生成
Wallet path resolution
钱包路径解析
- env variable
AGENT_WALLET_PATH - env variable
SOLANA_KEYPAIR_PATH - env variable
WALLET_OUTPUT_PATH - Default:
<project-root>/solana-keypair/keypair.json
- 环境变量
AGENT_WALLET_PATH - 环境变量
SOLANA_KEYPAIR_PATH - 环境变量
WALLET_OUTPUT_PATH - 默认:
<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 silentlyCRITICAL: The is written directly to via . It must NEVER be printed to stdout, logged, or displayed in any terminal output. Only the public key is shown to the user.
base58Secret~/.zshrcfs.appendFileSynctypescript
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关键提示: 通过直接写入。绝不能打印到标准输出、记录或在任何终端输出中显示。仅向用户显示公钥。
base58Secretfs.appendFileSync~/.zshrcDeriving 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 bodiestypescript
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请求体中使用signerPublicKeyAPI Quick Reference
API快速参考
For full request/response schemas, seereferences/api-reference.md
| Action | Method | Endpoint | Auth | Body |
|---|---|---|---|---|
| Launch agent profile | | | No | |
| Build vault tx | | | JWT | |
| Create DTF (policy + DB) | | | JWT | |
| My vaults | | | JWT | - |
| Vault state | | | JWT | - |
| Vault policy | | | JWT | - |
| Rebalance check | | | JWT | - |
| Rebalance | | | JWT | |
| Build distribute fees tx | | | JWT | |
| Revoke token | | | JWT | - |
| Refresh token (by profileId) | | | No | |
| Refresh token (by wallet) | | | No | |
完整请求/响应架构请参阅references/api-reference.md
| 操作 | 方法 | 端点 | 认证 | 请求体 |
|---|---|---|---|---|
| 启动代理配置文件 | | | 否 | |
| 构建金库交易 | | | JWT | |
| 创建DTF(策略+数据库) | | | JWT | |
| 我的金库 | | | JWT | - |
| 金库状态 | | | JWT | - |
| 金库策略 | | | JWT | - |
| 重新平衡检查 | | | JWT | - |
| 重新平衡 | | | JWT | |
| 构建费用分配交易 | | | JWT | |
| 吊销令牌 | | | JWT | - |
| 刷新令牌(通过profileId) | | | 否 | |
| 刷新令牌(通过钱包) | | | 否 | |
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 and
launch-dtf, always senddtf-create,metadataUri: "", andlogoUrl: "".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 say | What the agent does |
|---|---|
| Asks for wallet address, creates agent profile via |
| Researches top SOL tokens, picks name/symbol/allocations/policy, builds tx, signs, submits, creates policy |
| Finds trending meme tokens, builds diversified allocation, sets policy, deploys |
| |
| Checks policy, triggers server-side rebalance if approved |
| |
| Creates keypair, saves to file, writes env var, reports public key only |
| 您的指令 | Agent执行的操作 |
|---|---|
| 询问钱包地址,通过 |
| 研究顶级SOL代币,选择名称/符号/配置/策略,构建交易,签名,提交,创建策略 |
| 查找热门模因代币,构建多元化配置,设置策略,部署 |
| |
| 检查策略,若批准则触发服务器端重新平衡 |
| |
| 创建密钥对,保存到文件,写入环境变量,仅报告公钥 |
Troubleshooting
故障排除
| Problem | Fix |
|---|---|
| "Unauthorized" errors | Use the token refresh script in the Pre-flight section ( |
| "Keypair file not found" | Re-generate wallet (Step 4). Check: |
| "No signer keypair" / empty DFM_AGENT_KEYPAIR | |
| Transaction fails on-chain | Agent Wallet needs SOL for tx fees + USDC for vault creation fee. Fund the wallet first. |
Policy | Rebalance is non-blocking — the operation already proceeded. Inspect |
| Token revoked unexpectedly | Tokens are only invalidated by an explicit |
| 409 Conflict on dtf-create | A policy already exists for this vault name/symbol. Use a unique name and symbol. |
| 409 "Username is already taken" on profile-launch | The |
| 409 "An agent profile already exists for this wallet address" on profile-launch | The wallet has already been onboarded — do NOT call |
| 问题 | 解决方法 |
|---|---|
| "Unauthorized"错误 | 使用飞行前部分的令牌刷新脚本( |
| "Keypair file not found" | 重新生成钱包(步骤4)。检查: |
| "No signer keypair" / DFM_AGENT_KEYPAIR为空 | |
| 链上交易失败 | 代理钱包需要SOL支付交易费用 + USDC支付金库创建费用。先为钱包充值。 |
重新平衡时策略 | 重新平衡是非阻塞的——操作已执行。检查 |
| 令牌意外吊销 | 令牌仅会因显式 |
| dtf-create时409 Conflict | 此金库名称/符号的策略已存在。使用唯一名称和符号。 |
| profile-launch时409 "Username is already taken" | |
| profile-launch时409 "An agent profile already exists for this wallet address" | 该钱包已完成注册——不要再次调用 |
Security
安全
- NEVER display sensitive values in terminal output. This includes ,
DFM_AUTH_TOKEN, and any secret/private keys. Only public keys may be printed. Secrets are written silently to files.DFM_AGENT_KEYPAIR - Agent Wallet file -- permissions, gitignored, never committed.
0o600 - -- base58 secret key in env only, never in code, git, or terminal output.
DFM_AGENT_KEYPAIR - -- JWT token in env only, never printed or logged in terminal.
DFM_AUTH_TOKEN - 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 - 代理钱包文件 -- 权限为,已加入git忽略,绝不提交。
0o600 - -- 仅在环境变量中存储base58格式密钥,绝不放入代码、git或终端输出。
DFM_AGENT_KEYPAIR - -- 仅在环境变量中存储JWT令牌,绝不打印或记录到终端。
DFM_AUTH_TOKEN - 不向后端发送密钥 -- API负载中仅包含公钥。交易签名在本地进行。
- 代理钱包 = 链上权限主体 -- 像对待任何加密钱包一样对待它。安全备份。
- 策略引擎 = 安全网 -- 即使完全自主的Agent也无法绕过策略约束。