@insumermodel/mppx-condition-gate 3.0.2 → 3.1.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 CHANGED
@@ -15,6 +15,8 @@ Free access requires a payer your payment method has **proven** controls the req
15
15
  5. **Pass** → free receipt returned (`reference: "condition-gate:free:{attestationId}"`)
16
16
  6. **Fail, or no proven payer** → the normal paid path runs
17
17
 
18
+ Each credential gets free access at most once. See [Single-use credentials](#single-use-credentials) for what the gate records and the limits of that guarantee.
19
+
18
20
  The signed attestation is verifiable offline via [JWKS](https://insumermodel.com/.well-known/jwks.json). The adapter does not re-sign or wrap the result; the signature on the attestation is the one InsumerAPI produced. Since 2026-09-01 every attest response also carries an ML-DSA-65 post-quantum companion (`pqSig`, `pqKid`) beside `sig` and `kid`, added without changing them; the `InsumerAttestation` type declares `pqSig` and `pqKid` beside `sig` and `kid`, and `insumer-verify` 1.8.1+ reports the companion as a fifth verdict.
19
21
 
20
22
  ### Why you must supply the payer
@@ -32,18 +34,19 @@ npm install @insumermodel/mppx-condition-gate
32
34
  ## Usage
33
35
 
34
36
  ```ts
35
- import { Mppx, tempo } from 'mppx/server'
37
+ import { Mppx, evm } from 'mppx/server'
36
38
  import { conditionGate } from '@insumermodel/mppx-condition-gate'
37
39
 
38
- const tempoCharge = tempo({
39
- currency: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
40
- recipient: '0xYourAddress',
40
+ const evmCharge = evm.charge({
41
+ currency: evm.assets.base.USDC,
42
+ recipient: '0xYourAddress', // your checksummed receiving address
43
+ x402: { facilitator: 'https://your-facilitator.example' },
41
44
  })
42
45
 
43
- const gatedCharge = conditionGate(tempoCharge, {
46
+ const gatedCharge = conditionGate(evmCharge, {
44
47
  apiKey: process.env.INSUMER_API_KEY,
45
48
  // Return only a payer this method has PROVEN for this request, else null.
46
- // The argument is mppx's method-specific validation `details`.
49
+ // mppx's EVM charge reports the signer it recovered as `payer`.
47
50
  provenPayer: (details) => (details as { payer?: string }).payer ?? null,
48
51
  conditions: [{
49
52
  type: 'nft_ownership',
@@ -52,10 +55,69 @@ const gatedCharge = conditionGate(tempoCharge, {
52
55
  }],
53
56
  })
54
57
 
55
- const mppx = Mppx.create({ methods: [gatedCharge] })
58
+ const mppx = Mppx.create({ methods: [gatedCharge], secretKey: process.env.MPP_SECRET_KEY })
59
+ ```
60
+
61
+ Works with any framework (Hono, Express, Elysia, Next.js). The adapter wraps `Method.Server`, so no middleware changes needed.
62
+
63
+ ### Which credentials can get free access
64
+
65
+ Free access needs a method that exposes mppx's `validate` hook, a resolver that returns the payer that method proved, and a credential the gate can identify. mppx added `validate` in 0.8.14 for the Tempo charge and in 0.9.3 for the EVM charge; on older versions every request pays. In mppx 0.9.3:
66
+
67
+ - **EVM charge**, native and x402: EIP-3009 authorizations. The payer is the recovered signer, `details.payer`.
68
+ - **Tempo charge, proof credentials**: the payer is `details.sender`, checked against a signature over the challenge. Tempo accepts proofs only on zero-amount challenges.
69
+ - **Tempo hash and transaction credentials** are payments in their own right, a transfer already on chain or one waiting to be sent. They take the paid path, where the Tempo method's own replay protection applies.
70
+ - **Stripe** exposes no `validate` hook, so it is never gated.
71
+
72
+ ## Single-use credentials
73
+
74
+ When a payment settles, mppx spends the credential: the on-chain nonce is used, or the method records it. A free grant never settles, so the gate keeps its own record:
75
+
76
+ - It records a credential when it grants free access. It also records an EIP-3009 authorization just before the paid path settles it, so a copy presented while that payment is in flight is refused.
77
+ - A recorded credential is refused with a verification error. It is not passed on to payment, which would charge the original holder for someone else's request.
78
+ - If settlement throws, that record is released, so the holder can retry with the same credential.
79
+ - An EIP-3009 authorization is identified by its signer and nonce, and a Tempo proof by its challenge and payer. Neither changes when a signature is re-encoded or the challenge is rebuilt, and two users never share one. A native credential signs its challenge, so the same wallet signing the same native challenge twice (in the same millisecond, or on a route with a fixed `expires`) produces one credential, and the second is refused. x402 clients choose a fresh nonce each time.
80
+ - A Tempo proof gets free access once, but a repeated proof is not refused, and a proof that takes the paid path is not recorded. Proofs are only accepted on zero-amount routes, which move no value, so the Tempo method and its own replay rules (its `store` option) decide whether it may be presented again.
81
+
82
+ A record is kept until the credential expires, plus 60 seconds. A credential whose remaining validity is longer than `maxCredentialLifetimeSeconds` (600 by default) gets no free access and takes the paid path:
83
+
84
+ - **EIP-3009 authorization:** its `validBefore`. An x402 client sets it from the route's `maxTimeoutSeconds` (300 by default), whatever the challenge expiry, but a client can sign a longer one. An x402 route whose `maxTimeoutSeconds` is above the cap gives qualifying visitors no free access unless you raise the option. A native client sets it to the challenge's `expires` (5 minutes by default), so a native route configured with an `expires` longer than the cap gives no free access unless you raise the option.
85
+ - **Tempo proof:** the challenge's `expires`.
86
+
87
+ ### Limits of the guarantee
88
+
89
+ Each case below allows at most one extra use of a credential:
90
+
91
+ - **Settlement throws after the transfer actually happened.** The record is released, so the credential can be presented again. If its payer now meets the conditions, that presentation is free.
92
+ - **The default store's payment records are full** (100,000 live). Later payments are not recorded, so a settled credential can come back once for free access.
93
+ - **A caller-supplied store cannot be read or written.** The request gets no free access and pays without a record. A credential that already got free access can then be settled for a copy's request, charging its holder once, which is what bare mppx does with any credential that has not settled.
94
+
95
+ Concurrent copies of one credential may each spend an attestation before one of them is granted.
96
+
97
+ ### The default store
98
+
99
+ The default record lives in memory and is **shared by every gated route in the process**. It holds up to 100,000 live free grants, at most 5,000 per wallet, and up to 100,000 payment records. A wallet over its share, or any wallet once the free-grant capacity is full, pays instead.
100
+
101
+ The default store is not a defense against someone determined to deny free access to others. Free credentials cost nothing to sign, so about 20 qualifying addresses can fill the free-grant capacity. So can one qualifying asset moved between 20 addresses, because a passing attestation is cached for `cacheTtlSeconds`. Every other qualifying visitor in that process then pays the route price until those credentials expire, up to `maxCredentialLifetimeSeconds` plus a minute. Use the default store for development and low-stakes routes.
102
+
103
+ **For production traffic, or when the route runs on more than one instance, pass a shared store with its own capacity.** The store must claim atomically and remove expired entries. Options:
104
+
105
+ - `Store.redis`, `Store.upstash` or `Store.cloudflare`, built from a client that exposes an atomic `update`. Confirm that your backend expires keys.
106
+ - Any store with an atomic `tryClaim`.
107
+
108
+ mppx's `Store.memory()` never removes expired entries, so use it only in tests. A store that is not atomic lets concurrent presentations through. Give the store `delete` (or `update`) so a failed payment can be released.
109
+
110
+ ```ts
111
+ import { Store } from 'mppx/server'
112
+
113
+ const gatedCharge = conditionGate(evmCharge, {
114
+ provenPayer: (details) => (details as { payer?: string }).payer ?? null,
115
+ conditions: [ ... ],
116
+ replayStore: Store.redis(atomicRedisClient), // the client must provide `update`
117
+ })
56
118
  ```
57
119
 
58
- Works with any framework (Hono, Express, Elysia, Next.js) and any payment method (tempo, stripe). The adapter wraps `Method.Server`, so no middleware changes needed.
120
+ Refusals are mppx's own `VerificationFailedError`, returned as HTTP 402. mppx is a peer dependency; if your install ends up with two copies of it, a refusal surfaces as an internal error (HTTP 500) instead.
59
121
 
60
122
  ## Condition types
61
123
 
@@ -182,6 +244,9 @@ Either way, set `INSUMER_API_KEY` as an environment variable in your runtime.
182
244
  | `matchMode` | `'any' \| 'all'` | `'any'` | Wallet must satisfy any or all conditions |
183
245
  | `cacheTtlSeconds` | `number` | `300` | In-memory cache TTL |
184
246
  | `apiBaseUrl` | `string` | `https://api.insumermodel.com` | API base URL override |
247
+ | `provenPayer` | `(details) => ProvenPayer` | none | Returns the payer the method proved; required for free access |
248
+ | `replayStore` | `AtomicStore` | bounded in-memory, per process | Records used credentials; must be atomic; share it across instances |
249
+ | `maxCredentialLifetimeSeconds` | `number` | `600` | Longest remaining validity a credential may have and still get free access |
185
250
 
186
251
  ## Supported chains
187
252
 
package/dist/index.d.ts CHANGED
@@ -133,6 +133,47 @@ export type ConditionGateOptions = {
133
133
  * with `format: "jwt"` directly if you need the `jwt` and `pqJwt` tokens.
134
134
  */
135
135
  jwt?: boolean;
136
+ /**
137
+ * Where the gate records credentials it has accepted, so each one gets
138
+ * through at most once. Defaults to a bounded in-process store.
139
+ *
140
+ * Pass a shared store when the route runs on more than one instance, or a
141
+ * credential used on one instance can be presented again on another. The
142
+ * store must claim atomically and remove expired entries: `Store.redis`,
143
+ * `Store.upstash` or `Store.cloudflare` from `mppx/server` built from a client
144
+ * that exposes an atomic `update`, or any store with an atomic `tryClaim`.
145
+ * mppx's `Store.memory()` never removes expired entries; use it only in tests.
146
+ */
147
+ replayStore?: ReplayStore;
148
+ /**
149
+ * Longest remaining validity, in seconds, a credential may have and still be
150
+ * given free access. Defaults to 600. The gate has to remember a credential
151
+ * until it expires, and x402 clients choose their own expiry, so longer-lived
152
+ * credentials take the paid path instead.
153
+ */
154
+ maxCredentialLifetimeSeconds?: number;
155
+ };
156
+ /**
157
+ * An atomic claim store. Structurally compatible with mppx's `AtomicStore`:
158
+ * `tryClaim` is used when present, otherwise `update`. `get`, when present,
159
+ * lets the gate refuse a repeat before spending an attestation on it, and
160
+ * `delete` (or `update`) lets it release a credential whose payment failed.
161
+ */
162
+ export type ReplayStore = {
163
+ get?: (key: string) => unknown | Promise<unknown>;
164
+ delete?: (key: string) => void | Promise<void>;
165
+ tryClaim?: (key: string, expires: number) => boolean | Promise<boolean>;
166
+ update?: <result>(key: string, fn: (current: any) => {
167
+ op: 'noop';
168
+ result: result;
169
+ } | {
170
+ op: 'set';
171
+ value: any;
172
+ result: result;
173
+ } | {
174
+ op: 'delete';
175
+ result: result;
176
+ }) => Promise<result>;
136
177
  };
137
178
  export type InsumerAttestation = {
138
179
  ok: boolean;
@@ -174,7 +215,7 @@ export type InsumerAttestation = {
174
215
  creditsCharged: number;
175
216
  };
176
217
  };
177
- /** Clear the in-memory ownership cache. Useful in tests. */
218
+ /** Clear the in-memory ownership cache and the default replay store. Useful in tests. */
178
219
  export declare function clearConditionGateCache(): void;
179
220
  /**
180
221
  * Extracts an EVM address from a `did:pkh:eip155:{chainId}:{address}` string.
@@ -212,6 +253,16 @@ export declare function normalizeProvenPayer(payer: ProvenPayer): {
212
253
  * credential that fails validation, conditions not met, or an attestation
213
254
  * error. There is no path from an unproven wallet to a free receipt.
214
255
  *
256
+ * Each credential gets free access at most once. The gate records a
257
+ * credential when it grants free access, and records an authorization just
258
+ * before the paid path settles it; a recorded credential is refused with a
259
+ * verification error rather than passed on to payment. A payment that fails is
260
+ * released, so it can be retried. See the README for the limits of this. Free access is given only to credentials the gate can identify
261
+ * (EIP-3009 authorizations and Tempo proofs) whose remaining validity is within
262
+ * `maxCredentialLifetimeSeconds`. The default store is bounded and shared by
263
+ * every gated route in the process; pass a shared `replayStore` for production
264
+ * traffic or when the route runs on more than one instance.
265
+ *
215
266
  * Conditions are evaluated across the 35 chains this adapter reaches (32 EVM +
216
267
  * Solana + XRPL + Bitcoin; the engine itself covers 38). Six condition types
217
268
  * are typed here: token_balance, nft_ownership, eas_attestation, farcaster_id,
@@ -226,8 +277,9 @@ export declare function normalizeProvenPayer(payer: ProvenPayer): {
226
277
  * ```ts
227
278
  * import { conditionGate } from '@insumermodel/mppx-condition-gate'
228
279
  *
229
- * const gated = conditionGate(tempoCharge, {
280
+ * const gated = conditionGate(evmCharge, {
230
281
  * // Return only a payer this method has proven for THIS request.
282
+ * // mppx's EVM charge reports the recovered signer as `payer`.
231
283
  * provenPayer: (details) => (details as { payer?: string }).payer ?? null,
232
284
  * conditions: [
233
285
  * { type: 'eas_attestation', template: 'coinbase_verified_account', chainId: 8453 },
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { Errors } from 'mppx';
1
2
  const cache = new Map();
2
3
  function conditionSortKey(c) {
3
4
  if (c.type === 'farcaster_id')
@@ -15,9 +16,184 @@ function cacheKey(address, conditions) {
15
16
  });
16
17
  return `${address.toLowerCase()}:${JSON.stringify(sorted)}`;
17
18
  }
18
- /** Clear the in-memory ownership cache. Useful in tests. */
19
+ /** Clear the in-memory ownership cache and the default replay store. Useful in tests. */
19
20
  export function clearConditionGateCache() {
20
21
  cache.clear();
22
+ defaultMarkers.clear();
23
+ freeCountByOwner.clear();
24
+ freeCount = 0;
25
+ paidCount = 0;
26
+ nextSweepAt = 0;
27
+ }
28
+ // ---------------------------------------------------------------------------
29
+ // Single-use credentials
30
+ // ---------------------------------------------------------------------------
31
+ // mppx makes a credential single-use when it settles: the on-chain nonce is
32
+ // spent, or the method claims the hash or proof in its store. `validate` is
33
+ // non-mutating by design, so a credential that is granted free access and
34
+ // never settled would otherwise stay usable by anyone who holds a copy.
35
+ /** Kept this long past the credential's own expiry, to absorb clock skew. */
36
+ const REPLAY_SKEW_MS = 60 * 1000;
37
+ const DEFAULT_MAX_CREDENTIAL_LIFETIME_SECONDS = 600;
38
+ /** Default store: live free grants across the process, and per wallet. */
39
+ const DEFAULT_STORE_MAX_FREE = 100_000;
40
+ const DEFAULT_STORE_MAX_FREE_PER_WALLET = 5_000;
41
+ /** Default store: payments it remembers. Past this it stops recording payments. */
42
+ const DEFAULT_STORE_MAX_PAID = 100_000;
43
+ const DEFAULT_STORE_SWEEP_MS = 60_000;
44
+ const defaultMarkers = new Map();
45
+ const freeCountByOwner = new Map();
46
+ let freeCount = 0;
47
+ let paidCount = 0;
48
+ let nextSweepAt = 0;
49
+ function forgetDefault(key) {
50
+ const marker = defaultMarkers.get(key);
51
+ if (!marker)
52
+ return;
53
+ defaultMarkers.delete(key);
54
+ if (marker.kind === 'paid') {
55
+ paidCount--;
56
+ return;
57
+ }
58
+ freeCount--;
59
+ const remaining = (freeCountByOwner.get(marker.owner) ?? 1) - 1;
60
+ if (remaining <= 0)
61
+ freeCountByOwner.delete(marker.owner);
62
+ else
63
+ freeCountByOwner.set(marker.owner, remaining);
64
+ }
65
+ const defaultRecorder = {
66
+ async isTaken(key) {
67
+ const marker = defaultMarkers.get(key);
68
+ return marker !== undefined && marker.expires > Date.now();
69
+ },
70
+ async claim(key, expires, owner, kind) {
71
+ const now = Date.now();
72
+ if (now >= nextSweepAt) {
73
+ for (const [k, marker] of defaultMarkers)
74
+ if (marker.expires <= now)
75
+ forgetDefault(k);
76
+ nextSweepAt = now + DEFAULT_STORE_SWEEP_MS;
77
+ }
78
+ const current = defaultMarkers.get(key);
79
+ if (current !== undefined) {
80
+ if (current.expires > now)
81
+ return false;
82
+ forgetDefault(key);
83
+ }
84
+ if (kind === 'free') {
85
+ const held = freeCountByOwner.get(owner) ?? 0;
86
+ if (freeCount >= DEFAULT_STORE_MAX_FREE || held >= DEFAULT_STORE_MAX_FREE_PER_WALLET) {
87
+ throw new Error('mppx-condition-gate: default replay store has no room for this wallet');
88
+ }
89
+ freeCount++;
90
+ freeCountByOwner.set(owner, held + 1);
91
+ }
92
+ else {
93
+ if (paidCount >= DEFAULT_STORE_MAX_PAID) {
94
+ throw new Error('mppx-condition-gate: default replay store is full');
95
+ }
96
+ paidCount++;
97
+ }
98
+ defaultMarkers.set(key, { expires, owner, kind });
99
+ return true;
100
+ },
101
+ async release(key) {
102
+ forgetDefault(key);
103
+ },
104
+ };
105
+ function isReplayMarker(value) {
106
+ return (typeof value === 'object' &&
107
+ value !== null &&
108
+ typeof value.expires === 'number' &&
109
+ value.type === 'mppx:replay');
110
+ }
111
+ async function tryClaim(store, key, expires) {
112
+ if (typeof store.tryClaim === 'function')
113
+ return store.tryClaim(key, expires);
114
+ // Same marker as mppx's Store.tryClaim. A live marker, or any other stored
115
+ // value, counts as taken; an empty slot or an expired marker does not.
116
+ return store.update(key, (current) => {
117
+ const empty = current === null || current === undefined || current === false;
118
+ const taken = !empty && (!isReplayMarker(current) || current.expires > Date.now());
119
+ return taken
120
+ ? { op: 'noop', result: false }
121
+ : { op: 'set', value: { expires, type: 'mppx:replay' }, result: true };
122
+ });
123
+ }
124
+ function storeRecorder(store) {
125
+ return {
126
+ async isTaken(key) {
127
+ // Advisory only: the atomic claim decides. Anything but a live marker reads as free.
128
+ if (typeof store.get !== 'function')
129
+ return false;
130
+ const current = await store.get(key);
131
+ return isReplayMarker(current) && current.expires > Date.now();
132
+ },
133
+ async claim(key, expires) {
134
+ return tryClaim(store, key, expires);
135
+ },
136
+ async release(key) {
137
+ if (typeof store.delete === 'function') {
138
+ await store.delete(key);
139
+ return;
140
+ }
141
+ if (typeof store.update === 'function') {
142
+ await store.update(key, (current) => isReplayMarker(current)
143
+ ? { op: 'delete', result: undefined }
144
+ : { op: 'noop', result: undefined });
145
+ }
146
+ },
147
+ };
148
+ }
149
+ function lowerHex(value) {
150
+ return typeof value === 'string' && /^0x[0-9a-fA-F]+$/.test(value) ? value.toLowerCase() : null;
151
+ }
152
+ /**
153
+ * One key that identifies the credential, or null when the gate cannot
154
+ * identify it safely, in which case it gets no free access.
155
+ *
156
+ * - EIP-3009 authorization (mppx EVM charge, native and x402): signer + nonce.
157
+ * Neither changes when the signature is re-encoded or the challenge is
158
+ * rebuilt, and two users never share one.
159
+ * - Tempo proof: challenge id + proven payer. The proof signs the challenge
160
+ * id, so it cannot be moved to another challenge.
161
+ *
162
+ * Other credential types are payments in their own right (a Tempo hash is a
163
+ * transfer already on chain, a Tempo transaction is one waiting to be sent),
164
+ * so they take the paid path, where the method's own replay protection applies.
165
+ */
166
+ function credentialKey(methodName, credential, payerAddress) {
167
+ const payload = credential?.payload;
168
+ if (payload === null || typeof payload !== 'object')
169
+ return null;
170
+ let id = null;
171
+ if (payload.type === 'authorization') {
172
+ const from = lowerHex(payload.from);
173
+ const nonce = lowerHex(payload.nonce);
174
+ if (from && nonce)
175
+ id = `authorization:${from}:${nonce}`;
176
+ }
177
+ else if (payload.type === 'proof') {
178
+ const challengeId = credential?.challenge?.id;
179
+ if (typeof challengeId === 'string' && challengeId.length > 0) {
180
+ id = `proof:${challengeId}:${payerAddress.toLowerCase()}`;
181
+ }
182
+ }
183
+ return id === null ? null : `mppx-condition-gate:${methodName}:${id}`;
184
+ }
185
+ /** The latest moment the credential could still be accepted, or null if unknown. */
186
+ function credentialExpiry(credential) {
187
+ const payload = credential?.payload;
188
+ if (payload?.type === 'authorization') {
189
+ // An authorization is bounded by its own validBefore. For x402 the
190
+ // challenge is rebuilt on every request, so its expiry says nothing about
191
+ // the credential; a native credential's validBefore is its challenge's expiry.
192
+ const validBefore = Number(payload.validBefore);
193
+ return Number.isFinite(validBefore) && validBefore > 0 ? validBefore * 1000 : null;
194
+ }
195
+ const challengeExpires = Date.parse(credential?.challenge?.expires);
196
+ return Number.isFinite(challengeExpires) ? challengeExpires : null;
21
197
  }
22
198
  // ---------------------------------------------------------------------------
23
199
  // DID parsing
@@ -225,6 +401,16 @@ export function normalizeProvenPayer(payer) {
225
401
  * credential that fails validation, conditions not met, or an attestation
226
402
  * error. There is no path from an unproven wallet to a free receipt.
227
403
  *
404
+ * Each credential gets free access at most once. The gate records a
405
+ * credential when it grants free access, and records an authorization just
406
+ * before the paid path settles it; a recorded credential is refused with a
407
+ * verification error rather than passed on to payment. A payment that fails is
408
+ * released, so it can be retried. See the README for the limits of this. Free access is given only to credentials the gate can identify
409
+ * (EIP-3009 authorizations and Tempo proofs) whose remaining validity is within
410
+ * `maxCredentialLifetimeSeconds`. The default store is bounded and shared by
411
+ * every gated route in the process; pass a shared `replayStore` for production
412
+ * traffic or when the route runs on more than one instance.
413
+ *
228
414
  * Conditions are evaluated across the 35 chains this adapter reaches (32 EVM +
229
415
  * Solana + XRPL + Bitcoin; the engine itself covers 38). Six condition types
230
416
  * are typed here: token_balance, nft_ownership, eas_attestation, farcaster_id,
@@ -239,8 +425,9 @@ export function normalizeProvenPayer(payer) {
239
425
  * ```ts
240
426
  * import { conditionGate } from '@insumermodel/mppx-condition-gate'
241
427
  *
242
- * const gated = conditionGate(tempoCharge, {
428
+ * const gated = conditionGate(evmCharge, {
243
429
  * // Return only a payer this method has proven for THIS request.
430
+ * // mppx's EVM charge reports the recovered signer as `payer`.
244
431
  * provenPayer: (details) => (details as { payer?: string }).payer ?? null,
245
432
  * conditions: [
246
433
  * { type: 'eas_attestation', template: 'coinbase_verified_account', chainId: 8453 },
@@ -254,6 +441,18 @@ export function normalizeProvenPayer(payer) {
254
441
  */
255
442
  export function conditionGate(server, options) {
256
443
  const { conditions, matchMode = 'any', cacheTtlSeconds = 300, provenPayer } = options;
444
+ const lifetimeSeconds = options.maxCredentialLifetimeSeconds ?? DEFAULT_MAX_CREDENTIAL_LIFETIME_SECONDS;
445
+ if (typeof lifetimeSeconds !== 'number' || !Number.isFinite(lifetimeSeconds) || lifetimeSeconds < 0) {
446
+ throw new Error('mppx-condition-gate: maxCredentialLifetimeSeconds must be a finite number of seconds, 0 or more.');
447
+ }
448
+ const maxLifetimeMs = lifetimeSeconds * 1000;
449
+ const { replayStore } = options;
450
+ if (replayStore !== undefined &&
451
+ typeof replayStore.tryClaim !== 'function' &&
452
+ typeof replayStore.update !== 'function') {
453
+ throw new Error('mppx-condition-gate: replayStore must provide an atomic tryClaim or update (an mppx AtomicStore).');
454
+ }
455
+ const recorder = replayStore ? storeRecorder(replayStore) : defaultRecorder;
257
456
  const anyServer = server;
258
457
  const originalVerify = anyServer.verify;
259
458
  const originalBroadcast = anyServer.broadcast;
@@ -263,10 +462,30 @@ export function conditionGate(server, options) {
263
462
  // without `validate` (legacy verify-only, or broadcast-without-validate) has
264
463
  // no safe pre-check, so it is never gated.
265
464
  const canGate = typeof provenPayer === 'function' && typeof originalValidate === 'function';
266
- /** Returns a free receipt, or null to take the paid path. */
465
+ const refuse = () => new Errors.VerificationFailedError({ reason: 'this credential has already been presented' });
466
+ /** Attestation result for a payer, from the cache or a fresh call. Throws on API error. */
467
+ async function evaluate(payer) {
468
+ const key = cacheKey(payer.address, conditions);
469
+ const cached = cache.get(key);
470
+ if (cached && cached.expiresAt > Date.now())
471
+ return cached;
472
+ const result = await callAttest(payer.address, payer.type, conditions, options);
473
+ const attestation = result.data.attestation;
474
+ const pass = matchMode === 'all'
475
+ ? attestation.pass
476
+ : attestation.results.some((r) => r.met);
477
+ const entry = {
478
+ pass,
479
+ attestationId: attestation.id,
480
+ expiresAt: Date.now() + cacheTtlSeconds * 1000,
481
+ };
482
+ cache.set(key, entry);
483
+ return entry;
484
+ }
485
+ /** Decides between a free receipt and the paid path. Throws to refuse a repeat. */
267
486
  async function tryFree(params) {
268
487
  if (!canGate)
269
- return null;
488
+ return { free: false };
270
489
  let details;
271
490
  try {
272
491
  const validation = await originalValidate(params);
@@ -274,60 +493,124 @@ export function conditionGate(server, options) {
274
493
  }
275
494
  catch {
276
495
  // Credential is not currently acceptable → let the payment method decide.
277
- return null;
496
+ return { free: false };
278
497
  }
279
498
  let resolved;
280
499
  try {
281
500
  resolved = await provenPayer(details);
282
501
  }
283
502
  catch {
284
- return null;
503
+ return { free: false };
285
504
  }
286
505
  const payer = normalizeProvenPayer(resolved);
287
506
  if (!payer)
288
- return null;
289
- const key = cacheKey(payer.address, conditions);
290
- const cached = cache.get(key);
291
- if (cached && cached.expiresAt > Date.now()) {
292
- if (!cached.pass)
293
- return null;
294
- return {
295
- method: server.name,
296
- reference: `condition-gate:free:${cached.attestationId}`,
297
- status: 'success',
298
- timestamp: new Date().toISOString(),
299
- };
507
+ return { free: false };
508
+ const credential = params?.credential;
509
+ const key = credentialKey(server.name, credential, payer.address);
510
+ const expiry = credentialExpiry(credential);
511
+ if (key === null || expiry === null)
512
+ return { free: false };
513
+ const record = {
514
+ key,
515
+ owner: payer.address.toLowerCase(),
516
+ expires: Math.min(expiry + REPLAY_SKEW_MS, Number.MAX_SAFE_INTEGER),
517
+ // Only an authorization moves value; a Tempo proof is accepted on zero-amount routes.
518
+ payment: credential?.payload?.type === 'authorization',
519
+ };
520
+ // A recorded credential is refused before any attestation is spent on it.
521
+ let taken;
522
+ try {
523
+ taken = await recorder.isTaken(key);
524
+ }
525
+ catch {
526
+ return { free: false, record };
527
+ }
528
+ if (taken) {
529
+ // A repeated Tempo proof moves no value, so the method decides; a payment is refused.
530
+ if (!record.payment)
531
+ return { free: false };
532
+ throw refuse();
300
533
  }
534
+ if (expiry - Date.now() > maxLifetimeMs)
535
+ return { free: false, record };
536
+ let evaluation;
301
537
  try {
302
- const result = await callAttest(payer.address, payer.type, conditions, options);
303
- const attestation = result.data.attestation;
304
- const pass = matchMode === 'all'
305
- ? attestation.pass
306
- : attestation.results.some((r) => r.met);
307
- cache.set(key, {
308
- pass,
309
- attestationId: attestation.id,
310
- expiresAt: Date.now() + cacheTtlSeconds * 1000,
311
- });
312
- if (!pass)
313
- return null;
314
- return {
538
+ evaluation = await evaluate(payer);
539
+ }
540
+ catch {
541
+ // Attestation error → paid path.
542
+ return { free: false, record };
543
+ }
544
+ if (!evaluation.pass)
545
+ return { free: false, record };
546
+ let claimed;
547
+ try {
548
+ claimed = await recorder.claim(key, record.expires, record.owner, 'free');
549
+ }
550
+ catch {
551
+ // The store cannot record the credential, so it gets no free access.
552
+ return { free: false, record };
553
+ }
554
+ if (!claimed) {
555
+ if (!record.payment)
556
+ return { free: false };
557
+ throw refuse();
558
+ }
559
+ return {
560
+ free: true,
561
+ receipt: {
315
562
  method: server.name,
316
- reference: `condition-gate:free:${attestation.id}`,
563
+ reference: `condition-gate:free:${evaluation.attestationId}`,
317
564
  status: 'success',
318
565
  timestamp: new Date().toISOString(),
319
- };
566
+ },
567
+ };
568
+ }
569
+ /** Runs the gate, then the original method on the paid path. */
570
+ async function gated(original, params) {
571
+ const attempt = await tryFree(params);
572
+ if (attempt.free)
573
+ return attempt.receipt;
574
+ const record = attempt.record;
575
+ let claimed = false;
576
+ // Only a payment is recorded on this path. A Tempo proof moves no value, so
577
+ // the method's own rules decide whether it may be presented again.
578
+ if (record && record.payment) {
579
+ // Claimed before settlement, so a copy presented while this payment is
580
+ // in flight is refused too. Kept until the credential itself expires.
581
+ let result;
582
+ try {
583
+ result = await recorder.claim(record.key, record.expires, record.owner, 'paid');
584
+ }
585
+ catch {
586
+ // Cannot record: pay as bare mppx would; the method's own replay protection applies.
587
+ result = null;
588
+ }
589
+ if (result === false)
590
+ throw refuse();
591
+ claimed = result === true;
320
592
  }
321
- catch {
322
- // Attestation error → paid path.
323
- return null;
593
+ try {
594
+ return await original(params);
595
+ }
596
+ catch (error) {
597
+ // A payment that fails is released, so the holder can retry with the same credential.
598
+ if (claimed) {
599
+ try {
600
+ await recorder.release(record.key);
601
+ }
602
+ catch {
603
+ // Stays recorded until it expires.
604
+ }
605
+ }
606
+ throw error;
324
607
  }
325
608
  }
326
609
  const wrapped = { ...server };
327
610
  if (typeof originalBroadcast === 'function') {
328
- wrapped.broadcast = async (params) => (await tryFree(params)) ?? originalBroadcast(params);
611
+ wrapped.broadcast = async (params) => gated(originalBroadcast, params);
329
612
  }
330
- wrapped.verify = async (params) => (await tryFree(params)) ?? originalVerify(params);
613
+ wrapped.verify = async (params) => gated(originalVerify, params);
331
614
  return wrapped;
332
615
  }
333
616
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insumermodel/mppx-condition-gate",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "Condition-based access for mppx routes. Wallet auth via signed attestations. Six condition types: token balance, NFT ownership, EAS attestation, Farcaster ID, ratio_to_amount, ratio_to_supply.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,16 +45,12 @@
45
45
  "url": "git+https://github.com/douglasborthwick-crypto/mppx-condition-gate.git"
46
46
  },
47
47
  "peerDependencies": {
48
- "mppx": ">=0.1.0"
49
- },
50
- "peerDependenciesMeta": {
51
- "mppx": {
52
- "optional": true
53
- }
48
+ "mppx": ">=0.8.14"
54
49
  },
55
50
  "devDependencies": {
56
51
  "@types/node": "^25.5.0",
57
- "mppx": "latest",
58
- "typescript": "^5.7.0"
52
+ "mppx": "0.9.3",
53
+ "typescript": "^5.7.0",
54
+ "viem": "2.56.5"
59
55
  }
60
56
  }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Method, Receipt } from 'mppx'
2
+ import { Errors } from 'mppx'
2
3
 
3
4
  // ---------------------------------------------------------------------------
4
5
  // Types
@@ -160,6 +161,44 @@ export type ConditionGateOptions = {
160
161
  * with `format: "jwt"` directly if you need the `jwt` and `pqJwt` tokens.
161
162
  */
162
163
  jwt?: boolean
164
+ /**
165
+ * Where the gate records credentials it has accepted, so each one gets
166
+ * through at most once. Defaults to a bounded in-process store.
167
+ *
168
+ * Pass a shared store when the route runs on more than one instance, or a
169
+ * credential used on one instance can be presented again on another. The
170
+ * store must claim atomically and remove expired entries: `Store.redis`,
171
+ * `Store.upstash` or `Store.cloudflare` from `mppx/server` built from a client
172
+ * that exposes an atomic `update`, or any store with an atomic `tryClaim`.
173
+ * mppx's `Store.memory()` never removes expired entries; use it only in tests.
174
+ */
175
+ replayStore?: ReplayStore
176
+ /**
177
+ * Longest remaining validity, in seconds, a credential may have and still be
178
+ * given free access. Defaults to 600. The gate has to remember a credential
179
+ * until it expires, and x402 clients choose their own expiry, so longer-lived
180
+ * credentials take the paid path instead.
181
+ */
182
+ maxCredentialLifetimeSeconds?: number
183
+ }
184
+
185
+ /**
186
+ * An atomic claim store. Structurally compatible with mppx's `AtomicStore`:
187
+ * `tryClaim` is used when present, otherwise `update`. `get`, when present,
188
+ * lets the gate refuse a repeat before spending an attestation on it, and
189
+ * `delete` (or `update`) lets it release a credential whose payment failed.
190
+ */
191
+ export type ReplayStore = {
192
+ get?: (key: string) => unknown | Promise<unknown>
193
+ delete?: (key: string) => void | Promise<void>
194
+ tryClaim?: (key: string, expires: number) => boolean | Promise<boolean>
195
+ update?: <result>(
196
+ key: string,
197
+ fn: (current: any) =>
198
+ | { op: 'noop'; result: result }
199
+ | { op: 'set'; value: any; result: result }
200
+ | { op: 'delete'; result: result },
201
+ ) => Promise<result>
163
202
  }
164
203
 
165
204
  export type InsumerAttestation = {
@@ -232,9 +271,199 @@ function cacheKey(address: string, conditions: Condition[]): string {
232
271
  return `${address.toLowerCase()}:${JSON.stringify(sorted)}`
233
272
  }
234
273
 
235
- /** Clear the in-memory ownership cache. Useful in tests. */
274
+ /** Clear the in-memory ownership cache and the default replay store. Useful in tests. */
236
275
  export function clearConditionGateCache(): void {
237
276
  cache.clear()
277
+ defaultMarkers.clear()
278
+ freeCountByOwner.clear()
279
+ freeCount = 0
280
+ paidCount = 0
281
+ nextSweepAt = 0
282
+ }
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // Single-use credentials
286
+ // ---------------------------------------------------------------------------
287
+
288
+ // mppx makes a credential single-use when it settles: the on-chain nonce is
289
+ // spent, or the method claims the hash or proof in its store. `validate` is
290
+ // non-mutating by design, so a credential that is granted free access and
291
+ // never settled would otherwise stay usable by anyone who holds a copy.
292
+
293
+ /** Kept this long past the credential's own expiry, to absorb clock skew. */
294
+ const REPLAY_SKEW_MS = 60 * 1000
295
+ const DEFAULT_MAX_CREDENTIAL_LIFETIME_SECONDS = 600
296
+ /** Default store: live free grants across the process, and per wallet. */
297
+ const DEFAULT_STORE_MAX_FREE = 100_000
298
+ const DEFAULT_STORE_MAX_FREE_PER_WALLET = 5_000
299
+ /** Default store: payments it remembers. Past this it stops recording payments. */
300
+ const DEFAULT_STORE_MAX_PAID = 100_000
301
+ const DEFAULT_STORE_SWEEP_MS = 60_000
302
+
303
+ type RecordKind = 'free' | 'paid'
304
+ type DefaultMarker = { expires: number; owner: string; kind: RecordKind }
305
+
306
+ const defaultMarkers = new Map<string, DefaultMarker>()
307
+ const freeCountByOwner = new Map<string, number>()
308
+ let freeCount = 0
309
+ let paidCount = 0
310
+ let nextSweepAt = 0
311
+
312
+ function forgetDefault(key: string): void {
313
+ const marker = defaultMarkers.get(key)
314
+ if (!marker) return
315
+ defaultMarkers.delete(key)
316
+ if (marker.kind === 'paid') {
317
+ paidCount--
318
+ return
319
+ }
320
+ freeCount--
321
+ const remaining = (freeCountByOwner.get(marker.owner) ?? 1) - 1
322
+ if (remaining <= 0) freeCountByOwner.delete(marker.owner)
323
+ else freeCountByOwner.set(marker.owner, remaining)
324
+ }
325
+
326
+ /** What the gate needs from a store. The default store and a caller's store both sit behind it. */
327
+ type Recorder = {
328
+ isTaken(key: string): Promise<boolean>
329
+ /** true when claimed, false when already taken; throws when it cannot record. */
330
+ claim(key: string, expires: number, owner: string, kind: RecordKind): Promise<boolean>
331
+ release(key: string): Promise<void>
332
+ }
333
+
334
+ const defaultRecorder: Recorder = {
335
+ async isTaken(key) {
336
+ const marker = defaultMarkers.get(key)
337
+ return marker !== undefined && marker.expires > Date.now()
338
+ },
339
+ async claim(key, expires, owner, kind) {
340
+ const now = Date.now()
341
+ if (now >= nextSweepAt) {
342
+ for (const [k, marker] of defaultMarkers) if (marker.expires <= now) forgetDefault(k)
343
+ nextSweepAt = now + DEFAULT_STORE_SWEEP_MS
344
+ }
345
+ const current = defaultMarkers.get(key)
346
+ if (current !== undefined) {
347
+ if (current.expires > now) return false
348
+ forgetDefault(key)
349
+ }
350
+ if (kind === 'free') {
351
+ const held = freeCountByOwner.get(owner) ?? 0
352
+ if (freeCount >= DEFAULT_STORE_MAX_FREE || held >= DEFAULT_STORE_MAX_FREE_PER_WALLET) {
353
+ throw new Error('mppx-condition-gate: default replay store has no room for this wallet')
354
+ }
355
+ freeCount++
356
+ freeCountByOwner.set(owner, held + 1)
357
+ } else {
358
+ if (paidCount >= DEFAULT_STORE_MAX_PAID) {
359
+ throw new Error('mppx-condition-gate: default replay store is full')
360
+ }
361
+ paidCount++
362
+ }
363
+ defaultMarkers.set(key, { expires, owner, kind })
364
+ return true
365
+ },
366
+ async release(key) {
367
+ forgetDefault(key)
368
+ },
369
+ }
370
+
371
+ function isReplayMarker(value: unknown): value is { expires: number; type: 'mppx:replay' } {
372
+ return (
373
+ typeof value === 'object' &&
374
+ value !== null &&
375
+ typeof (value as { expires?: unknown }).expires === 'number' &&
376
+ (value as { type?: unknown }).type === 'mppx:replay'
377
+ )
378
+ }
379
+
380
+ async function tryClaim(store: ReplayStore, key: string, expires: number): Promise<boolean> {
381
+ if (typeof store.tryClaim === 'function') return store.tryClaim(key, expires)
382
+ // Same marker as mppx's Store.tryClaim. A live marker, or any other stored
383
+ // value, counts as taken; an empty slot or an expired marker does not.
384
+ return store.update!(key, (current) => {
385
+ const empty = current === null || current === undefined || current === false
386
+ const taken = !empty && (!isReplayMarker(current) || current.expires > Date.now())
387
+ return taken
388
+ ? { op: 'noop', result: false }
389
+ : { op: 'set', value: { expires, type: 'mppx:replay' }, result: true }
390
+ })
391
+ }
392
+
393
+ function storeRecorder(store: ReplayStore): Recorder {
394
+ return {
395
+ async isTaken(key) {
396
+ // Advisory only: the atomic claim decides. Anything but a live marker reads as free.
397
+ if (typeof store.get !== 'function') return false
398
+ const current = await store.get(key)
399
+ return isReplayMarker(current) && current.expires > Date.now()
400
+ },
401
+ async claim(key, expires) {
402
+ return tryClaim(store, key, expires)
403
+ },
404
+ async release(key) {
405
+ if (typeof store.delete === 'function') {
406
+ await store.delete(key)
407
+ return
408
+ }
409
+ if (typeof store.update === 'function') {
410
+ await store.update(key, (current) =>
411
+ isReplayMarker(current)
412
+ ? { op: 'delete', result: undefined }
413
+ : { op: 'noop', result: undefined },
414
+ )
415
+ }
416
+ },
417
+ }
418
+ }
419
+
420
+ function lowerHex(value: unknown): string | null {
421
+ return typeof value === 'string' && /^0x[0-9a-fA-F]+$/.test(value) ? value.toLowerCase() : null
422
+ }
423
+
424
+ /**
425
+ * One key that identifies the credential, or null when the gate cannot
426
+ * identify it safely, in which case it gets no free access.
427
+ *
428
+ * - EIP-3009 authorization (mppx EVM charge, native and x402): signer + nonce.
429
+ * Neither changes when the signature is re-encoded or the challenge is
430
+ * rebuilt, and two users never share one.
431
+ * - Tempo proof: challenge id + proven payer. The proof signs the challenge
432
+ * id, so it cannot be moved to another challenge.
433
+ *
434
+ * Other credential types are payments in their own right (a Tempo hash is a
435
+ * transfer already on chain, a Tempo transaction is one waiting to be sent),
436
+ * so they take the paid path, where the method's own replay protection applies.
437
+ */
438
+ function credentialKey(methodName: string, credential: any, payerAddress: string): string | null {
439
+ const payload = credential?.payload
440
+ if (payload === null || typeof payload !== 'object') return null
441
+ let id: string | null = null
442
+ if (payload.type === 'authorization') {
443
+ const from = lowerHex(payload.from)
444
+ const nonce = lowerHex(payload.nonce)
445
+ if (from && nonce) id = `authorization:${from}:${nonce}`
446
+ } else if (payload.type === 'proof') {
447
+ const challengeId = credential?.challenge?.id
448
+ if (typeof challengeId === 'string' && challengeId.length > 0) {
449
+ id = `proof:${challengeId}:${payerAddress.toLowerCase()}`
450
+ }
451
+ }
452
+ return id === null ? null : `mppx-condition-gate:${methodName}:${id}`
453
+ }
454
+
455
+ /** The latest moment the credential could still be accepted, or null if unknown. */
456
+ function credentialExpiry(credential: any): number | null {
457
+ const payload = credential?.payload
458
+ if (payload?.type === 'authorization') {
459
+ // An authorization is bounded by its own validBefore. For x402 the
460
+ // challenge is rebuilt on every request, so its expiry says nothing about
461
+ // the credential; a native credential's validBefore is its challenge's expiry.
462
+ const validBefore = Number(payload.validBefore)
463
+ return Number.isFinite(validBefore) && validBefore > 0 ? validBefore * 1000 : null
464
+ }
465
+ const challengeExpires = Date.parse(credential?.challenge?.expires)
466
+ return Number.isFinite(challengeExpires) ? challengeExpires : null
238
467
  }
239
468
 
240
469
  // ---------------------------------------------------------------------------
@@ -438,6 +667,16 @@ export function normalizeProvenPayer(
438
667
  * credential that fails validation, conditions not met, or an attestation
439
668
  * error. There is no path from an unproven wallet to a free receipt.
440
669
  *
670
+ * Each credential gets free access at most once. The gate records a
671
+ * credential when it grants free access, and records an authorization just
672
+ * before the paid path settles it; a recorded credential is refused with a
673
+ * verification error rather than passed on to payment. A payment that fails is
674
+ * released, so it can be retried. See the README for the limits of this. Free access is given only to credentials the gate can identify
675
+ * (EIP-3009 authorizations and Tempo proofs) whose remaining validity is within
676
+ * `maxCredentialLifetimeSeconds`. The default store is bounded and shared by
677
+ * every gated route in the process; pass a shared `replayStore` for production
678
+ * traffic or when the route runs on more than one instance.
679
+ *
441
680
  * Conditions are evaluated across the 35 chains this adapter reaches (32 EVM +
442
681
  * Solana + XRPL + Bitcoin; the engine itself covers 38). Six condition types
443
682
  * are typed here: token_balance, nft_ownership, eas_attestation, farcaster_id,
@@ -452,8 +691,9 @@ export function normalizeProvenPayer(
452
691
  * ```ts
453
692
  * import { conditionGate } from '@insumermodel/mppx-condition-gate'
454
693
  *
455
- * const gated = conditionGate(tempoCharge, {
694
+ * const gated = conditionGate(evmCharge, {
456
695
  * // Return only a payer this method has proven for THIS request.
696
+ * // mppx's EVM charge reports the recovered signer as `payer`.
457
697
  * provenPayer: (details) => (details as { payer?: string }).payer ?? null,
458
698
  * conditions: [
459
699
  * { type: 'eas_attestation', template: 'coinbase_verified_account', chainId: 8453 },
@@ -470,6 +710,21 @@ export function conditionGate(
470
710
  options: ConditionGateOptions,
471
711
  ): Method.AnyServer {
472
712
  const { conditions, matchMode = 'any', cacheTtlSeconds = 300, provenPayer } = options
713
+ const lifetimeSeconds = options.maxCredentialLifetimeSeconds ?? DEFAULT_MAX_CREDENTIAL_LIFETIME_SECONDS
714
+ if (typeof lifetimeSeconds !== 'number' || !Number.isFinite(lifetimeSeconds) || lifetimeSeconds < 0) {
715
+ throw new Error('mppx-condition-gate: maxCredentialLifetimeSeconds must be a finite number of seconds, 0 or more.')
716
+ }
717
+ const maxLifetimeMs = lifetimeSeconds * 1000
718
+
719
+ const { replayStore } = options
720
+ if (
721
+ replayStore !== undefined &&
722
+ typeof replayStore.tryClaim !== 'function' &&
723
+ typeof replayStore.update !== 'function'
724
+ ) {
725
+ throw new Error('mppx-condition-gate: replayStore must provide an atomic tryClaim or update (an mppx AtomicStore).')
726
+ }
727
+ const recorder = replayStore ? storeRecorder(replayStore) : defaultRecorder
473
728
 
474
729
  const anyServer = server as unknown as {
475
730
  verify: (params: any) => Promise<any>
@@ -488,9 +743,37 @@ export function conditionGate(
488
743
  const canGate =
489
744
  typeof provenPayer === 'function' && typeof originalValidate === 'function'
490
745
 
491
- /** Returns a free receipt, or null to take the paid path. */
492
- async function tryFree(params: any): Promise<any | null> {
493
- if (!canGate) return null
746
+ const refuse = () =>
747
+ new Errors.VerificationFailedError({ reason: 'this credential has already been presented' })
748
+
749
+ /** Attestation result for a payer, from the cache or a fresh call. Throws on API error. */
750
+ async function evaluate(payer: { address: string; type: WalletType }) {
751
+ const key = cacheKey(payer.address, conditions)
752
+ const cached = cache.get(key)
753
+ if (cached && cached.expiresAt > Date.now()) return cached
754
+
755
+ const result = await callAttest(payer.address, payer.type, conditions, options)
756
+ const attestation = result.data.attestation
757
+ const pass =
758
+ matchMode === 'all'
759
+ ? attestation.pass
760
+ : attestation.results.some((r) => r.met)
761
+ const entry = {
762
+ pass,
763
+ attestationId: attestation.id,
764
+ expiresAt: Date.now() + cacheTtlSeconds * 1000,
765
+ }
766
+ cache.set(key, entry)
767
+ return entry
768
+ }
769
+
770
+ type Attempt =
771
+ | { free: true; receipt: any }
772
+ | { free: false; record?: { key: string; expires: number; owner: string; payment: boolean } }
773
+
774
+ /** Decides between a free receipt and the paid path. Throws to refuse a repeat. */
775
+ async function tryFree(params: any): Promise<Attempt> {
776
+ if (!canGate) return { free: false }
494
777
 
495
778
  let details: unknown
496
779
  try {
@@ -498,68 +781,124 @@ export function conditionGate(
498
781
  details = (validation as { details?: unknown } | undefined)?.details
499
782
  } catch {
500
783
  // Credential is not currently acceptable → let the payment method decide.
501
- return null
784
+ return { free: false }
502
785
  }
503
786
 
504
787
  let resolved: ProvenPayer
505
788
  try {
506
789
  resolved = await provenPayer!(details)
507
790
  } catch {
508
- return null
791
+ return { free: false }
509
792
  }
510
793
 
511
794
  const payer = normalizeProvenPayer(resolved)
512
- if (!payer) return null
795
+ if (!payer) return { free: false }
796
+
797
+ const credential = params?.credential
798
+ const key = credentialKey(server.name, credential, payer.address)
799
+ const expiry = credentialExpiry(credential)
800
+ if (key === null || expiry === null) return { free: false }
801
+
802
+ const record = {
803
+ key,
804
+ owner: payer.address.toLowerCase(),
805
+ expires: Math.min(expiry + REPLAY_SKEW_MS, Number.MAX_SAFE_INTEGER),
806
+ // Only an authorization moves value; a Tempo proof is accepted on zero-amount routes.
807
+ payment: credential?.payload?.type === 'authorization',
808
+ }
513
809
 
514
- const key = cacheKey(payer.address, conditions)
515
- const cached = cache.get(key)
516
- if (cached && cached.expiresAt > Date.now()) {
517
- if (!cached.pass) return null
518
- return {
810
+ // A recorded credential is refused before any attestation is spent on it.
811
+ let taken: boolean
812
+ try {
813
+ taken = await recorder.isTaken(key)
814
+ } catch {
815
+ return { free: false, record }
816
+ }
817
+ if (taken) {
818
+ // A repeated Tempo proof moves no value, so the method decides; a payment is refused.
819
+ if (!record.payment) return { free: false }
820
+ throw refuse()
821
+ }
822
+
823
+ if (expiry - Date.now() > maxLifetimeMs) return { free: false, record }
824
+
825
+ let evaluation: CacheEntry
826
+ try {
827
+ evaluation = await evaluate(payer)
828
+ } catch {
829
+ // Attestation error → paid path.
830
+ return { free: false, record }
831
+ }
832
+ if (!evaluation.pass) return { free: false, record }
833
+
834
+ let claimed: boolean
835
+ try {
836
+ claimed = await recorder.claim(key, record.expires, record.owner, 'free')
837
+ } catch {
838
+ // The store cannot record the credential, so it gets no free access.
839
+ return { free: false, record }
840
+ }
841
+ if (!claimed) {
842
+ if (!record.payment) return { free: false }
843
+ throw refuse()
844
+ }
845
+
846
+ return {
847
+ free: true,
848
+ receipt: {
519
849
  method: server.name,
520
- reference: `condition-gate:free:${cached.attestationId}`,
850
+ reference: `condition-gate:free:${evaluation.attestationId}`,
521
851
  status: 'success' as const,
522
852
  timestamp: new Date().toISOString(),
853
+ },
854
+ }
855
+ }
856
+
857
+ /** Runs the gate, then the original method on the paid path. */
858
+ async function gated(original: (params: any) => Promise<any>, params: any) {
859
+ const attempt = await tryFree(params)
860
+ if (attempt.free) return attempt.receipt
861
+
862
+ const record = attempt.record
863
+ let claimed = false
864
+ // Only a payment is recorded on this path. A Tempo proof moves no value, so
865
+ // the method's own rules decide whether it may be presented again.
866
+ if (record && record.payment) {
867
+ // Claimed before settlement, so a copy presented while this payment is
868
+ // in flight is refused too. Kept until the credential itself expires.
869
+ let result: boolean | null
870
+ try {
871
+ result = await recorder.claim(record.key, record.expires, record.owner, 'paid')
872
+ } catch {
873
+ // Cannot record: pay as bare mppx would; the method's own replay protection applies.
874
+ result = null
523
875
  }
876
+ if (result === false) throw refuse()
877
+ claimed = result === true
524
878
  }
525
879
 
526
880
  try {
527
- const result = await callAttest(payer.address, payer.type, conditions, options)
528
- const attestation = result.data.attestation
529
-
530
- const pass =
531
- matchMode === 'all'
532
- ? attestation.pass
533
- : attestation.results.some((r) => r.met)
534
-
535
- cache.set(key, {
536
- pass,
537
- attestationId: attestation.id,
538
- expiresAt: Date.now() + cacheTtlSeconds * 1000,
539
- })
540
-
541
- if (!pass) return null
542
- return {
543
- method: server.name,
544
- reference: `condition-gate:free:${attestation.id}`,
545
- status: 'success' as const,
546
- timestamp: new Date().toISOString(),
881
+ return await original(params)
882
+ } catch (error) {
883
+ // A payment that fails is released, so the holder can retry with the same credential.
884
+ if (claimed) {
885
+ try {
886
+ await recorder.release(record!.key)
887
+ } catch {
888
+ // Stays recorded until it expires.
889
+ }
547
890
  }
548
- } catch {
549
- // Attestation error → paid path.
550
- return null
891
+ throw error
551
892
  }
552
893
  }
553
894
 
554
895
  const wrapped = { ...server } as unknown as Record<string, unknown>
555
896
 
556
897
  if (typeof originalBroadcast === 'function') {
557
- wrapped.broadcast = async (params: any) =>
558
- (await tryFree(params)) ?? originalBroadcast(params)
898
+ wrapped.broadcast = async (params: any) => gated(originalBroadcast, params)
559
899
  }
560
900
 
561
- wrapped.verify = async (params: any) =>
562
- (await tryFree(params)) ?? originalVerify(params)
901
+ wrapped.verify = async (params: any) => gated(originalVerify, params)
563
902
 
564
903
  return wrapped as unknown as Method.AnyServer
565
904
  }