diff --git a/chain/cosmos/tx.go b/chain/cosmos/tx.go index dc966df9..201ece27 100644 --- a/chain/cosmos/tx.go +++ b/chain/cosmos/tx.go @@ -30,17 +30,27 @@ const ( DefaultChainID = pack.String("testnet") // DefaultSignMode used in signing the tx DefaultSignMode = 1 + // DefaultDecimalsDivisor is used when estimating gas prices for some Cosmos + // chains, so that the result is an integer. + // For example, the recommended Terra gas price is currently 0.01133 uluna. + // To ensure we're only dealing with integers, the value can be represented + // as 1133. When the transaction builder is calculating fees, it will divide + // the total by the divisor (in this case 1e5), to calculate the actual + // value. + DefaultDecimalsDivisor = 1 ) // TxBuilderOptions only contains necessary options to build tx from tx builder type TxBuilderOptions struct { - ChainID pack.String + ChainID pack.String + DecimalsDivisor pack.U256 } // DefaultTxBuilderOptions returns TxBuilderOptions with the default settings. func DefaultTxBuilderOptions() TxBuilderOptions { return TxBuilderOptions{ - ChainID: DefaultChainID, + ChainID: DefaultChainID, + DecimalsDivisor: pack.NewU256FromU64(DefaultDecimalsDivisor), } } @@ -50,10 +60,16 @@ func (opts TxBuilderOptions) WithChainID(chainID pack.String) TxBuilderOptions { return opts } +func (opts TxBuilderOptions) WithDecimalsDivisor(decimalDivisor pack.U256) TxBuilderOptions { + opts.DecimalsDivisor = decimalDivisor + return opts +} + type txBuilder struct { - client *Client - chainID pack.String - signMode int32 + client *Client + chainID pack.String + signMode int32 + decimalsDivisor pack.U256 } // NewTxBuilder returns an implementation of the transaction builder interface @@ -61,9 +77,10 @@ type txBuilder struct { // Cosmos based transactions. func NewTxBuilder(options TxBuilderOptions, client *Client) account.TxBuilder { return txBuilder{ - signMode: DefaultSignMode, - client: client, - chainID: options.ChainID, + signMode: DefaultSignMode, + client: client, + chainID: options.ChainID, + decimalsDivisor: options.DecimalsDivisor, } } @@ -107,7 +124,7 @@ func (builder txBuilder) BuildTx(ctx context.Context, fromPubKey *id.PubKey, to fees := Coins{Coin{ Denom: builder.client.opts.CoinDenom, - Amount: pack.NewU64(gasPrice.Mul(gasLimit).Int().Uint64()), + Amount: pack.NewU64(gasPrice.Mul(gasLimit).Div(builder.decimalsDivisor).Int().Uint64()), }} accountNumber, err := builder.client.AccountNumber(ctx, from) diff --git a/chain/ethereum/gas.go b/chain/ethereum/gas.go index 917b6afe..914a9dc3 100644 --- a/chain/ethereum/gas.go +++ b/chain/ethereum/gas.go @@ -29,7 +29,7 @@ var ( ) type feeHistoryResult struct { - Reward [][]string `json:"reward"` + Reward [][]string `json:"reward"` } // GasOptions allow a user to configure the parameters used while heuristically recommending diff --git a/chain/terra/terra.go b/chain/terra/terra.go index 27a44549..ba2aa1fe 100644 --- a/chain/terra/terra.go +++ b/chain/terra/terra.go @@ -1,12 +1,21 @@ package terra import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + "github.com/cosmos/cosmos-sdk/types" "github.com/renproject/multichain/api/account" "github.com/renproject/multichain/chain/cosmos" + "github.com/renproject/pack" "github.com/terra-money/core/app" ) +const DefaultTerraDecimalsDivisor = 1e5 + type ( // Client re-exports cosmos.Client Client = cosmos.Client @@ -54,3 +63,40 @@ func NewClient(opts ClientOptions) *Client { func NewTxBuilder(opts TxBuilderOptions, client *Client) account.TxBuilder { return cosmos.NewTxBuilder(opts, client) } + +type GasEstimator struct { + url string + decimals int + fallbackGas pack.U256 +} + +func NewHttpGasEstimator(url string, decimals int, fallbackGas pack.U256) GasEstimator { + return GasEstimator{ + url: url, + decimals: decimals, + fallbackGas: fallbackGas, + } +} + +func (gasEstimator GasEstimator) EstimateGas(ctx context.Context) (pack.U256, pack.U256, error) { + response, err := http.Get(gasEstimator.url) + if err != nil { + return gasEstimator.fallbackGas, gasEstimator.fallbackGas, err + } + defer response.Body.Close() + + var results map[string]string + if err := json.NewDecoder(response.Body).Decode(&results); err != nil { + return gasEstimator.fallbackGas, gasEstimator.fallbackGas, err + } + gasPriceStr, ok := results["uluna"] + if !ok { + return gasEstimator.fallbackGas, gasEstimator.fallbackGas, fmt.Errorf("no uluna in response") + } + gasPriceFloat, err := strconv.ParseFloat(gasPriceStr, 64) + if err != nil { + return gasEstimator.fallbackGas, gasEstimator.fallbackGas, fmt.Errorf("invalid gas price, %v", err) + } + gasPrice := uint64(gasPriceFloat * float64(gasEstimator.decimals)) + return pack.NewU256FromUint64(gasPrice), pack.NewU256FromUint64(gasPrice), nil +}