mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
398 lines
11 KiB
Go
398 lines
11 KiB
Go
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)
|
|
}
|