filecoin-pin
Version:
Bridge IPFS content to Filecoin Onchain Cloud using familiar tools
182 lines (154 loc) • 6.36 kB
text/typescript
/**
* Automatic payment setup flow
*
* This module provides an automated, non-interactive setup experience for
* configuring payment approvals. It uses default values and command-line
* options to complete the setup without user interaction.
*/
import { ethers } from 'ethers'
import pc from 'picocolors'
import { TELEMETRY_CLI_APP_NAME } from '../common/constants.js'
import {
calculateDepositCapacity,
checkAndSetAllowances,
checkFILBalance,
checkUSDFCBalance,
depositUSDFC,
getPaymentStatus,
validatePaymentRequirements,
} from '../core/payments/index.js'
import { cleanupSynapseService, initializeSynapse } from '../core/synapse/index.js'
import { formatUSDFC } from '../core/utils/format.js'
import { getCLILogger, parseCLIAuth } from '../utils/cli-auth.js'
import { cancel, createSpinner, intro, outro } from '../utils/cli-helpers.js'
import { log } from '../utils/cli-logger.js'
import { displayAccountInfo, displayDepositWarning } from './setup.js'
import type { PaymentSetupOptions } from './types.js'
/**
* Run automatic payment setup with defaults
*
* @param options - Options from command line
*/
export async function runAutoSetup(options: PaymentSetupOptions): Promise<void> {
intro(pc.bold('Filecoin Onchain Cloud Payment Setup'))
log.message(pc.gray('Running in auto mode...'))
// Parse and validate deposit amount
let targetFilecoinPayBalance: bigint
try {
targetFilecoinPayBalance = ethers.parseUnits(options.deposit, 18)
} catch {
console.error(pc.red(`Error: Invalid deposit amount '${options.deposit}'`))
process.exit(1)
}
const spinner = createSpinner()
spinner.start('Initializing connection...')
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 network = synapse.getNetwork()
const client = synapse.getClient()
const address = await client.getAddress()
spinner.stop(`${pc.green('✓')} Connected to ${pc.bold(network)}`)
// Check balances
spinner.start('Checking balances...')
const filStatus = await checkFILBalance(synapse)
const walletUsdfcBalance = await checkUSDFCBalance(synapse)
spinner.stop(`${pc.green('✓')} Balance check complete`)
// Validate payment requirements
const validation = validatePaymentRequirements(filStatus.hasSufficientGas, walletUsdfcBalance, filStatus.isCalibnet)
if (!validation.isValid) {
log.line(`${pc.red('✗')} ${validation.errorMessage}`)
if (validation.helpMessage) {
log.line('')
log.line(` ${pc.cyan(validation.helpMessage)}`)
}
log.flush()
cancel('Please fund your wallet and try again')
process.exit(1)
}
// Now safe to get payment status since we know account exists
const status = await getPaymentStatus(synapse)
// Display account and balance info using shared function
displayAccountInfo(
address,
network,
filStatus.balance,
filStatus.isCalibnet,
filStatus.hasSufficientGas,
walletUsdfcBalance,
status.filecoinPayBalance
)
// Get storage pricing for capacity calculation
const storageInfo = await synapse.storage.getStorageInfo()
const pricePerTiBPerEpoch = storageInfo.pricing.noCDN.perTiBPerEpoch
// Track if any changes were made
let actionsTaken = false
let actualFilecoinPayTopUp = 0n
if (status.filecoinPayBalance < targetFilecoinPayBalance) {
const neededFilecoinPayTopUp = targetFilecoinPayBalance - status.filecoinPayBalance
actualFilecoinPayTopUp = neededFilecoinPayTopUp
if (neededFilecoinPayTopUp > walletUsdfcBalance) {
console.error(
pc.red(
`✗ Insufficient USDFC for deposit (need ${formatUSDFC(neededFilecoinPayTopUp)} USDFC, have ${formatUSDFC(walletUsdfcBalance)} USDFC)`
)
)
process.exit(1)
}
spinner.start(`Depositing ${formatUSDFC(neededFilecoinPayTopUp)} USDFC...`)
const { depositTx } = await depositUSDFC(synapse, neededFilecoinPayTopUp)
spinner.stop(`${pc.green('✓')} Deposited ${formatUSDFC(neededFilecoinPayTopUp)} USDFC`)
actionsTaken = true
log.line(pc.bold('Transaction details:'))
log.indent(pc.gray(`Deposit: ${depositTx}`))
log.flush()
} else {
// Use a dummy spinner to get consistent formatting
spinner.start('Checking deposit...')
const { updated, transactionHash } = await checkAndSetAllowances(synapse)
if (updated) {
spinner.stop(`${pc.green('✓')} Updated payment allowances, tx: ${transactionHash}`)
} else {
spinner.stop(`${pc.green('✓')} Deposit already sufficient (${formatUSDFC(status.filecoinPayBalance)} USDFC)`)
}
}
// Calculate capacity for final summary
const totalDeposit = status.filecoinPayBalance + actualFilecoinPayTopUp
const capacity = calculateDepositCapacity(totalDeposit, pricePerTiBPerEpoch)
// Final summary
spinner.start('Completing setup...')
spinner.stop('━━━ Configuration Summary ━━━')
log.line(`Network: ${pc.bold(network)}`)
log.line(`Deposit: ${formatUSDFC(totalDeposit)} USDFC`)
if (capacity.gibPerMonth > 0) {
const capacityStr =
capacity.gibPerMonth >= 1024
? `${(capacity.gibPerMonth / 1024).toFixed(1)} TiB`
: `${capacity.gibPerMonth.toFixed(1)} GiB`
log.line(`Storage: ~${capacityStr} for 1 month`)
}
log.line(`Status: ${pc.green('Ready to upload')}`)
log.flush()
// Show deposit warning if needed
displayDepositWarning(totalDeposit, status.currentAllowances.lockupUsed)
// Show appropriate outro message based on whether actions were taken
if (actionsTaken) {
outro('Payment setup completed successfully')
} else {
outro('Payment setup already configured - ready to use')
}
} catch (error) {
spinner.stop() // Stop spinner without message
console.error(pc.red('✗ Setup failed'))
console.error(pc.red('Error:'), error instanceof Error ? error.message : error)
process.exitCode = 1
} finally {
await cleanupSynapseService()
process.exit()
}
}