@tanakayuto/intmax402-express 0.3.1 → 0.3.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/dist/middleware.js +36 -5
- package/dist/verify-payment.js +33 -6
- package/package.json +2 -2
package/dist/middleware.js
CHANGED
|
@@ -18,9 +18,19 @@ function intmax402(config) {
|
|
|
18
18
|
return async (req, res, next) => {
|
|
19
19
|
try {
|
|
20
20
|
// Wait for payment verifier initialization if in progress
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
try {
|
|
22
|
+
if (initPromise) {
|
|
23
|
+
await initPromise;
|
|
24
|
+
initPromise = null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
res.status(503).json({
|
|
29
|
+
error: 'Payment verifier temporarily unavailable',
|
|
30
|
+
error_code: intmax402_core_1.INTMAX402_ERROR_CODES.INTMAX_NETWORK_UNAVAILABLE,
|
|
31
|
+
protocol: 'INTMAX402',
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
24
34
|
}
|
|
25
35
|
const authHeader = req.headers.authorization;
|
|
26
36
|
if (!authHeader) {
|
|
@@ -30,6 +40,7 @@ function intmax402(config) {
|
|
|
30
40
|
res.setHeader("WWW-Authenticate", (0, intmax402_core_1.buildWWWAuthenticate)(nonce, config));
|
|
31
41
|
res.status(statusCode).json({
|
|
32
42
|
error: config.mode === "payment" ? "Payment Required" : "Unauthorized",
|
|
43
|
+
error_code: intmax402_core_1.INTMAX402_ERROR_CODES.MISSING_AUTH_HEADER,
|
|
33
44
|
protocol: "INTMAX402",
|
|
34
45
|
mode: config.mode,
|
|
35
46
|
});
|
|
@@ -66,9 +77,29 @@ function intmax402(config) {
|
|
|
66
77
|
res.status(500).json({ error: "Server misconfigured: serverAddress and amount required for payment mode" });
|
|
67
78
|
return;
|
|
68
79
|
}
|
|
69
|
-
|
|
80
|
+
let paymentResult;
|
|
81
|
+
try {
|
|
82
|
+
paymentResult = await (0, verify_payment_1.verifyPayment)(credential.txHash, config.amount, config.serverAddress);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (err instanceof intmax402_core_1.INTMAX402Error) {
|
|
86
|
+
const status = err.code === intmax402_core_1.INTMAX402_ERROR_CODES.INTMAX_NETWORK_UNAVAILABLE ? 503 : 402;
|
|
87
|
+
res.status(status).json({
|
|
88
|
+
error: err.message,
|
|
89
|
+
error_code: err.code,
|
|
90
|
+
protocol: 'INTMAX402',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
res.status(402).json({
|
|
95
|
+
error: 'Payment verification failed',
|
|
96
|
+
protocol: 'INTMAX402',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
70
101
|
if (!paymentResult.valid) {
|
|
71
|
-
res.status(402).json({ error: paymentResult.error || "Payment verification failed" });
|
|
102
|
+
res.status(402).json({ error: paymentResult.error || "Payment verification failed", protocol: "INTMAX402" });
|
|
72
103
|
return;
|
|
73
104
|
}
|
|
74
105
|
}
|
package/dist/verify-payment.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.getPaymentVerifierAddress = getPaymentVerifierAddress;
|
|
|
5
5
|
exports.verifyPayment = verifyPayment;
|
|
6
6
|
exports._resetPaymentVerifier = _resetPaymentVerifier;
|
|
7
7
|
const intmax2_server_sdk_1 = require("intmax2-server-sdk");
|
|
8
|
+
const intmax402_core_1 = require("@tanakayuto/intmax402-core");
|
|
8
9
|
// Singleton IntMaxNodeClient
|
|
9
10
|
let client = null;
|
|
10
11
|
let loginPromise = null;
|
|
@@ -40,8 +41,16 @@ async function initPaymentVerifier(config) {
|
|
|
40
41
|
l1_rpc_url: config.l1_rpc_url,
|
|
41
42
|
loggerLevel: "warn",
|
|
42
43
|
});
|
|
43
|
-
loginPromise = client
|
|
44
|
+
loginPromise = client
|
|
45
|
+
.login()
|
|
46
|
+
.then(() => {
|
|
44
47
|
loginPromise = null;
|
|
48
|
+
})
|
|
49
|
+
.catch((err) => {
|
|
50
|
+
console.warn("[intmax402] INTMAX network login failed — payment verifier unavailable:", err instanceof Error ? err.message : String(err));
|
|
51
|
+
client = null;
|
|
52
|
+
loginPromise = null;
|
|
53
|
+
throw err;
|
|
45
54
|
});
|
|
46
55
|
await loginPromise;
|
|
47
56
|
}
|
|
@@ -52,7 +61,7 @@ function getPaymentVerifierAddress() {
|
|
|
52
61
|
}
|
|
53
62
|
async function verifyPayment(txHash, expectedAmount, serverAddress, tokenIndex) {
|
|
54
63
|
if (!client || !client.isLoggedIn) {
|
|
55
|
-
|
|
64
|
+
throw new intmax402_core_1.INTMAX402Error(intmax402_core_1.INTMAX402_ERROR_CODES.INTMAX_NETWORK_UNAVAILABLE, "Payment verifier temporarily unavailable. INTMAX network may be down.");
|
|
56
65
|
}
|
|
57
66
|
// Replay prevention: check if txHash was already used (or pending)
|
|
58
67
|
cleanupExpiredHashes();
|
|
@@ -71,23 +80,41 @@ async function verifyPayment(txHash, expectedAmount, serverAddress, tokenIndex)
|
|
|
71
80
|
transfers = transfers.concat(response2.items);
|
|
72
81
|
}
|
|
73
82
|
// Find matching transaction by digest
|
|
74
|
-
|
|
83
|
+
let match = transfers.find((tx) => tx.digest === txHash);
|
|
84
|
+
// Polling retry: if not found, retry up to 3 times with 5s delay
|
|
85
|
+
// (transfer may not be reflected immediately after submission)
|
|
86
|
+
if (!match) {
|
|
87
|
+
for (let retry = 0; retry < 3; retry++) {
|
|
88
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
89
|
+
const retryResponse = await client.fetchTransfers({ cursor: null, limit: 100 });
|
|
90
|
+
match = retryResponse.items.find((tx) => tx.digest === txHash);
|
|
91
|
+
if (match)
|
|
92
|
+
break;
|
|
93
|
+
// Also check next page on retry if needed
|
|
94
|
+
if (!match && retryResponse.pagination?.has_more && retryResponse.pagination?.next_cursor != null) {
|
|
95
|
+
const retryResponse2 = await client.fetchTransfers({ cursor: retryResponse.pagination.next_cursor, limit: 100 });
|
|
96
|
+
match = retryResponse2.items.find((tx) => tx.digest === txHash);
|
|
97
|
+
if (match)
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
75
102
|
if (!match) {
|
|
76
103
|
// Fix 1: Rollback on validation failure
|
|
77
104
|
usedTxHashes.delete(txHash);
|
|
78
|
-
|
|
105
|
+
throw new intmax402_core_1.INTMAX402Error(intmax402_core_1.INTMAX402_ERROR_CODES.PAYMENT_NOT_FOUND, "Transaction not found in recent transfers", { txHash });
|
|
79
106
|
}
|
|
80
107
|
// Verify recipient matches server address
|
|
81
108
|
if (match.to?.toLowerCase() !== serverAddress.toLowerCase()) {
|
|
82
109
|
// Fix 1: Rollback on validation failure
|
|
83
110
|
usedTxHashes.delete(txHash);
|
|
84
|
-
|
|
111
|
+
throw new intmax402_core_1.INTMAX402Error(intmax402_core_1.INTMAX402_ERROR_CODES.PAYMENT_RECIPIENT_MISMATCH, "Recipient does not match server address", { expected: serverAddress, got: match.to });
|
|
85
112
|
}
|
|
86
113
|
// Fix 2: Verify amount using BigInt comparison (allows >= expectedAmount)
|
|
87
114
|
if (BigInt(match.amount) < BigInt(expectedAmount)) {
|
|
88
115
|
// Fix 1: Rollback on validation failure
|
|
89
116
|
usedTxHashes.delete(txHash);
|
|
90
|
-
|
|
117
|
+
throw new intmax402_core_1.INTMAX402Error(intmax402_core_1.INTMAX402_ERROR_CODES.PAYMENT_AMOUNT_MISMATCH, `Amount mismatch: expected ${expectedAmount}, got ${match.amount}`, { expected: expectedAmount, got: match.amount });
|
|
91
118
|
}
|
|
92
119
|
// Verify token if specified
|
|
93
120
|
if (tokenIndex !== undefined && match.tokenIndex !== tokenIndex) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanakayuto/intmax402-express",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"typecheck": "tsc --noEmit"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@tanakayuto/intmax402-core": "0.3.
|
|
29
|
+
"@tanakayuto/intmax402-core": "0.3.2",
|
|
30
30
|
"ethers": "^6.16.0",
|
|
31
31
|
"intmax2-server-sdk": "^1.5.2"
|
|
32
32
|
},
|