diff --git a/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/SwapConfirmationBottomSheet.tsx b/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/SwapConfirmationBottomSheet.tsx
index 3b5b22fea..e16a80537 100644
--- a/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/SwapConfirmationBottomSheet.tsx
+++ b/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/SwapConfirmationBottomSheet.tsx
@@ -59,6 +59,7 @@ export const SwapConfirmationBottomSheet = ({
rateDisplay,
minimumReceivedDisplay,
peraFeeDisplay,
+ slippageDisplay,
hasHighPriceImpact,
priceImpactDisplay,
priceImpactStyle,
@@ -136,6 +137,7 @@ export const SwapConfirmationBottomSheet = ({
rateDisplay={rateDisplay}
minimumReceivedDisplay={minimumReceivedDisplay}
peraFeeDisplay={peraFeeDisplay}
+ slippageDisplay={slippageDisplay}
priceImpactDisplay={priceImpactDisplay}
priceImpactStyle={priceImpactStyle}
/>
diff --git a/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/SwapDetailsSection.tsx b/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/SwapDetailsSection.tsx
index 0f98bcd89..646feb79f 100644
--- a/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/SwapDetailsSection.tsx
+++ b/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/SwapDetailsSection.tsx
@@ -22,6 +22,7 @@ type SwapDetailsSectionProps = {
rateDisplay: string
minimumReceivedDisplay: string
peraFeeDisplay: string
+ slippageDisplay: string
priceImpactDisplay: string
priceImpactStyle: object
}
@@ -31,6 +32,7 @@ export const SwapDetailsSection = ({
rateDisplay,
minimumReceivedDisplay,
peraFeeDisplay,
+ slippageDisplay,
priceImpactDisplay,
priceImpactStyle,
}: SwapDetailsSectionProps) => {
@@ -58,7 +60,7 @@ export const SwapDetailsSection = ({
diff --git a/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/__tests__/SwapConfirmationBottomSheet.spec.tsx b/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/__tests__/SwapConfirmationBottomSheet.spec.tsx
index 48768bbad..3cb1dc61b 100644
--- a/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/__tests__/SwapConfirmationBottomSheet.spec.tsx
+++ b/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/__tests__/SwapConfirmationBottomSheet.spec.tsx
@@ -34,6 +34,7 @@ vi.mock('@perawallet/wallet-core-shared', () => ({
vi.mock('@perawallet/wallet-core-swaps', () => ({
useProvidersQuery: () => ({ data: undefined }),
+ apiSlippageToPercent: (slippage: Decimal) => slippage.mul(100).toString(),
}))
vi.mock('@perawallet/wallet-core-assets', () => ({
@@ -233,6 +234,54 @@ describe('SwapConfirmationBottomSheet', () => {
expect(screen.getByText('swap.quote.pera_fee')).toBeDefined()
})
+ it('renders slippage as a percent (5% from API fraction 0.05)', () => {
+ const quote = createQuote({ slippage: new Decimal('0.05') })
+ render(
+ ,
+ )
+
+ expect(screen.getByText('5%')).toBeDefined()
+ })
+
+ it('renders pera fee using peraFeeAsset when it differs from assetIn', () => {
+ const quote = createQuote({
+ peraFeeAmount: new Decimal('1000'),
+ peraFeeAsset: {
+ assetId: '31566704',
+ name: 'USDC',
+ unitName: 'USDC',
+ decimals: 6,
+ verificationTier: 'verified',
+ },
+ })
+ render(
+ ,
+ )
+
+ expect(screen.getByText('1000 USDC')).toBeDefined()
+ })
+
+ it('falls back to assetIn for pera fee when peraFeeAsset is absent', () => {
+ const quote = createQuote({
+ peraFeeAmount: new Decimal('1000'),
+ peraFeeAsset: undefined,
+ })
+ render(
+ ,
+ )
+
+ expect(screen.getByText('1000 ALGO')).toBeDefined()
+ })
+
it('shows high price impact warning when priceImpact >= 5', () => {
const quote = createQuote({ priceImpact: new Decimal('5.5') })
render(
diff --git a/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/useSwapConfirmation.ts b/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/useSwapConfirmation.ts
index b11f15c1a..9674d0e44 100644
--- a/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/useSwapConfirmation.ts
+++ b/apps/mobile/src/modules/swap/components/SwapConfirmationBottomSheet/useSwapConfirmation.ts
@@ -24,7 +24,10 @@ import {
type PeraAsset,
} from '@perawallet/wallet-core-assets'
import { useCurrency } from '@perawallet/wallet-core-currencies'
-import type { SwapQuote } from '@perawallet/wallet-core-swaps'
+import {
+ apiSlippageToPercent,
+ type SwapQuote,
+} from '@perawallet/wallet-core-swaps'
import type { SwapExecutionStatus } from '../../hooks/useSwapExecution'
import { useStyles } from './styles'
@@ -47,6 +50,7 @@ type UseSwapConfirmationResult = {
rateDisplay: string
minimumReceivedDisplay: string
peraFeeDisplay: string
+ slippageDisplay: string
hasHighPriceImpact: boolean
priceImpactDisplay: string
priceImpactStyle: object
@@ -118,8 +122,16 @@ export const useSwapConfirmation = ({
const peraFeeDisplay = useMemo(() => {
if (!quote?.peraFeeAmount) return '-'
- return formatAssetAmount(quote.peraFeeAmount, quote.assetIn)
- }, [quote?.peraFeeAmount, quote?.assetIn])
+ return formatAssetAmount(
+ quote.peraFeeAmount,
+ quote.peraFeeAsset ?? quote.assetIn,
+ )
+ }, [quote?.peraFeeAmount, quote?.peraFeeAsset, quote?.assetIn])
+
+ const slippageDisplay = useMemo(() => {
+ if (!quote?.slippage) return '-'
+ return `${apiSlippageToPercent(quote.slippage)}%`
+ }, [quote?.slippage])
const hasHighPriceImpact = useMemo(
() =>
@@ -154,6 +166,7 @@ export const useSwapConfirmation = ({
rateDisplay,
minimumReceivedDisplay,
peraFeeDisplay,
+ slippageDisplay,
hasHighPriceImpact,
priceImpactDisplay,
priceImpactStyle,
diff --git a/apps/mobile/src/modules/swap/components/SwapForm/__tests__/useSwapForm.spec.ts b/apps/mobile/src/modules/swap/components/SwapForm/__tests__/useSwapForm.spec.ts
index 953380434..157f44286 100644
--- a/apps/mobile/src/modules/swap/components/SwapForm/__tests__/useSwapForm.spec.ts
+++ b/apps/mobile/src/modules/swap/components/SwapForm/__tests__/useSwapForm.spec.ts
@@ -387,7 +387,7 @@ describe('useSwapForm', () => {
expect(mockSetPreferredCurrency).toHaveBeenCalledWith('ALGO')
})
- it('converts stored slippage percent to decimal fraction when fetching quotes', async () => {
+ it('converts stored slippage percent to decimal fraction', async () => {
mockSlippage = '1'
mockCreateQuotes.mockResolvedValueOnce([])
diff --git a/apps/mobile/src/modules/swap/components/SwapQuoteDetails/useSwapQuoteDetails.ts b/apps/mobile/src/modules/swap/components/SwapQuoteDetails/useSwapQuoteDetails.ts
index c457d2967..f0f36a81e 100644
--- a/apps/mobile/src/modules/swap/components/SwapQuoteDetails/useSwapQuoteDetails.ts
+++ b/apps/mobile/src/modules/swap/components/SwapQuoteDetails/useSwapQuoteDetails.ts
@@ -17,7 +17,10 @@ import {
RemoteConfigKeys,
useRemoteConfig,
} from '@perawallet/wallet-core-remote-config'
-import type { SwapQuote } from '@perawallet/wallet-core-swaps'
+import {
+ apiSlippageToPercent,
+ type SwapQuote,
+} from '@perawallet/wallet-core-swaps'
import { formatSwapRate } from '../../hooks/swapQuoteHelpers'
import type { Maybe } from '@perawallet/wallet-core-shared'
@@ -71,10 +74,13 @@ export const useSwapQuoteDetails = (
highThreshold,
),
slippageDisplay: quote.slippage
- ? `${quote.slippage.toString()}%`
+ ? `${apiSlippageToPercent(quote.slippage)}%`
: '-',
peraFeeDisplay: quote.peraFeeAmount
- ? formatAssetAmount(quote.peraFeeAmount, quote.assetIn)
+ ? formatAssetAmount(
+ quote.peraFeeAmount,
+ quote.peraFeeAsset ?? quote.assetIn,
+ )
: '-',
providerDisplay: quote.providerDisplayName ?? quote.provider ?? '-',
}),
diff --git a/apps/mobile/vitest.setup.ts b/apps/mobile/vitest.setup.ts
index 53677721c..86de54caa 100644
--- a/apps/mobile/vitest.setup.ts
+++ b/apps/mobile/vitest.setup.ts
@@ -1938,10 +1938,15 @@ vi.mock('@perawallet/wallet-core-walletconnect', () => ({
},
}))
-vi.mock('@perawallet/wallet-core-swaps', () => ({
- useSwaps: vi.fn(),
- isSwappableAsset: vi.fn(() => true),
-}))
+vi.mock('@perawallet/wallet-core-swaps', async () => {
+ const { Decimal } = await import('decimal.js')
+ return {
+ useSwaps: vi.fn(),
+ isSwappableAsset: vi.fn(() => true),
+ apiSlippageToPercent: (slippage: InstanceType) =>
+ slippage.mul(100).toString(),
+ }
+})
vi.mock('@perawallet/wallet-core-polling', () => ({
usePolling: vi.fn(),
diff --git a/packages/swaps/src/api/quotes/endpoints.ts b/packages/swaps/src/api/quotes/endpoints.ts
index c802441ba..a54bd9490 100644
--- a/packages/swaps/src/api/quotes/endpoints.ts
+++ b/packages/swaps/src/api/quotes/endpoints.ts
@@ -127,6 +127,9 @@ export const createQuotes = async (
price: toOptionalDecimal(quote.price),
priceImpact: toOptionalDecimal(quote.price_impact),
peraFeeAmount: toOptionalDecimal(quote.pera_fee_amount),
+ peraFeeAsset: quote.pera_fee_asset
+ ? transformDexSwapAsset(quote.pera_fee_asset)
+ : undefined,
transactionFees: toNullableDecimal(quote.transaction_fees),
}))
}
diff --git a/packages/swaps/src/api/quotes/schema.ts b/packages/swaps/src/api/quotes/schema.ts
index 0a74e31d8..04e22ada9 100644
--- a/packages/swaps/src/api/quotes/schema.ts
+++ b/packages/swaps/src/api/quotes/schema.ts
@@ -71,6 +71,7 @@ export const quoteSchema = z.object({
price: z.string().optional(),
price_impact: z.string().optional(),
pera_fee_amount: z.string().optional(),
+ pera_fee_asset: dexSwapAssetSchema.optional(),
transaction_fees: z.string().nullable().optional(),
})
diff --git a/packages/swaps/src/models/index.ts b/packages/swaps/src/models/index.ts
index 6c1a5cad5..813afad1a 100644
--- a/packages/swaps/src/models/index.ts
+++ b/packages/swaps/src/models/index.ts
@@ -106,6 +106,7 @@ export interface SwapQuote {
price?: Decimal
priceImpact?: Decimal
peraFeeAmount?: Decimal
+ peraFeeAsset?: DexSwapAsset
transactionFees?: Nullable
}
diff --git a/packages/swaps/src/utils/__tests__/slippage.spec.ts b/packages/swaps/src/utils/__tests__/slippage.spec.ts
index 6a5c3fe45..4e355826f 100644
--- a/packages/swaps/src/utils/__tests__/slippage.spec.ts
+++ b/packages/swaps/src/utils/__tests__/slippage.spec.ts
@@ -11,30 +11,69 @@
*/
import { describe, it, expect } from 'vitest'
-import { percentToApiSlippage } from '../slippage'
+import { Decimal } from 'decimal.js'
+import { apiSlippageToPercent, percentToApiSlippage } from '../slippage'
describe('percentToApiSlippage', () => {
- it('converts 1% to 0.01', () => {
+ it('converts "1" to "0.01"', () => {
expect(percentToApiSlippage('1')).toBe('0.01')
})
- it('converts 0.5% to 0.005', () => {
+ it('converts "0.5" to "0.005"', () => {
expect(percentToApiSlippage('0.5')).toBe('0.005')
})
- it('converts 100% to 1', () => {
- expect(percentToApiSlippage('100')).toBe('1')
+ it('converts "2" to "0.02"', () => {
+ expect(percentToApiSlippage('2')).toBe('0.02')
+ })
+
+ it('converts "5" to "0.05"', () => {
+ expect(percentToApiSlippage('5')).toBe('0.05')
})
- it('converts 0% to 0', () => {
+ it('converts "0" to "0"', () => {
expect(percentToApiSlippage('0')).toBe('0')
})
- it('converts 0.1% to 0.001', () => {
+ it('converts "0.1" to "0.001"', () => {
expect(percentToApiSlippage('0.1')).toBe('0.001')
})
- it('preserves precision for values like 2.5%', () => {
+ it('converts "100" to "1"', () => {
+ expect(percentToApiSlippage('100')).toBe('1')
+ })
+
+ it('preserves precision for values like "2.5"', () => {
expect(percentToApiSlippage('2.5')).toBe('0.025')
})
+
+ it('normalises trailing zeros via Decimal', () => {
+ expect(percentToApiSlippage('2.50')).toBe('0.025')
+ })
+})
+
+describe('apiSlippageToPercent', () => {
+ it('converts 0.05 to 5', () => {
+ expect(apiSlippageToPercent(new Decimal('0.05'))).toBe('5')
+ })
+
+ it('converts 0.01 to 1', () => {
+ expect(apiSlippageToPercent(new Decimal('0.01'))).toBe('1')
+ })
+
+ it('converts 0.005 to 0.5', () => {
+ expect(apiSlippageToPercent(new Decimal('0.005'))).toBe('0.5')
+ })
+
+ it('converts 0 to 0', () => {
+ expect(apiSlippageToPercent(new Decimal('0'))).toBe('0')
+ })
+
+ it('converts 1 to 100', () => {
+ expect(apiSlippageToPercent(new Decimal('1'))).toBe('100')
+ })
+
+ it('converts the API decimal-fraction response back to a percent', () => {
+ expect(apiSlippageToPercent(new Decimal('0.025'))).toBe('2.5')
+ })
})
diff --git a/packages/swaps/src/utils/slippage.ts b/packages/swaps/src/utils/slippage.ts
index 943d5747a..6e042a73d 100644
--- a/packages/swaps/src/utils/slippage.ts
+++ b/packages/swaps/src/utils/slippage.ts
@@ -13,6 +13,12 @@
import { Decimal } from 'decimal.js'
// The swap configuration UI collects slippage as a percent (e.g. "1" for 1%),
-// but the Pera Swap quotes API expects a decimal fraction (1% = "0.01").
+// but the Pera Swap quotes API expects a decimal fraction on the request
+// (e.g. "0.01" for 1%, validated as <= 0.9999).
export const percentToApiSlippage = (percent: string): string =>
new Decimal(percent).div(100).toString()
+
+// Quotes return slippage as a decimal fraction (e.g. "0.01" for 1%) and
+// the UI needs to render it as a percent.
+export const apiSlippageToPercent = (slippage: Decimal): string =>
+ slippage.mul(100).toString()