Core Package Overview

The @shade402/core package provides the foundational models, schemas, and utilities for the X402 payment protocol.

Installation

pnpm add @shade402/core @solana/web3.js @solana/spl-token zod

Overview

The core package includes:

  • Payment models (PaymentRequest, PaymentAuthorization)

  • Validation schemas (Zod schemas)

  • Error classes

  • Solana payment processor

  • Encryption utilities

  • Type definitions

Payment Models

PaymentRequest

Represents a payment request from a server:

import { PaymentRequest } from '@shade402/core';

const paymentRequest = new PaymentRequest({
  max_amount_required: '0.01',
  asset_type: 'SPL',
  asset_address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
  payment_address: 'RecipientWalletAddress',
  network: 'solana-devnet',
  expires_at: new Date(Date.now() + 600000), // 10 minutes
  nonce: 'unique-nonce',
  payment_id: 'unique-payment-id',
  resource: '/api/premium-data',
  description: 'Premium data access',
});

// Check if expired
if (paymentRequest.isExpired()) {
  console.log('Payment request expired');
}

// Convert to dictionary
const dict = paymentRequest.toDict();

// Convert to JSON
const json = paymentRequest.toJSON();

// Create from dictionary
const fromDict = PaymentRequest.fromDict(dict);

PaymentAuthorization

Represents proof of payment:

import { PaymentAuthorization } from '@shade402/core';

const authorization = new PaymentAuthorization({
  payment_id: 'unique-payment-id',
  actual_amount: '0.01',
  payment_address: 'RecipientWalletAddress',
  asset_address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
  network: 'solana-devnet',
  timestamp: new Date(),
  signature: 'transaction-signature',
  public_key: 'PayerPublicKey',
  transaction_hash: 'transaction-hash',
});

// Convert to header value (base64-encoded JSON)
const headerValue = authorization.toHeaderValue();

// Create from header value
const fromHeader = PaymentAuthorization.fromHeader(headerValue);

// Convert to dictionary
const dict = authorization.toDict();

// Convert to JSON
const json = authorization.toJSON();

Validation Schemas

Zod schemas for validating payment data:

import {
  PaymentRequestDataSchema,
  PaymentAuthorizationDataSchema,
} from '@shade402/core';

// Validate payment request data
const result = PaymentRequestDataSchema.safeParse(data);
if (!result.success) {
  console.error('Validation errors:', result.error.errors);
} else {
  const validData = result.data;
}

// Validate payment authorization data
const authResult = PaymentAuthorizationDataSchema.safeParse(authData);
if (!authResult.success) {
  console.error('Validation errors:', authResult.error.errors);
}

Error Classes

Shade402 defines specific error types:

import {
  PaymentRequiredError,
  PaymentExpiredError,
  InsufficientFundsError,
  PaymentVerificationError,
  TransactionBroadcastError,
  InvalidPaymentRequestError,
  ERROR_CODES,
} from '@shade402/core';

// Error codes reference
console.log(ERROR_CODES.PAYMENT_REQUIRED);
// {
//   code: "PAYMENT_REQUIRED",
//   message: "Payment is required to access this resource",
//   retry: true,
//   user_action: "Ensure wallet has sufficient funds and retry"
// }

// Catch specific errors
try {
  // Payment operation
} catch (error) {
  if (error instanceof PaymentExpiredError) {
    console.log('Payment expired:', error.paymentRequest);
  } else if (error instanceof InsufficientFundsError) {
    console.log('Insufficient funds:', error.requiredAmount);
  }
}

Solana Payment Processor

Handles Solana blockchain operations:

import { SolanaPaymentProcessor } from '@shade402/core';
import { Keypair } from '@solana/web3.js';

const processor = new SolanaPaymentProcessor(
  'https://api.devnet.solana.com',
  walletKeypair,
  {
    defaultDecimals: 6,
    memo: 'X402 payment',
  }
);

// Create payment transaction
const transaction = await processor.createPaymentTransaction(
  paymentRequest,
  '0.01',
  walletKeypair
);

// Sign and send transaction
const txHash = await processor.signAndSendTransaction(
  transaction,
  walletKeypair
);

// Verify payment
const isValid = await processor.verifyPayment(
  paymentRequest,
  authorization
);

// Get token balance
const balance = await processor.getTokenBalance(
  walletPublicKey,
  tokenMint
);

// Cleanup
await processor.close();

Encryption Utilities

RSA encryption for resource field privacy:

import {
  encryptResource,
  decryptResource,
  generateKeyPair,
} from '@shade402/core';

// Generate key pair
const { publicKey, privateKey } = generateKeyPair();

// Encrypt resource
const encrypted = encryptResource('/api/premium-data', publicKey);

// Decrypt resource
const decrypted = decryptResource(encrypted, privateKey);

Type Definitions

TypeScript types for all data structures:

import type {
  PaymentRequestData,
  PaymentAuthorizationData,
  SolanaPaymentProcessorOptions,
  ErrorDetails,
} from '@shade402/core';

Usage Examples

Creating a Payment Request

import { PaymentRequest } from '@shade402/core';

function createPaymentRequest(
  amount: string,
  paymentAddress: string,
  tokenMint: string,
  resource: string
): PaymentRequest {
  return new PaymentRequest({
    max_amount_required: amount,
    asset_type: 'SPL',
    asset_address: tokenMint,
    payment_address: paymentAddress,
    network: 'solana-devnet',
    expires_at: new Date(Date.now() + 600000),
    nonce: crypto.randomUUID(),
    payment_id: crypto.randomUUID(),
    resource: resource,
    description: 'Payment for resource access',
  });
}

Verifying Payment Authorization

import { PaymentAuthorization, PaymentRequest } from '@shade402/core';

function verifyAuthorization(
  request: PaymentRequest,
  authorization: PaymentAuthorization
): boolean {
  // Check payment ID matches
  if (authorization.paymentId !== request.paymentId) {
    return false;
  }

  // Check amount is sufficient
  if (parseFloat(authorization.actualAmount) < parseFloat(request.maxAmountRequired)) {
    return false;
  }

  // Check addresses match
  if (authorization.paymentAddress !== request.paymentAddress) {
    return false;
  }

  // Check token mint matches
  if (authorization.assetAddress !== request.assetAddress) {
    return false;
  }

  // Check network matches
  if (authorization.network !== request.network) {
    return false;
  }

  return true;
}

Next Steps

Last updated