Help us shape the future of Go! Take the 2026 Go Developer Survey

costs

package
v0.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package costs provides cost calculation for storage operations.

It computes upload costs from the warmstorage PriceList, per-piece raw sizes, lifecycle reserve state, one-time operation fees, lockup requirements, and CDN options. The calculation matches on-chain Solidity integer division for accuracy.

Entry points

Service reads require non-nil price, account, and approval results. Missing results return an error without an estimate. Explicit zero-value results are accepted; pure calculation helpers retain their documented nil semantics.

Piece sizes and existing state

Service entry points accept a non-empty []uint64 of raw payload sizes, replicated to each target. Every size must be between chain.MinUploadSize and chain.MaxUploadSize. Added leaves are summed per piece using ceil(4*rawSize/127). Current and final aggregate leaf counts are converted to billable bytes using floor(leaves*32*127/128) before calculating rates. Leaf counts and aggregate sizes use arbitrary-precision integers.

A new dataset requires UploadCostOptions.IsNewDataSet=true and ignores any supplied existing-dataset state. An existing dataset requires a non-negative CurrentDataSetLeafCount and CurrentLifecycleReserveBalance, plus a non-nil PDPEndEpoch that points to zero. PendingOneTimePayments defaults to zero and must otherwise be non-negative. A non-zero PDP end epoch returns DataSetServiceTerminatedError. Consequently, nil or empty single-target options return ErrInvalidArgument. Multi-target state comes from refs; nil options still uses defaults. Negative service runway and buffer values return ErrInvalidArgument.

When migrating a single piece, wrap its raw size in []uint64{size}. For multiple pieces, supply the actual sizes: a total and count cannot recover per-piece rounding. Obtain current leaves from PDPVerifier, rather than converting a previously estimated byte size. Fee and reserve estimates conservatively treat each piece as a separate add-pieces operation because runtime batch boundaries are not known during estimation. Actual fees can be lower when pieces are submitted together.

Lifecycle reserve funding is simulated in operation order. New data sets add the configured reserve target through AdditionalLockup.LifecycleLockup. Existing and new data sets add only conditional top-ups through AdditionalLockup.ReserveReplenishment. Upload fees remain visible in the result, but are not added directly to DepositNeeded because FWSS pays them from the reserve, reducing account funds and fixed lockup together.

Multi-context calculations simulate each data set independently, then apply account debt, runway, available funds, and the execution buffer once to the aggregate.

Glossary

Epoch — Filecoin block interval (30 seconds on mainnet and calibration). All on-chain rates and durations are denominated in epochs; 120 epochs equal one hour, ~86 400 equal one month. On-chain lockup rates are per-epoch; effective monthly rates preserve monthly pricing precision for display and comparison.

Basis points (bps) — one hundredth of one percent (1 bps = 0.01 %). Commission rates returned by warmstorage are expressed in basis points out of 10 000 (e.g. 500 bps = 5 %).

Fees — one-time operation charges such as dataset creation and add-pieces submission fees. Estimates conservatively charge one add-pieces base fee per piece and report the result separately from the required deposit.

Lifecycle reserve — fixed lockup used by FWSS to pay lifecycle operation fees. A new dataset starts at the configured target; an active reserve is replenished only when pending fees plus the threshold exceed its balance.

Lockup — funds reserved on the FilecoinPay contract to guarantee a stream of payments. Upload cost calculations include any additional lockup required by the new data and report the resulting deposit requirement.

Example

Example demonstrates estimating upload costs via costs.Service. In practice a Service is obtained from synapse.Client.Costs.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/ethereum/go-ethereum/common"
	"github.com/strahe/synapse-go/costs"
)

func main() {
	var svc *costs.Service // obtained from synapse.Client.Costs()

	ctx := context.Background()
	payer := common.HexToAddress("0x...")
	pieceSizes := []uint64{256 << 20}

	quote, err := svc.GetUploadCosts(ctx, payer, pieceSizes, &costs.UploadCostOptions{IsNewDataSet: true})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(quote.Rate.RatePerMonth)
}

Index

Examples

Constants

View Source
const (
	// DefaultExtraRunwayEpochs adds no runway above the lockup period.
	DefaultExtraRunwayEpochs int64 = 0
	// DefaultBufferEpochs is a 5-epoch deposit cushion for transaction execution latency.
	DefaultBufferEpochs int64 = 5
	// DefaultLockupPeriod is the standard lockup horizon (30 days in epochs).
	DefaultLockupPeriod int64 = chain.EpochsPerMonth
)

Variables

View Source
var ErrClosed = lifecycle.ErrClosed

ErrClosed is returned when a method is called after the owning Client has been closed. It aliases the shared closed-client sentinel.

View Source
var ErrInvalidArgument = errors.New("costs: invalid argument")

ErrInvalidArgument is returned, wrapped via fmt.Errorf with %w, when a caller passes an argument that violates a precondition. Match with errors.Is(err, costs.ErrInvalidArgument).

View Source
var ErrUninitialized = errors.New("costs: service not initialized; use costs.New")

ErrUninitialized is returned when a method is invoked on a zero-value Service (one that was not constructed via New).

Functions

func CalculateDepositNeeded

func CalculateDepositNeeded(calc DepositCalculation) *big.Int

CalculateDepositNeeded computes the USDFC deposit required to cover lockup, runway, and buffer.

Buffer is skipped when currentLockupRate is zero and isNewDataSet is true: the deposit lands before the payment rail is created so the contract cannot yet drain it.

Nil *big.Int fields are treated as zero. Negative epoch counts are clamped to zero. The calculation does not modify its inputs.

func LeafCountToBillableBytes added in v0.6.0

func LeafCountToBillableBytes(leaves *big.Int) (*big.Int, error)

LeafCountToBillableBytes returns the billable size in bytes for a data set's total leaf count, as expected by CalculateEffectiveRate. Convert the total, current plus added, rather than individual pieces. A nil count is zero; a negative count returns ErrInvalidArgument.

func PieceSizesToLeafCount added in v0.6.0

func PieceSizesToLeafCount(pieceSizes []uint64) (*big.Int, error)

PieceSizesToLeafCount returns the PDP leaf count added by pieces with the given raw sizes. Pass each piece's size: leaves are rounded per piece, so a total size gives a different result. Every size must be between chain.MinUploadSize and chain.MaxUploadSize; otherwise it returns ErrInvalidArgument.

Types

type AdditionalLockup

type AdditionalLockup struct {
	RateDeltaPerEpoch    *big.Int
	StreamingLockup      *big.Int
	LifecycleLockup      *big.Int
	ReserveReplenishment *big.Int
	CDNLockup            *big.Int
	CacheMissLockup      *big.Int
	Total                *big.Int
}

AdditionalLockup is the incremental lockup required when adding data to a dataset.

func CalculateAdditionalLockupRequired

func CalculateAdditionalLockupRequired(
	pieceSizes []uint64,
	currentDataSetLeafCount *big.Int,
	priceList *warmstorage.PriceList,
	lockupPeriod *big.Int,
	isNewDataSet bool,
	enableCDN bool,
) (AdditionalLockup, error)

CalculateAdditionalLockupRequired returns the incremental lockup for pieces with the supplied raw payload sizes. pieceSizes must be non-empty and every size must be between chain.MinUploadSize and chain.MaxUploadSize. A provided CurrentDataSetLeafCount is ignored for a new dataset; an existing dataset requires a non-negative leaf count. Invalid inputs return ErrInvalidArgument. A nil price list uses zero-value prices.

type ContractCaller

type ContractCaller interface {
	BlockNumber(ctx context.Context) (uint64, error)
}

ContractCaller provides the chain block number used by Service. The root Client uses its configured Ethereum client.

type DataSetServiceTerminatedError added in v0.6.0

type DataSetServiceTerminatedError struct {
	PDPEndEpoch types.Epoch
}

DataSetServiceTerminatedError is returned when an existing data set's PDP payment rail has an end epoch, so the data set cannot accept uploads.

func (*DataSetServiceTerminatedError) Error added in v0.6.0

type DepositCalculation added in v0.3.1

type DepositCalculation struct {
	// AdditionalLockup is the incremental lockup required by the upload.
	AdditionalLockup *big.Int
	// RateDelta is the incremental per-epoch payment rate added by the upload.
	RateDelta *big.Int
	// CurrentLockupRate is the account's existing per-epoch payment rate.
	CurrentLockupRate *big.Int
	// Debt is already-accrued payment debt that must be covered.
	Debt *big.Int
	// AvailableFunds is the account balance available after projected lockup.
	AvailableFunds *big.Int
	// RunwayInEpochs is the current account runway after projection.
	RunwayInEpochs *big.Int
	// ExtraRunwayEpochs is extra epoch runway on top of the required lockup.
	ExtraRunwayEpochs int64
	// BufferEpochs is the deposit buffer for execution latency.
	// Zero means no buffer for direct calculations; service options apply their
	// own default before calling CalculateDepositNeeded.
	BufferEpochs int64
	// IsNewDataSet is true when creating a fresh dataset.
	IsNewDataSet bool
}

DepositCalculation is the input to CalculateDepositNeeded.

type EffectiveRate

type EffectiveRate struct {
	RatePerEpoch *big.Int
	RatePerMonth *big.Int
}

EffectiveRate is the per-epoch and per-month storage rate for a given data size. RatePerEpoch uses integer division to match on-chain Solidity truncation. RatePerMonth preserves monthly pricing precision for display and comparison.

func CalculateEffectiveRate

func CalculateEffectiveRate(
	sizeBytes *big.Int,
	pricePerTiBPerMonth *big.Int,
	datasetFeePerMonth *big.Int,
	epochsPerMonth int64,
) EffectiveRate

CalculateEffectiveRate computes the storage rate for the given contract billable size in bytes, not the sum of raw piece payload sizes; obtain it with PieceSizesToLeafCount and LeafCountToBillableBytes. Integer division is used to match on-chain Solidity truncation. If epochsPerMonth is zero or negative, chain.EpochsPerMonth is used as a safe default. Nil sizeBytes, pricePerTiBPerMonth, or datasetFeePerMonth are treated as zero.

type LifecycleReserveCalculation added in v0.6.0

type LifecycleReserveCalculation struct {
	// PriceList is the canonical warm-storage price list. Nil uses zero values.
	PriceList *warmstorage.PriceList
	// PieceSizes contains every raw payload size in the planned upload.
	PieceSizes []uint64
	// IsNewDataSet is true when the upload creates a new data set.
	IsNewDataSet bool
	// CurrentLifecycleReserveBalance is required and non-negative for an
	// existing data set. It is ignored for a new data set.
	CurrentLifecycleReserveBalance *big.Int
	// PendingOneTimePayments is the non-negative fee total already waiting to be
	// paid from the reserve. Nil defaults to zero.
	PendingOneTimePayments *big.Int
}

LifecycleReserveCalculation is the input to CalculateLifecycleReserveFunding.

type LifecycleReserveFunding added in v0.6.0

type LifecycleReserveFunding struct {
	// InitialLockup is the reserve target locked when a new data set is created.
	InitialLockup *big.Int
	// ReserveReplenishment is the additional fixed lockup required while the
	// planned operation fees are processed.
	ReserveReplenishment *big.Int
	// Total is InitialLockup plus ReserveReplenishment.
	Total *big.Int
	// FinalReserveBalance is the simulated balance after all planned fees have
	// been processed.
	FinalReserveBalance *big.Int
}

LifecycleReserveFunding is the fixed-lockup funding required for lifecycle operation fees.

func CalculateLifecycleReserveFunding added in v0.6.0

func CalculateLifecycleReserveFunding(calc LifecycleReserveCalculation) (LifecycleReserveFunding, error)

CalculateLifecycleReserveFunding computes the lifecycle reserve lockup needed to process the planned upload fees. For a new data set, the current reserve is ignored and the reserve starts at the configured target. Pending payments default to zero and, when supplied, are included for either data-set mode. The calculation does not modify its inputs.

type MultiContextCosts

type MultiContextCosts struct {
	// RatePerEpoch is the sum of per-context effective rates (post-upload).
	RatePerEpoch *big.Int
	// RatePerMonth is the sum of per-context monthly effective rates.
	RatePerMonth *big.Int
	// Fees is the aggregate one-time fee breakdown.
	Fees UploadFees
	// Lockup is the aggregate lockup breakdown.
	Lockup AdditionalLockup
	// DepositNeeded is the single USDFC deposit covering all contexts.
	DepositNeeded *big.Int
	// RequiredLockupPeriod is the max lockup period required for FWSS
	// approval, sourced from the price list.
	RequiredLockupPeriod *big.Int
	// NeedsFWSSMaxApproval is true when the FWSS operator does not yet
	// hold max approval for the payer.
	NeedsFWSSMaxApproval bool
	// Ready is true when DepositNeeded is zero and FWSS approval is set.
	Ready bool
}

MultiContextCosts is the aggregate cost view across multiple upload targets: a single DepositNeeded / NeedsFWSSMaxApproval / Ready, plus summed RatePerEpoch / RatePerMonth covering all refs.

type MultiContextRef

type MultiContextRef struct {
	// IsNewDataSet is true when the target will create a new data set
	// on this provider (contributes lifecycle and optional CDN/cache-miss
	// lockup). When false, CurrentDataSetLeafCount is required.
	IsNewDataSet bool

	// CurrentDataSetLeafCount is the non-negative on-chain leaf count of an
	// existing data set. Zero means known empty; nil is invalid for an existing
	// data set. Ignored when IsNewDataSet is true.
	CurrentDataSetLeafCount *big.Int
	// CurrentLifecycleReserveBalance is required and non-negative for an
	// existing data set. It is ignored when IsNewDataSet is true.
	CurrentLifecycleReserveBalance *big.Int
	// PendingOneTimePayments is the non-negative operation-fee total already
	// waiting to be paid from an existing data set's reserve. Nil defaults to
	// zero. It is ignored when IsNewDataSet is true.
	PendingOneTimePayments *big.Int
	// PDPEndEpoch is required for an existing data set. It must point to zero;
	// a non-zero epoch means the data set can no longer accept uploads. It is
	// ignored when IsNewDataSet is true.
	PDPEndEpoch *types.Epoch

	// WithCDN toggles CDN and cache-miss lockup for this target. Only
	// meaningful when IsNewDataSet is true.
	WithCDN bool
}

MultiContextRef describes one upload target for Service.CalculateMultiContextCosts. Each ref yields its own lockup contribution; account-level debt, runway and buffer are computed once across the aggregate.

type Options

type Options struct {
	// Chain selects the network whose contract addresses are used.
	// Zero value is chain.Mainnet. Explicit addresses below override the
	// chain registry for callers that resolve current contracts dynamically.
	Chain chain.Chain

	// USDFCTokenAddress is the payment token used for account and approval
	// reads. Zero falls back to Chain.Addresses().USDFC.
	USDFCTokenAddress common.Address

	// WarmStorageAddress is the FWSS operator used for approval reads. Zero
	// falls back to Chain.Addresses().FWSS.
	WarmStorageAddress common.Address

	// WarmStorage reads on-chain service pricing. Required.
	WarmStorage WarmStorageReader

	// Payments reads account and allowance state. Required.
	Payments PaymentsReader

	// Caller provides chain reads for cost calculations. Required.
	Caller ContractCaller

	// Logger is the structured logger. If nil, logging is silent.
	Logger *slog.Logger

	// Lifecycle is checked before service operations that can touch configured
	// backends. Any error returned by CheckClosed is returned without touching
	// those backends. The root synapse Client injects a shared checker whose
	// closed error matches ErrClosed. Nil is allowed for standalone use. A
	// typed-nil value is treated as nil.
	Lifecycle interface{ CheckClosed() error }
}

Options configures a Service.

type PaymentsReader

type PaymentsReader interface {
	AccountInfo(ctx context.Context, token, owner common.Address) (*payments.AccountState, error)
	ServiceApproval(ctx context.Context, token, client, operator common.Address) (*payments.OperatorApproval, error)
}

PaymentsReader provides payment account and operator approval state for cost estimates. The root Client supplies payments.Service. Successful reads return non-nil results.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service computes upload costs for the FWSS ecosystem. All methods are safe for concurrent use.

func New

func New(opts Options) (*Service, error)

New constructs a Service. WarmStorage, Payments and Caller reject nil and typed-nil values.

func (*Service) CalculateMultiContextCosts

func (s *Service) CalculateMultiContextCosts(
	ctx context.Context,
	payer common.Address,
	pieceSizes []uint64,
	refs []MultiContextRef,
	opts *UploadCostOptions,
) (*MultiContextCosts, error)

CalculateMultiContextCosts aggregates upload costs across multiple prospective contexts for one piece plan.

Each ref contributes its own lockup; debt, runway and buffer are computed once from the payer's account state.

pieceSizes contains each piece's raw payload size, replicated to every target. Each size must be between chain.MinUploadSize and chain.MaxUploadSize. Only ExtraRunwayEpochs and BufferEpochs are used from opts. EnableCDN, IsNewDataSet, and CurrentDataSetLeafCount in opts are ignored; supply the dataset state, current leaf count, and CDN setting through each ref. Nil opts uses defaults; a nil BufferEpochs uses DefaultBufferEpochs.

func (*Service) GetPriceList added in v0.4.0

func (s *Service) GetPriceList(ctx context.Context) (*warmstorage.PriceList, error)

GetPriceList delegates to the warmstorage service.

func (*Service) GetUploadCosts

func (s *Service) GetUploadCosts(
	ctx context.Context,
	payer common.Address,
	pieceSizes []uint64,
	opts *UploadCostOptions,
) (*UploadCosts, error)

GetUploadCosts returns cost and deposit information for an upload.

payer is the client address. pieceSizes contains each piece's raw payload size and must be non-empty; every size must be between chain.MinUploadSize and chain.MaxUploadSize. New datasets require opts.IsNewDataSet=true; existing datasets require a non-negative leaf count and complete lifecycle reserve state. Nil or empty opts therefore returns ErrInvalidArgument.

type UploadCostOptions

type UploadCostOptions struct {
	// ExtraRunwayEpochs is extra epoch runway on top of lockup. Defaults to 0.
	// Negative values return ErrInvalidArgument.
	ExtraRunwayEpochs int64
	// BufferEpochs is the deposit buffer for execution latency.
	// Nil uses DefaultBufferEpochs (5); a pointer to zero disables the buffer.
	// Negative values return ErrInvalidArgument.
	BufferEpochs *int64
	// EnableCDN adds CDN and cache-miss lockup for a new dataset.
	EnableCDN bool
	// IsNewDataSet must be true when creating a fresh dataset.
	IsNewDataSet bool
	// CurrentDataSetLeafCount is required and non-negative for an existing
	// dataset. Zero means known empty. Ignored when IsNewDataSet is true.
	CurrentDataSetLeafCount *big.Int
	// CurrentLifecycleReserveBalance is required and non-negative for an
	// existing dataset. Ignored when IsNewDataSet is true.
	CurrentLifecycleReserveBalance *big.Int
	// PendingOneTimePayments is the non-negative operation-fee total already
	// waiting to be paid from an existing dataset's reserve. Nil defaults to
	// zero. Ignored when IsNewDataSet is true.
	PendingOneTimePayments *big.Int
	// PDPEndEpoch is required for an existing dataset. It must point to zero;
	// a non-zero epoch means the dataset can no longer accept uploads. Ignored
	// when IsNewDataSet is true.
	PDPEndEpoch *types.Epoch
}

UploadCostOptions customises the GetUploadCosts calculation.

type UploadCosts

type UploadCosts struct {
	Rate                 EffectiveRate
	Fees                 UploadFees
	Lockup               AdditionalLockup
	DepositNeeded        *big.Int
	RequiredLockupPeriod *big.Int
	NeedsFWSSMaxApproval bool
	Ready                bool
}

UploadCosts is the result of GetUploadCosts.

type UploadFees added in v0.4.0

type UploadFees struct {
	CreateDataSetFee *big.Int
	AddPiecesFee     *big.Int
	Total            *big.Int
}

UploadFees is the one-time fee breakdown for an upload.

func CalculateUploadFees added in v0.4.0

func CalculateUploadFees(priceList *warmstorage.PriceList, isNewDataSet bool, pieceSizes []uint64) (UploadFees, error)

CalculateUploadFees computes a conservative one-time upload fee estimate from the price list. Every piece is priced as its own add-pieces operation because runtime batch boundaries cannot be inferred from raw sizes alone.

type WarmStorageReader

type WarmStorageReader interface {
	GetPriceList(ctx context.Context) (*warmstorage.PriceList, error)
}

WarmStorageReader provides the storage price list used by Service. The root Client supplies warmstorage.Service. Successful reads return a non-nil result.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL