{"_id":"@bober3r/solana-payment-channels-server","name":"@bober3r/solana-payment-channels-server","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@bober3r/solana-payment-channels-server","version":"0.1.0","description":"Server-side middleware and integrations for x402 payment channels on Solana (Express, NestJS, Fastify)","main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./express":{"types":"./dist/express.d.ts","import":"./dist/express.mjs","require":"./dist/express.js"},"./nestjs":{"types":"./dist/nestjs.d.ts","import":"./dist/nestjs.mjs","require":"./dist/nestjs.js"},"./fastify":{"types":"./dist/fastify.d.ts","import":"./dist/fastify.mjs","require":"./dist/fastify.js"}},"scripts":{"build":"tsup src/index.ts src/express.ts src/nestjs.ts src/fastify.ts --format cjs,esm --dts --clean","dev":"tsup src/index.ts src/express.ts src/nestjs.ts src/fastify.ts --format cjs,esm --dts --watch","test":"vitest run","test:watch":"vitest","lint":"eslint src --ext .ts","lint:fix":"eslint src --ext .ts --fix","typecheck":"tsc --noEmit","clean":"rm -rf dist"},"keywords":["solana","x402","payment-channels","express","nestjs","fastify","middleware","blockchain","web3","server"],"author":{"name":"BOBER3r"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/BOBER3r/solana-payment-channel-kit.git","directory":"packages/server"},"bugs":{"url":"https://github.com/BOBER3r/solana-payment-channel-kit/issues"},"homepage":"https://github.com/BOBER3r/solana-payment-channel-kit#readme","dependencies":{"@solana/web3.js":"^1.95.8","@bober3r/solana-payment-channels-core":"workspace:*","@x402-solana/server":"latest"},"peerDependencies":{"@nestjs/common":"^10.0.0","express":"^4.18.0","fastify":"^4.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true},"express":{"optional":true},"fastify":{"optional":true}},"devDependencies":{"@nestjs/common":"^10.4.20","@nestjs/core":"^10.0.0","@types/express":"^5.0.0","@types/node":"^20.19.24","eslint":"^9.17.0","express":"^4.21.2","fastify":"^5.2.0","fastify-plugin":"^5.1.0","tsup":"^8.3.5","typescript":"^5.9.3","vitest":"^2.1.8"},"engines":{"node":">=18.0.0"},"_id":"@bober3r/solana-payment-channels-server@0.1.0","gitHead":"46bab13f27fd923d3ff1ca80c1d24dbd000ce11d","_nodeVersion":"22.14.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-tMDH4GJXyRUphS5IIehQIPo3rDx3CqGztbu3Ayw8XN/nwT4FF3WalYLT2L/5EBJ5fPciaswoaTwHWd8HqiGrSA==","shasum":"3f09f94e9263c9bad1c2d800148320391b0bd22a","tarball":"https://registry.npmjs.org/@bober3r/solana-payment-channels-server/-/solana-payment-channels-server-0.1.0.tgz","fileCount":20,"unpackedSize":9352312,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCayVbqqrSSWM+hzOcXKXz7PeL1ssH5i5v16jLoURvwCQIgGPH+ZLobMXdb7W7G0i86HP526lDkqDiYHltWGLm9o+8="}]},"_npmUser":{"name":"bober3r","email":"BOBER3r@proton.me"},"directories":{},"maintainers":[{"name":"bober3r","email":"BOBER3r@proton.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/solana-payment-channels-server_0.1.0_1762457275557_0.013339770824687625"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-06T19:27:55.445Z","0.1.0":"2025-11-06T19:27:55.799Z","modified":"2025-11-06T19:27:56.134Z"},"maintainers":[{"name":"bober3r","email":"BOBER3r@proton.me"}],"description":"Server-side middleware and integrations for x402 payment channels on Solana (Express, NestJS, Fastify)","homepage":"https://github.com/BOBER3r/solana-payment-channel-kit#readme","keywords":["solana","x402","payment-channels","express","nestjs","fastify","middleware","blockchain","web3","server"],"repository":{"type":"git","url":"git+https://github.com/BOBER3r/solana-payment-channel-kit.git","directory":"packages/server"},"author":{"name":"BOBER3r"},"bugs":{"url":"https://github.com/BOBER3r/solana-payment-channel-kit/issues"},"license":"MIT","readme":"# @bober3r/solana-payment-channels-server\n\n[![npm version](https://badge.fury.io/js/%40bober3r%2Fsolana-payment-channels-server.svg)](https://www.npmjs.com/package/@bober3r/solana-payment-channels-server)\n[![npm downloads](https://img.shields.io/npm/dm/%40bober3r%2Fsolana-payment-channels-server.svg)](https://www.npmjs.com/package/@bober3r/solana-payment-channels-server)\n[![GitHub issues](https://img.shields.io/github/issues/BOBER3r/solana-payment-channel-kit)](https://github.com/BOBER3r/solana-payment-channel-kit/issues)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Node.js Version](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen)](https://nodejs.org/)\n\nServer-side middleware and integrations for x402 payment channels on Solana. Accept instant, free off-chain payments via payment channels with automatic fallback to on-chain x402 protocol.\n\n## Features\n\n- **Off-chain Payment Channels**: Process payments instantly with zero transaction fees\n- **Automatic x402 Fallback**: Seamlessly fall back to on-chain payments when channels unavailable\n- **Multi-Framework Support**: Native integrations for Express, NestJS, and Fastify\n- **Type-Safe**: Built with TypeScript for complete type safety\n- **Production-Ready**: Comprehensive error handling, validation, and event system\n- **Easy Integration**: Simple middleware/guard/plugin patterns for each framework\n- **Server Capabilities**: Automatic discovery endpoint for client configuration\n\n## Installation\n\n```bash\nnpm install @x402-channels/server @x402-channels/core @solana/web3.js\n```\n\n## Quick Start\n\n### Express\n\n```typescript\nimport express from 'express';\nimport { Keypair, PublicKey } from '@solana/web3.js';\nimport {\n  ChannelPaymentService,\n  channelAuthMiddleware\n} from '@x402-channels/server/express';\n\nconst app = express();\napp.use(express.json());\n\n// Initialize payment service\nconst paymentService = new ChannelPaymentService({\n  rpcUrl: process.env.SOLANA_RPC_URL!,\n  network: 'devnet',\n  programId: new PublicKey(process.env.CHANNEL_PROGRAM_ID!),\n  usdcMint: new PublicKey(process.env.USDC_MINT!),\n  recipientWallet: new PublicKey(process.env.RECIPIENT_WALLET!),\n  serverKeypair: serverKeypair // Optional: for channel claiming\n});\n\n// Expose capabilities for client discovery\napp.get('/.well-known/x402-capabilities', (req, res) => {\n  res.json(paymentService.getCapabilities());\n});\n\n// Protected endpoint - requires 1 USDC payment\napp.get('/api/premium',\n  channelAuthMiddleware(paymentService, {\n    amount: BigInt(1_000_000) // 1 USDC\n  }),\n  (req, res) => {\n    res.json({\n      content: 'Premium content',\n      payment: req.payment\n    });\n  }\n);\n\napp.listen(3000);\n```\n\n### NestJS\n\n```typescript\nimport { Module, Controller, Get, UseGuards, Inject } from '@nestjs/common';\nimport { Keypair, PublicKey } from '@solana/web3.js';\nimport {\n  ChannelPaymentService,\n  ChannelPaymentGuard,\n  RequirePayment,\n  Payment,\n  PaymentResult\n} from '@x402-channels/server/nestjs';\n\n// Configure module\n@Module({\n  providers: [\n    {\n      provide: 'CHANNEL_PAYMENT_SERVICE',\n      useFactory: () => {\n        return new ChannelPaymentService({\n          rpcUrl: process.env.SOLANA_RPC_URL!,\n          network: 'devnet',\n          programId: new PublicKey(process.env.CHANNEL_PROGRAM_ID!),\n          usdcMint: new PublicKey(process.env.USDC_MINT!),\n          recipientWallet: serverKeypair.publicKey,\n          serverKeypair: serverKeypair\n        });\n      }\n    },\n    ChannelPaymentGuard\n  ],\n  controllers: [AppController, ApiController]\n})\nexport class AppModule {}\n\n// Public controller\n@Controller()\nexport class AppController {\n  constructor(\n    @Inject('CHANNEL_PAYMENT_SERVICE')\n    private readonly paymentService: ChannelPaymentService\n  ) {}\n\n  @Get('.well-known/x402-capabilities')\n  getCapabilities() {\n    return this.paymentService.getCapabilities();\n  }\n}\n\n// Protected controller\n@Controller('api')\n@UseGuards(ChannelPaymentGuard)\nexport class ApiController {\n  @Get('premium')\n  @RequirePayment(1_000_000n) // 1 USDC\n  getPremiumContent(@Payment() payment: PaymentResult) {\n    return {\n      content: 'Premium content',\n      method: payment.method,\n      balance: payment.remainingBalance?.toString()\n    };\n  }\n}\n```\n\n### Fastify\n\n```typescript\nimport Fastify from 'fastify';\nimport { Keypair, PublicKey } from '@solana/web3.js';\nimport channelPaymentPlugin from '@x402-channels/server/fastify';\n\nconst fastify = Fastify({ logger: true });\n\n// Register payment plugin\nawait fastify.register(channelPaymentPlugin, {\n  rpcUrl: process.env.SOLANA_RPC_URL!,\n  network: 'devnet',\n  programId: new PublicKey(process.env.CHANNEL_PROGRAM_ID!),\n  usdcMint: new PublicKey(process.env.USDC_MINT!),\n  recipientWallet: serverKeypair.publicKey,\n  serverKeypair: serverKeypair,\n  exposeCapabilities: true // Automatically adds /.well-known/x402-capabilities\n});\n\n// Protected route\nfastify.get('/api/premium', {\n  preHandler: fastify.requirePayment({ amount: 1_000_000n })\n}, async (request, reply) => {\n  return {\n    content: 'Premium content',\n    payment: request.payment\n  };\n});\n\nawait fastify.listen({ port: 3000 });\n```\n\n## Configuration\n\n### ChannelPaymentServiceConfig\n\n```typescript\ninterface ChannelPaymentServiceConfig {\n  // Required\n  rpcUrl: string;                    // Solana RPC endpoint\n  network: 'devnet' | 'mainnet-beta';\n  programId: PublicKey;              // Payment channel program ID\n  usdcMint: PublicKey;               // USDC token mint address\n  recipientWallet: PublicKey;        // Server's recipient wallet\n\n  // Optional\n  serverKeypair?: Keypair;           // Required for claiming channel payments\n  defaultExpiry?: number;            // Default: 604800 (7 days)\n  minBalance?: bigint;               // Default: 1_000_000 (1 USDC)\n  enableFallback?: boolean;          // Default: true\n  cacheTTL?: number;                 // Default: 30000 (30 seconds)\n}\n```\n\n## Express API\n\n### channelAuthMiddleware\n\nMiddleware that enforces payment requirements:\n\n```typescript\nimport { channelAuthMiddleware } from '@x402-channels/server/express';\n\n// Fixed price\napp.get('/api/data',\n  channelAuthMiddleware(paymentService, {\n    amount: BigInt(1_000_000)\n  }),\n  handler\n);\n\n// Dynamic pricing\napp.post('/api/process',\n  channelAuthMiddleware(paymentService, {\n    amount: async (req) => {\n      const items = req.body.items || [];\n      return BigInt(items.length * 100_000); // 0.1 USDC per item\n    }\n  }),\n  handler\n);\n\n// Channel-only (no x402 fallback)\napp.get('/api/channel-only',\n  channelAuthMiddleware(paymentService, {\n    amount: BigInt(1_000_000),\n    requireChannel: true\n  }),\n  handler\n);\n\n// Custom error handling\napp.get('/api/custom',\n  channelAuthMiddleware(paymentService, {\n    amount: BigInt(1_000_000),\n    onError: (error, req, res) => {\n      res.status(402).json({ error: error.message });\n    }\n  }),\n  handler\n);\n```\n\n### extractPaymentMiddleware\n\nExtract payment without enforcing it:\n\n```typescript\nimport { extractPaymentMiddleware } from '@x402-channels/server/express';\n\napp.get('/api/content',\n  extractPaymentMiddleware(paymentService),\n  (req, res) => {\n    if (req.payment?.success) {\n      res.json({ content: 'Premium', tier: 'paid' });\n    } else {\n      res.json({ content: 'Basic', tier: 'free' });\n    }\n  }\n);\n```\n\n### Helpers\n\n```typescript\nimport {\n  getPaymentResult,\n  hasValidPayment,\n  getPaymentMethod\n} from '@x402-channels/server/express';\n\napp.get('/api/info', (req, res) => {\n  const payment = getPaymentResult(req);\n  const isValid = hasValidPayment(req);\n  const method = getPaymentMethod(req);\n\n  res.json({ payment, isValid, method });\n});\n```\n\n## NestJS API\n\n### ChannelPaymentGuard\n\nGuard that enforces payment requirements:\n\n```typescript\nimport {\n  ChannelPaymentGuard,\n  RequirePayment,\n  Payment,\n  PaymentResult\n} from '@x402-channels/server/nestjs';\n\n@Controller('api')\n@UseGuards(ChannelPaymentGuard)\nexport class ApiController {\n  // Fixed price\n  @Get('premium')\n  @RequirePayment(1_000_000n)\n  getPremium(@Payment() payment: PaymentResult) {\n    return { content: 'Premium', payment };\n  }\n\n  // Dynamic pricing\n  @Post('process')\n  @RequirePayment((context) => {\n    const request = context.switchToHttp().getRequest();\n    return BigInt(request.body.items.length * 100_000);\n  })\n  processData(@Body() body: any) {\n    return { processed: body.items.length };\n  }\n\n  // Channel-only\n  @Get('channel-only')\n  @RequirePayment(1_000_000n, { requireChannel: true })\n  getChannelOnly() {\n    return { content: 'Channel-only' };\n  }\n}\n```\n\n### Decorators\n\n```typescript\nimport {\n  Payment,\n  PaymentMethod,\n  ChannelId,\n  RemainingBalance\n} from '@x402-channels/server/nestjs';\n\n@Controller('api')\n@UseGuards(ChannelPaymentGuard)\nexport class ApiController {\n  // Inject full payment result\n  @Get('info1')\n  @RequirePayment(1_000_000n)\n  getInfo1(@Payment() payment: PaymentResult) {\n    return payment;\n  }\n\n  // Inject specific properties\n  @Get('info2')\n  @RequirePayment(1_000_000n)\n  getInfo2(\n    @PaymentMethod() method: string,\n    @ChannelId() channelId: string,\n    @RemainingBalance() balance: bigint\n  ) {\n    return { method, channelId, balance: balance?.toString() };\n  }\n}\n```\n\n### Class-level Payment Requirements\n\n```typescript\n// Apply to entire controller\n@Controller('premium')\n@UseGuards(ChannelPaymentGuard)\n@RequirePayment(1_000_000n) // All routes require 1 USDC\nexport class PremiumController {\n  @Get('content1')\n  getContent1() {\n    return { data: 'Content 1' };\n  }\n\n  @Get('content2')\n  getContent2() {\n    return { data: 'Content 2' };\n  }\n\n  // Override with different price\n  @Get('vip')\n  @RequirePayment(5_000_000n)\n  getVip() {\n    return { data: 'VIP content' };\n  }\n}\n```\n\n## Fastify API\n\n### Plugin Registration\n\n```typescript\nimport channelPaymentPlugin from '@x402-channels/server/fastify';\n\nawait fastify.register(channelPaymentPlugin, {\n  rpcUrl: process.env.SOLANA_RPC_URL!,\n  network: 'devnet',\n  programId: new PublicKey(process.env.PROGRAM_ID!),\n  usdcMint: new PublicKey(process.env.USDC_MINT!),\n  recipientWallet: serverPublicKey,\n  serverKeypair: serverKeypair,\n  exposeCapabilities: true,  // Add /.well-known/x402-capabilities\n  exposeStats: false         // Add /payment/stats (protect in production!)\n});\n```\n\n### Route Protection\n\n```typescript\n// Fixed price\nfastify.get('/api/premium', {\n  preHandler: fastify.requirePayment({ amount: 1_000_000n })\n}, async (request, reply) => {\n  return { content: 'Premium', payment: request.payment };\n});\n\n// Dynamic pricing\nfastify.post('/api/process', {\n  preHandler: fastify.requirePayment({\n    amount: async (req) => {\n      const body = req.body as any;\n      return BigInt(body.items.length * 100_000);\n    }\n  })\n}, async (request, reply) => {\n  return { processed: true };\n});\n\n// Optional payment\nfastify.get('/api/content', {\n  preHandler: fastify.extractPayment()\n}, async (request, reply) => {\n  if (request.payment?.success) {\n    return { content: 'Premium', tier: 'paid' };\n  }\n  return { content: 'Basic', tier: 'free' };\n});\n```\n\n### Helpers\n\n```typescript\nimport {\n  getPayment,\n  hasValidPayment,\n  getPaymentMethod\n} from '@x402-channels/server/fastify';\n\nfastify.get('/api/info', async (request, reply) => {\n  const payment = getPayment(request);\n  const isValid = hasValidPayment(request);\n  const method = getPaymentMethod(request);\n\n  return { payment, isValid, method };\n});\n```\n\n## Payment Flow\n\n1. **Client sends request** with payment headers:\n   - `x-channel-payment`: Base64-encoded payment authorization\n   - `x-channel-id`: Channel identifier\n   - `x-payment-amount`: Amount in smallest units\n   - `x-payment-nonce`: Nonce for replay protection\n\n2. **Server validates** payment authorization:\n   - Extract authorization from headers\n   - Verify signature against client's public key\n   - Check channel status (open, not expired)\n   - Verify sufficient balance\n   - Validate nonce (must increment)\n\n3. **If valid**: Claim payment and continue request\n   - Update channel state (off-chain)\n   - Attach payment result to request\n   - Execute route handler\n\n4. **If invalid**: Fall back to x402\n   - Check for `x-solana-signature` header\n   - Verify on-chain transaction\n   - If valid, continue request\n   - If invalid, return 402 Payment Required\n\n## Payment Result\n\n```typescript\ninterface PaymentResult {\n  success: boolean;\n  method: 'channel' | 'x402' | 'none';\n  amount: bigint;\n  signature?: string;           // TX signature or authorization ID\n  newNonce?: bigint;            // New nonce (channels)\n  remainingBalance?: bigint;    // Remaining balance (channels)\n  channelId?: string;           // Channel ID (channels)\n  error?: string;               // Error message if failed\n  timestamp: Date;\n}\n```\n\n## 402 Response Format\n\nWhen payment is required or invalid:\n\n```json\n{\n  \"statusCode\": 402,\n  \"error\": \"Payment Required\",\n  \"message\": \"Valid payment required to access this resource\",\n  \"amount\": \"1000000\",\n  \"recipient\": \"8xKj...\",\n  \"network\": \"devnet\",\n  \"methods\": [\n    {\n      \"type\": \"channel\",\n      \"supported\": true,\n      \"details\": {\n        \"programId\": \"9xQe...\",\n        \"network\": \"devnet\",\n        \"recipient\": \"8xKj...\"\n      }\n    },\n    {\n      \"type\": \"x402\",\n      \"supported\": true,\n      \"details\": {\n        \"network\": \"devnet\",\n        \"recipient\": \"8xKj...\",\n        \"usdcMint\": \"Gh9Z...\"\n      }\n    }\n  ],\n  \"channelSetup\": {\n    \"programId\": \"9xQe...\",\n    \"minDeposit\": \"1000000\",\n    \"recommendedDeposit\": \"10000000\"\n  }\n}\n```\n\n## Event System\n\nListen to payment events for analytics and monitoring:\n\n```typescript\npaymentService.onPaymentEvent((event) => {\n  console.log(`Event: ${event.type}`);\n\n  switch (event.type) {\n    case 'payment_received':\n      console.log(`Received ${event.amount} via ${event.method}`);\n      // Log to analytics, update database, etc.\n      break;\n\n    case 'channel_depleted':\n      console.log(`Channel ${event.channelId} balance low`);\n      // Send notification to client to refill\n      break;\n\n    case 'fallback_triggered':\n      console.log(`Fallback to x402: ${event.error}`);\n      // Monitor fallback usage\n      break;\n\n    case 'payment_failed':\n      console.log(`Payment failed: ${event.error}`);\n      // Log failures for debugging\n      break;\n  }\n});\n```\n\n## Payment Statistics\n\nTrack payment metrics:\n\n```typescript\nconst stats = paymentService.getStats();\n\nconsole.log({\n  totalPayments: stats.totalPayments,\n  channelPayments: stats.channelPayments,\n  x402Payments: stats.x402Payments,\n  failedPayments: stats.failedPayments,\n  totalAmount: stats.totalAmount.toString(),\n  averageAmount: stats.averageAmount.toString(),\n  channelSavings: stats.channelSavings.toString() // Est. tx fees saved\n});\n\n// Reset statistics\npaymentService.resetStats();\n```\n\n## Server Capabilities Endpoint\n\nExpose capabilities for client discovery at `/.well-known/x402-capabilities`:\n\n```json\n{\n  \"supportsChannels\": true,\n  \"supportsX402\": true,\n  \"channelProgramId\": \"9xQe...\",\n  \"minChannelDeposit\": \"1000000\",\n  \"maxChannelExpiry\": 604800,\n  \"recipientWallet\": \"8xKj...\",\n  \"network\": \"devnet\",\n  \"usdcMint\": \"Gh9Z...\"\n}\n```\n\n## Best Practices\n\n1. **Always provide server keypair** for channel claiming\n2. **Expose capabilities endpoint** for client discovery\n3. **Monitor payment events** for analytics and debugging\n4. **Set appropriate cache TTL** based on your needs\n5. **Protect stats endpoints** in production\n6. **Use dynamic pricing** for flexible monetization\n7. **Handle 402 responses** gracefully on client side\n8. **Monitor channel depletion** events to notify clients\n9. **Test with both payment methods** (channel + x402)\n10. **Use TypeScript** for type safety\n\n## Environment Variables\n\n```bash\n# Solana Configuration\nSOLANA_RPC_URL=https://api.devnet.solana.com\nCHANNEL_PROGRAM_ID=9xQe...\nUSDC_MINT=Gh9Z...\nRECIPIENT_WALLET=8xKj...\nSERVER_KEYPAIR=[...]  # JSON array of keypair secret key\n\n# Optional\nNODE_ENV=production\nPORT=3000\n```\n\n## Security Considerations\n\n1. **Server Keypair**: Keep server keypair secret and never expose it\n2. **Rate Limiting**: Implement rate limiting to prevent abuse\n3. **Signature Verification**: All payments are cryptographically verified\n4. **Nonce Checking**: Prevents replay attacks\n5. **Balance Validation**: Prevents overspending\n6. **Error Handling**: Never expose sensitive error details in production\n\n## TypeScript Support\n\nFully typed with complete type definitions:\n\n```typescript\nimport type {\n  ChannelPaymentServiceConfig,\n  PaymentResult,\n  PaymentRequirement,\n  PaymentStats,\n  ServerCapabilities,\n  PaymentHeaders\n} from '@x402-channels/server';\n```\n\n## License\n\nMIT\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.\n\n## Support\n\n- GitHub Issues: https://github.com/BOBER3r/solana-payment-channel-kit/issues\n- Documentation: https://docs.x402.dev\n- Discord: https://discord.gg/x402\n","readmeFilename":"README.md","_rev":"1-dc603060b7e375028280d23b8ae865c4"}