Repository: onflow/flow-cli (go)

Issue:
Transaction confirmation screen
Currently, transaction confirmation screens are only visible for the signing command. It would be better if the confirmation screen is visible on all transactions commands as they have a big impact and can lead to losses if the wrong transaction is sent to the network.

Add confirmation screens to `build`, `send-signed` and `send` commands.

Pull request description:
Add prompts for build and send-signed
Closes #410

## Description

Adds prompt for the `sendsigned` and `build` commands.

Currently auto accepts for the `send` command, as this is currently used in ways that make upgrading it less convenient and would require other work in additional repos. It's also less important, as it's a single step process so it should be "easier" to know what is being sent. Still would be good to have, but i think is a bigger lift for a smaller gain, so better to just tackle to two higher value commands first.

______

For contributor use:

- [ ] Targeted PR against `master` branch
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work
- [ ] Code follows the [standards mentioned here](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md#styleguides)
- [ ] Updated relevant documentation
- [ ] Re-reviewed `Files changed` in the Github PR explorer
- [ ] Added appropriate labels


Patch:
diff --git a/internal/transactions/build.go b/internal/transactions/build.go
index fc3e1a2c..64b771c4 100644
--- a/internal/transactions/build.go
+++ b/internal/transactions/build.go
@@ -112,6 +112,7 @@ func build(
 		buildFlags.GasLimit,
 		transactionArgs,
 		globalFlags.Network,
+		globalFlags.Yes,
 	)
 	if err != nil {
 		return nil, err
diff --git a/internal/transactions/send-signed.go b/internal/transactions/send-signed.go
index 29d91c4f..7970b924 100644
--- a/internal/transactions/send-signed.go
+++ b/internal/transactions/send-signed.go
@@ -50,7 +50,7 @@ var SendSignedCommand = &command.Command{
 func sendSigned(
 	args []string,
 	readerWriter flowkit.ReaderWriter,
-	_ command.GlobalFlags,
+	globalFlags command.GlobalFlags,
 	services *services.Services,
 	_ *flowkit.State,
 ) (command.Result, error) {
@@ -61,7 +61,7 @@ func sendSigned(
 		return nil, fmt.Errorf("error loading transaction payload: %w", err)
 	}
 
-	tx, result, err := services.Transactions.SendSigned(code)
+	tx, result, err := services.Transactions.SendSigned(code, globalFlags.Yes)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/flowkit/output/prompt.go b/pkg/flowkit/output/prompt.go
index bb96355c..21a43924 100644
--- a/pkg/flowkit/output/prompt.go
+++ b/pkg/flowkit/output/prompt.go
@@ -32,7 +32,19 @@ import (
 	"github.com/manifoldco/promptui"
 )
 
-func ApproveTransactionPrompt(transaction *flowkit.Transaction) bool {
+func ApproveTransactionForSigningPrompt(transaction *flowkit.Transaction) bool {
+	return ApproveTransactionPrompt(transaction, "⚠️  Do you want to SIGN this transaction?")
+}
+
+func ApproveTransactionForBuildingPrompt(transaction *flowkit.Transaction) bool {
+	return ApproveTransactionPrompt(transaction, "⚠️  Do you want to BUILD this transaction?")
+}
+
+func ApproveTransactionForSendingPrompt(transaction *flowkit.Transaction) bool {
+	return ApproveTransactionPrompt(transaction, "⚠️  Do you want to SEND this transaction?")
+}
+
+func ApproveTransactionPrompt(transaction *flowkit.Transaction, promptMsg string) bool {
 	writer := uilive.New()
 	tx := transaction.FlowTransaction()
 
@@ -85,7 +97,7 @@ func ApproveTransactionPrompt(transaction *flowkit.Transaction) bool {
 	_ = writer.Flush()
 
 	prompt := promptui.Select{
-		Label: "⚠️  Do you want to sign this transaction?",
+		Label: promptMsg,
 		Items: []string{"No", "Yes"},
 	}
 
diff --git a/pkg/flowkit/services/transactions.go b/pkg/flowkit/services/transactions.go
index a932d09a..b7d49052 100644
--- a/pkg/flowkit/services/transactions.go
+++ b/pkg/flowkit/services/transactions.go
@@ -87,6 +87,7 @@ func (t *Transactions) Build(
 	gasLimit uint64,
 	args []cadence.Value,
 	network string,
+	approveBuild bool,
 ) (*flowkit.Transaction, error) {
 
 	latestBlock, err := t.gateway.GetLatestBlock()
@@ -143,6 +144,14 @@ func (t *Transactions) Build(
 		return nil, err
 	}
 
+	if approveBuild {
+		return tx, nil
+	}
+
+	if !output.ApproveTransactionForBuildingPrompt(tx) {
+		return nil, fmt.Errorf("transaction was not approved")
+	}
+
 	return tx, nil
 }
 
@@ -170,7 +179,7 @@ func (t *Transactions) Sign(
 		return tx.Sign()
 	}
 
-	if !output.ApproveTransactionPrompt(tx) {
+	if !output.ApproveTransactionForSigningPrompt(tx) {
 		return nil, fmt.Errorf("transaction was not approved for signing")
 	}
 
@@ -180,11 +189,15 @@ func (t *Transactions) Sign(
 // SendSigned sends the transaction that is already signed.
 func (t *Transactions) SendSigned(
 	payload []byte,
+	approveSend bool,
 ) (*flow.Transaction, *flow.TransactionResult, error) {
 	tx, err := flowkit.NewTransactionFromPayload(payload)
 	if err != nil {
 		return nil, nil, err
 	}
+	if !approveSend && !output.ApproveTransactionForSendingPrompt(tx) {
+		return nil, nil, fmt.Errorf("transaction was not approved for sending")
+	}
 
 	t.logger.StartProgress(fmt.Sprintf("Sending transaction with ID: %s", tx.FlowTransaction().ID()))
 	defer t.logger.StopProgress()
@@ -227,6 +240,7 @@ func (t *Transactions) Send(
 		gasLimit,
 		args,
 		network,
+		true,
 	)
 	if err != nil {
 		return nil, nil, err


Test patch:
diff --git a/pkg/flowkit/services/transactions_test.go b/pkg/flowkit/services/transactions_test.go
index 776a9f60..5792ca64 100644
--- a/pkg/flowkit/services/transactions_test.go
+++ b/pkg/flowkit/services/transactions_test.go
@@ -142,6 +142,7 @@ func TestTransactions_Integration(t *testing.T) {
 			gas     uint64
 			args    []cadence.Value
 			network string
+			yes     bool
 		}
 
 		a, _ := state.Accounts().ByName("Alice")
@@ -158,6 +159,7 @@ func TestTransactions_Integration(t *testing.T) {
 			1000,
 			nil,
 			"",
+			true,
 		}, {
 			c.Address(),
 			[]flow.Address{a.Address(), b.Address()},
@@ -168,10 +170,11 @@ func TestTransactions_Integration(t *testing.T) {
 			1000,
 			nil,
 			"",
+			true,
 		}}
 
 		for _, i := range txIns {
-			tx, err := s.Transactions.Build(i.prop, i.auth, i.payer, i.index, i.code, i.file, i.gas, i.args, i.network)
+			tx, err := s.Transactions.Build(i.prop, i.auth, i.payer, i.index, i.code, i.file, i.gas, i.args, i.network, i.yes)
 
 			assert.NoError(t, err)
 			ftx := tx.FlowTransaction()
@@ -227,6 +230,7 @@ func TestTransactions_Integration(t *testing.T) {
 			1000,
 			nil,
 			n.Name,
+			true,
 		)
 
 		assert.NoError(t, err)
@@ -258,6 +262,7 @@ func TestTransactions_Integration(t *testing.T) {
 			1000,
 			nil,
 			"",
+			true,
 		)
 
 		assert.Nil(t, err)
@@ -294,6 +299,7 @@ func TestTransactions_Integration(t *testing.T) {
 			1000,
 			nil,
 			"",
+			true,
 		)
 
 		assert.Nil(t, err)
@@ -309,6 +315,7 @@ func TestTransactions_Integration(t *testing.T) {
 
 		txSent, txResult, err := s.Transactions.SendSigned(
 			[]byte(fmt.Sprintf("%x", txSigned.FlowTransaction().Encode())),
+			true,
 		)
 		assert.Nil(t, err)
 		assert.Equal(t, txResult.Status, flow.TransactionStatusSealed)
@@ -333,6 +340,7 @@ func TestTransactions_Integration(t *testing.T) {
 			1000,
 			nil,
 			"",
+			true,
 		)
 
 		assert.Nil(t, err)
@@ -367,6 +375,7 @@ func TestTransactions_Integration(t *testing.T) {
 			1000,
 			nil,
 			"",
+			true,
 		)
 
 		assert.EqualError(t, err, "provided authorizers length mismatch, required authorizers 2, but provided 1")