Skip to content

Commit 13f557e

Browse files
authored
fix: refresh payments status CLI output (#223)
* fix: cleanup payments status output * fix: adjust payments status slightly * chore: fix lint * chore: normalize bigint comparisons * fix: normalize cost output * fix: be more clear about storage capacity
1 parent 00e7ff9 commit 13f557e

File tree

2 files changed

+103
-49
lines changed

2 files changed

+103
-49
lines changed

src/commands/payments.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ paymentsCommand.addCommand(withdrawCommand)
9191
// Status command
9292
const statusCommand = new Command('status')
9393
.description('Check current payment setup status')
94+
.option('--include-rails', 'Show payment rail details')
9495
.action(async (options) => {
9596
try {
9697
await showPaymentStatus({

src/payments/status.ts

Lines changed: 102 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,59 @@ import {
1818
getPaymentStatus,
1919
} from '../core/payments/index.js'
2020
import { cleanupSynapseService, initializeSynapse } from '../core/synapse/index.js'
21-
import { formatUSDFC } from '../core/utils/format.js'
21+
import { formatFIL, formatUSDFC } from '../core/utils/format.js'
2222
import { formatRunwaySummary } from '../core/utils/index.js'
2323
import { type CLIAuthOptions, getCLILogger, parseCLIAuth } from '../utils/cli-auth.js'
2424
import { cancel, createSpinner, intro, outro } from '../utils/cli-helpers.js'
2525
import { log } from '../utils/cli-logger.js'
2626
import { displayDepositWarning } from './setup.js'
2727

28-
interface StatusOptions extends CLIAuthOptions {}
28+
interface StatusOptions extends CLIAuthOptions {
29+
includeRails?: boolean
30+
}
31+
32+
const STORAGE_DISPLAY_PRECISION_DIGITS = 6
33+
const STORAGE_DISPLAY_PRECISION = 10n ** BigInt(STORAGE_DISPLAY_PRECISION_DIGITS)
34+
35+
/**
36+
* Derives the current warm storage size from the Filecoin Pay spend rate.
37+
*
38+
* rateUsed represents the USDFC burn per epoch while pricePerTiBPerEpoch
39+
* represents the quoted USDFC price for storing 1 TiB for the same duration.
40+
* Dividing the two gives the actively billed TiB, which we convert to GiB for
41+
* display with a small fixed precision.
42+
*/
43+
function formatStorageGiB(rateUsed: bigint, pricePerTiBPerEpoch: bigint): string {
44+
if (rateUsed === 0n || pricePerTiBPerEpoch === 0n) {
45+
return pc.gray('Stored: no active usage')
46+
}
47+
48+
if (pricePerTiBPerEpoch < 0n) {
49+
// pricePerTiBPerEpoch should always be positive
50+
return pc.gray('Stored: unknown')
51+
}
52+
53+
const storedTiBScaled = (rateUsed * STORAGE_DISPLAY_PRECISION) / pricePerTiBPerEpoch
54+
if (storedTiBScaled <= 0n) {
55+
return pc.gray('Stored: no active usage')
56+
}
57+
58+
const storedGiB = Number(ethers.formatUnits(storedTiBScaled * 1024n, STORAGE_DISPLAY_PRECISION_DIGITS))
59+
60+
if (storedGiB < 0.1) {
61+
return 'Stored: < 0.1 GiB'
62+
}
63+
64+
let digits = 1
65+
if (storedGiB < 10) {
66+
digits = 2
67+
}
68+
const formatted = storedGiB.toLocaleString(undefined, {
69+
maximumFractionDigits: digits,
70+
minimumFractionDigits: digits,
71+
})
72+
return `Stored: ~${formatted} GiB`
73+
}
2974

3075
/**
3176
* Display current payment status
@@ -104,52 +149,72 @@ export async function showPaymentStatus(options: StatusOptions): Promise<void> {
104149
const storageInfo = await synapse.storage.getStorageInfo()
105150
const pricePerTiBPerEpoch = storageInfo.pricing.noCDN.perTiBPerEpoch
106151

107-
const paymentRailsData = await fetchPaymentRailsData(synapse)
152+
let paymentRailsData: PaymentRailsData | null = null
153+
if (options.includeRails === true) {
154+
paymentRailsData = await fetchPaymentRailsData(synapse)
155+
}
108156
spinner.stop(`${pc.green('✓')} Configuration loaded`)
109157

110158
// Display all status information
111159
log.line('━━━ Current Status ━━━')
112160

113-
log.line(`Address: ${address}`)
114-
log.line(`Network: ${pc.bold(network)}`)
115-
log.line('')
116-
117161
// Show wallet balances
118162
log.line(pc.bold('Wallet'))
119-
const filUnit = filStatus.isCalibnet ? 'tFIL' : 'FIL'
120-
log.indent(`${ethers.formatEther(filStatus.balance)} ${filUnit}`)
121-
log.indent(`${formatUSDFC(walletUsdfcBalance)} USDFC`)
163+
log.indent(`Owner address: ${address}`)
164+
log.indent(`Network: ${network}`)
165+
log.indent(`FIL: ${formatFIL(filStatus.balance, filStatus.isCalibnet)}`)
166+
log.indent(`USDFC: ${formatUSDFC(walletUsdfcBalance)} USDFC`)
122167
log.line('')
123168

124169
// Show deposit and capacity
170+
const lockupUsed = status.currentAllowances.lockupUsed ?? 0n
171+
const rateUsed = status.currentAllowances.rateUsed ?? 0n
172+
const availableDeposit = status.filecoinPayBalance > lockupUsed ? status.filecoinPayBalance - lockupUsed : 0n
125173
const capacity = calculateDepositCapacity(status.filecoinPayBalance, pricePerTiBPerEpoch)
126-
log.line(pc.bold('Storage Deposit'))
127-
log.indent(`${formatUSDFC(status.filecoinPayBalance)} USDFC deposited`)
128-
if (capacity.gibPerMonth > 0) {
129-
const asTiB = capacity.tibPerMonth
130-
const tibStr = asTiB >= 100 ? Math.round(asTiB).toLocaleString() : asTiB.toFixed(1)
131-
log.indent(`Capacity: ~${tibStr} TiB/month ${pc.gray('(includes 10-day safety reserve)')}`)
132-
} else if (status.filecoinPayBalance > 0n) {
133-
log.indent(pc.gray('(insufficient for storage)'))
134-
}
135-
log.flush()
136-
137-
// Show payment rails summary
138-
displayPaymentRailsSummary(paymentRailsData, log)
139-
140-
// Show spend summaries (rateUsed, runway)
141174
const runway = calculateStorageRunway(status)
142175
const runwayDisplay = formatRunwaySummary(runway)
143-
const maxLockup = status.currentAllowances.maxLockupPeriod
144-
const lockupDays = maxLockup != null ? Number(maxLockup / TIME_CONSTANTS.EPOCHS_PER_DAY) : 10
176+
const dailyCost = runway.perDay
177+
const monthlyCost = dailyCost * TIME_CONSTANTS.DAYS_PER_MONTH
178+
179+
log.line(pc.bold('Filecoin Pay'))
180+
log.indent(`Balance: ${formatUSDFC(status.filecoinPayBalance)} USDFC`)
181+
log.indent(`Locked: ${formatUSDFC(lockupUsed)} USDFC (30-day reserve)`)
182+
log.indent(`Available: ${formatUSDFC(availableDeposit)} USDFC`)
183+
if (rateUsed > 0n) {
184+
log.indent(`Epoch cost: ${formatUSDFC(rateUsed)} USDFC`)
185+
log.indent(`Daily cost: ${formatUSDFC(dailyCost)} USDFC`)
186+
log.indent(`Monthly cost: ${formatUSDFC(monthlyCost)} USDFC`)
187+
} else {
188+
log.indent(`Epoch cost: ${pc.gray('0 USDFC')}`)
189+
log.indent(`Daily cost: ${pc.gray('0 USDFC')}`)
190+
log.indent(`Monthly cost: ${pc.gray('0 USDFC')}`)
191+
}
192+
if (paymentRailsData != null) {
193+
displayPaymentRailsSummary(paymentRailsData, 1)
194+
}
195+
log.line('')
145196

197+
// Show storage usage details
146198
log.line(pc.bold('WarmStorage Usage'))
199+
if (rateUsed > 0n) {
200+
log.indent(formatStorageGiB(rateUsed, pricePerTiBPerEpoch))
201+
} else if (status.filecoinPayBalance > 0n) {
202+
log.indent(pc.gray('Stored: no active usage'))
203+
} else {
204+
log.indent(pc.gray('Stored: none'))
205+
}
147206
if (runway.state === 'active') {
148-
log.indent(`Spend rate: ${formatUSDFC(runway.rateUsed)} USDFC/epoch`)
149-
log.indent(`Locked: ${formatUSDFC(runway.lockupUsed)} USDFC (~${lockupDays}-day reserve)`)
150207
log.indent(`Runway: ~${runwayDisplay}`)
151208
} else {
152-
log.indent(pc.gray(runwayDisplay))
209+
log.indent(pc.gray(`Runway: ${runwayDisplay}`))
210+
}
211+
const capacityTiB =
212+
capacity.tibPerMonth >= 100 ? Math.round(capacity.tibPerMonth).toLocaleString() : capacity.tibPerMonth.toFixed(1)
213+
const capacityLine = `Funding could cover ~${capacityTiB} TiB for one month`
214+
if (capacity.gibPerMonth > 0) {
215+
log.indent(capacityLine)
216+
} else {
217+
log.indent(pc.gray(capacityLine))
153218
}
154219
log.flush()
155220

@@ -251,38 +316,26 @@ async function fetchPaymentRailsData(synapse: Synapse): Promise<PaymentRailsData
251316
/**
252317
* Display payment rails summary
253318
*/
254-
function displayPaymentRailsSummary(data: PaymentRailsData, log: any): void {
255-
log.line(pc.bold('Payment Rails'))
319+
function displayPaymentRailsSummary(data: PaymentRailsData, indentLevel: number = 1): void {
320+
log.indent(pc.bold('Payment Rails'), indentLevel)
256321

257322
if (data.error) {
258-
log.indent(pc.gray(data.error))
259-
log.flush()
323+
log.indent(pc.gray(data.error), indentLevel + 1)
260324
return
261325
}
262326

263327
if (data.activeRails === 0 && data.terminatedRails === 0) {
264-
log.indent(pc.gray('No active payment rails'))
265-
log.flush()
328+
log.indent(pc.gray('No active payment rails'), indentLevel + 1)
266329
return
267330
}
268331

269-
log.indent(`${data.activeRails} active, ${data.terminatedRails} terminated`)
270-
271-
if (data.activeRails > 0) {
272-
const dailyCost = data.totalActiveRate * 2880n // 2880 epochs per day
273-
const monthlyCost = dailyCost * 30n
274-
275-
log.indent(`Daily cost: ${formatUSDFC(dailyCost)} USDFC`)
276-
log.indent(`Monthly cost: ${formatUSDFC(monthlyCost)} USDFC`)
277-
}
332+
log.indent(`${data.activeRails} active, ${data.terminatedRails} terminated`, indentLevel + 1)
278333

279334
if (data.totalPendingSettlements > 0n) {
280-
log.indent(`Pending settlement: ${formatUSDFC(data.totalPendingSettlements)} USDFC`)
335+
log.indent(`Pending settlement: ${formatUSDFC(data.totalPendingSettlements)} USDFC`, indentLevel + 1)
281336
}
282337

283338
if (data.railsNeedingSettlement > 0) {
284-
log.indent(`${data.railsNeedingSettlement} rail(s) need settlement`)
339+
log.indent(`${data.railsNeedingSettlement} rail(s) need settlement`, indentLevel + 1)
285340
}
286-
287-
log.flush()
288341
}

0 commit comments

Comments
 (0)