402mob-merchant-sdk 0.1.0 → 0.1.2

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.
Files changed (3) hide show
  1. package/README.md +17 -1
  2. package/dist/index.js +23 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,6 +6,18 @@ It wraps the existing HTTP merchant API. It does not custody keys, sign for wall
6
6
 
7
7
  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
8
 
9
+ ### Prefer MCP for a connected agent?
10
+
11
+ The SDK is optional. An agent already connected to `https://evidence.contact/mcp` can call `create_merchant_enrollment_challenge`, sign the exact returned message with its wallet tool, then submit it with `complete_merchant_enrollment_signature`. If its wallet cannot sign messages, use the returned zero-USDC action and complete it with `complete_merchant_enrollment_transaction`; this fallback requires Base ETH gas. The MCP caller and wallet signer are separate runtime tools. Either method returns the merchant API key once, so store it in the agent's secret manager. Use the [copy-ready enrollment prompt](https://evidence.contact/merchant-api.md#agent-enrollment-prompt). Choose this SDK when your Node.js app needs to call the merchant HTTP API directly.
12
+
13
+
14
+ ## Enrollment readiness
15
+
16
+ The runtime must provide a caller that can read the challenge response, one wallet proof method, and secure storage for the returned merchant API key. The SDK supplies HTTP calls; your callbacks supply signing or transaction submission. It does not install MCP tools or unlock a failed wallet tool. Check these capabilities before starting the five-minute challenge.
17
+
18
+ If a message signer is locked, stop retries. `enrollWithTransaction` can use a separate wallet transaction adapter that supports zero-USDC approval/transfer, with authorized Base ETH gas spending. A generic send/swap tool may reject zero amounts; verify support first. The SDK never submits this transaction itself. For MCP 406 errors, missing response bodies, expired challenges, and lost keys, see the [enrollment troubleshooting guide](https://evidence.contact/merchant-api.md#enrollment-troubleshooting).
19
+
20
+
9
21
  ## Install
10
22
 
11
23
  Install from npm:
@@ -14,7 +26,7 @@ Install from npm:
14
26
  npm install 402mob-merchant-sdk
15
27
  ```
16
28
 
17
- The source is available at [github.com/gggggggg424242/402mob-merchant-sdk](https://github.com/gggggggg424242/402mob-merchant-sdk).
29
+ Prepared package version: **0.1.2**. Publish this version before installing it from npm. 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).
18
30
 
19
31
  Requires Node.js 18 or newer. The SDK uses the built-in Fetch API and has no runtime dependencies.
20
32
 
@@ -117,3 +129,7 @@ The SDK tests use mocked HTTP responses and do not contact `evidence.contact` or
117
129
  See [`examples/enroll-and-publish.ts`](./examples/enroll-and-publish.ts) for an adapter-based integration that enrolls a wallet and creates its first offer.
118
130
 
119
131
  MIT licensed. See [LICENSE](./LICENSE).
132
+
133
+ ## Version 0.1.2
134
+
135
+ 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. The npm registry still serves 0.1.1 until 0.1.2 is published. 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.
package/dist/index.js CHANGED
@@ -129,15 +129,38 @@ export class MerchantClient {
129
129
  payload = await response.json();
130
130
  }
131
131
  catch {
132
+ if (response.ok)
133
+ throw new MerchantApiError(response.status, "invalid_response", "402Mob returned an unreadable response. The request may have completed; do not assume it failed.");
132
134
  payload = undefined;
133
135
  }
134
136
  if (!response.ok) {
135
137
  const error = isRecord(payload) ? payload : {};
136
138
  throw new MerchantApiError(response.status, typeof error.error === "string" ? error.error : "http_error", typeof error.message === "string" ? error.message : `402Mob returned HTTP ${response.status}.`);
137
139
  }
140
+ 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.");
141
+ if (!isRecord(payload))
142
+ throw invalidResponse();
143
+ if (path === "/v1/merchants/enrollment-challenges" || path === "/v1/merchants/credential-challenges") {
144
+ const proof = payload.transactionProof;
145
+ if (!nonempty(payload.challengeId) || !nonempty(payload.message) || !nonempty(payload.expiresAt) ||
146
+ !Number.isFinite(Date.parse(payload.expiresAt)) || !isRecord(proof) ||
147
+ proof.network !== "eip155:8453" || !address(proof.asset) || !address(proof.proofAddress) ||
148
+ !isRecord(proof.approve) || !isRecord(proof.send) ||
149
+ typeof proof.approve.spender !== "string" || proof.approve.spender.toLowerCase() !== proof.proofAddress.toLowerCase() ||
150
+ typeof proof.send.recipient !== "string" || proof.send.recipient.toLowerCase() !== proof.proofAddress.toLowerCase() ||
151
+ proof.approve.amountAtomic !== "0" || proof.send.amountAtomic !== "0" ||
152
+ typeof proof.requiredConfirmations !== "string" || !/^[1-9][0-9]*$/.test(proof.requiredConfirmations))
153
+ throw invalidResponse();
154
+ }
155
+ if (["/v1/merchants/enrollments", "/v1/merchants/enrollments/transaction", "/v1/merchants/credentials/rotate", "/v1/merchants/credentials/rotate-transaction"].includes(path)) {
156
+ if (!isRecord(payload.merchant) || !nonempty(payload.merchant.id) || !nonempty(payload.apiKey))
157
+ throw invalidResponse();
158
+ }
138
159
  return payload;
139
160
  }
140
161
  }
141
162
  function isRecord(value) {
142
163
  return typeof value === "object" && value !== null && !Array.isArray(value);
143
164
  }
165
+ function nonempty(value) { return typeof value === "string" && value.trim().length > 0; }
166
+ function address(value) { return typeof value === "string" && /^0x[0-9a-fA-F]{40}$/.test(value); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "402mob-merchant-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Server-side TypeScript client for enrolling and operating 402Mob merchants.",
5
5
  "license": "MIT",
6
6
  "repository": {