mpp-test-sdk 1.0.0 → 1.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 +111 -48
- package/dist/index.d.mts +92 -37
- package/dist/index.d.ts +92 -37
- package/dist/index.js +273 -85
- package/dist/index.mjs +285 -85
- package/package.json +15 -7
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
[](https://nodejs.org)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
Test pay-per-request APIs on
|
|
7
|
+
Test pay-per-request APIs on **Solana** - devnet, testnet, or mainnet. Auto-creates wallets, airdrops SOL, handles HTTP 402 MPP payments. Zero setup required.
|
|
8
8
|
|
|
9
|
-
**[mpptestkit.com](https://mpptestkit.com)** · [GitHub](https://github.com/mpptestkit/mpp-test-sdk) · [X](https://x.com/mpptestkit)
|
|
9
|
+
**[mpptestkit.com](https://mpptestkit.com)** · [Playground](https://mpptestkit.com/playground) · [Docs](https://mpptestkit.com/docs) · [GitHub](https://github.com/mpptestkit/mpp-test-sdk) · [X](https://x.com/mpptestkit)
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ Test pay-per-request APIs on Tempo testnet. Auto-creates wallets, funds from fau
|
|
|
16
16
|
npm i mpp-test-sdk
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Requires Node.js
|
|
19
|
+
Requires Node.js 18+.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -25,8 +25,16 @@ Requires Node.js 22+.
|
|
|
25
25
|
```ts
|
|
26
26
|
import { mppFetch } from "mpp-test-sdk";
|
|
27
27
|
|
|
28
|
+
// One line. No wallet setup. No config.
|
|
28
29
|
const res = await mppFetch("https://your-api.com/api/data");
|
|
29
30
|
const data = await res.json();
|
|
31
|
+
|
|
32
|
+
// What happened automatically:
|
|
33
|
+
// 1. SDK generated a Solana keypair
|
|
34
|
+
// 2. Airdropped 2 SOL from the devnet faucet
|
|
35
|
+
// 3. Server returned 402 Payment Required
|
|
36
|
+
// 4. SDK sent 0.001 SOL on Solana (~800ms finality)
|
|
37
|
+
// 5. Retried with Payment-Receipt header → 200 OK
|
|
30
38
|
```
|
|
31
39
|
|
|
32
40
|
Or with a dedicated client instance:
|
|
@@ -35,13 +43,14 @@ Or with a dedicated client instance:
|
|
|
35
43
|
import { createTestClient } from "mpp-test-sdk";
|
|
36
44
|
|
|
37
45
|
const client = await createTestClient({
|
|
46
|
+
network: "devnet",
|
|
38
47
|
onStep: (step) => console.log(step.type, step.message),
|
|
39
48
|
});
|
|
40
49
|
|
|
41
50
|
const res = await client.fetch("https://your-api.com/api/data");
|
|
42
51
|
```
|
|
43
52
|
|
|
44
|
-
|
|
53
|
+
Call `mppFetch.reset()` to discard the shared client and generate a fresh wallet on the next request.
|
|
45
54
|
|
|
46
55
|
---
|
|
47
56
|
|
|
@@ -52,17 +61,19 @@ import express from "express";
|
|
|
52
61
|
import { createTestServer } from "mpp-test-sdk";
|
|
53
62
|
|
|
54
63
|
const app = express();
|
|
55
|
-
const mpp = createTestServer(
|
|
64
|
+
const mpp = createTestServer(); // auto-generates server wallet
|
|
56
65
|
|
|
57
|
-
// Free
|
|
66
|
+
// Free - no middleware
|
|
58
67
|
app.get("/api/ping", (req, res) => res.json({ ok: true }));
|
|
59
68
|
|
|
60
|
-
// Paid
|
|
61
|
-
app.get("/api/data",
|
|
62
|
-
|
|
63
|
-
})
|
|
69
|
+
// Paid - one line (0.001 SOL)
|
|
70
|
+
app.get("/api/data",
|
|
71
|
+
mpp.charge({ amount: "0.001" }),
|
|
72
|
+
(req, res) => res.json({ data: "premium content" })
|
|
73
|
+
);
|
|
64
74
|
|
|
65
75
|
app.listen(3001);
|
|
76
|
+
console.log("Payments go to:", mpp.recipientAddress);
|
|
66
77
|
```
|
|
67
78
|
|
|
68
79
|
---
|
|
@@ -71,47 +82,66 @@ app.listen(3001);
|
|
|
71
82
|
|
|
72
83
|
### `mppFetch(url, init?)`
|
|
73
84
|
|
|
74
|
-
Drop-in replacement for `fetch`.
|
|
75
|
-
|
|
76
|
-
Call `mppFetch.reset()` to discard the shared client and generate a new wallet on the next request.
|
|
85
|
+
Drop-in replacement for `fetch`. Lazily creates a shared client on first call and reuses it across requests.
|
|
77
86
|
|
|
78
87
|
### `createTestClient(config?)`
|
|
79
88
|
|
|
80
|
-
Creates a client with its own isolated wallet.
|
|
89
|
+
Creates a client with its own isolated Solana wallet.
|
|
81
90
|
|
|
82
91
|
| Option | Type | Default | Description |
|
|
83
92
|
|---|---|---|---|
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
93
|
+
| `network` | `"devnet" \| "testnet" \| "mainnet"` | `"devnet"` | Solana network to use |
|
|
94
|
+
| `secretKey` | `Uint8Array \| number[]` | auto-generated | Reuse a pre-funded keypair (required on mainnet) |
|
|
95
|
+
| `onStep` | `(step: PaymentStep) => void` | - | Lifecycle event callback |
|
|
86
96
|
| `timeout` | `number` | `30000` | Full flow timeout in ms |
|
|
87
|
-
| `maxRetries` | `number` | `1` | Max retry attempts |
|
|
88
97
|
|
|
89
|
-
Returns `Promise<TestClient
|
|
98
|
+
Returns `Promise<TestClient>`:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
interface TestClient {
|
|
102
|
+
address: string; // Solana public key (base58)
|
|
103
|
+
network: SolanaNetwork;
|
|
104
|
+
fetch: (url: string, init?: RequestInit) => Promise<Response>;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
90
107
|
|
|
91
|
-
**Throws:**
|
|
108
|
+
**Throws:**
|
|
109
|
+
- `MppFaucetError` - devnet/testnet airdrop failed
|
|
110
|
+
- `MppNetworkError` - mainnet used without a `secretKey`
|
|
92
111
|
|
|
93
|
-
### `createTestServer(config)`
|
|
112
|
+
### `createTestServer(config?)`
|
|
94
113
|
|
|
95
114
|
Creates Express middleware that enforces payment on any route.
|
|
96
115
|
|
|
97
116
|
| Option | Type | Default | Description |
|
|
98
117
|
|---|---|---|---|
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `currency` | `` `0x${string}` `` | PathUSD | ERC-20 token address to accept |
|
|
118
|
+
| `network` | `"devnet" \| "testnet" \| "mainnet"` | `"devnet"` | Solana network to verify payments on |
|
|
119
|
+
| `secretKey` | `Uint8Array \| number[]` | auto-generated | Server wallet keypair |
|
|
102
120
|
|
|
103
|
-
|
|
121
|
+
**Properties:**
|
|
104
122
|
|
|
105
|
-
|
|
123
|
+
| Property | Description |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `mpp.recipientAddress` | Server wallet public key - payments land here |
|
|
126
|
+
| `mpp.network` | Active network |
|
|
127
|
+
|
|
128
|
+
### `mpp.charge(opts)`
|
|
129
|
+
|
|
130
|
+
Express `RequestHandler`. Place before your route handler.
|
|
131
|
+
|
|
132
|
+
| Option | Type | Description |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `amount` | `string` | Amount in SOL, e.g. `"0.001"` |
|
|
106
135
|
|
|
107
136
|
### `PaymentStep` events
|
|
108
137
|
|
|
109
138
|
| `step.type` | When |
|
|
110
139
|
|---|---|
|
|
111
|
-
| `"wallet-created"` |
|
|
112
|
-
| `"funded"` | Faucet
|
|
113
|
-
| `"request"` | Outgoing HTTP request |
|
|
114
|
-
| `"payment"` |
|
|
140
|
+
| `"wallet-created"` | Solana keypair generated |
|
|
141
|
+
| `"funded"` | Faucet airdrop confirmed (devnet/testnet) |
|
|
142
|
+
| `"request"` | Outgoing HTTP request fired |
|
|
143
|
+
| `"payment"` | SOL transaction submitted and confirmed |
|
|
144
|
+
| `"retry"` | Request retried with Payment-Receipt header |
|
|
115
145
|
| `"success"` | Final 200 response received |
|
|
116
146
|
| `"error"` | Flow failed |
|
|
117
147
|
|
|
@@ -120,46 +150,79 @@ Returns `MppServer` with `.charge({ amount })` middleware.
|
|
|
120
150
|
## Error Handling
|
|
121
151
|
|
|
122
152
|
```ts
|
|
123
|
-
import {
|
|
153
|
+
import {
|
|
154
|
+
MppError,
|
|
155
|
+
MppFaucetError,
|
|
156
|
+
MppPaymentError,
|
|
157
|
+
MppTimeoutError,
|
|
158
|
+
MppNetworkError,
|
|
159
|
+
} from "mpp-test-sdk";
|
|
124
160
|
|
|
125
161
|
try {
|
|
126
|
-
const res = await
|
|
162
|
+
const res = await mppFetch("https://api.example.com/data");
|
|
127
163
|
} catch (err) {
|
|
128
|
-
if (err instanceof
|
|
129
|
-
//
|
|
164
|
+
if (err instanceof MppNetworkError) {
|
|
165
|
+
// Mainnet used without secretKey
|
|
166
|
+
} else if (err instanceof MppFaucetError) {
|
|
167
|
+
// Devnet/testnet airdrop failed - err.address
|
|
130
168
|
} else if (err instanceof MppPaymentError) {
|
|
131
|
-
//
|
|
169
|
+
// Server rejected payment - err.status, err.url
|
|
132
170
|
} else if (err instanceof MppTimeoutError) {
|
|
133
|
-
// Flow timed out
|
|
171
|
+
// Flow timed out - err.url, err.timeoutMs
|
|
134
172
|
}
|
|
135
173
|
}
|
|
136
174
|
```
|
|
137
175
|
|
|
138
|
-
**Tip:** Pass a pre-funded `
|
|
176
|
+
**Tip:** Pass a pre-funded `secretKey` to skip faucet calls entirely - useful in CI or when devnet is rate-limiting.
|
|
139
177
|
|
|
140
178
|
---
|
|
141
179
|
|
|
142
|
-
##
|
|
180
|
+
## Protocol
|
|
143
181
|
|
|
144
|
-
|
|
182
|
+
The SDK implements **MPP (Machine Payments Protocol)** on Solana. Plain HTTP headers, no custom transport:
|
|
145
183
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
184
|
+
```
|
|
185
|
+
# Server → Client (402 Payment Required)
|
|
186
|
+
Payment-Request: solana; amount="0.001"; recipient="9WzDX..."; network="devnet"
|
|
187
|
+
|
|
188
|
+
# Client → Server (retry with proof)
|
|
189
|
+
Payment-Receipt: solana; signature="3xKm7..."; network="devnet"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
On-chain verification confirms: transaction exists, recipient matches, SOL delta ≥ required amount.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Networks
|
|
197
|
+
|
|
198
|
+
| | devnet | testnet | mainnet |
|
|
199
|
+
|---|---|---|---|
|
|
200
|
+
| Faucet | Auto (2 SOL) | Auto (2 SOL) | Bring your own |
|
|
201
|
+
| Cost | Free | Free | Real SOL |
|
|
202
|
+
| Use for | Local dev, CI | Pre-production | Production |
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
// Devnet (default)
|
|
206
|
+
const client = await createTestClient({ network: "devnet" });
|
|
207
|
+
|
|
208
|
+
// Mainnet - bring your own funded keypair
|
|
209
|
+
const client = await createTestClient({
|
|
210
|
+
network: "mainnet",
|
|
211
|
+
secretKey: Uint8Array.from(JSON.parse(process.env.SOLANA_SECRET_KEY!)),
|
|
212
|
+
});
|
|
213
|
+
```
|
|
153
214
|
|
|
154
215
|
---
|
|
155
216
|
|
|
156
217
|
## Troubleshooting
|
|
157
218
|
|
|
158
|
-
**`MppFaucetError`**
|
|
219
|
+
**`MppFaucetError`** - Devnet airdrop is rate-limited. Wait 30–60 seconds and retry, or pass a pre-funded `secretKey` to skip the faucet.
|
|
220
|
+
|
|
221
|
+
**`MppNetworkError`** - You passed `network: "mainnet"` without a `secretKey`. Mainnet has no faucet - provide a funded keypair.
|
|
159
222
|
|
|
160
|
-
**`MppTimeoutError`**
|
|
223
|
+
**`MppTimeoutError`** - Increase `timeout` in `createTestClient`. Solana devnet confirmations typically take 1–3 seconds.
|
|
161
224
|
|
|
162
|
-
**402 not handled**
|
|
225
|
+
**402 not handled** - Ensure `mpp.charge()` middleware is placed before the route handler on the server.
|
|
163
226
|
|
|
164
227
|
---
|
|
165
228
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,62 +1,86 @@
|
|
|
1
1
|
import { RequestHandler } from 'express';
|
|
2
2
|
|
|
3
|
+
/** Solana network to connect to. */
|
|
4
|
+
type SolanaNetwork = "devnet" | "testnet" | "mainnet";
|
|
3
5
|
interface PaymentStep {
|
|
4
|
-
type: "wallet-created" | "funded" | "request" | "payment" | "success" | "error";
|
|
6
|
+
type: "wallet-created" | "funded" | "request" | "payment" | "retry" | "success" | "error";
|
|
5
7
|
message: string;
|
|
6
8
|
data?: Record<string, unknown>;
|
|
7
9
|
}
|
|
8
10
|
interface TestClientConfig {
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Solana network to connect to.
|
|
13
|
+
* - `"devnet"` (default) - Free SOL airdrop, fast confirmation.
|
|
14
|
+
* - `"testnet"` - Solana's testnet. Also has free airdrop.
|
|
15
|
+
* - `"mainnet"` - Real SOL. Requires a pre-funded `secretKey`.
|
|
16
|
+
*/
|
|
17
|
+
network?: SolanaNetwork;
|
|
18
|
+
/**
|
|
19
|
+
* Pre-funded Solana keypair secret key (32 or 64 bytes).
|
|
20
|
+
* - On `devnet`/`testnet`: optional - wallet is auto-funded via airdrop.
|
|
21
|
+
* - On `mainnet`: **required** - no airdrop available.
|
|
22
|
+
*/
|
|
23
|
+
secretKey?: Uint8Array;
|
|
11
24
|
/** Lifecycle event callback for observing the payment flow. */
|
|
12
25
|
onStep?: (step: PaymentStep) => void;
|
|
13
|
-
/**
|
|
26
|
+
/** Full flow timeout in ms (wallet + payment + retry). Default: 30000. */
|
|
14
27
|
timeout?: number;
|
|
15
|
-
/**
|
|
16
|
-
|
|
28
|
+
/** Override the Solana RPC endpoint. Takes precedence over `network`. */
|
|
29
|
+
rpcUrl?: string;
|
|
17
30
|
}
|
|
18
31
|
interface TestClient {
|
|
19
|
-
/**
|
|
32
|
+
/** Solana wallet address (base58 public key). */
|
|
20
33
|
address: string;
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
/**
|
|
34
|
+
/** Network this client is connected to. */
|
|
35
|
+
network: SolanaNetwork;
|
|
36
|
+
/** Payment method. */
|
|
37
|
+
method: "solana";
|
|
38
|
+
/** Fetch a URL with automatic 402 MPP payment handling. */
|
|
24
39
|
fetch: (url: string, init?: RequestInit) => Promise<Response>;
|
|
25
40
|
}
|
|
26
41
|
/**
|
|
27
|
-
* Create
|
|
42
|
+
* Create a Solana MPP test client.
|
|
28
43
|
*
|
|
29
|
-
*
|
|
30
|
-
* and handles 402 payments
|
|
44
|
+
* Automatically creates a Solana wallet, funds it (via airdrop on devnet/testnet),
|
|
45
|
+
* and handles HTTP 402 MPP payments with automatic retry.
|
|
31
46
|
*
|
|
32
47
|
* @example
|
|
33
48
|
* ```ts
|
|
34
|
-
*
|
|
35
|
-
*
|
|
49
|
+
* // devnet (default) - zero config
|
|
36
50
|
* const client = await createTestClient();
|
|
37
|
-
*
|
|
38
|
-
*
|
|
51
|
+
*
|
|
52
|
+
* // testnet
|
|
53
|
+
* const client = await createTestClient({ network: "testnet" });
|
|
54
|
+
*
|
|
55
|
+
* // mainnet - must provide pre-funded wallet
|
|
56
|
+
* const client = await createTestClient({
|
|
57
|
+
* network: "mainnet",
|
|
58
|
+
* secretKey: loadKeypairFromFile("./wallet.json").secretKey,
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* const res = await client.fetch("http://localhost:3001/api/data");
|
|
39
62
|
* ```
|
|
40
63
|
*
|
|
41
|
-
* @throws {
|
|
64
|
+
* @throws {MppNetworkError} When `mainnet` is specified without a `secretKey`.
|
|
65
|
+
* @throws {MppFaucetError} When the devnet/testnet airdrop fails after retries.
|
|
42
66
|
*/
|
|
43
67
|
declare function createTestClient(config?: TestClientConfig): Promise<TestClient>;
|
|
44
68
|
/**
|
|
45
|
-
*
|
|
69
|
+
* Drop-in replacement for `fetch` with automatic Solana MPP payment.
|
|
46
70
|
*
|
|
47
|
-
* Uses a shared client instance
|
|
48
|
-
* Call `mppFetch.reset()` to discard the shared instance.
|
|
71
|
+
* Uses a shared client instance lazily created on first call (devnet by default).
|
|
72
|
+
* Call `mppFetch.reset()` to discard the shared instance and generate a new wallet.
|
|
49
73
|
*
|
|
50
74
|
* @example
|
|
51
75
|
* ```ts
|
|
52
76
|
* import { mppFetch } from "mpp-test-sdk";
|
|
53
77
|
*
|
|
54
|
-
* const res = await mppFetch("http://localhost:3001/api/
|
|
78
|
+
* const res = await mppFetch("http://localhost:3001/api/data");
|
|
55
79
|
* const data = await res.json();
|
|
56
80
|
* ```
|
|
57
81
|
*
|
|
58
|
-
* @throws {MppFaucetError} When the
|
|
59
|
-
* @throws {MppTimeoutError} When the
|
|
82
|
+
* @throws {MppFaucetError} When the devnet airdrop fails after retries.
|
|
83
|
+
* @throws {MppTimeoutError} When the full flow exceeds the timeout.
|
|
60
84
|
*/
|
|
61
85
|
declare function mppFetch(url: string, init?: RequestInit): Promise<Response>;
|
|
62
86
|
declare namespace mppFetch {
|
|
@@ -64,23 +88,46 @@ declare namespace mppFetch {
|
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
interface ChargeOptions {
|
|
67
|
-
/** Amount to charge in
|
|
91
|
+
/** Amount to charge in SOL (e.g. "0.001"). */
|
|
68
92
|
amount: string;
|
|
69
93
|
}
|
|
70
94
|
interface MppServer {
|
|
71
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Express middleware that requires SOL payment before passing to the route handler.
|
|
97
|
+
*
|
|
98
|
+
* - No receipt → 402 with `Payment-Request` header.
|
|
99
|
+
* - Valid receipt + on-chain confirmation → calls `next()`.
|
|
100
|
+
* - Invalid or insufficient payment → 403.
|
|
101
|
+
*/
|
|
72
102
|
charge: (opts: ChargeOptions) => RequestHandler;
|
|
103
|
+
/** The Solana address where payments are sent. */
|
|
104
|
+
recipientAddress: string;
|
|
105
|
+
/** Network this server is configured for. */
|
|
106
|
+
network: SolanaNetwork;
|
|
73
107
|
}
|
|
74
108
|
interface TestServerConfig {
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Solana network to connect to.
|
|
111
|
+
* - `"devnet"` (default) - Solana devnet.
|
|
112
|
+
* - `"testnet"` - Solana testnet.
|
|
113
|
+
* - `"mainnet"` - Solana mainnet (real SOL).
|
|
114
|
+
*/
|
|
115
|
+
network?: SolanaNetwork;
|
|
116
|
+
/** Server wallet keypair secret key. Auto-generated if omitted. */
|
|
117
|
+
secretKey?: Uint8Array;
|
|
118
|
+
/**
|
|
119
|
+
* Override the recipient Solana address (base58).
|
|
120
|
+
* Defaults to the server keypair's public key.
|
|
121
|
+
*/
|
|
122
|
+
recipientAddress?: string;
|
|
123
|
+
/** Override the Solana RPC endpoint. Takes precedence over `network`. */
|
|
124
|
+
rpcUrl?: string;
|
|
81
125
|
}
|
|
82
126
|
/**
|
|
83
|
-
* Create
|
|
127
|
+
* Create a Solana MPP-enabled Express server.
|
|
128
|
+
*
|
|
129
|
+
* Handles the HTTP 402 payment flow and verifies SOL transfers on-chain.
|
|
130
|
+
* No config needed - auto-generates a server wallet.
|
|
84
131
|
*
|
|
85
132
|
* @example
|
|
86
133
|
* ```ts
|
|
@@ -88,11 +135,15 @@ interface TestServerConfig {
|
|
|
88
135
|
* import { createTestServer } from "mpp-test-sdk";
|
|
89
136
|
*
|
|
90
137
|
* const app = express();
|
|
91
|
-
* const mpp = createTestServer({
|
|
92
|
-
*
|
|
138
|
+
* const mpp = createTestServer(); // or createTestServer({ network: "mainnet" })
|
|
139
|
+
*
|
|
140
|
+
* // Charge 0.001 SOL per request
|
|
141
|
+
* app.get("/api/data", mpp.charge({ amount: "0.001" }), (req, res) => {
|
|
142
|
+
* res.json({ data: "premium content" });
|
|
143
|
+
* });
|
|
93
144
|
* ```
|
|
94
145
|
*/
|
|
95
|
-
declare function createTestServer(config
|
|
146
|
+
declare function createTestServer(config?: TestServerConfig): MppServer;
|
|
96
147
|
|
|
97
148
|
declare class MppError extends Error {
|
|
98
149
|
constructor(message: string);
|
|
@@ -111,5 +162,9 @@ declare class MppTimeoutError extends MppError {
|
|
|
111
162
|
readonly timeoutMs: number;
|
|
112
163
|
constructor(url: string, timeoutMs: number);
|
|
113
164
|
}
|
|
165
|
+
declare class MppNetworkError extends MppError {
|
|
166
|
+
readonly network: string;
|
|
167
|
+
constructor(network: string, message?: string);
|
|
168
|
+
}
|
|
114
169
|
|
|
115
|
-
export { type ChargeOptions, MppError, MppFaucetError, MppPaymentError, type MppServer, MppTimeoutError, type PaymentStep, type TestClient, type TestClientConfig, type TestServerConfig, createTestClient, createTestServer, mppFetch };
|
|
170
|
+
export { type ChargeOptions, MppError, MppFaucetError, MppNetworkError, MppPaymentError, type MppServer, MppTimeoutError, type PaymentStep, type SolanaNetwork, type TestClient, type TestClientConfig, type TestServerConfig, createTestClient, createTestServer, mppFetch };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,62 +1,86 @@
|
|
|
1
1
|
import { RequestHandler } from 'express';
|
|
2
2
|
|
|
3
|
+
/** Solana network to connect to. */
|
|
4
|
+
type SolanaNetwork = "devnet" | "testnet" | "mainnet";
|
|
3
5
|
interface PaymentStep {
|
|
4
|
-
type: "wallet-created" | "funded" | "request" | "payment" | "success" | "error";
|
|
6
|
+
type: "wallet-created" | "funded" | "request" | "payment" | "retry" | "success" | "error";
|
|
5
7
|
message: string;
|
|
6
8
|
data?: Record<string, unknown>;
|
|
7
9
|
}
|
|
8
10
|
interface TestClientConfig {
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Solana network to connect to.
|
|
13
|
+
* - `"devnet"` (default) - Free SOL airdrop, fast confirmation.
|
|
14
|
+
* - `"testnet"` - Solana's testnet. Also has free airdrop.
|
|
15
|
+
* - `"mainnet"` - Real SOL. Requires a pre-funded `secretKey`.
|
|
16
|
+
*/
|
|
17
|
+
network?: SolanaNetwork;
|
|
18
|
+
/**
|
|
19
|
+
* Pre-funded Solana keypair secret key (32 or 64 bytes).
|
|
20
|
+
* - On `devnet`/`testnet`: optional - wallet is auto-funded via airdrop.
|
|
21
|
+
* - On `mainnet`: **required** - no airdrop available.
|
|
22
|
+
*/
|
|
23
|
+
secretKey?: Uint8Array;
|
|
11
24
|
/** Lifecycle event callback for observing the payment flow. */
|
|
12
25
|
onStep?: (step: PaymentStep) => void;
|
|
13
|
-
/**
|
|
26
|
+
/** Full flow timeout in ms (wallet + payment + retry). Default: 30000. */
|
|
14
27
|
timeout?: number;
|
|
15
|
-
/**
|
|
16
|
-
|
|
28
|
+
/** Override the Solana RPC endpoint. Takes precedence over `network`. */
|
|
29
|
+
rpcUrl?: string;
|
|
17
30
|
}
|
|
18
31
|
interface TestClient {
|
|
19
|
-
/**
|
|
32
|
+
/** Solana wallet address (base58 public key). */
|
|
20
33
|
address: string;
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
/**
|
|
34
|
+
/** Network this client is connected to. */
|
|
35
|
+
network: SolanaNetwork;
|
|
36
|
+
/** Payment method. */
|
|
37
|
+
method: "solana";
|
|
38
|
+
/** Fetch a URL with automatic 402 MPP payment handling. */
|
|
24
39
|
fetch: (url: string, init?: RequestInit) => Promise<Response>;
|
|
25
40
|
}
|
|
26
41
|
/**
|
|
27
|
-
* Create
|
|
42
|
+
* Create a Solana MPP test client.
|
|
28
43
|
*
|
|
29
|
-
*
|
|
30
|
-
* and handles 402 payments
|
|
44
|
+
* Automatically creates a Solana wallet, funds it (via airdrop on devnet/testnet),
|
|
45
|
+
* and handles HTTP 402 MPP payments with automatic retry.
|
|
31
46
|
*
|
|
32
47
|
* @example
|
|
33
48
|
* ```ts
|
|
34
|
-
*
|
|
35
|
-
*
|
|
49
|
+
* // devnet (default) - zero config
|
|
36
50
|
* const client = await createTestClient();
|
|
37
|
-
*
|
|
38
|
-
*
|
|
51
|
+
*
|
|
52
|
+
* // testnet
|
|
53
|
+
* const client = await createTestClient({ network: "testnet" });
|
|
54
|
+
*
|
|
55
|
+
* // mainnet - must provide pre-funded wallet
|
|
56
|
+
* const client = await createTestClient({
|
|
57
|
+
* network: "mainnet",
|
|
58
|
+
* secretKey: loadKeypairFromFile("./wallet.json").secretKey,
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* const res = await client.fetch("http://localhost:3001/api/data");
|
|
39
62
|
* ```
|
|
40
63
|
*
|
|
41
|
-
* @throws {
|
|
64
|
+
* @throws {MppNetworkError} When `mainnet` is specified without a `secretKey`.
|
|
65
|
+
* @throws {MppFaucetError} When the devnet/testnet airdrop fails after retries.
|
|
42
66
|
*/
|
|
43
67
|
declare function createTestClient(config?: TestClientConfig): Promise<TestClient>;
|
|
44
68
|
/**
|
|
45
|
-
*
|
|
69
|
+
* Drop-in replacement for `fetch` with automatic Solana MPP payment.
|
|
46
70
|
*
|
|
47
|
-
* Uses a shared client instance
|
|
48
|
-
* Call `mppFetch.reset()` to discard the shared instance.
|
|
71
|
+
* Uses a shared client instance lazily created on first call (devnet by default).
|
|
72
|
+
* Call `mppFetch.reset()` to discard the shared instance and generate a new wallet.
|
|
49
73
|
*
|
|
50
74
|
* @example
|
|
51
75
|
* ```ts
|
|
52
76
|
* import { mppFetch } from "mpp-test-sdk";
|
|
53
77
|
*
|
|
54
|
-
* const res = await mppFetch("http://localhost:3001/api/
|
|
78
|
+
* const res = await mppFetch("http://localhost:3001/api/data");
|
|
55
79
|
* const data = await res.json();
|
|
56
80
|
* ```
|
|
57
81
|
*
|
|
58
|
-
* @throws {MppFaucetError} When the
|
|
59
|
-
* @throws {MppTimeoutError} When the
|
|
82
|
+
* @throws {MppFaucetError} When the devnet airdrop fails after retries.
|
|
83
|
+
* @throws {MppTimeoutError} When the full flow exceeds the timeout.
|
|
60
84
|
*/
|
|
61
85
|
declare function mppFetch(url: string, init?: RequestInit): Promise<Response>;
|
|
62
86
|
declare namespace mppFetch {
|
|
@@ -64,23 +88,46 @@ declare namespace mppFetch {
|
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
interface ChargeOptions {
|
|
67
|
-
/** Amount to charge in
|
|
91
|
+
/** Amount to charge in SOL (e.g. "0.001"). */
|
|
68
92
|
amount: string;
|
|
69
93
|
}
|
|
70
94
|
interface MppServer {
|
|
71
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Express middleware that requires SOL payment before passing to the route handler.
|
|
97
|
+
*
|
|
98
|
+
* - No receipt → 402 with `Payment-Request` header.
|
|
99
|
+
* - Valid receipt + on-chain confirmation → calls `next()`.
|
|
100
|
+
* - Invalid or insufficient payment → 403.
|
|
101
|
+
*/
|
|
72
102
|
charge: (opts: ChargeOptions) => RequestHandler;
|
|
103
|
+
/** The Solana address where payments are sent. */
|
|
104
|
+
recipientAddress: string;
|
|
105
|
+
/** Network this server is configured for. */
|
|
106
|
+
network: SolanaNetwork;
|
|
73
107
|
}
|
|
74
108
|
interface TestServerConfig {
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Solana network to connect to.
|
|
111
|
+
* - `"devnet"` (default) - Solana devnet.
|
|
112
|
+
* - `"testnet"` - Solana testnet.
|
|
113
|
+
* - `"mainnet"` - Solana mainnet (real SOL).
|
|
114
|
+
*/
|
|
115
|
+
network?: SolanaNetwork;
|
|
116
|
+
/** Server wallet keypair secret key. Auto-generated if omitted. */
|
|
117
|
+
secretKey?: Uint8Array;
|
|
118
|
+
/**
|
|
119
|
+
* Override the recipient Solana address (base58).
|
|
120
|
+
* Defaults to the server keypair's public key.
|
|
121
|
+
*/
|
|
122
|
+
recipientAddress?: string;
|
|
123
|
+
/** Override the Solana RPC endpoint. Takes precedence over `network`. */
|
|
124
|
+
rpcUrl?: string;
|
|
81
125
|
}
|
|
82
126
|
/**
|
|
83
|
-
* Create
|
|
127
|
+
* Create a Solana MPP-enabled Express server.
|
|
128
|
+
*
|
|
129
|
+
* Handles the HTTP 402 payment flow and verifies SOL transfers on-chain.
|
|
130
|
+
* No config needed - auto-generates a server wallet.
|
|
84
131
|
*
|
|
85
132
|
* @example
|
|
86
133
|
* ```ts
|
|
@@ -88,11 +135,15 @@ interface TestServerConfig {
|
|
|
88
135
|
* import { createTestServer } from "mpp-test-sdk";
|
|
89
136
|
*
|
|
90
137
|
* const app = express();
|
|
91
|
-
* const mpp = createTestServer({
|
|
92
|
-
*
|
|
138
|
+
* const mpp = createTestServer(); // or createTestServer({ network: "mainnet" })
|
|
139
|
+
*
|
|
140
|
+
* // Charge 0.001 SOL per request
|
|
141
|
+
* app.get("/api/data", mpp.charge({ amount: "0.001" }), (req, res) => {
|
|
142
|
+
* res.json({ data: "premium content" });
|
|
143
|
+
* });
|
|
93
144
|
* ```
|
|
94
145
|
*/
|
|
95
|
-
declare function createTestServer(config
|
|
146
|
+
declare function createTestServer(config?: TestServerConfig): MppServer;
|
|
96
147
|
|
|
97
148
|
declare class MppError extends Error {
|
|
98
149
|
constructor(message: string);
|
|
@@ -111,5 +162,9 @@ declare class MppTimeoutError extends MppError {
|
|
|
111
162
|
readonly timeoutMs: number;
|
|
112
163
|
constructor(url: string, timeoutMs: number);
|
|
113
164
|
}
|
|
165
|
+
declare class MppNetworkError extends MppError {
|
|
166
|
+
readonly network: string;
|
|
167
|
+
constructor(network: string, message?: string);
|
|
168
|
+
}
|
|
114
169
|
|
|
115
|
-
export { type ChargeOptions, MppError, MppFaucetError, MppPaymentError, type MppServer, MppTimeoutError, type PaymentStep, type TestClient, type TestClientConfig, type TestServerConfig, createTestClient, createTestServer, mppFetch };
|
|
170
|
+
export { type ChargeOptions, MppError, MppFaucetError, MppNetworkError, MppPaymentError, type MppServer, MppTimeoutError, type PaymentStep, type SolanaNetwork, type TestClient, type TestClientConfig, type TestServerConfig, createTestClient, createTestServer, mppFetch };
|