@wttp/site
Version:
Web3 Transfer Protocol (WTTP) - Site Contracts and deployment tools
323 lines ⢠14.5 kB
JavaScript
;
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