@alleyboss/micropay-solana-x402-paywall 2.1.2 → 2.3.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 +32 -17
- package/dist/index.cjs +74 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +73 -15
- package/dist/index.js.map +1 -1
- package/dist/middleware/index.cjs +15 -3
- package/dist/middleware/index.cjs.map +1 -1
- package/dist/middleware/index.d.cts +2 -1
- package/dist/middleware/index.d.ts +2 -1
- package/dist/middleware/index.js +15 -3
- package/dist/middleware/index.js.map +1 -1
- package/dist/{nextjs-Bm272Jkj.d.cts → nextjs-BDyOqGAq.d.cts} +4 -1
- package/dist/{nextjs-BK0pVb9Y.d.ts → nextjs-CbX8_9yK.d.ts} +4 -1
- package/dist/pricing/index.cjs +7 -7
- package/dist/pricing/index.cjs.map +1 -1
- package/dist/pricing/index.js +7 -7
- package/dist/pricing/index.js.map +1 -1
- package/dist/solana/index.cjs +37 -2
- package/dist/solana/index.cjs.map +1 -1
- package/dist/solana/index.d.cts +5 -0
- package/dist/solana/index.d.ts +5 -0
- package/dist/solana/index.js +37 -2
- package/dist/solana/index.js.map +1 -1
- package/dist/x402/index.cjs +29 -1
- package/dist/x402/index.cjs.map +1 -1
- package/dist/x402/index.d.cts +23 -3
- package/dist/x402/index.d.ts +23 -3
- package/dist/x402/index.js +28 -2
- package/dist/x402/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ npm install @alleyboss/micropay-solana-x402-paywall @solana/web3.js
|
|
|
19
19
|
| Feature | Description |
|
|
20
20
|
|---------|-------------|
|
|
21
21
|
| 💰 **SOL & USDC Payments** | Native SOL and SPL tokens (USDC, USDT) |
|
|
22
|
-
| 🔐 **x402 Protocol** | HTTP 402 Payment
|
|
22
|
+
| 🔐 **x402 Protocol** | Full HTTP 402 compliance with `X-Payment-Required` headers |
|
|
23
23
|
| 🔑 **JWT Sessions** | Secure unlock tracking with anti-replay |
|
|
24
24
|
| 🛡️ **Signature Store** | Prevent double-spend at app layer |
|
|
25
25
|
| 🔌 **Express & Next.js** | Zero-boilerplate middleware |
|
|
@@ -82,27 +82,42 @@ import { getSolPrice, formatPriceDisplay, configurePricing } from '@alleyboss/mi
|
|
|
82
82
|
import { withRetry } from '@alleyboss/micropay-solana-x402-paywall/utils';
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
## 🔥 New in v2.
|
|
85
|
+
## 🔥 New in v2.2.0
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- **TDD Test Suite** — Comprehensive tests with vitest (must pass before npm publish)
|
|
87
|
+
### x402 Protocol Compliance
|
|
88
|
+
|
|
89
|
+
Full compliance with the [x402.org](https://x402.org) specification:
|
|
91
90
|
|
|
92
91
|
```typescript
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
import { create402Response, parsePaymentHeader, encodePaymentRequirement } from '@alleyboss/micropay-solana-x402-paywall/x402';
|
|
93
|
+
|
|
94
|
+
// Create 402 response with X-Payment-Required header
|
|
95
|
+
const response = create402Response({
|
|
96
|
+
scheme: 'exact',
|
|
97
|
+
network: 'solana-mainnet',
|
|
98
|
+
maxAmountRequired: '10000000',
|
|
99
|
+
payTo: 'CreatorWallet...',
|
|
100
|
+
resource: '/api/premium',
|
|
101
|
+
description: 'Premium content access',
|
|
102
|
+
maxTimeoutSeconds: 300,
|
|
103
|
+
asset: 'native',
|
|
104
|
+
});
|
|
105
|
+
// Response includes: X-Payment-Required: <base64-encoded-requirement>
|
|
103
106
|
|
|
107
|
+
// Parse X-Payment header from client
|
|
108
|
+
const payload = parsePaymentHeader(request.headers.get('x-payment'));
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Previous Features (v2.1.x)
|
|
112
|
+
|
|
113
|
+
- **RPC Fallback Support** — Automatic failover on primary RPC failure
|
|
114
|
+
- **Priority Fees** — Compute budget instructions for landing transactions faster
|
|
115
|
+
- **Versioned Transactions** — Full v0 transaction support with lookup tables
|
|
116
|
+
- **TDD Test Suite** — Comprehensive tests with vitest
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
104
119
|
// Priority fees
|
|
105
|
-
import { createPriorityFeeInstructions
|
|
120
|
+
import { createPriorityFeeInstructions } from '@alleyboss/micropay-solana-x402-paywall/solana';
|
|
106
121
|
|
|
107
122
|
const instructions = createPriorityFeeInstructions({
|
|
108
123
|
enabled: true,
|
package/dist/index.cjs
CHANGED
|
@@ -148,7 +148,8 @@ async function verifyPayment(params) {
|
|
|
148
148
|
expectedRecipient,
|
|
149
149
|
expectedAmount,
|
|
150
150
|
maxAgeSeconds = 300,
|
|
151
|
-
clientConfig
|
|
151
|
+
clientConfig,
|
|
152
|
+
signatureStore
|
|
152
153
|
} = params;
|
|
153
154
|
if (!isValidSignature(signature)) {
|
|
154
155
|
return { valid: false, confirmed: false, signature, error: "Invalid signature format" };
|
|
@@ -159,6 +160,12 @@ async function verifyPayment(params) {
|
|
|
159
160
|
if (expectedAmount <= 0n) {
|
|
160
161
|
return { valid: false, confirmed: false, signature, error: "Invalid expected amount" };
|
|
161
162
|
}
|
|
163
|
+
if (signatureStore) {
|
|
164
|
+
const isUsed = await signatureStore.hasBeenUsed(signature);
|
|
165
|
+
if (isUsed) {
|
|
166
|
+
return { valid: false, confirmed: true, signature, error: "Signature already used" };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
162
169
|
const effectiveMaxAge = Math.min(Math.max(maxAgeSeconds, 60), 3600);
|
|
163
170
|
const connection = getConnection(clientConfig);
|
|
164
171
|
try {
|
|
@@ -267,8 +274,6 @@ function solToLamports(sol) {
|
|
|
267
274
|
}
|
|
268
275
|
return BigInt(Math.floor(sol * web3_js.LAMPORTS_PER_SOL));
|
|
269
276
|
}
|
|
270
|
-
|
|
271
|
-
// src/solana/spl.ts
|
|
272
277
|
var SIGNATURE_REGEX2 = /^[1-9A-HJ-NP-Za-km-z]{87,88}$/;
|
|
273
278
|
var WALLET_REGEX2 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
274
279
|
function resolveMintAddress(asset, network) {
|
|
@@ -362,8 +367,15 @@ async function verifySPLPayment(params) {
|
|
|
362
367
|
expectedAmount,
|
|
363
368
|
asset,
|
|
364
369
|
clientConfig,
|
|
365
|
-
maxAgeSeconds = 300
|
|
370
|
+
maxAgeSeconds = 300,
|
|
371
|
+
signatureStore
|
|
366
372
|
} = params;
|
|
373
|
+
if (signatureStore) {
|
|
374
|
+
const isUsed = await signatureStore.hasBeenUsed(signature);
|
|
375
|
+
if (isUsed) {
|
|
376
|
+
return { valid: false, confirmed: true, signature, error: "Signature already used" };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
367
379
|
if (!SIGNATURE_REGEX2.test(signature)) {
|
|
368
380
|
return { valid: false, confirmed: false, signature, error: "Invalid signature format" };
|
|
369
381
|
}
|
|
@@ -408,6 +420,27 @@ async function verifySPLPayment(params) {
|
|
|
408
420
|
error: "No valid token transfer to recipient found"
|
|
409
421
|
};
|
|
410
422
|
}
|
|
423
|
+
if (transfer.to) {
|
|
424
|
+
try {
|
|
425
|
+
const destinationInfo = await connection.getParsedAccountInfo(new web3_js.PublicKey(transfer.to));
|
|
426
|
+
const owner = destinationInfo.value?.data?.parsed?.info?.owner;
|
|
427
|
+
if (owner && owner !== expectedRecipient) {
|
|
428
|
+
return {
|
|
429
|
+
valid: false,
|
|
430
|
+
confirmed: true,
|
|
431
|
+
signature,
|
|
432
|
+
error: "Recipient mismatch: Token account not owned by merchant"
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
} catch (e) {
|
|
436
|
+
return {
|
|
437
|
+
valid: false,
|
|
438
|
+
confirmed: true,
|
|
439
|
+
signature,
|
|
440
|
+
error: "Could not verify token account owner"
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
411
444
|
if (transfer.mint !== mintAddress) {
|
|
412
445
|
return {
|
|
413
446
|
valid: false,
|
|
@@ -829,9 +862,28 @@ function parsePaymentHeader(header) {
|
|
|
829
862
|
return null;
|
|
830
863
|
}
|
|
831
864
|
}
|
|
865
|
+
function encodePaymentRequirement(requirement) {
|
|
866
|
+
return Buffer.from(JSON.stringify(requirement)).toString("base64");
|
|
867
|
+
}
|
|
832
868
|
function encodePaymentResponse(response) {
|
|
833
869
|
return Buffer.from(JSON.stringify(response)).toString("base64");
|
|
834
870
|
}
|
|
871
|
+
function create402Response(requirement, body) {
|
|
872
|
+
const headers = new Headers({
|
|
873
|
+
"Content-Type": "application/json",
|
|
874
|
+
"X-Payment-Required": encodePaymentRequirement(requirement)
|
|
875
|
+
});
|
|
876
|
+
const responseBody = body || {
|
|
877
|
+
error: "Payment Required",
|
|
878
|
+
message: "This resource requires payment to access",
|
|
879
|
+
x402Version: 1,
|
|
880
|
+
accepts: [requirement]
|
|
881
|
+
};
|
|
882
|
+
return new Response(JSON.stringify(responseBody), {
|
|
883
|
+
status: 402,
|
|
884
|
+
headers
|
|
885
|
+
});
|
|
886
|
+
}
|
|
835
887
|
|
|
836
888
|
// src/store/memory.ts
|
|
837
889
|
function createMemoryStore(options = {}) {
|
|
@@ -976,16 +1028,22 @@ function createPaywallMiddleware(config2) {
|
|
|
976
1028
|
const sessionToken = cookies[cookieName];
|
|
977
1029
|
const result = await checkPaywallAccess(path, sessionToken, config2);
|
|
978
1030
|
if (!result.allowed && result.requiresPayment) {
|
|
1031
|
+
const headers = {
|
|
1032
|
+
"Content-Type": "application/json"
|
|
1033
|
+
};
|
|
1034
|
+
if (config2.paymentRequirement) {
|
|
1035
|
+
const requirement = typeof config2.paymentRequirement === "function" ? config2.paymentRequirement(path) : config2.paymentRequirement;
|
|
1036
|
+
headers["X-Payment-Required"] = encodePaymentRequirement(requirement);
|
|
1037
|
+
}
|
|
979
1038
|
const body = config2.custom402Response ? config2.custom402Response(path) : {
|
|
980
1039
|
error: "Payment Required",
|
|
981
1040
|
message: "This resource requires payment to access",
|
|
1041
|
+
x402Version: 1,
|
|
982
1042
|
path
|
|
983
1043
|
};
|
|
984
1044
|
return new Response(JSON.stringify(body), {
|
|
985
1045
|
status: 402,
|
|
986
|
-
headers
|
|
987
|
-
"Content-Type": "application/json"
|
|
988
|
-
}
|
|
1046
|
+
headers
|
|
989
1047
|
});
|
|
990
1048
|
}
|
|
991
1049
|
return null;
|
|
@@ -1261,14 +1319,14 @@ async function getSolPrice() {
|
|
|
1261
1319
|
}
|
|
1262
1320
|
}
|
|
1263
1321
|
if (cachedPrice) {
|
|
1264
|
-
return
|
|
1322
|
+
return {
|
|
1323
|
+
...cachedPrice,
|
|
1324
|
+
source: `${cachedPrice.source} (stale)`
|
|
1325
|
+
};
|
|
1265
1326
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
fetchedAt: /* @__PURE__ */ new Date(),
|
|
1270
|
-
source: "fallback"
|
|
1271
|
-
};
|
|
1327
|
+
throw new Error(
|
|
1328
|
+
"Failed to fetch SOL price from all providers. Configure a custom provider or ensure network connectivity."
|
|
1329
|
+
);
|
|
1272
1330
|
}
|
|
1273
1331
|
async function lamportsToUsd(lamports) {
|
|
1274
1332
|
const { solPrice } = await getSolPrice();
|
|
@@ -1314,6 +1372,7 @@ exports.checkPaywallAccess = checkPaywallAccess;
|
|
|
1314
1372
|
exports.clearPriceCache = clearPriceCache;
|
|
1315
1373
|
exports.configurePricing = configurePricing;
|
|
1316
1374
|
exports.create402Headers = create402Headers;
|
|
1375
|
+
exports.create402Response = create402Response;
|
|
1317
1376
|
exports.create402ResponseBody = create402ResponseBody;
|
|
1318
1377
|
exports.createMemoryStore = createMemoryStore;
|
|
1319
1378
|
exports.createPaymentFlow = createPaymentFlow;
|
|
@@ -1324,6 +1383,7 @@ exports.createRedisStore = createRedisStore;
|
|
|
1324
1383
|
exports.createSession = createSession;
|
|
1325
1384
|
exports.decodePaymentRequired = decodePaymentRequired;
|
|
1326
1385
|
exports.encodePaymentRequired = encodePaymentRequired;
|
|
1386
|
+
exports.encodePaymentRequirement = encodePaymentRequirement;
|
|
1327
1387
|
exports.encodePaymentResponse = encodePaymentResponse;
|
|
1328
1388
|
exports.estimatePriorityFee = estimatePriorityFee;
|
|
1329
1389
|
exports.fetchLookupTables = fetchLookupTables;
|