initial commit
This commit is contained in:
parent
45153699a2
commit
f6b69e7737
25
WpfApp1TRC20.sln
Normal file
25
WpfApp1TRC20.sln
Normal 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
14
WpfApp1TRC20/App.xaml
Normal 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
71
WpfApp1TRC20/App.xaml.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
WpfApp1TRC20/AssemblyInfo.cs
Normal file
10
WpfApp1TRC20/AssemblyInfo.cs
Normal 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)
|
||||||
|
)]
|
116
WpfApp1TRC20/Contract/Ganatch.cs
Normal file
116
WpfApp1TRC20/Contract/Ganatch.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
}
|
||||||
|
}
|
40
WpfApp1TRC20/Data/AppDbContext.cs
Normal file
40
WpfApp1TRC20/Data/AppDbContext.cs
Normal 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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
WpfApp1TRC20/Data/TokenBalance.cs
Normal file
11
WpfApp1TRC20/Data/TokenBalance.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
25
WpfApp1TRC20/Data/TokenCreationParams.cs
Normal file
25
WpfApp1TRC20/Data/TokenCreationParams.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
28
WpfApp1TRC20/Data/TokenInfo.cs
Normal file
28
WpfApp1TRC20/Data/TokenInfo.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
10
WpfApp1TRC20/Data/TokenStatus.cs
Normal file
10
WpfApp1TRC20/Data/TokenStatus.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace WpfApp1TRC20.Data
|
||||||
|
{
|
||||||
|
public enum TokenStatus
|
||||||
|
{
|
||||||
|
Creating,
|
||||||
|
Active,
|
||||||
|
Expired,
|
||||||
|
Failed
|
||||||
|
}
|
||||||
|
}
|
19
WpfApp1TRC20/Data/TransactionRecord.cs
Normal file
19
WpfApp1TRC20/Data/TransactionRecord.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
9
WpfApp1TRC20/Data/TransactionStatus.cs
Normal file
9
WpfApp1TRC20/Data/TransactionStatus.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace WpfApp1TRC20.Data
|
||||||
|
{
|
||||||
|
public enum TransactionStatus
|
||||||
|
{
|
||||||
|
Pending,
|
||||||
|
Confirmed,
|
||||||
|
Failed
|
||||||
|
}
|
||||||
|
}
|
18
WpfApp1TRC20/Data/WalletAccount.cs
Normal file
18
WpfApp1TRC20/Data/WalletAccount.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
147
WpfApp1TRC20/Services/DataService.cs
Normal file
147
WpfApp1TRC20/Services/DataService.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
WpfApp1TRC20/Services/IDataService.cs
Normal file
22
WpfApp1TRC20/Services/IDataService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
22
WpfApp1TRC20/Services/ITronService.cs
Normal file
22
WpfApp1TRC20/Services/ITronService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
255
WpfApp1TRC20/Services/TronService.cs
Normal file
255
WpfApp1TRC20/Services/TronService.cs
Normal 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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
WpfApp1TRC20/Utils/InverseBooleanConverter.cs
Normal file
27
WpfApp1TRC20/Utils/InverseBooleanConverter.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
WpfApp1TRC20/Utils/StatusToColorConverter.cs
Normal file
34
WpfApp1TRC20/Utils/StatusToColorConverter.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
445
WpfApp1TRC20/ViewModels/MainViewModel.cs
Normal file
445
WpfApp1TRC20/ViewModels/MainViewModel.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
WpfApp1TRC20/ViewModels/RelayCommand .cs
Normal file
31
WpfApp1TRC20/ViewModels/RelayCommand .cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
30
WpfApp1TRC20/ViewModels/ViewModelBase.cs
Normal file
30
WpfApp1TRC20/ViewModels/ViewModelBase.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
505
WpfApp1TRC20/Views/MainWindow.xaml
Normal file
505
WpfApp1TRC20/Views/MainWindow.xaml
Normal 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>
|
23
WpfApp1TRC20/Views/MainWindow.xaml.cs
Normal file
23
WpfApp1TRC20/Views/MainWindow.xaml.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
WpfApp1TRC20/WpfApp1TRC20.csproj
Normal file
21
WpfApp1TRC20/WpfApp1TRC20.csproj
Normal 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>
|
18
WpfApp1TRC20/appsetting.json
Normal file
18
WpfApp1TRC20/appsetting.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user