開発者へ / アプリケーション API
Pepper DEX API
Pepper のログの上に載った読み取り専用のインデックスで、二組のテーブルから二世代のプールを提供します。十進の文字列で答え、どの一覧も 500 行でそれと言わずに切り詰め、CORS のヘッダーをまったく送りません。
二つの DEX、一つの API
Pepper には定数積のペアと集中流動性のプールが並んで存在します。このサービスは二組の テーブルから、二つの接頭辞のもとでその両方を提供します。裸のパスが定数積のもので、/cl/ の下にあるものはすべて集中流動性です。互いの変種ではありません。
二つの一族は交換できません
定数積のペアは二つの準備金と一つの価格を持ちます。集中流動性のプールはそのどちらも 持ちません。持っているのは、価格、tick、その tickにおいて有効な流動性、そして まったく有効でない、上限のない数の価格帯です。activeLiquidity を深さとして、 あるいは残高を取引できる規模として読むことは、お金を失う特定の形で間違っています。現在の 価格での深さは残高の千分の一でもありえ、残りは次の取引が決して触れない価格帯に 座っています。
プールのアドレスはどちらか一方の一族に属します。集中流動性のプールについて /pool/{address} を尋ねると、リダイレクトではなく 404 になります。
Pepper で発行したトークンは、公開トークンリストに載るまで検証済みマークを持ちません。 Pickle のアプリが、リストに載ったトークンのロゴ、名前、シンボルを取ってくるのもそこです。 載せる方法は、ロゴとエントリーを含むプルリクエストです。手順と基準は トークンリストとアドレスタグにあります。
ベース URL と CORS
| エンドポイント | Value |
|---|---|
| 公開 | https://pepper.picklechain.xyz/api/Pepper サイトのオリジンへの nginx のマウント |
| 自分で動かすスタック | http://127.0.0.1:4020サービス自身のポート |
| CORS | まったくなしヘッダーなし、OPTIONS のハンドラーなし、GET 以外の動詞なし |
別のオリジンのブラウザは、このサービスを呼べません
どこにも access-control-allow-origin のヘッダーはなく、プリフライトに 答える do_OPTIONS もありません。自分のドメインのページからの fetch は失敗する一方、curl やサーバーからの同一のリクエストは成功します。 これは誰もがネットワーク障害と読み違える失敗です。自分のオリジンの背後に置くか、 バックエンドから呼んでください。
どの応答も no-store と x-content-type-options: nosniff を付けて 送られます。サービスの中にレート制限はありません。呼び出し側を縛るのは limit への切り詰めで、どの一覧のルートでも上限 500、既定値 50 です。
定数積のルート
/health
GET- Params
- none
- 戻り値
- {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.
知っておくとよいこと。 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.
/pools
GET- Params
- none
- 戻り値
- {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.
知っておくとよいこと。 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.
/pool/{pair}
GET- Params
- the pair address in the path, matched case-insensitively
- 戻り値
- one pool object, plus ethUsd
One pool, in the same shape as a row of /pools.
知っておくとよいこと。 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.
/tokens
GET- Params
- none
- 戻り値
- {tokens: [{address, symbol, decimals, ...}]}
Every token seen on either side of a constant-product pool, which is what a swap picker needs.
知っておくとよいこと。 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.
/swaps
GET- Params
- pair, sender, limit
- 戻り値
- {swaps: [{txHash, logIndex, block, pair, sender, recipient, amount0In, amount1In, amount0Out, amount1Out}]}
Newest first, filterable by pair and by sender, both exact addresses.
知っておくとよいこと。 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.
/liquidity
GET- Params
- pair, limit
- 戻り値
- {events: [{txHash, logIndex, block, pair, kind, sender, recipient, amount0, amount1}]}
Mints and burns, newest first, `kind` being one of those two words.
知っておくとよいこと。 `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.
/stats
GET- Params
- none
- 戻り値
- {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.
知っておくとよいこと。 `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.
# 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'集中流動性のルート
/cl/pools
GET- Params
- none
- 戻り値
- {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.
知っておくとよいこと。 `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
- 戻り値
- one pool object, plus ethUsd
One concentrated pool. Unlike /pool/{pair} this one is a keyed lookup, so it is cheap.
知っておくとよいこと。 404 when unknown. A malformed address is a 400.
/cl/tiers
GET- Params
- none
- 戻り値
- {tiers: [{fee, tickSpacing, enabledBlock}]}
The fee tiers the factory has enabled, in fee order. `fee` is in millionths, so 3000 is 0.30 percent.
知っておくとよいこと。 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
- 戻り値
- {positions: [{pool, owner, tickLower, tickUpper, liquidity, deposited0/1, withdrawn0/1, collected0/1, lastBlock}]}
Open ranges and their sizes, newest activity first.
知っておくとよいこと。 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.
/cl/ticks
GET- Params
- pool (required)
- 戻り値
- {ticks: [{tick, liquidityGross, liquidityNet}]}
The initialised ticks of one pool, in tick order - the input to a depth chart.
知っておくとよいこと。 `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.
/cl/swaps
GET- Params
- pool, sender, limit
- 戻り値
- {swaps: [{txHash, logIndex, block, pool, sender, recipient, amount0, amount1, sqrtPriceX96, liquidity, tick}]}
Newest first, with the pool's state as of that swap.
知っておくとよいこと。 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.
/cl/events
GET- Params
- pool, owner, kind, limit
- 戻り値
- {events: [{txHash, logIndex, block, pool, kind, owner, sender, recipient, tickLower, tickUpper, liquidity, amount0, amount1}]}
Mints, burns and collects, newest first.
知っておくとよいこと。 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.
sqrtPriceX96 はプール自身のストレージで、price は token0 の 1 単位が買えるものを、両方のトークンの小数桁で調整し、有効数字 18 桁に整形したものです。 平方根から自分で計算すると、微妙に間違えやすくなります。二乗する前に割ると丸めが入り、 二乗がその誤差を倍にします。ちょうど 1 の価格が長い 9 の並びとして返ってくるのは、 そのためです。
価値の数値が意味するもの
valueEthWei は TVL ではなく、そう札を付けてはなりません
それはプールの WETH 側を 2 倍し、WETH 側を持つプールについて合計したものです。 この 2 倍は、定数積のプールを片側から評価する標準的なやり方であり、意味を持つのはその側が チェーン自身のガス資産だからにすぎません。WETH 側のないプールはまったく寄与せず、poolsWithoutEth の下で別に数えられます。つまりこの数値は、合計ではなく、 部分集合に対する下限です。
集中流動性のプールがそこへ畳み込まれることは決してありません。その二つの側は同じ価値では ないので、片方を 2 倍することはデータが支えない主張です。価格がすべての価格帯より上へ 歩いていったプールは、一方のトークンだけを持ち、もう一方は持っていません。それらの数値は concentrated の下と、2 倍されておらず、まさにそのものの名前を持つ wethBalanceWei の下にあります。
いくつかのルートはドル建ての数値と価格のスナップショットも運びます。それらはこのページが 所有しないレイヤーから来ます。その数値が何を意味し、どれだけ古いかは、そちらのリファレンスを 読んでください。それでもここの二つの規則は持ち帰る価値があります。導出できないとき、その 数値は 0 ではなく null になります。ゼロは測定値だからです。そして 準備金から計算したトークンごとの価格は公開されず、推し量ってもなりません。
背後のインデクサー
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.
- トークンのシンボルと小数桁はプロセスごとに一度だけ読まれ、その一生のあいだキャッシュ されます。最適化のためではなく、ノードが
eth_callを、すべての利用者が 共有する一つのバケツで計量しているからです。
実際上の帰結は /health に出ます。streams.cl が streams.v2 よりはるかに遅れていることがありますが、それは壊れているのではなく 正常です。集中流動性のストリームはゼロから遡って埋めていく一方、もう一方は先端にとどまる からです。各ストリームは互いにではなく spoolHeadBlock と比べてください。
エラー
| ステータス | 意味 |
|---|---|
| 400 | bad parameterany address or number the handler could not parse. The message is always the same string. |
| 404 | unknown pool, or no such routethere is no route table - an unmatched path falls through to this. |
| 503 | index 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. |
三つしかなく、本文にエラーコードはなく、メッセージの文字列は説明的ではなく固定です。 ステータスで分岐してください。とくに 400 は、どのパラメータが拒否されたかについて 何も述べません。