Skill: liji3597/solana-market (version 0.1.0)

--- name: solana-market description: 查询 Solana 生态代币实时价格和市场概览。当用户想知道代币价格、行情、市场状况时触发。 version: 1.0.0 metadata: openclaw: requires: bins: ["node"] emoji: "💲" --- # Solana 市场情报 ## When to Use - 用户问代币价格（"SOL 多少钱"） - 用户想看整体市场行情 - 用户提到"价格"、"行情"、"市场"、"多少钱" ## Workflow ### 用户问某个代币的价格 1. 从用户消息中提取代币名称 2. `node skills/solana-market/scripts/get-price.js <SYMBOL>` - 支持的代币：SOL、USDC、USDT、JUP、RAY、BONK 3. 展示价格，并主动提供相关操作建议： - "SOL 目前 $150。需要设置价格警报吗？" - "JUP 目前 $0.82。要看看你持有多少吗？" ### 用户想看市场概览 1. `node skills/solana-market/scripts/market-overview.js` 2. 展示所有代币价格列表 3. 如果用户已连接钱包，可以主动建议："要看看你的持仓在这个行情下的表现吗？" ### 代币不支持时 如果用户查询的代币不在支持列表中，友善告知： > "目前支持查询 SOL、USDC、USDT、JUP、RAY、BONK 的价格。你想查哪个？" ## Guardrails - **不预测走势** — 不说"看起来要涨/跌了" - **数据来源透明** — 可以告知用户"价格来自 CoinGecko" - **不推荐交易** — 展示价格后不建议买入/卖出 - **缓存说明** — 如果用户反复查询相同代币，告知价格有 30 秒缓存 ## Available Scripts | 脚本 | 用途 | 参数 | |------|------|------| | `get-price.js` | 查询价格 | `<SYMBOL> [--lang en]` | | `market-overview.js` | 市场概览 | `[--lang en]` |

path: scripts/check-token-risk.js
content: #!/usr/bin/env node
/**
 * Check token risk report using RugCheck
 * Usage: node check-token-risk.js <token_symbol_or_mint> [--lang en]
 */
const path = require('path');
const sharedDir = path.resolve(__dirname, '..', '..', '..', 'shared');

const { getTokenPrice, resolveToken } = require(path.join(sharedDir, 'price-service'));
const { formatUSD } = require(path.join(sharedDir, 'formatter'));
const { formatError } = require(path.join(sharedDir, 'errors'));

const tokenInput = (process.argv[2] || '').trim();
const lang = process.argv.includes('--lang') ? process.argv[process.argv.indexOf('--lang') + 1] : 'zh';
const isZh = lang !== 'en';

if (!tokenInput) {
    console.log(JSON.stringify({
        code: 'MISSING_PARAMS',
        missing: ['token_symbol'],
        message: isZh
            ? '我需要知道代币符号，才能查询风险报告。'
            : 'I need the token symbol to fetch its risk report.',
    }, null, 2));
    process.exit(0);
}

if (!resolveToken(tokenInput)) {
    console.log(`❌ ${formatError('UNKNOWN_TOKEN', lang)}`);
    process.exit(0);
}

function formatWarningLine(warning) {
    const level = String(warning.level || 'unknown').toUpperCase();
    const icon = warning.isCritical ? '🔴' : '🟡';
    return `${icon} [${level}] ${warning.message}`;
}

function formatHolderLine(holder, index) {
    const pct = Number.isFinite(holder.percentage) ? `${holder.percentage.toFixed(2)}%` : '--';
    return `${index + 1}. ${holder.address} (${pct})`;
}

async function main() {
    try {
        const data = await getTokenPrice(tokenInput, { includeRisk: true });
        const risk = data.risk;

        console.log(isZh ? '🛡️ 代币风险报告' : '🛡️ Token Risk Report');
        console.log(`${isZh ? '代币' : 'Token'}: ${data.symbol} (${data.mint})`);
        console.log(`${isZh ? '价格' : 'Price'}: ${formatUSD(data.price || 0)}`);

        if (!risk) {
            console.log(isZh ? '⚠️ 暂未获取到 RugCheck 风险数据' : '⚠️ RugCheck risk data is currently unavailable');
            return;
        }

        console.log(`${isZh ? '风险评分' : 'Risk Score'}: ${risk.score} ${data.riskLabel}`);
        console.log(`${isZh ? '高风险' : 'High Risk'}: ${data.highRisk ? (isZh ? '是' : 'Yes') : (isZh ? '否' : 'No')}`);

        const warnings = Array.isArray(risk.warnings) ? risk.warnings : [];
        if (warnings.length > 0) {
            console.log(`\n${isZh ? '风险警告' : 'Warnings'}:`);
            warnings.slice(0, 5).forEach((warning) => console.log(`  ${formatWarningLine(warning)}`));
        }

        const topHolders = Array.isArray(risk.topHolders) ? risk.topHolders : [];
        if (topHolders.length > 0) {
            console.log(`\n${isZh ? '前5大持仓' : 'Top 5 Holders'}:`);
            topHolders.slice(0, 5).forEach((holder, index) => console.log(`  ${formatHolderLine(holder, index)}`));
        }
    } catch (err) {
        console.log(`❌ ${formatError(err.code || 'INTERNAL_ERROR', lang)}`);
        process.exit(1);
    }
}

main();


path: scripts/get-price.js
content: #!/usr/bin/env node
/**
 * Get current price for a token
 * Usage: node get-price.js <token_symbol> [--lang en]
 */
const path = require('path');
const sharedDir = path.resolve(__dirname, '..', '..', '..', 'shared');

const { getTokenPrice } = require(path.join(sharedDir, 'price-service'));
const { formatPrice } = require(path.join(sharedDir, 'formatter'));
const { formatError } = require(path.join(sharedDir, 'errors'));

const symbol = (process.argv[2] || '').toUpperCase();
const lang = process.argv.includes('--lang') ? process.argv[process.argv.indexOf('--lang') + 1] : 'zh';

if (!symbol) {
    console.log(JSON.stringify({
        code: 'MISSING_PARAMS',
        missing: ['代币符号 / Token symbol'],
        message: lang === 'zh'
            ? '我需要更多信息才能继续：代币符号'
            : 'I need more information to proceed: Token symbol'
    }, null, 2));
    process.exit(0);
}

async function main() {
    try {
        const price = await getTokenPrice(symbol);
        if (price > 0) {
            console.log(formatPrice(symbol, price, lang));
        } else {
            console.log(`⚠️ ${lang === 'zh' ? `未能获取 ${symbol} 价格` : `Could not fetch ${symbol} price`}`);
        }
    } catch (err) {
        console.log(`❌ ${formatError(err.code || 'INTERNAL_ERROR', lang)}`);
        process.exit(1);
    }
}

main();


path: scripts/market-overview.js
content: #!/usr/bin/env node
/**
 * Show Solana ecosystem market overview
 * Usage: node market-overview.js [--lang en]
 */
const path = require('path');
const sharedDir = path.resolve(__dirname, '..', '..', '..', 'shared');

const { getKnownTokens, getTokenPrices } = require(path.join(sharedDir, 'price-service'));
const { formatUSD } = require(path.join(sharedDir, 'formatter'));
const { formatError } = require(path.join(sharedDir, 'errors'));

const lang = process.argv.includes('--lang') ? process.argv[process.argv.indexOf('--lang') + 1] : 'zh';
const isZh = lang === 'zh';

async function main() {
    try {
        const tokens = getKnownTokens();
        const mints = tokens.map((t) => t.mint);
        const prices = await getTokenPrices(mints);

        console.log(isZh ? '📊 Solana 生态市场概览\n' : '📊 Solana Ecosystem Overview\n');

        for (const token of tokens) {
            const price = prices.get(token.mint) || 0;
            if (price > 0) {
                console.log(`  ${token.symbol.padEnd(6)} ${formatUSD(price)}`);
            } else {
                console.log(`  ${token.symbol.padEnd(6)} --`);
            }
        }

        console.log(`\n${isZh ? '数据来源: CoinGecko' : 'Source: CoinGecko'} | ${new Date().toLocaleTimeString()}`);
    } catch (err) {
        console.log(`❌ ${formatError(err.code || 'INTERNAL_ERROR', lang)}`);
        process.exit(1);
    }
}

main();


path: scripts/track-whale.js
content: #!/usr/bin/env node
/**
 * Track large transfers for a specific wallet via Helius.
 * Usage:
 *   node track-whale.js <wallet_address> <min_usd_value> [--lang zh|en]
 *   node track-whale.js <wallet_address> <min_usd_value> --watch [--interval 30] [--lang zh|en]
 */
const path = require('path');
const sharedDir = path.resolve(__dirname, '..', '..', '..', 'shared');

const { scanWhaleTransfers, createWhaleTracker } = require(path.join(sharedDir, 'services'));
const { formatUSD } = require(path.join(sharedDir, 'formatter'));
const { formatError } = require(path.join(sharedDir, 'errors'));
const { isValidAddress } = require(path.join(sharedDir, 'wallet'));
const config = require(path.join(sharedDir, 'config'));

const walletAddress = process.argv[2];
const minUsdRaw = process.argv[3];
const watchMode = process.argv.includes('--watch');
const lang = process.argv.includes('--lang') ? process.argv[process.argv.indexOf('--lang') + 1] : 'zh';
const intervalIndex = process.argv.indexOf('--interval');
const intervalSec = intervalIndex > -1 ? Number.parseInt(process.argv[intervalIndex + 1], 10) : 30;
const isZh = lang !== 'en';

function printMissing(missing) {
    console.log(JSON.stringify({
        code: 'MISSING_PARAMS',
        missing,
        message: `我需要更多信息才能继续：${missing.join('、')} / I need more information to proceed: ${missing.join(', ')}`,
    }, null, 2));
}

function formatEventLine(event) {
    const direction = event.direction === 'in'
        ? (isZh ? '流入' : 'IN')
        : event.direction === 'out'
            ? (isZh ? '流出' : 'OUT')
            : (isZh ? '未知方向' : 'UNKNOWN');
    return `🐋 ${event.symbol} ${direction} ${event.amount.toFixed(4)} | ${formatUSD(event.usdValue)} | ${event.signature}`;
}

async function runOnce(address, minUsdValue) {
    const result = await scanWhaleTransfers({
        address,
        minUsdValue,
        limit: 30,
    });

    if (!result.events || result.events.length === 0) {
        console.log(isZh
            ? `✅ 最近 ${result.checked} 笔交易中未发现超过 ${formatUSD(minUsdValue)} 的大额转账。`
            : `✅ No whale transfers above ${formatUSD(minUsdValue)} found in the latest ${result.checked} transactions.`);
        return;
    }

    console.log(isZh ? '🐋 鲸鱼转账扫描结果:' : '🐋 Whale transfer scan:');
    for (const event of result.events) {
        console.log(formatEventLine(event));
    }
}

async function runWatch(address, minUsdValue) {
    const pollMs = Math.max(10, Number.isFinite(intervalSec) ? intervalSec : 30) * 1000;
    const tracker = createWhaleTracker({
        address,
        minUsdValue,
        pollIntervalMs: pollMs,
        onEvent: (event) => {
            console.log(formatEventLine(event));
        },
        onError: () => {
            console.log(`⚠️ ${isZh ? '鲸鱼监听暂时失败，系统将继续重试。' : 'Whale tracking temporarily failed, retrying automatically.'}`);
        },
    });

    await tracker.start();
    console.log(isZh
        ? `👀 已开启实时监听，阈值 ${formatUSD(minUsdValue)}，轮询间隔 ${pollMs / 1000}s（Ctrl+C 停止）`
        : `👀 Live whale tracking started with threshold ${formatUSD(minUsdValue)} and ${pollMs / 1000}s polling (Ctrl+C to stop)`);

    process.on('SIGINT', () => {
        tracker.stop();
        console.log(isZh ? '\n🛑 已停止鲸鱼监听。' : '\n🛑 Whale tracking stopped.');
        process.exit(0);
    });
}

async function main() {
    const missing = [];
    if (!walletAddress) missing.push('钱包地址 / Wallet address');
    if (!minUsdRaw) missing.push('金额阈值(USD) / USD threshold');
    if (missing.length > 0) {
        printMissing(missing);
        process.exit(0);
    }

    if (!config.heliusApiKey) {
        console.log(`❌ ${isZh ? '未配置 HELIUS_API_KEY，无法启用鲸鱼追踪。' : 'HELIUS_API_KEY is not configured. Whale tracking is unavailable.'}`);
        process.exit(1);
    }

    if (!isValidAddress(walletAddress)) {
        console.log(`❌ ${isZh ? '地址格式无效。Solana 地址通常是 32-44 个字符的 base58 编码。' : 'Invalid address format. Solana addresses are usually 32-44 chars in base58.'}`);
        process.exit(1);
    }

    const minUsdValue = Number.parseFloat(minUsdRaw);
    if (!Number.isFinite(minUsdValue) || minUsdValue <= 0) {
        console.log(`❌ ${formatError('MISSING_REQUIRED_PARAMS', lang)}: ${isZh ? '金额阈值必须是大于 0 的数字' : 'USD threshold must be a number greater than 0'}`);
        process.exit(1);
    }

    if (watchMode) {
        await runWatch(walletAddress, minUsdValue);
    } else {
        await runOnce(walletAddress, minUsdValue);
    }
}

main().catch(() => {
    console.log(`❌ ${isZh ? '鲸鱼追踪失败，请稍后重试。' : 'Whale tracking failed. Please try again later.'}`);
    process.exit(1);
});


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)