github.com/status-im/status-go@v1.1.0/services/wallet/router/pathprocessor/processor_swap_paraswap.go (about) 1 package pathprocessor 2 3 import ( 4 "context" 5 "math/big" 6 "strconv" 7 "sync" 8 9 "github.com/ethereum/go-ethereum/common" 10 "github.com/ethereum/go-ethereum/common/hexutil" 11 ethTypes "github.com/ethereum/go-ethereum/core/types" 12 "github.com/status-im/status-go/account" 13 "github.com/status-im/status-go/eth-node/types" 14 "github.com/status-im/status-go/rpc" 15 walletCommon "github.com/status-im/status-go/services/wallet/common" 16 "github.com/status-im/status-go/services/wallet/thirdparty/paraswap" 17 walletToken "github.com/status-im/status-go/services/wallet/token" 18 "github.com/status-im/status-go/transactions" 19 ) 20 21 type SwapParaswapTxArgs struct { 22 transactions.SendTxArgs 23 ChainID uint64 `json:"chainId"` 24 ChainIDTo uint64 `json:"chainIdTo"` 25 TokenIDFrom string `json:"tokenIdFrom"` 26 TokenIDTo string `json:"tokenIdTo"` 27 SlippagePercentage float32 `json:"slippagePercentage"` 28 } 29 30 type SwapParaswapProcessor struct { 31 paraswapClient paraswap.ClientInterface 32 transactor transactions.TransactorIface 33 priceRoute sync.Map // [fromChainName-toChainName-fromTokenSymbol-toTokenSymbol, paraswap.Route] 34 } 35 36 const ( 37 partnerID = "status.app" 38 ) 39 40 func getPartnerAddressAndFeePcnt(chainID uint64) (common.Address, float64) { 41 const partnerFeePcnt = 0.7 42 43 switch chainID { 44 case walletCommon.EthereumMainnet: 45 return common.HexToAddress("0xd9abc564bfabefa88a6C2723d78124579600F568"), partnerFeePcnt 46 case walletCommon.OptimismMainnet: 47 return common.HexToAddress("0xE9B59dC0b30cd4646430c25de0111D651c395775"), partnerFeePcnt 48 case walletCommon.ArbitrumMainnet: 49 return common.HexToAddress("0x9a8278e856C0B191B9daa2d7DD1f7B28268E4DA2"), partnerFeePcnt 50 } 51 return common.Address{}, 0 52 } 53 54 func NewSwapParaswapProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface, tokenManager *walletToken.Manager) *SwapParaswapProcessor { 55 defaultChainID := walletCommon.EthereumMainnet 56 partnerAddress, partnerFeePcnt := getPartnerAddressAndFeePcnt(defaultChainID) 57 58 return &SwapParaswapProcessor{ 59 paraswapClient: paraswap.NewClientV5( 60 defaultChainID, 61 partnerID, 62 partnerAddress, 63 partnerFeePcnt, 64 ), 65 transactor: transactor, 66 priceRoute: sync.Map{}, 67 } 68 } 69 70 func createSwapParaswapErrorResponse(err error) error { 71 switch err.Error() { 72 case "Price Timeout": 73 return ErrPriceTimeout 74 case "No routes found with enough liquidity": 75 return ErrNotEnoughLiquidity 76 case "ESTIMATED_LOSS_GREATER_THAN_MAX_IMPACT": 77 return ErrPriceImpactTooHigh 78 } 79 return createErrorResponse(ProcessorSwapParaswapName, err) 80 } 81 82 func (s *SwapParaswapProcessor) Name() string { 83 return ProcessorSwapParaswapName 84 } 85 86 func (s *SwapParaswapProcessor) Clear() { 87 s.priceRoute = sync.Map{} 88 } 89 90 func (s *SwapParaswapProcessor) AvailableFor(params ProcessorInputParams) (bool, error) { 91 if params.FromChain == nil || params.ToChain == nil { 92 return false, ErrNoChainSet 93 } 94 if params.FromToken == nil || params.ToToken == nil { 95 return false, ErrToAndFromTokensMustBeSet 96 } 97 98 if params.FromChain.ChainID != params.ToChain.ChainID { 99 return false, ErrFromAndToChainsMustBeSame 100 } 101 102 if params.FromToken.Symbol == params.ToToken.Symbol { 103 return false, ErrFromAndToTokensMustBeDifferent 104 } 105 106 chainID := params.FromChain.ChainID 107 partnerAddress, partnerFeePcnt := getPartnerAddressAndFeePcnt(chainID) 108 s.paraswapClient.SetChainID(chainID) 109 s.paraswapClient.SetPartnerAddress(partnerAddress) 110 s.paraswapClient.SetPartnerFeePcnt(partnerFeePcnt) 111 112 searchForToken := params.FromToken.Address == ZeroAddress 113 searchForToToken := params.ToToken.Address == ZeroAddress 114 if searchForToToken || searchForToken { 115 tokensList, err := s.paraswapClient.FetchTokensList(context.Background()) 116 if err != nil { 117 return false, createSwapParaswapErrorResponse(err) 118 } 119 120 for _, t := range tokensList { 121 if searchForToken && t.Symbol == params.FromToken.Symbol { 122 params.FromToken.Address = common.HexToAddress(t.Address) 123 params.FromToken.Decimals = t.Decimals 124 if !searchForToToken { 125 break 126 } 127 } 128 129 if searchForToToken && t.Symbol == params.ToToken.Symbol { 130 params.ToToken.Address = common.HexToAddress(t.Address) 131 params.ToToken.Decimals = t.Decimals 132 if !searchForToken { 133 break 134 } 135 } 136 } 137 } 138 139 if params.FromToken.Address == ZeroAddress || params.ToToken.Address == ZeroAddress { 140 return false, ErrCannotResolveTokens 141 } 142 143 return true, nil 144 } 145 146 func calcReceivedAmountAndFee(baseDestAmount *big.Int, feePcnt float64) (destAmount *big.Int, destFee *big.Int) { 147 destAmount = new(big.Int).Set(baseDestAmount) 148 destFee = new(big.Int).SetUint64(0) 149 150 if feePcnt > 0 { 151 baseDestAmountFloat := new(big.Float).SetInt(baseDestAmount) 152 feePcntFloat := big.NewFloat(feePcnt / 100.0) 153 154 destFeeFloat := new(big.Float).Set(baseDestAmountFloat) 155 destFeeFloat = destFeeFloat.Mul(destFeeFloat, feePcntFloat) 156 destFeeFloat.Int(destFee) 157 158 destAmount = destAmount.Sub(destAmount, destFee) 159 } 160 return 161 } 162 163 func (s *SwapParaswapProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { 164 return ZeroBigIntValue, ZeroBigIntValue, nil 165 } 166 167 func (s *SwapParaswapProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { 168 // not sure what we can do here since we're using the api to build the transaction 169 return []byte{}, nil 170 } 171 172 func (s *SwapParaswapProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) { 173 if params.TestsMode { 174 if params.TestEstimationMap != nil { 175 if val, ok := params.TestEstimationMap[s.Name()]; ok { 176 return val.Value, val.Err 177 } 178 } 179 return 0, ErrNoEstimationFound 180 } 181 182 swapSide := paraswap.SellSide 183 if params.AmountOut != nil && params.AmountOut.Cmp(ZeroBigIntValue) > 0 { 184 swapSide = paraswap.BuySide 185 } 186 187 priceRoute, err := s.paraswapClient.FetchPriceRoute(context.Background(), params.FromToken.Address, params.FromToken.Decimals, 188 params.ToToken.Address, params.ToToken.Decimals, params.AmountIn, params.FromAddr, params.ToAddr, swapSide) 189 if err != nil { 190 return 0, createSwapParaswapErrorResponse(err) 191 } 192 193 key := makeKey(params.FromChain.ChainID, params.ToChain.ChainID, params.FromToken.Symbol, params.ToToken.Symbol) 194 s.priceRoute.Store(key, &priceRoute) 195 196 return priceRoute.GasCost.Uint64(), nil 197 } 198 199 func (s *SwapParaswapProcessor) GetContractAddress(params ProcessorInputParams) (address common.Address, err error) { 200 key := makeKey(params.FromChain.ChainID, params.ToChain.ChainID, params.FromToken.Symbol, params.ToToken.Symbol) 201 priceRouteIns, ok := s.priceRoute.Load(key) 202 if !ok { 203 err = ErrPriceRouteNotFound 204 return 205 } 206 priceRoute := priceRouteIns.(*paraswap.Route) 207 208 return priceRoute.TokenTransferProxy, nil 209 } 210 211 func (s *SwapParaswapProcessor) prepareTransaction(sendArgs *MultipathProcessorTxArgs) error { 212 slippageBP := uint(sendArgs.SwapTx.SlippagePercentage * 100) // convert to basis points 213 214 key := makeKey(sendArgs.SwapTx.ChainID, sendArgs.SwapTx.ChainIDTo, sendArgs.SwapTx.TokenIDFrom, sendArgs.SwapTx.TokenIDTo) 215 priceRouteIns, ok := s.priceRoute.Load(key) 216 if !ok { 217 return ErrPriceRouteNotFound 218 } 219 priceRoute := priceRouteIns.(*paraswap.Route) 220 221 tx, err := s.paraswapClient.BuildTransaction(context.Background(), priceRoute.SrcTokenAddress, priceRoute.SrcTokenDecimals, priceRoute.SrcAmount.Int, 222 priceRoute.DestTokenAddress, priceRoute.DestTokenDecimals, priceRoute.DestAmount.Int, slippageBP, 223 common.Address(sendArgs.SwapTx.From), common.Address(*sendArgs.SwapTx.To), 224 priceRoute.RawPriceRoute, priceRoute.Side) 225 if err != nil { 226 return createSwapParaswapErrorResponse(err) 227 } 228 229 value, ok := new(big.Int).SetString(tx.Value, 10) 230 if !ok { 231 return ErrConvertingAmountToBigInt 232 } 233 234 gas, err := strconv.ParseUint(tx.Gas, 10, 64) 235 if err != nil { 236 return createSwapParaswapErrorResponse(err) 237 } 238 239 gasPrice, ok := new(big.Int).SetString(tx.GasPrice, 10) 240 if !ok { 241 return ErrConvertingAmountToBigInt 242 } 243 244 sendArgs.ChainID = tx.ChainID 245 sendArgs.SwapTx.ChainID = tx.ChainID 246 toAddr := types.HexToAddress(tx.To) 247 sendArgs.SwapTx.From = types.HexToAddress(tx.From) 248 sendArgs.SwapTx.To = &toAddr 249 sendArgs.SwapTx.Value = (*hexutil.Big)(value) 250 sendArgs.SwapTx.Gas = (*hexutil.Uint64)(&gas) 251 sendArgs.SwapTx.GasPrice = (*hexutil.Big)(gasPrice) 252 sendArgs.SwapTx.Data = types.Hex2Bytes(tx.Data) 253 254 return nil 255 } 256 257 func (s *SwapParaswapProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { 258 err := s.prepareTransaction(sendArgs) 259 if err != nil { 260 return nil, 0, createSwapParaswapErrorResponse(err) 261 } 262 return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, sendArgs.SwapTx.SendTxArgs, lastUsedNonce) 263 } 264 265 func (s *SwapParaswapProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (types.Hash, uint64, error) { 266 err := s.prepareTransaction(sendArgs) 267 if err != nil { 268 return types.Hash{}, 0, createSwapParaswapErrorResponse(err) 269 } 270 271 return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, sendArgs.SwapTx.SendTxArgs, lastUsedNonce, verifiedAccount) 272 } 273 274 func (s *SwapParaswapProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { 275 key := makeKey(params.FromChain.ChainID, params.ToChain.ChainID, params.FromToken.Symbol, params.ToToken.Symbol) 276 priceRouteIns, ok := s.priceRoute.Load(key) 277 if !ok { 278 return nil, ErrPriceRouteNotFound 279 } 280 priceRoute := priceRouteIns.(*paraswap.Route) 281 282 _, partnerFeePcnt := getPartnerAddressAndFeePcnt(params.FromChain.ChainID) 283 destAmount, _ := calcReceivedAmountAndFee(priceRoute.DestAmount.Int, partnerFeePcnt) 284 285 return destAmount, nil 286 }