diff --git a/README.md b/README.md index fbde558..85eb918 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,71 @@ const connection = new web3.Connection("{rpc-url}", "confirmed"); const drive = await new ShdwDrive(connection, wallet).init(); ``` +## Authentication + +Shadow Drive uses **Solana wallet signatures** for authentication. You only need a Solana wallet - no additional API keys or Shadow Drive accounts required. + +### RPC Endpoint Configuration + +You can use any Solana RPC endpoint. The example code shows GenesysGo's RPC service, but this is optional: + +**Option 1: Public Solana RPC (No additional credentials needed)** +```js +const connection = new web3.Connection("https://api.mainnet-beta.solana.com"); +``` + +**Option 2: GenesysGo RPC (Requires their RPC credentials)** +```js +const connection = new web3.Connection( + "https://us-west-1.genesysgo.net/{YOUR_RPC_ACCOUNT_ID}", + { + commitment: "confirmed", + httpHeaders: { + Authorization: "Bearer {YOUR_RPC_ACCESS_TOKEN}", + }, + } +); +``` + +**Option 3: Other RPC providers** +```js +// Helius, QuickNode, Alchemy, etc. +const connection = new web3.Connection("{YOUR_PREFERRED_RPC_URL}"); +``` + +### Shadow Drive Setup + +Regardless of your RPC choice, Shadow Drive setup is the same: + +```js +// Your Solana wallet (browser wallet, keypair, etc.) +const wallet = /* your wallet instance */; + +// Initialize Shadow Drive +const drive = await new ShdwDrive(connection, wallet).init(); +``` + +### Understanding the Example Placeholders + +In `examples/web/src/App.tsx`, the placeholders refer to **GenesysGo's RPC service credentials**: + +- `{YOUR_ACCOUNT_UUID_HERE}` = Your GenesysGo RPC account identifier +- `{GENESYSGO AUTHENTICATION TOKEN HERE}` = Your GenesysGo RPC access token + +**To use the example:** +1. **Option A**: Replace with your GenesysGo RPC credentials +2. **Option B**: Change to a public RPC endpoint and remove the Authorization header + +### How Shadow Drive Authentication Works + +1. **RPC Authentication**: Handled by your connection configuration (varies by provider) +2. **Shadow Drive Authentication**: Automatic via wallet signatures + - No API keys needed + - Uses cryptographic message signing + - Wallet signs authentication messages for each operation + +> **Note**: GenesysGo RPC credentials are for blockchain access only, not Shadow Drive storage. Shadow Drive authenticates through your Solana wallet automatically. + ### Examples | package | description | diff --git a/examples/web/README.md b/examples/web/README.md index de85583..0a9178b 100644 --- a/examples/web/README.md +++ b/examples/web/README.md @@ -1,10 +1,37 @@ # React Shadow Drive Example +## Prerequisites + +Before running this example, you'll need: + +1. **Solana Wallet**: Any Solana-compatible wallet (Phantom, Solflare, etc.) + +2. **RPC Endpoint Configuration**: Choose one option for `src/App.tsx`: + + **Option A: Use Public RPC** (No additional setup required) + ```tsx + // Line 28: Replace with public RPC + const network = "https://api.mainnet-beta.solana.com"; + + // Lines 42-48: Remove the httpHeaders config entirely + + ``` + + **Option B: Use GenesysGo RPC** (Requires GenesysGo RPC credentials) + ```tsx + // Line 28: Replace with your GenesysGo RPC account ID + const network = "https://us-west-1.genesysgo.net/YOUR_RPC_ACCOUNT_ID"; + + // Line 46: Replace with your GenesysGo RPC access token + Authorization: "Bearer YOUR_RPC_ACCESS_TOKEN", + ``` + + > **Note**: The placeholders in App.tsx are for **RPC access**, not Shadow Drive. Shadow Drive authentication is handled automatically through your connected wallet. + ## Getting Started - From the root directory of this project: - ```bash yarn install cd examples/web diff --git a/examples/web/src/Drive.tsx b/examples/web/src/Drive.tsx index e88265e..22dbd94 100644 --- a/examples/web/src/Drive.tsx +++ b/examples/web/src/Drive.tsx @@ -4,7 +4,7 @@ import { useConnection, useWallet } from "@solana/wallet-adapter-react"; import * as anchor from "@project-serum/anchor"; import { WalletMultiButton } from "@solana/wallet-adapter-react-ui"; import { PublicKey } from "@solana/web3.js"; -import { CircularProgress, TextField, FormControl, Select, InputLabel, MenuItem, Button, FormLabel, RadioGroup, FormControlLabel, Radio, styled, LinearProgress, Container, Grid } from "@mui/material"; +import { CircularProgress, TextField, FormControl, Select, InputLabel, MenuItem, Button, FormLabel, RadioGroup, FormControlLabel, Radio, styled, LinearProgress, Container, Grid, Chip } from "@mui/material"; const bytesToHuman = (bytes: any, si = false, dp = 1) => { const thresh = si ? 1024 : 1024; @@ -29,6 +29,39 @@ const bytesToHuman = (bytes: any, si = false, dp = 1) => { return bytes.toFixed(dp) + " " + units[u]; } + +// SHDW token mint address +const SHDW_TOKEN_MINT = new PublicKey("SHDWyBxihqiCj6YekG2GUr7wqKLeLAMK1gHZck9pL6y"); + +// Helper function to find associated token account +async function findAssociatedTokenAddress( + walletAddress: PublicKey, + tokenMintAddress: PublicKey +): Promise { + const [address] = await PublicKey.findProgramAddress( + [ + walletAddress.toBuffer(), + new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").toBuffer(), + tokenMintAddress.toBuffer(), + ], + new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") + ); + return address; +} + +// Calculate maximum storage size based on SHDW balance +// Rough estimate: 1 SHDW ≈ 1GB storage per year +function calculateMaxStorageFromBalance(shdwBalance: number): string { + if (shdwBalance < 0.1) return "1GB"; // Default to minimum + + const maxGB = Math.floor(shdwBalance * 0.9); // Use 90% of balance for safety + + if (maxGB >= 50) return "50GB"; // Max option available + if (maxGB >= 10) return "10GB"; + if (maxGB >= 1) return "1GB"; + + return "1GB"; // Minimum option +} /** * * Simple usage examples for Shadow Drive @@ -51,6 +84,8 @@ export default function Drive() { const [loading, setLoading] = useState(); const [tx, setTx] = useState(); const [version, setVersion] = useState('v2'); + const [shdwBalance, setShdwBalance] = useState(0); + const [balanceLoading, setBalanceLoading] = useState(false); const submitForm = async () => { if (!acc?.publicKey || !fileList) return; try { @@ -100,12 +135,37 @@ export default function Drive() { setLoading(true); const result = await drive?.createStorageAccount(accName, accSize, version); setTx(result!.transaction_signature); + // Refresh balance after creating account + await getUserShdwBalance(); } catch (e) { console.log(e); } refreshAccounts(); setLoading(false); } + + // Get user's SHDW token balance + const getUserShdwBalance = async () => { + if (!wallet.publicKey || !connection) return; + + try { + setBalanceLoading(true); + const userATA = await findAssociatedTokenAddress(wallet.publicKey, SHDW_TOKEN_MINT); + const balance = await connection.getTokenAccountBalance(userATA); + setShdwBalance(balance.value.uiAmount || 0); + } catch (e) { + console.log("Error fetching SHDW balance:", e); + setShdwBalance(0); + } finally { + setBalanceLoading(false); + } + }; + + // Handle "Max Stake" button click + const handleMaxStake = () => { + const maxSize = calculateMaxStorageFromBalance(shdwBalance); + setAccSize(maxSize); + }; useEffect(() => { (async () => { if (wallet) { @@ -121,8 +181,15 @@ export default function Drive() { useEffect(() => { if (drive) { refreshAccounts(); + getUserShdwBalance(); } }, [drive]) + + useEffect(() => { + if (wallet.connected && connection) { + getUserShdwBalance(); + } + }, [wallet.connected]) useEffect(() => { console.log('uploaded'); if (displayFiles) { @@ -146,6 +213,16 @@ export default function Drive() {

Create a Shadow Drive account:

+ + {/* SHDW Balance Display */} +
+ 0 ? "success" : "default"} + variant="outlined" + size="medium" + /> +
50GB + + {/* Max Stake Button */} +