diff --git a/neo-cli/CLI/MainService.Wallet.cs b/neo-cli/CLI/MainService.Wallet.cs
index a820d4c00..b240ad5b0 100644
--- a/neo-cli/CLI/MainService.Wallet.cs
+++ b/neo-cli/CLI/MainService.Wallet.cs
@@ -16,6 +16,7 @@
using Neo.Persistence;
using Neo.SmartContract;
using Neo.SmartContract.Native;
+using Neo.VM;
using Neo.Wallets;
using Neo.Wallets.NEP6;
using System;
@@ -559,6 +560,88 @@ private void OnSendCommand(UInt160 asset, UInt160 to, string amount, UInt160 fro
SignAndSendTx(NeoSystem.StoreView, tx);
}
+ ///
+ /// Process "cancel" command
+ ///
+ /// conflict txid
+ /// Transaction's sender
+ /// Signer's accounts
+ [ConsoleCommand("cancel", Category = "Wallet Commands")]
+ private void OnCancelCommand(UInt256 txid, UInt160 sender = null, UInt160[] signerAccounts = null)
+ {
+ TransactionState state = NativeContract.Ledger.GetTransactionState(NeoSystem.StoreView, txid);
+ if (state != null)
+ {
+ ConsoleHelper.Error("This tx is already confirmed, can't be cancelled.");
+ return;
+ }
+
+ var conflict = new TransactionAttribute[] { new Conflicts() { Hash = txid } };
+ Signer[] signers = Array.Empty();
+ if (!NoWallet() && sender != null)
+ {
+ if (signerAccounts == null)
+ signerAccounts = new UInt160[1] { sender };
+ else if (signerAccounts.Contains(sender) && signerAccounts[0] != sender)
+ {
+ var signersList = signerAccounts.ToList();
+ signersList.Remove(sender);
+ signerAccounts = signersList.Prepend(sender).ToArray();
+ }
+ else if (!signerAccounts.Contains(sender))
+ {
+ signerAccounts = signerAccounts.Prepend(sender).ToArray();
+ }
+ signers = signerAccounts.Select(p => new Signer() { Account = p, Scopes = WitnessScope.CalledByEntry }).ToArray();
+ }
+
+ Transaction tx = new Transaction
+ {
+ Signers = signers,
+ Attributes = conflict,
+ Witnesses = Array.Empty(),
+ };
+
+ try
+ {
+ using ScriptBuilder scriptBuilder = new();
+ scriptBuilder.Emit(OpCode.RET);
+ tx = CurrentWallet.MakeTransaction(NeoSystem.StoreView, scriptBuilder.ToArray(), sender, signers, conflict);
+ }
+ catch (InvalidOperationException e)
+ {
+ ConsoleHelper.Error(GetExceptionMessage(e));
+ return;
+ }
+
+ if (NeoSystem.MemPool.TryGetValue(txid, out Transaction conflictTx))
+ {
+ tx.NetworkFee = Math.Max(tx.NetworkFee, conflictTx.NetworkFee) + 1;
+ }
+ else
+ {
+ var snapshot = NeoSystem.StoreView;
+ AssetDescriptor descriptor = new(snapshot, NeoSystem.Settings, NativeContract.GAS.Hash);
+ string extracFee = ReadUserInput("This tx is not in mempool, please input extra fee manually");
+ if (!BigDecimal.TryParse(extracFee, descriptor.Decimals, out BigDecimal decimalExtraFee) || decimalExtraFee.Sign <= 0)
+ {
+ ConsoleHelper.Error("Incorrect Amount Format");
+ return;
+ }
+ tx.NetworkFee += (long)decimalExtraFee.Value;
+ };
+
+ ConsoleHelper.Info("Network fee: ",
+ $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)}\t",
+ "Total fee: ",
+ $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS");
+ if (!ReadUserInput("Relay tx? (no|yes)").IsYes())
+ {
+ return;
+ }
+ SignAndSendTx(NeoSystem.StoreView, tx);
+ }
+
///
/// Process "show gas" command
///