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
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# http://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false
indent_size = unset

[*.{json,yml,yaml}]
indent_size = 2
21 changes: 21 additions & 0 deletions .editorconfig-checker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"Verbose": false,
"Format": "default",
"Path": ".",
"IgnoreDefaults": false,
"SpacesAfterTabs": false,
"NoColor": false,
"Exclude": [
"node_modules",
"\\.next",
"\\.git",
"coverage",
"package-lock\\.json",
"public",
"\\.storybook",
"\\.husky"
],
"AllowedEmptyFiles": [
"\\.nvmrc"
]
}
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# ─── App / Canonical URL ──────────────────────────────────────────────────
NEXT_PUBLIC_SITE_URL=http://localhost:3000 # Canonical site URL used for OpenGraph/metadata generation

# ─── Relay API ──────────────────────────────────────────────────────────
NEXT_PUBLIC_API_URL=http://localhost:4000 # Base URL of the vortex-backend REST API (set to your relay server address)
NEXT_PUBLIC_WS_URL=ws://localhost:4000/ws # WebSocket endpoint for the live intent feed (match the relay's WS port)
Expand All @@ -11,4 +14,4 @@ NEXT_PUBLIC_NETWORK=testnet # Stellar network to connect to: testnet | futurene

# Deployed contract IDs (leave blank until deployed)
NEXT_PUBLIC_SETTLEMENT_CONTRACT= # Contract ID for the settlement contract on the chosen network
NEXT_PUBLIC_SOLVER_REGISTRY_CONTRACT= # Contract ID for the solver registry contract on the chosen network
NEXT_PUBLIC_SOLVER_REGISTRY_CONTRACT= # Contract ID for the solver registry contract on the chosen network
13 changes: 8 additions & 5 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ Closes #

-

## Testing
## Testing & QA

<!-- Describe how you tested this. Paste relevant test output or screenshots. -->
<!-- Describe how you tested this. Paste relevant test output, screenshots, or staging demo links. -->

- [ ] `npm run check:editorconfig` passes
- [ ] `npm run typecheck` (`tsc --noEmit`) passes cleanly
- [ ] `npm test` and `npm run test:coverage` pass
- [ ] `npm run build` passes
- [ ] `npx tsc --noEmit` passes
- [ ] `npm test` passes (or note any skipped/unrelated failures)
- [ ] Verified on local / staging environment with seeded data (where applicable)

## Preview

Expand All @@ -27,6 +29,7 @@ Closes #
## Checklist

- [ ] Self-reviewed the diff
- [ ] Formatting adheres to `.editorconfig`
- [ ] Added or updated tests for new behaviour
- [ ] No secrets or credentials committed
- [ ] No secrets or credentials committed (used `.env.example` / `.env.staging.example` templates)
- [ ] PR title follows conventional commits (`feat:`, `fix:`, `chore:`, etc.)
143 changes: 143 additions & 0 deletions .github/workflows/uptime-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: Synthetic Uptime & Health Check

on:
schedule:
# Run every 30 minutes
- cron: '*/30 * * * *'
workflow_dispatch:
inputs:
production_url:
description: 'Target Frontend URL to check'
required: false
default: 'https://vortex-frontend.vercel.app'
api_url:
description: 'Target Backend API URL to check'
required: false
default: 'https://api.vortex-protocol.org'

jobs:
uptime-check:
name: Synthetic Health Monitor
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Resolve Target Endpoints
id: targets
run: |
PROD_URL="${{ github.event.inputs.production_url || vars.PRODUCTION_URL || 'https://vortex-frontend.vercel.app' }}"
API_URL="${{ github.event.inputs.api_url || vars.NEXT_PUBLIC_API_URL || 'https://api.vortex-protocol.org' }}"
echo "prod_url=$PROD_URL" >> $GITHUB_OUTPUT
echo "api_url=$API_URL" >> $GITHUB_OUTPUT
echo "Checking Frontend: $PROD_URL"
echo "Checking Backend Relay: $API_URL"

- name: Check Production Frontend Uptime
id: check-frontend
run: |
TARGET_URL="${{ steps.targets.outputs.prod_url }}"
MAX_ATTEMPTS=2
DELAY_SEC=5
SUCCESS=false
HTTP_CODE=""
LATENCY=""

echo "Starting frontend synthetic probe against $TARGET_URL (up to $MAX_ATTEMPTS attempts)..."

for attempt in $(seq 1 $MAX_ATTEMPTS); do
echo "Attempt $attempt of $MAX_ATTEMPTS..."
START_TIME=$(date +%s%3N)
HTTP_CODE=$(curl -s -o response_fe.html -w "%{http_code}" --connect-timeout 10 --max-time 15 -L "$TARGET_URL" || echo "000")
END_TIME=$(date +%s%3N)
LATENCY=$((END_TIME - START_TIME))

echo "Response status: $HTTP_CODE (Latency: ${LATENCY}ms)"

if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 400 ]; then
if grep -qi -E "vortex|stellar|intent" response_fe.html || [ -s response_fe.html ]; then
echo "Content validation passed: found expected frontend markup."
SUCCESS=true
break
else
echo "Warning: HTTP status was $HTTP_CODE, but expected branding text was not matched in response."
fi
fi

if [ "$attempt" -lt "$MAX_ATTEMPTS" ]; then
echo "Transient check failure; retrying in ${DELAY_SEC}s..."
sleep $DELAY_SEC
fi
done

if [ "$SUCCESS" != "true" ]; then
echo "::error title=Frontend Uptime Check Failed::Production URL $TARGET_URL is unhealthy (HTTP $HTTP_CODE, Latency: ${LATENCY}ms)"
echo "### ❌ Frontend Uptime Check Failed" >> $GITHUB_STEP_SUMMARY
echo "- **Target URL:** \`$TARGET_URL\`" >> $GITHUB_STEP_SUMMARY
echo "- **HTTP Code:** \`$HTTP_CODE\`" >> $GITHUB_STEP_SUMMARY
echo "- **Latency:** \`${LATENCY}ms\`" >> $GITHUB_STEP_SUMMARY
echo "- **Attempts:** $MAX_ATTEMPTS" >> $GITHUB_STEP_SUMMARY
exit 1
fi

echo "### ✅ Frontend Uptime Check Passed" >> $GITHUB_STEP_SUMMARY
echo "- **Target URL:** \`$TARGET_URL\`" >> $GITHUB_STEP_SUMMARY
echo "- **HTTP Code:** \`$HTTP_CODE\`" >> $GITHUB_STEP_SUMMARY
echo "- **Latency:** \`${LATENCY}ms\`" >> $GITHUB_STEP_SUMMARY

- name: Check Backend API Relay Health
id: check-backend
run: |
API_URL="${{ steps.targets.outputs.api_url }}"
HEALTH_ENDPOINT="${API_URL%/}/health"
MAX_ATTEMPTS=2
DELAY_SEC=5
SUCCESS=false
HTTP_CODE=""
LATENCY=""

echo "Starting backend relay probe against $HEALTH_ENDPOINT (up to $MAX_ATTEMPTS attempts)..."

for attempt in $(seq 1 $MAX_ATTEMPTS); do
echo "Attempt $attempt of $MAX_ATTEMPTS..."
START_TIME=$(date +%s%3N)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 15 "$HEALTH_ENDPOINT" || echo "000")
END_TIME=$(date +%s%3N)
LATENCY=$((END_TIME - START_TIME))

echo "Response status: $HTTP_CODE (Latency: ${LATENCY}ms)"

if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 400 ]; then
SUCCESS=true
break
fi

# Fallback to root if /health is not defined
if [ "$HTTP_CODE" = "404" ] || [ "$HTTP_CODE" = "000" ]; then
ROOT_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 15 "$API_URL" || echo "000")
if [ "$ROOT_CODE" -ge 200 ] && [ "$ROOT_CODE" -lt 500 ] && [ "$ROOT_CODE" != "000" ]; then
echo "Relay root responded with HTTP $ROOT_CODE (reachable)."
SUCCESS=true
HTTP_CODE=$ROOT_CODE
break
fi
fi

if [ "$attempt" -lt "$MAX_ATTEMPTS" ]; then
echo "Transient check failure; retrying in ${DELAY_SEC}s..."
sleep $DELAY_SEC
fi
done

if [ "$SUCCESS" != "true" ]; then
echo "::error title=Backend Relay Health Check Failed::Backend Relay $HEALTH_ENDPOINT is unreachable (HTTP $HTTP_CODE, Latency: ${LATENCY}ms)"
echo "### ❌ Backend Relay Health Check Failed" >> $GITHUB_STEP_SUMMARY
echo "- **Target URL:** \`$HEALTH_ENDPOINT\`" >> $GITHUB_STEP_SUMMARY
echo "- **HTTP Code:** \`$HTTP_CODE\`" >> $GITHUB_STEP_SUMMARY
echo "- **Latency:** \`${LATENCY}ms\`" >> $GITHUB_STEP_SUMMARY
exit 1
fi

echo "### ✅ Backend Relay Health Check Passed" >> $GITHUB_STEP_SUMMARY
echo "- **Target URL:** \`$HEALTH_ENDPOINT\`" >> $GITHUB_STEP_SUMMARY
echo "- **HTTP Code:** \`$HTTP_CODE\`" >> $GITHUB_STEP_SUMMARY
echo "- **Latency:** \`${LATENCY}ms\`" >> $GITHUB_STEP_SUMMARY
1 change: 1 addition & 0 deletions .lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
'*.{js,jsx,ts,tsx}': ['eslint --fix'],
'*.{ts,tsx}': () => 'tsc --noEmit',
'*': ['editorconfig-checker'],
};
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]

### Added

- Initial changelog entry

## [0.1.0] — 2025-07-30

### Added

- Wallet integration — connect/disconnect Freighter, sign swaps and solver registrations, persist sessions across reloads
- Swap interface with live quotes over SWR and end-to-end Freighter signing
- Intent explorer page (`/explore`) — browse all intents with status/chain filters, sorting, and pagination
Expand All @@ -28,4 +30,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## Contributing

Update `CHANGELOG.md` when merging a feature or fix PR. Every merged PR that ships a user-visible change (new feature, bug fix, or notable enhancement) should add an entry under the `[Unreleased]` section in the appropriate subsection (`### Added`, `### Changed`, `### Fixed`, etc.). Before tagging a release, merge all `[Unreleased]` entries into the latest version heading and remove the `[Unreleased]` section.
Update `CHANGELOG.md` when merging a feature or fix PR. Every merged PR that ships a user-visible change (new feature, bug fix, or notable enhancement) should add an entry under the `[Unreleased]` section in the appropriate subsection (`### Added`, `### Changed`, `### Fixed`, etc.). Before tagging a release, merge all `[Unreleased]` entries into the latest version heading and remove the `[Unreleased]` section.
90 changes: 72 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ npm run dev # http://localhost:3000

### Required Environment Variables

| Variable | Where to get the value |
|---|---|
| `NEXT_PUBLIC_API_URL` | URL of your running `vortex-backend` relay |
| `NEXT_PUBLIC_WS_URL` | WebSocket URL of the relay (usually with `/ws` path) |
| `NEXT_PUBLIC_NETWORK` | Stellar network: `testnet`, `futurenet`, or `mainnet` |
| `NEXT_PUBLIC_SETTLEMENT_CONTRACT` | Settlement contract ID from `vortex-contract` deployment |
| Variable | Where to get the value |
| -------------------------------------- | ------------------------------------------------------------- |
| `NEXT_PUBLIC_API_URL` | URL of your running `vortex-backend` relay |
| `NEXT_PUBLIC_WS_URL` | WebSocket URL of the relay (usually with `/ws` path) |
| `NEXT_PUBLIC_NETWORK` | Stellar network: `testnet`, `futurenet`, or `mainnet` |
| `NEXT_PUBLIC_SETTLEMENT_CONTRACT` | Settlement contract ID from `vortex-contract` deployment |
| `NEXT_PUBLIC_SOLVER_REGISTRY_CONTRACT` | Solver registry contract ID from `vortex-contract` deployment |

---
Expand Down Expand Up @@ -147,13 +147,67 @@ If preview-specific variables are not set, the workflow uses sensible defaults p

### Scripts

| Script | Description |
|---|---|
| `npm run dev` | Dev server |
| `npm run build` | Production build |
| `npm run start` | Serve the production build |
| `npm run lint` | `next lint` |
| `npm test` | Run the Vitest suite |
| Script | Description |
| ---------------------------- | ----------------------------------------------------------------- |
| `npm run dev` | Start Next.js local development server |
| `npm run build` | Compile and bundle production application |
| `npm run start` | Serve production build locally |
| `npm run lint` | Run ESLint across codebase |
| `npm run check:editorconfig` | Verify formatting consistency with `.editorconfig` |
| `npm run check:env` | Validate that all referenced environment variables are documented |
| `npm run typecheck` | Run strict TypeScript typechecking (`tsc --noEmit`) |
| `npm test` | Run the Vitest test suite |
| `npm run test:coverage` | Run tests with V8 coverage reports |

---

## Staging Environment & QA

A dedicated staging environment is configured for demoing features, verifying pull requests, and QA testing against realistic synthetic datasets without requiring live mainnet funds or local relay infrastructure.

### Connecting to Staging

1. Copy the staging configuration template:
```bash
cp .env.staging.example .env.local
```
2. Start the development server against the staging relay:
```bash
npm run dev
```

### Staging Architecture & Synthetic Datasets

The staging deployment targets Stellar Testnet and connects to a staging relay seeded with synthetic test datasets:

- **Intents:** Spans all lifecycle states (`pending`, `accepted`, `filled`, `failed`) across supported chains (Stellar, Ethereum, Arbitrum, Polygon).
- **Solvers:** Multiple solver profiles with varying bond sizes, completion volumes, and latency profiles to validate leaderboard rendering and edge cases.
- **Data Seeding Script:** Maintainers can seed or reset staging data using:
```bash
node scripts/seed-staging-data.mjs --api-url <STAGING_RELAY_URL>
```

---

## Operations & Monitoring

### Synthetic Uptime & Health Checks

Production availability is monitored automatically via a scheduled GitHub Actions workflow (`.github/workflows/uptime-check.yml`):

- **Frequency:** Probes every 30 minutes with `workflow_dispatch` manual trigger support.
- **Frontend Probe:** Asserts HTTP `200` response and validates presence of core branding/HTML markup.
- **Relay Health Probe:** Checks `NEXT_PUBLIC_API_URL/health` to ensure relay backend responsiveness.
- **Transient Mitigation:** Executes automated retries with backoff before flagging incidents.

---

## Code Standards

- **Formatting:** Enforced via `.editorconfig` (UTF-8, 2 spaces, LF line endings, trailing whitespace trimming). Validated in CI via `npm run check:editorconfig`.
- **TypeScript:** Configured with hardened strictness flags (`noImplicitOverride`, `noPropertyAccessFromIndexSignature`, `noUnusedLocals`, `noUnusedParameters`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`). Checked via `npm run typecheck`.

---

### Bundle Analysis

Expand Down Expand Up @@ -253,11 +307,11 @@ For more details, see the [CODEOWNERS](./.github/CODEOWNERS) file.

Issues on the Wave tracker use the following complexity labels with corresponding point values to help contributors find tasks that match their availability:

| Label | Points | Description |
|---|---|---|
| Trivial | 1 | Small fix, typo, or minor change — quick to complete |
| Medium | 3 | Feature work or bug fix requiring moderate investigation |
| High | 5 | Significant implementation effort or architectural change |
| Label | Points | Description |
| ------- | ------ | --------------------------------------------------------- |
| Trivial | 1 | Small fix, typo, or minor change — quick to complete |
| Medium | 3 | Feature work or bug fix requiring moderate investigation |
| High | 5 | Significant implementation effort or architectural change |

See our repository [CONTRIBUTING.md](./CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) for contribution rules and community standards.

Expand Down
10 changes: 5 additions & 5 deletions docs/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ status (not just color) so it doesn't rely on color alone to differentiate.
```tsx
import { IntentStatusBadge } from "@/components/IntentStatusBadge";

<IntentStatusBadge status="filled" />
<IntentStatusBadge status="filled" />;
```

**Props**

| prop | type | required | notes |
| -------- | -------------- | -------- | ------------------------------------------------- |
| prop | type | required | notes |
| -------- | -------------- | -------- | ------------------------------------------------------------------------------ |
| `status` | `IntentStatus` | yes | one of `"pending" \| "accepted" \| "filled" \| "failed"` (see `src/lib/types`) |

No other configuration — styling and icon are derived entirely from `status` via
Expand All @@ -39,7 +39,7 @@ toasts.
```tsx
import { ToastViewport } from "@/components/ToastViewport";

<ToastViewport />
<ToastViewport />;
```

**Props**: none. Mount it once, near the root of the tree — it's already mounted
Expand All @@ -64,7 +64,7 @@ import { ConnectWalletButton } from "@/components/ConnectWalletButton";

**Props**

| prop | type | required | default | notes |
| prop | type | required | default | notes |
| --------- | --------- | -------- | ------- | ---------------------------------------------------------------------- |
| `compact` | `boolean` | no | `false` | tighter padding/layout for constrained spaces (e.g. mobile nav/header) |

Expand Down
Loading
Loading