Skip to content

Commit

Permalink
Improvements and fixes of nep11 command (#391)
Browse files Browse the repository at this point in the history
* fix bug

* Allow contract names

* fixed bug of Account argument

* support Address

* update

* fixed bug

* update

* Description

* Description

* Description

* Description

* batch

---------

Co-authored-by: Jimmy <[email protected]>
  • Loading branch information
chenzhitong and Jim8y authored Nov 28, 2023
1 parent 41f38c8 commit 2021390
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 35 deletions.
27 changes: 26 additions & 1 deletion src/neoxp/Commands/BatchCommand.BatchFileCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace NeoExpress.Commands
partial class BatchCommand
{
[Command]
[Subcommand(typeof(Checkpoint), typeof(Contract), typeof(FastForward), typeof(Oracle), typeof(Policy), typeof(Transfer), typeof(Wallet))]
[Subcommand(typeof(Checkpoint), typeof(Contract), typeof(FastForward), typeof(Oracle), typeof(Policy), typeof(Transfer), typeof(TransferNFT), typeof(Wallet))]
internal class BatchFileCommands
{
[Command("checkpoint")]
Expand Down Expand Up @@ -292,6 +292,31 @@ internal class Transfer
[Option(Description = "password to use for NEP-2/NEP-6 sender")]
internal string Password { get; init; } = string.Empty;
}
[Command("transfernft")]
internal class TransferNFT
{
[Argument(0, Description = "NFT Contract (Symbol or Script Hash)")]
[Required]
internal string Contract { get; init; } = string.Empty;

[Argument(1, Description = "TokenId of NFT (Format: HEX, BASE64)")]
[Required]
internal string TokenId { get; init; } = string.Empty;

[Argument(2, Description = "Account to send NFT from (Format: Wallet name, WIF)")]
[Required]
internal string Sender { get; init; } = string.Empty;

[Argument(3, Description = "Account to send NFT to (Format: Script Hash, Address, Wallet name)")]
[Required]
internal string Receiver { get; init; } = string.Empty;

[Option(Description = "Optional data parameter to pass to transfer operation")]
internal string Data { get; init; } = string.Empty;

[Option(Description = "password to use for NEP-2/NEP-6 sender")]
internal string Password { get; init; } = string.Empty;
}

[Command("wallet")]
[Subcommand(typeof(Create))]
Expand Down
11 changes: 11 additions & 0 deletions src/neoxp/Commands/BatchCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,17 @@ await txExec.TransferAsync(
cmd.Model.Data).ConfigureAwait(false);
break;
}
case CommandLineApplication<BatchFileCommands.TransferNFT> cmd:
{
await txExec.TransferNFTAsync(
cmd.Model.Contract,
cmd.Model.TokenId,
cmd.Model.Sender,
cmd.Model.Password,
cmd.Model.Receiver,
cmd.Model.Data).ConfigureAwait(false);
break;
}
case CommandLineApplication<BatchFileCommands.Wallet.Create> cmd:
{
var wallet = chainManager.CreateWallet(
Expand Down
34 changes: 23 additions & 11 deletions src/neoxp/Commands/ShowCommand.NFT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
using McMaster.Extensions.CommandLineUtils;
using Neo;
using System.ComponentModel.DataAnnotations;
using System.Text;

namespace NeoExpress.Commands
{
Expand All @@ -28,11 +27,11 @@ public NFT(ExpressChainManagerFactory chainManagerFactory)
this.chainManagerFactory = chainManagerFactory;
}

[Argument(0, Description = "Contract to show NFT of (symbol or script hash)")]
[Argument(0, Description = "NFT Contract (Symbol or Script Hash)")]
[Required]
internal string Contract { get; init; } = string.Empty;

[Argument(1, Description = "Account to show asset balance for")]
[Argument(1, Description = "Account to show NFT (Format: Script Hash, Address, Wallet name)")]
[Required]
internal string Account { get; init; } = string.Empty;

Expand All @@ -45,20 +44,33 @@ internal async Task<int> OnExecuteAsync(CommandLineApplication app, IConsole con
{
var (chainManager, _) = chainManagerFactory.LoadChain(Input);
using var expressNode = chainManager.GetExpressNode();

var getHashResult = await expressNode.TryGetAccountHashAsync(chainManager.Chain, Account).ConfigureAwait(false);
if (getHashResult.TryPickT1(out _, out var accountHash))
if (!UInt160.TryParse(Account, out var accountHash)) //script hash
{
throw new Exception($"{Account} account not found.");
if (!chainManager.Chain.TryParseScriptHash(Account, out accountHash)) //address
{
var getHashResult = await expressNode.TryGetAccountHashAsync(chainManager.Chain, Account).ConfigureAwait(false); //wallet name
if (getHashResult.TryPickT1(out _, out accountHash))
{
throw new Exception($"{Account} account not found.");
}
}
}

var list = await expressNode.GetNFTAsync(accountHash, Contract).ConfigureAwait(false);
list.ForEach(p => console.Out.WriteLine($"TokenId: {p}, TokenId(Hex): {Encoding.UTF8.GetBytes(p).ToHexString()}"));
var parser = await expressNode.GetContractParameterParserAsync(chainManager.Chain).ConfigureAwait(false);
var scriptHash = parser.TryLoadScriptHash(Contract, out var value)
? value
: UInt160.TryParse(Contract, out var uint160)
? uint160
: throw new InvalidOperationException($"contract \"{Contract}\" not found");
var list = await expressNode.GetNFTAsync(accountHash, scriptHash).ConfigureAwait(false);
if (list.Count == 0)
await console.Out.WriteLineAsync($"No NFT yet. (Contract:{scriptHash}, Account:{accountHash})");
else
list.ForEach(p => console.Out.WriteLine($"TokenId(Base64): {p}, TokenId(Hex): 0x{Convert.FromBase64String(p).Reverse().ToArray().ToHexString()}"));
return 0;
}
catch (Exception ex)
{
app.WriteException(ex);
app.WriteException(ex, true);
return 1;
}
}
Expand Down
24 changes: 10 additions & 14 deletions src/neoxp/Commands/TransferNFTCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ public TransferNFTCommand(ExpressChainManagerFactory chainManagerFactory, Transa
this.txExecutorFactory = txExecutorFactory;
}

[Argument(0, Description = "NFT Contract (symbol or script hash)")]
[Argument(0, Description = "NFT Contract (Symbol or Script Hash)")]
[Required]
internal string Contract { get; init; } = string.Empty;

[Argument(1, Description = "TokenId of NFT (Hex string)")]
[Argument(1, Description = "TokenId of NFT (Format: HEX, BASE64)")]
[Required]
internal string TokenId { get; init; } = string.Empty;

[Argument(2, Description = "Account to send NFT from")]
[Argument(2, Description = "Account to send NFT from (Format: Wallet name, WIF)")]
[Required]
internal string Sender { get; init; } = string.Empty;

[Argument(3, Description = "Account to send NFT to")]
[Argument(3, Description = "Account to send NFT to (Format: Script Hash, Address, Wallet name)")]
[Required]
internal string Receiver { get; init; } = string.Empty;

Expand All @@ -67,7 +67,7 @@ internal async Task<int> OnExecuteAsync(CommandLineApplication app, IConsole con
var (chainManager, _) = chainManagerFactory.LoadChain(Input);
var password = chainManager.Chain.ResolvePassword(Sender, Password);
using var txExec = txExecutorFactory.Create(chainManager, Trace, Json);
await txExec.TransferNFTAsync(Contract, HexStringToUTF8(TokenId), Sender, password, Receiver, Data).ConfigureAwait(false);
await txExec.TransferNFTAsync(Contract, HexOrBase64ToUTF8(TokenId), Sender, password, Receiver, Data).ConfigureAwait(false);
return 0;
}
catch (Exception ex)
Expand All @@ -76,19 +76,15 @@ internal async Task<int> OnExecuteAsync(CommandLineApplication app, IConsole con
return 1;
}

static string HexStringToUTF8(string hex)
static string HexOrBase64ToUTF8(string input)
{
hex = hex.ToLower().Trim();
if (!new Regex("^(0x)?([0-9a-f]{2})+$").IsMatch(hex))
throw new FormatException();

if (new Regex("^([0-9a-f]{2})+$").IsMatch(hex))
try
{
return Encoding.UTF8.GetString(hex.HexToBytes());
return input.StartsWith("0x") ? Encoding.UTF8.GetString(input[2..].HexToBytes().Reverse().ToArray()) : Encoding.UTF8.GetString(Convert.FromBase64String(input));
}
else
catch (Exception)
{
return Encoding.UTF8.GetString(hex[2..].HexToBytes().Reverse().ToArray());
throw new ArgumentException($"Unknown Asset \"{input}\"", nameof(TokenId));
}
}
}
Expand Down
6 changes: 2 additions & 4 deletions src/neoxp/Extensions/ExpressNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -445,10 +445,8 @@ byte[] GetResponseData(string filter)
throw new Exception("invalid script results");
}

public static async Task<List<string>> GetNFTAsync(this IExpressNode expressNode, UInt160 accountHash, string asset)
public static async Task<List<string>> GetNFTAsync(this IExpressNode expressNode, UInt160 accountHash, UInt160 assetHash)
{
var assetHash = await expressNode.ParseAssetAsync(asset).ConfigureAwait(false);

using var sb = new ScriptBuilder();
sb.EmitDynamicCall(assetHash, "tokensOf", accountHash);

Expand All @@ -464,7 +462,7 @@ public static async Task<List<string>> GetNFTAsync(this IExpressNode expressNode
{
while (iterator.Next())
{
list.Add(iterator.Value(null).GetString()!);
list.Add(Convert.ToBase64String(iterator.Value(null).GetSpan()));
}
}
}
Expand Down
20 changes: 15 additions & 5 deletions src/neoxp/TransactionExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -452,10 +452,16 @@ public async Task TransferNFTAsync(string contract, string tokenId, string sende
throw new Exception($"{sender} sender not found.");
}

var getHashResult = await expressNode.TryGetAccountHashAsync(chainManager.Chain, receiver).ConfigureAwait(false);
if (getHashResult.TryPickT1(out _, out var receiverHash))
if (!UInt160.TryParse(receiver, out var receiverHash)) //script hash
{
throw new Exception($"{receiver} account not found.");
if (!chainManager.Chain.TryParseScriptHash(receiver, out receiverHash)) //address
{
var getHashResult = await expressNode.TryGetAccountHashAsync(chainManager.Chain, receiver).ConfigureAwait(false); //wallet name
if (getHashResult.TryPickT1(out _, out receiverHash))
{
throw new Exception($"{receiver} account not found.");
}
}
}

ContractParameter? dataParam = null;
Expand All @@ -464,8 +470,12 @@ public async Task TransferNFTAsync(string contract, string tokenId, string sende
var parser = await expressNode.GetContractParameterParserAsync(chainManager.Chain).ConfigureAwait(false);
dataParam = parser.ParseParameter(data);
}

var assetHash = await expressNode.ParseAssetAsync(contract).ConfigureAwait(false);
var parser2 = await expressNode.GetContractParameterParserAsync(chainManager.Chain).ConfigureAwait(false);
var assetHash = parser2.TryLoadScriptHash(contract, out var value)
? value
: UInt160.TryParse(contract, out var uint160)
? uint160
: throw new InvalidOperationException($"contract \"{contract}\" not found");
var txHash = await expressNode.TransferNFTAsync(assetHash, tokenId, senderWallet, senderAccountHash, receiverHash, dataParam);
await writer.WriteTxHashAsync(txHash, "TransferNFT", json).ConfigureAwait(false);
}
Expand Down

0 comments on commit 2021390

Please sign in to comment.