UNPKG

filecoin-pin

Version:

Bridge IPFS content to Filecoin Onchain Cloud using familiar tools

167 lines (142 loc) 5.32 kB
/** * Deposit/top-up command for Filecoin Pay * * Provides two modes: * - Explicit amount: --amount <USDFC> * - By duration: --days <N> (fund enough to keep current usage alive for N days) */ import { ethers } from 'ethers' import pc from 'picocolors' import { TELEMETRY_CLI_APP_NAME } from '../common/constants.js' import { calculateStorageRunway, checkFILBalance, checkUSDFCBalance, computeTopUpForDuration, depositUSDFC, getPaymentStatus, } from '../core/payments/index.js' import { cleanupSynapseService, initializeSynapse } from '../core/synapse/index.js' import { formatUSDFC } from '../core/utils/format.js' import { formatRunwaySummary } from '../core/utils/index.js' import { type CLIAuthOptions, getCLILogger, parseCLIAuth } from '../utils/cli-auth.js' import { cancel, createSpinner, intro, outro } from '../utils/cli-helpers.js' import { log } from '../utils/cli-logger.js' export interface DepositOptions extends CLIAuthOptions { amount?: string | undefined days?: number | undefined } /** * Run the deposit/top-up flow */ export async function runDeposit(options: DepositOptions): Promise<void> { intro(pc.bold('Filecoin Onchain Cloud Deposit')) const spinner = createSpinner() // Validate inputs const hasAmount = options.amount != null const hasDays = options.days != null if ((hasAmount && hasDays) || (!hasAmount && !hasDays)) { console.error(pc.red('Error: Specify exactly one of --amount <USDFC> or --days <N>')) process.exit(1) } // Connect spinner.start('Connecting...') try { // Parse and validate authentication const authConfig = parseCLIAuth(options) const logger = getCLILogger() const synapse = await initializeSynapse( { ...authConfig, telemetry: { sentrySetTags: { appName: TELEMETRY_CLI_APP_NAME } } }, logger ) const [filStatus, walletUsdfcBalance, status] = await Promise.all([ checkFILBalance(synapse), checkUSDFCBalance(synapse), getPaymentStatus(synapse), ]) spinner.stop(`${pc.green('✓')} Connected`) // Validate balances if (!filStatus.hasSufficientGas) { log.line(`${pc.red('✗')} Insufficient FIL for gas fees`) const help = filStatus.isCalibnet ? 'Get test FIL from: https://faucet.calibnet.chainsafe-fil.io/' : 'Acquire FIL for gas from an exchange' log.line(` ${pc.cyan(help)}`) log.flush() cancel('Deposit aborted') throw new Error('Insufficient FIL for gas fees') } let depositAmount: bigint = 0n if (hasAmount) { try { depositAmount = ethers.parseUnits(String(options.amount), 18) } catch { throw new Error(`Invalid amount '${options.amount}'`) } if (depositAmount <= 0n) { throw new Error('Amount must be greater than 0') } } else if (hasDays) { const days = Number(options.days) if (!Number.isFinite(days) || days <= 0) { throw new Error('--days must be a positive number') } const { topUp, rateUsed, perDay } = computeTopUpForDuration(status, days) if (rateUsed === 0n) { spinner.stop() log.line(`${pc.yellow('⚠')} No active storage payments detected (rateUsed = 0)`) log.line('Use --amount to deposit a specific USDFC value instead.') log.flush() cancel('Nothing to fund by duration') throw new Error('No active spend detected') } depositAmount = topUp if (depositAmount === 0n) { spinner.stop() log.line(`${pc.green('✓')} Already funded for at least ${days} day(s) at current spend rate`) log.indent(`Current daily spend: ${formatUSDFC(perDay)} USDFC/day`) log.flush() outro('No deposit needed') return } } // Ensure wallet has enough USDFC if (depositAmount > walletUsdfcBalance) { console.error( pc.red( `✗ Insufficient USDFC (need ${formatUSDFC(depositAmount)} USDFC, have ${formatUSDFC(walletUsdfcBalance)} USDFC)` ) ) throw new Error('Insufficient USDFC') } spinner.start(`Depositing ${formatUSDFC(depositAmount)} USDFC...`) const { depositTx } = await depositUSDFC(synapse, depositAmount) spinner.stop(`${pc.green('✓')} Deposit complete`) log.line(pc.bold('Transaction details:')) log.indent(pc.gray(`Deposit: ${depositTx}`)) log.flush() // Brief post-deposit summary const updated = await getPaymentStatus(synapse) const runway = calculateStorageRunway(updated) const runwayDisplay = formatRunwaySummary(runway) log.line('') log.line(pc.bold('Deposit Summary')) log.indent(`Total deposit: ${formatUSDFC(updated.filecoinPayBalance)} USDFC`) if (runway.state === 'active') { const dailySpend = runway.perDay log.indent(`Current spend: ${formatUSDFC(dailySpend)} USDFC/day`) log.indent(`Runway: ~${runwayDisplay} at current spend`) } else { log.indent(pc.gray(runwayDisplay)) } log.flush() outro('Deposit completed') } catch (error) { spinner.stop() console.error(pc.red('✗ Deposit failed')) console.error(pc.red('Error:'), error instanceof Error ? error.message : error) process.exitCode = 1 } finally { await cleanupSynapseService() } }