mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 18:31:41 +00:00
@@ -0,0 +1,397 @@
|
||||
package txns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// FeeEstimator interface for estimating transaction fees
|
||||
type FeeEstimator interface {
|
||||
// EstimateFee estimates the fee for a transaction
|
||||
EstimateFee(ctx context.Context, params Params) (*FeeEstimation, error)
|
||||
// EstimateGas estimates the gas required for a transaction
|
||||
EstimateGas(ctx context.Context, params Params) (uint64, error)
|
||||
// GetGasPrice retrieves current gas price
|
||||
GetGasPrice(ctx context.Context) (any, error)
|
||||
// ValidateFee validates if a fee is sufficient
|
||||
ValidateFee(fee any, gasUsed uint64) error
|
||||
}
|
||||
|
||||
// CosmosFeeEstimator estimates fees for Cosmos transactions
|
||||
type CosmosFeeEstimator struct {
|
||||
clientCtx client.Context
|
||||
minGasPrice sdk.DecCoin
|
||||
gasAdjustment float64
|
||||
}
|
||||
|
||||
// NewCosmosFeeEstimator creates a new Cosmos fee estimator
|
||||
func NewCosmosFeeEstimator(clientCtx client.Context, minGasPrice sdk.DecCoin) *CosmosFeeEstimator {
|
||||
return &CosmosFeeEstimator{
|
||||
clientCtx: clientCtx,
|
||||
minGasPrice: minGasPrice,
|
||||
gasAdjustment: 1.2, // Default gas adjustment factor
|
||||
}
|
||||
}
|
||||
|
||||
// SetGasAdjustment sets the gas adjustment factor
|
||||
func (cfe *CosmosFeeEstimator) SetGasAdjustment(adjustment float64) {
|
||||
cfe.gasAdjustment = adjustment
|
||||
}
|
||||
|
||||
// EstimateFee implements FeeEstimator interface
|
||||
func (cfe *CosmosFeeEstimator) EstimateFee(
|
||||
ctx context.Context,
|
||||
params Params,
|
||||
) (*FeeEstimation, error) {
|
||||
cosmosParams, ok := params.(*CosmosTransactionParams)
|
||||
if !ok {
|
||||
return nil, ErrInvalidTransactionParams
|
||||
}
|
||||
|
||||
if err := cosmosParams.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
|
||||
// Estimate gas usage
|
||||
gasLimit, err := cfe.EstimateGas(ctx, params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to estimate gas: %w", err)
|
||||
}
|
||||
|
||||
// Apply gas adjustment
|
||||
adjustedGasLimit := uint64(float64(gasLimit) * cfe.gasAdjustment)
|
||||
|
||||
// Calculate fee
|
||||
feeAmount := cfe.minGasPrice.Amount.MulInt64(int64(adjustedGasLimit))
|
||||
fee := sdk.NewCoins(sdk.NewCoin(cfe.minGasPrice.Denom, feeAmount.TruncateInt()))
|
||||
|
||||
return &FeeEstimation{
|
||||
GasLimit: adjustedGasLimit,
|
||||
GasPrice: cfe.minGasPrice,
|
||||
Fee: fee,
|
||||
Total: fee.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EstimateGas implements FeeEstimator interface
|
||||
func (cfe *CosmosFeeEstimator) EstimateGas(ctx context.Context, params Params) (uint64, error) {
|
||||
cosmosParams, ok := params.(*CosmosTransactionParams)
|
||||
if !ok {
|
||||
return 0, ErrInvalidTransactionParams
|
||||
}
|
||||
|
||||
// Base gas estimation by message type
|
||||
baseGas := uint64(0)
|
||||
for _, msg := range cosmosParams.Messages {
|
||||
baseGas += cfe.estimateGasForMessage(msg)
|
||||
}
|
||||
|
||||
// Add overhead for transaction processing
|
||||
overhead := uint64(10000) // Base transaction overhead
|
||||
if cosmosParams.Memo != "" {
|
||||
overhead += uint64(len(cosmosParams.Memo)) * 10 // Memo overhead
|
||||
}
|
||||
|
||||
return baseGas + overhead, nil
|
||||
}
|
||||
|
||||
// GetGasPrice implements FeeEstimator interface
|
||||
func (cfe *CosmosFeeEstimator) GetGasPrice(ctx context.Context) (any, error) {
|
||||
// In Cosmos, gas price is typically fixed or queried from chain parameters
|
||||
// For now, return the configured minimum gas price
|
||||
return cfe.minGasPrice, nil
|
||||
}
|
||||
|
||||
// ValidateFee implements FeeEstimator interface
|
||||
func (cfe *CosmosFeeEstimator) ValidateFee(fee any, gasUsed uint64) error {
|
||||
feeCoins, ok := fee.(sdk.Coins)
|
||||
if !ok {
|
||||
return ErrInvalidTransactionParams
|
||||
}
|
||||
|
||||
// Calculate minimum required fee
|
||||
minFeeAmount := cfe.minGasPrice.Amount.MulInt64(int64(gasUsed))
|
||||
minFee := sdk.NewCoins(sdk.NewCoin(cfe.minGasPrice.Denom, minFeeAmount.TruncateInt()))
|
||||
|
||||
// Check if provided fee is sufficient
|
||||
if !feeCoins.IsAllGTE(minFee) {
|
||||
return fmt.Errorf("insufficient fee: got %s, need at least %s", feeCoins, minFee)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// estimateGasForMessage estimates gas usage for a specific message type
|
||||
func (cfe *CosmosFeeEstimator) estimateGasForMessage(msg sdk.Msg) uint64 {
|
||||
switch msg.(type) {
|
||||
case *banktypes.MsgSend:
|
||||
return 80000 // Base gas for bank send
|
||||
case *banktypes.MsgMultiSend:
|
||||
return 120000 // Higher gas for multi-send
|
||||
default:
|
||||
return 100000 // Default gas estimate
|
||||
}
|
||||
}
|
||||
|
||||
// EVMFeeEstimator estimates fees for EVM transactions
|
||||
type EVMFeeEstimator struct {
|
||||
client *ethclient.Client
|
||||
chainID *big.Int
|
||||
gasAdjustment float64
|
||||
}
|
||||
|
||||
// NewEVMFeeEstimator creates a new EVM fee estimator
|
||||
func NewEVMFeeEstimator(client *ethclient.Client, chainID *big.Int) *EVMFeeEstimator {
|
||||
return &EVMFeeEstimator{
|
||||
client: client,
|
||||
chainID: chainID,
|
||||
gasAdjustment: 1.1, // Default gas adjustment factor
|
||||
}
|
||||
}
|
||||
|
||||
// SetGasAdjustment sets the gas adjustment factor
|
||||
func (efe *EVMFeeEstimator) SetGasAdjustment(adjustment float64) {
|
||||
efe.gasAdjustment = adjustment
|
||||
}
|
||||
|
||||
// EstimateFee implements FeeEstimator interface
|
||||
func (efe *EVMFeeEstimator) EstimateFee(
|
||||
ctx context.Context,
|
||||
params Params,
|
||||
) (*FeeEstimation, error) {
|
||||
evmParams, ok := params.(*EVMTransactionParams)
|
||||
if !ok {
|
||||
return nil, ErrInvalidTransactionParams
|
||||
}
|
||||
|
||||
if err := evmParams.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid parameters: %w", err)
|
||||
}
|
||||
|
||||
// Estimate gas usage
|
||||
gasLimit, err := efe.EstimateGas(ctx, params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to estimate gas: %w", err)
|
||||
}
|
||||
|
||||
// Get current gas price
|
||||
gasPrice, err := efe.GetGasPrice(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get gas price: %w", err)
|
||||
}
|
||||
|
||||
// Apply gas adjustment
|
||||
adjustedGasLimit := uint64(float64(gasLimit) * efe.gasAdjustment)
|
||||
|
||||
// Calculate fee
|
||||
var totalFee *big.Int
|
||||
var feeData any
|
||||
|
||||
if evmParams.MaxFeePerGas != nil && evmParams.MaxPriorityFeePerGas != nil {
|
||||
// EIP-1559 transaction
|
||||
totalFee = new(big.Int).Mul(evmParams.MaxFeePerGas, big.NewInt(int64(adjustedGasLimit)))
|
||||
feeData = map[string]*big.Int{
|
||||
"maxFeePerGas": evmParams.MaxFeePerGas,
|
||||
"maxPriorityFeePerGas": evmParams.MaxPriorityFeePerGas,
|
||||
}
|
||||
} else {
|
||||
// Legacy transaction
|
||||
gasPriceBig := gasPrice.(*big.Int)
|
||||
totalFee = new(big.Int).Mul(gasPriceBig, big.NewInt(int64(adjustedGasLimit)))
|
||||
feeData = gasPriceBig
|
||||
}
|
||||
|
||||
return &FeeEstimation{
|
||||
GasLimit: adjustedGasLimit,
|
||||
GasPrice: feeData,
|
||||
Fee: totalFee,
|
||||
Total: totalFee.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EstimateGas implements FeeEstimator interface
|
||||
func (efe *EVMFeeEstimator) EstimateGas(ctx context.Context, params Params) (uint64, error) {
|
||||
evmParams, ok := params.(*EVMTransactionParams)
|
||||
if !ok {
|
||||
return 0, ErrInvalidTransactionParams
|
||||
}
|
||||
|
||||
if efe.client == nil {
|
||||
// Fallback estimation without client
|
||||
return efe.estimateGasOffline(evmParams), nil
|
||||
}
|
||||
|
||||
// Create a call message for gas estimation
|
||||
callMsg := ethereum.CallMsg{
|
||||
To: evmParams.To,
|
||||
Value: evmParams.Value,
|
||||
Data: evmParams.Data,
|
||||
}
|
||||
|
||||
// Estimate gas using the client
|
||||
gasLimit, err := efe.client.EstimateGas(ctx, callMsg)
|
||||
if err != nil {
|
||||
// Fallback to offline estimation
|
||||
return efe.estimateGasOffline(evmParams), nil
|
||||
}
|
||||
|
||||
return gasLimit, nil
|
||||
}
|
||||
|
||||
// GetGasPrice implements FeeEstimator interface
|
||||
func (efe *EVMFeeEstimator) GetGasPrice(ctx context.Context) (any, error) {
|
||||
if efe.client == nil {
|
||||
// Return default gas price
|
||||
return big.NewInt(params.GWei * 20), nil // 20 Gwei
|
||||
}
|
||||
|
||||
gasPrice, err := efe.client.SuggestGasPrice(ctx)
|
||||
if err != nil {
|
||||
// Fallback to default
|
||||
return big.NewInt(params.GWei * 20), nil
|
||||
}
|
||||
|
||||
return gasPrice, nil
|
||||
}
|
||||
|
||||
// ValidateFee implements FeeEstimator interface
|
||||
func (efe *EVMFeeEstimator) ValidateFee(fee any, gasUsed uint64) error {
|
||||
switch f := fee.(type) {
|
||||
case *big.Int:
|
||||
// Legacy transaction
|
||||
minFee := new(big.Int).Mul(big.NewInt(params.GWei), big.NewInt(int64(gasUsed)))
|
||||
if f.Cmp(minFee) < 0 {
|
||||
return fmt.Errorf("insufficient fee: got %s, need at least %s", f, minFee)
|
||||
}
|
||||
case map[string]*big.Int:
|
||||
// EIP-1559 transaction
|
||||
maxFeePerGas, ok := f["maxFeePerGas"]
|
||||
if !ok {
|
||||
return fmt.Errorf("missing maxFeePerGas in fee data")
|
||||
}
|
||||
minFee := new(big.Int).Mul(big.NewInt(params.GWei), big.NewInt(int64(gasUsed)))
|
||||
totalMaxFee := new(big.Int).Mul(maxFeePerGas, big.NewInt(int64(gasUsed)))
|
||||
if totalMaxFee.Cmp(minFee) < 0 {
|
||||
return fmt.Errorf("insufficient max fee: got %s, need at least %s", totalMaxFee, minFee)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported fee type: %T", fee)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// estimateGasOffline provides offline gas estimation
|
||||
func (efe *EVMFeeEstimator) estimateGasOffline(params *EVMTransactionParams) uint64 {
|
||||
baseGas := uint64(21000) // Base transaction gas
|
||||
|
||||
if params.Data != nil && len(params.Data) > 0 {
|
||||
// Contract interaction
|
||||
baseGas += uint64(len(params.Data)) * 16 // Rough estimate for data
|
||||
if params.To == nil {
|
||||
// Contract deployment
|
||||
baseGas += 200000
|
||||
} else {
|
||||
// Contract call
|
||||
baseGas += 100000
|
||||
}
|
||||
}
|
||||
|
||||
return baseGas
|
||||
}
|
||||
|
||||
// FeeManager manages fee estimation for multiple transaction types
|
||||
type FeeManager struct {
|
||||
cosmosEstimator *CosmosFeeEstimator
|
||||
evmEstimator *EVMFeeEstimator
|
||||
}
|
||||
|
||||
// NewFeeManager creates a new fee manager
|
||||
func NewFeeManager(cosmosEstimator *CosmosFeeEstimator, evmEstimator *EVMFeeEstimator) *FeeManager {
|
||||
return &FeeManager{
|
||||
cosmosEstimator: cosmosEstimator,
|
||||
evmEstimator: evmEstimator,
|
||||
}
|
||||
}
|
||||
|
||||
// EstimateFee estimates fee for any transaction type
|
||||
func (fm *FeeManager) EstimateFee(
|
||||
ctx context.Context,
|
||||
txType TransactionType,
|
||||
params Params,
|
||||
) (*FeeEstimation, error) {
|
||||
switch txType {
|
||||
case TransactionTypeCosmos:
|
||||
if fm.cosmosEstimator == nil {
|
||||
return nil, fmt.Errorf("cosmos fee estimator not configured")
|
||||
}
|
||||
return fm.cosmosEstimator.EstimateFee(ctx, params)
|
||||
case TransactionTypeEVM:
|
||||
if fm.evmEstimator == nil {
|
||||
return nil, fmt.Errorf("EVM fee estimator not configured")
|
||||
}
|
||||
return fm.evmEstimator.EstimateFee(ctx, params)
|
||||
default:
|
||||
return nil, ErrUnsupportedChainType
|
||||
}
|
||||
}
|
||||
|
||||
// GetEstimator returns the appropriate fee estimator for a transaction type
|
||||
func (fm *FeeManager) GetEstimator(txType TransactionType) (FeeEstimator, error) {
|
||||
switch txType {
|
||||
case TransactionTypeCosmos:
|
||||
if fm.cosmosEstimator == nil {
|
||||
return nil, fmt.Errorf("cosmos fee estimator not configured")
|
||||
}
|
||||
return fm.cosmosEstimator, nil
|
||||
case TransactionTypeEVM:
|
||||
if fm.evmEstimator == nil {
|
||||
return nil, fmt.Errorf("EVM fee estimator not configured")
|
||||
}
|
||||
return fm.evmEstimator, nil
|
||||
default:
|
||||
return nil, ErrUnsupportedChainType
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultFeeConfig holds default fee configuration
|
||||
type DefaultFeeConfig struct {
|
||||
CosmosMinGasPrice sdk.DecCoin
|
||||
EVMGasPrice *big.Int
|
||||
GasAdjustment float64
|
||||
}
|
||||
|
||||
// GetDefaultFeeConfig returns default fee configuration
|
||||
func GetDefaultFeeConfig() *DefaultFeeConfig {
|
||||
return &DefaultFeeConfig{
|
||||
CosmosMinGasPrice: sdk.NewDecCoin("usnr", math.NewInt(1000)),
|
||||
EVMGasPrice: big.NewInt(params.GWei * 20), // 20 Gwei
|
||||
GasAdjustment: 1.2,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateDefaultFeeManager creates a fee manager with default configuration
|
||||
func CreateDefaultFeeManager(
|
||||
clientCtx client.Context,
|
||||
evmClient *ethclient.Client,
|
||||
chainID *big.Int,
|
||||
) *FeeManager {
|
||||
config := GetDefaultFeeConfig()
|
||||
|
||||
cosmosEstimator := NewCosmosFeeEstimator(clientCtx, config.CosmosMinGasPrice)
|
||||
cosmosEstimator.SetGasAdjustment(config.GasAdjustment)
|
||||
|
||||
evmEstimator := NewEVMFeeEstimator(evmClient, chainID)
|
||||
evmEstimator.SetGasAdjustment(config.GasAdjustment)
|
||||
|
||||
return NewFeeManager(cosmosEstimator, evmEstimator)
|
||||
}
|
||||
Reference in New Issue
Block a user