閱讀時間 24 分鐘
概覽
在 Solana 上,熱門資產(如比特幣或 xStocks)以多個相互競爭的鑄造版本形式存在,這些版本擁有不同的發行方、流動性池及信任等級。本指南將引導您使用Tokens API,為任何代幣找出最佳的鑄造版本,比較每個鏈上版本的流動性與風險,並在做出決定前查閱即時市場狀況。
注意:本指南僅供教育用途。Quicknode 並不提供財務建議,亦不背書任何交易策略。在做出任何投資決策前,請務必自行進行研究。
- 開發一個 TypeScript CLI,透過 Assets API 查詢任何 Solana 資產,並接受代碼符號或原始鑄造地址作為輸入
- 將資產解析為其標準識別碼,然後透過單次呼叫,列出所有鏈上變體及其首選的去中心化交易所(DEX)市場,請呼叫
主要市場變體 - 對各變體進行評分與排名,篩選出交易活躍的池,並為每個池評定風險等級
您將負責的工作內容
- 建立一個 TypeScript 專案,並透過 Tokens.xyz Assets API 進行身份驗證
- 將符號或 Mint 地址解析為標準資產
- 根據流動性、交易量及風險,列出並比較該資產的所有鏈上變體
- 根據執行品質分數對變體進行排序,並推薦最佳的鑄造方案
- 讀取即時市場數據,以產生「進場」或「觀望」的時機訊號
您需要準備的物品
本指南假設讀者對 TypeScript、REST API 以及 Solana 代幣(鑄造地址、SPL 代幣和 DEX 流動性池)已有基礎了解。
您還需要:
- Node.jsv22 及以上版本
- 一個 Tokens API 金鑰。請前往 (https://app.tokens.xyz/) 登入以建立一個
- (可選)一個具備 Solana 主網端點的Quicknode 帳戶,用以擴展此工具以執行交易
本指南使用以下依賴項:
| 依賴關係 | 版本 |
|---|---|
| 粉筆 | 5.3.0 |
| tsx | 4.19.0 |
| TypeScript | 5.5.0 |
什麼是 Tokens API?
Tokens.xyz(「Solana 上的代幣」)是一個專為 Solana 鏈上資產設計的代幣搜尋與流動性聚合平台,由 Solana 基金會作為公共利益項目所建置並維護。該平台涵蓋約 300 種標準資產,包括包裝代幣、代幣化股票、ETF、貴金屬及大宗商品,並針對每種資產的每個鏈上表示形式,提供即時價格、成交量、流動性及風險數據。
在撰寫任何程式碼之前,必須先理解的核心概念是「標準資產」與「變體」之間的差異:
- A 標準資產 具有類似以下格式的簡潔網址:
比特幣,usd,或特斯拉. 它代表了底層的概念,無論該概念在鏈上以何種形式存在。 - A 變體 是一種特定的鏈上鑄造,由一個 Solana 代幣地址來識別。一個標準資產可以有許多變體。例如,
usd分別解析為 USDC、USDT 和 PYUSD,每種都標記有其種類(例如原生,包裹,已建立連結,或穩定幣),一個流動性層級(tier1是流動性最高的,降至tier3),一個trustTier,以及一個發行人.
正是這個模型,才使得本指南中的比較成為可能。與其將 WBTC 和 cbBTC 視為兩個互不相關的地址,您只需查詢一次比特幣,便能獲得在 Solana 上所有持有該比特幣的方式,且這些方式已預先分組並分類。
設定專案
建立一個專案目錄並進行初始化:
mkdir tokens-research && cd tokens-research
npm 初始化 -y
npm 安裝 chalk
npm 安裝 -D tsx TypeScript @types/node
集合 "type": "module" 在 package.json 因此,ES 模組的導入能正確解析:
npm pkg set 類型=模組
建立一個 .env 在專案根目錄中建立一個檔案,並填入您的 API 金鑰:
TOKENS_API_KEY=請在此處填入您的 API 金鑰
新增一個 .gitignore 檔案,因此 API 金鑰位於 .env 絕不會意外地提交:
node_modules/
.env
建立一個名為 research.ts. 首先從進口、鍵值與參數檢查開始,以及一個小型 取得 一個用於附加該 x-api-key 在每個請求中加入標頭。該腳本接受一個參數,可以是類似 BTC 或一個未經處理的 Solana 鑄造地址。
import chalk from 'chalk';
const API_KEY = process.env.TOKENS_API_KEY;
if (!API_KEY) { console.error(chalk.red('TOKENS_API_KEY is not set')); process.exit(1); }
const SYMBOL = process.argv[2];
if (!SYMBOL) { console.error(chalk.red('Usage: tsx research.ts <symbol> e.g. tsx --env-file=.env research.ts BTC')); process.exit(1); }
const BASE = 'https://api.tokens.xyz/v1';
async function get<T = any>(path: string): Promise<T> {
const res = await fetch(`${BASE}${path}`, { headers: { 'x-api-key': API_KEY! } });
if (!res.ok) throw new Error(`${res.status} ${path}`);
return res.json() as T;
}
接著,在 取得 函式。這些函式可確保終端機的輸出內容易於閱讀: usd 縮寫美元金額, 前 將 Unix 時間戳轉換為相對時間, 章節 印出一段落標題,並 tierColor 以及 gradeColor 依品質劃分的顏色值。
// helpers
function usd(value: number | undefined | null): string {
if (value == null) return 'n/a';
if (value >= 1e9) return `$${(value / 1e9).toFixed(2)}B`;
if (value >= 1e6) return `$${(value / 1e6).toFixed(2)}M`;
if (value >= 1e3) return `$${(value / 1e3).toFixed(1)}K`;
return `$${value.toFixed(2)}`;
}
function ago(timestamp: number | undefined | null): string {
if (!timestamp) return 'unknown';
const seconds = Math.floor(Date.now() / 1000) - timestamp;
return seconds < 60 ? `${seconds}s ago` : seconds < 3600 ? `${Math.floor(seconds / 60)}m ago` : `${Math.floor(seconds / 3600)}h ago`;
}
function section(title: string) {
console.log('\n' + chalk.white.bold('━'.repeat(62)));
console.log(chalk.white.bold(` ${title}`));
console.log(chalk.white.bold('━'.repeat(62)) + '\n');
}
function tierColor(tier: string | undefined, text: string): string {
if (tier === 'tier1') return chalk.green(text);
if (tier === 'tier2') return chalk.yellow(text);
return chalk.red(text);
}
function gradeColor(grade: string | undefined): string {
if (!grade) return chalk.gray('n/a');
if (['A+', 'A', 'B+'].includes(grade)) return chalk.green(grade);
if (grade === 'B') return chalk.yellow(grade);
return chalk.red(grade);
}
腳本的其餘部分位於一個 主頁 函式。以下各節會分別在 主頁,依序進行。現在就啟動這個函式,並邊執行邊填入內容:
// main
async function main() {
// the code blocks in the next sections go here, in order
}
main().catch(err => { console.error(chalk.red(err.message)); process.exit(1); });
解析規範資產
一切都始於一個規範的 assetId. 此腳本可接受任一類型的輸入,因此會先檢查該參數是否類似於 Base58 格式的 Solana 鑄造地址。若是,則會呼叫 決心 將該「薄荷幣」映射至其標準資產。否則,它會呼叫 搜尋 根據名稱或代碼查詢該資產。
GET /assets/resolve?mint={address}將原始的「mint」映射至其規範形式assetId並回傳該資產的名稱以及類別.GET /assets/search?q={symbol}將名稱或代碼解析為標準資產,並回傳最符合的結果。
將此內容新增為其中的第一個區塊 主頁:
// Resolve canonical asset (accepts a symbol/name or a raw Solana mint address)
const isMint = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(SYMBOL);
let assetId: string, name: string, category: string;
if (isMint) {
const resolved = await get<any>(`/assets/resolve?mint=${SYMBOL}`);
if (!resolved?.assetId) { console.error(chalk.red(`Could not resolve mint "${SYMBOL}"`)); process.exit(1); }
assetId = resolved.assetId;
name = resolved.asset?.name ?? assetId;
category = resolved.asset?.category ?? 'crypto';
} else {
const search = await get(`/assets/search?q=${encodeURIComponent(SYMBOL)}&limit=3`);
const hit = search.results?.[0];
if (!hit) { console.error(chalk.red(`No results for "${SYMBOL}"`)); process.exit(1); }
({ assetId, name, category } = hit);
}
console.log(chalk.bold.white(`\n Tokens API — ${name} on Solana\n`));
此區塊執行完畢後,您將獲得一個 assetId (標準網址),一個易於閱讀的 名稱,以及一個 類別 (例如 加密貨幣, 穩定幣,或 股權) 供後續步驟重複使用。
探索鏈上期權
這一步驟揭露了碎片化問題。單次呼叫 主要市場變體 會返回標準資產的所有鏈上變體,並在同一則回應中附上每個變體在 DEX 上的最佳市場資訊。這一次呼叫便取代了原本需要發送的三到四次獨立請求,包括變體清單、市場資料及流動性池地址等。
GET /assets/{assetId}/variant-top-markets 各變體的回傳值如下:
薄荷:Solana 代幣地址符號,名稱,標籤: 身分識別欄位種類,流動性層級,trustTier,發行人: 分類topMarket.流動性,topMarket.volume24h: 泳池深度與24小時水體體積topMarket.source: DEX 名稱topMarket.name: 交易對topMarket.address: 池位址topMarket.trade24h,topMarket.uniqueWallet24h: 活動次數
將下一個區塊加入其中 主頁. 它會先擷取各種變體,接著預先為其中最多 10 種變體擷取風險等級(用於下一節),最後依流動性層級對清單進行排序,並在每個層級內再依流動性進行排序:
// Fetch variants and risk scores before displaying so the sort has all data
const topMarketsRes = await get(`/assets/${assetId}/variant-top-markets?limit=50`);
const rawVariants: any[] = topMarketsRes.variants ?? [];
if (!rawVariants.length) { console.error(chalk.red('No onchain variants found')); process.exit(1); }
type Risk = { score: number; grade: string; label: string; isTrustedLaunch: boolean; caps: any[] };
const riskMap = new Map<string, Risk>();
for (const variant of rawVariants.slice(0, 10)) {
try {
const risk = await get<any>(`/assets/risk-summary?mint=${variant.mint}`);
riskMap.set(variant.mint, risk);
} catch { /* no risk data for this variant */ }
}
const TIER_ORDER: Record<string, number> = { tier1: 0, tier2: 1, tier3: 2 };
const variants: any[] = [...rawVariants].sort((a, b) => {
const tierDiff = (TIER_ORDER[a.liquidityTier] ?? 3) - (TIER_ORDER[b.liquidityTier] ?? 3);
if (tierDiff !== 0) return tierDiff;
return (b.topMarket?.liquidity ?? 0) - (a.topMarket?.liquidity ?? 0);
});
現在將這些變體以表格形式列印出來。該 營業額 「欄位」(24 小時成交量除以流動性)是判斷哪些交易池實際上正在被使用的第一個線索。若某個層級的成交量接近於零,這便是一個警示訊號,你將在下一步採取相應行動。
// STEP 1
section('STEP 1 — Discover Onchain Options');
console.log(` ${chalk.white(variants.length + ' onchain representation' + (variants.length !== 1 ? 's' : '') + ' of ' + name)}\n`);
const COL = [4, 10, 22, 8, 12, 12, 10, 0];
const head = ['#', 'Symbol', 'Issuer', 'Tier', 'Liquidity', 'Vol 24h', 'Turnover', 'Top DEX'];
console.log(' ' + head.map((h, i) => chalk.cyan(COL[i] ? h.padEnd(COL[i]) : h)).join(''));
console.log(' ' + '─'.repeat(92));
variants.forEach((variant, index) => {
const topMarket = variant.topMarket;
const liquidity = topMarket?.liquidity ?? 0;
const volume24h = topMarket?.volume24h ?? 0;
const turnover = liquidity > 0 ? volume24h / liquidity : 0;
const turnoverColor = (text: string) => turnover > 5 ? chalk.red(text) : turnover > 3 ? chalk.yellow(text) : chalk.green(text);
const tier = (variant.liquidityTier ?? 'n/a').padEnd(8);
console.log(
' ' +
String(index + 1).padEnd(4) +
(variant.symbol ?? 'n/a').padEnd(10) +
(variant.label ?? variant.issuer ?? 'unknown').slice(0, 20).padEnd(22) +
tierColor(variant.liquidityTier, tier) +
usd(liquidity).padEnd(12) +
usd(volume24h).padEnd(12) +
turnoverColor(`${turnover.toFixed(1)}x`.padEnd(10)) +
(topMarket?.source ?? 'n/a')
);
});
比較不同版本
這正是 API 整合後的數據展現獨特價值之處。該腳本會列出每個變體的完整分析詳情,包括其風險等級,接著對其進行排名並推薦其中一個。
此步驟由三個終點提供資料:
GET /assets/risk-summary?mint={mint}(已在上方取得)會傳回一個分數(0 至 100,數值越高越安全),一個字母年級, 一個人標籤, 一個isTrustedLaunch旗幟,以及一個caps一系列已知的風險因素,每個因素都有一個語氣的成功,警告,或危險.GET /assets/{assetId}/risk-details?mint={mint}新增了按因子分類的明細,其中包含每個因子的值以及基準測試,這樣你就能說明該作業為何獲得該分數,而不只是單純報告分數。GET /assets/{assetId}/profile回傳標準資產的外部檔案(網站,Twitter,市值,供應).
首先,列印各變體的詳細資訊。將此區塊加入其中 主頁:
// STEP 2
section('STEP 2 — Compare & Select');
for (const variant of variants) {
const topMarket = variant.topMarket;
const risk = riskMap.get(variant.mint);
const liquidity = topMarket?.liquidity ?? 0;
const volume24h = topMarket?.volume24h ?? 0;
const turnover = liquidity > 0 ? volume24h / liquidity : 0;
const turnoverColor = (text: string) => turnover > 5 ? chalk.red(text) : turnover > 3 ? chalk.yellow(text) : chalk.green(text);
const tierLabel = variant.liquidityTier ?? 'n/a';
console.log(` ${chalk.white.bold(variant.symbol ?? variant.mint.slice(0, 8))} ${chalk.gray(variant.label ?? '')} ${tierColor(variant.liquidityTier, tierLabel)}`);
console.log(` ${'Issuer:'.padEnd(18)} ${variant.issuer ?? variant.label ?? 'unknown'}`);
console.log(` ${'Mint:'.padEnd(18)} ${chalk.gray(variant.mint)}`);
console.log(` ${'Liquidity:'.padEnd(18)} ${usd(liquidity)}`);
console.log(` ${'Vol 24h:'.padEnd(18)} ${usd(volume24h)}`);
console.log(` ${'Turnover:'.padEnd(18)} ${turnoverColor(`${turnover.toFixed(1)}x`)} ${turnover > 5 ? chalk.red('pool under stress') : turnover < 0.1 ? chalk.yellow('very low activity') : chalk.green('healthy')}`);
if (topMarket?.trade24h != null) console.log(` ${'Trades 24h:'.padEnd(18)} ${topMarket.trade24h.toLocaleString()}`);
if (topMarket?.uniqueWallet24h != null) console.log(` ${'Wallets 24h:'.padEnd(18)} ${topMarket.uniqueWallet24h.toLocaleString()}`);
if (risk) {
console.log(` ${'Risk:'.padEnd(18)} ${gradeColor(risk.grade)} score ${risk.score}/100 ${risk.isTrustedLaunch ? chalk.green('✓ trusted launch') : chalk.yellow('unverified')}`);
for (const cap of risk.caps ?? []) {
const capColor = cap.tone === 'success' ? chalk.green : cap.tone === 'warning' ? chalk.yellow : chalk.red;
console.log(` ${''.padEnd(20)}${capColor('▸')} ${cap.name}: ${capColor(cap.value)}`);
}
} else {
console.log(` ${'Risk:'.padEnd(18)} ${chalk.gray('n/a')}`);
}
console.log(` ${'Top DEX:'.padEnd(18)} ${topMarket?.source ?? 'n/a'} ${topMarket?.name ? chalk.gray(`(${topMarket.name})`) : ''}`);
console.log();
}
現在請選擇最佳選項。僅看原始流動性是會產生誤導的。該腳本會計算一個 執行分數 的 流動性 × min(周轉率, 3), 其中 成交量 = 24小時交易量 / 流動性. 將交易量上限設為 3,可避免一個規模微小、交易異常頻繁的交易池排名高於真正深度充足的交易池,同時仍能獎勵那些實際擁有交易量的交易池。
這為何重要?以下是 API 提供的實際數據範例:
- Solana 上的 WBTC:流動性達 7,591 萬美元,但 24 小時交易量僅有 0.44 美元(一筆交易,一個錢包)。執行分數約為 0.44。
- Solana 上的 cbBTC:流動性達 545 萬美元,24 小時交易量為 1,763 萬美元(周轉率 3.2 倍,767 個錢包)。執行評分為約 1,635 萬。
儘管 cbBTC 的原始流動性僅為 WBTC 的 1/14,其表現卻勝出約 3,700 萬倍。在此情況下,WBTC 雖在帳面上擁有流動性,但實際上無人交易,這意味著其定價滯後且點差過寬。若僅憑流動性的表面數字,本應指向 WBTC 是較差的發行方案。因為 API 返回的 流動性, 第24卷, trade24h,以及 uniqueWallet24h 合在一起,你就能一眼看穿它。
新增推薦區塊,該區塊會根據執行分數進行排序,並將獲勝的投資組合連結至 DexScreener:
// Recommendation: highest exec score (liquidity weighted by real turnover)
const execScore = (variant: any) => {
const liquidity = variant.topMarket?.liquidity ?? 0;
const volume24h = variant.topMarket?.volume24h ?? 0;
const turnover = liquidity > 0 ? volume24h / liquidity : 0;
return liquidity * Math.min(turnover, 3);
};
const ranked = [...variants].sort((a, b) => execScore(b) - execScore(a));
const best = ranked[0];
const runner = ranked[1];
const bestRisk = riskMap.get(best.mint);
const liqRatio = runner && (runner.topMarket?.liquidity ?? 0) > 0
? Math.round((best.topMarket?.liquidity ?? 0) / (runner.topMarket?.liquidity ?? 0))
: null;
console.log(chalk.green.bold(` ✓ Recommendation: ${best.symbol ?? best.mint.slice(0, 8)}`));
if (liqRatio && liqRatio > 1) {
const riskNote = bestRisk ? ` and ${bestRisk.grade ?? 'n/a'} risk grade` : '';
console.log(` Reason: ${liqRatio}x more liquidity than the next option${riskNote}`);
}
console.log(` DEX: ${best.topMarket?.source ?? 'n/a'}`);
if (best.topMarket?.address) {
console.log(` Pool: ${chalk.cyan(`https://dexscreener.com/solana/${best.topMarket.address}`)}`);
}
const chosenMint: string = best.mint;
最後,請調閱該推薦鑄幣廠的詳細風險分析與資產概況。風險分析會逐項說明評級因素,而資產概況則提供外部背景資訊。
// Risk details for the recommended variant (explains why it got its grade)
try {
const detailsRes = await get<any>(`/assets/${assetId}/risk-details?mint=${chosenMint}`);
const factors: any[] = detailsRes.factors ?? detailsRes.data?.factors ?? [];
if (factors.length) {
console.log(`\n ${chalk.yellow('Risk factor breakdown')} ${chalk.gray('(why it got this grade)')}`);
for (const factor of factors) {
const factorColor = (factor.score ?? 0) >= 70 ? chalk.green : (factor.score ?? 0) >= 40 ? chalk.yellow : chalk.red;
const benchmark = factor.benchmark != null ? chalk.gray(` · benchmark: ${factor.benchmark}`) : '';
const value = factor.value != null ? chalk.gray(` · value: ${factor.value}`) : '';
console.log(` ${factorColor((factor.name ?? '').padEnd(22))} score: ${factorColor(String(factor.score ?? 'n/a'))}${value}${benchmark}`);
if (factor.description) console.log(` ${''.padEnd(22)} ${chalk.gray(factor.description)}`);
}
}
} catch { console.log(chalk.yellow(' Risk factor breakdown not available')); }
// Profile (canonical asset). Description is intentionally skipped: for tokenized
// equities it is issuer copy, not a description of the underlying company.
try {
const profileRes = await get<any>(`/assets/${assetId}/profile`);
const profile = profileRes.profile?.ok !== false ? (profileRes.profile?.data ?? null) : null;
if (profile) {
console.log(`\n ${chalk.yellow('Asset profile')}`);
if (profile.website) console.log(` ${'Website:'.padEnd(14)} ${profile.website}`);
if (profile.twitter) console.log(` ${'Twitter:'.padEnd(14)} ${profile.twitter}`);
if (profile.marketCap) console.log(` ${'Market cap:'.padEnd(14)} ${usd(profile.marketCap)}`);
if (profile.supply) console.log(` ${'Supply:'.padEnd(14)} ${Number(profile.supply).toLocaleString()}`);
}
} catch { console.log(chalk.yellow(' Asset profile not available')); }
查看市場狀況
該建議會告訴您該使用哪個 Mint。這個最後步驟則會告訴您,現在是否是使用它的好時機。它會讀取所選 Mint 的即時快照、評估其新穎程度、測量交易量增長速度、檢查參與度,並與熱門清單進行交叉比對,最後才給出結論。
有兩個終點驅動它:
GET /assets/{assetId}/variant-market?mint={mint}回傳一個即時快取快照:價格,1小時價格變動百分比,24小時價格變動百分比,lastTradeAt(Unix 秒),以及一個指標來源. 當指標來源是clickhouse_trades, 您還會獲得分區的音量和計數(500萬美元,volume1hUSD,24小時交易量(美元),trade24h,uniqueWallet24h, 等等)。當來源為其他類型時,您僅能取得 1 小時與 24 小時的細分資料,因此腳本會據此進行回退處理。GET /assets/trending?category={category}&limit=50這是一項預先建置的動量綜合排名,根據短期成交量加速率與交易新鮮度對鑄幣廠進行排名。其作用在於進行交叉核對。
首先取得快照並套用新鮮度門控。若最後一筆交易距今已超過 10 分鐘,則該池已過期,判定結果為「WAIT」,因為後續所有時序訊號都將基於過期資料進行計算。將此區塊加入其中 主頁:
// STEP 3
section('STEP 3 — Check Market Conditions');
let snapshot: any;
try {
const snapshotRes = await get<any>(`/assets/${assetId}/variant-market?mint=${chosenMint}`);
snapshot = snapshotRes.market ?? snapshotRes;
} catch (err: any) {
console.log(chalk.red(` Could not fetch snapshot: ${err.message}`));
return;
}
const ageSeconds = snapshot.lastTradeAt ? Math.floor(Date.now() / 1000) - snapshot.lastTradeAt : Infinity;
const fresh = ageSeconds < 600;
console.log(` ${chalk.white.bold(best.symbol ?? chosenMint.slice(0, 8))} ${chalk.gray(`${best.topMarket?.source ?? 'n/a'} · ${best.topMarket?.name ?? ''} · last trade ${ago(snapshot.lastTradeAt)}`)}\n`);
console.log(` ${'Price:'.padEnd(18)} $${snapshot.price?.toFixed(4) ?? 'n/a'}`);
if (snapshot.priceChange1hPercent != null) {
const change = snapshot.priceChange1hPercent;
console.log(` ${'1h change:'.padEnd(18)} ${change >= 0 ? chalk.green(`+${change.toFixed(2)}%`) : chalk.red(`${change.toFixed(2)}%`)}`);
}
if (snapshot.priceChange24hPercent != null) {
const change = snapshot.priceChange24hPercent;
console.log(` ${'24h change:'.padEnd(18)} ${change >= 0 ? chalk.green(`+${change.toFixed(2)}%`) : chalk.red(`${change.toFixed(2)}%`)}`);
}
console.log();
if (!fresh) {
console.log(chalk.red(` Pool is stale (last trade ${ago(snapshot.lastTradeAt)}) — timing signals unreliable\n`));
console.log(' ' + '─'.repeat(56));
console.log(` ${chalk.red.bold('Verdict: WAIT')} — pool is inactive`);
console.log(' ' + '─'.repeat(56) + '\n');
return;
}
接下來,測量交易量加速度。其原理是將每個時間區間的數據歸一化為每小時的速率,並進行比較。若將最近 5 分鐘的數據外推至 1 小時(500萬美元 × 12),其成交量遠高於過去一小時的成交量,交易活動正在加速。若比率達到 1.5 倍或以上,即顯示交易活動正在加速;低於 0.5 倍則顯示交易活動正在減弱;介於兩者之間的則視為穩定。當較高的 clickhouse_trades 若無法取得 Windows 資料,腳本將改為將 1 小時配速與 24 小時基準值進行比較。
// Volume acceleration: normalize windows to an hourly rate and compare
let timingSignal = 'unknown';
const hasWindowed = snapshot.metricsSource === 'clickhouse_trades' && snapshot.volume5mUSD != null;
if (hasWindowed) {
const pace5m = snapshot.volume1hUSD > 0 ? (snapshot.volume5mUSD * 12) / snapshot.volume1hUSD : 0;
const pace1h = snapshot.volume24hUSD > 0 ? (snapshot.volume1hUSD * 24) / snapshot.volume24hUSD : 0;
const trendColor = pace5m >= 1.5 ? chalk.green : pace5m < 0.5 ? chalk.red : chalk.yellow;
const label = pace5m >= 1.5 ? '▲ Accelerating' : pace5m < 0.5 ? '▼ Fading' : '→ Steady';
console.log(` ${'Volume trend:'.padEnd(18)} ${trendColor(label)} ${chalk.gray(`(5m pace ${pace5m.toFixed(1)}x 1h avg · 1h pace ${pace1h.toFixed(1)}x daily avg)`)}`);
timingSignal = pace5m >= 1.5 ? 'accelerating' : pace5m < 0.5 ? 'fading' : 'steady';
} else if (snapshot.volume1hUSD != null && snapshot.volume24hUSD > 0) {
const pace1h = (snapshot.volume1hUSD * 24) / snapshot.volume24hUSD;
const trendColor = pace1h >= 1.5 ? chalk.green : pace1h < 0.5 ? chalk.red : chalk.yellow;
const label = pace1h >= 1.5 ? '▲ Accelerating' : pace1h < 0.5 ? '▼ Fading' : '→ Steady';
console.log(` ${'Volume trend:'.padEnd(18)} ${trendColor(label)} ${chalk.gray(`(1h pace ${pace1h.toFixed(1)}x daily avg)`)}`);
timingSignal = pace1h >= 1.5 ? 'accelerating' : pace1h < 0.5 ? 'fading' : 'steady';
} else {
console.log(` ${'Volume trend:'.padEnd(18)} ${chalk.gray('n/a')}`);
}
// Participation: trades per wallet hints at organic vs concentrated activity
let organicSignal = 'unknown';
if (snapshot.trade24h && snapshot.uniqueWallet24h) {
const tradesPerWallet = snapshot.trade24h / snapshot.uniqueWallet24h;
const participationColor = tradesPerWallet <= 5 ? chalk.green : tradesPerWallet <= 15 ? chalk.yellow : chalk.red;
const participationLabel = tradesPerWallet <= 5 ? 'broad' : tradesPerWallet <= 15 ? 'mixed' : 'concentrated';
console.log(` ${'Participation:'.padEnd(18)} ${snapshot.uniqueWallet24h.toLocaleString()} wallets · ${snapshot.trade24h.toLocaleString()} trades ${participationColor(`(${tradesPerWallet.toFixed(1)} trades/wallet, ${participationLabel})`)}`);
organicSignal = participationLabel;
} else if (snapshot.uniqueWallet24h) {
console.log(` ${'Participation:'.padEnd(18)} ${snapshot.uniqueWallet24h.toLocaleString()} unique wallets (24h)`);
}
參與度指標是將 24 小時內的交易筆數除以 24 小時內的獨特錢包數量。若比率偏低(每個錢包平均 5 筆交易或更少),則顯示交易活動廣泛且自然;若比率偏高(超過 15 筆),則表示多數交易是由少數幾個地址(可能是機器人)所進行。
最後,與熱門清單進行交叉比對,並綜合所有資訊得出結論。若該池子屬於新興、未趨於冷卻且未過度集中,則結果為「進場」;否則則為「觀望」,且必須始終附上具體理由。
// Trending cross-check: is the asset in the top 50 for its category?
let trendRank: number | null = null;
try {
const trendRes = await get<any>(`/assets/trending?category=${category}&limit=50`);
const found = trendRes.trending?.find((item: any) => item.assetId === assetId || item.mint === chosenMint);
if (found) {
trendRank = found.rank;
const rankColor = found.rank <= 10 ? chalk.green : found.rank <= 25 ? chalk.yellow : chalk.white;
console.log(` ${'Trending:'.padEnd(18)} ${rankColor(`#${found.rank}`)} ${chalk.gray(`score ${found.trending?.score?.toFixed(1) ?? 'n/a'}`)}`);
} else {
console.log(` ${'Trending:'.padEnd(18)} ${chalk.gray('not in top 50')}`);
}
} catch { console.log(` ${'Trending:'.padEnd(18)} ${chalk.yellow('not available')}`); }
// Verdict: GO if fresh + not fading + not concentrated
const go = fresh && timingSignal !== 'fading' && organicSignal !== 'concentrated';
const reason =
timingSignal === 'accelerating' && organicSignal === 'broad' ? 'strong momentum with broad participation' :
timingSignal === 'accelerating' ? 'volume accelerating; verify participation before entering' :
timingSignal === 'fading' ? 'activity is declining; wait for a better window' :
organicSignal === 'concentrated' ? 'trade activity concentrated; possible bots' :
trendRank != null && trendRank <= 10 ? 'trending token, active market' :
'market is active with no red flags';
console.log('\n ' + '─'.repeat(56));
console.log(` ${go ? chalk.green.bold('Verdict: GO') : chalk.red.bold('Verdict: WAIT')} — ${reason}`);
console.log(' ' + '─'.repeat(56) + '\n');
這就結束了 主頁. 請確保最終的 main().catch(...) 「setup」區段中的那行仍位於檔案底部。
執行腳本
請輸入股票代碼來執行此工具。該 --env-file flag 會載入您的 .env 無需安裝任何額外套件:
npx tsx --env-file=.env research.ts BTC
或者直接傳入一個原始的 Mint 地址,腳本會先將其解析為對應的標準資產:
npx tsx --env-file=.env research.ts cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij
您將看到這三個步驟依序顯示:鏈上選項清單、針對各變體的比較(附建議及 DexScreener 池連結),以及以「GO」或「WAIT」結論作結的時程分析。
Tokens API — Bitcoin on Solana
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
STEP 1 — Discover Onchain Options
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8 onchain representations of Bitcoin
# Symbol Issuer Tier Liquidity Vol 24h Turnover Top DEX
────────────────────────────────────────────────────────────────────────────────────────────
1 cbBTC cbBTC tier1 $5.54M $17.06M 3.1x Orca
2 WBTC unknown tier2 $75.91M $0.44 0.0x Orca
3 xBTC unknown tier3 $2.22M $504.7K 0.2x Orca
4 zenBTC unknown tier3 $963.3K $39.0K 0.0x Orca
5 zBTC zBTC tier3 $763.2K $63.2K 0.1x Saber
6 tBTC unknown tier3 $143.5K $0.00 0.0x Orca
7 WBTC unknown tier3 $48.2K $6.4K 0.1x Raydium Clamm
8 21BTC unknown tier3 $1.9K $0.00 0.0x Cropper
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
STEP 2 — Compare & Select
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
cbBTC cbBTC tier1
Issuer: cbBTC
Mint: cbbtcf3aa273Sn1dMGVU3ZtLv9yWSyUAanBni19YWDaznnkn
Liquidity: $5.54M
Vol 24h: $17.06M
Turnover: 3.1x healthy
Trades 24h: 4,811
Wallets 24h: 767
Risk: A score 86/100 unverified
Top DEX: Orca (SOL-cbBTC)
... (detail for remaining 7 variants) ...
✓ Recommendation: cbBTC
Reason: 3x more liquidity than the next option and A risk grade
DEX: Orca
Pool: https://dexscreener.com/solana/CeaZcxBNLpJWtxzt58qQmfMBtJY8pQLvursXTJYGQpbN
Asset profile
Market cap: $1251.60B
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
STEP 3 — Check Market Conditions
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
cbBTC Orca · SOL-cbBTC · last trade 17m ago
Price: $63854.1696
1h change: -0.09%
24h change: -3.32%
Pool is stale (last trade 17m ago) — timing signals unreliable
────────────────────────────────────────────────────────
Verdict: WAIT — pool is inactive
────────────────────────────────────────────────────────
總結
您開發了一款研究工具,能將單一代碼或鑄幣地址轉化為實際的決策依據。它能將資產解析為其標準識別碼,透過單次調用即列出所有鏈上變體及其市場資訊,並根據能穿透誤導性流動性數據的執行品質分數對其進行排序,評估其風險等級,並分析即時活動動態,以判斷現在是否是採取行動的好時機。
在 Solana 上交易碎片化資產時,最困難的部分在於如何從多個鑄造選項中決定實際該使用哪一個;而現在,您已擁有一套可重複執行且由 API 支援的工作流程,可在決定使用任何鑄造選項之前,先針對 Solana 代幣交易進行研究。接著,您可以將這些訊號整合到儀表板、投資組合追蹤工具,或是交易機器人的前端介面中。
下一步
範例腳本使用了五個端點,但 Assets API 還提供更多功能。以下是一些擴充此腳本的構想:
- 新增價格走勢圖。 使用
GET /assets/{assetId}/ohlcv?mint={mint}&interval={interval}(間隔自1m至1W) 以渲染推薦變體的K線歷史走勢,或GET /assets/{assetId}/price-chart以獲得標準化的價格檢視,該功能會自動選取最佳的可用來源。 - 批量估算錢包中的資產價值。 將一組 Quicknode Solana 端點 (讀取錢包的代幣帳戶)使用
POST /assets/market-snapshots(每次通話最多 250 顆薄荷糖)至 取得 Solana 錢包所持有的一切代幣帳戶 並在一次往返中,將每枚代幣映射至其標準資產。 - 建立觀察清單。 將其標記為第一號,並
GET /assets/curated(按 24 小時成交量排序,其中groupBy=asset或groupBy=薄荷),然後使用GET /assets/variant-markets?mints={csv}(最多 50 個鑄幣),同時會將每個鑄幣解析為其規範形式assetId. - 比較集中式與去中心化定價。 使用
GET /assets/{assetId}/tickers以擷取交易所層級的行情代碼(交易所、交易對、最新價格、成交量、買價、賣價),並在鏈上數據旁顯示滑點或套利情境。 - 新增重試邏輯。 該
取得腳本中的輔助程式在發生錯誤時不會重新嘗試。在生產環境中,請將fetch在採用帶有抖動的指數退避法的重試迴圈中呼叫(base = 250 * 2 ** attempt毫秒,抖動最高 150 毫秒,四次嘗試),並積極快取回應,因為大多數欄位每隔幾秒才會變更一次。
常見問題
在 Tokens API 中,標準資產與變體之間有何區別?
標準資產是指由簡潔的標籤(如 bitcoin、usd 或 tesla)所識別的基礎概念。變體則是指代表該資產的特定鏈上鑄造(一個 Solana 代幣地址),例如代表比特幣的 WBTC 或 cbBTC。 一個標準資產可以擁有多個變體,每個變體都有其專屬的發行者、流動性層級、信任層級及類型。透過標準模型,您只需查詢一次資產,即可獲得所有在鏈上持有該資產的方式,且這些方式已預先分組整理。
為什麼這個腳本是根據「exec 得分」來對變體進行排名,而不是根據流動性呢?
單看流動性數據可能會產生誤導。一個流動性池即使持有數千萬美元的流動性,卻幾乎沒有交易,這在實際操作中意味著價格滯後且點差過寬。 「執行分數」(流動性乘以 min(周轉率, 3),其中周轉率為 24 小時成交量除以流動性)旨在獎勵實際被使用的流動性池。在測試中,儘管 cbBTC 的原始流動性僅為 WBTC 的 1/14,其執行分數卻比 WBTC 高出約 3,700 萬倍,因為 WBTC 這個深度充足的流動性池在 24 小時內僅有一筆交易。
Tokens.xyz 會執行交易或代管資金嗎?
不。Tokens.xyz 僅具唯讀功能,僅處理發現、定價及風險資料。若要根據建議採取行動(例如讀取錢包餘額、建立交換交易或發送交易),您需使用 Solana RPC 端點,例如 Quicknode 提供的端點。該 API 提供研究資料;執行則透過獨立的基礎設施在鏈上進行。
為什麼有些標記所回傳的時序資料比其他標記更詳盡?
詳細的 5 分鐘及 15 分鐘分窗成交量與持倉數量,僅在「變體市場快照」報告的 metricsSource 為 clickhouse_trades 時才會顯示。若資產來源為 Birdeye 等供應商,則僅能取得 1 小時與 24 小時的細分數據。該腳本會偵測此情況,並改為將 1 小時的走勢與 24 小時的基準線進行比較,因此時間判斷功能仍能正常運作。
資源
我們 ❤️ 您的回饋!
如果您有任何意見回饋或對新主題的建議,請告訴我們。我們非常樂意聽取您的意見。
