← LiquidityWise

Карточка пула и её JSON

Основная сеть Ethereum

Две вещи на этом сайте сделаны для других сайтов: карточка с предложенным диапазоном одного пула, которую любая страница может поместить во фрейм, и те же цифры в JSON, которые может читать код любого сайта. Ни для одной не нужны ни ключ, ни учётная запись. Обе описаны здесь так, как их отдаёт код, и тесты привязывают эту страницу к этому коду: каждый параметр в ней — тот, по которому действительно читается адрес, а каждое поле — то, с чем ответ сверяется перед отправкой.

Карточка

/embed/pool — небольшая страница, написанная вручную, а не фреймворком сайта. На ней пара с протоколом, комиссией и сетью; предложенный диапазон с горизонтом и шириной, для которых он построен; текущая цена; строка о том, что это не финансовый совет; и ссылка обратно на страницу пула здесь, которая открывается в новой вкладке. Диапазон всегда строится по умолчаниям сайта — 30 дн. при 1σ, — какими бы ни были чьи-то собственные настройки. Пока текущая цена вне диапазона, добавляется примечание, и ещё одно — для пула v4, чей hook может изменить цену свопа.

В ней нет ни скрипта, ни формы, и она ничего не загружает: её собственная Content-Security-Policy разрешает только её встроенный стиль. Цвета у неё — цвета сайта, светлые или тёмные, как настроена система читателя. Страницу вокруг себя она не видит, и параметра, чтобы это выбрать, нет.

Дайте ей фрейм шириной 360 пикселей и высотой 220, или высотой 260 для пула, чей hook может изменить цену свопа: такая карточка несёт на одну строку больше. Ровно это предлагает страница каждого пула в разделе «Встроить этот пул», уже с нужной для этого пула высотой, и предложенный фрейм никогда не становится шире колонки, в которую его вставили.

Это единственная страница сайта, которую другой сайт может поместить во фрейм. Любой другой адрес, включая этот, отдаётся с X-Frame-Options: DENY и frame-ancestors 'none'; карточка — без первого и с frame-ancestors *.

Фрейм для пула USDC / WETH 0,3% в Ethereum — таким его предлагает страница этого пула:
<iframe src="https://liquiditywise.com/embed/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8" title="USDC / WETH on LiquidityWise" width="360" height="220" style="border:0;max-width:100%" loading="lazy"></iframe>
С чем отдаётся карточка с цифрами, помимо заголовков, которые есть у каждой страницы:
Content-Type: text/html; charset=utf-8
Cache-Control: public, max-age=300, s-maxage=300
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors *

Параметры

Карточка и JSON принимают одни и те же параметры и называют пул так же, как собственные страницы сайта: пул v3 — по его address, как на /pool, пул v4 — по его id, как на /v4. Нужен ровно один из двух, и то, какой пришёл, говорит, какой это протокол. Пул или сеть, названные дважды, отклоняются, а не выбирается одно из значений, и любой параметр, которого здесь нет, игнорируется.

Язык берётся только из адреса, никогда из браузера читателя или cookie: его выбирает сайт, который размещает карточку, и один и тот же адрес — один и тот же ответ для всех, кто его загружает; именно это позволяет кэшу его хранить. Карточка написана на всех 10 языках сайта, а на арабском читается справа налево.

address
Принимает Адрес контракта пула v3: 0x и 40 шестнадцатеричных цифр, в верхнем или нижнем регистре.
Иначе Всё остальное не называет пул, как и пул v3 в сети, где v3 не читается (Unichain): 400, и ничего не читается.
id
Принимает Id пула v4, хеш его ключа: 0x и 64 шестнадцатеричные цифры, в верхнем или нижнем регистре.
Иначе Всё остальное не называет пул: 400, и ничего не читается. Как и id рядом с address.
chain
Принимает Короткое имя сети из таблицы ниже. Если его нет, сеть — Ethereum.
Иначе Имя, которое сайт не читает, отклоняется и никогда не читается как Ethereum: 400.
lang
Принимает Код языка из списка ниже — для слов карточки и disclaimer в JSON; цифры на всех языках одни и те же. Если его нет — английский.
Иначе Любое другое значение или lang, указанный дважды, дают английский. Он никогда не отклоняется.

Сети — по короткому имени, которое принимает chain:

chainСетьпулы v3пулы v4
ethereumEthereumчитаютсячитаются
baseBaseчитаютсячитаются
arbitrumArbitrum Oneчитаютсячитаются
unichainUnichainне читаютсячитаются
optimismOP Mainnetчитаютсячитаются
polygonPolygonчитаютсячитаются

Языки — по коду, который принимает lang:

  • enEnglish
  • trTürkçe
  • deDeutsch
  • esEspañol
  • arالعربية
  • hiहिन्दी
  • zh中文
  • ruРусский
  • ptPortuguês
  • zh-Hant繁體中文

JSON

GET /api/embed/pool с теми же параметрами отвечает цифрами карточки в JSON — для сайта, который предпочитает рисовать их сам: то же чтение, хранимое столько же. Каждая цифра — число JSON, и каждая цена записана так, как её пишет страница пула: сколько price.quote стоит один price.base.

Перед отправкой ответ сверяется со своей схемой, и ответ, который ей не соответствует, не отправляется: вместо этого пул отвечается как непрочитанный. Поэтому в ответе со статусом 200 всегда ровно поля ниже — не больше и не меньше.

Его может читать скрипт любого сайта. Каждый ответ, включая ошибки и отказы, отдаётся с Access-Control-Allow-Origin: *. Он не ставит cookie и не требует учётных данных. Простому GET не нужен preflight, и на preflight никто не отвечает, так что отправляйте запрос без собственных заголовков.

С чем отдаётся ответ с цифрами, помимо заголовков, которые есть у каждой страницы:
Content-Type: application/json; charset=utf-8
Cache-Control: public, max-age=300, s-maxage=300
Access-Control-Allow-Origin: *

Каждое поле ответа 200, с его типом в JSON:

protocol"v3" | "v4"
v3 для пула, названного по address, v4 — для названного по id.
chain.idinteger > 0
Chain id сети.
chain.slugstring
Короткое имя сети — в том виде, в каком его принимает chain.
chain.namestring
Название сети.
poolstring
Адрес (v3) или id (v4) пула, в нижнем регистре.
pair.token0string
Символ первого токена пула — так, как его указывает контракт: это текст из контракта, который может развернуть кто угодно, поэтому экранируйте его, прежде чем вставлять в страницу.
pair.token1string
Символ второго токена, так же.
lpFeePpminteger ≥ 0 | null
Комиссия, которую пул платит поставщикам ликвидности за своп, в миллионных долях: 3000 — это 0,3%. null, когда hook пула v4 задаёт комиссию для каждого свопа отдельно.
price.basestring
Токен, за одну единицу которого дана каждая цена.
price.quotestring
Токен, в котором считается каждая цена.
price.currentnumber > 0
Цена пула в момент чтения.
range.lowernumber > 0
Нижняя граница предложенного диапазона, как цена.
range.uppernumber > 0
Его верхняя граница.
range.currentInRangeboolean
Находится ли текущая цена внутри диапазона. Если нет, карточка добавляет примечание.
range.lowerTruncatedboolean
true, когда сетка tick’ов пула не дотягивается до нижней границы полосы, и диапазон кончается раньше неё.
range.upperTruncatedboolean
То же — для верхней границы.
parameters.horizonDaysinteger > 0
Горизонт, для которого построен диапазон, в днях: всегда значение по умолчанию, 30.
parameters.standardDeviationMultipliernumber > 0
Ширина, для которой он построен, в стандартных отклонениях: всегда значение по умолчанию, 1σ.
hookMayAlterSwapsboolean
true для пула v4, чей hook может изменить цену свопа, — это читается из адреса hook’а; false для v3, для пула v4 без hook’а и для hook’а, чьи разрешения не затрагивают свопы. Где true, карточка несёт примечание.
analysedAtstring (ISO 8601)
Когда была прочитана цена, по UTC, с точностью до миллисекунды, — а не когда отправлен этот ответ: это может быть на несколько минут позже.
poolUrlstring
Страница пула на этом сайте, с тем же горизонтом и той же шириной.
disclaimerstring
Что такое эти цифры и чем они не являются, на языке, указанном в lang.
Ответ для пула из примеров. Цифры в нём — пример: их посчитал собственный код сайта по образцовому месяцу цен, а не прочитал из пула, и тест проверяет, что код по-прежнему отвечает ровно этим для этого месяца:
{
  "protocol": "v3",
  "chain": {
    "id": 1,
    "slug": "ethereum",
    "name": "Ethereum"
  },
  "pool": "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8",
  "pair": {
    "token0": "USDC",
    "token1": "WETH"
  },
  "lpFeePpm": 3000,
  "price": {
    "base": "WETH",
    "quote": "USDC",
    "current": 3000
  },
  "range": {
    "lower": 2824.2704157479307,
    "upper": 3184.336896959513,
    "currentInRange": true,
    "lowerTruncated": false,
    "upperTruncated": false
  },
  "parameters": {
    "horizonDays": 30,
    "standardDeviationMultiplier": 1
  },
  "hookMayAlterSwaps": false,
  "analysedAt": "2026-08-21T09:15:00.000Z",
  "poolUrl": "https://liquiditywise.com/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8&days=30&sigma=1",
  "disclaimer": "For information only, not financial advice. The range is worked out from how far this pool's price moved over the last 30 days: it is not a forecast and not a recommendation."
}

Коды ответа и ошибки

Карточка и JSON отвечают одними и теми же кодами. Ни та, ни другой никогда не отвечают собственной страницей ошибки сайта или тем, что сломалось внутри: карточка показывает одно предложение и свою ссылку обратно, а JSON отвечает полем error, которое скрипт может проверить, и disclaimer — кроме отказа, в котором вместо него время ожидания.

Ответ JSON и то, сколько его можно хранить:

200

Цифры: карточка с ними или JSON выше.

Cache-Control: public, max-age=300, s-maxage=300

400 not-a-pool

Адрес не называет пул, который читает сайт: нет ни address, ни id, или есть оба, или один из них искажён либо повторён, или это сеть, которую сайт не читает, или пул v3 там, где читается только v4 (Unichain). Ничего не прочитано, и повторный запрос ответа не изменит.

Cache-Control: public, max-age=3600, s-maxage=3600

{
  "error": "not-a-pool",
  "disclaimer": "For information only, not financial advice. The range is worked out from how far this pool's price moved over the last 30 days: it is not a forecast and not a recommendation."
}

503 unreadable

Правильно названный пул, который сейчас не удалось прочитать: источник не ответил вовремя, или такого пула в этой сети нет. poolUrl всё равно ведёт на его страницу. Повторный запрос через минуту может его найти.

Cache-Control: public, max-age=60, s-maxage=60

{
  "error": "unreadable",
  "poolUrl": "https://liquiditywise.com/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8&days=30&sigma=1",
  "disclaimer": "For information only, not financial advice. The range is worked out from how far this pool's price moved over the last 30 days: it is not a forecast and not a recommendation."
}

429 rate-limited

Слишком много запросов от одного клиента: см. лимит ниже. Retry-After и retryAfterSeconds оба говорят, сколько секунд ждать. Карточка отвечает короткой страницей, где это сказано, на своём языке.

Cache-Control: no-store
Retry-After: 42

{
  "error": "rate-limited",
  "retryAfterSeconds": 42
}

Кэширование и лимит запросов

Каждый ответ говорит, сколько его можно хранить — и браузеру, и общему кэшу: цифры — 300 с; пул, который не удалось прочитать, — 60 с, чтобы вернувшийся пул недолго показывался непрочитанным; адрес, не называющий пул, — 3 600 с: этого не изменит никакой следующий момент. Отказ не хранится никогда. Каждый ответ одинаков для всех, кто спрашивает по тому же адресу, — это и делает хранение безопасным.

За этим сервер хранит цифры каждого пула 300 с с момента, когда их прочитал, — одинаково для карточки и JSON и на всех языках: каждая карточка одного пула, на какой бы странице она ни стояла, — одно чтение, пока срок не истечёт. Пул, который не удалось прочитать, не хранится и запрашивается снова при следующем запросе. Поэтому цена в ответе может быть старше самого ответа: analysedAt говорит, когда она прочитана.

Запрос, называющий правильно записанный пул, читает этот пул и поэтому считается в тот же лимит, что и собственные страницы пулов на сайте: 10 запросов на клиента в окне 60 с, которое начинается с первого запроса клиента; клиент — это IP-адрес, с которого запрос приходит на сайт. Сверх лимита ответ — 429 с Retry-After. Считается каждый такой запрос, дошедший до сайта, включая отвеченный из хранилища сервера; запрос, не называющий пул, не считается.

Из браузера карточка или fetch считаются против читателя, который их загружает, а не против сайта, где они стоят, — поэтому на странице с более чем 10 карточками лишние будут отклонены для каждого читателя. С сервера все ваши запросы делят один лимит этого сервера: храните каждый ответ те 300 с, что он разрешает, — это почти ничего не стоит, ведь большую часть этого времени сервер и так ответил бы тем чтением, что у вас уже есть. Лимит нужен, чтобы чтение пулов оставалось дешёвым, и ничто здесь не обещает, что он останется прежним.

Примеры

Цифры из терминала:
curl -s "https://liquiditywise.com/api/embed/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8"
Из скрипта страницы или с сервера, с обработкой каждого ответа:
const response = await fetch(
  "https://liquiditywise.com/api/embed/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8",
);
const body = await response.json();

if (response.ok) {
  // The range, in body.price.quote per one body.price.base
  console.log(body.range.lower, body.range.upper, body.price.quote, body.price.base);
  console.log(body.disclaimer);
} else if (response.status === 429) {
  console.log(`Asked too often: wait ${body.retryAfterSeconds} seconds`);
} else {
  console.log(body.error); // "not-a-pool" or "unreadable"
}
Карточка на странице:
<iframe src="https://liquiditywise.com/embed/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8" title="USDC / WETH on LiquidityWise" width="360" height="220" style="border:0;max-width:100%" loading="lazy"></iframe>

Один щелчок выделяет весь блок, готовый к копированию.

Условия, простыми словами

Цифры — это измерения, а не совет. Диапазон рассчитан по тому, насколько цена пула двигалась за последние 30 дн.: это не прогноз и не рекомендация, и каждый ответ говорит это сам — карточка на своей лицевой стороне, JSON в disclaimer. Как получается каждая цифра и что она оставляет за кадром, рассказано на странице «Как это работает».

Карточка несёт собственную ссылку обратно — «Анализ LiquidityWise». В JSON нет поля для указания авторства, и ничто в коде его не требует; в нём есть poolUrl, страница пула здесь, и disclaimer. Показанные рядом с цифрами, эти два поля говорят читателю, откуда цифры и что они такое.

Нет ни номера версии, ни ключа, ни обещания, что форма ответа останется прежней или что сайт будет доступен в любой момент. Действует более узкое: ответ сверяется со своей схемой перед отправкой, а тесты привязывают эту страницу к этой схеме и к коду, который читает адрес, так что она описывает то, что отдаётся сейчас. Изменение любого из них видно в публичной истории кода.

Код открыт, под лицензией MIT.

Как это работаетКод на GitHubЛицензия MIT