This is pre-transaction gating: a signed yes-or-no before any machine payment executes. MPP routes charge money. @insumermodel/mppx-condition-gate puts that yes-or-no in front of any charge: wallets that meet your conditions get a free-access receipt referencing a per-call signed attestation, everyone else falls through to the normal paid path. First listed entry on Tempo's /extensions page (PR #445, merged March 23). The primitive underneath is the same wallet auth that backs every other InsumerAPI surface: read → evaluate → sign.
What it does
The Machine Payments Protocol (MPP) gives every payment method a typed shape. A Method.Server returns a Promise that resolves to a payment receipt. @insumermodel/mppx-condition-gate sits in front of any Method.Server and inserts one decision before the charge runs.
If the requesting wallet meets the configured conditions, the gate returns a free-access receipt that references a per-call signed attestation. If it does not, control falls through to the original Method.Server, and the charge proceeds normally.
Tempo's MPP documentation lists @insumermodel/mppx-condition-gate as the first entry on its /extensions page, merged March 23, 2026.
The gap MPP leaves open
MPP already carries the payer's wallet on every request. Each payment credential includes a credential.source field shaped like did:pkh:eip155:8453:0xABC.... The chain ID and address are part of the request envelope.
What MPP does not provide is the layer that decides whether a particular wallet should be charged at all. There is no native pattern for "this wallet meets a configured condition, give it a free receipt." Without an adapter, every Method.Server that wants this behavior has to:
- Parse the DID format
- Maintain RPC endpoints across however many chains it cares about
- Run its own ownership and attestation checks
- Cache the result
- Decide what to return when the check fails
Most projects skip the work. The ones that do build it end up reimplementing the same plumbing the next project will reimplement next month.
Four condition types
The package exposes one function. Hand it any Method.Server and a list of conditions. v2.0 supports four condition types. Mix any of them in a single call:
import { Mppx, tempo } from 'mppx/server'
import { conditionGate } from '@insumermodel/mppx-condition-gate'
const tempoCharge = tempo({
currency: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
recipient: '0xYourAddress',
})
const gatedCharge = conditionGate(tempoCharge, {
apiKey: process.env.INSUMER_API_KEY,
matchMode: 'any',
conditions: [
// Token holders pass
{ type: 'token_balance', contractAddress: '0xYourToken', chainId: 8453, threshold: 100 },
// NFT holders pass
{ type: 'nft_ownership', contractAddress: '0xYourNFT', chainId: 8453 },
// Coinbase-verified wallets pass
{ type: 'eas_attestation', template: 'coinbase_verified_account', chainId: 8453 },
// Wallets with a Farcaster account pass
{ type: 'farcaster_id' },
],
})
const mppx = Mppx.create({ methods: [gatedCharge] })
When a request lands, conditionGate extracts the wallet from credential.source, calls POST /v1/attest with that wallet and the conditions, and looks at the result.
If any of the conditions are met (or all of them, when matchMode: 'all'), the gate returns a receipt where the reference field is condition-gate:free:{attestationId}. The signed attestation is retrievable by ID, the receipt itself just points to it.
If no conditions are met, or the API is unreachable, the gate calls the original Method.Server's verify and lets the payment proceed normally.
What the adapter does not do
The adapter does not re-sign the attestation. The signature on the result is the one InsumerAPI produced, the adapter passes it through unchanged.
The adapter does not wrap the attestation in a second envelope. The receipt's reference field carries the attestation ID as a plain string, the attestation itself is retrievable byte-identical to what a direct curl to /v1/attest would produce.
The adapter does not introduce a second signing key. There is no MPP-side or adapter-side root key that signs anything. The only signature in the chain is the one from InsumerAPI's per-call signing.
The adapter does not call back to InsumerAPI for verification. Any downstream service can check the signature offline against the public JWKS.
The signed primitive stays untouched. The adapter is plumbing. It translates between MPP's request shape and the wallet auth call. It is not an actor that produces its own claims.
Why this shape composes
Method.Server is the right composition point. Every MPP payment method ships one. So the same conditionGate adapter applies to:
- Tempo charges
- Stripe charges
- Any other
Method.Serverbuilt on the MPP interface
It is also framework-agnostic. The adapter never touches request routing. It takes a Method.Server in and returns a Method.Server out. So the same package works under:
- Hono
- Express
- Elysia
- Next.js
- Any other framework that consumes an MPP server
And it is non-destructive. The adapter sits before the original verify, on miss it falls through to the original Method.Server. Adding it to a paid route does not break the paid path, it only adds a free path for wallets that meet the configured conditions.
Verifying offline
Every result that comes out of the attest call carries an ECDSA P-256 signature and a key ID (kid). The signing key is published at insumermodel.com/.well-known/jwks.json. Any party that has the attestation can verify the signature against that JWKS using any standard JOSE library. No callback to InsumerAPI required.
If you ask for jwt: true in the options, the same result also comes back as a standard ES256 JWT with the signed attestation as JWT claims. That turns the output into a drop-in bearer token. Any service that already verifies JWTs can verify the gate result without learning anything new.
Each result is a boolean per condition. Met or unmet. Boolean, not balance. The gate tells you whether the wallet meets the configured condition, never how much it holds.
Chains and pricing
InsumerAPI covers 33 chains today: 30 EVM (Ethereum, Base, Polygon, Arbitrum, Optimism, BNB, Avalanche, and 23 more), plus Solana, XRPL, and Bitcoin. Token and NFT conditions evaluate on any of these. EAS conditions evaluate on EVM chains. Farcaster ID conditions always evaluate on Optimism.
The wallet holder pays nothing at the gated route. The operator running the gate pays per attestation call out of the API key's credit balance.
Two ways to provision a key. POST /v1/keys/create takes an email and ships 10 free attestation credits, no credit card. POST /v1/keys/buy takes a USDC, USDT, or BTC transfer and provisions a key in one call. The transaction sender wallet is the identity, the payment is the auth. No email, no human in the loop. The first path is the natural fit for human-managed deployments, the second for an autonomous agent inside the gate.
Top up an existing key on-chain via POST /v1/credits/buy: USDC or USDT on any major EVM chain, USDC on Solana, or BTC on Bitcoin. Same rails the Method.Server underneath sits on, same self-serve loop.
Where this fits
The wallet auth primitive does not change shape when it lands inside MPP. It still reads on-chain state, evaluates conditions, and returns a signed boolean. @insumermodel/mppx-condition-gate is the thin adapter that lets MPP routes ask for that boolean across token, NFT, EAS, and Farcaster conditions before deciding whether to charge. Tempo's extensions page is the first listed surface. The shape extends to any payment-route platform that exposes a Method.Server-style interface.
Before a machine pays, it should qualify.
Top comments (0)