413 Commits
Author SHA1 Message Date
GMGN.AI 685c46b324 1.6.4 v1.6.4 2026-09-14 20:17:01 +08:00
GMGN.AIandGitHub c300178097 Merge pull request #231 from dylanzhong662-max/feat/support-arbitrum-hyperevm
feat: document Pro-only 1s K-line support
2026-09-14 20:14:39 +08:00
zhongsongzhi-cmyk af73701a8d chore: bump gmgn-cli to 1.6.3 2026-09-14 19:46:20 +08:00
zhongsongzhi-cmyk a8f40a4be3 feat(cli): document Pro-only 1s kline 2026-09-14 19:45:58 +08:00
zhongsongzhi-cmyk d2e4e96865 feat: document Pro-only 1s kline support 2026-09-14 19:45:58 +08:00
GMGN.AIandGitHub 4a48b8a31e Merge pull request #229 from Edwardchennnn/feat/gmgn-narrative-skill
feat(gmgn-narrative): add independent token narrative/due-diligence card skill
2026-09-11 19:25:37 +08:00
GMGN.AIandGitHub 5870e68558 Merge pull request #223 from Edwardchennnn/docs/contract-dd-display-templates
gmgn-contract-dd: reshape the report, and put the verdict on the buyability axis
2026-09-11 19:25:07 +08:00
GMGN.AIandGitHub d210d94bf8 Merge pull request #228 from Edwardchennnn/token-buy/direction-execution-gate
fix(token-buy): direction and volatility decide how to place the order, not whether to place it
2026-09-11 19:24:51 +08:00
chenjunfengandClaude Sonnet 5 be98adaeaa fix(gmgn-narrative): final self-audit — stale test-status claim, DNS-rebinding gap
Two findings from a final read-through before requesting merge:

- SKILL.md's Supported Chains section still said arbitrum/hyperevm were
  "untested" — stale as of this skill's own development, where 4 real
  tokens (2 per chain) were actually run through the full workflow with
  no chain-specific issue found. Corrected to describe that spot-check
  honestly (a signal, not a validation pass) instead of leaving a claim
  that had already been overtaken by the skill's own test history.
- interfaces.md's web-fetch address-range control named the private/
  loopback/link-local ranges to block but didn't say to pin the resolved
  address between the check and the connection — a hostname re-resolved
  a second time at connect can return a different (and unblocked)
  address, which is exactly what a DNS-rebinding attack targets. Added
  the one line closing that gap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 17:45:45 +08:00
chenjunfengandClaude Sonnet 5 35a0d812ee fix(gmgn-narrative): declare the web-fetch interface and fix rule/workflow contradiction
Review caught a real internal contradiction: step 4 requires opening a
linked source (a post's URL, a project's site) to verify a claim, but hard
rule 6 said the skill may only read from the two declared interfaces —
neither of which is a general web fetch. Declares a third interface
(web content fetch) with explicit controls: http/https only, private/
loopback/link-local address ranges rejected (closes the SSRF vector a
token's own attacker-controlled link.website field would otherwise open),
a timeout, a response-size cap, and re-validation on redirect. Rule 6 now
scopes "no browser automation" against open-ended browsing rather than
against fetching one already-known URL, which is what it was actually
contradicting.

Also, per the same review:
- New hard rule 7: a hard ~30-tool-call ceiling per card, so an
  adversarially ambiguous token can't turn one card into unbounded tool
  use — write the card with what's verified so far and say the cap was
  hit, same "neutral over fabricated" principle applied to cost/time.
- New hard rule 8: a product-facing note that a hedged single-sourced
  negative claim about a named/identifiable party still carries
  reputational and legal exposure, even when the hedge itself is correct.
- interfaces.md's turn-budget row and "what this skill must never do"
  section updated to match (three interfaces, not two; the 30-call cap
  is this skill's own stop condition, not one for the runtime to
  second-guess with a lower limit of its own).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 17:38:49 +08:00
chenjunfengandClaude Sonnet 5 130711ec2f feat(gmgn-narrative): add independent token narrative/due-diligence card skill
Produces a two-field narrative card (叙事背景 + 传播观察) for a token from
on-chain data (gmgn-cli) and independently-verified social posts, built to
catch the failure modes automated narrative cards on the market commonly
have: under-sampling social search, uncited/misattributed claims, launchpad
vs issuer confusion, and fabrication when evidence is thin.

Provider-agnostic by design: the social evidence step is a pluggable
interface (X API v2 or xAI Grok API), never browser automation or a
personal login — see references/interfaces.md for the configuration
contract and a validation-pass checklist for testing on a new model/runtime.
references/patterns.md is the red-flag checklist accumulated during
development (ticker collisions, similarly-named token mixups, creator
wallet history, paired-token mechanics, citation failures).

Explicitly out of scope for gmgn-contract-dd (safety score),
gmgn-heat-rank (screened hot list), and gmgn-token-buy/gmgn-swap (buy
decisions) — this only answers "what story is this token telling and
what's verifiable about its spread."

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 17:25:13 +08:00
chenjunfengandClaude Sonnet 5 dd6413c88f fix(token-buy): sync stale direction-gate wording in thresholds.md and pitfalls.md
Two leftover passages still described direction as a hard veto gate
that "must pass" alongside volume, contradicting the new rule in
section 三 where only the 5m ≥10% drawdown hard-stops and everything
else downgrades execution instead of vetoing the order.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 16:37:43 +08:00
chenjunfengandClaude Opus 5 48d61990cb gmgn-contract-dd: scope the EVM lock tightening to the six meme-native chains
Upstream added `arbitrum` and `hyperevm` (#227), making the file's chain counts
nine / eight rather than seven / six. Three things follow.

1. Chain counts in the passages this branch added still said six EVM chains and
   seven supported chains. Corrected, and the `burn_status: ""` coverage claim is
   extended with a 2026-09-11 measurement on the two new chains: ARB, WBTC, ODYS,
   SIGNAL, CPU and CHAMELEON all return `""`, so the burn half is missing there too.

2. The Step 3 tightening — deduct −12 and cap 79 on `lock_summary.is_locked ===
   false` alone — was calibrated on the six chains that existed then, where 12 of
   12 sampled tokens returned `is_locked: true`. It does not hold on the two new
   ones: measured 2026-09-11 on the top three of each chain's 24h trending list,
   ARB and WBTC on `arbitrum` and SIGNAL and CPU on `hyperevm` all return
   `is_locked: false` — 4 of 6, including the chain's own governance token and
   wrapped Bitcoin. Deep DEX liquidity from real market makers is not locked and
   is not meant to be, so there the deduction would fire hardest on the tokens it
   exists to clear. The row is now skipped on `arbitrum` and `hyperevm` and the
   tightening applies only to `bsc`, `base`, `eth`, `robinhood`, `arc`, `stable`.
   Cross-references in the cap summary, the cap-cause list and the Step 3 table
   row name the scope.

3. The two dated bsc trenches passages contradicted each other on the same
   180-row sample — 130 flap / 50 four.meme against flap 143 / fourmeme 37. The
   per-platform split is not a stable quantity: two captures of the same 180 rows
   hours apart on 2026-09-11 gave 150 / 30 and 137 / 43. The load-bearing claim is
   the mapping, not the mix, so the suffix passage now states the mapping with
   today's figures and says outright that a platform's share must not be read off
   one capture, and the non-load-bearing counts are dropped from the launchpad
   passage, whose actual claim is that `market trenches` never emits `openfour`.

No band boundary, weight or cap value changes. Inventory totals are unchanged at
23 on `sol` and 25 on the EVM chains — the skipped lock row stays in the
denominator as a skipped check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 16:35:29 +08:00
chenjunfengandClaude Opus 5 7b51b191d4 Merge up/main into contract-dd display-templates branch
Brings in the arbitrum + hyperevm chain support merged upstream in #227, so the
chain counts in this file are the nine-chain / eight-EVM ones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 16:32:30 +08:00
chenjunfengandClaude Opus 5 91088a8cf5 fix(token-buy): direction and volatility decide how to place the order, not whether to place it
The four gates were one flat "any single failure means no order" list, but they
answer two different questions. Volume, depth and security answer "can I get
back out once I am in" — structural facts that do not flip within hours.
Direction and volatility answer "where will a market order fill this second" —
which changes minute by minute. Merging them makes the same token get opposite
verdicts on the same day.

Measured: VC on BSC (0x36f01746a86639d0c3b6a850d328be1b634a4444) scored 91.4
"buyable" from gmgn-contract-dd, one-sided depth $91,442, 95% of LP burned to
the black hole, $6.6M 24h volume, 2,560 holders, zero taxes, owner renounced —
and was still reported as "not recommended" purely because 5m was -8.2% and all
three net-flow windows were negative. Two separate rules produced that: the
compound hard rule (three windows negative AND 5m drawdown >= 5%), and the
sentence that merged this section's warnings into the security section's
">= 2 warnings fails" count.

Section 三 now has three exits instead of two: 5m drawdown >= 10% remains the
one hard stop (a market order genuinely cannot execute there), everything else
downgrades execution — the order card is still produced, but it must name the
degraded execution path. Added a table of the three downgrade actions so none
of them can be given by feel: the limit trigger price is derived as
price.price * (1 - |5m drawdown|) and handed to gmgn-swap as
`order strategy create --order-type limit_order --sub-order-type buy_low
--check-price <price>`; waiting names the condition that has to converge (5m
drawdown back inside 5%, or buy_volume_5m - sell_volume_5m turning positive);
reducing size reuses the existing price-impact wording. The downgrade needs its
own separate confirmation — nodding yes to buying a token is not the same as
knowing you are no longer getting a market order.

Direction now produces no veto count at all, and missing direction data
downgrades to the strictest tier rather than failing the order: not being able
to read direction is not the same as a quiet market.

Nothing about the original intent is relaxed. The slippage formula is
untouched (its |5m change| * 0.5 term already prices volatility), the
>= 100x-with-negative-flow warning still refuses to widen slippage, and the
security gate's ten warnings and red flags are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 14:48:08 +08:00
GMGN.AI 4e48e25328 1.6.2 v1.6.2 2026-09-11 13:56:27 +08:00
GMGN.AIandGitHub 5fd50e552c Merge pull request #227 from dylanzhong662-max/feat/support-arbitrum-hyperevm
feat: support Arbitrum and HyperEVM
2026-09-11 13:55:10 +08:00
zhongsongzhi-cmyk 263e699397 feat: support arbitrum and hyperevm 2026-09-11 11:31:49 +08:00
chenjunfengandClaude Opus 5 de9ce030db gmgn-contract-dd: reconcile four live rules the reword left inconsistent
Step 6 still told the reader to print the section weights while Step 8 and
Display Templates both forbid printing coverage and weights, so the same
response could ship two different report shapes depending on which sentence
was read last. Step 5B's rug_ratio cap table still named the pre-reword band
("high risk") in a live rule, which also falsified the neighbouring claim that
every live occurrence had been corrected — that count is now three, with the
grep procedure that found the last one written down. Step 8's cap list named
Step 2, 5B and 7 but not Step 3's EVM lock cap, which the assembly list in
Step 6 already carries.

No threshold, weight or band boundary moves; every measured score in the file
is unchanged. Dated measurement passages keep the original audit vocabulary by
the policy stated in the file — all 32 remaining occurrences were classified
and every one of them is a record of a run, not a rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:19:02 +08:00
GMGN.AIandGitHub ec95135eca Merge pull request #225 from dylanzhong662-max/docs-clarify-cli-trenches
docs: correct market trenches --limit / --launchpad-platform description
2026-09-10 17:17:27 +08:00
GMGN.AIandGitHub 965b492fa1 Merge pull request #222 from Edwardchennnn/feat/gmgn-heat-rank
feat(skills): add gmgn-heat-rank — screened cross-chain hot list
2026-09-10 17:13:16 +08:00
chenjunfengandClaude Opus 5 176eb126ac gmgn-heat-rank: print contract addresses without a gmgn.ai link
The CA block was headed `CA (verify at https://gmgn.ai/<chain>/token/<CA>)`.
That path shape is the one thing in this file nobody was allowed to check: the
skill's own rules forbid reaching gmgn.ai by web search, WebFetch, curl or a
browser, so the URL was printed on the strength of an assumption. If the shape
is wrong the reader lands on a 404 and doubts the list rather than the link.

The addresses themselves were already complete and one per line, so dropping
the link costs nothing -- an address pastes into whatever front-end the reader
actually uses. The heading now says exactly that and names no host.

Behaviour is otherwise unchanged: replayed against five stored sweeps, the
scorer's output differs on that single heading line and nowhere else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 17:02:18 +08:00
chenjunfengandClaude Opus 5 685ce2177b gmgn-contract-dd: label the stat percentages by their real denominator
Five problems found by rendering the report against a live bsc token
(4Stock, 2026-09-10) and checking every printed line back against the
response. All five are output-wording problems; no scoring table and no
composite in this file moves.

- The stat percentages were rendered as shares of the supply. They are
  not: bot_degen_count 1068 / bot_degen_rate 0.5416 gives a denominator
  of 1971.9 on a token with 20,930 holders, and top_bot_degen_percentage
  returned exactly 0.5416 alongside it, which ties the whole
  top_*_trader_percentage family to that same analysed-trader cohort.
  The worked example in Display Templates carried the wrong wording and
  a live report inherited it, telling the reader that 61.1% of the float
  sat in entrapment wallets — a claim the response never made.
- The Limits of this read skeleton asked for off-chain presence
  unconditionally, so an all-empty link block was written up as "no
  off-chain presence at all". An empty link block cannot be told apart
  from GMGN not having collected the socials, so the bullet is now
  dropped unless a social field or the verification status has a value.
- An isolated stat zero was being printed as a passing check. The report
  now needs a second field from the same response to corroborate it:
  creator_hold_rate 0 qualifies (creator_token_balance "0" plus
  creator_token_status "creator_close"), top70_sniper_hold_rate 0
  qualifies (wallet_tags_stat.sniper_wallets 0), private_vault_hold_rate
  and dev_team_hold_rate do not and are dropped. Scoring is untouched —
  the zero is still an executed check in coverage.
- Market cap was being printed with no rule saying where it comes from,
  and the response carries no market_cap key at all. It is now fixed as
  price * circulating_supply, verified against a figure GMGN does
  publish: ath_price * circulating_supply = 82,633,663 matches
  dev.ath_token_info.ath_mc exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 16:53:27 +08:00
chenjunfengandClaude Opus 5 1ab16cb76e fix(heat-rank): iterate the chain list as an array, and match whole names
Step 1 held the chain list in a plain string and iterated it as
`for ch in $CHAINS`. That works under bash and silently does not under zsh,
which performs no word splitting on an unquoted parameter expansion: the loop
runs once with all seven names in one variable. The sweep then collapses to a
single call with `--chain "sol bsc base eth robinhood arc stable"`, which the
CLI refuses -- no data, no list, on every run in a zsh shell.

The whitelist did not catch it either. `case " sol bsc ... " in *" $ch "*` is a
substring test, so the whole concatenated string matched its own haystack and
was waved through; it accepts any run of adjacent names. It is now an exact
alternation over whole names, which also refuses `sol bsc` and anything
carrying shell metacharacters. `$ch` and the two output redirections are
quoted.

Introduced by b4b54ad, whose own verification ran the extracted block under
bash and so could not see it.

Verified: `-n` clean under zsh, bash and bash --posix; seven iterations with
the correct per-chain filters in all three; and a live run under zsh now makes
15 calls across all seven chains (eth/arc/stable short-circuiting on an empty
24h window as designed), 56 candidates -> 10 passed -> 3 listed. The scorer
block is byte-identical, so scoring is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 16:37:34 +08:00
chenjunfengandClaude Opus 5 dfbbb7c688 fix(gmgn-heat-rank): an unreadable age is unknown, not zero
`rage` fell back to `now` when a row carried neither `open_timestamp` nor
`creation_timestamp`, so a row with no age read as "launched this instant":
it walked through the `MAX_AGE_D` ceiling -- the one gate the whole list
rests on -- was judged on the wrong (new-launch) track, and would have taken
full freshness credit had it cleared that track's gates. That is the same
mistake as reading a missing risk field as clean, which this skill refuses
to make anywhere else.

An unreadable age is now placed past the ceiling and refused as
`no timestamp (age unknown)`, reported as the data fault it is rather than
as a token that failed a check. Both timestamp fields are normalised as
counts, so an unparseable value arrives as 0 and is caught by the same test
as an absent one.

Verified: on a row with both timestamps stripped, before/after is
rage=0.000 age=0.500 young=True (age gate silent) -> rage=8.000 young=False
fail=['no timestamp (age unknown)'], with no duplicate age-backstop reason.
Output is byte-identical on four unmodified fixtures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 16:19:19 +08:00
chenjunfengandClaude Opus 5 17d312bd1b fix(heat-rank): drop --filter is_out_market, which the API does not recognise
`is_out_market` is not a filter tag the API knows, and an unrecognised tag is not
refused -- it is silently ignored. Worse, a filters list made only of unrecognised
tags does not fall back to the server's defaults: it replaces them with nothing.

Measured on sol with the skill's own pool parameters: `--filter is_out_market` and
`--filter zzz_fake_tag_qqq` returned the identical 22 rows, both a superset of the
18 returned with no --filter at all.

On sol / bsc / base / eth the tag sat alongside real tags and was inert -- dropping
it returns the identical address set on both chains tested. On robinhood / arc /
stable it was the *only* tag, so those three chains were fetched with server-side
screening switched off. On one robinhood sweep that admitted 6 rows the defaults
exclude: 4 with is_honeypot=1, plus CLUBY and BRIAN, neither renounced nor open
source and neither caught by any local gate in this file.

So the fourth branch now sends no --filter and lets the server's defaults apply,
with a comment saying not to invent a tag to fill it, and `## Known limits` records
the measurement.

Verified end to end on a live sweep with the fix in place: 15 calls, the three
empty chains short-circuited, robinhood 20 rows instead of 26, and zero
is_honeypot=1 rows anywhere in the candidate pool. 52 candidates, 10 passed,
3 listed. Step 1 passes bash -n; an empty F array expands to no arguments; the
scorer is byte-identical to the previous commit's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:56:53 +08:00
chenjunfengandClaude Opus 5 7fe841b9fb gmgn-contract-dd: put the verdict on the buyability axis, drop the launchpad claim
The verdict band words were audit vocabulary — `relatively clean · mixed,
needs manual review · high risk · very high risk`. "Mixed" names the average
of the three sections, which is the one reading that hides a one-rotten
two-clean profile, and "needs manual review" hands the work back without
naming what to review. A reader who pastes an address is asking whether they
can buy it, so the bands now answer that: `buyable` / `buyable with
conditions` / `not advised` / `do not buy`, each paired with a clause derived
by rule from which sections fell under 60, so the pair carries how buyable
from the word and what is in the way from the clause.

The 80 / 60 / 40 boundaries did not move, so no composite in this file
changes grade. Dated measurement passages keep the original words as records
and a decoder maps them one-for-one; two live rules that still named a band
in the old vocabulary are corrected.

Three guards come with the new axis, because `buyable` is easier to
over-read than the audit words were: it means the token can be bought and
sold back, never that it will appreciate; `Buy or not` may not contradict the
band word nor merely repeat it; and the band word is not permission to size a
position, which stays gmgn-token-buy's job.

Also drops the launchpad line from the report. `info.launchpad` is not
reliable enough to name: the vocabulary is unstable across endpoints (4Stock
returns `openfour` from `token info` while all 180 `market trenches` bsc rows
use only `flap` and `fourmeme`), and 9 of 100 bsc trending rows return an
empty string with no way to tell "no launchpad" from "unknown launchpad".
Measured coverage is complete on sol and unmeasured on base/eth/robinhood/
arc/stable, so the claim goes rather than ships hedged. The address-suffix
rule stays — it rests on its own 180-row tally.

And corrects an over-absolute claim: `pool.initial_liquidity` is *usually* 0
on a migrated pool, not always. Measured on 4Stock, GMGN had switched the
reported main pool to a pancake_v3 pool created about half an hour after
launch, which returns 19893.57. A non-zero value is that pool's own opening
depth and must never be reported as the token's launch depth; a shrink ratio
above 1 means the pool deepened and is never a deduction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:42:23 +08:00
zhongsongzhi-cmykandClaude Opus 4.8 6572db0e2d docs: correct market trenches --limit / --launchpad-platform description
The CLI docs promised behavior the API does not deliver:

- --limit: was "max 80 (default 80)". In reality it is best-effort only —
  the new_creation category is served from a fixed live window (~60), so
  larger and smaller --limit values both tend to return ~60. Documented as
  best-effort.
- --launchpad-platform: was "default: all platforms for the chain". In
  reality, when omitted the server injects a fixed per-chain default
  allow-list (not literally all platforms), and passing values REPLACES that
  allow-list (fewer platforms => less data). Platforms outside the list
  (incl. some newer RWA / tokenized-stock launchpads) are filtered out.
- Added the previously-missing robinhood platform slug list, and noted that
  arc/stable apply no launchpad filter (all platforms).

Docs only; no code change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 15:36:35 +08:00
chenjunfengandClaude Opus 5 dd485face1 perf(heat-rank): fetch 24h first and skip a chain's other two windows when it is empty
A candidate has to be present in the 24h window to count at all -- the scorer
only uses a chain when a 24h file loaded, and keys candidates off it. So for a
chain whose 24h window comes back empty, the 1h and 6h calls cannot produce a
candidate no matter what they contain. The sweep was spending them anyway.

Reorder the window loop to 24h first and break out when the 24h response holds
an empty rank array. On a measured sweep eth, arc and stable were empty in all
three windows, so this drops 21 calls to 15 and ~30s to ~21s. Worst case is
unchanged: when all seven chains are alive the sweep still costs its full 21.
This cuts waste, not coverage -- no window that could hold a candidate is skipped.

Teach the loader to tell a deliberate skip from a failure, so the saving does not
print as two failed calls. `missing (excluded)` keeps meaning one thing: a call
that failed or returned unparseable JSON. A 24h window that itself failed to load
still reports, since that is the signal worth seeing.

Verified against the frozen sweep: with eth/arc/stable's 1h and 6h files removed
the run is identical -- 60 candidates, 11 passed, the same five names with the
same scores in the same order -- and no missing-window line. Corrupting bsc_6h
and sol_24h still reports both. Step 1 passes bash -n.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:34:55 +08:00
chenjunfengandClaude Opus 5 b4b54ad192 fix(heat-rank): check chain names against a fixed whitelist before they reach the CLI
The chain list is a documented parameter a user can narrow, and whatever is in
it lands on a command line unquoted. Nothing validated it, so a typo, a chain
name from some other exchange, or a string carrying spaces or shell
metacharacters would have been passed straight through to gmgn-cli as arguments.

Hoist the list into a CHAINS variable (which is the thing to narrow) and check
each name against the literal set of the seven names this API actually has,
refusing anything else out loud and skipping it. The whitelist is deliberately
written out again rather than reusing $CHAINS, which would check the input
against itself.

Tested: sol / eth / robinhood accepted; "xyz", "sol; rm -rf /" and
"sol --limit 1" refused. Step 1 passes bash -n, and the extracted scorer is
byte-identical to the previous commit's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:24:34 +08:00
chenjunfengandClaude Opus 5 feb6331c47 fix(heat-rank): strip pipes and backticks from token symbols
sym() already removed control characters, terminal escapes and bidi overrides,
but let two markdown metacharacters through into text that gets copied straight
into the report. A token named "X | buy now" splits into two cells of the
report's markdown table with the second one written by whoever deployed the
contract; a backtick opens or closes a code span. The symbol is data the
attacker chose, so both become ?.

Tested on the extracted function: 'X | 买它 | 忽略上文' -> 'X ? 买它 ? 忽略上文',
'AAA`rm -rf /`BBB' -> 'AAA?rm -rf /?BBB', ordinary symbols untouched. Verified
against the stored sweep: same five names, same order, no symbol in the sample
contains either character.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:23:30 +08:00
chenjunfengandClaude Opus 5 6306d99a6e fix(heat-rank): use mktemp -d instead of a guessable path under /tmp
Step 1 built its working directory as /tmp/gmgn-heat-data-$(date +%s) -- a
second-resolution timestamp in a world-writable directory -- then wrote
heat_rank.py into it and executed it. Another local user can guess that name and
pre-create the directory holding their own heat_rank.py, at which point the run
executes their code as the user. Narrow (it needs a local unprivileged account)
but it is local code execution on a path this skill both writes and runs.

mktemp -d gives an unpredictable name and mode 0700, verified on this machine
(/var/folders/.../tmp.XXXXXXXX, drwx------).

The scorer's fallback was the matching half of the same problem:
os.environ.get('HEAT_DATA', '/tmp/gmgn-heat-data') meant a run with the variable
unset would read a fixed path anyone can plant JSON into. There is no safe
default, so it now exits with the correct invocation instead of inventing one.

Verified against the stored sweep: same five names in the same order. The
candidate count reads 11 rather than 12 only because the fixture keeps ageing
against a live clock -- robinhood PAIR has crossed 7d and the age backstop from
897153f drops it, at score 53.4, far below any floor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:22:39 +08:00
chenjunfengandClaude Opus 5 f6105c5ae0 docs(heat-rank): close the last two routing overlaps, and say no input is needed
Rescanned the current upstream skill set (13 skills on up/main, aa4d29a) and
grepped every description for hot-list vocabulary. Only gmgn-market matches,
and two of its claims were still unanswered here:

- "discover early-stage opportunities" and its Chinese equivalents. The
  description already sent "just launched" / "新盘" to `market trenches`, but not
  the ambush framing (值得埋伏吗, 新币筛选, 早期机会), which is exactly where the two
  skills read alike. Named, with the reason: the pool here starts at 500k market
  cap and 100k liquidity and never holds a bonding-curve token.
- gmgn-track's "what smart money is buying" / "on-chain alpha" /
  "copy-trade signals". The body table routed it; the description, which is what
  a router actually reads, did not.

Also added what the description never said about using it: no input is needed at
all -- the trigger phrase alone runs it, no address, chain or amount -- and the
four values a user can narrow. Runtime and pacing stay out of the description,
where no skill in this repo puts them; they are already in ## Pacing.

Documentation only: the extracted scorer is byte-identical to the previous
commit's and the stored sweep returns the same five names in the same order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:10:01 +08:00
chenjunfengandClaude Opus 5 bc53c7d34c docs(heat-rank): stop advertising four flags the CLI does not have
argument-hint offered "--chains sol,bsc,... --max-age 7d --top 10
--min-score 60". Measured against `gmgn-cli market trending --help`, none of
the four exists: the CLI takes one `--chain` at a time (the seven-chain sweep
is the Step 1 loop, not a list argument), the age flag is spelled
`--max-created`, and TOP_N / MIN_SCORE are Python constants no flag reaches.
So a request like "this time only sol and bsc" or "raise the floor to 70" led
straight to an invalid command line, even though all four values are in fact
tunable and already listed in Parameters under their real names.

Rename the hint to the names used in the body, and add a paragraph under
Parameters saying these are spoken options rather than flags and mapping each
one to the single place it changes -- including that max-created now moves
together with MAX_AGE_D.

Documentation only: the extracted scorer is byte-identical to the previous
commit's and the stored sweep produces the same five names in the same order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:04:36 +08:00
chenjunfengandClaude Opus 5 897153fddd fix(heat-rank): re-check age locally instead of trusting the server alone
"Recently hot" is the premise of this list, and ## Rules calls age a hard gate
with no compensation logic. Its only enforcer was --max-created 7d in Step 1 --
a server-side filter. The script computes each row's true age from
open_timestamp and used it for the young/mature track split and the 24h-change
blanking, but never compared it to the ceiling, so an endpoint that ignored or
re-interpreted the parameter would put a months-old token on the list under the
word "recent" with nothing local to stop it.

Add MAX_AGE_D = 7.0 and reject any row above it alongside the other
unconditional gates, with its own rejection reason. Document it in Parameters as
tied to --max-created, and say in ## Rules that the gate is enforced in both
places and the two values move together.

Verified against the stored seven-chain sweep: no row is affected -- the oldest
candidate is 6.99d, so the gate is silent, which is the intended steady state.
Re-running the same data with MAX_AGE_D lowered to 5.0 drops robinhood ZZZ
(5.3d) and MEME (6.4d) with reason "age>5d(local backstop)", 5 listed -> 3,
confirming the gate fires and reports itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:03:44 +08:00
chenjunfengandClaude Opus 5 dc69d8fc08 fix(heat-rank): print both score floors instead of only the lower one
The floor is two-valued -- MIN_SCORE for a row every gate could judge,
MIN_SCORE + U_SCORE_ADD for one no manipulation gate could -- and floor_for()
applies it correctly. The diagnostic line printed only MIN_SCORE, so the one
number carried into the report was wrong for half the rows.

The visible damage was in the near-miss block, which exists so the boundary can
be inspected and instead made the skill look broken: on a measured sweep bsc
BREW at 63.4 was dropped while sol LOOM at 63.3 was listed. Correct -- BREW
sits on a chain with no rug model and needed 68 -- but a report claiming a
floor of 60 alongside those two lines reads as padding or a bug.

Three changes, none touching selection:
- the diagnostic line names both floors and how many rows fall in the higher
  tier ("floor 60, or 68 for the 6 of 12 rows no manipulation gate could judge")
- each near miss prints the bar it actually had to clear
- the reporting checklist forbids writing the floor as a single number, and
  still forbids explaining the two tiers, which would disclose what ## Rules
  keeps out of the report

Verified against the stored seven-chain sweep: same five names, same order,
same three near misses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 15:02:54 +08:00
chenjunfengandClaude Opus 5 9ddda22279 fix(heat-rank): blank the 24h change cell for tokens under a day old
The 24h% column is only meaningful once the token has 24h of history. Below
that the window opens before the token existed, so price_change_percent is
measured from the launch price and prints figures like +128168.0% -- correct
arithmetic, no information about momentum, and wide enough to break the
column's alignment.

This was latent until the previous commit: the reference row used to be
whichever window loaded last, so a young token showed its 1h change under the
24h heading and happened to look plausible. Reading the right window exposed
the real number.

Print n/a instead when age < 1 day, matching how an unknown ATH position is
already rendered. The age column sits immediately left of the cell, so the
reason the cell is empty is visible without a footnote. Rows a day or older
are untouched.

Verified against the stored seven-chain sweep: same five names in the same
order, the four rows aged 2.2d-6.4d keep their 24h figures unchanged
(-21.2 / -0.4 / -18.7 / +39.7), and the 8.2h-old row now reads n/a.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 14:53:46 +08:00
chenjunfengandClaude Opus 5 ad9ff57741 fix(heat-rank): take the reference row from the 24h window, not whichever loaded last
`U.setdefault(key,{})['ref']=t` only guaranteed the key existed -- the assignment
that followed ran unconditionally, so the reference row ended up being whichever
window was loaded last. Iteration order is 24h, 6h, 1h, so for any token that
appeared in the 1h list the reference row was the 1h row.

Most fields read off that row are a current snapshot and are unaffected, but
`price_change_percent` is the change over that row's own window, so the column
headed `24h%` printed a 1h move -- and printed a 24h move for tokens that only
appeared in the 24h list, so the column was not even consistent between rows.
Measured on a stored 7-chain sweep, all five listed tokens were showing their 1h
change: 4Stock printed +29.7% where its 24h move was -21.2%, and MEME printed
+0.1% against a real -18.7%. A reader comparing the list against the exchange
would have found two of five names moving the opposite way.

setdefault now keeps the first window that carried the token, which is 24h for
every listed name (a token absent from the 24h list is already rejected).
Re-scored against the same sweep: same five names, same order, and all five
percentages now match the 24h files exactly. Scores shift by up to 0.5 because a
few snapshot fields differ slightly between the 24h and 1h responses, which were
fetched seconds apart.

One thing this exposes rather than fixes: a token younger than a day now shows
its true 24h change, and for an 8-hour-old token that is +128168%. The number is
correct and useless; a separate change should blank the cell below a day of age.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 14:46:39 +08:00
chenjunfengandClaude Opus 5 3c49980398 fix(heat-rank): defend the boundary against gmgn-market, and drop three dead symbols
The routing collision was one-sided. This skill's description told the reader
where a raw ranking dump belongs, but `gmgn-market` still claims "trending
tokens, what's pumping, hot coins" and cannot point back here, so an English
"hot coins" or a bare Chinese 热门币 could land on either. Rather than edit that
skill, the deciding test now lives here: what shape of answer is being asked
for. An untouched ranking of N rows in the exchange's own order is
`gmgn-market trending`; a short list that survived risk gates and carries a
score is this skill. A bare ambiguous ask defaults here, because this skill can
point at the raw ranking while the raw ranking cannot screen itself. The
"what this skill is not" table now names the colliding wording too.

Also removed three symbols that never did anything:

- `MAX_BUNDLER = 0.40`, defined and never read. The ceiling that actually fires
  is each chain's leave-one-out p90, and a flat 40% is precisely the mistake
  that deleted whole chains before -- leaving the constant in suggests
  otherwise to the next reader.
- `RANK`, a dict of per-window ranks that nothing consumes.
- `import glob`, unused.

And `near` compared whole candidate dicts by value to decide what was already
listed; it now compares (chain, address), which is what identity means here.

No threshold, weight or gate changed. Re-scored against a stored 7-chain sweep:
the same five names in the same order, the same three near-misses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 14:45:46 +08:00
chenjunfengandClaude Opus 5 153b72320c gmgn-contract-dd: reshape the report to the dev-score output template
The Display Templates section shipped as a shape table — which section holds
what — and Step 8 asked for `field path → measured value → points → reason`
plus a `executed N / skipped M / held-out K of T` coverage line. Together they
produced a report written for whoever audits the scorer rather than for the
person deciding whether to buy: two dozen field paths, the section weights, and
a full list of every field that could not be read, on a token whose actual
answer was "the contract is fine, the holders are bots".

Replace it with the shape `gmgn-dev-score` already uses for its own report: a
header block carrying the number, narrative sections that each answer one
question a buyer has, and a fixed four-question decision block at the end. The
section is now a fill-in skeleton rather than a description of one, so the same
JSON lays out the same way every run.

Three rules the new section adds:

- No field paths and no internal accounting in the output. Coverage is still
  computed exactly as Step 6 specifies, because it sets the confidence label —
  it just is not printed, and neither are the weights.
- Numbers stay, jargon goes. Every measured value is still printed; what
  changes is the label beside it.
- Exactly one table in the report (`Key numbers`). Everything else is prose.

Step 8 items 2 through 6 are rewritten to match, since as written they mandated
the output the new section forbids. The `stat` disclosure stays mandatory and
moves to plain wording; absent fields are described in words instead of listed
with a reason column; fields that are not applicable on the chain are no longer
listed at all, so a Solana report stops padding itself with a honeypot
non-finding. A `[filtered]` string or a `neutralized … suspicious metadata`
stderr notice remains impossible to drop.

No scoring table, threshold, weight or cap changes. The composite, the grade
bands, the coverage arithmetic and every measurement in "Where the thresholds
came from" are untouched — this is the presentation layer only. Verified on
4Stock (bsc, 0xd270d4e1ec6e6e0d28c0ecb8be966ec75997ffff): 85.7, contract 90.0 /
holders 72.0 / price 100.0, identical before and after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 19:20:26 +08:00
chenjunfengandClaude Opus 5 a0ef19d543 feat(skills): add gmgn-heat-rank — screened cross-chain hot list
A screening skill that starts from no address: it sweeps all seven chains
across three time windows (21 paced `market trending` calls), gates every
candidate on liquidity, real volume, turnover, concentration and the
rug/dev risk fields, scores the survivors on one cross-chain scale, and
returns however many clear the score floor, capped at ten. It never pads
the list to fill the quota.

Two things it is careful about:

- Bot share is applied as a volume discount rather than a switch, and the
  bundler ceiling is each chain's own leave-one-out p90, so no flat
  threshold silently deletes a whole chain.
- Several manipulation fields read a literal 0 on chains GMGN's analytics
  do not cover — `bot_degen_rate` and `bundler_rate` on base/eth/arc/stable,
  and `rug_ratio` on six of the seven chains. That is "never measured", not
  "clean", so those rows must clear a set of compensating thresholds and a
  score floor 8 points higher instead of inheriting a pass. A row that
  cannot is dropped.

Self-contained: one SKILL.md, no extra files. Data comes only from
`gmgn-cli market trending`; the embedded scorer is read-only and uses the
Python standard library only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 19:07:35 +08:00
GMGN.AI aa4d29a9b7 1.6.1 v1.6.1 2026-09-04 16:12:14 +08:00
GMGN.AIandGitHub 3d9db45c18 Merge pull request #220 from Edwardchennnn/docs/contract-dd-buy-routing
docs(gmgn-contract-dd): append the gmgn-token-buy boundary
2026-09-04 16:11:28 +08:00
GMGN.AIandGitHub cff0764baf Merge pull request #219 from Edwardchennnn/docs/swap-buy-routing
docs(gmgn-swap): append the gmgn-token-buy boundary to the description
2026-09-04 16:11:16 +08:00
GMGN.AIandGitHub c9748da7c8 Merge pull request #217 from Edwardchennnn/feat/token-buy-skill
feat(skills): gmgn-token-buy — buy-side due diligence from a token name, plus the market search command it needs
2026-09-04 16:10:59 +08:00
chenjunfengandClaude Opus 5 949ecff138 gmgn-token-buy: 按 dev-score 模版整形,并补上实盘跑出来的四个缺口
结构对齐 skills/gmgn-dev-score/SKILL.md 的骨架,流程对齐 DefiLlama
token-research 的 step 形状:

- 去掉 H1 与 `## 适用与边界`(后者三段散文与 description 逐字重复),
  边界改成 `## Run` 里一张四行表。
- `## 流程` → `## Workflow`,`### 第 N 步 · X` → `### Step N — X`,
  每步只留「跑什么 / 关键字段 / 出口」加几条短要点,长散文移进 references。
- `## 订单卡必须说到的东西` → `## What the answer has to contain`。
- 删掉 `### 任一闸门未通过时`(Display Templates 与 Rules 已覆盖)。
- 新增 `references/resolution.md`:Step 1 的消歧判据全文。
- description 修掉一处 he/his 指代,并补上模版要求的收尾一句
  「边界按「这个请求还差什么没做」划分」。

100u 买 MEME 的实盘演练暴露四个缺口,一并补上(`references/thresholds.md`):

1. 新增第三道闸「方向与波动」。实测 robinhood 上的 MEME 24h 成交
   $63.14M、1h 32,446 笔,四道闸里的量闸全过,而它 5 分钟内从
   $0.1107 跌到 $0.0924(−16.5%),且 1m/5m/1h/24h 四个窗口卖出额
   全部大于买入额。量的大小和量的方向是两件事,原来只判前者。
2. 警告项从 6 条补到 10 条,加上响应里本来就有、却没被任何清单读到的
   `top_entrapment_trader_percentage`(实测 55.57%)、
   `top_bot_degen_percentage`(76.78%)、
   `wallet_tags_stat.bundler_wallets / holder_count`(8.5%)、
   `stat.fresh_wallet_rate`。补之前这个币只命中 1 项警告、过了安全闸,
   补之后命中 4 项、正确判不通过。
3. 滑点公式加波动项 `+ |5 分钟涨跌幅| × 0.5`。原公式只吃池深和税率,
   给这个币算出 2.0%,而它 5 分钟动了 16.5%,市价单必然失败。
4. EVM 的 gas 用量按 `pool.exchange` 给默认值(v2 150K / v3 180K /
   v4 200K / 未知 200K),并要求在卡里标明是估算——它是订单卡里唯一
   一个不来自任何字段的数,不给默认值就只能靠模型自己编。

另外记下两条实测字段语义:`price_5m` 是 5 分钟前的起点价而不是涨跌幅;
`lock_summary` 会自相矛盾(`is_locked=true` + `lock_percent="0"` +
`lock_detail[0]` 95% 打进黑洞),判定顺序以 `lock_detail` 为准。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 15:01:15 +08:00
chenjunfeng 20baf982d6 fix(token-buy): hand a launcher question to gmgn-dev-score
gmgn-dev-score landed upstream 18 minutes after this branch's last commit,
so its buy-intent phrases were never reconciled with this skill's. Both
claim a buy decision; the subject differs. This skill's subject is a COIN
("PENGU 能不能买"), dev-score's is the PERSON who launched it ("这个 dev 的
新盘能不能买", "should I buy his next launch"). A launcher question carries no
token name to resolve and no amount to size, which is everything this skill
does, so it goes to dev-score and comes back here once the user names the
coin.

Adds that clause to the description and one 与 gmgn-dev-score 的分工 line to
the boundaries section. Description 1990 -> 2299 chars (dev-score's own is
2300). Nothing else changed.
2026-09-04 14:33:10 +08:00
chenjunfeng 7d38be6dce refactor(token-buy): make the four-step workflow scannable
Borrows the workflow layout from DefiLlama's token-research skill
(DefiLlama/defillama-skills, token-research/SKILL.md): every step states the
command to run right at the step, then the fields it reads, then its exit
condition — instead of listing all four commands up top and describing them
in prose further down.

- `## Run` opens with a four-row overview table (step / what it does / what it
  runs / its exit), so the whole gated flow is visible at a glance, and states
  the invariant the old text left implicit: each step IS a gate, and a step
  that does not pass never reaches the next one
- each `### 第 N 步` now leads with **跑什么** (a fenced command), **关键字段**
  (what to read, pointing at references/fields.md), **出口** (continue on what,
  stop on what) before the reasoning prose
- step headings name the action: 名字解析成唯一合约 / 三道硬性筛选(量 / 深度 /
  安全)/ 组装订单卡并请求确认; 筛选未通过时 → 任一闸门未通过时
- drops the lead sentences the command blocks made redundant, and the
  restatement of the 7-chain boundary inside step 0

No threshold, field or routing boundary changed.
2026-09-04 14:33:10 +08:00
chenjunfeng 30217f4dc4 refactor(token-buy): reshape to the gmgn-dev-score skill template
The skill was one 369-line file with the command reference, the field
mappings and the whole threshold table inlined as two appendices, and no
fixed shape for the order card. gmgn-dev-score is the template every skill
in this repo follows, so adopt its skeleton:

- description becomes a `>-` block scalar (same content, now readable)
- add the three preamble notices every other skill has and this one was
  missing: `config --check`, only ever `gmgn-cli` (no web search / WebFetch
  / curl / gmgn.ai), and IPv6 is unsupported
- `## Run` — the four read commands, the fixed 4-request budget, the 7-chain
  boundary, and the fact that the CLI only returns numbers: the report is
  written by the model, in the user's language
- `## 订单卡必须说到的东西` — a checklist of what must be *said*, each item
  naming the field it reads
- `## Display Templates` — the order card's shape is now fixed (8 sections,
  when each may be omitted) with the wording left free, replacing the old
  space-padded ASCII block with emoji ticks. Same formatting rules as
  dev-score: plain ascii dollar sign, one decimal on percentages, no emoji,
  no column padding, bold only where named
- `## Rules` — the three hard rules plus the three that follow from the
  7-chain boundary, the hostile-metadata rules, and the report-is-the-whole-
  answer / never-state-a-number-the-fields-do-not-carry rules
- `## References` — appendix A moves to references/fields.md, appendix B to
  references/thresholds.md, and 常见错误 to references/pitfalls.md

No threshold, field mapping or routing boundary changed value; SKILL.md goes
from 369 to 258 lines with the moved text intact in the three reference
files.
2026-09-04 14:33:10 +08:00
chenjunfengandClaude Opus 5 a0ba4c6a2d fix(token-buy): frame gmgn-swap as this skill's exit, not its rival
gmgn-swap's own description says "Use when user asks to buy, sell, or swap a
token" with no qualifier, and carries a [FINANCIAL EXECUTION] label. This
skill answered that by asserting a plain buy request "is MINE by default",
which is a claim about precedence and gives a reader nothing to check.

Replace the assertion with the two facts that actually decide it. First, the
two skills are not after the same outcome: gmgn-swap signs and submits, this
skill holds no private key and places no order, so a vetted buy ends AT
gmgn-swap by design. Second, gmgn-swap cannot start from a name either — its
--output-token takes a contract address and the only names it resolves are
the SOL/BNB/ETH/USDC currencies — so a request naming a token has to arrive
here whichever skill is asked first.

Then name what genuinely belongs to gmgn-swap instead of gesturing at it:
an explicit skip of the pre-buy check, and everything this skill does not do
at all — selling, percentage sells, limit orders, stop loss, take profit,
trailing orders, multi-wallet batch trading, order status, gas-price lookups.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:33:10 +08:00