Docs
Read a wallet X Buy $DAWN
Docs · v1.0
Aurora communis

A field guide to Solana wallets

Dawnmoth reads any Solana address the way a naturalist reads a moth pinned to a board. It looks at what the chain already shows to everyone, measures it and names the species. No wallet connect, no keys, no signatures.

Every wallet ends up as one of nine species, from Larva nova, a wallet a few days old, to Balaena aurata, the whale. The reading takes a few seconds and runs entirely in your browser.

Quick start

The same module the site uses is served from the site itself. Import it and call identify.

import { identify } from "SITE/lib/dawnmoth.js";

const plate = await identify("A8A9GoxfmcjY2QAQ8tYS98SqDBF6hUFDmxAKBsLw29sL");

console.log(plate.species.latin);   // "Robotica insomnis"
console.log(plate.specimen);        // 9875
console.log(plate.notes);           // plain-language field notes
# The two public questions the module asks. Nothing else leaves the browser.

# 1. Last 1000 flights (signatures) and whether each one failed
curl -s https://solana-rpc.publicnode.com \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getSignaturesForAddress","params":["ADDRESS",{"limit":1000}]}'

# 2. SOL and every token jar the wallet owns
curl -s https://lite-api.jup.ag/ultra/v1/holdings/ADDRESS

# 3. Prices for up to 50 mints at a time
curl -s "https://lite-api.jup.ag/price/v3?ids=So11111111111111111111111111111111111111112"
<div id="moth">Reading…</div>
<script type="module">
  import { identify } from "SITE/lib/dawnmoth.js";
  const p = await identify("ADDRESS");
  document.getElementById("moth").innerHTML =
    "<img src='SITE/assets/" + p.species.img + "' width='240'>" +
    "<h3>" + p.species.latin + "</h3><p>" + p.species.lead + "</p>";
</script>
NOTEThe module is plain ES, about 12 KB, with no dependencies. It works on any page served over http or https, because both data sources allow requests from browsers.

Try it

This box runs identify() from lib/dawnmoth.js live, right here.

// waiting for an address
Examples:

The reading

One address in, one page of the field guide out. Five steps, all of them read-only.

  1. Check the address. It must be 32 to 44 base58 characters. Anything else stops here and nothing is sent.
  2. Read the flights. The last 1,000 signatures from a public Solana RPC: when each one landed and whether it failed.
  3. Open the jars. Jupiter returns the SOL balance and every token account, including the empty ones a wallet leaves behind after selling.
  4. Price the jars. Up to 150 held tokens are priced through Jupiter, 50 per request. A token with no price counts as dust.
  5. Measure and name. The browser turns all of that into the numbers in Measurements, scores each species and picks the highest.

Data sources

Two public services, both open to browsers. No keys are bundled with the site.

SourceEndpointWhat we take
Solana RPCgetSignaturesForAddress on solana-rpc.publicnode.comBlock time and error flag of the last 1,000 transactions
Jupiter/ultra/v1/holdings/<address>SOL balance, every token account and its amount
Jupiter/price/v3?ids=…USD price of SOL and held tokens
FallbackgetBalanceUsed only when the holdings call fails. Then the plate shows SOL only

Using your own RPC

Pass any endpoint that allows browser requests. The same goes for Jupiter if you run a proxy.

JS
const plate = await identify(address, {
  rpc: "https://your-rpc.example.com",   // any Solana JSON-RPC endpoint
  signatures: 500,                        // read fewer flights, faster
  dustUsd: 5                              // stricter idea of dust
});

Measurements

Everything the species key looks at. All of it comes from measure(raw).

FieldMeaningHow
flightsTransactions readUp to 1,000 most recent signatures
cappedWallet is older than what we readtrue when all 1,000 came back
ageDaysDays since the oldest flight readReal age only when capped is false
idleDaysDays since the last flightNow minus newest block time
perDayFlights a dayFlights over the span between oldest and newest
failRateShare of crashed flightsSignatures with a non-null error
activeHoursHours of the day it fliesDistinct UTC hours with at least one flight, 0 to 24
peakHourBusiest hourUTC hour with the most flights
medianGapTypical seconds between flightsMedian of gaps between neighbours
netUsdValue in plain sightSOL × price + priced tokens
tokens · dustFull jars · jars under $1Token accounts with a balance above zero
emptyJars · emptyShareToken accounts left at zeroUsually what is left after selling everything

Species key

Nine species. The order in this table is also the tie-break order.

SpeciesRecognise it byRarity
NOTEAurora communis always scores 40. If no other species reaches 41, the wallet is a common dawnmoth. It is not an error, it is a compliment.

Scoring

Each species gets a score from 0 to 100. The highest one wins. Ties go to the species that comes first in the key.

lib/dawnmoth.js · score(f)
larva:     flights == 0 ? 100 : (young ? 80 : 0) + (flights < 15 ? 25 : 0)
robotica:  (perDay > 2000 ? 85 : perDay > 400 ? 50 : perDay > 150 ? 25 : 0)
           + (activeHours >= 22 ? 30 : 0) + (flights > 50 && medianGap < 15 ? 15 : 0)
balaena:   netUsd >= 250000 ? 95 : netUsd >= 50000 ? 70 + (netUsd - 50000) / 10000 : 0
snipera:   failRate >= 0.1 ? 50 + failRate * 120 + (perDay > 40 ? 5 : 0) : 0
panica:    emptyJars >= 8 && emptyShare >= 0.55 ? 45 + emptyShare * 40 + min(emptyJars, 60) / 4 : 0
pulvis:    dust >= 12 && dustShare >= 0.65 ? 45 + dustShare * 30 + min(dust, 80) / 4 : 0
airdropia: tokens >= 8 && netUsd < 500 && 2 <= perDay <= 80 && failRate < 0.1 ? 52 + min(tokens, 40) / 2 : 0
holdia:    (capped || ageDays >= 60) && perDay < 3 && tokensUsd >= 50 ? 55 + min(idleDays, 60) / 2 : 0
aurora:    40
// every score is clamped to 0…100; young = not capped and ageDays < 10

Runner-up species

The plate shows the six best scores, not only the winner. A wallet that is 100 robot and 99 panic seller is worth knowing about.

Field notes

The bullet list on every plate. Each line is something any explorer already shows.

NoteShown when
First seen N days agoAlways. Says “older than its last 1,000 flights” when capped
Flies about N times a dayAlways
N% of flights crashedCrash rate is 2% or more
Most active around HH:00 UTCAlways. Adds “and at every other hour too” at 22+ active hours
Carries $N in plain sightAlways. SOL only when the token list was unavailable
N tokens worth less than a dollarAt least one dust token
Left N empty token jarsAt least one empty jar
Has not moved in N daysIdle for 7 days or more

The plate

A 1200 × 675 PNG drawn in the browser, sized for X.

It shows the species render, the Latin and common name, rarity, the specimen number and the first and last four characters of the address. The full address and balances are never drawn on it. The specimen number is a hash of the address, so the same wallet always gets the same number.

JS
import { specimenNumber } from "SITE/lib/dawnmoth.js";
specimenNumber("A8A9GoxfmcjY2QAQ8tYS98SqDBF6hUFDmxAKBsLw29sL");   // 9875, forever

dawnmoth.js API

Everything is a named export. The default export bundles them too.

ExportSignatureReturns
identify(address, opts?)Promise of a full plate, see Output format
readWallet(address, opts?)Raw data: signatures, SOL, jars, prices
measure(raw, opts?)The fields in Measurements
score(features)Object of nine scores, 0 to 100
classify(features){ species, scores }
fieldNotes(features)Array of plain-language strings
isAddress(string)true for a base58 string of 32 to 44 characters
specimenNumber(address)Stable number from 1000 to 9999
SPECIES · ORDERconstantsSpecies records and the tie-break order
DEFAULTSconstantEndpoints and limits, see below

Options

OptionDefaultMeaning
rpcsolana-rpc.publicnode.comSolana JSON-RPC endpoint
jupiterlite-api.jup.agBase for holdings and prices
signatures1000Flights to read, 1000 is the RPC maximum
maxPricedMints150Most tokens priced per reading
dustUsd1A jar worth less than this is dust
timeout15000Milliseconds per request

Output format

What identify() resolves to. Shortened.

JSON
{
  "address": "A8A9GoxfmcjY2QAQ8tYS98SqDBF6hUFDmxAKBsLw29sL",
  "specimen": 9875,
  "species": { "key": "robotica", "latin": "Robotica insomnis", "common": "The Sleepless Automaton", "rarity": "Rare", "img": "robotica.webp" },
  "scores": { "larva": 0, "robotica": 100, "balaena": 0, "snipera": 100, "panica": 99, "pulvis": 90, "airdropia": 0, "holdia": 0, "aurora": 40 },
  "features": { "flights": 1000, "capped": true, "perDay": 24000, "failRate": 0.85, "activeHours": 1, "netUsd": 4800, "tokens": 95, "dust": 80, "emptyJars": 4024 },
  "notes": ["Older than its last 1,000 flights. We stopped counting there.", "Flies about 24,000 times a day.", "…"],
  "readAt": "2026-11-04T18:20:00.000Z"
}

Errors

MessageCauseWhat to do
That does not look like a Solana address.Failed isAddressPaste the full address, no spaces
HTTP 429 from …Public endpoint rate limitWait a few seconds, or pass your own rpc
HTTP 5xx from lite-api.jup.agWallet too large for the holdings callHandled. The plate falls back to SOL only
The operation was abortedA request took longer than timeoutTry again, or raise timeout

Limits

What a field guide can and cannot tell from a moth on a board.

  • It sees the last 1,000 flights. A very old, very busy wallet is judged by its recent behaviour only.
  • It sees one wallet. People who split funds across many addresses look like many smaller moths.
  • Prices are today's. A token that was worth a lot last month and nothing today counts as dust.
  • Species are a joke with rules. The rules are exact and public, the names are not a verdict on anyone.
WARNINGEverything on a plate is already public, and reading someone's wallet does not ask them first. That is the point the site makes: if a field guide can do this in seconds, so can anyone else.

$DAWN

The coin of the field guide, on Solana.

Contract address soonBuy $DAWNChartX

The reader is free and stays free. It never asks for a wallet connection, holding $DAWN or anything else.

FAQ

Is it safe to paste my address?

An address is public by design. Dawnmoth only reads it. It cannot move funds, it never asks you to sign and it stores nothing.

Can I read someone else's wallet?

Yes, the same way any explorer can. That is exactly what the site wants you to notice.

I got the wrong species.

Look at the runner-up bars on your plate. Scores are close more often than you think, and the rules in Scoring show what tipped it.

Can my species change?

Yes. It reads your latest behaviour, so a caterpillar today can be a sniper next week. Most moths prefer not to discuss their past forms.

Why moths?

Moths are drawn to light. So are wallets, right up until someone turns it on.

dawnmoth.js v1.0 · data from Solana RPC and JupiterHome · Field guide · X