filecoin-pin
Version:
Bridge IPFS content to Filecoin Onchain Cloud using familiar tools
260 lines • 11.2 kB
JavaScript
/**
* Payment setup utilities and display functions
*
* This module provides UI utilities and display functions for payment setup,
* building on the shared payment operations exposed from `src/core/payments`.
*/
import { TIME_CONSTANTS } from '@filoz/synapse-sdk';
import { ethers } from 'ethers';
import pc from 'picocolors';
import { calculateActualCapacity, DEFAULT_LOCKUP_DAYS, getStorageScale, USDFC_DECIMALS, } from '../core/payments/index.js';
import { formatFIL, formatUSDFC } from '../core/utils/format.js';
import { log } from '../utils/cli-logger.js';
/**
* Parse storage allowance string
*
* Parses different storage allowance formats:
* - "1TiB/month" or "500GiB/month" - Human-friendly storage units
* - "0.0000565" - Direct USDFC per epoch (returns null, needs price lookup)
*
* @param input - Storage allowance string
* @returns Parsed TiB per month or null if it's a direct USDFC amount
*/
export function parseStorageAllowance(input) {
// Check if input is a storage unit (e.g., "1TiB/month", "500GiB/month")
const storageMatch = input.match(/^(\d+(?:\.\d+)?)\s*(TiB|GiB|MiB)\/month$/i);
if (storageMatch?.[1] && storageMatch[2]) {
const amount = parseFloat(storageMatch[1]);
const unit = storageMatch[2].toUpperCase();
// Convert to TiB
let tibPerMonth;
switch (unit) {
case 'TIB':
tibPerMonth = amount;
break;
case 'GIB':
tibPerMonth = amount / 1024;
break;
case 'MIB':
tibPerMonth = amount / (1024 * 1024);
break;
default:
throw new Error(`Unknown storage unit: ${unit}`);
}
return tibPerMonth;
}
// Validate that it's a valid number for USDFC per epoch
try {
ethers.parseUnits(input, USDFC_DECIMALS);
return null; // Valid USDFC amount, but need pricing to convert to TiB
}
catch {
throw new Error(`Invalid storage allowance format: ${input}. Use "1TiB/month", "500GiB/month", or a decimal number for USDFC per epoch (e.g., "0.0000565")`);
}
}
/**
* Display the payment status summary
*
* Shows three sections: Wallet, Filecoin Pay Deposit, and WarmStorage Service Permissions
*
* @param network - Network name
* @param filBalance - FIL balance in wei
* @param isCalibnet - Whether this is calibnet testnet
* @param walletUsdfcBalance - USDFC balance in wei
* @param filecoinPayBalance - Amount deposited in Filecoin Pay
* @param rateAllowance - Maximum rate per epoch
* @param lockupAllowance - Maximum lockup amount
* @param pricePerTiBPerEpoch - Current storage price
*/
export function displayPaymentSummary(network, filBalance, isCalibnet, walletUsdfcBalance, filecoinPayBalance, rateAllowance, lockupAllowance, pricePerTiBPerEpoch) {
// Start the summary section (Setup Complete is shown by spinner in auto mode)
log.line(`Network: ${pc.bold(network)}`);
log.line('');
// Section 1: Wallet
log.line(pc.bold('Wallet'));
log.indent(formatFIL(filBalance, isCalibnet));
log.indent(`${formatUSDFC(walletUsdfcBalance)} USDFC`);
log.line('');
// Section 2: Filecoin Pay deposit
log.line(pc.bold('Filecoin Pay Deposit'));
log.indent(`${formatUSDFC(filecoinPayBalance)} USDFC`);
log.indent(pc.gray('(spendable on any service)'));
// Section 3: WarmStorage service permissions
log.line('');
if (rateAllowance > 0n) {
const monthlyRate = rateAllowance * TIME_CONSTANTS.EPOCHS_PER_MONTH;
displayServicePermissions('Your WarmStorage Service Limits', monthlyRate, lockupAllowance, filecoinPayBalance, pricePerTiBPerEpoch, false);
}
else {
log.line(pc.bold('Your WarmStorage Service Limits'));
log.indent(pc.gray('No limits set'));
}
log.flush(); // Flush everything at the end
}
/**
* Display account and balance information
*
* @param address - Wallet address
* @param network - Network name (mainnet/calibration)
* @param filBalance - FIL balance in wei
* @param isCalibnet - Whether on calibration testnet
* @param hasSufficientGas - Whether wallet has enough FIL for gas
* @param walletUsdfcBalance - USDFC balance in wei
* @param filecoinPayBalance - Amount deposited to Filecoin Pay
*/
export function displayAccountInfo(address, network, filBalance, isCalibnet, _hasSufficientGas, walletUsdfcBalance, filecoinPayBalance) {
log.line(pc.bold('Account:'));
log.indent(pc.gray(`Wallet: ${address}`));
log.indent(pc.gray(`Network: ${network}`));
log.line(pc.bold('Balances:'));
log.indent(pc.gray(`FIL: ${formatFIL(filBalance, isCalibnet)}`));
log.indent(pc.gray(`USDFC wallet: ${formatUSDFC(walletUsdfcBalance)} USDFC`));
log.indent(pc.gray(`USDFC deposited: ${formatUSDFC(filecoinPayBalance)} USDFC`));
log.flush();
}
/**
* Display deposit warning if balance is too low for active storage
*
* Warns when the available deposit (total deposit minus locked amount)
* is insufficient to maintain active storage operations.
*
* @param filecoinPayBalance - Current deposit balance
* @param lockupUsed - Amount currently locked for active storage
*/
export function displayDepositWarning(filecoinPayBalance, lockupUsed) {
if (lockupUsed > 0n) {
// Calculate available deposit after accounting for locked funds
const availableDeposit = filecoinPayBalance - lockupUsed;
// Warn if available deposit is too low (less than 10% of lockup as safety margin)
const safetyMargin = lockupUsed / 10n; // 10% safety margin
if (availableDeposit < safetyMargin) {
const needed = lockupUsed + safetyMargin - filecoinPayBalance;
log.newline();
log.message(pc.yellow(`⚠ Warning: Low deposit balance`));
log.indent(pc.yellow(`Your deposit: ${formatUSDFC(filecoinPayBalance)} USDFC`));
log.indent(pc.yellow(`Amount locked: ${formatUSDFC(lockupUsed)} USDFC`));
log.indent(pc.yellow(`Available: ${formatUSDFC(availableDeposit > 0n ? availableDeposit : 0n)} USDFC`));
log.indent(pc.yellow(`Deposit at least ${formatUSDFC(needed)} more USDFC to maintain safety margin`));
log.indent(pc.gray(`Without sufficient deposit, storage may be terminated`));
}
}
}
/**
* Format storage capacity with smart unit selection
*
* @param gib - Capacity in GiB
* @returns Formatted string with appropriate unit
*/
function formatStorageCapacity(gib) {
if (gib >= 1024) {
const tib = gib / 1024;
// Use 1 decimal place if under 100 TiB
if (tib < 100) {
return `${tib.toFixed(1)} TiB/month`;
}
return `${Math.round(tib).toLocaleString()} TiB/month`;
}
// Use GiB between 1 GiB and 1 TiB
if (gib >= 0.9) {
if (gib < 100) {
return `${gib.toFixed(1)} GiB/month`;
}
return `${Math.round(gib).toLocaleString()} GiB/month`;
}
// For sub‑1 GiB, show MiB to avoid "0.0 GiB"
const mib = gib * 1024;
if (mib < 10) {
return `${mib.toFixed(1)} MiB/month`;
}
return `${Math.round(mib).toLocaleString()} MiB/month`;
}
/**
* Helper to calculate storage capacity in GiB from allowances
*/
function calculateStorageCapacity(rateAllowance, lockupAllowance, pricePerTiBPerEpoch) {
const tibFromRate = calculateActualCapacity(rateAllowance, pricePerTiBPerEpoch);
const defaultLockupEpochs = BigInt(DEFAULT_LOCKUP_DAYS) * TIME_CONSTANTS.EPOCHS_PER_DAY;
const maxRateFromLockup = lockupAllowance / defaultLockupEpochs;
const tibFromLockup = calculateActualCapacity(maxRateFromLockup, pricePerTiBPerEpoch);
const tibPerMonth = Math.min(tibFromRate, tibFromLockup * 3);
const gibPerMonth = tibPerMonth * 1024;
return gibPerMonth;
}
/**
* Calculate actual capacity with deposit limitations
*/
function calculateActualCapacityWithDeposit(filecoinPayBalance, rateAllowance, lockupAllowance, pricePerTiBPerEpoch) {
const potentialGiB = calculateStorageCapacity(rateAllowance, lockupAllowance, pricePerTiBPerEpoch);
const requiredLockup = lockupAllowance;
const monthlyPayment = rateAllowance * TIME_CONSTANTS.EPOCHS_PER_MONTH;
const requiredDeposit = requiredLockup + monthlyPayment;
let actualGiB;
let isDepositLimited = false;
let additionalDepositNeeded = 0n;
if (filecoinPayBalance >= requiredDeposit) {
actualGiB = potentialGiB;
}
else {
isDepositLimited = true;
additionalDepositNeeded = requiredDeposit - filecoinPayBalance;
const scale = getStorageScale(potentialGiB);
const scaleFactor = Number((filecoinPayBalance * BigInt(scale)) / requiredDeposit) / scale;
actualGiB = potentialGiB * scaleFactor;
}
return {
actualGiB,
potentialGiB,
isDepositLimited,
additionalDepositNeeded,
};
}
/**
* Display capacity information based on deposit and limits
*
* @param capacity - Calculated capacity information
*/
export function displayCapacity(capacity) {
if (capacity.isDepositLimited) {
log.indent(`→ Current capacity: ~${formatStorageCapacity(capacity.actualGiB)} ${pc.yellow('(deposit-limited)')}`);
log.indent(`→ Potential: ~${formatStorageCapacity(capacity.potentialGiB)} (deposit ${formatUSDFC(capacity.additionalDepositNeeded)} more)`);
}
else {
log.indent(`→ Estimated capacity: ~${formatStorageCapacity(capacity.actualGiB)}`);
log.indent(pc.gray(' (excludes data set creation fee and optional CDN add-on rates)'));
}
}
/**
* Display current pricing information
*
* @param pricePerGiBPerMonth - Price per GiB per month
* @param pricePerTiBPerMonth - Price per TiB per month
*/
export function displayPricing(pricePerGiBPerMonth, pricePerTiBPerMonth) {
log.line(pc.bold('Current Pricing:'));
log.indent(`1 GiB/month: ${formatUSDFC(pricePerGiBPerMonth)} USDFC`);
log.indent(`1 TiB/month: ${formatUSDFC(pricePerTiBPerMonth)} USDFC`);
log.indent(pc.gray('(for each upload, WarmStorage service will reserve 30 days of costs as security)'));
log.flush();
}
/**
* Display WarmStorage service permissions with capacity information
*
* @param title - Section title to display
* @param monthlyRate - Rate allowance in USDFC per month
* @param lockupAmount - Lockup allowance amount
* @param filecoinPayBalance - Total deposited amount
* @param pricePerTiBPerEpoch - Current pricing per TiB per epoch
*/
export function displayServicePermissions(title, monthlyRate, lockupAmount, filecoinPayBalance, pricePerTiBPerEpoch, shouldFlush = true) {
// Calculate capacity
const ratePerEpoch = monthlyRate / TIME_CONSTANTS.EPOCHS_PER_MONTH;
const capacity = calculateActualCapacityWithDeposit(filecoinPayBalance, ratePerEpoch, lockupAmount, pricePerTiBPerEpoch);
log.line(pc.bold(title));
log.indent(`Max payment: ${formatUSDFC(monthlyRate)} USDFC/month`);
log.indent(`Max reserve: ${formatUSDFC(lockupAmount)} USDFC (10-day lockup)`);
displayCapacity(capacity);
if (shouldFlush) {
log.flush();
}
}
//# sourceMappingURL=setup.js.map