From 44a7c85eea05caec9e2c25db56243d13f7f2c1b6 Mon Sep 17 00:00:00 2001 From: Anirudha Bose Date: Thu, 17 Oct 2024 16:58:31 +0530 Subject: [PATCH 1/8] feat(wallet): migrate 0x Swap API to v2 --- .../browser/brave_wallet_constants.cc | 26 + .../browser/brave_wallet_constants.h | 34 +- .../browser/swap_response_parser.cc | 333 ++++++---- .../browser/swap_response_parser.h | 6 +- .../browser/swap_response_parser_unittest.cc | 568 ++++++++-------- .../brave_wallet/browser/swap_responses.idl | 82 ++- .../brave_wallet/browser/swap_service.cc | 152 ++--- .../brave_wallet/browser/swap_service.h | 3 +- .../browser/swap_service_unittest.cc | 612 +++++++----------- .../brave_wallet/common/brave_wallet.mojom | 81 ++- .../page/screens/swap/hooks/useSwap.ts | 23 +- .../page/screens/swap/hooks/useZeroEx.ts | 4 +- .../page/screens/swap/swap.utils.ts | 30 +- 13 files changed, 903 insertions(+), 1051 deletions(-) diff --git a/components/brave_wallet/browser/brave_wallet_constants.cc b/components/brave_wallet/browser/brave_wallet_constants.cc index 87ca34ed1b6b..f7ff635cbb52 100644 --- a/components/brave_wallet/browser/brave_wallet_constants.cc +++ b/components/brave_wallet/browser/brave_wallet_constants.cc @@ -99,4 +99,30 @@ const base::flat_map& GetAnkrBlockchains() { return *blockchains; } + +// See https://0x.org/docs/introduction/0x-cheat-sheet#allowanceholder-address +const std::string GetZeroExAllowanceHolderAddress(const std::string& chain_id) { + // key = chain_id, value = allowance_holder_contract_address + static std::map allowance_holder_addresses = { + {mojom::kMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kArbitrumMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kAvalancheMainnetChainId, kZeroExAllowanceHolderShanghai}, + {mojom::kBaseMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kBlastMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kBnbSmartChainMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kLineaChainId, kZeroExAllowanceHolderLondon}, + {mojom::kOptimismMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kPolygonMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kScrollChainId, kZeroExAllowanceHolderShanghai}}; + + auto allowance_holder_address_pair = + allowance_holder_addresses.find(chain_id.c_str()); + + if (allowance_holder_address_pair == allowance_holder_addresses.end()) { + // not found + return ""; + } else { + return allowance_holder_address_pair->second; + } +} } // namespace brave_wallet diff --git a/components/brave_wallet/browser/brave_wallet_constants.h b/components/brave_wallet/browser/brave_wallet_constants.h index e7f96302b23f..092b87517eb0 100644 --- a/components/brave_wallet/browser/brave_wallet_constants.h +++ b/components/brave_wallet/browser/brave_wallet_constants.h @@ -1609,30 +1609,17 @@ inline constexpr webui::LocalizedString kLocalizedStrings[] = { {"braveWalletDetails", IDS_BRAVE_WALLET_DETAILS}}; // 0x swap constants -inline constexpr char kZeroExSepoliaBaseAPIURL[] = - "https://sepolia.api.0x.wallet.brave.com"; -inline constexpr char kZeroExPolygonBaseAPIURL[] = - "https://polygon.api.0x.wallet.brave.com"; -inline constexpr char kZeroExBinanceSmartChainBaseAPIURL[] = - "https://bsc.api.0x.wallet.brave.com"; -inline constexpr char kZeroExAvalancheBaseAPIURL[] = - "https://avalanche.api.0x.wallet.brave.com"; -inline constexpr char kZeroExFantomBaseAPIURL[] = - "https://fantom.api.0x.wallet.brave.com"; -inline constexpr char kZeroExCeloBaseAPIURL[] = - "https://celo.api.0x.wallet.brave.com"; -inline constexpr char kZeroExOptimismBaseAPIURL[] = - "https://optimism.api.0x.wallet.brave.com"; -inline constexpr char kZeroExArbitrumBaseAPIURL[] = - "https://arbitrum.api.0x.wallet.brave.com"; -inline constexpr char kZeroExBaseBaseAPIURL[] = - "https://base.api.0x.wallet.brave.com"; -inline constexpr char kZeroExEthereumBaseAPIURL[] = - "https://api.0x.wallet.brave.com"; +inline constexpr char kZeroExBaseAPIURL[] = "https://api.0x.wallet.brave.com"; inline constexpr char kEVMFeeRecipient[] = "0xbd9420A98a7Bd6B89765e5715e169481602D9c3d"; -inline constexpr char kAffiliateAddress[] = - "0xbd9420A98a7Bd6B89765e5715e169481602D9c3d"; +inline constexpr char kZeroExAllowanceHolderCancun[] = + "0x0000000000001fF3684f28c67538d4D072C22734"; +inline constexpr char kZeroExAllowanceHolderShanghai[] = + "0x0000000000005E88410CcDFaDe4a5EfaE4b49562"; +inline constexpr char kZeroExAllowanceHolderLondon[] = + "0x000000000000175a8b9bC6d539B3708EEd92EA6c"; +inline constexpr char kZeroExAPIVersionHeader[] = "0x-version"; +inline constexpr char kZeroExAPIVersion[] = "v2"; // Jupiter swap constants inline constexpr char kJupiterBaseAPIURL[] = "https://jupiter.wallet.brave.com"; @@ -1712,6 +1699,9 @@ const std::string GetAssetRatioBaseURL(); const base::flat_map& GetAnkrBlockchains(); // https://docs.rs/solana-program/1.18.10/src/solana_program/clock.rs.html#129-131 inline constexpr int kSolanaValidBlockHeightThreshold = 150; + +const std::string GetZeroExAllowanceHolderAddress(const std::string& chain_id); + } // namespace brave_wallet #endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_BRAVE_WALLET_CONSTANTS_H_ diff --git a/components/brave_wallet/browser/swap_response_parser.cc b/components/brave_wallet/browser/swap_response_parser.cc index b221a2c7e101..c63dedfde777 100644 --- a/components/brave_wallet/browser/swap_response_parser.cc +++ b/components/brave_wallet/browser/swap_response_parser.cc @@ -27,10 +27,6 @@ namespace brave_wallet { namespace zeroex { namespace { -constexpr char kSwapValidationErrorCode[] = "100"; -constexpr char kInsufficientAssetLiquidity[] = "INSUFFICIENT_ASSET_LIQUIDITY"; -constexpr char kTransferAmountExceedsAllowanceMessage[] = - "ERC20: transfer amount exceeds allowance"; mojom::ZeroExFeePtr ParseZeroExFee(const base::Value& value) { if (value.is_none()) { @@ -48,146 +44,220 @@ mojom::ZeroExFeePtr ParseZeroExFee(const base::Value& value) { } auto zero_ex_fee = mojom::ZeroExFee::New(); - zero_ex_fee->fee_type = zero_ex_fee_value->fee_type; - zero_ex_fee->fee_token = zero_ex_fee_value->fee_token; - zero_ex_fee->fee_amount = zero_ex_fee_value->fee_amount; - zero_ex_fee->billing_type = zero_ex_fee_value->billing_type; + zero_ex_fee->token = zero_ex_fee_value->token; + zero_ex_fee->amount = zero_ex_fee_value->amount; + zero_ex_fee->type = zero_ex_fee_value->type; return zero_ex_fee; } +mojom::ZeroExRoutePtr ParseRoute(const swap_responses::ZeroExRoute& value) { + auto route = mojom::ZeroExRoute::New(); + for (const auto& fill_value : value.fills) { + auto fill = mojom::ZeroExRouteFill::New(); + fill->from = fill_value.from; + fill->to = fill_value.to; + fill->source = fill_value.source; + fill->proportion_bps = fill_value.proportion_bps; + route->fills.push_back(std::move(fill)); + } + + return route; +} + +mojom::ZeroExQuotePtr ParseQuote( + const swap_responses::ZeroExQuoteResponse& value) { + auto quote = mojom::ZeroExQuote::New(); + + if (value.buy_amount.has_value()) { + quote->buy_amount = value.buy_amount.value(); + } else { + return nullptr; + } + + if (value.buy_token.has_value()) { + quote->buy_token = value.buy_token.value(); + } else { + return nullptr; + } + + if (value.gas.has_value()) { + quote->gas = value.gas.value(); + } else { + return nullptr; + } + + if (value.gas_price.has_value()) { + quote->gas_price = value.gas_price.value(); + } else { + return nullptr; + } + + quote->liquidity_available = value.liquidity_available; + + if (value.min_buy_amount.has_value()) { + quote->min_buy_amount = value.min_buy_amount.value(); + } else { + return nullptr; + } + + if (value.sell_amount.has_value()) { + quote->sell_amount = value.sell_amount.value(); + } else { + return nullptr; + } + + if (value.sell_token.has_value()) { + quote->sell_token = value.sell_token.value(); + } else { + return nullptr; + } + + if (value.total_network_fee.has_value()) { + quote->total_network_fee = value.total_network_fee.value(); + } else { + return nullptr; + } + + if (value.route.has_value()) { + quote->route = ParseRoute(value.route.value()); + } else { + return nullptr; + } + + if (value.fees.has_value()) { + auto fees = mojom::ZeroExFees::New(); + if (auto zero_ex_fee = ParseZeroExFee(value.fees.value().zero_ex_fee); + zero_ex_fee) { + fees->zero_ex_fee = std::move(zero_ex_fee); + } + quote->fees = std::move(fees); + } else { + return nullptr; + } + + return quote; +} + } // namespace -mojom::ZeroExQuotePtr ParseQuoteResponse(const base::Value& json_value, - bool expect_transaction_data) { +mojom::ZeroExQuoteInfoPtr ParseQuoteResponse(const base::Value& json_value, + const std::string& chain_id) { // { - // "price":"1916.27547998814058355", - // "guaranteedPrice":"1935.438234788021989386", - // "to":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - // "data":"...", - // "value":"0", - // "gas":"719000", - // "estimatedGas":"719000", - // "gasPrice":"26000000000", - // "protocolFee":"0", - // "minimumProtocolFee":"0", - // "buyTokenAddress":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - // "sellTokenAddress":"0x6b175474e89094c44da98b954eedeac495271d0f", - // "buyAmount":"1000000000000000000000", - // "sellAmount":"1916275479988140583549706", - // "sources":[...], - // "allowanceTarget":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - // "sellTokenToEthRate":"1900.44962824532464391", - // "buyTokenToEthRate":"1", - // "estimatedPriceImpact": "0.7232", - // "sources": [ - // { - // "name": "0x", - // "proportion": "0", + // "blockNumber": "20114692", + // "buyAmount": "100037537", + // "buyToken": "0xdac17f958d2ee523a2206206994597c13d831ec7", + // "fees": { + // "integratorFee": null, + // "zeroExFee": null, + // "gasFee": null + // }, + // "issues": { + // "allowance": { + // "actual": "0", + // "spender": "0x0000000000001ff3684f28c67538d4d072c22734" // }, - // { - // "name": "Uniswap_V2", - // "proportion": "1", + // "balance": { + // "token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + // "actual": "0", + // "expected": "100000000" // }, - // { - // "name": "Curve", - // "proportion": "0", - // } - // ], - // "fees": { - // "zeroExFee": { - // "feeType": "volume", - // "feeToken": "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063", - // "feeAmount": "148470027512868522", - // "billingType": "on-chain" + // "simulationIncomplete": false, + // "invalidSourcesPassed": [] + // }, + // "liquidityAvailable": true, + // "minBuyAmount": "99037162", + // "route": { + // "fills": [ + // { + // "from": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + // "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + // "source": "SolidlyV3", + // "proportionBps": "10000" + // } + // ], + // "tokens": [ + // { + // "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + // "symbol": "USDC" + // }, + // { + // "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", + // "symbol": "USDT" + // } + // ] + // }, + // "sellAmount": "100000000", + // "sellToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + // "tokenMetadata": { + // "buyToken": { + // "buyTaxBps": "0", + // "sellTaxBps": "0" + // }, + // "sellToken": { + // "buyTaxBps": "0", + // "sellTaxBps": "0" // } - // } + // }, + // "totalNetworkFee": "1393685870940000", + // "transaction": { + // "to": "0x7f6cee965959295cc64d0e6c00d99d6532d8e86b", + // "data": + // "0x1fff991f00000000000000000000000070a9f34f9b34c64957b9c401a97bfed35b95049e000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000005e72fea00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000144c1fb425e0000000000000000000000007f6cee965959295cc64d0e6c00d99d6532d8e86b000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000006e898131631616b1779bad70bc17000000000000000000000000000000000000000000000000000000006670d06c00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000041ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016438c9c147000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000027100000000000000000000000006146be494fee4c73540cb1c5f87536abf1452500000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000084c31b8d7a0000000000000000000000007f6cee965959295cc64d0e6c00d99d6532d8e86b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000001000276a40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + // "gas": "288079", + // "gasPrice": "4837860000", + // "value": "0" + // }, + // "zid": "0x111111111111111111111111" // } auto swap_response_value = - swap_responses::SwapResponse0x::FromValue(json_value); + swap_responses::ZeroExQuoteResponse::FromValue(json_value); if (!swap_response_value) { return nullptr; } - auto swap_response = mojom::ZeroExQuote::New(); - swap_response->price = swap_response_value->price; + auto swap_response = mojom::ZeroExQuoteInfo::New(); + swap_response->allowance_target = GetZeroExAllowanceHolderAddress(chain_id); + if (!swap_response_value->liquidity_available) { + swap_response->liquidity_available = false; + return swap_response; + } - if (expect_transaction_data) { - if (!swap_response_value->guaranteed_price) { - return nullptr; - } - swap_response->guaranteed_price = *swap_response_value->guaranteed_price; + if (auto quote = ParseQuote(swap_response_value.value()); quote) { + swap_response->quote = std::move(quote); + } else { + return nullptr; + } - if (!swap_response_value->to) { - return nullptr; - } - swap_response->to = *swap_response_value->to; - - if (!swap_response_value->data) { - return nullptr; - } - swap_response->data = *swap_response_value->data; - } - - swap_response->value = swap_response_value->value; - swap_response->gas = swap_response_value->gas; - swap_response->estimated_gas = swap_response_value->estimated_gas; - swap_response->gas_price = swap_response_value->gas_price; - swap_response->protocol_fee = swap_response_value->protocol_fee; - swap_response->minimum_protocol_fee = - swap_response_value->minimum_protocol_fee; - swap_response->buy_token_address = swap_response_value->buy_token_address; - swap_response->sell_token_address = swap_response_value->sell_token_address; - swap_response->buy_amount = swap_response_value->buy_amount; - swap_response->sell_amount = swap_response_value->sell_amount; - swap_response->allowance_target = swap_response_value->allowance_target; - swap_response->sell_token_to_eth_rate = - swap_response_value->sell_token_to_eth_rate; - swap_response->buy_token_to_eth_rate = - swap_response_value->buy_token_to_eth_rate; - swap_response->estimated_price_impact = - swap_response_value->estimated_price_impact; - - for (const auto& source_value : swap_response_value->sources) { - swap_response->sources.push_back( - mojom::ZeroExSource::New(source_value.name, source_value.proportion)); - } - - auto fees = mojom::ZeroExFees::New(); - if (auto zero_ex_fee = ParseZeroExFee(swap_response_value->fees.zero_ex_fee); - zero_ex_fee) { - fees->zero_ex_fee = std::move(zero_ex_fee); - } - swap_response->fees = std::move(fees); + swap_response->liquidity_available = swap_response_value->liquidity_available; return swap_response; } +mojom::ZeroExTransactionPtr ParseTransactionResponse( + const base::Value& json_value) { + auto swap_response_value = + swap_responses::ZeroExTransactionResponse::FromValue(json_value); + if (!swap_response_value) { + return nullptr; + } + + auto transaction = mojom::ZeroExTransaction::New(); + transaction->to = swap_response_value->transaction.to; + transaction->data = swap_response_value->transaction.data; + transaction->gas = swap_response_value->transaction.gas; + transaction->gas_price = swap_response_value->transaction.gas_price; + transaction->value = swap_response_value->transaction.value; + + return transaction; +} + mojom::ZeroExErrorPtr ParseErrorResponse(const base::Value& json_value) { - // https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/relayer_api_error_response_schema.json - // // { - // "code": "100", - // "reason": "Validation Failed", - // "validationErrors": [{ - // "field": "sellAmount", - // "code": "1001", - // "reason": "should match pattern \"^\\d+$\"" - // }, - // { - // "field": "sellAmount", - // "code": "1001", - // "reason": "should be integer" - // }, - // { - // "field": "sellAmount", - // "code": "1001", - // "reason": "should match some schema in anyOf" - // } - // ] + // "code": "SWAP_VALIDATION_FAILED", + // "message": "Validation Failed" // } - auto swap_error_response_value = swap_responses::ZeroExErrorResponse::FromValue(json_value); if (!swap_error_response_value) { @@ -195,31 +265,8 @@ mojom::ZeroExErrorPtr ParseErrorResponse(const base::Value& json_value) { } auto result = mojom::ZeroExError::New(); - result->code = swap_error_response_value->code; - result->reason = swap_error_response_value->reason; - - if (swap_error_response_value->validation_errors) { - for (auto& error_item : *swap_error_response_value->validation_errors) { - result->validation_errors.emplace_back(mojom::ZeroExValidationError::New( - error_item.field, error_item.code, error_item.reason)); - } - } - result->is_insufficient_liquidity = false; - if (result->code == kSwapValidationErrorCode) { - for (auto& item : result->validation_errors) { - if (item->reason == kInsufficientAssetLiquidity) { - result->is_insufficient_liquidity = true; - } - } - } - - // This covers the case when an insufficient allowance can only be detected - // by the 0x Quote API, for example when swapping in ExactOut mode. - if (swap_error_response_value->values && - base::Contains(swap_error_response_value->values->message, - kTransferAmountExceedsAllowanceMessage)) { - result->is_insufficient_allowance = true; - } + result->name = swap_error_response_value->name; + result->message = swap_error_response_value->message; return result; } diff --git a/components/brave_wallet/browser/swap_response_parser.h b/components/brave_wallet/browser/swap_response_parser.h index a8a8d8d381a6..36140791b4d1 100644 --- a/components/brave_wallet/browser/swap_response_parser.h +++ b/components/brave_wallet/browser/swap_response_parser.h @@ -15,8 +15,10 @@ namespace brave_wallet { namespace zeroex { -mojom::ZeroExQuotePtr ParseQuoteResponse(const base::Value& json_value, - bool expect_transaction_data); +mojom::ZeroExQuoteInfoPtr ParseQuoteResponse(const base::Value& json_value, + const std::string& chain_id); +mojom::ZeroExTransactionPtr ParseTransactionResponse( + const base::Value& json_value); mojom::ZeroExErrorPtr ParseErrorResponse(const base::Value& json_value); } // namespace zeroex diff --git a/components/brave_wallet/browser/swap_response_parser_unittest.cc b/components/brave_wallet/browser/swap_response_parser_unittest.cc index 2b103cf70866..53ac73fdd90b 100644 --- a/components/brave_wallet/browser/swap_response_parser_unittest.cc +++ b/components/brave_wallet/browser/swap_response_parser_unittest.cc @@ -88,294 +88,302 @@ TEST(SwapResponseParserUnitTest, ParseZeroExQuoteResponse) { // Case 1: non-null zeroExFee std::string json(R"( { - "price":"1916.27547998814058355", - "guaranteedPrice":"1935.438234788021989386", - "to":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "data":"0x0", - "value":"0", - "gas":"719000", - "estimatedGas":"719001", - "gasPrice":"26000000000", - "protocolFee":"0", - "minimumProtocolFee":"0", - "buyTokenAddress":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "sellTokenAddress":"0x6b175474e89094c44da98b954eedeac495271d0f", - "buyAmount":"1000000000000000000000", - "sellAmount":"1916275479988140583549706", - "sources":[], - "allowanceTarget":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "sellTokenToEthRate":"1900.44962824532464391", - "buyTokenToEthRate":"1", - "estimatedPriceImpact": "0.7232", - "sources": [ - { - "name": "Uniswap_V2", - "proportion": "1" - } - ], + "blockNumber": "20114676", + "buyAmount": "100032748", + "buyToken": "0xdac17f958d2ee523a2206206994597c13d831ec7", "fees": { + "integratorFee": null, "zeroExFee": { - "feeType": "volume", - "feeToken": "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063", - "feeAmount": "148470027512868522", - "billingType": "on-chain" + "amount": "0", + "token": "0xdeadbeef", + "type": "volume" + }, + "gasFee": null + }, + "gas": "288095", + "gasPrice": "7062490000", + "issues": { + "allowance": { + "actual": "0", + "spender": "0x0000000000001ff3684f28c67538d4d072c22734" + }, + "balance": { + "token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "actual": "0", + "expected": "100000000" + }, + "simulationIncomplete": false, + "invalidSourcesPassed": [] + }, + "liquidityAvailable": true, + "minBuyAmount": "99032421", + "route": { + "fills": [ + { + "from": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "source": "SolidlyV3", + "proportionBps": "10000" + } + ], + "tokens": [ + { + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "symbol": "USDC" + }, + { + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "symbol": "USDT" + } + ] + }, + "sellAmount": "100000000", + "sellToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "tokenMetadata": { + "buyToken": { + "buyTaxBps": "0", + "sellTaxBps": "0" + }, + "sellToken": { + "buyTaxBps": "0", + "sellTaxBps": "0" } - } + }, + "totalNetworkFee": "2034668056550000", + "zid": "0x111111111111111111111111" } )"); - mojom::ZeroExQuotePtr quote = - zeroex::ParseQuoteResponse(ParseJson(json), false); - ASSERT_TRUE(quote); - - EXPECT_EQ(quote->price, "1916.27547998814058355"); - EXPECT_TRUE(quote->guaranteed_price.empty()); - EXPECT_TRUE(quote->to.empty()); - EXPECT_TRUE(quote->data.empty()); - - EXPECT_EQ(quote->value, "0"); - EXPECT_EQ(quote->gas, "719000"); - EXPECT_EQ(quote->estimated_gas, "719001"); - EXPECT_EQ(quote->gas_price, "26000000000"); - EXPECT_EQ(quote->protocol_fee, "0"); - EXPECT_EQ(quote->minimum_protocol_fee, "0"); - EXPECT_EQ(quote->buy_token_address, - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); - EXPECT_EQ(quote->sell_token_address, - "0x6b175474e89094c44da98b954eedeac495271d0f"); - EXPECT_EQ(quote->buy_amount, "1000000000000000000000"); - EXPECT_EQ(quote->sell_amount, "1916275479988140583549706"); - EXPECT_EQ(quote->allowance_target, - "0xdef1c0ded9bec7f1a1670819833240f027b25eff"); - EXPECT_EQ(quote->sell_token_to_eth_rate, "1900.44962824532464391"); - EXPECT_EQ(quote->buy_token_to_eth_rate, "1"); - EXPECT_EQ(quote->estimated_price_impact, "0.7232"); - EXPECT_EQ(quote->sources.size(), 1UL); - EXPECT_EQ(quote->sources.at(0)->name, "Uniswap_V2"); - EXPECT_EQ(quote->sources.at(0)->proportion, "1"); - ASSERT_TRUE(quote->fees->zero_ex_fee); - EXPECT_EQ(quote->fees->zero_ex_fee->fee_type, "volume"); - EXPECT_EQ(quote->fees->zero_ex_fee->fee_token, - "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063"); - EXPECT_EQ(quote->fees->zero_ex_fee->fee_amount, "148470027512868522"); - EXPECT_EQ(quote->fees->zero_ex_fee->billing_type, "on-chain"); + auto quote_info = + zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId); + ASSERT_TRUE(quote_info); + ASSERT_TRUE(quote_info->quote); + + EXPECT_EQ(quote_info->quote->buy_amount, "100032748"); + EXPECT_EQ(quote_info->quote->buy_token, + "0xdac17f958d2ee523a2206206994597c13d831ec7"); + + ASSERT_TRUE(quote_info->quote->fees->zero_ex_fee); + EXPECT_EQ(quote_info->quote->fees->zero_ex_fee->amount, "0"); + EXPECT_EQ(quote_info->quote->fees->zero_ex_fee->token, "0xdeadbeef"); + EXPECT_EQ(quote_info->quote->fees->zero_ex_fee->type, "volume"); + + EXPECT_EQ(quote_info->quote->gas, "288095"); + EXPECT_EQ(quote_info->quote->gas_price, "7062490000"); + EXPECT_EQ(quote_info->quote->liquidity_available, true); + EXPECT_EQ(quote_info->quote->min_buy_amount, "99032421"); + + ASSERT_EQ(quote_info->quote->route->fills.size(), 1UL); + EXPECT_EQ(quote_info->quote->route->fills.at(0)->from, + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); + EXPECT_EQ(quote_info->quote->route->fills.at(0)->to, + "0xdac17f958d2ee523a2206206994597c13d831ec7"); + EXPECT_EQ(quote_info->quote->route->fills.at(0)->source, "SolidlyV3"); + EXPECT_EQ(quote_info->quote->route->fills.at(0)->proportion_bps, "10000"); + + EXPECT_EQ(quote_info->quote->sell_amount, "100000000"); + EXPECT_EQ(quote_info->quote->sell_token, + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); + EXPECT_EQ(quote_info->quote->total_network_fee, "2034668056550000"); + + EXPECT_EQ(quote_info->liquidity_available, true); + EXPECT_EQ(quote_info->allowance_target, + "0x0000000000001fF3684f28c67538d4D072C22734"); // Case 2: null zeroExFee json = R"( { - "price":"1916.27547998814058355", - "guaranteedPrice":"1935.438234788021989386", - "to":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "data":"0x0", - "value":"0", - "gas":"719000", - "estimatedGas":"719001", - "gasPrice":"26000000000", - "protocolFee":"0", - "minimumProtocolFee":"0", - "buyTokenAddress":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "sellTokenAddress":"0x6b175474e89094c44da98b954eedeac495271d0f", - "buyAmount":"1000000000000000000000", - "sellAmount":"1916275479988140583549706", - "sources":[], - "allowanceTarget":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "sellTokenToEthRate":"1900.44962824532464391", - "buyTokenToEthRate":"1", - "estimatedPriceImpact": "0.7232", - "sources":[], + "blockNumber": "20114676", + "buyAmount": "100032748", + "buyToken": "0xdac17f958d2ee523a2206206994597c13d831ec7", "fees": { - "zeroExFee": null - } + "integratorFee": null, + "zeroExFee": null, + "gasFee": null + }, + "gas": "288095", + "gasPrice": "7062490000", + "issues": { + "allowance": { + "actual": "0", + "spender": "0x0000000000001ff3684f28c67538d4d072c22734" + }, + "balance": { + "token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "actual": "0", + "expected": "100000000" + }, + "simulationIncomplete": false, + "invalidSourcesPassed": [] + }, + "liquidityAvailable": true, + "minBuyAmount": "99032421", + "route": { + "fills": [ + { + "from": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "source": "SolidlyV3", + "proportionBps": "10000" + } + ], + "tokens": [ + { + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "symbol": "USDC" + }, + { + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "symbol": "USDT" + } + ] + }, + "sellAmount": "100000000", + "sellToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "tokenMetadata": { + "buyToken": { + "buyTaxBps": "0", + "sellTaxBps": "0" + }, + "sellToken": { + "buyTaxBps": "0", + "sellTaxBps": "0" + } + }, + "totalNetworkFee": "2034668056550000", + "zid": "0x111111111111111111111111" } )"; - quote = zeroex::ParseQuoteResponse(ParseJson(json), false); - ASSERT_TRUE(quote); - EXPECT_FALSE(quote->fees->zero_ex_fee); + quote_info = + zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId); + ASSERT_TRUE(quote_info); + EXPECT_FALSE(quote_info->quote->fees->zero_ex_fee); + EXPECT_EQ(quote_info->liquidity_available, true); + EXPECT_EQ(quote_info->allowance_target, + "0x0000000000001fF3684f28c67538d4D072C22734"); // Case 3: malformed fees field json = R"( { - "price":"1916.27547998814058355", - "guaranteedPrice":"1935.438234788021989386", - "to":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "data":"0x0", - "value":"0", - "gas":"719000", - "estimatedGas":"719001", - "gasPrice":"26000000000", - "protocolFee":"0", - "minimumProtocolFee":"0", - "buyTokenAddress":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "sellTokenAddress":"0x6b175474e89094c44da98b954eedeac495271d0f", - "buyAmount":"1000000000000000000000", - "sellAmount":"1916275479988140583549706", - "sources":[], - "allowanceTarget":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "sellTokenToEthRate":"1900.44962824532464391", - "buyTokenToEthRate":"1", - "estimatedPriceImpact": "0.7232", - "sources":[], - "fees": null + "blockNumber": "20114676", + "buyAmount": "100032748", + "buyToken": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "fees": null, + "gas": "288095", + "gasPrice": "7062490000", + "issues": { + "allowance": { + "actual": "0", + "spender": "0x0000000000001ff3684f28c67538d4d072c22734" + }, + "balance": { + "token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "actual": "0", + "expected": "100000000" + }, + "simulationIncomplete": false, + "invalidSourcesPassed": [] + }, + "liquidityAvailable": true, + "minBuyAmount": "99032421", + "route": { + "fills": [ + { + "from": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "source": "SolidlyV3", + "proportionBps": "10000" + } + ], + "tokens": [ + { + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "symbol": "USDC" + }, + { + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "symbol": "USDT" + } + ] + }, + "sellAmount": "100000000", + "sellToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "tokenMetadata": { + "buyToken": { + "buyTaxBps": "0", + "sellTaxBps": "0" + }, + "sellToken": { + "buyTaxBps": "0", + "sellTaxBps": "0" + } + }, + "totalNetworkFee": "2034668056550000", + "zid": "0x111111111111111111111111" } )"; - EXPECT_FALSE(zeroex::ParseQuoteResponse(ParseJson(json), false)); + EXPECT_FALSE( + zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId)); - // Case 4: other invalid cases - json = R"({"price": "3"})"; - EXPECT_FALSE(zeroex::ParseQuoteResponse(ParseJson(json), false)); - json = R"({"price": 3})"; - EXPECT_FALSE(zeroex::ParseQuoteResponse(ParseJson(json), false)); + // Case 4: insufficient liquidity + json = R"( + { + "liquidityAvailable": false, + } + )"; + quote_info = + zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId); + ASSERT_TRUE(quote_info); + EXPECT_FALSE(quote_info->liquidity_available); + EXPECT_EQ(quote_info->allowance_target, + "0x0000000000001fF3684f28c67538d4D072C22734"); + EXPECT_FALSE(quote_info->quote); + + // Case 5: other invalid cases + json = R"({"totalNetworkFee": "2034668056550000"})"; + EXPECT_FALSE( + zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId)); + json = R"({"totalNetworkFee": 2034668056550000})"; + EXPECT_FALSE( + zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId)); json = "3"; - EXPECT_FALSE(zeroex::ParseQuoteResponse(ParseJson(json), false)); + EXPECT_FALSE( + zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId)); json = "[3]"; - EXPECT_FALSE(zeroex::ParseQuoteResponse(ParseJson(json), false)); - EXPECT_FALSE(zeroex::ParseQuoteResponse(base::Value(), false)); + EXPECT_FALSE( + zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId)); + EXPECT_FALSE( + zeroex::ParseQuoteResponse(base::Value(), mojom::kMainnetChainId)); } TEST(SwapResponseParserUnitTest, ParseZeroExTransactionResponse) { - // Case 1: non-null zeroExFee + // Case 1: valid transaction std::string json(R"( { - "price":"1916.27547998814058355", - "guaranteedPrice":"1935.438234788021989386", - "to":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "data":"0x0", - "value":"0", - "gas":"719000", - "estimatedGas":"719001", - "gasPrice":"26000000000", - "protocolFee":"0", - "minimumProtocolFee":"0", - "buyTokenAddress":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "sellTokenAddress":"0x6b175474e89094c44da98b954eedeac495271d0f", - "buyAmount":"1000000000000000000000", - "sellAmount":"1916275479988140583549706", - "sources":[], - "allowanceTarget":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "sellTokenToEthRate":"1900.44962824532464391", - "buyTokenToEthRate":"1", - "estimatedPriceImpact": "0.7232", - "sources": [ - { - "name": "Uniswap_V2", - "proportion": "1" - } - ], - "fees": { - "zeroExFee": { - "feeType": "volume", - "feeToken": "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063", - "feeAmount": "148470027512868522", - "billingType": "on-chain" - } + "transaction": { + "to": "0x7f6cee965959295cc64d0e6c00d99d6532d8e86b", + "data": "0xdeadbeef", + "gas": "288079", + "gasPrice": "4837860000", + "value": "0" } } )"); - mojom::ZeroExQuotePtr quote = - zeroex::ParseQuoteResponse(ParseJson(json), true); - ASSERT_TRUE(quote); - - EXPECT_EQ(quote->price, "1916.27547998814058355"); - EXPECT_EQ(quote->guaranteed_price, "1935.438234788021989386"); - EXPECT_EQ(quote->to, "0xdef1c0ded9bec7f1a1670819833240f027b25eff"); - EXPECT_EQ(quote->data, "0x0"); - - EXPECT_EQ(quote->value, "0"); - EXPECT_EQ(quote->gas, "719000"); - EXPECT_EQ(quote->estimated_gas, "719001"); - EXPECT_EQ(quote->gas_price, "26000000000"); - EXPECT_EQ(quote->protocol_fee, "0"); - EXPECT_EQ(quote->minimum_protocol_fee, "0"); - EXPECT_EQ(quote->buy_token_address, - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); - EXPECT_EQ(quote->sell_token_address, - "0x6b175474e89094c44da98b954eedeac495271d0f"); - EXPECT_EQ(quote->buy_amount, "1000000000000000000000"); - EXPECT_EQ(quote->sell_amount, "1916275479988140583549706"); - EXPECT_EQ(quote->allowance_target, - "0xdef1c0ded9bec7f1a1670819833240f027b25eff"); - EXPECT_EQ(quote->sell_token_to_eth_rate, "1900.44962824532464391"); - EXPECT_EQ(quote->buy_token_to_eth_rate, "1"); - EXPECT_EQ(quote->estimated_price_impact, "0.7232"); - EXPECT_EQ(quote->sources.size(), 1UL); - EXPECT_EQ(quote->sources.at(0)->name, "Uniswap_V2"); - EXPECT_EQ(quote->sources.at(0)->proportion, "1"); - ASSERT_TRUE(quote->fees->zero_ex_fee); - EXPECT_EQ(quote->fees->zero_ex_fee->fee_type, "volume"); - EXPECT_EQ(quote->fees->zero_ex_fee->fee_token, - "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063"); - EXPECT_EQ(quote->fees->zero_ex_fee->fee_amount, "148470027512868522"); - EXPECT_EQ(quote->fees->zero_ex_fee->billing_type, "on-chain"); - - // Case 2: null zeroExFee - json = R"( - { - "price":"1916.27547998814058355", - "guaranteedPrice":"1935.438234788021989386", - "to":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "data":"0x0", - "value":"0", - "gas":"719000", - "estimatedGas":"719001", - "gasPrice":"26000000000", - "protocolFee":"0", - "minimumProtocolFee":"0", - "buyTokenAddress":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "sellTokenAddress":"0x6b175474e89094c44da98b954eedeac495271d0f", - "buyAmount":"1000000000000000000000", - "sellAmount":"1916275479988140583549706", - "sources":[], - "allowanceTarget":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "sellTokenToEthRate":"1900.44962824532464391", - "buyTokenToEthRate":"1", - "estimatedPriceImpact": "0.7232", - "sources":[], - "fees": { - "zeroExFee": null - } - } - )"; - quote = zeroex::ParseQuoteResponse(ParseJson(json), true); - ASSERT_TRUE(quote); - EXPECT_FALSE(quote->fees->zero_ex_fee); + mojom::ZeroExTransactionPtr transaction = + zeroex::ParseTransactionResponse(ParseJson(json)); + ASSERT_TRUE(transaction); - // Case 3: malformed fees field - json = R"( - { - "price":"1916.27547998814058355", - "guaranteedPrice":"1935.438234788021989386", - "to":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "data":"0x0", - "value":"0", - "gas":"719000", - "estimatedGas":"719001", - "gasPrice":"26000000000", - "protocolFee":"0", - "minimumProtocolFee":"0", - "buyTokenAddress":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "sellTokenAddress":"0x6b175474e89094c44da98b954eedeac495271d0f", - "buyAmount":"1000000000000000000000", - "sellAmount":"1916275479988140583549706", - "sources":[], - "allowanceTarget":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "sellTokenToEthRate":"1900.44962824532464391", - "buyTokenToEthRate":"1", - "estimatedPriceImpact": "0.7232", - "sources":[], - "fees": null - } - )"; - EXPECT_FALSE(zeroex::ParseQuoteResponse(ParseJson(json), true)); + EXPECT_EQ(transaction->to, "0x7f6cee965959295cc64d0e6c00d99d6532d8e86b"); + EXPECT_EQ(transaction->data, "0xdeadbeef"); + EXPECT_EQ(transaction->gas, "288079"); + EXPECT_EQ(transaction->gas_price, "4837860000"); + EXPECT_EQ(transaction->value, "0"); - // Case 4: other invalid cases - json = R"({"price": "3"})"; - EXPECT_FALSE(zeroex::ParseQuoteResponse(ParseJson(json), true)); - json = R"({"price": 3})"; - EXPECT_FALSE(zeroex::ParseQuoteResponse(ParseJson(json), true)); + // Case 2: invalid cases json = "3"; - EXPECT_FALSE(zeroex::ParseQuoteResponse(ParseJson(json), true)); + EXPECT_FALSE(zeroex::ParseTransactionResponse(ParseJson(json))); json = "[3]"; - EXPECT_FALSE(zeroex::ParseQuoteResponse(ParseJson(json), true)); - EXPECT_FALSE(zeroex::ParseQuoteResponse(base::Value(), true)); + EXPECT_FALSE(zeroex::ParseTransactionResponse(ParseJson(json))); + EXPECT_FALSE(zeroex::ParseTransactionResponse(base::Value())); } TEST(SwapResponseParserUnitTest, ParseJupiterQuoteResponse) { @@ -493,52 +501,14 @@ TEST(SwapResponseParserUnitTest, ParseJupiterTransactionResponse) { TEST(SwapResponseParserUnitTest, ParseZeroExErrorResponse) { { std::string json(R"( - { - "code": "100", - "reason": "Validation Failed", - "validationErrors": [ - { - "field": "buyAmount", - "code": "1004", - "reason": "INSUFFICIENT_ASSET_LIQUIDITY" - } - ] - })"); - - auto swap_error = zeroex::ParseErrorResponse(ParseJson(json)); - EXPECT_EQ(swap_error->code, "100"); - EXPECT_EQ(swap_error->reason, "Validation Failed"); - EXPECT_EQ(swap_error->validation_errors.size(), 1u); - EXPECT_EQ(swap_error->validation_errors.front()->field, "buyAmount"); - EXPECT_EQ(swap_error->validation_errors.front()->code, "1004"); - EXPECT_EQ(swap_error->validation_errors.front()->reason, - "INSUFFICIENT_ASSET_LIQUIDITY"); - - EXPECT_TRUE(swap_error->is_insufficient_liquidity); - } - { - std::string json(R"( - { - "code": "100", - "reason": "Validation Failed", - "validationErrors": [ - { - "field": "buyAmount", - "code": "1004", - "reason": "SOMETHING_ELSE" - } - ] - })"); + { + "name": "INPUT_INVALID", + "message": "Validation Failed" + })"); auto swap_error = zeroex::ParseErrorResponse(ParseJson(json)); - EXPECT_EQ(swap_error->code, "100"); - EXPECT_EQ(swap_error->reason, "Validation Failed"); - EXPECT_EQ(swap_error->validation_errors.size(), 1u); - EXPECT_EQ(swap_error->validation_errors.front()->field, "buyAmount"); - EXPECT_EQ(swap_error->validation_errors.front()->code, "1004"); - EXPECT_EQ(swap_error->validation_errors.front()->reason, "SOMETHING_ELSE"); - - EXPECT_FALSE(swap_error->is_insufficient_liquidity); + EXPECT_EQ(swap_error->name, "INPUT_INVALID"); + EXPECT_EQ(swap_error->message, "Validation Failed"); } } diff --git a/components/brave_wallet/browser/swap_responses.idl b/components/brave_wallet/browser/swap_responses.idl index 7f84be791738..000fd7bac758 100644 --- a/components/brave_wallet/browser/swap_responses.idl +++ b/components/brave_wallet/browser/swap_responses.idl @@ -4,16 +4,10 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ namespace swap_responses { - dictionary ZeroExSource { - DOMString name; - DOMString proportion; - }; - dictionary ZeroExFee { - DOMString feeType; - DOMString feeToken; - DOMString feeAmount; - DOMString billingType; + DOMString amount; + DOMString token; + DOMString type; }; dictionary ZeroExFees { @@ -21,45 +15,47 @@ namespace swap_responses { any zeroExFee; }; - dictionary SwapResponse0x { - DOMString price; - DOMString? guaranteedPrice; - DOMString? to; - DOMString? data; - DOMString value; + dictionary ZeroExTransaction { + DOMString to; + DOMString data; DOMString gas; - DOMString estimatedGas; DOMString gasPrice; - DOMString protocolFee; - DOMString minimumProtocolFee; - DOMString buyTokenAddress; - DOMString buyTokenAddress; - DOMString sellTokenAddress; - DOMString buyAmount; - DOMString sellAmount; - DOMString allowanceTarget; - DOMString sellTokenToEthRate; - DOMString buyTokenToEthRate; - DOMString estimatedPriceImpact; - ZeroExSource[] sources; - ZeroExFees fees; - }; - - dictionary ZeroExValidationError { - DOMString field; - DOMString code; - DOMString reason; - }; - - dictionary ZeroExGenericError { - DOMString message; + DOMString value; + }; + + dictionary ZeroExRouteFill { + DOMString from; + DOMString to; + DOMString source; + DOMString proportionBps; + }; + + dictionary ZeroExRoute { + ZeroExRouteFill[] fills; + }; + + dictionary ZeroExQuoteResponse { + DOMString? blockNumber; + DOMString? buyAmount; + DOMString? buyToken; + ZeroExFees? fees; + DOMString? gas; + DOMString? gasPrice; + boolean liquidityAvailable; + DOMString? minBuyAmount; + ZeroExRoute? route; + DOMString? sellAmount; + DOMString? sellToken; + DOMString? totalNetworkFee; + }; + + dictionary ZeroExTransactionResponse { + ZeroExTransaction transaction; }; dictionary ZeroExErrorResponse { - DOMString code; - DOMString reason; - ZeroExValidationError[]? validationErrors; - ZeroExGenericError? values; + DOMString name; + DOMString message; }; dictionary JupiterPlatformFee { diff --git a/components/brave_wallet/browser/swap_service.cc b/components/brave_wallet/browser/swap_service.cc index e7365aecfc40..6971da9426e8 100644 --- a/components/brave_wallet/browser/swap_service.cc +++ b/components/brave_wallet/browser/swap_service.cc @@ -17,6 +17,7 @@ #include "brave/components/brave_wallet/browser/swap_response_parser.h" #include "brave/components/brave_wallet/common/brave_wallet.mojom.h" #include "brave/components/brave_wallet/common/buildflags.h" +#include "brave/components/brave_wallet/common/hex_utils.h" #include "brave/components/constants/brave_services_key.h" #include "net/base/load_flags.h" #include "net/base/url_util.h" @@ -54,17 +55,10 @@ net::NetworkTrafficAnnotationTag GetNetworkTrafficAnnotationTag() { namespace brave_wallet { namespace { + +// Ref: https://0x.org/docs/introduction/0x-cheat-sheet#-chain-support bool IsNetworkSupportedByZeroEx(const std::string& chain_id) { - return (chain_id == mojom::kSepoliaChainId || - chain_id == mojom::kMainnetChainId || - chain_id == mojom::kPolygonMainnetChainId || - chain_id == mojom::kBnbSmartChainMainnetChainId || - chain_id == mojom::kAvalancheMainnetChainId || - chain_id == mojom::kFantomMainnetChainId || - chain_id == mojom::kCeloMainnetChainId || - chain_id == mojom::kOptimismMainnetChainId || - chain_id == mojom::kArbitrumMainnetChainId || - chain_id == mojom::kBaseMainnetChainId); + return !GetZeroExAllowanceHolderAddress(chain_id).empty(); } bool IsNetworkSupportedByJupiter(const std::string& chain_id) { @@ -106,17 +100,17 @@ bool IsNetworkSupportedBySquid(const std::string& chain_id) { chain_id == mojom::kImmutableZkEVMChainId); } -bool HasRFQTLiquidity(const std::string& chain_id) { - return (chain_id == mojom::kMainnetChainId || - chain_id == mojom::kPolygonMainnetChainId); -} +std::optional EncodeChainId(const std::string& value) { + uint256_t val; + if (!HexValueToUint256(value, &val)) { + return std::nullopt; + } -std::string GetAffiliateAddress(const std::string& chain_id) { - if (IsNetworkSupportedByZeroEx(chain_id)) { - return kAffiliateAddress; + if (val > std::numeric_limits::max()) { + return std::nullopt; } - return ""; + return base::NumberToString(static_cast(val)); } mojom::SwapFeesPtr GetZeroSwapFee() { @@ -133,8 +127,17 @@ GURL AppendZeroExSwapParams(const GURL& swap_url, const mojom::SwapQuoteParams& params, const std::optional& fee_param) { GURL url = swap_url; + + if (!IsNetworkSupportedByZeroEx(params.from_chain_id)) { + return GURL(); + } + + if (auto chain_id = EncodeChainId(params.from_chain_id)) { + url = net::AppendQueryParameter(url, "chainId", chain_id.value()); + } + if (!params.from_account_id->address.empty()) { - url = net::AppendQueryParameter(url, "takerAddress", + url = net::AppendQueryParameter(url, "taker", params.from_account_id->address); } if (!params.from_amount.empty()) { @@ -144,32 +147,27 @@ GURL AppendZeroExSwapParams(const GURL& swap_url, url = net::AppendQueryParameter(url, "buyAmount", params.to_amount); } - url = net::AppendQueryParameter(url, "buyToken", - params.to_token.empty() - ? kNativeEVMAssetContractAddress - : params.to_token); + auto buy_token = params.to_token.empty() ? kNativeEVMAssetContractAddress + : params.to_token; + url = net::AppendQueryParameter(url, "buyToken", buy_token); url = net::AppendQueryParameter(url, "sellToken", params.from_token.empty() ? kNativeEVMAssetContractAddress : params.from_token); if (fee_param.has_value() && !fee_param->empty()) { - url = net::AppendQueryParameter(url, "buyTokenPercentageFee", - fee_param.value()); - url = net::AppendQueryParameter(url, "feeRecipient", kEVMFeeRecipient); + url = net::AppendQueryParameter(url, "swapFeeBps", fee_param.value()); + url = net::AppendQueryParameter(url, "swapFeeRecipient", kEVMFeeRecipient); + url = net::AppendQueryParameter(url, "swapFeeToken", buy_token); } double slippage_percentage = 0.0; if (base::StringToDouble(params.slippage_percentage, &slippage_percentage)) { url = net::AppendQueryParameter( - url, "slippagePercentage", - base::StringPrintf("%.6f", slippage_percentage / 100)); + url, "slippageBps", + base::StringPrintf("%d", static_cast(slippage_percentage * 100))); } - std::string affiliate_address = GetAffiliateAddress(params.from_chain_id); - if (!affiliate_address.empty()) { - url = net::AppendQueryParameter(url, "affiliateAddress", affiliate_address); - } // TODO(onyb): custom gas_price is currently unused and may be removed in // future. // if (!params.gas_price.empty()) { @@ -216,36 +214,13 @@ GURL AppendJupiterQuoteParams(const GURL& swap_url, return url; } -base::flat_map GetHeaders() { - return {{kBraveServicesKeyHeader, BUILDFLAG(BRAVE_SERVICES_KEY)}}; -} - -std::string GetBaseSwapURL(const std::string& chain_id) { - if (chain_id == brave_wallet::mojom::kSepoliaChainId) { - return brave_wallet::kZeroExSepoliaBaseAPIURL; - } else if (chain_id == brave_wallet::mojom::kMainnetChainId) { - return brave_wallet::kZeroExEthereumBaseAPIURL; - } else if (chain_id == brave_wallet::mojom::kPolygonMainnetChainId) { - return brave_wallet::kZeroExPolygonBaseAPIURL; - } else if (chain_id == brave_wallet::mojom::kBnbSmartChainMainnetChainId) { - return brave_wallet::kZeroExBinanceSmartChainBaseAPIURL; - } else if (chain_id == brave_wallet::mojom::kAvalancheMainnetChainId) { - return brave_wallet::kZeroExAvalancheBaseAPIURL; - } else if (chain_id == brave_wallet::mojom::kFantomMainnetChainId) { - return brave_wallet::kZeroExFantomBaseAPIURL; - } else if (chain_id == brave_wallet::mojom::kCeloMainnetChainId) { - return brave_wallet::kZeroExCeloBaseAPIURL; - } else if (chain_id == brave_wallet::mojom::kOptimismMainnetChainId) { - return brave_wallet::kZeroExOptimismBaseAPIURL; - } else if (chain_id == brave_wallet::mojom::kArbitrumMainnetChainId) { - return brave_wallet::kZeroExArbitrumBaseAPIURL; - } else if (chain_id == brave_wallet::mojom::kSolanaMainnet) { - return brave_wallet::kJupiterBaseAPIURL; - } else if (chain_id == brave_wallet::mojom::kBaseMainnetChainId) { - return brave_wallet::kZeroExBaseBaseAPIURL; +base::flat_map GetHeaders(bool is_zero_ex = false) { + base::flat_map headers = { + {kBraveServicesKeyHeader, BUILDFLAG(BRAVE_SERVICES_KEY)}}; + if (is_zero_ex) { + headers[kZeroExAPIVersionHeader] = kZeroExAPIVersion; } - - return ""; + return headers; } } // namespace @@ -271,49 +246,23 @@ void SwapService::Bind(mojo::PendingReceiver receiver) { GURL SwapService::GetZeroExQuoteURL( const mojom::SwapQuoteParams& params, const std::optional& fee_param) { - const bool use_rfqt = HasRFQTLiquidity(params.from_chain_id); - - // If chain has RFQ-T liquidity available, use the /quote endpoint for - // fetching the indicative price, since /price is often inaccurate. This is - // discouraged in 0x docs, particularly for RFQ-T trades, since it locks up - // the capital of market makers. However, the 0x team has approved us to do - // this, noting the inability of /price to discover optimal RFQ quotes. This - // should be considered a temporary workaround until 0x comes up with a - // solution. - auto url = GURL(GetBaseSwapURL(params.from_chain_id)) - .Resolve(use_rfqt ? "/swap/v1/quote" : "/swap/v1/price"); - url = AppendZeroExSwapParams(url, params, fee_param); - // That flag prevents an allowance validation by the 0x router. Disable it - // here and perform the validation on the client side. - url = net::AppendQueryParameter(url, "skipValidation", "true"); - - if (use_rfqt) { - url = net::AppendQueryParameter(url, "intentOnFilling", "false"); - } - - return url; + auto url = GURL(kZeroExBaseAPIURL).Resolve("/swap/allowance-holder/price"); + return AppendZeroExSwapParams(url, params, fee_param); } // static GURL SwapService::GetZeroExTransactionURL( const mojom::SwapQuoteParams& params, const std::optional& fee_param) { - auto url = - GURL(GetBaseSwapURL(params.from_chain_id)).Resolve("/swap/v1/quote"); - url = AppendZeroExSwapParams(url, params, fee_param); - - if (HasRFQTLiquidity(params.from_chain_id)) { - url = net::AppendQueryParameter(url, "intentOnFilling", "true"); - } - - return url; + auto url = GURL(kZeroExBaseAPIURL).Resolve("/swap/allowance-holder/quote"); + return AppendZeroExSwapParams(url, params, fee_param); } // static GURL SwapService::GetJupiterQuoteURL( const mojom::SwapQuoteParams& params, const std::optional& fee_param) { - auto url = GURL(GetBaseSwapURL(params.from_chain_id)).Resolve("/v6/quote"); + auto url = GURL(kJupiterBaseAPIURL).Resolve("/v6/quote"); url = AppendJupiterQuoteParams(url, params, fee_param); return url; @@ -321,7 +270,7 @@ GURL SwapService::GetJupiterQuoteURL( // static GURL SwapService::GetJupiterTransactionURL(const std::string& chain_id) { - return GURL(GetBaseSwapURL(chain_id)).Resolve("/v6/swap"); + return GURL(kJupiterBaseAPIURL).Resolve("/v6/swap"); } // static @@ -427,12 +376,12 @@ void SwapService::GetQuote(mojom::SwapQuoteParamsPtr params, auto internal_callback = base::BindOnce( &SwapService::OnGetZeroExQuote, weak_ptr_factory_.GetWeakPtr(), - std::move(swap_fee), std::move(callback)); + params->from_chain_id, std::move(swap_fee), std::move(callback)); api_request_helper_.Request(net::HttpRequestHeaders::kGetMethod, GetZeroExQuoteURL(*params, fee_param), "", "", - std::move(internal_callback), GetHeaders(), {}, - std::move(conversion_callback)); + std::move(internal_callback), GetHeaders(true), + {}, std::move(conversion_callback)); return; } @@ -466,7 +415,8 @@ void SwapService::GetQuote(mojom::SwapQuoteParamsPtr params, l10n_util::GetStringUTF8(IDS_BRAVE_WALLET_UNSUPPORTED_NETWORK)); } -void SwapService::OnGetZeroExQuote(mojom::SwapFeesPtr swap_fee, +void SwapService::OnGetZeroExQuote(const std::string& chain_id, + mojom::SwapFeesPtr swap_fee, GetQuoteCallback callback, APIRequestResult api_request_result) { if (!api_request_result.Is2XXResponseCode()) { @@ -484,8 +434,8 @@ void SwapService::OnGetZeroExQuote(mojom::SwapFeesPtr swap_fee, return; } - if (auto swap_response = - zeroex::ParseQuoteResponse(api_request_result.value_body(), false)) { + if (auto swap_response = zeroex::ParseQuoteResponse( + api_request_result.value_body(), chain_id)) { std::move(callback).Run( mojom::SwapQuoteUnion::NewZeroExQuote(std::move(swap_response)), std::move(swap_fee), nullptr, ""); @@ -605,7 +555,7 @@ void SwapService::GetTransaction(mojom::SwapTransactionParamsUnionPtr params, net::HttpRequestHeaders::kGetMethod, GetZeroExTransactionURL(*params->get_zero_ex_transaction_params(), swap_fee->fee_param), - "", "", std::move(internal_callback), GetHeaders(), {}, + "", "", std::move(internal_callback), GetHeaders(true), {}, std::move(conversion_callback)); return; @@ -705,7 +655,7 @@ void SwapService::OnGetZeroExTransaction(GetTransactionCallback callback, } if (auto swap_response = - zeroex::ParseQuoteResponse(api_request_result.value_body(), true)) { + zeroex::ParseTransactionResponse(api_request_result.value_body())) { std::move(callback).Run(mojom::SwapTransactionUnion::NewZeroExTransaction( std::move(swap_response)), nullptr, ""); diff --git a/components/brave_wallet/browser/swap_service.h b/components/brave_wallet/browser/swap_service.h index 5b5f1709abac..48ef666022ea 100644 --- a/components/brave_wallet/browser/swap_service.h +++ b/components/brave_wallet/browser/swap_service.h @@ -63,7 +63,8 @@ class SwapService : public KeyedService, public mojom::SwapService { static GURL GetSquidURL(); private: - void OnGetZeroExQuote(mojom::SwapFeesPtr swap_fee, + void OnGetZeroExQuote(const std::string& chain_id, + mojom::SwapFeesPtr swap_fee, GetQuoteCallback callback, APIRequestResult api_request_result); void OnGetJupiterQuote(mojom::SwapFeesPtr swap_fee, diff --git a/components/brave_wallet/browser/swap_service_unittest.cc b/components/brave_wallet/browser/swap_service_unittest.cc index ab87be55d50b..dc8ceb232545 100644 --- a/components/brave_wallet/browser/swap_service_unittest.cc +++ b/components/brave_wallet/browser/swap_service_unittest.cc @@ -490,70 +490,104 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuote) { // Case 1: non-null zeroExFee SetInterceptor(R"( { - "price":"1916.27547998814058355", - "value":"0", - "gas":"719000", - "estimatedGas":"719000", - "gasPrice":"26000000000", - "protocolFee":"0", - "minimumProtocolFee":"0", - "buyTokenAddress":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "sellTokenAddress":"0x6b175474e89094c44da98b954eedeac495271d0f", - "buyAmount":"1000000000000000000000", - "sellAmount":"1916275479988140583549706", - "allowanceTarget":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "sellTokenToEthRate":"1900.44962824532464391", - "buyTokenToEthRate":"1", - "estimatedPriceImpact": "0.7232", - "sources": [ - { - "name": "Uniswap_V2", - "proportion": "1" - } - ], + "blockNumber": "20114676", + "buyAmount": "100032748", + "buyToken": "0xdac17f958d2ee523a2206206994597c13d831ec7", "fees": { - "zeroExFee" : { - "feeType" : "volume", - "feeToken" : "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063", - "feeAmount" : "148470027512868522", - "billingType" : "on-chain" + "integratorFee": null, + "zeroExFee": { + "amount": "0", + "token": "0xdeadbeef", + "type": "volume" + }, + "gasFee": null + }, + "gas": "288095", + "gasPrice": "7062490000", + "issues": { + "allowance": { + "actual": "0", + "spender": "0x0000000000001ff3684f28c67538d4d072c22734" + }, + "balance": { + "token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "actual": "0", + "expected": "100000000" + }, + "simulationIncomplete": false, + "invalidSourcesPassed": [] + }, + "liquidityAvailable": true, + "minBuyAmount": "99032421", + "route": { + "fills": [ + { + "from": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "source": "SolidlyV3", + "proportionBps": "10000" + } + ], + "tokens": [ + { + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "symbol": "USDC" + }, + { + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "symbol": "USDT" + } + ] + }, + "sellAmount": "100000000", + "sellToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "tokenMetadata": { + "buyToken": { + "buyTaxBps": "0", + "sellTaxBps": "0" + }, + "sellToken": { + "buyTaxBps": "0", + "sellTaxBps": "0" } - } - })"); + }, + "totalNetworkFee": "2034668056550000", + "zid": "0x111111111111111111111111" + } + )"); - auto expected_zero_ex_quote = mojom::ZeroExQuote::New(); - expected_zero_ex_quote->price = "1916.27547998814058355"; - expected_zero_ex_quote->value = "0"; - expected_zero_ex_quote->gas = "719000"; - expected_zero_ex_quote->estimated_gas = "719000"; - expected_zero_ex_quote->gas_price = "26000000000"; - expected_zero_ex_quote->protocol_fee = "0"; - expected_zero_ex_quote->minimum_protocol_fee = "0"; - expected_zero_ex_quote->buy_token_address = - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; - expected_zero_ex_quote->sell_token_address = - "0x6b175474e89094c44da98b954eedeac495271d0f"; - expected_zero_ex_quote->buy_amount = "1000000000000000000000"; - expected_zero_ex_quote->sell_amount = "1916275479988140583549706"; - expected_zero_ex_quote->allowance_target = - "0xdef1c0ded9bec7f1a1670819833240f027b25eff"; - expected_zero_ex_quote->sell_token_to_eth_rate = "1900.44962824532464391"; - expected_zero_ex_quote->buy_token_to_eth_rate = "1"; - expected_zero_ex_quote->estimated_price_impact = "0.7232"; - - auto source = brave_wallet::mojom::ZeroExSource::New(); - source->name = "Uniswap_V2"; - source->proportion = "1"; - expected_zero_ex_quote->sources.push_back(source.Clone()); - - auto fees = brave_wallet::mojom::ZeroExFees::New(); - auto zero_ex_fee = brave_wallet::mojom::ZeroExFee::New(); - zero_ex_fee->fee_type = "volume"; - zero_ex_fee->fee_token = "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063"; - zero_ex_fee->fee_amount = "148470027512868522"; - zero_ex_fee->billing_type = "on-chain"; - fees->zero_ex_fee = std::move(zero_ex_fee); - expected_zero_ex_quote->fees = std::move(fees); + auto zero_ex_quote = mojom::ZeroExQuote::New(); + zero_ex_quote->buy_amount = "100032748"; + zero_ex_quote->buy_token = "0xdac17f958d2ee523a2206206994597c13d831ec7"; + zero_ex_quote->sell_amount = "100000000"; + zero_ex_quote->sell_token = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; + + auto zero_ex_fee = mojom::ZeroExFee::New(); + zero_ex_fee->type = "volume"; + zero_ex_fee->token = "0xdeadbeef"; + zero_ex_fee->amount = "0"; + zero_ex_quote->fees = mojom::ZeroExFees::New(); + zero_ex_quote->fees->zero_ex_fee = std::move(zero_ex_fee); + + zero_ex_quote->gas = "288095"; + zero_ex_quote->gas_price = "7062490000"; + zero_ex_quote->liquidity_available = true; + zero_ex_quote->min_buy_amount = "99032421"; + zero_ex_quote->total_network_fee = "2034668056550000"; + + auto fill = mojom::ZeroExRouteFill::New(); + fill->from = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; + fill->to = "0xdac17f958d2ee523a2206206994597c13d831ec7"; + fill->source = "SolidlyV3"; + fill->proportion_bps = "10000"; + zero_ex_quote->route = mojom::ZeroExRoute::New(); + zero_ex_quote->route->fills.push_back(fill.Clone()); + + auto expected_zero_ex_quote_info = mojom::ZeroExQuoteInfo::New(); + expected_zero_ex_quote_info->quote = std::move(zero_ex_quote); + expected_zero_ex_quote_info->liquidity_available = true; + expected_zero_ex_quote_info->allowance_target = + "0x0000000000001fF3684f28c67538d4D072C22734"; auto expected_swap_fees = mojom::SwapFees::New(); expected_swap_fees->fee_pct = "0"; @@ -564,7 +598,7 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuote) { base::MockCallback callback; EXPECT_CALL(callback, Run(EqualsMojo(mojom::SwapQuoteUnion::NewZeroExQuote( - expected_zero_ex_quote.Clone())), + expected_zero_ex_quote_info.Clone())), EqualsMojo(expected_swap_fees.Clone()), EqualsMojo(mojom::SwapErrorUnionPtr()), "")); @@ -580,35 +614,71 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuote) { // Case 2: null zeroExFee SetInterceptor(R"( { - "price":"1916.27547998814058355", - "value":"0", - "gas":"719000", - "estimatedGas":"719000", - "gasPrice":"26000000000", - "protocolFee":"0", - "minimumProtocolFee":"0", - "buyTokenAddress":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "sellTokenAddress":"0x6b175474e89094c44da98b954eedeac495271d0f", - "buyAmount":"1000000000000000000000", - "sellAmount":"1916275479988140583549706", - "allowanceTarget":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "sellTokenToEthRate":"1900.44962824532464391", - "buyTokenToEthRate":"1", - "estimatedPriceImpact": "0.7232", - "sources": [ - { - "name": "Uniswap_V2", - "proportion": "1" - } - ], + "blockNumber": "20114676", + "buyAmount": "100032748", + "buyToken": "0xdac17f958d2ee523a2206206994597c13d831ec7", "fees": { - "zeroExFee": null - } - })"); + "integratorFee": null, + "zeroExFee": null, + "gasFee": null + }, + "gas": "288095", + "gasPrice": "7062490000", + "issues": { + "allowance": { + "actual": "0", + "spender": "0x0000000000001ff3684f28c67538d4d072c22734" + }, + "balance": { + "token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "actual": "0", + "expected": "100000000" + }, + "simulationIncomplete": false, + "invalidSourcesPassed": [] + }, + "liquidityAvailable": true, + "minBuyAmount": "99032421", + "route": { + "fills": [ + { + "from": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "to": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "source": "SolidlyV3", + "proportionBps": "10000" + } + ], + "tokens": [ + { + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "symbol": "USDC" + }, + { + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "symbol": "USDT" + } + ] + }, + "sellAmount": "100000000", + "sellToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "tokenMetadata": { + "buyToken": { + "buyTaxBps": "0", + "sellTaxBps": "0" + }, + "sellToken": { + "buyTaxBps": "0", + "sellTaxBps": "0" + } + }, + "totalNetworkFee": "2034668056550000", + "zid": "0x111111111111111111111111" + } + )"); - expected_zero_ex_quote->fees->zero_ex_fee = nullptr; + expected_zero_ex_quote_info->quote->fees->zero_ex_fee = nullptr; EXPECT_CALL(callback, Run(EqualsMojo(mojom::SwapQuoteUnion::NewZeroExQuote( - std::move(expected_zero_ex_quote))), + std::move(expected_zero_ex_quote_info))), EqualsMojo(expected_swap_fees.Clone()), EqualsMojo(mojom::SwapErrorUnionPtr()), "")); @@ -625,25 +695,8 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuote) { TEST_F(SwapServiceUnitTest, GetZeroExQuoteError) { std::string error = R"( { - "code": "100", - "reason": "Validation Failed", - "validationErrors": [ - { - "code": "1000", - "field": "sellAmount", - "reason": "should have required property 'sellAmount'" - }, - { - "code": "1000", - "field": "buyAmount", - "reason": "should have required property 'buyAmount'" - }, - { - "code": "1001", - "field": "", - "reason": "should match exactly one schema in oneOf" - } - ] + "name": "INPUT_INVALID", + "message": "Validation Failed" })"; SetErrorInterceptor(error); @@ -684,80 +737,25 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuoteUnexpectedReturn) { } TEST_F(SwapServiceUnitTest, GetZeroExTransaction) { - // Case 1: non-null zeroExFee SetInterceptor(R"( { - "price":"1916.27547998814058355", - "guaranteedPrice":"1935.438234788021989386", - "to":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "data":"0x0", - "value":"0", - "gas":"719000", - "estimatedGas":"719000", - "gasPrice":"26000000000", - "protocolFee":"0", - "minimumProtocolFee":"0", - "buyTokenAddress":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "sellTokenAddress":"0x6b175474e89094c44da98b954eedeac495271d0f", - "buyAmount":"1000000000000000000000", - "sellAmount":"1916275479988140583549706", - "allowanceTarget":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "sellTokenToEthRate":"1900.44962824532464391", - "buyTokenToEthRate":"1", - "estimatedPriceImpact": "0.7232", - "sources": [ - { - "name": "Uniswap_V2", - "proportion": "1" - } - ], - "fees": { - "zeroExFee": { - "feeType": "volume", - "feeToken": "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063", - "feeAmount": "148470027512868522", - "billingType": "on-chain" - } + "transaction": { + "to": "0x7f6cee965959295cc64d0e6c00d99d6532d8e86b", + "data": "0xdeadbeef", + "gas": "288079", + "gasPrice": "4837860000", + "value": "0" } - })"); + } + )"); - auto expected_zero_ex_transaction = mojom::ZeroExQuote::New(); - expected_zero_ex_transaction->price = "1916.27547998814058355"; - expected_zero_ex_transaction->guaranteed_price = "1935.438234788021989386"; + auto expected_zero_ex_transaction = mojom::ZeroExTransaction::New(); expected_zero_ex_transaction->to = - "0xdef1c0ded9bec7f1a1670819833240f027b25eff"; - expected_zero_ex_transaction->data = "0x0"; + "0x7f6cee965959295cc64d0e6c00d99d6532d8e86b"; + expected_zero_ex_transaction->data = "0xdeadbeef"; + expected_zero_ex_transaction->gas = "288079"; + expected_zero_ex_transaction->gas_price = "4837860000"; expected_zero_ex_transaction->value = "0"; - expected_zero_ex_transaction->gas = "719000"; - expected_zero_ex_transaction->estimated_gas = "719000"; - expected_zero_ex_transaction->gas_price = "26000000000"; - expected_zero_ex_transaction->protocol_fee = "0"; - expected_zero_ex_transaction->minimum_protocol_fee = "0"; - expected_zero_ex_transaction->buy_token_address = - "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; - expected_zero_ex_transaction->sell_token_address = - "0x6b175474e89094c44da98b954eedeac495271d0f"; - expected_zero_ex_transaction->buy_amount = "1000000000000000000000"; - expected_zero_ex_transaction->sell_amount = "1916275479988140583549706"; - expected_zero_ex_transaction->allowance_target = - "0xdef1c0ded9bec7f1a1670819833240f027b25eff"; - expected_zero_ex_transaction->sell_token_to_eth_rate = - "1900.44962824532464391"; - expected_zero_ex_transaction->buy_token_to_eth_rate = "1"; - expected_zero_ex_transaction->estimated_price_impact = "0.7232"; - auto source = brave_wallet::mojom::ZeroExSource::New(); - source->name = "Uniswap_V2"; - source->proportion = "1"; - expected_zero_ex_transaction->sources.push_back(source.Clone()); - - auto fees = brave_wallet::mojom::ZeroExFees::New(); - auto zero_ex_fee = brave_wallet::mojom::ZeroExFee::New(); - zero_ex_fee->fee_type = "volume"; - zero_ex_fee->fee_token = "0x8f3cf7ad23cd3cadbd9735aff958023239c6a063"; - zero_ex_fee->fee_amount = "148470027512868522"; - zero_ex_fee->billing_type = "on-chain"; - fees->zero_ex_fee = std::move(zero_ex_fee); - expected_zero_ex_transaction->fees = std::move(fees); base::MockCallback callback; EXPECT_CALL(callback, @@ -774,59 +772,11 @@ TEST_F(SwapServiceUnitTest, GetZeroExTransaction) { callback.Get()); task_environment_.RunUntilIdle(); testing::Mock::VerifyAndClearExpectations(&callback); - - // Case 2: null zeroExFee - SetInterceptor(R"( - { - "price":"1916.27547998814058355", - "guaranteedPrice":"1935.438234788021989386", - "to":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "data":"0x0", - "value":"0", - "gas":"719000", - "estimatedGas":"719000", - "gasPrice":"26000000000", - "protocolFee":"0", - "minimumProtocolFee":"0", - "buyTokenAddress":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "sellTokenAddress":"0x6b175474e89094c44da98b954eedeac495271d0f", - "buyAmount":"1000000000000000000000", - "sellAmount":"1916275479988140583549706", - "allowanceTarget":"0xdef1c0ded9bec7f1a1670819833240f027b25eff", - "sellTokenToEthRate":"1900.44962824532464391", - "buyTokenToEthRate":"1", - "estimatedPriceImpact": "0.7232", - "sources": [ - { - "name": "Uniswap_V2", - "proportion": "1" - } - ], - "fees": { - "zeroExFee": null - } - })"); - - expected_zero_ex_transaction->fees->zero_ex_fee = nullptr; - EXPECT_CALL(callback, - Run(EqualsMojo(mojom::SwapTransactionUnion::NewZeroExTransaction( - std::move(expected_zero_ex_transaction))), - EqualsMojo(mojom::SwapErrorUnionPtr()), "")); - - swap_service_->GetTransaction( - mojom::SwapTransactionParamsUnion::NewZeroExTransactionParams( - GetCannedSwapQuoteParams( - mojom::CoinType::ETH, mojom::kPolygonMainnetChainId, "DAI", - mojom::CoinType::ETH, mojom::kPolygonMainnetChainId, "ETH", - mojom::SwapProvider::kZeroEx)), - callback.Get()); - task_environment_.RunUntilIdle(); - testing::Mock::VerifyAndClearExpectations(&callback); } TEST_F(SwapServiceUnitTest, GetZeroExTransactionError) { std::string error = - R"({"code":"100","reason":"Validation Failed","validationErrors":[{"code":"1000","field":"sellAmount","reason":"should have required property 'sellAmount'"},{"code":"1000","field":"buyAmount","reason":"should have required property 'buyAmount'"},{"code":"1001","field":"","reason":"should match exactly one schema in oneOf"}]})"; + R"({"name":"INPUT_INVALID","message":"Validation Failed"})"; SetErrorInterceptor(error); base::MockCallback callback; @@ -865,65 +815,19 @@ TEST_F(SwapServiceUnitTest, GetZeroExTransactionUnexpectedReturn) { } TEST_F(SwapServiceUnitTest, GetZeroExQuoteURL) { - const std::map non_rfqt_chain_ids = { - {mojom::kSepoliaChainId, "sepolia.api.0x.wallet.brave.com"}, - {mojom::kBnbSmartChainMainnetChainId, "bsc.api.0x.wallet.brave.com"}, - {mojom::kAvalancheMainnetChainId, "avalanche.api.0x.wallet.brave.com"}, - {mojom::kFantomMainnetChainId, "fantom.api.0x.wallet.brave.com"}, - {mojom::kCeloMainnetChainId, "celo.api.0x.wallet.brave.com"}, - {mojom::kOptimismMainnetChainId, "optimism.api.0x.wallet.brave.com"}, - {mojom::kArbitrumMainnetChainId, "arbitrum.api.0x.wallet.brave.com"}, - {mojom::kBaseMainnetChainId, "base.api.0x.wallet.brave.com"}}; - - const std::map rfqt_chain_ids = { - {mojom::kMainnetChainId, "api.0x.wallet.brave.com"}, - {mojom::kPolygonMainnetChainId, "polygon.api.0x.wallet.brave.com"}}; - - for (const auto& [chain_id, domain] : non_rfqt_chain_ids) { - SCOPED_TRACE(testing::Message() << "chain_id: " << chain_id); - - // OK: with fees - auto url = swap_service_->GetZeroExQuoteURL( - *GetCannedSwapQuoteParams(mojom::CoinType::ETH, chain_id, "DAI", - mojom::CoinType::ETH, chain_id, "ETH", - mojom::SwapProvider::kZeroEx), - "0.00875"); - EXPECT_EQ(url, - base::StringPrintf( - "https://%s/swap/v1/price?" - "takerAddress=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" - "sellAmount=1000000000000000000000&" - "buyToken=ETH&" - "sellToken=DAI&" - "buyTokenPercentageFee=0.00875&" - "feeRecipient=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" - "slippagePercentage=0.030000&" - "affiliateAddress=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" - "skipValidation=true", - domain.c_str())); - - // Ok: no fees - url = swap_service_->GetZeroExQuoteURL( - *GetCannedSwapQuoteParams(mojom::CoinType::ETH, chain_id, "DAI", - mojom::CoinType::ETH, chain_id, "ETH", - mojom::SwapProvider::kZeroEx), - ""); - EXPECT_EQ(url, - base::StringPrintf( - "https://%s/swap/v1/price?" - "takerAddress=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" - "sellAmount=1000000000000000000000&" - "buyToken=ETH&" - "sellToken=DAI&" - "slippagePercentage=0.030000&" - "affiliateAddress=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" - "skipValidation=true", - domain.c_str())); - } - - // If RFQ-T liquidity is available, so intentOnFilling=true is specified - // while fetching firm quotes. - for (const auto& [chain_id, domain] : rfqt_chain_ids) { + const std::map chain_ids = { + {mojom::kMainnetChainId, "1"}, + {mojom::kArbitrumMainnetChainId, "42161"}, + {mojom::kAvalancheMainnetChainId, "43114"}, + {mojom::kBaseMainnetChainId, "8453"}, + {mojom::kBlastMainnetChainId, "238"}, + {mojom::kBnbSmartChainMainnetChainId, "56"}, + {mojom::kLineaChainId, "59144"}, + {mojom::kOptimismMainnetChainId, "10"}, + {mojom::kPolygonMainnetChainId, "137"}, + {mojom::kScrollChainId, "534352"}}; + + for (const auto& [chain_id, encoded_chain_id] : chain_ids) { SCOPED_TRACE(testing::Message() << "chain_id: " << chain_id); // OK: with fees @@ -931,21 +835,20 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuoteURL) { *GetCannedSwapQuoteParams(mojom::CoinType::ETH, chain_id, "DAI", mojom::CoinType::ETH, chain_id, "ETH", mojom::SwapProvider::kZeroEx), - "0.00875"); + "85"); EXPECT_EQ(url, base::StringPrintf( - "https://%s/swap/v1/quote?" - "takerAddress=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" + "https://api.0x.wallet.brave.com/swap/allowance-holder/price?" + "chainId=%s&" + "taker=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" "sellAmount=1000000000000000000000&" "buyToken=ETH&" "sellToken=DAI&" - "buyTokenPercentageFee=0.00875&" - "feeRecipient=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" - "slippagePercentage=0.030000&" - "affiliateAddress=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" - "skipValidation=true&" - "intentOnFilling=false", - domain.c_str())); + "swapFeeBps=85&" + "swapFeeRecipient=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" + "swapFeeToken=ETH&" + "slippageBps=300", + encoded_chain_id.c_str())); // Ok: no fees url = swap_service_->GetZeroExQuoteURL( @@ -955,16 +858,14 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuoteURL) { ""); EXPECT_EQ(url, base::StringPrintf( - "https://%s/swap/v1/quote?" - "takerAddress=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" + "https://api.0x.wallet.brave.com/swap/allowance-holder/price?" + "chainId=%s&" + "taker=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" "sellAmount=1000000000000000000000&" "buyToken=ETH&" "sellToken=DAI&" - "slippagePercentage=0.030000&" - "affiliateAddress=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" - "skipValidation=true&" - "intentOnFilling=false", - domain.c_str())); + "slippageBps=300", + encoded_chain_id.c_str())); } // KO: unsupported network @@ -977,63 +878,19 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuoteURL) { } TEST_F(SwapServiceUnitTest, GetZeroExTransactionURL) { - const std::map non_rfqt_chain_ids = { - {mojom::kSepoliaChainId, "sepolia.api.0x.wallet.brave.com"}, - {mojom::kBnbSmartChainMainnetChainId, "bsc.api.0x.wallet.brave.com"}, - {mojom::kAvalancheMainnetChainId, "avalanche.api.0x.wallet.brave.com"}, - {mojom::kFantomMainnetChainId, "fantom.api.0x.wallet.brave.com"}, - {mojom::kCeloMainnetChainId, "celo.api.0x.wallet.brave.com"}, - {mojom::kOptimismMainnetChainId, "optimism.api.0x.wallet.brave.com"}, - {mojom::kArbitrumMainnetChainId, "arbitrum.api.0x.wallet.brave.com"}, - {mojom::kBaseMainnetChainId, "base.api.0x.wallet.brave.com"}}; - - const std::map rfqt_chain_ids = { - {mojom::kMainnetChainId, "api.0x.wallet.brave.com"}, - {mojom::kPolygonMainnetChainId, "polygon.api.0x.wallet.brave.com"}}; - - for (const auto& [chain_id, domain] : non_rfqt_chain_ids) { - SCOPED_TRACE(testing::Message() << "chain_id: " << chain_id); - - // OK: with fees - auto url = swap_service_->GetZeroExTransactionURL( - *GetCannedSwapQuoteParams(mojom::CoinType::ETH, chain_id, "DAI", - mojom::CoinType::ETH, chain_id, "ETH", - mojom::SwapProvider::kZeroEx), - "0.00875"); - EXPECT_EQ(url, - base::StringPrintf( - "https://%s/swap/v1/quote?" - "takerAddress=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" - "sellAmount=1000000000000000000000&" - "buyToken=ETH&" - "sellToken=DAI&" - "buyTokenPercentageFee=0.00875&" - "feeRecipient=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" - "slippagePercentage=0.030000&" - "affiliateAddress=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d", - domain.c_str())); - - // OK: no fees - url = swap_service_->GetZeroExTransactionURL( - *GetCannedSwapQuoteParams(mojom::CoinType::ETH, chain_id, "DAI", - mojom::CoinType::ETH, chain_id, "ETH", - mojom::SwapProvider::kZeroEx), - ""); - EXPECT_EQ(url, - base::StringPrintf( - "https://%s/swap/v1/quote?" - "takerAddress=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" - "sellAmount=1000000000000000000000&" - "buyToken=ETH&" - "sellToken=DAI&" - "slippagePercentage=0.030000&" - "affiliateAddress=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d", - domain.c_str())); - } - - // If RFQ-T liquidity is available, so intentOnFilling=true is specified - // while fetching firm quotes. - for (const auto& [chain_id, domain] : rfqt_chain_ids) { + const std::map chain_ids = { + {mojom::kMainnetChainId, "1"}, + {mojom::kArbitrumMainnetChainId, "42161"}, + {mojom::kAvalancheMainnetChainId, "43114"}, + {mojom::kBaseMainnetChainId, "8453"}, + {mojom::kBlastMainnetChainId, "238"}, + {mojom::kBnbSmartChainMainnetChainId, "56"}, + {mojom::kLineaChainId, "59144"}, + {mojom::kOptimismMainnetChainId, "10"}, + {mojom::kPolygonMainnetChainId, "137"}, + {mojom::kScrollChainId, "534352"}}; + + for (const auto& [chain_id, encoded_chain_id] : chain_ids) { SCOPED_TRACE(testing::Message() << "chain_id: " << chain_id); // OK: with fees @@ -1041,20 +898,20 @@ TEST_F(SwapServiceUnitTest, GetZeroExTransactionURL) { *GetCannedSwapQuoteParams(mojom::CoinType::ETH, chain_id, "DAI", mojom::CoinType::ETH, chain_id, "ETH", mojom::SwapProvider::kZeroEx), - "0.00875"); + "85"); EXPECT_EQ(url, base::StringPrintf( - "https://%s/swap/v1/quote?" - "takerAddress=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" + "https://api.0x.wallet.brave.com/swap/allowance-holder/quote?" + "chainId=%s&" + "taker=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" "sellAmount=1000000000000000000000&" "buyToken=ETH&" "sellToken=DAI&" - "buyTokenPercentageFee=0.00875&" - "feeRecipient=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" - "slippagePercentage=0.030000&" - "affiliateAddress=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" - "intentOnFilling=true", - domain.c_str())); + "swapFeeBps=85&" + "swapFeeRecipient=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" + "swapFeeToken=ETH&" + "slippageBps=300", + encoded_chain_id.c_str())); // OK: no fees url = swap_service_->GetZeroExTransactionURL( @@ -1064,15 +921,14 @@ TEST_F(SwapServiceUnitTest, GetZeroExTransactionURL) { ""); EXPECT_EQ(url, base::StringPrintf( - "https://%s/swap/v1/quote?" - "takerAddress=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" + "https://api.0x.wallet.brave.com/swap/allowance-holder/quote?" + "chainId=%s&" + "taker=0xa92D461a9a988A7f11ec285d39783A637Fdd6ba4&" "sellAmount=1000000000000000000000&" "buyToken=ETH&" "sellToken=DAI&" - "slippagePercentage=0.030000&" - "affiliateAddress=0xbd9420A98a7Bd6B89765e5715e169481602D9c3d&" - "intentOnFilling=true", - domain.c_str())); + "slippageBps=300", + encoded_chain_id.c_str())); } // KO: unsupported network @@ -1080,18 +936,18 @@ TEST_F(SwapServiceUnitTest, GetZeroExTransactionURL) { *GetCannedSwapQuoteParams(mojom::CoinType::ETH, "0x3", "DAI", mojom::CoinType::ETH, "0x3", "ETH", mojom::SwapProvider::kZeroEx), - "0.00875"), + "85"), ""); } TEST_F(SwapServiceUnitTest, IsSwapSupported) { const std::vector supported_chain_ids( {// ZeroEx - mojom::kMainnetChainId, mojom::kSepoliaChainId, - mojom::kPolygonMainnetChainId, mojom::kBnbSmartChainMainnetChainId, - mojom::kAvalancheMainnetChainId, mojom::kFantomMainnetChainId, - mojom::kCeloMainnetChainId, mojom::kOptimismMainnetChainId, - mojom::kArbitrumMainnetChainId, mojom::kBaseMainnetChainId, + mojom::kMainnetChainId, mojom::kArbitrumMainnetChainId, + mojom::kAvalancheMainnetChainId, mojom::kBaseMainnetChainId, + mojom::kBlastMainnetChainId, mojom::kBnbSmartChainMainnetChainId, + mojom::kLineaChainId, mojom::kOptimismMainnetChainId, + mojom::kPolygonMainnetChainId, mojom::kScrollChainId, // Jupiter mojom::kSolanaMainnet, diff --git a/components/brave_wallet/common/brave_wallet.mojom b/components/brave_wallet/common/brave_wallet.mojom index ce2b99865ab4..6c43f9aa1e49 100644 --- a/components/brave_wallet/common/brave_wallet.mojom +++ b/components/brave_wallet/common/brave_wallet.mojom @@ -448,74 +448,71 @@ union SwapTransactionParamsUnion { union SwapQuoteUnion { JupiterQuote jupiter_quote; - ZeroExQuote zero_ex_quote; + ZeroExQuoteInfo zero_ex_quote; LiFiQuote lifi_quote; SquidQuote squid_quote; }; union SwapTransactionUnion { string jupiter_transaction; - ZeroExQuote zero_ex_transaction; + ZeroExTransaction zero_ex_transaction; LiFiTransactionUnion lifi_transaction; SquidTransactionUnion squid_transaction; }; -struct ZeroExSource { - string name; - string proportion; -}; - struct ZeroExFee { - string fee_type; - string fee_token; - string fee_amount; - string billing_type; + string amount; + string token; + string type; }; struct ZeroExFees { ZeroExFee? zero_ex_fee; }; -struct ZeroExQuote { - string price; - string guaranteed_price; // Unused for price quote response - string to; // Unused for price quote response - string data; // Unused for price quote response - string value; +struct ZeroExTransaction { + string to; + string data; string gas; - string estimated_gas; string gas_price; - string protocol_fee; - string minimum_protocol_fee; - string buy_token_address; - string sell_token_address; - string buy_amount; - string sell_amount; - string allowance_target; - string sell_token_to_eth_rate; - string buy_token_to_eth_rate; - string estimated_price_impact; - array sources; - ZeroExFees fees; + string value; }; -struct ZeroExValidationError { - string field; - string code; - string reason; +struct ZeroExRouteFill { + string from; + string to; + string source; + string proportion_bps; }; -struct ZeroExGenericError { - string message; +struct ZeroExRoute { + array fills; +}; + +struct ZeroExQuote { + string buy_amount; + string buy_token; + ZeroExFees fees; + string gas; + string gas_price; + bool liquidity_available; + string min_buy_amount; + ZeroExRoute route; + string sell_amount; + string sell_token; + string total_network_fee; +}; + +struct ZeroExQuoteInfo { + // quote is undefined if liquidity_available is false + ZeroExQuote? quote; + string allowance_target; + bool liquidity_available; }; struct ZeroExError { - string code; - string reason; - array validation_errors; - ZeroExGenericError? values; - bool is_insufficient_liquidity; - bool is_insufficient_allowance; + string name; + string message; }; union SwapErrorUnion { diff --git a/components/brave_wallet_ui/page/screens/swap/hooks/useSwap.ts b/components/brave_wallet_ui/page/screens/swap/hooks/useSwap.ts index 82690469c263..d491d12f11c4 100644 --- a/components/brave_wallet_ui/page/screens/swap/hooks/useSwap.ts +++ b/components/brave_wallet_ui/page/screens/swap/hooks/useSwap.ts @@ -308,9 +308,9 @@ export const useSwap = () => { }) } - if (quoteUnion.zeroExQuote) { + if (quoteUnion.zeroExQuote?.quote) { return getZeroExQuoteOptions({ - quote: quoteUnion.zeroExQuote, + quote: quoteUnion.zeroExQuote.quote, fromNetwork, fromToken, toToken, @@ -565,18 +565,18 @@ export const useSwap = () => { } } - if (quoteResponse.response.zeroExQuote) { + if (quoteResponse.response.zeroExQuote?.quote) { if (params.editingFromOrToAmount === 'from') { setToAmount( getZeroExToAmount({ - quote: quoteResponse.response.zeroExQuote, + quote: quoteResponse.response.zeroExQuote.quote, toToken: params.toToken }).format(6) ) } else { setFromAmount( getZeroExFromAmount({ - quote: quoteResponse.response.zeroExQuote, + quote: quoteResponse.response.zeroExQuote.quote, fromToken: params.fromToken }).format(6) ) @@ -1006,15 +1006,12 @@ export const useSwap = () => { return 'insufficientAllowance' } - if (quoteErrorUnion?.zeroExError) { - if (quoteErrorUnion.zeroExError.isInsufficientLiquidity) { - return 'insufficientLiquidity' - } - - if (quoteErrorUnion.zeroExError.isInsufficientAllowance) { - return 'insufficientAllowance' - } + // 0x specific validations + if (quoteUnion?.zeroExQuote?.liquidityAvailable === false) { + return 'insufficientLiquidity' + } + if (quoteErrorUnion?.zeroExError) { return 'unknownError' } diff --git a/components/brave_wallet_ui/page/screens/swap/hooks/useZeroEx.ts b/components/brave_wallet_ui/page/screens/swap/hooks/useZeroEx.ts index b52885a63025..cef442252806 100644 --- a/components/brave_wallet_ui/page/screens/swap/hooks/useZeroEx.ts +++ b/components/brave_wallet_ui/page/screens/swap/hooks/useZeroEx.ts @@ -112,7 +112,7 @@ export function useZeroEx(params: SwapParams) { return } - const { data, to, value, estimatedGas } = + const { data, to, value, gas } = transactionResponse.response.zeroExTransaction try { @@ -120,7 +120,7 @@ export function useZeroEx(params: SwapParams) { fromAccount, to, value: new Amount(value).toHex(), - gasLimit: new Amount(estimatedGas).toHex(), + gasLimit: new Amount(gas).toHex(), data: hexStrToNumberArray(data), network: fromNetwork }) diff --git a/components/brave_wallet_ui/page/screens/swap/swap.utils.ts b/components/brave_wallet_ui/page/screens/swap/swap.utils.ts index 04b10559c361..5a4767d25263 100644 --- a/components/brave_wallet_ui/page/screens/swap/swap.utils.ts +++ b/components/brave_wallet_ui/page/screens/swap/swap.utils.ts @@ -71,6 +71,26 @@ export function getZeroExQuoteOptions({ }): QuoteOption[] { const networkFee = getZeroExNetworkFee({ quote, fromNetwork }) + const fromAmount = new Amount(quote.sellAmount).divideByDecimals( + fromToken.decimals + ) + + const toAmount = new Amount(quote.buyAmount).divideByDecimals( + toToken.decimals + ) + + const fromAmountFiat = fromAmount.times( + getTokenPriceAmountFromRegistry(spotPrices, fromToken) + ) + + const toAmountFiat = toAmount.times( + getTokenPriceAmountFromRegistry(spotPrices, toToken) + ) + + const fiatDiff = toAmountFiat.minus(fromAmountFiat) + const fiatDiffRatio = fiatDiff.div(fromAmountFiat) + const impact = fiatDiffRatio.times(100).toAbsoluteValue() + return [ { fromAmount: new Amount(quote.sellAmount).divideByDecimals( @@ -83,11 +103,11 @@ export function getZeroExQuoteOptions({ rate: new Amount(quote.buyAmount) .divideByDecimals(toToken.decimals) .div(new Amount(quote.sellAmount).divideByDecimals(fromToken.decimals)), - impact: new Amount(quote.estimatedPriceImpact), - sources: ensureUnique(quote.sources, 'name') - .map((source) => ({ - name: source.name, - proportion: new Amount(source.proportion) + impact, + sources: ensureUnique(quote.route.fills, 'source') + .map((fill) => ({ + name: fill.source, + proportion: new Amount(fill.proportionBps).times(0.00001) })) .filter((source) => source.proportion.gt(0)), routing: 'split', // 0x supports split routing only From 22110ee612569cea9f24838763080182ecfd917a Mon Sep 17 00:00:00 2001 From: Anirudha Bose Date: Fri, 25 Oct 2024 21:31:22 +0530 Subject: [PATCH 2/8] Fix presubmit errors --- components/brave_wallet/browser/swap_response_parser.cc | 4 ++-- components/brave_wallet/browser/swap_service.cc | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/brave_wallet/browser/swap_response_parser.cc b/components/brave_wallet/browser/swap_response_parser.cc index c63dedfde777..ac06633e5e9e 100644 --- a/components/brave_wallet/browser/swap_response_parser.cc +++ b/components/brave_wallet/browser/swap_response_parser.cc @@ -255,8 +255,8 @@ mojom::ZeroExTransactionPtr ParseTransactionResponse( mojom::ZeroExErrorPtr ParseErrorResponse(const base::Value& json_value) { // { - // "code": "SWAP_VALIDATION_FAILED", - // "message": "Validation Failed" + // "code": "SWAP_VALIDATION_FAILED", + // "message": "Validation Failed" // } auto swap_error_response_value = swap_responses::ZeroExErrorResponse::FromValue(json_value); diff --git a/components/brave_wallet/browser/swap_service.cc b/components/brave_wallet/browser/swap_service.cc index 6971da9426e8..4011898410b1 100644 --- a/components/brave_wallet/browser/swap_service.cc +++ b/components/brave_wallet/browser/swap_service.cc @@ -5,6 +5,7 @@ #include "brave/components/brave_wallet/browser/swap_service.h" +#include #include #include From a6df80cd6cbd795bcbdb74cb41cda0f07b653839 Mon Sep 17 00:00:00 2001 From: Anirudha Bose Date: Wed, 30 Oct 2024 12:15:00 +0530 Subject: [PATCH 3/8] review(supermassive): return std::nullopt instead of empty string --- .../brave_wallet/browser/brave_wallet_constants.cc | 10 ++++++---- .../brave_wallet/browser/brave_wallet_constants.h | 3 ++- .../brave_wallet/browser/swap_response_parser.cc | 3 ++- components/brave_wallet/browser/swap_service.cc | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/components/brave_wallet/browser/brave_wallet_constants.cc b/components/brave_wallet/browser/brave_wallet_constants.cc index f7ff635cbb52..27b90685f7a2 100644 --- a/components/brave_wallet/browser/brave_wallet_constants.cc +++ b/components/brave_wallet/browser/brave_wallet_constants.cc @@ -101,7 +101,8 @@ const base::flat_map& GetAnkrBlockchains() { } // See https://0x.org/docs/introduction/0x-cheat-sheet#allowanceholder-address -const std::string GetZeroExAllowanceHolderAddress(const std::string& chain_id) { +std::optional GetZeroExAllowanceHolderAddress( + const std::string& chain_id) { // key = chain_id, value = allowance_holder_contract_address static std::map allowance_holder_addresses = { {mojom::kMainnetChainId, kZeroExAllowanceHolderCancun}, @@ -120,9 +121,10 @@ const std::string GetZeroExAllowanceHolderAddress(const std::string& chain_id) { if (allowance_holder_address_pair == allowance_holder_addresses.end()) { // not found - return ""; - } else { - return allowance_holder_address_pair->second; + return std::nullopt; } + + return allowance_holder_address_pair->second; } + } // namespace brave_wallet diff --git a/components/brave_wallet/browser/brave_wallet_constants.h b/components/brave_wallet/browser/brave_wallet_constants.h index 092b87517eb0..be10dd772d01 100644 --- a/components/brave_wallet/browser/brave_wallet_constants.h +++ b/components/brave_wallet/browser/brave_wallet_constants.h @@ -1700,7 +1700,8 @@ const base::flat_map& GetAnkrBlockchains(); // https://docs.rs/solana-program/1.18.10/src/solana_program/clock.rs.html#129-131 inline constexpr int kSolanaValidBlockHeightThreshold = 150; -const std::string GetZeroExAllowanceHolderAddress(const std::string& chain_id); +std::optional GetZeroExAllowanceHolderAddress( + const std::string& chain_id); } // namespace brave_wallet diff --git a/components/brave_wallet/browser/swap_response_parser.cc b/components/brave_wallet/browser/swap_response_parser.cc index ac06633e5e9e..4f112a445642 100644 --- a/components/brave_wallet/browser/swap_response_parser.cc +++ b/components/brave_wallet/browser/swap_response_parser.cc @@ -218,7 +218,8 @@ mojom::ZeroExQuoteInfoPtr ParseQuoteResponse(const base::Value& json_value, } auto swap_response = mojom::ZeroExQuoteInfo::New(); - swap_response->allowance_target = GetZeroExAllowanceHolderAddress(chain_id); + swap_response->allowance_target = + GetZeroExAllowanceHolderAddress(chain_id).value_or(""); if (!swap_response_value->liquidity_available) { swap_response->liquidity_available = false; return swap_response; diff --git a/components/brave_wallet/browser/swap_service.cc b/components/brave_wallet/browser/swap_service.cc index 4011898410b1..f8a0c7f49ecf 100644 --- a/components/brave_wallet/browser/swap_service.cc +++ b/components/brave_wallet/browser/swap_service.cc @@ -59,7 +59,7 @@ namespace { // Ref: https://0x.org/docs/introduction/0x-cheat-sheet#-chain-support bool IsNetworkSupportedByZeroEx(const std::string& chain_id) { - return !GetZeroExAllowanceHolderAddress(chain_id).empty(); + return GetZeroExAllowanceHolderAddress(chain_id).has_value(); } bool IsNetworkSupportedByJupiter(const std::string& chain_id) { From 21ac0e1a62e536a5275548b08222ba1ca01ecd78 Mon Sep 17 00:00:00 2001 From: Anirudha Bose Date: Wed, 30 Oct 2024 12:19:49 +0530 Subject: [PATCH 4/8] review(supermassive): separate function for 0x headers --- .../brave_wallet/browser/swap_service.cc | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/components/brave_wallet/browser/swap_service.cc b/components/brave_wallet/browser/swap_service.cc index f8a0c7f49ecf..13ca3d778c78 100644 --- a/components/brave_wallet/browser/swap_service.cc +++ b/components/brave_wallet/browser/swap_service.cc @@ -215,12 +215,13 @@ GURL AppendJupiterQuoteParams(const GURL& swap_url, return url; } -base::flat_map GetHeaders(bool is_zero_ex = false) { - base::flat_map headers = { - {kBraveServicesKeyHeader, BUILDFLAG(BRAVE_SERVICES_KEY)}}; - if (is_zero_ex) { - headers[kZeroExAPIVersionHeader] = kZeroExAPIVersion; - } +base::flat_map GetHeaders() { + return {{kBraveServicesKeyHeader, BUILDFLAG(BRAVE_SERVICES_KEY)}}; +} + +base::flat_map GetHeadersForZeroEx() { + auto headers = GetHeaders(); + headers[kZeroExAPIVersionHeader] = kZeroExAPIVersion; return headers; } @@ -381,8 +382,9 @@ void SwapService::GetQuote(mojom::SwapQuoteParamsPtr params, api_request_helper_.Request(net::HttpRequestHeaders::kGetMethod, GetZeroExQuoteURL(*params, fee_param), "", "", - std::move(internal_callback), GetHeaders(true), - {}, std::move(conversion_callback)); + std::move(internal_callback), + GetHeadersForZeroEx(), {}, + std::move(conversion_callback)); return; } @@ -556,7 +558,7 @@ void SwapService::GetTransaction(mojom::SwapTransactionParamsUnionPtr params, net::HttpRequestHeaders::kGetMethod, GetZeroExTransactionURL(*params->get_zero_ex_transaction_params(), swap_fee->fee_param), - "", "", std::move(internal_callback), GetHeaders(true), {}, + "", "", std::move(internal_callback), GetHeadersForZeroEx(), {}, std::move(conversion_callback)); return; From dcf461624fec7eca206d06d9ba5cbc62775b406d Mon Sep 17 00:00:00 2001 From: Anirudha Bose Date: Wed, 30 Oct 2024 12:21:39 +0530 Subject: [PATCH 5/8] review(supermassive): concise if-condition --- components/brave_wallet/browser/swap_response_parser.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/brave_wallet/browser/swap_response_parser.cc b/components/brave_wallet/browser/swap_response_parser.cc index 4f112a445642..b51e9acfff39 100644 --- a/components/brave_wallet/browser/swap_response_parser.cc +++ b/components/brave_wallet/browser/swap_response_parser.cc @@ -225,7 +225,7 @@ mojom::ZeroExQuoteInfoPtr ParseQuoteResponse(const base::Value& json_value, return swap_response; } - if (auto quote = ParseQuote(swap_response_value.value()); quote) { + if (auto quote = ParseQuote(swap_response_value.value())) { swap_response->quote = std::move(quote); } else { return nullptr; From 8b20e1190d0c4cc13bec9af0c5b6529415b9a83f Mon Sep 17 00:00:00 2001 From: Anirudha Bose Date: Wed, 30 Oct 2024 13:03:16 +0530 Subject: [PATCH 6/8] review(supermassive): use base::NoDestructor --- .../browser/brave_wallet_constants.cc | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/components/brave_wallet/browser/brave_wallet_constants.cc b/components/brave_wallet/browser/brave_wallet_constants.cc index 27b90685f7a2..de46375488f9 100644 --- a/components/brave_wallet/browser/brave_wallet_constants.cc +++ b/components/brave_wallet/browser/brave_wallet_constants.cc @@ -104,22 +104,23 @@ const base::flat_map& GetAnkrBlockchains() { std::optional GetZeroExAllowanceHolderAddress( const std::string& chain_id) { // key = chain_id, value = allowance_holder_contract_address - static std::map allowance_holder_addresses = { - {mojom::kMainnetChainId, kZeroExAllowanceHolderCancun}, - {mojom::kArbitrumMainnetChainId, kZeroExAllowanceHolderCancun}, - {mojom::kAvalancheMainnetChainId, kZeroExAllowanceHolderShanghai}, - {mojom::kBaseMainnetChainId, kZeroExAllowanceHolderCancun}, - {mojom::kBlastMainnetChainId, kZeroExAllowanceHolderCancun}, - {mojom::kBnbSmartChainMainnetChainId, kZeroExAllowanceHolderCancun}, - {mojom::kLineaChainId, kZeroExAllowanceHolderLondon}, - {mojom::kOptimismMainnetChainId, kZeroExAllowanceHolderCancun}, - {mojom::kPolygonMainnetChainId, kZeroExAllowanceHolderCancun}, - {mojom::kScrollChainId, kZeroExAllowanceHolderShanghai}}; + static base::NoDestructor> + allowance_holder_addresses( + {{mojom::kMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kArbitrumMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kAvalancheMainnetChainId, kZeroExAllowanceHolderShanghai}, + {mojom::kBaseMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kBlastMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kBnbSmartChainMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kLineaChainId, kZeroExAllowanceHolderLondon}, + {mojom::kOptimismMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kPolygonMainnetChainId, kZeroExAllowanceHolderCancun}, + {mojom::kScrollChainId, kZeroExAllowanceHolderShanghai}}); auto allowance_holder_address_pair = - allowance_holder_addresses.find(chain_id.c_str()); + allowance_holder_addresses->find(chain_id.c_str()); - if (allowance_holder_address_pair == allowance_holder_addresses.end()) { + if (allowance_holder_address_pair == allowance_holder_addresses->end()) { // not found return std::nullopt; } From 1bbee57c1c8414b6fb3fe409da02134df3741f56 Mon Sep 17 00:00:00 2001 From: Anirudha Bose Date: Wed, 30 Oct 2024 13:07:36 +0530 Subject: [PATCH 7/8] review(nuo-xu): reuse ZeroExQuote and drop ZeroExQuoteInfo struct --- .../browser/swap_response_parser.cc | 17 +++-- .../browser/swap_response_parser.h | 4 +- .../browser/swap_response_parser_unittest.cc | 69 ++++++++---------- .../brave_wallet/browser/swap_service.cc | 12 ++++ .../browser/swap_service_unittest.cc | 70 +++++++++++++------ .../brave_wallet/common/brave_wallet.mojom | 9 +-- .../page/screens/swap/hooks/useSwap.ts | 18 ++--- 7 files changed, 114 insertions(+), 85 deletions(-) diff --git a/components/brave_wallet/browser/swap_response_parser.cc b/components/brave_wallet/browser/swap_response_parser.cc index b51e9acfff39..0ff8efa859fd 100644 --- a/components/brave_wallet/browser/swap_response_parser.cc +++ b/components/brave_wallet/browser/swap_response_parser.cc @@ -141,8 +141,8 @@ mojom::ZeroExQuotePtr ParseQuote( } // namespace -mojom::ZeroExQuoteInfoPtr ParseQuoteResponse(const base::Value& json_value, - const std::string& chain_id) { +mojom::ZeroExQuotePtr ParseQuoteResponse(const base::Value& json_value, + const std::string& chain_id) { // { // "blockNumber": "20114692", // "buyAmount": "100037537", @@ -217,21 +217,20 @@ mojom::ZeroExQuoteInfoPtr ParseQuoteResponse(const base::Value& json_value, return nullptr; } - auto swap_response = mojom::ZeroExQuoteInfo::New(); - swap_response->allowance_target = - GetZeroExAllowanceHolderAddress(chain_id).value_or(""); + auto swap_response = mojom::ZeroExQuote::New(); + if (!swap_response_value->liquidity_available) { swap_response->liquidity_available = false; return swap_response; } - if (auto quote = ParseQuote(swap_response_value.value())) { - swap_response->quote = std::move(quote); - } else { + swap_response = ParseQuote(swap_response_value.value()); + if (!swap_response) { return nullptr; } - swap_response->liquidity_available = swap_response_value->liquidity_available; + swap_response->allowance_target = + GetZeroExAllowanceHolderAddress(chain_id).value_or(""); return swap_response; } diff --git a/components/brave_wallet/browser/swap_response_parser.h b/components/brave_wallet/browser/swap_response_parser.h index 36140791b4d1..0acb01656120 100644 --- a/components/brave_wallet/browser/swap_response_parser.h +++ b/components/brave_wallet/browser/swap_response_parser.h @@ -15,8 +15,8 @@ namespace brave_wallet { namespace zeroex { -mojom::ZeroExQuoteInfoPtr ParseQuoteResponse(const base::Value& json_value, - const std::string& chain_id); +mojom::ZeroExQuotePtr ParseQuoteResponse(const base::Value& json_value, + const std::string& chain_id); mojom::ZeroExTransactionPtr ParseTransactionResponse( const base::Value& json_value); mojom::ZeroExErrorPtr ParseErrorResponse(const base::Value& json_value); diff --git a/components/brave_wallet/browser/swap_response_parser_unittest.cc b/components/brave_wallet/browser/swap_response_parser_unittest.cc index 53ac73fdd90b..d701380cfae9 100644 --- a/components/brave_wallet/browser/swap_response_parser_unittest.cc +++ b/components/brave_wallet/browser/swap_response_parser_unittest.cc @@ -153,40 +153,37 @@ TEST(SwapResponseParserUnitTest, ParseZeroExQuoteResponse) { "zid": "0x111111111111111111111111" } )"); - auto quote_info = + auto quote = zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId); - ASSERT_TRUE(quote_info); - ASSERT_TRUE(quote_info->quote); + ASSERT_TRUE(quote); - EXPECT_EQ(quote_info->quote->buy_amount, "100032748"); - EXPECT_EQ(quote_info->quote->buy_token, - "0xdac17f958d2ee523a2206206994597c13d831ec7"); + EXPECT_EQ(quote->buy_amount, "100032748"); + EXPECT_EQ(quote->buy_token, "0xdac17f958d2ee523a2206206994597c13d831ec7"); - ASSERT_TRUE(quote_info->quote->fees->zero_ex_fee); - EXPECT_EQ(quote_info->quote->fees->zero_ex_fee->amount, "0"); - EXPECT_EQ(quote_info->quote->fees->zero_ex_fee->token, "0xdeadbeef"); - EXPECT_EQ(quote_info->quote->fees->zero_ex_fee->type, "volume"); + ASSERT_TRUE(quote->fees->zero_ex_fee); + EXPECT_EQ(quote->fees->zero_ex_fee->amount, "0"); + EXPECT_EQ(quote->fees->zero_ex_fee->token, "0xdeadbeef"); + EXPECT_EQ(quote->fees->zero_ex_fee->type, "volume"); - EXPECT_EQ(quote_info->quote->gas, "288095"); - EXPECT_EQ(quote_info->quote->gas_price, "7062490000"); - EXPECT_EQ(quote_info->quote->liquidity_available, true); - EXPECT_EQ(quote_info->quote->min_buy_amount, "99032421"); + EXPECT_EQ(quote->gas, "288095"); + EXPECT_EQ(quote->gas_price, "7062490000"); + EXPECT_EQ(quote->liquidity_available, true); + EXPECT_EQ(quote->min_buy_amount, "99032421"); - ASSERT_EQ(quote_info->quote->route->fills.size(), 1UL); - EXPECT_EQ(quote_info->quote->route->fills.at(0)->from, + ASSERT_EQ(quote->route->fills.size(), 1UL); + EXPECT_EQ(quote->route->fills.at(0)->from, "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); - EXPECT_EQ(quote_info->quote->route->fills.at(0)->to, + EXPECT_EQ(quote->route->fills.at(0)->to, "0xdac17f958d2ee523a2206206994597c13d831ec7"); - EXPECT_EQ(quote_info->quote->route->fills.at(0)->source, "SolidlyV3"); - EXPECT_EQ(quote_info->quote->route->fills.at(0)->proportion_bps, "10000"); + EXPECT_EQ(quote->route->fills.at(0)->source, "SolidlyV3"); + EXPECT_EQ(quote->route->fills.at(0)->proportion_bps, "10000"); - EXPECT_EQ(quote_info->quote->sell_amount, "100000000"); - EXPECT_EQ(quote_info->quote->sell_token, - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); - EXPECT_EQ(quote_info->quote->total_network_fee, "2034668056550000"); + EXPECT_EQ(quote->sell_amount, "100000000"); + EXPECT_EQ(quote->sell_token, "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"); + EXPECT_EQ(quote->total_network_fee, "2034668056550000"); - EXPECT_EQ(quote_info->liquidity_available, true); - EXPECT_EQ(quote_info->allowance_target, + EXPECT_EQ(quote->liquidity_available, true); + EXPECT_EQ(quote->allowance_target, "0x0000000000001fF3684f28c67538d4D072C22734"); // Case 2: null zeroExFee @@ -253,12 +250,11 @@ TEST(SwapResponseParserUnitTest, ParseZeroExQuoteResponse) { "zid": "0x111111111111111111111111" } )"; - quote_info = - zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId); - ASSERT_TRUE(quote_info); - EXPECT_FALSE(quote_info->quote->fees->zero_ex_fee); - EXPECT_EQ(quote_info->liquidity_available, true); - EXPECT_EQ(quote_info->allowance_target, + quote = zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId); + ASSERT_TRUE(quote); + EXPECT_FALSE(quote->fees->zero_ex_fee); + EXPECT_EQ(quote->liquidity_available, true); + EXPECT_EQ(quote->allowance_target, "0x0000000000001fF3684f28c67538d4D072C22734"); // Case 3: malformed fees field @@ -330,13 +326,10 @@ TEST(SwapResponseParserUnitTest, ParseZeroExQuoteResponse) { "liquidityAvailable": false, } )"; - quote_info = - zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId); - ASSERT_TRUE(quote_info); - EXPECT_FALSE(quote_info->liquidity_available); - EXPECT_EQ(quote_info->allowance_target, - "0x0000000000001fF3684f28c67538d4D072C22734"); - EXPECT_FALSE(quote_info->quote); + quote = zeroex::ParseQuoteResponse(ParseJson(json), mojom::kMainnetChainId); + ASSERT_TRUE(quote); + EXPECT_FALSE(quote->liquidity_available); + EXPECT_EQ(quote->buy_token, ""); // Case 5: other invalid cases json = R"({"totalNetworkFee": "2034668056550000"})"; diff --git a/components/brave_wallet/browser/swap_service.cc b/components/brave_wallet/browser/swap_service.cc index 13ca3d778c78..20a45b0dd746 100644 --- a/components/brave_wallet/browser/swap_service.cc +++ b/components/brave_wallet/browser/swap_service.cc @@ -439,6 +439,18 @@ void SwapService::OnGetZeroExQuote(const std::string& chain_id, if (auto swap_response = zeroex::ParseQuoteResponse( api_request_result.value_body(), chain_id)) { + if (!swap_response->liquidity_available) { + std::move(callback).Run( + nullptr, nullptr, + mojom::SwapErrorUnion::NewZeroExError(mojom::ZeroExError::New( + "INSUFFICIENT_LIQUIDITY", + l10n_util::GetStringUTF8( + IDS_BRAVE_WALLET_SWAP_INSUFFICIENT_LIQUIDITY), + true)), + ""); + return; + } + std::move(callback).Run( mojom::SwapQuoteUnion::NewZeroExQuote(std::move(swap_response)), std::move(swap_fee), nullptr, ""); diff --git a/components/brave_wallet/browser/swap_service_unittest.cc b/components/brave_wallet/browser/swap_service_unittest.cc index dc8ceb232545..0749d1932ae6 100644 --- a/components/brave_wallet/browser/swap_service_unittest.cc +++ b/components/brave_wallet/browser/swap_service_unittest.cc @@ -556,37 +556,36 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuote) { } )"); - auto zero_ex_quote = mojom::ZeroExQuote::New(); - zero_ex_quote->buy_amount = "100032748"; - zero_ex_quote->buy_token = "0xdac17f958d2ee523a2206206994597c13d831ec7"; - zero_ex_quote->sell_amount = "100000000"; - zero_ex_quote->sell_token = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; + auto expected_zero_ex_quote = mojom::ZeroExQuote::New(); + expected_zero_ex_quote->buy_amount = "100032748"; + expected_zero_ex_quote->buy_token = + "0xdac17f958d2ee523a2206206994597c13d831ec7"; + expected_zero_ex_quote->sell_amount = "100000000"; + expected_zero_ex_quote->sell_token = + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; auto zero_ex_fee = mojom::ZeroExFee::New(); zero_ex_fee->type = "volume"; zero_ex_fee->token = "0xdeadbeef"; zero_ex_fee->amount = "0"; - zero_ex_quote->fees = mojom::ZeroExFees::New(); - zero_ex_quote->fees->zero_ex_fee = std::move(zero_ex_fee); + expected_zero_ex_quote->fees = mojom::ZeroExFees::New(); + expected_zero_ex_quote->fees->zero_ex_fee = std::move(zero_ex_fee); - zero_ex_quote->gas = "288095"; - zero_ex_quote->gas_price = "7062490000"; - zero_ex_quote->liquidity_available = true; - zero_ex_quote->min_buy_amount = "99032421"; - zero_ex_quote->total_network_fee = "2034668056550000"; + expected_zero_ex_quote->gas = "288095"; + expected_zero_ex_quote->gas_price = "7062490000"; + expected_zero_ex_quote->liquidity_available = true; + expected_zero_ex_quote->min_buy_amount = "99032421"; + expected_zero_ex_quote->total_network_fee = "2034668056550000"; auto fill = mojom::ZeroExRouteFill::New(); fill->from = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; fill->to = "0xdac17f958d2ee523a2206206994597c13d831ec7"; fill->source = "SolidlyV3"; fill->proportion_bps = "10000"; - zero_ex_quote->route = mojom::ZeroExRoute::New(); - zero_ex_quote->route->fills.push_back(fill.Clone()); + expected_zero_ex_quote->route = mojom::ZeroExRoute::New(); + expected_zero_ex_quote->route->fills.push_back(fill.Clone()); - auto expected_zero_ex_quote_info = mojom::ZeroExQuoteInfo::New(); - expected_zero_ex_quote_info->quote = std::move(zero_ex_quote); - expected_zero_ex_quote_info->liquidity_available = true; - expected_zero_ex_quote_info->allowance_target = + expected_zero_ex_quote->allowance_target = "0x0000000000001fF3684f28c67538d4D072C22734"; auto expected_swap_fees = mojom::SwapFees::New(); @@ -598,7 +597,7 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuote) { base::MockCallback callback; EXPECT_CALL(callback, Run(EqualsMojo(mojom::SwapQuoteUnion::NewZeroExQuote( - expected_zero_ex_quote_info.Clone())), + expected_zero_ex_quote.Clone())), EqualsMojo(expected_swap_fees.Clone()), EqualsMojo(mojom::SwapErrorUnionPtr()), "")); @@ -676,9 +675,9 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuote) { } )"); - expected_zero_ex_quote_info->quote->fees->zero_ex_fee = nullptr; + expected_zero_ex_quote->fees->zero_ex_fee = nullptr; EXPECT_CALL(callback, Run(EqualsMojo(mojom::SwapQuoteUnion::NewZeroExQuote( - std::move(expected_zero_ex_quote_info))), + std::move(expected_zero_ex_quote))), EqualsMojo(expected_swap_fees.Clone()), EqualsMojo(mojom::SwapErrorUnionPtr()), "")); @@ -693,6 +692,7 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuote) { } TEST_F(SwapServiceUnitTest, GetZeroExQuoteError) { + // Case 1: validation error std::string error = R"( { "name": "INPUT_INVALID", @@ -714,6 +714,34 @@ TEST_F(SwapServiceUnitTest, GetZeroExQuoteError) { mojom::SwapProvider::kZeroEx), callback.Get()); task_environment_.RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(&callback); + + // Case 2: insufficient liquidity + SetInterceptor(R"( + { + "liquidityAvailable": false, + "zid": "0x111111111111111111111111" + } + )"); + auto error_response = mojom::ZeroExError::New(); + error_response->name = "INSUFFICIENT_LIQUIDITY"; + error_response->message = + l10n_util::GetStringUTF8(IDS_BRAVE_WALLET_SWAP_INSUFFICIENT_LIQUIDITY); + error_response->is_insufficient_liquidity = true; + + EXPECT_CALL(callback, Run(EqualsMojo(mojom::SwapQuoteUnionPtr()), + EqualsMojo(mojom::SwapFeesPtr()), + EqualsMojo(mojom::SwapErrorUnion::NewZeroExError( + std::move(error_response))), + "")); + + swap_service_->GetQuote( + GetCannedSwapQuoteParams( + mojom::CoinType::ETH, mojom::kPolygonMainnetChainId, "DAI", + mojom::CoinType::ETH, mojom::kPolygonMainnetChainId, "ETH", + mojom::SwapProvider::kZeroEx), + callback.Get()); + task_environment_.RunUntilIdle(); } TEST_F(SwapServiceUnitTest, GetZeroExQuoteUnexpectedReturn) { diff --git a/components/brave_wallet/common/brave_wallet.mojom b/components/brave_wallet/common/brave_wallet.mojom index 6c43f9aa1e49..cc8fa0c4dd0c 100644 --- a/components/brave_wallet/common/brave_wallet.mojom +++ b/components/brave_wallet/common/brave_wallet.mojom @@ -448,7 +448,7 @@ union SwapTransactionParamsUnion { union SwapQuoteUnion { JupiterQuote jupiter_quote; - ZeroExQuoteInfo zero_ex_quote; + ZeroExQuote zero_ex_quote; LiFiQuote lifi_quote; SquidQuote squid_quote; }; @@ -501,18 +501,15 @@ struct ZeroExQuote { string sell_amount; string sell_token; string total_network_fee; -}; -struct ZeroExQuoteInfo { - // quote is undefined if liquidity_available is false - ZeroExQuote? quote; + // Custom field string allowance_target; - bool liquidity_available; }; struct ZeroExError { string name; string message; + bool is_insufficient_liquidity; }; union SwapErrorUnion { diff --git a/components/brave_wallet_ui/page/screens/swap/hooks/useSwap.ts b/components/brave_wallet_ui/page/screens/swap/hooks/useSwap.ts index d491d12f11c4..a0dab4ff549d 100644 --- a/components/brave_wallet_ui/page/screens/swap/hooks/useSwap.ts +++ b/components/brave_wallet_ui/page/screens/swap/hooks/useSwap.ts @@ -308,9 +308,9 @@ export const useSwap = () => { }) } - if (quoteUnion.zeroExQuote?.quote) { + if (quoteUnion.zeroExQuote) { return getZeroExQuoteOptions({ - quote: quoteUnion.zeroExQuote.quote, + quote: quoteUnion.zeroExQuote, fromNetwork, fromToken, toToken, @@ -565,18 +565,18 @@ export const useSwap = () => { } } - if (quoteResponse.response.zeroExQuote?.quote) { + if (quoteResponse.response.zeroExQuote) { if (params.editingFromOrToAmount === 'from') { setToAmount( getZeroExToAmount({ - quote: quoteResponse.response.zeroExQuote.quote, + quote: quoteResponse.response.zeroExQuote, toToken: params.toToken }).format(6) ) } else { setFromAmount( getZeroExFromAmount({ - quote: quoteResponse.response.zeroExQuote.quote, + quote: quoteResponse.response.zeroExQuote, fromToken: params.fromToken }).format(6) ) @@ -1007,11 +1007,11 @@ export const useSwap = () => { } // 0x specific validations - if (quoteUnion?.zeroExQuote?.liquidityAvailable === false) { - return 'insufficientLiquidity' - } - if (quoteErrorUnion?.zeroExError) { + if (quoteErrorUnion.zeroExError.isInsufficientLiquidity) { + return 'insufficientLiquidity' + } + return 'unknownError' } From bbcccca0f9343685f19ae22007027fbb51f3a42a Mon Sep 17 00:00:00 2001 From: Anirudha Bose Date: Wed, 30 Oct 2024 17:23:38 +0530 Subject: [PATCH 8/8] Fix storybook build --- .../common/async/__mocks__/bridge.ts | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/components/brave_wallet_ui/common/async/__mocks__/bridge.ts b/components/brave_wallet_ui/common/async/__mocks__/bridge.ts index f909c27f44e6..c3bb70ed175e 100644 --- a/components/brave_wallet_ui/common/async/__mocks__/bridge.ts +++ b/components/brave_wallet_ui/common/async/__mocks__/bridge.ts @@ -140,29 +140,30 @@ export class MockedWalletApiProxy { createEmptyTokenBalancesRegistry() mockZeroExQuote = { - price: '1705.399509', - guaranteedPrice: '', - to: '', - data: '', - value: '124067000000000000', - gas: '280000', - estimatedGas: '280000', - gasPrice: '2000000000', - protocolFee: '0', - minimumProtocolFee: '0', - sellTokenAddress: '0x07865c6e87b9f70255377e024ace6630c1eaa37f', - buyTokenAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - buyAmount: '211599920', - sellAmount: '124067000000000000', - allowanceTarget: '0x0000000000000000000000000000000000000000', - sellTokenToEthRate: '1', - buyTokenToEthRate: '1720.180416', - estimatedPriceImpact: '0.0782', - sources: [], + buyAmount: '100032748', + buyToken: '0xdac17f958d2ee523a2206206994597c13d831ec7', + sellAmount: '100000000', + sellToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', fees: { zeroExFee: undefined - } - } + }, + gas: '288095', + gasPrice: '7062490000', + liquidityAvailable: true, + minBuyAmount: '99032421', + route: { + fills: [ + { + from: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + to: '0xdac17f958d2ee523a2206206994597c13d831ec7', + source: 'SolidlyV3', + proportionBps: '10000' + } + ] + }, + totalNetworkFee: '2034668056550000', + allowanceTarget: '0x0000000000001fF3684f28c67538d4D072C22734' + } as BraveWallet.ZeroExQuote mockZeroExTransaction = { allowanceTarget: '', @@ -521,19 +522,15 @@ export class MockedWalletApiProxy { } } - const { fromToken, toToken, fromAmount, toAmount } = - zeroExTransactionParams - return { error: null, response: { zeroExTransaction: { - ...this.mockZeroExQuote, - buyTokenAddress: toToken, - sellTokenAddress: fromToken, - buyAmount: toAmount || '', - sellAmount: fromAmount || '', - price: '1' + to: '0x7f6cee965959295cc64d0e6c00d99d6532d8e86b', + data: '0xdeadbeef', + gas: '288079', + gasPrice: '4837860000', + value: '0' }, jupiterTransaction: undefined, lifiTransaction: undefined, @@ -1401,7 +1398,7 @@ export class MockedWalletApiProxy { this.mockZeroExQuote = newQuote } - setMockedTransactionPayload(newTx: typeof this.mockZeroExQuote) { + setMockedTransactionPayload(newTx: typeof this.mockZeroExTransaction) { this.mockZeroExTransaction = newTx }