Commit Graph

18 Commits

Author SHA1 Message Date
FilipBl4gojevic
069edcb076 feat(ocr): model-scoped OCR capability + gateway /v1/ocr/extract route
Adds the ocr.page.extract.chandra-ocr-2.v1 capability end to end: the
postOcrEscrow builder (locking the min-ada floor for the Submitted-state
datum, required since the min-ada fix), the gateway OCR route that selects
an OCR supplier by capability, posts a per-page escrow, calls the supplier,
verifies and settles, and returns content plus the signed receipt labelled
first-party-brokered. Supplier OCR handler with a hosted document-API
upstream and buyer-SDK OCR support included.

Squashed from the 2026-08-06 working subset; the deploy pack is
intentionally excluded (it lives in a private operator repo).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-10 16:40:00 +02:00
FilipBl4gojevic
36c5cfc6be fix(wallet): make wallet-health idempotent - structural health predicate + circuit breakers
Some checks failed
CI / typecheck + vitest (Node 20) (push) Has been cancelled
planConsolidate returned already-healthy only for wallets with EXACTLY
2 UTxOs, but buildConsolidateWalletTx lands 3 (collateral + working +
the fee-reserve remainder as change). The shape consolidation produces
can never satisfy its own health check, so every wallet-health tick
re-consolidates and pays the ~0.5 AP3X min fee again. Measured in a
production deployment: a 45s ticker submitted 232 consolidations and
burned ~116 AP3X in about 2.5 idle hours; at the default 600s interval
the same loop leaks ~3 AP3X per hour whenever the wallet is not exactly
2 UTxOs.

Healthy is now structural: a pure-lovelace UTxO >= collateralLovelace
exists, at least 1 AP3X sits outside it, and fragmentation is bounded
(<= 6 UTxOs, new optional maxUtxos param). The health check also runs
BEFORE the low-balance guard, so a wallet that can already run script
transactions is never reshaped or refused. walletHealth gains two
independent circuit breakers - halt after 3 consecutive consolidations
that never reach healthy, halt past 6 consolidations per 24h - so any
future non-convergence costs at most ~1.5 AP3X instead of the whole
wallet, plus a docstring that matches reality.

Tests: the exact post-consolidation-shape regression, one-step
idempotence against the builder output model, candidate boundaries
(native assets, 1,628-lovelace-short), healthy-below-reshape-threshold,
and breaker behavior including error and reset paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 12:10:45 +02:00
david
2f7b0194a5 feat(supplier,gateway,buyer): N-slot chat suppliers + entry-ticket settle mode
Two structural fixes for demo concurrency (3 single-slot suppliers thrashed
under Open WebUI's ~3 conversations per message):

Multi-slot suppliers (MAX_CHAT_SESSIONS, default 1):
- SupplierState is now capacity-aware: a Set of active escrow refs with
  free-until-full status semantics, ref-scoped release(ref) (also fixes the
  latent bug where handler catch-alls released another request's slot), and
  a walletMutex serializing every wallet-spending chain op — Claim, Submit,
  and wallet-health consolidation queue instead of racing the single
  working/collateral UTxO. /status gains active_sessions/max_sessions.

Entry-ticket settle mode (CHAT_SETTLE_MODE=ticket, default full, both sides):
- The buyer's escrow post at session open is the ONLY chain op. The supplier
  verifies the Open escrow as an entry ticket but never Claims/Submits
  (/v1/chat/start → {status:"ticket"}; /v1/chat/end → receipt-less
  {status:"ended"}); no hard-cap timer, so sessions may outlive deliver_by.
  Used tickets can't reopen sessions (records retained; 409 session_exists).
- Buyer SDK: startChat/endChat are settle-mode aware, driven by the
  supplier's response — mixed-mode rollout windows settle correctly.
- Gateway: ticket closes bill cost 0; the sweeper Reclaims Open tickets
  after deliver_by WITHOUT closing live sessions (the conversation and its
  affinity mirror survive their reclaimed ticket), gates reclaims on
  deliver_by, and keeps an in-process settled-refs memory so terminal
  indexer rows aren't retried every sweep forever.

Deployment (kimi-chat trio + gateway): ticket mode, 8 slots per supplier,
idle policy gateway 10 min / supplier 15 min (fixes the inversion where the
supplier idle-closed before the gateway janitor).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 20:48:52 +02:00
david
9a4f9f0699 fix(gateway,supplier): demo sessions stick to their supplier; free pinned suppliers
The demo endpoint lost its session on nearly every second message: affinity
required clients to re-echo the assistant reply byte-identically, so clients
that strip <think> blocks or reshape whitespace (Open WebUI, litellm) missed
every turn, opened a fresh escrow session on the next free single-slot
supplier, and left the old one pinned. Open WebUI's background title/tag-gen
requests each pinned another supplier — one message could exhaust all three
kimi replicas ("no available chat supplier").

- affinity: lenient assistant-content matching (think-strip + whitespace
  collapse); user/system/tool messages and tool_calls stay byte-strict;
  strict prefixes outrank lenient ones. The mirror is never rewritten with
  client text, so receipt hashes stay valid.
- demoChat: trim trailing assistant/system messages (regenerate semantics)
  instead of 400 "the last message must have role user or tool".
- supplier: roll the transcript back when the LLM call fails so retries
  don't duplicate the delta (ChatSessionStore.truncateTranscript).
- demoChat: LRU-evict a demo session when no supplier is available (supplier
  /v1/chat/end frees the slot, sweeper Accepts the escrow) and retry the
  open with selectCandidates ignoreStatusFor to bypass the ~20s-stale
  indexer status poll. Max 2 evictions per request.
- sweeper: idle janitor closes demo sessions unused for DEMO_SESSION_IDLE_MS
  (default 3 min, below the supplier's 5 min CHAT_IDLE_TIMEOUT_MS) so
  abandoned one-off sessions release their suppliers quickly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 02:29:10 +02:00
david
4ebe7c9683 feat(gateway,supplier,shared): shared demo key with session-backed OpenAI endpoint + tool-calling
MVP demo: one operator-funded API key anyone can plug into an IDE/harness,
running real escrow-per-session marketplace conversations (llm.chat.v1)
behind the standard OpenAI /chat/completions wire shape.

- shared: ChatMessage gains tool role + tool_calls; normalizeChatMessage/
  chatMessagesEquivalent keep supplier transcript and gateway mirror
  hash-identical for receipts (field presence is part of the identity)
- supplier: /v1/chat/message accepts {messages,tools,tool_choice} alongside
  legacy {content}; callOpenAiStream forwards tools, accumulates fragmented
  delta.tool_calls, allows content-less tool turns; done frame carries the
  assistant message verbatim + finish_reason + usage
- gateway: demo flag on api_keys (additive migration), create-demo-key CLI,
  withdraw 403 + hidden deposit address for demo keys, per-IP demo rate
  limit (demo keys skip the shared per-key limit), models filtered to chat
  capability
- gateway executor (demoChat.ts): transcript-prefix affinity maps stateless
  calls onto open sessions, forwards only unseen deltas, transparently
  reopens on supplier idle-close; never falls back to one-shot (its settle
  path assumes <=1 in-flight escrow per wallet)
- mutex scope: key mutex held only for chain work (open/close); off-chain
  turns serialize on per-session locks so replicas serve users concurrently
- sweeper: chat-aware reclaim guard (35min vs 11min) and abandoned-session
  cleanup; terminal state confirmed via on-chain queryUtxo (indexer keeps
  lineage rows at their old state forever)
- deploy: kimi-chat replicas 2+3 compose files; chat idle timeout 2.5min

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:30:17 +02:00
david
b92bff56a7 feat(supplier): env-gated OpenRouter reasoning-off + max_tokens
The supplier sent a bare {model,messages,stream} to the OpenAI/OpenRouter
endpoint — no max_tokens and no reasoning control — so reasoning ("thinking")
models like kimi-k2.6 emitted reasoning tokens that could consume the
completion budget and finish with reason=length (truncated/empty answer =
failed job). deepseek-v4-flash has no thinking; kimi-k2.6 does by default.

Add two optional, env-gated request params (defaults preserve current behavior,
so ChatMock/DeepSeek-direct suppliers are unaffected):
  - OPENAI_REASONING=off  -> send reasoning:{enabled:false} (disable thinking:
    faster, cheaper, straight answers; no reasoning-starved length truncation)
  - OPENAI_MAX_TOKENS=N   -> forward max_tokens:N. Must stay below the model
    context (kimi context = 262144), so the advert's 262144 cap cannot be sent
    verbatim (provider 400s when prompt+max_tokens > context).

openai.ts builds the body conditionally; config.ts parses the env; wired
through jobRunner.ts (text) and server.ts (chat-session stream).

Deployed on the 4 kimi + 3 deepseek-flash suppliers with OPENAI_REASONING=off
and OPENAI_MAX_TOKENS=131072 (env files are host-only/gitignored).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 16:47:30 +02:00
david
9acad5b2b8 feat: llm.chat.v1 multi-turn supplier type + free Kimi K2.6 chat demo
Some checks failed
CI / typecheck + vitest (Node 20) (push) Has been cancelled
Adds a conversational supplier capability and a free demo, both streaming.

New paid `llm.chat.v1` type: one escrow per conversation (flat fee). "Start
chat" posts the escrow with a session-init placeholder prompt_hash and the
supplier Claims (reserving its single slot); turns then stream off-chain via
SSE with zero chain interaction; "End chat" has the supplier Submit a receipt
over the full transcript and the buyer Accept — the moment the user is charged.
The on-chain validator is content-agnostic (pins but never verifies the
hashes), so this reuses the existing contract unchanged. A supplier-side idle
watchdog auto-ends abandoned sessions before deliver_by.

Free Kimi K2.6 chat demo: a synthetic "demo:kimi-chat" tile that uses the same
chat UI but proxies straight to OpenRouter (no escrow, no payment), mirroring
the existing Piper demo.

Shared:
- postChatEscrow.ts + chatSessionPromptHash() (session-init prompt_hash helper
  shared by the buyer builder and the supplier start gate).

Supplier:
- CAPABILITY_KIND="chat-session" (+ CHAT_IDLE_TIMEOUT_MS); callOpenAiStream()
  streaming client; ChatSessionStore + endChatSession(); /v1/chat/{start,
  message,end} handlers with idle + hard-cap watchdog timers.

Buyer:
- Marketplace.startChat()/endChat() with relaxed (session-init) receipt
  verification + crypto verifyReceipt; /v1/chat/{start,message,end} routes
  (SSE passthrough) and a free /v1/chat-demo/message OpenRouter proxy;
  OPENROUTER_API_KEY/OPENROUTER_BASE_URL config; ChatForm.tsx + Dashboard
  KIMI_DEMO_SUPPLIER tile and llm.chat.v1 dispatch.

Deploy:
- docker-compose.supplier-kimi-chat.yml + supplier/.env.kimi-chat.example.

Typecheck passes across all workspaces; full vitest suite green except 15
pre-existing time-bomb/RED failures (tx-claim/accept/post-advert live-CBOR
fixtures with hardcoded past deadlines + the M0-C golden-datum test), confirmed
identical on a clean tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 16:18:54 +02:00
david
2025ab7411 supplier: in-process wallet auto-consolidation
Periodic setInterval ticker + on-failure backstop from the buildClaimTx
catch in server.ts. Both reuse the existing single-slot lock and call
runConsolidateWallet under the hood — planConsolidate short-circuits to
already-healthy in the steady state so no tx, no fee on a clean wallet.

Driven by deepseek-lane outage 2026-05-28 (supplier wallet drifted to 14
UTxOs with 9 dust 2-AP3X outputs; Claim tx coin-selector picked dust as
working input → chain_submit_failed; lane stalled until manual
tx:consolidate-wallet).

buyer/scripts/monitor-wallets: surface deepseek + kimi as operator wallets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 11:52:54 +02:00
david
1466442870 supplier+deploy/mainnet: optional OPENAI_API_KEY for DeepSeek (and other hosted OpenAI-compatible APIs)
Adds an optional OPENAI_API_KEY env var that supplier/src/openai.ts forwards as
`Authorization: Bearer <key>`. Empty/unset preserves the ChatMock behaviour
(no auth header). Unlocks pointing the openai backend at DeepSeek directly
(https://api.deepseek.com) — no local proxy needed.

New compose: deploy/mainnet/docker-compose.supplier-deepseek.yml (third mainnet
supplier, model deepseek-v4-pro, mp-suppliers-deepseek.vector.apexfusion.org).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 11:23:57 +02:00
david
972c0f9b89 wallet: tx:consolidate-wallet CLI + collateral pre-check across script spends
Adds a one-shot operational tx that re-shapes any operator wallet to
{5 AP3X collateral candidate, working remainder} so subsequent Plutus
script-spends (Claim/Submit/Accept/Reclaim/RetireAdvert) find a UTxO
that satisfies lucid's setCollateral=5_000_000n default coin selector.

Symptom this addresses: open-webui mainnet supplier wallet held 8 UTxOs
max 2.40 AP3X, every Accept tx failed at lucid .complete() with the
"wallet must hold a pure-ADA UTxO ≥ 5 ADA" error path. Same fragmentation
risk on the other 4 mainnet operator wallets (one big UTxO surrounded by
dust — first script-spend consumes the big one and leaves nothing usable
as collateral).

Phase 1 — new tx:consolidate-wallet CLI (supplier + buyer):
  - planConsolidate.ts — pure planner; supports consolidate (N>1 → 2),
    split (1 → 2), and already-healthy skip.
  - consolidateWallet.ts — live builder; presetWalletInputs pins all
    UTxOs, two pay.ToAddress outputs (collateral + working).
  - runConsolidateWallet.ts — orchestration (build + submit + awaitTx).
  - supplier/src/cli/consolidate-wallet.ts + buyer mirror.
  - monitor-wallets.ts — add supplier-tts@vector-marketplace (was
    misclassified as 3rd-party).

Phase 2 — defensive pre-check in the 5 script-spend builders:
  - assertCollateralCandidate(utxos) fires at the top of
    buildSpendOpenOrClaimedTx (Claim/Submit), buildLiveTxForAccept,
    buildLiveTxForReclaim, buildLiveTxForRetireAdvert. Throws a
    TxConstructionError("collateral_required") pointing at
    tx:consolidate-wallet instead of waiting for lucid's deep failure.

Tests: 12 new passing (planConsolidate edge cases + assert helper).
Pre-existing lucid 0.4.30 WASM "unreachable" failures in tx-*-live.test.ts
fixtures are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:59:11 +02:00
david
87e126b22c supplier+deploy/mainnet: log job_failed reasons; bump openai timeout 180s→300s; chatmock reasoning_effort=low
- jobRunner.ts: console.warn at each jobs.fail site (chat + tts paths, 6 sites). Three mainnet GPT-supplier 502s on 2026-05-19 left no trace beyond Traefik — adding a one-line log restores grep-ability.
- docker-compose.supplier-gpt.yml: OPENAI_TIMEOUT_MS 180000→300000. The 3 observed 502s were all exactly 90 polls × 2s, i.e. the supplier's openai_timeout firing.
- docker-compose.chatmock.yml: serve --reasoning-effort low --reasoning-summary none. Default 'medium'/'auto' pushed GPT-5.4 think-time over the prior 180s budget.

Note: on-chain advert ADVERT_MAX_PROCESSING_MS still pins 180000 until a re-advert tx is posted — buyers compare against the on-chain value, not the runtime env.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:42:13 +02:00
david
965fabd3f0 supplier+deploy/mainnet: LLM_BACKEND=openai via ChatMock proxy
Adds a Codex/ChatGPT backend path for the supplier alongside the
existing Ollama one. The mainnet supplier is repointed at a localhost
ChatMock proxy (RayBytes/ChatMock, pinned :v1.35) that authenticates
against a ChatGPT Pro subscription via OAuth.

- supplier/src/openai.ts: HTTP client for OpenAI-compatible
  /v1/chat/completions. Same return shape as ollama.callOllama so
  jobRunner can branch cleanly.
- supplier/src/jobRunner.ts: branch on config.llmBackend.
- supplier/src/config.ts: LLM_BACKEND ("ollama" default | "openai"),
  OPENAI_BASE_URL, OPENAI_TIMEOUT_MS (default 180_000 — GPT first-token
  latency).
- supplier/src/cli/gen-keypair.ts: standalone Ed25519 keypair generator
  + bech32 address derivation. Used to bootstrap a fresh supplier
  wallet without round-tripping through tx:post-advert --dry-run.
- deploy/mainnet/docker-compose.chatmock.yml: long-running chatmock
  serve + one-shot --profile login flow.
- deploy/mainnet/docker-compose.supplier.yml: rewritten for openai
  backend; Traefik routes at mp-suppliers-gpt.vector.apexfusion.org so
  the unsuffixed slot stays free for a future supplier with a
  different backend.
- tests/unit/supplier-openai.test.ts: unit coverage for callOpenAi.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 13:47:44 +02:00
david
aecf4861bc shared+supplier: per-script lovelace overrides for ref-script publish
Splits the single `lovelacePerOutput` param in publishReferenceScripts.ts
into `escrowLovelace` and `advertLovelace`, exposed via env vars
ESCROW_PUBLISH_LOVELACE / ADVERT_PUBLISH_LOVELACE in the CLI. Defaults
unchanged at 30 AP3X each (60 AP3X total).

Motivation: mainnet rollout where the publishing wallet has only ~57 AP3X
(buyer wallet) — needs to publish at calculated min-utxo (~17 AP3X escrow
+ ~7 AP3X advert = 24 AP3X total) instead of the safe 60 AP3X default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 17:47:10 +02:00
david
57e77e0e25 shared: route ref-script server helpers via tx/server (keep buyer bundle clean)
Static re-exports from tx/index.ts caused vite to trace
loadEscrowScript/loadAdvertScript/buildLiveTxForPublishReferenceScripts
into the buyer UI bundle, hitting the CML WASM "ESM integration proposal"
limitation. Sibling builders avoid this with /* @vite-ignore */ dynamic
imports; we keep symmetry by parking the helpers on a separate
@marketplace/shared/tx/server entry that the buyer never imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:41:29 +02:00
david
5cae0c4f6c shared: opt-in CIP-33 reference-scripts for script-spending txs
When ESCROW_REF_UTXO / ADVERT_REF_UTXO env vars are set (format
<txhash>#<idx>), Claim/Submit/Accept/Reclaim/RetireAdvert call
.readFrom([refUtxo]) instead of inlining the validator CBOR via
.attach.SpendingValidator(). Lucid-evolution 0.4.30 auto-extracts
utxo.scriptRef into the script-witness map (dist/index.js:1903–1913),
so no separate attach call is needed. Default (env unset) is unchanged.

Adds buildLiveTxForPublishReferenceScripts (a one-shot tx that
publishes both validators as scriptRef-bearing UTxOs at a caller-
supplied address — typically the all-zero-pkh enterprise burn address)
and the supplier tx:publish-reference-scripts CLI that wraps it.

Targeted at testnet trial; mainnet rollout is a separate flip after
verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:25:59 +02:00
david
c09245a507 shared: live-CBOR for deferred builders (Reclaim, RetireAdvert)
Closes 2/4 builders from the M1-F-D-defer list (postAdvert already live in
7ea7328; updateAdvert and release remain).

Reclaim:
- buildLiveTxForReclaim in liveCbor.ts (mirrors buildLiveTxForAccept):
  spends the escrow UTxO with REDEEMER_RECLAIM (Constr3), pays
  payment + buyer_bond + supplier_bond to buyer, delegates UPLC eval
  to Ogmios via localUPLCEval:false. Validity range = tipMs ± 60s.
- reclaim.ts: replace the unconditional mockSlotToWallclockMs tipMs
  computation with `isLive ? Date.now() : mockSlotToWallclockMs(tipSlot)`,
  matching accept.ts:77. Adds the live-CBOR dispatch branch.

Funds-at-risk before this fix: with the broken tipMs path, every live
Reclaim was rejected client-side ("tip < deliver_by" using a Nov-1970
tip). If a supplier never Claimed, buyer's 6-7 AP3X stayed locked
indefinitely. Verified recovery of 4 stuck escrows on Vector L2 testnet
(txs f6660e52, 769f16fd, 79970d86, 9f4db14b — 14 AP3X total recovered).

RetireAdvert:
- buildLiveTxForRetireAdvert in liveCbor.ts: spends advert UTxO with
  REDEEMER_RETIRE_ADVERT (Constr2), advert script address.
- retireAdvert.ts: live-CBOR dispatch.
- supplier CLI: retire-advert.ts + retireAdvertFlow.ts.
- supplier/package.json: tx:retire-advert script.

Indexer:
- follower/blockProcessor + worker: handle RetireAdvert events,
  transition advert row state to Retired.
- db/cache.ts: markAdvertTerminal helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:57:51 +02:00
jelisaveta-talbot
3c3e009a67 supplier: TTS capability scaffolding (audio.synthesize.piper.v1)
Path-A foundation: extend the supplier service so the same on-chain
escrow lifecycle can serve a PiperTTS supplier alongside the existing
Ollama-backed chat one. Capability dispatch is boot-time; one process
serves one capability via a single advert.

Receipt mapping for TTS is committed inline:
- prompt_hash  = sha256(canonical({text, voice, format, speed}))
- result_hash  = sha256(audio_bytes)
- prompt_tokens / completion_tokens = 0 (off-chain only; Piper has no
  token semantics, on-chain validator never reads these fields)

New / changed files

- supplier/src/piper.ts (new): mirror of ollama.ts. callPiper(...)
  POSTs OpenAI-shape /v1/audio/speech, returns {audio: Uint8Array,
  contentType, wallclock_ms}. Typed errors: piper_failure | piper_
  timeout | piper_malformed (with a 64-byte minimum to catch upstream
  "200 OK, body length 0" cold-start)
- supplier/src/config.ts: + CapabilityKind = "chat" | "tts", + piperUrl
  / piperTimeoutMs. CAPABILITY_KIND env defaults to "chat" so existing
  chat suppliers keep booting unchanged
- supplier/src/jobs.ts: JobResponsePayload becomes a discriminated
  union (ChatJobResponsePayload | TtsJobResponsePayload). TTS variant
  carries audio_b64 + content_type + byte_length so the in-memory
  store stays JSON-clean and the GET poll route returns audio without
  binary content-type negotiation
- supplier/src/jobRunner.ts: + runTtsJob (parallels runChatJob with
  same 7-step shape: setRunning -> upstream -> receipt -> buildSubmit
  -> awaitTx -> complete -> finally release)
- supplier/src/server.ts: + makeTtsHandler (POST /v1/audio/synthesize)
  + makeGetTtsJobHandler (poll). createApp dispatches by config.
  capabilityKind: TTS supplier exposes the audio routes only, chat
  supplier exposes the chat routes only. The on-chain capability_id
  check on every request is belt-and-braces

Tests
- tests/unit/supplier-piper.test.ts: 9 tests (request shape, success,
  network throw, AbortError -> piper_timeout, too-small body ->
  piper_malformed, missing content-type fallback)
- tests/unit/supplier-routes-tts.test.ts: 9 tests (capability-routed
  mounting, all 400 validation paths, sanity that the route is reachable)
- tests/fixtures/supplier-side/sample-config.ts: + buildSampleTtsConfig

243 supplier tests passing; chat path behaviourally untouched.
2026-05-06 19:01:35 +02:00
jelisaveta-talbot
cbab81efbd M1-F: end-to-end LLM marketplace lifecycle on Vector testnet
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)
2026-05-06 13:27:48 +02:00