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
246 changes: 246 additions & 0 deletions projects/keepkey-vault/__tests__/balance-reconciliation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
import { describe, expect, test } from 'bun:test'
import type { ChainBalance, PendingSwap } from '../src/shared/types'
import {
balanceAmountForAsset,
beginSwapBalanceReconciliation,
completeSwapBalanceReconciliation,
expireBalanceReconciliations,
mergeTrustedBalanceSnapshot,
observeBalanceRefresh,
protectedBalanceChainIds,
reconcileTerminalSwapBalance,
RECONCILIATION_CEILING_MS,
shouldReplaceBalanceSnapshot,
} from '../src/shared/balance-reconciliation'

const SOL_USDT = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'

function solanaBalance(usdt?: string, syncState: ChainBalance['syncState'] = 'confirmed'): ChainBalance {
return {
chainId: 'solana',
symbol: 'SOL',
balance: '2.5',
balanceUsd: 500,
address: 'wallet',
syncState,
tokens: usdt === undefined ? undefined : [{
symbol: 'USDT',
name: 'Tether',
balance: usdt,
balanceUsd: Number(usdt),
priceUsd: 1,
caip: SOL_USDT,
}],
}
}

describe('post-swap balance reconciliation', () => {
test('failed swaps immediately discard their provisional record and release the chain', () => {
const balances = new Map([['solana', solanaBalance('4')]])
const record = beginSwapBalanceReconciliation({
txid: 'tx-failed',
fromChainId: 'bitcoin',
toChainId: 'solana',
toCaip: SOL_USDT,
fromSymbol: 'BTC',
toSymbol: 'USDT',
}, balances, 100)!

expect(protectedBalanceChainIds([record]).has('solana')).toBe(true)
const reconciled = reconcileTerminalSwapBalance([record], {
txid: 'tx-failed',
fromAsset: 'BTC.BTC',
toAsset: 'SOL.USDT',
fromSymbol: 'BTC',
toSymbol: 'USDT',
fromChainId: 'bitcoin',
toChainId: 'solana',
toCaip: SOL_USDT,
fromAmount: '0.001',
expectedOutput: '10',
memo: '',
inboundAddress: '',
integration: 'nearIntents',
status: 'failed',
confirmations: 0,
createdAt: 1,
updatedAt: 2,
estimatedTime: 30,
} satisfies PendingSwap, balances, 200)

expect(reconciled).toEqual([])
expect(protectedBalanceChainIds(reconciled).has('solana')).toBe(false)
})

test('wall-clock expiry releases protection without any balance refresh', () => {
const balances = new Map([['solana', solanaBalance('4')]])
const pending = beginSwapBalanceReconciliation({
txid: 'tx-offline',
fromChainId: 'bitcoin',
toChainId: 'solana',
toCaip: SOL_USDT,
fromSymbol: 'BTC',
toSymbol: 'USDT',
}, balances, 100)!
const terminal = completeSwapBalanceReconciliation(pending, {
txid: 'tx-offline',
fromAsset: 'BTC.BTC',
toAsset: 'SOL.USDT',
fromSymbol: 'BTC',
toSymbol: 'USDT',
fromChainId: 'bitcoin',
toChainId: 'solana',
toCaip: SOL_USDT,
fromAmount: '0.001',
expectedOutput: '10',
memo: '',
inboundAddress: '',
integration: 'nearIntents',
status: 'completed',
confirmations: 1,
createdAt: 1,
updatedAt: 2,
estimatedTime: 30,
} satisfies PendingSwap, balances, 200)

expect(expireBalanceReconciliations(
[terminal],
200 + RECONCILIATION_CEILING_MS - 1,
)[0].expired).not.toBe(true)
const expired = expireBalanceReconciliations(
[terminal],
200 + RECONCILIATION_CEILING_MS,
)
expect(expired[0].expired).toBe(true)
expect(protectedBalanceChainIds(expired).has('solana')).toBe(false)
})

test('protects a same-chain snapshot while SPL output is still missing', () => {
const before = new Map([['solana', solanaBalance()]])
const record = beginSwapBalanceReconciliation({
txid: 'tx1',
fromChainId: 'solana',
toChainId: 'solana',
fromCaip: 'solana:mainnet/slip44:501',
toCaip: SOL_USDT,
fromSymbol: 'SOL',
toSymbol: 'USDT',
expectedOutput: '10',
}, before, 100)

expect(record).not.toBeNull()
const waiting = observeBalanceRefresh([record!], [solanaBalance()])
expect(waiting).toHaveLength(1)
expect(waiting[0].observed).toBe(false)
expect(protectedBalanceChainIds(waiting).has('solana')).toBe(true)
})

test('does not trust a stale response even when it contains a higher amount', () => {
const before = new Map([['solana', solanaBalance('4')]])
const record = beginSwapBalanceReconciliation({
txid: 'tx2',
fromChainId: 'solana',
toChainId: 'solana',
toCaip: SOL_USDT,
fromSymbol: 'SOL',
toSymbol: 'USDT',
}, before)!

const waiting = observeBalanceRefresh([record], [solanaBalance('14', 'stale')])
expect(waiting[0].observed).toBe(false)
})

test('accepts a stale portfolio row when the target asset was verified directly', () => {
const before = new Map([['solana', solanaBalance('4')]])
const record = beginSwapBalanceReconciliation({
txid: 'tx-direct',
fromChainId: 'solana',
toChainId: 'solana',
toCaip: SOL_USDT,
fromSymbol: 'SOL',
toSymbol: 'USDT',
}, before)!
const direct = solanaBalance('14', 'stale')
direct.confirmedAssetCaips = [SOL_USDT]

const observed = observeBalanceRefresh([record], [direct])
expect(observed[0].observed).toBe(true)
expect(protectedBalanceChainIds(observed).has('solana')).toBe(false)
})

test('releases the protected chain after a confirmed token increase', () => {
const before = new Map([['solana', solanaBalance('4')]])
const pending = beginSwapBalanceReconciliation({
txid: 'tx3',
fromChainId: 'solana',
toChainId: 'solana',
toCaip: SOL_USDT,
fromSymbol: 'SOL',
toSymbol: 'USDT',
}, before)!
const observed = observeBalanceRefresh([pending], [solanaBalance('14')])

expect(observed[0].observed).toBe(true)
expect(protectedBalanceChainIds(observed).has('solana')).toBe(false)

const completed = completeSwapBalanceReconciliation(observed[0], {
txid: 'tx3',
fromAsset: 'SOL.SOL',
toAsset: 'SOL.USDT',
fromSymbol: 'SOL',
toSymbol: 'USDT',
fromChainId: 'solana',
toChainId: 'solana',
toCaip: SOL_USDT,
fromAmount: '0.1',
expectedOutput: '10',
memo: '',
inboundAddress: '',
integration: 'nearIntents',
status: 'completed',
confirmations: 1,
createdAt: 1,
updatedAt: 2,
estimatedTime: 30,
} satisfies PendingSwap, new Map([['solana', solanaBalance('14')]]), 200)

expect(observeBalanceRefresh([completed], [solanaBalance('14')])).toHaveLength(0)
})

test('reads native and token amounts independently', () => {
const balance = solanaBalance('9.25')
expect(balanceAmountForAsset(balance, SOL_USDT)).toBe(9.25)
expect(balanceAmountForAsset(balance, 'solana:mainnet/slip44:501')).toBe(2.5)
})

test('never replaces a trusted snapshot with stale, degraded, or protected data', () => {
const trusted = solanaBalance('10')
expect(shouldReplaceBalanceSnapshot(trusted, solanaBalance(undefined, 'stale'), false)).toBe(false)
expect(shouldReplaceBalanceSnapshot(trusted, solanaBalance(undefined, 'degraded'), false)).toBe(false)
expect(shouldReplaceBalanceSnapshot(trusted, solanaBalance('11'), true)).toBe(false)
expect(shouldReplaceBalanceSnapshot(trusted, solanaBalance('11'), false)).toBe(true)
expect(shouldReplaceBalanceSnapshot(undefined, solanaBalance(undefined, 'degraded'), false)).toBe(true)
})

test('merges only a directly confirmed token from an otherwise stale chain row', () => {
const trusted = solanaBalance()
trusted.tokens = [{
symbol: 'BONK',
name: 'Bonk',
balance: '5',
balanceUsd: 2,
priceUsd: 0.4,
caip: 'solana:main/token:bonk',
}]
trusted.balanceUsd = 502
const stale = solanaBalance('14', 'stale')
stale.balance = '0'
stale.balanceUsd = 14
stale.confirmedAssetCaips = [SOL_USDT]

const merged = mergeTrustedBalanceSnapshot(trusted, stale, false)!
expect(merged.balance).toBe('2.5')
expect(merged.balanceUsd).toBe(516)
expect(merged.tokens?.map(token => token.symbol)).toEqual(['BONK', 'USDT'])
})
})
37 changes: 37 additions & 0 deletions projects/keepkey-vault/__tests__/offline-asset-icons.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { describe, expect, test } from 'bun:test'
import { readFile, stat } from 'node:fs/promises'
import { join } from 'node:path'
import manifestJson from '../src/shared/offlineAssetIcons.json'
import { getAssetIcon, isBundledAssetIcon } from '../src/shared/assetLookup'

const manifest = manifestJson as Record<string, string>
const iconDir = join(import.meta.dir, '../src/mainview/public/assets/token-icons')
const SOL_USDT = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'
const SOL_USDC = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'
const TRON_USDT = 'tron:0x2b6653dc/token:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'

describe('offline asset icon pack', () => {
test('contains a broad cross-chain set and critical swap assets', () => {
expect(Object.keys(manifest).length).toBeGreaterThanOrEqual(180)
expect(manifest[SOL_USDT]).toBeTruthy()
expect(manifest[SOL_USDC]).toBeTruthy()
expect(manifest[TRON_USDT]).toBeTruthy()
expect(isBundledAssetIcon(getAssetIcon(SOL_USDT))).toBe(true)
})

test('every manifest target is a bounded raster file', async () => {
for (const filename of new Set(Object.values(manifest))) {
expect(filename).toMatch(/^[a-f0-9]{24}\.(png|jpg|webp|gif)$/)
const path = join(iconDir, filename)
const fileStat = await stat(path)
expect(fileStat.size).toBeGreaterThan(0)
expect(fileStat.size).toBeLessThanOrEqual(768 * 1024)
const bytes = new Uint8Array(await readFile(path))
const isPng = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47
const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8
const isGif = bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46
const isWebp = bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46
expect(isPng || isJpeg || isGif || isWebp).toBe(true)
}
})
})
Loading