Skip to content
Merged
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,11 @@
"homepage": "https://github.com/filecoin-project/filecoin-pin#readme",
"dependencies": {
"@clack/prompts": "^0.11.0",
"@filoz/synapse-sdk": "^0.34.0",
"@filoz/synapse-sdk": "^0.35.0",
"@helia/unixfs": "^6.0.1",
"@ipld/car": "^5.4.2",
"commander": "^14.0.1",
"ethers": "^6.15.0",
"fastify": "^5.6.0",
"helia": "^6.0.1",
"it-to-buffer": "^4.0.10",
Expand Down
3 changes: 0 additions & 3 deletions src/add/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,6 @@ export async function runAdd(options: AddOptions): Promise<AddResult> {
onProviderSelected: (provider) => {
spinner.message(`Connecting to storage provider: ${provider.name || provider.serviceProvider}...`)
},
onDataSetCreationStarted: (transaction) => {
spinner.message(`Creating data set (tx: ${transaction.hash.slice(0, 10)}...)`)
},
onDataSetResolved: (info) => {
if (info.isExisting) {
spinner.message(`Using existing data set #${info.dataSetId}`)
Expand Down
2 changes: 1 addition & 1 deletion src/add/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { CLIAuthOptions } from '../utils/cli-auth.js'
export interface AddOptions extends CLIAuthOptions {
filePath: string
bare?: boolean
/** Auto-fund: automatically ensure minimum 10 days of runway */
/** Auto-fund: automatically ensure minimum 30 days of runway */
autoFund?: boolean
}

Expand Down
2 changes: 1 addition & 1 deletion src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
* Minimum runway in days to ensure WarmStorage can cover costs. Used when `--auto-fund` is passed to import or add commands
*/
export const MIN_RUNWAY_DAYS = 10
export const MIN_RUNWAY_DAYS = 30
6 changes: 3 additions & 3 deletions src/common/upload-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { Synapse } from '@filoz/synapse-sdk'
import type { CID } from 'multiformats/cid'
import pc from 'picocolors'
import type { Logger } from 'pino'
import type { PaymentCapacityCheck } from '../core/payments/index.js'
import { DEFAULT_LOCKUP_DAYS, type PaymentCapacityCheck } from '../core/payments/index.js'
import { cleanupSynapseService, type SynapseService } from '../core/synapse/index.js'
import { checkUploadReadiness, executeUpload, getDownloadURL, type SynapseUploadResult } from '../core/upload/index.js'
import { formatUSDFC } from '../core/utils/format.js'
Expand Down Expand Up @@ -48,7 +48,7 @@ export interface UploadFlowResult extends SynapseUploadResult {

/**
* Perform auto-funding if requested
* Automatically ensures a minimum of 10 days of runway based on current usage + new file requirements
* Automatically ensures a minimum of 30 days of runway based on current usage + new file requirements
*
* @param synapse - Initialized Synapse instance
* @param fileSize - Size of file being uploaded (in bytes)
Expand Down Expand Up @@ -207,7 +207,7 @@ function displayPaymentIssues(capacityCheck: PaymentCapacityCheck, fileSize: num
log.indent(
`Required deposit: ${formatUSDFC(capacityCheck.required.lockupAllowance + capacityCheck.required.lockupAllowance / 10n)} USDFC`
)
log.indent(pc.gray('(includes 10-day safety reserve)'))
log.indent(pc.gray(`(includes ${DEFAULT_LOCKUP_DAYS}-day safety reserve)`))
log.line('')

log.line(pc.bold('Suggested actions:'))
Expand Down
36 changes: 19 additions & 17 deletions src/core/payments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { isSessionKeyMode } from '../synapse/index.js'
// Constants
export const USDFC_DECIMALS = 18
const MIN_FIL_FOR_GAS = ethers.parseEther('0.1') // Minimum FIL padding for gas
export const DEFAULT_LOCKUP_DAYS = 10 // WarmStorage requires 10 days lockup
export const DEFAULT_LOCKUP_DAYS = 30 // WarmStorage requires 30 days lockup

// Maximum allowances for trusted WarmStorage service
// Using MaxUint256 which MetaMask displays as "Unlimited"
Expand Down Expand Up @@ -381,7 +381,7 @@ export async function setServiceApprovals(
): Promise<string> {
const warmStorageAddress = synapse.getWarmStorageAddress()

// Max lockup period is always 10 days worth of epochs for WarmStorage
// Max lockup period is always 30 days worth of epochs for WarmStorage
const maxLockupPeriod = BigInt(DEFAULT_LOCKUP_DAYS) * TIME_CONSTANTS.EPOCHS_PER_DAY

// Set the service approval
Expand Down Expand Up @@ -415,9 +415,11 @@ export async function checkAllowances(synapse: Synapse): Promise<{
// Get current allowances
const currentAllowances = await synapse.payments.serviceApproval(warmStorageAddress, TOKENS.USDFC)

// Check if we need to update (not at max)
// Check if we need to update (not at max or max lockup period is not enough)
const needsUpdate =
currentAllowances.rateAllowance < MAX_RATE_ALLOWANCE || currentAllowances.lockupAllowance < MAX_LOCKUP_ALLOWANCE
currentAllowances.rateAllowance < MAX_RATE_ALLOWANCE ||
currentAllowances.lockupAllowance < MAX_LOCKUP_ALLOWANCE ||
currentAllowances.maxLockupPeriod < BigInt(DEFAULT_LOCKUP_DAYS) * TIME_CONSTANTS.EPOCHS_PER_DAY

return {
needsUpdate,
Expand Down Expand Up @@ -537,9 +539,9 @@ export function calculateStorageAllowances(storageTiB: number, pricePerTiBPerEpo
// Calculate rate allowance (per epoch payment)
const rateAllowance = (pricePerTiBPerEpoch * BigInt(scaledStorage)) / BigInt(scale)

// Calculate lockup allowance (10 days worth)
const epochsIn10Days = BigInt(DEFAULT_LOCKUP_DAYS) * TIME_CONSTANTS.EPOCHS_PER_DAY
const lockupAllowance = rateAllowance * epochsIn10Days
// Calculate lockup allowance
const epochsInLockupDays = BigInt(DEFAULT_LOCKUP_DAYS) * TIME_CONSTANTS.EPOCHS_PER_DAY
const lockupAllowance = rateAllowance * epochsInLockupDays

return {
rateAllowance,
Expand Down Expand Up @@ -580,7 +582,7 @@ export function calculateActualCapacity(rateAllowance: bigint, pricePerTiBPerEpo
* Calculate storage capacity from USDFC amount
*
* Determines how much storage can be purchased with a given USDFC amount,
* accounting for the 10-day lockup period.
* accounting for the 30-day lockup period.
*
* @param usdfcAmount - Amount of USDFC in its smallest unit
* @param pricePerTiBPerEpoch - Current pricing from storage service
Expand All @@ -589,17 +591,17 @@ export function calculateActualCapacity(rateAllowance: bigint, pricePerTiBPerEpo
export function calculateStorageFromUSDFC(usdfcAmount: bigint, pricePerTiBPerEpoch: bigint): number {
if (pricePerTiBPerEpoch === 0n) return 0

// Calculate how much this covers for 10 days
const epochsIn10Days = BigInt(DEFAULT_LOCKUP_DAYS) * TIME_CONSTANTS.EPOCHS_PER_DAY
const ratePerEpoch = usdfcAmount / epochsIn10Days
// Calculate how much this covers for lockup
const epochsInLockupDays = BigInt(DEFAULT_LOCKUP_DAYS) * TIME_CONSTANTS.EPOCHS_PER_DAY
const ratePerEpoch = usdfcAmount / epochsInLockupDays

return calculateActualCapacity(ratePerEpoch, pricePerTiBPerEpoch)
}

/**
* Compute the additional deposit required to fund current usage for a duration.
*
* The WarmStorage service maintains ~10 days of lockup (lockupUsed) and draws future
* The WarmStorage service maintains ~30 days of lockup (lockupUsed) and draws future
* lockups from the available deposit (deposited - lockupUsed). To keep the current
* rails alive for N days, ensure available >= N days of spend at the current rateUsed.
*
Expand Down Expand Up @@ -808,7 +810,7 @@ export function computeAdjustmentForExactDaysWithPiece(
* treating WarmStorage as fully trusted with max allowances, i.e. not
* accounting for allowance limits. If usage limits need to be accounted for
* then the capacity can be capped by either deposit or allowances.
* This function accounts for the 10-day lockup requirement.
* This function accounts for the 30-day lockup requirement.
*
* @param depositAmount - Amount deposited in USDFC
* @param pricePerTiBPerEpoch - Current pricing from storage service
Expand Down Expand Up @@ -837,22 +839,22 @@ export function calculateDepositCapacity(
}

// With infinite allowances, deposit is the only limiting factor
// Deposit needs to cover: lockup (10 days) + at least some buffer
const epochsIn10Days = BigInt(DEFAULT_LOCKUP_DAYS) * TIME_CONSTANTS.EPOCHS_PER_DAY
// Deposit needs to cover: lockup (30 days) + at least some buffer
const epochsInLockupDays = BigInt(DEFAULT_LOCKUP_DAYS) * TIME_CONSTANTS.EPOCHS_PER_DAY
const epochsPerMonth = TIME_CONSTANTS.EPOCHS_PER_MONTH

// Maximum storage we can support with this deposit
// Reserve 10% for buffer beyond the lockup
// Calculate max rate per epoch we can afford with deposit
const maxRatePerEpoch = (depositAmount * BUFFER_DENOMINATOR) / (epochsIn10Days * BUFFER_NUMERATOR)
const maxRatePerEpoch = (depositAmount * BUFFER_DENOMINATOR) / (epochsInLockupDays * BUFFER_NUMERATOR)

// Convert to storage capacity
const tibPerMonth = calculateActualCapacity(maxRatePerEpoch, pricePerTiBPerEpoch)
const gibPerMonth = tibPerMonth * 1024

// Calculate the actual costs for this capacity
const monthlyPayment = maxRatePerEpoch * epochsPerMonth
const requiredLockup = maxRatePerEpoch * epochsIn10Days
const requiredLockup = maxRatePerEpoch * epochsInLockupDays
const totalRequired = withBuffer(requiredLockup)

return {
Expand Down
34 changes: 3 additions & 31 deletions src/core/synapse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
type ProviderInfo,
RPC_URLS,
type StorageContext,
type StorageCreationCallbacks,
type StorageContextCallbacks,
type StorageServiceOptions,
Synapse,
type SynapseOptions,
Expand Down Expand Up @@ -144,11 +144,6 @@ export interface DatasetOptions {
metadata?: Record<string, string>
}

/**
* Progress callbacks for tracking dataset and provider selection.
*/
export type StorageProgressCallbacks = Omit<StorageCreationCallbacks, 'onDataSetCreationProgress'>

/**
* Options for creating a storage context.
*/
Expand All @@ -161,7 +156,7 @@ export interface CreateStorageContextOptions {
/**
* Progress callbacks for tracking creation.
*/
callbacks?: StorageProgressCallbacks
callbacks?: StorageContextCallbacks

/**
* Override provider selection by address.
Expand Down Expand Up @@ -440,7 +435,7 @@ export async function createStorageContext(
* Callbacks provide visibility into the storage lifecycle
* These are crucial for debugging and monitoring in production
*/
const callbacks: StorageCreationCallbacks = {
const callbacks: StorageContextCallbacks = {
onProviderSelected: (provider) => {
currentProviderInfo = provider

Expand Down Expand Up @@ -471,29 +466,6 @@ export async function createStorageContext(

options?.callbacks?.onDataSetResolved?.(info)
},
onDataSetCreationStarted: (transaction, statusUrl) => {
logger.info(
{
event: 'synapse.storage.data_set_creation_started',
txHash: transaction.hash,
statusUrl,
},
'Data set creation transaction submitted'
)

options?.callbacks?.onDataSetCreationStarted?.(transaction)
},
onDataSetCreationProgress: (status) => {
logger.info(
{
event: 'synapse.storage.data_set_creation_progress',
transactionMined: status.transactionMined,
dataSetLive: status.dataSetLive,
elapsedMs: status.elapsedMs,
},
'Data set creation progress'
)
},
}

sdkOptions.callbacks = callbacks
Expand Down
8 changes: 4 additions & 4 deletions src/core/upload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,11 @@ export async function executeUpload(
onUploadComplete: (pieceCid) => {
callbacks?.onUploadComplete?.(pieceCid)
},
onPieceAdded: (transaction) => {
if (transaction?.hash) {
transactionHash = transaction.hash
onPieceAdded: (txHash) => {
if (txHash) {
transactionHash = txHash
}
callbacks?.onPieceAdded?.(transaction)
callbacks?.onPieceAdded?.(txHash)
},
onPieceConfirmed: (pieceIds) => {
callbacks?.onPieceConfirmed?.(pieceIds)
Expand Down
8 changes: 4 additions & 4 deletions src/core/upload/synapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ export async function uploadToSynapse(
callbacks?.onUploadComplete?.(pieceCid)
},

onPieceAdded: (transaction) => {
if (transaction != null) {
onPieceAdded: (txHash) => {
if (txHash != null) {
logger.info(
{
event: 'synapse.upload.piece_added',
contextId,
txHash: transaction.hash,
txHash: txHash,
},
'Piece addition transaction submitted'
)
Expand All @@ -106,7 +106,7 @@ export async function uploadToSynapse(
'Piece added to data set'
)
}
callbacks?.onPieceAdded?.(transaction)
callbacks?.onPieceAdded?.(txHash)
},

onPieceConfirmed: (pieceIds) => {
Expand Down
17 changes: 7 additions & 10 deletions src/data-set/inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,22 @@ function formatPaymentToken(tokenAddress: string): string {
* Format storage price in USDFC per TiB per month
* Always shows TiB/month for consistency, with appropriate precision
*/
function formatStoragePrice(pricePerTiBPerMonth: bigint): string {
function formatStoragePrice(pricePerTiBPerDay: bigint): string {
try {
const priceInUSDFC = parseFloat(ethers.formatUnits(pricePerTiBPerMonth, 18))
const priceInUSDFC = parseFloat(ethers.formatUnits(pricePerTiBPerDay, 18))

// Handle very small prices that would show as 0.0000
if (priceInUSDFC < 0.0001) {
return '< 0.0001 USDFC/TiB/month'
return '< 0.0001 USDFC/TiB/day'
}

// For prices >= 0.0001, show with appropriate precision
if (priceInUSDFC >= 1) {
return `${priceInUSDFC.toFixed(2)} USDFC/TiB/month`
return `${priceInUSDFC.toFixed(2)} USDFC/TiB/day`
} else if (priceInUSDFC >= 0.01) {
return `${priceInUSDFC.toFixed(4)} USDFC/TiB/month`
return `${priceInUSDFC.toFixed(4)} USDFC/TiB/day`
} else {
return `${priceInUSDFC.toFixed(6)} USDFC/TiB/month`
return `${priceInUSDFC.toFixed(6)} USDFC/TiB/day`
}
} catch {
return pc.red('invalid price')
Expand Down Expand Up @@ -235,7 +235,7 @@ export function displayDataSetStatus(ctx: DataSetInspectionContext, dataSetId: n
log.indent(`Service URL: ${pdpData.serviceURL}`)
log.indent(`Min piece size: ${formatBytes(BigInt(pdpData.minPieceSizeInBytes))}`)
log.indent(`Max piece size: ${formatBytes(BigInt(pdpData.maxPieceSizeInBytes))}`)
log.indent(`Storage price: ${formatStoragePrice(pdpData.storagePricePerTibPerMonth)}`)
log.indent(`Storage price: ${formatStoragePrice(pdpData.storagePricePerTibPerDay)}`)
log.indent(`Min proving period: ${pdpData.minProvingPeriodInEpochs} epochs`)
log.indent(`Location: ${pdpData.location}`)
log.indent(`Payment token: ${formatPaymentToken(pdpData.paymentTokenAddress)}`)
Expand All @@ -244,9 +244,6 @@ export function displayDataSetStatus(ctx: DataSetInspectionContext, dataSetId: n
if (base.pdpEndEpoch > 0) {
log.indent(pc.yellow(`PDP payments ended @ epoch ${base.pdpEndEpoch}`))
}
if (base.cdnEndEpoch > 0) {
log.indent(pc.yellow(`CDN payments ended @ epoch ${base.cdnEndEpoch}`))
}

log.line('')
log.line(pc.bold('Metadata'))
Expand Down
3 changes: 0 additions & 3 deletions src/import/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,6 @@ export async function runCarImport(options: ImportOptions): Promise<ImportResult
onProviderSelected: (provider) => {
spinner.message(`Connecting to storage provider: ${provider.name || provider.serviceProvider}...`)
},
onDataSetCreationStarted: (transaction) => {
spinner.message(`Creating data set (tx: ${transaction.hash.slice(0, 10)}...)`)
},
onDataSetResolved: (info) => {
if (info.isExisting) {
spinner.message(`Using existing data set #${info.dataSetId}`)
Expand Down
2 changes: 1 addition & 1 deletion src/import/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { CLIAuthOptions } from '../utils/cli-auth.js'

export interface ImportOptions extends CLIAuthOptions {
filePath: string
/** Auto-fund: automatically ensure minimum 10 days of runway */
/** Auto-fund: automatically ensure minimum 30 days of runway */
autoFund?: boolean
}

Expand Down
Loading