개발자를 위한 문서 / 애플리케이션 API

Pepper DEX API

Pepper의 로그 위에 놓인 읽기 전용 인덱스이며, 두 테이블 묶음에서 두 세대의 풀을 제공합니다. 십진 문자열로 답하고, 말없이 모든 목록을 오백 행에서 자르며, CORS 헤더는 전혀 보내지 않습니다.

두 개의 DEX, 하나의 API

Pepper에는 상수곱 페어와 집중 유동성 풀이 나란히 있습니다. 이 서비스는 둘 다 두 테이블 묶음에서 두 개의 접두사 아래로 제공합니다. 벌거벗은 경로가 상수곱 쪽이고 /cl/ 아래의 모든 것이 집중 유동성입니다. 둘은 서로의 변형이 아닙니다.

두 계열은 서로 바꿔 쓸 수 없습니다

상수곱 페어에는 두 개의 준비금과 하나의 가격이 있습니다. 집중 유동성 풀에는 둘 다 없습니다. 가격 하나, 틱 하나, 그 틱에서 활성인 유동성, 그리고 전혀 활성이 아닌 무한한 구간 집합이 있을 뿐입니다. activeLiquidity를 깊이로, 또는 잔액을 거래 가능한 크기로 읽는 것은 돈이 드는 방식으로 틀립니다 - 현재 가격에서의 깊이는 잔액의 천분의 일일 수 있고, 나머지는 다음 거래가 결코 건드리지 않는 구간에 앉아 있습니다.

풀 주소는 한 계열에 속합니다. 집중 유동성 풀을 /pool/{address}로 묻는 것은 리다이렉트가 아니라 404입니다.

토큰을 출시했나요? 로고와 인증 마크를 달아 주십시오

Pepper에서 출시한 토큰은 공개 토큰 목록에 오르기 전까지 인증 마크가 없습니다. Pickle 앱들이 목록에 오른 토큰의 로고, 이름, 심볼을 가져오는 곳도 바로 그 목록입니다. 거기에 오르는 방법은 로고와 항목을 담은 pull request 하나입니다 - 토큰 목록과 주소 태그에 절차와 기준이 있습니다.

베이스 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입니다.

상수곱 라우트

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.

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.

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.

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.

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.

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.

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.

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'

집중 유동성 라우트

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.

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.

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.

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.

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인 가격이 9가 길게 이어진 문자열로 돌아오는 것이 그렇게 일어납니다.

가치 수치가 뜻하는 것

valueEthWei는 TVL이 아니며 그렇게 이름 붙여서는 안 됩니다

그것은 풀의 WETH 쪽을 두 배 한 값을, WETH 쪽이 있는 풀들에 걸쳐 더한 것입니다. 그 두 배 하기는 상수곱 풀을 한쪽에서 평가하는 표준 방법이며, 그쪽이 체인 자신의 가스 자산이기 때문에만 뜻이 있습니다. WETH 쪽이 없는 풀은 전혀 기여하지 않고 poolsWithoutEth로 따로 세어집니다 - 그러니 그 수치는 총계가 아니라 부분집합에 대한 하한입니다.

집중 유동성 풀은 결코 거기 접혀 들어가지 않습니다. 그쪽의 두 변은 같은 값어치가 아니므로, 한쪽을 두 배 하는 것은 데이터가 뒷받침하지 않는 주장입니다. 가격이 모든 구간 위로 걸어 올라간 풀은 한 토큰만 보유하고 다른 토큰은 전혀 보유하지 않습니다. 그쪽의 수치는 concentrated 아래와, 두 배 하지 않고 정확히 그 이름대로인 wethBalanceWei 아래에 삽니다.

몇몇 라우트는 달러 수치와 가격 스냅샷도 실어 나릅니다. 그것들은 이 페이지가 소유하지 않는 레이어에서 오므로, 그 숫자가 무엇을 뜻하고 얼마나 오래되었는지는 그쪽의 레퍼런스를 읽으십시오. 그래도 여기서 함께 가져갈 두 규칙이 있습니다. 유도할 수 없을 때 그 수치는 0이 아니라 null입니다. 0은 측정값이기 때문입니다. 그리고 준비금에서 계산한 토큰별 가격은 발표되지 않으며 추론해서도 안 됩니다.

그 뒤의 인덱서

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보다 한참 뒤처져 있을 수 있는데, 그것은 고장이 아니라 정상입니다. 집중 유동성 스트림은 0부터 백필하는 반면 다른 쪽은 머리에 머물러 있기 때문입니다. 각 스트림을 서로가 아니라 spoolHeadBlock과 비교하십시오.

오류

상태의미
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.

셋뿐이고, 본문에 오류 코드가 없으며, 메시지 문자열은 설명적이지 않고 고정되어 있습니다. 상태로 분기하십시오. 특히 400은 어느 파라미터가 거부되었는지에 대해 아무것도 말하지 않습니다.