| name | shelby-dapp-builder |
| description | Expert on building decentralized applications with Shelby Protocol storage on Aptos. Helps with dApp architecture, wallet integration (Petra), browser SDK usage, React/Vue integration, file uploads, content delivery, and building Shelby-powered applications. Triggers on keywords Shelby dApp, build on Shelby, Shelby application, Petra wallet, browser storage, web3 app, decentralized app Shelby, React Shelby, Vue Shelby. |
| allowed-tools | Read, Write, Edit, Grep, Glob, Bash |
| model | sonnet |
Shelby DApp Builder
Purpose
Guide developers in building decentralized applications (dApps) that leverage Shelby Protocol for storage. Covers wallet integration, browser SDK usage, frontend frameworks (React, Vue), user authentication, and complete dApp architecture patterns.
When to Use
Auto-invoke when users ask about:
- DApp Development - Build Shelby app, create dApp, web3 application
- Browser Integration - Browser SDK, client-side uploads, web app
- Wallet Integration - Petra wallet, Aptos wallet, authentication
- Frontend Frameworks - React Shelby, Vue Shelby, Next.js integration
- User Experience - File uploads, download flows, progress tracking
- Architecture - DApp architecture, frontend/backend split, design patterns
Knowledge Base
DApp development documentation:
.claude/skills/blockchain/aptos/docs_shelby/
Key files:
sdks_typescript_browser.md- Browser SDK overviewsdks_typescript_browser_guides_upload.md- Upload workflowstools_wallets_petra-setup.md- Wallet integrationsdks_typescript_acquire-api-keys.md- API key setupprotocol.md- Protocol fundamentals
Core DApp Architecture
Three-Layer Pattern
┌─────────────────────────────────────────────┐
│ Frontend (Browser) │
│ - React/Vue/vanilla JS │
│ - Shelby Browser SDK │
│ - Wallet integration (Petra) │
│ - User interface │
└──────────────────┬──────────────────────────┘
│ HTTPS
↓
┌─────────────────────────────────────────────┐
│ Shelby RPC Servers │
│ - Blob storage/retrieval │
│ - Erasure coding │
│ - Payment processing │
└──────────────────┬──────────────────────────┘
│
↓
┌─────────────────────────────────────────────┐
│ Aptos Blockchain │
│ - Smart contracts │
│ - Wallet accounts │
│ - State coordination │
└─────────────────────────────────────────────┘
Key Components
1. Wallet Integration
- User authentication via Petra wallet
- Transaction signing
- Account management
- Balance checking
2. Shelby Browser SDK
- File uploads from browser
- Blob downloads
- Session management
- Progress tracking
3. Frontend Framework
- React, Vue, or vanilla JavaScript
- File upload UI
- Content gallery
- User feedback
Wallet Integration (Petra)
Setup
Install Petra Wallet:
Chrome Extension: https://petra.app/
Detect Wallet:
// Check if Petra is installed
const isPetraInstalled = 'aptos' in window;
if (!isPetraInstalled) {
alert('Please install Petra Wallet');
window.open('https://petra.app/', '_blank');
}
Connect Wallet
import { Network } from '@aptos-labs/ts-sdk';
class WalletManager {
async connect(): Promise<string> {
try {
// Request wallet connection
const response = await window.aptos.connect();
// Get account address
const account = await window.aptos.account();
console.log('Connected:', account.address);
return account.address;
} catch (error) {
console.error('Wallet connection failed:', error);
throw error;
}
}
async disconnect(): Promise<void> {
await window.aptos.disconnect();
}
async getAccount(): Promise<any> {
const account = await window.aptos.account();
return account;
}
async getNetwork(): Promise<string> {
const network = await window.aptos.network();
return network;
}
async signAndSubmitTransaction(payload: any): Promise<any> {
const response = await window.aptos.signAndSubmitTransaction(payload);
return response;
}
}
export const walletManager = new WalletManager();
Check Balance
async function checkBalance(address: string): Promise<any> {
const response = await fetch(
`https://api.shelbynet.shelby.xyz/v1/accounts/${address}/resources`
);
const resources = await response.json();
// Find APT and ShelbyUSD balances
const aptCoin = resources.find(
r => r.type === '0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>'
);
const shelbyUSD = resources.find(
r => r.type.includes('ShelbyUSD')
);
return {
apt: aptCoin?.data?.coin?.value || '0',
shelbyUSD: shelbyUSD?.data?.coin?.value || '0'
};
}
Browser SDK Integration
Installation
npm install @shelby-protocol/sdk @aptos-labs/ts-sdk
Initialize SDK
import { ShelbyClient } from '@shelby-protocol/sdk/browser';
import { Network, Account, Ed25519PrivateKey } from '@aptos-labs/ts-sdk';
class ShelbyService {
private client: ShelbyClient;
private account: Account;
async initialize(walletAddress: string): Promise<void> {
// For wallet-based signing, use wallet provider
// For API key usage (server-side), create account differently
this.client = new ShelbyClient({
network: Network.SHELBYNET,
apiKey: process.env.NEXT_PUBLIC_SHELBY_API_KEY // Optional
});
}
getClient(): ShelbyClient {
return this.client;
}
}
export const shelbyService = new ShelbyService();
File Upload from Browser
class FileUploader {
async uploadFile(
file: File,
onProgress?: (progress: number) => void
): Promise<string> {
// Read file as ArrayBuffer
const arrayBuffer = await file.arrayBuffer();
const data = new Uint8Array(arrayBuffer);
// Generate blob name
const blobName = `uploads/${Date.now()}-${file.name}`;
// Set expiration (30 days from now)
const expirationTimestamp = Date.now() + 30 * 24 * 60 * 60 * 1000;
// Upload to Shelby
await shelbyService.getClient().uploadBlob({
blobName,
data,
expirationTimestamp,
onProgress: (progressEvent) => {
const percentage = (progressEvent.loaded / progressEvent.total) * 100;
onProgress?.(percentage);
}
});
return blobName;
}
async uploadMultiple(
files: FileList,
onProgress?: (file: string, progress: number) => void
): Promise<string[]> {
const uploads = Array.from(files).map(async (file) => {
const blobName = await this.uploadFile(file, (progress) => {
onProgress?.(file.name, progress);
});
return blobName;
});
return await Promise.all(uploads);
}
}
export const fileUploader = new FileUploader();
Download Blob
async function downloadBlob(blobName: string, account: string): Promise<Blob> {
const data = await shelbyService.getClient().getBlob(blobName);
// Convert to Blob for browser download
return new Blob([data]);
}
async function downloadAndSave(blobName: string, account: string, filename: string) {
const blob = await downloadBlob(blobName, account);
// Create download link
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
React Integration
Upload Component
import React, { useState } from 'react';
import { fileUploader } from './shelby-service';
export function FileUploadComponent() {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [blobName, setBlobName] = useState<string | null>(null);
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
setSelectedFile(file);
}
};
const handleUpload = async () => {
if (!selectedFile) return;
setUploading(true);
setProgress(0);
try {
const result = await fileUploader.uploadFile(
selectedFile,
(percentage) => {
setProgress(percentage);
}
);
setBlobName(result);
alert('Upload successful!');
} catch (error) {
console.error('Upload failed:', error);
alert('Upload failed: ' + error.message);
} finally {
setUploading(false);
}
};
return (
<div className="upload-component">
<h2>Upload to Shelby</h2>
<input
type="file"
onChange={handleFileSelect}
disabled={uploading}
/>
{selectedFile && (
<div className="file-info">
<p>Selected: {selectedFile.name}</p>
<p>Size: {(selectedFile.size / 1024 / 1024).toFixed(2)} MB</p>
</div>
)}
<button
onClick={handleUpload}
disabled={!selectedFile || uploading}
>
{uploading ? 'Uploading...' : 'Upload'}
</button>
{uploading && (
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
/>
<span>{progress.toFixed(1)}%</span>
</div>
)}
{blobName && (
<div className="success">
<p>Blob Name: {blobName}</p>
<p>Stored successfully on Shelby!</p>
</div>
)}
</div>
);
}
Gallery Component
import React, { useState, useEffect } from 'react';
interface BlobMetadata {
name: string;
size: number;
expirationTimestamp: number;
url: string;
}
export function BlobGalleryComponent({ account }: { account: string }) {
const [blobs, setBlobs] = useState<BlobMetadata[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadBlobs();
}, [account]);
const loadBlobs = async () => {
try {
// Query indexer or smart contract for user's blobs
const response = await fetch(
`https://api.shelbynet.shelby.xyz/indexer/v1/blobs/${account}`
);
const data = await response.json();
const blobList = data.map((blob: any) => ({
name: blob.name,
size: blob.size,
expirationTimestamp: blob.expiration,
url: `https://api.shelbynet.shelby.xyz/shelby/v1/blobs/${account}/${blob.name}`
}));
setBlobs(blobList);
} catch (error) {
console.error('Failed to load blobs:', error);
} finally {
setLoading(false);
}
};
const handleDownload = async (blob: BlobMetadata) => {
const filename = blob.name.split('/').pop() || 'download';
await downloadAndSave(blob.name, account, filename);
};
if (loading) {
return <div>Loading your blobs...</div>;
}
return (
<div className="blob-gallery">
<h2>Your Blobs</h2>
{blobs.length === 0 ? (
<p>No blobs found. Upload your first file!</p>
) : (
<div className="blob-grid">
{blobs.map((blob) => (
<div key={blob.name} className="blob-card">
<h3>{blob.name}</h3>
<p>Size: {(blob.size / 1024 / 1024).toFixed(2)} MB</p>
<p>
Expires: {new Date(blob.expirationTimestamp).toLocaleDateString()}
</p>
{blob.name.match(/\.(jpg|jpeg|png|gif)$/i) && (
<img src={blob.url} alt={blob.name} className="blob-preview" />
)}
<button onClick={() => handleDownload(blob)}>
Download
</button>
</div>
))}
</div>
)}
</div>
);
}
Complete React App
import React, { useState, useEffect } from 'react';
import { walletManager } from './wallet-manager';
import { shelbyService } from './shelby-service';
import { FileUploadComponent } from './FileUploadComponent';
import { BlobGalleryComponent } from './BlobGalleryComponent';
export function App() {
const [connected, setConnected] = useState(false);
const [account, setAccount] = useState<string | null>(null);
const [balance, setBalance] = useState({ apt: '0', shelbyUSD: '0' });
useEffect(() => {
checkWalletConnection();
}, []);
const checkWalletConnection = async () => {
try {
const isConnected = await window.aptos?.isConnected();
if (isConnected) {
const account = await window.aptos.account();
setAccount(account.address);
setConnected(true);
await loadBalance(account.address);
await shelbyService.initialize(account.address);
}
} catch (error) {
console.error('Failed to check wallet:', error);
}
};
const handleConnect = async () => {
try {
const address = await walletManager.connect();
setAccount(address);
setConnected(true);
await loadBalance(address);
await shelbyService.initialize(address);
} catch (error) {
console.error('Connection failed:', error);
}
};
const handleDisconnect = async () => {
await walletManager.disconnect();
setConnected(false);
setAccount(null);
};
const loadBalance = async (address: string) => {
const balances = await checkBalance(address);
setBalance(balances);
};
return (
<div className="app">
<header>
<h1>Shelby Storage DApp</h1>
{!connected ? (
<button onClick={handleConnect}>Connect Petra Wallet</button>
) : (
<div className="wallet-info">
<p>Account: {account?.slice(0, 6)}...{account?.slice(-4)}</p>
<p>APT: {(parseInt(balance.apt) / 1e8).toFixed(4)}</p>
<p>ShelbyUSD: {(parseInt(balance.shelbyUSD) / 1e8).toFixed(4)}</p>
<button onClick={handleDisconnect}>Disconnect</button>
</div>
)}
</header>
{connected && account ? (
<main>
<FileUploadComponent />
<BlobGalleryComponent account={account} />
</main>
) : (
<div className="connect-prompt">
<p>Please connect your Petra wallet to use this app</p>
</div>
)}
</div>
);
}
Vue Integration
Upload Component (Vue 3)
<template>
<div class="upload-component">
<h2>Upload to Shelby</h2>
<input
type="file"
@change="handleFileSelect"
:disabled="uploading"
/>
<div v-if="selectedFile" class="file-info">
<p>Selected: {{ selectedFile.name }}</p>
<p>Size: {{ fileSizeMB }} MB</p>
</div>
<button
@click="handleUpload"
:disabled="!selectedFile || uploading"
>
{{ uploading ? 'Uploading...' : 'Upload' }}
</button>
<div v-if="uploading" class="progress-bar">
<div
class="progress-fill"
:style="{ width: `${progress}%` }"
/>
<span>{{ progress.toFixed(1) }}%</span>
</div>
<div v-if="blobName" class="success">
<p>Blob Name: {{ blobName }}</p>
<p>Stored successfully on Shelby!</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { fileUploader } from './shelby-service';
const selectedFile = ref<File | null>(null);
const uploading = ref(false);
const progress = ref(0);
const blobName = ref<string | null>(null);
const fileSizeMB = computed(() => {
if (!selectedFile.value) return 0;
return (selectedFile.value.size / 1024 / 1024).toFixed(2);
});
const handleFileSelect = (event: Event) => {
const target = event.target as HTMLInputElement;
const file = target.files?.[0];
if (file) {
selectedFile.value = file;
}
};
const handleUpload = async () => {
if (!selectedFile.value) return;
uploading.value = true;
progress.value = 0;
try {
const result = await fileUploader.uploadFile(
selectedFile.value,
(percentage) => {
progress.value = percentage;
}
);
blobName.value = result;
alert('Upload successful!');
} catch (error) {
console.error('Upload failed:', error);
alert('Upload failed: ' + error.message);
} finally {
uploading.value = false;
}
};
</script>
Complete DApp Examples
Example 1: Decentralized Image Gallery
// Image Gallery DApp
class ImageGalleryDApp {
async uploadImage(imageFile: File, metadata: any) {
// Resize/compress image if needed
const optimized = await this.optimizeImage(imageFile);
// Upload to Shelby
const blobName = `gallery/${metadata.albumId}/${Date.now()}.jpg`;
await shelbyService.getClient().uploadBlob({
blobName,
data: optimized,
expirationTimestamp: Date.now() + 365 * 24 * 60 * 60 * 1000
});
// Store metadata on-chain or in separate index
await this.storeMetadata(blobName, metadata);
return blobName;
}
async loadGallery(albumId: string): Promise<Image[]> {
// Query blobs by prefix
const images = await this.queryBlobs(`gallery/${albumId}/`);
return images.map(blob => ({
url: this.getBlobURL(blob.name),
metadata: blob.metadata,
thumbnail: this.getBlobURL(blob.name, { size: 'thumbnail' })
}));
}
getBlobURL(blobName: string, options?: any): string {
const baseUrl = `https://api.shelbynet.shelby.xyz/shelby/v1/blobs`;
return `${baseUrl}/${this.account}/${blobName}`;
}
}
Example 2: Decentralized Video Platform
// Video Streaming DApp
class VideoStreamingDApp {
async uploadVideo(videoFile: File, metadata: any) {
// Transcode to HLS format
const hlsSegments = await this.transcodeToHLS(videoFile);
// Upload each segment
const videoId = Date.now().toString();
const uploadPromises = hlsSegments.map(segment =>
shelbyService.getClient().uploadBlob({
blobName: `videos/${videoId}/${segment.name}`,
data: segment.data,
expirationTimestamp: Date.now() + 90 * 24 * 60 * 60 * 1000
})
);
await Promise.all(uploadPromises);
// Upload playlist manifest
await this.uploadPlaylist(videoId, hlsSegments);
// Store video metadata
await this.storeVideoMetadata(videoId, metadata);
return videoId;
}
getStreamURL(videoId: string): string {
return `https://api.shelbynet.shelby.xyz/shelby/v1/blobs/${this.account}/videos/${videoId}/playlist.m3u8`;
}
}
Example 3: Decentralized File Sharing
// File Sharing DApp
class FileSharingDApp {
async shareFile(file: File, recipients: string[], expireDays: number) {
// Upload file to Shelby
const shareId = this.generateShareId();
const blobName = `shares/${shareId}/${file.name}`;
await shelbyService.getClient().uploadBlob({
blobName,
data: await file.arrayBuffer(),
expirationTimestamp: Date.now() + expireDays * 24 * 60 * 60 * 1000
});
// Create shareable link
const shareLink = `https://myapp.com/share/${shareId}`;
// Store access control (on-chain or off-chain)
await this.storeAccessControl(shareId, recipients);
return shareLink;
}
async accessSharedFile(shareId: string, userAccount: string) {
// Check access control
const hasAccess = await this.checkAccess(shareId, userAccount);
if (!hasAccess) {
throw new Error('Access denied');
}
// Download file
const metadata = await this.getShareMetadata(shareId);
return await downloadBlob(metadata.blobName, metadata.owner);
}
}
Best Practices
1. User Experience
Progress Feedback:
// Always show upload/download progress
const [progress, setProgress] = useState(0);
await uploadFile(file, (percent) => {
setProgress(percent);
// Update UI
});
Error Handling:
try {
await uploadFile(file);
} catch (error) {
if (error.code === 'INSUFFICIENT_FUNDS') {
alert('Please add ShelbyUSD to your wallet');
} else if (error.code === 'FILE_TOO_LARGE') {
alert('File size exceeds limit');
} else {
alert('Upload failed. Please try again.');
}
}
Loading States:
// Show loading indicators
const [loading, setLoading] = useState(false);
// Disable actions during operations
<button disabled={loading || uploading}>Upload</button>
2. Performance Optimization
Image Optimization:
async function optimizeImage(file: File): Promise<Uint8Array> {
return new Promise((resolve) => {
const img = new Image();
img.src = URL.createObjectURL(file);
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
// Resize if needed
const maxWidth = 1920;
const maxHeight = 1080;
let { width, height } = img;
if (width > maxWidth) {
height = (height * maxWidth) / width;
width = maxWidth;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
// Convert to blob with compression
canvas.toBlob(
(blob) => {
blob!.arrayBuffer().then(buffer => {
resolve(new Uint8Array(buffer));
});
},
'image/jpeg',
0.85 // Quality
);
};
});
}
Lazy Loading:
// Load blobs on-demand
const [visibleBlobs, setVisibleBlobs] = useState<string[]>([]);
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Load blob when visible
loadBlob(entry.target.dataset.blobName);
}
});
});
// Observe blob containers
}, []);
3. Security
Wallet Integration:
// Always verify wallet connection
const isConnected = await window.aptos?.isConnected();
if (!isConnected) {
throw new Error('Please connect wallet');
}
// Verify network
const network = await window.aptos.network();
if (network !== 'shelbynet') {
alert('Please switch to Shelby Network');
}
Input Validation:
// Validate file types
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!allowedTypes.includes(file.type)) {
throw new Error('File type not supported');
}
// Validate file size
const maxSize = 100 * 1024 * 1024; // 100MB
if (file.size > maxSize) {
throw new Error('File too large');
}
4. Cost Management
Estimate Costs:
async function estimateUploadCost(fileSize: number, durationDays: number): Promise<number> {
// Rough estimation based on size and duration
const costPerMBPerDay = 0.001; // Example rate in ShelbyUSD
const sizeMB = fileSize / (1024 * 1024);
return sizeMB * durationDays * costPerMBPerDay;
}
// Show cost before upload
const cost = await estimateUploadCost(file.size, 30);
const confirmed = confirm(`This upload will cost ~${cost.toFixed(4)} ShelbyUSD. Continue?`);
Process for Helping Users
1. Understand App Requirements
Questions:
- What type of dApp are you building?
- What files/data need storage?
- Who are your users?
- What's the user flow?
- Mobile or desktop (or both)?
2. Design Architecture
Choose Pattern:
- Pure client-side (browser SDK only)
- Hybrid (frontend + backend API)
- Server-side rendering with Shelby
Plan Components:
- Authentication/wallet
- Upload interface
- Download/display logic
- State management
3. Provide Implementation
Show:
- Complete code examples
- Component structure
- Integration patterns
- Best practices
4. Testing & Deployment
Guide:
- Local testing workflow
- Testnet deployment
- Production considerations
- Monitoring
Response Style
- Framework-aware - Provide React, Vue, or vanilla JS examples
- Complete - Show full component implementations
- User-focused - Emphasize UX and user feedback
- Practical - Working code ready to adapt
- Modern - Use current best practices (hooks, composition API)
Example Interaction
User: "How do I build a React app for uploading images to Shelby?"
Response:
1. Show wallet integration (Petra)
2. Provide complete React upload component
3. Show gallery/display component
4. Include image optimization
5. Add progress tracking and error handling
6. Suggest deployment strategy
7. Reference: sdks_typescript_browser_guides_upload.md
Limitations
- Browser SDK has different constraints than Node.js
- Wallet-based signing differs from API key approach
- CORS considerations for direct API calls
- Be clear about testnet vs mainnet differences
Follow-up Suggestions
- State management (Redux, Zustand)
- Mobile app development (React Native)
- PWA features (offline support)
- Testing strategies (Jest, Playwright)
- Performance monitoring
- User analytics