@insumermodel/mppx-condition-gate 2.0.3 → 3.0.1
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 +34 -6
- package/dist/index.d.ts +66 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +115 -77
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +157 -71
package/README.md
CHANGED
|
@@ -6,16 +6,23 @@ Condition-based access for [mppx](https://github.com/wevm/mppx) routes. One sign
|
|
|
6
6
|
|
|
7
7
|
## How it works
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Free access requires a payer your payment method has **proven** controls the request. You supply that payer through `provenPayer`; the gate never infers one.
|
|
10
10
|
|
|
11
11
|
1. Request arrives with a payment credential
|
|
12
|
-
2.
|
|
13
|
-
3.
|
|
14
|
-
4.
|
|
15
|
-
5. **
|
|
12
|
+
2. The gate calls the method's non-mutating `validate` hook
|
|
13
|
+
3. Your `provenPayer` resolver receives mppx's method-specific validation `details` and returns the proven payer, or `null`
|
|
14
|
+
4. [InsumerAPI](https://insumermodel.com/developers/api-reference/) evaluates the conditions for that payer and returns an ECDSA P-256 signed attestation
|
|
15
|
+
5. **Pass** → free receipt returned (`reference: "condition-gate:free:{attestationId}"`)
|
|
16
|
+
6. **Fail, or no proven payer** → the normal paid path runs
|
|
16
17
|
|
|
17
18
|
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.
|
|
18
19
|
|
|
20
|
+
### Why you must supply the payer
|
|
21
|
+
|
|
22
|
+
mppx credentials carry an optional `source` DID, but mppx documents it as *"an asserted identity, not independent proof of control"* — it is a claim by the caller, not a proof. Versions before 3.0.0 granted free access on it, which let anyone name a qualifying wallet and skip payment ([GHSA-jg6q-3qfh-r9f8](https://github.com/douglasborthwick-crypto/mppx-condition-gate/security/advisories/GHSA-jg6q-3qfh-r9f8)). The gate now refuses to guess.
|
|
23
|
+
|
|
24
|
+
`provenPayer` receives **only** the validation `details` — never the credential and never `source` — so the unproven identity is not reachable from it. Return a payer only if the payment method has actually established control of it. **Every path that cannot justify free access falls through to payment:** no resolver, no `validate` hook on the method, a resolver returning `null` or throwing, a credential that fails validation, conditions not met, or an attestation error.
|
|
25
|
+
|
|
19
26
|
## Install
|
|
20
27
|
|
|
21
28
|
```bash
|
|
@@ -35,6 +42,9 @@ const tempoCharge = tempo({
|
|
|
35
42
|
|
|
36
43
|
const gatedCharge = conditionGate(tempoCharge, {
|
|
37
44
|
apiKey: process.env.INSUMER_API_KEY,
|
|
45
|
+
// Return only a payer this method has PROVEN for this request, else null.
|
|
46
|
+
// The argument is mppx's method-specific validation `details`.
|
|
47
|
+
provenPayer: (details) => (details as { payer?: string }).payer ?? null,
|
|
38
48
|
conditions: [{
|
|
39
49
|
type: 'nft_ownership',
|
|
40
50
|
contractAddress: '0xYourNFT',
|
|
@@ -58,7 +68,7 @@ Mix any of the six in a single call. `matchMode: 'any'` (default) passes when an
|
|
|
58
68
|
type: 'token_balance',
|
|
59
69
|
contractAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
|
|
60
70
|
chainId: 1,
|
|
61
|
-
threshold: 1000,
|
|
71
|
+
threshold: '1000', // decimal string in token units; numbers are converted before sending
|
|
62
72
|
decimals: 6,
|
|
63
73
|
label: 'USDC >= 1000',
|
|
64
74
|
}
|
|
@@ -222,6 +232,24 @@ The condition shapes for `token_balance` and `nft_ownership` are unchanged. `eas
|
|
|
222
232
|
|
|
223
233
|
The `parsXrplDid` typo from v1 is fixed: use `parseXrplDid`. The cache helper renamed: `clearTokenGateCache` is now `clearConditionGateCache`.
|
|
224
234
|
|
|
235
|
+
## Migrating from 2.x (security release)
|
|
236
|
+
|
|
237
|
+
3.0.0 fixes an authentication bypass ([GHSA-jg6q-3qfh-r9f8](https://github.com/douglasborthwick-crypto/mppx-condition-gate/security/advisories/GHSA-jg6q-3qfh-r9f8)). All 2.x versions, and all versions of the predecessor `@insumermodel/mppx-token-gate`, are affected.
|
|
238
|
+
|
|
239
|
+
Add a `provenPayer` resolver:
|
|
240
|
+
|
|
241
|
+
```diff
|
|
242
|
+
const gated = conditionGate(server, {
|
|
243
|
+
apiKey: process.env.INSUMER_API_KEY,
|
|
244
|
+
+ provenPayer: (details) => (details as { payer?: string }).payer ?? null,
|
|
245
|
+
conditions: [ ... ],
|
|
246
|
+
})
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Without it the gate compiles and runs, but never grants free access — every request takes the paid path. That is deliberate: the insecure default is gone, and the safe default is to charge.
|
|
250
|
+
|
|
251
|
+
Your method must expose mppx's `validate` hook. Legacy `verify`-only methods have no non-mutating pre-check, so they are never gated.
|
|
252
|
+
|
|
225
253
|
## License
|
|
226
254
|
|
|
227
255
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,10 @@ export type TokenBalanceCondition = {
|
|
|
6
6
|
/** Token contract address. For XRPL native XRP or Bitcoin, use "native". */
|
|
7
7
|
contractAddress: string;
|
|
8
8
|
chainId: ChainId;
|
|
9
|
-
/** Minimum balance.
|
|
10
|
-
|
|
9
|
+
/** Minimum balance in token units. Pass a decimal string (e.g. "1000") for
|
|
10
|
+
* full precision; keys minted today sign under the v2 scheme and reject a
|
|
11
|
+
* JSON number, so numbers are converted to strings before sending. Defaults to "1". */
|
|
12
|
+
threshold?: number | string;
|
|
11
13
|
/** Token decimals. Auto-detected on most EVM chains if omitted. */
|
|
12
14
|
decimals?: number;
|
|
13
15
|
/** XRPL currency code (e.g. "USD", "RLUSD") for trust-line tokens. */
|
|
@@ -77,9 +79,42 @@ export type RatioToSupplyCondition = {
|
|
|
77
79
|
};
|
|
78
80
|
/** Any condition accepted by /v1/attest */
|
|
79
81
|
export type Condition = TokenBalanceCondition | NftOwnershipCondition | EasAttestationCondition | FarcasterIdCondition | RatioToAmountCondition | RatioToSupplyCondition;
|
|
82
|
+
/** Wallet family for a proven payer. */
|
|
83
|
+
export type WalletType = 'evm' | 'solana' | 'xrpl' | 'bitcoin';
|
|
84
|
+
/**
|
|
85
|
+
* A payer the caller has cryptographically proven for this request.
|
|
86
|
+
*
|
|
87
|
+
* Accepts a `did:pkh:...` string, a bare `0x` EVM address, or an explicit
|
|
88
|
+
* `{ address, type }` pair. Bare non-EVM addresses are rejected because their
|
|
89
|
+
* wallet family cannot be inferred unambiguously — pass the object form.
|
|
90
|
+
*/
|
|
91
|
+
export type ProvenPayer = string | {
|
|
92
|
+
address: string;
|
|
93
|
+
type: WalletType;
|
|
94
|
+
} | null | undefined;
|
|
80
95
|
export type ConditionGateOptions = {
|
|
81
96
|
/** InsumerAPI key. Falls back to INSUMER_API_KEY env var. */
|
|
82
97
|
apiKey?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Resolves the payer that the payment method has PROVEN controls this
|
|
100
|
+
* request. Required for free access — without it every request takes the
|
|
101
|
+
* paid path.
|
|
102
|
+
*
|
|
103
|
+
* It receives only mppx's method-specific validation `details` (for example
|
|
104
|
+
* a recovered payer address). It is deliberately NOT given the credential or
|
|
105
|
+
* the declared `source`: mppx documents `source` as "an asserted identity,
|
|
106
|
+
* not independent proof of control", and granting on it is the vulnerability
|
|
107
|
+
* fixed in 3.0.0 (GHSA-jg6q-3qfh-r9f8).
|
|
108
|
+
*
|
|
109
|
+
* Return `null` when no payer has been proven. Returning an address you have
|
|
110
|
+
* not proven reintroduces that vulnerability.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* provenPayer: (details) => (details as { payer?: string }).payer ?? null
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
provenPayer?: (details: unknown) => ProvenPayer | Promise<ProvenPayer>;
|
|
83
118
|
/** One or more conditions to evaluate. Mix any of the six types. */
|
|
84
119
|
conditions: Condition[];
|
|
85
120
|
/** Whether the wallet must satisfy "any" (default) or "all" conditions. */
|
|
@@ -142,20 +177,37 @@ export declare function parseSolanaDid(source: string): string | null;
|
|
|
142
177
|
export declare function parseXrplDid(source: string): string | null;
|
|
143
178
|
/** Extracts a Bitcoin address from a `did:pkh:bip122:{chainId}:{address}` string. */
|
|
144
179
|
export declare function parseBitcoinDid(source: string): string | null;
|
|
180
|
+
/**
|
|
181
|
+
* Normalizes a caller-supplied proven payer into an address + wallet family.
|
|
182
|
+
*
|
|
183
|
+
* Fail-closed: anything it cannot resolve unambiguously returns null, which
|
|
184
|
+
* sends the request to the paid path.
|
|
185
|
+
*/
|
|
186
|
+
export declare function normalizeProvenPayer(payer: ProvenPayer): {
|
|
187
|
+
address: string;
|
|
188
|
+
type: WalletType;
|
|
189
|
+
} | null;
|
|
145
190
|
/**
|
|
146
191
|
* Wraps an mppx Method.Server with condition-based access using signed attestations.
|
|
147
192
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
193
|
+
* Free access requires a PROVEN payer. The gate calls the method's non-mutating
|
|
194
|
+
* `validate` hook, hands the resulting method-specific `details` to your
|
|
195
|
+
* `provenPayer` resolver, and only then evaluates conditions. It never reads
|
|
196
|
+
* `credential.source` — mppx documents that field as an asserted identity
|
|
197
|
+
* rather than proof of control.
|
|
198
|
+
*
|
|
199
|
+
* The gate falls through to the paid path whenever free access cannot be
|
|
200
|
+
* justified: no `provenPayer` resolver, no `validate` hook on the method
|
|
201
|
+
* (legacy `verify`-only methods included), a resolver returning null, a
|
|
202
|
+
* credential that fails validation, conditions not met, or an attestation
|
|
203
|
+
* error. There is no path from an unproven wallet to a free receipt.
|
|
152
204
|
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
205
|
+
* Conditions are evaluated across the 35 chains this adapter reaches (32 EVM +
|
|
206
|
+
* Solana + XRPL + Bitcoin; the engine itself covers 38). Six condition types
|
|
207
|
+
* are typed here: token_balance, nft_ownership, eas_attestation, farcaster_id,
|
|
208
|
+
* ratio_to_amount, and ratio_to_supply. InsumerAPI also offers evm_view_call,
|
|
209
|
+
* erc8004_agent, and erc7710_delegation; those are not typed or normalized
|
|
210
|
+
* here, so call /v1/attest directly if you need them.
|
|
159
211
|
*
|
|
160
212
|
* The attestation is ECDSA P-256 signed and verifiable offline via the public
|
|
161
213
|
* JWKS at https://insumermodel.com/.well-known/jwks.json.
|
|
@@ -165,6 +217,8 @@ export declare function parseBitcoinDid(source: string): string | null;
|
|
|
165
217
|
* import { conditionGate } from '@insumermodel/mppx-condition-gate'
|
|
166
218
|
*
|
|
167
219
|
* const gated = conditionGate(tempoCharge, {
|
|
220
|
+
* // Return only a payer this method has proven for THIS request.
|
|
221
|
+
* provenPayer: (details) => (details as { payer?: string }).payer ?? null,
|
|
168
222
|
* conditions: [
|
|
169
223
|
* { type: 'eas_attestation', template: 'coinbase_verified_account', chainId: 8453 },
|
|
170
224
|
* { type: 'farcaster_id' },
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,MAAM,CAAA;AAM3C,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAA;AAE5D,4DAA4D;AAC5D,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,eAAe,CAAA;IACrB,4EAA4E;IAC5E,eAAe,EAAE,MAAM,CAAA;IACvB,OAAO,EAAE,OAAO,CAAA;IAChB
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -125,7 +125,8 @@ function buildBodyConditions(conditions) {
|
|
|
125
125
|
chainId: c.chainId,
|
|
126
126
|
};
|
|
127
127
|
if (c.type === 'token_balance') {
|
|
128
|
-
|
|
128
|
+
const threshold = c.threshold ?? '1';
|
|
129
|
+
cond.threshold = typeof threshold === 'string' ? threshold : String(threshold);
|
|
129
130
|
if (c.decimals !== undefined)
|
|
130
131
|
cond.decimals = c.decimals;
|
|
131
132
|
if (c.currency)
|
|
@@ -177,20 +178,61 @@ async function callAttest(wallet, walletType, conditions, options) {
|
|
|
177
178
|
// ---------------------------------------------------------------------------
|
|
178
179
|
// conditionGate adapter
|
|
179
180
|
// ---------------------------------------------------------------------------
|
|
181
|
+
/**
|
|
182
|
+
* Normalizes a caller-supplied proven payer into an address + wallet family.
|
|
183
|
+
*
|
|
184
|
+
* Fail-closed: anything it cannot resolve unambiguously returns null, which
|
|
185
|
+
* sends the request to the paid path.
|
|
186
|
+
*/
|
|
187
|
+
export function normalizeProvenPayer(payer) {
|
|
188
|
+
if (!payer)
|
|
189
|
+
return null;
|
|
190
|
+
if (typeof payer === 'object') {
|
|
191
|
+
const { address, type } = payer;
|
|
192
|
+
if (!address || !type)
|
|
193
|
+
return null;
|
|
194
|
+
return { address, type };
|
|
195
|
+
}
|
|
196
|
+
if (typeof payer !== 'string')
|
|
197
|
+
return null;
|
|
198
|
+
const evm = parseDid(payer);
|
|
199
|
+
if (evm)
|
|
200
|
+
return { address: evm, type: 'evm' };
|
|
201
|
+
const sol = parseSolanaDid(payer);
|
|
202
|
+
if (sol)
|
|
203
|
+
return { address: sol, type: 'solana' };
|
|
204
|
+
const xrpl = parseXrplDid(payer);
|
|
205
|
+
if (xrpl)
|
|
206
|
+
return { address: xrpl, type: 'xrpl' };
|
|
207
|
+
const btc = parseBitcoinDid(payer);
|
|
208
|
+
if (btc)
|
|
209
|
+
return { address: btc, type: 'bitcoin' };
|
|
210
|
+
// Bare EVM address is unambiguous; anything else needs the object form.
|
|
211
|
+
if (payer.startsWith('0x'))
|
|
212
|
+
return { address: payer, type: 'evm' };
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
180
215
|
/**
|
|
181
216
|
* Wraps an mppx Method.Server with condition-based access using signed attestations.
|
|
182
217
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
218
|
+
* Free access requires a PROVEN payer. The gate calls the method's non-mutating
|
|
219
|
+
* `validate` hook, hands the resulting method-specific `details` to your
|
|
220
|
+
* `provenPayer` resolver, and only then evaluates conditions. It never reads
|
|
221
|
+
* `credential.source` — mppx documents that field as an asserted identity
|
|
222
|
+
* rather than proof of control.
|
|
223
|
+
*
|
|
224
|
+
* The gate falls through to the paid path whenever free access cannot be
|
|
225
|
+
* justified: no `provenPayer` resolver, no `validate` hook on the method
|
|
226
|
+
* (legacy `verify`-only methods included), a resolver returning null, a
|
|
227
|
+
* credential that fails validation, conditions not met, or an attestation
|
|
228
|
+
* error. There is no path from an unproven wallet to a free receipt.
|
|
187
229
|
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
230
|
+
* Conditions are evaluated across the 35 chains this adapter reaches (32 EVM +
|
|
231
|
+
* Solana + XRPL + Bitcoin; the engine itself covers 38). Six condition types
|
|
232
|
+
* are typed here: token_balance, nft_ownership, eas_attestation, farcaster_id,
|
|
233
|
+
* ratio_to_amount, and ratio_to_supply. InsumerAPI also offers evm_view_call,
|
|
234
|
+
* erc8004_agent, and erc7710_delegation; those are not typed or normalized
|
|
235
|
+
* here, so call /v1/attest directly if you need them.
|
|
194
236
|
*
|
|
195
237
|
* The attestation is ECDSA P-256 signed and verifiable offline via the public
|
|
196
238
|
* JWKS at https://insumermodel.com/.well-known/jwks.json.
|
|
@@ -200,6 +242,8 @@ async function callAttest(wallet, walletType, conditions, options) {
|
|
|
200
242
|
* import { conditionGate } from '@insumermodel/mppx-condition-gate'
|
|
201
243
|
*
|
|
202
244
|
* const gated = conditionGate(tempoCharge, {
|
|
245
|
+
* // Return only a payer this method has proven for THIS request.
|
|
246
|
+
* provenPayer: (details) => (details as { payer?: string }).payer ?? null,
|
|
203
247
|
* conditions: [
|
|
204
248
|
* { type: 'eas_attestation', template: 'coinbase_verified_account', chainId: 8453 },
|
|
205
249
|
* { type: 'farcaster_id' },
|
|
@@ -211,87 +255,81 @@ async function callAttest(wallet, walletType, conditions, options) {
|
|
|
211
255
|
* ```
|
|
212
256
|
*/
|
|
213
257
|
export function conditionGate(server, options) {
|
|
214
|
-
const { conditions, matchMode = 'any', cacheTtlSeconds = 300 } = options;
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (!
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
258
|
+
const { conditions, matchMode = 'any', cacheTtlSeconds = 300, provenPayer } = options;
|
|
259
|
+
const anyServer = server;
|
|
260
|
+
const originalVerify = anyServer.verify;
|
|
261
|
+
const originalBroadcast = anyServer.broadcast;
|
|
262
|
+
const originalValidate = anyServer.validate;
|
|
263
|
+
// Free access is only reachable when BOTH exist: a resolver to supply the
|
|
264
|
+
// proven payer, and a non-mutating validate hook to derive it from. A method
|
|
265
|
+
// without `validate` (legacy verify-only, or broadcast-without-validate) has
|
|
266
|
+
// no safe pre-check, so it is never gated.
|
|
267
|
+
const canGate = typeof provenPayer === 'function' && typeof originalValidate === 'function';
|
|
268
|
+
/** Returns a free receipt, or null to take the paid path. */
|
|
269
|
+
async function tryFree(params) {
|
|
270
|
+
if (!canGate)
|
|
271
|
+
return null;
|
|
272
|
+
let details;
|
|
273
|
+
try {
|
|
274
|
+
const validation = await originalValidate(params);
|
|
275
|
+
details = validation?.details;
|
|
230
276
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
walletType = 'xrpl';
|
|
277
|
+
catch {
|
|
278
|
+
// Credential is not currently acceptable → let the payment method decide.
|
|
279
|
+
return null;
|
|
235
280
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
281
|
+
let resolved;
|
|
282
|
+
try {
|
|
283
|
+
resolved = await provenPayer(details);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return null;
|
|
240
287
|
}
|
|
241
|
-
|
|
242
|
-
if (!
|
|
243
|
-
return
|
|
244
|
-
|
|
245
|
-
const key = cacheKey(wallet, conditions);
|
|
288
|
+
const payer = normalizeProvenPayer(resolved);
|
|
289
|
+
if (!payer)
|
|
290
|
+
return null;
|
|
291
|
+
const key = cacheKey(payer.address, conditions);
|
|
246
292
|
const cached = cache.get(key);
|
|
247
293
|
if (cached && cached.expiresAt > Date.now()) {
|
|
248
|
-
if (cached.pass)
|
|
249
|
-
return
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
// Cached non-holder → fall through
|
|
257
|
-
return originalVerify(params);
|
|
294
|
+
if (!cached.pass)
|
|
295
|
+
return null;
|
|
296
|
+
return {
|
|
297
|
+
method: server.name,
|
|
298
|
+
reference: `condition-gate:free:${cached.attestationId}`,
|
|
299
|
+
status: 'success',
|
|
300
|
+
timestamp: new Date().toISOString(),
|
|
301
|
+
};
|
|
258
302
|
}
|
|
259
|
-
// Call InsumerAPI
|
|
260
303
|
try {
|
|
261
|
-
const result = await callAttest(
|
|
304
|
+
const result = await callAttest(payer.address, payer.type, conditions, options);
|
|
262
305
|
const attestation = result.data.attestation;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
pass = attestation.pass; // all conditions must be met
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
// "any" — at least one condition met
|
|
270
|
-
pass = attestation.results.some((r) => r.met);
|
|
271
|
-
}
|
|
272
|
-
// Cache the result
|
|
306
|
+
const pass = matchMode === 'all'
|
|
307
|
+
? attestation.pass
|
|
308
|
+
: attestation.results.some((r) => r.met);
|
|
273
309
|
cache.set(key, {
|
|
274
310
|
pass,
|
|
275
311
|
attestationId: attestation.id,
|
|
276
312
|
expiresAt: Date.now() + cacheTtlSeconds * 1000,
|
|
277
313
|
});
|
|
278
|
-
if (pass)
|
|
279
|
-
return
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
314
|
+
if (!pass)
|
|
315
|
+
return null;
|
|
316
|
+
return {
|
|
317
|
+
method: server.name,
|
|
318
|
+
reference: `condition-gate:free:${attestation.id}`,
|
|
319
|
+
status: 'success',
|
|
320
|
+
timestamp: new Date().toISOString(),
|
|
321
|
+
};
|
|
286
322
|
}
|
|
287
323
|
catch {
|
|
288
|
-
// Attestation
|
|
324
|
+
// Attestation error → paid path.
|
|
325
|
+
return null;
|
|
289
326
|
}
|
|
290
|
-
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
327
|
+
}
|
|
328
|
+
const wrapped = { ...server };
|
|
329
|
+
if (typeof originalBroadcast === 'function') {
|
|
330
|
+
wrapped.broadcast = async (params) => (await tryFree(params)) ?? originalBroadcast(params);
|
|
331
|
+
}
|
|
332
|
+
wrapped.verify = async (params) => (await tryFree(params)) ?? originalVerify(params);
|
|
333
|
+
return wrapped;
|
|
296
334
|
}
|
|
297
335
|
//# 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":"
|
|
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
|
+
"version": "3.0.1",
|
|
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",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc",
|
|
20
|
-
"prepublishOnly": "npm run build"
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"test": "tsc && node --test test/*.test.mjs"
|
|
21
22
|
},
|
|
22
23
|
"keywords": [
|
|
23
24
|
"condition-based-access",
|
package/src/index.ts
CHANGED
|
@@ -12,8 +12,10 @@ export type TokenBalanceCondition = {
|
|
|
12
12
|
/** Token contract address. For XRPL native XRP or Bitcoin, use "native". */
|
|
13
13
|
contractAddress: string
|
|
14
14
|
chainId: ChainId
|
|
15
|
-
/** Minimum balance.
|
|
16
|
-
|
|
15
|
+
/** Minimum balance in token units. Pass a decimal string (e.g. "1000") for
|
|
16
|
+
* full precision; keys minted today sign under the v2 scheme and reject a
|
|
17
|
+
* JSON number, so numbers are converted to strings before sending. Defaults to "1". */
|
|
18
|
+
threshold?: number | string
|
|
17
19
|
/** Token decimals. Auto-detected on most EVM chains if omitted. */
|
|
18
20
|
decimals?: number
|
|
19
21
|
/** XRPL currency code (e.g. "USD", "RLUSD") for trust-line tokens. */
|
|
@@ -101,9 +103,45 @@ export type Condition =
|
|
|
101
103
|
| RatioToAmountCondition
|
|
102
104
|
| RatioToSupplyCondition
|
|
103
105
|
|
|
106
|
+
/** Wallet family for a proven payer. */
|
|
107
|
+
export type WalletType = 'evm' | 'solana' | 'xrpl' | 'bitcoin'
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A payer the caller has cryptographically proven for this request.
|
|
111
|
+
*
|
|
112
|
+
* Accepts a `did:pkh:...` string, a bare `0x` EVM address, or an explicit
|
|
113
|
+
* `{ address, type }` pair. Bare non-EVM addresses are rejected because their
|
|
114
|
+
* wallet family cannot be inferred unambiguously — pass the object form.
|
|
115
|
+
*/
|
|
116
|
+
export type ProvenPayer =
|
|
117
|
+
| string
|
|
118
|
+
| { address: string; type: WalletType }
|
|
119
|
+
| null
|
|
120
|
+
| undefined
|
|
121
|
+
|
|
104
122
|
export type ConditionGateOptions = {
|
|
105
123
|
/** InsumerAPI key. Falls back to INSUMER_API_KEY env var. */
|
|
106
124
|
apiKey?: string
|
|
125
|
+
/**
|
|
126
|
+
* Resolves the payer that the payment method has PROVEN controls this
|
|
127
|
+
* request. Required for free access — without it every request takes the
|
|
128
|
+
* paid path.
|
|
129
|
+
*
|
|
130
|
+
* It receives only mppx's method-specific validation `details` (for example
|
|
131
|
+
* a recovered payer address). It is deliberately NOT given the credential or
|
|
132
|
+
* the declared `source`: mppx documents `source` as "an asserted identity,
|
|
133
|
+
* not independent proof of control", and granting on it is the vulnerability
|
|
134
|
+
* fixed in 3.0.0 (GHSA-jg6q-3qfh-r9f8).
|
|
135
|
+
*
|
|
136
|
+
* Return `null` when no payer has been proven. Returning an address you have
|
|
137
|
+
* not proven reintroduces that vulnerability.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* provenPayer: (details) => (details as { payer?: string }).payer ?? null
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
provenPayer?: (details: unknown) => ProvenPayer | Promise<ProvenPayer>
|
|
107
145
|
/** One or more conditions to evaluate. Mix any of the six types. */
|
|
108
146
|
conditions: Condition[]
|
|
109
147
|
/** Whether the wallet must satisfy "any" (default) or "all" conditions. */
|
|
@@ -283,7 +321,8 @@ function buildBodyConditions(conditions: Condition[]): Array<Record<string, unkn
|
|
|
283
321
|
chainId: c.chainId,
|
|
284
322
|
}
|
|
285
323
|
if (c.type === 'token_balance') {
|
|
286
|
-
|
|
324
|
+
const threshold = c.threshold ?? '1'
|
|
325
|
+
cond.threshold = typeof threshold === 'string' ? threshold : String(threshold)
|
|
287
326
|
if (c.decimals !== undefined) cond.decimals = c.decimals
|
|
288
327
|
if (c.currency) cond.currency = c.currency
|
|
289
328
|
}
|
|
@@ -343,20 +382,60 @@ async function callAttest(
|
|
|
343
382
|
// conditionGate adapter
|
|
344
383
|
// ---------------------------------------------------------------------------
|
|
345
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Normalizes a caller-supplied proven payer into an address + wallet family.
|
|
387
|
+
*
|
|
388
|
+
* Fail-closed: anything it cannot resolve unambiguously returns null, which
|
|
389
|
+
* sends the request to the paid path.
|
|
390
|
+
*/
|
|
391
|
+
export function normalizeProvenPayer(
|
|
392
|
+
payer: ProvenPayer,
|
|
393
|
+
): { address: string; type: WalletType } | null {
|
|
394
|
+
if (!payer) return null
|
|
395
|
+
|
|
396
|
+
if (typeof payer === 'object') {
|
|
397
|
+
const { address, type } = payer
|
|
398
|
+
if (!address || !type) return null
|
|
399
|
+
return { address, type }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (typeof payer !== 'string') return null
|
|
403
|
+
|
|
404
|
+
const evm = parseDid(payer)
|
|
405
|
+
if (evm) return { address: evm, type: 'evm' }
|
|
406
|
+
const sol = parseSolanaDid(payer)
|
|
407
|
+
if (sol) return { address: sol, type: 'solana' }
|
|
408
|
+
const xrpl = parseXrplDid(payer)
|
|
409
|
+
if (xrpl) return { address: xrpl, type: 'xrpl' }
|
|
410
|
+
const btc = parseBitcoinDid(payer)
|
|
411
|
+
if (btc) return { address: btc, type: 'bitcoin' }
|
|
412
|
+
|
|
413
|
+
// Bare EVM address is unambiguous; anything else needs the object form.
|
|
414
|
+
if (payer.startsWith('0x')) return { address: payer, type: 'evm' }
|
|
415
|
+
return null
|
|
416
|
+
}
|
|
417
|
+
|
|
346
418
|
/**
|
|
347
419
|
* Wraps an mppx Method.Server with condition-based access using signed attestations.
|
|
348
420
|
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
421
|
+
* Free access requires a PROVEN payer. The gate calls the method's non-mutating
|
|
422
|
+
* `validate` hook, hands the resulting method-specific `details` to your
|
|
423
|
+
* `provenPayer` resolver, and only then evaluates conditions. It never reads
|
|
424
|
+
* `credential.source` — mppx documents that field as an asserted identity
|
|
425
|
+
* rather than proof of control.
|
|
426
|
+
*
|
|
427
|
+
* The gate falls through to the paid path whenever free access cannot be
|
|
428
|
+
* justified: no `provenPayer` resolver, no `validate` hook on the method
|
|
429
|
+
* (legacy `verify`-only methods included), a resolver returning null, a
|
|
430
|
+
* credential that fails validation, conditions not met, or an attestation
|
|
431
|
+
* error. There is no path from an unproven wallet to a free receipt.
|
|
353
432
|
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
433
|
+
* Conditions are evaluated across the 35 chains this adapter reaches (32 EVM +
|
|
434
|
+
* Solana + XRPL + Bitcoin; the engine itself covers 38). Six condition types
|
|
435
|
+
* are typed here: token_balance, nft_ownership, eas_attestation, farcaster_id,
|
|
436
|
+
* ratio_to_amount, and ratio_to_supply. InsumerAPI also offers evm_view_call,
|
|
437
|
+
* erc8004_agent, and erc7710_delegation; those are not typed or normalized
|
|
438
|
+
* here, so call /v1/attest directly if you need them.
|
|
360
439
|
*
|
|
361
440
|
* The attestation is ECDSA P-256 signed and verifiable offline via the public
|
|
362
441
|
* JWKS at https://insumermodel.com/.well-known/jwks.json.
|
|
@@ -366,6 +445,8 @@ async function callAttest(
|
|
|
366
445
|
* import { conditionGate } from '@insumermodel/mppx-condition-gate'
|
|
367
446
|
*
|
|
368
447
|
* const gated = conditionGate(tempoCharge, {
|
|
448
|
+
* // Return only a payer this method has proven for THIS request.
|
|
449
|
+
* provenPayer: (details) => (details as { payer?: string }).payer ?? null,
|
|
369
450
|
* conditions: [
|
|
370
451
|
* { type: 'eas_attestation', template: 'coinbase_verified_account', chainId: 8453 },
|
|
371
452
|
* { type: 'farcaster_id' },
|
|
@@ -380,92 +461,97 @@ export function conditionGate(
|
|
|
380
461
|
server: Method.AnyServer,
|
|
381
462
|
options: ConditionGateOptions,
|
|
382
463
|
): Method.AnyServer {
|
|
383
|
-
const { conditions, matchMode = 'any', cacheTtlSeconds = 300 } = options
|
|
464
|
+
const { conditions, matchMode = 'any', cacheTtlSeconds = 300, provenPayer } = options
|
|
384
465
|
|
|
385
|
-
const
|
|
466
|
+
const anyServer = server as unknown as {
|
|
467
|
+
verify: (params: any) => Promise<any>
|
|
468
|
+
broadcast?: (params: any) => Promise<any>
|
|
469
|
+
validate?: (params: any) => Promise<any>
|
|
470
|
+
}
|
|
386
471
|
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
472
|
+
const originalVerify = anyServer.verify
|
|
473
|
+
const originalBroadcast = anyServer.broadcast
|
|
474
|
+
const originalValidate = anyServer.validate
|
|
390
475
|
|
|
391
|
-
|
|
392
|
-
|
|
476
|
+
// Free access is only reachable when BOTH exist: a resolver to supply the
|
|
477
|
+
// proven payer, and a non-mutating validate hook to derive it from. A method
|
|
478
|
+
// without `validate` (legacy verify-only, or broadcast-without-validate) has
|
|
479
|
+
// no safe pre-check, so it is never gated.
|
|
480
|
+
const canGate =
|
|
481
|
+
typeof provenPayer === 'function' && typeof originalValidate === 'function'
|
|
393
482
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
483
|
+
/** Returns a free receipt, or null to take the paid path. */
|
|
484
|
+
async function tryFree(params: any): Promise<any | null> {
|
|
485
|
+
if (!canGate) return null
|
|
397
486
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (wallet) walletType = 'xrpl'
|
|
487
|
+
let details: unknown
|
|
488
|
+
try {
|
|
489
|
+
const validation = await originalValidate!(params)
|
|
490
|
+
details = (validation as { details?: unknown } | undefined)?.details
|
|
491
|
+
} catch {
|
|
492
|
+
// Credential is not currently acceptable → let the payment method decide.
|
|
493
|
+
return null
|
|
406
494
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
495
|
+
|
|
496
|
+
let resolved: ProvenPayer
|
|
497
|
+
try {
|
|
498
|
+
resolved = await provenPayer!(details)
|
|
499
|
+
} catch {
|
|
500
|
+
return null
|
|
410
501
|
}
|
|
411
502
|
|
|
412
|
-
|
|
413
|
-
if (!
|
|
503
|
+
const payer = normalizeProvenPayer(resolved)
|
|
504
|
+
if (!payer) return null
|
|
414
505
|
|
|
415
|
-
|
|
416
|
-
const key = cacheKey(wallet, conditions)
|
|
506
|
+
const key = cacheKey(payer.address, conditions)
|
|
417
507
|
const cached = cache.get(key)
|
|
418
508
|
if (cached && cached.expiresAt > Date.now()) {
|
|
419
|
-
if (cached.pass)
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
509
|
+
if (!cached.pass) return null
|
|
510
|
+
return {
|
|
511
|
+
method: server.name,
|
|
512
|
+
reference: `condition-gate:free:${cached.attestationId}`,
|
|
513
|
+
status: 'success' as const,
|
|
514
|
+
timestamp: new Date().toISOString(),
|
|
426
515
|
}
|
|
427
|
-
// Cached non-holder → fall through
|
|
428
|
-
return originalVerify(params)
|
|
429
516
|
}
|
|
430
517
|
|
|
431
|
-
// Call InsumerAPI
|
|
432
518
|
try {
|
|
433
|
-
const result = await callAttest(
|
|
519
|
+
const result = await callAttest(payer.address, payer.type, conditions, options)
|
|
434
520
|
const attestation = result.data.attestation
|
|
435
521
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
} else {
|
|
441
|
-
// "any" — at least one condition met
|
|
442
|
-
pass = attestation.results.some((r) => r.met)
|
|
443
|
-
}
|
|
522
|
+
const pass =
|
|
523
|
+
matchMode === 'all'
|
|
524
|
+
? attestation.pass
|
|
525
|
+
: attestation.results.some((r) => r.met)
|
|
444
526
|
|
|
445
|
-
// Cache the result
|
|
446
527
|
cache.set(key, {
|
|
447
528
|
pass,
|
|
448
529
|
attestationId: attestation.id,
|
|
449
530
|
expiresAt: Date.now() + cacheTtlSeconds * 1000,
|
|
450
531
|
})
|
|
451
532
|
|
|
452
|
-
if (pass)
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
533
|
+
if (!pass) return null
|
|
534
|
+
return {
|
|
535
|
+
method: server.name,
|
|
536
|
+
reference: `condition-gate:free:${attestation.id}`,
|
|
537
|
+
status: 'success' as const,
|
|
538
|
+
timestamp: new Date().toISOString(),
|
|
459
539
|
}
|
|
460
540
|
} catch {
|
|
461
|
-
// Attestation
|
|
541
|
+
// Attestation error → paid path.
|
|
542
|
+
return null
|
|
462
543
|
}
|
|
463
|
-
|
|
464
|
-
return originalVerify(params)
|
|
465
544
|
}
|
|
466
545
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
546
|
+
const wrapped = { ...server } as unknown as Record<string, unknown>
|
|
547
|
+
|
|
548
|
+
if (typeof originalBroadcast === 'function') {
|
|
549
|
+
wrapped.broadcast = async (params: any) =>
|
|
550
|
+
(await tryFree(params)) ?? originalBroadcast(params)
|
|
470
551
|
}
|
|
552
|
+
|
|
553
|
+
wrapped.verify = async (params: any) =>
|
|
554
|
+
(await tryFree(params)) ?? originalVerify(params)
|
|
555
|
+
|
|
556
|
+
return wrapped as unknown as Method.AnyServer
|
|
471
557
|
}
|