Thanks for helping. This is a testnet-only, unaudited project; see docs/STATUS.md for what works today.
git clone --recurse-submodules https://github.com/OracleDesk/OracleDesk-Frontend.git
cd OracleDesk-Frontend
nvm use # Node 22 (see .nvmrc)
npm ci
cp .env.example .env.local
npm run devForgot --recurse-submodules? Run git submodule update --init.
- Branch names:
feat/…,fix/…,docs/…,chore/…,test/…. - Conventional Commits:
feat:,fix:,docs:,test:,chore:,refactor:. One logical change per commit. - No force-pushes to shared branches.
npm run check:bindings # generated contract code matches the submodule
npm run typecheck
npm run lint
npm test
npm run buildnpm run e2e:testnet is an optional, read-only check against a running
backend and live testnet; see the header of e2e/testnet.e2e.test.ts.
Use separate terminals:
node scripts/realtime-burst.mjs --manual --port 4310
NEXT_PUBLIC_API_URL=http://localhost:4310/api/v1 npm run devOpen /markets?profile=1 or /?profile=1 and keep that tab visible.
Development-only React Profiler logs [realtime-profile], the component
name, commit count and total actualDuration every second. Let the page
settle, then trigger each sample from another terminal:
curl 'http://localhost:4310/burst?count=500&rate=500'
# Wait for completion before starting the next sample.
curl 'http://localhost:4310/burst?count=500&rate=50'Sum logged intervals covering each burst and its final settling interval.
Use the same browser, viewport and animation preference for both revisions.
For a baseline, instrument the original feed with the same profiler wrapper;
the original feeds need no functional changes. Profiler time measures React
render work, not socket validation, paint, or end-to-end latency. Slow
baselines may coalesce events under load, so fewer commits alone do not
prove better responsiveness. Production ignores ?profile=1.
The simulator serves an empty market API and emits synthetic public trades;
it never signs or submits transactions. --count, --rate and --port also
configure its automatic burst (omit --manual to emit after the first
connection). See docs/STATUS.md for measured results.
The contracts live in
OracleDesk-SmartContract
and are the source of truth. This repo pins them as the contracts
submodule and copies the generated code into lib/stellar/generated/.
git -C contracts fetch origin
git -C contracts checkout <new commit>
npm run sync:bindings
npm run typecheck && npm test # the Category exhaustiveness check fails here if the enum changed
git add contracts lib/stellar/generatedNever edit lib/stellar/generated/ by hand. If you think a contract needs
to change, write it up in docs/contract-requests.md
and open the issue in the contracts repo.
e2e/x402/ runs the frontend's x402 client (lib/x402/client.ts) against the
real trace service from the submodule (x402/trace-api.ts), fully offline: a
stub facilitator (e2e/x402/stub-facilitator.ts) checks the signed payment,
a fake gateway serves the fixtures in e2e/x402/fixtures/traces/, the Soroban
RPC is answered in-process, and any connection to a non-loopback host fails.
CI runs it as the x402-contract job.
npm ci --ignore-scripts --prefix contracts/oracledesk-stellar/x402 # the service's own dependencies
npm run check:x402 # frontend and service resolve the same @x402/* versions
npx tsc -p e2e/x402 # typecheck the harness (kept out of the app's tsconfig)
npm run e2e:x402When the submodule moves:
- Run
npm ci --ignore-scripts --prefix contracts/oracledesk-stellar/x402, thennpm run check:x402. If the service now resolves different@x402/*versions, pin the frontend to exactly those (npm install --save-exact @x402/core@<v> @x402/stellar@<v>) and read the release notes for header, payload or settlement-flow changes. - Run
npm run e2e:x402. "contracts/x402 API changed: update e2e/x402/harness.ts and the frontend client" meanscreateTraceApiorMapTraceRepositorymoved or changed shape: adapte2e/x402/harness.ts, thenlib/x402/client.tsif the wire format changed. - If the service's trace format changes, regenerate the fixture with the
submodule's
agents/core/trace.tsbuildTrace(canonical JSON) and updateFIXTURE_TRACE_HASHine2e/x402/harness.tsto its sha256.
This repo takes part in Drips Wave. Per the current Drips docs:
- Find the issue on the Drips Wave Explore page (or in this repo's issues once a maintainer has added it to a Wave).
- Apply for it on Drips. Don't start coding until a maintainer assigns you; the issue isn't yours before that.
- Open a PR that links the issue (
Closes #123). - Points are earned when the PR is merged and the issue is marked resolved before the Wave ends. Complexity (Trivial 100 / Medium 150 / High 200 points) is set by the maintainer; if an issue turns out harder than labelled, say so on the issue before it's resolved.
Drips also limits how many issues one person can be assigned per organization in a Wave; check the Drips docs for the current numbers. Issue drafts live in docs/wave-issues/ before they're filed.
- A short description and the issue it closes.
- All checks above passing locally.
- Screenshots or a short recording for any UI change.
- Tests for new logic (
lib/**especially), and updated docs (docs/api.mdis owned by the backend; change it there first). - UI copy in plain sentence case, from the user's point of view. Don't claim anything the app doesn't do; label sample data as demo data.
- Amounts as
bigintin 7-decimal USDC base units (lib/stellar/units.ts), never floats, for anything that reaches a transaction.
All money, basis-point, and probability display strings in app/ must go
through lib/format/money.ts:
- USDC amounts (
formatUsdcDisplay): 2 decimal places by default, truncating toward zero (never rounding up money the user does not have), always grouped (en-UScommas), with aUSDCsuffix and no$symbol. Non-zero dust below0.01 USDCrenders as< 0.01 USDC. Compact notation ({ compact: true }) uses integerbigintdivision (1.23M USDC,14.1K USDC). - Signed USDC PnL (
formatSignedUsdc): explicit+or−(U+2212) sign for non-zero displayed amounts, and0.00 USDCwithout a sign when zero or truncated to zero. - Basis points (
formatBps) and probabilities (formatProbability): probabilities render with 1 decimal place and a%suffix; signed basis-point changes render with 2 decimal places by default. - Backend float records (
floatUsdcToRaw): always prefer*Rawdecimal string fields when the API provides them (availableCapitalRaw,seedAmountRaw,collateralRaw,amountRaw). Remaining backend float-only fields are converted once at the boundary viafloatUsdcToRawand labelled asbackend recordin the UI. - Lint enforcement:
eslint.config.mjsbans.toFixedand.toLocaleStringon money-named identifiers and$-prefixed template literals inapp/**/*.tsx. If a non-money value matches the naming heuristic, rename the identifier or add// eslint-disable-next-line no-restricted-syntax -- not money: <reason>.
Modal flows (market quick-view, copy-trade, premium checkout, and trace verification) must use the accessible primitive in components/ui/dialog.tsx:
- Always wrap modal content in
Dialog,DialogContent, and provide an accessible name viaDialogTitle(useclassName="sr-only"if visually redundant) andDialogDescription. - Choose the appropriate initial focus using
initialFocusRef(e.g. amount input or primary action control). - Announce transaction lifecycle states and settled quotes politely via
components/ui/live-region.tsx(<LiveRegion message="..." />) without announcing in-progress or raw URLs. - Mark simulated or blurred background elements as
aria-hidden="true"andinertto keep them out of the accessibility tree and tab order. - Block dismissals (Escape or outside click) during active transaction submissions to prevent accidental orphan states, and announce the reason why.
The site header navigation routes are declared in a single source of truth: components/layout/nav-items.ts (NAV_ITEMS).
- Any new primary route or nested route section must be added to
NAV_ITEMS. - Active styling is computed using
isActive(item, pathname). - The single exact link carrying
aria-current="page"is resolved usingactiveHref(pathname). Exact child matches take precedence over section parent links. - Desktop dropdown navigation is powered by Radix
NavigationMenu(disclosure navigation with keyboard arrow navigation and Escape support). - Mobile navigation is rendered by
MobileNav.tsxusing RadixDialogas an accessible sheet that traps focus and auto-closes on route changes.
When the contracts repository changes:
- Update the pinned submodule:
git -C contracts checkout <commit-or-tag> npm run sync:bindings
- Run the interface change report:
This decodes embedded contract specifications from both the base branch and the new bindings, producing a structured diff of functions, types, error codes, and events.
npm run spec:diff
- Uncovered Contract Errors: If a new contract error code was added without a friendly user-facing message, add an entry describing it to
FRIENDLYinlib/stellar/errors.ts(or add it toUNFRIENDLY_ALLOWLISTwith justification if it is internal/admin-only). - Breaking Call-Site References: If a contract function was removed or its signature changed, update the calling code in
lib/,app/,components/, ore2e/so no dead or mismatched invocations remain. - Renumbered Error Codes: Renumbering existing error codes breaks client-side error mapping and is rejected. Coordinate with contract maintainers to deprecate rather than renumber codes.
- Event Changes: Changes to
TradeorTracePublishedevents flag a requirement for backend indexer review.