Skip to main content
Accept crypto payments in your Elysia API with a single middleware plugin.

Installation

# npm
npm install @armory-sh/middleware-elysia

# yarn
yarn add @armory-sh/middleware-elysia

# pnpm
pnpm add @armory-sh/middleware-elysia

# bun
bun add @armory-sh/middleware-elysia

Quick Start

import { Elysia } from 'elysia';
import { paymentMiddleware } from '@armory-sh/middleware-elysia';

const app = new Elysia()
  .use(paymentMiddleware({
    requirements: {
      to: '0xYourWalletAddress...',
      amount: 1000000n, // 1 USDC (6 decimals)
      expiry: Date.now() + 3600 * 1000,
      chainId: 'eip155:8453',
      assetId: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
    }
  }))
  .get('/api/protected', ({ store }) => {
    return { message: `Hello ${store.payment.payerAddress}!` };
  })
  .listen(3000);

How It Works

The middleware plugin:
  1. Checks for payment headers (X-Payment or X402-PAYMENT)
  2. Verifies the payment signature and amount
  3. Stores payment info in the Elysia store
  4. Proceeds to your handler if valid, or returns 402 if invalid

Configuration

Payment Requirements

{
  requirements: {
    to: string,           // Your wallet address
    amount: bigint,       // Amount in smallest unit
    expiry: number,       // Expiry timestamp (milliseconds)
    chainId: string,      // CAIP-2 chain ID
    assetId: string       // CAIP-19 asset ID
  }
}

Verification Options

Provide your verification backend configuration in middleware options for production deployments.

Advanced Mix-and-Match Config (Current API)

import { Elysia } from "elysia";
import { paymentMiddleware } from "@armory-sh/middleware-elysia";

const app = new Elysia().use(
  paymentMiddleware({
    payTo: "0x1234567890123456789012345678901234567890",
    chains: ["base", "skale-base"],
    tokens: ["usdc", "usdt", "weth", "wbtc"],
    amount: "1.0",
    facilitatorUrl: "https://fallback-facilitator.example",
    facilitatorUrlByChain: {
      base: "https://payai.example",
    },
    facilitatorUrlByToken: {
      base: { usdc: "https://payai.example" },
      "skale-base": {
        usdc: "https://payai.example",
        usdt: "https://kobaru.example",
        weth: "https://kobaru.example",
        wbtc: "https://kobaru.example",
      },
    },
    extensions: {
      bazaar: { info: { input: { sku: "pro-plan" } }, schema: { type: "object" } },
      "sign-in-with-x": { info: { domain: "api.example.com" }, schema: { type: "object" } },
    },
  }),
);
For full custom amount-per-combination control, use explicit requirements:
paymentMiddleware({
  requirements: [
    {
      scheme: "exact",
      network: "eip155:8453",
      amount: "1000000",
      asset: "0x833589fCD6eDb6E08f4c7C32D4f71B54bdA02913",
      payTo: "0x1234567890123456789012345678901234567890",
      maxTimeoutSeconds: 300,
    },
    {
      scheme: "exact",
      network: "eip155:1187947933",
      amount: "2500000",
      asset: "0x5f9beea3f6f22be1f4f8efc120f5095f58dbb8a2",
      payTo: "0x1234567890123456789012345678901234567890",
      maxTimeoutSeconds: 300,
    },
  ],
  facilitatorUrlByToken: {
    base: { usdc: "https://payai.example" },
    "skale-base": { usdt: "https://kobaru.example" },
  },
});
extensions are fail-open filtered based on facilitator capability; unsupported keys are omitted from challenge headers automatically.

Accessing Payment Info

Payment information is stored in the Elysia store:
app.get('/api/user', ({ store }) => {
  const payment = store.payment;

  return {
    payer: payment.payerAddress,
    paid: payment.payload.amount,
    token: payment.payload.token
  };
});
Available Properties:
PropertyTypeDescription
payloadPaymentPayloadDecoded payment payload
payerAddressstringWallet address of the payer
version1 | 2Payment protocol version
verifiedbooleanWhether verification passed

Type Safety

For full TypeScript support, type your Elysia instance:
import type { PaymentContext } from '@armory-sh/middleware-elysia';

const app = new Elysia<{ store: PaymentContext }>()
  .use(paymentMiddleware({ ... }))
  .get('/api/data', ({ store }) => {
    // store.payment is fully typed
    console.log(store.payment.payerAddress);
  });

Response Headers

On successful verification:
X-Payment-Verified: true
X-Payer-Address: 0x...
X-Payment-Response: {"status":"verified","payerAddress":"0x...","version":2}

Error Responses

402 Payment Required

No payment header was provided.
{
  "error": "Payment required",
  "requirements": { ... }
}

400 Invalid Payload

Payment header exists but couldn’t be decoded.
{
  "error": "Invalid payment payload",
  "message": "..."
}

400 Version Mismatch

Payment version doesn’t match requirements.
{
  "error": "Payment version mismatch",
  "expected": 2,
  "received": 1
}

402 Verification Failed

Payment signature or amount was invalid.
{
  "error": "Verification failed: ..."
}

Tips

Elysia’s store is shared across all middleware and routes. Payment info will be available in any route handler after the middleware runs.