npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@solana-payment-channel/core

v0.2.7

Published

Core payment channel logic with x402 protocol integration for Solana

Downloads

54

Readme

@solana-payment-channel/core

npm version npm downloads GitHub issues License: MIT Node.js Version

Core payment channel management for Solana with seamless x402 protocol integration.

Features

  • Off-chain Payments: Process payments instantly without blockchain transactions
  • Overdraft/Credit Limit: Allow users to spend more than their deposit with customizable credit limits (up to any amount)
  • Auto-Settlement: Automatically pays off debt when funds are added to the channel
  • Cryptographic Security: Ed25519 signatures for payment authorization
  • State Management: Efficient in-memory caching with TTL
  • Automatic Fallback: Seamlessly falls back to x402 protocol when channels unavailable
  • Type-Safe: Full TypeScript support with comprehensive types
  • Event-Driven: Subscribe to channel state changes in real-time
  • Production-Ready: Comprehensive error handling and validation

Installation

npm install @solana-payment-channel/core @solana/web3.js @coral-xyz/anchor

Quick Start

import { ChannelManager, createChannelConfig } from '@solana-payment-channel/core';
import { Keypair, PublicKey } from '@solana/web3.js';

// Initialize configuration
const config = createChannelConfig('devnet', 'YOUR_PROGRAM_ID');

// Create channel manager
const manager = new ChannelManager(config, clientKeypair);

// Open a payment channel with optional credit limit
const channelId = await manager.openChannel({
  serverPubkey: new PublicKey('SERVER_PUBLIC_KEY'),
  initialDeposit: BigInt(10_000_000), // 10 USDC
  creditLimit: BigInt(5_000_000),     // 5 USDC credit (optional)
});

// Create payment authorization (client-side)
import { createPaymentAuthorization } from '@solana-payment-channel/core';

const authorization = await createPaymentAuthorization(
  Buffer.from(channelId, 'hex'),
  BigInt(1_000_000), // 1 USDC
  BigInt(1), // nonce
  clientKeypair
);

// Claim payment (server-side)
const result = await manager.claimPayment(channelId, {
  amount: BigInt(1_000_000),
  authorization,
});

console.log('Payment claimed:', result.success);
console.log('Remaining balance:', result.remainingBalance);

Overdraft/Credit Limit Feature

Payment channels support customizable credit limits, allowing users to spend more than their initial deposit. This is perfect for applications that need to provide temporary credit to users.

How It Works

When opening a channel, you can specify a creditLimit that determines how much the user can overdraft:

const channelId = await manager.openChannel({
  serverPubkey: serverPublicKey,
  initialDeposit: BigInt(100_000_000),   // 100 USDC deposited
  creditLimit: BigInt(1000_000_000),     // 1000 USDC credit limit
});

// User can now spend up to 1,100 USDC total (100 deposit + 1000 credit)

Credit Limit Examples

  • No overdraft: creditLimit: BigInt(0) - User can only spend their deposit
  • 50% overdraft: Deposit 100 USDC, credit 50 USDC - Can spend 150 USDC total
  • High credit: Deposit 100 USDC, credit 1000 USDC - Can spend 1,100 USDC total
  • Unlimited (up to u64 max): Set any credit limit your business model allows

Auto-Settlement

When a user goes into debt and later adds funds to the channel, the system automatically:

  1. Pays off debt first: Debt is transferred directly to the server
  2. Adds remaining to balance: Net deposit goes to the channel
// Channel state after overdraft:
// - Deposited: 100 USDC
// - Claimed: 120 USDC
// - Debt: 20 USDC

// User adds 50 USDC to channel
await manager.addFunds(channelId, BigInt(50_000_000));

// Auto-settlement:
// - 20 USDC pays off debt (sent directly to server)
// - 30 USDC added to channel balance
// - Debt: 0 USDC

Debt Protection

Channels with outstanding debt cannot be closed until the debt is settled:

try {
  await manager.closeChannel(channelId); // Has 20 USDC debt
} catch (error) {
  // Error: CannotCloseWithDebt
  // User must add funds to settle debt first
}

Use Cases

  • Gaming: Allow players temporary credit for in-game purchases
  • Subscriptions: Prevent service interruption during payment processing
  • B2B Services: Provide business customers with credit terms
  • Trial Periods: Offer limited credit during trial periods
  • Loyalty Programs: Reward high-value users with higher credit limits

Architecture

Payment Flow

┌─────────────┐                    ┌─────────────┐
│   Client    │                    │   Server    │
└──────┬──────┘                    └──────┬──────┘
       │                                  │
       │ 1. Open Channel (on-chain)      │
       ├─────────────────────────────────>│
       │                                  │
       │ 2. Create Payment Auth          │
       │    (sign off-chain)             │
       │                                  │
       │ 3. Send Authorization           │
       ├─────────────────────────────────>│
       │                                  │
       │                    4. Verify & Process
       │                       (instant, free)
       │                                  │
       │ 5. Payment Confirmed            │
       │<─────────────────────────────────┤
       │                                  │

Integration with x402

The package seamlessly integrates with the x402 protocol for fallback payments:

import { ChannelManager, FallbackManager } from '@solana-payment-channel/core';

const manager = new ChannelManager(config, wallet);
const fallback = manager.getFallbackManager();

// Automatically determine best payment method
const { method, reason } = await fallback.determinePaymentMethod(
  channelState,
  amount,
  serverUrl
);

if (method === 'channel') {
  // Use off-chain channel payment (instant, free)
  const result = await manager.claimPayment(channelId, options);
} else {
  // Fall back to x402 on-chain payment
  const receipt = await fallback.payWithX402({
    amount,
    recipient: serverPubkey,
  });
}

API Reference

ChannelManager

Main class for managing payment channels.

Constructor

new ChannelManager(config: ChannelConfig, wallet: Keypair)

Methods

openChannel

Opens a new payment channel on-chain.

async openChannel(options: OpenChannelOptions): Promise<string>

Parameters:

  • options.serverPubkey: Server's public key
  • options.initialDeposit: Initial deposit amount in smallest units
  • options.creditLimit?: Optional credit limit (defaults to 0, no overdraft)
  • options.expiry?: Optional expiry date (defaults to 7 days)

Returns: Channel ID as hex string

Throws:

  • InsufficientFundsError: If wallet lacks sufficient USDC
  • TransactionError: If transaction fails
addFunds

Adds funds to an existing channel.

async addFunds(channelId: string, amount: bigint): Promise<string>

Returns: Transaction signature

claimPayment

Claims a payment from a channel (server-side).

async claimPayment(
  channelId: string,
  options: ClaimPaymentOptions
): Promise<PaymentResult>

Parameters:

  • channelId: Channel identifier
  • options.amount: Payment amount
  • options.authorization: Signed payment authorization

Returns: Payment result with success status and updated balances

closeChannel

Closes a channel and returns remaining funds.

async closeChannel(channelId: string): Promise<string>
getChannelState

Retrieves current channel state from blockchain.

async getChannelState(channelId: string): Promise<ChannelState>
getAllChannels

Gets all channels for a public key.

async getAllChannels(pubkey: PublicKey): Promise<ChannelState[]>
subscribeToChannel

Subscribes to channel state changes.

subscribeToChannel(
  channelId: string,
  callback: (state: ChannelState) => void
): () => void

Returns: Unsubscribe function

ChannelStateManager

Manages channel state with in-memory caching.

const stateManager = new ChannelStateManager({ ttl: 60000 });

// Update state
stateManager.updateState(channelId, newState);

// Get cached state
const state = stateManager.getState(channelId);

// Subscribe to changes
const unsubscribe = stateManager.subscribe(channelId, (state) => {
  console.log('State updated:', state);
});

// Invalidate cache
stateManager.invalidate(channelId);

Signature Utilities

createPaymentAuthorization

Creates a signed payment authorization (client-side).

async function createPaymentAuthorization(
  channelId: Buffer,
  amount: bigint,
  nonce: bigint,
  signer: Keypair
): Promise<PaymentAuthorization>

verifyPaymentAuthorization

Verifies a payment authorization signature (server-side).

async function verifyPaymentAuthorization(
  authorization: PaymentAuthorization,
  expectedPublicKey: PublicKey
): Promise<boolean>

serializePaymentData

Serializes payment data for signing.

function serializePaymentData(
  channelId: Buffer,
  amount: bigint,
  nonce: bigint
): Buffer

FallbackManager

Manages fallback to x402 protocol.

const fallback = new FallbackManager({ connection });

// Check if server supports channels
const supportsChannels = await fallback.shouldUseChannel(serverUrl);

// Get server capabilities
const capabilities = await fallback.getServerCapabilities(serverUrl);

// Determine best payment method
const { method, reason } = await fallback.determinePaymentMethod(
  channelState,
  amount,
  serverUrl
);

Error Handling

The package provides comprehensive error classes:

import {
  ChannelError,
  InsufficientFundsError,
  ChannelNotFoundError,
  ChannelClosedError,
  ChannelExpiredError,
  InvalidSignatureError,
  InvalidNonceError,
  TransactionError,
  ConfigurationError,
} from '@solana-payment-channel/core';

try {
  await manager.claimPayment(channelId, options);
} catch (error) {
  if (error instanceof InsufficientFundsError) {
    console.log('Required:', error.required);
    console.log('Available:', error.available);
  } else if (error instanceof InvalidNonceError) {
    console.log('Expected:', error.expected);
    console.log('Received:', error.received);
  }
}

Configuration

ChannelConfig

interface ChannelConfig {
  rpcUrl: string;
  network: 'devnet' | 'mainnet-beta';
  programId: PublicKey;
  usdcMint: PublicKey;
  defaultExpiry?: number; // seconds
  minBalance?: bigint;
  autoRefillAmount?: bigint;
}

Helper Function

import { createChannelConfig, NETWORKS } from '@solana-payment-channel/core';

const config = createChannelConfig('devnet', programId, {
  defaultExpiry: 14 * 24 * 60 * 60, // 14 days
  minBalance: BigInt(500_000), // 0.5 USDC
});

Examples

Client: Opening a Channel

import { ChannelManager } from '@solana-payment-channel/core';
import { Keypair } from '@solana/web3.js';

const client = Keypair.generate();
const manager = new ChannelManager(config, client);

const channelId = await manager.openChannel({
  serverPubkey: serverPublicKey,
  initialDeposit: BigInt(10_000_000),  // 10 USDC
  creditLimit: BigInt(5_000_000),      // 5 USDC credit (optional)
});

console.log('Channel opened:', channelId);
console.log('Total spendable: 15 USDC (10 deposit + 5 credit)');

Client: Creating Payment Authorization

import { createPaymentAuthorization } from '@solana-payment-channel/core';

const state = await manager.getChannelState(channelId);
const nextNonce = state.nonce + BigInt(1);

const authorization = await createPaymentAuthorization(
  Buffer.from(channelId, 'hex'),
  BigInt(1_000_000), // 1 USDC
  nextNonce,
  clientKeypair
);

// Send authorization to server
await fetch(serverUrl, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ authorization }),
});

Server: Processing Payment

import { ChannelManager, verifyPaymentAuthorization } from '@solana-payment-channel/core';

// Receive authorization from client
const { authorization } = await request.json();

// Verify and process payment
const result = await manager.claimPayment(channelId, {
  amount: BigInt(1_000_000),
  authorization,
});

if (result.success) {
  console.log('Payment received!');
  console.log('Remaining balance:', result.remainingBalance);
  // Provide service to client
} else {
  console.error('Payment failed:', result.error);
}

Monitoring Channel State

const unsubscribe = manager.subscribeToChannel(channelId, (state) => {
  console.log('Channel updated:');
  console.log('  Balance:', state.currentBalance);
  console.log('  Nonce:', state.nonce);
  console.log('  Claimed:', state.claimedAmount);

  // Auto-refill if balance is low
  if (state.currentBalance < config.minBalance) {
    manager.addFunds(channelId, config.autoRefillAmount);
  }
});

Best Practices

Security

  1. Always validate nonces: Ensure nonces increment to prevent replay attacks
  2. Verify signatures: Use verifyPaymentAuthorization before processing payments
  3. Check expiry: Validate channel hasn't expired before accepting payments
  4. Secure key storage: Never expose private keys in client-side code

Performance

  1. Use caching: State manager caches reduce RPC calls
  2. Batch operations: Open channels for multiple services together
  3. Monitor balance: Subscribe to state changes to prevent insufficient funds

Integration

  1. Fallback strategy: Always have x402 fallback for when channels unavailable
  2. Server capabilities: Check server support before requiring channels
  3. Graceful degradation: Handle channel failures smoothly

License

MIT

Contributing

Contributions welcome! Please see CONTRIBUTING.md for guidelines.