UNPKG

filecoin-pin

Version:

Bridge IPFS content to Filecoin Onchain Cloud using familiar tools

173 lines 7.32 kB
import { checkAllowances, checkFILBalance, checkUSDFCBalance, setMaxAllowances, validatePaymentCapacity, validatePaymentRequirements, } from '../payments/index.js'; import { isSessionKeyMode } from '../synapse/index.js'; import { waitForIpniProviderResults, } from '../utils/validate-ipni-advertisement.js'; import { uploadToSynapse } from './synapse.js'; export { getDownloadURL, getServiceURL, uploadToSynapse } from './synapse.js'; /** * Check readiness for uploading a CAR file. * * This performs the same validation chain previously used by the CLI/action: * 1. Ensure basic wallet requirements (FIL for gas, USDFC balance) * 2. Confirm or configure WarmStorage allowances * 3. Validate that the current deposit can cover the upload * * The function only mutates state when `autoConfigureAllowances` is enabled * (default), in which case it will call {@link setMaxAllowances} as needed. * * **Session Key Authentication**: When using session key authentication, * `autoConfigureAllowances` is automatically disabled since payment operations * require the owner wallet to sign. Allowances must be configured separately * by the owner wallet before uploads can proceed. */ export async function checkUploadReadiness(options) { const { synapse, fileSize, autoConfigureAllowances = true, onProgress } = options; // Detect session key mode - payment operations cannot be performed const sessionKeyMode = isSessionKeyMode(synapse); const canConfigureAllowances = autoConfigureAllowances && !sessionKeyMode; onProgress?.({ type: 'checking-balances' }); const filStatus = await checkFILBalance(synapse); const walletUsdfcBalance = await checkUSDFCBalance(synapse); const validation = validatePaymentRequirements(filStatus.hasSufficientGas, walletUsdfcBalance, filStatus.isCalibnet); if (!validation.isValid) { return { status: 'blocked', validation, filStatus, walletUsdfcBalance, allowances: { needsUpdate: false, updated: false, }, suggestions: [], }; } onProgress?.({ type: 'checking-allowances' }); const allowanceStatus = await checkAllowances(synapse); let allowancesUpdated = false; let allowanceTxHash; // Only try to configure allowances if not in session key mode if (allowanceStatus.needsUpdate && canConfigureAllowances) { onProgress?.({ type: 'configuring-allowances' }); const setResult = await setMaxAllowances(synapse); allowancesUpdated = true; allowanceTxHash = setResult.transactionHash; onProgress?.({ type: 'allowances-configured', data: { transactionHash: allowanceTxHash } }); } onProgress?.({ type: 'validating-capacity' }); const capacityCheck = await validatePaymentCapacity(synapse, fileSize); const capacityStatus = determineCapacityStatus(capacityCheck); if (capacityStatus === 'insufficient') { return { status: 'blocked', validation, filStatus, walletUsdfcBalance, allowances: { needsUpdate: allowanceStatus.needsUpdate, updated: allowancesUpdated, transactionHash: allowanceTxHash, }, capacity: capacityCheck, suggestions: capacityCheck.suggestions, }; } return { status: 'ready', validation, filStatus, walletUsdfcBalance, allowances: { needsUpdate: allowanceStatus.needsUpdate, updated: allowancesUpdated, transactionHash: allowanceTxHash, }, capacity: capacityCheck, suggestions: capacityCheck.suggestions, }; } function determineCapacityStatus(capacity) { if (!capacity.canUpload) return 'insufficient'; if (capacity.suggestions.length > 0) return 'warning'; return 'sufficient'; } /** * Execute the upload to Synapse, returning the same structured data used by the * CLI and GitHub Action. */ export async function executeUpload(synapseService, carData, rootCid, options) { const { logger, contextId } = options; let transactionHash; let ipniValidationPromise; const onProgress = (event) => { switch (event.type) { case 'onPieceAdded': { // Begin IPNI validation as soon as the piece is added and parked in the data set if (options.ipniValidation?.enabled !== false && ipniValidationPromise == null) { const { enabled: _enabled, expectedProviders, ...restOptions } = options.ipniValidation ?? {}; // Build validation options const validationOptions = { ...restOptions, logger, }; // Forward progress events to caller if they provided a handler if (options?.onProgress != null) { validationOptions.onProgress = options.onProgress; } // Determine which providers to expect in IPNI // Priority: user-provided expectedProviders > current provider > none (generic validation) // Note: If expectedProviders is explicitly [], we respect that (no provider expectations) if (expectedProviders != null) { validationOptions.expectedProviders = expectedProviders; } else if (synapseService.providerInfo != null) { validationOptions.expectedProviders = [synapseService.providerInfo]; } // Start validation (runs in parallel with other operations) ipniValidationPromise = waitForIpniProviderResults(rootCid, validationOptions).catch((error) => { logger.warn({ error }, 'IPNI provider results check was rejected'); return false; }); } if (event.data.txHash != null) { transactionHash = event.data.txHash; } break; } default: { break; } } options.onProgress?.(event); }; const uploadOptions = { onProgress, }; if (contextId) { uploadOptions.contextId = contextId; } if (options.pieceMetadata) { uploadOptions.pieceMetadata = options.pieceMetadata; } const uploadResult = await uploadToSynapse(synapseService, carData, rootCid, logger, uploadOptions); // Optionally validate IPNI advertisement of the root CID before returning let ipniValidated = false; if (ipniValidationPromise != null) { try { ipniValidated = await ipniValidationPromise; } catch (error) { logger.error({ error }, 'Could not validate IPNI provider records'); ipniValidated = false; } } const result = { ...uploadResult, network: synapseService.synapse.getNetwork(), transactionHash, ipniValidated, }; return result; } //# sourceMappingURL=index.js.map