Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions OneGateApp/Controls/Popups/AddTokenPopup.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<og:MyPopup xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:og="http://schemas.neoorder.org/onegate/controls"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="NeoOrder.OneGate.Controls.Popups.AddTokenPopup"
x:TypeArguments="x:Boolean"
BindingContext="{Binding Source={RelativeSource Self}}">
<VerticalStackLayout Spacing="15" MaximumWidthRequest="450">
<Grid ColumnDefinitions="*,auto">
<Label Text="{x:Static og:Strings.AddToken}" FontAttributes="Bold" />
<Button Grid.Column="1" StyleClass="Icon" Text="&#xe6b5;" FontSize="24" Margin="-10" Padding="10" Clicked="OnCancel" />
</Grid>
<Label StyleClass="Secondary" Text="{x:Static og:Strings.AddTokenText}" />
<Border StrokeShape="RoundRectangle 10" BackgroundColor="{toolkit:AppThemeResource TextBox}" Padding="10,0">
<Entry Text="{Binding ContractHash}" Placeholder="{x:Static og:Strings.ContractHashPlaceholder}" og:Border.IsVisible="False" />
</Border>
<Label StyleClass="Danger" Margin="0,-10,0,0" Text="{Binding ErrorMessage}" IsVisible="{Binding HasError}" FontSize="12" />
<Grid ColumnDefinitions="*,auto,*" Margin="-15,0,-15,-15">
<BoxView Grid.ColumnSpan="3" HeightRequest="1" VerticalOptions="Start" />
<Button Grid.Column="0" StyleClass="Secondary" Text="{x:Static og:Strings.Cancel}" BorderWidth="0" Clicked="OnCancel" />
<BoxView Grid.Column="1" WidthRequest="1" VerticalOptions="Fill" />
<Button Grid.Column="2" Text="{x:Static og:Strings.Add}" TextColor="#5676e7" BackgroundColor="Transparent" BorderWidth="0" Clicked="OnAdd" />
</Grid>
</VerticalStackLayout>
</og:MyPopup>
54 changes: 54 additions & 0 deletions OneGateApp/Controls/Popups/AddTokenPopup.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Neo;
using NeoOrder.OneGate.Properties;
using NeoOrder.OneGate.Services;
using NeoOrder.OneGate.Services.RPC;

namespace NeoOrder.OneGate.Controls.Popups;

public partial class AddTokenPopup : MyPopup<bool>
{
readonly TokenManager tokenManager;

public string? ContractHash
{
get;
set { field = value; OnPropertyChanged(); ErrorMessage = null; }
}
public string? ErrorMessage
{
get;
set { field = value; OnPropertyChanged(); OnPropertyChanged(nameof(HasError)); }
}
public bool HasError => !string.IsNullOrEmpty(ErrorMessage);

public AddTokenPopup(TokenManager tokenManager)
{
this.tokenManager = tokenManager;
InitializeComponent();
}

async void OnAdd(object sender, EventArgs e)
{
if (!UInt160.TryParse(ContractHash?.Trim() ?? string.Empty, out UInt160? parsedHash) || parsedHash is null)
{
ErrorMessage = Strings.InvalidContractHash;
return;
}
UInt160 hash = parsedHash;
try
{
await tokenManager.AddCustomTokenAsync(hash);
await CloseAsync(true);
}
catch (RpcException)
{
ErrorMessage = Strings.TokenNotFound;
}
catch
{
ErrorMessage = Strings.UnknownError;
}
}

async void OnCancel(object sender, EventArgs e) => await CloseAsync(false);
}
1 change: 1 addition & 0 deletions OneGateApp/Pages/WalletPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<toolkit:InvertedBoolConverter x:Key="InvertedBoolConverter" />
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Order="Primary" IconImageSource="{FontImageSource &#xe642;, FontFamily=Icons, Color={toolkit:AppThemeResource Primary}}" Clicked="OnAddToken" />
<ToolbarItem Order="Primary" IconImageSource="{FontImageSource &#xe69a;, FontFamily=Icons, Color={toolkit:AppThemeResource Primary}}" Command="{x:Static og:Commands.ScanQRCode}" />
</ContentPage.ToolbarItems>
<RefreshView Margin="20,8" IsRefreshing="{Binding LoadingService.IsReloading}" Command="{Binding LoadingService}">
Expand Down
14 changes: 13 additions & 1 deletion OneGateApp/Pages/WalletPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using CommunityToolkit.Maui.Alerts;
using CommunityToolkit.Maui.Extensions;
using Neo;
using Neo.Wallets;
using NeoOrder.OneGate.Controls;
using NeoOrder.OneGate.Controls.Popups;
using NeoOrder.OneGate.Data;
using NeoOrder.OneGate.Models;
using NeoOrder.OneGate.Properties;
Expand All @@ -13,6 +15,7 @@ public partial class WalletPage : ContentPage
{
readonly ApplicationDbContext dbContext;
readonly TokenManager tokenManager;
readonly IServiceProvider serviceProvider;

public LoadingService LoadingService { get; set { field = value; OnPropertyChanged(); } }
public Wallet Wallet { get; set { field = value; OnPropertyChanged(); } }
Expand All @@ -23,9 +26,10 @@ public partial class WalletPage : ContentPage
public IReadOnlyList<NFT>? NFTs { get; set { field = value; OnPropertyChanged(); } }
public string TotalValuation { get; set { field = value; OnPropertyChanged(); } } = "N/A";

public WalletPage(ApplicationDbContext dbContext, IWalletProvider walletProvider, TokenManager tokenManager)
public WalletPage(IServiceProvider serviceProvider, ApplicationDbContext dbContext, IWalletProvider walletProvider, TokenManager tokenManager)
{
this.LoadingService = new(RefreshWallet, LoadAssetsAsync, LoadNFTsAsync);
this.serviceProvider = serviceProvider;
this.dbContext = dbContext;
this.tokenManager = tokenManager;
Wallet = walletProvider.GetWallet()!;
Expand Down Expand Up @@ -92,4 +96,12 @@ async void OnNFTTapped(object sender, TappedEventArgs e)
NFT nft = (NFT)e.Parameter!;
await Shell.Current.GoToAsync("//wallet/nft/details", new Dictionary<string, object> { ["nft"] = nft });
}

async void OnAddToken(object sender, EventArgs e)
{
AddTokenPopup popup = serviceProvider.GetServiceOrCreateInstance<AddTokenPopup>();
var result = await this.ShowPopupAsync<bool>(popup);
if (result.Result)
LoadingService.BeginLoad();
}
}
54 changes: 54 additions & 0 deletions OneGateApp/Properties/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions OneGateApp/Properties/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -989,4 +989,22 @@ It is recommended to store the NEP-2 key and password separately and back them u
<data name="Unavailable" xml:space="preserve">
<value>Unavailable</value>
</data>
<data name="AddToken" xml:space="preserve">
<value>Add token</value>
</data>
<data name="AddTokenText" xml:space="preserve">
<value>Enter a NEP-17 contract hash to track a custom token.</value>
</data>
<data name="ContractHashPlaceholder" xml:space="preserve">
<value>0x… contract hash</value>
</data>
<data name="InvalidContractHash" xml:space="preserve">
<value>Enter a valid contract hash.</value>
</data>
<data name="TokenNotFound" xml:space="preserve">
<value>No NEP-17 token found at this contract.</value>
</data>
<data name="Add" xml:space="preserve">
<value>Add</value>
</data>
</root>
18 changes: 18 additions & 0 deletions OneGateApp/Properties/Strings.zh-Hans.resx
Original file line number Diff line number Diff line change
Expand Up @@ -989,4 +989,22 @@
<data name="Unavailable" xml:space="preserve">
<value>不可用</value>
</data>
<data name="AddToken" xml:space="preserve">
<value>添加代币</value>
</data>
<data name="AddTokenText" xml:space="preserve">
<value>输入 NEP-17 合约哈希以添加自定义代币。</value>
</data>
<data name="ContractHashPlaceholder" xml:space="preserve">
<value>0x… 合约哈希</value>
</data>
<data name="InvalidContractHash" xml:space="preserve">
<value>请输入有效的合约哈希。</value>
</data>
<data name="TokenNotFound" xml:space="preserve">
<value>该合约上未找到 NEP-17 代币。</value>
</data>
<data name="Add" xml:space="preserve">
<value>添加</value>
</data>
</root>
18 changes: 18 additions & 0 deletions OneGateApp/Properties/Strings.zh-Hant.resx
Original file line number Diff line number Diff line change
Expand Up @@ -989,4 +989,22 @@
<data name="Unavailable" xml:space="preserve">
<value>不可用</value>
</data>
<data name="AddToken" xml:space="preserve">
<value>新增代幣</value>
</data>
<data name="AddTokenText" xml:space="preserve">
<value>輸入 NEP-17 合約雜湊以新增自訂代幣。</value>
</data>
<data name="ContractHashPlaceholder" xml:space="preserve">
<value>0x… 合約雜湊</value>
</data>
<data name="InvalidContractHash" xml:space="preserve">
<value>請輸入有效的合約雜湊。</value>
</data>
<data name="TokenNotFound" xml:space="preserve">
<value>該合約上未找到 NEP-17 代幣。</value>
</data>
<data name="Add" xml:space="preserve">
<value>新增</value>
</data>
</root>
20 changes: 20 additions & 0 deletions OneGateApp/Services/TokenManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ public class TokenManager(ApplicationDbContext dbContext, IWalletProvider wallet
public async Task<IReadOnlyList<TokenInfo>> LoadTokensAsync(bool includeHiddens = false)
{
List<TokenInfo> tokens = EmbeddedResource.LoadJson<List<TokenInfo>>("tokens.json");
UInt160[]? custom = await dbContext.Settings.GetAsync<UInt160[]>("tokens/custom");
if (custom is not null)
{
foreach (UInt160 hash in custom)
{
if (tokens.Any(p => p.Hash == hash)) continue;
try { tokens.Add(await rpcClient.GetTokenInfo(hash)); }
catch (RpcException) { /* custom token no longer resolvable on chain; skip */ }
}
}
if (!includeHiddens)
{
UInt160[]? hiddens = await dbContext.Settings.GetAsync<UInt160[]>("tokens/hidden");
Expand All @@ -23,6 +33,16 @@ public async Task<IReadOnlyList<TokenInfo>> LoadTokensAsync(bool includeHiddens
return tokens;
}

// Resolve a NEP-17 by contract hash (throws if not a valid token) and persist it as a user-added token.
public async Task<TokenInfo> AddCustomTokenAsync(UInt160 hash)
{
TokenInfo token = await rpcClient.GetTokenInfo(hash);
UInt160[] custom = await dbContext.Settings.GetAsync<UInt160[]>("tokens/custom") ?? [];
if (!custom.Contains(hash))
await dbContext.Settings.PutAsync("tokens/custom", custom.Append(hash).ToArray());
return token;
}

public async Task<AssetInfo> LoadAssetAsync(UInt160 assetId)
{
Wallet wallet = walletProvider.GetWallet()!;
Expand Down