diff --git a/internal/transactions/build.go b/internal/transactions/build.go index e2b3ee926..a96448106 100644 --- a/internal/transactions/build.go +++ b/internal/transactions/build.go @@ -127,6 +127,7 @@ func build( return &transactionResult{ tx: tx.FlowTransaction(), include: []string{"code", "payload", "signatures"}, + network: flow.Network().Name, }, nil } diff --git a/internal/transactions/decode.go b/internal/transactions/decode.go index aebf5b857..4a7037802 100644 --- a/internal/transactions/decode.go +++ b/internal/transactions/decode.go @@ -69,5 +69,6 @@ func decode( return &transactionResult{ tx: tx.FlowTransaction(), include: decodeFlags.Include, + network: "", // decode doesn't have network context }, nil } diff --git a/internal/transactions/get-system.go b/internal/transactions/get-system.go index ac1856085..1e92ec41d 100644 --- a/internal/transactions/get-system.go +++ b/internal/transactions/get-system.go @@ -76,5 +76,6 @@ func getSystemTransaction( tx: tx, include: getSystemFlags.Include, exclude: getSystemFlags.Exclude, + network: flow.Network().Name, }, nil } diff --git a/internal/transactions/get.go b/internal/transactions/get.go index f36a6e032..6cc40c1dd 100644 --- a/internal/transactions/get.go +++ b/internal/transactions/get.go @@ -70,5 +70,6 @@ func get( tx: tx, include: getFlags.Include, exclude: getFlags.Exclude, + network: flow.Network().Name, }, nil } diff --git a/internal/transactions/send-signed.go b/internal/transactions/send-signed.go index 0239d9141..0da58ed15 100644 --- a/internal/transactions/send-signed.go +++ b/internal/transactions/send-signed.go @@ -88,5 +88,6 @@ func sendSigned( tx: sentTx, include: sendSignedFlags.Include, exclude: sendSignedFlags.Exclude, + network: flow.Network().Name, }, nil } diff --git a/internal/transactions/send.go b/internal/transactions/send.go index 8237e9dc2..a7dbe5f33 100644 --- a/internal/transactions/send.go +++ b/internal/transactions/send.go @@ -157,5 +157,6 @@ func SendTransaction(code []byte, args []string, location string, flow flowkit.S tx: tx, include: sendFlags.Include, exclude: sendFlags.Exclude, + network: flow.Network().Name, }, nil } diff --git a/internal/transactions/sign.go b/internal/transactions/sign.go index a21f85e85..689e157ec 100644 --- a/internal/transactions/sign.go +++ b/internal/transactions/sign.go @@ -141,6 +141,7 @@ func sign( return &transactionResult{ tx: signed.FlowTransaction(), include: signFlags.Include, + network: flow.Network().Name, }, nil } diff --git a/internal/transactions/transactions.go b/internal/transactions/transactions.go index 63da701c0..54624408b 100644 --- a/internal/transactions/transactions.go +++ b/internal/transactions/transactions.go @@ -58,15 +58,39 @@ type transactionResult struct { tx *flow.Transaction include []string exclude []string + network string } func NewTransactionResult(tx *flow.Transaction, result *flow.TransactionResult) *transactionResult { return &transactionResult{ - result: result, - tx: tx, + result: result, + tx: tx, + network: "", // Default to empty, should be set by caller } } +// getBlockExplorerLink returns the block explorer link for the transaction if it's on mainnet or testnet +func (r *transactionResult) getBlockExplorerLink() string { + if r.network == "" { + return "" + } + + // Only show block explorer links for mainnet and testnet + if r.network != "mainnet" && r.network != "testnet" { + return "" + } + + txID := r.tx.ID().String() + + if r.network == "mainnet" { + return fmt.Sprintf("https://www.flowscan.io/tx/%s", txID) + } else if r.network == "testnet" { + return fmt.Sprintf("https://testnet.flowscan.io/tx/%s", txID) + } + + return "" +} + func (r *transactionResult) JSON() any { result := make(map[string]any) result["id"] = r.tx.ID().String() @@ -74,6 +98,10 @@ func (r *transactionResult) JSON() any { result["authorizers"] = fmt.Sprintf("%s", r.tx.Authorizers) result["payer"] = r.tx.Payer.String() + if blockExplorerLink := r.getBlockExplorerLink(); blockExplorerLink != "" { + result["view_on_block_explorer"] = blockExplorerLink + } + if r.result != nil { result["block_id"] = r.result.BlockID.String() result["block_height"] = r.result.BlockHeight @@ -120,6 +148,7 @@ func (r *transactionResult) String() string { } _, _ = fmt.Fprintf(writer, "ID\t%s\n", r.tx.ID()) + _, _ = fmt.Fprintf(writer, "Payer\t%s\n", r.tx.Payer.Hex()) _, _ = fmt.Fprintf(writer, "Authorizers\t%s\n", r.tx.Authorizers) @@ -212,6 +241,10 @@ func (r *transactionResult) String() string { _, _ = fmt.Fprint(writer, "\n\nFee Events (hidden, use --include fee-events)") } + if blockExplorerLink := r.getBlockExplorerLink(); blockExplorerLink != "" { + _, _ = fmt.Fprintf(writer, "\n\nšŸ”— View on Block Explorer:\n%s", blockExplorerLink) + } + _ = writer.Flush() return b.String() } diff --git a/internal/transactions/transactions_test.go b/internal/transactions/transactions_test.go index b69eb0d14..5f48b801f 100644 --- a/internal/transactions/transactions_test.go +++ b/internal/transactions/transactions_test.go @@ -591,4 +591,56 @@ Code (hidden, use --include code) Payload (hidden, use --include payload)`, output.OkEmoji()), "\n"), result.String()) }) + + t.Run("Block explorer link for mainnet", func(t *testing.T) { + result := transactionResult{tx: tx, result: txResult, network: "mainnet"} + + output := result.String() + assert.Contains(t, output, "šŸ”— View on Block Explorer:") + assert.Contains(t, output, "https://www.flowscan.io/tx/e913d1f3e431c7df49c99845bea9ebff9db11bbf25d507b9ad0fad45652d515f") + + jsonResult := result.JSON() + jsonMap, ok := jsonResult.(map[string]any) + assert.True(t, ok) + assert.Contains(t, jsonMap, "view_on_block_explorer") + assert.Equal(t, "https://www.flowscan.io/tx/e913d1f3e431c7df49c99845bea9ebff9db11bbf25d507b9ad0fad45652d515f", jsonMap["view_on_block_explorer"]) + }) + + t.Run("Block explorer link for testnet", func(t *testing.T) { + result := transactionResult{tx: tx, result: txResult, network: "testnet"} + + output := result.String() + assert.Contains(t, output, "šŸ”— View on Block Explorer:") + assert.Contains(t, output, "https://testnet.flowscan.io/tx/e913d1f3e431c7df49c99845bea9ebff9db11bbf25d507b9ad0fad45652d515f") + + jsonResult := result.JSON() + jsonMap, ok := jsonResult.(map[string]any) + assert.True(t, ok) + assert.Contains(t, jsonMap, "view_on_block_explorer") + assert.Equal(t, "https://testnet.flowscan.io/tx/e913d1f3e431c7df49c99845bea9ebff9db11bbf25d507b9ad0fad45652d515f", jsonMap["view_on_block_explorer"]) + }) + + t.Run("No block explorer link for emulator", func(t *testing.T) { + result := transactionResult{tx: tx, result: txResult, network: "emulator"} + + output := result.String() + assert.NotContains(t, output, "šŸ”— View on Block Explorer:") + + jsonResult := result.JSON() + jsonMap, ok := jsonResult.(map[string]any) + assert.True(t, ok) + assert.NotContains(t, jsonMap, "view_on_block_explorer") + }) + + t.Run("No block explorer link for empty network", func(t *testing.T) { + result := transactionResult{tx: tx, result: txResult, network: ""} + + output := result.String() + assert.NotContains(t, output, "šŸ”— View on Block Explorer:") + + jsonResult := result.JSON() + jsonMap, ok := jsonResult.(map[string]any) + assert.True(t, ok) + assert.NotContains(t, jsonMap, "view_on_block_explorer") + }) }