Skip to content

Commit 6ab9568

Browse files
authored
Merge pull request #15248 from brave/f/wallet/jupiter-fees
feat(wallet): charge Jupiter fees for selective output mints
2 parents 11d518d + 376c2f1 commit 6ab9568

File tree

10 files changed

+141
-18
lines changed

10 files changed

+141
-18
lines changed

components/brave_wallet/browser/brave_wallet_constants.cc

+17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <map>
77
#include <string>
88

9+
#include "base/containers/contains.h"
910
#include "brave/components/brave_wallet/browser/brave_wallet_constants.h"
1011

1112
namespace brave_wallet {
@@ -295,4 +296,20 @@ const base::flat_map<std::string, std::string>& GetInfuraChainEndpoints() {
295296
return *endpoints;
296297
}
297298

299+
bool HasJupiterFeesForTokenMint(const std::string& mint) {
300+
static std::vector<std::string> mints(
301+
{"So11111111111111111111111111111111111111112", // wSOL
302+
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
303+
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", // USDT
304+
"7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs" // WETH (Wormhole)
305+
"2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk" // ETH (Sollet)
306+
"9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", // BTC (Sollet)
307+
"qfnqNqs3nCAHjnyCgLRDbBtq4p2MtHZxw8YjSyYhPoL", // wWBTC (Wormhole)
308+
"7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", // stSOL
309+
"mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", // mSOL
310+
"FYpdBuyAHSbdaAyD1sKkxyLWbAP8uUW9h6uvdhK74ij1"}); // DAI
311+
312+
return base::Contains(mints, mint);
313+
}
314+
298315
} // namespace brave_wallet

components/brave_wallet/browser/brave_wallet_constants.h

+1
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,7 @@ const std::vector<mojom::OnRampCurrency>& GetOnRampCurrenciesList();
986986
const std::vector<mojom::BlockchainToken>& GetSardineBuyTokens();
987987
const std::string GetSardineNetworkName(const std::string& chain_id);
988988
const base::flat_map<std::string, std::string>& GetInfuraChainEndpoints();
989+
bool HasJupiterFeesForTokenMint(const std::string& mint);
989990

990991
} // namespace brave_wallet
991992

components/brave_wallet/browser/swap_request_helper.cc

+12-3
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,24 @@ absl::optional<std::string> EncodeJupiterTransactionParams(
2222
DCHECK(params);
2323
base::Value::Dict tx_params;
2424

25-
// feeAccount is the ATA account for the output mint where the fee will be
26-
// sent to.
25+
// The code below does the following two things:
26+
// - compute the ATA address that should be used to receive fees
27+
// - verify if output_mint is a valid address
2728
absl::optional<std::string> associated_token_account =
2829
SolanaKeyring::GetAssociatedTokenAccount(
2930
params->output_mint, brave_wallet::kSolanaFeeRecipient);
3031
if (!associated_token_account)
3132
return absl::nullopt;
3233

33-
tx_params.Set("feeAccount", *associated_token_account);
34+
// If the if-condition below is false, associated_token_account is unused,
35+
// but the originating call to SolanaKeyring::GetAssociatedTokenAccount()
36+
// is still done to ensure output_mint is always valid.
37+
if (HasJupiterFeesForTokenMint(params->output_mint)) {
38+
// feeAccount is the ATA account for the output mint where the fee will be
39+
// sent to.
40+
tx_params.Set("feeAccount", *associated_token_account);
41+
}
42+
3443
tx_params.Set("userPublicKey", params->user_public_key);
3544

3645
base::Value::Dict route;

components/brave_wallet/browser/swap_request_helper_unittest.cc

+46-2
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,60 @@ TEST(SwapRequestHelperUnitTest, EncodeJupiterTransactionParams) {
105105
"userPublicKey": "mockPubKey"
106106
})");
107107

108+
// OK: Jupiter transaction params with feeAccount
108109
auto expected_params_value = base::JSONReader::Read(
109110
expected_params,
110111
base::JSON_PARSE_CHROMIUM_EXTENSIONS | base::JSON_ALLOW_TRAILING_COMMAS);
111112
ASSERT_NE(encoded_params, absl::nullopt);
112113
ASSERT_EQ(*encoded_params, GetJSON(*expected_params_value));
113114

114-
// Empty params
115+
// OK: Jupiter transaction params WITHOUT feeAccount
116+
params.output_mint = "SHDWyBxihqiCj6YekG2GUr7wqKLeLAMK1gHZck9pL6y"; // SHDW
117+
encoded_params = EncodeJupiterTransactionParams(params.Clone());
118+
expected_params = R"(
119+
{
120+
"route": {
121+
"inAmount": 10000,
122+
"outAmount": 261273,
123+
"amount": 10000,
124+
"otherAmountThreshold": 258660,
125+
"swapMode": "ExactIn",
126+
"priceImpactPct": 0.008955716118219659,
127+
"marketInfos": [
128+
{
129+
"id": "2yNwARmTmc3NzYMETCZQjAE5GGCPgviH6hiBsxaeikTK",
130+
"label": "Orca",
131+
"inputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
132+
"outputMint": "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey",
133+
"notEnoughLiquidity": false,
134+
"inAmount": 10000,
135+
"outAmount": 117001203,
136+
"priceImpactPct": 1.196568750220778e-7,
137+
"lpFee": {
138+
"amount": 30,
139+
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
140+
"pct": 0.003
141+
},
142+
"platformFee": {
143+
"amount": 0,
144+
"mint": "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey",
145+
"pct": 0.0
146+
}
147+
}
148+
]
149+
},
150+
"userPublicKey": "mockPubKey"
151+
})";
152+
expected_params_value = base::JSONReader::Read(
153+
expected_params,
154+
base::JSON_PARSE_CHROMIUM_EXTENSIONS | base::JSON_ALLOW_TRAILING_COMMAS);
155+
ASSERT_NE(encoded_params, absl::nullopt);
156+
ASSERT_EQ(*encoded_params, GetJSON(*expected_params_value));
157+
158+
// KO: empty params
115159
EXPECT_DCHECK_DEATH(EncodeJupiterTransactionParams(nullptr));
116160

117-
// Invalid output mint
161+
// KO: invalid output mint
118162
params.output_mint = "invalid output mint";
119163
encoded_params = EncodeJupiterTransactionParams(params.Clone());
120164
ASSERT_EQ(encoded_params, absl::nullopt);

components/brave_wallet/browser/swap_service.cc

+12-2
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ GURL AppendJupiterQuoteParams(
105105
url = net::AppendQueryParameter(url, "outputMint", params.output_mint);
106106
if (!params.amount.empty())
107107
url = net::AppendQueryParameter(url, "amount", params.amount);
108-
url = net::AppendQueryParameter(url, "feeBps",
109-
brave_wallet::SwapService::GetFee(chain_id));
108+
109+
if (brave_wallet::HasJupiterFeesForTokenMint(params.output_mint)) {
110+
url = net::AppendQueryParameter(
111+
url, "feeBps", brave_wallet::SwapService::GetFee(chain_id));
112+
}
113+
110114
url = net::AppendQueryParameter(
111115
url, "slippage", base::StringPrintf("%.6f", params.slippage_percentage));
112116

@@ -447,4 +451,10 @@ void SwapService::OnGetJupiterSwapTransactions(
447451
std::move(callback).Run(true, std::move(swap_transactions), absl::nullopt);
448452
}
449453

454+
void SwapService::HasJupiterFeesForTokenMint(
455+
const std::string& mint,
456+
HasJupiterFeesForTokenMintCallback callback) {
457+
std::move(callback).Run(brave_wallet::HasJupiterFeesForTokenMint(mint));
458+
}
459+
450460
} // namespace brave_wallet

components/brave_wallet/browser/swap_service.h

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ class SwapService : public KeyedService, public mojom::SwapService {
7878
mojom::JupiterSwapParamsPtr params,
7979
GetJupiterSwapTransactionsCallback callback) override;
8080

81+
void HasJupiterFeesForTokenMint(
82+
const std::string& mint,
83+
HasJupiterFeesForTokenMintCallback callback) override;
84+
8185
static void SetBaseURLForTest(const GURL& base_url_for_test);
8286

8387
private:

components/brave_wallet/browser/swap_service_unittest.cc

+18-2
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,11 @@ TEST_F(SwapServiceUnitTest, IsSwapSupported) {
452452
EXPECT_FALSE(IsSwapSupported("invalid chain_id"));
453453
}
454454
TEST_F(SwapServiceUnitTest, GetJupiterQuoteURL) {
455-
auto url = swap_service_->GetJupiterQuoteURL(GetCannedJupiterQuoteParams(),
456-
mojom::kSolanaMainnet);
455+
auto params = GetCannedJupiterQuoteParams();
456+
auto url =
457+
swap_service_->GetJupiterQuoteURL(params.Clone(), mojom::kSolanaMainnet);
458+
459+
// OK: output mint has Jupiter fees
457460
ASSERT_EQ(url,
458461
"https://quote-api.jup.ag/v1/quote?"
459462
"inputMint=So11111111111111111111111111111111111111112&"
@@ -462,6 +465,19 @@ TEST_F(SwapServiceUnitTest, GetJupiterQuoteURL) {
462465
"feeBps=85&"
463466
"slippage=0.500000&"
464467
"onlyDirectRoutes=true");
468+
469+
params->output_mint = "SHDWyBxihqiCj6YekG2GUr7wqKLeLAMK1gHZck9pL6y";
470+
url =
471+
swap_service_->GetJupiterQuoteURL(params.Clone(), mojom::kSolanaMainnet);
472+
473+
// OK: output mint does not have Jupiter fees
474+
ASSERT_EQ(url,
475+
"https://quote-api.jup.ag/v1/quote?"
476+
"inputMint=So11111111111111111111111111111111111111112&"
477+
"outputMint=SHDWyBxihqiCj6YekG2GUr7wqKLeLAMK1gHZck9pL6y&"
478+
"amount=10000&"
479+
"slippage=0.500000&"
480+
"onlyDirectRoutes=true");
465481
}
466482

467483
TEST_F(SwapServiceUnitTest, GetJupiterSwapTransactionsURL) {

components/brave_wallet/common/brave_wallet.mojom

+4
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,10 @@ interface SwapService {
713713

714714
// Obtains whether the given chain id supports swap
715715
IsSwapSupported(string chain_id) => (bool result);
716+
717+
// Obtains whether the given Solana token mint should have fees for Jupiter
718+
// swaps.
719+
HasJupiterFeesForTokenMint(string mint) => (bool result);
716720
};
717721

718722
interface JsonRpcServiceObserver {

components/brave_wallet_ui/common/async/lib.ts

+5
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,11 @@ export async function getIsSwapSupported (network: BraveWallet.NetworkInfo): Pro
322322
return (await swapService.isSwapSupported(network.chainId)).result
323323
}
324324

325+
export async function hasJupiterFeesForMint (mint: string): Promise<boolean> {
326+
const { swapService } = getAPIProxy()
327+
return (await swapService.hasJupiterFeesForTokenMint(mint)).result
328+
}
329+
325330
export function refreshVisibleTokenInfo (currentNetwork: BraveWallet.NetworkInfo) {
326331
return async (dispatch: Dispatch, getState: () => State) => {
327332
const { braveWalletService } = getAPIProxy()

components/brave_wallet_ui/components/buy-send-swap/swap/index.tsx

+22-9
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@ import * as React from 'react'
22
import { useSelector } from 'react-redux'
33

44
import { getLocale, splitStringForTag } from '../../../../common/locale'
5-
import {
6-
AmountPresetTypes,
7-
BraveWallet,
8-
BuySendSwapViewTypes,
9-
ToOrFromType,
10-
WalletState
11-
} from '../../../constants/types'
5+
import { AmountPresetTypes, BraveWallet, BuySendSwapViewTypes, ToOrFromType, WalletState } from '../../../constants/types'
126
import SwapInputComponent from '../swap-input-component'
137
import { SwapTooltip } from '../../desktop'
148

@@ -29,7 +23,7 @@ import {
2923
} from './style'
3024
import { LoaderIcon } from 'brave-ui/components/icons'
3125
import { ResetButton } from '../shared-styles'
32-
import { useSwap } from '../../../common/hooks'
26+
import { useLib, useSwap } from '../../../common/hooks'
3327
import { SwapProvider } from '../../../common/hooks/swap'
3428

3529
export interface Props {
@@ -100,6 +94,25 @@ function Swap (props: Props) {
10094
selectedNetwork
10195
} = useSelector((state: { wallet: WalletState }) => state.wallet)
10296

97+
const [hasFees, setHasFees] = React.useState<boolean>(true)
98+
const { hasJupiterFeesForMint } = useLib()
99+
100+
React.useEffect(() => {
101+
(async () => {
102+
if (swapProvider === SwapProvider.ZeroEx) {
103+
setHasFees(true)
104+
return
105+
}
106+
107+
if (!toAsset) {
108+
return
109+
}
110+
111+
const result = await hasJupiterFeesForMint(toAsset.contractAddress)
112+
setHasFees(result)
113+
})()
114+
}, [toAsset, swapProvider])
115+
103116
const onShowAssetTo = () => {
104117
onChangeSwapView('assets', 'to')
105118
onFilterAssetList(fromAsset)
@@ -241,7 +254,7 @@ function Swap (props: Props) {
241254
</ResetButton>
242255
<SwapFeesNoticeRow>
243256
<SwapFeesNoticeText>
244-
{getLocale('braveWalletSwapFeesNotice')
257+
{hasFees && getLocale('braveWalletSwapFeesNotice')
245258
.replace('$1', swapProvider === SwapProvider.Jupiter
246259
? '0.85%'
247260
: '0.875%')}

0 commit comments

Comments
 (0)