package pathprocessor import ( "context" "encoding/json" "fmt" "math/big" netUrl "net/url" "strings" "sync" "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/status-im/status-go/account" "github.com/status-im/status-go/contracts" "github.com/status-im/status-go/contracts/hop" hopL1CctpImplementation "github.com/status-im/status-go/contracts/hop/l1Contracts/l1CctpImplementation" hopL1Erc20Bridge "github.com/status-im/status-go/contracts/hop/l1Contracts/l1Erc20Bridge" hopL1EthBridge "github.com/status-im/status-go/contracts/hop/l1Contracts/l1EthBridge" hopL1HopBridge "github.com/status-im/status-go/contracts/hop/l1Contracts/l1HopBridge" hopL2AmmWrapper "github.com/status-im/status-go/contracts/hop/l2Contracts/l2AmmWrapper" hopL2ArbitrumBridge "github.com/status-im/status-go/contracts/hop/l2Contracts/l2ArbitrumBridge" hopL2CctpImplementation "github.com/status-im/status-go/contracts/hop/l2Contracts/l2CctpImplementation" hopL2OptimismBridge "github.com/status-im/status-go/contracts/hop/l2Contracts/l2OptimismBridge" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/rpc/chain" "github.com/status-im/status-go/rpc/network" "github.com/status-im/status-go/services/wallet/bigint" walletCommon "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/thirdparty" "github.com/status-im/status-go/services/wallet/token" "github.com/status-im/status-go/transactions" ) type HopBridgeTxArgs struct { transactions.SendTxArgs ChainID uint64 `json:"chainId"` ChainIDTo uint64 `json:"chainIdTo"` Symbol string `json:"symbol"` Recipient common.Address `json:"recipient"` Amount *hexutil.Big `json:"amount"` BonderFee *hexutil.Big `json:"bonderFee"` } type BonderFee struct { AmountIn *bigint.BigInt `json:"amountIn"` Slippage float32 `json:"slippage"` AmountOutMin *bigint.BigInt `json:"amountOutMin"` DestinationAmountOutMin *bigint.BigInt `json:"destinationAmountOutMin"` BonderFee *bigint.BigInt `json:"bonderFee"` EstimatedRecieved *bigint.BigInt `json:"estimatedRecieved"` Deadline int64 `json:"deadline"` DestinationDeadline int64 `json:"destinationDeadline"` } func (bf *BonderFee) UnmarshalJSON(data []byte) error { type Alias BonderFee aux := &struct { AmountIn string `json:"amountIn"` Slippage float32 `json:"slippage"` AmountOutMin string `json:"amountOutMin"` DestinationAmountOutMin string `json:"destinationAmountOutMin"` BonderFee string `json:"bonderFee"` EstimatedRecieved string `json:"estimatedRecieved"` Deadline int64 `json:"deadline"` DestinationDeadline *int64 `json:"destinationDeadline"` *Alias }{ Alias: (*Alias)(bf), } if err := json.Unmarshal(data, aux); err != nil { return createBridgeHopErrorResponse(err) } bf.AmountIn = &bigint.BigInt{Int: new(big.Int)} bf.AmountIn.SetString(aux.AmountIn, 10) bf.Slippage = aux.Slippage bf.AmountOutMin = &bigint.BigInt{Int: new(big.Int)} bf.AmountOutMin.SetString(aux.AmountOutMin, 10) bf.DestinationAmountOutMin = &bigint.BigInt{Int: new(big.Int)} bf.DestinationAmountOutMin.SetString(aux.DestinationAmountOutMin, 10) bf.BonderFee = &bigint.BigInt{Int: new(big.Int)} bf.BonderFee.SetString(aux.BonderFee, 10) bf.EstimatedRecieved = &bigint.BigInt{Int: new(big.Int)} bf.EstimatedRecieved.SetString(aux.EstimatedRecieved, 10) bf.Deadline = aux.Deadline if aux.DestinationDeadline != nil { bf.DestinationDeadline = *aux.DestinationDeadline } return nil } type HopBridgeProcessor struct { transactor transactions.TransactorIface httpClient *thirdparty.HTTPClient tokenManager *token.Manager contractMaker *contracts.ContractMaker networkManager network.ManagerInterface bonderFee *sync.Map // [fromChainName-toChainName]BonderFee } func NewHopBridgeProcessor(rpcClient rpc.ClientInterface, transactor transactions.TransactorIface, tokenManager *token.Manager, networkManager network.ManagerInterface) *HopBridgeProcessor { return &HopBridgeProcessor{ contractMaker: &contracts.ContractMaker{RPCClient: rpcClient}, httpClient: thirdparty.NewHTTPClient(), transactor: transactor, tokenManager: tokenManager, networkManager: networkManager, bonderFee: &sync.Map{}, } } func createBridgeHopErrorResponse(err error) error { return createErrorResponse(ProcessorBridgeHopName, err) } func (h *HopBridgeProcessor) Name() string { return ProcessorBridgeHopName } func (h *HopBridgeProcessor) Clear() { h.bonderFee = &sync.Map{} } func (h *HopBridgeProcessor) AvailableFor(params ProcessorInputParams) (bool, error) { if params.FromChain == nil || params.ToChain == nil { return false, ErrNoChainSet } if params.FromToken == nil { return false, ErrNoTokenSet } if params.ToToken != nil { return false, ErrToTokenShouldNotBeSet } if params.FromChain.ChainID == params.ToChain.ChainID { return false, ErrFromAndToChainsMustBeDifferent } // We chcek if the contract is available on the network for the token _, err := h.GetContractAddress(params) // toToken is not nil only if the send type is Swap return err == nil, err } func (c *HopBridgeProcessor) getAppropriateABI(contractType string, chainID uint64, token *token.Token) (abi.ABI, error) { switch contractType { case hop.CctpL1Bridge: return abi.JSON(strings.NewReader(hopL1CctpImplementation.HopL1CctpImplementationABI)) case hop.L1Bridge: if token.IsNative() { return abi.JSON(strings.NewReader(hopL1EthBridge.HopL1EthBridgeABI)) } if token.Symbol == HopSymbol { return abi.JSON(strings.NewReader(hopL1HopBridge.HopL1HopBridgeABI)) } return abi.JSON(strings.NewReader(hopL1Erc20Bridge.HopL1Erc20BridgeABI)) case hop.L2AmmWrapper: return abi.JSON(strings.NewReader(hopL2AmmWrapper.HopL2AmmWrapperABI)) case hop.CctpL2Bridge: return abi.JSON(strings.NewReader(hopL2CctpImplementation.HopL2CctpImplementationABI)) case hop.L2Bridge: if chainID == walletCommon.OptimismMainnet || chainID == walletCommon.OptimismSepolia { return abi.JSON(strings.NewReader(hopL2OptimismBridge.HopL2OptimismBridgeABI)) } if chainID == walletCommon.ArbitrumMainnet || chainID == walletCommon.ArbitrumSepolia { return abi.JSON(strings.NewReader(hopL2ArbitrumBridge.HopL2ArbitrumBridgeABI)) } } return abi.ABI{}, ErrNotAvailableForContractType } func (h *HopBridgeProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { _, contractType, err := hop.GetContractAddress(params.FromChain.ChainID, params.FromToken.Symbol) if err != nil { return []byte{}, createBridgeHopErrorResponse(err) } return h.packTxInputDataInternally(params, contractType) } func (h *HopBridgeProcessor) packTxInputDataInternally(params ProcessorInputParams, contractType string) ([]byte, error) { abi, err := h.getAppropriateABI(contractType, params.FromChain.ChainID, params.FromToken) if err != nil { return []byte{}, createBridgeHopErrorResponse(err) } bonderKey := makeKey(params.FromChain.ChainID, params.ToChain.ChainID, "", "") bonderFeeIns, ok := h.bonderFee.Load(bonderKey) if !ok { return nil, ErrNoBonderFeeFound } bonderFee := bonderFeeIns.(*BonderFee) switch contractType { case hop.CctpL1Bridge: return h.packCctpL1BridgeTx(abi, params.ToChain.ChainID, params.ToAddr, bonderFee) case hop.L1Bridge: return h.packL1BridgeTx(abi, params.ToChain.ChainID, params.ToAddr, bonderFee) case hop.L2AmmWrapper: return h.packL2AmmWrapperTx(abi, params.ToChain.ChainID, params.ToAddr, bonderFee) case hop.CctpL2Bridge: return h.packCctpL2BridgeTx(abi, params.ToChain.ChainID, params.ToAddr, bonderFee) case hop.L2Bridge: return h.packL2BridgeTx(abi, params.ToChain.ChainID, params.ToAddr, bonderFee) } return []byte{}, ErrContractTypeNotSupported } func (h *HopBridgeProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) { if params.TestsMode { if params.TestEstimationMap != nil { if val, ok := params.TestEstimationMap[h.Name()]; ok { return val, nil } } return 0, ErrNoEstimationFound } value := big.NewInt(0) if params.FromToken.IsNative() { value = params.AmountIn } contractAddress, contractType, err := hop.GetContractAddress(params.FromChain.ChainID, params.FromToken.Symbol) if err != nil { return 0, createBridgeHopErrorResponse(err) } input, err := h.packTxInputDataInternally(params, contractType) if err != nil { return 0, createBridgeHopErrorResponse(err) } ethClient, err := h.contractMaker.RPCClient.EthClient(params.FromChain.ChainID) if err != nil { return 0, createBridgeHopErrorResponse(err) } msg := ethereum.CallMsg{ From: params.FromAddr, To: &contractAddress, Value: value, Data: input, } estimation, err := ethClient.EstimateGas(context.Background(), msg) if err != nil { if !params.FromToken.IsNative() { // TODO: this is a temporary solution until we find a better way to estimate the gas // hardcoding the estimation for other than ETH, cause we cannot get a proper estimation without having an approval placed first // this is an error we're facing otherwise: `execution reverted: ERC20: transfer amount exceeds allowance` estimation = 350000 } else { return 0, createBridgeHopErrorResponse(err) } } increasedEstimation := float64(estimation) * IncreaseEstimatedGasFactor return uint64(increasedEstimation), nil } func (h *HopBridgeProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { address, _, err := hop.GetContractAddress(params.FromChain.ChainID, params.FromToken.Symbol) return address, createBridgeHopErrorResponse(err) } func (h *HopBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (tx *ethTypes.Transaction, err error) { fromChain := h.networkManager.Find(sendArgs.HopTx.ChainID) if fromChain == nil { return tx, fmt.Errorf("ChainID not supported %d", sendArgs.HopTx.ChainID) } token := h.tokenManager.FindToken(fromChain, sendArgs.HopTx.Symbol) var nonce uint64 if lastUsedNonce < 0 { nonce, err = h.transactor.NextNonce(h.contractMaker.RPCClient, fromChain.ChainID, sendArgs.HopTx.From) if err != nil { return tx, createBridgeHopErrorResponse(err) } } else { nonce = uint64(lastUsedNonce) + 1 } argNonce := hexutil.Uint64(nonce) sendArgs.HopTx.Nonce = &argNonce txOpts := sendArgs.HopTx.ToTransactOpts(signerFn) if token.IsNative() { txOpts.Value = (*big.Int)(sendArgs.HopTx.Amount) } ethClient, err := h.contractMaker.RPCClient.EthClient(fromChain.ChainID) if err != nil { return tx, createBridgeHopErrorResponse(err) } contractAddress, contractType, err := hop.GetContractAddress(fromChain.ChainID, sendArgs.HopTx.Symbol) if err != nil { return tx, createBridgeHopErrorResponse(err) } bonderKey := makeKey(sendArgs.HopTx.ChainID, sendArgs.HopTx.ChainIDTo, "", "") bonderFeeIns, ok := h.bonderFee.Load(bonderKey) if !ok { return nil, ErrNoBonderFeeFound } bonderFee := bonderFeeIns.(*BonderFee) switch contractType { case hop.CctpL1Bridge: tx, err = h.sendCctpL1BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainIDTo, sendArgs.HopTx.Recipient, txOpts, bonderFee) case hop.L1Bridge: tx, err = h.sendL1BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainIDTo, sendArgs.HopTx.Recipient, txOpts, token, bonderFee) case hop.L2AmmWrapper: tx, err = h.sendL2AmmWrapperTx(contractAddress, ethClient, sendArgs.HopTx.ChainIDTo, sendArgs.HopTx.Recipient, txOpts, bonderFee) case hop.CctpL2Bridge: tx, err = h.sendCctpL2BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainIDTo, sendArgs.HopTx.Recipient, txOpts, bonderFee) case hop.L2Bridge: tx, err = h.sendL2BridgeTx(contractAddress, ethClient, sendArgs.HopTx.ChainIDTo, sendArgs.HopTx.Recipient, txOpts, bonderFee) default: return tx, ErrContractTypeNotSupported } if err != nil { return tx, createBridgeHopErrorResponse(err) } err = h.transactor.StoreAndTrackPendingTx(txOpts.From, sendArgs.HopTx.Symbol, sendArgs.HopTx.ChainID, sendArgs.HopTx.MultiTransactionID, tx) if err != nil { return tx, createBridgeHopErrorResponse(err) } return tx, nil } func (h *HopBridgeProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (hash types.Hash, nonce uint64, err error) { tx, err := h.sendOrBuild(sendArgs, getSigner(sendArgs.HopTx.ChainID, sendArgs.HopTx.From, verifiedAccount), lastUsedNonce) if err != nil { return types.Hash{}, 0, createBridgeHopErrorResponse(err) } return types.Hash(tx.Hash()), tx.Nonce(), nil } func (h *HopBridgeProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { tx, err := h.sendOrBuild(sendArgs, nil, lastUsedNonce) return tx, tx.Nonce(), createBridgeHopErrorResponse(err) } func (h *HopBridgeProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { bonderKey := makeKey(params.FromChain.ChainID, params.ToChain.ChainID, "", "") if params.TestsMode { if val, ok := params.TestBonderFeeMap[params.FromToken.Symbol]; ok { res := new(big.Int).Sub(params.AmountIn, val) bonderFee := &BonderFee{ AmountIn: &bigint.BigInt{Int: params.AmountIn}, Slippage: 5, AmountOutMin: &bigint.BigInt{Int: res}, DestinationAmountOutMin: &bigint.BigInt{Int: res}, BonderFee: &bigint.BigInt{Int: val}, EstimatedRecieved: &bigint.BigInt{Int: res}, Deadline: time.Now().Add(SevenDaysInSeconds).Unix(), } h.bonderFee.Store(bonderKey, bonderFee) return val, ZeroBigIntValue, nil } return nil, nil, ErrNoBonderFeeFound } hopChainsMap := map[uint64]string{ walletCommon.EthereumMainnet: "ethereum", walletCommon.OptimismMainnet: "optimism", walletCommon.ArbitrumMainnet: "arbitrum", } fromChainName, ok := hopChainsMap[params.FromChain.ChainID] if !ok { return nil, nil, ErrFromChainNotSupported } toChainName, ok := hopChainsMap[params.ToChain.ChainID] if !ok { return nil, nil, ErrToChainNotSupported } reqParams := netUrl.Values{} reqParams.Add("amount", params.AmountIn.String()) reqParams.Add("token", params.FromToken.Symbol) reqParams.Add("fromChain", fromChainName) reqParams.Add("toChain", toChainName) reqParams.Add("slippage", "0.5") // menas 0.5% url := "https://api.hop.exchange/v1/quote" response, err := h.httpClient.DoGetRequest(context.Background(), url, reqParams, nil) if err != nil { return nil, nil, err } bonderFee := &BonderFee{} err = json.Unmarshal(response, bonderFee) if err != nil { return nil, nil, createBridgeHopErrorResponse(err) } h.bonderFee.Store(bonderKey, bonderFee) // Remove token fee from bonder fee as said here: // https://docs.hop.exchange/v/developer-docs/api/api#get-v1-quote // `bonderFee` - The suggested bonder fee for the amount in. The bonder fee also includes the cost of the destination transaction fee. tokenFee := new(big.Int).Sub(bonderFee.AmountIn.Int, bonderFee.EstimatedRecieved.Int) return bonderFee.BonderFee.Int, tokenFee, nil } func (h *HopBridgeProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { bonderKey := makeKey(params.FromChain.ChainID, params.ToChain.ChainID, "", "") bonderFeeIns, ok := h.bonderFee.Load(bonderKey) if !ok { return nil, ErrNoBonderFeeFound } bonderFee := bonderFeeIns.(*BonderFee) return bonderFee.AmountOutMin.Int, nil } func (h *HopBridgeProcessor) packCctpL1BridgeTx(abi abi.ABI, toChainID uint64, to common.Address, bonderFee *BonderFee) ([]byte, error) { return abi.Pack("send", big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.BonderFee.Int) } func (h *HopBridgeProcessor) sendCctpL1BridgeTx(contractAddress common.Address, ethClient chain.ClientInterface, toChainID uint64, to common.Address, txOpts *bind.TransactOpts, bonderFee *BonderFee) (tx *ethTypes.Transaction, err error) { contractInstance, err := hopL1CctpImplementation.NewHopL1CctpImplementation( contractAddress, ethClient, ) if err != nil { return tx, createBridgeHopErrorResponse(err) } return contractInstance.Send( txOpts, big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.BonderFee.Int) } func (h *HopBridgeProcessor) packL1BridgeTx(abi abi.ABI, toChainID uint64, to common.Address, bonderFee *BonderFee) ([]byte, error) { return abi.Pack("sendToL2", big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline), common.Address{}, ZeroBigIntValue) } func (h *HopBridgeProcessor) sendL1BridgeTx(contractAddress common.Address, ethClient chain.ClientInterface, toChainID uint64, to common.Address, txOpts *bind.TransactOpts, token *token.Token, bonderFee *BonderFee) (tx *ethTypes.Transaction, err error) { if token.IsNative() { contractInstance, err := hopL1EthBridge.NewHopL1EthBridge( contractAddress, ethClient, ) if err != nil { return tx, createBridgeHopErrorResponse(err) } return contractInstance.SendToL2( txOpts, big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline), common.Address{}, ZeroBigIntValue) } if token.Symbol == HopSymbol { contractInstance, err := hopL1HopBridge.NewHopL1HopBridge( contractAddress, ethClient, ) if err != nil { return tx, createBridgeHopErrorResponse(err) } return contractInstance.SendToL2( txOpts, big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline), common.Address{}, ZeroBigIntValue) } contractInstance, err := hopL1Erc20Bridge.NewHopL1Erc20Bridge( contractAddress, ethClient, ) if err != nil { return tx, createBridgeHopErrorResponse(err) } return contractInstance.SendToL2( txOpts, big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline), common.Address{}, ZeroBigIntValue) } func (h *HopBridgeProcessor) packCctpL2BridgeTx(abi abi.ABI, toChainID uint64, to common.Address, bonderFee *BonderFee) ([]byte, error) { return abi.Pack("send", big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.BonderFee.Int) } func (h *HopBridgeProcessor) sendCctpL2BridgeTx(contractAddress common.Address, ethClient chain.ClientInterface, toChainID uint64, to common.Address, txOpts *bind.TransactOpts, bonderFee *BonderFee) (tx *ethTypes.Transaction, err error) { contractInstance, err := hopL2CctpImplementation.NewHopL2CctpImplementation( contractAddress, ethClient, ) if err != nil { return tx, createBridgeHopErrorResponse(err) } return contractInstance.Send( txOpts, big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.BonderFee.Int, ) } func (h *HopBridgeProcessor) packL2AmmWrapperTx(abi abi.ABI, toChainID uint64, to common.Address, bonderFee *BonderFee) ([]byte, error) { return abi.Pack("swapAndSend", big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.BonderFee.Int, bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline), bonderFee.DestinationAmountOutMin.Int, big.NewInt(bonderFee.DestinationDeadline)) } func (h *HopBridgeProcessor) sendL2AmmWrapperTx(contractAddress common.Address, ethClient chain.ClientInterface, toChainID uint64, to common.Address, txOpts *bind.TransactOpts, bonderFee *BonderFee) (tx *ethTypes.Transaction, err error) { contractInstance, err := hopL2AmmWrapper.NewHopL2AmmWrapper( contractAddress, ethClient, ) if err != nil { return tx, createBridgeHopErrorResponse(err) } return contractInstance.SwapAndSend( txOpts, big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.BonderFee.Int, bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline), bonderFee.DestinationAmountOutMin.Int, big.NewInt(bonderFee.DestinationDeadline)) } func (h *HopBridgeProcessor) packL2BridgeTx(abi abi.ABI, toChainID uint64, to common.Address, bonderFee *BonderFee) ([]byte, error) { return abi.Pack("send", big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.BonderFee.Int, bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline)) } func (h *HopBridgeProcessor) sendL2BridgeTx(contractAddress common.Address, ethClient chain.ClientInterface, toChainID uint64, to common.Address, txOpts *bind.TransactOpts, bonderFee *BonderFee) (tx *ethTypes.Transaction, err error) { fromChainID := ethClient.NetworkID() if fromChainID == walletCommon.OptimismMainnet || fromChainID == walletCommon.OptimismSepolia { contractInstance, err := hopL2OptimismBridge.NewHopL2OptimismBridge( contractAddress, ethClient, ) if err != nil { return tx, createBridgeHopErrorResponse(err) } return contractInstance.Send( txOpts, big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.BonderFee.Int, bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline)) } if fromChainID == walletCommon.ArbitrumMainnet || fromChainID == walletCommon.ArbitrumSepolia { contractInstance, err := hopL2ArbitrumBridge.NewHopL2ArbitrumBridge( contractAddress, ethClient, ) if err != nil { return tx, createBridgeHopErrorResponse(err) } return contractInstance.Send( txOpts, big.NewInt(int64(toChainID)), to, bonderFee.AmountIn.Int, bonderFee.BonderFee.Int, bonderFee.AmountOutMin.Int, big.NewInt(bonderFee.Deadline)) } return tx, ErrTxForChainNotSupported }