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.
| Status | Meaning |
|---|---|
402 | The quote. Nothing is paid and no job exists yet. |
202 | Paid. The job runs. The body holds job_id and status_url. |
400 | The filter, the IDL or the range is not valid. The body says why. |
503 | Send 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
}
| Field | Type | Meaning |
|---|---|---|
programs | base58 list | Keep instructions to these programs, top-level and inner. |
accounts | base58 list | Keep transactions that touch one of these accounts, also through an address lookup table. |
discriminators | hex list, 8 bytes each | Keep instructions whose data starts with one of these. |
include_votes | bool, default false | Keep vote transactions. |
include_failed | bool, default true | Keep failed transactions. |
include_success | bool, default true | Keep successful transactions. |
only_idl_instructions | bool, default false | Keep only what the IDL names. Needs an IDL. |
start_slot, end_slot | integer | Narrow the epoch range. The end is exclusive. The price is still for whole epochs. |
min_fee, max_fee | lamports | Fee 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.
| Preset | Category | Program | Decodes |
|---|---|---|---|
Pump.funpumpfun | Launchpad | 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P | 56 ix, 32 ev |
Raydium LaunchLabraydium-launchpad | Launchpad | LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj | 28 ix, 4 ev |
Meteora Dynamic Bonding Curvemeteora-dbc | Launchpad | dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN | 35 ix, 29 ev |
PumpSwappump-swap | DEX | pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA | 38 ix, 27 ev |
Raydium CLMMraydium-clmm | DEX | CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK | 34 ix, 15 ev |
Raydium CPMMraydium-cpmm | DEX | CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C | 14 ix, 2 ev |
Meteora DLMMmeteora-dlmm | DEX | LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo | 76 ix, 30 ev |
Meteora DAMM v2meteora-damm-v2 | DEX | cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG | 39 ix, 25 ev |
Orca Whirlpoolorca-whirlpool | DEX | whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc | 66 ix, 6 ev |
Raydium AMM v4raydium-amm-v4 | DEX | 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 | raw |
Jupiter Swap v6jupiter-v6 | Aggregator | JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 | 19 ix, 6 ev |
Jupiter Perpetualsjupiter-perpetuals | Perpetuals | PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu | 59 ix, 25 ev |
Kamino Lendingkamino-lending | Lending | KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD | 66 ix, 5 ev |
marginfi v2marginfi-v2 | Lending | MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA | 124 ix, 50 ev |
OpenBook v2openbook-v2 | Order book | opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb | 29 ix, 8 ev |
Phoenixphoenix | Order book | PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY | raw |
Jupiter DCAjupiter-dca | Limit order and DCA | DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M | 12 ix, 6 ev |
Jupiter Limit Order v2jupiter-limit-order-v2 | Limit order and DCA | j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X | 8 ix, 4 ev |
SPL Tokenspl-token | Core | TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA | raw |
Token-2022token-2022 | Core | TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb | raw |
Metaplex Token Metadatametaplex-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:
| Column | Type | Meaning |
|---|---|---|
slot | u64 | The slot of the block. |
tx_index | u32 | The position of the transaction in the block. |
signature | string | The transaction signature, base58. |
success | bool | False for a failed transaction. |
fee | u64 | The transaction fee, in lamports. |
program_id | string | The program the instruction calls. |
ix_index | u32 | The top-level instruction index. An inner row has the index of its parent. |
is_inner | bool | True for an inner instruction (CPI). |
inner_index | u32, nullable | The position in the parent's inner list. Null on a top-level row. |
stack_height | u32, nullable | 2 for a direct CPI, 3 under that. Null when not recorded. |
accounts | list of string | All 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 type | Column |
|---|---|
bool, u8…u64, i8…i64, f32, f64 | The matching Arrow type |
u128, i128 | String, in decimal. Arrow has no 128-bit integer. |
string, pubkey | String. A pubkey is base58. |
bytes, [u8; N], vec<u8> | Binary |
option<T> of the types above | That column, nullable |
| An enum with no data | String, the variant name |
vec, array, struct, an enum with data | String, 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
| Route | What it does |
|---|---|
POST /scan | Quote, payment and start. See above. |
GET /job/<id> | The state. Download URLs and a DuckDB query when done. |
GET /job/<id>/urls | Plain text, one URL for each line, for wget -i. |
GET /presets | The presets, without their IDLs. |
GET /presets/<slug> | One preset with its filter and IDL. |
GET /health | Liveness, and the newest epoch in the archive. |
Limits
| Archive for one job | 13 TB |
| Epochs for one job | 100 |
| Output for one job | 60 GB |
| IDL size | 512 KB |
| Download URLs | 7 days |
| Archive delay behind the chain | 2 to 4 days |