🧩 Peake DEX β€” Full Architecture & Deployment Guide (Docs + Tutorial)


http://geocities.ws/peakecoin/pekdex

1) Complete Architecture Analysis

File structure (at a glance)

Peake_Dex/
β”œβ”€ frontend/                 # Static site (Geocities-friendly)
β”‚  β”œβ”€ index.html             # Landing + balances/auth state
β”‚  β”œβ”€ config.js              # Backend base URL
β”‚  └─ pairs/
β”‚     └─ pair.html           # Per-pair trading UI
β”‚
β”œβ”€ backend/                  # Flask API
β”‚  β”œβ”€ app.py                 # Endpoints + matching loop
β”‚  β”œβ”€ peake-dex.service      # systemd unit
β”‚  β”œβ”€ deploy.sh              # Production deploy script
β”‚  β”œβ”€ deploy-simple.sh       # Minimal deploy script
β”‚  β”œβ”€ requirements.txt       # Python dependencies
β”‚  └─ README.md              # Backend setup/how-to
β”‚
β”œβ”€ README.md                 # Top-level docs
└─ LICENSE                   # MIT

Separation Strategy

  • Frontend = static HTML/JS. No private keys; signing is delegated to Hive Keychain in the browser. Hosted on free/static hosting (e.g., Geocities).
  • Backend = Flask API with SQLite storage, background order-matching loop, and optional Hive integration (e.g., account validation, on-chain transfers, or audit trails) using beem.

Deployment Shape

  • Recommended split: Frontend on Geocities (static), Backend on a VPS (Python/Flask). This keeps costs low, avoids storing keys server-side, and keeps the API controlled.

2) Frontend Deep Dive

index.html β€” User auth & balance management (+ CORS awareness)

What it does

  • Detects Hive Keychain and toggles UI accordingly.
  • Fetches and renders: supported pairs, and the user’s balances (via backend).
  • Relies on the backend to be CORS-enabled (or you use an allowed-origin policy).

Example (minimal pattern)

(html comment removed:  index.html core behavior (conceptual) )
<script src="./config.js"></script>
<script>
  const hasKeychain = typeof window.hive_keychain !== 'undefined';
  const username = localStorage.getItem('pek_user') || '';

  // Fetch available pairs for the UI
  fetch(`${CONFIG.API_BASE_URL}/api/pairs`)
    .then(r => r.json())
    .then(pairs => renderPairs(pairs))
    .catch(console.error);

  // Fetch balances (if we have a username)
  async function loadBalances() {
    if (!username) return;
    const r = await fetch(`${CONFIG.API_BASE_URL}/api/balances?user=${encodeURIComponent(username)}`);
    if (r.ok) {
      const data = await r.json();
      renderBalances(data);
    }
  }

  // CORS note:
  // The backend enables CORS. If you reverse-proxy or use HTTPS,
  // configure Access-Control-Allow-Origin to your Geocities domain.
</script>

pairs/pair.html β€” Trading interface, order execution, Keychain integration

What it does

  • Parses ?base=PEK&quote=SWAP.HIVE from the URL.
  • Renders order form (side/price/amount).
  • Pulls live orderbook + trade history from the backend.
  • Uses Hive Keychain to sign the user’s intent (no private keys in JS).

Example (placing an order with Keychain)

<script src="../config.js"></script>
<script>
  const params = new URLSearchParams(location.search);
  const base  = params.get('base')  || 'PEK';
  const quote = params.get('quote') || 'SWAP.HIVE';

  async function placeOrder(side, price, amount, username) {
    if (!window.hive_keychain) {
      alert('Hive Keychain not detected. Please install/enable it.');
      return;
    }

    // Build an order intent payload (signed by Keychain).
    const intent = { base, quote, side, price, amount, username, ts: Date.now() };

    // Example: request a custom_json signature for audit/authorization
    // (You can adapt to your preferred on-chain pattern.)
    hive_keychain.requestCustomJson(
      username,
      'ssc-mainnet-hive',         // Hive-Engine domain tag
      'Active',                   // needs active for market ops
      JSON.stringify(intent),     // your order intent payload
      'Place PEK DEX order',
      async (res) => {
        if (!res || !res.success) {
          alert(`Keychain rejected: ${res && res.message ? res.message : 'unknown error'}`);
          return;
        }
        // Send the signed order to backend for validation + matching
        const r = await fetch(`${CONFIG.API_BASE_URL}/api/order`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ intent, keychain_result: res })
        });
        const out = await r.json();
        out.ok ? notify('Order accepted!') : alert(out.error || 'Order failed.');
      }
    );
  }
</script>

config.js β€” Configuration management

Shape

// frontend/config.js
const CONFIG = {
  API_BASE_URL: 'http://YOUR_SERVER_IP:8080'
};
  • Change API_BASE_URL to your Flask server’s address/port.
  • Keep this file under frontend/ so it’s easy to switch environments.

3) Backend Implementation

app.py β€” All API endpoints, matching, optional blockchain ops

What it exposes (typical set)

  • GET /api/pairs β€” list supported pairs
  • GET /api/orderbook?base=PEK&quote=SWAP.HIVE β€” current bids/asks
  • GET /api/history?base=PEK&quote=SWAP.HIVE β€” recent trades
  • GET /api/balances?user=<name> β€” simplified balance view
  • POST /api/order β€” place a signed order intent
  • FTP admin utilities (optional, controlled): /api/ftp/... for backup/import

Skeleton (conceptual)

# backend/app.py
import os, sqlite3, threading, time, json
from flask import Flask, request, jsonify
from flask_cors import CORS

app = Flask(__name__)
CORS(app)  # allow your Geocities origin

DB_PATH = os.getenv('PEK_DB_PATH', 'dex.db')
def _db():
  return sqlite3.connect(DB_PATH, check_same_thread=False)

@app.get('/api/pairs')
def pairs():
  # Could also be dynamic from a table
  return jsonify(["PEK/SWAP.HIVE", "PEK/SWAP.BTC", "PEK/SWAP.LTC", "PEK/SWAP.ETH", "PEK/SWAP.DOGE"])

@app.get('/api/orderbook')
def orderbook():
  base  = request.args.get('base', 'PEK')
  quote = request.args.get('quote', 'SWAP.HIVE')
  with _db() as cx:
    bids = cx.execute("SELECT price, amount FROM orders WHERE base=? AND quote=? AND side='buy'  AND status='open' ORDER BY price DESC", (base, quote)).fetchall()
    asks = cx.execute("SELECT price, amount FROM orders WHERE base=? AND quote=? AND side='sell' AND status='open' ORDER BY price ASC",  (base, quote)).fetchall()
  return jsonify({ "bids": [dict(price=p, amount=a) for (p,a) in bids],
                   "asks": [dict(price=p, amount=a) for (p,a) in asks] })

@app.get('/api/history')
def history():
  base  = request.args.get('base', 'PEK')
  quote = request.args.get('quote', 'SWAP.HIVE')
  with _db() as cx:
    rows = cx.execute("SELECT ts, price, amount, taker_side FROM trades WHERE base=? AND quote=? ORDER BY id DESC LIMIT 200", (base, quote)).fetchall()
  return jsonify([dict(ts=ts, price=pr, amount=am, taker_side=side) for (ts, pr, am, side) in rows])

@app.get('/api/balances')
def balances():
  user = request.args.get('user', '')
  if not user:
    return jsonify([])

  with _db() as cx:
    # Simplified: internal ledger (off-chain) balances from fills/deposits
    rows = cx.execute("SELECT token, amount FROM balances WHERE username=?", (user,)).fetchall()
  return jsonify([dict(token=t, amount=a) for (t,a) in rows])

@app.post('/api/order')
def place_order():
  data = request.get_json(force=True) or {}
  intent = data.get('intent', {})
  kcres  = data.get('keychain_result', {})

  # 1) Validate inputs
  ok, msg = validate_intent(intent)
  if not ok:
    return jsonify({"ok": False, "error": msg}), 400

  # 2) Validate Keychain signature/format (shape, username matches, etc.)
  ok, msg = validate_keychain_result(kcres, intent)
  if not ok:
    return jsonify({"ok": False, "error": msg}), 400

  # 3) Insert order; matcher thread will execute fills
  with _db() as cx:
    cx.execute("""INSERT INTO orders (username, base, quote, side, price, amount, status, ts)
                  VALUES (?,?,?,?,?,?, 'open', strftime('%s','now'))""",
               (intent['username'], intent['base'], intent['quote'], intent['side'],
                float(intent['price']), float(intent['amount'])))
    cx.commit()
  return jsonify({"ok": True})

# --- Matching loop (very simple example) ---
def match_loop():
  while True:
    try:
      with _db() as cx:
        # Find best bid/ask and cross if possible
        best_bid = cx.execute("SELECT id, price, amount FROM orders WHERE side='buy'  AND status='open' ORDER BY price DESC, id ASC LIMIT 1").fetchone()
        best_ask = cx.execute("SELECT id, price, amount FROM orders WHERE side='sell' AND status='open' ORDER BY price ASC,  id ASC LIMIT 1").fetchone()
        if best_bid and best_ask and best_bid[1] >= best_ask[1]:
          # Execute at midpoint or ask price (policy choice)
          exec_price = best_ask[1]
          filled_amt = min(best_bid[2], best_ask[2])
          # ... update orders/trades/balances ...
          # (Omitted for brevity; see DB schema below)
          pass
    except Exception as e:
      print("matcher error:", e)
    time.sleep(1)

threading.Thread(target=match_loop, daemon=True).start()

if __name__ == "__main__":
  app.run(host="0.0.0.0", port=8080)

Database schema & SQLite usage

Core tables (example DDL)

-- Pairs you enable
CREATE TABLE IF NOT EXISTS pairs (
  id INTEGER PRIMARY KEY,
  base TEXT NOT NULL,
  quote TEXT NOT NULL,
  UNIQUE(base, quote)
);

-- Orders
CREATE TABLE IF NOT EXISTS orders (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT NOT NULL,
  base TEXT NOT NULL,
  quote TEXT NOT NULL,
  side TEXT CHECK(side IN ('buy','sell')) NOT NULL,
  price REAL NOT NULL,
  amount REAL NOT NULL,
  status TEXT CHECK(status IN ('open','filled','canceled','partial')) NOT NULL DEFAULT 'open',
  ts INTEGER NOT NULL
);

-- Trades (executions)
CREATE TABLE IF NOT EXISTS trades (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  base TEXT NOT NULL,
  quote TEXT NOT NULL,
  price REAL NOT NULL,
  amount REAL NOT NULL,
  taker_side TEXT CHECK(taker_side IN ('buy','sell')) NOT NULL,
  ts INTEGER NOT NULL
);

-- Ledger balances (off-chain internal accounting)
CREATE TABLE IF NOT EXISTS balances (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT NOT NULL,
  token TEXT NOT NULL,
  amount REAL NOT NULL,
  UNIQUE(username, token)
);

-- Optional: config for FTP backups
CREATE TABLE IF NOT EXISTS ftp_config (
  id INTEGER PRIMARY KEY,
  host TEXT, user TEXT, password TEXT, path TEXT
);

Multi-account strategy per trading pair

  • Export different env vars for each quote asset. The backend chooses the correct key/account per pair.

    export PEK_SWAP_HIVE_KEY="your_active_key"
    export PEK_SWAP_BTC_KEY="your_active_key"
    export PEK_SWAP_LTC_KEY="your_active_key"
    export PEK_SWAP_ETH_KEY="your_active_key"
    export PEK_SWAP_DOGE_KEY="your_active_key"

In app.py, map (base, quote) β†’ the proper environment variable. This keeps operational separation across markets.

Security features & validation (server-side)

  • Validate user/account name shape (regex), allowed charset, max lengths.
  • Enforce numeric bounds for price/amount.
  • Verify Keychain result username matches intent.username.
  • Enforce per-IP / per-account rate limits (optional).
  • Strict CORS (see below).

4) Blockchain Integration

Hive Keychain implementation (frontend)

  • The UI asks Keychain to sign a custom_json intent (or specific market action), keeping private keys in the browser extension, not in your app.
  • The signed response (res) includes a transaction id/metadata you can store alongside the order for auditing.

Hive Engine API / beem (backend)

  • Use beem where needed (e.g., account existence checks, simple transfers, or logging). Keep all sensitive keys in env varsβ€”never in code or the repo.
  • If you later choose on-chain settlement (e.g., Hive-Engine market contract actions), adapt the payload to the canonical HE JSON format and verify with dry-runs in a sandbox/test account first.

Balance tracking across multiple tokens

  • Internal (balances table) reflects fills and on/off-ramp events.
  • If you credit/debit on-chain tokens, reconcile by recording tx ids (audit fields) to prove solvency.

5) Security Features

Account validation

  • Check intent.username exists on Hive (via beem).
  • Match Keychain-returned account to intent.username.

Private key management

  • Never store private keys client-side or server-side.
  • Server-only keys (for house ops) live in environment variables (per pair).

Input sanitization

  • Validate everything (types, ranges, enums).
  • Use parameterized SQL (as shown) to prevent injection.

CORS protection

  • Enable CORS in Flask, but restrict Access-Control-Allow-Origin to your Geocities domain in production.
  • Block credentials if not explicitly required.

6) Deployment Strategy

Frontend β†’ Geocities

  • Upload all files from frontend/.
  • Set index.html as the landing page.
  • Edit frontend/config.js with your backend API URL.

Backend server setup (Ubuntu example)

# 1) System packages
sudo apt update && sudo apt install -y python3 python3-venv python3-pip

# 2) Project
git clone https://github.com/PaulMoon410/Peake_Dex.git
cd Peake_Dex/backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

# 3) Environment
export PEK_DB_PATH="/var/lib/peke_dex/dex.db"
export PEK_SWAP_HIVE_KEY="your_active_key"
# ... set other pair keys as needed

# 4) First run
python app.py      # Ensure it starts locally (port 8080)

systemd service

# /etc/systemd/system/peake-dex.service
[Unit]
Description=Peake DEX Flask API
After=network.target

[Service]
User=www-data
WorkingDirectory=/opt/Peake_Dex/backend
Environment="PEK_DB_PATH=/var/lib/peke_dex/dex.db"
Environment="PEK_SWAP_HIVE_KEY=your_active_key"
# ...more keys per market...
ExecStart=/opt/Peake_Dex/backend/.venv/bin/python /opt/Peake_Dex/backend/app.py
Restart=on-failure

[Install]
WantedBy=multi-user.target

# Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable peake-dex
sudo systemctl start peake-dex
sudo systemctl status peake-dex

Optional nginx (reverse proxy)

  • Proxy https://api.yourdomain.com β†’ http://127.0.0.1:8080.
  • Add strict Access-Control-Allow-Origin for your Geocities domain.

FTP backup system (simple pattern)

# backend/ftp_backup.py (conceptual)
from ftplib import FTP
import os

def ftp_upload(db_path, host, user, password, dest):
  with FTP(host) as ftp:
    ftp.login(user, password)
    with open(db_path, 'rb') as f:
      ftp.storbinary(f'STOR {dest}', f)

# Use from a cronjob or a /api/ftp/upload admin endpoint.

7) Advanced Features

Automated order matching

  • Background thread scans best bid/ask and executes crosses.
  • Policy choices:
    • Match at ask, bid, or midpoint.
    • Partial fills: decrement remaining amount and keep order open.
    • Fees: add maker/taker fee logic if needed.

Real-time price discovery

  • Simple approach: poll /api/orderbook and diff client-side.
  • Advanced: server-sent events or WebSocket gateway for events.

Database backup

  • Nightly cron to FTP/S3; keep rolling 7/30-day snapshots.
  • Optionally export a CSV dump of orders/trades for transparency.

Error handling strategies

  • Structured JSON errors: { ok: false, error: "message", code: "E_INPUT" }
  • Centralized try/except in the matcher; exponential backoff on DB locks.
  • Health endpoint /api/health returning version, DB status, and last match tick.

🎯 Why This Matters (Key Benefits)

  • Real code patterns you can paste and adapt now.
  • Security-first: Keychain signing, no key storage, strict validation.
  • Scalable deployment: Static frontend + slim Python backend.
  • DEX best practices: matching loop, separate markets, auditability.
  • Complete path: dev β†’ systemd β†’ backups β†’ (optional) reverse-proxy.

Who this helps

  • Developers: understand and extend the codebase quickly.
  • Users: see how balances and orders flow through the system.
  • Contributors: where to add features (pairs, fees, sockets).
  • Ops/Deploy: step-by-step service, env vars, and backups.

πŸ”§ Quick Checklist Before Production

  • [ ] Restrict CORS to your frontend origin.
  • [ ] Enforce strict input validation + rate limits.
  • [ ] Run the matcher with tests for partial fills & edge cases.
  • [ ] Enable nightly FTP/S3 backups (DB + logs).
  • [ ] Monitor with systemd + journald, and add /api/health.
  • [ ] Keep your pair keys in env vars only (never in code/repo).

Appendix: Minimal Local Smoke Test

# Frontend: open frontend/index.html locally and set CONFIG.API_BASE_URL to:
#   http://127.0.0.1:8080

# Backend:
cd Peake_Dex/backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export PEK_DB_PATH="./dex.db"
python app.py

# Now visit your local frontend, place a tiny test order,
# and confirm it appears in /api/orderbook and /api/history.


0
0
0.000
0 comments