
Tenero
- 165 installs
- 9 repo stars
- Updated July 16, 2026
- aibtcdev/skills
Embed Tenero services for Stacks/Bitcoin apps needing specialized indexing, notifications, or agent-facing chain APIs without rebuilding infrastructure from scratch.
About
Tenero skill enables Claude Code to integrate the Tenero platform into aibtcdev Stacks and Bitcoin applications: authentication, querying indexed chain data, configuring webhooks, testing integrations, and exposing reliable agent tools atop managed infrastructure.
- Explains Tenero auth and project scoping
- Lists core resources agents should request
- Covers webhook verification and retries
- Provides local stubbing for CI pipelines
- Aligns payloads with downstream business logic
Tenero by the numbers
- 165 all-time installs (skills.sh)
- Ranked #116 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aibtcdev/skills --skill teneroAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 165 |
|---|---|
| repo stars | ★ 9 |
| Last updated | July 16, 2026 |
| Repository | aibtcdev/skills ↗ |
What it does
Embed Tenero services for Stacks/Bitcoin apps needing specialized indexing, notifications, or agent-facing chain APIs without rebuilding infrastructure from scratch.
Files
Tenero Skill
Provides real-time market analytics for tokens, wallets, DEXs, and markets via the Tenero API (formerly STXTools). All endpoints are read-only and require no authentication. Data covers Stacks, Spark, and SportsFun chains.
Usage
bun run tenero/tenero.ts <subcommand> [options]Subcommands
token-info
Get token details including metadata, price, and volume.
bun run tenero/tenero.ts token-info --token <address> [--chain <chain>]Options:
--token(required) — Token contract address (e.g.SP1AY6K3PQV5MRT6R4S671NWW2FRVPKM0BR162CT6.leo-token)--chain(optional) — Chain to query (default:stacks)
Output:
{
"contract_id": "SP1AY6K3PQV5MRT6R4S671NWW2FRVPKM0BR162CT6.leo-token",
"symbol": "LEO",
"name": "Leo",
"decimals": 6,
"price_usd": "0.0012",
"price_stx": "0.0042",
"volume_24h_usd": "45000",
"market_cap_usd": "1200000"
}market-summary
Get token market summary including price history, volume, and liquidity.
bun run tenero/tenero.ts market-summary --token <address> [--chain <chain>]Options:
--token(required) — Token contract address--chain(optional) — Chain to query (default:stacks)
Output:
{
"contract_id": "SP1AY6K3PQV5MRT6R4S671NWW2FRVPKM0BR162CT6.leo-token",
"price_usd": "0.0012",
"price_change_1h": "1.2",
"price_change_24h": "-3.5",
"price_change_7d": "12.4",
"volume_24h_usd": "45000",
"liquidity_usd": "350000",
"holders": 1842
}market-stats
Get overall market statistics including total volume, market cap, and active tokens.
bun run tenero/tenero.ts market-stats [--chain <chain>]Options:
--chain(optional) — Chain to query (default:stacks)
Output:
{
"total_volume_24h_usd": "2500000",
"total_market_cap_usd": "85000000",
"active_tokens": 342,
"total_trades_24h": 18420,
"unique_traders_24h": 3210
}top-gainers
Get top gaining tokens by price change percentage over the past 24 hours.
bun run tenero/tenero.ts top-gainers [--chain <chain>] [--limit <number>]Options:
--chain(optional) — Chain to query (default:stacks)--limit(optional) — Maximum number of results (default:10)
Output:
[
{
"contract_id": "SP1AY6K3PQV5MRT6R4S671NWW2FRVPKM0BR162CT6.leo-token",
"symbol": "LEO",
"price_usd": "0.0012",
"price_change_24h": "42.5",
"volume_24h_usd": "45000"
}
]top-losers
Get top losing tokens by price change percentage over the past 24 hours.
bun run tenero/tenero.ts top-losers [--chain <chain>] [--limit <number>]Options:
--chain(optional) — Chain to query (default:stacks)--limit(optional) — Maximum number of results (default:10)
Output:
[
{
"contract_id": "SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-token",
"symbol": "DIKO",
"price_usd": "0.045",
"price_change_24h": "-18.3",
"volume_24h_usd": "12000"
}
]wallet-holdings
Get wallet token holdings with current USD value. Uses the active wallet address if --address is omitted.
bun run tenero/tenero.ts wallet-holdings [--address <stx_address>] [--chain <chain>]Options:
--address(optional) — Stacks address to check (uses active wallet if omitted)--chain(optional) — Chain to query (default:stacks)
Output:
{
"address": "SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173",
"total_value_usd": "1250.42",
"holdings": [
{
"contract_id": "SP1AY6K3PQV5MRT6R4S671NWW2FRVPKM0BR162CT6.leo-token",
"symbol": "LEO",
"balance": "1000000",
"value_usd": "1.20"
}
]
}wallet-trades
Get wallet trade history. Uses the active wallet address if --address is omitted.
bun run tenero/tenero.ts wallet-trades [--address <stx_address>] [--chain <chain>] [--limit <number>]Options:
--address(optional) — Stacks address to check (uses active wallet if omitted)--chain(optional) — Chain to query (default:stacks)--limit(optional) — Maximum number of results (default:20)
Output:
[
{
"tx_id": "0xabc123...",
"timestamp": "2024-01-15T12:00:00Z",
"type": "swap",
"token_in": "STX",
"token_out": "LEO",
"amount_in_usd": "10.00",
"amount_out_usd": "9.85"
}
]trending-pools
Get trending DEX pools by volume within a timeframe.
bun run tenero/tenero.ts trending-pools [--timeframe <1h|6h|24h>] [--chain <chain>] [--limit <number>]Options:
--timeframe(optional) — Time window:1h,6h, or24h(default:24h)--chain(optional) — Chain to query (default:stacks)--limit(optional) — Maximum number of results (default:10)
Output:
[
{
"pool_id": "SP1Y5YSTAHZ88XYK1VPDH24GY0HPX5J4JECTMY4A1.univ2-share-fee-to",
"token_x": "STX",
"token_y": "LEO",
"volume_usd": "85000",
"liquidity_usd": "420000",
"fee_24h_usd": "255"
}
]whale-trades
Get large/whale trades above a threshold value.
bun run tenero/tenero.ts whale-trades [--chain <chain>] [--limit <number>]Options:
--chain(optional) — Chain to query (default:stacks)--limit(optional) — Maximum number of results (default:10)
Output:
[
{
"tx_id": "0xdef456...",
"timestamp": "2024-01-15T11:45:00Z",
"wallet": "SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173",
"type": "buy",
"token": "ALEX",
"amount_usd": "25000"
}
]holder-stats
Get token holder distribution and statistics.
bun run tenero/tenero.ts holder-stats --token <address> [--chain <chain>]Options:
--token(required) — Token contract address--chain(optional) — Chain to query (default:stacks)
Output:
{
"contract_id": "SP1AY6K3PQV5MRT6R4S671NWW2FRVPKM0BR162CT6.leo-token",
"total_holders": 1842,
"top_10_concentration": "45.2",
"top_25_concentration": "62.8",
"top_holders": [
{
"address": "SP2X0TZ59D5SZ8ACQ6YMCHHNR2ZN51Z32E2CJ173",
"balance": "50000000",
"percentage": "5.0"
}
]
}search
Search tokens, pools, and wallets by name or address fragment.
bun run tenero/tenero.ts search --query <string> [--chain <chain>]Options:
--query(required) — Search query string (token name, symbol, or address)--chain(optional) — Chain to query (default:stacks)
Output:
{
"tokens": [
{
"contract_id": "SP1AY6K3PQV5MRT6R4S671NWW2FRVPKM0BR162CT6.leo-token",
"symbol": "LEO",
"name": "Leo"
}
],
"pools": [],
"wallets": []
}Notes
- All endpoints are read-only — no API key or wallet required for most subcommands
wallet-holdingsandwallet-tradesuse the active unlocked wallet address when--addressis omitted- The
--chainoption supportsstacks(default),spark, andsportsfun - Response data is passed through directly from the Tenero API
datafield - Base URL:
https://api.tenero.io
Tenero Agent
This agent queries the Tenero API (formerly STXTools) for real-time market analytics on Stacks and related chains. All operations are read-only and require no authentication. wallet-holdings and wallet-trades fall back to the active wallet when no address is given.
Prerequisites
- No wallet required for:
token-info,market-summary,market-stats,top-gainers,top-losers,trending-pools,whale-trades,holder-stats,search - Wallet must be unlocked for
wallet-holdingsandwallet-tradeswhen--addressis omitted — usebun run wallet/wallet.ts unlock --password <password>first, or provide--addressexplicitly
Decision Logic
| Goal | Subcommand |
|---|---|
| Understand a specific token (price, volume, metadata) | token-info — quick overview |
| Analyze a token's price history and liquidity depth | market-summary — detailed stats |
| Survey the overall market condition | market-stats — aggregate metrics |
| Find tokens with strong recent momentum | top-gainers — 24h price change leaders |
| Find tokens under selling pressure | top-losers — 24h price change laggards |
| Audit a wallet's current token positions | wallet-holdings — balances + USD values |
| Review a wallet's recent trading activity | wallet-trades — swap history |
| Discover high-volume DEX pools | trending-pools — use --timeframe 1h for recent spikes |
| Detect large market-moving trades | whale-trades — identify smart money activity |
| Analyze token ownership concentration | holder-stats — distribution and top holders |
| Discover tokens/pools by partial name or symbol | search — broad discovery |
Safety Checks
- These endpoints are read-only — no transaction risk
- Do not pass sensitive wallet data (mnemonics, passwords) to any option in this skill
- When using
wallet-holdingsorwallet-tradeswithout--address, confirm the wallet is unlocked and the session address belongs to the intended wallet - Interpret large
top_10_concentrationvalues (>50%) as centralization risk before recommending a token
Error Handling
| Error message | Cause | Fix |
|---|---|---|
| "Tenero API error: 404 Not Found" | Token address or chain path is invalid | Verify the contract address format (address.contract-name) and chain name |
| "Tenero API error: 429 Too Many Requests" | Rate limit exceeded | Wait briefly before retrying |
| "Tenero API error: 5xx" | Tenero service is down | Retry after a short delay; report if persistent |
| "No Stacks address provided and wallet is not unlocked" | wallet-holdings/wallet-trades called without --address and wallet is locked | Provide --address or unlock wallet first |
Output Handling
token-info: useprice_usdandprice_stxfor valuation;volume_24h_usdfor liquidity assessmentmarket-summary: useprice_change_24handprice_change_7dfor trend direction;liquidity_usdfor depth;holdersfor adoption signalmarket-stats: usetotal_volume_24h_usdandactive_tokensto gauge overall market healthtop-gainers/top-losers: passcontract_idtotoken-infoormarket-summaryfor deeper analysiswallet-holdings: usetotal_value_usdfor portfolio summary; iterateholdingsfor per-token breakdownwallet-trades: usetype,token_in,token_out, andamount_in_usdto reconstruct trade historytrending-pools: usevolume_usdandfee_24h_usdto identify fee-earning liquidity opportunitieswhale-trades: usewalletandamount_usdto track large players; passwallettowallet-holdingsfor position analysisholder-stats: usetop_10_concentrationas a decentralization signal;total_holdersas adoption metricsearch: pass matchingcontract_idvalues fromtokensresults totoken-infofor full details
Example Invocations
# Get info on a specific token
bun run tenero/tenero.ts token-info --token SP1AY6K3PQV5MRT6R4S671NWW2FRVPKM0BR162CT6.leo-token
# Check the active wallet's holdings
bun run tenero/tenero.ts wallet-holdings
# Find trending pools in the last hour
bun run tenero/tenero.ts trending-pools --timeframe 1h --limit 5
# Search for a token by symbol
bun run tenero/tenero.ts search --query "LEO"#!/usr/bin/env bun
/**
* Tenero skill CLI
* Market analytics for tokens, wallets, DEXs, and markets via Tenero (formerly STXTools)
*
* Usage: bun run tenero/tenero.ts <subcommand> [options]
*/
import { Command } from "commander";
import { getWalletManager } from "../src/lib/services/wallet-manager.js";
import { printJson, handleError } from "../src/lib/utils/cli.js";
const BASE_URL = "https://api.tenero.io";
// ---------------------------------------------------------------------------
// API helper
// ---------------------------------------------------------------------------
async function fetchTenero(path: string): Promise<unknown> {
const response = await fetch(`${BASE_URL}${path}`);
if (!response.ok) {
throw new Error(
`Tenero API error: ${response.status} ${response.statusText}`
);
}
const json = (await response.json()) as {
statusCode: number;
message: string;
data: unknown;
};
return json.data;
}
// ---------------------------------------------------------------------------
// Address helper
// ---------------------------------------------------------------------------
async function getStxAddress(address?: string): Promise<string> {
if (address) {
return address;
}
const walletManager = getWalletManager();
const sessionInfo = walletManager.getSessionInfo();
if (sessionInfo?.address) {
return sessionInfo.address;
}
throw new Error(
"No Stacks address provided and wallet is not unlocked. " +
"Either provide --address or unlock your wallet first."
);
}
// ---------------------------------------------------------------------------
// Program
// ---------------------------------------------------------------------------
const program = new Command();
program
.name("tenero")
.description(
"Tenero market analytics — token info, market stats, top gainers/losers, " +
"wallet holdings and trades, trending DEX pools, whale trades, holder distribution, and search. " +
"Covers Stacks, Spark, and SportsFun chains. No API key required."
)
.version("0.1.0");
// ---------------------------------------------------------------------------
// token-info
// ---------------------------------------------------------------------------
program
.command("token-info")
.description("Get token details including metadata, price, and volume.")
.requiredOption("--token <address>", "Token contract address")
.option("--chain <chain>", "Chain to query", "stacks")
.action(async (opts: { token: string; chain: string }) => {
try {
const data = await fetchTenero(
`/v1/${opts.chain}/tokens/${opts.token}`
);
printJson(data);
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// market-summary
// ---------------------------------------------------------------------------
program
.command("market-summary")
.description(
"Get token market summary including price history, volume, and liquidity."
)
.requiredOption("--token <address>", "Token contract address")
.option("--chain <chain>", "Chain to query", "stacks")
.action(async (opts: { token: string; chain: string }) => {
try {
const data = await fetchTenero(
`/v1/${opts.chain}/tokens/${opts.token}/market_summary`
);
printJson(data);
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// market-stats
// ---------------------------------------------------------------------------
program
.command("market-stats")
.description(
"Get overall market statistics including total volume, market cap, and active tokens."
)
.option("--chain <chain>", "Chain to query", "stacks")
.action(async (opts: { chain: string }) => {
try {
const data = await fetchTenero(`/v1/${opts.chain}/market/stats`);
printJson(data);
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// top-gainers
// ---------------------------------------------------------------------------
program
.command("top-gainers")
.description("Get top gaining tokens by price change percentage.")
.option("--chain <chain>", "Chain to query", "stacks")
.option("--limit <number>", "Maximum number of results", "10")
.action(async (opts: { chain: string; limit: string }) => {
try {
const limit = parseInt(opts.limit, 10);
const data = await fetchTenero(
`/v1/${opts.chain}/market/top_gainers?limit=${limit}`
);
printJson(data);
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// top-losers
// ---------------------------------------------------------------------------
program
.command("top-losers")
.description("Get top losing tokens by price change percentage.")
.option("--chain <chain>", "Chain to query", "stacks")
.option("--limit <number>", "Maximum number of results", "10")
.action(async (opts: { chain: string; limit: string }) => {
try {
const limit = parseInt(opts.limit, 10);
const data = await fetchTenero(
`/v1/${opts.chain}/market/top_losers?limit=${limit}`
);
printJson(data);
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// wallet-holdings
// ---------------------------------------------------------------------------
program
.command("wallet-holdings")
.description(
"Get wallet token holdings with current value. Uses active wallet if --address is omitted."
)
.option(
"--address <stx_address>",
"Stacks address to check (uses active wallet if omitted)"
)
.option("--chain <chain>", "Chain to query", "stacks")
.action(async (opts: { address?: string; chain: string }) => {
try {
const address = await getStxAddress(opts.address);
const data = await fetchTenero(
`/v1/${opts.chain}/wallets/${address}/holdings_value`
);
printJson(data);
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// wallet-trades
// ---------------------------------------------------------------------------
program
.command("wallet-trades")
.description(
"Get wallet trade history. Uses active wallet if --address is omitted."
)
.option(
"--address <stx_address>",
"Stacks address to check (uses active wallet if omitted)"
)
.option("--chain <chain>", "Chain to query", "stacks")
.option("--limit <number>", "Maximum number of results", "20")
.action(async (opts: { address?: string; chain: string; limit: string }) => {
try {
const address = await getStxAddress(opts.address);
const limit = parseInt(opts.limit, 10);
const data = await fetchTenero(
`/v1/${opts.chain}/wallets/${address}/trades?limit=${limit}`
);
printJson(data);
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// trending-pools
// ---------------------------------------------------------------------------
program
.command("trending-pools")
.description(
"Get trending DEX pools by volume within a timeframe (1h, 6h, 24h)."
)
.option("--timeframe <string>", "Timeframe: 1h, 6h, or 24h", "24h")
.option("--chain <chain>", "Chain to query", "stacks")
.option("--limit <number>", "Maximum number of results", "10")
.action(
async (opts: { timeframe: string; chain: string; limit: string }) => {
try {
const limit = parseInt(opts.limit, 10);
const data = await fetchTenero(
`/v1/${opts.chain}/pools/trending/${opts.timeframe}?limit=${limit}`
);
printJson(data);
} catch (error) {
handleError(error);
}
}
);
// ---------------------------------------------------------------------------
// whale-trades
// ---------------------------------------------------------------------------
program
.command("whale-trades")
.description("Get large/whale trades above threshold value.")
.option("--chain <chain>", "Chain to query", "stacks")
.option("--limit <number>", "Maximum number of results", "10")
.action(async (opts: { chain: string; limit: string }) => {
try {
const limit = parseInt(opts.limit, 10);
const data = await fetchTenero(
`/v1/${opts.chain}/market/whale_trades?limit=${limit}`
);
printJson(data);
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// holder-stats
// ---------------------------------------------------------------------------
program
.command("holder-stats")
.description("Get token holder distribution and statistics.")
.requiredOption("--token <address>", "Token contract address")
.option("--chain <chain>", "Chain to query", "stacks")
.action(async (opts: { token: string; chain: string }) => {
try {
const data = await fetchTenero(
`/v1/${opts.chain}/tokens/${opts.token}/holder_stats`
);
printJson(data);
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// search
// ---------------------------------------------------------------------------
program
.command("search")
.description("Search tokens, pools, and wallets by name or address.")
.requiredOption("--query <string>", "Search query string")
.option("--chain <chain>", "Chain to query", "stacks")
.action(async (opts: { query: string; chain: string }) => {
try {
const data = await fetchTenero(
`/v1/${opts.chain}/search?query=${encodeURIComponent(opts.query)}`
);
printJson(data);
} catch (error) {
handleError(error);
}
});
// ---------------------------------------------------------------------------
// Parse
// ---------------------------------------------------------------------------
program.parse(process.argv);