SkyeGate

If their wallet meets the condition, they see the content. If it doesn't, they don't.

SkyeGate Pro for Vercel

Drop a wallet gate into any Next.js app. Bring your own wallet stack: wagmi, RainbowKit, ConnectKit, Privy, whatever you already use. @skyemeta/skyegate handles the gating layer. Four condition types, 37 blockchains, server-side JWT validation, same SKYE license key as the WordPress plugin.

npm install @skyemeta/skyegate

Powered by The Insumer Model™ condition-based access API.

Five Minutes to a Wallet Gate

Three steps. No configuration. No magic.

1. Install

npm install @skyemeta/skyegate

Peer deps: react >=18 (only if you import the React entry) and @noble/post-quantum (optional, to verify the post-quantum companion). Runtime dep: jose. Zero wallet libs. You use your own.

2. Set your license key

# .env.local
NEXT_PUBLIC_SKYE_LICENSE_KEY=SKYE-XXXX-XXXX-XXXX

Your SKYE license key is safe to expose. The proxy auto-binds it to your production domain on first use; other apexes are rejected.

3. Drop in <GatedContent />

Wrap whatever you want gated. Prove wallet ownership once when the wallet connects (a free EIP-191 signature; the proxy requires it for licensed calls), then the component handles the verify call, status transitions, and conditional rendering. Server-side validation goes in a route handler.

app/page.tsx
'use client';
import { useEffect, useState } from 'react';
import { useAccount } from 'wagmi';
import { proveWalletOwnership } from '@skyemeta/skyegate';
import { GatedContent } from '@skyemeta/skyegate/react';

export default function Page() {
  const { address } = useAccount();

  // One free signature proves the visitor controls the
  // address; the token covers the whole visit.
  const [proof, setProof] = useState<string>();
  useEffect(() => {
    setProof(undefined);
    if (!address) return;
    proveWalletOwnership({ address, provider: window.ethereum })
      .then((r) => setProof(r.proofToken ?? undefined));
  }, [address]);

  return (
    <GatedContent
      address={address}
      walletProof={proof}
      enabled={!!proof}
      conditions={[{ type: 'farcaster_id' }]}
      licenseKey={process.env.NEXT_PUBLIC_SKYE_LICENSE_KEY!}
      loading={<p>Verifying…</p>}
      fallback={<p>Connect a Farcaster-linked wallet.</p>}
      onPass={async (jwt, pqJwt) => {
        const res = await fetch('/api/gated', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ jwt, pqJwt }),
        });
        // render res.json().secret …
      }}
    >
      <p>Welcome, Farcaster user.</p>
    </GatedContent>
  );
}
app/api/gated/route.ts
import { validateContentToken } from '@skyemeta/skyegate';

export const runtime = 'nodejs';

export async function POST(req: Request) {
  const { jwt, pqJwt } = await req.json();
  const result = await validateContentToken(jwt, {
    pqJwt, // post-quantum companion; reported as result.pq
    expectedConditions: [{ type: 'farcaster_id' }],
  });
  if (!result.pass) {
    return Response.json(
      { error: result.error },
      { status: 403 },
    );
  }
  return Response.json({
    secret: 'Real gated content here.',
  });
}

Clone the full starter app →

Four Condition Types

Same vocabulary as the WordPress plugin. Stack up to 10 per gate; pass=true requires all of them to be met.

Token balance

ERC-20, SPL, native ETH/SOL/MATIC, XRPL trust line tokens, plus Bitcoin, Tron, Stellar & Sui. 37 chains.

{
  type: 'token_balance',
  contractAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  chainId: 1,
  threshold: '100',    // decimal string — keeps full precision
  decimals: 6,         // USDC
}

NFT ownership

ERC-721, ERC-1155, Solana SPL NFTs. Match by collection contract.

{
  type: 'nft_ownership',
  contractAddress: '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85',
  chainId: 1,          // ENS .eth
}

EAS attestation

Ethereum Attestation Service templates: Coinbase Verified, Gitcoin Passport, more.

{
  type: 'eas_attestation',
  template: 'coinbase_verified_account',
}

Farcaster identity

Wallet linked to a Farcaster account (FID present). New in Pro 1.4.0.

{
  type: 'farcaster_id',
}

Match the same conditions in validateContentToken's expectedConditions on the server side. That's how a JWT earned for one route can't unlock another.

API at a Glance

proveWalletOwnership(params)

Promise<{ proofToken, expiresInSec?, error? }>

Proves the person present controls the wallet, not just that an address was supplied. Requests a one-time challenge, has the wallet sign it (EIP-191 personal_sign: free, gasless, no transaction; smart wallets verify on-chain via EIP-1271/6492), and returns a session-scoped proof token. The signature goes from the visitor's browser to the proof endpoint directly and never touches your server.

Required params: address, plus provider (e.g. window.ethereum) or a signMessage callback (wagmi's signMessageAsync, viem wallet clients, Privy). Prove once per visit; pass the token as walletProof below. EVM wallets in this wave.

verifyConditions(params)

Promise<{ pass, jwt, pqJwt, raw, error? }>

Low-level imperative call. The React hook + component use this internally; reach for it in non-React contexts (server actions, API routes, Node scripts).

Required params: address, conditions[], licenseKey, and walletProof (from proveWalletOwnership; the proxy rejects licensed EVM calls without it). Optional: walletType ('evm' | 'solana'), endpoint, domain.

validateContentToken(jwt, options?)

Promise<{ valid, pass, payload?, pq?, error? }>

Server-side JWT validation. Verifies the ECDSA P-256 signature against InsumerAPI's JWKS, checks issuer + expiry via jose, and (if you pass expectedConditions) confirms the signed conditions match what the route requires.

Since September 2026 InsumerAPI returns an ML-DSA-65 post-quantum companion (pqJwt) beside the ES256 jwt. Pass it as options.pqJwt and the result carries pq: verified, refuted, absent, or unverifiable, on every outcome once the JWT verified. A refuted companion always rejects; an absent or unverifiable one rejects only past your own pqRequiredFrom cutoff. Install the optional peer @noble/post-quantum to verify companions.

Trust the math, not a company, including us. Every result is independently verifiable by any third party using the public JWKS.

If you cache the result, key it on the JWT

Nothing here caches a verdict: every call re-verifies the signature, so a route that follows the examples above is checking cryptographic proof on every request. Caching that away is a reasonable optimisation, and it is where this goes wrong.

Key any cache on something only the person who passed could produce — the JWT itself, or a session you issued them. Never on the wallet address, and never on a content or product id. Both are public. A cached “yes” filed under a public value can be claimed by anyone who knows it, and because a cache hit returns before your validation runs, the signature you were relying on is never checked.

A wallet address feels like an identifier for a person. It is not a secret, it is a name anyone can read off a block explorer, and anyone can send you one.

useSkyeGate(options)

{ status, pass, jwt, pqJwt, error, refetch }

React hook. Same options as verifyConditions (including walletProof), plus enabled to gate the call. status cycles through 'idle' → 'verifying' → 'pass' | 'fail' | 'error'.

<GatedContent />

JSX.Element

Declarative wrapper. Renders children on pass, fallback otherwise, loading while verifying. Optional onPass(jwt, pqJwt?) callback fires once when the gate first passes; forward both tokens to your server. Use it to fetch gated content from your server.

Full API reference on GitHub →

How It Works

your Next.js app
  ↓ proveWalletOwnership(address, provider)
  ↓
skyemeta.com/api/wallet-proof  ← one-time challenge; the wallet signs
  ↓                           (EIP-191, free) and a session-scoped
  ↓                           proof token comes back
  ↓ verifyConditions(address, conditions, licenseKey, walletProof)
  ↓
skyemeta.com/api/verify    ← SkyeMeta proxy validates SKYE key + domain
  ↓                       + the ownership proof for the address
  ↓
api.insumermodel.com       ← InsumerAPI returns a signed boolean
  ↓                       (no balances exposed)
JWT signed with ECDSA P-256
  ↓ POSTed to your server
  ↓
validateContentToken(jwt, { pqJwt })
  ↓                    ← jose + JWKS, signature + issuer + expiry +
  ↓                       condition match + post-quantum companion (pq)
gated content delivered

The wallet's actual balances never reach your server or your customers. Only the signed yes-or-no on whether the condition was met. Trust the math, not a company, including us.

One License, One Site, with Sensible Defaults

Auto-bind on first production use

Your license has no domain at first. The first call from a real apex (e.g. myapp.com) auto-binds the license to that apex. Subsequent calls from myapp.com and any subdomain (staging.myapp.com, preview.myapp.com, app.myapp.com) are accepted.

Calls from a different apex are rejected with License bound to myapp.com.

Dev / preview hosts skip the bind

localhost, 127.0.0.1, *.vercel.app, and *.local verify normally but never bind the license. Vercel preview deploys, local dev, and .local hostnames don't burn your production bind.

Treat your license key as you would any per-domain credential. To move a key to a different production apex, use the self-serve Move License to New Domain flow at skyemeta.com/account.

WordPress Plugin vs Vercel SDK

WordPress plugin Vercel SDK (this page)
DistributionDirect .zip from skyemeta.comnpm install @skyemeta/skyegate
StackPHP + bundled JSTypeScript / React
Wallet connectBundled, multi-walletBring your own (wagmi, RainbowKit, ConnectKit, Privy)
License keySame SKYE keySame SKYE key
ConditionsAll 4 types, up to 10 stackedAll 4 types, up to 10 stacked
Chains3737
SourceClosed (proprietary plugin)Open (MIT, GitHub)

One Pro license. Pick the channel that matches your stack, or use both, on the same key, no extra cost.

Get a Key

SkyeGate Pro is $49/mo or $350/yr per domain. Same key works on the WordPress plugin and this SDK.

$350/yr, Save 40% or subscribe monthly

Comparison, FAQ, and the WordPress option at skyemeta.com/skyegate.

Same Engine. Different Use Cases.

Ready to gate some content?

Bring your own wallet stack. We bring the gating layer.