UNPKG

wttp-core

Version:

Core contracts, interfaces, and TypeScript types for the Web3 Transfer Protocol (WTTP).

832 lines (708 loc) 24.7 kB
# WTTP Core - LLM Context Guide **Version:** 0.1.0 **Package:** `wttp-core` or `@tw3/wttp-core` **License:** AGPL-3.0 ## Project Summary WTTP Core provides the foundational contracts, interfaces, and TypeScript types for the Web3 Transfer Protocol (WTTP). This package serves as the base dependency for all WTTP implementations including sites, gateways, and client applications. Key features: - **Complete Protocol Types** - Standardized WTTP/3.0 request/response structures with TypeScript integration - **Interface Contracts** - Production-ready Solidity interfaces for sites, gateways, storage, and permissions - **TypeChain Integration** - Full TypeScript type generation with factory contracts and artifact exports - **Multi-Storage Support** - Abstracted storage layer supporting IPFS, Arweave, and custom backends via ESP integration - **Role-Based Access Control** - Comprehensive permission system for resource management Target use cases: - Building decentralized websites and web applications - Creating WTTP-compatible storage providers and gateways - Developing client applications that interact with WTTP sites - Implementing custom permission and access control systems Technology stack: - Solidity ^0.8.20 for smart contract interfaces - TypeScript with strict type checking - Hardhat for compilation and TypeChain generation - Multi-format builds (CommonJS, ES Modules, TypeScript declarations) ## Architecture Overview ``` WTTP Core Package Structure ├── Protocol Layer │ ├── WTTPTypes.sol ───────────── Core protocol type definitions │ ├── IWTTPSite.sol ──────────── Site interface (content serving) │ ├── IWTTPGateway.sol ───────── Gateway interface (routing/range handling) │ ├── IWTTPStorage.sol ───────── Storage interface (content storage) │ └── IWTTPPermissions.sol ───── Permission interface (role-based access) │ ├── TypeScript Integration │ ├── TypeChain Generated ────── Contract type definitions & factories │ ├── Artifact Exports ───────── Contract ABIs and bytecode │ └── Index Exports ──────────── Unified package interface │ └── Build System ├── Hardhat Compilation ────── Contract compilation & validation ├── TypeChain Generation ───── TypeScript type generation └── Multi-format Build ─────── CJS, ESM, Types output ``` **Data Flow:** 1. Client creates WTTP request → 2. Gateway handles ranges/routing → 3. Site queries Storage → 4. Permission validation → 5. Response with data points **Component Relationships:** - **Sites** implement IWTTPSite and use IWTTPStorage + IWTTPPermissions - **Gateways** implement IWTTPGateway and provide range/caching functionality - **Storage Providers** implement IWTTPStorage for different backends - **Permission Systems** implement IWTTPPermissions for role-based access control ## Package Structure ### Main Exports (`wttp-core` or `@tw3/wttp-core`) ```typescript // Contract Types & Factories (Generated from Solidity) import { IWTTPSite, IWTTPSite__factory, IWTTPGateway, IWTTPGateway__factory, IWTTPStorage, IWTTPStorage__factory, IWTTPPermissions, IWTTPPermissions__factory } from 'wttp-core'; // Protocol Types (from WTTPTypes.sol) import type { RequestLine, ResponseLine, HEADRequest, HEADResponse, LOCATERequest, LOCATEResponse, GETRequest, GETResponse, PUTRequest, PATCHRequest, DEFINERequest, DEFINEResponse, OPTIONSResponse, ResourceMetadata, HeaderInfo, DataRegistration, Method, CacheControl, Redirect, Range } from 'wttp-core'; // Contract Artifacts (ABIs & Bytecode) import { artifacts } from 'wttp-core'; // ESP Integration Types (from @tw3/esp dependency) import type { IDataPointRegistry, IDataPointStorage } from 'wttp-core'; ``` ### Subpath Exports ```typescript // TypeChain types only import { IWTTPSite, IWTTPGateway, IWTTPStorage, IWTTPPermissions } from 'wttp-core/types'; // Individual contract artifacts const siteABI = artifacts.IWTTPSite.abi; const gatewayABI = artifacts.IWTTPGateway.abi; const storageABI = artifacts.IWTTPStorage.abi; const permissionsABI = artifacts.IWTTPPermissions.abi; const typesABI = artifacts.WTTPTypes.abi; ``` ## Interface Documentation ### IWTTPSite - Core Site Functionality ```typescript interface IWTTPSite { // Get Data Point Storage contract instance DPS(): Promise<IDataPointStorage>; // Get Data Point Registry contract instance DPR(): Promise<IDataPointRegistry>; // Handle OPTIONS requests to check available methods OPTIONS(optionsRequest: RequestLine): Promise<OPTIONSResponse>; // Handle HEAD requests for metadata retrieval HEAD(headRequest: HEADRequest): Promise<HEADResponse>; // Handle LOCATE requests to find resource storage locations LOCATE(locateRequest: HEADRequest): Promise<LOCATEResponse>; // Handle GET requests for content retrieval GET(getRequest: HEADRequest): Promise<LOCATEResponse>; } ``` ### IWTTPGateway - Request Routing & Range Handling ```typescript interface IWTTPGateway { // Forward OPTIONS requests to a specified site OPTIONS( _site: string, _optionsRequest: RequestLine ): Promise<OPTIONSResponse>; // Handle GET requests with byte range support GET( _site: string, _getRequest: GETRequest ): Promise<GETResponse>; // Forward HEAD requests to a specified site HEAD( _site: string, _headRequest: HEADRequest ): Promise<HEADResponse>; // Handle LOCATE requests with data point range support LOCATE( _site: string, _locateRequest: LOCATERequest ): Promise<LOCATEResponse>; } ``` ### IWTTPStorage - Content Storage & Resource Management ```typescript interface IWTTPStorage { // Get Data Point Storage contract instance DPS(): Promise<IDataPointStorage>; // Get Data Point Registry contract instance DPR(): Promise<IDataPointRegistry>; // Update the Data Point Registry contract address setDPR(_dpr: string): Promise<void>; // Create a new header in storage createHeader(_header: HeaderInfo): Promise<string>; // Retrieve header information by its address readHeader(_headerAddress: string): Promise<HeaderInfo>; // Set the default header information setDefaultHeader(_header: HeaderInfo): Promise<void>; // Get metadata for a resource path readMetadata(_path: string): Promise<ResourceMetadata>; // Update metadata for a resource updateMetadata(_path: string, _metadata: ResourceMetadata): Promise<void>; // Delete metadata for a resource deleteMetadata(_path: string): Promise<void>; // Create a new data point for a resource createResource( _path: string, _dataRegistration: DataRegistration ): Promise<string>; // Get all data point addresses for a resource readResource(_path: string): Promise<string[]>; // Update a specific chunk of a resource updateResource( _path: string, _dataPointAddress: string, _chunkIndex: BigNumberish ): Promise<void>; // Remove a resource and its metadata deleteResource(_path: string): Promise<void>; // Bulk upload of data points for a resource uploadResource( _path: string, _dataRegistration: DataRegistration[] ): Promise<string[]>; } ``` ### IWTTPPermissions - Role-Based Access Control ```typescript interface IWTTPPermissions { // Check if an account has a specific role hasRole(role: string, account: string): Promise<boolean>; // Create a new resource-specific admin role createResourceRole(_role: string): Promise<void>; // Change the SITE_ADMIN_ROLE identifier changeSiteAdmin(_newSiteAdmin: string): Promise<void>; // Grant a role to an account grantRole(role: string, account: string): Promise<void>; // Revoke a role from an account revokeRole(role: string, account: string): Promise<void>; // Renounce a role for the calling account renounceRole(role: string, callerConfirmation: string): Promise<void>; // Get the admin role that controls a role getRoleAdmin(role: string): Promise<string>; // Get the default admin role DEFAULT_ADMIN_ROLE(): Promise<string>; } ``` ## Deployment/Configuration ### Network Support WTTP Core interfaces are network-agnostic and can be deployed on: - Ethereum Mainnet - Polygon - Arbitrum - Optimism - Base - Any EVM-compatible chain ### Configuration Utilities ```typescript // Network configuration helper interface NetworkConfig { chainId: number; name: string; rpcUrl: string; blockExplorer: string; } const SUPPORTED_NETWORKS: Record<number, NetworkConfig> = { 1: { chainId: 1, name: 'Ethereum', rpcUrl: '...', blockExplorer: 'etherscan.io' }, 137: { chainId: 137, name: 'Polygon', rpcUrl: '...', blockExplorer: 'polygonscan.com' }, // ... other networks }; // Method enum for type safety enum Method { HEAD = 0, GET = 1, POST = 2, PUT = 3, PATCH = 4, DELETE = 5, OPTIONS = 6, LOCATE = 7, DEFINE = 8 } ``` ### Environment Setup ```typescript // Required environment variables interface WTTPConfig { CHAIN_ID: number; RPC_URL: string; PRIVATE_KEY?: string; INFURA_API_KEY?: string; ALCHEMY_API_KEY?: string; } // Configuration validation function validateConfig(config: WTTPConfig): void { if (!config.RPC_URL) throw new Error('RPC_URL required'); if (!SUPPORTED_NETWORKS[config.CHAIN_ID]) throw new Error('Unsupported chain'); } ``` ## Complete Integration Examples ### Basic Site Contract Usage ```typescript import { ethers } from 'ethers'; import { IWTTPSite__factory, type HEADRequest, Method } from 'wttp-core'; async function querySite( provider: ethers.Provider, siteAddress: string, path: string ) { // Connect to site contract const site = IWTTPSite__factory.connect(siteAddress, provider); // Prepare HEAD request const request: HEADRequest = { requestLine: { protocol: "WTTP/3.0", path: path, method: Method.HEAD }, ifModifiedSince: 0, ifNoneMatch: "0x0000000000000000000000000000000000000000000000000000000000000000" }; try { // Execute request const response = await site.HEAD(request); console.log('Response:', { statusCode: response.responseLine.code, protocol: response.responseLine.protocol, mimeType: response.metadata.mimeType, size: response.metadata.size.toString(), lastModified: new Date(Number(response.metadata.lastModified) * 1000), etag: response.etag }); return response; } catch (error) { console.error('Request failed:', error); throw error; } } ``` ### Advanced Gateway Client ```typescript import { IWTTPGateway__factory, type HEADRequest, type GETRequest, type GETResponse, Method } from 'wttp-core'; class WTTPGatewayClient { private gateway: IWTTPGateway; constructor( provider: ethers.Provider, gatewayAddress: string ) { this.gateway = IWTTPGateway__factory.connect(gatewayAddress, provider); } async getContent( siteAddress: string, path: string, options: { rangeBytes?: { start: number; end: number }; useHead?: boolean; } = {} ): Promise<GETResponse | HEADResponse> { const { rangeBytes, useHead = false } = options; if (useHead) { // Use HEAD request for metadata only const headRequest: HEADRequest = { requestLine: { protocol: "WTTP/3.0", path, method: Method.HEAD }, ifModifiedSince: 0, ifNoneMatch: "0x0000000000000000000000000000000000000000000000000000000000000000" }; return await this.gateway.HEAD(siteAddress, headRequest); } else { // Use GET request for content const getRequest: GETRequest = { head: { requestLine: { protocol: "WTTP/3.0", path, method: Method.GET }, ifModifiedSince: 0, ifNoneMatch: "0x0000000000000000000000000000000000000000000000000000000000000000" }, rangeBytes: rangeBytes ? { start: rangeBytes.start, end: rangeBytes.end } : { start: 0, end: 0 } // 0 means to end }; return await this.gateway.GET(siteAddress, getRequest); } } async locateDataPoints( siteAddress: string, path: string, chunkRange?: { start: number; end: number } ): Promise<LOCATEResponse> { const locateRequest: LOCATERequest = { head: { requestLine: { protocol: "WTTP/3.0", path, method: Method.LOCATE }, ifModifiedSince: 0, ifNoneMatch: "0x0000000000000000000000000000000000000000000000000000000000000000" }, rangeChunks: chunkRange ? { start: chunkRange.start, end: chunkRange.end } : { start: 0, end: 0 } // 0 means all chunks }; return await this.gateway.LOCATE(siteAddress, locateRequest); } } ``` ### Complete Storage Provider Integration ```typescript import { IWTTPStorage__factory, type ResourceMetadata, type HeaderInfo, type DataRegistration } from 'wttp-core'; class WTTPStorageClient { private storage: IWTTPStorage; constructor(provider: ethers.Provider, storageAddress: string) { this.storage = IWTTPStorage__factory.connect(storageAddress, provider); } async uploadResource( path: string, chunks: { data: string; publisher: string }[], metadata: { mimeType: string; charset?: string; encoding?: string; language?: string; } ): Promise<string[]> { // Prepare data registrations const dataRegistrations: DataRegistration[] = chunks.map((chunk, index) => ({ data: ethers.toUtf8Bytes(chunk.data), chunkIndex: index, publisher: chunk.publisher })); // Upload all chunks const dataPointAddresses = await this.storage.uploadResource(path, dataRegistrations); // Update metadata const resourceMetadata: ResourceMetadata = { mimeType: ethers.encodeBytes32String(metadata.mimeType.slice(0, 31)), charset: metadata.charset ? ethers.encodeBytes32String(metadata.charset.slice(0, 31)) : "0x0000000000000000000000000000000000000000000000000000000000000000", encoding: metadata.encoding ? ethers.encodeBytes32String(metadata.encoding.slice(0, 31)) : "0x0000000000000000000000000000000000000000000000000000000000000000", language: metadata.language ? ethers.encodeBytes32String(metadata.language.slice(0, 31)) : "0x0000000000000000000000000000000000000000000000000000000000000000", size: chunks.reduce((total, chunk) => total + chunk.data.length, 0), version: 1, lastModified: Math.floor(Date.now() / 1000), header: "0x0000000000000000000000000000000000000000000000000000000000000000" // Default header }; await this.storage.updateMetadata(path, resourceMetadata); console.log('Uploaded resource:', { path, chunks: dataPointAddresses.length, dataPoints: dataPointAddresses }); return dataPointAddresses; } async getResource(path: string): Promise<{ metadata: ResourceMetadata; dataPoints: string[]; }> { const [metadata, dataPoints] = await Promise.all([ this.storage.readMetadata(path), this.storage.readResource(path) ]); return { metadata, dataPoints }; } async createCustomHeader( allowedMethods: Method[], cacheControl?: { maxAge?: number; noStore?: boolean; noCache?: boolean; immutableFlag?: boolean; publicFlag?: boolean; } ): Promise<string> { // Convert methods array to bitmask const methodsBitmask = allowedMethods.reduce((mask, method) => mask | (1 << method), 0); const headerInfo: HeaderInfo = { methods: methodsBitmask, cache: { maxAge: cacheControl?.maxAge || 0, noStore: cacheControl?.noStore || false, noCache: cacheControl?.noCache || false, immutableFlag: cacheControl?.immutableFlag || false, publicFlag: cacheControl?.publicFlag || true }, redirect: { code: 0, location: "" }, resourceAdmin: "0x0000000000000000000000000000000000000000000000000000000000000000" }; return await this.storage.createHeader(headerInfo); } } ``` ## Type Safety Guidelines ### Proper TypeScript Usage Patterns ```typescript // Always use generated types from the package import type { HEADRequest, HEADResponse, LOCATERequest, LOCATEResponse, Method, ResourceMetadata } from 'wttp-core'; // Type-safe request construction function createWTTPRequest( path: string, method: Method = Method.HEAD ): HEADRequest { return { requestLine: { protocol: "WTTP/3.0" as const, // Use const assertion path, method }, ifModifiedSince: 0, ifNoneMatch: "0x0000000000000000000000000000000000000000000000000000000000000000" as const }; } // Type narrowing for response validation function isValidResponse(response: HEADResponse): boolean { return ( response.responseLine.code >= 200 && response.responseLine.code < 300 && response.responseLine.protocol === "WTTP/3.0" ); } // Method bitmask utilities function methodsToMask(methods: Method[]): number { return methods.reduce((mask, method) => mask | (1 << method), 0); } function maskToMethods(mask: number): Method[] { const methods: Method[] = []; for (let i = 0; i < 9; i++) { if (mask & (1 << i)) { methods.push(i as Method); } } return methods; } ``` ### Type Assertions and Runtime Validation ```typescript // Safe type assertions with validation function assertValidAddress(address: string): asserts address is string { if (!ethers.isAddress(address)) { throw new Error(`Invalid address: ${address}`); } } // Type guards for runtime validation function isResourceMetadata(obj: any): obj is ResourceMetadata { return ( obj && typeof obj.mimeType === 'string' && typeof obj.size === 'bigint' && typeof obj.version === 'bigint' && typeof obj.lastModified === 'bigint' ); } // Error handling with discriminated unions type WTTPResult<T> = | { success: true; data: T } | { success: false; error: string; code: number }; async function safeWTTPCall<T>( operation: () => Promise<T> ): Promise<WTTPResult<T>> { try { const data = await operation(); return { success: true, data }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error', code: 500 }; } } ``` ## Common Integration Patterns ### Error Handling Strategies ```typescript class WTTPError extends Error { constructor( message: string, public code: number, public context?: string, public cause?: Error ) { super(message); this.name = 'WTTPError'; } } // Comprehensive error handling async function handleWTTPOperation<T>( operation: () => Promise<T>, context: string ): Promise<T> { try { return await operation(); } catch (error) { if (error instanceof Error) { // Contract revert errors if (error.message.includes('revert')) { throw new WTTPError( `Contract reverted: ${context}`, 400, context, error ); } // Network errors if (error.message.includes('network') || error.message.includes('timeout')) { throw new WTTPError( `Network error: ${context}`, 503, context, error ); } // Permission errors if (error.message.includes('Forbidden') || error.message.includes('InvalidRole')) { throw new WTTPError( `Permission denied: ${context}`, 403, context, error ); } } throw new WTTPError( `Unknown error: ${context}`, 500, context, error instanceof Error ? error : undefined ); } } ``` ### Performance Optimization ```typescript // Batch operations for efficiency async function batchSiteRequests( site: IWTTPSite, paths: string[], options: { concurrency?: number } = {} ): Promise<HEADResponse[]> { const { concurrency = 5 } = options; const requests = paths.map(path => createWTTPRequest(path, Method.HEAD)); // Process in chunks to avoid overwhelming the RPC const results: HEADResponse[] = []; for (let i = 0; i < requests.length; i += concurrency) { const chunk = requests.slice(i, i + concurrency); const chunkPromises = chunk.map(req => site.HEAD(req)); const chunkResults = await Promise.allSettled(chunkPromises); results.push(...chunkResults .filter((result): result is PromiseFulfilledResult<HEADResponse> => result.status === 'fulfilled' ) .map(result => result.value) ); } return results; } // Connection pooling class ProviderPool { private providers: ethers.Provider[] = []; private currentIndex = 0; addProvider(provider: ethers.Provider) { this.providers.push(provider); } getProvider(): ethers.Provider { const provider = this.providers[this.currentIndex]; this.currentIndex = (this.currentIndex + 1) % this.providers.length; return provider; } } ``` ## Key Implementation Notes ### Security Considerations - **Address Validation**: Always validate contract addresses before connecting - **Role Checks**: Verify user roles before write operations using `hasRole()` - **Input Sanitization**: Validate all path and content inputs - **Method Validation**: Check allowed methods using OPTIONS before requests - **Gas Limits**: Set appropriate gas limits for storage operations ### Protocol-Specific Behaviors - **WTTP/3.0 Compliance**: All requests must use "WTTP/3.0" protocol identifier - **Method Enum**: Use the Method enum for type safety and correct values - **Data Point Addressing**: Content is stored in chunks via ESP data points - **Role-Based Access**: Use bytes32 role identifiers for permission management - **Header System**: Headers define allowed methods and cache behavior ### Upgrade/Migration Considerations - **Interface Stability**: Current interfaces are designed for long-term stability - **Storage Migration**: Use storage contract upgrade patterns for data migration - **Role Migration**: Plan role transfers when upgrading permission systems - **Type Updates**: Keep TypeScript types synchronized with contract changes ## Quick Reference ### Core Functions Summary | Interface | Method | Purpose | Gas Cost | |-----------|--------|---------|----------| | IWTTPSite | `HEAD()` | Get resource metadata | Low (view) | | IWTTPSite | `GET()` | Get resource location | Low (view) | | IWTTPSite | `LOCATE()` | Get data point addresses | Low (view) | | IWTTPSite | `OPTIONS()` | Get allowed methods | Low (view) | | IWTTPGateway | `HEAD()` | Proxy HEAD to site | Low (view) | | IWTTPGateway | `GET()` | Handle ranged GET | Low (view) | | IWTTPStorage | `uploadResource()` | Store resource chunks | High | | IWTTPStorage | `readResource()` | Get data points | Low (view) | | IWTTPPermissions | `hasRole()` | Check role | Low (view) | | IWTTPPermissions | `grantRole()` | Grant role | Medium | ### Method Enum Values | Method | Value | Purpose | |--------|-------|---------| | HEAD | 0 | Get metadata only | | GET | 1 | Get content | | POST | 2 | Submit data | | PUT | 3 | Create/replace resource | | PATCH | 4 | Update resource parts | | DELETE | 5 | Remove resource | | OPTIONS | 6 | Query allowed methods | | LOCATE | 7 | Get storage locations | | DEFINE | 8 | Update headers | ### Common Parameters | Parameter | Type | Format | Example | |-----------|------|--------|---------| | `protocol` | string | "WTTP/3.0" | "WTTP/3.0" | | `path` | string | Unix-style path | "/index.html" | | `method` | Method | Enum value | Method.HEAD | | `role` | bytes32 | Hex string | "0x1234..." | | `address` | string | Ethereum address | "0xabcd..." | ### Installation Commands ```bash # Install standalone package npm install wttp-core # Install organizational package npm install @tw3/wttp-core # Development setup npm install wttp-core --save-dev npm run build # Compile contracts and generate types ``` --- *This LLM context provides complete integration guidance for WTTP Core v3.0. All code examples are production-ready and follow TypeScript best practices. Interface definitions match the actual Solidity contracts.*