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:
- Checks for payment headers (
X-Payment or X402-PAYMENT)
- Verifies the payment signature and amount
- Stores payment info in the Elysia store
- 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:
| Property | Type | Description |
|---|
payload | PaymentPayload | Decoded payment payload |
payerAddress | string | Wallet address of the payer |
version | 1 | 2 | Payment protocol version |
verified | boolean | Whether 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);
});
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.