UNPKG

delegate-framework

Version:

A TypeScript framework for building robust, production-ready blockchain workflows with comprehensive error handling, logging, and testing. Maintained by delegate.fun

299 lines 12 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.IrysClient = void 0; const upload_1 = require("@irys/upload"); const upload_solana_1 = require("@irys/upload-solana"); const web3_js_1 = require("@solana/web3.js"); const bs58_1 = __importDefault(require("bs58")); const fs_1 = __importDefault(require("fs")); const os_1 = __importDefault(require("os")); const path_1 = __importDefault(require("path")); const error_handling_1 = require("../../../utils/error-handling"); class IrysClient { constructor(config) { this.requestId = 0; if (!config.privateKey) { (0, error_handling_1.throwError)('Private key is required for Irys client', 'Irys Config Error'); } this.config = { network: 'mainnet-beta', minBalanceSol: IrysClient.DEFAULT_MIN_BALANCE_SOL, timeout: IrysClient.DEFAULT_TIMEOUT, retries: IrysClient.DEFAULT_RETRIES, ...config, }; this.logger = this.config.logger; } /** * Upload metadata to Arweave using Irys * @param metadata - The metadata object to upload * @returns Promise<IrysUploadResult> */ async uploadMetadata(metadata) { const requestId = ++this.requestId; this.logger?.debug(`Request ${requestId} started: uploadMetadata`); try { const data = JSON.stringify(metadata); this.logger?.debug('Preparing metadata upload', { dataSize: data.length, }); return await this.makeRequest(async () => { const irys = await this.createIrysUploader(); await this.fundIrysIfNeeded(irys); this.logger?.debug('Uploading metadata to Irys'); const receipt = await irys.upload(data); if (!receipt || !receipt.id) { throw new Error('No ID returned from Irys upload'); } const uri = `https://arweave.net/${receipt.id}`; this.logger?.debug('Metadata upload completed', { txId: receipt.id, uri, }); // Verify the upload is accessible const verified = await this.verifyUpload(uri); if (!verified) { throw new Error(`Irys metadata upload not found after verification attempts: ${uri}`); } return { success: true, uri, txId: receipt.id, }; }, 'uploadMetadata'); } catch (error) { this.logger?.error(`Request ${requestId} failed: uploadMetadata`, error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Upload image to Arweave using Irys * @param imageBuffer - The image buffer to upload * @param mimeType - The MIME type of the image (e.g., "image/png", "image/jpeg") * @returns Promise<IrysUploadResult> */ async uploadImage(imageBuffer, mimeType) { const requestId = ++this.requestId; this.logger?.debug(`Request ${requestId} started: uploadImage`, { mimeType, dataSize: imageBuffer.length, }); try { return await this.makeRequest(async () => { const irys = await this.createIrysUploader(); await this.fundIrysIfNeeded(irys); // Create temporary file for upload const tmpDir = os_1.default.tmpdir(); const tmpFile = path_1.default.join(tmpDir, `irys-upload-${Date.now()}`); try { fs_1.default.writeFileSync(tmpFile, imageBuffer); this.logger?.debug('Uploading image file to Irys', { tmpFile }); const receipt = await irys.uploadFile(tmpFile); if (!receipt || !receipt.id) { throw new Error('No ID returned from Irys uploadFile'); } const uri = `https://arweave.net/${receipt.id}`; this.logger?.debug('Image upload completed', { txId: receipt.id, uri, }); return { success: true, uri, txId: receipt.id, }; } finally { // Clean up temporary file if (fs_1.default.existsSync(tmpFile)) { fs_1.default.unlinkSync(tmpFile); } } }, 'uploadImage'); } catch (error) { this.logger?.error(`Request ${requestId} failed: uploadImage`, error); return { success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Get the cost estimate for uploading data to Arweave via Irys * @param dataSize - Size of data in bytes * @returns Promise<IrysCostResult> */ async getUploadCost(dataSize) { const requestId = ++this.requestId; this.logger?.debug(`Request ${requestId} started: getUploadCost`, { dataSize }); try { return await this.makeRequest(async () => { let irys; try { irys = await this.createIrysUploader(); } catch (e) { throw new Error(e?.message || 'Uploader creation failed'); } if (!irys) throw new Error('Uploader creation failed'); const price = await irys.getPrice(dataSize); this.logger?.debug('Upload cost calculated', { dataSize, cost: price, }); return { cost: price, dataSize }; }, 'getUploadCost'); } catch (error) { this.logger?.error(`Request ${requestId} failed: getUploadCost`, error); (0, error_handling_1.throwError)(error, 'Irys Cost Calculation Failed'); } } /** * Create an Irys uploader instance * @returns Promise<any> - The Irys uploader */ async createIrysUploader() { try { const privateKeyBytes = bs58_1.default.decode(this.config.privateKey); const keypair = web3_js_1.Keypair.fromSecretKey(privateKeyBytes); this.logger?.debug('Creating Irys uploader', { fundingAccount: keypair.publicKey.toBase58(), }); const irys = await (0, upload_1.Uploader)(upload_solana_1.Solana).withWallet(privateKeyBytes); this.logger?.debug('Irys uploader created', { irysAddress: irys.address, }); return irys; } catch (error) { this.logger?.error('Irys Uploader Creation Failed', error); throw error; } } /** * Fund the Irys account if needed * @param irys - The Irys uploader instance */ async fundIrysIfNeeded(irys) { try { const balance = await irys.getLoadedBalance(); // returns balance in lamports const solBalance = Number(balance) / web3_js_1.LAMPORTS_PER_SOL; if (solBalance < this.config.minBalanceSol) { const requiredLamports = Math.ceil((this.config.minBalanceSol - solBalance) * web3_js_1.LAMPORTS_PER_SOL); if (requiredLamports > 0) { this.logger?.debug('Funding Irys account', { currentBalance: solBalance, minBalance: this.config.minBalanceSol, requiredLamports, }); await irys.fund(requiredLamports.toString()); const newBalance = await irys.getLoadedBalance(); const newSolBalance = Number(newBalance) / web3_js_1.LAMPORTS_PER_SOL; this.logger?.debug('Irys account funded', { newBalance: newSolBalance, }); } } else { this.logger?.debug('Sufficient balance, no funding needed', { currentBalance: solBalance, minBalance: this.config.minBalanceSol, }); } } catch (error) { this.logger?.error('Irys Funding Failed', error); throw error; } } /** * Verify that an Arweave upload is accessible * @param uri - The Arweave URI to verify * @returns Promise<boolean> */ async verifyUpload(uri) { for (let i = 0; i < IrysClient.VERIFICATION_RETRIES; i++) { try { const response = await fetch(uri); if (response.ok) { this.logger?.debug('Arweave upload verified successfully', { uri }); return true; } } catch (error) { this.logger?.debug(`Verification attempt ${i + 1} failed`, { uri, error }); } await this.delay(IrysClient.VERIFICATION_DELAY); } return false; } /** * Make a request with retry logic and error handling * @param operation - The operation to perform * @param operationName - Name of the operation for logging * @returns Result of the operation */ async makeRequest(operation, operationName) { const requestId = ++this.requestId; this.logger?.debug(`Request ${requestId} started: ${operationName}`); let lastError; for (let attempt = 1; attempt <= this.config.retries; attempt++) { try { const result = await Promise.race([ operation(), new Promise((_, reject) => { setTimeout(() => { reject(new Error(`Operation timed out after ${this.config.timeout}ms`)); }, this.config.timeout); }), ]); this.logger?.debug(`Request ${requestId} completed: ${operationName}`, { result }); return result; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); this.logger?.warn(`Request ${requestId} attempt ${attempt} failed: ${operationName}`, lastError); if (attempt === this.config.retries) { this.logger?.error(`Request ${requestId} failed after ${attempt} attempts: ${operationName}`, lastError); throw lastError; } await this.delay(Math.pow(2, attempt - 1) * 1000); } } throw lastError; } /** * Utility method for delays * @param ms - Milliseconds to delay */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Get the current configuration * @returns Current client configuration */ getConfig() { return this.config; } } exports.IrysClient = IrysClient; IrysClient.DEFAULT_TIMEOUT = 60000; // 60 seconds for uploads IrysClient.DEFAULT_RETRIES = 3; IrysClient.DEFAULT_MIN_BALANCE_SOL = 0.02; IrysClient.VERIFICATION_RETRIES = 5; IrysClient.VERIFICATION_DELAY = 3000; // 3 seconds //# sourceMappingURL=irys.js.map