cmd/pushtx: fix gas price 0 rejection, show gas price in summary, print raw hex on error

Co-authored-by: drQedwards <213266729+drQedwards@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-12 16:20:59 +00:00
parent 53ff818308
commit cc2957508f
2 changed files with 58 additions and 6 deletions

View file

@ -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] <tx-hex>

View file

@ -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()