Skip to content
Back to skills

Token Report

BSecurity

Price performance report for the project's token — price, volume, liquidity, and context

  • 12 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 21, 2026
ai-agentsrustgobashapiperformance

Works with

  • terminal
  • api

Security analysis

B88/100
  • criticalSends environment variables or credentials to an external URL

Pro shows the line behind each finding and how to fix it

Scanned October 21, 2026

npx -y skills add aeonfun/aeon-agent --skill token-report --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Token Report?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Token Report
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/aeonfun-token-report/badge)](https://www.skillsdirectory.com/skills/aeonfun-token-report)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
type: Skill
name: token-report
category: crypto
description: Price performance report for the project's token — price, volume, liquidity, and context
var: ""
tags: [crypto]
mode: write
requires: [TWITTER_API_KEY, ALCHEMY_API_KEY?, XAI_API_KEY?, BASE_RPC_URL?]
capabilities: [external_api]
---
<!-- autoresearch: variation B — verdict-first template, threshold-based classification, true deltas from persistent STATE log, skip-when-empty sections -->

> **${var}** — Token contract address. If empty, uses tracked token from MEMORY.md.

## Config

This skill reads the token to track from the "Tracked Token" section in `memory/MEMORY.md`.

```markdown
## Tracked Token
| Token | Contract | Chain |
|-------|----------|-------|
| AEON  | 0xbf8e... | base |
```

If no tracked token is configured and `${var}` is empty, abort silently — no article, no notification.

Read the last 30 days of `memory/logs/*.md` for prior `TOKEN_REPORT_STATE:` lines (written in step 8). These are the authoritative source of 1d / 7d / 30d deltas, because a stored price yesterday beats an API window that shifts under you.

## Thesis

A daily token report is only useful if it tells the reader *what changed and whether it matters*. Snapshots of price, volume, and liquidity are table-stakes; the value is in the verdict. Every section below must either sharpen the verdict or be dropped. No filler, no "N/A", no "no specific context" sentences.

## Steps

### 1. Fetch core market data (GeckoTerminal — primary)

```bash
curl -s "https://api.geckoterminal.com/api/v2/networks/base/tokens/CONTRACT_ADDRESS"
curl -s "https://api.geckoterminal.com/api/v2/networks/base/tokens/CONTRACT_ADDRESS/pools?page=1"
# Top pool address from the pools response:
curl -s "https://api.geckoterminal.com/api/v2/networks/base/pools/POOL_ADDRESS/ohlcv/day?aggregate=1&limit=30"
curl -s "https://api.geckoterminal.com/api/v2/networks/base/pools/POOL_ADDRESS/ohlcv/hour?aggregate=1&limit=24"
curl -s "https://api.geckoterminal.com/api/v2/networks/base/pools/POOL_ADDRESS/trades"
```

If curl fails (sandbox block), retry each URL with **WebFetch**. If the token endpoint returns no data or 404 after both paths, go to step 9 with `TOKEN_REPORT_NO_DATA` — do not notify, do not write an article.

### 2. Cross-check with DexScreener (sanity + alt signal)

```bash
curl -s "https://api.dexscreener.com/latest/dex/tokens/CONTRACT_ADDRESS"
```

Use DexScreener for two things only:
- **Price sanity:** if DS price deviates >3% from GT price on the deepest-liquidity pair, mark `ds=divergent` in the sources footer and trust the deeper pool. Do not average.
- **Boost/trending flag:** if the pair is `boosted` or on `trending`, add one sentence to the Context section.

If DexScreener fails, continue with GT only (`ds=fail` in footer). Never abort on DS failure.

**Low-liquidity pair addendum:** the 3% deviation threshold above is calibrated for liquid pairs. On thin pairs it produces false `ds=divergent` flags from harmless tick noise. **If the pair's 24h volume is below $100k, raise the DS deviation threshold to 10% instead of 3%** before flagging `ds=divergent`. The deep-pool-wins rule still applies when the larger threshold is exceeded.

### 2b. Treasury wallets (on-chain liquidity)

If `memory/treasury-wallets.json` exists, fetch native-ETH balance for each entry on Base. This optional file declares the project's protocol wallets (treasury, deployer, etc.). If it's absent, skip this step (treasury reporting is off).

```bash
WALLETS_FILE="memory/treasury-wallets.json"
[ -f "$WALLETS_FILE" ] && jq -c '.wallets[] | select(.chain=="base")' "$WALLETS_FILE" > /tmp/treasury-wallets.jsonl
```

For each wallet line, query Base in this fallback order:

1. **Public Base RPC `eth_getBalance` (primary, keyless — the same endpoint the repo's other Base skills use):**
   ```bash
   RPC="${BASE_RPC_URL:-https://mainnet.base.org}"
   curl -m 10 -s -X POST "$RPC" -H "Content-Type: application/json" \
     -d '{"jsonrpc":"2.0","method":"eth_getBalance","params":["ADDRESS","latest"],"id":1}'
   ```
   `https://mainnet.base.org` is the public, keyless Base JSON-RPC endpoint that `wallet-profile`, `tx-explain`, `lp-lock`, and `fund-flow` already default to; override with `BASE_RPC_URL` for an authenticated endpoint (append any key in the URL path, **never a `-H` header** — the sandbox blocks env-var expansion in headers). The static `-H "Content-Type: application/json"` carries no secret, so it is safe. Response is JSON-RPC `{"jsonrpc":"2.0","result":"0x<hex_wei>","id":1}`. Convert hex → decimal → ÷1e18. If the response has no `result`, the `result` is `null`/non-hex, or it carries an `error`, mark this wallet `eth=fetch_fail` and continue.

   > Why not Etherscan here: the legacy per-chain `api.basescan.org/api` host (BaseScan V1) is deprecated, and the unified `api.etherscan.io/v2` endpoint gates Base (`chainid=8453`) behind a paid plan (`"Free API access is not supported for this chain"`). Neither works keyless, so a plain JSON-RPC `eth_getBalance` is the reliable keyless path — matching `wallet-profile`/`tx-explain`.

2. **Alchemy (secondary, only if `ALCHEMY_API_KEY` is set AND the public RPC failed):** use `./secretcurl` with the literal `{ALCHEMY_API_KEY}` placeholder (never `$ALCHEMY_API_KEY` — the permission layer blocks any command text containing a secret expansion, URL path included; see CLAUDE.md → Network & Secrets):
   ```bash
   ./secretcurl -m 10 -s -X POST "https://base-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}" \
     -H "Content-Type: application/json" \
     -d '{"jsonrpc":"2.0","id":1,"method":"eth_getBalance","params":["ADDRESS","latest"]}'
   ```
   Identical JSON-RPC shape and hex → decimal → ÷1e18 conversion as above.

3. **WebFetch fallback** (sandbox block on either curl): retry the same POST with **WebFetch** before declaring `fetch_fail`.

Compute, per wallet:
- `eth_balance` — decimal ETH, 4 decimals.
- `eth_balance_delta_24h` — diff vs yesterday's `TREASURY_STATE:` log line (omit when prior is missing).

Aggregate:
- `treasury_eth_total` — sum across **role=treasury** wallets only (deployer wallets are operational, not protocol funds).
- `treasury_low_alert` — `true` if `treasury_eth_total < 0.01` AND `treasury_eth_total > 0` (a zero balance is a config error, not a depletion; do not alarm).

If `memory/treasury-wallets.json` is missing or contains no `chain=base` entries, set `treasury=skip` in the sources footer and OMIT the Treasury subsection from the article + notification. Do not invent the section.

### 3. Compute true deltas

From the `TOKEN_REPORT_STATE:` key=value lines in prior logs, load:
- **1d-ago price, liquidity, volume_24h, buys, sells, whales** (yesterday's run)
- **7d-ago price**
- **30d-ago price** (fall back to GT daily OHLCV close if missing)

For each:
- If prior value exists, compute pct delta against it.
- If prior is missing, compute from OHLCV candles and mark the figure `(~7d)` or `(~30d)` to signal the fallback source.

Derived signals:
- **Liq Δ 24h:** pct change vs yesterday's stored liquidity.
- **Vol ratio:** today's 24h volume ÷ mean(last 7 days of 24h volume). Report as `Z.Z×`.
- **Buy/sell shift:** (today_buys/today_sells) vs yesterday's ratio. Report both.
- **Whale trades 24h:** count of single trades with `volume_in_usd >= 1000` in the trades feed. List the top 3 with direction and size in the "What changed" section if ≥1 exists.

### 4. Classify the day (one verdict)

Pick exactly one label from the table. Thresholds use *today's true deltas* from step 3. Evaluate top-to-bottom; the first row whose trigger fully matches wins.

| Label | Trigger |
|-------|---------|
| `BREAKOUT` | Δprice ≥ +10% AND vol ratio ≥ 2.0 |
| `BREAKDOWN` | Δprice ≤ −10% AND vol ratio ≥ 2.0 |
| `RALLYING` | +3% ≤ Δprice < +10% AND vol ratio ≥ 1.0 |
| `SLIDING` | −10% < Δprice ≤ −3% AND vol ratio ≥ 1.0 |
| `ACCUMULATING` | abs(Δprice) < 3% AND buy/sell ratio ≥ 1.3 AND whale buys ≥ 1 |
| `DISTRIBUTING` | abs(Δprice) < 3% AND buy/sell ratio ≤ 0.7 AND whale sells ≥ 1 |
| `QUIET` | vol ratio < 0.5 AND whale trades = 0 |
| `CONSOLIDATING` | (everything else) |

Do not freelance labels. The verdict drives the lede, the TL;DR, and the notification.

### 5. Compile the report

Save to `output/articles/token-report-${today}.md`:

```markdown
# $TOKEN — ${today}

**Verdict:** [LABEL] — [≤18 words, citing the 1–2 numbers that drove the label]

## 24h at a glance

| Metric | Now | 24h Δ | vs 7d avg |
|--------|-----|-------|-----------|
| Price | $X.XXXX | ±Y.Y% | — |
| Liquidity | $X.XK | ±Y.Y% | — |
| Volume (24h) | $X.XK | — | Z.Z× |
| Buys / Sells | X / Y | ratio Z.ZZ (yest Z.ZZ) | — |
| Whale trades (≥$1k) | N | — | — |
| FDV | $X.XM | — | — |

## Trend
- **7d:** ±X.X% ([one phrase: rallying, range-bound, rolling over, etc.])
- **30d:** ±X.X% ([one phrase])

## Treasury
[Include ONLY if step 2b returned at least one wallet with a real balance. One row per wallet, role-sorted (treasury first, then deployer, then other). If `treasury_low_alert` is true, lead the section with one sentence naming the floor that was crossed.]

| Wallet | Role | ETH | 24h Δ |
|--------|------|-----|-------|
| 0xf1e9…158e | treasury | X.XXXX | ±Y.YY |
| 0x6797…e3a2 | deployer | X.XXXX | ±Y.YY |

## What changed
[2–4 sentences. Name the specific deltas that matter and the verdict they produced. If whale trades exist, list the top 3 as `buy $1.2K @ $0.0042 · 11:03 UTC`. If liquidity moved >5%, name the pool and the $ amount. Tie every sentence back to the verdict. No filler.]

## Social Pulse
[Only include if the social fetch (Step 6) returns >=2 tweets with >=10 engagement. Lead with a one-line read of the conversation shape, then quote 1-3 tweets with @handle + engagement counts. Otherwise OMIT this section entirely.]

## Context
[Only include when there is a genuine link to known activity: a recent repo release, a broader market regime shift, a boost/trending flag, an on-chain event. If none, OMIT. Never write "no specific context".]

---
*Chart: https://www.geckoterminal.com/base/pools/POOL_ADDRESS*
*Contract: CONTRACT_ADDRESS | Chain: Base*
*Sources: gt=ok · ds=[ok|fail|divergent] · xai=[ok|skip|fail] · treasury=[ok|skip|fetch_fail]*
```

**Section discipline:**
- If Social Pulse or Context has no real content, drop the section — do not write placeholder text.
- Never round in a way that flips a sign or crosses a threshold (e.g. don't render `−0.05%` as `0.0%`).
- Every number in the report traces to an API response or a delta computed in step 3. Do not invent figures.

### 6. Social sentiment (conditional)

**Path A - twitterapi.io (primary).** Structured tweet objects with exact engagement counts and real permalinks - no fabrication risk, ~700ms. Search the last 24h for the ticker or contract, then keep tweets clearing the engagement bar. Auth via `./secretcurl` with the literal `{TWITTER_API_KEY}` placeholder (never `$TWITTER_API_KEY`; see CLAUDE.md -> Network & Secrets):

```bash
TODAY=$(date -u +%Y-%m-%d)
SINCE=$(date -u -d '1 day ago' +%Y-%m-%d 2>/dev/null || date -u -v-1d +%Y-%m-%d)
Q="$TOKEN_SYMBOL OR CONTRACT_ADDRESS since:$SINCE until:$TODAY"
HTTP=$(./secretcurl -s -o /tmp/tw-token.json -w '%{http_code}' -G "https://api.twitterapi.io/twitter/tweet/advanced_search" \
  --data-urlencode "query=$Q" --data-urlencode "queryType=Latest" -H "X-API-Key: {TWITTER_API_KEY}")
echo "twitterapi http=$HTTP bytes=$(wc -c </tmp/tw-token.json)"
# on HTTP 200: keep tweets with >=10 engagement, top 5 by likes
jq -r '[.tweets[] | select((.likeCount + .retweetCount + .replyCount) >= 10)]
  | sort_by(-.likeCount) | .[:5][]
  | [.author.userName, .likeCount, .retweetCount, .replyCount, .url, .text] | @tsv' /tmp/tw-token.json
```

Substitute the tracked ticker (cashtag form) for `$TOKEN_SYMBOL` and the token address for `CONTRACT_ADDRESS`. On `HTTP=200` with >=2 tweets clearing the bar, build the Social Pulse section, exclude obvious bots and generic shill posts, and set `xai=ok` in the footer.

**Path B - xAI Grok `x_search` (fallback).** Only if Path A returned non-2xx, empty, timed out, or `TWITTER_API_KEY` is unset. Call it **in-run** with `./secretcurl` and the literal `{XAI_API_KEY}` placeholder (never `$XAI_API_KEY`; see CLAUDE.md -> Network & Secrets):

```bash
./secretcurl -s -X POST "https://api.x.ai/v1/responses" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {XAI_API_KEY}" \
  -d '{
    "model": "grok-4-1-fast",
    "input": [{"role": "user", "content": "Search X for $TOKEN_SYMBOL or CONTRACT_ADDRESS mentions in the last 24 hours with at least 10 likes. Return up to 5 notable tweets with @handle, engagement counts, and a one-line summary of the claim or vibe. Exclude obvious bots and generic shill posts."}],
    "tools": [{"type": "x_search"}]
  }'
```

If neither path returns at least 2 tweets that clear the engagement bar, skip the Social Pulse section and set `xai=skip` in the footer. On API error on both paths, set `xai=fail` and skip. If neither `TWITTER_API_KEY` nor `XAI_API_KEY` is set, set `xai=skip`.

### 7. Save article

Write the compiled report to `output/articles/token-report-${today}.md`.

### 8. State log (powers tomorrow's deltas)

Append to `memory/logs/${today}.md`:

```
### token-report
- Verdict: [LABEL]
- TOKEN_REPORT_STATE: price=X.XXXX liquidity=XXXX.XX volume_24h=XXXX.XX buys=N sells=N whales=N pool=POOL_ADDRESS
- TREASURY_STATE: treasury_eth_total=X.XXXX wallets=N (one TREASURY_WALLET_STATE line per wallet below)
- TREASURY_WALLET_STATE: addr=0xf1e9…158e role=treasury eth=X.XXXX
- TREASURY_WALLET_STATE: addr=0x6797…e3a2 role=deployer eth=X.XXXX
- 24h: ±X.X% | 7d: ±X.X% | 30d: ±X.X%
- Article: output/articles/token-report-${today}.md
- Sources: gt=ok ds=[ok|fail|divergent] xai=[ok|skip|fail] treasury=[ok|skip|fetch_fail]
```

The `TOKEN_REPORT_STATE:` line is a contract — step 3 of the next run parses it with a key=value split. Keep the keys, order, and numeric formats stable. No currency symbols, no thousands separators. The `TREASURY_WALLET_STATE:` lines (one per fetched wallet) feed step 2b's 24h delta — parse them with the same key=value split, keyed on `addr`. Omit `TREASURY_STATE` and `TREASURY_WALLET_STATE` lines entirely on `treasury=skip` runs so a wallets.json that disappears later doesn't leave stale balances in the log.

### 9. Notify

Lead with the verdict, not raw numbers. One short paragraph plus metrics.

```
*$TOKEN — [LABEL]*

[One sentence citing the driving number(s).]

Price $X.XXXX (±Y.Y% 24h) | Liq $X.XK (±Z.Z%) | Vol $X.XK (W.W× 7d)
Buys/Sells X/Y (ratio Z.ZZ) | Whales: N
Treasury: X.XXXX ETH (±Y.YY 24h)

Chart: https://www.geckoterminal.com/base/pools/POOL_ADDRESS
```

The `Treasury:` line is included ONLY when step 2b populated treasury_eth_total > 0. Omit the line entirely on `treasury=skip` / `treasury=fetch_fail` runs — silence beats a misleading number.

**Skip rules:**
- `TOKEN_REPORT_NO_DATA` (step 1 bailout): log only, **no notification, no article**.
- `QUIET` verdict with whales=0 AND abs(Δprice 24h) <1%: send a single-line notification `$TOKEN quiet — $X.XXXX flat, vol $X.XK.` (no table). This confirms the skill ran without pinging channels with filler on dead days. **Exception:** if `treasury_low_alert` is true, override QUIET and send the full notification with a leading `*Treasury gas reserve low — X.XXXX ETH on treasury, floor 0.01 ETH.*` line. A token going quiet on a day when the agent can no longer pay for gas is the exact regime where the operator needs to see it.
- Any other verdict: full notification above.

**Treasury alert (independent of verdict):** if `treasury_low_alert` is true on any run, prepend this line to the notification body — even on QUIET, even on CONSOLIDATING:

```
⚠️ *Treasury gas reserve low — X.XXXX ETH on treasury (floor 0.01 ETH).*
```

## Sandbox note

Public reads (GeckoTerminal, DexScreener, the keyless Base RPC `mainnet.base.org`) use plain `curl`; on a real failure (non-2xx / timeout / empty), retry with **WebFetch** - it accepts the JSON body for the `eth_getBalance` POST. The auth'd calls (Alchemy fallback, twitterapi.io + api.x.ai Social Pulse) run **in-run** via `./secretcurl` with `{ALCHEMY_API_KEY}` / `{TWITTER_API_KEY}` / `{XAI_API_KEY}` placeholders - no pre-fetch / post-process plumbing.

The Alchemy fallback in step 2b uses `$ALCHEMY_API_KEY` in the URL path (not in a header), so curl envvar expansion is safe here. If Alchemy is unset, skip silently — the keyless public RPC + WebFetch are enough.

## Constraints

- Never invent numbers. Every figure traces to an API response or a computed delta.
- Never write filler sections. Drop them.
- Verdict must come from the step-4 table — no freelance labels.
- On `TOKEN_REPORT_NO_DATA`, exit silently. No notification about the failure.
- Preserve the `TOKEN_REPORT_STATE:` log line schema — tomorrow's run depends on it.
- Preserve the `TREASURY_WALLET_STATE:` log line schema (one line per fetched wallet, keyed on `addr`) — step 2b's 24h delta depends on it.
- A treasury fetch failure is not a token-report failure. On `treasury=fetch_fail`, omit the Treasury subsection + notification line but still write the full token report — the token data is the primary product, treasury is an annotation.
- Wallets with `role` outside {`treasury`, `deployer`} appear in the article table sorted last under "other"; only `role=treasury` wallets count toward `treasury_eth_total` and the low-balance alert.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.