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