PerroAgent

Documentation

How Perro reads the chain

What is checked, how each label is decided, where the data comes from, and where the view ends. If something here and the app disagree, that is a bug in one of them.

What PerroAgent does

PerroAgent is a read-only monitoring and investigation tool for Solana mainnet-beta. It supports one workflow: observe, investigate, explain, monitor.

  • Token Sniff reads a token mint and reports each control as its own finding.
  • Wallet Patrol reads any public wallet: SOL, token balances and recent transactions.
  • Follow the Trail builds a bounded graph of observable transfers.
  • Watchlist and rules re-read targets on a schedule, on the server, and store deduplicated alerts.

It is not a scam detector, not a price predictor and not a trading system. It never asks for a seed phrase or private key, never requests a token approval, and never asks you to sign a transaction. Scanning a public address needs no wallet at all.

How token checks work

A token scan is one read of the mint account and its Metaplex metadata account at the same slot, using the node's parsed form (jsonParsed). Two token programs are supported: the classic SPL Token program and Token-2022. An address owned by anything else is not opened as a token; the app says what it is instead.

Findings, not a score

Each check produces a finding with five parts: the observation, a severity, the evidence, a plain explanation, and what the check cannot show. There is no combined safety score, because adding unlike things together hides which one matters.

SeverityMeaning
CheckedThis one check completed and found no alert condition (for example, the mint authority is revoked). It is not a statement that the token is safe.
InfoA fact worth knowing that asks for no particular attention.
AttentionA control exists that can change how the token behaves: a mint authority, a freeze authority, a changeable transfer fee, a transfer hook.
CriticalAn observed condition with direct power over holders: a permanent delegate is set, or the token is paused right now.
UnknownNot evaluated, unavailable or unsupported. Never the same as zero and never the same as “no risk”.

Token-2022 extensions

These extensions are described when present: transfer fee, permanent delegate, transfer hook, default account state, non-transferable, pausable, mint close authority, interest-bearing display, scaled display amount, confidential transfers (mint, fee, mint-and-burn), metadata pointer, token metadata, and the group and member extensions. Any other extension the node reports is listed by name as Not evaluated. For a transfer hook, the hook program's address is reported; what that program does is not evaluated.

Holder distribution

A token account is not a holder, and an owner address is not a person. Balances are summed per owner address and each owner gets an address type read from the chain: a wallet-type address (System Program account on the curve), a key with no SOL account, a program-owned account, a program-derived address, or a pool address the market-data provider lists.

  • All accounts: when the token index reports at most 25,000 holders (or nothing), every token account of the mint is read in one filtered scan.
  • Largest accounts: otherwise the 20 largest token accounts are read. This is not a census and the page says so.
  • Unavailable: the key-free public RPC refuses the largest-accounts method, so large tokens show this state until an RPC provider is configured. Nothing is estimated.

The concentration finding asks for attention when one wallet-type address holds 20% or more of supply. No total holder count is derived by PerroAgent; a provider-reported count is shown separately, labeled with its provider. Insider ownership, bundled supply, developer identity and ownership clusters are not claimed anywhere.

Liquidity

Pools come from DexScreener's token-pairs endpoint. The page names the provider, lists each pool by address, shows the retrieval time, and states the scope of the total: the sum of the USD liquidity the provider reports per pool, each pool counted once by its address. Liquidity lock status is not evaluated. No listed pool does not mean no liquidity.

How wallet activity is classified

Each transaction is decoded from the node's parsed form (legacy and versioned transactions, top-level and inner instructions) and described from the wallet's point of view by rules that are written down:

LabelRule
Failed transactionThe node recorded an error. Nothing moved; the fee payer still paid the fee.
Decoded swapA program known to exchange assets ran, the wallet signed, and exactly one asset left the wallet while exactly one arrived.
SOL transfer in / outA System Program transfer instruction names the wallet as destination or source. SOL sent to a token account (rent, wrapping) is not a transfer to a party.
Token transfer in / outThe wallet's token balance changed while another owner's balance of the same token moved the other way, and no known trading program ran.
Mint, Burn, Account setupNamed by the instruction that did it.
Balance changeA balance changed and none of the rules above explains it. A balance that went down is not called a sale.
Unknown interactionThe wallet signed instructions PerroAgent does not decode and no balance changed.

Every transaction shows how many of its instructions were decoded. The list of trading programs comes from operators' own documentation and Jupiter's published program map; a program that is not on it never produces a swap label.

Transfer trails

A trail starts from one address and a time window (1, 7, 30 days). Each step fetches the most recent transactions of one address and keeps the transfers that involve it. You choose which counterparties to expand. Limits are shown in the graph and enforced:

LimitPublic RPCConfigured RPC
Transactions per step820
Transactions per investigation48160
Addresses per investigation4080
Hops22

A line means “address A transferred X to address B”, with its transactions listed. It never means the two addresses belong to the same person. Exchanges, routers and custodians create links between unrelated people. Transactions that ran a trading program are counted and left out of the graph, because their transfers are the legs of an exchange with a pool.

Monitoring and rules

Rules belong to a signed-in wallet address and are evaluated on the server by a scheduler, so they keep working when the browser is closed. Polling from a browser tab is not used for monitoring.

Rule typeTargetWhat it sees and misses
SOL transfer above a thresholdWalletReads System Program transfer instructions in the wallet’s new transactions since the last checkpoint. Wrapping SOL and account rent are not counted. Wrapped SOL (wSOL) token transfers are not native transfers and are not covered. If more transactions arrive between two evaluations than one evaluation examines, the rule catches up over the next evaluations and records a possible gap when it cannot.
Token balance reductionWalletCompares the sum of the wallet’s token accounts for the mint at each evaluation. It sees the balance at evaluation time only: a drop that reverses between two evaluations is not seen. A reduction is not a sale: tokens can be transferred, burned or moved.
Mint authority changeToken mintReads the mint account’s authority field at each evaluation. A change that is made and undone between two evaluations is not seen.
Freeze authority changeToken mintReads the mint account’s authority field at each evaluation. A change that is made and undone between two evaluations is not seen.

Liquidity-drop rules are not offered: the market-data provider's pool set for a token is not stable enough to compare reliably between evaluations.

Intervals, baseline, deduplication

  • Intervals: 5 min, 15 min, 1 h, 6 h, 1 d. The operator can raise the shortest one with MIN_RULE_INTERVAL_SECONDS.
  • The first valid observation only establishes a baseline. It cannot raise an alert.
  • A failed read is recorded as a failed evaluation and concludes nothing. It is never read as a change. Failing rules are retried with backoff.
  • Each alert has a deduplication key made from the facts that caused it (the transaction signature; or the previous and current value with the previous observation). Evaluating the same facts twice stores one alert.
  • Transfer rules keep a signature checkpoint and process new transactions oldest first. If more arrive than one evaluation examines, the rest wait for the next evaluation; beyond 100 a possible data gap is recorded on the evaluation.
  • The quiet period holds back further alerts from a transfer or balance rule after one fires. Authority changes always alert.

Rule states

A rule shows Active only when it has been evaluated successfully and a scheduler has completed a pass recently, together with the time of its last successful evaluation. The other states are named for what they are: Paused, Awaiting baseline, Delayed, Provider unavailable, Scheduler not configured and Unsupported target.

The engine

One engine serves the worker process and the scheduled route. Rules are leased with FOR UPDATE SKIP LOCKED, so two runners never evaluate the same rule at once and a runner that dies does not strand its rules. The alert insert, the evaluation record and the rule update are one database transaction. An evaluation has a 30-second timeout, a pass has a time budget, and a provider rate limit ends the pass early.

Evidence and data freshness

  • Every panel names its source. Chain reads carry the slot and the time PerroAgent read them; provider data carries the provider's name and the retrieval time.
  • Short server caches (about 12 to 45 seconds) protect the providers. Refresh asks for a new read. Data older than a few minutes is labeled stale.
  • Zero, unknown, unsupported, unavailable, loading and stale are different states and are shown differently.
  • Token names, symbols and memos are untrusted text: cleaned, capped and rendered as text. Metadata links and images are never fetched.

Data sources and their limits

SourceUsed forLimits
Solana JSON-RPCAccounts, mints, balances, signatures, transactionsThe key-free public endpoint allows about 10 transaction lookups per 10 seconds and refuses getTokenLargestAccounts. Nodes keep limited history, so old transactions can be missing.
Helius RPC (optional)The same standard RPC methods, faster, when HELIUS_API_KEY is setUsed as an RPC endpoint only. Its enhanced-transaction parsing is not used. This path has not been exercised with a real key.
DexScreenerPools, liquidity and price per tokenProvider-reported. Only indexed pools appear. Says nothing about locked liquidity.
Jupiter Tokens API v2A provider-reported holder count that decides whether a full holder scan is attemptedKey-free requests are paced to one about every two seconds. The index can lag the chain.
Solana ExplorerVerification links onlyNothing is read from it.

The server contacts a fixed list of provider hosts and nothing else. It never fetches a URL that comes from a user, from token metadata or from a web page. Provider adapters live in their own modules, so one can be replaced without touching the interface.

Ask Perro

When a model key is configured, Ask Perro answers questions through a fixed list of read-only lookups: address type, token checks, holders, market data, wallet balances, recent activity, saved-observation comparison, your rules and your alerts. Each lookup result becomes a numbered record, the answer must cite the records it used, and citations of records that do not exist are removed before the answer is shown.

  • It has no tool that writes, signs, sends or fetches a URL. No key, cookie or signing capability is given to the model.
  • A rule is only ever drafted. You confirm the target, condition, threshold and interval in the rule form before anything is created.
  • “What changed since the last scan?” is answered only from observations you saved. With fewer than two, it says a comparison cannot be made yet.
  • Without a model key the panel says conversational assistance is unavailable. Nothing is simulated; findings keep their written explanations.

Sign-in and privacy

Sign-in is a message signature. The server creates a random nonce, stores it with your address, this site's domain and a five-minute expiry, and returns the exact text to sign. On verification it rebuilds the text from its own record, checks the ed25519 signature, and consumes the nonce in one atomic statement, so the same signature cannot be used twice. The session is an HttpOnly cookie; the database stores only its hash. A connected wallet without a verified signature is not a session.

Monitored addresses, rules, evaluations and alerts are stored for your account, and every address you inspect or monitor is sent to the configured providers. See Privacy.

Running it

Local development

pnpm install
cp .env.example .env.local      # then edit it
pnpm db:dev                     # local PostgreSQL (or: docker compose up -d db)
pnpm db:migrate
pnpm dev                        # http://localhost:29300
pnpm worker                     # the monitoring worker, in another terminal

Without DATABASE_URL the app still scans tokens and wallets; sign-in, watchlists, rules and alerts show a setup state. Without an RPC provider it uses the key-free public endpoint. Checks: pnpm check (types, lint, unit tests), pnpm live-check (real provider requests), pnpm e2e (the flow against a running server and database), pnpm build.

Production

  • Set DATABASE_URL to a PostgreSQL database and run pnpm db:migrate (the app also applies pending migrations on first use).
  • Set SOLANA_RPC_URL or HELIUS_API_KEY. The public endpoint is too slow for a deployment many people share.
  • Set CRON_SECRET. On Vercel, vercel.json schedules /api/cron/monitor every 15 minutes and the platform sends the secret as a bearer token. That cadence, together with MIN_RULE_INTERVAL_SECONDS=900, keeps a free serverless database inside its monthly compute allowance; with a paid database or the worker the schedule can be every five minutes. Sub-daily schedules need a paid plan; on a plan limited to daily schedules, run pnpm worker on any always-on host pointed at the same database instead.
  • Optionally set ANTHROPIC_API_KEY to enable Ask Perro.

Rate limits are kept in process memory, so on a serverless host they are per instance: a courtesy limit, with provider quotas as the real bound.

Known limitations

  • No transaction preview or simulation is built. It is future work and has no navigation entry.
  • No external notifications (email, push, messaging). Alerts go to the in-app inbox only.
  • No liquidity-drop rules, no prices on wallet pages, no valuation.
  • Wallets that cannot sign messages cannot sign in. They can still be inspected as public addresses.
  • Holder data for large tokens needs a configured RPC provider.
  • A monitoring rule sees state at evaluation time. Something that happens and reverses between two evaluations of a balance or authority rule is not seen.
  • PerroAgent has not been audited, and nothing here is legal, financial or security advice.