github.com/Finschia/finschia-sdk@v0.49.1/client/tx/factory.go (about) 1 package tx 2 3 import ( 4 "errors" 5 "fmt" 6 "math/big" 7 "os" 8 9 "github.com/spf13/pflag" 10 11 "github.com/Finschia/finschia-sdk/client" 12 "github.com/Finschia/finschia-sdk/client/flags" 13 "github.com/Finschia/finschia-sdk/crypto/keyring" 14 "github.com/Finschia/finschia-sdk/crypto/keys/secp256k1" 15 cryptotypes "github.com/Finschia/finschia-sdk/crypto/types" 16 sdk "github.com/Finschia/finschia-sdk/types" 17 "github.com/Finschia/finschia-sdk/types/tx/signing" 18 ) 19 20 // Factory defines a client transaction factory that facilitates generating and 21 // signing an application-specific transaction. 22 type Factory struct { 23 keybase keyring.Keyring 24 txConfig client.TxConfig 25 accountRetriever client.AccountRetriever 26 accountNumber uint64 27 sequence uint64 28 gas uint64 29 timeoutHeight uint64 30 gasAdjustment float64 31 chainID string 32 memo string 33 fees sdk.Coins 34 gasPrices sdk.DecCoins 35 signMode signing.SignMode 36 simulateAndExecute bool 37 } 38 39 // NewFactoryCLI creates a new Factory. 40 func NewFactoryCLI(clientCtx client.Context, flagSet *pflag.FlagSet) Factory { 41 signModeStr := clientCtx.SignModeStr 42 43 signMode := signing.SignMode_SIGN_MODE_UNSPECIFIED 44 switch signModeStr { 45 case flags.SignModeDirect: 46 signMode = signing.SignMode_SIGN_MODE_DIRECT 47 case flags.SignModeLegacyAminoJSON: 48 signMode = signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON 49 case flags.SignModeEIP191: 50 signMode = signing.SignMode_SIGN_MODE_EIP_191 51 } 52 53 accNum, _ := flagSet.GetUint64(flags.FlagAccountNumber) 54 accSeq, _ := flagSet.GetUint64(flags.FlagSequence) 55 gasAdj, _ := flagSet.GetFloat64(flags.FlagGasAdjustment) 56 memo, _ := flagSet.GetString(flags.FlagNote) 57 timeoutHeight, _ := flagSet.GetUint64(flags.FlagTimeoutHeight) 58 59 gasStr, _ := flagSet.GetString(flags.FlagGas) 60 gasSetting, _ := flags.ParseGasSetting(gasStr) 61 62 f := Factory{ 63 txConfig: clientCtx.TxConfig, 64 accountRetriever: clientCtx.AccountRetriever, 65 keybase: clientCtx.Keyring, 66 chainID: clientCtx.ChainID, 67 gas: gasSetting.Gas, 68 simulateAndExecute: gasSetting.Simulate, 69 accountNumber: accNum, 70 sequence: accSeq, 71 timeoutHeight: timeoutHeight, 72 gasAdjustment: gasAdj, 73 memo: memo, 74 signMode: signMode, 75 } 76 77 feesStr, _ := flagSet.GetString(flags.FlagFees) 78 f = f.WithFees(feesStr) 79 80 gasPricesStr, _ := flagSet.GetString(flags.FlagGasPrices) 81 f = f.WithGasPrices(gasPricesStr) 82 83 return f 84 } 85 86 func (f Factory) AccountNumber() uint64 { return f.accountNumber } 87 func (f Factory) Sequence() uint64 { return f.sequence } 88 func (f Factory) Gas() uint64 { return f.gas } 89 func (f Factory) GasAdjustment() float64 { return f.gasAdjustment } 90 func (f Factory) Keybase() keyring.Keyring { return f.keybase } 91 func (f Factory) ChainID() string { return f.chainID } 92 func (f Factory) Memo() string { return f.memo } 93 func (f Factory) Fees() sdk.Coins { return f.fees } 94 func (f Factory) GasPrices() sdk.DecCoins { return f.gasPrices } 95 func (f Factory) AccountRetriever() client.AccountRetriever { return f.accountRetriever } 96 func (f Factory) TimeoutHeight() uint64 { return f.timeoutHeight } 97 98 // SimulateAndExecute returns the option to simulate and then execute the transaction 99 // using the gas from the simulation results 100 func (f Factory) SimulateAndExecute() bool { return f.simulateAndExecute } 101 102 // WithTxConfig returns a copy of the Factory with an updated TxConfig. 103 func (f Factory) WithTxConfig(g client.TxConfig) Factory { 104 f.txConfig = g 105 return f 106 } 107 108 // WithAccountRetriever returns a copy of the Factory with an updated AccountRetriever. 109 func (f Factory) WithAccountRetriever(ar client.AccountRetriever) Factory { 110 f.accountRetriever = ar 111 return f 112 } 113 114 // WithChainID returns a copy of the Factory with an updated chainID. 115 func (f Factory) WithChainID(chainID string) Factory { 116 f.chainID = chainID 117 return f 118 } 119 120 // WithGas returns a copy of the Factory with an updated gas value. 121 func (f Factory) WithGas(gas uint64) Factory { 122 f.gas = gas 123 return f 124 } 125 126 // WithFees returns a copy of the Factory with an updated fee. 127 func (f Factory) WithFees(fees string) Factory { 128 parsedFees, err := sdk.ParseCoinsNormalized(fees) 129 if err != nil { 130 panic(err) 131 } 132 133 f.fees = parsedFees 134 return f 135 } 136 137 // WithGasPrices returns a copy of the Factory with updated gas prices. 138 func (f Factory) WithGasPrices(gasPrices string) Factory { 139 parsedGasPrices, err := sdk.ParseDecCoins(gasPrices) 140 if err != nil { 141 panic(err) 142 } 143 144 f.gasPrices = parsedGasPrices 145 return f 146 } 147 148 // WithKeybase returns a copy of the Factory with updated Keybase. 149 func (f Factory) WithKeybase(keybase keyring.Keyring) Factory { 150 f.keybase = keybase 151 return f 152 } 153 154 // WithSequence returns a copy of the Factory with an updated sequence number. 155 func (f Factory) WithSequence(sequence uint64) Factory { 156 f.sequence = sequence 157 return f 158 } 159 160 // WithMemo returns a copy of the Factory with an updated memo. 161 func (f Factory) WithMemo(memo string) Factory { 162 f.memo = memo 163 return f 164 } 165 166 // WithAccountNumber returns a copy of the Factory with an updated account number. 167 func (f Factory) WithAccountNumber(accnum uint64) Factory { 168 f.accountNumber = accnum 169 return f 170 } 171 172 // WithGasAdjustment returns a copy of the Factory with an updated gas adjustment. 173 func (f Factory) WithGasAdjustment(gasAdj float64) Factory { 174 f.gasAdjustment = gasAdj 175 return f 176 } 177 178 // WithSimulateAndExecute returns a copy of the Factory with an updated gas 179 // simulation value. 180 func (f Factory) WithSimulateAndExecute(sim bool) Factory { 181 f.simulateAndExecute = sim 182 return f 183 } 184 185 // SignMode returns the sign mode configured in the Factory 186 func (f Factory) SignMode() signing.SignMode { 187 return f.signMode 188 } 189 190 // WithSignMode returns a copy of the Factory with an updated sign mode value. 191 func (f Factory) WithSignMode(mode signing.SignMode) Factory { 192 f.signMode = mode 193 return f 194 } 195 196 // WithTimeoutHeight returns a copy of the Factory with an updated timeout height. 197 func (f Factory) WithTimeoutHeight(height uint64) Factory { 198 f.timeoutHeight = height 199 return f 200 } 201 202 // BuildUnsignedTx builds a transaction to be signed given a set of messages. 203 // Once created, the fee, memo, and messages are set. 204 func (f Factory) BuildUnsignedTx(msgs ...sdk.Msg) (client.TxBuilder, error) { 205 if f.chainID == "" { 206 return nil, fmt.Errorf("chain ID required but not specified") 207 } 208 209 fees := f.fees 210 211 if !f.gasPrices.IsZero() { 212 if !fees.IsZero() { 213 return nil, errors.New("cannot provide both fees and gas prices") 214 } 215 216 // f.gas is a uint64 and we should convert to LegacyDec 217 // without the risk of under/overflow via uint64->int64. 218 glDec := sdk.NewDecFromBigInt(new(big.Int).SetUint64(f.gas)) 219 220 // Derive the fees based on the provided gas prices, where 221 // fee = ceil(gasPrice * gasLimit). 222 fees = make(sdk.Coins, len(f.gasPrices)) 223 224 for i, gp := range f.gasPrices { 225 fee := gp.Amount.Mul(glDec) 226 fees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) 227 } 228 } 229 230 tx := f.txConfig.NewTxBuilder() 231 232 if err := tx.SetMsgs(msgs...); err != nil { 233 return nil, err 234 } 235 236 tx.SetMemo(f.memo) 237 tx.SetFeeAmount(fees) 238 tx.SetGasLimit(f.gas) 239 tx.SetTimeoutHeight(f.TimeoutHeight()) 240 241 return tx, nil 242 } 243 244 // PrintUnsignedTx will generate an unsigned transaction and print it to the writer 245 // specified by ctx.Output. If simulation was requested, the gas will be 246 // simulated and also printed to the same writer before the transaction is 247 // printed. 248 func (f Factory) PrintUnsignedTx(clientCtx client.Context, msgs ...sdk.Msg) error { 249 if f.SimulateAndExecute() { 250 if clientCtx.Offline { 251 return errors.New("cannot estimate gas in offline mode") 252 } 253 254 _, adjusted, err := CalculateGas(clientCtx, f, msgs...) 255 if err != nil { 256 return err 257 } 258 259 f = f.WithGas(adjusted) 260 _, _ = fmt.Fprintf(os.Stderr, "%s\n", GasEstimateResponse{GasEstimate: f.Gas()}) 261 } 262 263 unsignedTx, err := f.BuildUnsignedTx(msgs...) 264 if err != nil { 265 return err 266 } 267 268 json, err := clientCtx.TxConfig.TxJSONEncoder()(unsignedTx.GetTx()) 269 if err != nil { 270 return err 271 } 272 273 return clientCtx.PrintString(fmt.Sprintf("%s\n", json)) 274 } 275 276 // BuildSimTx creates an unsigned tx with an empty single signature and returns 277 // the encoded transaction or an error if the unsigned transaction cannot be 278 // built. 279 func (f Factory) BuildSimTx(msgs ...sdk.Msg) ([]byte, error) { 280 txb, err := f.BuildUnsignedTx(msgs...) 281 if err != nil { 282 return nil, err 283 } 284 285 // use the first element from the list of keys in order to generate a valid 286 // pubkey that supports multiple algorithms 287 288 var pk cryptotypes.PubKey = &secp256k1.PubKey{} // use default public key type 289 290 if f.keybase != nil { 291 infos, _ := f.keybase.List() 292 if len(infos) == 0 { 293 return nil, errors.New("cannot build signature for simulation, key infos slice is empty") 294 } 295 296 // take the first info record just for simulation purposes 297 pk = infos[0].GetPubKey() 298 } 299 300 // Create an empty signature literal as the ante handler will populate with a 301 // sentinel pubkey. 302 sig := signing.SignatureV2{ 303 PubKey: pk, 304 Data: &signing.SingleSignatureData{ 305 SignMode: f.signMode, 306 }, 307 Sequence: f.Sequence(), 308 } 309 if err := txb.SetSignatures(sig); err != nil { 310 return nil, err 311 } 312 313 return f.txConfig.TxEncoder()(txb.GetTx()) 314 } 315 316 // Prepare ensures the account defined by ctx.GetFromAddress() exists and 317 // if the account number and/or the account sequence number are zero (not set), 318 // they will be queried for and set on the provided Factory. A new Factory with 319 // the updated fields will be returned. 320 func (f Factory) Prepare(clientCtx client.Context) (Factory, error) { 321 fc := f 322 323 from := clientCtx.GetFromAddress() 324 325 if err := fc.accountRetriever.EnsureExists(clientCtx, from); err != nil { 326 return fc, err 327 } 328 329 initNum, initSeq := fc.accountNumber, fc.sequence 330 if initNum == 0 || initSeq == 0 { 331 num, seq, err := fc.accountRetriever.GetAccountNumberSequence(clientCtx, from) 332 if err != nil { 333 return fc, err 334 } 335 336 if initNum == 0 { 337 fc = fc.WithAccountNumber(num) 338 } 339 340 if initSeq == 0 { 341 fc = fc.WithSequence(seq) 342 } 343 } 344 345 return fc, nil 346 }