Сети и токены¶
У каждого инвойса, платежа, баланса и вывода есть chainId и tokenId. И то и другое — UUID
NullSwap, а не идентификаторы самой сети: маршрута, который принял бы вместо них "ethereum", 1
или адрес контракта, в API нет. Эти два эндпоинта и нужны, чтобы перевести то, что вы знаете, в
идентификаторы, которые от вас требуются.
Сети¶
{
"data": [
{
"id": "1c9a7f36-5f2b-4a83-9d51-0e6b7c8d9a10",
"name": "Ethereum",
"networkChainId": 1,
"type": "EVM",
"logoURI": "https://cdn.pay.nullswap.com/chains/ethereum.svg",
"priority": 10,
"confirmationBlocks": 12,
"latestBlockNumber": 21345678,
"explorerTxUrlTemplate": "https://etherscan.io/tx/{value}",
"explorerAddressUrlTemplate": "https://etherscan.io/address/{value}"
}
]
}
Фильтры: id, type, name. Все повторяются и сравниваются точно; name — без учёта регистра, но
это не поиск по подстроке: name=Ethereum сработает, а name=eth не вернёт ничего.
| Поле | Примечания |
|---|---|
networkChainId |
Идентификатор EIP-155. null в не-EVM сетях, где его попросту нет |
type |
Семейство сетей: EVM, TRON, SOLANA, BITCOIN, LITECOIN, TON, MONERO |
priority |
Подсказка для порядка отображения. Меньшее значение идёт первым |
confirmationBlocks |
Глубина, которую должен набрать платёж, чтобы стать CONFIRMED |
latestBlockNumber |
Вершина цепочки по последним данным нашего индексатора |
explorerTxUrlTemplate |
Подставьте хеш транзакции вместо {value}. Пустая строка, если у сети нет обозревателя |
explorerAddressUrlTemplate |
То же самое, только с адресом |
Ответ — { "data": [...] } без объекта pagination: список короткий и отдаётся целиком,
offset и limit здесь не принимаются.
latestBlockNumber — единственное поле, которое меняется от минуты к минуте; остальное достаточно
стабильно, чтобы кешировать на сутки. В паре с blockNumber платежа оно показывает, на какой глубине
платёж находится прямо сейчас:
Считать глубину приходится самому: confirmations у платежа — снимок на момент последней записи, а
не живой счётчик.
Токены¶
curl "https://api.pay.nullswap.com/api/v1/tokens?symbol=USDT&status=ENABLED" \
-H "x-api-key: sk_live_..."
{
"data": [
{
"id": "3b7e1a48-2c6d-4e9f-8a01-5d4c3b2a1908",
"chain": {
"id": "1c9a7f36-5f2b-4a83-9d51-0e6b7c8d9a10",
"name": "Ethereum",
"networkChainId": 1,
"type": "EVM",
"logoURI": "https://cdn.pay.nullswap.com/chains/ethereum.svg",
"priority": 10,
"confirmationBlocks": 12,
"latestBlockNumber": 21345678,
"explorerTxUrlTemplate": "https://etherscan.io/tx/{value}",
"explorerAddressUrlTemplate": "https://etherscan.io/address/{value}"
},
"currency": {
"id": "7d8e9f0a-1b2c-4d3e-8f40-5a6b7c8d9e0f",
"name": "Tether USD",
"symbol": "USDT",
"logoURI": "https://cdn.pay.nullswap.com/tokens/usdt.svg",
"decimals": 6,
"priority": 20
},
"address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"type": "ERC20",
"category": "HEDGE",
"maxAmountOut": "500000000000",
"decimals": 6,
"price": "1000200000000000000",
"status": "ENABLED",
"monitoringCode": "usdt_eth"
}
]
}
В каждый токен вложена сеть целиком, так что одного вызова хватит, чтобы отрисовать селектор, — склеивать ничего не придётся. Пагинации здесь тоже нет.
| Фильтр | Как сравнивается |
|---|---|
query |
Подстрока без учёта регистра — по имени, символу и адресу контракта |
id, chainId, currencyId, address, symbol |
Точно, без учёта регистра, можно повторять |
status |
ENABLED, DISABLED |
category |
NATIVE, HEDGE, ASSET |
Разные фильтры объединяются через И, повторы одного фильтра — через ИЛИ. То есть
?chainId=A&chainId=B&status=ENABLED читается как (сеть A или сеть B) и включён.
Валюта и токен¶
Валюта — это актив таким, каким его называет человек: «Tether USD», USDT, 6 знаков после
запятой. Токен — та же валюта, развёрнутая в одной конкретной сети по одному адресу. USDT в
Ethereum и USDT в Tron — это два токена с общим currency.id.
Инвойсы, балансы и выводы всегда указывают на токен. currency.id пригодится разве что для
группировки в вашем собственном интерфейсе.
decimals встречается в двух местах, и они не взаимозаменяемы
По token.decimals вы приводите к человеческому виду amount, expectedAmount, paidAmount и
любые суммы в балансах и выводах. token.currency.decimals описывает валюту абстрактно. Почти у
всех токенов из списка они совпадают, но переданная вам сумма масштабирована именно по
token.decimals — его и читайте.
Остальные поля¶
| Поле | Примечания |
|---|---|
address |
Адрес контракта. У токена NATIVE контракта нет, поэтому поле пустое |
type |
NATIVE (собственная монета сети), ERC20 (контракты EVM и TRON), SPL (Solana) |
category |
NATIVE, HEDGE (стейблкоин), ASSET (всё остальное) |
maxAmountOut |
Наибольшая сумма в базовых единицах, которую мы выплатим в этом токене за раз |
price |
Доллары США за целый токен, умноженные на 10^18 |
status |
На ENABLED можно создавать новые инвойсы, на DISABLED — нет |
monitoringCode |
Внутренняя метка, не обращайте внимания |
price — справочный курс, чтобы что-то показать или пересчитать. Инвойс по нему не рассчитывается:
сумму NullSwap фиксирует в момент создания инвойса.
Список без фильтров включает и токены со статусом DISABLED
Так задумано. Инвойс, созданный месяц назад на токен, который с тех пор делистнули, всё ещё на
него ссылается — и чтобы такой инвойс отрисовать, вам по-прежнему нужны его символ и
decimals. Ставьте status=ENABLED, когда строите селектор, и убирайте фильтр, когда
расшифровываете уже имеющийся у вас идентификатор.
Создать инвойс на токен со статусом DISABLED не выйдет — но откажут вам с
404 TokenWithIdNotFoundError, а не отдельной ошибкой «токен отключён».
Курсы¶
curl "https://api.pay.nullswap.com/api/v1/tokens/rate?srcTokenId=3b7e1a48-...&dstTokenId=8c1f2e3d-..." \
-H "x-api-key: sk_live_..."
{
"srcToken": { "id": "3b7e1a48-2c6d-4e9f-8a01-5d4c3b2a1908", "…": "полный объект токена" },
"dstToken": { "id": "8c1f2e3d-4a5b-4c6d-8e70-9f0a1b2c3d4e", "…": "полный объект токена" },
"rate": "999400000000000000",
"minAmountIn": "10000000",
"maxAmountIn": "1000000000000",
"maxAmountOut": "999000000000"
}
Оба query-параметра обязательны. rate — сколько единиц целевого токена даёт одна единица исходного,
умноженное на 10^18. Комиссия платформы уже вычтена: вычитать что-либо самому не нужно.
— с поправкой на разницу в decimals, если она у двух токенов есть.
minAmountIn и maxAmountIn ограничивают то, что маршрут примет, а maxAmountOut — то, что он
выдаст. Так что пара может не пройти по ограничению на выход, даже если вход укладывается в свой
диапазон.
Курс — это котировка, а не бронь
Ничего за вами не резервируется. Между этим вызовом и созданием инвойса курс успеет измениться,
поэтому показывайте по нему цифру, а настоящим числом считайте expectedAmount в уже созданном
инвойсе.
Этот эндпоинт — единственное место в API, где 404 отвечает в другом формате:
Текстовый message, лишний ключ error, timestamp отсутствует. Означает это одно: маршрута между
двумя токенами нет — обычно пара не поддерживается, изредка временно не хватает ликвидности. В
остальном API message — всегда имя класса ошибки; см. Ошибки.
Кеширование¶
| Эндпоинт | Кешировать на |
|---|---|
/api/v1/chains |
Часы. Меняется тут только latestBlockNumber |
/api/v1/tokens |
Часы для списка; минуты, если показываете price |
/api/v1/tokens/rate |
Секунды — если кешировать вообще |
Загружайте оба списка при старте и обновляйте по таймеру, а не дёргайте их на каждой оплате.
Бессрочно не стоит кешировать только один случай — токен, идентификатор которого вы видите впервые:
если на инвойсе, который должен был пройти, вернулся TokenWithIdNotFoundError, сперва обновите
список, а потом делайте выводы.