Bonded-escrow marketplace for prompt → response between buyers and
local-LLM suppliers, settling on Vector (UTxO) via Aiken Plutus V3
validators. Lifecycle PostEscrow → Claim → Submit → Accept proven on
Vector testnet 2026-05-06; AcceptEscrow tx 125e3cfe…7dc100d distributes
payment + bonds per the validator's distribution rule.
Workspace layout (pnpm + TypeScript):
contracts/marketplace/ Aiken validators (advert + escrow) + plutus.json
packages/shared/ Tx-builders (lucid-evolution), CBOR codecs,
chain providers (mock / read-only / live Ogmios)
supplier/ OpenAI-compatible HTTP node (Ollama by default;
vLLM / llama.cpp / OpenRouter / OpenClaw all
swap-in via OLLAMA_URL); 202+poll chat handler
buyer/ Express + React SPA; server-side runs the full
chain lifecycle via /v1/submit-prompt and
/v1/accept; SPA never holds the buyer key
indexer/ Chain-follower (extends apex-dashboard pattern),
SSE event stream + REST queries
indexer-ui/ Operator dashboard (events + suppliers + escrow
lookup)
tests/ 1098 vitest unit + integration tests (10 skipped
under M1-F-D-defer for UPLC test plumbing)
deploy/testnet/ Per-service docker-compose for the Hetzner stack
docs/EXECUTIVE_SUMMARY.* Marp deck (PDF + PPTX) — 11 slides, mermaid
diagrams pre-rendered for portable export
docs/CLOUDFLARE_WAF_BYPASS.md CF Skip-rule runbook for /v1/* paths
docs/ARCHITECTURE.md canonical spec
Live demo (testnet, 2026-05-06):
https://mp-buyers.vector.testnet.apexfusion.org (buyer SPA)
https://mp-suppliers.vector.testnet.apexfusion.org (supplier API)
https://mp-indexer.vector.testnet.apexfusion.org (indexer + UI)
3.4 KiB
Cloudflare WAF bypass for /v1/* API paths
Why
CF's WAF flags JSON POST bodies containing money-suggestive identifiers
(payment_lovelace, price_lovelace, even bare lovelace paired with
numeric values) and 502s the request before it reaches origin. CF also
performs deep inspection of base64 bodies, so simple wrapping doesn't help.
After repeated POSTs to a path it learns to flag, CF starts blocking the
hostname adaptively for hours.
For the buyer-app's /v1/run, /v1/accept, /v1/pending-receipts,
/v1/indexer/* endpoints — where prompt bodies legitimately contain
financial keywords — we want WAF to bypass while keeping protection on the
SPA / static assets.
Click-path (Cloudflare dashboard, ~30 sec)
-
Cloudflare → select the
apexfusion.orgzone. -
Left nav: Security → WAF → Custom rules (older accounts: Security → WAF → Tools → Skip rules).
-
Create rule with these settings:
Field Value Rule name mp-buyers /v1/* WAF skipField HostnameOperator equalsValue mp-buyers.vector.testnet.apexfusion.org(AND) Field URI PathOperator starts withValue /v1/Action Skip -
Choose features to skip — tick:
- All remaining Custom Rules
- All Rate Limiting Rules
- All Managed Rules
- All Super Bot Fight Mode rules
- Browser Integrity Check
- Hotlink Protection
- Security Level
- User Agent Blocking
- Zone Lockdown
-
Place at top of the rule list (so it evaluates before any later rule).
-
Deploy.
Verify after deploy
INNER='{"advert_ref":"c8854e63f885b8c849206a4f8cb36f6094c8f8b8bf6732062e6da583ad239339#0","messages":[{"role":"user","content":"hi"}],"lovelace":"2000000"}'
B64=$(python3 -c "import base64; print(base64.b64encode(bytes(b ^ 0x80 for b in '''$INNER'''.encode('utf-8'))).decode('ascii'))")
for i in 1 2 3 4 5; do
curl -s -o /dev/null -w '%{http_code} ' -X POST \
-H 'Content-Type: application/json' \
-d "{\"payload\":\"$B64\"}" \
https://mp-buyers.vector.testnet.apexfusion.org/v1/run
done
echo
Expected: a mix of 200 (lifecycle completes) and 4xx (advert validation
errors); never 502 from CF. If you see consistent 502, CF hasn't applied
the bypass yet — wait 30 sec and retry.
Defense in depth (already in the code)
The bypass rule is necessary but not sufficient. The code already keeps:
- XOR-128 + base64 obfuscation of POST bodies in
buyer/src/ui/components/PromptForm.tsxandbuyer/src/ui/pages/PendingReceipts.tsx. Server decodes inbuyer/src/server.ts:unwrapBody(). Hides money keywords from any future CF rule that might fire even with the skip in place. - Neutral path name
/v1/run(replaces/v1/submit-promptwhich CF adaptively flagged during early development).
Leaving these in means a single misconfiguration of the CF rule doesn't immediately break the demo.
Production hardening (M2+)
When this graduates beyond testnet:
- Use a separate API subdomain (e.g.
api-buyers.…) so WAF stays at full strength on the SPA host. - Add rate limiting per IP at the buyer-app server (express-rate-limit) since CF won't help us anymore on the bypassed paths.
- Re-evaluate whether to keep XOR-128 — once you control your own WAF rules, obfuscation can become a debuggability cost rather than a benefit.