mtok-verify 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/onchain.js +26 -14
- package/package.json +1 -1
package/onchain.js
CHANGED
|
@@ -98,6 +98,7 @@ export function createOnchainVerifier({ rpcUrl, rpcUrls, usdcAddress, expectedCh
|
|
|
98
98
|
// a few times before giving up. Bounded so a genuinely-missing tx still fails fast.
|
|
99
99
|
receiptRetries = 3, receiptRetryMs = 700, sleepImpl = (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
100
100
|
nowMs = () => Date.now(), // injectable clock for the optional draw-age guard (verifyDrawPaid maxPaidAgeMs).
|
|
101
|
+
rpcTimeoutMs = 5000,
|
|
101
102
|
} = {}) {
|
|
102
103
|
// Accept one URL, a comma-separated list, or an array — rpc() rotates across them
|
|
103
104
|
// so a single rate-limited or down RPC can't strand a settlement verification (#108).
|
|
@@ -110,6 +111,7 @@ export function createOnchainVerifier({ rpcUrl, rpcUrls, usdcAddress, expectedCh
|
|
|
110
111
|
method: 'POST',
|
|
111
112
|
headers: { 'content-type': 'application/json' },
|
|
112
113
|
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
|
|
114
|
+
signal: AbortSignal.timeout(rpcTimeoutMs),
|
|
113
115
|
});
|
|
114
116
|
if (!res.ok) throw apiError(502, 'rpc_error', `chain RPC returned ${res.status}`);
|
|
115
117
|
const body = await res.json();
|
|
@@ -145,12 +147,14 @@ export function createOnchainVerifier({ rpcUrl, rpcUrls, usdcAddress, expectedCh
|
|
|
145
147
|
async function assertChain() {
|
|
146
148
|
if (!chainPinned) return true; // no (valid) expected chain configured -> today's behavior
|
|
147
149
|
if (chainOkUrls.size) return true; // already found at least one on-chain url (cached)
|
|
150
|
+
let timeout;
|
|
148
151
|
for (const url of urls) {
|
|
149
152
|
try {
|
|
150
153
|
const hex = await rpcOn(url, 'eth_chainId', []);
|
|
151
154
|
if (typeof hex === 'string' && parseInt(hex, 16) === expChain) chainOkUrls.add(url);
|
|
152
|
-
} catch {
|
|
155
|
+
} catch (error) { if (error.name === 'TimeoutError') timeout = error; }
|
|
153
156
|
}
|
|
157
|
+
if (!chainOkUrls.size && timeout) throw timeout;
|
|
154
158
|
return chainOkUrls.size > 0;
|
|
155
159
|
}
|
|
156
160
|
|
|
@@ -270,21 +274,29 @@ export function createOnchainVerifier({ rpcUrl, rpcUrls, usdcAddress, expectedCh
|
|
|
270
274
|
if (requestHash != null && lc(event.requestHash) !== lc(requestHash)) return { ok: false, reason: 'request_hash_mismatch' };
|
|
271
275
|
if (BigInt(event.sellerUsdAtomic) < BigInt(minSellerAtomic)) return { ok: false, reason: 'amount_too_low' };
|
|
272
276
|
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
// lost. Fail CLOSED on an unreadable block (the receipt just read fine on this same
|
|
276
|
-
// RPC pool, so this is a money guard, not a liveness knob). Opt-in: unset => no
|
|
277
|
-
// extra RPC, exact current behavior.
|
|
277
|
+
// Once redemption records expire, verified payment age is the replay boundary.
|
|
278
|
+
// An unreadable timestamp is retryable uncertainty, never permission to spend.
|
|
278
279
|
const maxAge = Number(maxPaidAgeMs);
|
|
279
280
|
if (Number.isFinite(maxAge) && maxAge > 0) {
|
|
280
|
-
const bn = matchedLog?.blockNumber;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
281
|
+
const bn = matchedLog?.blockNumber ?? got.receipt.blockNumber;
|
|
282
|
+
let paidAtMs = null;
|
|
283
|
+
if (bn != null) {
|
|
284
|
+
for (let i = 0; i <= receiptRetries; i++) {
|
|
285
|
+
try {
|
|
286
|
+
const blockTag = '0x' + BigInt(bn).toString(16);
|
|
287
|
+
const block = await rpc('eth_getBlockByNumber', [blockTag, false]);
|
|
288
|
+
if (block?.timestamp != null) {
|
|
289
|
+
const timestamp = Number(BigInt(block.timestamp)) * 1000;
|
|
290
|
+
if (Number.isSafeInteger(timestamp) && timestamp >= 0 && timestamp <= nowMs() + 60_000) {
|
|
291
|
+
paidAtMs = timestamp;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} catch { /* transient: fall through to retry */ }
|
|
296
|
+
if (i < receiptRetries) await sleepImpl(receiptRetryMs);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (paidAtMs == null) return { ok: false, reason: 'payment_age_unavailable' };
|
|
288
300
|
if (nowMs() - paidAtMs > maxAge) return { ok: false, reason: 'payment_too_old' };
|
|
289
301
|
}
|
|
290
302
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mtok-verify",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "The on-chain verify core for mtok.market sellers: decode + verify a MtokDripLedger DrawPaid receipt (and its USDC transfer legs) against a paid draw, with the canonical DrawPaid topic set. Dependency-free, runs anywhere fetch runs (node, a CF Worker). The SSOT the platform + reference relay + house seller all share.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|