MeteraDocs

Build

Gates

Compose verified facts into an allow / allow_with_caveat / refuse decision — with a policy YOU write. Metera verifies, this decides, your app executes.

Metera is where a policy is written, not the owner of the policy. A gate runs the facts it already verifies — a token's risk score, a swap's real simulated outcome — and applies thresholds YOU supply. Nothing about "too risky" is Metera's opinion baked into the response; if your request has no policy, the call is refused, not given a default we picked for you.

Today there is one gate: evaluate_gate, for a swap on a Raydium CPMM or Meteora DAMM V2 pool. It runs solana.swap_quote and solana.token_risk and returns a verdict, reachable two ways — no install required for either.

The three verdicts

allow — nothing in the checks that ran gave a reason to block. Not "this is safe" — see scope on every verdict for exactly what was and was not covered.

allow_with_caveat — nothing tripped your policy, but at least one check came back without a definitive positive: a source with no data on the token, or a swap quote that only reached single_source instead of consensus. A source with no data is never relabelled "safe" — this is the third state that keeps that distinction real instead of a sentence buried in a passing result.

refuse — a threshold you set was crossed: risk score, price impact, staleness, minimum level, or a check that could not run at all.

Calling it — MCP

// tools/call "evaluate_gate"
{
  "pool": "Q2sPHPdUWFMg7M7wwrQKLrn619cAucfRsmhVJffodSp",
  "payer": "<your wallet>",
  "inputTokenAccount": "<your input token account>",
  "outputTokenAccount": "<your output token account>",
  "amountIn": "10000000",
  "policy": { "maxRiskScore": 50, "maxPriceImpactBps": 1000 }
}

Calling it — REST

curl -X POST https://api.metera.xyz/v1/gate \
  -H "Authorization: Bearer mk_live_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{
    "pool": "Q2sPHPdUWFMg7M7wwrQKLrn619cAucfRsmhVJffodSp",
    "payer": "<your wallet>",
    "inputTokenAccount": "<your input token account>",
    "outputTokenAccount": "<your output token account>",
    "amountIn": "10000000",
    "policy": { "maxRiskScore": 50, "maxPriceImpactBps": 1000 }
  }'

Same decision, two transports. Verified live: identical verdict and reasoning from both, every time.

Policy fields

{
  "maxRiskScore": 50,       // optional — omit to exclude destination-token risk from the
                            // decision entirely, not to assume it is fine
  "maxPriceImpactBps": 1000,// REQUIRED — no server-side default, there is no external
                            // floor to copy the way maxRiskScore can copy a source's own bucket
  "maxSlotsStale": 2,       // optional, defaults to 2
  "minLevel": "consensus"   // optional — see below
}

The guarantee: the token checked is the token delivered

An earlier version of this gate took outputMint as a request parameter and risk-checked it independently of the swap itself. A caller could pass a clean mint while the trade actually delivered something else — the gate would risk-check the wrong token and allow the real one through. Found live, fixed at the root: outputMint is not a request parameter any more, on either transport. solana.swap_quote runs first; whatever it resolves as the real output mint — derived from the pool, never typed by a caller — is what solana.token_risk checks.

Every verdict carries evaluatedOutputMint, so this is auditable, not just asserted:

// A real production call, deliberately passing a mismatched outputMint
// (an OFAC-sanctioned address) alongside the real pool above — an old
// field the API no longer reads. The response, verbatim:
{
  "verdict": "allow",
  "evaluatedOutputMint": "Dz9mQ9NzkBcCsuGPFJ3r1bS4wgqKMHBPiVuniW8Mbonk",
  "reasoning": "allow: token_risk scored below the threshold with real data, the quote is corroborated and not reverted, within price-impact and freshness policy",
  "checks": [
    { "type": "solana.token_risk", "status": "delivered", "trippedPolicy": false, "traceId": "tr_byc101" },
    { "type": "solana.swap_quote", "status": "delivered", "trippedPolicy": false, "traceId": "tr_94czxw" }
  ],
  "debitedCreditsTotal": 8
}

evaluatedOutputMint is the pool's real mint — the mismatched value was silently ignored, exactly as designed. A caller who wants to confirm this doesn't have to trust the sentence, they can read the field.

The solana.token_risk check this gate runs now composes Webacy's score with Token-2022 extensions, LP-burn status, and a lock/vesting scan — see the Known limits page for the full reference and caveats. Nothing here changes: the gate still reads only overallRisk against maxRiskScore, and still reports no_data — with the same caveat below — when Webacy specifically has no data for the token, even though the on-chain sections may still be present.

allow_with_caveat, real output

Same pool, same trade, no policy change — the swap quote reached single_source instead of consensus on this particular call:

{
  "verdict": "allow_with_caveat",
  "reasoning": "allow_with_caveat: no check tripped policy, but solana.swap_quote: only reached level \"single_source\" — amountOut is not corroborated",
  "checks": [
    { "type": "solana.token_risk", "status": "delivered", "level": "single_source", "trippedPolicy": false },
    { "type": "solana.swap_quote", "status": "delivered", "level": "single_source", "trippedPolicy": false }
  ]
}

minLevel — the corroboration threshold, made actionable

A degraded quote used to be informational only — a caveat a caller could read but never enforce. minLevel makes it a real threshold. Set to consensus, the exact same call above now refuses instead of carrying a caveat:

// policy: { "maxRiskScore": 50, "maxPriceImpactBps": 1000, "minLevel": "consensus" }
{
  "verdict": "refuse",
  "reasoning": "refuse: solana.swap_quote reached level \"single_source\", below minLevel \"consensus\""
}

minLevel applies to solana.swap_quote only, not solana.token_risk. The risk check is structurally single-source today — one provider, no fan-out — so enforcing a level on it would refuse every risk-evaluated call regardless of what that call actually found, forever. That is friction with no safety behind it. The quote's level genuinely varies call to call (same-slot dual-RPC agreement can degrade under real conditions), so that is where a threshold means something.

Billing

No gate fee. debitedCreditsTotal is exactly the sum of what the two underlying checks cost — each one billed exactly as it would be calling get_verified_data directly, itemized via its own traceId in checks[]. When solana.swap_quote does not deliver, there is no authoritative mint to risk-check, so solana.token_risk is skipped rather than run against something unverified — and nothing is charged for a check that would have been discarded anyway.

Privacy

Your policy and the computed verdict are never persisted — they are returned in the response and nowhere else. The two underlying checks are logged exactly as they would be if you called get_verified_data directly for each: the identifier checked, not your threshold and not the decision. A trader's risk policy is strategy; Metera does not retain it.

Scope

What every verdict covers, verbatim from a real response:

"covers": [
  "the token the swap actually delivers, flagged as malicious, when maxRiskScore is set (solana.token_risk) — derived from the swap itself, never a caller-supplied mint",
  "the swap reverting against current on-chain state (solana.swap_quote)",
  "price impact above the caller's own maxPriceImpactBps",
  "the swap quote being staler than the caller's own maxSlotsStale"
],
"doesNotCover": [
  "MEV/sandwich risk between this check and actual execution",
  "state changes after the verified slot",
  "anything about a pool that is not Raydium CPMM or Meteora DAMM V2 — solana.swap_quote refuses cleanly (pool_unrecognized) rather than guessing at an unsupported AMM"
]

Meteora DAMM V2's priceImpactBps is reported the same way CPMM's is, but reads oddly on a concentrated-liquidity pool: a real trade against one can show a triple-digit basis-point number while amountOut is still the correct, real simulated result — concentrated liquidity moves the effective price faster inside a narrow range than the constant-product formula priceImpactBps was modelled on. Treat amountOut as authoritative on a concentrated pool and priceImpactBps as noisier there, not wrong.

Gate-then-execute — the pattern, and its limit

A gate answers “should this proceed” — it does not act on the answer, that is your code's job. The pattern: call the gate first, only run your own execution if the verdict allows it.

const verdict = await fetch('https://api.metera.xyz/v1/gate', {
  method: 'POST',
  headers: { Authorization: 'Bearer mk_live_YOUR_KEY', 'Content-Type': 'application/json' },
  body: JSON.stringify({ pool, payer, inputTokenAccount, outputTokenAccount, amountIn, policy }),
}).then((r) => r.json())

if (verdict.verdict === 'allow' || verdict.verdict === 'allow_with_caveat') {
  await yourExecution(...)   // whatever actually moves value — your backend, your program
}
// verdict.verdict === 'refuse' -> do not execute

The honest limit: this is convention, not enforcement. Nothing here stops your own backend from skipping the call, or executing anyway after a refuse — the check binds because your code chooses to respect it, not because anything requires it to. That is a real, useful guarantee: it moves the decision out of scattered ad-hoc logic into one verified call instead of trusting each call site to get it right. It is not the same claim as “this action cannot happen without Metera's verification.” Making that a structural guarantee — a program that itself cannot execute without a verified attest, checked on-chain rather than by convention — is roadmap, not shipped.

Roadmap

Live today: evaluate_gate (MCP) and POST /v1/gate (REST), for a Raydium CPMM or Meteora DAMM V2 pool.
Roadmap, not present: @metera/gates, a standalone open-source package that runs the identical decision logic client-side, for a caller who does not want their policy passing through Metera's server at all — built, not yet published. Other Solana AMMs beyond these two. Neither exists as a public surface yet; both are described here only so the direction is clear, not because either can be called today.