2024-08-28 08:59:19 +00:00
package router
import (
"context"
"errors"
"math/big"
2024-11-05 21:20:27 +00:00
"slices"
"strings"
2024-08-28 08:59:19 +00:00
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
2024-08-29 13:40:30 +00:00
"github.com/ethereum/go-ethereum/common/hexutil"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/contracts"
gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
"github.com/status-im/status-go/params"
2024-11-28 20:28:25 +00:00
"github.com/status-im/status-go/rpc/chain"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/services/wallet/bigint"
2024-11-05 21:20:27 +00:00
"github.com/status-im/status-go/services/wallet/collectibles"
2024-08-28 08:59:19 +00:00
walletCommon "github.com/status-im/status-go/services/wallet/common"
2024-11-05 21:20:27 +00:00
"github.com/status-im/status-go/services/wallet/market"
2024-08-29 13:40:30 +00:00
"github.com/status-im/status-go/services/wallet/router/fees"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
2024-08-29 13:40:30 +00:00
routs "github.com/status-im/status-go/services/wallet/router/routes"
2024-08-28 11:17:59 +00:00
"github.com/status-im/status-go/services/wallet/router/sendtype"
2024-08-28 08:59:19 +00:00
"github.com/status-im/status-go/services/wallet/token"
)
2024-08-28 11:17:59 +00:00
func ( r * Router ) requireApproval ( ctx context . Context , sendType sendtype . SendType , approvalContractAddress * common . Address , params pathprocessor . ProcessorInputParams ) (
2024-08-29 13:40:30 +00:00
bool , * big . Int , error ) {
2024-08-28 08:59:19 +00:00
if sendType . IsCollectiblesTransfer ( ) || sendType . IsEnsTransfer ( ) || sendType . IsStickersTransfer ( ) {
2024-08-29 13:40:30 +00:00
return false , nil , nil
2024-08-28 08:59:19 +00:00
}
if params . FromToken . IsNative ( ) {
2024-08-29 13:40:30 +00:00
return false , nil , nil
2024-08-28 08:59:19 +00:00
}
contractMaker , err := contracts . NewContractMaker ( r . rpcClient )
if err != nil {
2024-08-29 13:40:30 +00:00
return false , nil , err
2024-08-28 08:59:19 +00:00
}
contract , err := contractMaker . NewERC20 ( params . FromChain . ChainID , params . FromToken . Address )
if err != nil {
2024-08-29 13:40:30 +00:00
return false , nil , err
2024-08-28 08:59:19 +00:00
}
2024-09-23 06:35:34 +00:00
if approvalContractAddress == nil || * approvalContractAddress == walletCommon . ZeroAddress ( ) {
2024-08-29 13:40:30 +00:00
return false , nil , nil
2024-08-28 08:59:19 +00:00
}
if params . TestsMode {
2024-08-29 13:40:30 +00:00
return true , params . AmountIn , nil
2024-08-28 08:59:19 +00:00
}
allowance , err := contract . Allowance ( & bind . CallOpts {
Context : ctx ,
} , params . FromAddr , * approvalContractAddress )
if err != nil {
2024-08-29 13:40:30 +00:00
return false , nil , err
2024-08-28 08:59:19 +00:00
}
if allowance . Cmp ( params . AmountIn ) >= 0 {
2024-08-29 13:40:30 +00:00
return false , nil , nil
2024-08-28 08:59:19 +00:00
}
2024-08-29 13:40:30 +00:00
return true , params . AmountIn , nil
}
func ( r * Router ) estimateGasForApproval ( params pathprocessor . ProcessorInputParams , approvalContractAddress * common . Address ) ( uint64 , error ) {
2024-09-23 07:06:55 +00:00
data , err := walletCommon . PackApprovalInputData ( params . AmountIn , approvalContractAddress )
2024-08-29 13:40:30 +00:00
if err != nil {
return 0 , err
}
ethClient , err := r . rpcClient . EthClient ( params . FromChain . ChainID )
2024-08-28 08:59:19 +00:00
if err != nil {
2024-08-29 13:40:30 +00:00
return 0 , err
2024-08-28 08:59:19 +00:00
}
2024-08-29 13:40:30 +00:00
return ethClient . EstimateGas ( context . Background ( ) , ethereum . CallMsg {
2024-08-28 08:59:19 +00:00
From : params . FromAddr ,
To : & params . FromToken . Address ,
2024-09-23 06:35:34 +00:00
Value : walletCommon . ZeroBigIntValue ( ) ,
2024-08-28 08:59:19 +00:00
Data : data ,
} )
2024-08-29 13:40:30 +00:00
}
func ( r * Router ) calculateApprovalL1Fee ( amountIn * big . Int , chainID uint64 , approvalContractAddress * common . Address ) ( uint64 , error ) {
2024-11-28 20:28:25 +00:00
ethClient , err := r . rpcClient . EthClient ( chainID )
2024-08-29 13:40:30 +00:00
if err != nil {
return 0 , err
}
2024-11-28 20:28:25 +00:00
return CalculateApprovalL1Fee ( amountIn , chainID , approvalContractAddress , ethClient )
}
func CalculateApprovalL1Fee ( amountIn * big . Int , chainID uint64 , approvalContractAddress * common . Address , ethClient chain . ClientInterface ) ( uint64 , error ) {
data , err := walletCommon . PackApprovalInputData ( amountIn , approvalContractAddress )
2024-08-28 08:59:19 +00:00
if err != nil {
2024-08-29 13:40:30 +00:00
return 0 , err
2024-08-28 08:59:19 +00:00
}
var l1Fee uint64
2024-08-29 13:40:30 +00:00
oracleContractAddress , err := gaspriceoracle . ContractAddress ( chainID )
2024-08-28 08:59:19 +00:00
if err == nil {
oracleContract , err := gaspriceoracle . NewGaspriceoracleCaller ( oracleContractAddress , ethClient )
if err != nil {
2024-08-29 13:40:30 +00:00
return 0 , err
2024-08-28 08:59:19 +00:00
}
callOpt := & bind . CallOpts { }
2024-11-28 20:28:25 +00:00
l1FeeResult , err := oracleContract . GetL1Fee ( callOpt , data )
if err == nil {
l1Fee = l1FeeResult . Uint64 ( )
}
2024-08-28 08:59:19 +00:00
}
2024-11-28 20:28:25 +00:00
// return 0 if we failed to get the fee
2024-08-29 13:40:30 +00:00
return l1Fee , nil
2024-08-28 08:59:19 +00:00
}
func ( r * Router ) getERC1155Balance ( ctx context . Context , network * params . Network , token * token . Token , account common . Address ) ( * big . Int , error ) {
tokenID , success := new ( big . Int ) . SetString ( token . Symbol , 10 )
if ! success {
return nil , errors . New ( "failed to convert token symbol to big.Int" )
}
balances , err := r . collectiblesManager . FetchERC1155Balances (
ctx ,
account ,
walletCommon . ChainID ( network . ChainID ) ,
token . Address ,
[ ] * bigint . BigInt { & bigint . BigInt { Int : tokenID } } ,
)
if err != nil {
return nil , err
}
if len ( balances ) != 1 || balances [ 0 ] == nil {
return nil , errors . New ( "invalid ERC1155 balance fetch response" )
}
return balances [ 0 ] . Int , nil
}
func ( r * Router ) getBalance ( ctx context . Context , chainID uint64 , token * token . Token , account common . Address ) ( * big . Int , error ) {
client , err := r . rpcClient . EthClient ( chainID )
if err != nil {
return nil , err
}
return r . tokenManager . GetBalance ( ctx , client , account , token . Address )
}
2024-08-29 13:40:30 +00:00
func ( r * Router ) cacluateFees ( ctx context . Context , path * routs . Path , fetchedFees * fees . SuggestedFees , testsMode bool , testApprovalL1Fee uint64 ) ( err error ) {
var (
l1ApprovalFee uint64
)
if path . ApprovalRequired {
if testsMode {
l1ApprovalFee = testApprovalL1Fee
} else {
l1ApprovalFee , err = r . calculateApprovalL1Fee ( path . AmountIn . ToInt ( ) , path . FromChain . ChainID , path . ApprovalContractAddress )
if err != nil {
return err
}
}
}
// TODO: keep l1 fees at 0 until we have the correct algorithm, as we do base fee x 2 that should cover the l1 fees
var l1FeeWei uint64 = 0
// if input.SendType.needL1Fee() {
// txInputData, err := pProcessor.PackTxInputData(processorInputParams)
// if err != nil {
// continue
// }
// l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
// }
r . lastInputParamsMutex . Lock ( )
gasFeeMode := r . lastInputParams . GasFeeMode
r . lastInputParamsMutex . Unlock ( )
maxFeesPerGas := fetchedFees . FeeFor ( gasFeeMode )
// calculate ETH fees
ethTotalFees := big . NewInt ( 0 )
txFeeInWei := new ( big . Int ) . Mul ( maxFeesPerGas , big . NewInt ( int64 ( path . TxGasAmount ) ) )
ethTotalFees . Add ( ethTotalFees , txFeeInWei )
txL1FeeInWei := big . NewInt ( 0 )
if l1FeeWei > 0 {
txL1FeeInWei = big . NewInt ( int64 ( l1FeeWei ) )
ethTotalFees . Add ( ethTotalFees , txL1FeeInWei )
}
approvalFeeInWei := big . NewInt ( 0 )
approvalL1FeeInWei := big . NewInt ( 0 )
if path . ApprovalRequired {
approvalFeeInWei . Mul ( maxFeesPerGas , big . NewInt ( int64 ( path . ApprovalGasAmount ) ) )
ethTotalFees . Add ( ethTotalFees , approvalFeeInWei )
if l1ApprovalFee > 0 {
approvalL1FeeInWei = big . NewInt ( int64 ( l1ApprovalFee ) )
ethTotalFees . Add ( ethTotalFees , approvalL1FeeInWei )
}
}
// calculate required balances (bonder and token fees are already included in the amountIn by Hop bridge (once we include Celar we need to check how they handle the fees))
requiredNativeBalance := big . NewInt ( 0 )
requiredTokenBalance := big . NewInt ( 0 )
if path . FromToken . IsNative ( ) {
requiredNativeBalance . Add ( requiredNativeBalance , path . AmountIn . ToInt ( ) )
if ! path . SubtractFees {
requiredNativeBalance . Add ( requiredNativeBalance , ethTotalFees )
}
} else {
requiredTokenBalance . Add ( requiredTokenBalance , path . AmountIn . ToInt ( ) )
requiredNativeBalance . Add ( requiredNativeBalance , ethTotalFees )
}
// set the values
path . SuggestedLevelsForMaxFeesPerGas = fetchedFees . MaxFeesLevels
path . MaxFeesPerGas = ( * hexutil . Big ) ( maxFeesPerGas )
path . TxBaseFee = ( * hexutil . Big ) ( fetchedFees . BaseFee )
path . TxPriorityFee = ( * hexutil . Big ) ( fetchedFees . MaxPriorityFeePerGas )
path . TxFee = ( * hexutil . Big ) ( txFeeInWei )
path . TxL1Fee = ( * hexutil . Big ) ( txL1FeeInWei )
path . ApprovalBaseFee = ( * hexutil . Big ) ( fetchedFees . BaseFee )
path . ApprovalPriorityFee = ( * hexutil . Big ) ( fetchedFees . MaxPriorityFeePerGas )
path . ApprovalFee = ( * hexutil . Big ) ( approvalFeeInWei )
path . ApprovalL1Fee = ( * hexutil . Big ) ( approvalL1FeeInWei )
path . TxTotalFee = ( * hexutil . Big ) ( ethTotalFees )
path . RequiredTokenBalance = requiredTokenBalance
path . RequiredNativeBalance = requiredNativeBalance
return nil
}
2024-11-05 21:20:27 +00:00
func findToken ( sendType sendtype . SendType , tokenManager * token . Manager , collectibles * collectibles . Service , account common . Address , network * params . Network , tokenID string ) * token . Token {
if ! sendType . IsCollectiblesTransfer ( ) {
return tokenManager . FindToken ( network , tokenID )
}
parts := strings . Split ( tokenID , ":" )
contractAddress := common . HexToAddress ( parts [ 0 ] )
collectibleTokenID , success := new ( big . Int ) . SetString ( parts [ 1 ] , 10 )
if ! success {
return nil
}
uniqueID , err := collectibles . GetOwnedCollectible ( walletCommon . ChainID ( network . ChainID ) , account , contractAddress , collectibleTokenID )
if err != nil || uniqueID == nil {
return nil
}
return & token . Token {
Address : contractAddress ,
Symbol : collectibleTokenID . String ( ) ,
Decimals : 0 ,
ChainID : network . ChainID ,
}
}
func fetchPrices ( sendType sendtype . SendType , marketManager * market . Manager , tokenIDs [ ] string ) ( map [ string ] float64 , error ) {
nonUniqueSymbols := append ( tokenIDs , "ETH" )
// remove duplicate enteries
slices . Sort ( nonUniqueSymbols )
symbols := slices . Compact ( nonUniqueSymbols )
if sendType . IsCollectiblesTransfer ( ) {
symbols = [ ] string { "ETH" }
}
pricesMap , err := marketManager . GetOrFetchPrices ( symbols , [ ] string { "USD" } , market . MaxAgeInSecondsForFresh )
if err != nil {
return nil , err
}
prices := make ( map [ string ] float64 , 0 )
for symbol , pricePerCurrency := range pricesMap {
prices [ symbol ] = pricePerCurrency [ "USD" ] . Price
}
if sendType . IsCollectiblesTransfer ( ) {
for _ , tokenID := range tokenIDs {
prices [ tokenID ] = 0
}
}
return prices , nil
}