| name | investigation-report |
| description | One-shot Base-token investigation - runs any subset of six onchain-security checks (rug-scan, contract-audit, deployer-trace, holder-concentration, honeypot, lp-lock) into one verdict. Keyless core. |
| metadata | {"title":"Investigation Report","mode":"read-only","category":"crypto","var":"","tags":["crypto","security","base"],"requires":["ETHERSCAN_API_KEY?","BASESCAN_API_KEY?","BASE_RPC_URL?"],"capabilities":["external_api","read_only","sends_notifications"]} |
${var} — Base subject to investigate, plus optional flags: <token-address> [--checks=rug,contract,deployer,holders,honeypot,lp] [--depth=quick|deep]. The first token is the subject contract address (0x…, required). --checks= is a comma-list selecting which analyzers to run (default = all six). --depth= is quick (the old rug-scan fast path — minimal reads) or deep (full standalone logic of each selected check; default). If the subject address is empty, log REPORT_NO_TARGET and exit cleanly (no notify).
Examples:
0xToken → all six checks, deep report.
0xToken --checks=honeypot → only the honeypot simulation (reproduces the standalone honeypot-check exactly, incl. its HONEYPOT_* end-states).
0xToken --checks=rug,lp --depth=quick → rug verdict + LP-lock, fast path.
0xToken --checks=contract,deployer,holders --depth=deep → structural audit + deployer entity intel + full concentration.
The "tell me everything about this token" skill. Instead of running six checks by hand, this composes them into one structured report behind a selector: rug risk, contract audit (verification / owner powers / proxy), deployer trace (who shipped it and their history), holder concentration (whale risk), honeypot (can you actually sell?), and LP lock (can the team pull liquidity?) — with a one-line summary on top.
Designed to degrade gracefully: each selected section runs independently, so a section that needs a key (or returns nothing) is marked unavailable without aborting the rest. Selecting a single check makes the composite behave as that one analyzer — same steps, same thresholds, same notify format, same status codes.
Config
- Subject = the first token of
${var} (validate: 0x + 40 hex). Chain = Base (chainid=8453, explorer basescan.org).
- Etherscan v2 unified API (
https://api.etherscan.io/v2/api?chainid=8453&…) — used by the rug, contract, deployer, holders checks. Works keyless at a lower rate limit.
- Base RPC (
${BASE_RPC_URL:-https://mainnet.base.org}) — used by honeypot, lp, and the eth_call/eth_getLogs/eth_getStorageAt/eth_getCode reads inside the other checks. Keyless; any standard JSON-RPC endpoint works.
- Secrets (all optional):
ETHERSCAN_API_KEY (a.k.a. BASESCAN_API_KEY — same Etherscan v2 key) — appended to the Etherscan URL as &apikey=… via ./secretcurl's {ETHERSCAN_API_KEY} placeholder (never a bare $SECRET on the line, never a header). Raises the rate limit and unlocks verified source, full deployer history, and the holder list. Used by rug, contract, deployer, holders.
BASE_RPC_URL — overrides the default public Base RPC. Used by every RPC read; primary for honeypot and lp.
- Preamble (run once, before dispatch): read
memory/MEMORY.md and the last ~2–3 days of memory/logs/ so a repeat investigation can note what changed since last time and avoid re-reporting the same signal. Parse ${var} → subject address, --checks (default all six), --depth (default deep).
Steps
Dispatch to each selected check below (default: all six). Each is self-contained — collect its verdict/section; never let one check's failure stop the others. --depth=quick runs the lightweight path noted in each branch (rug-scan-style inline sampling, fewer calls); --depth=deep runs the full standalone logic.
Check rug — Rug Scan
A fast, opinionated rug verdict: does the contract let someone print, freeze, or drain — and is supply/liquidity concentrated enough to pull?
1. Verify contract + pull source
TOKEN="${var}"
KEYQ=""; [ -n "${ETHERSCAN_API_KEY:+x}" ] && KEYQ="&apikey={ETHERSCAN_API_KEY}"
./secretcurl -m 10 -s "https://api.etherscan.io/v2/api?chainid=8453&module=contract&action=getsourcecode&address=${TOKEN}${KEYQ}" | jq '.result[0]'
Capture ContractName, Proxy, Implementation, SourceCode. Empty SourceCode = unverified → strong risk signal.
2. Scan source for dangerous powers — grep the returned source (case-insensitive) for these signals and record which fire:
| Signal | Patterns | Weight |
|---|
| Unverified source | empty SourceCode | +3 |
| Mint authority | function mint, _mint( callable by owner | +2 |
| Blacklist / freeze | blacklist, isBlocked, _freeze, addBan | +2 |
| Pausable transfers | whenNotPaused, function pause | +1 |
| Mutable fees/tax | setFee, setTax, updateTaxes | +2 |
| Owner not renounced | owner != 0x0 (see step 3) | +1 |
| Proxy / upgradeable | Proxy == "1" or delegatecall + upgrade fn | +2 |
| Trading toggle | enableTrading, tradingActive, setSwapEnabled | +1 |
3. Check ownership state — call owner() (selector 0x8da5cb5b) via eth_call:
curl -m 10 -s -X POST "${BASE_RPC_URL:-https://mainnet.base.org}" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"'"$TOKEN"'","data":"0x8da5cb5b"},"latest"],"id":1}' | jq -r '.result'
Trailing 40 hex chars = the owner address. All-zero → ownership renounced (lowers risk). A live EOA/multisig → flag the step-2 powers as currently exercisable.
4. Holder concentration (quick read)
KEYQ=""; [ -n "${ETHERSCAN_API_KEY:+x}" ] && KEYQ="&apikey={ETHERSCAN_API_KEY}"
./secretcurl -m 10 -s "https://api.etherscan.io/v2/api?chainid=8453&module=token&action=tokenholderlist&contractaddress=${TOKEN}&page=1&offset=10${KEYQ}" | jq '.result'
Compute top-1 and top-10 share of supply. Flag +2 if top-1 > 30% (excluding known LP/lock/burn addresses), +1 if top-10 > 70%. If this endpoint returns empty on the keyless tier, note holders=unavailable and skip this signal rather than failing. Depth: on --depth=deep when the holders check is also selected, take the top-1/top-10 EOA share from that check's full result instead of this 10-row sample.
5. LP / liquidity check — identify the token's main pool (Aerodrome / Uniswap V3 on Base). If LP tokens sit in a known locker or burn address (0x000…dead, Unicrypt, Team Finance) → liquidity locked (lowers risk). If LP is held by the deployer EOA → +2 (pull risk). Depth: on --depth=deep when the lp check is also selected, use that check's LOCKED/PARTIAL/UNLOCKED verdict here.
6. Score + verdict — sum the weights:
| Score | Verdict |
|---|
| 0–2 | LOW |
| 3–5 | ELEVATED |
| 6–8 | HIGH |
| 9+ | CRITICAL |
The verdict must come from this table — no freelance labels. Section end-states: RUG_SCAN_OK (LOW), RUG_SCAN_FLAGGED (≥ELEVATED), RUG_SCAN_ERROR (all fetches failed).
Check contract — Contract Audit
Deep structural inspection: what powers exist, who holds them, and whether they're still exercisable.
1. Source + verification
ADDR="${var}"
KEYQ=""; [ -n "${ETHERSCAN_API_KEY:+x}" ] && KEYQ="&apikey={ETHERSCAN_API_KEY}"
./secretcurl -m 10 -s "https://api.etherscan.io/v2/api?chainid=8453&module=contract&action=getsourcecode&address=${ADDR}${KEYQ}" | jq '.result[0] | {ContractName, Proxy, Implementation, CompilerVersion, verified: (.SourceCode != "")}'
If unverified, say so plainly: no static analysis is possible and audit confidence is low. Continue with the onchain checks below.
2. Proxy / upgradeability — if Proxy == "1" or the source contains delegatecall, read the EIP-1967 implementation slot:
curl -m 10 -s -X POST "${BASE_RPC_URL:-https://mainnet.base.org}" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["'"$ADDR"'","0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","latest"],"id":1}' | jq -r '.result'
A non-zero slot = upgradeable (Transparent/UUPS). Upgradeable means post-deploy logic can change — flag who controls the upgrade (admin/owner from step 3).
3. Ownership & admin roles — probe common accessors via eth_call and record any that return a non-zero address:
| Function | Selector |
|---|
owner() | 0x8da5cb5b |
admin() | 0xf851a440 |
paused() | 0x5c975abb |
curl -m 10 -s -X POST "${BASE_RPC_URL:-https://mainnet.base.org}" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"'"$ADDR"'","data":"0x8da5cb5b"},"latest"],"id":1}' | jq -r '.result'
Check whether the owner address itself has code (multisig/contract) vs is an EOA via eth_getCode.
4. Dangerous function surface — from verified source, enumerate externally-callable, owner-gated functions and classify:
- Supply:
mint, burnFrom
- Access:
blacklist, setFreeze, pause/unpause
- Economics:
setFee, setTax, setMaxTx, setLimits
- Control:
transferOwnership, upgradeTo, setImplementation
- Drain: arbitrary
call/delegatecall reachable by admin, withdraw/rescueTokens that can move user funds
Depth: --depth=quick may stop after the onchain reads (steps 1–3) and report the capability matrix from those; --depth=deep runs the full step-4 source-based surface enumeration. Report a power as a risk only if it's live AND not renounced. Section end-states: AUDIT_OK, AUDIT_FLAGGED (a live, non-renounced power in {upgrade, mint, blacklist, drain}), AUDIT_UNVERIFIED, AUDIT_ERROR.
Check deployer — Deployer Trace
"What else did this person ship, and how did those end?" — entity intel for spotting serial ruggers.
1. Resolve deployer — the subject is a token, so resolve its creator first:
TARGET="${var}"
KEYQ=""; [ -n "${ETHERSCAN_API_KEY:+x}" ] && KEYQ="&apikey={ETHERSCAN_API_KEY}"
./secretcurl -m 10 -s "https://api.etherscan.io/v2/api?chainid=8453&module=contract&action=getcontractcreation&contractaddresses=${TARGET}${KEYQ}" | jq -r '.result[0].contractCreator'
Use contractCreator as the deployer for the rest of this check; if the subject is already an EOA, use it directly.
2. Enumerate deployments — pull the deployer's tx list, keep only contract-creation txns (empty to, or a receipt contractAddress):
DEPLOYER="<contractCreator from step 1>"
KEYQ=""; [ -n "${ETHERSCAN_API_KEY:+x}" ] && KEYQ="&apikey={ETHERSCAN_API_KEY}"
./secretcurl -m 10 -s "https://api.etherscan.io/v2/api?chainid=8453&module=account&action=txlist&address=${DEPLOYER}&startblock=0&endblock=99999999&sort=asc${KEYQ}" | jq '[.result[] | select(.to == "")]'
For each creation record: contract address, creation date, and cheap current state (has code? verified?).
3. Pattern linkage — group deployments that share signals (same bytecode, same token-name template, identical owner, sequential deploys minutes apart). Repeated identical templates from one deployer is a strong serial-launcher signal.
4. Outcome per contract — for each deployed token, a fate check (reuse rug logic lightly): liquidity pulled? ownership renounced? holders → near-zero? Classify each ALIVE, ABANDONED, or RUGGED (LP removed AND price → 0; never infer RUGGED from a low balance alone).