Files
bank/app/dash/e2e/crypto-loop.spec.ts
zeekayandHanzo Dev 1bed68b1ad bank: close the chain-boundary honesty gaps from the ultracode audit (v1.6.0)
The visual swarm confirmed the sandbox + testnet crypto loop is
genuinely live end-to-end (12/12 Playwright specs, 12 screenshots)
over a real double-entry ledger — and flagged that the chain boundary
is simulated and mislabeled. Fixes:

- Live-mode crypto send now 503s ("on-chain send unavailable") instead
  of debiting the ledger against a random txHash that is never
  broadcast — the same discipline the exchange applies without forexd.
  The faucet was already sandbox-gated; send was not.
- Network label is derived from mode via networkName() (lux-testnet /
  lux-mainnet), not hardcoded; the wallet and crypto-prices responses
  report sandbox: Sandbox() instead of a literal true, so a production
  deploy can no longer report itself as testnet.
- getMonthlyVolume normalizes to USD cents (was summing raw minor
  units, letting crypto micro-units inflate volume ~10^4x and grant
  the fee discount by accident) — matching the limit/AML fix.
- Transaction history carries txHash + network from metadata, so the
  activity feed can render a send's hash, not just the live response.
- Wallet drops the false "Non-custodial" label (the sandbox wallet is
  custodial with an unspendable display address).

Also lands the swarm's visual e2e suite (crypto-loop, dashboard,
exchange specs + screenshots on every flow) and the agent's fix to
Exchange.tsx, where the convert confirmation was wiped on the same
tick it was set — a customer never saw "Converted X to Y".

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-21 14:11:51 -07:00

38 lines
1.6 KiB
TypeScript

import { test, expect } from '@playwright/test'
import { signIn, holding, holdingAmount, shot } from './demo'
// An EIP-55 checksummed address — the send path verifies the checksum, so this
// is the shape a customer's wallet actually hands over.
const CHECKSUMMED = '0x52908400098527886E0F7030069857D2E4169EE7'
test('faucet, hold, send: the whole crypto loop in one pass', async ({ page }) => {
await signIn(page)
await page.getByRole('link', { name: 'Wallet' }).first().click()
await expect(page.getByRole('heading', { name: 'Crypto wallet' })).toBeVisible()
// Receive: draw 1 ETH from the testnet faucet.
const before = (await holdingAmount(page, 'ETH')) ?? 0
await page.getByRole('button', { name: 'Receive', exact: true }).click()
await page.getByRole('button', { name: '+1 ETH' }).click()
await expect(holding(page, 'ETH')).toBeVisible()
await expect
.poll(() => holdingAmount(page, 'ETH'), { message: 'faucet should credit the ETH holding' })
.toBeGreaterThan(before)
const funded = (await holdingAmount(page, 'ETH')) as number
// Send: 0.1 ETH out to a checksummed destination.
await page.getByRole('button', { name: 'Send', exact: true }).click()
await page.getByRole('combobox').selectOption('ETH')
await page.getByPlaceholder('Amount').fill('0.1')
await page.getByPlaceholder('Destination address').fill(CHECKSUMMED)
await page.getByRole('button', { name: 'Send ETH' }).click()
await expect(page.getByText(/Sent · 0x[0-9a-f]{64}/)).toBeVisible()
await expect
.poll(() => holdingAmount(page, 'ETH'), { message: 'the send should debit the ETH holding' })
.toBeLessThan(funded)
await shot(page, 'crypto-loop')
})