Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
68cc780
add mocked dashboard app
Sharqiewicz Jun 23, 2026
15b8ba6
update the login
Sharqiewicz Jun 24, 2026
7db78b9
update recipient mock
Sharqiewicz Jun 24, 2026
88bde3c
adjust mock flow to the planned flow
Sharqiewicz Jun 25, 2026
4df3ed0
Create schema for unifying user management.
ebma Jun 25, 2026
53a4bee
add mocked wallet connection, update tranfer form
Sharqiewicz Jul 2, 2026
68e3b5b
Merge branch 'feature/dashboard-app-staging' of github.com:pendulum-c…
Sharqiewicz Jul 2, 2026
1de0a25
update recipient link flow
Sharqiewicz Jul 3, 2026
6b3f9ba
add a backend connection to dashboard plan
Sharqiewicz Jul 3, 2026
1a4c186
Merge branch 'staging' into feature/dashboard-app-staging
gianfra-t Jul 7, 2026
9a5aec2
Add full target schema: migrations 038-043 + models (migrate-once, no…
gianfra-t Jul 7, 2026
2e2480d
test(contracts): add Alfredpay external API contract schemas and suite
ebma Jul 7, 2026
f2a6e18
test(contracts): add Avenia/BRLA external API contract schemas and suite
ebma Jul 7, 2026
eccdc5e
test(contracts): add CoinGecko price feed contract schema and suite
ebma Jul 7, 2026
1a6b335
docs(contracts): wire nightly secrets, document fixtures, update status
ebma Jul 7, 2026
151e708
Sync bun.lock with committed package.json changes
gianfra-t Jul 7, 2026
fc40d52
Cut partner/pricing reads over to partners + partner_pricing_configs
gianfra-t Jul 7, 2026
97e57ce
Cut mykobo + avenia identity over to customer_entities/provider_custo…
gianfra-t Jul 7, 2026
631bc30
Cut alfredpay identity over to provider_customers; drop dead legacy m…
gianfra-t Jul 7, 2026
f2aed61
Add recipient, notification and onboarding-status endpoints
gianfra-t Jul 7, 2026
d71c79c
Wire dashboard to the real backend with a ported transfer machine
gianfra-t Jul 7, 2026
1aebd2f
Record S1 obsolescence and Phase-0 wiring status in the dashboard plan
gianfra-t Jul 7, 2026
0ef41d1
Reframe transfer enforcement as the recipient-context registration gap
gianfra-t Jul 7, 2026
9114aa6
Wire dashboard recipients to the real backend; self-send offramps
gianfra-t Jul 8, 2026
83a5214
Gate dashboard self-recipients on real onboarding status
gianfra-t Jul 8, 2026
08b61b2
Remove all dashboard mock data; source everything from the DB
gianfra-t Jul 8, 2026
0d7c867
Add in_review onboarding state and fix widget deep-link path
gianfra-t Jul 9, 2026
ac4b3cc
Move sender onboarding back into the dashboard; restore the mock wizard
gianfra-t Jul 9, 2026
e03f227
Enable the AR KYB deep link; reframe KYC sharing as decouple-then-ext…
gianfra-t Jul 9, 2026
75b95b0
Extract Alfredpay KYC into packages/kyc; unmock dashboard individual KYC
gianfra-t Jul 9, 2026
9932068
improve corridor card display, validation bugs
gianfra-t Jul 9, 2026
4eeb01c
Share Alfredpay KYC API adapter
gianfra-t Jul 9, 2026
d4a3cca
Extract Avenia KYC machine
gianfra-t Jul 9, 2026
53a80da
Wire dashboard Avenia KYC live
gianfra-t Jul 9, 2026
05fb742
Fix dashboard Avenia KYC flow
gianfra-t Jul 9, 2026
f3c6a9e
Document dashboard AlfredPay fiat account follow-up
gianfra-t Jul 9, 2026
0e4dab8
docs(api): add SEO metadata, update AI-agent page and skill for all c…
ebma Jul 10, 2026
f512696
docs(api): correct auth model, quote shape, and payment-instruction t…
ebma Jul 10, 2026
8186b09
Lock Alfredpay onboarding email fields
gianfra-t Jul 10, 2026
76c27e3
Add Playwright E2E harness for the dashboard auth surface
gianfra-t Jul 10, 2026
619c0d0
Add dashboard app spec; lock invite redemption to the widget
gianfra-t Jul 10, 2026
5647dea
Allow the accepting recipient to re-enter their invite link
gianfra-t Jul 10, 2026
e451648
Add dashboard MXN offramp transfer E2E journey
gianfra-t Jul 10, 2026
fbfcbaa
Add dashboard Alfredpay MX KYC E2E journey
gianfra-t Jul 10, 2026
1eb4562
docs(api): fix register request shape and BRL error messages per PR r…
ebma Jul 13, 2026
c901567
fix(sdk): map current BRL missing-parameter error messages
ebma Jul 13, 2026
116a4a6
docs(api): fix Moonbeam account type, drop Stellar from OpenAPI, clar…
ebma Jul 13, 2026
c8708c0
docs: add codebase map and layered per-workspace CLAUDE.md
ebma Jul 13, 2026
f89029d
chore: add version-controlled Claude Code permission denies
ebma Jul 13, 2026
4417d18
docs(skills): scope sentry-vortex skill to apps/frontend
ebma Jul 13, 2026
93ff6f8
Merge pull request #1267 from pendulum-chain/fix/sdk-brl-error-mapping
ebma Jul 13, 2026
36df075
Wire recipient invite redemption
gianfra-t Jul 13, 2026
299c9c5
chore: address PR review — recursive api-key deny, clarify SDK lint
ebma Jul 13, 2026
1d09fcd
Merge pull request #1268 from pendulum-chain/chore/large-codebase-pla…
ebma Jul 13, 2026
570ea7f
re-introduce monerium oauth kyc
gianfra-t Jul 13, 2026
9ab7d87
Fix Monerium startup and dashboard transitions
gianfra-t Jul 13, 2026
570b449
Resume Monerium onboarding in dashboard
gianfra-t Jul 13, 2026
9d7aaf4
Surface Monerium reauthentication on corridor cards
gianfra-t Jul 13, 2026
428abab
new schema modifications
gianfra-t Jul 13, 2026
de35120
Hydrate Avenia company name from KYC status endpoint
gianfra-t Jul 13, 2026
5cc6f63
Merge pull request #1266 from pendulum-chain/docs/api-seo-and-all-cor…
ebma Jul 14, 2026
ffd12f2
Potential fix for pull request finding
ebma Jul 14, 2026
fcb1961
Merge pull request #1254 from pendulum-chain/test/external-api-contra…
ebma Jul 14, 2026
84f33eb
fix(contracts): encode wire truths surfaced by the first nightly run
ebma Jul 14, 2026
946838c
Adjust comments
ebma Jul 14, 2026
b8c0eb6
fix(alfredpay): follow the configs endpoint rename and harden the lim…
ebma Jul 14, 2026
d22a232
fix(alfredpay): bound configs decimals to two digits
ebma Jul 14, 2026
58c4fe7
Merge pull request #1269 from pendulum-chain/fix/alfredpay-configs-en…
ebma Jul 14, 2026
47ddbc5
Fix started onboarding dashboard crash
gianfra-t Jul 14, 2026
51644ba
Fix shared dependency type resolution
gianfra-t Jul 14, 2026
f637e48
Redirect dashboard dev server root
gianfra-t Jul 14, 2026
f5c46db
Fix Monerium reauthentication identity binding
gianfra-t Jul 14, 2026
df0f1ba
Fix dashboard invitation and status handling
gianfra-t Jul 14, 2026
3cf79b5
Prevent concurrent user ramps
gianfra-t Jul 14, 2026
8d5081c
Add dashboard company KYB flows
gianfra-t Jul 14, 2026
6b9d8ab
Merge staging into feature/dashboard-app-staging
gianfra-t Jul 14, 2026
3dfe36f
Fix API type errors and run typecheck per workspace
ebma Jul 14, 2026
c1771b1
Stop the Monerium test's module mocks from poisoning later test files
ebma Jul 14, 2026
f46c7cf
Pin that a ramp wedged past initial keeps blocking registration
ebma Jul 14, 2026
ad6ed63
Restore business default for KYB deep links and drop the EU region
ebma Jul 14, 2026
6a2efa5
Remove the mocked onboarding wizard instead of simulating approval
ebma Jul 14, 2026
451ba8c
Isolate dashboard ephemeral keys and make widget URL safe by default
ebma Jul 14, 2026
403d634
Harden invite acceptance, entity uniqueness, and orphaned API keys
ebma Jul 14, 2026
31a6a17
Run kyc and dashboard tests in CI; re-baseline frontend coverage
ebma Jul 14, 2026
09363a8
Align the dashboard spec with iteration-1 scope and shipped behavior
ebma Jul 14, 2026
3aa8868
Fix CI-only failures: entity findOne leak and missing @types/node
ebma Jul 14, 2026
1a15f23
Align recipient status views across api, dashboard and widget
gianfra-t Jul 14, 2026
5a6e932
Align recipient and dashboard specs with shipped behavior
gianfra-t Jul 14, 2026
962cdef
Support a separately-hosted dashboard: netlify.toml per site + CORS env
ebma Jul 15, 2026
42104d2
Serve the dashboard at the domain root instead of /dashboard
ebma Jul 15, 2026
d418ba1
Whitelist dashboard.vortexfinance.co in CORS permanently
ebma Jul 15, 2026
f8c7c98
Merge pull request #1236 from pendulum-chain/feature/dashboard-app-st…
ebma Jul 15, 2026
6b3aae3
Add production rollout runbook and schema parity-check script
ebma Jul 15, 2026
c5ad61e
Add invite-link management: alias, re-copy from row, archive removal
gianfra-t Jul 15, 2026
2ccf2c5
Polish invite-link copy and make recipient statuses provider-neutral
gianfra-t Jul 15, 2026
f74bbb2
Dashboard onboarding polishing: resumable KYC + live status refresh
gianfra-t Jul 15, 2026
0fc66fa
Unlock all live corridors for recipient invites once any corridor is …
gianfra-t Jul 15, 2026
1279f93
Fix blank page on individual Brazil invite deep links: collect the CP…
gianfra-t Jul 15, 2026
f7d1f84
handling alfredpay (kyb) incorrect first submission and retry
gianfra-t Jul 15, 2026
78548de
Avenia KYB: keep unfinished attempts pending and resume without re-as…
gianfra-t Jul 15, 2026
b204149
Alfredpay KYB: keep the persisted submission id when provider discove…
ebma Jul 16, 2026
538ff01
Dashboard recipients: make the row management modal keyboard-accessible
ebma Jul 16, 2026
41f1160
Onboarding refresh: keep unfinished Avenia individual attempts resumable
ebma Jul 16, 2026
13a6d47
Alfredpay: surface in-place KYB update failures and harden the status…
ebma Jul 16, 2026
39417af
Onboarding refresh: throttle provider status calls and time out Alfre…
ebma Jul 16, 2026
b47a236
Validate CPF/CNPJ check digits and require a tax id to resume Avenia KYC
ebma Jul 16, 2026
26c6b50
Enforce the corridor capability matrix and approval unlock server-sid…
ebma Jul 16, 2026
194cd4d
Recipients: keep expired invites visible, close the accept/expiry rac…
ebma Jul 16, 2026
256bc3e
Avenia KYB: probe the live attempt before allowing re-initiation
ebma Jul 16, 2026
161be8a
Dashboard recipients polish: live-derived modal data, honest archive …
ebma Jul 16, 2026
2f94513
Widget e2e: pin the invited US individual KYC journey
ebma Jul 16, 2026
6559a12
Widget invites: honor the invitation's recipient type in wording and …
ebma Jul 16, 2026
dd5d62a
Dashboard recipients: label and focus-style the actionable rows, hone…
ebma Jul 16, 2026
4738058
Merge pull request #1270 from pendulum-chain/dashboard-staging-adjust…
ebma Jul 16, 2026
f6ae671
more Alfredpay KYB improvements, disable dashboard transfer
gianfra-t Jul 16, 2026
0164503
Alfredpay KYB: cover the flow with live contract tests, add the field…
gianfra-t Jul 16, 2026
547717b
use right vortex support address
gianfra-t Jul 16, 2026
47419ff
Alfredpay KYB: collect the questionnaire and shareholder registry the…
gianfra-t Jul 16, 2026
4441ac4
Alfredpay: send an ASCII filename, so a screenshot no longer dead-end…
gianfra-t Jul 16, 2026
366dd35
Dashboard funding gate: point the support mailto at the address it di…
gianfra-t Jul 16, 2026
4e8dad4
Dashboard recipients: sort focus classes
gianfra-t Jul 17, 2026
5545d30
fix(api): let a rejected Avenia KYC account become approved on a succ…
ebma Jul 17, 2026
ce55835
chore(dashboard): sort Tailwind classes in RecipientsTable
ebma Jul 17, 2026
42f8945
Merge pull request #1273 from pendulum-chain/fix/avenia-kyc-retry-app…
ebma Jul 17, 2026
7a281d8
Make squidRouterApproveHash optional in fundEphemeral verification
ebma Jul 17, 2026
4021a24
fix(api): value offramp discount input in USD before applying inverte…
ebma Jul 17, 2026
5fece49
fix(api): update BRLA golden, add EURC coverage, graceful rate-feed f…
ebma Jul 17, 2026
45aa108
Merge pull request #1275 from pendulum-chain/fix/offramp-discount-usd…
ebma Jul 17, 2026
c3478e3
fix(api): keep active phase retries below lock expiry
gianfra-t Jul 17, 2026
cca74cf
test(api): cover SquidRouter bridge timeout
gianfra-t Jul 17, 2026
cd352c7
revert: remove phase retry mitigation from staging
gianfra-t Jul 17, 2026
9fef0cd
fix(api): keep active phase retries below lock expiry
gianfra-t Jul 17, 2026
e9a09e1
test(api): cover SquidRouter bridge timeout
gianfra-t Jul 17, 2026
f56098e
fix(api): auto-recover Axelar stuck-confirm and stop leaked squidRout…
ebma Jul 19, 2026
ef6e15c
fix(api): harden Axelar stuck-confirm recovery per review feedback
ebma Jul 19, 2026
597c0e3
perf(shared): chunk base64 encoding of the recovery tx bytes
ebma Jul 19, 2026
b6af8ed
Merge pull request #1276 from pendulum-chain/fix/squidrouter-pay-abor…
ebma Jul 20, 2026
4c07519
Merge branch 'staging' into fix-alfred-kyb-flo
gianfra-t Jul 20, 2026
016ec91
Merge pull request #1274 from pendulum-chain/fix/optional-squid-appro…
ebma Jul 20, 2026
f12606e
fix(kyb): address review feedback
gianfra-t Jul 20, 2026
b81256e
Merge pull request #1271 from pendulum-chain/fix-alfred-kyb-flo
gianfra-t Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .agents/skills/sentry-vortex/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: sentry-vortex
description: Audit code against Vortex's Sentry conventions and guide correct error instrumentation. Triggers on: sentry, captureException, error reporting, error monitoring, beforeSend, ignoreErrors, adding an API service method, new error class, new XState machine error handling, ErrorBoundary, "is this reported to Sentry".
description: "Scoped to apps/frontend (the React web app). Audit code against Vortex's Sentry conventions and guide correct error instrumentation. Triggers when working under apps/frontend on: sentry, captureException, error reporting, error monitoring, beforeSend, ignoreErrors, adding an API service method, new error class, new XState machine error handling, ErrorBoundary, \"is this reported to Sentry\"."
user-invocable: true
argument-hint: "[files or 'current changes' — defaults to the working-tree diff]"
---
Expand Down
240 changes: 202 additions & 38 deletions .agents/skills/vortex-integration/SKILL.md

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$comment": "Team-shared, version-controlled Claude Code settings. Personal overrides go in the git-ignored settings.local.json. deny takes precedence over any allow.",
"permissions": {
"deny": [
"Bash(git push --force*)",
"Bash(git push -f *)",
"Bash(git push origin --force*)",
"Read(./.env)",
"Read(./.env.*)",
"Read(**/.env)",
"Read(**/.env.*)",
"Read(./.api-key.json)",
"Read(**/.api-key.json)"
]
}
}
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ jobs:
- name: 🧪 SDK coverage gate
run: cd packages/sdk && bun run test:coverage

- name: 🧪 KYC package tests (coverage-gated)
run: cd packages/kyc && bun run test:coverage

- name: 🧪 Dashboard tests
run: cd apps/dashboard && bun run test

- name: 🧪 Rebalancer tests (coverage-gated)
run: cd apps/rebalancer && bun run test:coverage

Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ jobs:

- name: 🧪 Live contract suites
working-directory: apps/api
# Sandbox credentials and pre-provisioned fixtures (see .env.example). A missing
# secret resolves to "" -> the suite's live half skips -> CONTRACT_EXPECT_LIVE
# fails that suite loudly instead of letting the drift detector rot as green.
env:
ALFREDPAY_BASE_URL: ${{ secrets.CONTRACT_ALFREDPAY_BASE_URL }}
ALFREDPAY_API_KEY: ${{ secrets.CONTRACT_ALFREDPAY_API_KEY }}
ALFREDPAY_API_SECRET: ${{ secrets.CONTRACT_ALFREDPAY_API_SECRET }}
ALFREDPAY_CONTRACT_CUSTOMER_ID: ${{ secrets.CONTRACT_ALFREDPAY_CUSTOMER_ID }}
ALFREDPAY_CONTRACT_FIAT_ACCOUNT_ID: ${{ secrets.CONTRACT_ALFREDPAY_FIAT_ACCOUNT_ID }}
ALFREDPAY_CONTRACT_KYC_SUBMISSION_ID: ${{ secrets.CONTRACT_ALFREDPAY_KYC_SUBMISSION_ID }}
BRLA_BASE_URL: ${{ secrets.CONTRACT_BRLA_BASE_URL }}
BRLA_API_KEY: ${{ secrets.CONTRACT_BRLA_API_KEY }}
BRLA_PRIVATE_KEY: ${{ secrets.CONTRACT_BRLA_PRIVATE_KEY }}
AVENIA_CONTRACT_SUBACCOUNT_ID: ${{ secrets.CONTRACT_AVENIA_SUBACCOUNT_ID }}
COINGECKO_API_KEY: ${{ secrets.COINGECKO_API_KEY }}
run: bun test src/tests/contracts/

# Non-blocking runs are only useful if somebody hears about failures.
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ jobs:
path: apps/frontend/playwright-report/
retention-days: 7

# The dashboard runs its own Playwright config (own Vite server on 5174). Always run it,
# even when the frontend journeys failed — one broken app should not hide the other's status.
- name: 🌐 Install Playwright browsers (dashboard)
if: always()
working-directory: apps/dashboard
run: bunx playwright install --with-deps chromium

- name: 🧪 Dashboard E2E journeys
if: always()
working-directory: apps/dashboard
run: bun run test:e2e

- name: 📤 Upload dashboard report on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report-dashboard
path: apps/dashboard/playwright-report/
retention-days: 7

# Non-blocking runs are only useful if somebody hears about failures.
# Uses the same webhook token the backend's Slack notifier uses
# (repo secret SLACK_WEB_HOOK_TOKEN); skips silently when unset.
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ storybook-static


CLAUDE.local.md
.claude
# Ignore everything under .claude except the team-shared settings.json
.claude/*
!.claude/settings.json
/.roo/*

# hardhat generated files in workspace contract projects
Expand All @@ -67,6 +69,8 @@ contracts/*/.env
# Playwright E2E artifacts
apps/frontend/test-results/
apps/frontend/playwright-report/
apps/dashboard/test-results/
apps/dashboard/playwright-report/

# Local credentials and agent-tool artifacts
.api-key.json
Expand Down
210 changes: 72 additions & 138 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,179 +1,112 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Guidance for Claude Code (claude.ai/code) working in this repository. This root file
holds **cross-cutting** context only. Each app/package has its own `CLAUDE.md` with
scoped architecture and commands — `cd` into the relevant one before working there, and
read it first.

## Project Overview

Vortex is a cross-border payments gateway built on the Pendulum blockchain. It enables on-ramping and off-ramping of fiat currencies through stablecoins using cross-chain swaps via XCM (Cross-Consensus Messaging).
Vortex is a cross-border payments gateway built on the Pendulum blockchain. It enables
on-ramping and off-ramping of fiat currencies through stablecoins using cross-chain swaps
via XCM (Cross-Consensus Messaging).

## Monorepo Structure
## Repository Map

This is a **Bun monorepo** using workspaces:
Full wayfinding is in [`MAP.md`](MAP.md). This is a **Bun monorepo** using workspaces:

- **apps/frontend** - React 19 + Vite web application
- **apps/api** - Express backend service (PostgreSQL + Sequelize)
- **apps/rebalancer** - Liquidity rebalancing service
- **packages/shared** - Shared utilities, types, token configs, and helpers
- **packages/sdk** - Public SDK for Vortex API integration
- **apps/frontend** React 19 + Vite web app → [`apps/frontend/CLAUDE.md`](apps/frontend/CLAUDE.md)
- **apps/api** Express backend (PostgreSQL + Sequelize) → [`apps/api/CLAUDE.md`](apps/api/CLAUDE.md)
- **apps/rebalancer** — liquidity rebalancing service → [`apps/rebalancer/CLAUDE.md`](apps/rebalancer/CLAUDE.md)
- **packages/shared** — `@vortexfi/shared` utilities/configs → [`packages/shared/CLAUDE.md`](packages/shared/CLAUDE.md)
- **packages/sdk** — `@vortexfi/sdk` public SDK → [`packages/sdk/CLAUDE.md`](packages/sdk/CLAUDE.md)

## Essential Commands
## Monorepo Commands

> Always use `bun`- never `npm`, `yarn`, or `pnpm`. Run `bun lint:fix` after any code change.
> Always use `bun` — never `npm`, `yarn`, or `pnpm`. Run `bun lint:fix` after any code
> change — **except in `packages/sdk`, which is linted by ESLint** (`bun lint` inside that
> package); Biome does not govern it. Per-app test/dev/migrate commands live in each
> subdirectory's `CLAUDE.md`.

```bash
# Install all dependencies
bun install

# Development (runs frontend, backend, and shared concurrently)
bun dev

# Individual app development
bun dev:frontend # Frontend at http://127.0.0.1:5173
bun dev:backend # Backend at http://localhost:3000
bun install # install all dependencies
bun dev # frontend + backend + shared concurrently
bun dev:frontend # http://127.0.0.1:5173
bun dev:backend # http://localhost:3000
bun dev:rebalancer

# Build
bun build # Build all (shared -> sdk -> frontend -> backend)
bun build:shared # Must build shared first when making changes
bun build:frontend
bun build:backend

# Linting and formatting (uses Biome)
bun lint # Run linter
bun lint:fix # Auto-fix lint issues
bun format # Format all files
bun verify # Check without fixing

# Type checking
bun typecheck

# Database (from apps/api)
cd apps/api
bun migrate # Run migrations
bun migrate:revert # Revert all migrations
bun migrate:revert-last # Revert last migration
bun seed:phase-metadata # Seed phase configuration

# Testing
cd apps/frontend && bun test # Frontend tests (Vitest)
cd apps/api && bun test # Backend tests
```

## Architecture

### State Machine Pattern

The ramping process uses a state machine with defined phases:
- **Offramp**: prepareTransactions → squidRouter → pendulumFundEphemeral → subsidizePreSwap → nablaApprove → nablaSwap → subsidizePostSwap → performBrlaPayout → pendulumCleanup
- **Onramp**: brlaTeleport → createMoonbeamEphemeral → executeMoonbeamToPendulumXCM → subsidizePreSwap → nablaApprove → nablaSwap → executePendulumToAssetHubXCM → pendulumCleanup

Phase metadata and valid transitions are stored in PostgreSQL and seeded via `seed:phase-metadata`.

### Frontend Architecture

- **State**: Zustand stores (`stores/`) + React Context (`contexts/`)
- **Forms**: React Hook Form with Zod validation (not Yup)
- **Data Fetching**: TanStack Query
- **Routing**: TanStack Router (route tree auto-generated in `routeTree.gen.ts`)
- **State Machines**: XState machines in `machines/` for complex flows (KYC, ramp process)
- **Wallet Integration**: Wagmi/AppKit (EVM) + Talisman (Polkadot)

### Backend Architecture

- **API Layer**: Express routes in `api/routes/`, controllers in `api/controllers/`
- **Services**: Business logic in `api/services/`
- **Models**: Sequelize models in `models/` (RampState, QuoteTicket, Partner, etc.)
- **Workers**: Background jobs in `api/workers/`
- **Cross-chain**: XCM handlers, Nabla AMM integration, Stellar/BRLA APIs

### Shared Package (`@vortexfi/shared`)

Contains cross-package utilities:
- Token configurations and network definitions
- Endpoint helpers for API calls
- Contract ABIs and addresses
- Decimal/BigNumber helpers
- Logger configuration

**Important**: Always rebuild shared when making changes: `bun build:shared`
bun build # build all (shared -> sdk -> frontend -> backend)
bun build:shared # rebuild shared (see below)

After ANY change to `packages/shared`, run `bun build:shared` before running frontend/api.

## Code Style Guidelines

From `.clinerules/`:

### General
- Prefer composition over inheritance
- Create ADRs in `/docs/adr` for major architectural changes

### Frontend-Specific
- Avoid `useState` unless absolutely needed; prefer derived data and `useRef`
- Avoid `useEffect` except for external system synchronization
- Avoid `setTimeout` (always comment why if used)
- Extract complex conditional rendering into new components
- Skip useless comments; only comment race conditions, TODOs, or genuinely confusing code
bun lint # Biome lint bun lint:fix # auto-fix
bun format # format all bun verify # check without fixing
bun typecheck # type check
```

### XState v5
- Use `setup({ ... }).createMachine(...)` API- not `createMachine` directly
- Actor refs from `useActor` / `useSelector` from `@xstate/react`
- Machine files live in `apps/frontend/src/machines/`
### Always rebuild shared after changing it

### Biome Configuration
- Line width: 128
- Indent: 2 spaces
- Semicolons: always
- Trailing commas: none
- Quote style: double
- Sorted Tailwind classes enforced via `useSortedClasses` rule
`packages/shared` is consumed as built output. **After ANY change to `packages/shared`,
run `bun build:shared` before running frontend/api** — otherwise they use stale code.

## Token Exhaustiveness

`FiatToken` currently has 6 values: `EURC`, `ARS`, `BRL`, `USD`, `MXN`, `COP`.

Any `Record<FiatToken, X>` must include ALL six. Missing entries cause TypeScript errors
when shared is rebuilt. Check: tokenAvailability, mapFiatToDestination, success page
ARRIVAL_TEXT_BY_TOKEN, sep10 tokenMapping.
when shared is rebuilt. Check: `tokenAvailability`, `mapFiatToDestination`, success page
`ARRIVAL_TEXT_BY_TOKEN`, sep10 `tokenMapping`.

## Code Style

Biome config: line width 128, 2-space indent, semicolons always, no trailing commas,
double quotes, sorted Tailwind classes (`useSortedClasses`). General: prefer composition
over inheritance; create ADRs in `/docs/adr` for major architectural changes.
Frontend-specific and XState conventions live in
[`apps/frontend/CLAUDE.md`](apps/frontend/CLAUDE.md).

## No Over-Engineering

- Don't add features, refactors, or "improvements" beyond what was asked
- Don't add docstrings/comments to code you didn't touch
- Don't create helpers/utilities for one-time operations
- Don't validate inputs that can't be invalid (internal calls, typed params)
- Three similar lines is better than a premature abstraction
- Don't add features, refactors, or "improvements" beyond what was asked.
- Don't add docstrings/comments to code you didn't touch.
- Don't create helpers/utilities for one-time operations.
- Don't validate inputs that can't be invalid (internal calls, typed params).
- Three similar lines is better than a premature abstraction.

## Testing

### Test Coverage Requirements

These apply to every agent working in this repo:

- **Bug fixes and regressions**: if a bug or regression slipped past the existing tests, add a test that reproduces it (and fails without the fix) before/with the fix — so it can't silently come back. Write the test at the level that actually covers the gap (unit or integration). Only skip when a test genuinely can't capture it (e.g. purely cosmetic, environment/config, or third-party behavior) — and say why you skipped.
- **New features**: always ship the appropriate tests alongside the feature. Cover the core behavior and the edge cases that matter, not just the happy path.

### Backend Integration Tests
```bash
cd apps/api
bun test phase-processor.integration.test.ts --timeout X
```
State is stored in `lastRampState.json`. For recovery testing, copy failed state to `failedRampStateRecovery.json` and run the recovery test.
- **Bug fixes and regressions**: if a bug slipped past existing tests, add a test that
reproduces it (and fails without the fix) before/with the fix, so it can't silently
come back. Write it at the level that covers the gap (unit or integration). Only skip
when a test genuinely can't capture it (purely cosmetic, environment/config, or
third-party behavior) — and say why you skipped.
- **New features**: always ship the appropriate tests alongside the feature. Cover the
core behavior and the edge cases that matter, not just the happy path.

### Frontend Tests
```bash
cd apps/frontend
bun test
```
Per-app test commands and integration-test state handling live in each subdirectory's
`CLAUDE.md`.

## Security Spec Sync

`docs/security-spec/` is the audit-facing source of truth for security-sensitive behavior, and it must not go stale. Any change to an API feature or its business logic — auth, admin routes, quote/ramp state, signing, fees, partner pricing, integrations, migrations/schema that affect invariants, or cross-chain fund flow — must be cross-checked against the matching spec file and updated in the same change whenever the behavior it documents changed. This applies to every agent working in this repo, not just the one that first touched the code.
`docs/security-spec/` is the audit-facing source of truth for security-sensitive
behavior, and it must not go stale. Any change to an API feature or its business logic —
auth, admin routes, quote/ramp state, signing, fees, partner pricing, integrations,
migrations/schema that affect invariants, or cross-chain fund flow — must be cross-checked
against the matching spec file and updated in the same change whenever the behavior it
documents changed. This applies to every agent working in this repo.

Keep this lightweight: grep/read only the relevant spec path from `docs/security-spec/README.md`; skip this for cosmetic refactors, test-only changes, or implementation changes that do not alter security-relevant behavior. If a change alters documented behavior but you are unsure which spec file owns it, say so rather than leaving the spec silently stale.
Keep this lightweight: grep/read only the relevant spec path from
`docs/security-spec/README.md`; skip it for cosmetic refactors, test-only changes, or
implementation changes that do not alter security-relevant behavior. If a change alters
documented behavior but you are unsure which spec file owns it, say so rather than leaving
the spec silently stale.

## Type Issues

If IDE doesn't detect `@pendulum-chain/types` properly, ensure all `@polkadot/*` packages match versions in the types package. The root `package.json` uses `catalog:` for version management.
If the IDE doesn't detect `@pendulum-chain/types` properly, ensure all `@polkadot/*`
packages match versions in the types package. The root `package.json` uses `catalog:` for
version management.

---

Expand Down Expand Up @@ -233,4 +166,5 @@ For multi-step tasks, state a brief plan:
3. [Step] → verify: [check]
```

Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
Strong success criteria let you loop independently. Weak criteria ("make it work")
require constant clarification.
Loading
Loading