Skip to content

Commit 1bf1238

Browse files
committed
fix: correct storage cost and fee calculations
1 parent 1b8b54e commit 1bf1238

42 files changed

Lines changed: 1580 additions & 714 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/src/content/docs/cookbooks/payments-and-storage.mdx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Two recurring rates apply per data set:
4646
| Component | Cost | Notes |
4747
| -------------- | -------------------- | ------------------------------------------------------- |
4848
| **Storage** | $2.50/TiB/month per copy | Charged only while pieces exist. Empty data sets have no recurring cost |
49-
| **Proving Service** | $0.024/data set/month | Flat per-data-set fee, added on top of the storage rate |
49+
| **Proving Service** | $0.12/data set/month | Flat per-data-set fee, added on top of the storage rate |
5050
| **CDN egress** | up to $14/TiB (≈ $0.014/GiB) downloaded | Optional, via Filecoin Beam. About half this when served from cache |
5151

5252
:::note
@@ -58,24 +58,24 @@ Each on-chain operation also pays a small one-time fee to the storage provider,
5858
| Operation | Fee |
5959
| -------------- | -------------------- |
6060
| **Create data set** | $0.025 |
61-
| **Add pieces** | $0.0005 + $0.0003 per piece |
62-
| **Schedule piece removals** | $0.002 per call |
63-
| **Terminate service** | $0.00112 (user-initiated only) |
61+
| **Add pieces** | $0.008 + $0.003 per piece |
62+
| **Schedule piece removals** | $0.007 per call |
63+
| **Terminate service** | $0.006 for client-authorized, provider-relayed termination; no fee for direct payer or provider termination |
6464

65-
These are fractions of a cent. They are drawn from a ~$0.10 lockup reserve held while the data set is active, not billed separately on each call. See [Storage Costs: How the fees and lockup work](/developer-guides/storage/storage-costs/#how-the-fees-and-lockup-work) for the mechanics.
65+
These are paid from a refundable ~$0.50 lifecycle reserve held while the data set is active, not added separately to the required deposit on each call. See [Storage Costs: How the fees and lockup work](/developer-guides/storage/storage-costs/#how-the-fees-and-lockup-work) for the mechanics.
6666

6767
### Proving Fee
6868

69-
The monthly cost of an active data set is `(bytes / TiB) × $2.50 + $0.024`. There is no minimum floor: a data set with no pieces has no recurring cost, and the rate activates when the first piece is added. The proving fee is a flat addition per data set, on top of the per-size storage rate.
69+
The monthly cost of an active data set is `(unpadded bytes / TiB) × $2.50 + $0.12`. There is no minimum floor: a data set with no pieces has no recurring cost, and the rate activates when the first piece is added. The proving fee is a flat addition per data set, on top of the per-size storage rate. The SDK converts PDP's padded leaf counts to unpadded bytes using the Filecoin `127 / 128` ratio.
7070

7171
### Data Set Creation Costs
7272

7373
Creating a data set locks a small lifecycle reserve. The storage rate starts at zero and is set automatically when you add data:
7474

7575
| Scenario | Minimum Funds Required |
7676
| -------- | ---------------------- |
77-
| No CDN | ~0.10 USDFC (lifecycle reserve lockup) |
78-
| With CDN | ~1.10 USDFC (0.10 reserve + 0.70 CDN egress + 0.30 cache-miss) |
77+
| No CDN | ~0.50 USDFC (lifecycle reserve lockup) |
78+
| With CDN | ~1.50 USDFC (0.50 reserve + 0.70 CDN egress + 0.30 cache-miss) |
7979

8080
One-time fees such as the $0.025 creation fee are drawn from the lifecycle reserve, not added on top of these amounts.
8181

@@ -89,7 +89,7 @@ For each data set, FWSS creates payment channels called **rails** that stream fu
8989
| **CDN rail** | Fixed | 0.7 USDFC lockup for CDN egress credits |
9090
| **Cache miss** | Fixed | 0.3 USDFC lockup for cache miss credits |
9191

92-
Every data set gets a PDP rail, which also holds the ~$0.10 lifecycle reserve that one-time fees draw from. CDN and cache miss rails are added only when CDN is enabled, which must happen at data set creation. CDN cannot currently be added to an existing data set. Follow [FilOzone/filecoin-services#529](https://github.com/FilOzone/filecoin-services/issues/529) to share your use case or track changes.
92+
Every data set gets a PDP rail, which also holds the ~$0.50 lifecycle reserve that one-time fees draw from. FWSS replenishes it when the balance is below pending fees plus the $0.025 threshold. CDN and cache miss rails are added only when CDN is enabled, which must happen at data set creation. CDN cannot currently be added to an existing data set. Follow [FilOzone/filecoin-services#529](https://github.com/FilOzone/filecoin-services/issues/529) to share your use case or track changes.
9393

9494
FWSS enforces a **30-day lockup period**: your account must always have enough funds to cover at least 30 days of storage at the current rate. Think of it as a security deposit. The funds aren't spent upfront, but they're reserved so providers are guaranteed payment even if you stop topping up.
9595

docs/src/content/docs/developer-guides/storage/storage-costs.mdx

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ Pricing has two recurring rates and a set of small one-time fees.
2626
| Component | Cost | Notes |
2727
| -------------- | ------------------ | -------------------------------------------------------- |
2828
| **Storage** | $2.50/TiB/month per copy | Charged only while pieces exist. Empty data sets have no recurring cost. |
29-
| **Proving Service** | $0.024/data set/month | Flat per-data-set fee, added on top of the storage rate. |
29+
| **Proving Service** | $0.12/data set/month | Flat per-data-set fee, added on top of the storage rate. |
3030
| **CDN egress** | up to $14/TiB (≈ $0.014/GiB) downloaded | Optional, via Filecoin Beam. About half this when served from cache. |
3131

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

36-
The monthly rate for an active data set is `(bytes / TiB) × $2.50 + $0.024`. There is no minimum floor: a data set with no pieces has no recurring cost, and the rate activates when the first piece is added.
36+
The monthly rate for an active data set is `(FWSS-priced bytes / TiB) × $2.50 + $0.12`. There is no minimum floor: a data set with no pieces has no recurring cost, and the rate activates when the first piece is added. PDP counts the data-bearing Merkle-tree leaves of each piece, then FWSS converts the aggregate leaf count using the Filecoin `127 / 128` ratio. This priced size can exceed the exact raw payload size by up to 31 bytes per piece.
3737

3838
### One-time fees
3939

@@ -42,11 +42,11 @@ Each on-chain operation pays a small fee to the storage provider, covering the g
4242
| Operation | Fee |
4343
| -------------- | ------------------ |
4444
| **Create data set** | $0.025 |
45-
| **Add pieces** | $0.0005 + $0.0003 per piece |
46-
| **Schedule piece removals** | $0.002 per call |
47-
| **Terminate service** | $0.00112 (user-initiated only) |
45+
| **Add pieces** | $0.008 + $0.003 per piece |
46+
| **Schedule piece removals** | $0.007 per call |
47+
| **Terminate service** | $0.006 for client-authorized, provider-relayed termination; no fee for direct payer or provider termination |
4848

49-
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.
49+
These are small one-time charges paid from a refundable lifecycle reserve (~$0.50) held while the data set is active, not added separately to the required deposit on every call. See [How the fees and lockup work](#how-the-fees-and-lockup-work) below.
5050

5151
:::note[CDN setup]
5252
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 if unused.
@@ -63,20 +63,20 @@ These examples use plain arithmetic for clarity. For programmatic cost calculati
6363
// 48.82 MiB is tiny, so the storage rate is negligible.
6464
// The recurring cost is dominated by the flat proving fee.
6565
const STORAGE_PRICE_PER_TIB_PER_MONTH = 2.5;
66-
const PROVING_FEE_PER_MONTH = 0.024;
66+
const PROVING_FEE_PER_MONTH = 0.12;
6767

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

7171
// Per copy, per month
72-
const costPerMonth = storageCostPerMonth + PROVING_FEE_PER_MONTH; // ≈ 0.0241 USDFC
73-
const costFor24Months = costPerMonth * 24; //0.578 USDFC
72+
const costPerMonth = storageCostPerMonth + PROVING_FEE_PER_MONTH; // ≈ 0.1201 USDFC
73+
const costFor24Months = costPerMonth * 24; //2.883 USDFC
7474
```
7575

7676
| Duration | Per copy |
7777
| --------- | ---------- |
78-
| 1 month | ≈ 0.024 USDFC |
79-
| 24 months |0.58 USDFC |
78+
| 1 month | ≈ 0.120 USDFC |
79+
| 24 months |2.88 USDFC |
8080

8181
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.
8282

@@ -90,7 +90,7 @@ The default 2-copy configuration roughly doubles the recurring cost. One-time fe
9090
```ts twoslash
9191
// @lib: esnext,dom
9292
const STORAGE_PRICE_PER_TIB_PER_MONTH = 2.5; // $2.50/TiB/month
93-
const PROVING_FEE_PER_MONTH = 0.024; // flat per-data-set
93+
const PROVING_FEE_PER_MONTH = 0.12; // flat per-data-set
9494
const CDN_EGRESS_PRICE_PER_TIB = 14; // up to $14/TiB on a cache miss
9595
const storageMiB = 100_000;
9696
const egressMiB = 100_000;
@@ -107,38 +107,38 @@ const storageCostPerMonth = storageTiB * STORAGE_PRICE_PER_TIB_PER_MONTH;
107107
// Egress cost per month (worst case, all cache misses): 0.0953 TiB × $14 ≈ $1.334/month
108108
const egressCostPerMonth = egressTiB * CDN_EGRESS_PRICE_PER_TIB;
109109

110-
// Total cost per month: $0.238 + $0.024 proving + $1.334 egress ≈ $1.596/month
110+
// Total cost per month: $0.238 + $0.12 proving + $1.335 egress ≈ $1.694/month
111111
const totalCostPerMonth = storageCostPerMonth + PROVING_FEE_PER_MONTH + egressCostPerMonth;
112112

113-
// Total cost for 24 months: $1.596/month × 24 ≈ $38.31
113+
// Total cost for 24 months: $1.694/month × 24 ≈ $40.65
114114
const totalCostFor24Months = totalCostPerMonth * 24;
115115
```
116116

117117
| Cost Component | Per Month | 24 Months |
118118
| -------------- | ---------------- | ----------------- |
119-
| Storage | ≈ 0.238 USDFC | ≈ 5.71 USDFC |
120-
| Proving Service | ≈ 0.024 USDFC |0.576 USDFC |
121-
| CDN Egress | ≈ 1.334 USDFC | ≈ 32.016 USDFC |
122-
| **Total** | **≈ 1.596 USDFC** | **38.31 USDFC** |
119+
| Storage | ≈ 0.238 USDFC | ≈ 5.72 USDFC |
120+
| Proving Service | ≈ 0.120 USDFC |2.88 USDFC |
121+
| CDN Egress | ≈ 1.335 USDFC | ≈ 32.04 USDFC |
122+
| **Total** | **≈ 1.694 USDFC** | **40.65 USDFC** |
123123

124124
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.
125125

126126
## How the fees and lockup work
127127

128128
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.
129129

130-
**The proving fee is additive, not a floor.** The rate is `(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 has no recurring charge until the first piece is added.
130+
**The proving fee is additive, not a floor.** The rate is `(FWSS-priced bytes / TiB) × $2.50 + $0.12`, so storage scales with size and the proving fee is a flat addition per data set. A data set with no pieces has no recurring charge until the first piece is added.
131131

132-
**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.
132+
**Add-pieces pricing scales with batch size.** Each `addPieces` call costs `$0.008 + $0.003 × 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. A single-piece call costs `$0.011`, and one SDK commit accepts at most 40 pieces.
133133

134-
**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.
134+
**Fees are paid from a lifecycle reserve, not added twice to deposits.** When a data set is created, ~$0.50 of USDFC is locked as a lifecycle reserve on the data set's payment rail. Before pending one-time fees are paid, FWSS checks whether the reserve is below `pending fees + $0.025`. If so, it raises the fixed lockup enough that paying those fees leaves the reserve at its $0.50 target. The remaining reserve is refundable when the rail is finalized.
135135

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

139139
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.
140140

141-
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.
141+
If you expect to need more wind-down operations than the reserve covers, account for them before terminating. A client-authorized termination relayed by the provider charges the $0.006 termination fee, pays pending fees, and releases the remaining reserve rather than replenishing it. Direct payer and provider termination paths do not charge this termination fee.
142142

143143
For the full lockup model (streaming versus fixed lockup, rail settlement, finalization), see the [Payments & Storage cookbook](/cookbooks/payments-and-storage/) and the [Filecoin Pay spec](https://github.com/FilOzone/filecoin-pay/blob/main/SPEC.md).
144144

@@ -154,23 +154,25 @@ Getting the costs for uploading to an existing data set:
154154
// @lib: esnext,dom
155155
// @noErrors
156156
import { Synapse, SIZE_CONSTANTS } from "@filoz/synapse-sdk"
157+
import { getDataSetLeafCount } from '@filoz/synapse-core/pdp-verifier'
158+
import { getDataSet } from '@filoz/synapse-core/warm-storage'
157159
import { privateKeyToAccount } from 'viem/accounts'
158160
const synapse = Synapse.create({ account: privateKeyToAccount('0x...'), source: 'my-app' })
159161
// ---cut---
160-
// Pass currentDataSetSize so rates reflect the data set's new total size
162+
// Read the existing data set so reserve replenishment is estimated correctly.
163+
const dataSet = await getDataSet(synapse.readClient, { dataSetId: 42n })
164+
if (dataSet == null) throw new Error('Data set not found')
165+
const dataSetLeafCount = await getDataSetLeafCount(synapse.readClient, { dataSetId: 42n })
166+
161167
const { rates, fees, depositNeeded } = await synapse.storage.getUploadCosts({
162168
isNewDataSet: false,
163-
currentDataSetSize: 50n * SIZE_CONSTANTS.MiB,
164-
dataSize: 100n * SIZE_CONSTANTS.MiB,
169+
dataSetLeafCount,
170+
currentLifecycleReserveBalance: dataSet.lifecycleReserveBalance,
171+
pendingOneTimePayments: dataSet.pendingOneTimePayments,
172+
pieceSizes: [100n * SIZE_CONSTANTS.MiB],
165173
})
166174
// fees.addPiecesFee is the one-time fee for this upload
167-
// (pass pieceCount when adding more than one piece)
168-
169-
// Without currentDataSetSize, rates cover the new data alone
170-
const costs2 = await synapse.storage.getUploadCosts({
171-
isNewDataSet: false,
172-
dataSize: 100n * SIZE_CONSTANTS.MiB,
173-
})
175+
// Pass every known raw piece size when committing a multi-piece batch.
174176
```
175177

176178
Getting the costs for uploading to a new data set:
@@ -183,7 +185,7 @@ import { privateKeyToAccount } from 'viem/accounts'
183185
const synapse = Synapse.create({ account: privateKeyToAccount('0x...'), source: 'my-app' })
184186
// ---cut---
185187
const { rates, fees, lockups, depositNeeded, needsFwssMaxApproval, ready } = await synapse.storage.getUploadCosts({
186-
dataSize: 100n * SIZE_CONSTANTS.MiB,
188+
pieceSizes: [100n * SIZE_CONSTANTS.MiB],
187189
isNewDataSet: true,
188190
withCDN: true,
189191
})
@@ -221,7 +223,7 @@ import { privateKeyToAccount } from 'viem/accounts'
221223
const synapse = Synapse.create({ account: privateKeyToAccount('0x...'), source: 'my-app' });
222224
// ---cut---
223225
const { costs, transaction } = await synapse.storage.prepare({
224-
dataSize: SIZE_CONSTANTS.GiB,
226+
pieceSizes: [SIZE_CONSTANTS.GiB],
225227
})
226228

227229
// Inspect costs
@@ -258,7 +260,7 @@ const oneYear = TIME_CONSTANTS.EPOCHS_PER_MONTH * 12n
258260
// Prepare a single deposit covering all 3 copies for 1 year
259261
const { costs, transaction } = await synapse.storage.prepare({
260262
context: contexts,
261-
dataSize: 50n * SIZE_CONSTANTS.GiB,
263+
pieceSizes: [50n * SIZE_CONSTANTS.GiB],
262264
extraRunwayEpochs: oneYear,
263265
})
264266

docs/src/content/docs/developer-guides/storage/upload-pipeline.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ import { privateKeyToAccount } from "viem/accounts"
9494

9595
const synapse = Synapse.create({ account: privateKeyToAccount("0x...") })
9696

97-
const prep = await synapse.storage.prepare({ dataSize: 1073741824n }) // 1 GiB
97+
const prep = await synapse.storage.prepare({ pieceSizes: [1073741824n] }) // one 1 GiB piece
9898
if (prep.transaction) {
9999
await prep.transaction.execute()
100100
}

docs/src/content/docs/getting-started/index.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const file = new TextEncoder().encode(
8585
requirement of 127 bytes per upload.`
8686
);
8787
const prep = await synapse.storage.prepare({
88-
dataSize: BigInt(file.byteLength),
88+
pieceSizes: [BigInt(file.byteLength)],
8989
})
9090
if (prep.transaction) {
9191
const { hash } = await prep.transaction.execute()
@@ -165,7 +165,7 @@ Now let's break down each step...
165165

166166
// Prepare account
167167
const prep = await synapse.storage.prepare({
168-
dataSize: 1073741824n, // 1 GiB
168+
pieceSizes: [1073741824n], // one 1 GiB piece
169169
});
170170

171171
console.log("Deposit needed:", prep.costs.depositNeeded);

docs/src/content/docs/introduction/about.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ All payments are settled automatically through Filecoin Pay, ensuring on-chain a
7070
| **Service** | **Pricing** | **Description** |
7171
| ------- | --------- | -------------------- |
7272
| **Storage** | **$2.50/TiB/month/copy** (minimum 2 copies) | Redundant storage with continuous PDP verification. Empty data sets have no recurring cost; charges begin when the first piece is added. |
73-
| **Proving Service** | **$0.024/data set/month** | Flat per-data-set fee for proof verification, added on top of the storage rate. Charged only while the data set holds pieces. |
73+
| **Proving Service** | **$0.12/data set/month** | Flat per-data-set fee for proof verification, added on top of the storage rate. Charged only while the data set holds pieces. |
7474
| **Add-on Retrieval (Filecoin Beam)** | **Up to $14/TiB (≈ $0.014/GiB) egress** | Outbound data delivered via Filecoin Beam. Roughly half this when served from cache, up to the full rate on a cache miss. |
7575

76-
Beyond these recurring rates, each on-chain operation (creating a data set, adding or removing pieces, terminating service) carries a small one-time fee paid to the storage provider to cover its gas. These fees are fractions of a cent, drawn from a small refundable lockup reserve (~$0.10 USDFC) held while the data set is active, and only matter at very high operation volumes. See [Storage Costs](/developer-guides/storage/storage-costs/) for the full breakdown.
76+
Beyond these recurring rates, each on-chain operation (creating a data set, adding or removing pieces, and some termination paths) carries a one-time fee paid to the storage provider to cover its gas. These fees are drawn from a refundable lifecycle reserve (~$0.50 USDFC) held while the data set is active. See [Storage Costs](/developer-guides/storage/storage-costs/) for the full breakdown.
7777

7878
:::note[Verifiable Payment]
7979
All storage and retrieval charges are denominated in **USDFC** (or supported ERC-20 tokens) and settled via **Filecoin Pay**. Every transaction is on-chain, auditable, and linked to verifiable service proofs.

0 commit comments

Comments
 (0)