UNPKG

@wttp/site

Version:

Web3 Transfer Protocol (WTTP) - Site Contracts and deployment tools

323 lines • 14.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.estimateFile = estimateFile; exports.estimateDirectory = estimateDirectory; // Standalone version of estimate - works with standard ethers.js const ethers_1 = require("ethers"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const core_1 = require("@wttp/core"); // Import artifact directly to avoid loading tasks const utils_1 = require("../utils"); const fetchResource_1 = require("./fetchResource"); const utils_2 = require("./utils"); // Constants const CHUNK_SIZE = 32 * 1024; // 32KB chunks // ESP contract ABIs const DPS_ABI = [ "function calculateAddress(bytes memory) public pure returns (bytes32)" ]; const DPR_ABI = [ "function DPS() external view returns (address)", "function getDataPointRoyalty(bytes32) external view returns (uint256)" ]; async function estimateFile(wttpSiteAddress, sourcePath, destinationPath, options) { const { provider, gasPriceGwei, rate = 2, minGasPriceGwei = 150, customPublisher } = options; console.log(`šŸ“Š Estimating gas for: ${sourcePath} → ${destinationPath}`); // Get gas price for estimation const gasPrice = await (0, utils_1.getEstimationGasPrice)(provider, gasPriceGwei, rate, minGasPriceGwei); console.log(`⛽ Using gas price: ${ethers_1.ethers.formatUnits(gasPrice, "gwei")} gwei`); // Create contract instance with proper ABI typing const wttpSite = (0, utils_2.getWttpSite)(wttpSiteAddress, provider); // Parameter validation if (!wttpSiteAddress) { throw new Error("Web3Site contract address is required"); } if (!sourcePath || !destinationPath) { throw new Error("Both source and destination paths are required"); } if (!fs_1.default.existsSync(sourcePath)) { throw new Error(`Source file does not exist: ${sourcePath}`); } if (!fs_1.default.statSync(sourcePath).isFile()) { throw new Error(`Source path is not a file: ${sourcePath}`); } // Normalize the destination path try { destinationPath = (0, core_1.normalizePath)(destinationPath); } catch (error) { throw new Error(`Invalid destination path format: ${error instanceof Error ? error.message : 'Unknown error'}`); } // Read file let fileData; try { fileData = fs_1.default.readFileSync(sourcePath); } catch (error) { throw new Error(`Failed to read file ${sourcePath}: ${error}`); } console.log(`šŸ“ File size: ${fileData.length} bytes`); // Validate file size if (fileData.length === 0) { throw new Error("Cannot estimate empty file"); } // Chunk the data const chunks = (0, utils_1.chunkData)(fileData, CHUNK_SIZE); console.log(`Split into ${chunks.length} chunks of ${CHUNK_SIZE} bytes`); // Prepare data registrations // Use custom publisher if provided, otherwise use site address for estimation const publisherAddress = customPublisher || wttpSiteAddress; const dataRegistrations = chunks.map((chunk, index) => ({ data: chunk, chunkIndex: index, publisher: publisherAddress })); // Get the DPR contract first, then get DPS from it const dprAddress = await wttpSite.DPR(); const dpr = new ethers_1.Contract(dprAddress, DPR_ABI, provider); // Get DPS address from DPR contract (avoiding issue with wttpSite.DPS()) let dpsAddress; try { dpsAddress = await wttpSite.DPS(); } catch (error) { // Fallback: get DPS from DPR contract directly dpsAddress = await dpr.DPS(); } const dps = new ethers_1.Contract(dpsAddress, DPS_ABI, provider); let totalRoyalty = 0n; let dataPointAddresses = new Array(dataRegistrations.length).fill(""); let chunksToUpload = []; // Check existing resource let resourceResponse; let resourceDataPointAddresses = []; try { resourceResponse = await (0, fetchResource_1.fetchResource)(provider, wttpSiteAddress, destinationPath); resourceDataPointAddresses = resourceResponse.response.resource.dataPoints.map(dp => dp.toString()); } catch (error) { // Resource doesn't exist yet resourceResponse = null; } // Calculate royalties and determine which chunks need uploading console.log(`šŸ“Š Calculating royalties for ${dataRegistrations.length} chunks...`); for (let i = 0; i < dataRegistrations.length; i++) { const chunk = dataRegistrations[i]; // Calculate the data point address const dataPointAddress = await dps.calculateAddress(chunk.data); dataPointAddresses[i] = dataPointAddress; if (resourceDataPointAddresses.length > i) { // Check if this chunk already exists in the resource const existingAddress = resourceDataPointAddresses[i] || ""; if (existingAddress === dataPointAddress) { console.log(`āœ… Chunk ${i + 1}/${dataRegistrations.length} already exists, skipping`); } else { chunksToUpload.push(i); const royalty = await dpr.getDataPointRoyalty(dataPointAddress); totalRoyalty += royalty; } } else { chunksToUpload.push(i); const royalty = await dpr.getDataPointRoyalty(dataPointAddress); totalRoyalty += royalty; } } const currencySymbol = await (0, utils_1.getChainSymbol)(provider); console.log(`šŸ’° Total royalty required: ${ethers_1.ethers.formatEther(totalRoyalty)} ${currencySymbol}`); console.log(`šŸ“‹ Chunks to upload: ${chunksToUpload.length}/${dataRegistrations.length}`); // Get MIME type and charset const { mimeType, charset } = (0, utils_1.getMimeTypeWithCharset)(sourcePath); const mimeTypeBytes2 = (0, core_1.encodeMimeType)(mimeType); const charsetBytes2 = (0, core_1.encodeCharset)(charset || ""); const headRequest = { path: destinationPath, ifModifiedSince: 0, ifNoneMatch: ethers_1.ethers.ZeroHash }; const putRequest = { head: headRequest, properties: { mimeType: mimeTypeBytes2, charset: charsetBytes2, encoding: (0, core_1.encodeEncoding)("identity"), language: (0, core_1.encodeLanguage)("en-US") }, data: [dataRegistrations[0]] }; // Use PUT for all chunks (overestimates but works without existing resources) console.log(`šŸ“ Using PUT for all chunks (conservative overestimation)`); let totalGas = 0n; // Estimate PUT for all chunks for (let i = 0; i < chunksToUpload.length; i++) { const chunkIndex = chunksToUpload[i]; const chunkRoyalty = await dpr.getDataPointRoyalty(dataPointAddresses[chunkIndex]); // PUT always expects chunkIndex: 0 in the DataRegistration const putRequestForChunk = { head: headRequest, properties: { mimeType: mimeTypeBytes2, charset: charsetBytes2, encoding: (0, core_1.encodeEncoding)("identity"), language: (0, core_1.encodeLanguage)("en-US") }, data: [{ data: dataRegistrations[chunkIndex].data, chunkIndex: 0, // PUT always uses chunkIndex 0 publisher: dataRegistrations[chunkIndex].publisher }] }; try { const putGasEstimate = await wttpSite.PUT.estimateGas(putRequestForChunk, { value: chunkRoyalty }); totalGas += putGasEstimate; if ((i + 1) % 10 === 0 || i === chunksToUpload.length - 1) { console.log(`⛽ Estimated ${i + 1}/${chunksToUpload.length} PUT transactions...`); } } catch (error) { console.warn(`āš ļø Could not estimate PUT gas for chunk ${chunkIndex}: ${error}`); // Use conservative default totalGas += 200000n; } } const totalCost = totalGas * gasPrice; const transactionCount = chunksToUpload.length; const averageGas = transactionCount > 0 ? totalGas / BigInt(transactionCount) : 0n; // Calculate cost per MB const fileSizeMB = fileData.length / (1024 * 1024); const costPerMBWei = fileSizeMB > 0 ? (totalCost * BigInt(1e18)) / BigInt(Math.floor(fileSizeMB * 1e18)) : 0n; console.log(`\nšŸ“Š Estimation Summary:`); console.log(` Total gas: ${totalGas.toString()}`); console.log(` Average gas per transaction: ${averageGas.toString()}`); console.log(` Total cost: ${ethers_1.ethers.formatEther(totalCost)} ${currencySymbol}`); console.log(` Royalty cost: ${ethers_1.ethers.formatEther(totalRoyalty)} ${currencySymbol}`); console.log(` Cost per MB: ${fileSizeMB > 0 ? ethers_1.ethers.formatEther(costPerMBWei) : "0"} ${currencySymbol}/MB`); console.log(` Transactions: ${transactionCount} PUT (conservative overestimation)`); console.log(` Gas price: ${ethers_1.ethers.formatUnits(gasPrice, "gwei")} gwei`); console.log(` Settings: ${gasPriceGwei ? `gasprice=${gasPriceGwei}` : `rate=${rate}, min=${minGasPriceGwei}`}`); return { totalGas, totalCost, royaltyCost: totalRoyalty, transactionCount, chunksToUpload: chunksToUpload.length, needsPUT: true, gasPrice }; } async function estimateDirectory(wttpSiteAddress, sourcePath, destinationPath, options) { const { provider, gasPriceGwei, rate = 2, minGasPriceGwei = 150 } = options; console.log(`šŸ“Š Estimating gas for directory: ${sourcePath} → ${destinationPath}`); // Get gas price for estimation const gasPrice = await (0, utils_1.getEstimationGasPrice)(provider, gasPriceGwei, rate, minGasPriceGwei); console.log(`⛽ Using gas price: ${ethers_1.ethers.formatUnits(gasPrice, "gwei")} gwei`); // Create contract instance with proper ABI typing const wttpSite = (0, utils_2.getWttpSite)(wttpSiteAddress, provider); // This is a simplified version - for full implementation, see src/scripts/uploadDirectory.ts // For now, estimate each file individually const { createWTTPIgnore } = await Promise.resolve().then(() => __importStar(require("../scripts/wttpIgnore"))); // Helper to get all files recursively const getAllFiles = (dirPath, wttpIgnore, arrayOfFiles = []) => { const files = fs_1.default.readdirSync(dirPath); files.forEach((file) => { const fullPath = path_1.default.join(dirPath, file); if (wttpIgnore && wttpIgnore.shouldIgnore(fullPath)) { return; } if (fs_1.default.statSync(fullPath).isDirectory()) { arrayOfFiles = getAllFiles(fullPath, wttpIgnore, arrayOfFiles); } else { arrayOfFiles.push(fullPath); } }); return arrayOfFiles; }; const wttpIgnore = createWTTPIgnore(sourcePath, { includeDefaults: true }); const allFiles = getAllFiles(sourcePath, wttpIgnore); let totalGas = 0n; let totalRoyaltyCost = 0n; let totalTransactions = 0; let fileCount = 0; // Estimate DEFINE for root directory (conservative) totalGas += 50000n; totalTransactions += 1; // Estimate each file for (const filePath of allFiles) { const relativePath = path_1.default.relative(sourcePath, filePath); const fullDestPath = path_1.default.join(destinationPath, relativePath).replace(/\\/g, '/'); try { const fileEstimate = await estimateFile(wttpSiteAddress, filePath, fullDestPath, { provider, gasPriceGwei, rate, minGasPriceGwei }); totalGas += fileEstimate.totalGas; totalRoyaltyCost += fileEstimate.royaltyCost; totalTransactions += fileEstimate.transactionCount; fileCount++; } catch (error) { console.error(`āŒ Failed to estimate file ${relativePath}:`, error); } } const totalCost = totalGas * gasPrice; const currencySymbol = await (0, utils_1.getChainSymbol)(provider); console.log(`\nšŸ“Š Directory Estimation Summary:`); console.log(` Total gas: ${totalGas.toString()}`); console.log(` Total cost: ${ethers_1.ethers.formatEther(totalCost)} ${currencySymbol}`); console.log(` Total royalty cost: ${ethers_1.ethers.formatEther(totalRoyaltyCost)} ${currencySymbol}`); console.log(` Total transactions: ${totalTransactions}`); console.log(` Files: ${fileCount}`); return { totalGas, totalCost, totalRoyaltyCost, totalTransactions, fileCount, directoryCount: 1, // Simplified gasPrice }; } //# sourceMappingURL=estimate.js.map