Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/src/content/docs/cookbooks/payments-and-storage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Most developers won't need this section. The SDK abstracts these mechanics: `pre

### Rate Precision

Storage prices are stored per-month on-chain but rails operate per-epoch. Integer division during conversion means `perEpoch × EPOCHS_PER_MONTH` is slightly less than the true monthly rate due to truncation. The SDK returns both values: use `rate.perMonth` for display and `rate.perEpoch` for on-chain math.
Storage prices are stored per-month on-chain but rails operate per-epoch. Integer division during conversion means `perEpoch × EPOCHS_PER_MONTH` is slightly less than the true monthly rate due to truncation. The SDK returns both values: use `rates.perMonth` for display and `rates.perEpoch` for on-chain math.

### Deposit Components

Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/developer-guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ sequenceDiagram

Note over Client,Payments: Step 2: Payment Setup
Client->>SDK: Check allowances
SDK->>WarmStorage: getServicePrice()
SDK->>WarmStorage: getPriceList()
SDK->>Payments: accountInfo(client)
alt Needs setup
Client->>Payments: depositWithPermitAndApproveOperator()
Expand Down
48 changes: 48 additions & 0 deletions docs/src/content/docs/developer-guides/migration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,54 @@ SessionKey.TerminateServicePermission
TypedData.signTerminateService(client, { dataSetId })
```

### Action: Read pricing with `getPriceList()`

`getServicePrice()` was removed from both `@filoz/synapse-core` and `WarmStorageService`. Use `getPriceList()`, which returns the full on-chain price catalogue (`token`, `rates`, `fees`, `lockups`).

```ts
// before
const price = await warmStorage.getServicePrice()
price.pricePerTiBPerMonthNoCDN
price.pricePerTiBCdnEgress

// after
const priceList = await warmStorage.getPriceList()
priceList.rates.storagePerTibPerMonth
priceList.rates.cdnEgressPerTib
```

The React `useServicePrice()` hook was removed in favor of `usePriceList()`.

```tsx
// before
import { useServicePrice } from '@filoz/synapse-react'
const { data } = useServicePrice()
data?.pricePerTiBPerMonthNoCDN

// after
import { usePriceList } from '@filoz/synapse-react'
const { data } = usePriceList()
data?.rates.storagePerTibPerMonth
```

### Action: Read upload rates from `costs.rates`

The `rate` alias on upload-cost results was removed. Use `rates`.

```ts
// before
const { costs } = await synapse.storage.prepare({ dataSize })
costs.rate.perMonth

// after
const { costs } = await synapse.storage.prepare({ dataSize })
costs.rates.perMonth
```

### Action: Replace the `LOCKUP_PERIOD` constant

The `LOCKUP_PERIOD` export was removed from `@filoz/synapse-core`. The lockup period is now read from the chain; use `getPriceList().lockups.defaultLockupPeriod` if you need the value.

## 0.37.0

`synapse-sdk` moved to a viem-first API, removed deprecated modules/methods, and standardized method signatures around options objects plus `bigint` identifiers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ 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({
const { rates, depositNeeded } = await synapse.storage.getUploadCosts({
isNewDataSet: false,
currentDataSetSize: 50n * SIZE_CONSTANTS.MiB,
dataSize: 100n * SIZE_CONSTANTS.MiB,
Expand All @@ -133,16 +133,16 @@ 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({
const { rates, 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")
console.log("Rate per epoch:", formatUnits(rates.perEpoch), "USDFC")
// Storage rate per month
console.log("Rate per month:", formatUnits(rate.perMonth), "USDFC")
console.log("Rate per month:", formatUnits(rates.perMonth), "USDFC")
// USDFC to deposit
console.log("Deposit needed:", formatUnits(depositNeeded), "USDFC")
// Whether FWSS needs to be approved
Expand Down Expand Up @@ -172,8 +172,8 @@ const { costs, transaction } = await synapse.storage.prepare({
})

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

// Execute if the account isn't ready
Expand Down Expand Up @@ -209,8 +209,8 @@ const { costs, transaction } = await synapse.storage.prepare({
extraRunwayEpochs: oneYear,
})

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

Expand Down Expand Up @@ -251,7 +251,7 @@ const { totalSizeBytes, datasetCount } = await getAccountTotalStorageSize(client
})
```

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.
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 `rates.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/).
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/getting-started/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ Now let's break down each step...
});

console.log("Deposit needed:", prep.costs.depositNeeded);
console.log("Rate per month:", prep.costs.rate.perMonth);
console.log("Rate per month:", prep.costs.rates.perMonth);
console.log("Ready to upload:", prep.costs.ready);

// Execute the transaction if needed (handles deposit + approval in one tx)
Expand Down
58 changes: 28 additions & 30 deletions packages/synapse-core/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,24 +159,22 @@ export type OutputType = ContractFunctionReturnType<
When the contract function return type is an array try to convert into an object using the contract source code to choose the best descritive property names. When the return type is already an object inline it with documentation for each property.

```ts
export type ContractOutputType = ContractFunctionReturnType<typeof storageAbi, 'pure' | 'view', 'getServicePrice'>
export type ContractOutputType = ContractFunctionReturnType<typeof fwssViewAbi, 'pure' | 'view', 'getPriceList'>

/**
* The service price for the warm storage.
* The canonical warm storage price list.
*/
export type OutputType = {
/** Price per TiB per month without CDN (in base units) */
pricePerTiBPerMonthNoCDN: bigint
/** CDN egress price per TiB (usage-based, in base units) */
pricePerTiBCdnEgress: bigint
/** Cache miss egress price per TiB (usage-based, in base units) */
pricePerTiBCacheMissEgress: bigint
/** Token address for payments */
tokenAddress: string
/** Number of epochs per month */
epochsPerMonth: bigint
/** Minimum monthly charge for any dataset size (in base units) */
minimumPricePerMonth: bigint
token: Address
rates: {
/** Storage price per TiB per month (in base units) */
storagePerTibPerMonth: bigint
/** Per-dataset monthly proving service fee (in base units) */
datasetFeePerMonth: bigint
// ...remaining rate fields documented the same way
}
// ...fees and lockups objects follow the same inline-with-docs pattern
}
```

Expand All @@ -201,15 +199,15 @@ All read and write action require a call function to enable composition with oth
```ts

/**
* Create a call to the {@link getServicePrice} function for use with the Viem multicall, readContract, or simulateContract functions.
* Create a call to the {@link getPriceList} function for use with the Viem multicall, readContract, or simulateContract functions.
*
* @param options - {@link getServicePriceCall.OptionsType}
* @returns Call object {@link getServicePriceCall.OutputType}
* @throws Errors {@link getServicePriceCall.ErrorType}
* @param options - {@link getPriceListCall.OptionsType}
* @returns Call object {@link getPriceListCall.OutputType}
* @throws Errors {@link getPriceListCall.ErrorType}
*
* @example
* ```ts
* import { getServicePriceCall } from '@filoz/synapse-core/warm-storage'
* import { getPriceListCall } from '@filoz/synapse-core/warm-storage'
* import { createPublicClient, http } from 'viem'
* import { multicall } from 'viem/actions'
* import { calibration } from '@filoz/synapse-core/chains'
Expand All @@ -221,32 +219,32 @@ All read and write action require a call function to enable composition with oth
*
* const results = await multicall(client, {
* contracts: [
* getServicePriceCall({ chain: calibration }),
* getPriceListCall({ chain: calibration }),
* ],
* })
*
* console.log(results[0])
* ```
*/
export function getServicePriceCall(options: getServicePriceCall.OptionsType) {
export function getPriceListCall(options: getPriceListCall.OptionsType) {
const chain = asChain(options.chain)
return {
abi: chain.contracts.storage.abi,
address: options.address ?? chain.contracts.storage.address,
functionName: 'getServicePrice',
abi: chain.contracts.fwssView.abi,
address: options.contractAddress ?? chain.contracts.fwssView.address,
functionName: 'getPriceList',
args: [],
} satisfies getServicePriceCall.OutputType
} satisfies getPriceListCall.OutputType
}

```

They should have their own namespaced types

```ts
export namespace getServicePriceCall {
export type OptionsType = Simplify<getServicePrice.OptionsType & ActionCallChain>
export namespace getPriceListCall {
export type OptionsType = Simplify<getPriceList.OptionsType & ActionCallChain>
export type ErrorType = asChain.ErrorType
export type OutputType = ContractFunctionParameters<typeof storageAbi, 'pure' | 'view', 'getServicePrice'>
export type OutputType = ContractFunctionParameters<typeof fwssViewAbi, 'pure' | 'view', 'getPriceList'>
}
```

Expand Down Expand Up @@ -314,7 +312,7 @@ export function extractSetOperatorApprovalEvent(logs: Log[]) {

#### Parse function

When the `ContractOutputType` is different from the `OutputType` we need a parse function to transform the contract output into the action output. It should called `parse<actionName>` .ie `parseGetServicePrice`.
When the `ContractOutputType` is different from the `OutputType` we need a parse function to transform the contract output into the action output. It should called `parse<actionName>` .ie `parseGetPriceList`.

## Decision-Making

Expand All @@ -339,6 +337,6 @@ Reference contract source code for expected behavior and always create tests for

Use mocks and constants inside `/mocks` to test the actions.

See `test/get-service-price.test.ts` for a comprehensive example of test patterns and structure.
See `test/get-price-list.test.ts` for a comprehensive example of test patterns and structure.

Run the tests with `pnpm exec playwright-test "test/get-service-price.test.ts" --mode node`
Run the tests with `pnpm exec playwright-test "test/get-price-list.test.ts" --mode node`
6 changes: 5 additions & 1 deletion packages/synapse-core/src/abis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ export * from './erc20.ts'
export * as generated from './generated.ts'

import * as generated from './generated.ts'
import { priceListAbi } from './price-list.ts'

// Merge the storage and errors ABIs
export const fwss = [...generated.filecoinWarmStorageServiceAbi, ...generated.errorsAbi] as const
export const serviceProviderRegistry = [...generated.serviceProviderRegistryAbi, ...generated.errorsAbi] as const
// The view ABI plus the standalone getPriceList fragment. See abis/price-list.ts.
// TODO: drop the priceListAbi merge and re-export filecoinWarmStorageServiceStateViewAbi
// as fwssView once the generated ABI ref includes getPriceList.
export const fwssView = [...generated.filecoinWarmStorageServiceStateViewAbi, ...priceListAbi] as const

export {
filecoinPayV1Abi as filecoinPay,
filecoinWarmStorageServiceStateViewAbi as fwssView,
pdpVerifierAbi as pdp,
providerIdSetAbi as providerIdSet,
sessionKeyRegistryAbi as sessionKeyRegistry,
Expand Down
63 changes: 63 additions & 0 deletions packages/synapse-core/src/abis/price-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* `getPriceList()` view fragment for `FilecoinWarmStorageServiceStateView`,
* mirroring the on-chain `PriceList` struct from
* [FilOzone/filecoin-services#501](https://github.com/FilOzone/filecoin-services/pull/501).
*
* TODO: remove this file and the `fwssView` merge in `abis/index.ts` once
* `FILECOIN_SERVICES_GIT_REF` (`wagmi.config.ts`) points at a release whose
* generated view ABI exposes `getPriceList`.
*/
export const priceListAbi = [
{
type: 'function',
inputs: [],
name: 'getPriceList',
outputs: [
{
name: 'list',
internalType: 'struct PriceList',
type: 'tuple',
components: [
{ name: 'token', internalType: 'contract IERC20', type: 'address' },
{
name: 'rates',
internalType: 'struct PriceListRates',
type: 'tuple',
components: [
{ name: 'storagePerTibPerMonth', internalType: 'uint256', type: 'uint256' },
{ name: 'datasetFeePerMonth', internalType: 'uint256', type: 'uint256' },
{ name: 'cdnEgressPerTib', internalType: 'uint256', type: 'uint256' },
{ name: 'cacheMissEgressPerTib', internalType: 'uint256', type: 'uint256' },
],
},
{
name: 'fees',
internalType: 'struct PriceListFees',
type: 'tuple',
components: [
{ name: 'createDataSetFee', internalType: 'uint256', type: 'uint256' },
{ name: 'addPiecesBaseFee', internalType: 'uint256', type: 'uint256' },
{ name: 'addPiecesPerPieceFee', internalType: 'uint256', type: 'uint256' },
{ name: 'schedulePieceRemovalsFee', internalType: 'uint256', type: 'uint256' },
{ name: 'terminateFee', internalType: 'uint256', type: 'uint256' },
],
},
{
name: 'lockups',
internalType: 'struct PriceListLockups',
type: 'tuple',
components: [
{ name: 'lifecycleReserveTarget', internalType: 'uint256', type: 'uint256' },
{ name: 'replenishThreshold', internalType: 'uint256', type: 'uint256' },
{ name: 'defaultLockupPeriod', internalType: 'uint256', type: 'uint256' },
{ name: 'cdnLockupAmount', internalType: 'uint256', type: 'uint256' },
{ name: 'cacheMissLockupAmount', internalType: 'uint256', type: 'uint256' },
{ name: 'cdnLockupPeriod', internalType: 'uint256', type: 'uint256' },
],
},
],
},
],
stateMutability: 'view',
},
] as const
36 changes: 26 additions & 10 deletions packages/synapse-core/src/mocks/jsonrpc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,16 +401,6 @@ export const presets = {
viewContractAddress: () => [ADDRESSES.calibration.viewContract],
serviceProviderRegistry: () => [ADDRESSES.calibration.spRegistry],
sessionKeyRegistry: () => [ADDRESSES.calibration.sessionKeyRegistry],
getServicePrice: () => [
{
pricePerTiBPerMonthNoCDN: parseUnits('2.5', 18),
pricePerTiBCdnEgress: parseUnits('7', 18),
pricePerTiBCacheMissEgress: parseUnits('7', 18),
minimumPricePerMonth: parseUnits('6', 16),
tokenAddress: ADDRESSES.calibration.usdfcToken,
epochsPerMonth: TIME_CONSTANTS.EPOCHS_PER_MONTH,
},
],
owner: () => [ADDRESSES.client1],
terminateService: () => [],
topUpCDNPaymentRails: () => [],
Expand Down Expand Up @@ -517,6 +507,32 @@ export const presets = {
getClientDataSetsLength: () => {
return [1n]
},
getPriceList: () => [
{
token: ADDRESSES.calibration.usdfcToken,
rates: {
storagePerTibPerMonth: parseUnits('2.5', 18),
datasetFeePerMonth: parseUnits('0.024', 18),
cdnEgressPerTib: parseUnits('7', 18),
cacheMissEgressPerTib: parseUnits('7', 18),
},
fees: {
createDataSetFee: parseUnits('0.025', 18),
addPiecesBaseFee: parseUnits('0.0005', 18),
addPiecesPerPieceFee: parseUnits('0.0003', 18),
schedulePieceRemovalsFee: parseUnits('0.002', 18),
terminateFee: parseUnits('0.00112', 18),
},
lockups: {
lifecycleReserveTarget: parseUnits('0.1', 18),
replenishThreshold: parseUnits('0.005', 18),
defaultLockupPeriod: TIME_CONSTANTS.DEFAULT_LOCKUP_DAYS * TIME_CONSTANTS.EPOCHS_PER_DAY,
cdnLockupAmount: parseUnits('0.7', 18),
cacheMissLockupAmount: parseUnits('0.3', 18),
cdnLockupPeriod: 5n * TIME_CONSTANTS.EPOCHS_PER_DAY,
},
},
],
},
pdpVerifier: {
dataSetLive: () => [true],
Expand Down
Loading
Loading