github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/crypto/keys/client/maketx.go (about)

     1  package client
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  
     7  	"github.com/gnolang/gno/tm2/pkg/amino"
     8  	types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types"
     9  	"github.com/gnolang/gno/tm2/pkg/commands"
    10  	"github.com/gnolang/gno/tm2/pkg/crypto/keys"
    11  	"github.com/gnolang/gno/tm2/pkg/errors"
    12  	"github.com/gnolang/gno/tm2/pkg/std"
    13  )
    14  
    15  type MakeTxCfg struct {
    16  	RootCfg *BaseCfg
    17  
    18  	GasWanted int64
    19  	GasFee    string
    20  	Memo      string
    21  
    22  	Broadcast bool
    23  	// Valid options are SimulateTest, SimulateSkip or SimulateOnly.
    24  	Simulate string
    25  	ChainID  string
    26  }
    27  
    28  // These are the valid options for MakeTxConfig.Simulate.
    29  const (
    30  	SimulateTest = "test"
    31  	SimulateSkip = "skip"
    32  	SimulateOnly = "only"
    33  )
    34  
    35  func (c *MakeTxCfg) Validate() error {
    36  	switch c.Simulate {
    37  	case SimulateTest, SimulateSkip, SimulateOnly:
    38  	default:
    39  		return fmt.Errorf("invalid simulate option: %q", c.Simulate)
    40  	}
    41  	return nil
    42  }
    43  
    44  func NewMakeTxCmd(rootCfg *BaseCfg, io commands.IO) *commands.Command {
    45  	cfg := &MakeTxCfg{
    46  		RootCfg: rootCfg,
    47  	}
    48  
    49  	cmd := commands.NewCommand(
    50  		commands.Metadata{
    51  			Name:       "maketx",
    52  			ShortUsage: "<subcommand> [flags] [<arg>...]",
    53  			ShortHelp:  "composes a tx document to sign",
    54  		},
    55  		cfg,
    56  		commands.HelpExec,
    57  	)
    58  
    59  	cmd.AddSubCommands(
    60  		NewMakeSendCmd(cfg, io),
    61  	)
    62  
    63  	return cmd
    64  }
    65  
    66  func (c *MakeTxCfg) RegisterFlags(fs *flag.FlagSet) {
    67  	fs.Int64Var(
    68  		&c.GasWanted,
    69  		"gas-wanted",
    70  		0,
    71  		"gas requested for tx",
    72  	)
    73  
    74  	fs.StringVar(
    75  		&c.GasFee,
    76  		"gas-fee",
    77  		"",
    78  		"gas payment fee",
    79  	)
    80  
    81  	fs.StringVar(
    82  		&c.Memo,
    83  		"memo",
    84  		"",
    85  		"any descriptive text",
    86  	)
    87  
    88  	fs.BoolVar(
    89  		&c.Broadcast,
    90  		"broadcast",
    91  		false,
    92  		"sign, simulate and broadcast",
    93  	)
    94  
    95  	fs.StringVar(
    96  		&c.Simulate,
    97  		"simulate",
    98  		"test",
    99  		`select how to simulate the transaction (only useful with --broadcast); valid options are
   100  		- test: attempts simulating the transaction, and if successful performs broadcasting (default)
   101  		- skip: avoids performing transaction simulation
   102  		- only: avoids broadcasting transaction (ie. dry run)`,
   103  	)
   104  
   105  	fs.StringVar(
   106  		&c.ChainID,
   107  		"chainid",
   108  		"dev",
   109  		"chainid to sign for (only useful with --broadcast)",
   110  	)
   111  }
   112  
   113  func SignAndBroadcastHandler(
   114  	cfg *MakeTxCfg,
   115  	nameOrBech32 string,
   116  	tx std.Tx,
   117  	pass string,
   118  ) (*types.ResultBroadcastTxCommit, error) {
   119  	baseopts := cfg.RootCfg
   120  	txopts := cfg
   121  
   122  	kb, err := keys.NewKeyBaseFromDir(cfg.RootCfg.Home)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	info, err := kb.GetByNameOrAddress(nameOrBech32)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	accountAddr := info.GetAddress()
   132  
   133  	qopts := &QueryCfg{
   134  		RootCfg: baseopts,
   135  		Path:    fmt.Sprintf("auth/accounts/%s", accountAddr),
   136  	}
   137  	qres, err := QueryHandler(qopts)
   138  	if err != nil {
   139  		return nil, errors.Wrap(err, "query account")
   140  	}
   141  	var qret struct{ BaseAccount std.BaseAccount }
   142  	err = amino.UnmarshalJSON(qres.Response.Data, &qret)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	// sign tx
   148  	accountNumber := qret.BaseAccount.AccountNumber
   149  	sequence := qret.BaseAccount.Sequence
   150  
   151  	sOpts := signOpts{
   152  		chainID:         txopts.ChainID,
   153  		accountSequence: sequence,
   154  		accountNumber:   accountNumber,
   155  	}
   156  
   157  	kOpts := keyOpts{
   158  		keyName:     nameOrBech32,
   159  		decryptPass: pass,
   160  	}
   161  
   162  	if err := signTx(&tx, kb, sOpts, kOpts); err != nil {
   163  		return nil, fmt.Errorf("unable to sign transaction, %w", err)
   164  	}
   165  
   166  	// broadcast signed tx
   167  	bopts := &BroadcastCfg{
   168  		RootCfg: baseopts,
   169  		tx:      &tx,
   170  
   171  		DryRun:       cfg.Simulate == SimulateOnly,
   172  		testSimulate: cfg.Simulate == SimulateTest,
   173  	}
   174  
   175  	return BroadcastHandler(bopts)
   176  }
   177  
   178  func ExecSignAndBroadcast(
   179  	cfg *MakeTxCfg,
   180  	args []string,
   181  	tx std.Tx,
   182  	io commands.IO,
   183  ) error {
   184  	if err := cfg.Validate(); err != nil {
   185  		return err
   186  	}
   187  
   188  	baseopts := cfg.RootCfg
   189  
   190  	// query account
   191  	nameOrBech32 := args[0]
   192  
   193  	var err error
   194  	var pass string
   195  	if baseopts.Quiet {
   196  		pass, err = io.GetPassword("", baseopts.InsecurePasswordStdin)
   197  	} else {
   198  		pass, err = io.GetPassword("Enter password.", baseopts.InsecurePasswordStdin)
   199  	}
   200  
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	bres, err := SignAndBroadcastHandler(cfg, nameOrBech32, tx, pass)
   206  	if err != nil {
   207  		return errors.Wrap(err, "broadcast tx")
   208  	}
   209  	if bres.CheckTx.IsErr() {
   210  		return errors.Wrap(bres.CheckTx.Error, "check transaction failed: log:%s", bres.CheckTx.Log)
   211  	}
   212  	if bres.DeliverTx.IsErr() {
   213  		return errors.Wrap(bres.DeliverTx.Error, "deliver transaction failed: log:%s", bres.DeliverTx.Log)
   214  	}
   215  
   216  	io.Println(string(bres.DeliverTx.Data))
   217  	io.Println("OK!")
   218  	io.Println("GAS WANTED:", bres.DeliverTx.GasWanted)
   219  	io.Println("GAS USED:  ", bres.DeliverTx.GasUsed)
   220  	io.Println("HEIGHT:    ", bres.Height)
   221  	io.Println("EVENTS:    ", string(bres.DeliverTx.EncodeEvents()))
   222  
   223  	return nil
   224  }