-
Notifications
You must be signed in to change notification settings - Fork 678
feat: Extract paywall into modular package #562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
* Add Onchain to x402 Ecosystem - Services/Endpoints * Add Onchain to x402 Ecosystem - Services/Endpoints
… payer validation tests and fix mocks to Instruction accounts array; chore: align high-level tests with signer-aware verification (#546)
* Improve Solana paywall wallet connection * feat: prettier * Feat: cleaaning the bip boop slop into cleaner components
Phase 1: Extract and modularize the x402 paywall
## Changes
- Create new @x402/paywall package at typescript/packages/http/paywall/
- Extract all paywall components from legacy x402 package:
- EvmPaywall.tsx: EVM wallet connection and payment UI
- SolanaPaywall.tsx: Solana wallet connection and payment UI
- PaywallApp.tsx: Main app coordinator
- Providers.tsx: OnchainKit provider setup
- Copy Solana-specific hooks and utilities to solana/ directory
- Setup esbuild to generate self-contained 3.4MB HTML template
- Export getPaywallHtml() function for generating paywall pages
- Update imports to use x402/types instead of @x402/core
- Configure package with proper TypeScript and ESLint settings
## Usage
```typescript
import { getPaywallHtml } from '@x402/paywall';
const html = getPaywallHtml({
amount: 0.10,
paymentRequirements: [...],
currentUrl: 'https://api.example.com/data',
testnet: true,
cdpClientKey: 'your-key',
appName: 'My App'
});
res.status(402).send(html);
```
## Testing
- Package builds successfully with pnpm build
- Generates 3.4MB self-contained HTML with all dependencies
- No linter errors
- Supports both EVM (Base) and Solana networks
## Next Steps
- Phase 2: Add builder pattern for composability
- Phase 3: Create network-specific subpath exports (evm, svm)
- Integrate with HTTP middleware packages (express, hono, next)
- Update @x402/core to automatically use @x402/paywall if installed
- Add optional peer dependency to @x402/express
- Falls back to basic HTML if paywall not installed
- Update Express README with paywall configuration options
How it works:
- If @x402/paywall is installed: Full wallet UI with EVM/Solana support
- If not installed: Basic HTML with note to install @x402/paywall
- Always backwards compatible with custom HTML templates
Usage:
```typescript
// Install paywall (optional)
pnpm add @x402/paywall
// Configure in middleware
app.use(paymentMiddleware(routes, facilitators, schemes, {
cdpClientKey: 'key',
appName: 'My App',
testnet: true
}));
```
Phase 1 complete - SDKs now support modular paywall
Phase 2: Builder pattern implementation
## Changes
### @x402/paywall
- Add PaywallBuilder class with fluent API
- Add createPaywall() factory function
- Add PaywallProvider and PaywallConfig type definitions
- Export both legacy getPaywallHtml() and new builder API
- Add comprehensive unit tests (32 tests passing)
### @x402/core
- Add PaywallProvider interface to HTTP service
- Add setPaywallProvider() method to x402HTTPResourceService
- Update generatePaywallHTML() to support custom providers
- Export PaywallProvider type from server module
- Priority: Custom provider > @x402/paywall > Basic HTML fallback
### @x402/express
- Add optional PaywallProvider parameter to paymentMiddleware()
- Update to use custom provider if provided
- Export PaywallProvider and PaywallConfig types
- Update README with builder pattern examples
## Usage
### Legacy API (still works)
```typescript
import { getPaywallHtml } from '@x402/paywall';
const html = getPaywallHtml({...});
```
### Builder Pattern API (new)
```typescript
import { createPaywall } from '@x402/paywall';
const paywall = createPaywall()
.withConfig({
appName: 'My App',
cdpClientKey: 'key',
testnet: true
})
.build();
app.use(paymentMiddleware(routes, facilitators, schemes, undefined, paywall));
```
## Testing
- 32 unit tests passing (paywallUtils, builder)
- All packages build successfully
- Builder config merging tested
- Runtime config override tested
- Backwards compatibility verified
## Next Steps
Phase 3: Network-specific modules for bundle size reduction
Phase 2: Builder pattern and testing infrastructure
## Changes
### @x402/paywall
- Add builder.ts with PaywallBuilder class and createPaywall()
- Add types.ts with PaywallProvider, PaywallConfig, PaymentRequired
- Update index.ts to export both legacy and builder APIs
- Add builder.test.ts with 12 tests for builder functionality
- Add paywallUtils.test.ts with 19 tests for utility functions
- Update paywall.ts to use shared types
### @x402/core
- Add PaywallProvider interface to x402HTTPResourceService
- Add setPaywallProvider() method for custom providers
- Update generatePaywallHTML() to use custom provider first
- Export PaywallProvider from http and server modules
### @x402/express
- Update paymentMiddleware to accept optional PaywallProvider param
- Call setPaywallProvider() when custom provider is passed
- Export PaywallProvider and PaywallConfig types
- Update README with builder pattern examples
## API Comparison
### Legacy (still supported)
```typescript
import { getPaywallHtml } from '@x402/paywall';
const html = getPaywallHtml({...});
```
### Builder Pattern (new)
```typescript
import { createPaywall } from '@x402/paywall';
const paywall = createPaywall().withConfig({...}).build();
app.use(paymentMiddleware(routes, facs, schemes, undefined, paywall));
```
## Testing
- 32 unit tests passing (19 utils + 12 builder + 1 placeholder)
- Config merging tested
- Runtime override tested
- V1/V2 payment requirement parsing tested
- Network detection tested (EVM, Solana, CAIP-2)
## Size
Bundle still 3.4MB (both networks included)
Phase 3 will add network-specific imports for tree shaking
Phase 3: Network-specific paywalls with bundle size reduction
- Reorganize into evm/ and svm/ subdirectories
- Move EvmPaywall.tsx -> evm/EvmPaywall.tsx
- Move SolanaPaywall.tsx -> svm/SolanaPaywall.tsx
- Move Solana hooks -> svm/solana/
- Duplicate utilities into evm-utils.ts and svm-utils.ts
- Add evm/build.ts - Generates EVM-only template
- Add svm/build.ts - Generates Solana-only template
- Update main build.ts to orchestrate all three builds
- Add evm/index.tsx and svm/index.tsx entry points
- Configure tsup for multiple entry points with tree shaking
- Add PaywallNetworkHandler interface to types.ts
- Implement evmPaywall handler (supports eip155:* and legacy)
- Implement svmPaywall handler (supports solana:* and legacy)
- Update PaywallBuilder with withNetwork() method
- Add first-match selection strategy
Add package.json exports:
- @x402/paywall (main - full paywall)
- @x402/paywall/evm (EVM only)
- @x402/paywall/svm (Solana only)
- Add network-handlers.test.ts (8 tests)
- Add withNetwork() tests to builder.test.ts (5 tests)
- Test first-match selection
- Test network support detection
- All 45 tests passing
| Import | Size | Reduction |
|--------|------|-----------|
| @x402/paywall | 3.5MB | Baseline |
| @x402/paywall/evm | 3.4MB | ~3% |
| @x402/paywall/svm | 1.0MB | 71% ✅ |
SVM-only apps save 2.5MB!
```typescript
import { createPaywall } from '@x402/paywall';
import { evmPaywall } from '@x402/paywall/evm';
const paywall = createPaywall()
.withNetwork(evmPaywall)
.withConfig({ appName: 'My App' })
.build();
```
```typescript
import { svmPaywall } from '@x402/paywall/svm';
const paywall = createPaywall()
.withNetwork(svmPaywall)
.build();
```
```typescript
import { evmPaywall } from '@x402/paywall/evm';
import { svmPaywall } from '@x402/paywall/svm';
const paywall = createPaywall()
.withNetwork(evmPaywall)
.withNetwork(svmPaywall)
.build();
```
EVM bundle is 3.4MB because @coinbase/onchainkit is ~2.5MB.
Tree shaking works correctly - no Solana deps in EVM build.
- Refactor to use x402Client internally (cleaner architecture)
- Investigate OnchainKit bundle size optimizations
- Add more network handlers (Sui, Aptos, etc.)
- Run prettier on all paywall files - Fix code style across 15 files - No functional changes
- Remove evm/evm-utils.ts and svm/svm-utils.ts (100% identical duplicates) - Update imports to use shared src/utils.ts - Fix eslint config to match @x402/express pattern - Relax JSDoc requirements for UI code - Add browser globals and generated file ignores - Run lint and format Benefits: - Saves 238 lines of duplicate code - Clearer for maintainers (no confusion about differences) - No impact on tree shaking or bundle sizes Verified: - All 45 tests passing - EVM bundle: 3.38 MB (unchanged) - SVM bundle: 0.95 MB (unchanged)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| } | ||
|
|
||
| // Support v1 legacy EVM networks | ||
| const evmNetworks = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like these networks should be pulled up.
Right now @x402/core has a /types export for v2 types, and /types/v1 for v1.
I know these are constants and not types, but I feel like these should be moved to /types/v1 as EVM_NETWORKS and we do the same for SVM_NETWORKS with solana/solana-devnet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * @param requirement - Payment requirement to check | ||
| * @returns True if this handler can process this requirement | ||
| */ | ||
| supports(requirement: PaymentRequirements): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally supports should take x402Version and PaymentRequirements, so that it can do a check on the version. Shape of PaymentRequirements is fine for now as there's just two versions, but as we improve it'll be much easier to maintain backwards compatibility if we push these v1/v2 scenarios to switch on x402Version rather than shape
| * @param provider - PaywallProvider instance | ||
| */ | ||
| setPaywallProvider(provider: PaywallProvider): void { | ||
| this.paywallProvider = provider; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Devx question, how about this join the builder pattern?
registerPaywallProvider(provider?: PaywallProvider): x402HTTPResourceService {
this.paywallProvider = provider;
return this;
}This would allow middleware implementers to add it to the builder chain
| export function ensureValidAmount(paymentRequirements: PaymentRequirements): PaymentRequirements { | ||
| const updatedRequirements = safeClone(paymentRequirements); | ||
|
|
||
| if (window.x402?.amount) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One pattern I'm following is to operate as if v2 is the default, and v1 is the branching logic.
For example, this function can be a bit hard to read with the two cases mixed in every step of the way
I would suggest ensureValidAmount and ensureValidAmountV1 are two functions that each do their one thing right, and the caller should check the x402Version to determine which to call.
Either that, or the x402Version should be passed in, and there should be a switch statement about how handling v1 and v2 differ
…const into the core types pkg
5234720 to
02876cc
Compare
|
saddest rebase in the universe. new branch here: #600 |
Extracts the paywall from the monolithic legacy package into a standalone
@x402/paywallpackage with modular architecture, builder pattern API, and tree-shakeable network-specific imports.New Package: @x402/paywall
Location:
typescript/packages/http/paywall/Key Features:
/evmand/svmx402/paywallAPIBundle Sizes:
Core Integration
@x402/core:
PaywallProviderinterfacesetPaywallProvider()method tox402HTTPResourceService@x402/paywallif installed, otherwise falls back to basic HTML@x402/express:
PaywallProviderparameter topaymentMiddleware()Architecture
API
Legacy (Backwards Compatible)
Builder Pattern
Network-Specific Imports
Migration Path
From legacy
x402/paywall:Future Work
x402Clientinternally for cleaner architectureChecklist