diff --git a/cli/nep_test/nep11_test.go b/cli/nep_test/nep11_test.go index 334a27dd7..e2866c107 100644 --- a/cli/nep_test/nep11_test.go +++ b/cli/nep_test/nep11_test.go @@ -328,6 +328,14 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) { e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) checkBalanceResult(t, nftOwnerAddr, tokenID1) + // check --await flag + tokenID2 := mint(t) + e.In.WriteString(nftOwnerPass + "\r") + e.Run(t, append(cmdTransfer, "--await", "--id", hex.EncodeToString(tokenID2))...) + e.CheckAwaitableTxPersisted(t) + e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...) + checkBalanceResult(t, nftOwnerAddr, tokenID1) + // transfer: good, to NEP-11-Payable contract, with data verifyH := deployVerifyContract(t, e) cmdTransfer = []string{ diff --git a/cli/nep_test/nep17_test.go b/cli/nep_test/nep17_test.go index 5964added..f933e0559 100644 --- a/cli/nep_test/nep17_test.go +++ b/cli/nep_test/nep17_test.go @@ -223,12 +223,19 @@ func TestNEP17Transfer(t *testing.T) { "neo-go", "wallet", "nep17", "transfer", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, - "--to", address.Uint160ToString(e.Chain.GetNotaryContractScriptHash()), "--token", "GAS", "--amount", "1", - "--from", testcli.ValidatorAddr, "--force", - "[", testcli.ValidatorAddr, strconv.Itoa(int(validTil)), "]"} + "--from", testcli.ValidatorAddr} + + t.Run("with await", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(cmd, "--to", nftOwnerAddr, "--await")...) + e.CheckAwaitableTxPersisted(t) + }) + + cmd = append(cmd, "--to", address.Uint160ToString(e.Chain.GetNotaryContractScriptHash()), + "[", testcli.ValidatorAddr, strconv.Itoa(int(validTil)), "]") t.Run("with data", func(t *testing.T) { e.In.WriteString("one\r") diff --git a/cli/options/options.go b/cli/options/options.go index 1cef56915..5fdc59c2f 100644 --- a/cli/options/options.go +++ b/cli/options/options.go @@ -32,8 +32,14 @@ import ( "gopkg.in/yaml.v3" ) -// DefaultTimeout is the default timeout used for RPC requests. -const DefaultTimeout = 10 * time.Second +const ( + // DefaultTimeout is the default timeout used for RPC requests. + DefaultTimeout = 10 * time.Second + // DefaultAwaitableTimeout is the default timeout used for RPC requests that + // require transaction awaiting. It is set to the approximate time of three + // Neo N3 mainnet blocks accepting. + DefaultAwaitableTimeout = 3 * 15 * time.Second +) // RPCEndpointFlag is a long flag name for an RPC endpoint. It can be used to // check for flag presence in the context. @@ -129,6 +135,9 @@ func GetTimeoutContext(ctx *cli.Context) (context.Context, func()) { if dur == 0 { dur = DefaultTimeout } + if !ctx.IsSet("timeout") && ctx.Bool("await") { + dur = DefaultAwaitableTimeout + } return context.WithTimeout(context.Background(), dur) } diff --git a/cli/smartcontract/contract_test.go b/cli/smartcontract/contract_test.go index a263413cc..4051952e0 100644 --- a/cli/smartcontract/contract_test.go +++ b/cli/smartcontract/contract_test.go @@ -338,7 +338,7 @@ func TestContractDeployWithData(t *testing.T) { "--config", "testdata/deploy/neo-go.yml", "--out", nefName, "--manifest", manifestName) - deployContract := func(t *testing.T, haveData bool, scope string) { + deployContract := func(t *testing.T, haveData bool, scope string, await bool) { e := testcli.NewExecutor(t, true) cmd := []string{ "neo-go", "contract", "deploy", @@ -348,6 +348,9 @@ func TestContractDeployWithData(t *testing.T) { "--force", } + if await { + cmd = append(cmd, "--await") + } if haveData { cmd = append(cmd, "[", "key1", "12", "key2", "take_me_to_church", "]") } @@ -358,8 +361,13 @@ func TestContractDeployWithData(t *testing.T) { } e.In.WriteString(testcli.ValidatorPass + "\r") e.Run(t, cmd...) + var tx *transaction.Transaction + if await { + tx, _ = e.CheckAwaitableTxPersisted(t) + } else { + tx, _ = e.CheckTxPersisted(t) + } - tx, _ := e.CheckTxPersisted(t, "Sent invocation transaction ") require.Equal(t, scope, tx.Signers[0].Scopes.String()) if !haveData { return @@ -396,9 +404,12 @@ func TestContractDeployWithData(t *testing.T) { require.Equal(t, []byte("take_me_to_church"), res.Stack[0].Value()) } - deployContract(t, true, "") - deployContract(t, false, "Global") - deployContract(t, true, "Global") + deployContract(t, true, "", false) + deployContract(t, false, "Global", false) + deployContract(t, true, "Global", false) + deployContract(t, false, "", true) + deployContract(t, true, "Global", true) + deployContract(t, true, "", true) } func TestDeployWithSigners(t *testing.T) { @@ -772,6 +783,12 @@ func TestComlileAndInvokeFunction(t *testing.T) { e.Run(t, append(cmd, h.StringLE(), "getValue", "--", testcli.ValidatorAddr, hVerify.StringLE())...) }) + + t.Run("with await", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(cmd, "--force", "--await", h.StringLE(), "getValue")...) + e.CheckAwaitableTxPersisted(t) + }) }) t.Run("real invoke and save tx", func(t *testing.T) { diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index ec3fe68fc..00c0cce89 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -91,6 +91,7 @@ func NewCommands() []cli.Command { txctx.SysGasFlag, txctx.OutFlag, txctx.ForceFlag, + txctx.AwaitFlag, } invokeFunctionFlags = append(invokeFunctionFlags, options.Wallet...) invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...) @@ -188,10 +189,12 @@ func NewCommands() []cli.Command { { Name: "deploy", Usage: "deploy a smart contract (.nef with description)", - UsageText: "neo-go contract deploy -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] --in contract.nef --manifest contract.manifest.json [--out file] [--force] [data]", + UsageText: "neo-go contract deploy -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] --in contract.nef --manifest contract.manifest.json [--out file] [--force] [--await] [data]", Description: `Deploys given contract into the chain. The gas parameter is for additional gas to be added as a network fee to prioritize the transaction. The data - parameter is an optional parameter to be passed to '_deploy' method. + parameter is an optional parameter to be passed to '_deploy' method. When + --await flag is specified, it waits for the transaction to be included + in a block. `, Action: contractDeploy, Flags: deployFlags, @@ -201,13 +204,14 @@ func NewCommands() []cli.Command { { Name: "invokefunction", Usage: "invoke deployed contract on the blockchain", - UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] [--out file] [--force] scripthash [method] [arguments...] [--] [signers...]", + UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] [--out file] [--force] [--await] scripthash [method] [arguments...] [--] [signers...]", Description: `Executes given (as a script hash) deployed script with the given method, arguments and signers. Sender is included in the list of signers by default with None witness scope. If you'd like to change default sender's scope, specify it via signers parameter. See testinvokefunction documentation for the details about parameters. It differs from testinvokefunction in that this - command sends an invocation transaction to the network. + command sends an invocation transaction to the network. When --await flag is + specified, it waits for the transaction to be included in a block. `, Action: invokeFunction, Flags: invokeFunctionFlags, diff --git a/cli/txctx/tx.go b/cli/txctx/tx.go index 64a6b0f67..c7cee5c07 100644 --- a/cli/txctx/tx.go +++ b/cli/txctx/tx.go @@ -5,13 +5,16 @@ package txctx import ( "fmt" + "io" "time" "github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/paramcontext" + "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/urfave/cli" ) @@ -37,6 +40,11 @@ var ( Name: "force", Usage: "Do not ask for a confirmation (and ignore errors)", } + // AwaitFlag is a flag used to wait for the transaction to be included in a block. + AwaitFlag = cli.BoolFlag{ + Name: "await", + Usage: "wait for the transaction to be included in a block", + } ) // SignAndSend adds network and system fees to the provided transaction and @@ -48,6 +56,7 @@ func SignAndSend(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *tr gas = flags.Fixed8FromContext(ctx, "gas") sysgas = flags.Fixed8FromContext(ctx, "sysgas") ver = act.GetVersion() + aer *state.AppExecResult ) tx.SystemFee += int64(sysgas) @@ -68,12 +77,37 @@ func SignAndSend(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *tr // Compensate for confirmation waiting. tx.ValidUntilBlock += uint32((waitTime.Milliseconds() / int64(ver.Protocol.MillisecondsPerBlock))) + 1 } - _, _, err = act.SignAndSend(tx) + var ( + resTx util.Uint256 + vub uint32 + ) + resTx, vub, err = act.SignAndSend(tx) + if err != nil { + return cli.NewExitError(err, 1) + } + if ctx.Bool("await") { + aer, err = act.Wait(resTx, vub, err) + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", resTx.StringLE(), err), 1) + } + } } if err != nil { return cli.NewExitError(err, 1) } - fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE()) + DumpTransactionInfo(ctx.App.Writer, tx.Hash(), aer) return nil } + +// DumpTransactionInfo prints transaction info to the given writer. +func DumpTransactionInfo(w io.Writer, h util.Uint256, res *state.AppExecResult) { + fmt.Fprintln(w, h.StringLE()) + if res != nil { + fmt.Fprintf(w, "OnChain:\t%t\n", res != nil) + fmt.Fprintf(w, "VMState:\t%s\n", res.VMState.String()) + if res.FaultException != "" { + fmt.Fprintf(w, "FaultException:\t%s\n", res.FaultException) + } + } +} diff --git a/cli/util/cancel.go b/cli/util/cancel.go index 19376217d..531b941b6 100644 --- a/cli/util/cancel.go +++ b/cli/util/cancel.go @@ -1,15 +1,19 @@ package util import ( + "errors" "fmt" "strings" "github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/cli/txctx" + "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/urfave/cli" @@ -55,7 +59,7 @@ func cancelTx(ctx *cli.Context) error { return cli.NewExitError(fmt.Errorf("account %s is not a signer of the conflicting transaction", acc.Address), 1) } - resHash, _, err := a.SendTunedRun([]byte{byte(opcode.RET)}, []transaction.Attribute{{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: txHash}}}, func(r *result.Invoke, t *transaction.Transaction) error { + resHash, resVub, err := a.SendTunedRun([]byte{byte(opcode.RET)}, []transaction.Attribute{{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: txHash}}}, func(r *result.Invoke, t *transaction.Transaction) error { err := actor.DefaultCheckerModifier(r, t) if err != nil { return err @@ -72,6 +76,29 @@ func cancelTx(ctx *cli.Context) error { if err != nil { return cli.NewExitError(fmt.Errorf("failed to send conflicting transaction: %w", err), 1) } - fmt.Fprintln(ctx.App.Writer, resHash.StringLE()) + var ( + acceptedH = resHash + res *state.AppExecResult + ) + if ctx.Bool("await") { + res, err = a.WaitAny(gctx, resVub, txHash, resHash) + if err != nil { + if errors.Is(err, waiter.ErrTxNotAccepted) { + if mainTx == nil { + return cli.NewExitError(fmt.Errorf("neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of conlicting transaction). Main transaction is unknown to the provided RPC node, thus still has chances to be accepted, you may try cancellation again", resVub), 1) + } + fmt.Fprintf(ctx.App.Writer, "Neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of both target and conflicting transactions). Main transaction is not valid anymore, cancellation is successful\n", resVub) + return nil + } + return cli.NewExitError(fmt.Errorf("failed to await target/ conflicting transaction %s/ %s: %w", txHash.StringLE(), resHash.StringLE(), err), 1) + } + if txHash.Equals(res.Container) { + fmt.Fprintln(ctx.App.Writer, "Target transaction accepted") + acceptedH = txHash + } else { + fmt.Fprintln(ctx.App.Writer, "Conflicting transaction accepted") + } + } + txctx.DumpTransactionInfo(ctx.App.Writer, acceptedH, res) return nil } diff --git a/cli/util/convert.go b/cli/util/convert.go index 98ea3ac22..5d8090fef 100644 --- a/cli/util/convert.go +++ b/cli/util/convert.go @@ -17,12 +17,14 @@ import ( // NewCommands returns util commands for neo-go CLI. func NewCommands() []cli.Command { txDumpFlags := append([]cli.Flag{}, options.RPC...) + txSendFlags := append(txDumpFlags, txctx.AwaitFlag) txCancelFlags := append([]cli.Flag{ flags.AddressFlag{ Name: "address, a", Usage: "address to use as conflicting transaction signee (and gas source)", }, txctx.GasFlag, + txctx.AwaitFlag, }, options.RPC...) txCancelFlags = append(txCancelFlags, options.Wallet...) return []cli.Command{ @@ -42,19 +44,20 @@ func NewCommands() []cli.Command { { Name: "sendtx", Usage: "Send complete transaction stored in a context file", - UsageText: "sendtx [-r ] ", + UsageText: "sendtx [-r ] [--await]", Description: `Sends the transaction from the given context file to the given RPC node if it's completely signed and ready. This command expects a ContractParametersContext JSON file for input, it can't handle binary (or hex- or base64-encoded) - transactions. + transactions. If the --await flag is included, the command waits for the + transaction to be included in a block before exiting. `, Action: sendTx, - Flags: txDumpFlags, + Flags: txSendFlags, }, { Name: "canceltx", Usage: "Cancel transaction by sending conflicting transaction", - UsageText: "canceltx -r --wallet [--account ] [--wallet-config ] [--gas ]", + UsageText: "canceltx -r --wallet [--account ] [--wallet-config ] [--gas ] [--await]", Description: `Aims to prevent a transaction from being added to the blockchain by dispatching a more prioritized conflicting transaction to the specified RPC node. The input for this command should be the transaction hash. If another account is not specified, the conflicting transaction is @@ -65,7 +68,8 @@ func NewCommands() []cli.Command { target transaction's ValidUntilBlock value. If the target transaction is not in the memory pool, standard NetworkFee calculations are performed based on the calculatenetworkfee RPC request. If the --gas flag is included, the specified value is added to the resulting conflicting transaction network fee - in both scenarios.`, + in both scenarios. When the --await flag is included, the command waits for one of the conflicting + or target transactions to be included in a block.`, Action: cancelTx, Flags: txCancelFlags, }, @@ -75,6 +79,11 @@ func NewCommands() []cli.Command { UsageText: "txdump [-r ] ", Action: txDump, Flags: txDumpFlags, + Description: `Dumps the transaction from the given parameter context file to + the output. This command expects a ContractParametersContext JSON file for input, it can't handle + binary (or hex- or base64-encoded) transactions. If --rpc-endpoint flag is specified the result + of the given script after running it true the VM will be printed. Otherwise only transaction will + be printed.`, }, { Name: "ops", diff --git a/cli/util/send.go b/cli/util/send.go index 59814c103..a67465274 100644 --- a/cli/util/send.go +++ b/cli/util/send.go @@ -5,6 +5,9 @@ import ( "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/paramcontext" + "github.com/nspcc-dev/neo-go/cli/txctx" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter" "github.com/urfave/cli" ) @@ -37,6 +40,14 @@ func sendTx(ctx *cli.Context) error { if err != nil { return cli.NewExitError(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1) } - fmt.Fprintln(ctx.App.Writer, res.StringLE()) + var aer *state.AppExecResult + if ctx.Bool("await") { + version, err := c.GetVersion() + aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err) + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1) + } + } + txctx.DumpTransactionInfo(ctx.App.Writer, res, aer) return nil } diff --git a/cli/util/util_test.go b/cli/util/util_test.go index 33b3ade74..64364bc25 100644 --- a/cli/util/util_test.go +++ b/cli/util/util_test.go @@ -136,3 +136,44 @@ func TestUtilCancelTx(t *testing.T) { return aerErr == nil }, time.Second*2, time.Millisecond*50) } + +func TestAwaitUtilCancelTx(t *testing.T) { + e := testcli.NewExecutor(t, true) + + w, err := wallet.NewWalletFromFile("../testdata/testwallet.json") + require.NoError(t, err) + + transferArgs := []string{ + "neo-go", "wallet", "nep17", "transfer", + "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--to", w.Accounts[0].Address, + "--token", "NEO", + "--from", testcli.ValidatorAddr, + "--force", + } + args := []string{"neo-go", "util", "canceltx", + "-r", "http://" + e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--address", testcli.ValidatorAddr, + "--await"} + + e.In.WriteString("one\r") + e.Run(t, append(transferArgs, "--amount", "1")...) + line := e.GetNextLine(t) + txHash, err := util.Uint256DecodeStringLE(line) + require.NoError(t, err) + + _, ok := e.Chain.GetMemPool().TryGetValue(txHash) + require.True(t, ok) + + e.In.WriteString("one\r") + e.Run(t, append(args, txHash.StringLE())...) + e.CheckNextLine(t, "Conflicting transaction accepted") + resHash, _ := e.CheckAwaitableTxPersisted(t) + + require.Eventually(t, func() bool { + _, aerErr := e.Chain.GetAppExecResults(resHash.Hash(), trigger.Application) + return aerErr == nil + }, time.Second*2, time.Millisecond*50) +} diff --git a/cli/wallet/candidate_test.go b/cli/wallet/candidate_test.go index 446b9a82f..6a23ab020 100644 --- a/cli/wallet/candidate_test.go +++ b/cli/wallet/candidate_test.go @@ -159,4 +159,61 @@ func TestRegisterCandidate(t *testing.T) { e.RunWithError(t, "neo-go", "query", "voter", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], validatorAddress, validatorAddress) e.RunWithError(t, "neo-go", "query", "committee", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "something") e.RunWithError(t, "neo-go", "query", "candidates", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "something") + + t.Run("VoteUnvote await", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, "neo-go", "wallet", "candidate", "register", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--address", validatorAddress, + "--force", "--await") + e.CheckAwaitableTxPersisted(t) + + e.In.WriteString("one\r") + e.Run(t, "neo-go", "wallet", "candidate", "vote", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--address", validatorAddress, + "--candidate", validatorHex, + "--force", + "--await") + + e.CheckAwaitableTxPersisted(t) + b, _ := e.Chain.GetGoverningTokenBalance(testcli.ValidatorPriv.GetScriptHash()) + + // unvote + e.In.WriteString("one\r") + e.Run(t, "neo-go", "wallet", "candidate", "vote", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--address", validatorAddress, + "--force", "--await") + _, index := e.CheckAwaitableTxPersisted(t) + + vs, err = e.Chain.GetEnrollments() + require.Equal(t, 1, len(vs)) + require.Equal(t, validatorPublic, vs[0].Key) + require.Equal(t, big.NewInt(0), vs[0].Votes) + + // check state + e.Run(t, "neo-go", "query", "voter", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + validatorAddress) + e.CheckNextLine(t, "^\\s*Voted:\\s+"+"null") // no vote. + e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$") + e.CheckNextLine(t, "^\\s*Block\\s*:\\s*"+strconv.FormatUint(uint64(index), 10)) + e.CheckEOF(t) + }) + + e.In.WriteString("one\r") + e.Run(t, "neo-go", "wallet", "candidate", "unregister", + "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], + "--wallet", testcli.ValidatorWallet, + "--address", validatorAddress, + "--force", + "--await") + e.CheckAwaitableTxPersisted(t) + + vs, err = e.Chain.GetEnrollments() + require.Equal(t, 0, len(vs)) } diff --git a/cli/wallet/multisig.go b/cli/wallet/multisig.go index 337064e77..f6d600da9 100644 --- a/cli/wallet/multisig.go +++ b/cli/wallet/multisig.go @@ -8,7 +8,10 @@ import ( "github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/paramcontext" + "github.com/nspcc-dev/neo-go/cli/txctx" + "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter" "github.com/urfave/cli" ) @@ -17,6 +20,7 @@ func signStoredTransaction(ctx *cli.Context) error { out = ctx.String("out") rpcNode = ctx.String(options.RPCEndpointFlag) addrFlag = ctx.Generic("address").(*flags.Address) + aer *state.AppExecResult ) if err := cmdargs.EnsureNone(ctx); err != nil { return err @@ -84,10 +88,15 @@ func signStoredTransaction(ctx *cli.Context) error { if err != nil { return cli.NewExitError(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1) } - fmt.Fprintln(ctx.App.Writer, res.StringLE()) - return nil + if ctx.Bool("await") { + version, err := c.GetVersion() + aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err) + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1) + } + } } - fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE()) + txctx.DumpTransactionInfo(ctx.App.Writer, tx.Hash(), aer) return nil } diff --git a/cli/wallet/nep11.go b/cli/wallet/nep11.go index 8c5b9caf6..9fcce1a86 100644 --- a/cli/wallet/nep11.go +++ b/cli/wallet/nep11.go @@ -101,7 +101,7 @@ func newNEP11Commands() []cli.Command { { Name: "transfer", Usage: "transfer NEP-11 tokens", - UsageText: "transfer -w wallet [--wallet-config path] --rpc-endpoint --timeout