Für Entwickler / Anwendungs-APIs

Pepper-DEX-API

Ein nur lesender Index über die Logs von Pepper, der zwei Generationen von Pool aus zwei Satz Tabellen ausliefert. Er antwortet in dezimalen Strings, kappt jede Liste bei fünfhundert Zeilen, ohne es zu sagen, und sendet überhaupt keinen CORS-Header.

Zwei DEXe, eine API

Pepper hat Paare mit konstantem Produkt und konzentrierte Pools nebeneinander. Dieser Dienst liefert beide aus, aus zwei Satz Tabellen, unter zwei Präfixen: Die nackten Pfade sind die mit konstantem Produkt, und alles unter /cl/ ist konzentriert. Sie sind keine Varianten voneinander.

Die beiden Familien sind nicht austauschbar

Ein Paar mit konstantem Produkt hat zwei Reserven und einen Preis. Ein konzentrierter Pool hat weder das eine noch das andere: Er hat einen Preis, einen Tick, die an diesem Tick aktive Liquidität und eine unbegrenzte Menge von Spannen, die überhaupt nicht aktiv sind. activeLiquidity als Tiefe zu lesen oder ein Guthaben als handelbare Größe ist auf genau die Weise falsch, die Geld kostet - die Tiefe am aktuellen Preis kann ein Tausendstel des Guthabens sein, während der Rest in Spannen liegt, die der nächste Handel nie berührt.

Eine Pool-Adresse gehört zu einer Familie. /pool/{address} nach einem konzentrierten Pool zu fragen ist eine 404, keine Umleitung.

Einen Token gestartet? Geben Sie ihm ein Logo und das Verifiziert-Kennzeichen

Ein auf Pepper gestarteter Token hat kein Verifiziert-Kennzeichen, bis er auf der öffentlichen Token-Liste steht, und von dort nehmen die Pickle-Apps auch Logo, Namen und Symbol eines gelisteten Tokens. Der Weg dorthin ist ein Pull Request mit seinem Logo und seinem Eintrag - Token-Liste und Adress-Tags nennt die Schritte und die Kriterien.

Basis-URL und CORS

EndpunktValue
Öffentlichhttps://pepper.picklechain.xyz/api/ein nginx-Einhängepunkt auf der Herkunft der Pepper-Website
Ein Stack, den Sie betreibenhttp://127.0.0.1:4020der eigene Port des Dienstes
CORSüberhaupt keineskein Header, kein OPTIONS-Handler, kein Verb außer GET

Ein Browser auf einer anderen Herkunft kann diesen Dienst nicht aufrufen

Es gibt darin nirgends einen access-control-allow-origin-Header und kein do_OPTIONS, das einen Preflight beantworten würde. Ein fetch von einer Seite auf Ihrer eigenen Domain scheitert, während die identische Anfrage von curl oder einem Server gelingt, und das ist der Fehlschlag, den alle als Netzwerkfehler fehldeuten. Stellen Sie ihn hinter Ihre eigene Herkunft, oder rufen Sie ihn aus einem Backend auf.

Jede Antwort wird als no-store mit x-content-type-options: nosniff gesendet. Es gibt im Dienst keine Ratenbegrenzung: Was eine Aufruferin begrenzt, ist die Kappung auf limit, die auf jeder Listenroute 500 beträgt, mit einem Standardwert von 50.

Routen mit konstantem Produkt

Params
none
Rückgabe
{cursorBlock, lagBlocks, updatedAt, spoolHeadBlock, streams:{v2, cl}}

Where each indexer stream has got to, against the head of the spool. The three top-level keys are the constant-product stream, kept there so a caller written before the concentrated pools existed still reads what it meant.

Gut zu wissen. Check `lagBlocks` before trusting anything else in this service. A stream that has stopped serves its last answer indefinitely and nothing else on any route says so.

Params
none
Rückgabe
{pools: [...], ethUsd}

Every constant-product pool, ordered by swap count: both tokens' metadata, reserves, the block each reserve was read at, creation block, swap count and cumulative per-side volume.

Gut zu wissen. An OBJECT, not a bare array - the pools are under `pools`. `volume0`/`volume1` are cumulative amounts PAID IN per side, so they are not comparable across pools and are not a price.

Params
the pair address in the path, matched case-insensitively
Rückgabe
one pool object, plus ethUsd

One pool, in the same shape as a row of /pools.

Gut zu wissen. It is implemented by building the FULL pool list and scanning it, so it costs exactly what /pools costs. Fetching ten pools one at a time does ten times the work of fetching all of them. 404 when unknown.

Params
none
Rückgabe
{tokens: [{address, symbol, decimals, ...}]}

Every token seen on either side of a constant-product pool, which is what a swap picker needs.

Gut zu wissen. Token metadata is read once per process and cached for the life of that process - not to be fast, but because the node meters `eth_call` through one bucket shared by every client of it. A token that changes its symbol keeps the old one until a restart.

Params
pair, sender, limit
Rückgabe
{swaps: [{txHash, logIndex, block, pair, sender, recipient, amount0In, amount1In, amount0Out, amount1Out}]}

Newest first, filterable by pair and by sender, both exact addresses.

Gut zu wissen. Amounts are decimal strings. A malformed address parameter is a 400, but an address written without its 0x prefix is accepted rather than silently truncated.

Params
pair, limit
Rückgabe
{events: [{txHash, logIndex, block, pair, kind, sender, recipient, amount0, amount1}]}

Mints and burns, newest first, `kind` being one of those two words.

Gut zu wissen. `recipient` is null on a mint. The pool's Mint event carries no recipient, so the field is left empty rather than filled in from the sender, which would read as a fact.

Params
none
Rückgabe
{pools, swaps, mints, burns, poolsWithEth, poolsWithoutEth, valueEthWei, valueEthDenominator, valueUsd, ethUsd, concentrated}

Counters for the constant-product side, an ETH-denominated figure for the pools that hold WETH, and the concentrated side's counters nested under `concentrated` rather than added in.

Gut zu wissen. `valueEthWei` IS NOT TVL and the service says so in its own source. It is the WETH side doubled, summed over the pools that have a WETH side; a pool without one contributes nothing and is counted separately under `poolsWithoutEth`. The concentrated figures are kept apart because the doubling rule does not hold for them at all.

bash
# Health first, always. lagBlocks is the only field that tells you
# whether anything else on this service is current.
curl -s "$DEX_API/health"

# Pools are under a key, not at the top level.
curl -s "$DEX_API/pools" | jq '.pools[0] | {pair, reserve0, reserve1, swapCount}'

# Swaps for one pair. The address may be written with or without 0x.
curl -s "$DEX_API/swaps?pair=$PAIR&limit=100" | jq '.swaps | length'

Konzentrierte Routen

Params
none
Rückgabe
{pools: [...], ethUsd}

Every concentrated pool: fee tier, tick spacing, whether it is initialised, the square-root price, the current tick, the liquidity active at that tick, both balances and the derived human price.

Gut zu wissen. `activeLiquidity` is the depth AT the current price, not the size of the pool, and `balance0`/`balance1` are the size. They are the same number only in a pool where every position spans the whole range, which is the one shape nobody opens a concentrated pool to build.

Params
the pool address in the path
Rückgabe
one pool object, plus ethUsd

One concentrated pool. Unlike /pool/{pair} this one is a keyed lookup, so it is cheap.

Gut zu wissen. 404 when unknown. A malformed address is a 400.

Params
none
Rückgabe
{tiers: [{fee, tickSpacing, enabledBlock}]}

The fee tiers the factory has enabled, in fee order. `fee` is in millionths, so 3000 is 0.30 percent.

Gut zu wissen. One pair can have a pool at every enabled tier, so a tier is part of a pool's identity here rather than a property of the pair.

Params
pool, owner, closed, limit
Rückgabe
{positions: [{pool, owner, tickLower, tickUpper, liquidity, deposited0/1, withdrawn0/1, collected0/1, lastBlock}]}

Open ranges and their sizes, newest activity first.

Gut zu wissen. A closed position is kept as a row of zeroes and is HIDDEN unless you pass `closed=1`. And `collected0`/`collected1` are principal and fees together - no event separates them, so subtracting to show fees earned is right only for a fully closed position.

Params
pool (required)
Rückgabe
{ticks: [{tick, liquidityGross, liquidityNet}]}

The initialised ticks of one pool, in tick order - the input to a depth chart.

Gut zu wissen. `pool` is REQUIRED and its absence is a 400, not an empty list. Every tick of every pool in one answer would be a depth chart of nothing, so the service refuses rather than serving it.

Params
pool, sender, limit
Rückgabe
{swaps: [{txHash, logIndex, block, pool, sender, recipient, amount0, amount1, sqrtPriceX96, liquidity, tick}]}

Newest first, with the pool's state as of that swap.

Gut zu wissen. The amounts are SIGNED, as the chain emits them: one side is always negative and that is which way the trade went. Summing them without taking absolute values nets a market to nothing.

Params
pool, owner, kind, limit
Rückgabe
{events: [{txHash, logIndex, block, pool, kind, owner, sender, recipient, tickLower, tickUpper, liquidity, amount0, amount1}]}

Mints, burns and collects, newest first.

Gut zu wissen. A burn moves no tokens. It credits what is owed inside the pool and waits for a collect, so a burn and its collect are two events and only the second is money moving.

Preise kommen als Strings zurück, einmal berechnet

sqrtPriceX96 ist der eigene Speicher des Pools, und price ist das, was eine ganze Einheit von token0 kauft, um die Dezimalstellen beider Token bereinigt und auf achtzehn signifikante Stellen formatiert. Es selbst aus der Wurzel zu berechnen geht leicht auf subtile Weise schief: Vor dem Quadrieren zu teilen rundet, und das Quadrieren verdoppelt den Fehler dann, und so kommt ein Preis von genau eins als eine lange Reihe von Neunen zurück.

Was die Wertzahlen bedeuten

valueEthWei ist nicht TVL und darf nicht so beschriftet werden

Es ist die WETH-Seite eines Pools, verdoppelt, summiert über die Pools, die eine WETH-Seite haben. Diese Verdopplung ist die übliche Art, einen Pool mit konstantem Produkt von einer Seite aus zu bewerten, und sie ist nur deshalb aussagekräftig, weil diese Seite das eigene Gas-Asset der Chain ist. Ein Pool ohne WETH-Seite steuert überhaupt nichts bei und wird gesondert unter poolsWithoutEth gezählt - die Zahl ist also eine Untergrenze über eine Teilmenge, keine Gesamtsumme.

Die konzentrierten Pools werden nie eingerechnet. Ihre beiden Seiten sind nicht gleich viel wert, eine davon zu verdoppeln ist also eine Behauptung, die die Daten nicht tragen: Ein Pool, dessen Preis über jede Spanne gewandert ist, hält einen Token und nichts vom anderen. Ihre Zahlen leben unter concentrated und unter wethBalanceWei, das unverdoppelt ist und genau nach dem benannt, was es ist.

Mehrere Routen tragen außerdem eine Dollarzahl und eine Preismomentaufnahme. Die kommen aus einer Schicht, die dieser Seite nicht gehört - lesen Sie deren eigene Referenz dazu, was die Zahl bedeutet und wie alt sie ist. Zwei Regeln von hier lohnen es trotzdem, mitgenommen zu werden: Die Zahl ist null und nie 0, wenn sie nicht abgeleitet werden kann, denn eine Null ist eine Messung; und ein aus Reserven berechneter Preis pro Token wird nicht veröffentlicht und darf nicht erschlossen werden.

Der Indexer dahinter

It reads the sequencer's log spool in Postgres, not `eth_getLogs`. The node serves roughly two minutes of logs, so an indexer built on the RPC would see a window and present it as history.

Two streams, two topic sets, two cursors, and they never run in the same pass. The constant-product cursor has long been at the head of the chain; adding the concentrated topics to its filter would have skipped every such log already behind it, permanently and silently, so the concentrated stream starts at zero and backfills on its own.

  • It sleeps two seconds between passes once it has caught up, and takes at most 5000 logs per batch; both are environment variables.
  • The cursor moves only inside the transaction that wrote the rows, so a crash repeats a batch rather than skipping one.
  • Token-Symbole und Dezimalstellen werden einmal pro Prozess gelesen und für dessen ganzes Leben zwischengespeichert - nicht als Optimierung, sondern weil der Node eth_call durch einen Eimer misst, den sich alle seine Clients teilen.

Die praktische Folge steht auf /health: streams.cl kann weit hinter streams.v2 zurückliegen, und das ist normal statt kaputt, denn der konzentrierte Strom lädt von null nach, während der andere an der Spitze bleibt. Vergleichen Sie jeden Strom mit spoolHeadBlock, nicht mit dem anderen.

Fehler

StatusBedeutung
400bad parameterany address or number the handler could not parse. The message is always the same string.
404unknown pool, or no such routethere is no route table - an unmatched path falls through to this.
503index not readya database error, including the ordinary case of the indexer never having run, so the tables do not exist yet. It carries the first line of the driver's own message.

Es gibt nur drei, es gibt keine Fehlercodes im Körper, und die Nachrichten-Strings sind fest statt beschreibend. Verzweigen Sie über den Status. Eine 400 sagt insbesondere nichts darüber, welcher Parameter abgelehnt wurde.