initial commit

This commit is contained in:
sanjoy 2025-07-25 16:05:44 +05:30
parent 45153699a2
commit f6b69e7737
26 changed files with 1976 additions and 0 deletions

25
WpfApp1TRC20.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33815.320
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApp1TRC20", "WpfApp1TRC20\WpfApp1TRC20.csproj", "{B5C8DC98-1B86-4718-B4D2-CE2EF908249E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B5C8DC98-1B86-4718-B4D2-CE2EF908249E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5C8DC98-1B86-4718-B4D2-CE2EF908249E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5C8DC98-1B86-4718-B4D2-CE2EF908249E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5C8DC98-1B86-4718-B4D2-CE2EF908249E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CE428E59-6FEF-42AE-BF99-6493814BA044}
EndGlobalSection
EndGlobal

14
WpfApp1TRC20/App.xaml Normal file
View File

@ -0,0 +1,14 @@
<Application x:Class="TRC20TokenManager.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Dark" PrimaryColor="Teal" SecondaryColor="Lime" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

71
WpfApp1TRC20/App.xaml.cs Normal file
View File

@ -0,0 +1,71 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Windows;
using Serilog;
using System;
using WpfApp1TRC20;
using WpfApp1TRC20.Views;
using WpfApp1TRC20.Data;
using WpfApp1TRC20.Services;
using WpfApp1TRC20.ViewModels;
namespace WpfApp1TRC20
{
public partial class App : Application
{
private IHost _host;
protected override async void OnStartup(StartupEventArgs e)
{
// Configure Serilog
Log.Logger = new LoggerConfiguration()
.WriteTo.File("logs/app-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
_host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
// Database
services.AddDbContext<AppDbContext>();
// Services
services.AddSingleton<ITronService, TronService>();
services.AddScoped<IDataService, DataService>();
// ViewModels
services.AddTransient<MainViewModel>();
// Views
services.AddTransient<MainWindow>();
// Logging
services.AddLogging(a =>
a.AddProvider( ));
})
.Build();
await _host.StartAsync();
// Ensure database is created
using (var scope = _host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await context.Database.EnsureCreatedAsync();
}
var mainWindow = _host.Services.GetRequiredService<MainWindow>();
mainWindow.Show();
base.OnStartup(e);
}
protected override async void OnExit(ExitEventArgs e)
{
await _host?.StopAsync();
_host?.Dispose();
Log.CloseAndFlush();
base.OnExit(e);
}
}
}

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,116 @@
namespace TRC20TokenManager.Contracts
{
public static class TimedTRC20Contract
{
public const string Bytecode = @"
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
}
contract TimedTRC20 is IERC20 {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
uint8 private _decimals;
uint256 public immutable expiryTimestamp;
address private _owner;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(
string memory name_,
string memory symbol_,
uint8 decimals_,
uint256 totalSupply_,
uint256 expiryDays
) {
_name = name_;
_symbol = symbol_;
_decimals = decimals_;
_totalSupply = totalSupply_ * 10**decimals_;
_owner = msg.sender;
_balances[msg.sender] = _totalSupply;
expiryTimestamp = block.timestamp + (expiryDays * 1 days);
}
modifier notExpired() {
require(block.timestamp < expiryTimestamp, ""Token has expired"");
_;
}
function name() public view returns (string memory) { return _name; }
function symbol() public view returns (string memory) { return _symbol; }
function decimals() public view returns (uint8) { return _decimals; }
function totalSupply() public view override returns (uint256) { return _totalSupply; }
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
function transfer(address recipient, uint256 amount) public override notExpired returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
function allowance(address owner, address spender) public view override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public override notExpired returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) public override notExpired returns (bool) {
uint256 currentAllowance = _allowances[sender][msg.sender];
require(currentAllowance >= amount, ""ERC20: transfer amount exceeds allowance"");
_transfer(sender, recipient, amount);
_approve(sender, msg.sender, currentAllowance - amount);
return true;
}
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), ""ERC20: transfer from the zero address"");
require(recipient != address(0), ""ERC20: transfer to the zero address"");
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, ""ERC20: transfer amount exceeds balance"");
_balances[sender] = senderBalance - amount;
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
}
function _approve(address owner, address spender, uint256 amount) internal {
require(owner != address(0), ""ERC20: approve from the zero address"");
require(spender != address(0), ""ERC20: approve to the zero address"");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function isExpired() public view returns (bool) {
return block.timestamp >= expiryTimestamp;
}
function timeRemaining() public view returns (uint256) {
if (block.timestamp >= expiryTimestamp) return 0;
return expiryTimestamp - block.timestamp;
}
}";
}
}

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp1TRC20.Data
{
public class AppDbContext : DbContext
{
public DbSet<TokenInfo> Tokens { get; set; }
public DbSet<WalletAccount> Wallets { get; set; }
public DbSet<TransactionRecord> Transactions { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var dbPath = Path.Combine(appDataPath, "TRC20TokenManager", "app.db");
Directory.CreateDirectory(Path.GetDirectoryName(dbPath));
optionsBuilder.UseSqlite($"Data Source={dbPath}");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TokenInfo>()
.Property(e => e.TotalSupply)
.HasConversion<string>();
modelBuilder.Entity<TransactionRecord>()
.Property(e => e.Amount)
.HasConversion<string>();
modelBuilder.Entity<WalletAccount>()
.Property(e => e.TrxBalance)
.HasConversion<string>();
}
}
}

View File

@ -0,0 +1,11 @@
namespace WpfApp1TRC20.Data
{
public class TokenBalance
{
public string TokenSymbol { get; set; }
public string ContractAddress { get; set; }
public decimal Balance { get; set; }
public int Decimals { get; set; }
public string WalletAddress { get; set; }
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp1TRC20.Data
{
public class TokenCreationParams
{
[Required]
public string Name { get; set; }
[Required]
public string Symbol { get; set; }
[Range(0, 18)]
public int Decimals { get; set; } = 18;
[Range(1, double.MaxValue)]
public decimal TotalSupply { get; set; }
[Range(10, 90)]
public int ExpiryDays { get; set; } = 30;
public string OwnerAddress { get; set; }
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp1TRC20.Data
{
public class TokenInfo
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Symbol { get; set; }
public int Decimals { get; set; }
public decimal TotalSupply { get; set; }
public string ContractAddress { get; set; }
public string OwnerAddress { get; set; }
public DateTime CreationDate { get; set; }
public DateTime ExpiryDate { get; set; }
public int ExpiryDays { get; set; }
public bool IsExpired => DateTime.UtcNow > ExpiryDate;
public string TransactionHash { get; set; }
public TokenStatus Status { get; set; }
}
}

View File

@ -0,0 +1,10 @@
namespace WpfApp1TRC20.Data
{
public enum TokenStatus
{
Creating,
Active,
Expired,
Failed
}
}

View File

@ -0,0 +1,19 @@
using System;
namespace WpfApp1TRC20.Data
{
public class TransactionRecord
{
public int Id { get; set; }
public string TransactionHash { get; set; }
public string FromAddress { get; set; }
public string ToAddress { get; set; }
public decimal Amount { get; set; }
public string TokenSymbol { get; set; }
public string ContractAddress { get; set; }
public DateTime Timestamp { get; set; }
public TransactionStatus Status { get; set; }
public string ErrorMessage { get; set; }
public decimal GasFee { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace WpfApp1TRC20.Data
{
public enum TransactionStatus
{
Pending,
Confirmed,
Failed
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace WpfApp1TRC20.Data
{
public class WalletAccount
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Address { get; set; }
public string EncryptedPrivateKey { get; set; }
public DateTime CreatedDate { get; set; }
public decimal TrxBalance { get; set; }
public bool IsImported { get; set; }
}
}

View File

@ -0,0 +1,147 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfApp1TRC20.Data;
namespace WpfApp1TRC20.Services
{
public class DataService : IDataService
{
private readonly AppDbContext _context;
private readonly ITronService _tronService;
public DataService(AppDbContext context, ITronService tronService)
{
_context = context;
_tronService = tronService;
}
public async Task<List<TokenInfo>> GetTokensAsync()
{
return await _context.Tokens.ToListAsync();
}
public async Task<List<WalletAccount>> GetWalletsAsync()
{
var wallets = await _context.Wallets.ToListAsync();
// Update TRX balances
foreach (var wallet in wallets)
{
try
{
wallet.TrxBalance = await _tronService.GetTrxBalanceAsync(wallet.Address);
}
catch
{
// Continue if balance check fails
}
}
return wallets;
}
public async Task<List<TransactionRecord>> GetTransactionsAsync()
{
return await _context.Transactions
.OrderByDescending(t => t.Timestamp)
.ToListAsync();
}
public async Task<TokenInfo> SaveTokenAsync(TokenInfo token)
{
if (token.Id == 0)
{
_context.Tokens.Add(token);
}
else
{
_context.Tokens.Update(token);
}
await _context.SaveChangesAsync();
return token;
}
public async Task<WalletAccount> SaveWalletAsync(WalletAccount wallet)
{
if (wallet.Id == 0)
{
_context.Wallets.Add(wallet);
}
else
{
_context.Wallets.Update(wallet);
}
await _context.SaveChangesAsync();
return wallet;
}
public async Task<TransactionRecord> SaveTransactionAsync(TransactionRecord transaction)
{
if (transaction.Id == 0)
{
_context.Transactions.Add(transaction);
}
else
{
_context.Transactions.Update(transaction);
}
await _context.SaveChangesAsync();
return transaction;
}
public async Task DeleteTokenAsync(int tokenId)
{
var token = await _context.Tokens.FindAsync(tokenId);
if (token != null)
{
_context.Tokens.Remove(token);
await _context.SaveChangesAsync();
}
}
public async Task DeleteWalletAsync(int walletId)
{
var wallet = await _context.Wallets.FindAsync(walletId);
if (wallet != null)
{
_context.Wallets.Remove(wallet);
await _context.SaveChangesAsync();
}
}
public async Task<List<TokenBalance>> GetTokenBalancesAsync(string walletAddress)
{
var tokens = await _context.Tokens.Where(t => t.Status == TokenStatus.Active).ToListAsync();
var balances = new List<TokenBalance>();
foreach (var token in tokens)
{
try
{
var balance = await _tronService.GetTokenBalanceAsync(token.ContractAddress, walletAddress);
balances.Add(new TokenBalance
{
TokenSymbol = token.Symbol,
ContractAddress = token.ContractAddress,
Balance = balance,
Decimals = token.Decimals,
WalletAddress = walletAddress
});
}
catch
{
// Continue if balance check fails
}
}
return balances;
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfApp1TRC20.Data;
namespace WpfApp1TRC20.Services
{
public interface IDataService
{
Task<List<TokenInfo>> GetTokensAsync();
Task<List<WalletAccount>> GetWalletsAsync();
Task<List<TransactionRecord>> GetTransactionsAsync();
Task<TokenInfo> SaveTokenAsync(TokenInfo token);
Task<WalletAccount> SaveWalletAsync(WalletAccount wallet);
Task<TransactionRecord> SaveTransactionAsync(TransactionRecord transaction);
Task DeleteTokenAsync(int tokenId);
Task DeleteWalletAsync(int walletId);
Task<List<TokenBalance>> GetTokenBalancesAsync(string walletAddress);
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfApp1TRC20.Data;
namespace WpfApp1TRC20.Services
{
public interface ITronService
{
Task<string> CreateTokenAsync(TokenCreationParams parameters, string ownerPrivateKey);
Task<string> TransferTokenAsync(string contractAddress, string fromPrivateKey, string toAddress, decimal amount);
Task<decimal> GetTokenBalanceAsync(string contractAddress, string address);
Task<decimal> GetTrxBalanceAsync(string address);
Task<TransactionRecord> GetTransactionAsync(string txHash);
Task<bool> IsTokenExpiredAsync(string contractAddress);
WalletAccount CreateWallet(string name);
WalletAccount ImportWallet(string name, string privateKey);
string DecryptPrivateKey(string encryptedPrivateKey);
}
}

View File

@ -0,0 +1,255 @@
using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
using TronNet;
using TronNet.Accounts;
using WpfApp1TRC20.Data;
using TransactionStatus = WpfApp1TRC20.Data.TransactionStatus;
namespace WpfApp1TRC20.Services
{
public class TronService : ITronService
{
private readonly ITronClient _tronClient;
private readonly ILogger<TronService> _logger;
private const string TRON_MAINNET = "https://api.trongrid.io";
private const string TRON_TESTNET = "https://api.shasta.trongrid.io";
public TronService(ILogger<TronService> logger)
{
_logger = logger;
// Use testnet for development, mainnet for production
var httpClient = new HttpClient();
_tronClient = new TronClient(httpClient, TRON_TESTNET);
}
public async Task<string> CreateTokenAsync(TokenCreationParams parameters, string ownerPrivateKey)
{
try
{
var account = new TronAccount(ownerPrivateKey);
// Compile and deploy the smart contract
var contractData = CompileContract(parameters);
var transaction = await _tronClient.CreateTransactionAsync(
account.Address,
contractData,
0,
"Smart Contract Creation"
);
var signedTransaction = transaction.Sign(account);
var result = await _tronClient.BroadcastTransactionAsync(signedTransaction);
_logger.LogInformation($"Token creation transaction broadcast: {result.Txid}");
return result.Txid;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating token");
throw;
}
}
public async Task<string> TransferTokenAsync(string contractAddress, string fromPrivateKey, string toAddress, decimal amount)
{
try
{
var fromAccount = new TronAccount(fromPrivateKey);
// Check if token is expired first
if (await IsTokenExpiredAsync(contractAddress))
{
throw new InvalidOperationException("Token has expired and cannot be transferred");
}
var contract = _tronClient.GetContract(contractAddress);
var transferFunction = contract.GetFunction("transfer");
var transaction = await transferFunction.CreateTransactionAsync(
fromAccount.Address,
toAddress,
(BigInteger)(amount * (decimal)Math.Pow(10, 18)) // Assuming 18 decimals
);
var signedTransaction = transaction.Sign(fromAccount);
var result = await _tronClient.BroadcastTransactionAsync(signedTransaction);
_logger.LogInformation($"Token transfer transaction broadcast: {result.Txid}");
return result.Txid;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error transferring tokens");
throw;
}
}
public async Task<decimal> GetTokenBalanceAsync(string contractAddress, string address)
{
try
{
var contract = _tronClient.GetContract(contractAddress);
var balanceFunction = contract.GetFunction("balanceOf");
var balance = await balanceFunction.CallAsync<BigInteger>(address);
return (decimal)balance / (decimal)Math.Pow(10, 18); // Assuming 18 decimals
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting token balance");
return 0;
}
}
public async Task<decimal> GetTrxBalanceAsync(string address)
{
try
{
var account = await _tronClient.GetAccountAsync(address);
return (decimal)account.Balance / 1_000_000; // TRX has 6 decimals
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting TRX balance");
return 0;
}
}
public async Task<TransactionRecord> GetTransactionAsync(string txHash)
{
try
{
var transaction = await _tronClient.GetTransactionByIdAsync(txHash);
var transactionInfo = await _tronClient.GetTransactionInfoByIdAsync(txHash);
return new TransactionRecord
{
TransactionHash = txHash,
Status = transactionInfo.Receipt.Result == "SUCCESS" ?
TransactionStatus.Confirmed : TransactionStatus.Failed,
Timestamp = DateTimeOffset.FromUnixTimeMilliseconds(transaction.RawData.Timestamp).DateTime,
GasFee = (decimal)transactionInfo.Fee / 1_000_000
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting transaction info");
throw;
}
}
public async Task<bool> IsTokenExpiredAsync(string contractAddress)
{
try
{
var contract = _tronClient.GetContract(contractAddress);
var isExpiredFunction = contract.GetFunction("isExpired");
var isExpired = await isExpiredFunction.CallAsync<bool>();
return isExpired;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking token expiry");
return false;
}
}
public WalletAccount CreateWallet(string name)
{
var account = TronAccount.GenerateAccount();
return new WalletAccount
{
Name = name,
Address = account.Address,
EncryptedPrivateKey = EncryptPrivateKey(account.PrivateKey),
CreatedDate = DateTime.UtcNow,
IsImported = false
};
}
public WalletAccount ImportWallet(string name, string privateKey)
{
try
{
var account = new TronAccount(privateKey);
return new WalletAccount
{
Name = name,
Address = account.Address,
EncryptedPrivateKey = EncryptPrivateKey(privateKey),
CreatedDate = DateTime.UtcNow,
IsImported = true
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error importing wallet");
throw new ArgumentException("Invalid private key");
}
}
public string DecryptPrivateKey(string encryptedPrivateKey)
{
try
{
var encryptedBytes = Convert.FromBase64String(encryptedPrivateKey);
var decryptedBytes = ProtectedData.Unprotect(
encryptedBytes,
null,
DataProtectionScope.CurrentUser
);
return Encoding.UTF8.GetString(decryptedBytes);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error decrypting private key");
throw;
}
}
private string EncryptPrivateKey(string privateKey)
{
var dataBytes = Encoding.UTF8.GetBytes(privateKey);
var encryptedBytes = ProtectedData.Protect(
dataBytes,
null,
DataProtectionScope.CurrentUser
);
return Convert.ToBase64String(encryptedBytes);
}
private string CompileContract(TokenCreationParams parameters)
{
// In a real implementation, you would use a Solidity compiler
// For now, we'll use pre-compiled bytecode with constructor parameters
// This is a simplified approach - in production, use tools like Nethereum.Contracts
var constructorParams = EncodeConstructorParameters(
parameters.Name,
parameters.Symbol,
(byte)parameters.Decimals,
parameters.TotalSupply,
parameters.ExpiryDays
);
return TRC20TokenManager.Contracts.TimedTRC20Contract.Bytecode + constructorParams;
}
private string EncodeConstructorParameters(string name, string symbol, byte decimals, decimal totalSupply, int expiryDays)
{
// Simplified ABI encoding - in production use proper ABI encoding library
// This would need to be implemented properly using ABI encoding standards
return "";
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace WpfApp1TRC20.Utils
{
public class InverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
return !boolValue;
return true;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
return !boolValue;
return false;
}
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using WpfApp1TRC20.Data;
namespace WpfApp1TRC20.Utils
{
public class StatusToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is TransactionStatus status)
{
return status switch
{
TransactionStatus.Confirmed => "Green",
TransactionStatus.Pending => "Orange",
TransactionStatus.Failed => "Red",
_ => "Gray"
};
}
return "Gray";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,445 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using WpfApp1TRC20.Data;
using WpfApp1TRC20.Services;
using TransactionStatus = WpfApp1TRC20.Data.TransactionStatus;
namespace WpfApp1TRC20.ViewModels
{
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
private readonly ITronService _tronService;
private readonly ILogger<MainViewModel> _logger;
private TokenInfo _selectedToken;
private WalletAccount _selectedWallet;
private string _selectedTabItem = "Dashboard";
public MainViewModel(IDataService dataService, ITronService tronService, ILogger<MainViewModel> logger)
{
_dataService = dataService;
_tronService = tronService;
_logger = logger;
Tokens = new ObservableCollection<TokenInfo>();
Wallets = new ObservableCollection<WalletAccount>();
Transactions = new ObservableCollection<TransactionRecord>();
TokenBalances = new ObservableCollection<TokenBalance>();
// Commands
CreateTokenCommand = new RelayCommand(async () => await CreateTokenAsync());
CreateWalletCommand = new RelayCommand(async () => await CreateWalletAsync());
ImportWalletCommand = new RelayCommand(async () => await ImportWalletAsync());
TransferTokenCommand = new RelayCommand(async () => await TransferTokenAsync(), CanTransferToken);
RefreshDataCommand = new RelayCommand(async () => await RefreshDataAsync());
// Load initial data
_ = Task.Run(RefreshDataAsync);
}
// Properties
public ObservableCollection<TokenInfo> Tokens { get; }
public ObservableCollection<WalletAccount> Wallets { get; }
public ObservableCollection<TransactionRecord> Transactions { get; }
public ObservableCollection<TokenBalance> TokenBalances { get; }
public TokenInfo SelectedToken
{
get => _selectedToken;
set => SetProperty(ref _selectedToken, value);
}
public WalletAccount SelectedWallet
{
get => _selectedWallet;
set
{
SetProperty(ref _selectedWallet, value);
if (value != null)
_ = Task.Run(() => LoadTokenBalancesAsync(value.Address));
}
}
public string SelectedTabItem
{
get => _selectedTabItem;
set => SetProperty(ref _selectedTabItem, value);
}
// Token Creation Properties
private string _newTokenName;
public string NewTokenName
{
get => _newTokenName;
set => SetProperty(ref _newTokenName, value);
}
private string _newTokenSymbol;
public string NewTokenSymbol
{
get => _newTokenSymbol;
set => SetProperty(ref _newTokenSymbol, value);
}
private int _newTokenDecimals = 18;
public int NewTokenDecimals
{
get => _newTokenDecimals;
set => SetProperty(ref _newTokenDecimals, value);
}
private decimal _newTokenSupply = 1000000;
public decimal NewTokenSupply
{
get => _newTokenSupply;
set => SetProperty(ref _newTokenSupply, value);
}
private int _newTokenExpiryDays = 30;
public int NewTokenExpiryDays
{
get => _newTokenExpiryDays;
set => SetProperty(ref _newTokenExpiryDays, value);
}
// Transfer Properties
private string _transferToAddress;
public string TransferToAddress
{
get => _transferToAddress;
set => SetProperty(ref _transferToAddress, value);
}
private decimal _transferAmount;
public decimal TransferAmount
{
get => _transferAmount;
set => SetProperty(ref _transferAmount, value);
}
private TokenBalance _selectedTokenBalance;
public TokenBalance SelectedTokenBalance
{
get => _selectedTokenBalance;
set => SetProperty(ref _selectedTokenBalance, value);
}
private string _newWalletName;
public string NewWalletName
{
get => _newWalletName;
set => SetProperty(ref _newWalletName, value);
}
private string _importPrivateKey;
public string ImportPrivateKey
{
get => _importPrivateKey;
set => SetProperty(ref _importPrivateKey, value);
}
private bool _isLoading;
public bool IsLoading
{
get => _isLoading;
set => SetProperty(ref _isLoading, value);
}
private string _statusMessage;
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
// Commands
public ICommand CreateTokenCommand { get; }
public ICommand CreateWalletCommand { get; }
public ICommand ImportWalletCommand { get; }
public ICommand TransferTokenCommand { get; }
public ICommand RefreshDataCommand { get; }
// Methods
private async Task CreateTokenAsync()
{
if (string.IsNullOrWhiteSpace(NewTokenName) || string.IsNullOrWhiteSpace(NewTokenSymbol) || SelectedWallet == null)
{
StatusMessage = "Please fill all required fields and select a wallet";
return;
}
IsLoading = true;
StatusMessage = "Creating token...";
try
{
var parameters = new TokenCreationParams
{
Name = NewTokenName,
Symbol = NewTokenSymbol,
Decimals = NewTokenDecimals,
TotalSupply = NewTokenSupply,
ExpiryDays = NewTokenExpiryDays,
OwnerAddress = SelectedWallet.Address
};
var privateKey = _tronService.DecryptPrivateKey(SelectedWallet.EncryptedPrivateKey);
var txHash = await _tronService.CreateTokenAsync(parameters, privateKey);
var token = new TokenInfo
{
Name = parameters.Name,
Symbol = parameters.Symbol,
Decimals = parameters.Decimals,
TotalSupply = parameters.TotalSupply,
OwnerAddress = parameters.OwnerAddress,
CreationDate = DateTime.UtcNow,
ExpiryDate = DateTime.UtcNow.AddDays(parameters.ExpiryDays),
ExpiryDays = parameters.ExpiryDays,
TransactionHash = txHash,
Status = TokenStatus.Creating
};
await _dataService.SaveTokenAsync(token);
Tokens.Add(token);
// Clear form
NewTokenName = "";
NewTokenSymbol = "";
NewTokenDecimals = 18;
NewTokenSupply = 1000000;
NewTokenExpiryDays = 30;
StatusMessage = $"Token creation initiated. Transaction: {txHash}";
// Monitor transaction status
_ = Task.Run(() => MonitorTokenCreation(token));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating token");
StatusMessage = $"Error creating token: {ex.Message}";
}
finally
{
IsLoading = false;
}
}
private async Task CreateWalletAsync()
{
if (string.IsNullOrWhiteSpace(NewWalletName))
{
StatusMessage = "Please enter a wallet name";
return;
}
try
{
var wallet = _tronService.CreateWallet(NewWalletName);
await _dataService.SaveWalletAsync(wallet);
Wallets.Add(wallet);
NewWalletName = "";
StatusMessage = $"Wallet '{wallet.Name}' created successfully";
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating wallet");
StatusMessage = $"Error creating wallet: {ex.Message}";
}
}
private async Task ImportWalletAsync()
{
if (string.IsNullOrWhiteSpace(NewWalletName) || string.IsNullOrWhiteSpace(ImportPrivateKey))
{
StatusMessage = "Please enter wallet name and private key";
return;
}
try
{
var wallet = _tronService.ImportWallet(NewWalletName, ImportPrivateKey);
await _dataService.SaveWalletAsync(wallet);
Wallets.Add(wallet);
NewWalletName = "";
ImportPrivateKey = "";
StatusMessage = $"Wallet '{wallet.Name}' imported successfully";
}
catch (Exception ex)
{
_logger.LogError(ex, "Error importing wallet");
StatusMessage = $"Error importing wallet: {ex.Message}";
}
}
private bool CanTransferToken()
{
return SelectedWallet != null && SelectedTokenBalance != null &&
!string.IsNullOrWhiteSpace(TransferToAddress) && TransferAmount > 0;
}
private async Task TransferTokenAsync()
{
if (!CanTransferToken()) return;
IsLoading = true;
StatusMessage = "Transferring tokens...";
try
{
var privateKey = _tronService.DecryptPrivateKey(SelectedWallet.EncryptedPrivateKey);
var txHash = await _tronService.TransferTokenAsync(
SelectedTokenBalance.ContractAddress,
privateKey,
TransferToAddress,
TransferAmount
);
var transaction = new TransactionRecord
{
TransactionHash = txHash,
FromAddress = SelectedWallet.Address,
ToAddress = TransferToAddress,
Amount = TransferAmount,
TokenSymbol = SelectedTokenBalance.TokenSymbol,
ContractAddress = SelectedTokenBalance.ContractAddress,
Timestamp = DateTime.UtcNow,
Status = TransactionStatus.Pending
};
await _dataService.SaveTransactionAsync(transaction);
Transactions.Insert(0, transaction);
// Clear form
TransferToAddress = "";
TransferAmount = 0;
StatusMessage = $"Transfer initiated. Transaction: {txHash}";
// Refresh balances
await LoadTokenBalancesAsync(SelectedWallet.Address);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error transferring tokens");
StatusMessage = $"Error transferring tokens: {ex.Message}";
}
finally
{
IsLoading = false;
}
}
private async Task RefreshDataAsync()
{
try
{
var tokens = await _dataService.GetTokensAsync();
var wallets = await _dataService.GetWalletsAsync();
var transactions = await _dataService.GetTransactionsAsync();
Application.Current.Dispatcher.Invoke(() =>
{
Tokens.Clear();
foreach (var token in tokens) Tokens.Add(token);
Wallets.Clear();
foreach (var wallet in wallets) Wallets.Add(wallet);
Transactions.Clear();
foreach (var transaction in transactions) Transactions.Add(transaction);
});
if (SelectedWallet != null)
{
await LoadTokenBalancesAsync(SelectedWallet.Address);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error refreshing data");
}
}
private async Task LoadTokenBalancesAsync(string walletAddress)
{
try
{
var balances = await _dataService.GetTokenBalancesAsync(walletAddress);
Application.Current.Dispatcher.Invoke(() =>
{
TokenBalances.Clear();
foreach (var balance in balances) TokenBalances.Add(balance);
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading token balances");
}
}
private async Task MonitorTokenCreation(TokenInfo token)
{
// Wait for transaction confirmation
for (int i = 0; i < 30; i++) // Wait up to 5 minutes
{
await Task.Delay(10000); // Wait 10 seconds
try
{
var txInfo = await _tronService.GetTransactionAsync(token.TransactionHash);
if (txInfo.Status == TransactionStatus.Confirmed)
{
// Extract contract address from transaction (simplified)
// In real implementation, parse the transaction receipt
token.ContractAddress = GenerateContractAddress(token.TransactionHash);
token.Status = TokenStatus.Active;
await _dataService.SaveTokenAsync(token);
Application.Current.Dispatcher.Invoke(() =>
{
StatusMessage = $"Token '{token.Symbol}' deployed successfully!";
});
break;
}
else if (txInfo.Status == TransactionStatus.Failed)
{
token.Status = TokenStatus.Failed;
await _dataService.SaveTokenAsync(token);
Application.Current.Dispatcher.Invoke(() =>
{
StatusMessage = $"Token '{token.Symbol}' deployment failed";
});
break;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error monitoring token creation");
}
}
}
private string GenerateContractAddress(string transactionHash)
{
// Simplified contract address generation
// In real implementation, calculate from transaction details
return "T" + transactionHash.Substring(0, 33);
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace WpfApp1TRC20.ViewModels
{
public class RelayCommand : ICommand
{
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Func<Task> execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
public async void Execute(object parameter) => await _execute();
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp1TRC20.ViewModels
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
}
}

View File

@ -0,0 +1,505 @@
<Window x:Class="WpfApp1TRC20.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:vm="clr-namespace:WpfApp1TRC20.ViewModels"
Title="TRC20 Token Manager"
Height="800"
Width="1200"
WindowStartupLocation="CenterScreen"
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="Auto"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="{DynamicResource MaterialDesignFont}">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Header -->
<materialDesign:ColorZone Grid.Row="0"
Mode="PrimaryMid"
Padding="16">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="TRC20 Token Manager"
Style="{DynamicResource MaterialDesignHeadline5TextBlock}"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="1"
Orientation="Horizontal">
<Button Command="{Binding RefreshDataCommand}"
Style="{DynamicResource MaterialDesignIconButton}"
ToolTip="Refresh Data">
<materialDesign:PackIcon Kind="Refresh"/>
</Button>
</StackPanel>
</Grid>
</materialDesign:ColorZone>
<!-- Main Content -->
<TabControl Grid.Row="1"
SelectedValue="{Binding SelectedTabItem}"
Style="{DynamicResource MaterialDesignTabControl}">
<!-- Dashboard Tab -->
<TabItem Header="Dashboard" Name="DashboardTab">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Stats Cards -->
<UniformGrid Grid.Row="0"
Rows="1"
Columns="3"
Margin="0,0,0,16">
<materialDesign:Card Margin="8">
<StackPanel Margin="16">
<TextBlock Text="Active Tokens"
Style="{DynamicResource MaterialDesignSubtitle1TextBlock}"/>
<TextBlock Text="{Binding Tokens.Count}"
Style="{DynamicResource MaterialDesignHeadline4TextBlock}"
Foreground="{DynamicResource PrimaryHueMidBrush}"/>
</StackPanel>
</materialDesign:Card>
<materialDesign:Card Margin="8">
<StackPanel Margin="16">
<TextBlock Text="Wallets"
Style="{DynamicResource MaterialDesignSubtitle1TextBlock}"/>
<TextBlock Text="{Binding Wallets.Count}"
Style="{DynamicResource MaterialDesignHeadline4TextBlock}"
Foreground="{DynamicResource PrimaryHueMidBrush}"/>
</StackPanel>
</materialDesign:Card>
<materialDesign:Card Margin="8">
<StackPanel Margin="16">
<TextBlock Text="Transactions"
Style="{DynamicResource MaterialDesignSubtitle1TextBlock}"/>
<TextBlock Text="{Binding Transactions.Count}"
Style="{DynamicResource MaterialDesignHeadline4TextBlock}"
Foreground="{DynamicResource PrimaryHueMidBrush}"/>
</StackPanel>
</materialDesign:Card>
</UniformGrid>
<!-- Recent Activity -->
<materialDesign:Card Grid.Row="1">
<StackPanel>
<TextBlock Text="Recent Transactions"
Style="{DynamicResource MaterialDesignHeadline6TextBlock}"
Margin="16,16,16,8"/>
<DataGrid ItemsSource="{Binding Transactions}"
MaxHeight="300"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
IsReadOnly="True"
Style="{DynamicResource MaterialDesignDataGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Hash"
Binding="{Binding TransactionHash}"
Width="200"/>
<DataGridTextColumn Header="From"
Binding="{Binding FromAddress}"
Width="150"/>
<DataGridTextColumn Header="To"
Binding="{Binding ToAddress}"
Width="150"/>
<DataGridTextColumn Header="Amount"
Binding="{Binding Amount}"
Width="100"/>
<DataGridTextColumn Header="Token"
Binding="{Binding TokenSymbol}"
Width="80"/>
<DataGridTextColumn Header="Status"
Binding="{Binding Status}"
Width="100"/>
<DataGridTextColumn Header="Time"
Binding="{Binding Timestamp, StringFormat={}{0:MM/dd/yyyy HH:mm}}"
Width="120"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</materialDesign:Card>
</Grid>
</TabItem>
<!-- Tokens Tab -->
<TabItem Header="Tokens" Name="TokensTab">
<Grid Margin="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="300"/>
</Grid.ColumnDefinitions>
<!-- Token List -->
<materialDesign:Card Grid.Column="0" Margin="0,0,8,0">
<StackPanel>
<TextBlock Text="My Tokens"
Style="{DynamicResource MaterialDesignHeadline6TextBlock}"
Margin="16,16,16,8"/>
<DataGrid ItemsSource="{Binding Tokens}"
SelectedItem="{Binding SelectedToken}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
IsReadOnly="True"
Style="{DynamicResource MaterialDesignDataGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Name}"
Width="120"/>
<DataGridTextColumn Header="Symbol"
Binding="{Binding Symbol}"
Width="80"/>
<DataGridTextColumn Header="Supply"
Binding="{Binding TotalSupply}"
Width="120"/>
<DataGridTextColumn Header="Created"
Binding="{Binding CreationDate, StringFormat={}{0:MM/dd/yyyy}}"
Width="100"/>
<DataGridTextColumn Header="Expires"
Binding="{Binding ExpiryDate, StringFormat={}{0:MM/dd/yyyy}}"
Width="100"/>
<DataGridTextColumn Header="Status"
Binding="{Binding Status}"
Width="80"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</materialDesign:Card>
<!-- Create Token Panel -->
<materialDesign:Card Grid.Column="1" Margin="8,0,0,0">
<StackPanel Margin="16">
<TextBlock Text="Create New Token"
Style="{DynamicResource MaterialDesignHeadline6TextBlock}"
Margin="0,0,0,16"/>
<TextBox materialDesign:HintAssist.Hint="Token Name"
Text="{Binding NewTokenName}"
Margin="0,0,0,16"
Style="{DynamicResource MaterialDesignFloatingHintTextBox}"/>
<TextBox materialDesign:HintAssist.Hint="Symbol"
Text="{Binding NewTokenSymbol}"
Margin="0,0,0,16"
Style="{DynamicResource MaterialDesignFloatingHintTextBox}"/>
<TextBox materialDesign:HintAssist.Hint="Decimals"
Text="{Binding NewTokenDecimals}"
Margin="0,0,0,16"
Style="{DynamicResource MaterialDesignFloatingHintTextBox}"/>
<TextBox materialDesign:HintAssist.Hint="Total Supply"
Text="{Binding NewTokenSupply}"
Margin="0,0,0,16"
Style="{DynamicResource MaterialDesignFloatingHintTextBox}"/>
<TextBox materialDesign:HintAssist.Hint="Expiry Days (10-90)"
Text="{Binding NewTokenExpiryDays}"
Margin="0,0,0,16"
Style="{DynamicResource MaterialDesignFloatingHintTextBox}"/>
<ComboBox materialDesign:HintAssist.Hint="Owner Wallet"
ItemsSource="{Binding Wallets}"
SelectedItem="{Binding SelectedWallet}"
DisplayMemberPath="Name"
Margin="0,0,0,16"
Style="{DynamicResource MaterialDesignFloatingHintComboBox}"/>
<Button Content="CREATE TOKEN"
Command="{Binding CreateTokenCommand}"
IsEnabled="{Binding IsLoading, Converter={StaticResource InverseBooleanConverter}}"
Style="{DynamicResource MaterialDesignRaisedButton}"
Margin="0,8,0,0"/>
</StackPanel>
</materialDesign:Card>
</Grid>
</TabItem>
<!-- Wallets Tab -->
<TabItem Header="Wallets" Name="WalletsTab">
<Grid Margin="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="300"/>
</Grid.ColumnDefinitions>
<!-- Wallet List -->
<materialDesign:Card Grid.Column="0" Margin="0,0,8,0">
<StackPanel>
<TextBlock Text="My Wallets"
Style="{DynamicResource MaterialDesignHeadline6TextBlock}"
Margin="16,16,16,8"/>
<DataGrid ItemsSource="{Binding Wallets}"
SelectedItem="{Binding SelectedWallet}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
IsReadOnly="True"
Style="{DynamicResource MaterialDesignDataGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Name}"
Width="120"/>
<DataGridTextColumn Header="Address"
Binding="{Binding Address}"
Width="200"/>
<DataGridTextColumn Header="TRX Balance"
Binding="{Binding TrxBalance, StringFormat={}{0:F2}}"
Width="100"/>
<DataGridTextColumn Header="Created"
Binding="{Binding CreatedDate, StringFormat={}{0:MM/dd/yyyy}}"
Width="100"/>
<DataGridCheckBoxColumn Header="Imported"
Binding="{Binding IsImported}"
Width="80"/>
</DataGrid.Columns>
</DataGrid>
<!-- Token Balances -->
<TextBlock Text="Token Balances"
Style="{DynamicResource MaterialDesignSubtitle1TextBlock}"
Margin="16,16,16,8"/>
<DataGrid ItemsSource="{Binding TokenBalances}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
IsReadOnly="True"
MaxHeight="200"
Style="{DynamicResource MaterialDesignDataGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Token"
Binding="{Binding TokenSymbol}"
Width="80"/>
<DataGridTextColumn Header="Balance"
Binding="{Binding Balance, StringFormat={}{0:F4}}"
Width="120"/>
<DataGridTextColumn Header="Contract"
Binding="{Binding ContractAddress}"
Width="200"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</materialDesign:Card>
<!-- Wallet Actions Panel -->
<materialDesign:Card Grid.Column="1" Margin="8,0,0,0">
<StackPanel Margin="16">
<TextBlock Text="Wallet Actions"
Style="{DynamicResource MaterialDesignHeadline6TextBlock}"
Margin="0,0,0,16"/>
<!-- Create Wallet -->
<GroupBox Header="Create New Wallet" Margin="0,0,0,16">
<StackPanel Margin="8">
<TextBox materialDesign:HintAssist.Hint="Wallet Name"
Text="{Binding NewWalletName}"
Margin="0,0,0,8"
Style="{DynamicResource MaterialDesignFloatingHintTextBox}"/>
<Button Content="CREATE WALLET"
Command="{Binding CreateWalletCommand}"
Style="{DynamicResource MaterialDesignOutlinedButton}"/>
</StackPanel>
</GroupBox>
<!-- Import Wallet -->
<GroupBox Header="Import Wallet">
<StackPanel Margin="8">
<TextBox materialDesign:HintAssist.Hint="Wallet Name"
Text="{Binding NewWalletName}"
Margin="0,0,0,8"
Style="{DynamicResource MaterialDesignFloatingHintTextBox}"/>
<PasswordBox materialDesign:HintAssist.Hint="Private Key"
x:Name="PrivateKeyBox"
Margin="0,0,0,8"
Style="{DynamicResource MaterialDesignFloatingHintPasswordBox}"/>
<Button Content="IMPORT WALLET"
Command="{Binding ImportWalletCommand}"
Style="{DynamicResource MaterialDesignOutlinedButton}"/>
</StackPanel>
</GroupBox>
</StackPanel>
</materialDesign:Card>
</Grid>
</TabItem>
<!-- Transfer Tab -->
<TabItem Header="Transfer" Name="TransferTab">
<Grid Margin="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="400"/>
</Grid.ColumnDefinitions>
<!-- Transaction History -->
<materialDesign:Card Grid.Column="0" Margin="0,0,8,0">
<StackPanel>
<TextBlock Text="Transaction History"
Style="{DynamicResource MaterialDesignHeadline6TextBlock}"
Margin="16,16,16,8"/>
<DataGrid ItemsSource="{Binding Transactions}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
IsReadOnly="True"
Style="{DynamicResource MaterialDesignDataGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Hash"
Binding="{Binding TransactionHash}"
Width="180">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
<Setter Property="ToolTip" Value="{Binding TransactionHash}"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="From"
Binding="{Binding FromAddress}"
Width="120">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="To"
Binding="{Binding ToAddress}"
Width="120">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Amount"
Binding="{Binding Amount, StringFormat={}{0:F4}}"
Width="100"/>
<DataGridTextColumn Header="Token"
Binding="{Binding TokenSymbol}"
Width="80"/>
<DataGridTemplateColumn Header="Status" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border CornerRadius="10"
Padding="8,4">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="Confirmed">
<Setter Property="Background" Value="LightGreen"/>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Pending">
<Setter Property="Background" Value="LightYellow"/>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Failed">
<Setter Property="Background" Value="LightCoral"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{Binding Status}"
FontWeight="Bold"
HorizontalAlignment="Center"/>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Time"
Binding="{Binding Timestamp, StringFormat={}{0:MM/dd/yyyy HH:mm}}"
Width="120"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</materialDesign:Card>
<!-- Transfer Panel -->
<materialDesign:Card Grid.Column="1" Margin="8,0,0,0">
<StackPanel Margin="16">
<TextBlock Text="Transfer Tokens"
Style="{DynamicResource MaterialDesignHeadline6TextBlock}"
Margin="0,0,0,16"/>
<ComboBox materialDesign:HintAssist.Hint="From Wallet"
ItemsSource="{Binding Wallets}"
SelectedItem="{Binding SelectedWallet}"
DisplayMemberPath="Name"
Margin="0,0,0,16"
Style="{DynamicResource MaterialDesignFloatingHintComboBox}"/>
<ComboBox materialDesign:HintAssist.Hint="Token"
ItemsSource="{Binding TokenBalances}"
SelectedItem="{Binding SelectedTokenBalance}"
DisplayMemberPath="TokenSymbol"
Margin="0,0,0,16"
Style="{DynamicResource MaterialDesignFloatingHintComboBox}"/>
<TextBlock Text="{Binding SelectedTokenBalance.Balance, StringFormat='Available: {0:F4}'}"
Style="{DynamicResource MaterialDesignCaptionTextBlock}"
Margin="0,0,0,8"
Foreground="{DynamicResource MaterialDesignBodyLight}"/>
<TextBox materialDesign:HintAssist.Hint="To Address"
Text="{Binding TransferToAddress}"
Margin="0,0,0,16"
Style="{DynamicResource MaterialDesignFloatingHintTextBox}"/>
<TextBox materialDesign:HintAssist.Hint="Amount"
Text="{Binding TransferAmount}"
Margin="0,0,0,16"
Style="{DynamicResource MaterialDesignFloatingHintTextBox}"/>
<Button Content="TRANSFER"
Command="{Binding TransferTokenCommand}"
IsEnabled="{Binding IsLoading, Converter={StaticResource InverseBooleanConverter}}"
Style="{DynamicResource MaterialDesignRaisedButton}"
Margin="0,8,0,0"/>
<ProgressBar IsIndeterminate="True"
Visibility="{Binding IsLoading, Converter={StaticResource BoolToVisibilityConverter}}"
Margin="0,16,0,0"/>
</StackPanel>
</materialDesign:Card>
</Grid>
</TabItem>
</TabControl>
<!-- Status Bar -->
<materialDesign:ColorZone Grid.Row="2"
Mode="PrimaryDark"
Padding="16,8">
<TextBlock Text="{Binding StatusMessage}"
Style="{DynamicResource MaterialDesignCaptionTextBlock}"/>
</materialDesign:ColorZone>
</Grid>
</Window>

View File

@ -0,0 +1,23 @@
using System.Windows;
using System.Windows.Controls;
using WpfApp1TRC20.ViewModels;
namespace WpfApp1TRC20.Views
{
public partial class MainWindow : Window
{
public MainWindow(MainViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
private void PrivateKeyBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (DataContext is MainViewModel vm && sender is PasswordBox pb)
{
vm.ImportPrivateKey = pb.Password;
}
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TronNet" Version="0.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="7.0.0" />
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,18 @@
{
"TronNetwork": {
"UseMainnet": false,
"MainnetUrl": "https://api.trongrid.io",
"TestnetUrl": "https://api.shasta.trongrid.io",
"ApiKey": "your-trongrid-api-key"
},
"Database": {
"ConnectionString": "Data Source=app.db"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}