Docs

The request, the filter and the output

Everything you need to send a scan and read the result. For agents, the same facts are in /llms.txt.

Send a request

Send POST /scan with a JSON body. Name a preset, or send a filter and an optional IDL. Add the first and the last epoch; both are inclusive.

curl -X POST https://trawl.run/scan \
  -H 'Content-Type: application/json' -d '{
  "preset": "pumpfun",
  "start_epoch": 1050,
  "end_epoch": 1052
}'

The answer is 402 Payment Required, with the firm price in quote and the x402 payment details in accepts. Send the same request again with an X-PAYMENT header from any x402 client. The answer is then 202 with a job_id.

StatusMeaning
402The quote. Nothing is paid and no job exists yet.
202Paid. The job runs. The body holds job_id and status_url.
400The filter, the IDL or the range is not valid. The body says why.
503Send it again after Retry-After seconds. The archive refused for now, or a long range needs one more request to finish its sizing.

A failed job issues a credit. Send the same job again with X-CREDIT: <credit id> in place of a payment.

The filter

A transaction must pass every field that is set. programs or accounts must hold at least one key.

{
  "programs": ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
  "accounts": [],
  "discriminators": [],
  "include_votes": false,
  "include_failed": true,
  "include_success": true,
  "only_idl_instructions": false,
  "start_slot": null, "end_slot": null,
  "min_fee": null, "max_fee": null
}
FieldTypeMeaning
programsbase58 listKeep instructions to these programs, top-level and inner.
accountsbase58 listKeep transactions that touch one of these accounts, also through an address lookup table.
discriminatorshex list, 8 bytes eachKeep instructions whose data starts with one of these.
include_votesbool, default falseKeep vote transactions.
include_failedbool, default trueKeep failed transactions.
include_successbool, default trueKeep successful transactions.
only_idl_instructionsbool, default falseKeep only what the IDL names. Needs an IDL.
start_slot, end_slotintegerNarrow the epoch range. The end is exclusive. The price is still for whole epochs.
min_fee, max_feelamportsFee bounds. Both are inclusive.

The account set compiles to a bloom filter, and each hit is checked in a hash set, so the match is exact.

Presets

A preset is one program, a filter on it, and its IDL, checked against real transactions of that program. A filter sent with a preset replaces the preset fields one by one. This request keeps the program and adds a wallet:

{"preset": "pumpfun", "filter": {"accounts": ["<wallet>"]}, "start_epoch": 1058, "end_epoch": 1058}

A preset with an idl beside it is refused. GET /presets lists them, and GET /presets/<slug> returns one with its filter and IDL.

PresetCategoryProgramDecodes
Pump.fun
pumpfun
Launchpad 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P 56 ix, 32 ev
Raydium LaunchLab
raydium-launchpad
Launchpad LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj 28 ix, 4 ev
Meteora Dynamic Bonding Curve
meteora-dbc
Launchpad dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN 35 ix, 29 ev
PumpSwap
pump-swap
DEX pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA 38 ix, 27 ev
Raydium CLMM
raydium-clmm
DEX CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK 34 ix, 15 ev
Raydium CPMM
raydium-cpmm
DEX CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C 14 ix, 2 ev
Meteora DLMM
meteora-dlmm
DEX LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo 76 ix, 30 ev
Meteora DAMM v2
meteora-damm-v2
DEX cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG 39 ix, 25 ev
Orca Whirlpool
orca-whirlpool
DEX whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc 66 ix, 6 ev
Raydium AMM v4
raydium-amm-v4
DEX 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 raw
Jupiter Swap v6
jupiter-v6
Aggregator JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 19 ix, 6 ev
Jupiter Perpetuals
jupiter-perpetuals
Perpetuals PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu 59 ix, 25 ev
Kamino Lending
kamino-lending
Lending KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD 66 ix, 5 ev
marginfi v2
marginfi-v2
Lending MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA 124 ix, 50 ev
OpenBook v2
openbook-v2
Order book opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb 29 ix, 8 ev
Phoenix
phoenix
Order book PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY raw
Jupiter DCA
jupiter-dca
Limit order and DCA DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M 12 ix, 6 ev
Jupiter Limit Order v2
jupiter-limit-order-v2
Limit order and DCA j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X 8 ix, 4 ev
SPL Token
spl-token
Core TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA raw
Token-2022
token-2022
Core TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb raw
Metaplex Token Metadata
metaplex-token-metadata
NFT metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s raw

raw: not an Anchor program. The result holds the raw instruction bytes and all context columns.

Your own IDL

Send an Anchor IDL in the idl field, in the 0.29 layout or the 0.30 layout, up to 512 KB. The scan derives each discriminator from the IDL. An explicit discriminator array wins; without one it is the first eight bytes of sha256("global:<snake_case name>").

Send one program for each IDL. The IDL applies to every program in the filter, so a second program can decode with the wrong layout.

Output files

part-tNN-SSSS.parquet         matched instructions, with a raw data column
ix-<name>-tNN-SSSS.parquet    one IDL instruction, decoded
ev-<name>-tNN-SSSS.parquet    one IDL event, decoded

NN is the scan thread. SSSS is the file sequence: a file closes at 2 GB and the next one opens. Each file is its own object in R2. There is no archive to unpack. Anything the IDL does not name, and anything that fails to decode, keeps its raw bytes in the part-* files.

Columns

Every row carries the transaction context:

ColumnTypeMeaning
slotu64The slot of the block.
tx_indexu32The position of the transaction in the block.
signaturestringThe transaction signature, base58.
successboolFalse for a failed transaction.
feeu64The transaction fee, in lamports.
program_idstringThe program the instruction calls.
ix_indexu32The top-level instruction index. An inner row has the index of its parent.
is_innerboolTrue for an inner instruction (CPI).
inner_indexu32, nullableThe position in the parent's inner list. Null on a top-level row.
stack_heightu32, nullable2 for a direct CPI, 3 under that. Null when not recorded.
accountslist of stringAll accounts of the instruction, in order.

part-* files add data, the raw bytes. A decoded table adds one column for each account the IDL names, then one column for each argument. Event tables add log_index and one column for each field. A name that matches a context column gets a prefix: acct_, arg_ or field_. Pump.fun's TradeEvent.fee is field_fee.

Decoded types

IDL typeColumn
bool, u8…u64, i8…i64, f32, f64The matching Arrow type
u128, i128String, in decimal. Arrow has no 128-bit integer.
string, pubkeyString. A pubkey is base58.
bytes, [u8; N], vec<u8>Binary
option<T> of the types aboveThat column, nullable
An enum with no dataString, the variant name
vec, array, struct, an enum with dataString, JSON. 64-bit and 128-bit integers inside are strings.

Every decoded column is nullable. A missing argument at the end of the data reads as null. A named account column is null when the transaction passed fewer accounts than the IDL names.

Events

An Anchor program writes an event in one of two ways, and the scan reads both: emit_cpi!, an inner instruction from the program to itself, and emit!, a Program data: log line. When a program writes one event both ways, the scan keeps the self-CPI copy, so one event is one row.

The scan reads a log line only in the frame of a program that passed the filter. The runtime cuts a log at 10 KB, and an emit! event after the cut is lost. (slot, tx_index, ix_index, inner_index, log_index) is unique.

Inner instructions

programs matches a top-level instruction and an inner one. Jupiter is called top-level, but the Raydium, Orca and Meteora swaps it routes are inner. (slot, tx_index, ix_index, inner_index) is unique for one instruction, and to sort by it replays the transaction in call order.

Download and query

GET /job/<id> returns the state. When it is done, it holds a URL for each file, a manifest URL and a DuckDB query. The URLs stay valid for 7 days from the end of the job.

# every file, in one command
wget -i https://trawl.run/job/<id>/urls

-- or query in place, with no download
SELECT mint, count(*) AS trades, sum(sol_amount) / 1e9 AS sol
FROM read_parquet(['https://…/ev-trade_event-t00-0000.parquet', …])
WHERE is_buy
GROUP BY mint ORDER BY sol DESC LIMIT 10;

API routes

RouteWhat it does
POST /scanQuote, payment and start. See above.
GET /job/<id>The state. Download URLs and a DuckDB query when done.
GET /job/<id>/urlsPlain text, one URL for each line, for wget -i.
GET /presetsThe presets, without their IDLs.
GET /presets/<slug>One preset with its filter and IDL.
GET /healthLiveness, and the newest epoch in the archive.

Limits

Archive for one job13 TB
Epochs for one job100
Output for one job60 GB
IDL size512 KB
Download URLs7 days
Archive delay behind the chain2 to 4 days