C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins;
[Info("CryptoTrade", "BLOOD KRAKEN BLKR", "3.2.3")]
internal class CryptoTrade : RustPlugin
{
[PluginReference] private Plugin IQEconomic;
[PluginReference] private Plugin ImageLibrary;
private Configuration _config;
private StoredData _data;
private Dictionary<ulong, PendingValidation> _pendingValidations = new Dictionary<ulong, PendingValidation>();
private Dictionary<ulong, string> _linkedWallets = new Dictionary<ulong, string>();
private List<ulong> _toRemoveBuffer = new List<ulong>();
private const string BUTTON_IMAGE_NAME = "CryptoTrade_Button";
private const string PermAdmin = "cryptotrade.admin";
private class PendingValidation
{
public string Wallet;
public int Amount;
public float StartTime;
}
#region Config
private class Configuration
{
[JsonProperty(PropertyName = "Ключ от тг бота", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public string ApiKey = "ВАШ_ТОКЕН_ЗДЕСЬ";
[JsonProperty(PropertyName = "Минимальное количество игровой валюты для обмена",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public int MinCoin = 100000;
[JsonProperty(PropertyName = "Курс 1 коина к Solana",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public int Course = 1000;
[JsonProperty(PropertyName = "TG ChatId", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public long ChatId = -4979038064;
[JsonProperty(PropertyName = "Включить логирование в файл",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public bool EnableFileLogging = true;
[JsonProperty(PropertyName = "Helius API Key (для проверки кошельков)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public string HeliusApiKey = "ВАШ_HELIUS_API_KEY";
[JsonProperty(PropertyName = "Включить проверку кошельков",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public bool EnableWalletValidation = true;
[JsonProperty(PropertyName = "Таймаут проверки кошелька (секунды)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public int ValidationTimeout = 10;
[JsonProperty(PropertyName = "Включить кнопку запуска",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public bool EnableLaunchButton = true;
[JsonProperty(PropertyName = "URL картинки кнопки (рекомендуется 512x512)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public string ButtonImageUrl = "https://i.yapx.ru/c29vJ.png";
[JsonProperty(PropertyName = "Размер кнопки (пиксели)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public float ButtonSize = 80f;
[JsonProperty(PropertyName = "Отступ от правого края (пиксели)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public float ButtonRightOffset = 20f;
[JsonProperty(PropertyName = "Отступ от верхнего края (пиксели, 0 = центр)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public float ButtonTopOffset = 100f;
[JsonProperty(PropertyName = "Включить недельный лимит вывода",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public bool EnableWeeklyLimit = true;
[JsonProperty(PropertyName = "Максимальная сумма вывода за неделю (в игровой валюте)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public int WeeklyLimitAmount = 5000000;
[JsonProperty(PropertyName = "День сброса лимита (0 = Воскресенье, 1 = Понедельник...)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public int ResetDay = 1;
[JsonProperty(PropertyName = "Час сброса лимита (0-23)",
ObjectCreationHandling = ObjectCreationHandling.Replace)]
public int ResetHour = 0;
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<Configuration>();
if (_config == null) throw new Exception();
SaveConfig();
}
catch
{
PrintError("Your configuration file contains an error. Using default configuration values.");
LoadDefaultConfig();
}
}
protected override void SaveConfig()
{
Config.WriteObject(_config);
}
protected override void LoadDefaultConfig()
{
_config = new Configuration();
}
#endregion
#region Data
private class WithdrawalData
{
public string PlayerName;
public ulong PlayerId;
public string Wallet;
public int Amount;
public long SolanaAmount; // ИСПРАВЛЕНО: было int, стало long
public DateTime Timestamp;
public string Status;
}
private class StoredData
{
public List<WithdrawalData> Withdrawals = new List<WithdrawalData>();
public Dictionary<ulong, PlayerWeeklyLimit> PlayerLimits = new Dictionary<ulong, PlayerWeeklyLimit>();
}
private class PlayerWeeklyLimit
{
public int WithdrawnThisWeek = 0;
public DateTime LastResetTime = DateTime.MinValue;
public List<WeeklyWithdrawal> WeeklyHistory = new List<WeeklyWithdrawal>();
}
private class WeeklyWithdrawal
{
public DateTime Timestamp;
public int Amount;
public long SolanaAmount; // ИСПРАВЛЕНО: было int, стало long
public string Wallet;
}
private class PlayerWalletData
{
public string PlayerName;
public ulong SteamID;
public string SolanaWallet;
public List<WithdrawalHistory> WithdrawalHistory = new List<WithdrawalHistory>();
}
private class WithdrawalHistory
{
public DateTime Timestamp;
public int Amount;
public long SolanaAmount; // ИСПРАВЛЕНО: было int, стало long
}
private void LoadData()
{
try
{
_data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>("CryptoTrade_Data");
if (_data == null) _data = new StoredData();
CheckAndResetWeeklyLimits();
}
catch
{
_data = new StoredData();
}
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject("CryptoTrade_Data", _data);
}
private void EnsureDirectoryExists()
{
var directory = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem");
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
}
private void LoadLinkedWallets()
{
EnsureDirectoryExists();
var directory = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem");
if (!Directory.Exists(directory))
{
PrintWarning($"Директория {directory} не существует");
return;
}
var files = Directory.GetFiles(directory, "*.json");
PrintWarning($"Найдено {files.Length} файлов кошельков");
foreach (var file in files)
{
try
{
var fileName = Path.GetFileNameWithoutExtension(file);
if (ulong.TryParse(fileName, out var steamId))
{
var json = File.ReadAllText(file);
var walletData = JsonConvert.DeserializeObject<PlayerWalletData>(json);
if (walletData != null && !string.IsNullOrEmpty(walletData.SolanaWallet))
{
_linkedWallets[steamId] = walletData.SolanaWallet;
PrintWarning($"Загружен кошелек для {steamId}: {walletData.SolanaWallet.Substring(0, 8)}...");
}
else
{
PrintWarning($"Файл {file} пустой или некорректный");
}
}
else
{
PrintWarning($"Не удалось распарсить SteamID из имени файла: {fileName}");
}
}
catch (Exception ex)
{
PrintError($"Ошибка загрузки кошелька из {file}: {ex.Message}");
}
}
PrintWarning($"Всего загружено кошельков: {_linkedWallets.Count}");
}
private void SavePlayerWallet(BasePlayer player, string wallet)
{
EnsureDirectoryExists();
var directory = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem");
var filePath = Path.Combine(directory, $"{player.userID}.json");
PlayerWalletData walletData;
if (File.Exists(filePath))
{
try
{
var json = File.ReadAllText(filePath);
walletData = JsonConvert.DeserializeObject<PlayerWalletData>(json) ?? new PlayerWalletData();
}
catch
{
walletData = new PlayerWalletData();
}
}
else
{
walletData = new PlayerWalletData();
}
walletData.PlayerName = player.displayName;
walletData.SteamID = player.userID.Get();
walletData.SolanaWallet = wallet;
try
{
File.WriteAllText(filePath, JsonConvert.SerializeObject(walletData, Formatting.Indented));
_linkedWallets[player.userID.Get()] = wallet;
PrintWarning($"Сохранен кошелек для {player.userID}: {wallet.Substring(0, 8)}...");
}
catch (Exception ex)
{
PrintError($"Ошибка сохранения кошелька: {ex.Message}");
}
}
private void UnlinkPlayerWallet(BasePlayer player)
{
var directory = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem");
var filePath = Path.Combine(directory, $"{player.userID}.json");
if (File.Exists(filePath))
{
try
{
File.Delete(filePath);
PrintWarning($"Удален файл кошелька для {player.userID}");
}
catch (Exception ex)
{
PrintError($"Ошибка удаления файла кошелька: {ex.Message}");
}
}
_linkedWallets.Remove(player.userID.Get());
}
private void AddWithdrawalToHistory(BasePlayer player, int amount, long solanaAmount)
{
var directory = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem");
var filePath = Path.Combine(directory, $"{player.userID}.json");
PlayerWalletData walletData;
// Загружаем существующие данные или создаём новые
if (File.Exists(filePath))
{
try
{
var json = File.ReadAllText(filePath);
walletData = JsonConvert.DeserializeObject<PlayerWalletData>(json);
if (walletData == null) walletData = new PlayerWalletData();
}
catch (Exception ex)
{
PrintError($"[CryptoTrade] Ошибка чтения истории: {ex.Message}");
walletData = new PlayerWalletData();
}
}
else
{
walletData = new PlayerWalletData();
}
// Обновляем данные игрока
walletData.PlayerName = player.displayName;
walletData.SteamID = player.userID.Get();
// Инициализируем список если null
if (walletData.WithdrawalHistory == null)
walletData.WithdrawalHistory = new List<WithdrawalHistory>();
// Добавляем в историю (не перезаписываем!)
walletData.WithdrawalHistory.Add(new WithdrawalHistory
{
Timestamp = DateTime.Now,
Amount = amount,
SolanaAmount = solanaAmount
});
// Сохраняем
try
{
File.WriteAllText(filePath, JsonConvert.SerializeObject(walletData, Formatting.Indented));
PrintWarning($"[CryptoTrade] История сохранена: {player.displayName} - {amount} (всего записей: {walletData.WithdrawalHistory.Count})");
}
catch (Exception ex)
{
PrintError($"[CryptoTrade] Ошибка сохранения истории: {ex.Message}");
}
}
private string GetLinkedWallet(ulong steamId)
{
if (_linkedWallets.ContainsKey(steamId))
return _linkedWallets[steamId];
return "";
}
#endregion
#region Weekly Limit System
private void CheckAndResetWeeklyLimits()
{
if (!_config.EnableWeeklyLimit) return;
var now = DateTime.Now;
var lastReset = GetLastGlobalResetTime();
if (now >= lastReset.AddDays(7))
{
var nextReset = GetNextResetTime(now.AddDays(-7));
if (now >= nextReset)
{
PrintWarning($"[CryptoTrade] Сброс недельных лимитов. Время: {now}");
foreach (var kvp in _data.PlayerLimits.ToList())
{
var playerLimit = kvp.Value;
playerLimit.WithdrawnThisWeek = 0;
playerLimit.LastResetTime = now;
}
SaveGlobalResetTime(now);
SaveData();
}
}
}
private DateTime GetLastGlobalResetTime()
{
try
{
var resetFile = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem", "last_reset.txt");
if (File.Exists(resetFile))
{
var text = File.ReadAllText(resetFile);
if (DateTime.TryParse(text, out var date))
return date;
}
}
catch { }
return GetCurrentWeekStart();
}
private void SaveGlobalResetTime(DateTime time)
{
try
{
var resetFile = Path.Combine(Interface.Oxide.DataDirectory, "CryptoTradeSystem", "last_reset.txt");
File.WriteAllText(resetFile, time.ToString("O"));
}
catch (Exception ex)
{
PrintError($"Ошибка сохранения времени сброса: {ex.Message}");
}
}
private DateTime GetCurrentWeekStart()
{
var now = DateTime.Now;
var daysSinceReset = ((int)now.DayOfWeek - _config.ResetDay + 7) % 7;
return new DateTime(now.Year, now.Month, now.Day, _config.ResetHour, 0, 0)
.AddDays(-daysSinceReset);
}
private DateTime GetNextResetTime(DateTime from)
{
var daysUntilReset = ((int)DayOfWeek.Sunday - (int)from.DayOfWeek + _config.ResetDay + 7) % 7;
if (daysUntilReset == 0 && from.Hour >= _config.ResetHour)
daysUntilReset = 7;
return new DateTime(from.Year, from.Month, from.Day, _config.ResetHour, 0, 0)
.AddDays(daysUntilReset);
}
private PlayerWeeklyLimit GetPlayerLimit(ulong steamId)
{
if (!_data.PlayerLimits.ContainsKey(steamId))
{
_data.PlayerLimits[steamId] = new PlayerWeeklyLimit
{
LastResetTime = DateTime.Now
};
}
return _data.PlayerLimits[steamId];
}
private int GetRemainingWeeklyLimit(ulong steamId)
{
if (!_config.EnableWeeklyLimit) return int.MaxValue;
CheckAndResetWeeklyLimits();
var limit = GetPlayerLimit(steamId);
return Math.Max(0, _config.WeeklyLimitAmount - limit.WithdrawnThisWeek);
}
private void AddToWeeklyLimit(ulong steamId, int amount)
{
if (!_config.EnableWeeklyLimit) return;
var limit = GetPlayerLimit(steamId);
limit.WithdrawnThisWeek += amount;
// Используем long для расчета
long solanaAmount = (long)amount * (long)_config.Course;
limit.WeeklyHistory.Add(new WeeklyWithdrawal
{
Timestamp = DateTime.Now,
Amount = amount,
SolanaAmount = solanaAmount,
Wallet = _linkedWallets.ContainsKey(steamId) ? _linkedWallets[steamId] : ""
});
SaveData();
PrintWarning($"[CryptoTrade] Лимит обновлен: {steamId} +{amount} (всего: {limit.WithdrawnThisWeek}/{_config.WeeklyLimitAmount})");
}
private string GetTimeUntilReset()
{
var now = DateTime.Now;
var nextReset = GetNextResetTime(now);
var timeLeft = nextReset - now;
if (timeLeft.TotalDays >= 1)
return $"{(int)timeLeft.TotalDays}д {timeLeft.Hours}ч";
else if (timeLeft.TotalHours >= 1)
return $"{(int)timeLeft.TotalHours}ч {timeLeft.Minutes}м";
else
return $"{(int)timeLeft.TotalMinutes}м";
}
#endregion
#region OxideHooks
private void Init()
{
permission.RegisterPermission(PermAdmin, this);
LoadData();
LoadLinkedWallets();
}
private void OnServerInitialized()
{
if (IQEconomic == null)
{
PrintError("|-----------------------------------|");
PrintError("| IQEconomic НЕ НАЙДЕН! |");
PrintError("| Плагин не будет работать без него|");
PrintError("|-----------------------------------|");
return;
}
PrintError("|-----------------------------------|");
PrintWarning($"| Plugin {Title} v{Version} is loaded |");
PrintWarning("| Telegram: SNBLKR |");
PrintError("|-----------------------------------|");
if (_config.EnableWalletValidation && _config.HeliusApiKey == "ВАШ_HELIUS_API_KEY")
{
PrintWarning("ВНИМАНИЕ: Helius API Key не настроен! Проверка кошельков будет отключена.");
}
if (ImageLibrary == null)
{
PrintWarning("ВНИМАНИЕ: ImageLibrary не найден! Кнопка запуска работать не будет.");
}
else if (_config.EnableLaunchButton && !string.IsNullOrEmpty(_config.ButtonImageUrl))
{
RegisterButtonImage();
}
LoadLinkedWallets();
timer.Every(3600f, () => CheckAndResetWeeklyLimits());
}
private void Unload()
{
SaveData();
foreach (var player in BasePlayer.activePlayerList)
{
HideLaunchButton(player);
}
}
private void OnTick()
{
_toRemoveBuffer.Clear();
foreach (var kvp in _pendingValidations)
{
if (Time.realtimeSinceStartup - kvp.Value.StartTime > _config.ValidationTimeout)
{
_toRemoveBuffer.Add(kvp.Key);
}
}
foreach (var id in _toRemoveBuffer)
{
_pendingValidations.Remove(id);
}
}
private void OnPlayerRespawned(BasePlayer player)
{
if (_config.EnableLaunchButton && ImageLibrary != null)
{
timer.Once(2f, () => ShowLaunchButton(player));
}
}
private void OnPlayerSleepEnded(BasePlayer player)
{
if (_config.EnableLaunchButton && ImageLibrary != null)
{
timer.Once(2f, () => ShowLaunchButton(player));
}
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
HideLaunchButton(player);
}
#endregion
#region ImageLibrary Integration
private void RegisterButtonImage()
{
if (ImageLibrary == null) return;
var hasImage = ImageLibrary.Call("HasImage", BUTTON_IMAGE_NAME, (ulong)0);
if (hasImage is bool has && has)
{
PrintWarning("Картинка кнопки уже зарегистрирована в ImageLibrary");
return;
}
ImageLibrary.Call("AddImage", _config.ButtonImageUrl, BUTTON_IMAGE_NAME, (ulong)0, (Action)(() =>
{
PrintWarning($"Картинка кнопки успешно загружена в ImageLibrary");
foreach (var player in BasePlayer.activePlayerList)
{
ShowLaunchButton(player);
}
}));
PrintWarning($"Регистрация картинки кнопки в ImageLibrary: {_config.ButtonImageUrl}");
}
private string GetButtonImage()
{
if (ImageLibrary == null) return null;
var result = ImageLibrary.Call("GetImage", BUTTON_IMAGE_NAME, (ulong)0, false);
if (result == null || string.IsNullOrEmpty(result.ToString()))
{
PrintWarning("Картинка кнопки не найдена в ImageLibrary");
return null;
}
return result.ToString();
}
#endregion
#region Launch Button
private void ShowLaunchButton(BasePlayer player)
{
if (!_config.EnableLaunchButton) return;
if (ImageLibrary == null) return;
if (player == null || !player.IsConnected) return;
HideLaunchButton(player);
var imageId = GetButtonImage();
if (string.IsNullOrEmpty(imageId))
{
RegisterButtonImage();
return;
}
var container = new CuiElementContainer();
var screenWidth = 1920f;
var screenHeight = 1080f;
var buttonSize = _config.ButtonSize;
var rightOffset = _config.ButtonRightOffset;
var topOffset = _config.ButtonTopOffset;
var anchorMinX = 1f - (rightOffset + buttonSize) / screenWidth;
var anchorMaxX = 1f - rightOffset / screenWidth;
var anchorMinY = 1f - (topOffset + buttonSize) / screenHeight;
var anchorMaxY = 1f - topOffset / screenHeight;
if (topOffset <= 0)
{
var centerY = 0.5f;
var halfHeight = (buttonSize / screenHeight) / 2f;
anchorMinY = centerY - halfHeight;
anchorMaxY = centerY + halfHeight;
}
anchorMinX = Mathf.Round(anchorMinX * 1000f) / 1000f;
anchorMaxX = Mathf.Round(anchorMaxX * 1000f) / 1000f;
anchorMinY = Mathf.Round(anchorMinY * 1000f) / 1000f;
anchorMaxY = Mathf.Round(anchorMaxY * 1000f) / 1000f;
container.Add(new CuiElement
{
Name = "CryptoTrade_LaunchButton",
Parent = "Overlay",
Components =
{
new CuiRawImageComponent
{
Png = imageId,
Color = "1 1 1 1"
},
new CuiRectTransformComponent
{
AnchorMin = $"{anchorMinX} {anchorMinY}",
AnchorMax = $"{anchorMaxX} {anchorMaxY}",
OffsetMin = "0 0",
OffsetMax = "0 0"
}
}
});
container.Add(new CuiButton
{
Button =
{
Color = "0 0 0 0",
Command = "cryptolaunch"
},
RectTransform =
{
AnchorMin = $"{anchorMinX} {anchorMinY}",
AnchorMax = $"{anchorMaxX} {anchorMaxY}",
OffsetMin = "0 0",
OffsetMax = "0 0"
},
Text =
{
Text = ""
}
}, "Overlay", "CryptoTrade_LaunchButton_Click");
CuiHelper.AddUi(player, container);
}
private void HideLaunchButton(BasePlayer player)
{
if (player == null) return;
CuiHelper.DestroyUi(player, "CryptoTrade_LaunchButton");
CuiHelper.DestroyUi(player, "CryptoTrade_LaunchButton_Click");
}
[ConsoleCommand("cryptolaunch")]
private void CmdCryptoLaunch(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
HideLaunchButton(player);
CmdChatcrypto(player, "crypto", new string[0]);
}
#endregion
#region Function
[ChatCommand("crypto")]
private void CmdChatcrypto(BasePlayer player, string command, string[] args)
{
if (IQEconomic == null)
{
player.ChatMessage("<color=red>Ошибка: экономика не загружена!</color>");
return;
}
HideLaunchButton(player);
var linkedWallet = GetLinkedWallet(player.userID.Get());
var isLinked = !string.IsNullOrEmpty(linkedWallet);
PrintWarning($"Игрок {player.displayName} открыл UI. Кошелек привязан: {isLinked}");
ShowMainUI(player, linkedWallet, _config.MinCoin, "", isLinked);
}
[ConsoleCommand("UI_CRYPTOTRADE")]
private void CmdConsoleUI_CRYPTOTRADE(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null || arg.Args == null || arg.Args.Length < 1) return;
switch (arg.Args[0])
{
case "CLOSE":
{
if (_config.EnableLaunchButton && ImageLibrary != null)
{
ShowLaunchButton(player);
}
break;
}
case "ADDRESS":
{
if (arg.Args.Length < 3) return;
var amount = arg.GetInt(1);
var wallet = arg.Args[2] ?? "";
var isLinked = !string.IsNullOrEmpty(GetLinkedWallet(player.userID.Get()));
ShowMainUI(player, wallet, amount, "", isLinked);
break;
}
case "COIN":
{
if (arg.Args.Length < 3) return;
var wallet = arg.Args[1] ?? "";
var amountStr = arg.Args[2];
int amount;
if (!int.TryParse(amountStr, out amount))
{
amount = _config.MinCoin;
}
var isLinked = !string.IsNullOrEmpty(GetLinkedWallet(player.userID.Get()));
ShowMainUI(player, wallet, amount, "", isLinked);
break;
}
case "LINK_WALLET":
{
if (arg.Args.Length < 2) return;
var wallet = arg.Args[1] ?? "";
LinkWallet(player, wallet);
break;
}
case "UNLINK_WALLET":
{
UnlinkWallet(player);
break;
}
case "CONFIRM":
{
if (arg.Args.Length < 3) return;
var wallet = arg.Args[1] ?? "";
var amount = arg.GetInt(2);
if (_config.EnableWeeklyLimit)
{
var remainingLimit = GetRemainingWeeklyLimit(player.userID.Get());
if (amount > remainingLimit)
{
ShowMainUI(player, wallet, amount, $"<color=red>❌ Превышен недельный лимит! Доступно: {remainingLimit}</color>", true);
return;
}
}
if (_config.EnableWalletValidation)
{
ValidateWalletAndProcess(player, wallet, amount);
}
else
{
ProcessWithdrawal(player, wallet, amount);
}
break;
}
}
}
private void LinkWallet(BasePlayer player, string wallet)
{
PrintWarning($"Попытка привязки кошелька {wallet.Substring(0, 8)}... для {player.displayName}");
if (!IsValidSolanaAddress(wallet))
{
ShowMainUI(player, wallet, _config.MinCoin, "<color=red>Некорректный формат адреса Solana!</color>", false);
return;
}
if (_config.EnableWalletValidation && _config.HeliusApiKey != "ВАШ_HELIUS_API_KEY")
{
ShowMainUI(player, wallet, _config.MinCoin, "<color=yellow>⏳ Проверка кошелька...</color>", false);
var heliusUrl = $"https://mainnet.helius-rpc.com/?api-key={_config.HeliusApiKey}";
var payload = new
{
jsonrpc = "2.0",
id = 1,
method = "getBalance",
@params = new object[] { wallet }
};
var jsonPayload = JsonConvert.SerializeObject(payload);
webrequest.Enqueue(heliusUrl, jsonPayload, (code, response) =>
{
if (code != 200 || string.IsNullOrEmpty(response))
{
ShowMainUI(player, wallet, _config.MinCoin, "<color=red>Ошибка проверки кошелька.</color>", false);
return;
}
try
{
var json = JObject.Parse(response);
var error = json["error"];
if (error != null)
{
var errorMessage = error["message"]?.ToString() ?? "";
if (errorMessage.Contains("could not find account") || errorMessage.Contains("Invalid param"))
{
ShowMainUI(player, wallet, _config.MinCoin, "<color=red>❌ Кошелек не найден в сети Solana!</color>", false);
return;
}
}
SavePlayerWallet(player, wallet);
PrintWarning($"Кошелек привязан для {player.displayName}");
ShowMainUI(player, wallet, _config.MinCoin, "<color=green>✅ Кошелек успешно привязан!</color>", true);
}
catch (Exception ex)
{
PrintError($"Ошибка: {ex.Message}");
ShowMainUI(player, wallet, _config.MinCoin, "<color=red>Ошибка проверки.</color>", false);
}
}, this, Core.Libraries.RequestMethod.POST);
}
else
{
SavePlayerWallet(player, wallet);
PrintWarning($"Кошелек привязан (без проверки) для {player.displayName}");
ShowMainUI(player, wallet, _config.MinCoin, "<color=green>✅ Кошелек успешно привязан!</color>", true);
}
}
private void UnlinkWallet(BasePlayer player)
{
PrintWarning($"Отвязка кошелька для {player.displayName}");
UnlinkPlayerWallet(player);
ShowMainUI(player, "", _config.MinCoin, "<color=yellow>Кошелек отвязан.</color>", false);
}
private void ValidateWalletAndProcess(BasePlayer player, string wallet, int amount)
{
if (!IsValidSolanaAddress(wallet))
{
ShowMainUI(player, wallet, amount, "<color=red>Некорректный формат адреса Solana!</color>", true);
return;
}
if (amount < _config.MinCoin)
{
ShowMainUI(player, wallet, amount, $"<color=red>Минимальная сумма: {_config.MinCoin}</color>", true);
return;
}
var currentBalance = GetBalance(player);
if (currentBalance < amount)
{
ShowMainUI(player, wallet, amount, $"<color=red>Недостаточно средств! Доступно: {currentBalance}</color>", true);
return;
}
if (_config.EnableWeeklyLimit)
{
var remainingLimit = GetRemainingWeeklyLimit(player.userID.Get());
if (amount > remainingLimit)
{
ShowMainUI(player, wallet, amount, $"<color=red>❌ Превышен недельный лимит! Доступно: {remainingLimit}</color>", true);
return;
}
}
if (_config.HeliusApiKey == "ВАШ_HELIUS_API_KEY")
{
ProcessWithdrawal(player, wallet, amount);
return;
}
ShowMainUI(player, wallet, amount, "<color=yellow>⏳ Проверка кошелька...</color>", true);
_pendingValidations[player.userID.Get()] = new PendingValidation
{
Wallet = wallet,
Amount = amount,
StartTime = Time.realtimeSinceStartup
};
var heliusUrl = $"https://mainnet.helius-rpc.com/?api-key={_config.HeliusApiKey}";
var payload = new
{
jsonrpc = "2.0",
id = 1,
method = "getBalance",
@params = new object[] { wallet }
};
var jsonPayload = JsonConvert.SerializeObject(payload);
webrequest.Enqueue(heliusUrl, jsonPayload, (code, response) =>
{
_pendingValidations.Remove(player.userID.Get());
if (code != 200 || string.IsNullOrEmpty(response))
{
ShowMainUI(player, wallet, amount, "<color=red>Ошибка проверки кошелька.</color>", true);
return;
}
try
{
var json = JObject.Parse(response);
var error = json["error"];
if (error != null)
{
var errorMessage = error["message"]?.ToString() ?? "";
if (errorMessage.Contains("could not find account") || errorMessage.Contains("Invalid param"))
{
ShowMainUI(player, wallet, amount, "<color=red>❌ Кошелек не найден!</color>", true);
return;
}
}
ProcessWithdrawal(player, wallet, amount);
}
catch (Exception ex)
{
PrintError($"Ошибка: {ex.Message}");
ShowMainUI(player, wallet, amount, "<color=red>Ошибка проверки.</color>", true);
}
}, this, Core.Libraries.RequestMethod.POST);
}
private void ProcessWithdrawal(BasePlayer player, string wallet, int amount)
{
// ПРОВЕРКА ЛИМИТА В НАЧАЛЕ
if (_config.EnableWeeklyLimit)
{
var remainingLimit = GetRemainingWeeklyLimit(player.userID.Get());
if (amount > remainingLimit)
{
ShowMainUI(player, wallet, amount, $"<color=red>❌ Превышен недельный лимит! Доступно: {remainingLimit}</color>", true);
player.ChatMessage($"<color=red>❌ Ошибка: превышен недельный лимит вывода!</color>");
PrintWarning($"[CryptoTrade] Блокировка: {player.displayName} пытался вывести {amount}, доступно {remainingLimit}");
return;
}
}
var oldBalance = GetBalance(player);
try
{
IQEconomic.Call("API_REMOVE_BALANCE", player.userID.Get(), amount);
}
catch (Exception ex)
{
PrintError($"Ошибка при списании: {ex.Message}");
ShowMainUI(player, wallet, amount, "<color=red>Ошибка при списании.</color>", true);
return;
}
var newBalance = GetBalance(player);
if (newBalance >= oldBalance)
{
ShowMainUI(player, wallet, amount, "<color=red>Не удалось списать средства.</color>", true);
return;
}
// РАСЧЁТ В LONG ЧТОБЫ ИЗБЕЖАТЬ ПЕРЕПОЛНЕНИЯ
long solanaAmount = (long)amount * (long)_config.Course;
PrintWarning($"[CryptoTrade] Расчёт: {amount} * {_config.Course} = {solanaAmount}");
AddToWeeklyLimit(player.userID.Get(), amount);
var withdrawal = new WithdrawalData
{
PlayerName = player.displayName,
PlayerId = player.userID.Get(),
Wallet = wallet,
Amount = amount,
SolanaAmount = solanaAmount,
Timestamp = DateTime.Now,
Status = "Pending"
};
_data.Withdrawals.Add(withdrawal);
SaveData();
AddWithdrawalToHistory(player, amount, solanaAmount);
var message = $"💰 *Новый запрос на вывод*\n\n" +
$"👤 Игрок: `{player.displayName}`\n" +
$"🆔 SteamID: `{player.userID}`\n" +
$"💵 Сумма: `{amount}` игровой валюты\n" +
$"🪙 Solana: `{solanaAmount}`\n" +
$"📫 Кошелек: `{wallet}`\n" +
$"✅ Проверка: Пройдена\n" +
$"🕐 Время: `{DateTime.Now:dd.MM.yyyy HH:mm:ss}`";
SendTgMessage(message);
player.ChatMessage($"<color=green>✅ Запрос на вывод {solanaAmount} Solana успешно создан!</color>");
ShowMainUI(player, wallet, _config.MinCoin, "", true);
}
private bool IsValidSolanaAddress(string address)
{
if (string.IsNullOrEmpty(address)) return false;
var base58Pattern = "^[1-9A-HJ-NP-Za-km-z]+$";
if (!Regex.IsMatch(address, base58Pattern)) return false;
if (address.Length < 32 || address.Length > 44) return false;
return true;
}
public void SendTgMessage(string message)
{
if (string.IsNullOrEmpty(_config.ApiKey) || _config.ApiKey == "ВАШ_ТОКЕН_ЗДЕСЬ")
{
PrintError("API ключ Telegram не настроен!");
return;
}
var url = $"https://api.telegram.org/bot{_config.ApiKey}/sendMessage" +
$"?chat_id={_config.ChatId}" +
$"&text={Uri.EscapeDataString(message)}" +
$"&parse_mode=Markdown";
webrequest.Enqueue(url, "", (code, response) =>
{
if (code != 200)
{
PrintError($"Ошибка отправки в Telegram. Код: {code}");
}
else
{
if (_config.EnableFileLogging)
{
LogToFile("telegram", $"[{DateTime.Now}] Сообщение отправлено", this);
}
}
}, this);
}
#endregion
#region UI
private void ShowMainUI(BasePlayer player, string wallet, int amount, string message, bool isLinked)
{
var container = new CuiElementContainer();
container.Add(new CuiPanel
{
CursorEnabled = true,
KeyboardEnabled = true,
Image = { Color = "0 0 0 0.9113" },
RectTransform =
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
OffsetMin = "-293.139 -149.992", OffsetMax = "293.139 150.008"
}
}, "Overlay", "Panel_305");
container.Add(new CuiButton()
{
RectTransform = { AnchorMin = "-100 -100", AnchorMax = "100 100", OffsetMax = "0 0" },
Button = {
Color = "0 0 0 0.8",
Sprite = "assets/content/ui/uibackgroundblur-ingamemenu.mat",
Close = "Panel_305",
Command = "UI_CRYPTOTRADE CLOSE"
},
Text = { Text = "" }
}, "Panel_305");
container.Add(new CuiElement
{
Name = "Label_Title",
Parent = "Panel_305",
Components =
{
new CuiTextComponent
{
Text = "ОБМЕН ИГРОВОЙ ВАЛЮТЫ",
Font = "robotocondensed-bold.ttf",
FontSize = 25,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 1"
},
new CuiRectTransformComponent
{
AnchorMin = "0.5 1", AnchorMax = "0.5 1",
OffsetMin = "-188.658 -47.178", OffsetMax = "188.658 0"
}
}
});
if (!string.IsNullOrEmpty(message))
{
container.Add(new CuiElement
{
Name = "MessageLabel",
Parent = "Panel_305",
Components =
{
new CuiTextComponent
{
Text = message,
Font = "robotocondensed-bold.ttf",
FontSize = 14,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 1"
},
new CuiRectTransformComponent
{
AnchorMin = "0.5 1", AnchorMax = "0.5 1",
OffsetMin = "-250 -70", OffsetMax = "250 -48"
}
}
});
}
container.Add(new CuiPanel
{
CursorEnabled = true,
Image = { Color = "0.3490566 0.3490566 0.3490566 1" },
RectTransform =
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
OffsetMin = "-281.867 59.258", OffsetMax = "107.267 80.742"
}
}, "Panel_305", "Panel_WalletInput");
container.Add(new CuiElement
{
Name = "Label_WalletTitle",
Parent = "Panel_WalletInput",
Components =
{
new CuiTextComponent
{
Text = isLinked ? "Привязанный кошелек Solana" : "Введите адрес кошелька Solana",
Font = "robotocondensed-regular.ttf",
FontSize = 10,
Align = TextAnchor.MiddleLeft,
Color = "1 1 1 1"
},
new CuiRectTransformComponent
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
OffsetMin = "-191.034 13.845", OffsetMax = "83.234 30.154"
}
}
});
container.Add(new CuiElement
{
Name = "Input_Wallet",
Parent = "Panel_WalletInput",
Components =
{
new CuiInputFieldComponent
{
Color = "1 1 1 1",
Text = wallet,
Font = "robotocondensed-regular.ttf",
FontSize = 14,
Align = TextAnchor.MiddleLeft,
CharsLimit = 44,
IsPassword = false,
Command = $"UI_CRYPTOTRADE ADDRESS {amount}"
},
new CuiRectTransformComponent
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
OffsetMin = "-188.067 -10.742", OffsetMax = "194.568 10.741"
}
}
});
if (isLinked)
{
container.Add(new CuiButton
{
Button = {
Color = "0.8 0.2 0.2 1",
Command = ""
},
Text =
{
Text = "✓ КОШЕЛЕК ПРИВЯЗАН",
Font = "robotocondensed-bold.ttf",
FontSize = 12,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 1"
},
RectTransform =
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
OffsetMin = "207.388 -10.742", OffsetMax = "365.012 10.742"
}
}, "Panel_WalletInput", "Button_WalletStatus");
}
else
{
container.Add(new CuiButton
{
Button = {
Color = "0.2847544 0.745283 0.3062837 1",
Command = $"UI_CRYPTOTRADE LINK_WALLET {wallet}"
},
Text =
{
Text = "ПОДТВЕРДИТЬ КОШЕЛЕК",
Font = "robotocondensed-bold.ttf",
FontSize = 12,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 1"
},
RectTransform =
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
OffsetMin = "207.388 -10.742", OffsetMax = "365.012 10.742"
}
}, "Panel_WalletInput", "Button_WalletStatus");
}
if (isLinked)
{
container.Add(new CuiButton
{
Button = {
Color = "0.2847544 0.745283 0.3062837 1",
Command = "UI_CRYPTOTRADE UNLINK_WALLET"
},
Text =
{
Text = "ОТВЯЗАТЬ КОШЕЛЕК",
Font = "robotocondensed-bold.ttf",
FontSize = 12,
Align = TextAnchor.MiddleCenter,
Color = "1 1 1 1"
},
RectTransform =
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
OffsetMin = "119.888 34.516", OffsetMax = "278.012 56"
}
}, "Panel_305", "Button_Unlink");
}
container.Add(new CuiPanel
{
CursorEnabled = true,
Image = { Color = "0.3490566 0.3490566 0.3490566 1" },
RectTransform =
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
OffsetMin = "-281.867 -66.842", OffsetMax = "107.267 -45.358"
}
}, "Panel_305", "Panel_AmountInput");
var balance = GetBalance(player);
container.Add(new CuiElement
{
Name = "Label_AmountTitle",
Parent = "Panel_AmountInput",
Components =
{
new CuiTextComponent
{
Text = $"Укажите количество валюты (доступно: {balance})",
Font = "robotocondensed-regular.ttf",
FontSize = 10,
Align = TextAnchor.MiddleLeft,
Color = "1 1 1 1"
},
new CuiRectTransformComponent
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
OffsetMin = "-191.034 13.845", OffsetMax = "140.857 30.154"
}
}
});
container.Add(new CuiElement
{
Name = "Input_Amount",
Parent = "Panel_AmountInput",
Components =
// ... [Code preview truncated due to length. Full source in attached file] ...