filecoin-pin
Version:
Bridge IPFS content to Filecoin Onchain Cloud using familiar tools
173 lines • 7.32 kB
JavaScript
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