diff --git a/cmd/pushtx/main.go b/cmd/pushtx/main.go index 87fce39a57..e4a8ff226a 100644 --- a/cmd/pushtx/main.go +++ b/cmd/pushtx/main.go @@ -105,6 +105,9 @@ func run(args []string, stdin io.Reader) error { // Send to the RPC endpoint. hash, err := sendRawTransaction(rpcURL, rawTx) if err != nil { + // Still print the raw hex so the user can submit it elsewhere + // (e.g. etherscan.io/pushTx). + fmt.Println("Raw tx:", txHex) return fmt.Errorf("sending transaction: %w", err) } fmt.Println("Transaction submitted successfully") @@ -112,7 +115,7 @@ func run(args []string, stdin io.Reader) error { // Print the raw hex transaction as the last output for easy // copy-paste into block explorers like etherscan.io/pushTx. - fmt.Println("Raw tx: 0x" + hex.EncodeToString(rawTx)) + fmt.Println("Raw tx:", txHex) return nil } @@ -152,6 +155,7 @@ func printTxSummary(tx *types.Transaction) { fmt.Println(" Nonce: ", tx.Nonce()) fmt.Println(" Value: ", formatWei(tx.Value())) fmt.Println(" Gas limit:", tx.Gas()) + fmt.Println(" Gas price:", formatGwei(tx.GasPrice())) fmt.Println(" Chain ID: ", tx.ChainId()) } @@ -165,6 +169,15 @@ func formatWei(wei *big.Int) string { return fmt.Sprintf("%s wei (%s ETH)", wei.String(), ether.Text('f', 18)) } +// formatGwei converts a wei gas price to a human-readable string in Gwei. +func formatGwei(wei *big.Int) string { + if wei == nil || wei.Sign() == 0 { + return "0 wei (0 Gwei)" + } + gwei := new(big.Float).Quo(new(big.Float).SetInt(wei), new(big.Float).SetFloat64(1e9)) + return fmt.Sprintf("%s wei (%s Gwei)", wei.String(), gwei.Text('f', 9)) +} + func printUsage() { fmt.Fprintf(os.Stderr, `Usage: pushtx [--rpc URL] diff --git a/cmd/pushtx/main_test.go b/cmd/pushtx/main_test.go index d815ab3b73..0459602928 100644 --- a/cmd/pushtx/main_test.go +++ b/cmd/pushtx/main_test.go @@ -49,7 +49,7 @@ func signedTestTxWithKey(t *testing.T, key *ecdsa.PrivateKey) (*types.Transactio tx := types.NewTx(&types.LegacyTx{ Nonce: 6, - GasPrice: big.NewInt(0), + GasPrice: big.NewInt(1_000_000_000), // 1 Gwei – real networks reject gas price 0 Gas: 21055, To: addrPtr(common.HexToAddress("0x78b5290269740033b05bd8d71c97331295eb5918")), Value: new(big.Int).Mul(big.NewInt(10), big.NewInt(1e18)), // 10 ETH @@ -149,12 +149,33 @@ func TestRunRPCError(t *testing.T) { defer srv.Close() _, txHex := signedTestTx(t) - err := run([]string{"--rpc", srv.URL, txHex}, strings.NewReader("")) - if err == nil { + + // Capture stdout – raw hex should still be printed on RPC failure. + oldStdout := os.Stdout + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + os.Stdout = w + + runErr := run([]string{"--rpc", srv.URL, txHex}, strings.NewReader("")) + + w.Close() + os.Stdout = oldStdout + + if runErr == nil { t.Fatal("expected error, got nil") } - if !strings.Contains(err.Error(), "already known") { - t.Fatalf("unexpected error message: %v", err) + if !strings.Contains(runErr.Error(), "already known") { + t.Fatalf("unexpected error message: %v", runErr) + } + + out, err := io.ReadAll(r) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(out), "Raw tx: 0x") { + t.Fatal("expected raw hex in output even on RPC error") } } @@ -233,6 +254,24 @@ func TestFormatWei(t *testing.T) { } } +func TestFormatGwei(t *testing.T) { + tests := []struct { + wei *big.Int + want string + }{ + {nil, "0 wei (0 Gwei)"}, + {big.NewInt(0), "0 wei (0 Gwei)"}, + {big.NewInt(1_000_000_000), "1000000000 wei (1.000000000 Gwei)"}, + {big.NewInt(20_000_000_000), "20000000000 wei (20.000000000 Gwei)"}, + } + for _, tt := range tests { + got := formatGwei(tt.wei) + if got != tt.want { + t.Errorf("formatGwei(%v) = %q, want %q", tt.wei, got, tt.want) + } + } +} + func TestRunEqualsSyntax(t *testing.T) { srv := fakeRPC(t, false) defer srv.Close()