github.com/status-im/status-go@v1.1.0/services/wallet/router/pathprocessor/processor_bridge_celar.go (about) 1 package pathprocessor 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "math/big" 9 "net/url" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/ethereum/go-ethereum" 15 "github.com/ethereum/go-ethereum/accounts/abi" 16 "github.com/ethereum/go-ethereum/accounts/abi/bind" 17 "github.com/ethereum/go-ethereum/common" 18 "github.com/ethereum/go-ethereum/common/hexutil" 19 ethTypes "github.com/ethereum/go-ethereum/core/types" 20 "github.com/status-im/status-go/account" 21 "github.com/status-im/status-go/contracts/celer" 22 "github.com/status-im/status-go/eth-node/types" 23 "github.com/status-im/status-go/rpc" 24 25 "github.com/status-im/status-go/params" 26 "github.com/status-im/status-go/services/wallet/router/pathprocessor/cbridge" 27 "github.com/status-im/status-go/services/wallet/thirdparty" 28 "github.com/status-im/status-go/services/wallet/token" 29 "github.com/status-im/status-go/transactions" 30 ) 31 32 const ( 33 baseURL = "https://cbridge-prod2.celer.app" 34 testBaseURL = "https://cbridge-v2-test.celer.network" 35 36 maxSlippage = uint32(1000) 37 ) 38 39 type CelerBridgeTxArgs struct { 40 transactions.SendTxArgs 41 ChainID uint64 `json:"chainId"` 42 Symbol string `json:"symbol"` 43 Recipient common.Address `json:"recipient"` 44 Amount *hexutil.Big `json:"amount"` 45 } 46 47 type CelerBridgeProcessor struct { 48 rpcClient *rpc.Client 49 httpClient *thirdparty.HTTPClient 50 transactor transactions.TransactorIface 51 tokenManager *token.Manager 52 prodTransferConfig *cbridge.GetTransferConfigsResponse 53 testTransferConfig *cbridge.GetTransferConfigsResponse 54 } 55 56 func NewCelerBridgeProcessor(rpcClient *rpc.Client, transactor transactions.TransactorIface, tokenManager *token.Manager) *CelerBridgeProcessor { 57 return &CelerBridgeProcessor{ 58 rpcClient: rpcClient, 59 httpClient: thirdparty.NewHTTPClient(), 60 transactor: transactor, 61 tokenManager: tokenManager, 62 } 63 } 64 65 func createBridgeCellerErrorResponse(err error) error { 66 return createErrorResponse(ProcessorBridgeCelerName, err) 67 } 68 69 func (s *CelerBridgeProcessor) Name() string { 70 return ProcessorBridgeCelerName 71 } 72 73 func (s *CelerBridgeProcessor) estimateAmt(from, to *params.Network, amountIn *big.Int, symbol string) (*cbridge.EstimateAmtResponse, error) { 74 base := baseURL 75 if from.IsTest { 76 base = testBaseURL 77 } 78 79 params := url.Values{} 80 params.Add("src_chain_id", strconv.Itoa(int(from.ChainID))) 81 params.Add("dst_chain_id", strconv.Itoa(int(to.ChainID))) 82 params.Add("token_symbol", symbol) 83 params.Add("amt", amountIn.String()) 84 params.Add("usr_addr", "0xaa47c83316edc05cf9ff7136296b026c5de7eccd") 85 params.Add("slippage_tolerance", "500") 86 87 url := fmt.Sprintf("%s/v2/estimateAmt", base) 88 response, err := s.httpClient.DoGetRequest(context.Background(), url, params, nil) 89 if err != nil { 90 return nil, createBridgeCellerErrorResponse(err) 91 } 92 93 var res cbridge.EstimateAmtResponse 94 err = json.Unmarshal(response, &res) 95 if err != nil { 96 return nil, createBridgeCellerErrorResponse(err) 97 } 98 return &res, nil 99 } 100 101 func (s *CelerBridgeProcessor) getTransferConfig(isTest bool) (*cbridge.GetTransferConfigsResponse, error) { 102 if !isTest && s.prodTransferConfig != nil { 103 return s.prodTransferConfig, nil 104 } 105 106 if isTest && s.testTransferConfig != nil { 107 return s.testTransferConfig, nil 108 } 109 110 base := baseURL 111 if isTest { 112 base = testBaseURL 113 } 114 url := fmt.Sprintf("%s/v2/getTransferConfigs", base) 115 response, err := s.httpClient.DoGetRequest(context.Background(), url, nil, nil) 116 if err != nil { 117 return nil, createBridgeCellerErrorResponse(err) 118 } 119 120 var res cbridge.GetTransferConfigsResponse 121 err = json.Unmarshal(response, &res) 122 if err != nil { 123 return nil, createBridgeCellerErrorResponse(err) 124 } 125 if isTest { 126 s.testTransferConfig = &res 127 } else { 128 s.prodTransferConfig = &res 129 } 130 return &res, nil 131 } 132 133 func (s *CelerBridgeProcessor) AvailableFor(params ProcessorInputParams) (bool, error) { 134 if params.FromChain.ChainID == params.ToChain.ChainID || params.ToToken != nil { 135 return false, nil 136 } 137 138 transferConfig, err := s.getTransferConfig(params.FromChain.IsTest) 139 if err != nil { 140 return false, createBridgeCellerErrorResponse(err) 141 } 142 if transferConfig.Err != nil { 143 return false, createBridgeCellerErrorResponse(errors.New(transferConfig.Err.Msg)) 144 } 145 146 var fromAvailable *cbridge.Chain 147 var toAvailable *cbridge.Chain 148 for _, chain := range transferConfig.Chains { 149 if uint64(chain.GetId()) == params.FromChain.ChainID && chain.GasTokenSymbol == EthSymbol { 150 fromAvailable = chain 151 } 152 153 if uint64(chain.GetId()) == params.ToChain.ChainID && chain.GasTokenSymbol == EthSymbol { 154 toAvailable = chain 155 } 156 } 157 158 if fromAvailable == nil || toAvailable == nil { 159 return false, nil 160 } 161 162 found := false 163 if _, ok := transferConfig.ChainToken[fromAvailable.GetId()]; !ok { 164 return false, nil 165 } 166 167 for _, tokenInfo := range transferConfig.ChainToken[fromAvailable.GetId()].Token { 168 if tokenInfo.Token.Symbol == params.FromToken.Symbol { 169 found = true 170 break 171 } 172 } 173 if !found { 174 return false, nil 175 } 176 177 found = false 178 for _, tokenInfo := range transferConfig.ChainToken[toAvailable.GetId()].Token { 179 if tokenInfo.Token.Symbol == params.FromToken.Symbol { 180 found = true 181 break 182 } 183 } 184 185 if !found { 186 return false, nil 187 } 188 189 return true, nil 190 } 191 192 func (s *CelerBridgeProcessor) CalculateFees(params ProcessorInputParams) (*big.Int, *big.Int, error) { 193 amt, err := s.estimateAmt(params.FromChain, params.ToChain, params.AmountIn, params.FromToken.Symbol) 194 if err != nil { 195 return nil, nil, createBridgeCellerErrorResponse(err) 196 } 197 baseFee, ok := new(big.Int).SetString(amt.BaseFee, 10) 198 if !ok { 199 return nil, nil, ErrFailedToParseBaseFee 200 } 201 percFee, ok := new(big.Int).SetString(amt.PercFee, 10) 202 if !ok { 203 return nil, nil, ErrFailedToParsePercentageFee 204 } 205 206 return ZeroBigIntValue, new(big.Int).Add(baseFee, percFee), nil 207 } 208 209 func (c *CelerBridgeProcessor) PackTxInputData(params ProcessorInputParams) ([]byte, error) { 210 abi, err := abi.JSON(strings.NewReader(celer.CelerABI)) 211 if err != nil { 212 return []byte{}, createBridgeCellerErrorResponse(err) 213 } 214 215 if params.FromToken.IsNative() { 216 return abi.Pack("sendNative", 217 params.ToAddr, 218 params.AmountIn, 219 params.ToChain.ChainID, 220 uint64(time.Now().UnixMilli()), 221 maxSlippage, 222 ) 223 } else { 224 return abi.Pack("send", 225 params.ToAddr, 226 params.FromToken.Address, 227 params.AmountIn, 228 params.ToChain.ChainID, 229 uint64(time.Now().UnixMilli()), 230 maxSlippage, 231 ) 232 } 233 } 234 235 func (s *CelerBridgeProcessor) EstimateGas(params ProcessorInputParams) (uint64, error) { 236 if params.TestsMode { 237 if params.TestEstimationMap != nil { 238 if val, ok := params.TestEstimationMap[s.Name()]; ok { 239 return val.Value, val.Err 240 } 241 } 242 return 0, ErrNoEstimationFound 243 } 244 245 value := new(big.Int) 246 247 input, err := s.PackTxInputData(params) 248 if err != nil { 249 return 0, createBridgeCellerErrorResponse(err) 250 } 251 252 contractAddress, err := s.GetContractAddress(params) 253 if err != nil { 254 return 0, createBridgeCellerErrorResponse(err) 255 } 256 257 ethClient, err := s.rpcClient.EthClient(params.FromChain.ChainID) 258 if err != nil { 259 return 0, createBridgeCellerErrorResponse(err) 260 } 261 262 ctx := context.Background() 263 264 msg := ethereum.CallMsg{ 265 From: params.FromAddr, 266 To: &contractAddress, 267 Value: value, 268 Data: input, 269 } 270 271 estimation, err := ethClient.EstimateGas(ctx, msg) 272 if err != nil { 273 if !params.FromToken.IsNative() { 274 // TODO: this is a temporary solution until we find a better way to estimate the gas 275 // hardcoding the estimation for other than ETH, cause we cannot get a proper estimation without having an approval placed first 276 // this is an error we're facing otherwise: `execution reverted: ERC20: transfer amount exceeds allowance` 277 estimation = 350000 278 } else { 279 return 0, createBridgeCellerErrorResponse(err) 280 } 281 } 282 increasedEstimation := float64(estimation) * IncreaseEstimatedGasFactor 283 return uint64(increasedEstimation), nil 284 } 285 286 func (s *CelerBridgeProcessor) GetContractAddress(params ProcessorInputParams) (common.Address, error) { 287 transferConfig, err := s.getTransferConfig(params.FromChain.IsTest) 288 if err != nil { 289 return common.Address{}, createBridgeCellerErrorResponse(err) 290 } 291 if transferConfig.Err != nil { 292 return common.Address{}, createBridgeCellerErrorResponse(errors.New(transferConfig.Err.Msg)) 293 } 294 295 for _, chain := range transferConfig.Chains { 296 if uint64(chain.Id) == params.FromChain.ChainID { 297 return common.HexToAddress(chain.ContractAddr), nil 298 } 299 } 300 301 return common.Address{}, ErrContractNotFound 302 } 303 304 func (s *CelerBridgeProcessor) sendOrBuild(sendArgs *MultipathProcessorTxArgs, signerFn bind.SignerFn, lastUsedNonce int64) (*ethTypes.Transaction, error) { 305 fromChain := s.rpcClient.NetworkManager.Find(sendArgs.ChainID) 306 if fromChain == nil { 307 return nil, ErrNetworkNotFound 308 } 309 token := s.tokenManager.FindToken(fromChain, sendArgs.CbridgeTx.Symbol) 310 if token == nil { 311 return nil, ErrTokenNotFound 312 } 313 addrs, err := s.GetContractAddress(ProcessorInputParams{ 314 FromChain: fromChain, 315 }) 316 if err != nil { 317 return nil, createBridgeCellerErrorResponse(err) 318 } 319 320 backend, err := s.rpcClient.EthClient(sendArgs.ChainID) 321 if err != nil { 322 return nil, createBridgeCellerErrorResponse(err) 323 } 324 contract, err := celer.NewCeler(addrs, backend) 325 if err != nil { 326 return nil, createBridgeCellerErrorResponse(err) 327 } 328 329 if lastUsedNonce >= 0 { 330 lastUsedNonceHexUtil := hexutil.Uint64(uint64(lastUsedNonce) + 1) 331 sendArgs.CbridgeTx.Nonce = &lastUsedNonceHexUtil 332 } 333 334 var tx *ethTypes.Transaction 335 txOpts := sendArgs.CbridgeTx.ToTransactOpts(signerFn) 336 if token.IsNative() { 337 tx, err = contract.SendNative( 338 txOpts, 339 sendArgs.CbridgeTx.Recipient, 340 (*big.Int)(sendArgs.CbridgeTx.Amount), 341 sendArgs.CbridgeTx.ChainID, 342 uint64(time.Now().UnixMilli()), 343 maxSlippage, 344 ) 345 } else { 346 tx, err = contract.Send( 347 txOpts, 348 sendArgs.CbridgeTx.Recipient, 349 token.Address, 350 (*big.Int)(sendArgs.CbridgeTx.Amount), 351 sendArgs.CbridgeTx.ChainID, 352 uint64(time.Now().UnixMilli()), 353 maxSlippage, 354 ) 355 } 356 if err != nil { 357 return tx, createBridgeCellerErrorResponse(err) 358 } 359 err = s.transactor.StoreAndTrackPendingTx(txOpts.From, sendArgs.CbridgeTx.Symbol, sendArgs.ChainID, sendArgs.CbridgeTx.MultiTransactionID, tx) 360 if err != nil { 361 return tx, createBridgeCellerErrorResponse(err) 362 } 363 return tx, nil 364 } 365 366 func (s *CelerBridgeProcessor) Send(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64, verifiedAccount *account.SelectedExtKey) (types.Hash, uint64, error) { 367 tx, err := s.sendOrBuild(sendArgs, getSigner(sendArgs.ChainID, sendArgs.CbridgeTx.From, verifiedAccount), lastUsedNonce) 368 if err != nil { 369 return types.HexToHash(""), 0, createBridgeCellerErrorResponse(err) 370 } 371 372 return types.Hash(tx.Hash()), tx.Nonce(), nil 373 } 374 375 func (s *CelerBridgeProcessor) BuildTransaction(sendArgs *MultipathProcessorTxArgs, lastUsedNonce int64) (*ethTypes.Transaction, uint64, error) { 376 tx, err := s.sendOrBuild(sendArgs, nil, lastUsedNonce) 377 return tx, tx.Nonce(), err 378 } 379 380 func (s *CelerBridgeProcessor) CalculateAmountOut(params ProcessorInputParams) (*big.Int, error) { 381 amt, err := s.estimateAmt(params.FromChain, params.ToChain, params.AmountIn, params.FromToken.Symbol) 382 if err != nil { 383 return nil, createBridgeCellerErrorResponse(err) 384 } 385 if amt.Err != nil { 386 return nil, createBridgeCellerErrorResponse(err) 387 } 388 amountOut, _ := new(big.Int).SetString(amt.EqValueTokenAmt, 10) 389 return amountOut, nil 390 }