package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"math/big"
	"sort"
	"strings"
	"time"

	"github.com/trezor/blockbook/bchain"
	"github.com/trezor/blockbook/bchain/coins/eth"
	"github.com/trezor/blockbook/common"
	"github.com/trezor/blockbook/db"
)

const maxUint32 = ^uint32(0)
const maxInt = int(^uint(0) >> 1)
const maxInt64 = int64(^uint64(0) >> 1)

// AccountDetails specifies what data returns GetAddress and GetXpub calls
type AccountDetails int

const (
	// AccountDetailsBasic - only that address is indexed and some basic info
	AccountDetailsBasic AccountDetails = iota
	// AccountDetailsTokens - basic info + tokens
	AccountDetailsTokens
	// AccountDetailsTokenBalances - basic info + token with balance
	AccountDetailsTokenBalances
	// AccountDetailsTxidHistory - basic + token balances + txids, subject to paging
	AccountDetailsTxidHistory
	// AccountDetailsTxHistoryLight - basic + tokens + easily obtained tx data (not requiring requests to backend), subject to paging
	AccountDetailsTxHistoryLight
	// AccountDetailsTxHistory - basic + tokens + full tx data, subject to paging
	AccountDetailsTxHistory
)

// ErrUnsupportedXpub is returned when coin type does not support xpub address derivation or provided string is not an xpub
var ErrUnsupportedXpub = errors.New("XPUB not supported")

// APIError extends error by information if the error details should be returned to the end user
type APIError struct {
	Text   string
	Public bool
}

func (e *APIError) Error() string {
	return e.Text
}

// NewAPIError creates ApiError
func NewAPIError(s string, public bool) error {
	return &APIError{
		Text:   s,
		Public: public,
	}
}

// Amount is datatype holding amounts
type Amount big.Int

// IsZeroBigInt if big int has zero value
func IsZeroBigInt(b *big.Int) bool {
	return len(b.Bits()) == 0
}

// Compare returns an integer comparing two Amounts. The result will be 0 if a == b, -1 if a < b, and +1 if a > b.
// Nil Amount is always less then non nil amount, two nil Amounts are equal
func (a *Amount) Compare(b *Amount) int {
	if b == nil {
		if a == nil {
			return 0
		}
		return 1
	}
	if a == nil {
		return -1
	}
	return (*big.Int)(a).Cmp((*big.Int)(b))
}

// MarshalJSON Amount serialization
func (a *Amount) MarshalJSON() (out []byte, err error) {
	if a == nil {
		return []byte(`"0"`), nil
	}
	return []byte(`"` + (*big.Int)(a).String() + `"`), nil
}

func (a *Amount) UnmarshalJSON(data []byte) error {
	s := strings.Trim(string(data), "\"")
	if len(s) > 0 {
		bigValue, parsed := new(big.Int).SetString(s, 10)
		if !parsed {
			return fmt.Errorf("couldn't parse number: %s", s)
		}
		*a = Amount(*bigValue)
	} else {
		// assuming empty string means zero
		*a = Amount{}
	}
	return nil
}

func (a *Amount) String() string {
	if a == nil {
		return ""
	}
	return (*big.Int)(a).String()
}

// DecimalString returns amount with decimal point placed according to parameter d
func (a *Amount) DecimalString(d int) string {
	return bchain.AmountToDecimalString((*big.Int)(a), d)
}

// AsBigInt returns big.Int type for the Amount (empty if Amount is nil)
func (a *Amount) AsBigInt() big.Int {
	if a == nil {
		return *new(big.Int)
	}
	return big.Int(*a)
}

// AsInt64 returns Amount as int64 (0 if Amount is nil).
// It is used only for legacy interfaces (socket.io)
// and generally not recommended to use for possible loss of precision.
func (a *Amount) AsInt64() int64 {
	if a == nil {
		return 0
	}
	return (*big.Int)(a).Int64()
}

// Vin contains information about single transaction input
type Vin struct {
	Txid      string                   `json:"txid,omitempty"`
	Vout      uint32                   `json:"vout,omitempty"`
	Sequence  int64                    `json:"sequence,omitempty"`
	N         int                      `json:"n"`
	AddrDesc  bchain.AddressDescriptor `json:"-"`
	Addresses []string                 `json:"addresses,omitempty"`
	IsAddress bool                     `json:"isAddress"`
	IsOwn     bool                     `json:"isOwn,omitempty"`
	ValueSat  *Amount                  `json:"value,omitempty"`
	Hex       string                   `json:"hex,omitempty"`
	Asm       string                   `json:"asm,omitempty"`
	Coinbase  string                   `json:"coinbase,omitempty"`
}

// Vout contains information about single transaction output
type Vout struct {
	ValueSat    *Amount                  `json:"value,omitempty"`
	N           int                      `json:"n"`
	Spent       bool                     `json:"spent,omitempty"`
	SpentTxID   string                   `json:"spentTxId,omitempty"`
	SpentIndex  int                      `json:"spentIndex,omitempty"`
	SpentHeight int                      `json:"spentHeight,omitempty"`
	Hex         string                   `json:"hex,omitempty"`
	Asm         string                   `json:"asm,omitempty"`
	AddrDesc    bchain.AddressDescriptor `json:"-"`
	Addresses   []string                 `json:"addresses"`
	IsAddress   bool                     `json:"isAddress"`
	IsOwn       bool                     `json:"isOwn,omitempty"`
	Type        string                   `json:"type,omitempty"`
}

// MultiTokenValue contains values for contract with id and value (like ERC1155)
type MultiTokenValue struct {
	Id    *Amount `json:"id,omitempty"`
	Value *Amount `json:"value,omitempty"`
}

// Token contains info about tokens held by an address
type Token struct {
	// Deprecated: Use Standard instead.
	Type             bchain.TokenStandardName `json:"type" ts_type:"'' | 'XPUBAddress' | 'ERC20' | 'ERC721' | 'ERC1155' | 'BEP20' | 'BEP721' | 'BEP1155'" ts_doc:"@deprecated: Use standard instead."`
	Standard         bchain.TokenStandardName `json:"standard" ts_type:"'' | 'XPUBAddress' | 'ERC20' | 'ERC721' | 'ERC1155' | 'BEP20' | 'BEP721' | 'BEP1155'"`
	Name             string                   `json:"name"`
	Path             string                   `json:"path,omitempty"`
	Contract         string                   `json:"contract,omitempty"`
	Transfers        int                      `json:"transfers"`
	Symbol           string                   `json:"symbol,omitempty"`
	Decimals         int                      `json:"decimals"`
	BalanceSat       *Amount                  `json:"balance,omitempty"`
	BaseValue        float64                  `json:"baseValue,omitempty"`        // value in the base currency (ETH for Ethereum)
	SecondaryValue   float64                  `json:"secondaryValue,omitempty"`   // value in secondary (fiat) currency, if specified
	Ids              []Amount                 `json:"ids,omitempty"`              // multiple ERC721 tokens
	MultiTokenValues []MultiTokenValue        `json:"multiTokenValues,omitempty"` // multiple ERC1155 tokens
	TotalReceivedSat *Amount                  `json:"totalReceived,omitempty"`
	TotalSentSat     *Amount                  `json:"totalSent,omitempty"`
	ContractIndex    string                   `json:"-"`
}

// Tokens is array of Token
type Tokens []Token

func (a Tokens) Len() int      { return len(a) }
func (a Tokens) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a Tokens) Less(i, j int) bool {
	ti := &a[i]
	tj := &a[j]
	// sort by BaseValue descending  and then Name and then by Contract
	if ti.BaseValue < tj.BaseValue {
		return false
	} else if ti.BaseValue > tj.BaseValue {
		return true
	}
	if ti.Name == "" {
		if tj.Name != "" {
			return false
		}
	} else {
		if tj.Name == "" {
			return true
		}
		return ti.Name < tj.Name
	}
	return ti.Contract < tj.Contract
}

// TokenTransfer contains info about a token transfer done in a transaction
type TokenTransfer struct {
	// Deprecated: Use Standard instead.
	Type             bchain.TokenStandardName `json:"type" ts_type:"'' | 'XPUBAddress' | 'ERC20' | 'ERC721' | 'ERC1155' | 'BEP20' | 'BEP721' | 'BEP1155'" ts_doc:"@deprecated: Use standard instead."`
	Standard         bchain.TokenStandardName `json:"standard" ts_type:"'' | 'XPUBAddress' | 'ERC20' | 'ERC721' | 'ERC1155' | 'BEP20' | 'BEP721' | 'BEP1155'"`
	From             string                   `json:"from"`
	To               string                   `json:"to"`
	Contract         string                   `json:"contract"`
	Name             string                   `json:"name,omitempty"`
	Symbol           string                   `json:"symbol,omitempty"`
	Decimals         int                      `json:"decimals"`
	Value            *Amount                  `json:"value,omitempty"`
	MultiTokenValues []MultiTokenValue        `json:"multiTokenValues,omitempty"`
}

type EthereumInternalTransfer struct {
	Type  bchain.EthereumInternalTransactionType `json:"type"`
	From  string                                 `json:"from"`
	To    string                                 `json:"to"`
	Value *Amount                                `json:"value"`
}

// EthereumSpecific contains ethereum specific transaction data
type EthereumSpecific struct {
	Type                 bchain.EthereumInternalTransactionType `json:"type,omitempty"`
	CreatedContract      string                                 `json:"createdContract,omitempty"`
	Status               eth.TxStatus                           `json:"status"` // 1 OK, 0 Fail, -1 pending
	Error                string                                 `json:"error,omitempty"`
	Nonce                uint64                                 `json:"nonce"`
	GasLimit             *big.Int                               `json:"gasLimit"`
	GasUsed              *big.Int                               `json:"gasUsed,omitempty"`
	GasPrice             *Amount                                `json:"gasPrice,omitempty"`
	MaxPriorityFeePerGas *Amount                                `json:"maxPriorityFeePerGas,omitempty"`
	MaxFeePerGas         *Amount                                `json:"maxFeePerGas,omitempty"`
	BaseFeePerGas        *Amount                                `json:"baseFeePerGas,omitempty"`
	L1Fee                *big.Int                               `json:"l1Fee,omitempty"`
	L1FeeScalar          string                                 `json:"l1FeeScalar,omitempty"`
	L1GasPrice           *Amount                                `json:"l1GasPrice,omitempty"`
	L1GasUsed            *big.Int                               `json:"l1GasUsed,omitempty"`
	Data                 string                                 `json:"data,omitempty"`
	ParsedData           *bchain.EthereumParsedInputData        `json:"parsedData,omitempty"`
	InternalTransfers    []EthereumInternalTransfer             `json:"internalTransfers,omitempty"`
}

type AddressAlias struct {
	Type  string
	Alias string
}
type AddressAliasesMap map[string]AddressAlias

// Tx holds information about a transaction
type Tx struct {
	Txid                   string            `json:"txid"`
	Version                int32             `json:"version,omitempty"`
	Locktime               uint32            `json:"lockTime,omitempty"`
	Vin                    []Vin             `json:"vin"`
	Vout                   []Vout            `json:"vout"`
	Blockhash              string            `json:"blockHash,omitempty"`
	Blockheight            int               `json:"blockHeight"`
	Confirmations          uint32            `json:"confirmations"`
	ConfirmationETABlocks  uint32            `json:"confirmationETABlocks,omitempty"`
	ConfirmationETASeconds int64             `json:"confirmationETASeconds,omitempty"`
	Blocktime              int64             `json:"blockTime"`
	Size                   int               `json:"size,omitempty"`
	VSize                  int               `json:"vsize,omitempty"`
	ValueOutSat            *Amount           `json:"value"`
	ValueInSat             *Amount           `json:"valueIn,omitempty"`
	FeesSat                *Amount           `json:"fees,omitempty"`
	Hex                    string            `json:"hex,omitempty"`
	Rbf                    bool              `json:"rbf,omitempty"`
	CoinSpecificData       json.RawMessage   `json:"coinSpecificData,omitempty" ts_type:"any"`
	TokenTransfers         []TokenTransfer   `json:"tokenTransfers,omitempty"`
	EthereumSpecific       *EthereumSpecific `json:"ethereumSpecific,omitempty"`
	AddressAliases         AddressAliasesMap `json:"addressAliases,omitempty"`
}

// FeeStats contains detailed block fee statistics
type FeeStats struct {
	TxCount         int       `json:"txCount"`
	TotalFeesSat    *Amount   `json:"totalFeesSat"`
	AverageFeePerKb int64     `json:"averageFeePerKb"`
	DecilesFeePerKb [11]int64 `json:"decilesFeePerKb"`
}

// Paging contains information about paging for address, blocks and block
type Paging struct {
	Page        int `json:"page,omitempty"`
	TotalPages  int `json:"totalPages,omitempty"`
	ItemsOnPage int `json:"itemsOnPage,omitempty"`
}

// TokensToReturn specifies what tokens are returned by GetAddress and GetXpubAddress
type TokensToReturn int

const (
	// AddressFilterVoutOff disables filtering of transactions by vout
	AddressFilterVoutOff = -1
	// AddressFilterVoutInputs specifies that only txs where the address is as input are returned
	AddressFilterVoutInputs = -2
	// AddressFilterVoutOutputs specifies that only txs where the address is as output are returned
	AddressFilterVoutOutputs = -3
	// AddressFilterVoutQueryNotNecessary signals that query for transactions is not necessary as there are no transactions for specified contract filter
	AddressFilterVoutQueryNotNecessary = -4

	// TokensToReturnNonzeroBalance - return only tokens with nonzero balance
	TokensToReturnNonzeroBalance TokensToReturn = 0
	// TokensToReturnUsed - return tokens with some transfers (even if they have zero balance now)
	TokensToReturnUsed TokensToReturn = 1
	// TokensToReturnDerived - return all derived tokens
	TokensToReturnDerived TokensToReturn = 2
)

// AddressFilter is used to filter data returned from GetAddress api method
type AddressFilter struct {
	Vout           int
	Contract       string
	FromHeight     uint32
	ToHeight       uint32
	TokensToReturn TokensToReturn
	// OnlyConfirmed set to true will ignore mempool transactions; mempool is also ignored if FromHeight/ToHeight filter is specified
	OnlyConfirmed bool
}

// StakingPool holds data about address participation in a staking pool contract
type StakingPool struct {
	Contract                string  `json:"contract"`
	Name                    string  `json:"name"`
	PendingBalance          *Amount `json:"pendingBalance"`
	PendingDepositedBalance *Amount `json:"pendingDepositedBalance"`
	DepositedBalance        *Amount `json:"depositedBalance"`
	WithdrawTotalAmount     *Amount `json:"withdrawTotalAmount"`
	ClaimableAmount         *Amount `json:"claimableAmount"`
	RestakedReward          *Amount `json:"restakedReward"`
	AutocompoundBalance     *Amount `json:"autocompoundBalance"`
}

// Address holds information about address and its transactions
type Address struct {
	Paging
	AddrStr               string               `json:"address"`
	BalanceSat            *Amount              `json:"balance"`
	TotalReceivedSat      *Amount              `json:"totalReceived,omitempty"`
	TotalSentSat          *Amount              `json:"totalSent,omitempty"`
	UnconfirmedBalanceSat *Amount              `json:"unconfirmedBalance"`
	UnconfirmedTxs        int                  `json:"unconfirmedTxs"`
	Txs                   int                  `json:"txs"`
	AddrTxCount           int                  `json:"addrTxCount,omitempty"`
	NonTokenTxs           int                  `json:"nonTokenTxs,omitempty"`
	InternalTxs           int                  `json:"internalTxs,omitempty"`
	Transactions          []*Tx                `json:"transactions,omitempty"`
	Txids                 []string             `json:"txids,omitempty"`
	Nonce                 string               `json:"nonce,omitempty"`
	UsedTokens            int                  `json:"usedTokens,omitempty"`
	Tokens                Tokens               `json:"tokens,omitempty"`
	SecondaryValue        float64              `json:"secondaryValue,omitempty"` // address value in secondary currency
	TokensBaseValue       float64              `json:"tokensBaseValue,omitempty"`
	TokensSecondaryValue  float64              `json:"tokensSecondaryValue,omitempty"`
	TotalBaseValue        float64              `json:"totalBaseValue,omitempty"`      // value including tokens in base currency
	TotalSecondaryValue   float64              `json:"totalSecondaryValue,omitempty"` // value including tokens in secondary currency
	ContractInfo          *bchain.ContractInfo `json:"contractInfo,omitempty"`
	// Deprecated: replaced by ContractInfo
	Erc20Contract  *bchain.ContractInfo `json:"erc20Contract,omitempty" ts_doc:"@deprecated: replaced by contractInfo"`
	AddressAliases AddressAliasesMap    `json:"addressAliases,omitempty"`
	StakingPools   []StakingPool        `json:"stakingPools,omitempty"`
	// helpers for explorer
	Filter        string              `json:"-"`
	XPubAddresses map[string]struct{} `json:"-"`
}

// Utxo is one unspent transaction output
type Utxo struct {
	Txid          string  `json:"txid"`
	Vout          int32   `json:"vout"`
	AmountSat     *Amount `json:"value"`
	Height        int     `json:"height,omitempty"`
	Confirmations int     `json:"confirmations"`
	Address       string  `json:"address,omitempty"`
	Path          string  `json:"path,omitempty"`
	Locktime      uint32  `json:"lockTime,omitempty"`
	Coinbase      bool    `json:"coinbase,omitempty"`
}

// Utxos is array of Utxo
type Utxos []Utxo

func (a Utxos) Len() int      { return len(a) }
func (a Utxos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a Utxos) Less(i, j int) bool {
	// sort in reverse order, unconfirmed (height==0) utxos on top
	hi := a[i].Height
	hj := a[j].Height
	if hi == 0 {
		hi = maxInt
	}
	if hj == 0 {
		hj = maxInt
	}
	return hi >= hj
}

// BalanceHistory contains info about one point in time of balance history
type BalanceHistory struct {
	Time          uint32             `json:"time"`
	Txs           uint32             `json:"txs"`
	ReceivedSat   *Amount            `json:"received"`
	SentSat       *Amount            `json:"sent"`
	SentToSelfSat *Amount            `json:"sentToSelf"`
	FiatRates     map[string]float32 `json:"rates,omitempty"`
	Txid          string             `json:"txid,omitempty"`
}

// BalanceHistories is array of BalanceHistory
type BalanceHistories []BalanceHistory

func (a BalanceHistories) Len() int      { return len(a) }
func (a BalanceHistories) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a BalanceHistories) Less(i, j int) bool {
	ti := a[i].Time
	tj := a[j].Time
	if ti == tj {
		return a[i].Txid < a[j].Txid
	}
	return ti < tj
}

// SortAndAggregate sums BalanceHistories to groups defined by parameter groupByTime
func (a BalanceHistories) SortAndAggregate(groupByTime uint32) BalanceHistories {
	bhs := make(BalanceHistories, 0)
	if len(a) > 0 {
		bha := BalanceHistory{
			ReceivedSat:   &Amount{},
			SentSat:       &Amount{},
			SentToSelfSat: &Amount{},
		}
		sort.Sort(a)
		for i := range a {
			bh := &a[i]
			time := bh.Time - bh.Time%groupByTime
			if bha.Time != time {
				if bha.Time != 0 {
					// in aggregate, do not return txid as it could multiple of them
					bha.Txid = ""
					bhs = append(bhs, bha)
				}
				bha = BalanceHistory{
					Time:          time,
					ReceivedSat:   &Amount{},
					SentSat:       &Amount{},
					SentToSelfSat: &Amount{},
				}
			}
			if bha.Txid != bh.Txid {
				bha.Txs += bh.Txs
				bha.Txid = bh.Txid
			}
			(*big.Int)(bha.ReceivedSat).Add((*big.Int)(bha.ReceivedSat), (*big.Int)(bh.ReceivedSat))
			(*big.Int)(bha.SentSat).Add((*big.Int)(bha.SentSat), (*big.Int)(bh.SentSat))
			(*big.Int)(bha.SentToSelfSat).Add((*big.Int)(bha.SentToSelfSat), (*big.Int)(bh.SentToSelfSat))
		}
		if bha.Txs > 0 {
			bha.Txid = ""
			bhs = append(bhs, bha)
		}
	}
	return bhs
}

// Blocks is list of blocks with paging information
type Blocks struct {
	Paging
	Blocks []db.BlockInfo `json:"blocks"`
}

// BlockInfo contains extended block header data and a list of block txids
type BlockInfo struct {
	Hash          string            `json:"hash"`
	Prev          string            `json:"previousBlockHash,omitempty"`
	Next          string            `json:"nextBlockHash,omitempty"`
	Height        uint32            `json:"height"`
	Confirmations int               `json:"confirmations"`
	Size          int               `json:"size"`
	Time          int64             `json:"time,omitempty"`
	Version       common.JSONNumber `json:"version"`
	MerkleRoot    string            `json:"merkleRoot"`
	Nonce         string            `json:"nonce"`
	Bits          string            `json:"bits"`
	Difficulty    string            `json:"difficulty"`
	Txids         []string          `json:"tx,omitempty"`
}

// Block contains information about block
type Block struct {
	Paging
	BlockInfo
	TxCount        int               `json:"txCount"`
	Transactions   []*Tx             `json:"txs,omitempty"`
	AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"`
}

// BlockRaw contains raw block in hex
type BlockRaw struct {
	Hex string `json:"hex"`
}

// BlockbookInfo contains information about the running blockbook instance
type BlockbookInfo struct {
	Coin                         string                       `json:"coin"`
	Network                      string                       `json:"network"`
	Host                         string                       `json:"host"`
	Version                      string                       `json:"version"`
	GitCommit                    string                       `json:"gitCommit"`
	BuildTime                    string                       `json:"buildTime"`
	SyncMode                     bool                         `json:"syncMode"`
	InitialSync                  bool                         `json:"initialSync"`
	InSync                       bool                         `json:"inSync"`
	BestHeight                   uint32                       `json:"bestHeight"`
	LastBlockTime                time.Time                    `json:"lastBlockTime"`
	InSyncMempool                bool                         `json:"inSyncMempool"`
	LastMempoolTime              time.Time                    `json:"lastMempoolTime"`
	MempoolSize                  int                          `json:"mempoolSize"`
	Decimals                     int                          `json:"decimals"`
	DbSize                       int64                        `json:"dbSize"`
	HasFiatRates                 bool                         `json:"hasFiatRates,omitempty"`
	HasTokenFiatRates            bool                         `json:"hasTokenFiatRates,omitempty"`
	CurrentFiatRatesTime         *time.Time                   `json:"currentFiatRatesTime,omitempty"`
	HistoricalFiatRatesTime      *time.Time                   `json:"historicalFiatRatesTime,omitempty"`
	HistoricalTokenFiatRatesTime *time.Time                   `json:"historicalTokenFiatRatesTime,omitempty"`
	SupportedStakingPools        []string                     `json:"supportedStakingPools,omitempty"`
	DbSizeFromColumns            int64                        `json:"dbSizeFromColumns,omitempty"`
	DbColumns                    []common.InternalStateColumn `json:"dbColumns,omitempty"`
	About                        string                       `json:"about"`
}

// SystemInfo contains information about the running blockbook and backend instance
type SystemInfo struct {
	Blockbook *BlockbookInfo      `json:"blockbook"`
	Backend   *common.BackendInfo `json:"backend"`
}

// MempoolTxid contains information about a transaction in mempool
type MempoolTxid struct {
	Time int64  `json:"time"`
	Txid string `json:"txid"`
}

// MempoolTxids contains a list of mempool txids with paging information
type MempoolTxids struct {
	Paging
	Mempool     []MempoolTxid `json:"mempool"`
	MempoolSize int           `json:"mempoolSize"`
}

// FiatTicker contains formatted CurrencyRatesTicker data
type FiatTicker struct {
	Timestamp int64              `json:"ts,omitempty"`
	Rates     map[string]float32 `json:"rates"`
	Error     string             `json:"error,omitempty"`
}

// FiatTickers contains a formatted CurrencyRatesTicker list
type FiatTickers struct {
	Tickers []FiatTicker `json:"tickers"`
}

// AvailableVsCurrencies contains formatted data about available versus currencies for exchange rates
type AvailableVsCurrencies struct {
	Timestamp int64    `json:"ts,omitempty"`
	Tickers   []string `json:"available_currencies"`
	Error     string   `json:"error,omitempty"`
}

// Eip1559Fee
type Eip1559Fee struct {
	MaxFeePerGas         *Amount `json:"maxFeePerGas"`
	MaxPriorityFeePerGas *Amount `json:"maxPriorityFeePerGas"`
	MinWaitTimeEstimate  int     `json:"minWaitTimeEstimate,omitempty"`
	MaxWaitTimeEstimate  int     `json:"maxWaitTimeEstimate,omitempty"`
}

// Eip1559Fees
type Eip1559Fees struct {
	BaseFeePerGas              *Amount     `json:"baseFeePerGas,omitempty"`
	Low                        *Eip1559Fee `json:"low,omitempty"`
	Medium                     *Eip1559Fee `json:"medium,omitempty"`
	High                       *Eip1559Fee `json:"high,omitempty"`
	Instant                    *Eip1559Fee `json:"instant,omitempty"`
	NetworkCongestion          float64     `json:"networkCongestion,omitempty"`
	LatestPriorityFeeRange     []*Amount   `json:"latestPriorityFeeRange,omitempty"`
	HistoricalPriorityFeeRange []*Amount   `json:"historicalPriorityFeeRange,omitempty"`
	HistoricalBaseFeeRange     []*Amount   `json:"historicalBaseFeeRange,omitempty"`
	PriorityFeeTrend           string      `json:"priorityFeeTrend,omitempty" ts_type:"'up' | 'down'"`
	BaseFeeTrend               string      `json:"baseFeeTrend,omitempty" ts_type:"'up' | 'down'"`
}
