Перейти к содержанию

Статические кошельки

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

Он подходит для пополнения счёта и регулярных депозитов. А когда нужна конкретная сумма за конкретный заказ — берите инвойс.

Выпуск адреса

curl -X POST https://api.pay.nullswap.com/api/v1/static-wallets \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "chainType": "EVM", "reference": "customer-42" }'
Поле Тип Обяз. Примечания
chainType перечисление да EVM, TRON, SOLANA, BITCOIN, LITECOIN, DASH, DOGECOIN, RIPPLE, TON, MONERO
reference строка да Ваше собственное имя для клиента. Максимум 128 символов — считаются после обрезки пробелов по краям
{
  "id": "f6a7b8c9-d0e1-4234-a567-8b9c0d1e2f34",
  "merchantId": "9f1d2c3b-4a5e-4f60-8b71-2c3d4e5f6a7b",
  "chainType": "EVM",
  "address": "0x1234567890abcdef1234567890abcdef12345678",
  "destinationTag": null,
  "xAddress": null,
  "reference": "customer-42",
  "createdAt": "2026-01-01T12:00:00.000Z"
}

Два свойства этого эндпоинта:

  • Он идемпотентен по паре chainType и reference. Повторный запрос с той же парой вернёт уже выданный ей адрес, а не выпустит второй; тот же референс с другим chainType получит собственный адрес этого семейства. Перед сравнением и сохранением референс обрезается по краям. Отвечает вызов всегда 201 — независимо от того, создал что-нибудь или нет, — так что его можно вызывать при каждом визите клиента и не хранить адрес у себя.
  • Адрес выпускается на семейство сетей, а не на отдельную сеть. Один адрес EVM обслуживает и Ethereum, и Arbitrum, и BNB Chain, и любую другую EVM-сеть. Клиенту нужен один адрес на семейство, а не по адресу на каждую сеть, — поэтому маршрут и принимает chainType, а не chainId.

destinationTag и xAddress равны null во всех семействах, кроме RIPPLE; см. ниже.

Статус message Причина
400 INVALID_REQUEST_BODY Поля нет, reference пуст либо chainType не входит в перечисление
403 MERCHANT_BLOCKED Мерчант заблокирован
422 INVALID_STATIC_WALLET_REFERENCE reference состоит из одних пробелов или после обрезки длиннее 128 символов

Чтение адресов

Метод Путь Назначение
GET /api/v1/static-wallets Список с фильтрами по id, chainType, reference, address
GET /api/v1/static-wallets/{staticWalletId} Прочитать один

Обычно выпущенный ранее адрес ищут именно по reference:

GET /api/v1/static-wallets?reference=customer-42&limit=10
Статус message Причина
400 INVALID_PATH_PARAMETER staticWalletId — не UUID
403 PERMISSION_DENIED Адрес принадлежит другому мерчанту
404 STATIC_WALLET_NOT_FOUND Такого адреса нет

На RIPPLE кошелёк — это тег

На RIPPLE, то есть в XRP Ledger, у всех статических кошельков один и тот же address. Различает ваших клиентов тег назначения (destination tag): у каждого кошелька свой destinationTag, и платёж зачисляется тому кошельку, чей тег он несёт.

{
  "id": "f6a7b8c9-d0e1-4234-a567-8b9c0d1e2f34",
  "merchantId": "9f1d2c3b-4a5e-4f60-8b71-2c3d4e5f6a7b",
  "chainType": "RIPPLE",
  "address": "rHsMGQEkVNJmpGWs8XUBoTBiAAbwxZN5v3",
  "destinationTag": 959725,
  "xAddress": "XVMFQQBMhdouRqhPMuawgBMN1AVFToG5NjwwoxoAYuiUyDF",
  "reference": "customer-42",
  "createdAt": "2026-01-01T12:00:00.000Z"
}
Поле Примечания
destinationTag Целое число, 0–4294967295. Тег, с которым платёж на address должен прийти, чтобы попасть на этот кошелёк. Теги не последовательны: показывайте полученный
xAddress address и destinationTag одним X-адресом (X…). Платёж на него — то же, что платёж на пару; его же стоит закладывать в QR-код

Отдавайте клиенту адрес и тег вместе, с пометкой, что тег обязателен, либо один X-адрес. Фильтр address в GET /api/v1/static-wallets совпадает и с xAddress; голый address кошелька RIPPLE совпадает со всеми вашими статическими кошельками RIPPLE.

Платёж без нужного тега не попадёт ни на один ваш кошелёк

Платёж на address без тега назначения сеть отклоняет. Платёж с тегом, который не принадлежит ни одному кошельку, удерживается и автоматически ни одному кошельку не зачисляется — обратитесь в поддержку с хешем транзакции. Клиент, сохранивший только адрес, сохранил половину.

Платежи

curl "https://api.pay.nullswap.com/api/v1/static-wallets/payments?settlement=PENDING&limit=100" \
  -H "x-api-key: sk_live_..."

Без staticWalletId эндпоинт отдаёт поступления сразу по всем вашим адресам — именно это и нужно для сверки. Фильтры: id, staticWalletId, status, settlement плюс обычная пагинация. Каждая строка data — один платёж:

{
  "id": "a7b8c9d0-e1f2-4345-a678-9b0c1d2e3f45",
  "staticWalletId": "f6a7b8c9-d0e1-4234-a567-8b9c0d1e2f34",
  "chainTransactionId": "cc33dd44-ee55-4f66-8077-889900112233",
  "chainTransferId": "dd44ee55-ff66-4077-8188-990011223344",
  "chainId": "1c9a7f36-5f2b-4a83-9d51-0e6b7c8d9a10",
  "tokenId": "3b7e1a48-2c6d-4e9f-8a01-5d4c3b2a1908",
  "fromAddress": "0xfedcba0987654321fedcba0987654321fedcba09",
  "amount": "1500000",
  "status": "CONFIRMED",
  "confirmations": 12,
  "amlStatus": "PASSED",
  "amlResultId": "5c6d7e8f-9a0b-41c2-8d34-5e6f7a8b9c0d",
  "settlement": "SWEEPING",
  "sweepTransferId": "ee55ff66-0077-4188-8299-001122334455",
  "sweepId": "0c1d2e3f-4a5b-4c6d-8e7f-8091a2b3c4d5",
  "createdAt": "2026-01-01T12:00:00.000Z",
  "updatedAt": "2026-01-01T12:03:00.000Z"
}

Платежи на статические кошельки не отправляют вебхуки

Ни одно событие из каталога вебхуков платежей на статические кошельки не касается. Сверять их можно только опросом этого эндпоинта. Хватит фоновой задачи, которая раз в минуту-другую запрашивает settlement=PENDING за нужное окно с сортировкой по createdAt. Единственные события, которые может вызвать статический кошелёк, — conversion.completed и conversion.skipped, когда на его свип распространяется правило автоконвертации: они сообщают о свипе, а не о платежах в нём.

Нативная монета, которую мы отправляем на адрес, чтобы оплатить сетевую комиссию за перемещение ваших денег дальше, платежом не считается: здесь её нет, и на ваш баланс она не свипается.

Смотрите на settlement, а не на status

Статический адрес собирает средства и передаёт их дальше, на ваш баланс, поэтому важно не то, подтвердилась ли транзакция, а то, где в итоге оказались деньги.

Каждый платёж, который подтвердился и прошёл скрининг, свипается на ваш баланс автоматически. Переключателя autoSweep у статических кошельков нет. Свип, который не удалось запустить, повторяется, пока его не примут, а провалившийся поднимается заново ограниченное число раз.

flowchart TD
    A["Приходит депозит"] --> B["status DETECTED<br/>settlement PENDING"]
    B --> C["Подтверждён до нужной глубины"]
    C --> D{"AML-скрининг"}
    D -- "PASSED / SKIPPED" --> E{"Стоит больше<br/>сетевой комиссии?"}
    D -- "FAILED" --> F["settlement WITHHELD<br/>деньги остаются на адресе"]
    E -- "да" --> G["settlement SWEEPING<br/>sweepTransferId установлен"]
    E -- "нет, пыль" --> H["settlement PENDING<br/>ждёт следующего платежа"]
    H -. "следующий платёж в том же токене" .-> E
    G --> I["settlement SWEPT<br/>зачислено на ваш баланс"]
settlement Значение
PENDING Скрининг ещё не прошёл либо прошёл чисто, но свипа ещё не было — пыль или свип, который ещё повторяется. Деньги никуда не двигались.
SWEEPING Чисто, свип на ваш баланс принят. sweepTransferId заполнен
SWEPT Свип завершён, деньги на вашем балансе за вычетом комиссии мерчанта и сетевой комиссии за их перемещение — в другом токене, если их сконвертировало правило автоконвертации
WITHHELD Помечено скринингом, деньги остаются на статическом адресе
status Значение
DETECTED Видно в сети, но ещё не подтверждено и не проверено скринингом. Это пока не ваши деньги
CONFIRMED Подтверждено и проверено скринингом
FAILED Транзакция откатилась или выпала при реорганизации цепочки

PENDING неоднозначен намеренно: под ним и «мы это ещё не проверяли», и «оно чистое, но ещё не перемещено». Различить два случая помогает amlStatus: null означает, что скрининг не запускался.

Чистый платёж, который стоит не больше сетевой комиссии за его перемещение, — это пыль. Он остаётся на адресе в PENDING и свипается вместе со следующим платежом в том же токене. Один свип забирает всю чистую сумму токена на адресе, поэтому у нескольких платежей могут быть общие sweepId и sweepTransferId. sweepId заполняется, когда для платежа поднят свип, sweepTransferId — когда этот свип принят. Когда свип зачислен, удержанная им комиссия — строка с этим id в списке удержанных комиссий: по свипу, а не по платежу.

Разблокировать платёж в статусе WITHHELD через API нельзя — такого маршрута нет. Если считаете вердикт ошибочным, укажите его amlResultId, когда будете обращаться с этим вопросом.

Когда платёж дошёл до SWEPT, деньги появляются в балансах — оттуда вы их и выводите. Статический адрес можно свипнуть и самому через POST /api/v1/wallets/{walletId}/sweep, передав id статического кошелька как walletId, — например, чтобы повторить сразу, а не ждать. Если автоматический свип уже идёт, вызов вернёт его, а не запустит второй.