Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Fix #8590: Wallet Swap Service Refactor also Bump Core to 1.63.90 #8613

Merged
merged 4 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 54 additions & 57 deletions Sources/BraveWallet/Crypto/Stores/SwapTokenStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
didSet {
if sellAmount != oldValue {
// sell amount changed, new quotes are needed
swapResponse = nil
zeroExQuote = nil
jupiterQuote = nil
braveFee = nil
// price quote requested for a different amount
Expand Down Expand Up @@ -117,9 +117,9 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
@Published var isUpdatingPriceQuote = false
/// The brave fee for the current price quote
@Published var braveFee: BraveWallet.BraveSwapFeeResponse?
/// The SwapResponse / price quote currently being displayed for Ethereum swap.
/// The ZeroExQuote / price quote currently being displayed for Ethereum swap.
/// The quote needs preserved to know when to show `protocolFeesForDisplay` fees.
@Published var swapResponse: BraveWallet.SwapResponse?
@Published var zeroExQuote: BraveWallet.ZeroExQuote?

/// If the Brave Fee is voided for this swap.
var isBraveFeeVoided: Bool {
Expand Down Expand Up @@ -150,7 +150,7 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
guard let braveFee,
let protocolFeePct = Double(braveFee.protocolFeePct),
!protocolFeePct.isZero else { return nil }
if let swapResponse, swapResponse.fees.zeroExFee == nil {
if let zeroExQuote, zeroExQuote.fees.zeroExFee == nil {
// `protocolFeePct` should only be surfaced to users if `zeroExFee` is non-null.
return nil
}
Expand Down Expand Up @@ -308,10 +308,10 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
}
}

private func swapParameters(
private func swapQuoteParameters(
for base: SwapParamsBase,
in network: BraveWallet.NetworkInfo
) -> BraveWallet.SwapParams? {
) -> BraveWallet.SwapQuoteParams? {
guard
let accountInfo = accountInfo,
let sellToken = selectedFromToken,
Expand Down Expand Up @@ -340,18 +340,22 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
sellAmountInWei = ""
buyAmountInWei = weiFormatter.weiString(from: buyAmount.normalizedDecimals, radix: .decimal, decimals: Int(buyToken.decimals)) ?? "0"
}
let swapParams = BraveWallet.SwapParams(
chainId: network.chainId,
takerAddress: accountInfo.address,
sellAmount: sellAmountInWei,
buyAmount: buyAmountInWei,
buyToken: buyAddress,
sellToken: sellAddress,
slippagePercentage: slippage,
gasPrice: ""
// We store 0.5% as 0.005, so multiply by 100 to get 0.5
let slippagePercentage = slippage * 100
let swapQuoteParams = BraveWallet.SwapQuoteParams(
from: accountInfo.accountId,
fromChainId: network.chainId,
fromToken: sellAddress,
fromAmount: sellAmountInWei,
to: accountInfo.accountId,
toChainId: network.chainId,
toToken: buyAddress,
toAmount: buyAmountInWei,
slippagePercentage: "\(slippagePercentage)",
routePriority: .recommended
)

return swapParams
return swapQuoteParams
}

@MainActor private func createEthSwapTransaction() async -> Bool {
Expand All @@ -364,13 +368,13 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
defer { self.isMakingTx = false }
let coin = accountInfo.coin
let network = await rpcService.network(coin, origin: nil)
guard let swapParams = self.swapParameters(for: .perSellAsset, in: network) else {
guard let swapQuoteParams = self.swapQuoteParameters(for: .perSellAsset, in: network) else {
self.state = .error(Strings.Wallet.unknownError)
self.clearAllAmount()
return false
}
let (swapResponse, _, _) = await swapService.transactionPayload(swapParams)
guard let swapResponse = swapResponse else {
let (swapTransactionUnion, _, _) = await swapService.transaction(.init(zeroExTransactionParams: swapQuoteParams))
guard let swapResponse = swapTransactionUnion?.zeroExTransaction else {
self.state = .error(Strings.Wallet.unknownError)
self.clearAllAmount()
return false
Expand Down Expand Up @@ -427,9 +431,9 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {

/// Update price market and sell/buy amount fields based on `SwapParamsBase`
@MainActor private func handleEthPriceQuoteResponse(
_ response: BraveWallet.SwapResponse,
_ response: BraveWallet.ZeroExQuote,
base: SwapParamsBase,
swapParams: BraveWallet.SwapParams
swapQuoteParams: BraveWallet.SwapQuoteParams
) async {
let weiFormatter = WeiFormatter(decimalFormatStyle: .decimals(precision: 18))
switch base {
Expand Down Expand Up @@ -465,12 +469,14 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
}
}

self.swapResponse = response
self.zeroExQuote = response
let network = await rpcService.network(selectedFromToken?.coin ?? .eth, origin: nil)
let (braveSwapFeeResponse, _) = await swapService.braveFee(
.init(
chainId: network.chainId,
swapParams: swapParams
inputToken: swapQuoteParams.fromToken,
outputToken: swapQuoteParams.toToken,
taker: swapQuoteParams.fromAccountId.address
)
)
if let braveSwapFeeResponse {
Expand Down Expand Up @@ -576,7 +582,7 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
return bumpedValue.rounded().asString(radix: 16)
}

@MainActor private func checkBalanceShowError(swapResponse: BraveWallet.SwapResponse) async {
@MainActor private func checkBalanceShowError(swapResponse: BraveWallet.ZeroExQuote) async {
guard
let accountInfo = accountInfo,
let sellAmountValue = BDouble(sellAmount.normalizedDecimals),
Expand Down Expand Up @@ -647,26 +653,16 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
}

@MainActor private func fetchSolPriceQuote(
swapParams: BraveWallet.SwapParams,
swapQuoteParams: BraveWallet.SwapQuoteParams,
network: BraveWallet.NetworkInfo
) async {
// 0.5% is 50bps. We store 0.5% as 0.005, so multiply by 10_000
let slippageBps = Int32(swapParams.slippagePercentage * 10_000)
let jupiterQuoteParams: BraveWallet.JupiterQuoteParams = .init(
chainId: network.chainId,
inputMint: swapParams.sellToken,
outputMint: swapParams.buyToken,
amount: swapParams.sellAmount,
slippageBps: slippageBps,
userPublicKey: swapParams.takerAddress
)
self.isUpdatingPriceQuote = true
let (jupiterQuote, swapErrorResponse, _) = await swapService.jupiterQuote(jupiterQuoteParams)
let (swapQuoteUnion, swapQuoteErrorUnion, _) = await swapService.quote(swapQuoteParams)
defer { self.isUpdatingPriceQuote = false }
guard !Task.isCancelled else { return }
if let jupiterQuote {
await self.handleSolPriceQuoteResponse(jupiterQuote, swapParams: swapParams)
} else if let swapErrorResponse {
if let jupiterQuote = swapQuoteUnion?.jupiterQuote {
await self.handleSolPriceQuoteResponse(jupiterQuote, swapQuoteParams: swapQuoteParams)
} else if let swapErrorResponse = swapQuoteErrorUnion?.jupiterError {
// check balance first because error can be caused by insufficient balance
if let sellTokenBalance = self.selectedFromTokenBalance,
let sellAmountValue = BDouble(self.sellAmount.normalizedDecimals),
Expand All @@ -689,7 +685,7 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {

@MainActor private func handleSolPriceQuoteResponse(
_ response: BraveWallet.JupiterQuote,
swapParams: BraveWallet.SwapParams
swapQuoteParams: BraveWallet.SwapQuoteParams
) async {
guard let route = response.routes.first else { return }
self.jupiterQuote = response
Expand All @@ -715,8 +711,9 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
let (braveSwapFeeResponse, _) = await swapService.braveFee(
.init(
chainId: network.chainId,
swapParams: swapParams
)
inputToken: swapQuoteParams.fromToken,
outputToken: swapQuoteParams.toToken,
taker: swapQuoteParams.fromAccountId.address)
)
if let braveSwapFeeResponse {
self.braveFee = braveSwapFeeResponse
Expand Down Expand Up @@ -754,15 +751,16 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
self.isMakingTx = true
defer { self.isMakingTx = false }
let network = await rpcService.network(.sol, origin: nil)
let jupiterSwapParams: BraveWallet.JupiterSwapParams = .init(

let jupiterSwapParams: BraveWallet.JupiterTransactionParams = .init(
chainId: network.chainId,
route: route,
userPublicKey: accountInfo.address,
inputMint: selectedFromToken.contractAddress(in: network),
outputMint: selectedToToken.contractAddress(in: network)
)
let (swapTransactions, errorResponse, _) = await swapService.jupiterSwapTransactions(jupiterSwapParams)
guard let swapTransactions else {
let (swapTransactionsUnion, errorResponseUnion, _) = await swapService.transaction(.init(jupiterTransactionParams: jupiterSwapParams))
guard let swapTransactions = swapTransactionsUnion?.jupiterTransaction else {
// check balance first because error can cause by insufficient balance
if let sellTokenBalance = self.selectedFromTokenBalance,
let sellAmountValue = BDouble(self.sellAmount.normalizedDecimals),
Expand All @@ -771,15 +769,15 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
return false
}
// check if jupiterQuote fails due to insufficient liquidity
if errorResponse?.isInsufficientLiquidity == true {
if let errorResponse = errorResponseUnion?.jupiterError, errorResponse.isInsufficientLiquidity == true {
self.state = .error(Strings.Wallet.insufficientLiquidity)
return false
}
self.state = .error(Strings.Wallet.unknownError)
return false
}
let (solTxData, status, _) = await solTxManagerProxy.makeTxData(
fromBase64EncodedTransaction: swapTransactions.swapTransaction,
fromBase64EncodedTransaction: swapTransactions,
txType: .solanaSwap,
send: .init(
maxRetries: .init(maxRetries: 2),
Expand Down Expand Up @@ -841,7 +839,7 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
priceQuoteTask?.cancel()
priceQuoteTask = Task { @MainActor in
// reset quotes before fetching new quote
swapResponse = nil
zeroExQuote = nil
jupiterQuote = nil
braveFee = nil
guard let accountInfo else {
Expand All @@ -853,7 +851,7 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
// Entering a buy amount is disabled for Solana swaps, always use
// `SwapParamsBase.perSellAsset` to fetch quote based on the sell amount.
// `SwapParamsBase.perBuyAsset` is sent when `selectedToToken` is changed.
guard let swapParams = self.swapParameters(
guard let swapQuoteParams = self.swapQuoteParameters(
for: accountInfo.coin == .sol ? .perSellAsset : base,
in: network
) else {
Expand All @@ -862,9 +860,9 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {
}
switch accountInfo.coin {
case .eth:
await fetchEthPriceQuote(base: base, swapParams: swapParams, network: network)
await fetchEthPriceQuote(base: base, swapQuoteParams: swapQuoteParams, network: network)
case .sol:
await fetchSolPriceQuote(swapParams: swapParams, network: network)
await fetchSolPriceQuote(swapQuoteParams: swapQuoteParams, network: network)
default:
break
}
Expand All @@ -873,16 +871,15 @@ public class SwapTokenStore: ObservableObject, WalletObserverStore {

@MainActor private func fetchEthPriceQuote(
base: SwapParamsBase,
swapParams: BraveWallet.SwapParams,
swapQuoteParams: BraveWallet.SwapQuoteParams,
network: BraveWallet.NetworkInfo
) async {
self.isUpdatingPriceQuote = true
defer { self.isUpdatingPriceQuote = false }
let (swapResponse, swapErrorResponse, _) = await swapService.priceQuote(swapParams)
guard !Task.isCancelled else { return }
if let swapResponse = swapResponse {
await self.handleEthPriceQuoteResponse(swapResponse, base: base, swapParams: swapParams)
} else if let swapErrorResponse = swapErrorResponse {
let (swapQuoteUnion, swapQuoteErrorUnion, _) = await swapService.quote(swapQuoteParams)
if let swapResponse = swapQuoteUnion?.zeroExQuote {
await self.handleEthPriceQuoteResponse(swapResponse, base: base, swapQuoteParams: swapQuoteParams)
} else if let swapErrorResponse = swapQuoteErrorUnion?.zeroExError {
// check balance first because error can cause by insufficient balance
if let sellTokenBalance = self.selectedFromTokenBalance,
let sellAmountValue = BDouble(self.sellAmount.normalizedDecimals),
Expand Down
11 changes: 0 additions & 11 deletions Sources/BraveWallet/Extensions/BraveWalletExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -523,17 +523,6 @@ extension BraveWallet.KeyringId {
}
}

extension BraveWallet.BraveSwapFeeParams {
convenience init(chainId: String, swapParams: BraveWallet.SwapParams) {
self.init(
chainId: chainId,
inputToken: swapParams.sellToken,
outputToken: swapParams.buyToken,
taker: swapParams.takerAddress
)
}
}

public extension String {
/// Returns true if the string ends with a supported ENS extension.
var endsWithSupportedENSExtension: Bool {
Expand Down
24 changes: 4 additions & 20 deletions Sources/BraveWallet/Preview Content/MockSwapService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,12 @@ class MockSwapService: BraveWalletSwapService {
completion(true)
}

func transactionPayload(_ params: BraveWallet.SwapParams, completion: @escaping (BraveWallet.SwapResponse?, BraveWallet.SwapErrorResponse?, String) -> Void) {
completion(.init(price: "", guaranteedPrice: "", to: "", data: "", value: "", gas: "", estimatedGas: "", gasPrice: "", protocolFee: "", minimumProtocolFee: "", buyTokenAddress: "", sellTokenAddress: "", buyAmount: "", sellAmount: "", allowanceTarget: "", sellTokenToEthRate: "", buyTokenToEthRate: "", estimatedPriceImpact: "", sources: [], fees: .init(zeroExFee: nil)), nil, "")
func transaction(_ params: BraveWallet.SwapTransactionParamsUnion, completion: @escaping (BraveWallet.SwapTransactionUnion?, BraveWallet.SwapErrorUnion?, String) -> Void) {
completion(.init(zeroExTransaction: .init(price: "", guaranteedPrice: "", to: "", data: "", value: "", gas: "", estimatedGas: "", gasPrice: "", protocolFee: "", minimumProtocolFee: "", buyTokenAddress: "", sellTokenAddress: "", buyAmount: "", sellAmount: "", allowanceTarget: "", sellTokenToEthRate: "", buyTokenToEthRate: "", estimatedPriceImpact: "", sources: [], fees: .init(zeroExFee: nil))), nil, "")
}

func priceQuote(_ params: BraveWallet.SwapParams, completion: @escaping (BraveWallet.SwapResponse?, BraveWallet.SwapErrorResponse?, String) -> Void) {
completion(.init(price: "", guaranteedPrice: "", to: "", data: "", value: "", gas: "", estimatedGas: "", gasPrice: "", protocolFee: "", minimumProtocolFee: "", buyTokenAddress: "", sellTokenAddress: "", buyAmount: "", sellAmount: "", allowanceTarget: "", sellTokenToEthRate: "", buyTokenToEthRate: "", estimatedPriceImpact: "", sources: [], fees: .init(zeroExFee: nil)), nil, "")
}

func jupiterQuote(_ params: BraveWallet.JupiterQuoteParams, completion: @escaping (BraveWallet.JupiterQuote?, BraveWallet.JupiterErrorResponse?, String) -> Void) {
completion(nil, nil, "")
}

func jupiterSwapTransactions(_ params: BraveWallet.JupiterSwapParams, completion: @escaping (Bool, BraveWallet.JupiterSwapTransactions?, String?) -> Void) {
completion(false, nil, nil)
}

func hasJupiterFees(forTokenMint mint: String, completion: @escaping (Bool) -> Void) {
completion(false)
}

func jupiterSwapTransactions(_ params: BraveWallet.JupiterSwapParams, completion: @escaping (BraveWallet.JupiterSwapTransactions?, BraveWallet.JupiterErrorResponse?, String) -> Void) {
completion(nil, .init(statusCode: "0", error: "Error", message: "Error Message", isInsufficientLiquidity: false), "")
func quote(_ params: BraveWallet.SwapQuoteParams, completion: @escaping (BraveWallet.SwapQuoteUnion?, BraveWallet.SwapErrorUnion?, String) -> Void) {
completion(.init(zeroExQuote: .init(price: "", guaranteedPrice: "", to: "", data: "", value: "", gas: "", estimatedGas: "", gasPrice: "", protocolFee: "", minimumProtocolFee: "", buyTokenAddress: "", sellTokenAddress: "", buyAmount: "", sellAmount: "", allowanceTarget: "", sellTokenToEthRate: "", buyTokenToEthRate: "", estimatedPriceImpact: "", sources: [], fees: .init(zeroExFee: nil))), nil, "")
}

func braveFee(_ params: BraveWallet.BraveSwapFeeParams, completion: @escaping (BraveWallet.BraveSwapFeeResponse?, String) -> Void) {
Expand Down
Loading