402mob-merchant-sdk 0.1.1 → 0.1.3
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 +21 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +26 -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
|
-
The current release is **0.1.
|
|
29
|
+
The current published release is **0.1.2**. The working tree prepares **0.1.3** with a typed directory visibility method; publish it from the SDK package directory when ready. 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
|
|
|
@@ -48,6 +56,9 @@ const offer = await merchant.createOffer({
|
|
|
48
56
|
result: { summary: "Prepared by the seller agent" },
|
|
49
57
|
});
|
|
50
58
|
|
|
59
|
+
// Make active offers searchable in the public merchant directory.
|
|
60
|
+
await merchant.setDiscoverable(true);
|
|
61
|
+
|
|
51
62
|
console.log({ offerId: offer.id, checkout: offer.checkout });
|
|
52
63
|
```
|
|
53
64
|
|
|
@@ -94,6 +105,7 @@ This action moves no USDC and grants no allowance, but it costs Base ETH for gas
|
|
|
94
105
|
- `getMerchant()`
|
|
95
106
|
- `createOffer(input)`
|
|
96
107
|
- `setOfferActive(offerId, active)`
|
|
108
|
+
- `setDiscoverable(discoverable)` — opt the merchant into or out of public offer search. Defaults to hidden.
|
|
97
109
|
- `listSales(limit?)`
|
|
98
110
|
- `getSale(operationId)`
|
|
99
111
|
|
|
@@ -121,3 +133,11 @@ The SDK tests use mocked HTTP responses and do not contact `evidence.contact` or
|
|
|
121
133
|
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
134
|
|
|
123
135
|
MIT licensed. See [LICENSE](./LICENSE).
|
|
136
|
+
|
|
137
|
+
## Version 0.1.3 (unpublished)
|
|
138
|
+
|
|
139
|
+
Adds `setDiscoverable(discoverable)`, which calls the authenticated `PUT /v1/agent/merchant/discovery` endpoint. The service controls visibility; enrollment still defaults to hidden. Directory search exposes active offer descriptions and prices to public callers.
|
|
140
|
+
|
|
141
|
+
## Version 0.1.2
|
|
142
|
+
|
|
143
|
+
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.
|
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface MerchantProfile {
|
|
|
33
33
|
name: string;
|
|
34
34
|
payoutAddress: EvmAddress;
|
|
35
35
|
createdAt: string;
|
|
36
|
+
/** Present in authenticated merchant workspace responses; opt-in controls directory search. */
|
|
37
|
+
discoverable?: boolean;
|
|
36
38
|
}
|
|
37
39
|
export interface MerchantAgent {
|
|
38
40
|
id: string;
|
|
@@ -209,6 +211,10 @@ export declare class MerchantClient {
|
|
|
209
211
|
rotateWithSignature(input: CredentialRotationSignatureInput): Promise<CredentialRotationResult>;
|
|
210
212
|
rotateWithTransaction(input: CredentialRotationTransactionInput): Promise<CredentialRotationResult>;
|
|
211
213
|
getMerchant(): Promise<MerchantWorkspace>;
|
|
214
|
+
setDiscoverable(discoverable: boolean): Promise<{
|
|
215
|
+
merchantId: string;
|
|
216
|
+
discoverable: boolean;
|
|
217
|
+
}>;
|
|
212
218
|
createOffer(input: CreateOfferInput): Promise<MerchantOffer>;
|
|
213
219
|
setOfferActive(offerId: string, active: boolean): Promise<MerchantOffer>;
|
|
214
220
|
listSales(limit?: number): Promise<SaleList>;
|
package/dist/index.js
CHANGED
|
@@ -97,6 +97,9 @@ export class MerchantClient {
|
|
|
97
97
|
getMerchant() {
|
|
98
98
|
return this.request("GET", "/v1/agent/merchant", undefined, true);
|
|
99
99
|
}
|
|
100
|
+
setDiscoverable(discoverable) {
|
|
101
|
+
return this.request("PUT", "/v1/agent/merchant/discovery", { discoverable }, true);
|
|
102
|
+
}
|
|
100
103
|
createOffer(input) {
|
|
101
104
|
return this.request("POST", "/v1/agent/merchant/offers", input, true);
|
|
102
105
|
}
|
|
@@ -129,15 +132,38 @@ export class MerchantClient {
|
|
|
129
132
|
payload = await response.json();
|
|
130
133
|
}
|
|
131
134
|
catch {
|
|
135
|
+
if (response.ok)
|
|
136
|
+
throw new MerchantApiError(response.status, "invalid_response", "402Mob returned an unreadable response. The request may have completed; do not assume it failed.");
|
|
132
137
|
payload = undefined;
|
|
133
138
|
}
|
|
134
139
|
if (!response.ok) {
|
|
135
140
|
const error = isRecord(payload) ? payload : {};
|
|
136
141
|
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
142
|
}
|
|
143
|
+
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.");
|
|
144
|
+
if (!isRecord(payload))
|
|
145
|
+
throw invalidResponse();
|
|
146
|
+
if (path === "/v1/merchants/enrollment-challenges" || path === "/v1/merchants/credential-challenges") {
|
|
147
|
+
const proof = payload.transactionProof;
|
|
148
|
+
if (!nonempty(payload.challengeId) || !nonempty(payload.message) || !nonempty(payload.expiresAt) ||
|
|
149
|
+
!Number.isFinite(Date.parse(payload.expiresAt)) || !isRecord(proof) ||
|
|
150
|
+
proof.network !== "eip155:8453" || !address(proof.asset) || !address(proof.proofAddress) ||
|
|
151
|
+
!isRecord(proof.approve) || !isRecord(proof.send) ||
|
|
152
|
+
typeof proof.approve.spender !== "string" || proof.approve.spender.toLowerCase() !== proof.proofAddress.toLowerCase() ||
|
|
153
|
+
typeof proof.send.recipient !== "string" || proof.send.recipient.toLowerCase() !== proof.proofAddress.toLowerCase() ||
|
|
154
|
+
proof.approve.amountAtomic !== "0" || proof.send.amountAtomic !== "0" ||
|
|
155
|
+
typeof proof.requiredConfirmations !== "string" || !/^[1-9][0-9]*$/.test(proof.requiredConfirmations))
|
|
156
|
+
throw invalidResponse();
|
|
157
|
+
}
|
|
158
|
+
if (["/v1/merchants/enrollments", "/v1/merchants/enrollments/transaction", "/v1/merchants/credentials/rotate", "/v1/merchants/credentials/rotate-transaction"].includes(path)) {
|
|
159
|
+
if (!isRecord(payload.merchant) || !nonempty(payload.merchant.id) || !nonempty(payload.apiKey))
|
|
160
|
+
throw invalidResponse();
|
|
161
|
+
}
|
|
138
162
|
return payload;
|
|
139
163
|
}
|
|
140
164
|
}
|
|
141
165
|
function isRecord(value) {
|
|
142
166
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
143
167
|
}
|
|
168
|
+
function nonempty(value) { return typeof value === "string" && value.trim().length > 0; }
|
|
169
|
+
function address(value) { return typeof value === "string" && /^0x[0-9a-fA-F]{40}$/.test(value); }
|