Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const SwapConfirmationBottomSheet = ({
rateDisplay,
minimumReceivedDisplay,
peraFeeDisplay,
slippageDisplay,
hasHighPriceImpact,
priceImpactDisplay,
priceImpactStyle,
Expand Down Expand Up @@ -136,6 +137,7 @@ export const SwapConfirmationBottomSheet = ({
rateDisplay={rateDisplay}
minimumReceivedDisplay={minimumReceivedDisplay}
peraFeeDisplay={peraFeeDisplay}
slippageDisplay={slippageDisplay}
priceImpactDisplay={priceImpactDisplay}
priceImpactStyle={priceImpactStyle}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type SwapDetailsSectionProps = {
rateDisplay: string
minimumReceivedDisplay: string
peraFeeDisplay: string
slippageDisplay: string
priceImpactDisplay: string
priceImpactStyle: object
}
Expand All @@ -31,6 +32,7 @@ export const SwapDetailsSection = ({
rateDisplay,
minimumReceivedDisplay,
peraFeeDisplay,
slippageDisplay,
priceImpactDisplay,
priceImpactStyle,
}: SwapDetailsSectionProps) => {
Expand Down Expand Up @@ -58,7 +60,7 @@ export const SwapDetailsSection = ({
</DetailRow>
<DetailRow
label={t('swap.quote.slippage_tolerance')}
value={quote.slippage ? `${quote.slippage.toString()}%` : '-'}
value={slippageDisplay}
valueStyle={styles.detailValue}
info={t('swap.info.slippage_tolerance')}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => ({
Expand Down Expand Up @@ -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(
<SwapConfirmationBottomSheet
{...defaultProps}
quote={quote}
/>,
)

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(
<SwapConfirmationBottomSheet
{...defaultProps}
quote={quote}
/>,
)

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(
<SwapConfirmationBottomSheet
{...defaultProps}
quote={quote}
/>,
)

expect(screen.getByText('1000 ALGO')).toBeDefined()
})

it('shows high price impact warning when priceImpact >= 5', () => {
const quote = createQuote({ priceImpact: new Decimal('5.5') })
render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -47,6 +50,7 @@ type UseSwapConfirmationResult = {
rateDisplay: string
minimumReceivedDisplay: string
peraFeeDisplay: string
slippageDisplay: string
hasHighPriceImpact: boolean
priceImpactDisplay: string
priceImpactStyle: object
Expand Down Expand Up @@ -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(
() =>
Expand Down Expand Up @@ -154,6 +166,7 @@ export const useSwapConfirmation = ({
rateDisplay,
minimumReceivedDisplay,
peraFeeDisplay,
slippageDisplay,
hasHighPriceImpact,
priceImpactDisplay,
priceImpactStyle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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([])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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 ?? '-',
}),
Expand Down
13 changes: 9 additions & 4 deletions apps/mobile/vitest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof Decimal>) =>
slippage.mul(100).toString(),
}
})

vi.mock('@perawallet/wallet-core-polling', () => ({
usePolling: vi.fn(),
Expand Down
3 changes: 3 additions & 0 deletions packages/swaps/src/api/quotes/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}))
}
1 change: 1 addition & 0 deletions packages/swaps/src/api/quotes/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})

Expand Down
1 change: 1 addition & 0 deletions packages/swaps/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export interface SwapQuote {
price?: Decimal
priceImpact?: Decimal
peraFeeAmount?: Decimal
peraFeeAsset?: DexSwapAsset
transactionFees?: Nullable<Decimal>
}

Expand Down
55 changes: 47 additions & 8 deletions packages/swaps/src/utils/__tests__/slippage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
8 changes: 7 additions & 1 deletion packages/swaps/src/utils/slippage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Loading