Skip to content

Commit

Permalink
Send tokens from ao processes
Browse files Browse the repository at this point in the history
  • Loading branch information
michielpost committed Apr 2, 2024
1 parent 06cad8a commit 98e3839
Show file tree
Hide file tree
Showing 11 changed files with 277 additions and 106 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ ao Web Wallet is a wallet for the [ao network](https://ao.arweave.dev) running o

Features:
- Add ArConnect Wallets
- Add read-only wallets
- Create new wallets and import wallet.json files
- View your ao processes
- Send tokens for your owned ao processes
- View balances of a wallet
- View all transactions for a wallet
- Send tokens for ArConnect Wallets
- Receive instructions for all wallets
- Add custom tokens
- Dark and Light theme

Expand Down
18 changes: 18 additions & 0 deletions src/aoWebWallet/Extensions/TagExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using ArweaveBlazor.Models;

namespace aoWebWallet.Extensions
{
public static class TagExtensions
{
public static string ToSendCommand(this List<ArweaveBlazor.Models.Tag> tagList)
{
var tagListString = string.Join(", ", tagList.Select(x => x.ToSendCommand()));
return "{" + tagListString + "}";
}

public static string ToSendCommand(this ArweaveBlazor.Models.Tag tag)
{
return $"{tag.Name} = \"{tag.Value}\"";
}
}
}
1 change: 1 addition & 0 deletions src/aoWebWallet/Models/AoProcessInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ public class AoProcessInfo
public required string Id { get; set; }
public required string Name { get; set; }
public string? Version { get; set; }
public string? Owner { get; set; }
}
}
29 changes: 4 additions & 25 deletions src/aoWebWallet/Models/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ namespace aoWebWallet.Models
public class Wallet
{
public required string Address { get; set; }
public string? OwnerAddress { get; set; }
public string? Name { get; set; }
public string? Jwk { get; set; }
public WalletTypes Source { get; set; }
public bool IsConnected { get; set; }

public bool IsReadOnly { get; set; }

public DateTimeOffset? LastBackedUpDate { get; set; }
Expand All @@ -18,29 +19,6 @@ public class Wallet

public bool NeedsBackup => Source == WalletTypes.Generated && !string.IsNullOrEmpty(Jwk) && !LastBackedUpDate.HasValue;

public bool CanSend
{
get
{
if (IsReadOnly)
return false;

var result = Source switch
{
WalletTypes.Manual => false,
WalletTypes.None => false,
WalletTypes.ArConnect => IsConnected,
WalletTypes.Explorer => false,
WalletTypes.Generated => !string.IsNullOrEmpty(Jwk),
WalletTypes.Imported => !string.IsNullOrEmpty(Jwk),
_ => false
};

return result;

}
}

}

public enum WalletTypes
Expand All @@ -50,6 +28,7 @@ public enum WalletTypes
ArConnect,
Explorer,
Generated,
Imported
Imported,
AoProcess
}
}
10 changes: 3 additions & 7 deletions src/aoWebWallet/Pages/About.razor
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,13 @@
<MudList>
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Open Source" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Add ArConnect Wallets" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Add read-only wallets" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Create new wallets or import a wallet.json file" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="View balances of a wallet" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="View all transactions for a wallet" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Send tokens for ArConnect Wallets" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Receive instructions for all wallets" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Send tokens" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Add custom tokens" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Dark and Light theme" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Explorer" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="View transactions" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Inspect the balances of any address" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="View all transactions for a token" />
<MudListItem Icon="@Icons.Material.Filled.Star" Text="Dark and Light theme" />
</MudList>
</MudPaper>
</MudItem>
Expand Down
24 changes: 17 additions & 7 deletions src/aoWebWallet/Pages/WalletDetail.razor
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,37 @@
<MudText Class="KodeMono" style="text-overflow: ellipsis; white-space: nowrap;overflow: hidden;" Typo="Typo.h6">
@Address
</MudText>
<MudText Typo="Typo.body2">@BindingContext.SelectedWallet?.Name</MudText>
@if (BindingContext.SelectedWallet?.NeedsBackup ?? false)
<MudStack Row="true">
@if (BindingContext.SelectedWallet?.IsConnected ?? false)
{
<MudIcon style="margin-left:2px; width:20px; padding-bottom:4px;" Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" />
}
<MudText Typo="Typo.body2">@BindingContext.SelectedWallet?.Wallet.Name</MudText>
@if (BindingContext.SelectedWallet?.Wallet.OwnerAddress != null)
{
<MudChip>owner: @BindingContext.SelectedWallet?.Wallet.OwnerAddress</MudChip>
}
</MudStack>
@if (BindingContext.SelectedWallet?.Wallet.NeedsBackup ?? false)
{
<MudStack Row="true">
<MudIcon style="margin-left:2px; width:20px; padding-bottom:4px;" Icon="@Icons.Material.Filled.Warning" Color="Color.Warning" />
<MudText Typo="Typo.body2">Wallet not backed up yet!</MudText>
</MudStack>
}
</MudStack>
<MudIconButton Class="copy-clipboard mb-4 ml-0" Icon="@Icons.Material.Filled.ContentCopy" Color="Color.Default" OnClick="async () => { await BindingContext.CopyToClipboard(BindingContext.SelectedWallet?.Address); }" />
<MudIconButton Class="copy-clipboard mb-4 ml-0" Icon="@Icons.Material.Filled.ContentCopy" Color="Color.Default" OnClick="async () => { await BindingContext.CopyToClipboard(BindingContext.SelectedWallet?.Wallet.Address); }" />
<MudSpacer />
@if (!string.IsNullOrEmpty(BindingContext.SelectedWallet?.Jwk))
@if (!string.IsNullOrEmpty(BindingContext.SelectedWallet?.Wallet.Jwk))
{
<MudTooltip Text="Download wallet file" Arrow="true" Placement="Placement.Left">
<MudIconButton Icon="@Icons.Material.Filled.Save" aria-label="backup" OnClick="() => { DownloadWallet(BindingContext.SelectedWallet); }"></MudIconButton>
<MudIconButton Icon="@Icons.Material.Filled.Save" aria-label="backup" OnClick="() => { DownloadWallet(BindingContext.SelectedWallet.Wallet); }"></MudIconButton>
</MudTooltip>
}
<MudTooltip Text="Refresh balances" Arrow="true" Placement="Placement.Left">
<MudIconButton Class="mt-1" Icon="@Icons.Material.Filled.Refresh" aria-label="refresh transactions" OnClick="RefreshBalances"></MudIconButton>
</MudTooltip>
@if (BindingContext.SelectedWallet?.Source == WalletTypes.Explorer)
@if (BindingContext.SelectedWallet?.Wallet.Source == WalletTypes.Explorer)
{
<MudTooltip Text="Add as readonly wallet" Arrow="true" Placement="Placement.Left">
<MudIconButton Icon="@Icons.Material.Outlined.Grade" aria-label="add to wallets" OnClick="AddWalletAsReadonly"></MudIconButton>
Expand All @@ -45,7 +55,7 @@
</MudStack>
</MudPaper>

@if (!(BindingContext.SelectedWallet?.IsReadOnly ?? true) && (BindingContext.SelectedWallet?.IsConnected ?? false))
@if (BindingContext.SelectedWallet?.IsConnected ?? false)
{
<MudPaper>
<MudContainer style="max-width: 100%;" Width="100%" Class="d-flex mb-4 pr-4">
Expand Down
5 changes: 1 addition & 4 deletions src/aoWebWallet/Pages/Wallets.razor
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,7 @@
<MudText Typo="Typo.body2">read-only &nbsp;</MudText>
}
<MudText Typo="Typo.body2">@wallet.Name</MudText>
@if (wallet.IsConnected)
{
<MudIcon style="margin-left:2px; width:20px; padding-bottom:4px;" Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" />
}

@if (wallet.NeedsBackup)
{
<MudIcon style="margin-left:2px; width:20px; padding-bottom:4px;" Icon="@Icons.Material.Filled.Warning" Color="Color.Warning" />
Expand Down
44 changes: 40 additions & 4 deletions src/aoWebWallet/Services/GraphqlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,13 @@ public async Task<List<TokenTransfer>> GetTransactionsIn(string adddress, string
else
transaction.Timestamp = DateTimeOffset.UtcNow;

var fromProcess = edge.Node.Tags.Where(x => x.Name == "From-Process").Select(x => x.Value).FirstOrDefault();
if (!string.IsNullOrEmpty(fromProcess))
transaction.From = fromProcess;

transaction.TokenId = edge.Node.Recipient;
transaction.To = edge.Node.Tags.Where(x => x.Name == "Recipient").Select(x => x.Value).FirstOrDefault();

string? quantity = edge.Node.Tags.Where(x => x.Name == "Quantity").Select(x => x.Value).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(quantity) && long.TryParse(quantity, out long quantityLong))
transaction.Quantity = quantityLong;
Expand All @@ -83,6 +87,7 @@ public async Task<List<TokenTransfer>> GetTransactionsIn(string adddress, string
var processInfo = new AoProcessInfo()
{
Id = edge.Node.Id,
Owner = edge.Node.Owner?.Address,
Name = name,
};

Expand All @@ -109,6 +114,24 @@ public async Task<List<TokenTransfer>> GetTransactionsOut(string adddress, strin
return result;
}

public async Task<List<TokenTransfer>> GetTransactionsOutFromProcess(string address, string? fromTxId = null)
{
string query = "query {\r\n transactions(\r\n first: 100\r\n sort: HEIGHT_DESC\r\n tags: [\r\n { name: \"From-Process\", values: [\"" + address + "\"] }\r\n { name: \"Data-Protocol\", values: [\"ao\"] }\r\n { name: \"Action\", values: [\"Transfer\"] }\r\n ]\r\n ) {\r\n edges {\r\n node {\r\n id\r\n recipient\r\n owner {\r\n address\r\n }\r\n block {\r\n timestamp\r\n height\r\n }\r\n tags {\r\n name\r\n value\r\n }\r\n }\r\n }\r\n }\r\n}\r\n";
var queryResult = await PostQueryAsync(query);

var result = new List<TokenTransfer>();

foreach (var edge in queryResult?.Data?.Transactions?.Edges ?? new())
{
TokenTransfer? transaction = GetTransaction(edge);

if (transaction != null)
result.Add(transaction);
}

return result;
}

public async Task<TokenTransfer?> GetTransactionsById(string txId)
{
string query = "query {\r\n transactions(\r\n first: 1\r\n sort: HEIGHT_DESC\r\n ids: [\"" + txId + "\"]\r\n tags: [\r\n { name: \"Data-Protocol\", values: [\"ao\"] }\r\n { name: \"Action\", values: [\"Transfer\"] }\r\n ]\r\n ) {\r\n edges {\r\n node {\r\n id\r\n recipient\r\n owner {\r\n address\r\n }\r\n block {\r\n timestamp\r\n height\r\n }\r\n tags {\r\n name\r\n value\r\n }\r\n }\r\n }\r\n }\r\n}\r\n";
Expand Down Expand Up @@ -163,10 +186,23 @@ public async Task<List<AoProcessInfo>> GetAoProcessesForAddress(string address)
return result;
}

//public async Task GetTransactionsForToken(string tokenId, string fromTxId)
//{
public async Task<AoProcessInfo?> GetOwnerForAoProcessAddress(string address)
{
string query = "query {\r\n transactions(\r\n first: 50,\r\n ids: [\"" + address + "\"],\r\n tags: [\r\n { name: \"Data-Protocol\", values: [\"ao\"] },\r\n { name: \"Type\", values: [\"Process\"]},\r\n { name: \"App-Name\", values: [\"aos\"]},\r\n \r\n ]\r\n ) {\r\n edges {\r\n node {\r\n id\r\n \towner {\r\n \t address\r\n \t}\r\n tags {\r\n name\r\n value\r\n }\r\n }\r\n }\r\n }\r\n }";
var queryResult = await PostQueryAsync(query);

var result = new List<AoProcessInfo>();

foreach (var edge in queryResult?.Data?.Transactions?.Edges ?? new())
{
AoProcessInfo? processInfo = GetAoProcessInfo(edge);

if (processInfo != null)
result.Add(processInfo);
}

//}
return result.FirstOrDefault();
}

protected async Task<GraphqlResponse?> PostQueryAsync(string query)
{
Expand Down
2 changes: 1 addition & 1 deletion src/aoWebWallet/Shared/SendTokenDialog.razor
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
}

long amountLong = BalanceHelper.DecimalToTokenAmount(Amount, BindingContext.SelectedBalanceDataVM.Token.TokenData.Denomination!.Value);
var result = await BindingContext.SendToken(BindingContext.SelectedWallet, BindingContext.SelectedBalanceDataVM.Token.TokenId, Address, amountLong);
var result = await BindingContext.SendToken(BindingContext.SelectedWallet.Wallet, BindingContext.SelectedBalanceDataVM.Token.TokenId, Address, amountLong);
TransactionId = result?.Id;

if (!string.IsNullOrEmpty(BindingContext.SelectedAddress))
Expand Down
Loading

0 comments on commit 98e3839

Please sign in to comment.