402mob-merchant-sdk 0.4.2 → 0.6.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,29 @@
1
1
  # Changelog
2
2
 
3
- ## 0.4.2 — prepared, unpublished
3
+ ## 0.6.0
4
+
5
+ - Accept semantically identical inspection-plan property order in proposal responses while rejecting unknown or substituted fields.
6
+
7
+ - Add the protected inspection seller example: reviewed worker publication, exact capability search and explicit buyer-funded proposal construction; no automatic signing or payment.
8
+
9
+ - Add explicit protected inspection proposals/handoffs and `ProtectedInspectionClient`, isolated from direct Work Tickets and JSON escrow. A separate configured deployment is required; no signing or spending is added to the SDK.
10
+
11
+ - Retain requested format, selected payout, title and proposal note in the grant input. Reject unexpected handoff fields and substituted request lifetimes. Proposal publication checks the 32 KiB canonical grant-input limit.
12
+
13
+ - Add typed fresh-work request, proposal, preflight decline and signed-selection helpers with response/handoff validation. Direct selection returns a separately signed Work Ticket grant template; protected inspection selection returns an escrow task. Neither selection authorizes payment.
14
+ - Existing prepared-offer demand methods remain unchanged. The SDK never signs, purchases external tests or broadcasts settlement transactions; spending authority remains in the caller runtime.
15
+
16
+ ## 0.5.0
17
+
18
+ - 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.
19
+
20
+ - 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.
21
+
22
+ - 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.
23
+
24
+ - Add wallet-signed delivery complaint, seller response, buyer closure and evidence-read methods and types. No automatic payment or refund.
25
+
26
+ ## 0.4.2 — published
4
27
 
5
28
  - Add optional `artifact` commitment and `share` fields to resolved resources. Validate supplied commitments and preserve compatibility with older servers that omit them.
6
29
 
package/README.md CHANGED
@@ -1,6 +1,9 @@
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.6.0** adds fresh-work proposals and `ProtectedInspectionClient`, plus a seller example for advertising buyer-funded inspections. Use `402mob-merchant-sdk@0.6.0` and check the target server’s `workRequests` and `protectedInspections` capabilities before starting.
4
+
5
+
6
+ Version 0.5.0 adds wallet-signed delivery complaints and the separate `ProtectedWorkClient` for experimental JSON escrow. Install `402mob-merchant-sdk@0.6.0` for these methods; earlier versions do not include them.
4
7
 
5
8
  **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
9
 
@@ -254,7 +257,7 @@ To test a local build in another project, install the generated file with `npm i
254
257
 
255
258
  The SDK tests use mocked HTTP responses and type-check every bundled example. They do not contact `evidence.contact` or submit wallet transactions. Service integration tests in the main repository cover actual local HTTP/enrollment signatures; Work Ticket and public-request live Base validation remain pending.
256
259
 
257
- Examples included in the tarball import the built package output: [enroll and publish](./examples/enroll-and-publish.ts), [resolve prepared resources](./examples/resolve-resources.ts), [worker discovery](./examples/worker-discovery.ts), and [signed request to checkout handoff](./examples/request-to-checkout.ts).
260
+ Examples included in the tarball import the built package output: [enroll and publish](./examples/enroll-and-publish.ts), [resolve prepared resources](./examples/resolve-resources.ts), [worker discovery](./examples/worker-discovery.ts), [signed request to checkout handoff](./examples/request-to-checkout.ts), and [protected inspection service](./examples/protected-inspection-service.ts).
258
261
 
259
262
  MIT licensed. See [LICENSE](./LICENSE).
260
263
 
@@ -282,3 +285,51 @@ The supported states are `open`, `claimed`, `quoted`, `accepted`, `paying`, `pai
282
285
  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
286
 
284
287
  These methods are available from version 0.3.0.
288
+
289
+
290
+ ## Delivery complaints (unreleased source)
291
+
292
+ `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.
293
+
294
+ ```ts
295
+ const challenge = await client.createDeliveryDisputeChallenge(operationId, {
296
+ wallet: account.address, action: "read"
297
+ });
298
+ const signature = await account.signMessage({ message: challenge.message });
299
+ const response = await client.submitDeliveryDisputeAction(operationId, {
300
+ challengeId: challenge.challengeId, challengeToken: challenge.challengeToken, signature
301
+ });
302
+ if (response.kind === "evidence") {
303
+ // Inspect response.dispute and response.payment.
304
+ }
305
+ ```
306
+
307
+ 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).
308
+
309
+ 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.
310
+
311
+ 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.
312
+
313
+ ## Protected work (0.5.0)
314
+
315
+ `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.
316
+
317
+ 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.
318
+
319
+ ## Fresh-work requests (0.6.0)
320
+
321
+ Use `createWorkRequestChallenge` / `completeWorkRequest` to publish a buyer-signed public need with acceptance criteria, then `listWorkRequests` / `getWorkRequest` to read it. A merchant client can `proposeWorkRequest` with its declared accepting capability, fee, proposed deadline and seller-funded test budget, or `declineWorkRequest` with a free preflight reason. One immutable response per merchant is allowed.
322
+
323
+ `createWorkRequestSelectionChallenge` / `selectWorkRequestProposal` require the buyer's separate EIP-191 signature. For direct proposals, the SDK validates the returned `workTicketHandoff`, including its exact input fields and the selected format, payout and proposal note. Add the intended buyer wallet and a new persisted grant idempotency key, then follow the existing Work Ticket grant, quote acceptance and payment flow. Selection creates no ticket or payment and does not track either. The deadline and test budget are declarations, not escrow or wallet spending limits. Inputs and responses are public; never include live payment authorizations or secrets.
324
+
325
+ See [the auditor response example](examples/respond-to-work-request.ts) and [the protocol guide](https://evidence.contact/work-requests.md). Existing wallet/CLI/Python packages do not yet wrap these routes; retain credentials in your runtime and use HTTP/MCP or this SDK.
326
+
327
+ The grant input is limited to 32 KiB in canonical JSON; an oversized proposal is rejected before it becomes immutable. Compare the actual quote with the retained proposal terms before signing, and check that its deadline is still useful. The copied format/payout/deadline fields are review context, not extra Work Ticket enforcement. Reuse the saved grant key after timeouts; another key can create another ticket. Public demand proves neither buyer funds nor authorization to test an external endpoint. For direct proposals, external test costs remain the seller’s risk, and a free decline does not reimburse them. Explicit protected inspection proposals instead use buyer-funded probes and a separate escrow handoff; see below.
328
+
329
+ ## Protected inspections (0.6.0)
330
+
331
+ `ProtectedInspectionClient` provides the separate inspection HTTP flow: `getCapabilities`, `prepare`, `prepareFunding` (including the exact task), `getJob`, `createReadChallenge`, `submitObservation`, `start`, candidate submission, replicas, evaluation, result reads and settlement/appeal transaction preparation. Pin the new inspection contract and verifier hash. There is no signing, external HTTP probe, automatic retry or transaction broadcast in this client. The source probe script lives in the service repository. Negative observations may satisfy the narrow paid task; HTTP truth and general audit quality are not certified. Stop on `configured:false`; the separate same-operator pilot is configured, but its full funded lifecycle has not been live-money tested. See https://evidence.contact/protected-inspections.md.
332
+
333
+ ### Offer a protected inspection service
334
+
335
+ Use the free [service recipe](https://evidence.contact/protected-inspection-service.json) and [seller walkthrough](https://evidence.contact/protected-inspections.md#offer-this-service). Publish the reviewed `payment-readiness-inspection` worker profile, then propose an explicit protected inspection against an actual `/v1/work-requests` record. Profile writes replace existing capabilities. Buyer selection returns `protectedWorkHandoff`; it does not authorize payment. This is not a prepared-artifact offer or Payment Link. Require confirmed escrow and retained buyer evidence before work. SDK 0.6.0 includes `examples/protected-inspection-service.ts`; earlier SDK 0.5.0 can advertise/search profiles but lacks fresh-proposal and inspection helpers. No automatic job inbox or notification is provided.
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;
@@ -312,6 +425,108 @@ export interface WorkerDirectoryPage {
312
425
  items: WorkerProfileView[];
313
426
  nextCursor: number | null;
314
427
  }
428
+ export interface FreshWorkRequestInput extends BuyerRequestInput {
429
+ acceptanceCriteria: string[];
430
+ }
431
+ export interface InspectionPlan {
432
+ version: "buyer-probe-v1";
433
+ targetUrl: string;
434
+ method: "GET";
435
+ paymentRecipient: EvmAddress;
436
+ maxTestAmountAtomic: string;
437
+ maxPaidAttempts: 1;
438
+ }
439
+ export interface FreshWorkProposalInput {
440
+ capability: string;
441
+ deliverable: string;
442
+ priceUSDC: string;
443
+ deliveryDeadline: string;
444
+ testPurchaseBudgetUSDC: string;
445
+ testPurchasePayer: "seller" | "buyer";
446
+ note: string;
447
+ settlementMode?: "protected_inspection";
448
+ inspection?: InspectionPlan;
449
+ }
450
+ export interface FreshWorkDeclineInput {
451
+ reason: "endpoint_unavailable" | "unsupported_payment" | "scope_unsupported" | "capacity_unavailable" | "other";
452
+ note: string;
453
+ }
454
+ export interface PublicFreshWorkRequest extends Omit<FreshWorkRequestInput, "buyer"> {
455
+ id: string;
456
+ kind: "fresh";
457
+ state: "open" | "selected" | "expired";
458
+ createdAt: string;
459
+ expiresAt: string;
460
+ selectedProposalId: string | null;
461
+ }
462
+ export type FreshWorkResponse = {
463
+ id: string;
464
+ requestId: string;
465
+ merchantId: string;
466
+ payoutAddress: EvmAddress;
467
+ termsHash: string;
468
+ createdAt: string;
469
+ } & ({
470
+ kind: "proposal";
471
+ terms: FreshWorkProposalInput;
472
+ } | {
473
+ kind: "decline";
474
+ terms: FreshWorkDeclineInput;
475
+ });
476
+ export interface FreshWorkHandoff {
477
+ kind: "work_ticket_grant";
478
+ method: "POST";
479
+ path: "/v1/work-tickets/challenges";
480
+ body: {
481
+ merchantId: string;
482
+ capability: string;
483
+ input: {
484
+ workRequestId: string;
485
+ proposalId: string;
486
+ proposalTermsHash: string;
487
+ title: string;
488
+ contentType: ArtifactContentType;
489
+ payoutAddress: EvmAddress;
490
+ proposalNote: string;
491
+ scope: string;
492
+ acceptanceCriteria: string[];
493
+ deliverable: string;
494
+ deliveryDeadline: string;
495
+ testPurchaseBudgetUSDC: string;
496
+ testPurchasePayer: "seller";
497
+ };
498
+ maxPriceUSDC: string;
499
+ expiresInSeconds: number;
500
+ };
501
+ buyerWalletRequired: true;
502
+ grantSignatureRequired: true;
503
+ paymentAuthorized: false;
504
+ quoteAcceptanceRequired: true;
505
+ tracksTicketCreation: false;
506
+ }
507
+ export interface InspectionHandoff {
508
+ kind: "protected_inspection";
509
+ path: "/v1/protected-inspections/prepare";
510
+ task: {
511
+ version: "funded-inspection-v1";
512
+ requestId: string;
513
+ proposalId: string;
514
+ proposalTermsHash: string;
515
+ plan: InspectionPlan;
516
+ };
517
+ seller: EvmAddress;
518
+ amountAtomic: string;
519
+ deliveryDeadline: number;
520
+ fundingRequired: true;
521
+ buyerProbeRequired: true;
522
+ paymentAuthorized: false;
523
+ }
524
+ export interface FreshWorkDetail {
525
+ request: PublicFreshWorkRequest;
526
+ responses: FreshWorkResponse[];
527
+ workTicketHandoff: FreshWorkHandoff | null;
528
+ protectedWorkHandoff: InspectionHandoff | null;
529
+ }
315
530
  export interface BuyerRequestInput {
316
531
  buyer: EvmAddress;
317
532
  title: string;
@@ -494,6 +709,41 @@ export declare class MerchantClient {
494
709
  retentionDays: number;
495
710
  }>;
496
711
  /** Free public demand. Caller persists the key, signs the returned message and completes separately. */
712
+ createWorkRequestChallenge(input: FreshWorkRequestInput, idempotencyKey: string): Promise<BuyerRequestChallenge>;
713
+ completeWorkRequest(input: FreshWorkRequestInput & {
714
+ challengeId: string;
715
+ signature: EvmSignature;
716
+ }, idempotencyKey: string): Promise<FreshWorkDetail & {
717
+ replay: boolean;
718
+ }>;
719
+ listWorkRequests(input?: ResolveAgentResourcesInput): Promise<{
720
+ items: PublicFreshWorkRequest[];
721
+ nextCursor: number | null;
722
+ }>;
723
+ getWorkRequest(id: string): Promise<FreshWorkDetail>;
724
+ proposeWorkRequest(id: string, input: FreshWorkProposalInput): Promise<{
725
+ response: FreshWorkResponse;
726
+ replay: boolean;
727
+ }>;
728
+ declineWorkRequest(id: string, input: FreshWorkDeclineInput): Promise<{
729
+ response: FreshWorkResponse;
730
+ replay: boolean;
731
+ }>;
732
+ createWorkRequestSelectionChallenge(id: string, input: {
733
+ buyer: EvmAddress;
734
+ proposalId: string;
735
+ }): Promise<{
736
+ challengeId: string;
737
+ message: string;
738
+ expiresAt: string;
739
+ proposal: FreshWorkResponse;
740
+ }>;
741
+ selectWorkRequestProposal(id: string, input: {
742
+ challengeId: string;
743
+ signature: EvmSignature;
744
+ }): Promise<FreshWorkDetail & {
745
+ replay: boolean;
746
+ }>;
497
747
  createBuyerRequestChallenge(input: BuyerRequestInput, idempotencyKey: string): Promise<BuyerRequestChallenge>;
498
748
  completeBuyerRequest(input: BuyerRequestInput & {
499
749
  challengeId: string;
@@ -529,6 +779,19 @@ export declare class MerchantClient {
529
779
  }): Promise<BuyerRequestDetail & {
530
780
  replay: boolean;
531
781
  }>;
782
+ /** Free, wallet-signed complaint/response/closure/read. No bearer key or payment is sent. */
783
+ createDeliveryDisputeChallenge(operationId: string, input: DeliveryDisputeRequest): Promise<DeliveryDisputeChallenge>;
784
+ /** Retry the same signed write after interruption; private reads require a fresh challenge. */
785
+ submitDeliveryDisputeAction(operationId: string, input: {
786
+ challengeId: string;
787
+ challengeToken: string;
788
+ signature: EvmSignature;
789
+ }): Promise<DeliveryDisputeResponse>;
532
790
  private request;
533
791
  }
534
- export {};
792
+ export { ProtectedWorkClient, ProtectedWorkApiError } from "./protected-work.js";
793
+ export type { ProtectedWorkClientOptions } from "./protected-work.js";
794
+ export type * from "./protected-work-types.js";
795
+ export { ProtectedInspectionClient, ProtectedInspectionApiError } from "./protected-inspection.js";
796
+ export type * from "./protected-inspection-types.js";
797
+ export type { ProtectedInspectionClientOptions } from "./protected-inspection.js";