diff --git a/SUMMARY.md b/SUMMARY.md index 9482912..78d937a 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -39,8 +39,18 @@ ## Intermediate * [Using eth.limo with IPFS (Kubo)](intermediate/using-eth.limo-with-ipfs-kubo.md) +* [Running Your Own IPFS Node and IPNS Publishing](intermediate/running-your-own-ipfs-node.md) *** * [.ART Resolution](.art-resolution.md) * [.gno Resolution](./gnosis/gateway.md) + +## Advanced + +* [Advanced dWebsite Topics](advanced/README.md) +* [ENS Subdomains and CCIP](advanced/ens-subdomains-ccip.md) +* [Registry vs Resolver](advanced/registry-vs-resolver.md) +* [Record Types](advanced/record-types.md) +* [Alternatives to IPFS](advanced/alternatives-to-ipfs.md) +* [Arweave and ArNS](advanced/arweave-arns.md) diff --git a/advanced/README.md b/advanced/README.md new file mode 100644 index 0000000..edcfb34 --- /dev/null +++ b/advanced/README.md @@ -0,0 +1,35 @@ +# Advanced dWebsite Topics + +This section covers advanced concepts and techniques for building sophisticated dWebsites and managing complex ENS configurations. + +## ⚠️ Technical Review Required + +**Important:** The code examples and technical implementation details in this section should be verified with current versions of the respective technologies. While the conceptual framework and workflows are accurate, specific commands, API calls, and configuration details may need updates based on: + +- Current IPFS/IPNS versions +- Latest ENS contract addresses +- Updated Arweave SDK +- Current library versions (ethers.js, etc.) + +**Recommendation:** Test all code examples in your development environment and verify with official documentation before production use. + +## Topics Covered + +- [ENS Subdomains and CCIP](ens-subdomains-ccip.md) - Wildcard resolvers and cross-chain interoperability +- [Registry vs Resolver](registry-vs-resolver.md) - Understanding ENS architecture components +- [Record Types](record-types.md) - Contenthash, TXT records, and multiformats +- [Alternatives to IPFS](alternatives-to-ipfs.md) - Other decentralized storage solutions +- [Arweave and ArNS](arweave-arns.md) - Permanent storage and naming system + +## Prerequisites + +Before diving into these advanced topics, ensure you have: + +- Completed the [Beginner](../beginner/) and [Intermediate](../intermediate/) sections +- Basic understanding of blockchain concepts +- Familiarity with ENS and IPFS fundamentals +- Experience with command-line tools + +## Getting Started + +Each topic in this section builds upon the previous knowledge and introduces new concepts for advanced dWebsite development. Start with the topic that most interests you or follow the recommended order above. diff --git a/advanced/alternatives-to-ipfs.md b/advanced/alternatives-to-ipfs.md new file mode 100644 index 0000000..5ed1d83 --- /dev/null +++ b/advanced/alternatives-to-ipfs.md @@ -0,0 +1,547 @@ +# Alternatives to IPFS: Other Decentralized Storage Solutions + +While IPFS is a popular choice for dWebsites, there are several other decentralized storage solutions that offer different trade-offs in terms of permanence, cost, and features. This guide explores the major alternatives and how to integrate them with ENS. + +## Overview of Decentralized Storage Solutions + +| Solution | Permanence | Cost Model | Primary Use Case | +|----------|------------|------------|------------------| +| IPFS | Temporary (unless pinned) | Free (with pinning costs) | Content distribution | +| Arweave | Permanent | One-time payment | Permanent storage | +| Swarm | Temporary | Free | Web3 infrastructure | +| Sia | Permanent | Ongoing rental | Enterprise storage | +| Skynet | Permanent | One-time payment | Decentralized apps | +| Filecoin | Permanent | Ongoing rental | IPFS incentivization | + +## Arweave + +Arweave provides permanent, decentralized storage with a one-time payment model. + +### Key Features + +- **Permanent Storage**: Content is stored forever with a single payment +- **Proof of Access**: Novel consensus mechanism ensures data availability +- **Endowment Model**: One-time payment covers indefinite storage +- **Fast Retrieval**: Optimized for quick content access + +### Getting Started with Arweave + +#### 1. Install Arweave Tools + +```bash +# Install arweave-js +npm install arweave + +# Or use the Arweave CLI +npm install -g arweave-cli +``` + +#### 2. Create a Wallet + +```javascript +import Arweave from 'arweave'; + +const arweave = Arweave.init({ + host: 'arweave.net', + port: 443, + protocol: 'https' +}); + +// Generate a new wallet +const wallet = await arweave.wallets.generate(); +const address = await arweave.wallets.jwkToAddress(wallet); + +console.log('Wallet address:', address); +``` + +#### 3. Upload Content + +```javascript +// Upload a file to Arweave +async function uploadToArweave(filePath, wallet) { + const data = await fs.readFile(filePath); + + const transaction = await arweave.createTransaction({ + data: data + }, wallet); + + // Add tags for better organization + transaction.addTag('Content-Type', 'text/html'); + transaction.addTag('App-Name', 'dWebsite'); + + await arweave.transactions.sign(transaction); + const response = await arweave.transactions.post(transaction); + + return transaction.id; +} + +// Upload website files +const websiteFiles = ['index.html', 'style.css', 'script.js']; +const uploadPromises = websiteFiles.map(file => + uploadToArweave(`./website/${file}`, wallet) +); + +const transactionIds = await Promise.all(uploadPromises); +console.log('Uploaded files:', transactionIds); +``` + +#### 4. Integrate with ENS + +```javascript +// Set Arweave content hash in ENS +async function setArweaveContenthash(domain, arweaveId) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, signer); + const node = ethers.utils.namehash(domain); + + // Encode Arweave ID for contenthash + const encoded = ethers.utils.hexlify( + ethers.utils.concat([ + 0x6b, // Arweave multicodec + ethers.utils.toUtf8Bytes(arweaveId) + ]) + ); + + await resolver.setContenthash(node, encoded); +} + +// Example usage +await setArweaveContenthash('mydomain.eth', 'arweave-transaction-id'); +``` + +### Arweave Best Practices + +- **Bundle Multiple Files**: Use Arweave's bundling feature for multiple files +- **Set Appropriate Tags**: Use tags for better content organization +- **Consider Costs**: Calculate storage costs before uploading large files +- **Backup Wallets**: Securely store your Arweave wallet + +## Swarm + +Swarm is Ethereum's native storage layer, designed to work seamlessly with the Ethereum ecosystem. + +### Key Features + +- **Ethereum Native**: Built specifically for Ethereum +- **Incentivized Storage**: Nodes earn rewards for storing data +- **Automatic Replication**: Data is automatically distributed across nodes +- **Privacy Features**: Built-in encryption and privacy controls + +### Getting Started with Swarm + +#### 1. Install Swarm + +```bash +# Download Swarm binary +wget https://github.com/ethersphere/bee/releases/latest/download/bee-linux-amd64 + +# Make executable +chmod +x bee-linux-amd64 + +# Move to PATH +sudo mv bee-linux-amd64 /usr/local/bin/bee +``` + +#### 2. Start a Swarm Node + +```bash +# Initialize Swarm node +bee init + +# Start the node +bee start +``` + +#### 3. Upload Content + +```bash +# Upload a single file +bee upload ./website/index.html + +# Upload a directory +bee upload ./website/ + +# Get the Swarm hash +SWARM_HASH=$(bee upload ./website/ | grep -o '^[a-f0-9]\{64\}$') +echo "Swarm hash: $SWARM_HASH" +``` + +#### 4. JavaScript Integration + +```javascript +import { Bee } from '@ethersphere/bee-js'; + +const bee = new Bee('http://localhost:1633'); + +// Note: Verify Swarm Bee version and API compatibility +// Check: https://docs.ethswarm.org/docs/ + +// Upload file +async function uploadToSwarm(file) { + const fileData = await file.arrayBuffer(); + const result = await bee.uploadData(fileData); + return result.reference; +} + +// Upload directory +async function uploadDirectory(files) { + const result = await bee.uploadFiles(files); + return result.reference; +} +``` + +#### 5. ENS Integration + +```javascript +// Set Swarm content hash in ENS +async function setSwarmContenthash(domain, swarmHash) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, signer); + const node = ethers.utils.namehash(domain); + + // Encode Swarm hash for contenthash + const encoded = ethers.utils.hexlify( + ethers.utils.concat([ + 0x7b, // Swarm multicodec + ethers.utils.hexToBytes(swarmHash) + ]) + ); + + await resolver.setContenthash(node, encoded); +} +``` + +## Sia + +Sia is a decentralized storage platform that allows users to rent storage space from hosts. + +### Key Features + +- **Rental Model**: Pay for storage on a per-month basis +- **Redundancy**: Data is automatically split and distributed +- **Encryption**: All data is encrypted by default +- **Enterprise Focus**: Designed for large-scale storage needs + +### Getting Started with Sia + +#### 1. Install Sia + +```bash +# Download Sia +wget https://sia.tech/releases/Sia-v1.5.7-linux-amd64.zip +unzip Sia-v1.5.7-linux-amd64.zip + +# Start Sia daemon +./siad +``` + +#### 2. Upload Content + +```bash +# Create a wallet +siac wallet init + +# Upload files +siac renter upload ./website/index.html website/index.html +siac renter upload ./website/style.css website/style.css +``` + +#### 3. JavaScript Integration + +```javascript +import { SiaClient } from 'sia-client'; + +const sia = new SiaClient({ + host: 'localhost:9980' +}); + +// Upload file +async function uploadToSia(filePath, siaPath) { + const fileBuffer = await fs.readFile(filePath); + await sia.renter.upload(fileBuffer, siaPath); +} + +// Download file +async function downloadFromSia(siaPath, localPath) { + const fileData = await sia.renter.download(siaPath); + await fs.writeFile(localPath, fileData); +} +``` + +## Skynet + +Skynet is a decentralized CDN and file sharing platform built on Sia. + +### Key Features + +- **CDN-like Performance**: Fast global content delivery +- **Permanent Storage**: One-time payment for permanent storage +- **Portal Network**: Multiple portals for redundancy +- **Developer-Friendly**: Simple API for integration + +### Getting Started with Skynet + +#### 1. Upload Content + +```javascript +import { SkynetClient } from 'skynet-js'; + +const client = new SkynetClient(); + +// Upload a file +async function uploadToSkynet(file) { + const skylink = await client.uploadFile(file); + return skylink; +} + +// Upload a directory +async function uploadDirectory(files) { + const skylink = await client.uploadDirectory(files); + return skylink; +} +``` + +#### 2. ENS Integration + +```javascript +// Set Skynet content hash in ENS +async function setSkynetContenthash(domain, skylink) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, signer); + const node = ethers.utils.namehash(domain); + + // Store Skynet link as TXT record + await resolver.setText(node, 'skynet', skylink); + + // Or use custom resolver for contenthash + const encoded = encodeSkynetContenthash(skylink); + await resolver.setContenthash(node, encoded); +} +``` + +## Filecoin + +Filecoin is a decentralized storage network that incentivizes IPFS storage. + +### Key Features + +- **IPFS Compatible**: Built on top of IPFS +- **Incentivized Storage**: Miners earn FIL for storing data +- **Proof of Storage**: Cryptographic proofs ensure data availability +- **Marketplace**: Dynamic pricing based on supply and demand + +### Getting Started with Filecoin + +#### 1. Install Lotus (Filecoin Node) + +```bash +# Clone Lotus +git clone https://github.com/filecoin-project/lotus.git +cd lotus + +# Build Lotus +make clean && make all + +# Start Lotus daemon +./lotus daemon +``` + +#### 2. Upload Content + +```bash +# Import file +lotus client import ./website/index.html + +# Make storage deal +lotus client deal 0.0000000005 518400 +``` + +#### 3. JavaScript Integration + +```javascript +import { Filecoin } from '@filecoin-sdk/core'; + +const filecoin = new Filecoin({ + nodeUrl: 'http://localhost:1234/rpc/v0' +}); + +// Upload file +async function uploadToFilecoin(file) { + const cid = await filecoin.client.import(file); + return cid; +} +``` + +## Comparison and Selection Guide + +### When to Use Each Solution + +#### Choose IPFS when: +- You need temporary content distribution +- You want to avoid ongoing costs +- You're building a content-heavy application +- You need fast content discovery + +#### Choose Arweave when: +- You need permanent storage +- You want predictable one-time costs +- You're building applications that require data permanence +- You need fast retrieval times + +#### Choose Swarm when: +- You're building Ethereum-native applications +- You want automatic data replication +- You need privacy features +- You want to contribute to Ethereum's storage layer + +#### Choose Sia when: +- You need enterprise-grade storage +- You want fine-grained control over redundancy +- You're building applications with large storage requirements +- You need encryption by default + +#### Choose Skynet when: +- You need CDN-like performance +- You want simple developer APIs +- You need permanent storage with one-time payment +- You're building web applications + +#### Choose Filecoin when: +- You want IPFS with economic incentives +- You need verifiable storage proofs +- You want to participate in the storage marketplace +- You need enterprise-grade storage guarantees + +## Multi-Protocol Strategies + +### Fallback Systems + +```javascript +class MultiProtocolStorage { + constructor() { + this.protocols = { + ipfs: new IPFSClient(), + arweave: new ArweaveClient(), + swarm: new SwarmClient(), + skynet: new SkynetClient() + }; + } + + async uploadWithFallback(content, primaryProtocol = 'ipfs') { + try { + // Try primary protocol + return await this.protocols[primaryProtocol].upload(content); + } catch (error) { + console.log(`Primary protocol failed, trying fallbacks...`); + + // Try fallback protocols + for (const [protocol, client] of Object.entries(this.protocols)) { + if (protocol !== primaryProtocol) { + try { + return await client.upload(content); + } catch (fallbackError) { + console.log(`${protocol} failed:`, fallbackError.message); + } + } + } + + throw new Error('All protocols failed'); + } + } +} +``` + +### ENS Multi-Protocol Support + +```javascript +// Set multiple protocol references in ENS +async function setMultiProtocolContent(domain, contentRefs) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, signer); + const node = ethers.utils.namehash(domain); + + // Set primary contenthash + if (contentRefs.ipfs) { + const encoded = encodeIPFSContenthash(contentRefs.ipfs); + await resolver.setContenthash(node, encoded); + } + + // Set fallback protocols as TXT records + if (contentRefs.arweave) { + await resolver.setText(node, 'arweave', contentRefs.arweave); + } + + if (contentRefs.swarm) { + await resolver.setText(node, 'swarm', contentRefs.swarm); + } + + if (contentRefs.skynet) { + await resolver.setText(node, 'skynet', contentRefs.skynet); + } +} +``` + +## Cost Analysis + +### Storage Costs Comparison + +| Protocol | Cost Model | Example Cost (1GB/month) | +|----------|------------|---------------------------| +| IPFS | Free (with pinning) | $5-20 (pinning service) | +| Arweave | One-time | $0.50-1.00 (permanent) | +| Swarm | Free | $0 (incentivized) | +| Sia | Monthly rental | $2-5/month | +| Skynet | One-time | $0.50-1.00 (permanent) | +| Filecoin | Market rate | $1-3/month | + +### Cost Optimization Strategies + +1. **Hybrid Approach**: Use IPFS for temporary content, Arweave for permanent +2. **Compression**: Compress content before uploading +3. **Deduplication**: Avoid storing duplicate content +4. **Selective Storage**: Store only essential content permanently + +## Best Practices + +### 1. Protocol Selection + +- **Evaluate Requirements**: Consider permanence, cost, and performance needs +- **Test Multiple Protocols**: Try different solutions before committing +- **Monitor Costs**: Track storage costs across protocols +- **Plan for Migration**: Design systems that can migrate between protocols + +### 2. Content Management + +- **Version Control**: Implement versioning for content updates +- **Backup Strategy**: Use multiple protocols for redundancy +- **Content Optimization**: Optimize file sizes and formats +- **Metadata Management**: Maintain proper content metadata + +### 3. Integration Patterns + +- **Abstraction Layer**: Build abstraction layers for protocol switching +- **Fallback Mechanisms**: Implement automatic fallbacks +- **Monitoring**: Monitor content availability across protocols +- **User Experience**: Ensure seamless experience regardless of protocol + +## Tools and Resources + +### Development Tools + +- [Arweave JS](https://github.com/ArweaveTeam/arweave-js) +- [Swarm Bee](https://github.com/ethersphere/bee) +- [Sia Client](https://github.com/SiaFoundation/siad) +- [Skynet JS](https://github.com/SkynetLabs/skynet-js) +- [Filecoin Lotus](https://github.com/filecoin-project/lotus) + +### Documentation + +- [Arweave Documentation](https://docs.arweave.org/) +- [Swarm Documentation](https://docs.ethswarm.org/) +- [Sia Documentation](https://sia.tech/docs/) +- [Skynet Documentation](https://siasky.net/docs/) +- [Filecoin Documentation](https://docs.filecoin.io/) + +## Next Steps + +With knowledge of storage alternatives, explore: + +- [Arweave and ArNS](arweave-arns.md) - Deep dive into permanent storage +- [ENS Subdomains and CCIP](ens-subdomains-ccip.md) - Advanced ENS features +- [Record Types](record-types.md) - Understanding ENS record systems diff --git a/advanced/arweave-arns.md b/advanced/arweave-arns.md new file mode 100644 index 0000000..a7bbdf3 --- /dev/null +++ b/advanced/arweave-arns.md @@ -0,0 +1,634 @@ +# Arweave and ArNS: Permanent Storage and Decentralized Naming + +This guide covers Arweave, a permanent decentralized storage solution, and ArNS (Arweave Name System), a decentralized naming system built on Arweave. Together, they provide a complete solution for permanent dWebsites and applications. + +## What is Arweave? + +Arweave is a decentralized storage network that enables permanent, low-cost data storage. Unlike traditional storage solutions that require ongoing payments, Arweave uses an endowment model where a one-time payment covers indefinite storage. + +### Key Features + +- **Permanent Storage**: Data is stored forever with a single payment +- **Proof of Access**: Novel consensus mechanism ensures data availability +- **Endowment Model**: One-time payment covers indefinite storage +- **Fast Retrieval**: Optimized for quick content access +- **Built-in Incentives**: Miners are rewarded for storing and serving data + +## Getting Started with Arweave + +### 1. Setting Up Your Environment + +```bash +# Install Arweave JavaScript SDK +npm install arweave + +# Install Arweave CLI (optional) +npm install -g arweave-cli +``` + +### 2. Creating a Wallet + +```javascript +import Arweave from 'arweave'; + +// Initialize Arweave +const arweave = Arweave.init({ + host: 'arweave.net', + port: 443, + protocol: 'https' +}); + +// Note: Verify Arweave SDK version and API compatibility +// npm install arweave@latest + +// Generate a new wallet +async function createWallet() { + const wallet = await arweave.wallets.generate(); + const address = await arweave.wallets.jwkToAddress(wallet); + + console.log('Wallet address:', address); + console.log('Wallet (keep secure):', JSON.stringify(wallet)); + + return { wallet, address }; +} + +// Load existing wallet +async function loadWallet(walletPath) { + const walletData = await fs.readFile(walletPath, 'utf8'); + const wallet = JSON.parse(walletData); + const address = await arweave.wallets.jwkToAddress(wallet); + + return { wallet, address }; +} +``` + +### 3. Funding Your Wallet + +```javascript +// Check wallet balance +async function checkBalance(address) { + const balance = await arweave.wallets.getBalance(address); + const arBalance = arweave.ar.winstonToAr(balance); + + console.log(`Balance: ${arBalance} AR`); + return arBalance; +} + +// Get wallet balance in Winston (smallest unit) +async function getBalanceInWinston(address) { + return await arweave.wallets.getBalance(address); +} +``` + +### 4. Uploading Content + +```javascript +// Upload a single file +async function uploadFile(filePath, wallet, tags = {}) { + const fileData = await fs.readFile(filePath); + + const transaction = await arweave.createTransaction({ + data: fileData + }, wallet); + + // Add standard tags + transaction.addTag('Content-Type', 'text/html'); + transaction.addTag('App-Name', 'dWebsite'); + + // Add custom tags + Object.entries(tags).forEach(([key, value]) => { + transaction.addTag(key, value); + }); + + await arweave.transactions.sign(transaction); + const response = await arweave.transactions.post(transaction); + + return transaction.id; +} + +// Upload website directory +async function uploadWebsite(websitePath, wallet) { + const files = await fs.readdir(websitePath); + const uploadPromises = files.map(async (file) => { + const filePath = path.join(websitePath, file); + const stats = await fs.stat(filePath); + + if (stats.isFile()) { + const contentType = getContentType(file); + return await uploadFile(filePath, wallet, { + 'Content-Type': contentType, + 'File-Name': file + }); + } + }); + + const transactionIds = await Promise.all(uploadPromises); + return transactionIds.filter(id => id); // Remove undefined values +} + +// Helper function to determine content type +function getContentType(filename) { + const ext = path.extname(filename).toLowerCase(); + const contentTypes = { + '.html': 'text/html', + '.css': 'text/css', + '.js': 'application/javascript', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml' + }; + + return contentTypes[ext] || 'application/octet-stream'; +} +``` + +### 5. Retrieving Content + +```javascript +// Download content by transaction ID +async function downloadContent(transactionId) { + const data = await arweave.transactions.getData(transactionId, { + decode: true + }); + + return data; +} + +// Get transaction metadata +async function getTransactionInfo(transactionId) { + const transaction = await arweave.transactions.get(transactionId); + + return { + id: transaction.id, + owner: transaction.owner, + tags: transaction.tags, + data_size: transaction.data_size, + block: transaction.block + }; +} + +// Search for content by tags +async function searchByTags(tags) { + const query = { + op: 'and', + expr1: { + op: 'equals', + expr1: 'App-Name', + expr2: 'dWebsite' + }, + expr2: { + op: 'equals', + expr1: 'Content-Type', + expr2: 'text/html' + } + }; + + const results = await arweave.arql(query); + return results; +} +``` + +## What is ArNS? + +ArNS (Arweave Name System) is a decentralized naming system built on Arweave that provides human-readable names for Arweave content. It's similar to ENS but specifically designed for Arweave's permanent storage model. + +### Key Features + +- **Human-Readable Names**: Map names to Arweave transaction IDs +- **Permanent Records**: Names are stored permanently on Arweave +- **Decentralized**: No central authority controls the naming system +- **Cost-Effective**: One-time payment for permanent name registration + +## Setting Up ArNS + +### 1. Understanding ArNS Structure + +ArNS uses a hierarchical naming system: +- **Root Domain**: `.ar` (Arweave's equivalent of `.eth`) +- **Subdomains**: `mydomain.ar`, `blog.mydomain.ar` +- **Records**: Point to Arweave transaction IDs + +### 2. Creating an ArNS Record + +```javascript +// Create an ArNS record +async function createArNSRecord(name, targetTransactionId, wallet) { + const record = { + name: name, + target: targetTransactionId, + timestamp: Date.now(), + version: '1.0' + }; + + const transaction = await arweave.createTransaction({ + data: JSON.stringify(record) + }, wallet); + + // Add ArNS-specific tags + transaction.addTag('App-Name', 'ArNS'); + transaction.addTag('App-Version', '1.0'); + transaction.addTag('Content-Type', 'application/json'); + transaction.addTag('Name', name); + transaction.addTag('Target', targetTransactionId); + + await arweave.transactions.sign(transaction); + await arweave.transactions.post(transaction); + + return transaction.id; +} + +// Example usage +const websiteTransactionId = await uploadFile('./website/index.html', wallet); +await createArNSRecord('mydomain.ar', websiteTransactionId, wallet); +``` + +### 3. Resolving ArNS Names + +```javascript +// Resolve an ArNS name to its target +async function resolveArNSName(name) { + const query = { + op: 'and', + expr1: { + op: 'equals', + expr1: 'App-Name', + expr2: 'ArNS' + }, + expr2: { + op: 'equals', + expr1: 'Name', + expr2: name + } + }; + + const results = await arweave.arql(query); + + if (results.length === 0) { + throw new Error(`ArNS name not found: ${name}`); + } + + // Get the latest record (most recent transaction) + const latestTransactionId = results[results.length - 1]; + const recordData = await arweave.transactions.getData(latestTransactionId, { + decode: true + }); + + const record = JSON.parse(recordData); + return record.target; +} + +// Example usage +const targetId = await resolveArNSName('mydomain.ar'); +const content = await downloadContent(targetId); +``` + +## Building a dWebsite with Arweave and ArNS + +### 1. Complete Website Upload + +```javascript +class ArweaveWebsite { + constructor(wallet) { + this.arweave = Arweave.init({ + host: 'arweave.net', + port: 443, + protocol: 'https' + }); + this.wallet = wallet; + } + + async uploadWebsite(websitePath, domainName) { + console.log('Uploading website files...'); + + // Upload all files + const files = await this.getAllFiles(websitePath); + const uploadResults = {}; + + for (const file of files) { + const relativePath = path.relative(websitePath, file); + const transactionId = await this.uploadFile(file, { + 'File-Path': relativePath, + 'Website-Domain': domainName + }); + + uploadResults[relativePath] = transactionId; + console.log(`Uploaded ${relativePath}: ${transactionId}`); + } + + // Create manifest file + const manifest = { + domain: domainName, + files: uploadResults, + timestamp: Date.now(), + version: '1.0' + }; + + const manifestId = await this.uploadManifest(manifest); + + // Create ArNS record + await this.createArNSRecord(domainName, manifestId); + + return { + manifestId, + files: uploadResults + }; + } + + async uploadFile(filePath, tags = {}) { + const fileData = await fs.readFile(filePath); + const contentType = this.getContentType(filePath); + + const transaction = await this.arweave.createTransaction({ + data: fileData + }, this.wallet); + + transaction.addTag('Content-Type', contentType); + transaction.addTag('App-Name', 'dWebsite'); + + Object.entries(tags).forEach(([key, value]) => { + transaction.addTag(key, value); + }); + + await this.arweave.transactions.sign(transaction); + await this.arweave.transactions.post(transaction); + + return transaction.id; + } + + async uploadManifest(manifest) { + const transaction = await this.arweave.createTransaction({ + data: JSON.stringify(manifest) + }, this.wallet); + + transaction.addTag('Content-Type', 'application/json'); + transaction.addTag('App-Name', 'dWebsite-Manifest'); + + await this.arweave.transactions.sign(transaction); + await this.arweave.transactions.post(transaction); + + return transaction.id; + } + + async createArNSRecord(name, targetId) { + const record = { + name: name, + target: targetId, + timestamp: Date.now() + }; + + const transaction = await this.arweave.createTransaction({ + data: JSON.stringify(record) + }, this.wallet); + + transaction.addTag('App-Name', 'ArNS'); + transaction.addTag('Content-Type', 'application/json'); + transaction.addTag('Name', name); + transaction.addTag('Target', targetId); + + await this.arweave.transactions.sign(transaction); + await this.arweave.transactions.post(transaction); + + return transaction.id; + } + + async getAllFiles(dirPath) { + const files = []; + + const items = await fs.readdir(dirPath); + for (const item of items) { + const fullPath = path.join(dirPath, item); + const stats = await fs.stat(fullPath); + + if (stats.isDirectory()) { + files.push(...await this.getAllFiles(fullPath)); + } else { + files.push(fullPath); + } + } + + return files; + } + + getContentType(filePath) { + const ext = path.extname(filePath).toLowerCase(); + const contentTypes = { + '.html': 'text/html', + '.css': 'text/css', + '.js': 'application/javascript', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml' + }; + + return contentTypes[ext] || 'application/octet-stream'; + } +} +``` + +### 2. Website Deployment + +```javascript +// Deploy a complete website +async function deployWebsite(websitePath, domainName, walletPath) { + const walletData = await fs.readFile(walletPath, 'utf8'); + const wallet = JSON.parse(walletData); + + const arweaveWebsite = new ArweaveWebsite(wallet); + + console.log(`Deploying website to ${domainName}.ar...`); + + const result = await arweaveWebsite.uploadWebsite(websitePath, domainName); + + console.log('Deployment complete!'); + console.log(`Manifest: ${result.manifestId}`); + console.log(`Access your site at: https://${domainName}.ar`); + + return result; +} + +// Example usage +deployWebsite('./my-website', 'mydomain', './wallet.json'); +``` + +## Integrating with ENS + +### 1. Setting Arweave Contenthash in ENS + +```javascript +// Set Arweave content hash in ENS +async function setArweaveInENS(ensDomain, arweaveTransactionId) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, signer); + const node = ethers.utils.namehash(ensDomain); + + // Encode Arweave transaction ID for contenthash + const encoded = ethers.utils.hexlify( + ethers.utils.concat([ + 0x6b, // Arweave multicodec + ethers.utils.toUtf8Bytes(arweaveTransactionId) + ]) + ); + + await resolver.setContenthash(node, encoded); +} + +// Example: Set both ArNS and ENS +async function setDualNames(arweaveId, ensDomain) { + // Set ArNS record + await createArNSRecord('mydomain.ar', arweaveId, wallet); + + // Set ENS contenthash + await setArweaveInENS(ensDomain, arweaveId); + + console.log(`Content accessible via:`); + console.log(`- ArNS: https://mydomain.ar`); + console.log(`- ENS: https://${ensDomain}`); +} +``` + +### 2. Multi-Protocol Resolution + +```javascript +class MultiProtocolResolver { + constructor(arweave, ensResolver) { + this.arweave = arweave; + this.ensResolver = ensResolver; + } + + async resolveContent(domain) { + // Try ArNS first + try { + const arweaveId = await this.resolveArNS(domain); + return { + protocol: 'arweave', + id: arweaveId, + url: `https://arweave.net/${arweaveId}` + }; + } catch (error) { + console.log('ArNS resolution failed, trying ENS...'); + } + + // Try ENS + try { + const contenthash = await this.ensResolver.contenthash(domain); + const decoded = this.decodeContenthash(contenthash); + + if (decoded.system === 'arweave') { + return { + protocol: 'arweave', + id: decoded.content, + url: `https://arweave.net/${decoded.content}` + }; + } + } catch (error) { + console.log('ENS resolution failed'); + } + + throw new Error(`Could not resolve ${domain}`); + } + + async resolveArNS(name) { + // ArNS resolution logic (as shown above) + return await resolveArNSName(name); + } + + decodeContenthash(encoded) { + // Contenthash decoding logic (as shown in Record Types guide) + // Implementation here... + } +} +``` + +## Cost Analysis + +### Arweave Storage Costs + +Arweave uses a one-time payment model based on: +- **Data size**: Larger files cost more +- **Network demand**: Prices fluctuate based on usage +- **Storage duration**: Endowment model covers indefinite storage + +```javascript +// Calculate storage cost +async function calculateStorageCost(dataSize) { + const price = await arweave.transactions.getPrice(dataSize); + const costInAR = arweave.ar.winstonToAr(price); + + console.log(`Storage cost for ${dataSize} bytes: ${costInAR} AR`); + return costInAR; +} + +// Example: Calculate cost for a 1MB file +const oneMB = 1024 * 1024; +const cost = await calculateStorageCost(oneMB); +``` + +### Cost Comparison + +| Storage Solution | Cost Model | 1GB Cost | Permanence | +|------------------|------------|----------|------------| +| Arweave | One-time | ~$0.50-1.00 | Permanent | +| IPFS + Pinning | Monthly | $5-20/month | Temporary | +| Traditional Cloud | Monthly | $0.02-0.10/month | Temporary | + +## Best Practices + +### 1. Content Organization + +- **Use meaningful tags**: Tag your content for easy discovery +- **Create manifests**: Use manifest files to organize multiple files +- **Version your content**: Include version information in tags +- **Document your structure**: Maintain clear documentation of your content organization + +### 2. Cost Optimization + +- **Compress content**: Reduce file sizes before uploading +- **Bundle files**: Use Arweave's bundling features for multiple files +- **Optimize images**: Use appropriate image formats and sizes +- **Remove duplicates**: Avoid storing duplicate content + +### 3. Security Considerations + +- **Secure wallet storage**: Keep your Arweave wallet secure +- **Backup wallet**: Maintain secure backups of your wallet +- **Verify content**: Always verify uploaded content +- **Monitor costs**: Track storage costs and wallet balances + +### 4. Performance Optimization + +- **Use CDNs**: Consider using Arweave gateways for faster access +- **Optimize file structure**: Organize files for efficient retrieval +- **Cache frequently accessed content**: Implement caching strategies +- **Monitor performance**: Track content access times and optimize + +## Tools and Resources + +### Development Tools + +- [Arweave JS](https://github.com/ArweaveTeam/arweave-js) - Official JavaScript SDK +- [Arweave CLI](https://github.com/ArweaveTeam/arweave-cli) - Command-line interface +- [Arweave Gateway](https://arweave.net/) - Web gateway for Arweave content + +### ArNS Tools + +- [ArNS SDK](https://github.com/ArweaveTeam/arns-js) - JavaScript SDK for ArNS +- [ArNS Gateway](https://arweave.net/) - Gateway supporting ArNS resolution + +### Documentation + +- [Arweave Documentation](https://docs.arweave.org/) +- [ArNS Specification](https://github.com/ArweaveTeam/arns) +- [Arweave Whitepaper](https://arweave.org/whitepaper.pdf) + +## Next Steps + +With Arweave and ArNS mastered, explore: + +- [Alternatives to IPFS](alternatives-to-ipfs.md) - Other storage solutions +- [ENS Subdomains and CCIP](ens-subdomains-ccip.md) - Advanced ENS features +- [Record Types](record-types.md) - Understanding ENS record systems diff --git a/advanced/ens-subdomains-ccip.md b/advanced/ens-subdomains-ccip.md new file mode 100644 index 0000000..20f5a76 --- /dev/null +++ b/advanced/ens-subdomains-ccip.md @@ -0,0 +1,300 @@ +# ENS Subdomains and CCIP (Cross-Chain Interoperability Protocol) + +This guide covers advanced ENS subdomain management and the Cross-Chain Interoperability Protocol (CCIP) for creating sophisticated dWebsite architectures with wildcard resolvers. + +## Understanding ENS Subdomains + +ENS subdomains allow you to create hierarchical naming structures under your main domain. For example, if you own `mydomain.eth`, you can create subdomains like: +- `blog.mydomain.eth` +- `docs.mydomain.eth` +- `api.mydomain.eth` + +### Types of Subdomains + +1. **Static Subdomains**: Manually created and managed +2. **Dynamic Subdomains**: Programmatically generated +3. **Wildcard Subdomains**: Catch-all subdomains using CCIP + +## Creating Static Subdomains + +### Using the ENS App + +1. Visit [app.ens.domains](https://app.ens.domains) +2. Connect your wallet +3. Navigate to your domain +4. Click "Add/Edit Record" +5. Add a subdomain with desired records + +### Using Etherscan + +```javascript +// Example: Creating a subdomain via contract interaction +const ensRegistry = new ethers.Contract(ENS_REGISTRY_ADDRESS, ENS_ABI, signer); + +await ensRegistry.setSubnodeRecord( + ethers.utils.namehash('mydomain.eth'), + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('blog')), + ownerAddress, + resolverAddress, + 0 // TTL +); +``` + +## Cross-Chain Interoperability Protocol (CCIP) + +CCIP enables ENS domains to resolve across different blockchains and networks, making it possible to create truly decentralized and cross-chain dWebsites. + +### How CCIP Works + +1. **Off-Chain Resolution**: CCIP allows resolvers to fetch data from off-chain sources +2. **Cross-Chain Data**: Resolvers can pull data from other blockchains +3. **Dynamic Content**: Content can be updated without on-chain transactions + +### CCIP-Read Specification + +CCIP-Read enables off-chain data resolution: + +```solidity +interface IExtendedResolver { + function resolve(bytes calldata name, bytes calldata data) + external view returns (bytes memory result, uint64 validUntil, address resolver); +} +``` + +## Wildcard Resolvers + +Wildcard resolvers allow you to handle any subdomain dynamically without creating individual records for each one. + +### Setting Up a Wildcard Resolver + +```solidity +// Example wildcard resolver contract +contract WildcardResolver { + mapping(bytes32 => address) public owners; + + function resolve(bytes calldata name, bytes calldata data) + external view returns (bytes memory result, uint64 validUntil, address resolver) { + + // Parse the name to extract subdomain + string memory subdomain = extractSubdomain(name); + + // Return appropriate content based on subdomain + if (keccak256(abi.encodePacked(subdomain)) == keccak256(abi.encodePacked("blog"))) { + return (abi.encode("ipfs://QmBlogContent"), block.timestamp + 1 days, address(this)); + } else if (keccak256(abi.encodePacked(subdomain)) == keccak256(abi.encodePacked("docs"))) { + return (abi.encode("ipfs://QmDocsContent"), block.timestamp + 1 days, address(this)); + } + + // Default fallback + return (abi.encode("ipfs://QmDefaultContent"), block.timestamp + 1 days, address(this)); + } +} +``` + +### Popular Wildcard Resolver Implementations + +#### 1. NameStone + +NameStone provides a comprehensive wildcard resolver solution: + +```javascript +// Using NameStone for wildcard resolution +const namestone = new NameStone({ + domain: 'mydomain.eth', + resolver: '0x...' // NameStone resolver address +}); + +// Configure subdomain routing +await namestone.setSubdomainRoute('blog', 'ipfs://QmBlogContent'); +await namestone.setSubdomainRoute('docs', 'ipfs://QmDocsContent'); +await namestone.setSubdomainRoute('*', 'ipfs://QmDefaultContent'); // Wildcard +``` + +#### 2. NameSys + +NameSys offers advanced subdomain management: + +```javascript +// NameSys configuration +const namesys = new NameSys({ + domain: 'mydomain.eth', + gateway: 'https://gateway.namesys.xyz' +}); + +// Set up dynamic subdomains +await namesys.configureSubdomain({ + pattern: 'user-*', + template: 'https://app.mydomain.eth/user/{username}', + resolver: 'custom-resolver-address' +}); +``` + +#### 3. NameSpace + +NameSpace provides namespace-based subdomain management: + +```javascript +// NameSpace setup +const namespace = new NameSpace({ + domain: 'mydomain.eth', + namespace: 'app' +}); + +// Configure namespace rules +await namespace.setRule({ + pattern: '*.app.mydomain.eth', + action: 'redirect', + target: 'https://app.mydomain.eth/{subdomain}' +}); +``` + +## Advanced Subdomain Patterns + +### 1. User-Specific Subdomains + +```javascript +// Pattern: user123.mydomain.eth +const userResolver = { + resolve: (name) => { + const username = extractUsername(name); + return `ipfs://${getUserContent(username)}`; + } +}; +``` + +### 2. Environment-Based Subdomains + +```javascript +// Pattern: dev.mydomain.eth, staging.mydomain.eth, prod.mydomain.eth +const environmentResolver = { + resolve: (name) => { + const env = extractEnvironment(name); + return `ipfs://${getEnvironmentContent(env)}`; + } +}; +``` + +### 3. Geographic Subdomains + +```javascript +// Pattern: us.mydomain.eth, eu.mydomain.eth, asia.mydomain.eth +const geoResolver = { + resolve: (name) => { + const region = extractRegion(name); + return `ipfs://${getRegionalContent(region)}`; + } +}; +``` + +## Implementing CCIP with IPFS + +### CCIP-Read with IPFS Gateway + +```javascript +// CCIP resolver that fetches from IPFS +class IPFSCCIPResolver { + async resolve(name, data) { + const subdomain = this.extractSubdomain(name); + + // Fetch configuration from IPFS + const configCID = await this.getConfigCID(subdomain); + const config = await this.fetchFromIPFS(configCID); + + return { + result: config.contentHash, + validUntil: Date.now() + (config.ttl * 1000), + resolver: this.address + }; + } + + async fetchFromIPFS(cid) { + const response = await fetch(`https://ipfs.io/ipfs/${cid}`); + return await response.json(); + } +} +``` + +### Dynamic Content Updates + +```javascript +// Update content without on-chain transactions +const updateContent = async (subdomain, newContent) => { + // Upload new content to IPFS + const newCID = await uploadToIPFS(newContent); + + // Update configuration (stored off-chain or in IPFS) + await updateConfig(subdomain, { + contentHash: `ipfs://${newCID}`, + updatedAt: Date.now() + }); +}; +``` + +## Best Practices + +### 1. Security Considerations + +- **Access Control**: Implement proper access controls for subdomain management +- **Rate Limiting**: Prevent abuse of wildcard resolvers +- **Validation**: Validate all inputs and subdomain names + +### 2. Performance Optimization + +- **Caching**: Implement caching for frequently accessed subdomains +- **CDN Integration**: Use CDNs for global content delivery +- **Lazy Loading**: Load subdomain configurations on-demand + +### 3. Monitoring and Analytics + +```javascript +// Track subdomain usage +const trackSubdomainAccess = (subdomain) => { + analytics.track('subdomain_access', { + subdomain, + timestamp: Date.now(), + userAgent: navigator.userAgent + }); +}; +``` + +## Troubleshooting + +### Common Issues + +**Subdomain not resolving:** +- Check resolver contract configuration +- Verify CCIP implementation +- Ensure proper gas limits for resolution + +**Wildcard not working:** +- Verify wildcard pattern matching +- Check resolver contract logic +- Ensure proper fallback handling + +**Cross-chain resolution failing:** +- Verify CCIP implementation +- Check network connectivity +- Ensure proper error handling + +## Tools and Resources + +### Development Tools + +- [ENS Manager](https://app.ens.domains/) - Official ENS management interface +- [NameStone](https://namestone.xyz/) - Wildcard resolver service +- [NameSys](https://namesys.xyz/) - Advanced subdomain management +- [NameSpace](https://namespace.xyz/) - Namespace-based management + +### Documentation + +- [ENS Documentation](https://docs.ens.domains/) +- [CCIP-Read Specification](https://eips.ethereum.org/EIPS/eip-3668) +- [IPFS Documentation](https://docs.ipfs.io/) + +## Next Steps + +With subdomains and CCIP mastered, explore: + +- [Registry vs Resolver](registry-vs-resolver.md) - Deep dive into ENS architecture +- [Record Types](record-types.md) - Understanding different ENS record types +- [Alternatives to IPFS](alternatives-to-ipfs.md) - Other storage solutions diff --git a/advanced/record-types.md b/advanced/record-types.md new file mode 100644 index 0000000..18574b9 --- /dev/null +++ b/advanced/record-types.md @@ -0,0 +1,475 @@ +# ENS Record Types: Contenthash, TXT Records, and Multiformats + +This guide covers the various record types supported by ENS, with a focus on contenthash records, TXT records, and the multiformats specification that enables interoperability across different content addressing systems. + +## Overview of ENS Record Types + +ENS supports multiple record types that can be stored and resolved for each domain: + +- **Address Records**: Ethereum and other cryptocurrency addresses +- **Contenthash Records**: Content addressing (IPFS, IPNS, Arweave, etc.) +- **Text Records**: Key-value pairs for metadata +- **Custom Records**: Application-specific data + +## Contenthash Records + +Contenthash records are the primary mechanism for linking ENS domains to decentralized content, enabling dWebsites and other decentralized applications. + +### Contenthash Format + +Contenthash records use a standardized format that includes: +1. **Multicodec**: Identifies the content addressing system +2. **Content**: The actual content identifier + +``` + +``` + +### Supported Content Addressing Systems + +| System | Multicodec | Example | +|--------|------------|---------| +| IPFS | `0xe3` | `ipfs://Qm...` | +| IPNS | `0xe5` | `ipns://k51...` | +| Arweave | `0x6b` | `ar://...` | +| Swarm | `0x7b` | `bzz://...` | + +### Setting Contenthash Records + +#### Using the ENS App + +1. Visit [app.ens.domains](https://app.ens.domains) +2. Connect your wallet +3. Navigate to your domain +4. Click "Add/Edit Record" +5. Set the content hash to your IPFS/Arweave hash + +#### Using Ethers.js + +```javascript +const { ethers } = require('ethers'); + +// Encode contenthash for IPFS +function encodeContenthash(content) { + if (content.startsWith('ipfs://')) { + const hash = content.replace('ipfs://', ''); + const bytes = ethers.utils.base58decode(hash); + return ethers.utils.hexlify(ethers.utils.concat(['0xe3', bytes])); + } + + if (content.startsWith('ipns://')) { + const hash = content.replace('ipns://', ''); + const bytes = ethers.utils.base58decode(hash); + return ethers.utils.hexlify(ethers.utils.concat(['0xe5', bytes])); + } + + throw new Error('Unsupported content addressing system'); +} + +// Set contenthash record +async function setContenthash(domain, content) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, signer); + const node = ethers.utils.namehash(domain); + const encoded = encodeContenthash(content); + + await resolver.setContenthash(node, encoded); +} +``` + +#### Using IPFS + +```bash +# Add content to IPFS +ipfs add -r /path/to/website + +# Get the CID +CID=$(ipfs add -r -Q /path/to/website) + +# Set contenthash (requires ENS resolver interaction) +# This would be done through a web interface or script +``` + +### Decoding Contenthash Records + +```javascript +function decodeContenthash(encoded) { + const bytes = ethers.utils.arrayify(encoded); + + if (bytes.length === 0) { + return null; + } + + const multicodec = bytes[0]; + const content = bytes.slice(1); + + switch (multicodec) { + case 0xe3: // IPFS + const ipfsHash = ethers.utils.base58encode(content); + return `ipfs://${ipfsHash}`; + + case 0xe5: // IPNS + const ipnsHash = ethers.utils.base58encode(content); + return `ipns://${ipnsHash}`; + + case 0x6b: // Arweave + const arweaveId = ethers.utils.toUtf8String(content); + return `ar://${arweaveId}`; + + default: + throw new Error(`Unknown multicodec: ${multicodec}`); + } +} +``` + +## Multiformats and Multicodecs + +Multiformats is a self-describing data format that enables interoperability between different content addressing systems. + +### Multicodec Table + +```javascript +const MULTICODECS = { + // IPFS + 'ipfs': 0xe3, + 'ipfs-ns': 0xe5, + + // Arweave + 'arweave': 0x6b, + + // Swarm + 'swarm': 0x7b, + + // Other systems + 'skynet': 0x1b, + 'sia': 0x1c +}; + +// Note: Verify multicodec values with current multiformats specification +// https://github.com/multiformats/multicodec/blob/master/table.csv +``` + +### Multiformat Encoding + +```javascript +class MultiformatEncoder { + static encode(system, content) { + const multicodec = MULTICODECS[system]; + if (!multicodec) { + throw new Error(`Unknown system: ${system}`); + } + + let encodedContent; + switch (system) { + case 'ipfs': + case 'ipfs-ns': + encodedContent = ethers.utils.base58decode(content); + break; + case 'arweave': + encodedContent = ethers.utils.toUtf8Bytes(content); + break; + default: + throw new Error(`Unsupported system: ${system}`); + } + + return ethers.utils.hexlify( + ethers.utils.concat([multicodec, encodedContent]) + ); + } + + static decode(encoded) { + const bytes = ethers.utils.arrayify(encoded); + const multicodec = bytes[0]; + const content = bytes.slice(1); + + // Find system by multicodec + const system = Object.keys(MULTICODECS).find( + key => MULTICODECS[key] === multicodec + ); + + if (!system) { + throw new Error(`Unknown multicodec: ${multicodec}`); + } + + let decodedContent; + switch (system) { + case 'ipfs': + case 'ipfs-ns': + decodedContent = ethers.utils.base58encode(content); + break; + case 'arweave': + decodedContent = ethers.utils.toUtf8String(content); + break; + default: + throw new Error(`Unsupported system: ${system}`); + } + + return { + system, + content: decodedContent, + full: `${system}://${decodedContent}` + }; + } +} +``` + +## TXT Records + +TXT records allow you to store arbitrary key-value pairs as metadata for your domain. + +### Common TXT Record Keys + +| Key | Purpose | Example | +|-----|---------|---------| +| `url` | Website URL | `https://example.com` | +| `email` | Contact email | `contact@example.com` | +| `description` | Domain description | `My personal website` | +| `avatar` | Profile picture | `ipfs://QmAvatar...` | +| `notice` | Legal notice | `Terms of service apply` | + +### Setting TXT Records + +```javascript +async function setTXTRecord(domain, key, value) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, signer); + const node = ethers.utils.namehash(domain); + + await resolver.setText(node, key, value); +} + +// Examples +await setTXTRecord('mydomain.eth', 'url', 'https://mydomain.com'); +await setTXTRecord('mydomain.eth', 'email', 'contact@mydomain.com'); +await setTXTRecord('mydomain.eth', 'avatar', 'ipfs://QmAvatarHash'); +``` + +### Reading TXT Records + +```javascript +async function getTXTRecord(domain, key) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, provider); + const node = ethers.utils.namehash(domain); + + return await resolver.text(node, key); +} + +// Examples +const url = await getTXTRecord('mydomain.eth', 'url'); +const email = await getTXTRecord('mydomain.eth', 'email'); +``` + +## Address Records + +Address records map domain names to cryptocurrency addresses. + +### Supported Address Types + +```javascript +const ADDRESS_TYPES = { + 'ETH': 60, // Ethereum + 'BTC': 0, // Bitcoin + 'LTC': 2, // Litecoin + 'DOGE': 3, // Dogecoin + 'BCH': 145, // Bitcoin Cash + 'XRP': 144, // Ripple + 'ADA': 1815, // Cardano + 'DOT': 354, // Polkadot + 'LINK': 1977, // Chainlink + 'UNI': 708, // Uniswap + 'MATIC': 966 // Polygon +}; +``` + +### Setting Address Records + +```javascript +async function setAddressRecord(domain, coinType, address) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, signer); + const node = ethers.utils.namehash(domain); + + await resolver.setAddr(node, coinType, address); +} + +// Examples +await setAddressRecord('mydomain.eth', 60, '0x1234...'); // ETH +await setAddressRecord('mydomain.eth', 0, '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'); // BTC +``` + +### Reading Address Records + +```javascript +async function getAddressRecord(domain, coinType) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, provider); + const node = ethers.utils.namehash(domain); + + return await resolver.addr(node, coinType); +} + +// Examples +const ethAddress = await getAddressRecord('mydomain.eth', 60); +const btcAddress = await getAddressRecord('mydomain.eth', 0); +``` + +## Advanced Record Patterns + +### 1. Dynamic Content Routing + +```javascript +class DynamicContentRouter { + constructor(resolver) { + this.resolver = resolver; + } + + async setContentRoute(domain, path, contentHash) { + const key = `content.${path}`; + await this.resolver.setText( + ethers.utils.namehash(domain), + key, + contentHash + ); + } + + async getContentRoute(domain, path) { + const key = `content.${path}`; + return await this.resolver.text( + ethers.utils.namehash(domain), + key + ); + } +} +``` + +### 2. Multi-Protocol Support + +```javascript +class MultiProtocolResolver { + async setMultiProtocolContent(domain, protocols) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, signer); + const node = ethers.utils.namehash(domain); + + // Set primary contenthash + if (protocols.ipfs) { + const encoded = MultiformatEncoder.encode('ipfs', protocols.ipfs); + await resolver.setContenthash(node, encoded); + } + + // Set fallback protocols as TXT records + if (protocols.arweave) { + await resolver.setText(node, 'arweave', protocols.arweave); + } + + if (protocols.swarm) { + await resolver.setText(node, 'swarm', protocols.swarm); + } + } +} +``` + +### 3. Versioned Content + +```javascript +class VersionedContent { + async setVersionedContent(domain, version, contentHash) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, signer); + const node = ethers.utils.namehash(domain); + + // Set version-specific content + await resolver.setText(node, `version.${version}`, contentHash); + + // Update current version pointer + await resolver.setText(node, 'current-version', version); + } + + async getCurrentContent(domain) { + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, provider); + const node = ethers.utils.namehash(domain); + + const currentVersion = await resolver.text(node, 'current-version'); + return await resolver.text(node, `version.${currentVersion}`); + } +} +``` + +## Best Practices + +### 1. Content Hash Management + +- **Use IPNS for mutable content**: Update content without changing ENS records +- **Set appropriate TTL**: Balance between freshness and gas costs +- **Implement fallbacks**: Use multiple content addressing systems + +### 2. TXT Record Organization + +- **Use consistent naming**: Follow established conventions +- **Keep records minimal**: Only store essential metadata +- **Document your schema**: Maintain a record of all TXT keys used + +### 3. Address Record Security + +- **Verify addresses**: Double-check all cryptocurrency addresses +- **Use hardware wallets**: Store private keys securely +- **Monitor for changes**: Track address record modifications + +## Tools and Libraries + +### JavaScript Libraries + +```bash +npm install @ensdomains/ensjs +npm install multiformats +npm install @ipld/dag-cbor +``` + +### Example Usage + +```javascript +import { ENS } from '@ensdomains/ensjs'; +import { multiformats } from 'multiformats'; + +const ens = new ENS({ provider, chainId: 1 }); + +// Set contenthash +await ens.setContenthash('mydomain.eth', 'ipfs://QmHash'); + +// Get contenthash +const contenthash = await ens.getContenthash('mydomain.eth'); + +// Set TXT record +await ens.setText('mydomain.eth', 'url', 'https://example.com'); + +// Get TXT record +const url = await ens.getText('mydomain.eth', 'url'); +``` + +## Troubleshooting + +### Common Issues + +**Contenthash not resolving:** +- Verify the multicodec is correct +- Check that the content exists on the network +- Ensure the resolver supports contenthash records + +**TXT records not updating:** +- Check gas limits +- Verify resolver permissions +- Wait for transaction confirmation + +**Address records showing wrong values:** +- Verify the coin type is correct +- Check for address format issues +- Ensure the resolver supports the coin type + +## Resources + +- [ENS Documentation](https://docs.ens.domains/) +- [Multiformats Specification](https://multiformats.io/) +- [IPFS Content Addressing](https://docs.ipfs.io/concepts/content-addressing/) +- [EIP-1577](https://eips.ethereum.org/EIPS/eip-1577) - Contenthash Specification + +## Next Steps + +With a solid understanding of record types, explore: + +- [Alternatives to IPFS](alternatives-to-ipfs.md) - Other storage solutions +- [Arweave and ArNS](arweave-arns.md) - Permanent storage and naming +- [ENS Subdomains and CCIP](ens-subdomains-ccip.md) - Advanced subdomain management diff --git a/advanced/registry-vs-resolver.md b/advanced/registry-vs-resolver.md new file mode 100644 index 0000000..858e5aa --- /dev/null +++ b/advanced/registry-vs-resolver.md @@ -0,0 +1,390 @@ +# Registry vs Resolver: Understanding ENS Architecture + +This guide explains the fundamental components of the Ethereum Name Service (ENS) architecture: the Registry and Resolver contracts, and how they work together to provide decentralized naming. + +## ENS Architecture Overview + +ENS consists of two main smart contract components: + +1. **Registry**: The core contract that manages domain ownership and subdomain delegation +2. **Resolver**: Contracts that translate names into addresses and other data + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ ENS Registry │───▶│ Resolver │───▶│ Target Data │ +│ │ │ │ │ │ +│ - Domain Owner │ │ - ETH Address │ │ - IPFS Hash │ +│ - Resolver │ │ - IPFS Hash │ │ - Website URL │ +│ - TTL │ │ - TXT Records │ │ - Email │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +## The ENS Registry + +The Registry is the core contract that maintains the mapping between domain names and their owners, resolvers, and TTL values. + +### Registry Functions + +```solidity +interface ENSRegistry { + // Core domain management + function setOwner(bytes32 node, address owner) external; + function setResolver(bytes32 node, address resolver) external; + function setTTL(bytes32 node, uint64 ttl) external; + + // Subdomain management + function setSubnodeOwner(bytes32 node, bytes32 label, address owner) external; + function setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl) external; + + // Query functions + function owner(bytes32 node) external view returns (address); + function resolver(bytes32 node) external view returns (address); + function ttl(bytes32 node) external view returns (uint64); +} +``` + +### Registry Data Structure + +```solidity +struct Record { + address owner; // Domain owner + address resolver; // Resolver contract address + uint64 ttl; // Time-to-live for cached records +} +``` + +### Registry Operations + +#### 1. Domain Registration + +```javascript +// Register a new domain +const ensRegistry = new ethers.Contract(ENS_REGISTRY_ADDRESS, ENS_ABI, signer); + +await ensRegistry.setSubnodeRecord( + ethers.constants.HashZero, // Root node + ethers.utils.keccak256(ethers.utils.toUtf8Bytes('mydomain')), + ownerAddress, + resolverAddress, + 0 // TTL +); +``` + +#### 2. Setting a Resolver + +```javascript +// Set resolver for a domain +await ensRegistry.setResolver( + ethers.utils.namehash('mydomain.eth'), + resolverAddress +); +``` + +#### 3. Transferring Ownership + +```javascript +// Transfer domain ownership +await ensRegistry.setOwner( + ethers.utils.namehash('mydomain.eth'), + newOwnerAddress +); +``` + +## The Resolver + +Resolvers are contracts that implement the actual resolution logic, translating domain names into addresses, content hashes, and other data. + +### Standard Resolver Interface + +```solidity +interface IResolver { + // Address resolution + function addr(bytes32 node) external view returns (address); + function setAddr(bytes32 node, address addr) external; + + // Content hash resolution + function contenthash(bytes32 node) external view returns (bytes memory); + function setContenthash(bytes32 node, bytes calldata hash) external; + + // Text record resolution + function text(bytes32 node, string calldata key) external view returns (string memory); + function setText(bytes32 node, string calldata key, string calldata value) external; +} +``` + +### Public Resolver Implementation + +The Public Resolver is the most commonly used resolver implementation: + +```solidity +contract PublicResolver { + ENS public ens; + + // Address records + mapping(bytes32 => address) private _addresses; + + // Content hash records + mapping(bytes32 => bytes) private _contenthashes; + + // Text records + mapping(bytes32 => mapping(string => string)) private _texts; + + function addr(bytes32 node) public view returns (address) { + return _addresses[node]; + } + + function setAddr(bytes32 node, address addr) public { + require(ens.owner(node) == msg.sender, "Not authorized"); + _addresses[node] = addr; + emit AddrChanged(node, addr); + } + + function contenthash(bytes32 node) public view returns (bytes memory) { + return _contenthashes[node]; + } + + function setContenthash(bytes32 node, bytes calldata hash) public { + require(ens.owner(node) == msg.sender, "Not authorized"); + _contenthashes[node] = hash; + emit ContenthashChanged(node, hash); + } +} +``` + +## Registry vs Resolver: Key Differences + +| Aspect | Registry | Resolver | +|--------|----------|----------| +| **Purpose** | Domain ownership and delegation | Data resolution and storage | +| **Data Stored** | Owner, resolver address, TTL | Addresses, content hashes, text records | +| **Update Frequency** | Rare (ownership changes) | Frequent (content updates) | +| **Gas Costs** | High (ownership operations) | Low (data updates) | +| **Authorization** | Domain owner only | Domain owner or authorized accounts | + +## How They Work Together + +### Resolution Process + +1. **Name to Node**: Convert domain name to node hash +2. **Registry Lookup**: Query registry for resolver address +3. **Resolver Query**: Query resolver for specific data +4. **Return Data**: Return resolved data to caller + +```javascript +// Complete resolution process +async function resolveENS(domainName, recordType) { + const node = ethers.utils.namehash(domainName); + + // Step 1: Get resolver from registry + const resolverAddress = await ensRegistry.resolver(node); + + if (resolverAddress === ethers.constants.AddressZero) { + throw new Error('No resolver set'); + } + + // Step 2: Query resolver for data + const resolver = new ethers.Contract(resolverAddress, RESOLVER_ABI, provider); + + switch (recordType) { + case 'addr': + return await resolver.addr(node); + case 'contenthash': + return await resolver.contenthash(node); + case 'text': + return await resolver.text(node, 'url'); + default: + throw new Error('Unknown record type'); + } +} +``` + +### Example: Setting Up a dWebsite + +```javascript +// Current Public Resolver addresses (verify with ENS documentation) +const PUBLIC_RESOLVER_ADDRESSES = { + mainnet: '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41', + goerli: '0x4B1488B7a6B320d2D721406204aBc3eeAa9AD329', + sepolia: '0x8FADE66B79cC9f707aB26799354482EB93a5B7dD' +}; + +// 1. Set resolver (Registry operation) +await ensRegistry.setResolver( + ethers.utils.namehash('mydomain.eth'), + PUBLIC_RESOLVER_ADDRESSES.mainnet // Use appropriate network +); + +// 2. Set content hash (Resolver operation) +const resolver = new ethers.Contract(PUBLIC_RESOLVER_ADDRESSES.mainnet, RESOLVER_ABI, signer); +await resolver.setContenthash( + ethers.utils.namehash('mydomain.eth'), + ethers.utils.toUtf8Bytes('ipfs://QmYourWebsiteHash') +); +``` + +## Advanced Resolver Patterns + +### 1. Custom Resolvers + +```solidity +contract CustomResolver { + ENS public ens; + + // Custom resolution logic + function resolve(bytes32 node, string calldata name) + external view returns (bytes memory) { + + // Implement custom resolution logic + if (keccak256(abi.encodePacked(name)) == keccak256(abi.encodePacked("blog"))) { + return abi.encode("ipfs://QmBlogContent"); + } + + return abi.encode("ipfs://QmDefaultContent"); + } +} +``` + +### 2. Multi-Signature Resolvers + +```solidity +contract MultiSigResolver { + mapping(bytes32 => mapping(address => bool)) public authorized; + mapping(bytes32 => uint256) public requiredSignatures; + + modifier onlyAuthorized(bytes32 node) { + require(authorized[node][msg.sender], "Not authorized"); + _; + } + + function setContenthash(bytes32 node, bytes calldata hash) + external onlyAuthorized(node) { + // Implementation with multi-sig logic + } +} +``` + +### 3. Time-Based Resolvers + +```solidity +contract TimeBasedResolver { + mapping(bytes32 => mapping(uint256 => bytes)) public timeBasedContent; + + function getContentAtTime(bytes32 node, uint256 timestamp) + external view returns (bytes memory) { + return timeBasedContent[node][timestamp]; + } +} +``` + +## Gas Optimization Strategies + +### 1. Batch Operations + +```javascript +// Batch multiple resolver operations +const batchResolver = new ethers.Contract(resolverAddress, BATCH_RESOLVER_ABI, signer); + +await batchResolver.setMultipleRecords( + node, + ['addr', 'contenthash', 'text'], + [address, contentHash, textValue] +); +``` + +### 2. Proxy Resolvers + +```solidity +contract ProxyResolver { + address public implementation; + + function setImplementation(address _implementation) external { + implementation = _implementation; + } + + fallback() external { + // Delegate calls to implementation + (bool success,) = implementation.delegatecall(msg.data); + require(success, "Delegate call failed"); + } +} +``` + +## Security Considerations + +### Registry Security + +- **Ownership Management**: Secure domain ownership transfer +- **Access Control**: Restrict registry operations to authorized accounts +- **Emergency Procedures**: Implement emergency pause functionality + +### Resolver Security + +- **Authorization**: Proper access control for resolver updates +- **Input Validation**: Validate all inputs to prevent attacks +- **Upgradeability**: Consider upgradeable resolver patterns + +## Monitoring and Analytics + +### Registry Events + +```solidity +event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); +event Transfer(bytes32 indexed node, address owner); +event NewResolver(bytes32 indexed node, address resolver); +``` + +### Resolver Events + +```solidity +event AddrChanged(bytes32 indexed node, address addr); +event ContenthashChanged(bytes32 indexed node, bytes hash); +event TextChanged(bytes32 indexed node, string indexed indexedKey, string key); +``` + +## Best Practices + +### 1. Registry Management + +- Use a dedicated wallet for registry operations +- Implement proper backup and recovery procedures +- Monitor for unauthorized ownership changes + +### 2. Resolver Configuration + +- Choose appropriate resolver based on use case +- Implement proper access controls +- Consider gas costs for frequent updates + +### 3. Testing + +```javascript +// Test resolution workflow +describe('ENS Resolution', () => { + it('should resolve domain correctly', async () => { + const result = await resolveENS('mydomain.eth', 'contenthash'); + expect(result).to.equal('ipfs://QmExpectedHash'); + }); +}); +``` + +## Tools and Resources + +### Development Tools + +- [ENS Manager](https://app.ens.domains/) - Official ENS interface +- [Etherscan](https://etherscan.io/) - Contract verification and interaction +- [Hardhat](https://hardhat.org/) - Development framework + +### Documentation + +- [ENS Documentation](https://docs.ens.domains/) +- [ENS Contracts](https://github.com/ensdomains/ens-contracts) +- [EIP-137](https://eips.ethereum.org/EIPS/eip-137) - ENS Specification + +## Next Steps + +With a solid understanding of Registry vs Resolver, explore: + +- [Record Types](record-types.md) - Deep dive into different ENS record types +- [ENS Subdomains and CCIP](ens-subdomains-ccip.md) - Advanced subdomain management +- [Alternatives to IPFS](alternatives-to-ipfs.md) - Other storage solutions diff --git a/intermediate/running-your-own-ipfs-node.md b/intermediate/running-your-own-ipfs-node.md new file mode 100644 index 0000000..8385f8b --- /dev/null +++ b/intermediate/running-your-own-ipfs-node.md @@ -0,0 +1,281 @@ +# Running Your Own IPFS Node and IPNS Publishing + +This guide covers how to run your own IPFS node for complete control over your dWebsite hosting and IPNS publishing, including republishing strategies and TTL management. + +## ⚠️ Version Compatibility Note + +**Important:** This guide provides general workflows and concepts. Specific commands and configuration options may vary based on your IPFS version. Always verify commands with your current installation: + +```bash +ipfs --version +``` + +For the most up-to-date command reference, visit: https://docs.ipfs.io/reference/cli/ + +## Why Run Your Own IPFS Node? + +Running your own IPFS node provides several advantages: + +- **Complete Control**: You have full control over your data and don't rely on third-party pinning services +- **Privacy**: Your content isn't stored on external services +- **Cost-Effective**: No ongoing fees for pinning services +- **Customization**: You can configure your node exactly as needed +- **Reliability**: No dependency on external service availability + +## Prerequisites + +Before setting up your own IPFS node, ensure you have: + +- A machine with at least 4GB RAM and 50GB storage +- Stable internet connection +- Basic command-line knowledge +- IPFS Kubo installed (see [How to Install IPFS Locally](../beginner/how-to-install-ipfs-locally/README.md)) + +## Setting Up Your IPFS Node + +### 1. Initialize Your Node + +```bash +# Initialize a new IPFS repository +ipfs init + +# Start the IPFS daemon +ipfs daemon +``` + +### 2. Configure Your Node + +Edit your IPFS configuration file located at `~/.ipfs/config`: + +```json +{ + "Addresses": { + "Swarm": [ + "/ip4/0.0.0.0/tcp/4001", + "/ip6/::/tcp/4001" + ], + "API": "/ip4/127.0.0.1/tcp/5001", + "Gateway": "/ip4/127.0.0.1/tcp/8080" + }, + "Datastore": { + "StorageMax": "10GB" + }, + "Reprovider": { + "Interval": "12h" + } +} +``` + +### 3. Enable IPNS Publishing + +Ensure IPNS is enabled in your configuration: + +```bash +# Check if IPNS is enabled +ipfs config show | grep ipns + +# If not enabled, add it +ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["*"]' +ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "POST", "GET"]' +``` + +## IPNS Publishing and Republishing + +### Understanding IPNS Keys + +IPNS (InterPlanetary Name System) allows you to create mutable pointers to your content: + +```bash +# Generate a new IPNS key +ipfs key gen my-website + +# List your keys +ipfs key list -l + +# Note: Verify command syntax with your IPFS version +# ipfs --version +``` + +### Publishing Content to IPNS + +```bash +# Add your website files to IPFS +ipfs add -r /path/to/your/website + +# Publish the CID to IPNS +ipfs name publish --key=my-website /ipfs/QmYourWebsiteCID +``` + +### Republishing Strategies + +IPNS records have a Time-To-Live (TTL) and need to be republished periodically: + +#### Manual Republishing + +```bash +# Republish your IPNS record +ipfs name publish --key=my-website /ipfs/QmYourWebsiteCID +``` + +#### Automated Republishing + +Create a script for automatic republishing: + +```bash +#!/bin/bash +# republish.sh + +WEBSITE_PATH="/path/to/your/website" +KEY_NAME="my-website" + +# Add website files +CID=$(ipfs add -r -Q $WEBSITE_PATH) + +# Publish to IPNS +ipfs name publish --key=$KEY_NAME $CID + +echo "Published $CID to IPNS at $(date)" +``` + +Set up a cron job to run this script: + +```bash +# Add to crontab (republish every 12 hours) +0 */12 * * * /path/to/republish.sh +``` + +### TTL Management + +IPNS records have configurable TTL values: + +```bash +# Publish with custom TTL (24 hours) +ipfs name publish --key=my-website --ttl=24h /ipfs/QmYourWebsiteCID + +# Publish with custom TTL (7 days) +ipfs name publish --key=my-website --ttl=7d /ipfs/QmYourWebsiteCID +``` + +**Recommended TTL Values:** +- **12 hours**: For frequently updated content +- **24 hours**: For moderately updated content +- **7 days**: For stable content +- **30 days**: For rarely updated content + +> **Note:** TTL values may vary based on IPFS version and network conditions. Test with your specific setup. + +## Advanced Configuration + +### 1. Enable DHT Server Mode + +For better network participation: + +```bash +# Enable DHT server mode +ipfs config Routing.Type "dhtserver" +``` + +### 2. Configure Storage + +Optimize your storage configuration: + +```json +{ + "Datastore": { + "StorageMax": "50GB", + "StorageGCWatermark": 90, + "GCPeriod": "1h" + } +} +``` + +### 3. Set Up Monitoring + +Monitor your node's health: + +```bash +# Check node status +ipfs stats repo + +# Check connected peers +ipfs swarm peers + +# Check bandwidth usage +ipfs stats bw +``` + +## Connecting Your ENS Domain + +Once your IPFS node is running and you've published to IPNS: + +1. **Get your IPNS key hash**: + ```bash + ipfs key list -l + ``` + +2. **Update your ENS content hash** with the IPNS key hash (prefixed with `/ipns/`) + +3. **Access your dWebsite** through your ENS domain + +## Troubleshooting + +### Common Issues + +**Node not starting:** +```bash +# Check if another IPFS process is running +ps aux | grep ipfs + +# Kill existing process if needed +pkill ipfs +``` + +**IPNS not resolving:** +```bash +# Check IPNS record +ipfs name resolve /ipns/QmYourKeyHash + +# Republish if needed +ipfs name publish --key=my-website /ipfs/QmYourWebsiteCID +``` + +**Storage issues:** +```bash +# Run garbage collection +ipfs repo gc + +# Check storage usage +ipfs stats repo +``` + +### Performance Optimization + +1. **Increase file descriptor limits**: + ```bash + ulimit -n 4096 + ``` + +2. **Use SSD storage** for better performance + +3. **Configure appropriate memory limits** in your system + +## Security Considerations + +- **Firewall Configuration**: Only expose necessary ports +- **API Access**: Restrict API access to localhost +- **Key Management**: Secure your IPNS keys +- **Regular Updates**: Keep IPFS Kubo updated + +## Next Steps + +With your own IPFS node running, you can: + +- [Configure your ENS domain](../beginner/configuring-your-ens-name/README.md) +- [Set up automated publishing workflows](how-to-publish-to-ipns.md) +- [Explore advanced IPFS features](../advanced/) + +## Resources + +- [IPFS Documentation](https://docs.ipfs.io/) +- [IPNS Specification](https://specs.ipfs.tech/ipns/ipns-record/) +- [IPFS Configuration Reference](https://github.com/ipfs/kubo/blob/master/docs/config.md)