diff --git a/src/app/shred-api/api-docs/page.tsx b/src/app/shred-api/api-docs/page.tsx index d8582d5..ad48f4b 100644 --- a/src/app/shred-api/api-docs/page.tsx +++ b/src/app/shred-api/api-docs/page.tsx @@ -4,53 +4,56 @@ import CodeBlock from '@/components/ui/CodeBlock'; export default function ShredApiDocs() { return ( -
+
-

Getting Started

- -

- The Shred API provides millisecond-fast access to pre-confirmation transaction data via WebSocket streaming. - This guide covers everything you need to integrate real-time blockchain data into your application. +

JSON RPC API Overview

+

+ The Shred API implements the JSON-RPC 2.0 protocol over WebSocket for real-time communication. + This low-level interface provides maximum flexibility and can be used from any programming language + that supports WebSocket connections.

+
+
-

Prerequisites

-
    -
  • - - WebSocket client library for your language -
  • -
  • - - Basic understanding of JSON-RPC protocol -
  • -
  • - - Access to RISE testnet (no API key required) -
  • -
-
- -
-

Connection Setup

+

Connection Setup

Connect to the RISE WebSocket endpoint to start receiving real-time Shred data. No authentication required.

+
+ + + + + + + + + + + + + + + +
EnvironmentWebSocket URLStatus
Testnetwss://testnet.riselabs.xyz/ws✓ Live
+
+ { +ws.onopen = () => { console.log('Connected to RISE WebSocket'); - // Start subscribing to events + // Subscribe to logs from a specific contract ws.send(JSON.stringify({ jsonrpc: '2.0', id: 1, @@ -58,49 +61,31 @@ ws.on('open', () => { params: [ 'logs', { - // Optional filters - address: '0x...', // Contract address to monitor - topics: ['0x...'] // Event signatures to filter + address: '0x6257c5f110900a8E02A7A480b097D44F96360d16', + topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'] } ] })); -});`} - title="WebSocket Connection" - /> +}; -
-

- Note: The Shred API provides instant access to pre-confirmation data without requiring API keys. - Simply connect and subscribe to start receiving real-time updates. -

-
-
+ws.onmessage = (event) => { + const data = JSON.parse(event.data); + console.log('Received:', data); +}; -
-

Connection URLs

-
- - - - - - - - - - - - - - +ws.onclose = () => { + console.log('Connection closed'); +}; - -
EnvironmentWebSocket URLStatus
Testnetwss://testnet.riselabs.xyz/ws✓ Live
-
+ws.onerror = (error) => { + console.error('WebSocket error:', error); +};`} + title="Basic Connection Example" + />
-

Supported Subscription Channels

+

Subscription Channels

@@ -134,37 +119,40 @@ ws.on('open', () => {

RPC Methods

-

- The Shred API implements the JSON-RPC 2.0 protocol with the following methods for managing subscriptions - and retrieving real-time data. -

-
- -
-
-

rise_subscribe

-

- Create a subscription to receive real-time shred/log notifications based on filters. Returns a subscription ID - that will be included with all notifications. -

- -

Parameters

-
-
    -
  • - address (optional) - - Contract address(es) to match (string or string[]) -
  • -
  • - topics (optional) - - Topic filters, up to 4 indexed event topics (same semantics as Ethereum) -
  • -
-
- - +
+

rise_subscribe

+

+ Create a subscription to receive real-time shred/log notifications based on filters. Returns a subscription ID + that will be included with all notifications. +

+ +

Parameters

+
+
    +
  • + channel (optional) - + Subscription channel: "logs" for events, empty for full shreds +
  • +
  • + filter (optional) - + Filter object with address and topics +
  • +
  • + address (optional) - + Contract address(es) to match (string or string[]) +
  • +
  • + topics (optional) - + Topic filters, up to 4 indexed event topics (same semantics as Ethereum) +
  • +
+
+ + { "logs", { "address": "0x6257c5f110900a8E02A7A480b097D44F96360d16", - "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"] // Transfer event + "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"] } ] } -// Subscribe to multiple addresses +// Subscribe to all logs (no filter) { "jsonrpc": "2.0", "id": 2, "method": "rise_subscribe", - "params": [ - "logs", - { - "address": ["0x...", "0x..."], - "topics": [] - } - ] + "params": ["logs", {}] } -// Subscribe to all logs (no filter) +// Subscribe to full shreds { "jsonrpc": "2.0", "id": 3, "method": "rise_subscribe", - "params": ["logs", {}] + "params": [] +} + +// Subscribe to multiple contracts +{ + "jsonrpc": "2.0", + "id": 4, + "method": "rise_subscribe", + "params": [ + "logs", + { + "address": [ + "0x6257c5f110900a8E02A7A480b097D44F96360d16", + "0x742d35Cc6634C0532925a3b844Bc9e7595f8c8C5" + ] + } + ] }`} - title="Subscribe Examples" - /> + title="Subscribe Examples" + /> - -
- -
-

rise_unsubscribe

-

- Cancel an active subscription. The subscription will stop sending notifications immediately. -

- -

Parameters

-
-
    -
  • - subscriptionId (required) - - The subscription ID to cancel -
  • -
+ title="Success Response" + />
- - +

rise_unsubscribe

+

+ Cancel an active subscription. The subscription will stop sending notifications immediately. +

+ +

Parameters

+
+
    +
  • + subscriptionId (required) - + The subscription ID returned by rise_subscribe +
  • +
+
+ + + title="Unsubscribe Request" + /> - -
- -
-

Computing Event Signatures

-

- To filter for specific events, you need to compute the event signature hash. This is the keccak256 hash of the event declaration. -

- - + title="Success Response" + /> +
-
+
-

WebSocket Implementation

-

- The Shred API uses WebSocket for bi-directional real-time communication. This section covers connection - management, message handling, and best practices for production deployments. -

-
- -
-

Quick Start Example

- { - const id = 1; - - ws.send(JSON.stringify({ - jsonrpc: "2.0", - id, - method: "rise_subscribe", - params: [ - "logs", - { - address: TOKEN_ADDRESS, - topics: [TRANSFER_SIG] - } - ] - })); -}); - -ws.on("message", raw => { - const msg = JSON.parse(raw.toString()); - - // First response contains subscription id - if (msg.id && msg.result) { - console.log("Subscribed – id =", msg.result); - return; - } - - // Subsequent notifications - if (msg.method === "rise_subscription") { - const log = msg.params.result; - console.log("🟢 New Transfer", { - from: "0x" + log.topics[1].slice(26), - to: "0x" + log.topics[2].slice(26), - value: BigInt(log.data).toString(), - tx: log.transactionHash - }); - } -});`} - title="Quick Start - Monitoring ERC20 Transfers" - /> -
- -
-

Production WebSocket Client

- { - this.ws = new WebSocket('wss://testnet.riselabs.xyz'); - - this.ws.onopen = () => { - console.log('Connected to Shred API'); - this.reconnectDelay = 1000; - - // Authenticate - this.send({ - jsonrpc: '2.0', - id: 0, - method: 'auth', - params: { apiKey: this.apiKey } - }); - - // Start ping/pong to keep connection alive - this.startPing(); - - // Process queued messages - this.processMessageQueue(); - - resolve(); - }; - - this.ws.onmessage = (event) => { - const data = JSON.parse(event.data); - this.handleMessage(data); - }; - - this.ws.onclose = (event) => { - console.log('Disconnected:', event.code, event.reason); - this.stopPing(); - this.scheduleReconnect(); - }; - - this.ws.onerror = (error) => { - console.error('WebSocket error:', error); - reject(error); - }; - }); - } - - handleMessage(data) { - // Handle authentication response - if (data.id === 0 && data.result) { - console.log('Authenticated successfully'); - this.resubscribeAll(); - return; - } - - // Handle subscription confirmations - if (data.result && data.result.subscriptionId) { - const pending = this.pendingSubscriptions.get(data.id); - if (pending) { - this.subscriptions.set(data.result.subscriptionId, pending.handler); - this.pendingSubscriptions.delete(data.id); - } - return; - } - - // Handle notifications - if (data.method === 'shred_notification') { - const { subscriptionId, shred } = data.params; - const handler = this.subscriptions.get(subscriptionId); - if (handler) { - handler(shred); - } - return; - } - - // Handle pong - if (data.method === 'pong') { - this.lastPong = Date.now(); - return; - } - } - - subscribe(filter, handler) { - const id = this.generateId(); - const message = { - jsonrpc: '2.0', - id, - method: 'shred_subscribe', - params: filter - }; - - if (this.isConnected()) { - this.send(message); - this.pendingSubscriptions.set(id, { filter, handler }); - } else { - // Queue subscription for when we reconnect - this.messageQueue.push({ message, handler }); - } - - return id; - } - - startPing() { - this.pingInterval = setInterval(() => { - if (this.isConnected()) { - this.send({ method: 'ping' }); - - // Check if we've received a pong recently - if (this.lastPong && Date.now() - this.lastPong > 60000) { - console.warn('No pong received in 60s, reconnecting...'); - this.ws.close(); - } - } - }, 30000); - } - - scheduleReconnect() { - setTimeout(() => { - console.log('Attempting to reconnect...'); - this.connect(); - }, this.reconnectDelay); - - // Exponential backoff - this.reconnectDelay = Math.min( - this.reconnectDelay * 2, - this.maxReconnectDelay - ); - } -} - -// Usage -const client = new ShredClient('YOUR_API_KEY'); -await client.connect(); - -client.subscribe({ - address: '0x742d35Cc6634C0532925a3b844Bc9e7595f...', - includeReceipts: true -}, (shred) => { - console.log('New shred:', shred); - updateUI(shred); -});`} - title="Production-Ready WebSocket Client" - /> -
- -
-

Message Types

+

Message Types

-

Initial Response

-

- After subscribing, you'll receive a confirmation with your subscription ID. -

- -
- -
-

Log Notification

+

Log Notification

For log subscriptions, you'll receive Ethereum-style Log objects. Note that blockHash is null until the block is finalized.

@@ -560,7 +291,7 @@ client.subscribe({
-

Shred Notification

+

Shred Notification

For Shred subscriptions (empty subscription type), you'll receive full Shred objects with transactions, receipts, and state changes.

@@ -610,14 +341,16 @@ client.subscribe({
+
+
-

Error Handling

+

Error Handling

The API uses standard JSON-RPC error codes with additional context in the data field.

-
+
@@ -652,76 +385,146 @@ client.subscribe({ - - - - - - - - - -
Server error Internal server error
-32001Rate limitedToo many requests
-32002Subscription limitMax subscriptions reached
-
- -
+ title="Error Response Example" + />
-

Best Practices

-
-
-

Connection Management

-
    -
  • • Implement exponential backoff for reconnections
  • -
  • • Use ping/pong to detect stale connections
  • -
  • • Queue messages during disconnection
  • -
  • • Resubscribe automatically after reconnection
  • -
-
- -
-

Performance

-
    -
  • • Use filters to reduce unnecessary messages
  • -
  • • Process notifications asynchronously
  • -
  • • Batch UI updates when possible
  • -
  • • Monitor memory usage with many subscriptions
  • -
-
- -
-

Security

-
    -
  • • Connection is open - no API keys needed
  • -
  • • Validate all incoming data
  • -
  • • Use TLS for all connections
  • -
  • • Implement request signing for sensitive operations
  • -
-
-
+

Connection Management

+

+ Implement robust connection handling for production applications: +

+ + { + console.log('Connected to Shred API'); + this.reconnectAttempts = 0; + this.resubscribeAll(); + }; + + this.ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + this.handleMessage(data); + } catch (error) { + console.error('Failed to parse message:', error); + } + }; + + this.ws.onclose = (event) => { + console.log('Connection closed:', event.code, event.reason); + this.scheduleReconnect(); + }; + + this.ws.onerror = (error) => { + console.error('WebSocket error:', error); + }; + } + + handleMessage(data) { + if (data.method === 'rise_subscription') { + // Handle subscription notification + const { subscription, result } = data.params; + const handler = this.subscriptions.get(subscription); + if (handler) { + handler(result); + } + } else if (data.id) { + // Handle response to request + const pending = this.pendingRequests.get(data.id); + if (pending) { + this.pendingRequests.delete(data.id); + if (data.error) { + pending.reject(new Error(data.error.message)); + } else { + pending.resolve(data.result); + } + } + } + } + + scheduleReconnect() { + if (this.reconnectAttempts >= this.maxReconnectAttempts) { + console.error('Max reconnection attempts reached'); + return; + } + + const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); + this.reconnectAttempts++; + + console.log(\`Reconnecting in \${delay}ms (attempt \${this.reconnectAttempts})\`); + setTimeout(() => this.connect(), delay); + } + + async subscribe(channel, filter = {}) { + return new Promise((resolve, reject) => { + const id = Date.now(); + const params = channel ? [channel, filter] : []; + + this.pendingRequests.set(id, { resolve, reject }); + + this.ws.send(JSON.stringify({ + jsonrpc: '2.0', + id, + method: 'rise_subscribe', + params + })); + }); + } + + async unsubscribe(subscriptionId) { + return new Promise((resolve, reject) => { + const id = Date.now(); + + this.pendingRequests.set(id, { resolve, reject }); + + this.ws.send(JSON.stringify({ + jsonrpc: '2.0', + id, + method: 'rise_unsubscribe', + params: [subscriptionId] + })); + }); + } +}`} + title="Robust Connection Management" + />
); -} \ No newline at end of file +} diff --git a/src/app/shred-api/explainer/page.tsx b/src/app/shred-api/explainer/page.tsx index ba3206b..3951ed1 100644 --- a/src/app/shred-api/explainer/page.tsx +++ b/src/app/shred-api/explainer/page.tsx @@ -251,18 +251,44 @@ shreds.subscribe({
-

Next Steps

+

Choose Your Integration Method

+
+
+

JSON RPC API

+

Direct WebSocket connection using JSON-RPC protocol. Maximum flexibility and control.

+
    +
  • • Raw WebSocket connection
  • +
  • • Language agnostic
  • +
  • • Full protocol control
  • +
  • • Custom implementation required
  • +
+ + View JSON RPC Docs → + +
+
+

TypeScript Client Library

+

High-level TypeScript client built on Viem. Type-safe and easy to use.

+
    +
  • • Built on Viem framework
  • +
  • • Full TypeScript support
  • +
  • • Automatic reconnection
  • +
  • • Ready-to-use abstractions
  • +
+ + View TypeScript Docs → + +
+
+
+ +
+

Additional Resources

- Ready to build real-time blockchain applications? Explore our comprehensive documentation: + Explore our comprehensive documentation and examples:

); -} \ No newline at end of file +} diff --git a/src/app/shred-api/typescript-client/page.tsx b/src/app/shred-api/typescript-client/page.tsx new file mode 100644 index 0000000..5ca593c --- /dev/null +++ b/src/app/shred-api/typescript-client/page.tsx @@ -0,0 +1,587 @@ +import DocPage from '@/components/templates/DocPage'; +import CodeBlock from '@/components/ui/CodeBlock'; + +export default function TypeScriptClientDocs() { + return ( + +
+
+

TypeScript Client Overview

+

+ The official TypeScript client library provides a high-level, type-safe interface to the Shred API. + Built on top of Viem, it offers seamless integration with existing Ethereum development workflows + while adding powerful real-time capabilities. +

+
+
+ +
+
+

Installation

+

+ Install the Shred API client library using your preferred package manager: +

+ + + +
+

+ Note: The client library requires Viem v2.31.0 or higher as a peer dependency. +

+
+
+
+ +
+
+

Quick Start

+

+ Get started with the TypeScript client in just a few lines of code: +

+ + { + console.log('New shred received:', shred) + // Handle the shred data + processShred(shred) + }, + onError: (error) => { + console.error('Shred subscription error:', error) + } +}) + +// Unsubscribe when done +// unsubscribe()`} + title="Basic Usage" + /> + +
+

React Integration

+

+ Perfect integration with React applications using hooks: +

+ + + createPublicShredClient({ + transport: shredsWebSocket('wss://testnet.riselabs.xyz/ws') + }) + ) + + return client +} + +function ShredMonitor() { + const [shreds, setShreds] = useState([]) + const client = useShredClient() + + useEffect(() => { + const unsubscribe = client.watchShreds({ + onShred: (shred) => { + setShreds(prev => [shred, ...prev.slice(0, 9)]) // Keep last 10 + }, + onError: (error) => { + console.error('Subscription error:', error) + } + }) + + return unsubscribe + }, [client]) + + return ( +
+

Recent Shreds

+ {shreds.map((shred, index) => ( +
+ Block: {shred.block_number}, Transactions: {shred.transactions.length} +
+ ))} +
+ ) +}`} + title="React Hook Example" + /> +
+
+
+ +
+
+

Watching Contract Events

+

+ Monitor specific contract events with type-safe event handling: +

+ + { + logs.forEach((log) => { + console.log('Transfer detected:', { + from: log.args.from, + to: log.args.to, + value: log.args.value, + txHash: log.transactionHash + }) + + // Update UI immediately + updateTransferUI(log.args) + }) + }, + onError: (error) => { + console.error('Event subscription error:', error) + } +})`} + title="Contract Event Monitoring" + /> + +
+

Advanced Event Filtering

+

+ Use advanced filtering to monitor multiple events and contracts: +

+ + { + logs.forEach((log) => { + switch (log.eventName) { + case 'Transfer': + if ('tokenId' in log.args) { + // NFT Transfer + console.log('NFT Transfer:', { + from: log.args.from, + to: log.args.to, + tokenId: log.args.tokenId + }) + } else { + // ERC20 Transfer + console.log('Token Transfer:', { + from: log.args.from, + to: log.args.to, + value: log.args.value + }) + } + break + case 'Approval': + console.log('NFT Approval:', log.args) + break + } + }) + }, + onError: (error) => { + console.error('Multi-contract subscription error:', error) + } +})`} + title="Multi-Contract Event Monitoring" + /> +
+ +
+

Integration with Existing Viem Code

+

+ The Shred client extends standard Viem functionality, so you can use it alongside your existing Ethereum code: +

+ + { + const relevantLog = logs.find(log => log.transactionHash === txHash) + if (relevantLog) { + console.log('Transaction detected in shred:', relevantLog) + // Update UI optimistically + updateUIOptimistically(relevantLog) + unsubscribe() // Stop watching once we see our transaction + } + }, + onError: (error) => { + console.error('Shred monitoring error:', error) + } + }) + + // Also wait for confirmation using standard Viem + try { + const receipt = await publicClient.waitForTransactionReceipt({ + hash: txHash, + timeout: 60_000 + }) + console.log('Transaction confirmed:', receipt) + // Update UI with confirmed data + updateUIConfirmed(receipt) + } catch (error) { + console.error('Transaction confirmation failed:', error) + } +} + +// Example: Send transaction and monitor in real-time +async function sendTokenTransfer() { + // Send transaction (using wallet client) + const hash = await walletClient.writeContract({ + address: '0x6257c5f110900a8E02A7A480b097D44F96360d16', + abi: erc20Abi, + functionName: 'transfer', + args: [recipientAddress, amount] + }) + + // Monitor the transaction + await handleTransaction(hash) +}`} + title="Hybrid Real-time and Confirmed Data" + /> +
+
+
+ +
+
+

API Reference

+

+ Complete reference for all TypeScript client methods: +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescriptionReturns
createPublicShredClientCreate a new Shred client instancePublicShredClient
watchShredsWatch for new shreds (full objects)Unsubscribe function
watchContractShredEventWatch for specific contract eventsUnsubscribe function
watchShredEventWatch for events with advanced filteringUnsubscribe function
shredsWebSocketCreate WebSocket transport for Shred APITransport instance
+
+ +
+

Method Details

+ +
+
+

createPublicShredClient

+

+ Creates a new Shred client instance with the specified transport configuration. +

+ + +
+ +
+

watchShreds

+

+ Subscribe to receive full shred objects containing all transactions and state changes. +

+ + { + // Process full shred object + console.log('Block:', shred.block_number) + console.log('Transactions:', shred.transactions.length) + console.log('State changes:', Object.keys(shred.state_changes).length) + }, + onError: (error) => { + console.error('Shred subscription error:', error) + } +})`} + title="watchShreds Usage" + /> +
+ +
+

watchContractShredEvent

+

+ Watch for specific events from a single contract with full type safety. +

+ + { + logs.forEach((log) => { + // Fully typed log.args + const { from, to, value } = log.args + console.log(\`Transfer: \${from} -> \${to}, amount: \${value}\`) + }) + }, + onError: (error) => { + console.error('Event subscription error:', error) + } +})`} + title="watchContractShredEvent Usage" + /> +
+
+
+
+
+ +
+
+

Best Practices

+
+
+

Performance

+
    +
  • • Use specific event filters to reduce unnecessary data
  • +
  • • Unsubscribe from events when components unmount
  • +
  • • Batch UI updates when processing multiple events
  • +
  • • Consider using React.useMemo for expensive computations
  • +
  • • Implement debouncing for high-frequency events
  • +
+
+ +
+

Type Safety

+
    +
  • • Use parseAbiItem for type-safe event definitions
  • +
  • • Leverage TypeScript's strict mode for better error catching
  • +
  • • Define custom types for your application's event data
  • +
  • • Use Viem's built-in type utilities for address and hash validation
  • +
  • • Create typed wrappers for commonly used contract interactions
  • +
+
+ +
+

Error Handling

+
    +
  • • Always implement onError callbacks for subscriptions
  • +
  • • Handle network disconnections gracefully
  • +
  • • Implement exponential backoff for failed reconnections
  • +
  • • Log errors for debugging but don't expose sensitive data
  • +
  • • Provide fallback UI states during connection issues
  • +
+
+ +
+

Production Deployment

+
    +
  • • Use environment variables for WebSocket URLs
  • +
  • • Implement proper connection pooling for multiple clients
  • +
  • • Monitor subscription health and connection status
  • +
  • • Set up alerts for connection failures or high error rates
  • +
  • • Test reconnection behavior under various network conditions
  • +
+
+
+
+ +
+

Error Handling Example

+

+ Comprehensive error handling and reconnection logic: +

+ + { + try { + logs.forEach((log) => { + // Process each log with error handling + try { + processTransferLog(log) + } catch (logError) { + console.error('Error processing individual log:', logError, log) + // Continue processing other logs + } + }) + } catch (error) { + console.error('Error processing logs batch:', error) + // Handle batch processing errors gracefully + } + }, + onError: (error) => { + console.error('Subscription error:', error) + + // Implement custom error handling based on error type + if (error.message.includes('connection')) { + // Connection lost - client will automatically reconnect + showConnectionStatus('reconnecting') + // Optionally notify user of temporary disconnection + } else if (error.message.includes('rate limit')) { + // Rate limited - back off and retry + console.warn('Rate limited, backing off...') + setTimeout(() => { + // Retry subscription after delay + console.log('Retrying subscription...') + }, 5000) + } else if (error.message.includes('invalid')) { + // Invalid parameters - this won't resolve automatically + console.error('Invalid subscription parameters:', error) + showError('Configuration error - please check your settings') + } else { + // Unknown error + console.error('Unknown subscription error:', error) + showError('Connection error - please try again') + } + } +}) + +// Helper functions for UI updates +function showConnectionStatus(status: 'connected' | 'reconnecting' | 'disconnected') { + // Update UI to show connection status + document.getElementById('connection-status')?.setAttribute('data-status', status) +} + +function showError(message: string) { + // Show error message to user + console.error(message) + // Could trigger toast notification, modal, etc. +} + +function processTransferLog(log: any) { + // Your log processing logic here + console.log('Processing transfer:', log.args) +}`} + title="Comprehensive Error Handling" + /> +
+
+
+ ); +} diff --git a/src/data/navigation.ts b/src/data/navigation.ts index b7531b4..677dbce 100644 --- a/src/data/navigation.ts +++ b/src/data/navigation.ts @@ -15,12 +15,24 @@ export const sideNavigation: Record = { 'shred-api': [ { title: 'Explainer', href: '/shred-api/explainer' }, { - title: 'API Docs', + title: 'JSON RPC API', href: '/shred-api/api-docs', children: [ - { title: 'Getting Started', href: '/shred-api/api-docs#getting-started' }, + { title: 'Connection Setup', href: '/shred-api/api-docs#connection-setup' }, { title: 'RPC Methods', href: '/shred-api/api-docs#rpc-methods' }, - { title: 'WebSocket', href: '/shred-api/api-docs#websocket' }, + { title: 'Message Types', href: '/shred-api/api-docs#message-types' }, + { title: 'Error Handling', href: '/shred-api/api-docs#error-handling' }, + ] + }, + { + title: 'TypeScript Client', + href: '/shred-api/typescript-client', + children: [ + { title: 'Installation', href: '/shred-api/typescript-client#installation' }, + { title: 'Quick Start', href: '/shred-api/typescript-client#quick-start' }, + { title: 'Contract Events', href: '/shred-api/typescript-client#contract-events' }, + { title: 'API Reference', href: '/shred-api/typescript-client#api-reference' }, + { title: 'Best Practices', href: '/shred-api/typescript-client#best-practices' }, ] }, { title: 'Code Examples', href: '/shred-api/code-examples' },