Пишем софт по раскидыванию баланса с биржи
Привет! В этой статье ты вместе с нами сможешь забабахать свой первый полезный софт. Перед прочтением, мы настоятельно рекомендуем прочитать нашу первую статью о написании скрипта, если ты раньше никогда не кодил.
Что будет делать написанный скрипт?
Софт будет раскидывать баланс с биржи OKX на кошельки, которые ты укажешь в файле. Выводить сможешь любой токен и его количество, указанное в настройках.
Для понимания материала потребуется базовое знание синтаксиса Python. Если что-то будет непонятно, не бойся обратиться к ChatGPT за помощью.
Где я могу посмотреть код скрипта?
Ты можешь посмотреть и скачать наш код с GitHub репозитория.
Оглавление
Получаем ключи для взаимодействия с OKX API
Создаем проект и структуру файлов
Что такое API
Для написания софта нам понадобится работать с OKX API. Так что сначала разберемся с тем, что такое API.
API (Application Programming Interface) — Если упростить, то это набор инструментов, которые позволяют различным программам взаимодействовать друг с другом. В нашем случае API OKX — это способ для нашего софта взаимодействовать с биржей для выполнения операций, таких как вывод средств, просмотр баланса и так далее.
Ты приходишь в ресторан, и чтобы заказать еду, ты говоришь официанту (API), что именно тебе нужно. Ты не общаешься напрямую с кухней (биржей), а через официанта, который передает заказ. Он также доставляет твою еду (ответ от биржи) обратно к твоему столику.
Получаем ключи для взаимодействия с OKX API
Чтобы пользоваться OKX API нам нужно создать ключи доступа. Делается легко, просто следуй гайду ниже.
1. Переходим сюда и нажимаем "Create API key".
2. Заполняем поля и в Permissions указываем все возможные разрешения, иначе наш скрипт не сможет выводить или торговать токенами. Также можете указать список разрешенных IP для взаимодействия с API. После того как все заполнили жмём на "Submit all".
3. После создания нас вернет обратно. Тут нужно нажать на кнопку "view" рядом с созданным нами API ключем.
4. Теперь необходимо сохранить API-ключ как OKX_API_KEY, Secret key как OKX_API_SECRET, а также Passphrase, который ты указывал при создании API, как OKX_API_PASSPHRASE. Эти данные будут использованы позже.
Создаем проект и структуру файлов
Приступаем к созданию нашего фундамента для софта. Создаем папку, например, okx_spreader и открываем её в VS Code или другом редакторе кода. Далее создаем следующую структуру файлов:
Давай разберем каждый попунктно:
- modules — директория, в которой хранятся модули для нашего скрипта, такие как
okx.pyиtransfer.py. Эти модули будут реализовывать основную функциональность скрипта. - modules/okx.py — модуль, отвечающий за взаимодействие с API биржи OKX.
- modules/transfer.py — модуль, который управляет выводом средств. Для каждого кошелька он инициирует вывод токенов используя
okx.pyмодуль. - main.py — основной файл для запуска нашего софта.
- recipients.txt — файл, содержащий список адресов, на которые будут переводиться токены.
- settings.py — файл для хранения различных настроек, необходимых для работы скрипта.
- utils.py — файл с вспомогательными функциями, которые могут быть использованы в разных модулях проекта.
Начинаем писать код
settings.py
Нам нужно создать отдельный конфигурационный файл, чтобы все параметры были в одном месте, и их можно было легко изменять без необходимости редактировать основной код. В этом файле мы определяем следующие параметры:
- TRANSFER_AMOUNT_RANGE — диапазон, в котором будет генерироваться сумма перевода (например, от 3 до 5). Это необходимо, чтобы сумма перевода была не фиксированной, а случайной в пределах заданного диапазона.
- TRANSFER_CURRENCY — тип валюты, который будем переводить, в нашем случае это USDT.
- TRANSFER_CHAIN — сеть, на которую будем инициировать перевод, например, Arbitrum One.
- OKX_API_KEY, OKX_API_SECRET, OKX_API_PASSPHRASE — это параметры для аутентификации при работе с API биржи OKX. Помнишь я тебя попросил сохранить данные под определенным неймингом? Самое время будет их заполнить.
Конфигурация хранится отдельно, чтобы избежать повторного написания одинаковых данных в разных местах кода.
# transfer settings TRANSFER_AMOUNT_RANGE = [3, 5] # диапазон суммы перевода с OKX на кошелек TRANSFER_CURRENCY = "USDT" # токен для перевода (как записано на OKX) TRANSFER_CHAIN = "Arbitrum One" # сеть куда перевести (как записано на OKX) # exchange settings OKX_API_KEY = "your_okx_api_key" OKX_API_SECRET = "your_okx_api_secret" OKX_API_PASSPHRASE = "your_okx_api_passphrase"
main.py
Основной файл, он будет работать с вводом пользователя и запускать нужный нам модуль.
Нам нужно спросить пользователя, хочет ли он начать процесс перевода. Если ответ положительный (пользователь вводит "YES" или "Y" в любом регистре), то запускается функция, которая отвечает за переводы. Если же пользователь отказывается или вводит что-то иное, программа завершится.
from modules.transfer import start_transfer
def main():
# Спрашиваем пользователя, хочет ли он начать раскидку баланса и сохраняем ответ в переменную choice
choice = input("Начать раскидку баланса? [Y/N]: ")
# Если пользователь ответил "YES" или "Y" (в любом регистре), то запускаем функцию start_transfer
if choice.upper() in ["YES", "Y"]:
start_transfer()
else:
print("Выход...")
exit()
if __name__ == "__main__":
main()1. Импорт функции start_transfer:
from modules.transfer import start_transfer
Эта строка импортирует функцию start_transfer из модуля transfer, расположенного в папке modules. Функция start_transfer будет использоваться для страта вывода токенов с OKX на кошельки, указанные в файле recipients.txt
def main():
Функция main — это точка входа в вашу программу. Она будет управлять основным процессом взаимодействия с пользователем.
3. Получение ввода от пользователя:
choice = input("Начать раскидку баланса? [Y/N]: ")Здесь мы просим пользователя ввести, хочет ли он начать процесс распределения баланса, и сохраняем его ответ в переменной choice.
4. Проверка ввода пользователя:
if choice.upper() in ["YES", "Y"]:
Мы проверяем, ввел ли пользователь "YES" или "Y", используя метод upper() для преобразования ввода в верхний регистр. Это делает проверку нечувствительной к регистру. Вместо нескольких отдельных условий, мы просто проверяем, содержится ли преобразованный ввод в списке ["YES", "Y"]. Например, если пользователь ввел "yes", метод upper() преобразует его в "YES", который присутствует в списке. Таким образом, условие выполняется, и мы попадаем внутрь блока if.
5. Вызов функции start_transfer:
Если пользователь ввел "YES" или "Y", мы вызываем функцию start_transfer, чтобы начать процесс распределения баланса.
start_transfer()
Если пользователь ввел что-то другое, не "YES" или "Y", то мы выводим сообщение "Выход..." и завершаем программу с помощью стандартной функции exit().
else:
print("Выход...")
exit()В конце добавляется проверка, чтобы убедиться, что этот код выполняется только при непосредственном запуске файла, а не при его импорте как модуля. То есть, если файл запускается командой python main.py, а не импортируется через import main.py. Если условие выполняется, вызывается функция main
if __name__ == "__main__":
main()modules/transfer.py
Этот файл отвечает за сам процесс перевода средств. Мы будем читать список получателей (адресов кошельков) из файла recipients.txt. Для каждого получателя вызываем функцию withdraw(), которая выполнит перевод на конкретный кошелек через OKX API.
Таким образом, основной логикой будет являться итерация по списку адресов и вызов функции перевода.
from modules.okx import withdraw
def start_transfer():
with open("recipients.txt", "r") as file:
recipients = file.readlines()
for recipient in recipients:
withdraw(recipient.strip())Сначала мы импортируем функцию withdraw из модуля okx.py. Эта функция будет использоваться для выполнения вывода средств на указанные адреса используя функцию withdraw из okx.py
from modules.okx import withdraw
2. Определение функции start_transfer:
Мы создаем функцию start_transfer, которая будет отвечать за чтение списка адресов и вызов функции withdraw для каждого из них.
def start_transfer():
Мы используем контекстный менеджер with, чтобы открыть файл recipients.txt для чтения. Контекстный менеджер гарантирует, что файл будет закрыт автоматически, как только выполнение выйдет из блока with, независимо от того, произошла ли ошибка в процессе работы с файлом. Это делает код более безопасным и удобным, так как не нужно вручную закрывать файл.
Функция open используется для открытия файла. Она принимает два аргумента. Первый — это путь к файлу. В нашем случае файл называется recipients.txt и находится в той же папке, что и скрипт, поэтому достаточно указать только имя файла. Второй аргумент — это режим открытия файла. В данном случае мы используем "r", что означает режим чтения (read). Функция open возвращает объект файла, который мы сохраняем в переменной file.
Файл recipients.txt должен содержать список адресов получателей, где каждый адрес находится на новой строке. Мы считываем все строки файла с помощью метода readlines(), который возвращает список, где каждая строка файла становится отдельным элементом списка.
with open("recipients.txt", "r") as file:
recipients = file.readlines()Мы перебираем все адреса из списка recipients с помощью цикла for. Для каждого адреса вызываем функцию withdraw, передавая ей в качестве аргумента адрес получателя. Перед этим мы убираем из адреса лишние пробелы и символы новой строки (\n) с помощью метода strip(), чтобы убедиться, что передаем только саму строку адреса без ненужных символов.
for recipient in recipients:
withdraw(recipient.strip())modules/okx.py
Теперь перейдем к самой сложной части — разберем логику взаимодействия с API OKX для вывода средств на кошелек. Дальше будут термины, которые могут быть не совсем понятны, но не переживай, мы все подробно объясним в пояснении.
Этот модуль отвечает за выполнение перевода средств через API OKX. В функции withdraw() мы формируем HTTP-запрос с нужными параметрами для перевода. Важным моментом является рандомизация суммы перевода из диапазона, который мы указали в конфигурационном файле.
Каждый запрос к API требует подписи для безопасности, поэтому мы используем алгоритм HMAC для генерации подписи (как того требует OKX API), которая затем будет добавлена в заголовки запроса.
- Вычисляет случайную сумму для перевода.
- Формирует тело запроса с нужными параметрами: валютой, суммой, сетью, адресом получателя и другими данными.
- Подписывает запрос с помощью вспомогательной функции
sign_request(). - Отправляет запрос на сервер API OKX и выводит сообщение об успешности перевода.
import json
import random
import requests
from settings import (
OKX_API_KEY,
OKX_API_PASSPHRASE,
TRANSFER_AMOUNT_RANGE,
TRANSFER_CHAIN,
TRANSFER_CURRENCY,
)
from utils import sign_request
OKX_BASE_API_URL = "https://www.okx.com" # базовый URL для запросов к API OKX
def withdraw(recipient: str):
method = "POST"
request_path = "/api/v5/asset/withdrawal"
amount_to_transfer = random.uniform(
TRANSFER_AMOUNT_RANGE[0], TRANSFER_AMOUNT_RANGE[1]
)
body = {
"ccy": TRANSFER_CURRENCY,
"amt": str(amount_to_transfer),
"dest": "4", # 4 - кошелек, 3 - биржа
"chain": f"{TRANSFER_CURRENCY}-{TRANSFER_CHAIN}",
"toAddr": recipient,
"walletType": "private",
}
timestamp, base64_signature = sign_request(
method=method, request_path=request_path, body=json.dumps(body)
)
headers = {
"OK-ACCESS-KEY": OKX_API_KEY,
"OK-ACCESS-SIGN": base64_signature,
"OK-ACCESS-PASSPHRASE": OKX_API_PASSPHRASE,
"OK-ACCESS-TIMESTAMP": timestamp,
}
response = requests.request(
method=method,
url=OKX_BASE_API_URL + request_path,
headers=headers,
json=body,
)
response_data = response.json()
if response_data.get("code", "") == "0":
print(
f"Инициировал перевод | {amount_to_transfer} {TRANSFER_CURRENCY} | {recipient}"
)
else:
print(f"Не удалось инициировать перевод | {response_data.get('msg', 'No message provided')}")
return response_dataНа данном этапе у тебя, вероятно, возникло множество вопросов, так что давай разберемся по порядку.
Откуда мы знаем, куда и как обращаться к OKX API?
У любого качественного API есть документация, и у OKX она тоже есть. Вот тут можно посмотреть как использовать их API для вывода средств.
HTTP — протокол, который используется для обмена данными в интернете. Когда мы отправляем запрос к серверу, мы зачастую используем HTTP, чтобы попросить сервер выполнить какую-то задачу, например, вывести средства на кошелек. Существуют и другие протоколы общения в сети, но сегодня не об этом.
Что такое HMAC и зачем нужно подписывать запросы?
- Для безопасности. Подпись запроса помогает убедиться, что никто не подменил данные, которые ты отправляешь на сервер. Без подписи кто-то мог бы изменить твой запрос и, например, перевести деньги на свой кошелек. Подпись защищает от этого.
- Требование API OKX. OKX требует, чтобы все запросы были подписаны. Если запрос не подписан, API просто отклонит его. В документации OKX подробно объясняется, как правильно подписывать запросы, чтобы сервер принял их.
HMAC — метод, с помощью которого мы создаем подпись для наших запросов. Он использует специальный секретный ключ и алгоритм, который помогает создать уникальную "отпечаток" данных запроса, чтобы сервер знал, что запрос действительно пришел от тебя и не был изменен.
1. Импорт необходимых библиотек и настроек:
Мы импортируем стандартные библиотеки json, random и requests для работы с JSON-данными, генерации случайных чисел и отправки HTTP-запросов, соответственно. Также импортируем несколько настроек из файла settings.py, таких как ключи для API, диапазон сумм для перевода, сеть и валюту. И из файла utils.py импортируется функция sign_request, которая используется для создания подписи запроса.
import json
import random
import requests
from settings import (
OKX_API_KEY,
OKX_API_PASSPHRASE,
TRANSFER_AMOUNT_RANGE,
TRANSFER_CHAIN,
TRANSFER_CURRENCY,
)
from utils import sign_request2. Базовый URL для запросов к API OKX:
Здесь определяется базовый URL, по которому будут отправляться запросы к API биржи OKX. Если проще, то просто указываем домен OKX вместе с https схемой.
OKX_BASE_API_URL = "https://www.okx.com"
3. Определение функции withdraw:
Функция withdraw принимает один аргумент — recipient, который представляет собой адрес получателя перевода. Мы его передаем из modules/transfer.py, если вдруг ты забыл.
def withdraw(recipient: str):
method = "POST" request_path = "/api/v5/asset/withdrawal"
Устанавливаем метод HTTP запроса (POST) и путь к API-методу для вывода средств. Мы пишем именно такие значение, так как в документации OKX API по выводу средств указан именно такое метод и данный request_path
5. Генерация случайной суммы для перевода:
С помощью функции random.uniform генерируется случайное число с плавующей запятой в пределах диапазона, заданного в настройках TRANSFER_AMOUNT_RANGE. Это будет сумма, которую мы будем выводить.
amount_to_transfer = random.uniform(
TRANSFER_AMOUNT_RANGE[0], TRANSFER_AMOUNT_RANGE[1]
)Формируем словарь body, который будет передан в запросе. Здесь мы указываем валюту перевода, сумму, тип назначения (кошелек или биржа), сеть и адрес получателя.
Ты можешь заметить, что "dest" может быть как 4, так и 3. Если бы мы хотели провести внутрибиржевый перевод, то нам понадобилось указать тут цифру 3 и вместа адреса указать email или другой индификатор OKX аккаунта.
body = {
"ccy": TRANSFER_CURRENCY,
"amt": str(amount_to_transfer),
"dest": "4", # 4 - кошелек, 3 - биржа
"chain": f"{TRANSFER_CURRENCY}-{TRANSFER_CHAIN}",
"toAddr": recipient,
"walletType": "private",
}Для безопасности запросов к API необходимо подписывать их. Мы используем функцию sign_request (напишем её позже) для создания подписи. Она возвращает timestamp и signature.
timestamp, base64_signature = sign_request(
method=method, request_path=request_path, body=json.dumps(body)
)8. Подготовка заголовков для запроса:
Здесь мы формируем заголовки для HTTP-запроса, включая ключ API, подпись, passphrase и метку времени.
headers = {
"OK-ACCESS-KEY": OKX_API_KEY,
"OK-ACCESS-SIGN": base64_signature,
"OK-ACCESS-PASSPHRASE": OKX_API_PASSPHRASE,
"OK-ACCESS-TIMESTAMP": timestamp,
}Мы отправляем HTTP-запрос с помощью библиотеки requests, передавая метод, сформированный URL, заголовки и тело запроса.
response = requests.request(
method=method,
url=OKX_BASE_API_URL + request_path,
headers=headers,
json=body,
)Полученный ответ от OKX API парсим как JSON.
JSON — это широко используемый формат данных для обмена информацией с API. Он универсален и совместим с большинством языков программирования, что делает его удобным для применения в различных системах.
Если в ответе код "0", это означает, что перевод был успешно инициирован, и мы выводим сообщение с информацией о переводе. В противном случае выводим сообщение об ошибке.
Спросишь как мы узнали, что будет в ответе? Тут есть два варианта:
response_data = response.json()
if response_data.get("code", "") == "0":
print(
f"Инициировал перевод | {amount_to_transfer} {TRANSFER_CURRENCY} | {recipient}"
)
else:
print(f"Не удалось инициировать перевод | {response_data.get('msg', 'No message provided')}")Функция возвращает полученные данные ответа от API, чтобы мы могли использовать их дальше, если это необходимо. В нашем случае ответ вернется обратно в modules/transfer.py
return response_data
utils.py
Здесь мы распишем различные вспомогательные функции, такие как подпись запроса к OKX API или получение timestamp.
import hmac
from base64 import b64encode
from datetime import datetime, timezone
from typing import Tuple
from settings import OKX_API_SECRET
def sign_request(method: str, request_path: str, body: str = "") -> Tuple[str, str]:
timestamp = get_iso_timestamp()
message = timestamp + method + request_path + body
signature = hmac.new(OKX_API_SECRET.encode(), message.encode(), "SHA256").digest()
base64_signature = b64encode(signature).decode()
return timestamp, base64_signature
def get_iso_timestamp() -> str:
utc_timestamp = datetime.now(timezone.utc).isoformat(timespec="milliseconds")
utc_timestamp = utc_timestamp.replace("+00:00", "Z")
return utc_timestamp1. Импорт необходимых библиотек:
Импортируем необходимые библиотеки: hmac для создания подписи к запросу для вывода, b64encode для кодирования подписи в формат base64, как того требует OKX API, datetime и timezone для работы с текущим временем в UTC, а также Tuple для аннотирования типов возвращаемых значений функции. Кроме того, из файла settings загружается секретный ключ API — OKX_API_SECRET, который используется для генерации подписи.
import hmac from base64 import b64encode from datetime import datetime, timezone from typing import Tuple from settings import OKX_API_SECRET
Функция sign_request используется для создания подписи запроса к API OKX. Она принимает три аргумента: HTTP-метод запроса (method), путь запроса (request_path) и тело запроса (body), которое по умолчанию пустое.
def sign_request(method: str, request_path: str, body: str = "") -> Tuple[str, str]:
Мы вызываем функцию get_iso_timestamp, чтобы получить метку времени в формате ISO и записать ответ в переменнюу timestamp.
timestamp = get_iso_timestamp()
4. Формирование сообщения для подписи:
Для создания подписи мы формируем строку сообщения, которое включает в себя:
- Метку времени (
timestamp). - HTTP-метод запроса (
method). - Путь запроса (
request_path). - Тело запроса (
body), которое может быть пустым.
Это также расписано в документации OKX API
message = timestamp + method + request_path + body
5. Создание подписи с помощью HMAC:
Мы используем алгоритм HMAC с SHA256 для создания хэш-значения подписи. Хэш создается с использованием секретного ключа API (OKX_API_SECRET) и строки message, которую мы сформировали ранее.
Для обеих переменных — OKX_API_SECRET и message — применяется метод encode(). Это преобразует их из строкового представления в байтовое, так как функция hmac.new() требует, чтобы входные данные были в виде байтов.
Затем создается объект HMAC с использованием метода hmac.new(), где передаются:
- Секретный ключ (
OKX_API_SECRET.encode()), - Сообщение (
message.encode()), - Алгоритм хэширования (
"SHA256").
Метод .digest() возвращает итоговое хэш-значение подписи в виде байтов. Это значение используется для аутентификации запросов к API.
signature = hmac.new(OKX_API_SECRET.encode(), message.encode(), "SHA256").digest()
6. Конвертация подписи в формат base64
После того как мы подписали наш запрос, мы конвертируем это в формат base64 для передачи в заголовках HTTP-запроса.
BASE64 — это способ кодирования строки, но не шифрования. То есть, любой текст, закодированный в base64, можно легко расшифровать. Этот формат используется исключительно для удобства представления данных.
base64_signature = b64encode(signature).decode()
7. Возврат timestamp и подписи
Функция возвращает два значения: временную метку и подпись в формате base64.
return timestamp, base64_signature
8. Определение функции get_iso_timestamp
Эта функция получает текущее время в формате ISO 8601. Мы используем datetime.now(timezone.utc) для получения текущего времени в UTC. Затем, с помощью метода isoformat(timespec="milliseconds"), мы получаем строку с точностью до миллисекунд. В конце заменяем строку "+00:00" на "Z", чтобы привести метку времени к стандарту ISO 8601, который используется в OKX API.
def get_iso_timestamp() -> str:
utc_timestamp = datetime.now(timezone.utc).isoformat(timespec="milliseconds")
utc_timestamp = utc_timestamp.replace("+00:00", "Z")
return utc_timestampПодготовка и запуск скрипта
Окей, мы написали полностью код, теперь нам нужно сделать необходимую подготовку для его запуска.
1. Создаем виртуальное окружение.
python -m venv .venv
Далее нам нужно активировать его
.venv\Scripts\activate
Готово, теперь у нас есть изолированное Python окружение для работы с написанным скриптом.
2. Устанавливаем необходимые зависимости
Мы использовали в основном стандартные Python библиотеки, правда одна одна сторонняя все же есть. Это requests, она используется для выполнения HTTP запросов, давай её установим.
pip install requests
3. Указываем адреса в recipients.txt
Тут все просто, берем и вставляем наши адреса кошельков в recipients.txt. Каждый кошелек должен быть на новой строке.
Теперь мы готовы к запуску, для этого вводим следующую команду:
python main.py
После того как скрипт запущен он должен спросить нас, готовы ли мы начать раскидку баланса.
Как можно улучшить скрипт
Мы с тобой написали лишь базовый функционал, который можно расширять. Например, ты можешь добавить проверку баланса на OKX перед переводом средств или добавить ожидания выполнения транзакции.
Небольшие наводки, если вдруг тебе станет это интересно:
- Для проверки баланса можешь использовать эту ручку.
- Если ты захотел добавить ожидание тразакции, то тебе понадобится вытащить из ответа Withdrawal ID (хранится в data["wdId"]) и затем использовать эту ручку.
Заключение
Надеемся, эта статья оказалась для тебя полезной и принесла много ценной информации. В процессе написания кода мы охватили множество тем, с которыми тебе предстоит столкнуться еще не раз. И, конечно, мы создали скрипт, который автоматически распределяет токены по нескольким кошелькам. Это простой, но невероятно полезный инструмент, который позволяет попробовать себя в разработке.
Если статья тебе понравилась, сохрани её 📋, чтобы не потерять.
Не забудь написать в комментариях 👇, что именно ты хотел бы увидеть в следующий раз!