mpp-test-sdk 1.0.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/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # mpp-test-sdk
2
+
3
+ [![npm](https://img.shields.io/npm/v/mpp-test-sdk)](https://www.npmjs.com/package/mpp-test-sdk)
4
+ [![Node.js](https://img.shields.io/node/v/mpp-test-sdk)](https://nodejs.org)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-zinc.svg)](LICENSE)
6
+
7
+ Test pay-per-request APIs on Tempo testnet. Auto-creates wallets, funds from faucet, handles 402 payments — zero setup required.
8
+
9
+ **[mpptestkit.com](https://mpptestkit.com)** · [GitHub](https://github.com/mpptestkit/mpp-test-sdk) · [X](https://x.com/mpptestkit)
10
+
11
+ ---
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm i mpp-test-sdk
17
+ ```
18
+
19
+ Requires Node.js 22+.
20
+
21
+ ---
22
+
23
+ ## Client
24
+
25
+ ```ts
26
+ import { mppFetch } from "mpp-test-sdk";
27
+
28
+ const res = await mppFetch("https://your-api.com/api/data");
29
+ const data = await res.json();
30
+ ```
31
+
32
+ Or with a dedicated client instance:
33
+
34
+ ```ts
35
+ import { createTestClient } from "mpp-test-sdk";
36
+
37
+ const client = await createTestClient({
38
+ onStep: (step) => console.log(step.type, step.message),
39
+ });
40
+
41
+ const res = await client.fetch("https://your-api.com/api/data");
42
+ ```
43
+
44
+ The SDK handles the full flow: wallet generation, testnet faucet funding, 402 detection, on-chain payment, and automatic retry with payment proof.
45
+
46
+ ---
47
+
48
+ ## Server
49
+
50
+ ```ts
51
+ import express from "express";
52
+ import { createTestServer } from "mpp-test-sdk";
53
+
54
+ const app = express();
55
+ const mpp = createTestServer({ secretKey: process.env.MPP_SECRET_KEY });
56
+
57
+ // Free — no middleware
58
+ app.get("/api/ping", (req, res) => res.json({ ok: true }));
59
+
60
+ // Paid — one line
61
+ app.get("/api/data", mpp.charge({ amount: "0.01" }), (req, res) => {
62
+ res.json({ data: "premium content" });
63
+ });
64
+
65
+ app.listen(3001);
66
+ ```
67
+
68
+ ---
69
+
70
+ ## API Reference
71
+
72
+ ### `mppFetch(url, init?)`
73
+
74
+ Drop-in replacement for `fetch`. Uses a shared client lazily created on first call.
75
+
76
+ Call `mppFetch.reset()` to discard the shared client and generate a new wallet on the next request.
77
+
78
+ ### `createTestClient(config?)`
79
+
80
+ Creates a client with its own isolated wallet.
81
+
82
+ | Option | Type | Default | Description |
83
+ |---|---|---|---|
84
+ | `privateKey` | `` `0x${string}` `` | auto-generated | Reuse a pre-funded wallet |
85
+ | `onStep` | `(step: PaymentStep) => void` | — | Lifecycle event callback |
86
+ | `timeout` | `number` | `30000` | Full flow timeout in ms |
87
+ | `maxRetries` | `number` | `1` | Max retry attempts |
88
+
89
+ Returns `Promise<TestClient>` with `{ address, method, fetch }`.
90
+
91
+ **Throws:** `MppFaucetError` if the testnet faucet is unreachable.
92
+
93
+ ### `createTestServer(config)`
94
+
95
+ Creates Express middleware that enforces payment on any route.
96
+
97
+ | Option | Type | Default | Description |
98
+ |---|---|---|---|
99
+ | `secretKey` | `string` | **required** | MPP secret key for payment verification |
100
+ | `privateKey` | `` `0x${string}` `` | auto-generated | Server wallet private key |
101
+ | `currency` | `` `0x${string}` `` | PathUSD | ERC-20 token address to accept |
102
+
103
+ Returns `MppServer` with `.charge({ amount })` middleware.
104
+
105
+ **Throws:** `Error` synchronously if `secretKey` is missing.
106
+
107
+ ### `PaymentStep` events
108
+
109
+ | `step.type` | When |
110
+ |---|---|
111
+ | `"wallet-created"` | New ephemeral wallet generated |
112
+ | `"funded"` | Faucet funding confirmed |
113
+ | `"request"` | Outgoing HTTP request |
114
+ | `"payment"` | On-chain payment submitted |
115
+ | `"success"` | Final 200 response received |
116
+ | `"error"` | Flow failed |
117
+
118
+ ---
119
+
120
+ ## Error Handling
121
+
122
+ ```ts
123
+ import { MppFaucetError, MppPaymentError, MppTimeoutError } from "mpp-test-sdk";
124
+
125
+ try {
126
+ const res = await client.fetch("https://api.example.com/data");
127
+ } catch (err) {
128
+ if (err instanceof MppFaucetError) {
129
+ // Testnet faucet unreachable — err.address
130
+ } else if (err instanceof MppPaymentError) {
131
+ // Payment rejected — err.status, err.url
132
+ } else if (err instanceof MppTimeoutError) {
133
+ // Flow timed out — err.url, err.timeoutMs
134
+ }
135
+ }
136
+ ```
137
+
138
+ **Tip:** Pass a pre-funded `privateKey` to `createTestClient` during development to skip faucet calls.
139
+
140
+ ---
141
+
142
+ ## Network
143
+
144
+ All payments use **PathUSD** test tokens on **Tempo Moderato Testnet** (chain ID 42431). Real on-chain transactions, zero financial risk.
145
+
146
+ | | Value |
147
+ |---|---|
148
+ | Chain ID | `42431` |
149
+ | RPC | `https://rpc.testnet.tempo.xyz` |
150
+ | Explorer | `https://explorer.testnet.tempo.xyz` |
151
+ | PathUSD | `0x20c0000000000000000000000000000000000000` |
152
+ | Faucet | Automatic via SDK |
153
+
154
+ ---
155
+
156
+ ## Troubleshooting
157
+
158
+ **`MppFaucetError`** — Faucet has rate limits. Wait a few seconds and retry, or pass a pre-funded `privateKey` to skip faucet calls.
159
+
160
+ **`MppTimeoutError`** — Increase `timeout` in `createTestClient`. On-chain confirmations typically take 2–5 seconds.
161
+
162
+ **402 not handled** — Ensure `createTestServer` is called with a valid `secretKey` and the middleware is placed before the route handler.
163
+
164
+ ---
165
+
166
+ ## License
167
+
168
+ MIT
@@ -0,0 +1,115 @@
1
+ import { RequestHandler } from 'express';
2
+
3
+ interface PaymentStep {
4
+ type: "wallet-created" | "funded" | "request" | "payment" | "success" | "error";
5
+ message: string;
6
+ data?: Record<string, unknown>;
7
+ }
8
+ interface TestClientConfig {
9
+ /** Use a specific wallet. If omitted, a new wallet is auto-generated. */
10
+ privateKey?: `0x${string}`;
11
+ /** Lifecycle event callback for observing the payment flow. */
12
+ onStep?: (step: PaymentStep) => void;
13
+ /** Request timeout in milliseconds. Default: 30000 (30s). */
14
+ timeout?: number;
15
+ /** Maximum retry attempts for transient failures. Default: 1 (single 402 retry). */
16
+ maxRetries?: number;
17
+ }
18
+ interface TestClient {
19
+ /** The wallet address. */
20
+ address: string;
21
+ /** The payment method being used. */
22
+ method: "tempo";
23
+ /** Fetch a URL with automatic 402 payment handling. */
24
+ fetch: (url: string, init?: RequestInit) => Promise<Response>;
25
+ }
26
+ /**
27
+ * Create an MPP test client.
28
+ *
29
+ * Auto-generates a wallet, funds it from the Tempo testnet faucet,
30
+ * and handles 402 payments automatically.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { createTestClient } from "mpp-test-sdk";
35
+ *
36
+ * const client = await createTestClient();
37
+ * const res = await client.fetch("http://localhost:3001/api/ping/paid");
38
+ * const data = await res.json();
39
+ * ```
40
+ *
41
+ * @throws {MppFaucetError} When the testnet faucet is unreachable or returns an error.
42
+ */
43
+ declare function createTestClient(config?: TestClientConfig): Promise<TestClient>;
44
+ /**
45
+ * One-shot fetch with automatic wallet creation, funding, and 402 payment.
46
+ *
47
+ * Uses a shared client instance across calls (lazy-initialized on first call).
48
+ * Call `mppFetch.reset()` to discard the shared instance.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { mppFetch } from "mpp-test-sdk";
53
+ *
54
+ * const res = await mppFetch("http://localhost:3001/api/ping/paid");
55
+ * const data = await res.json();
56
+ * ```
57
+ *
58
+ * @throws {MppFaucetError} When the testnet faucet is unreachable.
59
+ * @throws {MppTimeoutError} When the request times out.
60
+ */
61
+ declare function mppFetch(url: string, init?: RequestInit): Promise<Response>;
62
+ declare namespace mppFetch {
63
+ var reset: () => void;
64
+ }
65
+
66
+ interface ChargeOptions {
67
+ /** Amount to charge in PathUSD (e.g. "0.01"). */
68
+ amount: string;
69
+ }
70
+ interface MppServer {
71
+ /** Express middleware that charges the specified amount before passing through. */
72
+ charge: (opts: ChargeOptions) => RequestHandler;
73
+ }
74
+ interface TestServerConfig {
75
+ /** Your MPP secret key. Required. */
76
+ secretKey: string;
77
+ /** Optional private key for the server wallet. Auto-generated if omitted. */
78
+ privateKey?: `0x${string}`;
79
+ /** Optional currency token address. Defaults to PathUSD on Tempo testnet. */
80
+ currency?: `0x${string}`;
81
+ }
82
+ /**
83
+ * Create an MPP-enabled Express middleware.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * import express from "express";
88
+ * import { createTestServer } from "mpp-test-sdk";
89
+ *
90
+ * const app = express();
91
+ * const mpp = createTestServer({ secretKey: "sk_test_..." });
92
+ * app.get("/api/paid", mpp.charge({ amount: "0.01" }), (req, res) => res.json({ ok: true }));
93
+ * ```
94
+ */
95
+ declare function createTestServer(config: TestServerConfig): MppServer;
96
+
97
+ declare class MppError extends Error {
98
+ constructor(message: string);
99
+ }
100
+ declare class MppFaucetError extends MppError {
101
+ readonly address: string;
102
+ constructor(address: string, cause?: unknown);
103
+ }
104
+ declare class MppPaymentError extends MppError {
105
+ readonly status: number;
106
+ readonly url: string;
107
+ constructor(url: string, status: number, cause?: unknown);
108
+ }
109
+ declare class MppTimeoutError extends MppError {
110
+ readonly url: string;
111
+ readonly timeoutMs: number;
112
+ constructor(url: string, timeoutMs: number);
113
+ }
114
+
115
+ export { type ChargeOptions, MppError, MppFaucetError, MppPaymentError, type MppServer, MppTimeoutError, type PaymentStep, type TestClient, type TestClientConfig, type TestServerConfig, createTestClient, createTestServer, mppFetch };
@@ -0,0 +1,115 @@
1
+ import { RequestHandler } from 'express';
2
+
3
+ interface PaymentStep {
4
+ type: "wallet-created" | "funded" | "request" | "payment" | "success" | "error";
5
+ message: string;
6
+ data?: Record<string, unknown>;
7
+ }
8
+ interface TestClientConfig {
9
+ /** Use a specific wallet. If omitted, a new wallet is auto-generated. */
10
+ privateKey?: `0x${string}`;
11
+ /** Lifecycle event callback for observing the payment flow. */
12
+ onStep?: (step: PaymentStep) => void;
13
+ /** Request timeout in milliseconds. Default: 30000 (30s). */
14
+ timeout?: number;
15
+ /** Maximum retry attempts for transient failures. Default: 1 (single 402 retry). */
16
+ maxRetries?: number;
17
+ }
18
+ interface TestClient {
19
+ /** The wallet address. */
20
+ address: string;
21
+ /** The payment method being used. */
22
+ method: "tempo";
23
+ /** Fetch a URL with automatic 402 payment handling. */
24
+ fetch: (url: string, init?: RequestInit) => Promise<Response>;
25
+ }
26
+ /**
27
+ * Create an MPP test client.
28
+ *
29
+ * Auto-generates a wallet, funds it from the Tempo testnet faucet,
30
+ * and handles 402 payments automatically.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { createTestClient } from "mpp-test-sdk";
35
+ *
36
+ * const client = await createTestClient();
37
+ * const res = await client.fetch("http://localhost:3001/api/ping/paid");
38
+ * const data = await res.json();
39
+ * ```
40
+ *
41
+ * @throws {MppFaucetError} When the testnet faucet is unreachable or returns an error.
42
+ */
43
+ declare function createTestClient(config?: TestClientConfig): Promise<TestClient>;
44
+ /**
45
+ * One-shot fetch with automatic wallet creation, funding, and 402 payment.
46
+ *
47
+ * Uses a shared client instance across calls (lazy-initialized on first call).
48
+ * Call `mppFetch.reset()` to discard the shared instance.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { mppFetch } from "mpp-test-sdk";
53
+ *
54
+ * const res = await mppFetch("http://localhost:3001/api/ping/paid");
55
+ * const data = await res.json();
56
+ * ```
57
+ *
58
+ * @throws {MppFaucetError} When the testnet faucet is unreachable.
59
+ * @throws {MppTimeoutError} When the request times out.
60
+ */
61
+ declare function mppFetch(url: string, init?: RequestInit): Promise<Response>;
62
+ declare namespace mppFetch {
63
+ var reset: () => void;
64
+ }
65
+
66
+ interface ChargeOptions {
67
+ /** Amount to charge in PathUSD (e.g. "0.01"). */
68
+ amount: string;
69
+ }
70
+ interface MppServer {
71
+ /** Express middleware that charges the specified amount before passing through. */
72
+ charge: (opts: ChargeOptions) => RequestHandler;
73
+ }
74
+ interface TestServerConfig {
75
+ /** Your MPP secret key. Required. */
76
+ secretKey: string;
77
+ /** Optional private key for the server wallet. Auto-generated if omitted. */
78
+ privateKey?: `0x${string}`;
79
+ /** Optional currency token address. Defaults to PathUSD on Tempo testnet. */
80
+ currency?: `0x${string}`;
81
+ }
82
+ /**
83
+ * Create an MPP-enabled Express middleware.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * import express from "express";
88
+ * import { createTestServer } from "mpp-test-sdk";
89
+ *
90
+ * const app = express();
91
+ * const mpp = createTestServer({ secretKey: "sk_test_..." });
92
+ * app.get("/api/paid", mpp.charge({ amount: "0.01" }), (req, res) => res.json({ ok: true }));
93
+ * ```
94
+ */
95
+ declare function createTestServer(config: TestServerConfig): MppServer;
96
+
97
+ declare class MppError extends Error {
98
+ constructor(message: string);
99
+ }
100
+ declare class MppFaucetError extends MppError {
101
+ readonly address: string;
102
+ constructor(address: string, cause?: unknown);
103
+ }
104
+ declare class MppPaymentError extends MppError {
105
+ readonly status: number;
106
+ readonly url: string;
107
+ constructor(url: string, status: number, cause?: unknown);
108
+ }
109
+ declare class MppTimeoutError extends MppError {
110
+ readonly url: string;
111
+ readonly timeoutMs: number;
112
+ constructor(url: string, timeoutMs: number);
113
+ }
114
+
115
+ export { type ChargeOptions, MppError, MppFaucetError, MppPaymentError, type MppServer, MppTimeoutError, type PaymentStep, type TestClient, type TestClientConfig, type TestServerConfig, createTestClient, createTestServer, mppFetch };
package/dist/index.js ADDED
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ MppError: () => MppError,
24
+ MppFaucetError: () => MppFaucetError,
25
+ MppPaymentError: () => MppPaymentError,
26
+ MppTimeoutError: () => MppTimeoutError,
27
+ createTestClient: () => createTestClient,
28
+ createTestServer: () => createTestServer,
29
+ mppFetch: () => mppFetch
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/client.ts
34
+ var import_accounts = require("viem/accounts");
35
+ var import_client = require("mppx/client");
36
+
37
+ // src/errors.ts
38
+ var MppError = class extends Error {
39
+ constructor(message) {
40
+ super(message);
41
+ this.name = "MppError";
42
+ }
43
+ };
44
+ var MppFaucetError = class extends MppError {
45
+ address;
46
+ constructor(address, cause) {
47
+ super(`Failed to fund wallet ${address} from testnet faucet`);
48
+ this.name = "MppFaucetError";
49
+ this.address = address;
50
+ this.cause = cause;
51
+ }
52
+ };
53
+ var MppPaymentError = class extends MppError {
54
+ status;
55
+ url;
56
+ constructor(url, status, cause) {
57
+ super(`Payment failed for ${url} (status: ${status})`);
58
+ this.name = "MppPaymentError";
59
+ this.status = status;
60
+ this.url = url;
61
+ this.cause = cause;
62
+ }
63
+ };
64
+ var MppTimeoutError = class extends MppError {
65
+ url;
66
+ timeoutMs;
67
+ constructor(url, timeoutMs) {
68
+ super(`Request to ${url} timed out after ${timeoutMs}ms`);
69
+ this.name = "MppTimeoutError";
70
+ this.url = url;
71
+ this.timeoutMs = timeoutMs;
72
+ }
73
+ };
74
+
75
+ // src/client.ts
76
+ var FAUCET_RPC = "https://rpc.testnet.tempo.xyz";
77
+ async function fundWallet(address) {
78
+ const res = await fetch(FAUCET_RPC, {
79
+ method: "POST",
80
+ headers: { "Content-Type": "application/json" },
81
+ body: JSON.stringify({
82
+ jsonrpc: "2.0",
83
+ id: 1,
84
+ method: "tempo_fundAddress",
85
+ params: [address]
86
+ })
87
+ });
88
+ if (!res.ok) {
89
+ throw new MppFaucetError(address, new Error(`HTTP ${res.status}`));
90
+ }
91
+ const json = await res.json();
92
+ if (json.error) {
93
+ throw new MppFaucetError(address, new Error(json.error.message ?? "RPC error"));
94
+ }
95
+ }
96
+ async function createTestClient(config) {
97
+ const emit = config?.onStep ?? (() => {
98
+ });
99
+ const timeout = config?.timeout ?? 3e4;
100
+ const key = config?.privateKey ?? (0, import_accounts.generatePrivateKey)();
101
+ const account = (0, import_accounts.privateKeyToAccount)(key);
102
+ emit({
103
+ type: "wallet-created",
104
+ message: `Wallet ${account.address}`,
105
+ data: { address: account.address }
106
+ });
107
+ await fundWallet(account.address);
108
+ emit({ type: "funded", message: "Wallet funded on testnet" });
109
+ const mppxClient = import_client.Mppx.create({
110
+ methods: [(0, import_client.tempo)({ account, testnet: true })]
111
+ });
112
+ return {
113
+ address: account.address,
114
+ method: "tempo",
115
+ fetch: async (url, init) => {
116
+ emit({ type: "request", message: `\u2192 ${url}` });
117
+ const controller = new AbortController();
118
+ const timer = setTimeout(() => controller.abort(), timeout);
119
+ const signal = init?.signal ? anySignal([init.signal, controller.signal]) : controller.signal;
120
+ try {
121
+ const response = await mppxClient.fetch(url, { ...init, signal });
122
+ if (!response.ok && response.status !== 402) {
123
+ emit({
124
+ type: "error",
125
+ message: `\u2190 ${response.status}`,
126
+ data: { status: response.status }
127
+ });
128
+ throw new MppPaymentError(url, response.status);
129
+ }
130
+ emit({
131
+ type: response.ok ? "success" : "payment",
132
+ message: `\u2190 ${response.status}`,
133
+ data: { status: response.status }
134
+ });
135
+ return response;
136
+ } catch (err) {
137
+ if (err instanceof Error && err.name === "AbortError") {
138
+ throw new MppTimeoutError(url, timeout);
139
+ }
140
+ throw err;
141
+ } finally {
142
+ clearTimeout(timer);
143
+ }
144
+ }
145
+ };
146
+ }
147
+ function anySignal(signals) {
148
+ const controller = new AbortController();
149
+ for (const signal of signals) {
150
+ if (signal.aborted) {
151
+ controller.abort(signal.reason);
152
+ return controller.signal;
153
+ }
154
+ signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
155
+ }
156
+ return controller.signal;
157
+ }
158
+ var _sharedClient = null;
159
+ async function mppFetch(url, init) {
160
+ if (!_sharedClient) {
161
+ _sharedClient = await createTestClient();
162
+ }
163
+ return _sharedClient.fetch(url, init);
164
+ }
165
+ mppFetch.reset = () => {
166
+ _sharedClient = null;
167
+ };
168
+
169
+ // src/server.ts
170
+ var import_express = require("mppx/express");
171
+ var import_accounts2 = require("viem/accounts");
172
+ var PATHUSD = "0x20c0000000000000000000000000000000000000";
173
+ function createTestServer(config) {
174
+ if (!config.secretKey) {
175
+ throw new Error("createTestServer: secretKey is required");
176
+ }
177
+ const serverKey = config.privateKey ?? (0, import_accounts2.generatePrivateKey)();
178
+ const serverAccount = (0, import_accounts2.privateKeyToAccount)(serverKey);
179
+ const mppx = import_express.Mppx.create({
180
+ secretKey: config.secretKey,
181
+ methods: [
182
+ (0, import_express.tempo)({
183
+ testnet: true,
184
+ currency: config.currency ?? PATHUSD,
185
+ recipient: serverAccount.address,
186
+ account: serverAccount
187
+ })
188
+ ]
189
+ });
190
+ return mppx;
191
+ }
192
+ // Annotate the CommonJS export names for ESM import in node:
193
+ 0 && (module.exports = {
194
+ MppError,
195
+ MppFaucetError,
196
+ MppPaymentError,
197
+ MppTimeoutError,
198
+ createTestClient,
199
+ createTestServer,
200
+ mppFetch
201
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,168 @@
1
+ // src/client.ts
2
+ import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
3
+ import { Mppx, tempo } from "mppx/client";
4
+
5
+ // src/errors.ts
6
+ var MppError = class extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "MppError";
10
+ }
11
+ };
12
+ var MppFaucetError = class extends MppError {
13
+ address;
14
+ constructor(address, cause) {
15
+ super(`Failed to fund wallet ${address} from testnet faucet`);
16
+ this.name = "MppFaucetError";
17
+ this.address = address;
18
+ this.cause = cause;
19
+ }
20
+ };
21
+ var MppPaymentError = class extends MppError {
22
+ status;
23
+ url;
24
+ constructor(url, status, cause) {
25
+ super(`Payment failed for ${url} (status: ${status})`);
26
+ this.name = "MppPaymentError";
27
+ this.status = status;
28
+ this.url = url;
29
+ this.cause = cause;
30
+ }
31
+ };
32
+ var MppTimeoutError = class extends MppError {
33
+ url;
34
+ timeoutMs;
35
+ constructor(url, timeoutMs) {
36
+ super(`Request to ${url} timed out after ${timeoutMs}ms`);
37
+ this.name = "MppTimeoutError";
38
+ this.url = url;
39
+ this.timeoutMs = timeoutMs;
40
+ }
41
+ };
42
+
43
+ // src/client.ts
44
+ var FAUCET_RPC = "https://rpc.testnet.tempo.xyz";
45
+ async function fundWallet(address) {
46
+ const res = await fetch(FAUCET_RPC, {
47
+ method: "POST",
48
+ headers: { "Content-Type": "application/json" },
49
+ body: JSON.stringify({
50
+ jsonrpc: "2.0",
51
+ id: 1,
52
+ method: "tempo_fundAddress",
53
+ params: [address]
54
+ })
55
+ });
56
+ if (!res.ok) {
57
+ throw new MppFaucetError(address, new Error(`HTTP ${res.status}`));
58
+ }
59
+ const json = await res.json();
60
+ if (json.error) {
61
+ throw new MppFaucetError(address, new Error(json.error.message ?? "RPC error"));
62
+ }
63
+ }
64
+ async function createTestClient(config) {
65
+ const emit = config?.onStep ?? (() => {
66
+ });
67
+ const timeout = config?.timeout ?? 3e4;
68
+ const key = config?.privateKey ?? generatePrivateKey();
69
+ const account = privateKeyToAccount(key);
70
+ emit({
71
+ type: "wallet-created",
72
+ message: `Wallet ${account.address}`,
73
+ data: { address: account.address }
74
+ });
75
+ await fundWallet(account.address);
76
+ emit({ type: "funded", message: "Wallet funded on testnet" });
77
+ const mppxClient = Mppx.create({
78
+ methods: [tempo({ account, testnet: true })]
79
+ });
80
+ return {
81
+ address: account.address,
82
+ method: "tempo",
83
+ fetch: async (url, init) => {
84
+ emit({ type: "request", message: `\u2192 ${url}` });
85
+ const controller = new AbortController();
86
+ const timer = setTimeout(() => controller.abort(), timeout);
87
+ const signal = init?.signal ? anySignal([init.signal, controller.signal]) : controller.signal;
88
+ try {
89
+ const response = await mppxClient.fetch(url, { ...init, signal });
90
+ if (!response.ok && response.status !== 402) {
91
+ emit({
92
+ type: "error",
93
+ message: `\u2190 ${response.status}`,
94
+ data: { status: response.status }
95
+ });
96
+ throw new MppPaymentError(url, response.status);
97
+ }
98
+ emit({
99
+ type: response.ok ? "success" : "payment",
100
+ message: `\u2190 ${response.status}`,
101
+ data: { status: response.status }
102
+ });
103
+ return response;
104
+ } catch (err) {
105
+ if (err instanceof Error && err.name === "AbortError") {
106
+ throw new MppTimeoutError(url, timeout);
107
+ }
108
+ throw err;
109
+ } finally {
110
+ clearTimeout(timer);
111
+ }
112
+ }
113
+ };
114
+ }
115
+ function anySignal(signals) {
116
+ const controller = new AbortController();
117
+ for (const signal of signals) {
118
+ if (signal.aborted) {
119
+ controller.abort(signal.reason);
120
+ return controller.signal;
121
+ }
122
+ signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
123
+ }
124
+ return controller.signal;
125
+ }
126
+ var _sharedClient = null;
127
+ async function mppFetch(url, init) {
128
+ if (!_sharedClient) {
129
+ _sharedClient = await createTestClient();
130
+ }
131
+ return _sharedClient.fetch(url, init);
132
+ }
133
+ mppFetch.reset = () => {
134
+ _sharedClient = null;
135
+ };
136
+
137
+ // src/server.ts
138
+ import { Mppx as Mppx2, tempo as tempo2 } from "mppx/express";
139
+ import { privateKeyToAccount as privateKeyToAccount2, generatePrivateKey as generatePrivateKey2 } from "viem/accounts";
140
+ var PATHUSD = "0x20c0000000000000000000000000000000000000";
141
+ function createTestServer(config) {
142
+ if (!config.secretKey) {
143
+ throw new Error("createTestServer: secretKey is required");
144
+ }
145
+ const serverKey = config.privateKey ?? generatePrivateKey2();
146
+ const serverAccount = privateKeyToAccount2(serverKey);
147
+ const mppx = Mppx2.create({
148
+ secretKey: config.secretKey,
149
+ methods: [
150
+ tempo2({
151
+ testnet: true,
152
+ currency: config.currency ?? PATHUSD,
153
+ recipient: serverAccount.address,
154
+ account: serverAccount
155
+ })
156
+ ]
157
+ });
158
+ return mppx;
159
+ }
160
+ export {
161
+ MppError,
162
+ MppFaucetError,
163
+ MppPaymentError,
164
+ MppTimeoutError,
165
+ createTestClient,
166
+ createTestServer,
167
+ mppFetch
168
+ };
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "mpp-test-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Test pay-per-request APIs on Tempo testnet. Zero setup - auto-creates wallets and handles 402 payments.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format cjs,esm --dts",
22
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "prepublishOnly": "npm run build && npm run test"
26
+ },
27
+ "dependencies": {
28
+ "mppx": "^0.6.3",
29
+ "viem": "^2.48.4"
30
+ },
31
+ "devDependencies": {
32
+ "tsup": "^8.0.0",
33
+ "typescript": "^5.4.0",
34
+ "vitest": "^3.1.0"
35
+ },
36
+ "engines": {
37
+ "node": ">=22"
38
+ },
39
+ "keywords": [
40
+ "mpp",
41
+ "payments",
42
+ "test",
43
+ "sdk",
44
+ "402",
45
+ "pay-per-request",
46
+ "tempo",
47
+ "testnet",
48
+ "machine-to-machine"
49
+ ],
50
+ "license": "MIT",
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/mpptestkit/mpp-test-sdk"
54
+ },
55
+ "homepage": "https://mpptestkit.com",
56
+ "bugs": {
57
+ "url": "https://github.com/mpptestkit/mpp-test-sdk/issues"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ }
62
+ }