402mob-merchant-sdk 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # Changelog
2
2
 
3
- ## 0.4.2 — prepared, unpublished
3
+ ## 0.5.0
4
+
5
+ - Add `ProtectedWorkClient` for the separate experimental JSON escrow HTTP API, generated request/response types and schema checks. Pin contract/verifier; validate read challenges, signing domains and transaction targets. No signing, broadcasting, automatic retry or replica management. Includes protocol integration tests and an example. Earlier versions do not include these methods.
6
+
7
+ - Require an authenticated-read binding for dispute writes. Reject unexpected signing origins/consent, contradictory payment and delivery states, and immutable binding drift across evidence events. Challenge issuance reveals no operation or case state.
8
+
9
+ - Require stateless dispute challenge tokens; distinguish write acknowledgments from authenticated evidence reads. Validate operation, statement, binding and lifecycle consistency; preserve settlement status and post-closure seller evidence.
10
+
11
+ - Add wallet-signed delivery complaint, seller response, buyer closure and evidence-read methods and types. No automatic payment or refund.
12
+
13
+ ## 0.4.2 — published
4
14
 
5
15
  - Add optional `artifact` commitment and `share` fields to resolved resources. Validate supplied commitments and preserve compatibility with older servers that omit them.
6
16
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 402Mob Merchant SDK
2
2
 
3
- Source 0.4.2 is prepared locally with optional Payment Links commitment/share types and validation. Published 0.4.1 remains unchanged; these additions require the new source build until publication.
3
+ Version 0.5.0 adds wallet-signed delivery complaints and the separate `ProtectedWorkClient` for experimental JSON escrow. Install `402mob-merchant-sdk@0.5.0` for these methods; earlier versions do not include them.
4
4
 
5
5
  **Version 0.4.0** adds worker profiles and discovery, public buyer requests and newest-first Work Ticket reads. Upgrade from 0.3.1 to use these additions. Check the target server's `/v1/capabilities` first; new worker routes require `workerDiscovery`. See [CHANGELOG.md](./CHANGELOG.md).
6
6
 
@@ -282,3 +282,33 @@ The supported states are `open`, `claimed`, `quoted`, `accepted`, `paying`, `pai
282
282
  SDK requests reject HTTP redirects and never retry automatically. An interrupted mutation may have completed. Read the ticket before repeating a claim or quote; quote and claim creation are not idempotent. Repeating identical result content is supported while retained. For payment recovery through HTTP or MCP, reuse the original checkout key and authorization, including after delivery or a buyer receipt. The SDK does not submit payments.
283
283
 
284
284
  These methods are available from version 0.3.0.
285
+
286
+
287
+ ## Delivery complaints (unreleased source)
288
+
289
+ `createDeliveryDisputeChallenge(operationId, request)` and `submitDeliveryDisputeAction(operationId, {challengeId, challengeToken, signature})` support buyer complaints, original-recipient responses, buyer closure and private reads. These methods require version 0.5.0 or later.
290
+
291
+ ```ts
292
+ const challenge = await client.createDeliveryDisputeChallenge(operationId, {
293
+ wallet: account.address, action: "read"
294
+ });
295
+ const signature = await account.signMessage({ message: challenge.message });
296
+ const response = await client.submitDeliveryDisputeAction(operationId, {
297
+ challengeId: challenge.challengeId, challengeToken: challenge.challengeToken, signature
298
+ });
299
+ if (response.kind === "evidence") {
300
+ // Inspect response.dispute and response.payment.
301
+ }
302
+ ```
303
+
304
+ No API key or payment is sent. Sign with the original payer or recipient wallet; keep proofs private. Retry exact signed writes after interruption; reads need fresh challenges. This records statements, not arbitration or refunds. See [the complete workflow](https://evidence.contact/delivery-disputes.md).
305
+
306
+ Dispute writes (including retries) return `kind: "acknowledgment"` and the original action receipt only. Fresh wallet-signed reads return `kind: "evidence"` with the current case. Return the opaque `challengeToken` unchanged; it is required for submission. Buyer closure preserves the seller’s right to append evidence. Settlement status is independent of operation/result expiry. SDK checks enforce structural and lifecycle consistency, not cryptographic verification of exported signatures.
307
+
308
+ Dispute writes require `binding` from a fresh signed read. Read challenges contain no private operation data and do not confirm that an operation or case exists. Check the returned origin and consent text before signing; `dispute_binding_conflict` requires a new read and signed statement. The SDK rejects origin/consent changes, contradictory delivery/payment states and inconsistent immutable bindings across events.
309
+
310
+ ## Protected work (0.5.0)
311
+
312
+ `ProtectedWorkClient` covers all primary protected-work HTTP endpoints with generated OpenAPI types and request/response validation. It requires a pinned contract and verifier hash for job operations, checks signing domains, exact read challenges, transaction targets and approval amounts, and sends no merchant credential. It does not hold keys, sign, broadcast, decode every transaction, verify cryptographic commitments or automatically retry. A configured server may relay signed actions; funding is never relayed.
313
+
314
+ Start with `new ProtectedWorkClient({baseUrl}).getCapabilities()` and stop when `configured` is false. The isolated Base test does not activate production. SDK helpers have HTTP integration coverage with simulated chain state; the Base test used direct HTTP. Use version 0.5.0 for these helpers. See [the full client guide](https://evidence.contact/merchant-sdk.md#protected-json-work-source-050), [protocol](https://evidence.contact/protected-work.md) and [source examples](examples/protected-work.ts). Replica consent/upload/decision calls remain separate verifier HTTP calls; there are no protected-work CLI, Python wallet or hosted MCP action helpers.
package/dist/index.d.ts CHANGED
@@ -4,6 +4,119 @@ export type TransactionHash = `0x${string}`;
4
4
  export type ProofMethod = "approve_zero" | "transfer_zero";
5
5
  export type ArtifactContentType = "application/json" | "text/plain; charset=utf-8";
6
6
  export type WorkTicketState = "open" | "claimed" | "quoted" | "accepted" | "paying" | "paid" | "delivered" | "accepted_by_buyer" | "disputed" | "expired";
7
+ export interface DeliveryDisputeBinding {
8
+ payer: EvmAddress;
9
+ recipient: EvmAddress;
10
+ requestHash: string;
11
+ workTicketId: string | null;
12
+ quoteTermsHash: string | null;
13
+ resultHashAtOpen: string | null;
14
+ transaction: string | null;
15
+ amountAtomic: string;
16
+ asset: EvmAddress;
17
+ network: "eip155:8453";
18
+ }
19
+ export type DeliveryDisputeRequest = {
20
+ wallet: EvmAddress;
21
+ action: "open";
22
+ expectedRevision: 0;
23
+ binding: DeliveryDisputeBinding;
24
+ reason: "not_delivered" | "unreadable" | "terms_mismatch";
25
+ detail: string;
26
+ evidenceHashes: string[];
27
+ } | {
28
+ wallet: EvmAddress;
29
+ action: "respond";
30
+ expectedRevision: number;
31
+ binding: DeliveryDisputeBinding;
32
+ detail: string;
33
+ evidenceHashes: string[];
34
+ } | {
35
+ wallet: EvmAddress;
36
+ action: "close";
37
+ expectedRevision: number;
38
+ binding: DeliveryDisputeBinding;
39
+ outcome: "resolved" | "unresolved";
40
+ detail: string;
41
+ evidenceHashes: string[];
42
+ } | {
43
+ wallet: EvmAddress;
44
+ action: "read";
45
+ };
46
+ export interface DeliveryDisputeChallenge {
47
+ challengeToken: string;
48
+ challengeId: string;
49
+ message: string;
50
+ expiresAt: string;
51
+ submit: string;
52
+ }
53
+ export interface DeliveryDisputeEvent {
54
+ revision: number;
55
+ action: "open" | "respond" | "close";
56
+ wallet: string;
57
+ createdAt: string;
58
+ statement: Exclude<DeliveryDisputeRequest, {
59
+ action: "read";
60
+ }>;
61
+ message: string;
62
+ signature: string;
63
+ challengeId: string;
64
+ }
65
+ export interface DeliveryDisputeEvidence {
66
+ protocol: "402mob.delivery-dispute.v1";
67
+ kind: "evidence";
68
+ operationId: string;
69
+ operationState: string;
70
+ binding: DeliveryDisputeBinding;
71
+ dispute: null | {
72
+ operationId: string;
73
+ buyer: string;
74
+ seller: string;
75
+ status: "open" | "responded" | "closed";
76
+ outcome: "resolved" | "unresolved" | null;
77
+ revision: number;
78
+ openedAt: string;
79
+ expiresAt: string;
80
+ responseDueAt: string;
81
+ buyerReviewDueAt: string | null;
82
+ binding: {
83
+ requestHash: string;
84
+ workTicketId: string | null;
85
+ quoteTermsHash: string | null;
86
+ resultHashAtOpen: string | null;
87
+ transaction: string | null;
88
+ amountAtomic: string;
89
+ asset: string;
90
+ network: string;
91
+ };
92
+ events: DeliveryDisputeEvent[];
93
+ };
94
+ payment: {
95
+ state: "settled" | "pending" | "failed" | "unconfirmed";
96
+ transaction: string | null;
97
+ };
98
+ delivery: {
99
+ resultHash: string | null;
100
+ resultAvailable: boolean;
101
+ };
102
+ responseOverdue: boolean;
103
+ buyerReviewOverdue: boolean;
104
+ scope: string;
105
+ replay: boolean;
106
+ }
107
+ export interface DeliveryDisputeAcknowledgment {
108
+ protocol: "402mob.delivery-dispute.v1";
109
+ kind: "acknowledgment";
110
+ operationId: string;
111
+ receipt: {
112
+ challengeId: string;
113
+ action: "open" | "respond" | "close";
114
+ revision: number;
115
+ createdAt: string;
116
+ };
117
+ replay: boolean;
118
+ }
119
+ export type DeliveryDisputeResponse = DeliveryDisputeEvidence | DeliveryDisputeAcknowledgment;
7
120
  export interface EnrollmentChallenge {
8
121
  challengeId: string;
9
122
  message: string;
@@ -529,6 +642,16 @@ export declare class MerchantClient {
529
642
  }): Promise<BuyerRequestDetail & {
530
643
  replay: boolean;
531
644
  }>;
645
+ /** Free, wallet-signed complaint/response/closure/read. No bearer key or payment is sent. */
646
+ createDeliveryDisputeChallenge(operationId: string, input: DeliveryDisputeRequest): Promise<DeliveryDisputeChallenge>;
647
+ /** Retry the same signed write after interruption; private reads require a fresh challenge. */
648
+ submitDeliveryDisputeAction(operationId: string, input: {
649
+ challengeId: string;
650
+ challengeToken: string;
651
+ signature: EvmSignature;
652
+ }): Promise<DeliveryDisputeResponse>;
532
653
  private request;
533
654
  }
534
- export {};
655
+ export { ProtectedWorkClient, ProtectedWorkApiError } from "./protected-work.js";
656
+ export type { ProtectedWorkClientOptions } from "./protected-work.js";
657
+ export type * from "./protected-work-types.js";
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ function requireEip191Signature(signature) {
4
4
  }
5
5
  return signature;
6
6
  }
7
+ const disputeConsent = "This signature authorizes only this statement or private read. It does not authorize payment, refund or adjudication. Response targets: seller 48 hours after opening; buyer 7 days after first response. Silence is not acceptance.";
7
8
  function buyerRequestKey(key) {
8
9
  if (!/^[A-Za-z0-9_-]{16,128}$/.test(key))
9
10
  throw new TypeError("Persist an idempotency key of 16–128 letters, digits, underscores or hyphens.");
@@ -274,6 +275,18 @@ export class MerchantClient {
274
275
  selectBuyerRequestOffer(requestId, input) {
275
276
  return this.request("POST", `/v1/buyer-requests/${checkedWorkTicketId(requestId)}/selections`, { ...input, signature: requireEip191Signature(input.signature) });
276
277
  }
278
+ /** Free, wallet-signed complaint/response/closure/read. No bearer key or payment is sent. */
279
+ createDeliveryDisputeChallenge(operationId, input) {
280
+ if (!isDisputeRequest(input))
281
+ throw new TypeError("Provide a participant wallet, supported action, current revision and bounded statement/evidence hashes.");
282
+ return this.request("POST", `/v1/operations/${checkedWorkTicketId(operationId)}/dispute-challenges`, input);
283
+ }
284
+ /** Retry the same signed write after interruption; private reads require a fresh challenge. */
285
+ submitDeliveryDisputeAction(operationId, input) {
286
+ if (!digest(input.challengeId) || !disputeToken(input.challengeToken))
287
+ throw new TypeError("Return the exact challengeId and challengeToken from the challenge response.");
288
+ return this.request("POST", `/v1/operations/${checkedWorkTicketId(operationId)}/dispute-actions`, { ...input, signature: requireEip191Signature(input.signature) });
289
+ }
277
290
  async request(method, path, body, authenticated = false, extraHeaders = {}) {
278
291
  if (authenticated && !this.#apiKey) {
279
292
  throw new Error("This request requires a merchant API key. Use client.withApiKey(apiKey).");
@@ -305,6 +318,17 @@ export class MerchantClient {
305
318
  const invalidResponse = () => new MerchantApiError(response.status, "invalid_response", "402Mob returned an incomplete response. The request may have completed; preserve recovery data. If enrollment or rotation completed without a usable key, use credential rotation.");
306
319
  if (!isRecord(payload))
307
320
  throw invalidResponse();
321
+ if (path.endsWith("/dispute-challenges")) {
322
+ const expected = path.replace(/dispute-challenges$/, "dispute-actions"), id = path.split("/")[3];
323
+ if (!digest(payload.challengeId) || !disputeToken(payload.challengeToken) || !nonempty(payload.message) || !timestamp(payload.expiresAt) || payload.submit !== expected || !isDisputeRequest(body))
324
+ throw invalidResponse();
325
+ const statement = { ...body, wallet: body.wallet.toLowerCase(), ...(body.action !== "read" ? { detail: body.detail.trim(), binding: { ...body.binding, payer: body.binding.payer.toLowerCase(), recipient: body.binding.recipient.toLowerCase(), asset: body.binding.asset.toLowerCase() } } : {}) };
326
+ const fields = disputeMessage(payload.message);
327
+ if (!fields || fields.origin !== new URL(this.baseUrl).origin || fields.operation !== id || fields.nonce !== payload.challengeId || fields.expires !== payload.expiresAt || stableJson(fields.statement) !== stableJson(statement) || stableJson(fields.binding) !== stableJson(body.action === "read" ? null : statement.binding))
328
+ throw invalidResponse();
329
+ }
330
+ if (path.endsWith("/dispute-actions") && !isDisputeResponse(payload, path.split("/")[3], body, new URL(this.baseUrl).origin))
331
+ throw invalidResponse();
308
332
  if (path.startsWith("/v1/worker-directory?")) {
309
333
  if (!Array.isArray(payload.items) || !payload.items.every(item => isWorkerProfileView(item) && item.workerProfile.discoverable && item.workerProfile.availability === "accepting") || !(payload.nextCursor === null || Number.isSafeInteger(payload.nextCursor) && Number(payload.nextCursor) >= 0))
310
334
  throw invalidResponse();
@@ -479,3 +503,127 @@ function checkedWorkTicketId(id) {
479
503
  }
480
504
  return id;
481
505
  }
506
+ function isDisputeRequest(v) {
507
+ if (!isRecord(v) || !address(v.wallet))
508
+ return false;
509
+ if (v.action === "read")
510
+ return onlyKeys(v, ["wallet", "action"]);
511
+ if (!["open", "respond", "close"].includes(String(v.action)) || !Number.isInteger(v.expectedRevision) || Number(v.expectedRevision) < 0 || Number(v.expectedRevision) > 32 || !boundedWorkerText(v.detail, 2000) || !isRecord(v.binding) || !validMessageBinding(v.binding) || !Array.isArray(v.evidenceHashes) || v.evidenceHashes.length > 8 || !v.evidenceHashes.every(digest))
512
+ return false;
513
+ const common = ["wallet", "action", "expectedRevision", "binding", "detail", "evidenceHashes"];
514
+ if (v.action === "open")
515
+ return v.expectedRevision === 0 && ["not_delivered", "unreadable", "terms_mismatch"].includes(String(v.reason)) && onlyKeys(v, [...common, "reason"]);
516
+ if (v.action === "close")
517
+ return ["resolved", "unresolved"].includes(String(v.outcome)) && onlyKeys(v, [...common, "outcome"]);
518
+ return onlyKeys(v, common);
519
+ }
520
+ function disputeToken(v) { return typeof v === "string" && /^[A-Za-z0-9_-]{40,40000}$/.test(v); }
521
+ function stableJson(v) {
522
+ if (Array.isArray(v))
523
+ return `[${v.map(stableJson).join(",")}]`;
524
+ if (isRecord(v))
525
+ return `{${Object.keys(v).sort().map(k => JSON.stringify(k) + ":" + stableJson(v[k])).join(",")}}`;
526
+ return JSON.stringify(v);
527
+ }
528
+ function disputeMessage(v) {
529
+ if (typeof v !== "string")
530
+ return;
531
+ const lines = v.split("\n");
532
+ if (lines.length !== 8 || lines[7] !== disputeConsent || lines[0] !== "402Mob delivery dispute authorization" || !lines[1]?.startsWith("Origin: ") || !lines[2]?.startsWith("Operation: ") || !lines[3]?.startsWith("Statement: ") || !lines[4]?.startsWith("Binding: ") || !lines[5]?.startsWith("Nonce: ") || !lines[6]?.startsWith("Expires: "))
533
+ return;
534
+ try {
535
+ const binding = JSON.parse(lines[4].slice(9));
536
+ if (binding !== null && !isRecord(binding))
537
+ return;
538
+ return { origin: lines[1].slice(8), operation: lines[2].slice(11), nonce: lines[5].slice(7), expires: lines[6].slice(9), statement: JSON.parse(lines[3].slice(11)), binding };
539
+ }
540
+ catch {
541
+ return;
542
+ }
543
+ }
544
+ function validDisputeBinding(b) {
545
+ return isRecord(b) && onlyKeys(b, ["requestHash", "workTicketId", "quoteTermsHash", "resultHashAtOpen", "transaction", "amountAtomic", "asset", "network"]) && digest(b.requestHash) &&
546
+ ((b.workTicketId === null && b.quoteTermsHash === null) || (uuid(b.workTicketId) && digest(b.quoteTermsHash))) && (b.resultHashAtOpen === null || digest(b.resultHashAtOpen)) &&
547
+ (b.transaction === null || typeof b.transaction === "string" && /^0x[0-9a-fA-F]{64}$/.test(b.transaction)) && atomicAmount(b.amountAtomic) && address(b.asset) && b.asset.toLowerCase() === baseUsdc && b.network === "eip155:8453";
548
+ }
549
+ function validMessageBinding(b) {
550
+ const { payer, recipient, ...rest } = b;
551
+ return address(payer) && address(recipient) && validDisputeBinding(rest);
552
+ }
553
+ function isDisputeResponse(v, operationId, proof, origin) {
554
+ if (v.protocol !== "402mob.delivery-dispute.v1" || v.operationId !== operationId || !isRecord(proof) || typeof v.replay !== "boolean")
555
+ return false;
556
+ if (v.kind === "acknowledgment") {
557
+ const r = v.receipt;
558
+ return onlyKeys(v, ["protocol", "kind", "operationId", "receipt", "replay"]) && isRecord(r) && onlyKeys(r, ["challengeId", "action", "revision", "createdAt"]) && r.challengeId === proof.challengeId && ["open", "respond", "close"].includes(String(r.action)) && Number.isInteger(r.revision) && Number(r.revision) >= 1 && Number(r.revision) <= 32 && timestamp(r.createdAt);
559
+ }
560
+ if (v.kind !== "evidence" || v.replay !== false || !onlyKeys(v, ["protocol", "kind", "operationId", "operationState", "binding", "dispute", "payment", "delivery", "responseOverdue", "buyerReviewOverdue", "scope", "replay"]) || !["executing", "staged", "settling", "pending", "settled", "failed", "expired"].includes(String(v.operationState)) || !isRecord(v.payment) || !onlyKeys(v.payment, ["state", "transaction"]) || !["settled", "pending", "failed", "unconfirmed"].includes(String(v.payment.state)) || !(v.payment.transaction === null || typeof v.payment.transaction === "string" && /^0x[0-9a-fA-F]{64}$/.test(v.payment.transaction)) || !isRecord(v.delivery) || !onlyKeys(v.delivery, ["resultHash", "resultAvailable"]) || typeof v.delivery.resultAvailable !== "boolean" || !(v.delivery.resultHash === null || digest(v.delivery.resultHash)) || typeof v.responseOverdue !== "boolean" || typeof v.buyerReviewOverdue !== "boolean" || !nonempty(v.scope))
561
+ return false;
562
+ if (!isRecord(v.binding) || !validMessageBinding(v.binding) || v.binding.transaction !== v.payment.transaction || v.binding.resultHashAtOpen !== v.delivery.resultHash)
563
+ return false;
564
+ if (v.payment.state === "settled" && v.payment.transaction === null)
565
+ return false;
566
+ const expectedPayment = { executing: "unconfirmed", staged: "unconfirmed", settling: "pending", pending: "pending", settled: "settled", failed: "failed" };
567
+ if (v.operationState !== "expired" && v.payment.state !== expectedPayment[String(v.operationState)])
568
+ return false;
569
+ if (v.operationState === "expired" && !["settled", "unconfirmed"].includes(String(v.payment.state)))
570
+ return false;
571
+ if (v.delivery.resultAvailable && (v.delivery.resultHash === null || v.payment.state !== "settled"))
572
+ return false;
573
+ if (v.binding.workTicketId === null && v.delivery.resultAvailable && v.operationState !== "settled")
574
+ return false;
575
+ if (v.dispute === null)
576
+ return !v.responseOverdue && !v.buyerReviewOverdue;
577
+ const d = v.dispute;
578
+ if (!isRecord(d) || d.operationId !== operationId || !address(d.buyer) || !address(d.seller) || !["open", "responded", "closed"].includes(String(d.status)) || !(d.outcome === null || ["resolved", "unresolved"].includes(String(d.outcome))) || !Number.isInteger(d.revision) || Number(d.revision) < 1 || Number(d.revision) > 32 || !timestamp(d.openedAt) || !timestamp(d.expiresAt) || !timestamp(d.responseDueAt) || !(d.buyerReviewDueAt === null || timestamp(d.buyerReviewDueAt)) || !validDisputeBinding(d.binding) || !Array.isArray(d.events) || d.events.length !== d.revision || v.payment.state !== "settled" || d.binding.transaction !== v.payment.transaction)
579
+ return false;
580
+ const currentBinding = v.binding, caseBinding = d.binding;
581
+ const immutable = ["requestHash", "workTicketId", "quoteTermsHash", "transaction", "amountAtomic", "asset", "network"];
582
+ if (v.binding.payer !== d.buyer || v.binding.recipient !== d.seller || immutable.some(k => currentBinding[k] !== caseBinding[k]))
583
+ return false;
584
+ if (Date.parse(d.expiresAt) !== Date.parse(d.openedAt) + 2592000000 || Date.parse(d.responseDueAt) !== Date.parse(d.openedAt) + 172800000)
585
+ return false;
586
+ let closed, firstResponse, lastTime = Date.parse(d.openedAt);
587
+ const seen = new Set();
588
+ for (let i = 0; i < d.events.length; i++) {
589
+ const e = d.events[i];
590
+ if (!isRecord(e) || e.revision !== i + 1 || !address(e.wallet) || !timestamp(e.createdAt) || !isDisputeRequest(e.statement) || e.statement.action === "read" || e.statement.action !== e.action || e.statement.expectedRevision !== i || e.wallet.toLowerCase() !== e.statement.wallet.toLowerCase() || typeof e.signature !== "string" || !/^0x[0-9a-fA-F]{130}$/.test(e.signature) || !digest(e.challengeId) || seen.has(e.challengeId))
591
+ return false;
592
+ seen.add(e.challengeId);
593
+ const at = Date.parse(e.createdAt);
594
+ if (at < lastTime || at >= Date.parse(d.expiresAt))
595
+ return false;
596
+ lastTime = at;
597
+ const m = disputeMessage(e.message);
598
+ if (!m || m.origin !== origin || m.operation !== operationId || m.nonce !== e.challengeId || !timestamp(m.expires) || !isRecord(m.binding) || !validMessageBinding(m.binding) || m.binding.payer !== d.buyer || m.binding.recipient !== d.seller || m.binding.requestHash !== d.binding.requestHash || m.binding.quoteTermsHash !== d.binding.quoteTermsHash || immutable.some(k => m.binding[k] !== caseBinding[k]) || stableJson(m.binding) !== stableJson(e.statement.binding) || stableJson(m.statement) !== stableJson(e.statement))
599
+ return false;
600
+ if (i === 0) {
601
+ const { payer: _payer, recipient: _recipient, ...binding } = m.binding;
602
+ if (e.action !== "open" || e.wallet.toLowerCase() !== d.buyer.toLowerCase() || e.createdAt !== d.openedAt || stableJson(binding) !== stableJson(d.binding))
603
+ return false;
604
+ }
605
+ else if (e.action === "respond") {
606
+ if (e.wallet.toLowerCase() !== d.seller.toLowerCase())
607
+ return false;
608
+ if (!closed)
609
+ firstResponse ??= e;
610
+ }
611
+ else if (e.action === "close") {
612
+ if (closed || e.wallet.toLowerCase() !== d.buyer.toLowerCase())
613
+ return false;
614
+ closed = e;
615
+ }
616
+ else
617
+ return false;
618
+ }
619
+ if (d.status !== (closed ? "closed" : firstResponse ? "responded" : "open"))
620
+ return false;
621
+ if (d.outcome !== (closed ? closed.statement.outcome : null))
622
+ return false;
623
+ if (d.buyerReviewDueAt !== (firstResponse ? new Date(Date.parse(firstResponse.createdAt) + 604800000).toISOString() : null))
624
+ return false;
625
+ if ((d.status !== "open" && v.responseOverdue) || (d.status !== "responded" && v.buyerReviewOverdue))
626
+ return false;
627
+ return true;
628
+ }
629
+ export { ProtectedWorkClient, ProtectedWorkApiError } from "./protected-work.js";
@@ -0,0 +1,2 @@
1
+ export declare const protectedSchemaHash = "5c69849fb52f35cf29367043b58156d274955a79ef7ee52f0c78d6b4561e1b40";
2
+ export declare const protectedSchemas: Record<string, unknown>;