run-agent-a.ts: drives Marketplace.submitPrompt + runAccept for 3 cycles against the live supplier. Logs every tx hash, HTTP code, receipt verification step. Verified 2026-05-12 on Vector L2 testnet: 4 cycles clean, 28 AP3X net, supplier wallclock 19-53s (cold/hot). run-agent-b.ts: adversarial buyer orchestrator covering 14 sub-attacks across A (within-protocol cheating), B (protocol fuzz), C (liveness/DoS), D (social/spec). Hard caps enforced inline (10 escrows / 25 AP3X burn / 100 reqs at <=2 rps / stop-on-first-GAP). Post-run reclaim sweep recovers any Open/Claimed escrows past deliver_by. Verified 2026-05-13: 11 of 14 attacks BLOCKED, 2 UNCLEAR (test-design faults, not defense gaps), 1 SKIPPED (builder controls deliver_by). Surfaced the Reclaim live-CBOR funds-at-risk bug fixed in the prior commit. reclaim-orphans.ts + cleanup-agent-b.ts: throwaway helpers that close out leftover Open/Claimed/Submitted escrows after an Agent-B run. prompts/marketplace-buyer-pair.md: paired spec for Agent A + Agent B (the source for these runs). buyer/.env is gitignored and stays out — contains the testnet ed25519 seed for the runner. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 KiB
Marketplace Buyer Pair — Two-Agent Exercise
A paired prompt for running the agents-marketplace from the buyer side on Vector L2 testnet. Hand each section below to a separate agent run.
- Agent A — Successful User completes the happy-path lifecycle (PostEscrow → Accept) cleanly.
- Agent B — Adversary Buyer probes defenses across four attack styles (within-protocol cheating, protocol-fuzzing, liveness/DoS, social/spec).
Each agent's prompt below is self-contained — copy from the ═══ BEGIN AGENT … PROMPT ═══ line through ═══ END AGENT … PROMPT ═══ and paste. The two prompts share a header (Marketplace primer, target config, hard caps); if you edit the target advert ref or endpoint, update both sections.
Operator note: testnet AP3X only. Never reuse mainnet keys. Both agents must log every tx hash, escrow ref, HTTP status, and response body — that's the evidence trail.
Agent A — Successful User
═══ BEGIN AGENT A PROMPT ═══
You are a buyer using the agents-marketplace on Vector L2 testnet. Your job: get a useful LLM completion from a supplier and pay them for it correctly. Demonstrate that the marketplace works end-to-end.
Marketplace primer (read first)
Buyers post on-chain escrow against a supplier's on-chain advert; the supplier claims the escrow, runs inference locally, submits a signed receipt on chain, and the buyer accepts. Symmetric 1 AP3X bonds plus on-chain deadlines keep both sides honest.
Lifecycle (escrow state machine): Open → Claimed → Submitted → {Accepted | Released} | Reclaimed.
The seven lifecycle tx names — all defined in packages/shared/src/tx/:
PostAdvert,Retire(supplier-side, advert lifecycle)PostEscrow,Claim,Submit,Accept,Reclaim,Release(escrow lifecycle)
Key invariants:
advert_refin the escrow datum spec-locks the escrow to a specific advert UTxO (price, capability, model can't be swapped mid-flight).deliver_by = posted_at + advert.max_processing_ms + NETWORK_BUFFER_MS(30_000).ACCEPT_WINDOW_MS = 600_000(10 min). Aftersubmitted_at + ACCEPT_WINDOWthe supplier canReleaseand take both bonds + payment.prompt_hash = sha256(canonical(messages));request_spec_hash = sha256(canonical(envelope)). Supplier recomputes both and rejects mismatches atsupplier/src/routes/chat.ts.- Receipt is Ed25519-signed by supplier's pkh.
Target supplier (testnet)
Primary:
- Endpoint:
https://supplier.summitstak.ing - Supplier pkh:
8e93e53214eb778fa71beadc4914ad1961ece1d71710f5e597e9494a - Active advert ref:
386d30fdc3e69c5a7063b9bb99a6ea3e2bee79498c16a22cf3a9c74a4a004040#0 - Capability:
llm.text.generate.v1, modelqwen3.6:35b, price 2 AP3X, max_output_tokens 2048, max_processing_ms 60000
Backup (run by the Vector / Apex Fusion team — not the operator of this exercise):
mp-suppliers.vector.testnet.apexfusion.org
Indexer (read-only chain view):
GET /suppliers,GET /capabilities,GET /escrows/{ref},GET /events?stream=1(SSE)
Buyer surface
Pick whichever fits the tools you have. The SDK is the easy path; CLI plus raw HTTP works if you want lower-level control.
- Buyer SDK in
buyer/src/sdk/Marketplace.ts:Marketplace.discoverSuppliers(opts?)Marketplace.submitPrompt({advertRef, messages, …})— runs the full lifecycle except AcceptMarketplace.acceptResult({escrowRef})— builds & submits Accept
- CLI:
pnpm --filter @marketplace/buyer tx:accept --escrow-ref <txHash>#<ix>
- Direct HTTP to supplier:
POST https://supplier.summitstak.ing/v1/chat/completionswith headerX-Escrow-Ref: <txHash>#<ix>and OpenAI-compatible JSON body.
Hard caps (must respect)
- Testnet AP3X only.
- Maximum 5 escrows posted across this whole run.
- Maximum 2 AP3X locked per escrow (the advertised price). Never inflate.
- Always log: every tx hash, every escrow ref, every HTTP response code and body.
- If the supplier endpoint returns 5xx three times in a row, pause 60s and try once more before reporting failure.
Steps
- Discover the supplier:
GET <indexer>/suppliers, or use the target advert ref directly. Confirmadvert.status == "Active"andadvert.price_lovelacematches the 2 AP3X you expect. - Compose a small prompt — e.g.
[{role: "user", content: "Summarize the Cardano EUTxO model in 3 bullets."}]. Computeprompt_hashover the canonicalized messages. PostEscrowreferencingadvert_ref, lockingprice + 1 AP3Xbuyer bond. Capture the result asescrow_ref = txHash#ix. Wait for chain confirmation (poll indexerGET /escrows/{ref}or subscribe to/events?stream=1).POST <supplier>/v1/chat/completionswith headerX-Escrow-Ref: <escrow_ref>and the OpenAI-compatible body matching the prompt you committed to.- Wait for the supplier to
ClaimthenSubmiton chain. Save both tx hashes from the indexer. - Verify the response payload: check that
receipt.prompt_hashmatches what you computed, thatreceipt.response_hashmatchessha256(canonical(choices)), and thatreceipt_signatureverifies against the supplier's pkh. Accept: callMarketplace.acceptResult({escrowRef})or runpnpm --filter @marketplace/buyer tx:accept --escrow-ref <ref>. Capture the Accept tx hash.- Confirm settlement: payment + supplier bond went to supplier, your buyer bond returned. Your wallet's net delta should be exactly
-price(≈ -2 AP3X plus fees). - Repeat steps 2-8 two more times with slightly different prompts to demonstrate stability.
Failure handling
- If the supplier hasn't
Submittedbydeliver_by(advert.max_processing_ms + 30s buffer + posted_at), runReclaim. The buyer recovers payment + both bonds. - Do not retry blindly. If a step fails, report which step, which tx/HTTP call, and the full error.
Report (final output)
Produce a markdown table with one row per cycle:
| Cycle | Prompt summary | PostEscrow tx | Claim tx | Submit tx | Accept tx | Wall-clock latency | Supplier wallclock_ms | Net wallet delta |
Then a short paragraph: anything surprising, any retries, any mismatch between expected and observed behavior.
═══ END AGENT A PROMPT ═══
Agent B — Adversary Buyer
═══ BEGIN AGENT B PROMPT ═══
You are a buyer running an adversarial assessment of the agents-marketplace on Vector L2 testnet. Your job: probe defenses across four attack styles and report concrete evidence — every tx hash, every HTTP response code and body, every log line. Finding one real gap is more valuable than completing the whole matrix; stop and report immediately when a defense fails.
Marketplace primer (read first)
Buyers post on-chain escrow against a supplier's on-chain advert; the supplier claims, runs inference, submits a signed receipt, the buyer accepts. Symmetric 1 AP3X bonds plus on-chain deadlines keep both sides honest.
Lifecycle (escrow state machine): Open → Claimed → Submitted → {Accepted | Released} | Reclaimed.
The seven lifecycle tx names — all defined in packages/shared/src/tx/:
PostAdvert,Retire(supplier-side, advert lifecycle)PostEscrow,Claim,Submit,Accept,Reclaim,Release(escrow lifecycle)
Key invariants and defenses you will be probing:
advert_refin the escrow datum spec-locks the escrow (price/capability/model frozen at advert UTxO).deliver_by = posted_at + advert.max_processing_ms + NETWORK_BUFFER_MS(30_000).ACCEPT_WINDOW_MS = 600_000(10 min). Buyer who skips Accept after Submit forfeits both bonds + payment via supplier-sideRelease.prompt_hash,request_spec_hashcanonical-hashed atsupplier/src/routes/chat.ts; mismatch → 4xx.- All escrow datum integer fields are CBOR major type 0/1 (BigInt). Any FLOAT64 leak → script crash. (Prior history: AdvertDatum had this bug; EscrowDatum may or may not.)
Target supplier (testnet)
Primary:
- Endpoint:
https://supplier.summitstak.ing - Supplier pkh:
8e93e53214eb778fa71beadc4914ad1961ece1d71710f5e597e9494a - Active advert ref:
386d30fdc3e69c5a7063b9bb99a6ea3e2bee79498c16a22cf3a9c74a4a004040#0 - Capability:
llm.text.generate.v1, modelqwen3.6:35b, price 2 AP3X, max_output_tokens 2048, max_processing_ms 60000
Backup (run by the Vector / Apex Fusion team — not the operator of this exercise):
mp-suppliers.vector.testnet.apexfusion.org
Indexer (read-only chain view):
GET /suppliers,GET /capabilities,GET /escrows/{ref},GET /events?stream=1(SSE)
Buyer surface
- Buyer SDK in
buyer/src/sdk/Marketplace.ts:discoverSuppliers,submitPrompt,acceptResult. - CLI:
pnpm --filter @marketplace/buyer tx:accept --escrow-ref <…>. - Direct HTTP:
POST <supplier>/v1/chat/completionswith headerX-Escrow-Ref: <txHash>#<ix>. - Raw tx-building:
buildPostEscrowTx,buildReclaimTx,buildReleaseTx, etc., exported from@marketplace/shared/tx.
Hard caps (must respect — this is a safety-bounded probe, not a real attack)
- Testnet AP3X only.
- Maximum 10 escrows posted total. Maximum 2 AP3X locked per escrow.
- Total burn ceiling: 5 AP3X across failed-tx fees + forfeited bonds. If you would exceed this, stop and report.
- Rate cap: ≤ 2 requests per second to the supplier endpoint, ≤ 100 requests total.
- Single slow-loris socket attempt only — do not hold many sockets open.
- If the supplier endpoint returns 5xx three times in a row, pause 60s and drop your rate by half before continuing.
- Always log: tx hashes, HTTP method+URL+status+body excerpt (first 1KB), timestamps.
- Stop the moment any defense fails and report the gap — finding one real issue beats finishing the matrix.
Attack matrix
For each attempt, record:
- What you tried (concrete payload / tx / sequence)
- Expected defense (what should fire)
- What happened (response code, validator error, on-chain state, timing)
- Verdict — one of: BLOCKED (defense held), GAP (defense failed), UNCLEAR (need follow-up)
- Evidence — tx hash, response body excerpt, timestamp
A. Within-protocol cheating (buyer-side, syntactically valid)
- A1. Post an escrow, accept the supplier's receipt off-line, then never submit Accept. Wait
ACCEPT_WINDOW_MS(10 min) pastsubmitted_at. Expected: supplier callsReleaseand takes payment + both bonds. GAP = you recover any funds. - A2. Build a
Releasetx yourself (as buyer) beforesubmitted_at + ACCEPT_WINDOW. Expected: on-chain validator rejects (validity-lower-bound check,escrow.ak). - A3. Post an escrow with
deliver_byalready in the past. Expected: build-time validator inbuildPostEscrowTxrejects, or supplier refuses toClaim. - A4. Post an escrow then immediately try
Reclaimbeforedeliver_by. Expected: validator rejects (state=Open butnow < deliver_by).
B. Protocol-fuzzing (malformed datums, replay, double-spend)
- B1. Build a
PostEscrowtx whose datum hascapability_idnot matching the advert (e.g.llm.text.generate.v2). Expected: supplier'schat.tsrejects with 4xx (capability mismatch). - B2.
PostEscrowwithprompt_hashset to a value ≠sha256(canonical(messages))that you'll send. Expected: supplier rejects 400 withprompt_hash mismatch. - B3.
POST /v1/chat/completionswithX-Escrow-Refpointing at an escrow ref that doesn't exist on chain (random txHash#ix). Expected: 404 or 400. - B4. Replay a previously-Accepted escrow ref in
X-Escrow-Ref. Expected: supplier rejects (UTxO spent / state terminal). - B5.
PostEscrowpaying strictly less thanadvert.price_lovelace. Expected: build-time price-equality check inpostEscrow.tsrejects. - B6. Construct a
PostEscrowdatum where one of the integer fields (deliver_by,posted_at,max_output_tokens) is encoded as CBOR FLOAT64 instead of major-type 0/1. (Use a hand-rolled CBOR encoder or patch cbor-x's number coercion.) Expected: Plutus validator rejects withunreachable/ script crash. This mirrors the AdvertDatum bug pattern — verify whether EscrowDatum has the same defense.
C. Liveness / DoS (within hard caps)
- C1. Send a request body with
messagestotalling >100KB. Expected: 413 or 400 before LLM invocation. - C2. Send
max_output_tokens: 999999in the request body. Expected: supplier clamps toadvert.max_output_tokens=2048or rejects 400. - C3. Open one TCP connection and dribble bytes (slow-loris). Single attempt only. Expected: server timeout, connection closed.
- C4. Burst 50 requests at 2 rps with bogus
X-Escrow-Refheaders. Expected: all 4xx; supplier stays responsive. Verify liveness mid-burst by issuing one valid happy-path probe (small honest escrow) in parallel; confirm it completes successfully. - C5. Spam the indexer (
GET /suppliers,GET /escrows/{ref}) at 2 rps for 30s. Expected: 200s throughout. Report any 5xx.
D. Social / spec attacks (lying, sybil, free-inference)
- D1.
POST /v1/chat/completionswith noX-Escrow-Refheader. Expected: 401/400. GAP = supplier returns inference for free. - D2.
POST /v1/chat/completionswithX-Escrow-Refpointing at someone else's escrow (you did not sign thePostEscrow). Expected: supplier rejects (signer / buyer-pkh mismatch). - D3.
PostEscrowagainst the advert (which announcesqwen3.6:35b) but in the chat body setmodel: "gpt-4". Expected: supplier rejects (model mismatch). - D4.
PostEscrowwhoseadvert_refpoints at aRetiredadvert UTxO. Find one via indexer history, or retire one and reuse the ref. Expected: build-time or on-chain validator rejects (advert.status check). - D5. From 3 sibling sybil wallets (derive new keys; fund each from your testnet faucet within hard caps),
PostEscrowagainst the same advert in quick succession. Expected: each escrow is independently valid on chain. Observe whether the supplier deduplicates, fairly serializes, or refuses concurrent claims. Report behavior — there is no single "correct" answer here, the report itself is the deliverable.
Report (final output)
A markdown table:
| Attack ID | What you tried | Expected defense | What happened | Verdict | Evidence |
|---|
Then a section "GAPs found" listing any GAP rows with reproduction steps detailed enough that a developer can confirm without re-running the whole matrix.
Then a section "Hard-cap accounting": AP3X spent (fees + forfeited bonds), escrows posted, total requests sent. If you stopped early (gap found or cap hit), say why.
═══ END AGENT B PROMPT ═══