From ed62439020fa6e8e28b486117dd15f2b244ef722 Mon Sep 17 00:00:00 2001 From: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> Date: Tue, 17 Mar 2026 13:46:57 +0900 Subject: [PATCH] add preferInputAsFeeToken for okx --- packages/swapper/src/okx/models.ts | 1 + packages/swapper/src/okx/provider.test.ts | 21 ++++++++++++++++++++- packages/swapper/src/okx/provider.ts | 12 ++++++++++-- packages/swapper/src/referrer.test.ts | 22 ++++++++++++++++++++++ packages/swapper/src/referrer.ts | 21 +++++++++++++++++++++ 5 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 packages/swapper/src/referrer.test.ts diff --git a/packages/swapper/src/okx/models.ts b/packages/swapper/src/okx/models.ts index 0a96a41..c9d89ca 100644 --- a/packages/swapper/src/okx/models.ts +++ b/packages/swapper/src/okx/models.ts @@ -60,4 +60,5 @@ export interface SwapParams { dexIds?: string; feePercent?: string; fromTokenReferrerWalletAddress?: string; + toTokenReferrerWalletAddress?: string; } diff --git a/packages/swapper/src/okx/provider.test.ts b/packages/swapper/src/okx/provider.test.ts index f7ef31a..f19b008 100644 --- a/packages/swapper/src/okx/provider.test.ts +++ b/packages/swapper/src/okx/provider.test.ts @@ -1,6 +1,6 @@ import { Chain, Quote } from "@gemwallet/types"; -import { createOkxEvmQuoteRequest, createSolanaUsdcQuoteRequest, XLAYER_USD0_ADDRESS } from "../testkit/mock"; +import { createOkxEvmQuoteRequest, createSolanaUsdcQuoteRequest, SOLANA_USDC_MINT, XLAYER_USD0_ADDRESS } from "../testkit/mock"; import type { OkxDexClient } from "./client"; import { OkxProvider } from "./provider"; @@ -178,6 +178,21 @@ describe("OkxProvider", () => { expect(result.gasLimit).toBeUndefined(); }); + it("sets toTokenReferrerWalletAddress when toToken is native", async () => { + const { provider, getSwapData } = createProvider(); + getSwapData.mockResolvedValue(mockSolanaSwapResponse()); + + const request = createSolanaUsdcQuoteRequest({ + from_asset: { id: `${Chain.Solana}_${SOLANA_USDC_MINT}`, symbol: "USDC", decimals: 6 }, + to_asset: { id: Chain.Solana, symbol: "SOL", decimals: 9 }, + }); + await provider.get_quote_data(mockSolanaQuote(request)); + + const params = getSwapData.mock.calls[0][0] as Record; + expect(params.toTokenReferrerWalletAddress).toBe("5fmLrs2GuhfDP1B51ziV5Kd1xtAr9rw1jf3aQ4ihZ2gy"); + expect(params.fromTokenReferrerWalletAddress).toBeUndefined(); + }); + it("falls back to 1% slippage when slippage_bps is 0", async () => { const { provider, getSwapData } = createProvider(); getSwapData.mockResolvedValue(mockSolanaSwapResponse()); @@ -242,6 +257,10 @@ describe("OkxProvider", () => { spender: MOCK_APPROVE_ADDRESS, value: "1000000000000000000", }); + + const params = getSwapData.mock.calls[0][0] as Record; + expect(params.toTokenReferrerWalletAddress).toBe("0x0D9DAB1A248f63B0a48965bA8435e4de7497a3dC"); + expect(params.fromTokenReferrerWalletAddress).toBeUndefined(); }); }); }); diff --git a/packages/swapper/src/okx/provider.ts b/packages/swapper/src/okx/provider.ts index 3237953..1b0b8af 100644 --- a/packages/swapper/src/okx/provider.ts +++ b/packages/swapper/src/okx/provider.ts @@ -12,7 +12,7 @@ import { DEFAULT_COMMITMENT } from "../chain/solana/constants"; import { estimateComputeUnitLimit as simulateComputeUnits } from "../chain/solana/tx_builder"; import { SwapperException } from "../error"; import { Protocol } from "../protocol"; -import { getReferrerAddresses } from "../referrer"; +import { getReferrerAddresses, preferInputAsFeeToken } from "../referrer"; import { CHAIN_INDEX, DEFAULT_SLIPPAGE_PERCENT, @@ -85,6 +85,14 @@ function maxAutoSlippagePercent(request: QuoteRequest): string | undefined { return bpsToPercent(request.slippage_bps * 2); } +function referrerWalletAddresses(request: QuoteRequest, chain: Chain): Partial { + const address = referralFeeAddress(request, chain); + if (!address) return {}; + return preferInputAsFeeToken(request) + ? { fromTokenReferrerWalletAddress: address } + : { toTokenReferrerWalletAddress: address }; +} + function buildSwapParams(request: QuoteRequest, route: QuoteData, chain: Chain): SwapParams { return { chainIndex: chainIndex(chain), @@ -97,7 +105,7 @@ function buildSwapParams(request: QuoteRequest, route: QuoteData, chain: Chain): autoSlippage: true, maxAutoSlippagePercent: maxAutoSlippagePercent(request), feePercent: referralFeePercent(request), - fromTokenReferrerWalletAddress: referralFeeAddress(request, chain), + ...referrerWalletAddresses(request, chain), }; } diff --git a/packages/swapper/src/referrer.test.ts b/packages/swapper/src/referrer.test.ts new file mode 100644 index 0000000..4fd1431 --- /dev/null +++ b/packages/swapper/src/referrer.test.ts @@ -0,0 +1,22 @@ +import { AssetId, Chain } from "@gemwallet/types"; + +import { isPreferredFeeToken } from "./referrer"; +import { SOLANA_USDC_MINT } from "./testkit/mock"; + +describe("isPreferredFeeToken", () => { + it("returns true for native asset", () => { + expect(isPreferredFeeToken(new AssetId(Chain.Solana), "SOL")).toBe(true); + }); + + it("returns true for USD token", () => { + expect(isPreferredFeeToken(new AssetId(Chain.Solana, SOLANA_USDC_MINT), "USDC")).toBe(true); + }); + + it("returns true for USDT token", () => { + expect(isPreferredFeeToken(new AssetId(Chain.Solana, "mint1"), "USDT")).toBe(true); + }); + + it("returns false for non-native non-USD token", () => { + expect(isPreferredFeeToken(new AssetId(Chain.Solana, "mint1"), "BONK")).toBe(false); + }); +}); diff --git a/packages/swapper/src/referrer.ts b/packages/swapper/src/referrer.ts index 48e4df5..ffe7e6d 100644 --- a/packages/swapper/src/referrer.ts +++ b/packages/swapper/src/referrer.ts @@ -1,3 +1,5 @@ +import { AssetId, QuoteRequest } from "@gemwallet/types"; + export type Referrers = { evm: string; solana: string; @@ -22,4 +24,23 @@ export function getReferrerAddresses(): Referrers { }; } +export function isPreferredFeeToken(asset: AssetId, symbol: string): boolean { + if (asset.isNative()) return true; + if (symbol.includes("USD")) return true; + return false; +} + +export function preferInputAsFeeToken(request: QuoteRequest): boolean { + const fromAsset = AssetId.fromString(request.from_asset.id); + const toAsset = AssetId.fromString(request.to_asset.id); + + if (fromAsset.isNative()) return true; + if (toAsset.isNative()) return false; + + if (isPreferredFeeToken(fromAsset, request.from_asset.symbol)) return true; + if (isPreferredFeeToken(toAsset, request.to_asset.symbol)) return false; + + return true; +} + export const CETUS_PARTNER_ID = "0x08b1875b6541c847f05ed71d04cbcfa66e4e8619bf3b8923b07c5b5409433366";