mirror of
https://github.com/GMGNAI/gmgn-skills.git
synced 2026-11-02 07:46:11 +00:00
ff663aa3b919212255094c0807c9d9a366bba52d
70
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ff663aa3b9 |
fix(holder-analysis): a missing buy count was read as proof of zero-cost chips
`buy_tx_count_cur == 0` was taken to mean "these chips were transferred or
airdropped in, the holder paid nothing". Measured data refutes that inference.
On musebook (robinhood) 38 wallets read `buy_tx_count_cur: 0` and
`avg_cost: null`, but 34 of them carry a finite `unrealized_pnl` ratio — +2.7%,
+76.5%, +546% — and a chip with no cost cannot produce a finite PnL ratio; a
+2.7% ratio is only possible against a cost basis near the current price. 32 of
the 38 also carry a `fomo` frontend tag and 2 a `gmgn` tag, which is the trace of
active trading, the opposite of receiving an airdrop. What is actually missing is
buy-transaction indexing on that chain, while the PnL fields come from a separate
computation and arrive populated.
The error was systematic per chain rather than per token. Never-bought share of
supply across one batch: robinhood 27.0% and 41.2%, arc 26.3%, sol 4.8%. The 20%
airdrop warn gate therefore fired on essentially every robinhood and arc token and
essentially never on a sol token — a difference in index coverage, not in chip
structure. Reporting an unpopulated field as a risk conclusion is what this skill
explicitly forbids elsewhere.
So zero cost now requires positive evidence of no cost:
def has_cost_basis(h):
return (h.get('avg_cost') or 0) > 0 or h.get('unrealized_pnl') is not None
Six consumers of the old predicate move to it, all the same root cause:
- the `airdrop` cohort and its >20% warn gate, plus the chip-quality buckets and
the exit-signal line that read it
- `diamond`, whose own stated rationale is "a wallet that never paid has no cost
to hold through" — a proven cost basis satisfies that, and requiring an indexed
buy count discarded most real diamond hands (JOLLY: 34 → 75 wallets,
21.1% → 55.7% of float)
- `idle_airdrop`, whose "zero cost" caption was a false statement for those rows
- Top5 sell-risk classification, which labelled a wallet sitting at +6.6x as
"zero-cost airdrop — can dump anytime" and simultaneously dropped it from the
trapped and in-profit counts
Wallets with no indexed buy count but a real cost basis are not dropped from the
report — they get their own neutral line, `买入未记录 / Buy count unindexed`, with
their float share. That upstream did not index their buys is true and stays
visible; it just no longer drives a zero-cost gate.
Known limit, stated in SKILL.md: a genuine airdrop wallet whose `unrealized_pnl`
is a huge ratio computed against a dust-level cost is missed here. No second ratio
threshold is added — there is no measured sample to calibrate one on.
Also in the Advice section: a ⚠️ Caution rating needs two or more warns by
definition, but only `warns[0]` was printed, so a reader could never see why the
rating was what it was. Every triggered reason is now listed.
Ratings, up/main → this branch, measured end to end:
musebook robinhood 🔴 Not Recommended → 🟡 Light Position
JOLLY robinhood 🟡 Light Position → ✅ Normal
ARGUS arc ⚠️ Caution → 🟡 Light Position
SI sol 🟡 Light Position → 🟡 Light Position (control)
SI is the control: sol indexes buy counts normally, so it barely moves and its
🔴 risk-wallet reading of 45.36% is untouched. This is not a blanket loosening —
it only takes effect where the index is thin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
338b0efdb9 |
fix(holder-analysis): the token's own contract address was scored as a holder wallet
Upstream returns the token's own CA in `token holders` with `addr_type: 0`, i.e. as an ordinary wallet. Measured on musebook (robinhood, 0x91a2dae9699f0b82540b5886b0d8759c22820ba3): `addr_type: 0`, 0 buys, 0 sells, 0 transfer-out, 15.01% of total supply, $5.06M, carrying a `fresh_wallet` tag. Left in the wallet cohort that one row polluted `biggest`, Top10, Top20, airdrop, fresh wallets, risk wallets and the Top5 display at once, and on its own tripped the danger gate as "largest wallet holds 16.30% — extreme concentration", rating the token 🔴 Not Recommended. The largest genuine wallet there is 4.45% of float. Controls on the same batch — JOLLY (robinhood), SI (sol), ARGUS (arc) — carry no such row, so this is a per-token upstream classification gap, not a chain convention, and cannot be handled by skipping a chain. The address is partitioned out of `normal` and reported on its own line on a total-supply basis, with its own warn gate at >10% of supply. It is not removed from the judgement: contract-held supply reaches the market as soon as one release transaction lands, so it stays visible and still counts toward the rating. Warn rather than danger because that release is an observable prior step, unlike a whale who can sell at will; no higher danger tier is added, because one measured sample cannot calibrate a second threshold. It is deliberately not deducted from the float denominator. Burn is permanent and the DEX pool is the market itself, so neither can dump; self-held supply can. Removing it from the denominator would raise every other wallet's percentage on every token carrying this row, manufacturing new false positives while fixing one. The same address is also excluded from `top100_map`, which feeds the dev sock-puppet danger gate: chips sent back to the token contract, or gas paid to it, is not "transferred to an internal wallet". musebook: 🔴 Not Recommended → ⚠️ Caution. Top10 34.7% → 19.4%, Top20 43.4% → 27.8%, risk wallets 21.01% → 4.71%, fresh wallets 18.25% → 1.95%. Regression on the three control tokens: JOLLY and SI byte-identical, ARGUS differs only on the market-cap header line (live price drift between runs). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
117884db9c |
fix(gmgn-heat-rank): growth rates off creation time, and stop one bad created.json entry discarding the rest
Three fixes found by testing the age split, not by reading it. 1. holders/day and KOLs/day still divided by the pool-open age. Those counts accumulate from the moment the token exists, so a token that spent 18 days on a bonding curve and migrated yesterday reported 10,836 holders/day and 22.0 KOLs/day against a true 993 and 2.0 -- a wrong number printed on the address line and fed into an axis carrying weight 0.15. They now divide by the same creation age the track and the freshness axis read, which falls back to the open-time age when Step 3b has no answer and is clamped never to read younger, so a lookup can only lower a row's own rate. `c['age']` had no other reader and is retired rather than left beside `cage` as a second age. 2. The created.json loader judged every entry under one try block, and comparing a non-number against the plausibility window raises. So a single unreadable entry threw away every entry after it, and those rows returned in the next NEEDS-CREATED block -- a loop with no exit if the feed keeps answering the same value. Each entry is now judged on its own and no comparison can raise. Measured: ten bad values x first and last position, 20 runs, every one exits 0, the readable entries survive, and each unreadable one falls back to open time. 3. Pass 1 stopped with SystemExit(0), so "stopped to ask for Step 3b" and "ran to completion" were the same status. It now exits 3, leaving 0 = the list is the answer, 3 = incomplete, 1 = could not read its inputs. Net effect on the same saved sweep: the same four names in the same order, ARGUS 72.1 -> 69.5 as its growth rate corrects. Two claims the first commits left imprecise are amended: the second pass is not purely subtractive, since the growth axis is a within-chain percentile, and only gate-surviving rows are looked up, so a listed migrated token is measured on its true rate against neighbours still on their inflated one -- an error that costs listed names points rather than granting them, now stated under `## Known limits`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5eecc8574b |
fix(gmgn-heat-rank): close four holes found reviewing the age split
Re-audit of the change in this PR, all four in code or claims the PR itself introduced. 1. A creation time that was not a readable number crashed the whole run. A string-typed timestamp in `created.json` raised `TypeError: unsupported operand type(s) for -: 'float' and 'str'` out of `creage()` and took the entire scoring pass with it, instead of dropping one row. The loader now coerces once, up front. 2. Worse than the crash: the values that did NOT crash were accepted and silently moved the list. `NaN` compares False against everything, so it passed the track test as "not young" AND came out of the freshness clamp at 1.0 — the easier mature track and a full newborn bonus at the same time, which is precisely the bug this PR exists to remove. A negative or absurdly small number read as "very old" and also bought the mature track. Unreadable is now stored as None (asked, no answer), which falls back to open time and keeps the row on the stricter track. Verified against string / list / NaN / negative / float / null / bool / zero / future-dated: nine cases, no crash, and every unreadable one now resolves in the strict direction instead of the generous one. 3. A lookup that never landed was indistinguishable from one that came back empty, and both were written as null. Exit status cannot separate them — `gmgn-cli token info` answers 0 with a fully zeroed body for an address it does not know (measured: `creation_timestamp: 0`, empty stderr). So the body itself decides: one that parses and echoes the address back is a real answer and a zero creation time is written as null, while one that does not parse or does not echo it back is a call that never landed, is left out of `created.json` entirely, and is named on stdout so Step 3 lists it again rather than scoring the row off an age nobody read. Step 3b also keeps its stderr now, in an `.err` file beside the response, the way Step 1 does, instead of sending it to /dev/null. 4. The report could state an age that was off by weeks. The table's age column is the pool-open age, which is right for the 7-day ceiling, but after this PR a token can be listed whose real age is far older — and nothing printed that number, so the writer could not have known. arc/ARGUS would have been reported as "1.9d" on a list whose headline promise is recency, when the token is 19.5 days old. The address line now prints `created=N.Nd ago` whenever the two differ by more than a day, and stating it is required in `## What the answer has to contain`. Two claims elsewhere in the file that this PR had made false are corrected rather than left to contradict it: the ceiling bullet said `MAX_AGE_D` stops a months-old token reaching the list, which is now true only of months of trading and not of the token's age, and the `YOUNG_D` cliff note did not say which of the two ages the cliff is measured on. No threshold, weight, gate constant or routing description changes. Against the same saved 7-chain sweep, pass 1 names the same 5 rows and pass 2 returns the same 4 names in the same order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5c4841342a |
fix(gmgn-heat-rank): read the track and the freshness axis off creation time, not pool-open time
`open_timestamp` was doing all three of the age's jobs: the 7-day recency ceiling, the new-launch/mature track choice, and the freshness axis. For a token that sat on a bonding curve for weeks before it migrated, that one number pulls in two opposite directions at once -- the token reads as hours old, so it is held to the new-launch run-rate floors (thresholds set for tokens that have no history to measure) while simultaneously being paid a newborn's freshness bonus. Measured on a live sweep this cost a correct listing: arc/ARGUS, real age 18.4d, was dropped for missing the $150,000 1h floor by $824 while carrying a 21.8h freshness score. The jobs are now split onto the timestamps that actually mean them: - the 7-day ceiling (`--max-created` / `MAX_AGE_D`) still reads `open_timestamp`, because "recently hot" is a claim about the tape and migration is that event; - the track choice (`YOUNG_D`) and the freshness axis read `creation_timestamp` from `token info`, because those are claims about the token's own history. Cost is bounded rather than per-candidate. Step 3 now runs twice: pass 1 emits a `NEEDS-CREATED` block naming only the rows whose outcome can still turn on their age -- a row already dead on liquidity, rug score or a one-sided tape gets no call -- new Step 3b fetches exactly those, pass 2 prints the list. On the verification sweep that was 5 lookups against 58 candidates, so a whole run is 21-27 calls instead of 21. The block warns itself if it ever asks for more than 20. Guards on the new code: - a creation time that reads newer than the pool open time is clamped, so a lookup can never move a row onto the easier track or buy it a freshness bonus it did not already have; - a key present with a null value means "the lookup ran and there is no answer" and falls back to open time, which keeps the row on the stricter track instead of re-asking forever; - Step 3b refuses to run with `DATA` unset, validates each chain name against the literal seven and rejects any address that is not alphanumeric, and refuses to write an empty `created.json` that would send pass 1 back to the same `NEEDS-CREATED` block in a loop. No thresholds, weights, gate constants or report rules change, and the frontmatter is untouched. Verified against a saved 7-chain sweep: pass 1 names the same 5 rows, pass 2 returns the same ranking, and the only list difference the rule itself makes is the one row it was written for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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
|
||
|
|
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,
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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.
|
||
|
|
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. |
||
|
|
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. |
||
|
|
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> |
||
|
|
79b8aa471e |
fix(token-buy): hand a bare address with no amount to gmgn-contract-dd
The description claimed gmgn-contract-dd "only look[s] up an address and return[s] no verdict", which is false — it returns a 0-100 composite capped by GMGN's own rug label — and it contradicted this skill's own body, which already calls that skill for the safety verdict and defers to it on disagreement. It also claimed the bare phrase 能不能买, which gmgn-contract-dd claims verbatim in its own description and its neighbouring-skills table. Both skills can serve a bare address, so the phrase alone cannot route. Split the trigger by whether a buy is being prepared instead: a NAME or symbol to resolve, or an AMOUNT to size, is this skill — neither of which gmgn-contract-dd can accept, since it has no search step and no amount parameter. A bare address with neither is a verdict ask, and taking it would hand the user an order card he never asked for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
33e7e62836 |
fix(skills): defer the safety verdict to gmgn-contract-dd, and refuse to
guess a contract when `market search` is unavailable Two changes, both driven by running the skill for real rather than only measuring routing. 1. The safety verdict now names `gmgn-contract-dd` as the owner. This skill and `gmgn-contract-dd` were computing two parallel safety verdicts on the same contract. Step 2 now states that once step 1 has resolved the one right contract, the 0-100 composite is `gmgn-contract-dd`'s job — its deductions are finer and each one names the field it read — and that its score and red flags are taken as this step's result, with its verdict winning any disagreement. The local gates are kept as an explicitly-labelled fallback rather than deleted, because a live run shows the hand-off does not fire: two real queries, Bash restricted to `gmgn-cli`, and `gmgn-contract-dd` was invoked in neither. That matches the earlier arm-Z result (a hand-off written into a skill body, 20 cases, 0 invocations). So the fallback is the path that actually executes today; deleting the local gates would have removed the safety verdict without connecting the replacement. The fallback thresholds are unchanged, and the order card must say the verdict was computed locally without `gmgn-contract-dd` review. 2. A hard stop when `gmgn-cli market search` is missing. The released CLI has no `market search` — that command is added by this PR's `aa0e7fd`. On a machine with the released CLI the live run showed the model improvise: `market search` failed, and it moved on to `market hot-searches --limit 500`, `market trending`, `market trenches` to find the token by name. Those are popularity and gain rankings, not a name index, so resolving a name through them lands on whatever is being pumped — which is exactly the copycat this skill exists to rule out. Step 1 now stops on `unknown command 'search'`, names the three commands that must not be substituted, and tells the user to upgrade the CLI or supply the address directly. Routing unchanged by all of the above: 16/16 on the 16 plain-buy cases (11/11 recall, 100% precision) and 18/20 on the 20 due-diligence cases (8/10 recall, 0 false positives), measured on the real installed state. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c3d0035f90 |
docs(skills): state the name-resolution exclusivity in the description
The description implied exclusivity by describing what the siblings cannot do; it never said it directly, so the router had to compose two clauses to reach it. States it in the first sentence instead. Measured neutral, not an improvement: on the real installed state (12 skills + the routing table) the arm with this clause scores exactly what the arm without it scores — 16/16 on the 16 plain-buy cases (11/11 recall, 100% precision) and 17/20 on the 20 due-diligence cases (7/7 on the runnable positives, 0 false positives). Kept for the reader, not for the router. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
91349504e0 |
fix(routing): make a plain buy request default to gmgn-token-buy
Measured on the novice phrasing the skill actually targets — a plain
"我想买 200u 的 PENGU" with no due-diligence and no skip-the-checks
wording. Four arms, 16 cases (11 positives / 5 negatives), one live
`claude -p` session per case:
arm recall precision score
user's machine today (no CLAUDE.md) 3/11 27% 100% 8/16
+ the 11-line routing table 8/11 73% 100% 13/16
repo skills + the 11-line table 11/11 100% 92% 15/16
+ these 5 lines 11/11 100% 100% 16/16
Three changes:
- CLAUDE.md: the `gmgn-swap` registry row claimed `"buy this token"`
unconditionally. The earlier fix only narrowed the quick-route table,
not the registry, so a plain buy request still had a competing claim.
- CLAUDE.md: one quick-route row making `gmgn-token-buy` the explicit
DEFAULT for any plain buy request. The failure mode being fixed is
silence, not theft — every miss in the 8/11 arm was "no skill fired".
- SKILL.md description: lead with the plain phrasings, and state that a
plain buy request is this skill's by default rather than framing the
boundary defensively around what it hands to gmgn-swap.
Also adds a hard boundary for sells, limit orders and order-status
queries, which fixes the one false positive in the 15/16 arm
("帮我挂个 BONK 的限价单" bounced from gmgn-swap into this skill).
Negatives hold in every arm: "直接买 200u 的 BONK,不用检查了" routes to
gmgn-swap, never here — an explicit skip-the-checks instruction must not
be intercepted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
04dadf6fa9 |
fix(routing): register the 5 unregistered skills and split the buy-intent route
The routing table listed only 7 of the repo's 12 skills. gmgn-contract-dd,
gmgn-cooking, gmgn-holder-analysis, gmgn-wallet-analysis and gmgn-token-buy
were absent, so an unregistered skill effectively did not exist to the router.
Three routes also sent buy-side asks somewhere that cannot answer them:
- "is this token safe" / "check this token" went to gmgn-token, which
returns raw fields and no verdict. Split into three rows: a token name or
symbol with buy intent goes to gmgn-token-buy (the only skill that
resolves a name to the one right contract and sizes the order); a bare
address wanting a score goes to gmgn-contract-dd; raw fields for an
address already in hand stay with gmgn-token.
- "buy TOKEN" went straight to gmgn-swap, bypassing due diligence. Narrowed
to the skip-the-checks case; a plain "buy TOKEN" is gmgn-token-buy.
Measured on a 20-case routing eval (10 positives) with `claude -p`, first
Skill call recorded, 12 skills installed:
routing table unchanged recall 1/10 and 3/10 over two runs
routing table with this change recall 7/10, reproduced exactly twice
Precision stayed 100% with zero false positives in every run. The 3 remaining
misses are eval cases with no token name, a placeholder address, and an
unsupported chain — none of them resolvable by routing.
Description-level fixes were tried first and measured dead: narrowing
gmgn-token's description, adding a runtime hand-off to gmgn-token's body, and
rewriting gmgn-token-buy's own description each scored 0/10.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
5a221cb259 |
feat(skills): add gmgn-token-buy — buy-side due diligence from a name
Covers the request shape no existing skill can serve: the user names a
token and wants to know whether to buy it, and for how much. Every other
token skill takes a contract address as input, so none of them can start
from "搜一下 PENGU 能不能买" or "帮我买 200u 的 XX".
The skill resolves the name to the one correct contract among its
copycats, runs three hard gates (24h volume / pool depth / security
audit), sizes slippage and gas for the stated amount, and emits an order
card. It never holds a private key and never submits a transaction: after
the user confirms in-conversation, the parameters go to gmgn-swap. That
confirmation is required even when the user has said "don't ask, just
buy" — it is a real spend of real funds.
All data access goes through gmgn-cli, so the skill reads, stores and
transmits no credentials, and it inherits the output sanitiser that
src/sanitize.ts applies in printResult. What the sanitiser does not cover
— identity confusion and gate-softening prose in attacker-written token
metadata — the skill handles in its own section: metadata is data and
never an instruction, a "sanitized N field(s)" notice is itself a risk
signal that belongs in the order card, an official-looking name never
skips contract locking, and a contract address is only ever read from the
address field, never from prose.
Verifying the rules against the live API corrected five of them, each of
which would have misjudged a blue chip:
- pool.base_reserve_value can be "0" on a fully liquid token (BONK's
meteora_dlmm pool reports base "0" while holding 14.7B tokens), so
min(base, quote) scored a 996k-holder token at $0 depth. The gate now
falls back to pool.liquidity / 2.
- pool.liquidity is twice ONE side and which side is unpredictable:
PEPE/SHIB/BONK/PENGU/BRETT are 2 x quote, CAKE is 2 x base.
- pool is not necessarily the deepest pool — BONK's
biggest_pool_address differs from pool.pool_address — so depth is a
lower bound, and volume far above depth means "there are other
pools", not wash trading.
- creation_timestamp and created_at can be 0 (real BONK returns 0 for
both), which naive arithmetic turns into 56 years of history. Zero
means unknown; skip the age bonus instead of crediting it.
- Solana's *_prio_fee is a placeholder 1 while BSC's is legitimately 0,
and a tier is not reconstructible from suggest_base_fee + prio (BSC
high is 561M wei against a 120M base fee). The tier field itself is
the authoritative all-in price; only *_prio_fee_mixed is real.
Routing was measured with all 12 skills installed: precision is 100% (no
false trigger in 10 negatives), but recall is low and two full
description rewrites moved it only within run-to-run noise. The
attribution is stable — gmgn-token takes 4-5 of the misses every run,
because its own description ends with "or wants due diligence before
buying", which is this skill's whole job stated in 429 characters. That
overlap is structural rather than cosmetic and gmgn-token is deliberately
left untouched here; see the PR description.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
0f48a31a9d |
docs: register the skills the tables were missing, and gmgn-token-buy
The two Readme tables and the marketplace description had drifted behind the repo: both tables listed 8 skills and the marketplace blurb said "9 GMGN skills", while skills/ actually shipped 11. gmgn-contract-dd, gmgn-holder-analysis and gmgn-wallet-analysis were installed but undiscoverable from the docs. Rather than bump a stale count by one, complete both tables and the blurb to the full set, including the gmgn-token-buy skill added in this branch. Each new row is placed next to the skill it reads like, so the tables still group by subject: contract-dd and holder-analysis after gmgn-token, wallet-analysis after gmgn-portfolio, and token-buy immediately before gmgn-swap, which is what it hands its order card to. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
016535ffcf |
feat(market): implement the documented market search command
`skills/gmgn-market/SKILL.md` has documented `market search` in full — the
capability table, the rate-limit table, parameters, response fields and usage
examples — but no implementation ever landed: `/v1/market/search` appears
nowhere in `src/`, and `gmgn-cli market --help` does not list the command. Any
agent following the skill doc hits a hard failure.
This adds the command behind the documented contract:
- `-q, --query` (required), `--chain`, `--launchpad-platform` (repeatable,
max 50), `--is-og`, `--is-launched`, `--order-by weight`, `--raw`.
- `--chain` is shape-checked rather than matched against the 7-chain core
allowlist, because search accepts any chain the platform has enabled
(tron / monad / megaeth / xlayer / hyperevm) and a stale local list would
reject valid searches.
- `--query` is stripped of C0/C1 control, zero-width and bidi-override
characters before the documented 1-100 length check, and the cleaned string
is what gets sent. Length is counted in code points so emoji tickers are not
over-counted. Without this, `pe<ZWSP>pe` is a different search term than
`pepe` and can be used to point a caller at a copycat contract.
One upstream deviation is worth calling out: the endpoint does not understand
`chain=all`. It answers with `{"coins":[],"wallets":[]}` rather than an error,
which an agent reads as "no such token" — strictly worse than failing. Since
the skill doc promises `all` works, the CLI accepts that spelling and searches
all chains by omitting the filter, warning on stderr so the substitution is
visible. This follows the existing `parseDuration` precedent of correcting a
known-ambiguous input loudly instead of rejecting it.
Verified against the live API: `-q pepe --chain eth` returns fields identical
to `token info` for the same token (`liquidity`, `volume_24h`, `swaps_1h/24h`,
`holder_count` all byte-for-byte equal), bare-contract-address lookup resolves
the chain in one call, and an address on an unsupported chain returns an empty
result set.
|
||
|
|
ffc8b741ce |
docs(gmgn-contract-dd): append the gmgn-token-buy boundary
Purely additive — the existing description, its trigger list (尽调 / 打个分 / 这个币安全吗 / 能不能买 / rug check / bare address), the neighbouring-skills table and every scoring rule are untouched, so nothing this skill triggers on today stops triggering. What is added is only the missing half of one phrase. 能不能买 is claimed by this skill and by gmgn-token-buy, and only one of them can serve it: the input here is --address, and this skill resolves no names, so a named token plus buy intent (帮我买 200u 的 PENGU, PENGU 能不能买, 能不能冲) has no path through it. The appended clauses say so, and say the two are sequential — token-buy calls this skill for the safety verdict — so a bare address still scores here exactly as before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4a106ee773 |
docs(gmgn-swap): append the gmgn-token-buy boundary to the description
Purely additive. The original description sentence is kept byte-for-byte; the appended clauses only say which buys arrive here pre-vetted and which start at gmgn-token-buy, so no existing trigger is removed or narrowed. Why the boundary is stated here at all: the pre-swap check in this skill reads only is_honeypot and rug_ratio, and --output-token takes a contract address (the only names resolved here are the SOL/BNB/ETH/USDC currencies), so a same-name copycat is a loss this skill has no way to catch. Selling, swaps, batch trading, limit/stop/take-profit/trailing orders, order status and gas prices stay unconditionally this skill's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9b90a3eaea |
docs(dev-score): fix the report's shape with a Display Templates section
Removing the script's layout engine also removed the only thing keeping the report a stable shape. Two runs of the same JSON came out with different heading levels, different table/bullet choices and bold on nearly every clause, and one leaked the fullwidth dollar sign — a convention that exists only to protect this file's own text from positional-argument substitution — into user-facing output. Display Templates fixes the shape without fixing the wording: nine sections in a fixed order, each with its block type (which sections are tables, which are prose), sections 1-5 and 9 always present, 6-8 conditional on their data being there, no reordering, no merging, no tenth section. Plus the formatting rules that were being decided per run: plain ascii dollar sign with thousands separators, one decimal on percentages, no emoji, no box drawing, no column padding, and bold restricted to the verdict number, the band, and the four labels of the action section. Section names are given in English so they are translated into the user's language rather than printed as-is, so the file stays English-only. The content checklist above it said "order, headings and phrasing are yours", which now contradicts this section: it is reworded to own what must be *said* while Display Templates owns *where it goes*, and its bullets are reordered to match the section order. This is an instructional constraint, not a mechanical one — the script prints no report, so nothing enforces the shape at runtime. That is the intended trade of keeping output format out of the calculation code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9b6e7023da |
refactor(dev-score): split the skill into a slim SKILL.md, a calc script and references
SKILL.md was a single 1,854-line file carrying the routing, the whole 1,432-line inline Python, the scoring rationale and a 78-column layout engine with 142 hardcoded zh/en string pairs. All of it loaded into context on every invocation. Now: - SKILL.md (95 lines) — routing, how to run, and what the answer must contain as a checklist, not a template. Pure English: the trigger phrases no longer name a language, and the model translates the concepts itself. - dev_score.py — fetch and calculation only. Emits one JSON object on stdout and nothing else; progress notes go to stderr. The layout engine and every zh/en string pair are gone, so the output format is not fixed by the script. - references/scoring.md — the model: gates, severity, the thin-record shrink, tiers, caps, verdict bands, and why each is shaped that way. - references/fields.md — every JSON key emitted, every confirmed API field, the chain probe, rate limits, and the data caveats. - references/glossary.md — only the trader slang that has no literal English equivalent, plus the tier and band renderings. Scoring is unchanged. Verified old vs new across 8 mock scenarios (empty index, empty-but-trading, factory, implausible peak, bot-scale, hostile input, both factory-gate boundaries, LP drain): identical totals, conduct, power, tiers and term breakdowns, and matching not-scored reasons. Prose exits became reason codes (index_empty / not_a_person / peak_implausible / never_launched) and the flagship note became a status code. safe() is kept as input sanitisation, now also applied to token addresses and to severity_by_token keys, so an attacker-chosen symbol still cannot forge a line. Truncate first, sanitise second — slicing a sanitised value strips its fence. Per-invocation context drops from 151,566 to 9,652 bytes; references are read on demand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f03d44ea44 |
docs(gmgn-dev-score): name every concept in English, map the zh report labels
The file's prose, headings and script comments carried Chinese concept names (信誉 / 实力 / 综合 / 割率 / 狠度 / 抽水倍数 / 代表作 …), which made the rules readable only to a Chinese reader even though the routing and the English report are fully supported. Every concept is now named in English, taken from the script's own `_(zh, en)` pairs so the document and the report agree: CONDUCT / POWER / TOTAL, dump / open-dump / dump rate, severity, pull multiple, 1st sell, the curve, flagship, walk-away & CTO rate, self-snipe rate, book quality, unaccounted exit, untradeable rate, and the five tiers systematic / frequent / occasional / unproven / measured clean. Report lines quoted as evidence inside comments are translated with them, so a comment explaining a past defect no longer requires reading the Chinese output to follow. Adds a `### Terms` section: English concept, the exact zh wording the report prints, and a one-line definition — 17 concepts, the 5 tiers, and the 9 section headers. Terms that translate one-to-one (peak, drawdown, survival rate, graduation rate, holders, market cap) are stated to be English-only and are deliberately not listed, so the table stays short enough to read. Also fixes three pass artifacts it uncovered: `flagshipdumped coins` in two gate-table rows and one comment, and the doubled `dump (dump)` / `severity (severity)` labels. What is deliberately still Chinese: the frontmatter trigger phrases and the routing-table phrasings (they are the literal user input the router matches — translating them breaks Chinese routing), the `_()` zh strings that ARE the Chinese report, the CJK punctuation the line-breaker demonstrates in its own comments, and the zh half of the new glossary. The routing boundary lines now lead in English with the Chinese phrasing in parentheses. Verified: description folds with no space injected inside a Chinese phrase and all 41 trigger phrases intact (22 zh / 19 en); script compiles in both languages; 8 mock scenarios x 2 languages all rc=0, width <= 78, brackets balanced, no CJK outside 「」 in English output; no ascii dollar-before-digit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
fbf533fc9f |
fix(gmgn-dev-score): English-context correctness and bracket balance
Four defects found while checking whether the skill routes correctly for a question asked in English or another language. Routing itself was fine -- the description's spine, reasoning and DO-NOT-USE logic are English, and 信誉/实力 carry inline English glosses -- but: - Four hardcoded strings leaked 信誉/综合/抽水 into the English report while its header reads TOTAL/CONDUCT/POWER. Now CONDUCT/TOTAL/pull. - LANG had no selection rule, only the comment "'zh' or 'en'", with 'zh' listed first. State it explicitly: LANG follows the language the user asked in, not the chain and not the model's default. The report is the whole deliverable, so handing an English trader a Chinese one makes it unreadable. - Trigger phrases were lopsided: 22 zh vs 5 en positive, and every one of the 9 DO-NOT-USE phrases was Chinese. The real English-context risk is over-triggering on copy-trade and profitability questions that belong to gmgn-wallet-score, and nothing guarded against it. Now 41 phrases, 19 English, 10 of them on the negative side, plus a note on the "how is this dev's reputation" (profile) vs "dev score" (decision) boundary. Also folds a pre-existing Chinese phrase that YAML's >- had split across lines, inserting a space inside it. - Language-independent: `to = safe(addr, 64)` printed as `m['to'][:8]` sliced off the closing 」 that safe() adds, so an attacker-supplied to_address rendered as 「判为割 100… with no visible end and the report's own words appeared to continue inside the quoted name -- exactly the confusion the quoting exists to prevent. Truncate first, sanitise second, via a separate to_disp; `to` stays full so the fund_from_address comparison still matches. Real addresses display unchanged; a hostile value shows as ?. Verified: script compiles under both languages; 6 mock scenarios x 2 languages all rc=0 with width <=78 and balanced brackets (hostile 4/4); zero CJK in English output; description folds with no space inserted inside any Chinese phrase; no ascii dollar-sign-before-digit hazards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9b20187d74 |
feat(dev-score): score a launcher on 信誉 and 实力, and say when to buy
Adds gmgn-dev-score, a risk gate you consult before buying a launch, as a single self-contained SKILL.md. The skill answers one question — 我要不要买他下一个盘 — and keeps two things apart that must never cancel each other out: 信誉 (will he dump on you, built only from what he did with his own hands in his own coins) and 实力 (has he ever built anything, built only from what the market did with those coins). They combine asymmetrically: 综合 = 信誉 + a bonus 实力 can earn, and the 割率 gate caps 综合 before the bonus lands, so a big coin can never buy a dumper a passing score. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c4d612d2b3 |
docs(contract-dd): fold the routing split into this file and drop the CLAUDE.md PR
#213 fixed the collision by editing CLAUDE.md's Quick Decision Guide. Keeping the library consistent matters more than closing that collision from outside: no other skill in skills/ reaches into a project doc, and this skill should not be the first. So the split now lives where the rest of the repo puts it — in the skill file — and #213 is closed rather than merged. The existing hand-off table only covered the outbound direction (user wants the explanation, not the verdict). It now covers the inbound direction too, which is where the collision actually was: - **One number vs the raw fields.** "check this token", "research this token", "what's the liquidity", "who holds this" are gmgn-token; 打个分, 尽调, 这个币安全吗, 能不能买, rug check, score this contract are this skill. The description already competes at selection time; this states the same boundary for a reader who has already loaded the file. - **A bare address with no question** is a verdict ask: score here, offer the fields after. Step 0 still resolves token vs wallet first, and the wallet branch now names gmgn-portfolio alongside gmgn-wallet-analysis, since raw holdings / P&L / activity are the portfolio skill's, not the dossier's. Still one file, still no changes outside skills/gmgn-contract-dd/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d5987fb226 |
docs(contract-dd): contest the phrases CLAUDE.md routes elsewhere, from inside the description
Routing happens before a skill's body is ever read, so nothing in this file can
override CLAUDE.md's row sending "is this token safe" and any bare token address
to gmgn-token. The description is the only surface that competes at selection
time, so it now does more of the work.
Three changes, all inside the existing 1024-char budget (907 after, 970 before):
- **Added the English trigger `is this token safe`.** The description carried the
Chinese 这个币安全吗 but not its English twin, which is the exact phrase the
routing table hands to gmgn-token.
- **Added an explicit priority hint** — "prefer this over gmgn-token whenever the
ask is a verdict rather than a field dump" — following the pattern
gmgn-wallet-analysis already uses to disambiguate itself.
- **Named the rug-label cap**, which the description predated and never mentioned
even though Step 5B is now what stops a labelled rug reading as clean.
Compressed the internals wording ("the three exceptions where absence does move
the number are named explicitly") to pay for it — that sentence describes
mechanics a router does not select on.
This narrows the collision; it does not close it. CLAUDE.md's row is project
guidance and still names gmgn-token for the same input, which is what #213 fixes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
f23cb57752 |
fix(contract-dd): a listing row without a rug_ratio key is not rug_ratio 0
Found by running the demo against a bsc token: Step 5B reported `rug_ratio 0.000 via trenches/completed → no cap`, and the field was not in the row at all. Measured over 840 listing rows on 2026-08-28: **`rug_ratio` is absent from the row entirely on 323 of them**, and which rows lack it is chain-dependent — 176 of 180 bsc trenches rows and 147 of 180 base trenches rows carry no such key, while sol trenches and all three trending listings always do. Reading a missing key as 0.0 reports "measured clean" for a label that was never supplied, on the majority of bsc and base launches. That is this skill's own headline failure mode, committed inside the step added to catch it. The step now stops at the first row that actually carries `rug_ratio` rather than the first row that matches the address, so a bare trenches row falls through to trending. Measured recovery on bsc: of the 176 trenches rows missing the key, 26 are also in trending and get their label there; the remaining 150 end genuinely unavailable. "Listed but no row carried the key" and "not listed at all" are different facts, so Step 8 must print which one it was, and neither may be written as `rug_ratio 0`. The age is still taken from the first matching row whether or not that row carried the label — `created_timestamp` was never absent in the sample. Both filter pipelines now print `rug_ratio=ABSENT` instead of an empty-looking `None`, and the fall-through condition covers it. Verified end to end on a bsc token whose trenches row lacks the key: `trenches/completed rug_ratio=ABSENT age=0.6h` then `trending rug_ratio=0 age=0.6h`. Invariant sweep: 14/14 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7f9acf56d3 |
fix(contract-dd): remove a stranded paragraph that revoked an active scoring row, and correct the self-check totals
Both defects came from review feedback and both are mine — incomplete patches whose verification checked the strings I knew I had changed instead of checking the invariants. **A paragraph told the implementer not to apply a deduction the table applies.** When `vol_ratio < 0.20` was promoted to a scored row, the replacement only covered the first paragraph of the old reported-not-scored argument. The second paragraph survived and still read "compute `vol_ratio` … and **do not deduct on it**", plus "**That test has not been run**" — describing the rate-limit interruption that the completed three-batch lift test had already superseded four commits earlier. An implementer copying the scoring table would have shipped the −18; one reading the prose would have skipped it. Deleted, and the row is now stated as ACTIVE at that spot. Acting on the reviewer's wider point rather than just the instance: a note under the Step 5 table now says every row in every scoring table is active and this skill has no candidate, disabled or "pending" rules anywhere — a rule that failed measurement was deleted outright rather than parked, which is what happened to `sell_volume_24h / buy_volume_24h` and to `vol_ratio`'s own 0.20–0.40 tier. The tables are safe to implement on their own. **The coverage self-check named the wrong totals.** It said "does not equal 22 or 24" while the inventory it checks is 23 on `sol` and 25 on the EVM chains — the numbers were updated in the "Inventory total" sentence when the price section gained `vol_ratio` and missed four lines below. Since executed + skipped + held-out always sums to the inventory total by construction, the check would have mis-fired on every correct run, which is the worst possible failure for a line whose only job is to catch a miscount. Corrected, and the identity is now spelled out in both directions. To stop repeating the class of mistake, verification is now an invariant sweep rather than a string grep: no prose may revoke a row the tables mark active (excluding availability rules, which legitimately say "do not deduct" on struct defaults), every inventory-total statement must say 23/25, the Step 5 table row count must equal the inventory's Price count, all caps must be 59 or 79, every Step reference must resolve, and the file-hygiene checks. All fourteen pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b4749a15f8 |
feat(contract-dd): report token age, the one open item that did affect the output
Checked the deliberately-unfixed list for actual user impact. One item did have some, and it has a free fix. **A few-hours-old launch could read "relatively clean · evidence sufficient" with nothing on the line saying it was young.** Measured: a pump.fun launch scored 89.4 with no failed check beyond thin liquidity, a small holder count and an 8% drawdown — all true, none of them conveying age. The `len(kline.list)` deduction only reaches tokens under 24 candles, so six hours of history escapes it. `token info` cannot supply the age: `creation_timestamp`, `open_timestamp` and `pool.creation_timestamp` all read 0 there, on USDC as well as on a launch. But the Step 5B listing row already carries it, so it costs no extra call — the filters now extract it alongside `rug_ratio`, and Step 8 prints it next to the grade. The field differs per listing, like the envelope does: `trenches` rows carry `created_timestamp` (with `open_timestamp` at 0 on ungraduated `near_completion` tokens) while `trending` rows carry `open_timestamp` and no `created_timestamp`. Both pipelines were re-run and verified — `trending rug_ratio=1 age=21.7h` on ANTSEM, `trenches/near_completion rug_ratio=0 age=3.6h` on a live launch. Also checked, and no change needed: **`stat.creator_hold_rate` is trustworthy.** It never fired across 25 tokens, which could have been a false pass hiding a holding dev. It is not: on six live launches the rate is non-zero exactly when `stat.creator_token_balance` and `dev.creator_token_balance` are non-zero, and it agrees with the independent `creator_balance_rate` on the trenches row. MERP reads 1.35% and fone2 0.82% — real holdings that sit below the >2% tier. Whether that tier is set too high for bonding-curve launches is a calibration question, not a data bug. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
bbfaefa14f |
docs(contract-dd): cite the repo's own definition of rug_ratio behind Step 5B's bands
The bands were chosen by measurement, but the field's meaning was inferred. It is documented in this repo: src/commands/market.ts describes rug_ratio as a "rug pull risk score (0-1, e.g. 0.3 to exclude rugs)", and the CLI's own --filter-preset safe for market trenches is built on max_rug_ratio: 0.3. docs/workflow-early-project-screening.md already flags > 0.3 and docs/workflow-project-deep-report.md uses > 0.5. So both of Step 5B's cutoffs match thresholds already in use across the project's source and docs, arrived at independently from live measurement. That removes the one input to this step whose semantics I had not verified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
24c0df5e91 |
fix(contract-dd): Step 5B must filter the listings in the shell, not read them
The step added in the previous commit was a context bomb. Measured 2026-08-28:
`market trenches --chain sol --raw` is **757,598 bytes** and
`market trending --interval 24h --limit 100 --raw` is **233,821 bytes**, against
about 5 KB for `token info` and 15 KB for a 100-candle `kline`. Reading the
trenches payload to find one address costs on the order of 200,000 tokens of
context for a single number — more than the entire rest of the procedure
combined, and enough to blow the window before any scoring happens. `--limit`
does not help: it caps rows per category at 80, and an arbitrary-address lookup
needs the whole listing.
Step 5B now specifies two verified shell pipelines that return one short line —
16 and 11 bytes measured — instead of the payload, and the Sub-commands block
marks both calls as filter-only. Both pipelines were run end to end before being
written down.
Two details that make the difference between working and silently wrong:
- The address must be `export`ed. In `VAR=x cmd | filter` the assignment applies
only to `cmd`, so the filter would not see it.
- The two responses have different shapes — `trenches` is unwrapped with the
bucket names at top level, `trending` keeps the `{"code":…,"data":…}` envelope
with rows at `data.rank` — so the two scripts are not interchangeable. Pointing
the wrong one at either endpoint finds nothing and reports `rug_ratio`
unavailable, which is indistinguishable from an unlisted token: a silent false
negative on exactly the tokens the step exists for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
77ec2d436e |
feat(contract-dd): read GMGN's own rug label and cap on it (Step 5B)
Closes the failure the verification pass found: on ten tokens carrying
`rug_ratio >= 0.5`, three scored "relatively clean" — ANTSEM at `rug_ratio: 1.00`
scored 92.8 — seven scored "mixed", none reached "high risk", and nine reported
"evidence sufficient". A verdict number that calls a maximum-rug-label token
clean at high confidence is worse than no number.
**There is no address lookup for `rug_ratio`.** Verified absent from
`token info`, `token security` and `token pool`, and `gmgn-cli` has no
`market search` sub-command in any version measured (not in the installed 1.5.8,
not in the repo source at 1.5.9 — though `skills/gmgn-market/SKILL.md` documents
one, which is a separate bug in that file). So Step 5B scans the per-chain
listings and stops on the first match: `market trenches` first, where launches
live, then `market trending --interval 24h --limit 100`, where actively traded
tokens live.
Bands, each measured rather than picked:
- **`rug_ratio >= 0.50` → cap 59.** All ten labelled rugs move to "high risk",
removing three false "relatively clean" verdicts. Of six fresh launches scored
the same day only MERP is touched — `rug_ratio: 1.00`, composite 89.6.
- **`0.30–0.50` → cap 79.** Scored five tokens in the band: RAX 80.0, BANDOS
83.5, POKEMON 88.0 and AFD 90.5 all read "relatively clean" and are all
withheld; JWA at 76.5 was already below. Four of five changed.
- **`< 0.30` → nothing.** Deliberately inert; a cap there would be a guess.
Caps only, never a bonus, and `rug_ratio: 0` is not evidence of safety — 302 of
400 trending rows read exactly 0, so zero is the population default. Unlike Step
2's caps, which sit at 79 because they fire on an absent field, this one fires on
a measured positive, which is why its lower band goes to 59.
**Absence cannot hurt a token, structurally.** The listings rank churning tokens,
so established assets are not in them: all six bluechips in this file were absent
from every listing across four chains, so the cap has no path to a bluechip.
Acceptance test run live — USDC 100.0, RAY 88.4, CAKE 100.0 all unlisted and
uncapped; ANTSEM 92.8 → `rug_ratio 1.000` via trending → **59 "high risk"**.
`rug_ratio` is a cross-check, not a scored check, and is deliberately **not** in
the Step 6 inventory: coverage measures how much of the token's own contract,
holder and price data could be read, while this is GMGN's label about the token
and its availability tracks whether the token happens to be ranked. Putting it in
the denominator would dock every established token's coverage for being
established — the inversion Step 4 already rejects for the `stat` block. So the
inventory totals stay 23 and 25 and every verified coverage figure still holds.
Also recorded: the two listings have **different response shapes** — `trenches`
is unwrapped with the bucket names at top level, `trending` keeps the
`{"code":…,"data":…}` envelope — and assuming either for both reads as empty
data, i.e. a false "unavailable" on exactly the tokens this step exists for. The
Notes now carry a per-command shape table instead of the old blanket claim.
**The fresh-launch gap is dropped as a target, with reasons.** After Step 5B the
highest fresh launch is GROKCHAIN at 89.4 with `rug_ratio: 0.00` and no failed
check beyond thin liquidity, a small holder count and an 8% drawdown, so the gap
stays about −1. That is not a mis-score: the original calibration treated "young"
as a proxy for "risky". Youth is conveyed by the `len(kline.list)` deduction and
the confidence line, not by a depressed composite. The metric that matters is
whether a labelled rug can read as clean, and that is now closed by construction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
f5114f703c |
fix(contract-dd): the calibration gap does not survive re-measurement — record it
Verification of the five open items. Four came back clean; the first one did not, and it is the most important thing in this file. **The +15.3 bluechip-vs-fresh-launch gap is gone.** Re-run against six current fresh launches (three pump.fun near_completion on sol, three flap on bsc, all over $20K 24h volume and $10K liquidity): lowest bluechip 88.4, highest fresh launch 89.6, **gap −1.2**, and three of six read "relatively clean". The original +15.3 came from four launches captured at one moment. **Worse: the composite does not separate tokens GMGN itself labels as rugs.** Ten tokens with `rug_ratio >= 0.5` and over $20K of 24h volume, from `market trending`: three read "relatively clean" — ANTSEM at `rug_ratio: 1.00` scored 92.8, GASSPAS at 0.54 scored 96.5, Pistacio at 0.96 scored 86.8 — seven read "mixed", and none reached "high risk". Median 73.8, nine of ten at 100% coverage / "evidence sufficient". This is not a regression from the review commits; if anything the new `vol_ratio` row helped, firing on 2 of the 10. Two causes, both now recorded: `rug_ratio` lives on `market trending` / `market trenches`, outside the three endpoints this skill calls, so the most predictive field GMGN publishes is outside the design; and the rows that fire hardest on labelled rugs are `drawdown` (8/10) and `worst_candle` (5/10), which detect a token that has already dumped rather than one about to. The grade line and the appendix now say so, and Step 6 states that "relatively clean" means "no measured red flag among the fields this skill reads" rather than "not a rug". Fixing it properly is a threshold-and-scope decision — either bring `rug_ratio` in as a fourth endpoint or tighten the `stat` tiers materially, then re-calibrate against a labelled sample far larger than ten. **Per-row firing rates, over the 25 tokens scored end to end this session.** Eleven of twenty-seven rows never fired once, including every permission field in Step 2 across all seven chains — so the whole chain-mode branch contributed nothing, and the 0.45 contract weight is carried by liquidity, `creator_created_count`, `pool_shrink`, `image_dup_count` and `len(kline.list)`. Also never fired: the tax row, `creator_hold_rate`, `fresh_wallet_rate`, `private_vault_hold_rate`, and `price / price_24h`. The full table replaces the placeholder that said firing rates should be measured someday. **The other four items came back clean.** - arc, stable and robinhood score end to end, inventory closing at 25 on each (24/1/0, 23/2/0, 23/2/0). All three populate `stat`. - Executing the prose by hand, step by step, on FEFER/stable lands on exactly 92.3 — the same number the script produced. The rules are executable as written. - No live `is_honeypot === true` token surfaced in 300 trending rows across bsc, base and eth, so the Step 7 change could not be re-tested against one directly. It is monotone in the safe direction: requiring `privileges` present-and-empty can only make the exemption harder to open, never easier, so it cannot turn a honeypot clean. `privileges` was `null` on every token measured across all seven chains. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
de83ae7012 |
chore(contract-dd): move the CLAUDE.md, marketplace and docs edits to their own PR
This PR is back to what it was scoped as: one new file, skills/gmgn-contract-dd/SKILL.md. The routing-table registration and the token-security field-shape corrections in docs/workflow-token-due-diligence.md are on feat/contract-dd-bookkeeping instead, to be merged after this one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
35966e4095 |
feat(contract-dd): the volume row ships, with the noise tier measured out of it
The lift test the previous commit left open is done, and it split the row in two. `market trenches` cannot label this measurement at all: all 32 sampled trenches tokens returned 1 to 17 candles against the 40 `vol_ratio` needs. Re-sourced from `market trending --interval 24h --limit 100` on sol, bsc and base — 299 tokens over $2K of 24h volume, which are old enough to have candles — then three sequential batches, 47 scorable `rug_ratio > 0` against 47 scorable `rug_ratio = 0`: < 0.20 -> -18 risky 10/47 (21.3%) clean 2/47 (4.3%) lift 5.00x p=0.013 0.20-0.40 -> -8 risky 9/47 (19.1%) clean 9/47 (19.1%) lift 1.00x p=1.000 both as one rule risky 19/47 (40.4%) clean 11/47 (23.4%) lift 1.73x p=0.077 The two tiers are not the same rule. A collapse below 20% separates the populations at 5x and survives significance; the 20-40% band is 9 against 9, z of exactly 0.00 — and it is the band that was doing all the damage to bluechips. Measured `vol_ratio`: USDC 0.28, RAY 0.384, CAKE 0.587, WETH 0.380, eth USDT 1.593. USDC and WETH sit squarely in the deleted band, so shipping both tiers would have cost each 8 section points for a quiet day; the deep tier alone fires on 0 of 5. Read as one rule the row looked anti-correlated, and the three batches read 0.80x, 3.00x, 2.00x — the instability was the tier mix, not the market. So `vol_ratio < 0.20 -> -18` ships as a scored check and the shallow tier is deleted, with the measurement recorded so it is not re-litigated from one batch. Consequences, all re-verified live: the price section is 4 checks, so the Step 6 inventory is 23 on sol and 25 on the EVM chains. It closes exactly on all six bluechips — 21/2/0, 17/6/0, 22/1/0, 14/2/9, 13/3/9, 13/3/9. **All six composites are still unchanged** (100.0, 93.2, 88.4, 100.0, 97.3, 97.3) and so is every confidence label; coverage becomes 91.3 / 73.9 / 95.7 / 87.5 / 81.2 / 81.2. WETH and eth USDT sit one check above the 80% boundary, now stated as such. Also corrected a claim from the previous commit: `sniper_count` is not "no such field" — it exists on `market trending` / `market trenches` rows, just not on `token security`. And the Step 5 guard list now names `vol_ratio`'s own guards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9d0c7c2bdc |
fix(contract-dd): resolve the sub-command contradiction and make coverage auditable
Third review pass, over the sections the first two did not grill: Step 2, Step 8, Notes, and the two files outside the skill. **A contradiction introduced by the previous commit.** Step 0 gained a `portfolio stats` probe to tell a wallet apart from an address GMGN has no record of, while Sub-commands still read "runs exactly these three ... Nothing else" and Notes still said "three GET endpoints". The probe is now documented where it belongs: a conditional fourth call that runs only when `info.symbol` is empty, feeds no threshold, and sits on the CLI's API-key-only auth path — verified against `src/client/OpenApiClient.ts`, where `getWalletStats` uses `authExistRequest`, so the "never needs GMGN_PRIVATE_KEY" prerequisite still holds. The `--raw` envelope note now covers all four shapes. **Coverage was stated in a form that cannot be checked.** Step 8 asked for `executed N / skipped M` against an inventory total, but never for the held-out count — so `N + M + K = T`, the identity the fixed inventory exists to make verifiable, was unprintable. The report line is now `executed N / skipped M / held-out K of T` with all four numbers mandatory. **The disclosure line named the wrong set.** "chain-analysis metrics (9 checks)" covered eight holder metrics plus `creator_created_count`, which is a contract check. It now says so explicitly in both Step 4 and Step 8. **CLAUDE.md was left self-contradictory by the previous commit**, which routed a bare wallet address to `gmgn-wallet-analysis` — a skill CLAUDE.md names nowhere and has no table row for, while a separate row sent "wallet address provided" to `gmgn-portfolio`. Added the missing row and split that route the same way the token route was split: dossier verdict to `gmgn-wallet-analysis`, raw holdings and P&L to `gmgn-portfolio`. **The DD workflow doc read a struct-default zero as a pass.** Its thresholds call `top_10_holder_rate < 0.20` safe, but the field arrives as the string `"0"` on Solana and on some EVM tokens — 0% top-ten concentration does not exist. Noted the `stat` fallback, and the `"0"` versus `""` distinction for the taxes. No scoring rule changed in this commit, so no composite or coverage figure moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a8e7ad3a7e |
fix(contract-dd): six more scoring defects found on a second review pass
All six were found by re-reading the scoring tables against live responses
rather than against the prose, and four of them can change a score.
1. The LP row demanded "neither locked nor burned" — a two-part claim — but
accepted `burn_status: ""` as the burn half. Measured: `burn_status` is `""`
on every EVM token sampled (10 of 10 bsc, one each arc/stable/robinhood), so
any EVM token with `is_locked: false` was taking −12 on a burn status GMGN
never reported. The row now needs both halves present, which makes it
decidable on Solana and usually unavailable on EVM. RAY is the case that
shows the row still works: `is_locked: false` with `burn_status: "none"` —
an explicit negative — and it takes the −12.
2. `buy_tax` / `sell_tax` did not distinguish `"0"` from `""`. On a populated
block `"0"` is a real 0% tax; on an unpopulated one `""` is exactly what Step
1A keys on and is unavailable. Coercing `""` to 0.0 scores a passing check on
a field that was never reported.
3. The liquidity row "0 and `volume_24h` also 0 → −15" was dead text: under the
worst-row rule a liquidity of 0 already matches `< $10K` at the same −15, so
it could never change an outcome — while implying a `0` is always a
measurement. Removed, and the ambiguity it papered over is now resolved
explicitly: 0 liquidity with 0 volume is a dead pool and scores; 0 liquidity
with non-zero volume is unavailable, because a pool cannot turn over volume
it does not have.
4. Step 6's combine order named only Step 2's caps, leaving Step 7's cap of 59
with no place to be applied — a downgraded honeypot kept its raw composite.
All caps are now collected together, lowest wins.
5. `info.image_dup_count` had no stated condition ("logo duplicates another
token"); it is `> 0`.
6. My own claim that arc/stable/robinhood matched bsc "byte for byte" was an
overstatement — `top_10_holder_rate` differs. Corrected, as was the
"Three field traps" heading over five bullets.
Re-verified live: **all six bluechip composites are still unchanged** (100.0,
93.2, 88.4, 100.0, 97.3, 97.3) and every confidence label holds. The Step 6
inventory closes exactly on all six — executed + skipped + held-out came to 22
on each sol token and 24 on each EVM token, nothing left over or double-counted.
Coverage corrected where the LP fix moved a check from executed to unavailable:
CAKE 93.3% → 86.7%, WETH and eth USDT → 80.0% each, which is exactly on the
"evidence sufficient" boundary and is now flagged as such.
Also recorded the measured `vol_ratio` values (0.28 / 0.42 / 0.616 / 0.381 /
1.427 on the five scorable bluechips — a deduction on 2 of 5 at the drafted
tiers) and the fact that its lift test against risky tokens was cut short by a
rate-limit ban, so the row stays reported-not-scored.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
849d709579 |
docs: route the DD verdict to gmgn-contract-dd and fix stale security field shapes
CLAUDE.md's Quick Decision Guide sent "is this token safe" and any bare token address to `gmgn-token`, which collides head-on with the new skill's trigger set — and with `gmgn-holder-analysis`, whose description also claims "whether a token is safe to buy". Split the row by what the user actually wants: one number goes to `gmgn-contract-dd`, the fields go to `gmgn-token`, and a bare address gets the verdict first with the fields offered after. Added the missing skill table row, and corrected the `skills/` count and the marketplace plugin description, both of which still said 9. docs/workflow-token-due-diligence.md carried the same field-shape bug PR #211 fixes in the skills: it documented `is_honeypot` as `"yes"` / `"no"` when the field is `true` / `false` / `null`, and named `open_source`, `owner_renounced`, `rug_ratio`, `creator_token_status` and `sniper_count` as `token security` fields. Verified against live responses on 2026-08-28: `owner_renounced` and `sniper_count` do not exist at all, `rug_ratio` is a `market trenches` row field, and `creator_token_status` lives at `token info` → `dev`. Also noted that the integer mirrors (`honeypot`, `open_source`) are not a safe substitute, since USDC returns `honeypot: 0` with `is_honeypot: null` — `0` conflates "no" with "unknown". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6cff8ba323 |
fix(contract-dd): close four field-reading holes found in review
Step 1B — the `stat` population test keyed on `stat.holder_count`, which is populated independently of the chain-analysis fields. Measured: WETH on base returns `stat.holder_count: 4818570` mirroring `info.holder_count` while all ten scored `stat.*` fields are zero, so the block was declared populated and nine checks were scored as nine *passing* checks. Added a second test (all ten zero → unpopulated). Deliberately all-ten rather than per-field: USDC reads zero on eight of ten but carries a real `top70_sniper_hold_rate`, and 0% bundlers on USDC is the truth. `stat.creator_created_count` is now gated with the eight, so the held-out group is nine, not eight. Step 7 — the exemption required "no privileged functions", but `security.privileges` came back `null` on every response measured across all six chains sampled. Reading `null` as a pass would let silence lift a honeypot verdict from 0 to 59. The gate now opens only on a present-and-empty value; same rule for the tax condition. Step 6 — coverage had no check inventory, so the denominator was whatever each executor counted and the published percentages were not reproducible. Added the fixed inventory: 22 checks on sol, 24 on the EVM chains, with the held-out and not-applicable groups named. Step 5 — the three kline measurements had no operational definition. Named the window `W` and its arrays, stated that every kline field is a string and `time` is milliseconds, and added divide-by-zero guards for `max(H)`, `O[i]` and `price_24h`. The volume row is defined but **reported, not scored**: defining it makes it executable for the first time and it immediately costs USDC 8 section points while its lift against risky tokens is unmeasured — the same shape as the sell/buy row this skill already removed. Records the test to run before it scores. Also: Step 0 probes `portfolio stats` before reporting "no record", because a bare address may be a wallet and the empty `info` block is identical either way; the untrusted-input clause now says the tell is gmgn-cli's own `[filtered]` substitution; the ">1 means already a percent" heuristic is replaced with an anomaly report, since no measured rate ever exceeded 1; description trimmed from 1006 to 968 chars; section order matches the CLAUDE.md rule. Re-verified by reimplementing the procedure as a script: all six bluechip composites and all six confidence labels are unchanged. Only coverage moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9cb023889b |
fix(contract-dd): correct Step 5 field paths, drop an anti-correlated scoring row, make the tier rule explicit
Full critical review of this skill turned up five defects. Fixes, in priority order, with what each was measured against. Step 5 named `price / price_24h` and `sell_volume_24h / buy_volume_24h` as if they were top-level `token info` keys. They are not: `info.price` is an object and all of those names live inside it as strings, so both rows were arithmetic on the wrong object and could never execute. Same omission in Step 3's "no 24h volume" clause and Step 7's sells/sell-volume gate; all now carry the `info.price.` prefix. Correcting the paths made `sell_volume_24h / buy_volume_24h > 1.3 -> -10` execute for the first time, and it turned out to be anti-correlated with risk, so the row is removed rather than fixed. Because net_buy_24h = buy - sell and volume_24h = buy + sell, the rule is exactly computable from `market trenches` rows, which also carry GMGN's own rug_ratio label. Over 415 live tokens on sol, bsc and base above $2K 24h volume it fired on 0.0% of the 114 with rug_ratio > 0 and 10.0% of the 301 with rug_ratio = 0 — lift 0.00x. Reversing the direction does not rescue it (best variant lift 1.16x) and neither does the sells/buys count ratio (0.52x); median net_buy/volume is +0.0166 on risky against +0.0196 on clean. Its only measured effect was costing USDC on sol 2.0 composite points off a stablecoin's redemption flow while penalising 0 of 12 fresh launches. The price section keeps full separation without it: the three kline measurements fire on 6 of 6 scorable tokens in a rug-weighted sample and 0 of 6 bluechips. The ratio survives in Step 7 as a two-sidedness band, which is a different question and is now labelled as one. Tier exclusivity was never stated, leaving six multi-row groups ambiguous — a creator with 1971 launches matches all five creator_created_count rows, worst-row -18 against summed -51. Scoring 16 live tokens both ways puts the difference at a median of 22.2 composite points and up to 33.3, more than the 20-point grade band: one token reads 71.3 "mixed" or 38.0 "very high risk" depending only on the reading. Worst-row is now stated in Step 3 and referenced from Steps 4 and 5. Step 2's honeypot hard stop said "stop scoring" and Step 6 said "0 regardless of everything else", so Step 7's tokenized-equity exemption was unreachable — which matters because robinhood, arc and stable are three of the seven supported chains. Both now point at Step 7 first. Checked against three live is_honeypot === true tokens on base: none comes near Step 7's gate (sells_24h 243/0/0 against 500, sell_volume_24h $63.6K/$0/$0 against $100K), so all three still resolve to 0 and the reordering opens no hole. Section scores had no lower bound while the eight stat checks alone total -117, so both section and composite are now clamped to 0-100. Measured on the 14 worst rug_ratio tokens, no section goes negative under the worst-row rule, so this changes no published number. Finally, three places claimed missing data never moves the score, which the len(kline.list) deduction has always contradicted. Step 6 now names all three exceptions explicitly and the frontmatter matches. No published score changes: all six bluechips come back identical after the path correction and the row removal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
414ed35548 |
fix(contract-dd): pin the scoring window, drop the nonstandard frontmatter key, add the hand-off table
Three review items, none of which change a single deduction or weight: - Step 5 said "score the last 96 15m candles, roughly 24h" while the skill passes no --from/--to. Measured: the default window is exactly 100 candles on sol, bsc and eth, so 96 is satisfiable today with only four candles of headroom. Read len(kline.list) and score min(96, len) instead of assuming, and state the real window when it is shorter — the measurements are ratios and survive a short window, the "24h" label does not. - metadata.catalogSlug was the only occurrence of that key in the repo: nothing reads it and none of the ten existing skills carry one. Removed for consistency; a one-line revert if the skill catalog consumes it downstream. - The holder (0.35) and price (0.20) sections are coarse by design, which makes them easy to mistake for gmgn-holder-analysis and gmgn-kline-pattern. Added a hand-off table and said outright that those skills use different thresholds and weights, so their numbers will not match this composite and neither corrects the other. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4d43962289 |
feat(contract-dd): add contract due-diligence scoring skill
Turns three read-only GMGN calls (token info, token security, market kline) into one auditable 0-100 composite: contract safety 0.45, holder structure 0.35, price action 0.20, renormalised over only the sections that returned data. Every deduction names the field it came from. The skill exists because scoring GMGN's security fields naively condemns clean tokens. An unpopulated response object still carries false on its booleans, "" on its strings and 0 on its numbers, and reading those as measurements is the failure mode the whole design is built around. Missing data is therefore neither a deduction nor a bonus: it lowers reported coverage, and coverage bounds how strong a claim the score may make rather than moving the score itself. Validated by running the procedure end to end over 14 addresses on 2026-08-27 — five bluechips, four fresh launches, three well-formed addresses with no GMGN record, one malformed address. What that measured, and what the rules had to say as a result: - token existence is decided by info.symbol, not by security.address. GMGN echoes the requested address back into security.address for addresses it holds no record for; two fabricated addresses came back echoed with renounced_mint=false, and one also returned a full 100-candle kline series, while their info blocks were empty. Step 0 refuses to score those instead of reporting a risk grade for a token that is not there. - every rate and tax field is a decimal fraction, including the four named *_percentage. top_10_holder_rate "0.1783" is 17.83% and buy_tax "0.01" is a 1% tax, so a threshold written in percent needs a x100 before the comparison or the deduction silently never fires. - on sol the four is_* fields are null by design and the numeric ones read "0", so an emptiness test declares every Solana block empty. Step 1A is scoped to the EVM fields; renounced_mint and renounced_freeze_account are read unconditionally once the token is known to exist, or Solana loses its only two contract signals. - info.stat is populated per token, not per chain: full block on a four-hour-old bsc meme, zeros on CAKE. - the two dev X fields are aggregates over the linked X account across every token it has been attached to. USDC's rename history reads circlepay -> circle -> arc and CAKE reports 44 deleted launch posts, so deducting on them penalises projects for having a past. Both are reported and neither scores. - under --raw these three commands print the unwrapped payload, so there is no code field to read. Read-only by construction: three GET endpoints, no signing, no private key, and the skill explicitly forbids calling swap, order or cooking commands. One new file, nothing else touched. CLAUDE.md and marketplace.json bookkeeping is left to maintainers; see the PR body. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |