# Storage Costs

Understanding storage costs helps you budget effectively and avoid service interruptions. This guide covers the SDK APIs for calculating costs, funding your account, and querying account state.

:::tip[How It Works]
For a conceptual overview of how payments work: deposits, payment rails, lockup, and what happens when funds run low, see the [Payments & Storage cookbook](/cookbooks/payments-and-storage/).
:::

Storage operates on a **pay-per-epoch** model where you deposit USDFC tokens and set allowances that control how much the storage service can spend.

:::tip[What is an Epoch?]
An **epoch** is Filecoin's block time, which is 30 seconds. Storage costs are calculated per epoch, ensuring you only pay for the storage time you actually use.
:::

## Pricing Components

Pricing has two recurring rates and a set of small one-time fees.

### Recurring rates

| Component | Cost | Notes |
| -------------- | ------------------ | -------------------------------------------------------- |
| **Storage** | $2.50/TiB/month per copy | Charged only while pieces exist. Empty data sets are free. |
| **Proving Service** | $0.024/data set/month | Flat per-data-set fee, added on top of the storage rate. |
| **CDN egress** | up to $0.014/GiB downloaded | Optional, via Filecoin Beam. About half this when served from cache. |

:::note[Be aware]
"Month" means **30 days** here (86,400 epochs), not a calendar month.
:::

The monthly rate for an active data set is `(bytes / TiB) × $2.50 + $0.024`. There is no minimum floor.

### One-time fees

Each on-chain operation pays a small fee to the storage provider, covering the gas that provider spends on your behalf:

| Operation | Fee |
| -------------- | ------------------ |
| **Create data set** | $0.025 |
| **Add pieces** | $0.0005 + $0.0003 per piece |
| **Schedule piece removals** | $0.002 per call |
| **Terminate service** | $0.00112 (user-initiated only) |

These are fractions of a cent. Think of them like gas: negligible for normal use, only adding up at very high operation volumes. They are paid from a small lockup reserve (~$0.10) held while the data set is active, not billed separately on every call. See [How the fees and lockup work](#how-the-fees-and-lockup-work) below.

:::note[CDN setup]
Enabling CDN adds two refundable lockups on first use: ~0.7 USDFC for egress credits and ~0.3 USDFC for cache-miss credits. Reusing an existing CDN data set incurs no new setup. These are lockups, not fees, and are returned when the rail is finalized.
:::

### Real-World Cost Examples

These examples use plain arithmetic for clarity. For programmatic cost calculation, use `getUploadCosts()` — see [Querying Upload Costs](#querying-upload-costs) below.

#### Example 1: NFT Collection (10,000 × 5 KiB ≈ 48.82 MiB)

```ts twoslash
// @lib: esnext,dom
// 48.82 MiB is tiny, so the storage rate is negligible.
// The recurring cost is dominated by the flat proving fee.
const STORAGE_PRICE_PER_TIB_PER_MONTH = 2.5;
const PROVING_FEE_PER_MONTH = 0.024;

const storageTiB = 48.82 / 1024 / 1024; // ≈ 0.0000466 TiB
const storageCostPerMonth = storageTiB * STORAGE_PRICE_PER_TIB_PER_MONTH; // ≈ $0.00012

// Per copy, per month
const costPerMonth = storageCostPerMonth + PROVING_FEE_PER_MONTH; // ≈ 0.0241 USDFC
const costFor24Months = costPerMonth * 24; // ≈ 0.578 USDFC
```

| Duration | Per copy |
| --------- | ---------- |
| 1 month | ≈ 0.024 USDFC |
| 24 months | ≈ 0.58 USDFC |

The default 2-copy configuration roughly doubles the recurring cost. One-time fees apply once: $0.025 to create the data set plus the per-batch add-pieces fee.

---

#### Example 2: User Content Platform with CDN

- **Storage:** 1,000 users × 100 MiB ≈ 100,000 MiB
- **Traffic:** 1,000 users × 100 MiB/month ≈ 100,000 MiB/month egress

```ts twoslash
// @lib: esnext,dom
const STORAGE_PRICE_PER_TIB_PER_MONTH = 2.5; // $2.50/TiB/month
const PROVING_FEE_PER_MONTH = 0.024; // flat per-data-set
const CDN_EGRESS_PRICE_PER_TIB = 14; // up to $14/TiB on a cache miss
const storageMiB = 100_000;
const egressMiB = 100_000;

// Storage: 100,000 MiB ≈ 0.0953 TiB
const storageTiB = storageMiB / 1024 / 1024;

// Egress: 100,000 MiB ≈ 0.0953 TiB
const egressTiB = egressMiB / 1024 / 1024;

// Storage cost per month: 0.0953 TiB × $2.50 ≈ $0.238/month
const storageCostPerMonth = storageTiB * STORAGE_PRICE_PER_TIB_PER_MONTH;

// Egress cost per month (worst case, all cache misses): 0.0953 TiB × $14 ≈ $1.334/month
const egressCostPerMonth = egressTiB * CDN_EGRESS_PRICE_PER_TIB;

// Total cost per month: $0.238 + $0.024 proving + $1.334 egress ≈ $1.596/month
const totalCostPerMonth = storageCostPerMonth + PROVING_FEE_PER_MONTH + egressCostPerMonth;

// Total cost for 24 months: $1.596/month × 24 ≈ $38.30
const totalCostFor24Months = totalCostPerMonth * 24;
```

| Cost Component | Per Month | 24 Months |
| -------------- | ---------------- | ----------------- |
| Storage | ≈ 0.238 USDFC | ≈ 5.71 USDFC |
| Proving Service | ≈ 0.024 USDFC | ≈ 0.576 USDFC |
| CDN Egress | ≈ 1.334 USDFC | ≈ 32.016 USDFC |
| **Total** | **≈ 1.596 USDFC** | **≈ 38.30 USDFC** |

Egress here is the worst case where every byte is a cache miss. When content is served from the Filecoin Beam cache, egress is roughly half this.

## How the fees and lockup work

Most developers can stop at the tables above. This section explains the mechanics behind the per-operation fees and the lockup reserve for anyone who needs the detail.

**The proving fee is additive, not a floor.** Earlier pricing used a single monthly minimum. The rate is now `(bytes / TiB) × $2.50 + $0.024`, so storage scales with size and the proving fee is a flat addition per data set. A data set with no pieces pays nothing until the first piece is added.

**Add-pieces pricing scales with batch size.** Each `addPieces` call costs `$0.0005 + $0.0003 × N`, where `N` is the number of pieces in the batch. The base fee is shared across the batch, so adding many pieces in one call is cheaper per piece than adding them one at a time. When estimating the cost of a single piece, assume the single-piece price (`$0.0008`); batching only makes it cheaper.

**Fees are paid from a small lockup reserve, not billed per call.** When a data set is created, ~$0.10 of USDFC is locked as a lifecycle reserve on the data set's payment rail. One-time fees are drawn from this reserve as operations happen, and the reserve is topped back up toward its target as it is drawn down. You see ~$0.10 of additional locked USDFC while the data set is active. It is refunded when the rail is finalized.

<details>
<summary>Why the reserve exists: wind-down after termination</summary>

Filecoin Pay does not allow raising a rail's fixed lockup once the rail has been terminated. The lifecycle reserve gives you headroom to run wind-down operations (such as scheduling piece removals) after termination, when the reserve can no longer be refilled.

If you expect to need more wind-down operations than the reserve covers, fund the reserve before terminating. User-initiated termination charges the $0.00112 termination fee and makes a best-effort top-up of the reserve. Provider-initiated termination charges no fee and does not top up, so those data sets are limited to whatever was in the reserve at termination.

For the full lockup model (streaming versus fixed lockup, rail settlement, finalization), see the [Filecoin Pay spec](https://github.com/FilOzone/filecoin-pay/blob/main/SPEC.md). The [Payments & Storage cookbook](/cookbooks/payments-and-storage/) covers the same mechanics in SDK terms, though its pricing figures predate this update.

</details>

## Querying Upload Costs

:::caution[SDK pricing update in progress]
The per-operation fees and the additive proving fee above are live in the Warm Storage contract. The SDK cost APIs (`getUploadCosts()`, `prepare()`) are still being updated to surface them, tracked in [synapse-sdk#763](https://github.com/FilOzone/synapse-sdk/issues/763). Until that lands, the returned `rate` and `depositNeeded` values reflect the previous model and may not include every new fee.
:::

Use `getUploadCosts()` to preview costs without executing any transaction. This is useful for displaying pricing in a UI or letting users confirm before proceeding.

Getting the costs for uploading to an existing data set:

```ts twoslash
// @lib: esnext,dom
// @noErrors
import { Synapse, SIZE_CONSTANTS } from "@filoz/synapse-sdk"
import { privateKeyToAccount } from 'viem/accounts'
const synapse = Synapse.create({ account: privateKeyToAccount('0x...'), source: 'my-app' })
// ---cut---
// With currentDataSetSize — accurate floor-aware delta
const { rate, depositNeeded } = await synapse.storage.getUploadCosts({
  isNewDataSet: false,
  currentDataSetSize: 50n * SIZE_CONSTANTS.MiB,
  dataSize: 100n * SIZE_CONSTANTS.MiB,
})
// Shows incremental cost (newRate - currentRate), handles floor-to-floor

// Without currentDataSetSize — safe overestimate for floor cases
const costs2 = await synapse.storage.getUploadCosts({
  isNewDataSet: false,
  dataSize: 100n * SIZE_CONSTANTS.MiB,
})
// Accurate for above-floor datasets, overestimates for floor-priced ones
```

Getting the costs for uploading to a new data set:

```ts twoslash
// @lib: esnext,dom
// @noErrors
import { Synapse, formatUnits, SIZE_CONSTANTS } from "@filoz/synapse-sdk"
import { privateKeyToAccount } from 'viem/accounts'
const synapse = Synapse.create({ account: privateKeyToAccount('0x...'), source: 'my-app' })
// ---cut---
const { rate, depositNeeded, needsFwssMaxApproval, ready } = await synapse.storage.getUploadCosts({
  dataSize: 100n * SIZE_CONSTANTS.MiB,
  isNewDataSet: true,
  withCDN: true,
})

// Storage rate per epoch (30 seconds)
console.log("Rate per epoch:", formatUnits(rate.perEpoch), "USDFC")
// Storage rate per month
console.log("Rate per month:", formatUnits(rate.perMonth), "USDFC")
// USDFC to deposit
console.log("Deposit needed:", formatUnits(depositNeeded), "USDFC")
// Whether FWSS needs to be approved
console.log("Needs approval:", needsFwssMaxApproval)
// Whether the account is ready to upload
console.log("Ready to upload:", ready)
```

## Upload Preparation

Before uploading, the **WarmStorage operator** must be approved and your account must be funded. FWSS uses a 30-day lockup (prepayment) period. If your account becomes underfunded, the provider may terminate the storage agreement, after which you have 30 days before data removal.

:::tip[Typical Flow]
**Query costs** → **prepare transaction** → **execute** → **upload**. The `prepare()` method handles the middle steps in one call.
:::

`prepare()` computes costs and returns a ready-to-execute transaction. This is the recommended approach — it handles deposit calculation, operator approval, and contract call routing in one step.

```ts twoslash
// @lib: esnext,dom
import { Synapse, formatUnits, SIZE_CONSTANTS } from "@filoz/synapse-sdk";
import { privateKeyToAccount } from 'viem/accounts'
const synapse = Synapse.create({ account: privateKeyToAccount('0x...'), source: 'my-app' });
// ---cut---
const { costs, transaction } = await synapse.storage.prepare({
  dataSize: SIZE_CONSTANTS.GiB,
})

// Inspect costs
const { rate, depositNeeded } = costs // Upload costs breakdown
console.log("Rate per month:", formatUnits(rate.perMonth))
console.log("Deposit needed:", formatUnits(depositNeeded))

// Execute if the account isn't ready
if (transaction) {
  const { depositAmount, includesApproval } = transaction
  console.log("Deposit amount:", formatUnits(depositAmount))
  console.log("Includes approval:", includesApproval)

  const { hash } = await transaction.execute()
  console.log("Transaction confirmed:", hash)
}

// Now safe to upload
```

When storing data across multiple providers, pass an array of contexts. The SDK aggregates per-context lockup while computing account-level values (debt, runway, buffer) once.

```ts twoslash
// @lib: esnext,dom
import { Synapse, formatUnits, SIZE_CONSTANTS, TIME_CONSTANTS } from "@filoz/synapse-sdk"
import { privateKeyToAccount } from 'viem/accounts'
const synapse = Synapse.create({ account: privateKeyToAccount('0x...'), source: 'my-app' })

const contexts = await synapse.storage.createContexts({ copies: 3 })

// 1-year runway
const oneYear = TIME_CONSTANTS.EPOCHS_PER_MONTH * 12n

// Prepare a single deposit covering all 3 copies for 1 year
const { costs, transaction } = await synapse.storage.prepare({
  context: contexts,
  dataSize: 50n * SIZE_CONSTANTS.GiB,
  extraRunwayEpochs: oneYear,
})

const { rate, depositNeeded, ready } = costs
console.log("Monthly rate (per copy):", formatUnits(rate.perMonth), "USDFC")
console.log("Total deposit needed:", formatUnits(depositNeeded), "USDFC")
console.log("Account ready:", ready)

if (transaction) {
  const { hash } = await transaction.execute()
  console.log("Funded:", hash)
}
```

`prepare()` returns:

- **`costs`**: The full `UploadCosts` breakdown
- **`transaction`**: A transaction object with `execute()`, or `null` if the account is already ready

The transaction automatically picks the right contract call:

- **Needs deposit + approval**: calls `depositWithPermitAndApproveOperator`
- **Needs approval only**: calls `approveService`
- **Needs deposit only**: calls `depositWithPermit`
- **Already ready**: returns `transaction: null`

## Account Queries

Read-only APIs for account-level cost visibility.

```ts twoslash
// @noErrors
// Aggregate rate across all active rails
const { ratePerEpoch, ratePerMonth } = await synapse.payments.totalAccountRate()

// Sum of lockupFixed across all rails (including terminated but not finalized)
const { totalFixedLockup } = await synapse.payments.totalAccountLockup()

// Total storage size across all live datasets
import { getAccountTotalStorageSize } from '@filoz/synapse-core/warm-storage'
const { totalSizeBytes, datasetCount } = await getAccountTotalStorageSize(client, {
  address: '0x...',
})
```

All rate values come in both per-epoch (contract-native) and per-month (`ratePerEpoch * EPOCHS_PER_MONTH`) units. Note that `ratePerMonth` from `totalAccountRate()` is computed as `ratePerEpoch * EPOCHS_PER_MONTH` — this is slightly less than the full-precision `rate.perMonth` returned by `getUploadCosts()` due to integer truncation. See [Rate Precision](/cookbooks/payments-and-storage/#rate-precision) for details.

:::tip[API Reference]
For the full API — including pure calculation functions and lower-level utilities — see the [synapse-core pay reference](/reference/filoz/synapse-core/pay/toc/) and [synapse-core warm-storage reference](/reference/filoz/synapse-core/warm-storage/toc/).
:::

## Next Steps

- [Payments & Storage](/cookbooks/payments-and-storage/) — Deep dive into the payment model mechanics
- [StorageManager API Reference](/reference/filoz/synapse-sdk/storage/classes/storagemanager/) — Full SDK storage API
- [PaymentsService API Reference](/reference/filoz/synapse-sdk/payments/classes/paymentsservice/) — Full SDK payments API
- [Upload Pipeline](/developer-guides/storage/upload-pipeline/) — Upload workflows from simple to advanced
- [Storage Operations](/developer-guides/storage/storage-operations/) — Contexts, data sets, and retrieval