Skip to content
Open
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
119 changes: 119 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Turbolong

Leveraged yield positions on [Blend Protocol](https://blend.capital) pools — single-click recursive lending loops on Stellar.

## Quick start (5 minutes)

**Prerequisites:** Docker, Node.js ≥ 20, Git.

```bash
git clone https://github.com/Dgetsylver/TurboLong.git
cd TurboLong
bash dev/start.sh
```

Open **http://localhost:5173** in your browser. That's it.

`dev/start.sh` will:
1. Pull and start a `stellar/quickstart` container (Soroban RPC on port 8000, Horizon on 8001).
2. Install frontend dependencies.
3. Launch the Vite dev server at `http://localhost:5173`.
4. Start the Alerts Cloudflare Worker at `http://localhost:8787` (requires `wrangler` — skipped if absent).

To skip the local node and use Stellar Testnet instead:

```bash
bash dev/start.sh --testnet
```

### Environment variables

Copy `dev/.env.example` to `.env.local` in the repo root and fill in any overrides:

| Variable | Default | Description |
|---|---|---|
| `STELLAR_RPC_PORT` | `8000` | Local Quickstart Soroban RPC port |
| `STELLAR_HORIZON_PORT` | `8001` | Local Quickstart Horizon port |
| `VITE_RPC_URL` | _(network default)_ | Override RPC URL in the frontend |
| `VITE_HORIZON_URL` | _(network default)_ | Override Horizon URL in the frontend |
| `CLOUDFLARE_ACCOUNT_ID` | — | Needed to deploy the Alerts worker |
| `CLOUDFLARE_API_TOKEN` | — | Needed to deploy the Alerts worker |

---

## Project layout

```
frontend/ Vite + TypeScript UI
src/
main.ts App entry — wallet, views, interactions
blend.ts Blend Protocol pool helpers
defindex.ts DefIndex vault helpers
.storybook/ Storybook config
src/stories/ Component stories

alerts/ Cloudflare Worker — position health alerts
scripts/ Off-chain utility scripts (Node.js)
src/ Rust binaries (rate_calc, simulate, execute_loop)
dev/ Local dev quickstart scripts
e2e/ Playwright end-to-end tests
.github/ CI workflows
```

---

## Running tests

### Unit tests (Vitest)

```bash
cd frontend && npm test
```

### Rust parity tests

```bash
cargo test
```

### End-to-end tests (Playwright)

```bash
cd e2e
npm install
npm run test:e2e
```

Screenshots are captured automatically on failure and saved to `e2e/test-results/`.

---

## Storybook

Isolate and iterate on UI components in isolation:

```bash
cd frontend
npm install
npm run storybook # dev server at http://localhost:6006
npm run build-storybook # static build
```

Components covered: HF Badge, Leverage Slider, Pool Card, Asset Picker, APR Card, Toast, Stat Card.

---

## CI

| Workflow | Trigger | What it does |
|---|---|---|
| `rust-lint.yml` | Push/PR touching `src/**` or `Cargo.toml` | `cargo clippy -D warnings` + `cargo fmt --check` |
| `parity.yml` | Pull request | Rust parity test suite |
| `e2e.yml` | Push/PR touching `frontend/**` or `e2e/**` | Playwright E2E suite; uploads screenshots on failure |
| `deploy.yml` | Push to non-main branches touching `frontend/**` | Deploys frontend to GitHub Pages |

---

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md).
19 changes: 19 additions & 0 deletions dev/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copy this file to .env.local and fill in the values.
# All variables are optional — defaults are shown.

# Stellar Quickstart port for Soroban RPC (local dev only)
STELLAR_RPC_PORT=8000

# Stellar Quickstart port for Horizon (local dev only)
STELLAR_HORIZON_PORT=8001

# Override the RPC URL used by the frontend (leave blank to use defaults)
# Set to http://localhost:8000/soroban/rpc when running with --quickstart
VITE_RPC_URL=

# Override the Horizon URL used by the frontend
VITE_HORIZON_URL=

# Cloudflare D1 / Alerts Worker config (only needed if running the Alerts worker)
CLOUDFLARE_ACCOUNT_ID=
CLOUDFLARE_API_TOKEN=
131 changes: 131 additions & 0 deletions dev/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/env bash
# Turbolong local dev quickstart.
# Spins up a local Stellar Quickstart node, deploys contracts,
# starts the frontend dev server, and the Alerts worker — all in one command.
#
# Prerequisites: Docker, Node.js >= 20, Rust + cargo, wrangler (npm i -g wrangler)
# Usage: bash dev/start.sh [--testnet]

set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="$ROOT/.env.local"

# ── Colours ───────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
info() { echo -e "${BLUE}[turbolong]${NC} $*"; }
success() { echo -e "${GREEN}[turbolong]${NC} $*"; }
warn() { echo -e "${YELLOW}[turbolong]${NC} $*"; }
die() { echo -e "${RED}[turbolong] ERROR:${NC} $*" >&2; exit 1; }

# ── Prerequisites check ───────────────────────────────────────────────────────
check_dep() {
command -v "$1" >/dev/null 2>&1 || die "'$1' is required but not installed. See README for setup instructions."
}

check_dep docker
check_dep node
check_dep npm

NODE_VERSION=$(node -e "process.exit(parseInt(process.versions.node) < 20 ? 1 : 0)" 2>/dev/null) \
|| die "Node.js >= 20 is required (found $(node --version))"

# ── Parse args ────────────────────────────────────────────────────────────────
USE_QUICKSTART=true
NETWORK="local"
for arg in "$@"; do
case $arg in
--testnet) USE_QUICKSTART=false; NETWORK="testnet" ;;
esac
done

# ── Load .env.local if it exists ──────────────────────────────────────────────
if [[ -f "$ENV_FILE" ]]; then
# shellcheck disable=SC1090
set -o allexport; source "$ENV_FILE"; set +o allexport
info "Loaded env from .env.local"
fi

# ── 1. Stellar Quickstart (local only) ───────────────────────────────────────
QUICKSTART_CONTAINER="turbolong-stellar"
RPC_PORT="${STELLAR_RPC_PORT:-8000}"
HORIZON_PORT="${STELLAR_HORIZON_PORT:-8001}"

if $USE_QUICKSTART; then
info "Starting Stellar Quickstart container…"

if docker ps --format '{{.Names}}' | grep -q "^${QUICKSTART_CONTAINER}$"; then
warn "Container '$QUICKSTART_CONTAINER' already running — skipping."
else
docker run -d \
--name "$QUICKSTART_CONTAINER" \
--rm \
-p "${RPC_PORT}:8000" \
-p "${HORIZON_PORT}:8001" \
stellar/quickstart:latest \
--standalone \
--enable-soroban-rpc \
--protocol-version 22 \
--limits default
info "Waiting for Quickstart RPC to become ready…"
for i in $(seq 1 30); do
if curl -sf "http://localhost:${RPC_PORT}/soroban/rpc" -d '{"jsonrpc":"2.0","id":1,"method":"getHealth"}' \
-H "Content-Type: application/json" >/dev/null 2>&1; then
success "Stellar Quickstart is ready on port ${RPC_PORT}"
break
fi
[[ $i -eq 30 ]] && die "Quickstart failed to start after 60s"
sleep 2
done
fi
fi

# ── 2. Install frontend deps ──────────────────────────────────────────────────
info "Installing frontend dependencies…"
npm install --prefix "$ROOT/frontend" --silent

# ── 3. Start frontend dev server ──────────────────────────────────────────────
info "Starting frontend dev server at http://localhost:5173 …"
npm run dev --prefix "$ROOT/frontend" &
FRONTEND_PID=$!

# ── 4. Start Alerts worker (optional — requires wrangler) ────────────────────
if command -v wrangler >/dev/null 2>&1; then
info "Starting Alerts Cloudflare Worker…"
(cd "$ROOT/alerts" && npm install --silent && wrangler dev --port 8787) &
ALERTS_PID=$!
else
warn "wrangler not found — skipping Alerts worker. Install with: npm i -g wrangler"
ALERTS_PID=""
fi

# ── Done ───────────────────────────────────────────────────────────────────────
echo ""
success "Turbolong is running!"
echo ""
echo " Frontend: http://localhost:5173"
if $USE_QUICKSTART; then
echo " Stellar RPC: http://localhost:${RPC_PORT}/soroban/rpc"
echo " Horizon: http://localhost:${HORIZON_PORT}"
else
echo " Network: Stellar Testnet"
fi
if [[ -n "$ALERTS_PID" ]]; then
echo " Alerts: http://localhost:8787"
fi
echo ""
echo "Press Ctrl+C to stop all services."

# ── Cleanup on exit ───────────────────────────────────────────────────────────
cleanup() {
info "Shutting down…"
[[ -n "$FRONTEND_PID" ]] && kill "$FRONTEND_PID" 2>/dev/null || true
[[ -n "${ALERTS_PID:-}" ]] && kill "$ALERTS_PID" 2>/dev/null || true
if $USE_QUICKSTART; then
docker stop "$QUICKSTART_CONTAINER" 2>/dev/null || true
fi
success "Done."
}
trap cleanup EXIT INT TERM

wait