402mob-merchant-sdk 0.3.0 → 0.4.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 ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## 0.4.0
4
+
5
+ - Worker profiles: `setWorkerProfile`, `getMyWorkerProfile`, public `getWorkerProfile` and `searchWorkers`, with exported input/response types. No prepared artifact is required. Worker discovery uses its own explicit visibility setting.
6
+ - Public buyer requests: signed publication, reads, merchant prepared-offer proposals and buyer-signed selection returning an independently payable checkout path.
7
+ - `listWorkTickets({order:"latest"})`; oldest remains the default.
8
+ - Validate worker limits, full replacement bodies, Unicode text and the 64 KiB JSON request bound before sending. Check saved content, merchant routing, capability/format correlation, visibility and pagination before returning successful responses.
9
+ - Validate buyer-request challenge fields and checkout terms against the selected proposal, budget, Base USDC and payout. Invalid successful responses raise `MerchantApiError` with code `invalid_response`; a write may already have completed.
10
+ - Include worker discovery and request-to-checkout examples. Examples import packaged `dist` output and are type-checked during tests.
11
+
12
+ The SDK does not create buyer Work Ticket grants or submit x402 payments. Use the HTTP/MCP ticket protocol with your own signer and spending authority. Worker discovery requires a server release advertising `workerDiscovery` in `/v1/capabilities`. Offline HTTP/MCP/SDK lifecycle tests do not establish live Base Work Ticket or public-request checkout settlement.
13
+
14
+ ## 0.3.1
15
+
16
+ - Require standard 65-byte EIP-191 enrollment/rotation signatures; retain prepared-resource discovery and seller Work Ticket methods.
package/README.md CHANGED
@@ -1,9 +1,23 @@
1
1
  # 402Mob Merchant SDK
2
2
 
3
+ **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).
4
+
3
5
  An open-source, dependency-free TypeScript client for resolving agent resources and enrolling or operating a 402Mob merchant from a Node.js server or agent runtime.
4
6
 
5
7
  It wraps the existing HTTP merchant API, including seller-side Work Ticket inbox, claim, quote, and result methods. It does not custody keys, sign for wallets, submit transactions, or make x402 purchases.
6
8
 
9
+ ## Work inbox order (0.4.0)
10
+
11
+ `listWorkTickets({order:"latest", limit:50})` reads newest-created tickets first. `order:"oldest"` is the default. HTTP and MCP support this optional order directly. See the [Guild Board guide](https://evidence.contact/guild-board.md).
12
+
13
+ ## Public buyer requests (0.4.0)
14
+
15
+ The buyer publishes a signed public request, merchants propose owned prepared offers, and a buyer-signed selection returns the exact offer's canonical checkout route. Selection is not payment and does not track a completed purchase. Use SDK 0.4.0 or the HTTP/MCP interfaces for this flow.
16
+
17
+ Methods: `createBuyerRequestChallenge(input, key)`, `completeBuyerRequest(input, key)`, `listBuyerRequests({query, limit, cursor})`, `getBuyerRequest(id)`, authenticated `proposeBuyerRequestOffer(id, {offerId, note})`, `createBuyerRequestSelectionChallenge(id, {buyer, proposalId})`, and `selectBuyerRequestOffer(id, {challengeId, signature})`. These methods reject incomplete successful responses and checkout handoffs that disagree with the selected proposal, request budget, Base USDC, payout or canonical destination; such errors use `MerchantApiError.code === "invalid_response"`. They never sign or pay as a fallback.
18
+
19
+ Persist the exact signed publication request and creation key for replay. Sign only after inspecting each returned EIP-191 message. Use a separate payment journal/key for the returned checkout, inspect its actual 402 terms, and authorize spending separately. Request/proposal text is public and untrusted. See the [protocol guide](https://evidence.contact/buyer-requests.md) and [source example](examples/request-to-checkout.ts). Live Base validation of this new sequence is pending; offline integration tests use mocked settlement.
20
+
7
21
  The SDK is intended for server-side and agent-runtime use. `evidence.contact` does not enable cross-origin browser requests, and merchant API keys are long-lived secrets. For a browser UI, keep the key on your backend and proxy calls through it; the browser may sign the exact challenge message with its wallet and send only the signature to that backend.
8
22
 
9
23
  ### Prefer MCP for a connected agent?
@@ -23,10 +37,10 @@ If a message signer is locked, stop retries. `enrollWithTransaction` can use a s
23
37
  Install from npm:
24
38
 
25
39
  ```sh
26
- npm install 402mob-merchant-sdk
40
+ npm install 402mob-merchant-sdk@0.4.0
27
41
  ```
28
42
 
29
- This source prepares SDK version **0.3.0**, adding seller Work Ticket methods and public buyer status reads. The published npm version remains **0.2.0** until 0.3.0 is separately released. See the [npm package page](https://www.npmjs.com/package/402mob-merchant-sdk) or browse the [source repository](https://github.com/gggggggg424242/402mob-merchant-sdk).
43
+ Version **0.4.0** retains enrollment, resource resolution and seller Work Ticket methods. Enrollment and credential rotation require standard 65-byte EIP-191 signatures; malformed or compact 64-byte signatures are rejected before submission. See the [npm package](https://www.npmjs.com/package/402mob-merchant-sdk) and [source](https://github.com/gggggggg424242/402mob-merchant-sdk).
30
44
 
31
45
  Requires Node.js 18 or newer. The SDK uses the built-in Fetch API and has no runtime dependencies.
32
46
 
@@ -94,6 +108,44 @@ if (page.items[0]) {
94
108
 
95
109
  `resolveResources` searches provider names, resource titles, and descriptions. It returns only active prepared resources from providers that opted into directory visibility. `getProviderResources(merchantId)` reads a known provider's public profile and resources even when it is not discoverable. `resolveLatestResource(merchantId, offerId)` follows an older offer ID to the current version in its series. These calls return descriptors and checkout paths; they do not execute work, purchase an artifact, sign a payment, or endorse the provider. Use an x402-capable buyer or the documented checkout flow to purchase.
96
110
 
111
+ ## Worker capabilities and discovery (0.4.0)
112
+
113
+ An enrolled merchant can offer fresh work without a prepared artifact. Worker visibility is independent of prepared-resource discovery. Only owner calls require `merchant:manage`; search and public known-ID reads send no API key.
114
+
115
+ ```ts
116
+ const merchant = new MerchantClient({ apiKey: process.env.MOB402_API_KEY });
117
+ const saved = await merchant.setWorkerProfile({
118
+ description: "Scoped repository engineering in our agent runtime.",
119
+ availability: "accepting",
120
+ discoverable: true, // Publish only with the merchant owner's approval.
121
+ capabilities: [{
122
+ id: "repository-review",
123
+ title: "Repository review",
124
+ description: "Find reproducible defects in an agreed revision.",
125
+ inputDescription: "Repository reference, revision and acceptance criteria; arrange access separately.",
126
+ resultDescription: "JSON findings, reproduction steps and checks run. Scope and price require a ticket quote.",
127
+ resultContentTypes: ["application/json"],
128
+ }],
129
+ });
130
+ const settings = await merchant.getMyWorkerProfile(); // Includes hidden/paused settings.
131
+
132
+ const buyer = new MerchantClient();
133
+ const page = await buyer.searchWorkers({
134
+ capability: "repository-review", contentType: "application/json", limit: 20,
135
+ });
136
+ const candidate = page.items[0];
137
+ if (candidate) {
138
+ const current = await buyer.getWorkerProfile(candidate.merchant.id);
139
+ console.log(current.merchant.id, current.workerProfile.revision, current.workTicket);
140
+ }
141
+ ```
142
+
143
+ `setWorkerProfile` is a **full replacement**: description, capabilities, availability and discoverable are all required. Do not pass the entire GET response back as input; read-only or unknown fields are rejected. Unchanged normalized content preserves revision/timestamps. Text is trimmed, IDs/formats must be unique, and the whole JSON request must fit within 64 KiB. See the [worker guide](https://evidence.contact/worker-discovery.md) for field limits.
144
+
145
+ `searchWorkers` returns only opted-in, accepting active merchants. Capability ID and result format must match the same capability. Follow numeric `nextCursor` until null and deduplicate merchant IDs; declarations can change between offset pages. `getWorkerProfile` requires opt-in and can return paused profiles; owner reads include hidden settings. Pausing/unpublishing affects discovery, not existing or independently addressed tickets.
146
+
147
+ A declaration is not verified expertise or a price. The buyer must independently choose a merchant/capability, sign a Work Ticket grant, review/sign the exact quote and separately authorize payment. This SDK exposes the discovery handoff and seller ticket methods; buyer grant creation, quote acceptance and payment use the documented HTTP/MCP protocol. A server without the new routes can return 404; the SDK preserves that error and never falls back to publishing an artifact or paying.
148
+
97
149
  ## Seller Work Tickets
98
150
 
99
151
  Merchant agents with the `merchant:work` scope can inspect their private inbox, claim a buyer-addressed task, submit a fixed quote, and deliver a result after the buyer's x402 payment is confirmed. The SDK methods are `listWorkTickets`, `claimWorkTicket`, `quoteWorkTicket`, `getWorkTicket`, and `submitWorkTicketResult`.
@@ -142,6 +194,13 @@ This action moves no USDC and grants no allowance, but it costs Base ETH for gas
142
194
 
143
195
  ### Public methods
144
196
 
197
+ - `searchWorkers({ query?, capability?, contentType?, limit?, cursor? })` — declared accepting workers; no prepared artifact needed.
198
+ - `getWorkerProfile(merchantId)` — public opted-in profile, including paused availability.
199
+ - `createBuyerRequestChallenge(input, idempotencyKey)` — prepare free public demand for the buyer's EIP-191 signature.
200
+ - `completeBuyerRequest(input, idempotencyKey)` — publish/replay the saved signed body using the original key.
201
+ - `listBuyerRequests({ query?, limit?, cursor? })` and `getBuyerRequest(requestId)`.
202
+ - `createBuyerRequestSelectionChallenge(requestId, { buyer, proposalId })` and `selectBuyerRequestOffer(requestId, { challengeId, signature })` — signed selection, returning a checkout handoff without payment.
203
+ - `getWorkTicketStatus(ticketId)` — public progress, not private input or result bytes.
145
204
  - `resolveResources({ query?, limit?, cursor? })` — search public, opted-in prepared resources without an API key.
146
205
  - `getProviderResources(merchantId)` — read one known provider's public profile and published resources.
147
206
  - `resolveLatestResource(merchantId, offerId)` — resolve an older offer ID to the current published version.
@@ -156,12 +215,18 @@ This action moves no USDC and grants no allowance, but it costs Base ETH for gas
156
215
  - `rotateWithSignature({ wallet, signMessage })`
157
216
  - `rotateWithTransaction({ wallet, submitProof })`
158
217
  - `withApiKey(apiKey)`
218
+
219
+ ### Authenticated merchant methods
220
+
221
+ - `setWorkerProfile(input)` and `getMyWorkerProfile()` — require `merchant:manage`.
222
+ - `proposeBuyerRequestOffer(requestId, { offerId, note })` — an owned active prepared offer; requires `merchant:manage`.
159
223
  - `getMerchant()`
160
224
  - `createOffer(input)`
161
225
  - `setOfferActive(offerId, active)`
162
226
  - `setDiscoverable(discoverable)` — opt the merchant into or out of public offer search. Defaults to hidden.
163
227
  - `listSales(limit?)`
164
228
  - `getSale(operationId)`
229
+ - `listWorkTickets({ states?, limit?, order? })`, `getWorkTicket(ticketId)`, `claimWorkTicket(ticketId, leaseSeconds?)`, `quoteWorkTicket(ticketId, input)` and `submitWorkTicketResult(ticketId, input)` — require `merchant:work`; default inbox order is oldest.
165
230
 
166
231
  Create offer prices as decimal strings (for example `"0.02"`) to avoid floating-point rounding. Prices must be positive, from `0.000001` through `999999.999999` USDC, with at most six fractional digits. Results are prepared JSON or text artifacts, limited by the service to 48 KiB. An offer's price and content are immutable. To publish an update, submit a higher semantic version and the current `supersedesOfferId`; the new version unpublishes its predecessor.
167
232
 
@@ -180,11 +245,14 @@ Create offer prices as decimal strings (for example `"0.02"`) to avoid floating-
180
245
  npm install
181
246
  npm run build
182
247
  npm test
248
+ npm pack --pack-destination /tmp
183
249
  ```
184
250
 
185
- The SDK tests use mocked HTTP responses and do not contact `evidence.contact` or submit wallet transactions.
251
+ To test a local build in another project, install the generated file with `npm install /tmp/402mob-merchant-sdk-0.4.0.tgz`. The CLI's published SDK dependency remains 0.3.1 until a separate CLI release.
252
+
253
+ 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.
186
254
 
187
- See [`examples/enroll-and-publish.ts`](./examples/enroll-and-publish.ts) for an adapter-based seller integration and [`examples/resolve-resources.ts`](./examples/resolve-resources.ts) for public buyer-agent discovery. Both examples import the built package output and are included in the npm tarball.
255
+ 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).
188
256
 
189
257
  MIT licensed. See [LICENSE](./LICENSE).
190
258
 
@@ -201,7 +269,7 @@ Adds `setDiscoverable(discoverable)`, which calls the authenticated `PUT /v1/age
201
269
  The source now rejects unreadable or incomplete successful API responses with `MerchantApiError` code `invalid_response`. Challenge fields and zero-USDC instructions are checked before invoking the wallet callback; enrollment and rotation responses must contain a merchant ID and a nonempty API key. An invalid completion response can mean the server already completed the operation: recover access through credential rotation if its one-time key was lost. This validation is included in published 0.1.2. Integrations using 0.1.1 should validate required fields explicitly. Preserve the original challenge and submitted transaction hash when completion is interrupted; rerunning `enrollWithTransaction` creates a fresh challenge and invokes the wallet callback again. A completion error is not permission to submit another proof transaction automatically.
202
270
 
203
271
 
204
- ## Work Ticket reads and recovery (0.3.0 source)
272
+ ## Work Ticket reads and recovery
205
273
 
206
274
  Buyer agents can call `client.getWorkTicketStatus(ticketId)` without an API key to read state, quote terms, payment references, and result hashes. This public response excludes the buyer wallet, private input, and signed buyer receipt. It does not fetch result bytes or authorize payment.
207
275
 
@@ -211,4 +279,4 @@ The supported states are `open`, `claimed`, `quoted`, `accepted`, `paying`, `pai
211
279
 
212
280
  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.
213
281
 
214
- These methods are prepared in the 0.3.0 source release; publishing the package is a separate step.
282
+ These methods are available from version 0.3.0.
package/dist/index.d.ts CHANGED
@@ -251,6 +251,120 @@ export interface MerchantClientOptions {
251
251
  /** Defaults to globalThis.fetch. */
252
252
  fetch?: typeof globalThis.fetch;
253
253
  }
254
+ export interface WorkerCapability {
255
+ /** Exact routing hint: 3–64 lowercase letters, digits or hyphens, alphanumeric ends. */
256
+ id: string;
257
+ title: string;
258
+ description: string;
259
+ inputDescription: string;
260
+ resultDescription: string;
261
+ resultContentTypes: ArtifactContentType[];
262
+ }
263
+ /** Full replacement. Discovery flags do not cancel or restrict independently addressed tickets. */
264
+ export interface WorkerProfileInput {
265
+ description: string;
266
+ capabilities: WorkerCapability[];
267
+ availability: "accepting" | "paused";
268
+ discoverable: boolean;
269
+ }
270
+ export interface WorkerProfile extends WorkerProfileInput {
271
+ merchantId: string;
272
+ revision: number;
273
+ createdAt: string;
274
+ updatedAt: string;
275
+ }
276
+ export interface WorkerProfileView {
277
+ merchant: {
278
+ id: string;
279
+ name: string;
280
+ payoutAddress: EvmAddress;
281
+ };
282
+ workerProfile: WorkerProfile;
283
+ workTicket: {
284
+ method: "POST";
285
+ challengePath: "/v1/work-tickets/challenges";
286
+ merchantId: string;
287
+ guide: "/work-tickets.md";
288
+ paymentRequiredToCreate: false;
289
+ quoteRequired: true;
290
+ };
291
+ }
292
+ export interface WorkerSearchInput {
293
+ /** Case-insensitive substring, at most 80 characters after trimming. */
294
+ query?: string;
295
+ /** Exact declared capability; the same capability must satisfy contentType. */
296
+ capability?: string;
297
+ contentType?: ArtifactContentType;
298
+ /** 1–50, default 20. */
299
+ limit?: number;
300
+ /** Numeric offset, default 0; follow nextCursor and deduplicate merchant IDs. */
301
+ cursor?: number;
302
+ }
303
+ export interface WorkerDirectoryPage {
304
+ items: WorkerProfileView[];
305
+ nextCursor: number | null;
306
+ }
307
+ export interface BuyerRequestInput {
308
+ buyer: EvmAddress;
309
+ title: string;
310
+ description: string;
311
+ maxPriceUSDC: string;
312
+ contentType: ArtifactContentType;
313
+ expiresInSeconds: number;
314
+ }
315
+ export interface BuyerRequestChallenge {
316
+ challengeId: string;
317
+ message: string;
318
+ expiresAt: string;
319
+ requestExpiresAt: string;
320
+ policyHash: string;
321
+ }
322
+ export interface PublicBuyerRequest {
323
+ id: string;
324
+ title: string;
325
+ description: string;
326
+ maxPriceAtomic: string;
327
+ contentType: ArtifactContentType;
328
+ state: "open" | "selected" | "expired";
329
+ createdAt: string;
330
+ expiresAt: string;
331
+ selectedProposalId: string | null;
332
+ }
333
+ export interface BuyerRequestProposal {
334
+ id: string;
335
+ requestId: string;
336
+ merchantId: string;
337
+ offerId: string;
338
+ note: string;
339
+ title: string;
340
+ version: string;
341
+ amountAtomic: string;
342
+ contentType: ArtifactContentType;
343
+ payoutAddress: EvmAddress;
344
+ termsHash: string;
345
+ createdAt: string;
346
+ }
347
+ export interface BuyerCheckoutHandoff {
348
+ merchantId: string;
349
+ offerId: string;
350
+ amountAtomic: string;
351
+ payTo: EvmAddress;
352
+ network: "eip155:8453";
353
+ asset: EvmAddress;
354
+ contentType: ArtifactContentType;
355
+ method: "POST";
356
+ path: string;
357
+ body: Record<string, never>;
358
+ directTransferChallenge: string;
359
+ mcpTool: "buy_prepared_artifact";
360
+ paymentRequired: true;
361
+ paymentStatus: "not_observed";
362
+ }
363
+ export interface BuyerRequestDetail {
364
+ request: PublicBuyerRequest;
365
+ proposals: BuyerRequestProposal[];
366
+ checkoutHandoff: BuyerCheckoutHandoff | null;
367
+ }
254
368
  export interface CreateEnrollmentInput {
255
369
  wallet: EvmAddress;
256
370
  name: string;
@@ -302,6 +416,14 @@ export declare class MerchantClient {
302
416
  constructor(options?: MerchantClientOptions);
303
417
  /** Return a new client with the supplied key; no key is written to disk or logged. */
304
418
  withApiKey(apiKey: string): MerchantClient;
419
+ /** Full replacement, with separate worker-discovery opt-in. No artifact or wallet action. */
420
+ setWorkerProfile(input: WorkerProfileInput): Promise<WorkerProfileView>;
421
+ /** Requires merchant:manage; includes this merchant's unpublished or paused settings. */
422
+ getMyWorkerProfile(): Promise<WorkerProfileView>;
423
+ /** Public known-ID read. Opt-in is required; a paused profile can remain visible. */
424
+ getWorkerProfile(merchantId: string): Promise<WorkerProfileView>;
425
+ /** Public opted-in, accepting worker declarations; no key, quote or payment authorization. */
426
+ searchWorkers(input?: WorkerSearchInput): Promise<WorkerDirectoryPage>;
305
427
  createEnrollmentChallenge(input: CreateEnrollmentInput): Promise<EnrollmentChallenge>;
306
428
  completeEnrollmentWithSignature(input: CompleteSignatureInput): Promise<EnrollmentResult>;
307
429
  completeEnrollmentWithTransaction(input: CompleteTransactionInput): Promise<EnrollmentResult>;
@@ -338,6 +460,7 @@ export declare class MerchantClient {
338
460
  listWorkTickets(input?: {
339
461
  states?: WorkTicketState[];
340
462
  limit?: number;
463
+ order?: "oldest" | "latest";
341
464
  }): Promise<WorkTicketInbox>;
342
465
  /** Public progress and recovery metadata; no API key, private input, or result bytes. */
343
466
  getWorkTicketStatus(ticketId: string): Promise<{
@@ -362,6 +485,42 @@ export declare class MerchantClient {
362
485
  buyerResultChallenge: string;
363
486
  retentionDays: number;
364
487
  }>;
488
+ /** Free public demand. Caller persists the key, signs the returned message and completes separately. */
489
+ createBuyerRequestChallenge(input: BuyerRequestInput, idempotencyKey: string): Promise<BuyerRequestChallenge>;
490
+ completeBuyerRequest(input: BuyerRequestInput & {
491
+ challengeId: string;
492
+ signature: EvmSignature;
493
+ }, idempotencyKey: string): Promise<BuyerRequestDetail & {
494
+ replay: boolean;
495
+ }>;
496
+ listBuyerRequests(input?: ResolveAgentResourcesInput): Promise<{
497
+ items: PublicBuyerRequest[];
498
+ nextCursor: number | null;
499
+ }>;
500
+ getBuyerRequest(requestId: string): Promise<BuyerRequestDetail>;
501
+ proposeBuyerRequestOffer(requestId: string, input: {
502
+ offerId: string;
503
+ note: string;
504
+ }): Promise<{
505
+ proposal: BuyerRequestProposal;
506
+ replay: boolean;
507
+ }>;
508
+ createBuyerRequestSelectionChallenge(requestId: string, input: {
509
+ buyer: EvmAddress;
510
+ proposalId: string;
511
+ }): Promise<{
512
+ challengeId: string;
513
+ message: string;
514
+ expiresAt: string;
515
+ proposal: BuyerRequestProposal;
516
+ }>;
517
+ /** Return a checkout handoff; this method never authorizes or submits payment. */
518
+ selectBuyerRequestOffer(requestId: string, input: {
519
+ challengeId: string;
520
+ signature: EvmSignature;
521
+ }): Promise<BuyerRequestDetail & {
522
+ replay: boolean;
523
+ }>;
365
524
  private request;
366
525
  }
367
526
  export {};
package/dist/index.js CHANGED
@@ -1,3 +1,14 @@
1
+ function requireEip191Signature(signature) {
2
+ if (!/^0x[0-9a-fA-F]{130}$/.test(signature)) {
3
+ throw new TypeError("signature must be a 65-byte EIP-191 signature encoded as 0x-prefixed hex.");
4
+ }
5
+ return signature;
6
+ }
7
+ function buyerRequestKey(key) {
8
+ if (!/^[A-Za-z0-9_-]{16,128}$/.test(key))
9
+ throw new TypeError("Persist an idempotency key of 16–128 letters, digits, underscores or hyphens.");
10
+ return key;
11
+ }
1
12
  export class MerchantApiError extends Error {
2
13
  status;
3
14
  code;
@@ -40,11 +51,46 @@ export class MerchantClient {
40
51
  withApiKey(apiKey) {
41
52
  return new MerchantClient({ baseUrl: this.baseUrl, apiKey, fetch: this.fetcher });
42
53
  }
54
+ /** Full replacement, with separate worker-discovery opt-in. No artifact or wallet action. */
55
+ setWorkerProfile(input) {
56
+ if (!isWorkerProfileInput(input) || !onlyKeys(input, ["description", "capabilities", "availability", "discoverable"]) || !input.capabilities.every(cap => onlyKeys(cap, ["id", "title", "description", "inputDescription", "resultDescription", "resultContentTypes"])))
57
+ throw new TypeError("Provide the worker profile PUT body: bounded descriptions, unique capability IDs/formats, availability and explicit discoverable. Unknown or read-only fields are not allowed.");
58
+ const body = normalizedWorkerProfile(input);
59
+ if (new TextEncoder().encode(JSON.stringify(body)).byteLength > 64 * 1024)
60
+ throw new TypeError("The whole worker profile JSON request must fit within 64 KiB.");
61
+ return this.request("PUT", "/v1/agent/merchant/worker-profile", body, true);
62
+ }
63
+ /** Requires merchant:manage; includes this merchant's unpublished or paused settings. */
64
+ getMyWorkerProfile() { return this.request("GET", "/v1/agent/merchant/worker-profile", undefined, true); }
65
+ /** Public known-ID read. Opt-in is required; a paused profile can remain visible. */
66
+ getWorkerProfile(merchantId) {
67
+ if (!uuid(merchantId))
68
+ throw new TypeError("merchantId must be a UUID.");
69
+ return this.request("GET", `/v1/merchants/${merchantId}/worker-profile`);
70
+ }
71
+ /** Public opted-in, accepting worker declarations; no key, quote or payment authorization. */
72
+ searchWorkers(input = {}) {
73
+ if (input.query !== undefined && (typeof input.query !== "string" || input.query.trim().length > 80))
74
+ throw new TypeError("query must be at most 80 characters.");
75
+ if (input.capability !== undefined && !validWorkerCapabilityId(input.capability))
76
+ throw new TypeError("capability must be a lowercase slug of 3–64 characters.");
77
+ if (input.contentType !== undefined && !artifactType(input.contentType))
78
+ throw new TypeError("Unsupported worker result format.");
79
+ const limit = input.limit ?? 20, cursor = input.cursor ?? 0;
80
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50 || !Number.isSafeInteger(cursor) || cursor < 0 || cursor > 100000000)
81
+ throw new TypeError("Use limit 1–50 and a nonnegative numeric cursor.");
82
+ const query = new URLSearchParams({ q: input.query?.trim() ?? "", limit: String(limit), cursor: String(cursor) });
83
+ if (input.capability)
84
+ query.set("capability", input.capability);
85
+ if (input.contentType)
86
+ query.set("contentType", input.contentType);
87
+ return this.request("GET", "/v1/worker-directory?" + query);
88
+ }
43
89
  createEnrollmentChallenge(input) {
44
90
  return this.request("POST", "/v1/merchants/enrollment-challenges", input);
45
91
  }
46
92
  completeEnrollmentWithSignature(input) {
47
- return this.request("POST", "/v1/merchants/enrollments", input);
93
+ return this.request("POST", "/v1/merchants/enrollments", { ...input, signature: requireEip191Signature(input.signature) });
48
94
  }
49
95
  completeEnrollmentWithTransaction(input) {
50
96
  return this.request("POST", "/v1/merchants/enrollments/transaction", input);
@@ -52,7 +98,7 @@ export class MerchantClient {
52
98
  /** Request a challenge, ask the caller's wallet adapter to sign its exact message, then enroll. */
53
99
  async enrollWithSignature(input) {
54
100
  const challenge = await this.createEnrollmentChallenge({ wallet: input.wallet, name: input.name });
55
- const signature = await input.signMessage(challenge.message);
101
+ const signature = requireEip191Signature(await input.signMessage(challenge.message));
56
102
  return this.completeEnrollmentWithSignature({
57
103
  wallet: input.wallet,
58
104
  name: input.name,
@@ -75,14 +121,14 @@ export class MerchantClient {
75
121
  return this.request("POST", "/v1/merchants/credential-challenges", { wallet });
76
122
  }
77
123
  rotateCredentialWithSignature(wallet, challengeId, signature) {
78
- return this.request("POST", "/v1/merchants/credentials/rotate", { wallet, challengeId, signature });
124
+ return this.request("POST", "/v1/merchants/credentials/rotate", { wallet, challengeId, signature: requireEip191Signature(signature) });
79
125
  }
80
126
  rotateCredentialWithTransaction(input) {
81
127
  return this.request("POST", "/v1/merchants/credentials/rotate-transaction", input);
82
128
  }
83
129
  async rotateWithSignature(input) {
84
130
  const challenge = await this.createCredentialRotationChallenge(input.wallet);
85
- const signature = await input.signMessage(challenge.message);
131
+ const signature = requireEip191Signature(await input.signMessage(challenge.message));
86
132
  return this.rotateCredentialWithSignature(input.wallet, challenge.challengeId, signature);
87
133
  }
88
134
  async rotateWithTransaction(input) {
@@ -165,6 +211,11 @@ export class MerchantClient {
165
211
  if (!Number.isInteger(limit) || limit < 1 || limit > 50)
166
212
  throw new TypeError("limit must be an integer from 1 through 50.");
167
213
  const query = new URLSearchParams({ limit: String(limit) });
214
+ if (input.order !== undefined) {
215
+ if (input.order !== "oldest" && input.order !== "latest")
216
+ throw new TypeError("order must be oldest or latest.");
217
+ query.set("order", input.order);
218
+ }
168
219
  if (input.states !== undefined) {
169
220
  if (!Array.isArray(input.states) || input.states.length === 0 || input.states.length > 10 || !input.states.every(state => workTicketStates.includes(state))) {
170
221
  throw new TypeError("states must contain 1–10 supported Work Ticket states.");
@@ -197,11 +248,37 @@ export class MerchantClient {
197
248
  submitWorkTicketResult(ticketId, input) {
198
249
  return this.request("POST", `/v1/agent/work-tickets/${checkedWorkTicketId(ticketId)}/results`, input, true);
199
250
  }
200
- async request(method, path, body, authenticated = false) {
251
+ /** Free public demand. Caller persists the key, signs the returned message and completes separately. */
252
+ createBuyerRequestChallenge(input, idempotencyKey) {
253
+ return this.request("POST", "/v1/buyer-requests/challenges", input, false, { "Idempotency-Key": buyerRequestKey(idempotencyKey) });
254
+ }
255
+ completeBuyerRequest(input, idempotencyKey) {
256
+ return this.request("POST", "/v1/buyer-requests", { ...input, signature: requireEip191Signature(input.signature) }, false, { "Idempotency-Key": buyerRequestKey(idempotencyKey) });
257
+ }
258
+ listBuyerRequests(input = {}) {
259
+ const query = input.query ?? "", limit = input.limit ?? 20, cursor = input.cursor ?? 0;
260
+ if (typeof query !== "string" || query.trim().length > 80 || !Number.isInteger(limit) || limit < 1 || limit > 50 || !Number.isSafeInteger(cursor) || cursor < 0 || cursor > 100000000)
261
+ throw new TypeError("Use a query up to 80 characters, limit 1–50 and cursor 0–100000000.");
262
+ return this.request("GET", "/v1/buyer-requests?" + new URLSearchParams({ q: query.trim(), limit: String(limit), cursor: String(cursor) }));
263
+ }
264
+ getBuyerRequest(requestId) {
265
+ return this.request("GET", `/v1/buyer-requests/${checkedWorkTicketId(requestId)}`);
266
+ }
267
+ proposeBuyerRequestOffer(requestId, input) {
268
+ return this.request("POST", `/v1/agent/buyer-requests/${checkedWorkTicketId(requestId)}/proposals`, input, true);
269
+ }
270
+ createBuyerRequestSelectionChallenge(requestId, input) {
271
+ return this.request("POST", `/v1/buyer-requests/${checkedWorkTicketId(requestId)}/selection-challenges`, input);
272
+ }
273
+ /** Return a checkout handoff; this method never authorizes or submits payment. */
274
+ selectBuyerRequestOffer(requestId, input) {
275
+ return this.request("POST", `/v1/buyer-requests/${checkedWorkTicketId(requestId)}/selections`, { ...input, signature: requireEip191Signature(input.signature) });
276
+ }
277
+ async request(method, path, body, authenticated = false, extraHeaders = {}) {
201
278
  if (authenticated && !this.#apiKey) {
202
279
  throw new Error("This request requires a merchant API key. Use client.withApiKey(apiKey).");
203
280
  }
204
- const headers = { Accept: "application/json" };
281
+ const headers = { Accept: "application/json", ...extraHeaders };
205
282
  if (body !== undefined)
206
283
  headers["Content-Type"] = "application/json";
207
284
  if (authenticated && this.#apiKey)
@@ -228,6 +305,25 @@ export class MerchantClient {
228
305
  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.");
229
306
  if (!isRecord(payload))
230
307
  throw invalidResponse();
308
+ if (path.startsWith("/v1/worker-directory?")) {
309
+ 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
+ throw invalidResponse();
311
+ const params = new URL(path, "https://schema.invalid").searchParams, capability = params.get("capability"), type = params.get("contentType"), limit = Number(params.get("limit")), cursor = Number(params.get("cursor"));
312
+ const items = payload.items;
313
+ if (items.length > limit || new Set(items.map(item => item.merchant.id)).size !== items.length || payload.nextCursor !== null && (items.length !== limit || Number(payload.nextCursor) !== cursor + limit) || !items.every(item => item.workerProfile.capabilities.some(cap => (!capability || cap.id === capability) && (!type || cap.resultContentTypes.includes(type)))))
314
+ throw invalidResponse();
315
+ }
316
+ if (path.endsWith("/worker-profile")) {
317
+ if (!isWorkerProfileView(payload))
318
+ throw invalidResponse();
319
+ const requested = path.match(/^\/v1\/merchants\/([^/]+)\/worker-profile$/)?.[1];
320
+ if (requested && (payload.merchant.id !== requested || !payload.workerProfile.discoverable))
321
+ throw invalidResponse();
322
+ if (method === "PUT" && JSON.stringify(normalizedWorkerProfile(payload.workerProfile)) !== JSON.stringify(body))
323
+ throw invalidResponse();
324
+ }
325
+ if ((path.startsWith("/v1/buyer-requests") || path.startsWith("/v1/agent/buyer-requests/")) && !isBuyerRequestResponse(method, path, payload, body))
326
+ throw invalidResponse();
231
327
  if (path === "/v1/merchants/enrollment-challenges" || path === "/v1/merchants/credential-challenges") {
232
328
  const proof = payload.transactionProof;
233
329
  if (!nonempty(payload.challengeId) || !nonempty(payload.message) || !nonempty(payload.expiresAt) ||
@@ -252,6 +348,96 @@ function isRecord(value) {
252
348
  }
253
349
  function nonempty(value) { return typeof value === "string" && value.trim().length > 0; }
254
350
  function address(value) { return typeof value === "string" && /^0x[0-9a-fA-F]{40}$/.test(value); }
351
+ const baseUsdc = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
352
+ function uuid(value) { return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); }
353
+ function timestamp(value) { return typeof value === "string" && Number.isFinite(Date.parse(value)); }
354
+ function digest(value) { return typeof value === "string" && /^[0-9a-f]{64}$/.test(value); }
355
+ function atomicAmount(value) { return typeof value === "string" && /^[1-9][0-9]{0,11}$/.test(value); }
356
+ function publicText(value, max) { return typeof value === "string" && value.trim().length > 0 && Array.from(value).length <= max; }
357
+ function artifactType(value) { return value === "application/json" || value === "text/plain; charset=utf-8"; }
358
+ function validWorkerCapabilityId(value) { return typeof value === "string" && /^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/.test(value); }
359
+ function onlyKeys(value, keys) { return Object.keys(value).every(key => keys.includes(key)); }
360
+ function boundedWorkerText(value, max) { return typeof value === "string" && value.trim().length > 0 && Array.from(value.trim()).length <= max; }
361
+ function normalizedWorkerProfile(value) {
362
+ return { description: value.description.trim(), capabilities: value.capabilities.map(cap => ({ id: cap.id, title: cap.title.trim(), description: cap.description.trim(), inputDescription: cap.inputDescription.trim(), resultDescription: cap.resultDescription.trim(), resultContentTypes: [...cap.resultContentTypes] })), availability: value.availability, discoverable: value.discoverable };
363
+ }
364
+ function isWorkerProfileInput(value) {
365
+ if (!isRecord(value) || !boundedWorkerText(value.description, 1000) || (value.availability !== "accepting" && value.availability !== "paused") || typeof value.discoverable !== "boolean" || !Array.isArray(value.capabilities) || !value.capabilities.length || value.capabilities.length > 10)
366
+ return false;
367
+ if (new Set(value.capabilities.map(c => isRecord(c) ? c.id : null)).size !== value.capabilities.length)
368
+ return false;
369
+ return value.capabilities.every(c => isRecord(c) && validWorkerCapabilityId(c.id) && boundedWorkerText(c.title, 100) && boundedWorkerText(c.description, 1000) && boundedWorkerText(c.inputDescription, 1000) && boundedWorkerText(c.resultDescription, 1000) && Array.isArray(c.resultContentTypes) && c.resultContentTypes.length >= 1 && c.resultContentTypes.length <= 2 && new Set(c.resultContentTypes).size === c.resultContentTypes.length && c.resultContentTypes.every(artifactType));
370
+ }
371
+ function isWorkerProfileView(value) {
372
+ if (!isRecord(value) || !isRecord(value.merchant) || !uuid(value.merchant.id) || !publicText(value.merchant.name, 100) || !address(value.merchant.payoutAddress) || !isWorkerProfileInput(value.workerProfile))
373
+ return false;
374
+ const profile = value.workerProfile;
375
+ return profile.merchantId === value.merchant.id && Number.isSafeInteger(profile.revision) && profile.revision > 0 && timestamp(profile.createdAt) && timestamp(profile.updatedAt) && isRecord(value.workTicket) && value.workTicket.method === "POST" && value.workTicket.challengePath === "/v1/work-tickets/challenges" && value.workTicket.merchantId === value.merchant.id && value.workTicket.guide === "/work-tickets.md" && value.workTicket.paymentRequiredToCreate === false && value.workTicket.quoteRequired === true;
376
+ }
377
+ function usdcAtomic(value) {
378
+ if (typeof value !== "string")
379
+ return;
380
+ const parts = /^(0|[1-9][0-9]{0,5})(?:\.([0-9]{1,6}))?$/.exec(value);
381
+ return parts ? BigInt(parts[1] + (parts[2] ?? "").padEnd(6, "0")).toString() : undefined;
382
+ }
383
+ function isPublicBuyerRequest(value) {
384
+ if (!isRecord(value))
385
+ return false;
386
+ return uuid(value.id) && publicText(value.title, 100) && publicText(value.description, 2000) && atomicAmount(value.maxPriceAtomic) && artifactType(value.contentType) &&
387
+ ["open", "selected", "expired"].includes(String(value.state)) && timestamp(value.createdAt) && timestamp(value.expiresAt) &&
388
+ (value.state === "selected" ? uuid(value.selectedProposalId) : value.selectedProposalId === null);
389
+ }
390
+ function isBuyerRequestProposal(value) {
391
+ if (!isRecord(value))
392
+ return false;
393
+ return uuid(value.id) && uuid(value.requestId) && uuid(value.merchantId) && uuid(value.offerId) && publicText(value.note, 300) && publicText(value.title, 100) &&
394
+ typeof value.version === "string" && /^(0|[1-9][0-9]{0,8})\.(0|[1-9][0-9]{0,8})\.(0|[1-9][0-9]{0,8})$/.test(value.version) &&
395
+ atomicAmount(value.amountAtomic) && artifactType(value.contentType) && address(value.payoutAddress) && digest(value.termsHash) && timestamp(value.createdAt);
396
+ }
397
+ function isBuyerCheckoutHandoff(value, proposal) {
398
+ if (!isRecord(value))
399
+ return false;
400
+ const path = `/v1/merchants/${proposal.merchantId}/offers/${proposal.offerId}/checkout`;
401
+ return value.merchantId === proposal.merchantId && value.offerId === proposal.offerId && value.amountAtomic === proposal.amountAtomic &&
402
+ address(value.payTo) && value.payTo.toLowerCase() === proposal.payoutAddress.toLowerCase() && value.contentType === proposal.contentType &&
403
+ value.network === "eip155:8453" && typeof value.asset === "string" && value.asset.toLowerCase() === baseUsdc && value.method === "POST" && value.path === path &&
404
+ value.directTransferChallenge === path + "/transfer-challenges" && isRecord(value.body) && Object.keys(value.body).length === 0 &&
405
+ value.mcpTool === "buy_prepared_artifact" && value.paymentRequired === true && value.paymentStatus === "not_observed";
406
+ }
407
+ function isBuyerRequestDetail(value, expectedId) {
408
+ if (!isRecord(value) || !isPublicBuyerRequest(value.request) || !Array.isArray(value.proposals) || value.proposals.length > 20 || !value.proposals.every(isBuyerRequestProposal))
409
+ return false;
410
+ const request = value.request, proposals = value.proposals;
411
+ if (expectedId && request.id !== expectedId)
412
+ return false;
413
+ if (new Set(proposals.map(p => p.id)).size !== proposals.length || new Set(proposals.map(p => p.merchantId)).size !== proposals.length ||
414
+ proposals.some(p => p.requestId !== request.id || p.contentType !== request.contentType || BigInt(p.amountAtomic) > BigInt(request.maxPriceAtomic)))
415
+ return false;
416
+ if (request.state !== "selected")
417
+ return value.checkoutHandoff === null;
418
+ const selected = proposals.find(p => p.id === request.selectedProposalId);
419
+ return Boolean(selected && isBuyerCheckoutHandoff(value.checkoutHandoff, selected));
420
+ }
421
+ function isBuyerRequestResponse(method, path, value, body) {
422
+ const args = isRecord(body) ? body : {};
423
+ const challenge = () => uuid(value.challengeId) && nonempty(value.message) && timestamp(value.expiresAt);
424
+ const id = path.match(/^\/v1\/(?:agent\/)?buyer-requests\/([^/?]+)/)?.[1];
425
+ if (path === "/v1/buyer-requests/challenges")
426
+ return challenge() && timestamp(value.requestExpiresAt) && digest(value.policyHash) && Date.parse(value.expiresAt) <= Date.parse(value.requestExpiresAt);
427
+ if (path === "/v1/buyer-requests" && method === "POST")
428
+ return isBuyerRequestDetail(value) && typeof value.replay === "boolean" &&
429
+ value.request.title === (typeof args.title === "string" ? args.title.trim() : undefined) && value.request.description === (typeof args.description === "string" ? args.description.trim() : undefined) && value.request.contentType === args.contentType && value.request.maxPriceAtomic === usdcAtomic(args.maxPriceUSDC);
430
+ if (path.startsWith("/v1/buyer-requests?") || (path === "/v1/buyer-requests" && method === "GET"))
431
+ return Array.isArray(value.items) && value.items.length <= 50 && value.items.every(isPublicBuyerRequest) &&
432
+ (value.nextCursor === null || (Number.isSafeInteger(value.nextCursor) && value.nextCursor >= 0));
433
+ if (path.endsWith("/selection-challenges"))
434
+ return challenge() && isBuyerRequestProposal(value.proposal) && value.proposal.requestId === id && value.proposal.id === args.proposalId;
435
+ if (path.endsWith("/proposals"))
436
+ return isBuyerRequestProposal(value.proposal) && value.proposal.requestId === id && value.proposal.offerId === args.offerId && value.proposal.note === (typeof args.note === "string" ? args.note.trim() : undefined) && typeof value.replay === "boolean";
437
+ if (path.endsWith("/selections"))
438
+ return isBuyerRequestDetail(value, id) && value.request.state === "selected" && typeof value.replay === "boolean";
439
+ return method === "GET" && isBuyerRequestDetail(value, id);
440
+ }
255
441
  function isAgentResource(value) {
256
442
  if (!isRecord(value))
257
443
  return false;
@@ -0,0 +1,25 @@
1
+ // SDK 0.4.0 example. This stops before payment.
2
+ // Buyer and seller run independently; exchange only the public request/proposal IDs.
3
+ import { MerchantClient, type BuyerRequestInput, type EvmSignature } from "../dist/index.js";
4
+
5
+ export async function publishDemand(client:MerchantClient,input:BuyerRequestInput,key:string,sign:(message:string)=>Promise<EvmSignature>,persist:(value:unknown)=>Promise<void>) {
6
+ const challenge=await client.createBuyerRequestChallenge(input,key);
7
+ // The caller must inspect origin, public text, budget, format and expiry first.
8
+ const completion={...input,challengeId:challenge.challengeId,signature:await sign(challenge.message)};
9
+ await persist({key,completion}); // Save BEFORE dispatch; replay exactly after interruption.
10
+ return client.completeBuyerRequest(completion,key);
11
+ }
12
+
13
+ export async function proposePreparedOffer(merchant:MerchantClient,requestId:string,offerId:string) {
14
+ return merchant.proposeBuyerRequestOffer(requestId,{offerId,note:"Prepared artifact matching the public request."});
15
+ }
16
+
17
+ export async function selectForCheckout(buyer:MerchantClient,requestId:string,wallet:`0x${string}`,proposalId:string,sign:(message:string)=>Promise<EvmSignature>,persist:(value:unknown)=>Promise<void>) {
18
+ const challenge=await buyer.createBuyerRequestSelectionChallenge(requestId,{buyer:wallet,proposalId});
19
+ // Review the exact offer, price, payout and version before signing.
20
+ const selection={challengeId:challenge.challengeId,signature:await sign(challenge.message)};
21
+ await persist({requestId,selection});
22
+ const selected=await buyer.selectBuyerRequestOffer(requestId,selection);
23
+ // Separate payment authorization/journal is required to use this handoff.
24
+ return selected.checkoutHandoff;
25
+ }
@@ -0,0 +1,43 @@
1
+ import {
2
+ MerchantClient,
3
+ type WorkerProfileInput,
4
+ type WorkerProfileView,
5
+ type ArtifactContentType,
6
+ } from "../dist/index.js";
7
+
8
+ /** The caller approves capabilities, availability and public visibility before this full replacement. */
9
+ export async function publishWorkerProfile(merchant: MerchantClient, input: WorkerProfileInput) {
10
+ const saved = await merchant.setWorkerProfile(input);
11
+ return { saved, current: await merchant.getMyWorkerProfile() };
12
+ }
13
+
14
+ /** Read-only: inspect declarations, then use the Work Ticket protocol with a separate buyer signer. */
15
+ export async function findWorkers(
16
+ buyer: MerchantClient,
17
+ capability: string,
18
+ contentType: ArtifactContentType = "application/json",
19
+ ): Promise<WorkerProfileView[]> {
20
+ const seen = new Map<string, WorkerProfileView>();
21
+ let cursor: number | null = 0;
22
+ // Bounded pass, since concurrent declaration changes can shift offset pages.
23
+ for (let page = 0; page < 4 && cursor !== null; page++) {
24
+ const result = await buyer.searchWorkers({ capability, contentType, limit: 50, cursor });
25
+ for (const worker of result.items) seen.set(worker.merchant.id, worker);
26
+ cursor = result.nextCursor;
27
+ }
28
+ return [...seen.values()];
29
+ }
30
+
31
+ export const repositoryReview: WorkerProfileInput = {
32
+ description: "Bounded repository review in our agent runtime.",
33
+ availability: "accepting",
34
+ discoverable: false, // Explicit opt-in belongs to the merchant owner.
35
+ capabilities: [{
36
+ id: "repository-review",
37
+ title: "Repository review",
38
+ description: "Find reproducible defects within an agreed revision and scope.",
39
+ inputDescription: "Repository reference, revision and acceptance criteria; arrange access separately.",
40
+ resultDescription: "JSON findings, reproduction steps and checks run. Price and scope require a ticket quote.",
41
+ resultContentTypes: ["application/json"],
42
+ }],
43
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "402mob-merchant-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "TypeScript client for resolving agent resources and enrolling and operating 402Mob merchants.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -15,6 +15,7 @@
15
15
  "dist",
16
16
  "examples",
17
17
  "README.md",
18
+ "CHANGELOG.md",
18
19
  "LICENSE"
19
20
  ],
20
21
  "main": "./dist/index.js",
@@ -27,7 +28,8 @@
27
28
  },
28
29
  "scripts": {
29
30
  "build": "tsc -p tsconfig.json",
30
- "test": "npm run build && node --test tests/*.test.mjs",
31
+ "test": "npm run build && npm run typecheck:examples && node --test tests/*.test.mjs",
32
+ "typecheck:examples": "tsc -p tsconfig.examples.json",
31
33
  "prepare": "npm run build"
32
34
  },
33
35
  "devDependencies": {