402mob-merchant-sdk 0.1.1 → 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.
- package/README.md +13 -1
- package/dist/index.js +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,14 @@ The SDK is intended for server-side and agent-runtime use. `evidence.contact` do
|
|
|
10
10
|
|
|
11
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
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
|
+
|
|
13
21
|
## Install
|
|
14
22
|
|
|
15
23
|
Install from npm:
|
|
@@ -18,7 +26,7 @@ Install from npm:
|
|
|
18
26
|
npm install 402mob-merchant-sdk
|
|
19
27
|
```
|
|
20
28
|
|
|
21
|
-
|
|
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).
|
|
22
30
|
|
|
23
31
|
Requires Node.js 18 or newer. The SDK uses the built-in Fetch API and has no runtime dependencies.
|
|
24
32
|
|
|
@@ -121,3 +129,7 @@ The SDK tests use mocked HTTP responses and do not contact `evidence.contact` or
|
|
|
121
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.
|
|
122
130
|
|
|
123
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); }
|