Skip to content
Merged
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
4 changes: 2 additions & 2 deletions benchmarks/token-deployment-cost.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ seo_description: "Live USD cost to create a fungible token on 7 non-EVM L1 chain
subtitle: "Live USD cost to bring a fungible token into existence on each chain, using that chain's canonical method. 7 non-EVM L1 chains, 5 minute refresh, no transactions broadcast."

seo_intro: |
This page answers a question every memecoin founder, RWA issuer and non-EVM L1 marketing team asks. How much does it actually cost in dollars to create a token on each non-EVM chain right now. We track 7 non-EVM L1 chains in parallel and refresh the number every 5 minutes. Solana uses the SPL mint plus the Associated Token Account plus the Metaplex metadata account so the comparison is fair against ERC20 which embeds name and symbol natively. Sui and Aptos use Move publish gas budgets. Cardano reads coins_per_utxo_size from koios and applies the Conway minUTxO formula. Stellar uses the canonical issuer plus distribution plus trustline two account flow. Cosmos TokenFactory chains (Osmosis, Injective) read denom_creation_fee from the LCD. Every number is reproducible from source. No transactions are broadcast. EVM chains are excluded from this leaderboard for now while we finalise the canonical OpenZeppelin v5 ERC20 artifact bytecode used in eth_estimateGas.
This page answers a question every memecoin founder, RWA issuer and non-EVM L1 marketing team asks. How much does it actually cost in dollars to create a token on each non-EVM chain right now. We track 7 non-EVM L1 chains in parallel and refresh the number every 5 minutes. Solana uses the SPL mint plus the Associated Token Account plus the Metaplex metadata account so the comparison is fair against ERC20 which embeds name and symbol natively. Sui and Aptos use Move publish gas budgets. Cardano reads coins_per_utxo_size from koios and applies the Conway minUTxO formula. Stellar uses the canonical issuer plus distribution plus trustline two account flow at protocol-minimum reserves. Cosmos TokenFactory chains (Osmosis, Injective) read denom_creation_fee from the LCD. Every number is reproducible from source. No transactions are broadcast. EVM chains are excluded from this leaderboard for now while we finalise the canonical OpenZeppelin v5 ERC20 artifact bytecode used in eth_estimateGas.

faq:
- q: "What does this benchmark measure?"
Expand Down Expand Up @@ -58,7 +58,7 @@ methodology:
- "Aptos. /v1/estimate_gas_price.gas_estimate octas per unit times 150000 gas units for a canonical FA standard publish. Converted to USD via APT price."
- "Cosmos TokenFactory (Osmosis, Injective). LCD /<chain>/tokenfactory/v1beta1/params.denom_creation_fee. Empty array (Osmosis) means gas only, rendered as less than 0.001 dollar. Injective charges a flat 0.1 INJ."
- "Cardano. Koios /epoch_params.coins_per_utxo_size. Apply Conway minUTxO formula = (160 byte overhead + 70 byte single asset bundle) × coins_per_utxo_size + 180000 lovelace mint tx fee. Converted to USD via ADA price."
- "Stellar. Horizon /ledgers.base_reserve_in_stroops and base_fee_in_stroops on the latest ledger. Total cost = 3 base reserves (issuer account + distribution account + trustline on distribution) + 2 base fees (create_account + change_trust). Converted to USD via XLM price."
- "Stellar. Horizon /ledgers.base_reserve_in_stroops and base_fee_in_stroops on the latest ledger. Total cost = 5 base reserves (the protocol minimum of 2 per new account for issuer and distribution, plus 1 for the trustline) + 2 base fees (create_account + change_trust). Reserves are locked capital, refundable on account closure, counted here because bringing the asset into existence requires them upfront. Converted to USD via XLM price."
- "USD prices. api.mobula.io/api/1/market/multi-data?symbols=SOL,SUI,APT,OSMO,INJ,ADA,XLM polled every 5 minutes."
- "Failures. Any upstream error increments token_deployment_samples_total{chain,status=error} and leaves the previous gauge value in place."
- "EVM chains excluded. Ethereum, BNB Chain, Avalanche, Polygon, Arbitrum, Optimism, Base, Blast, Mantle, opBNB, Celo, Scroll and Linea were previously exposed but relied on a placeholder init bytecode that undercounted real ERC20 deploy cost by 30 to 500x. Removed until the canonical OZ v5 artifact is finalised."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,18 @@ type koiosEpochParams []struct {
CoinsPerUtxoSize json.Number `json:"coins_per_utxo_size"`
}

// Cached for an hour: epoch params move once per 5-day epoch and the
// shared koios public tier (5k/day per IP) is also consumed by the
// transaction-fee harness on this host.
var cardanoCostCache struct {
sample Sample
fetchedAt time.Time
}

func (s *cardanoSampler) Sample(ch ChainConfig) (Sample, error) {
if time.Since(cardanoCostCache.fetchedAt) < time.Hour && cardanoCostCache.sample.CostNative > 0 {
return cardanoCostCache.sample, nil
}
resp, err := s.http.Get(ch.RPCURL + "/epoch_params")
if err != nil {
return Sample{}, err
Expand All @@ -200,13 +211,16 @@ func (s *cardanoSampler) Sample(ch ChainConfig) (Sample, error) {
}
utxoSize := float64(cardanoEntryOverheadBytes + cardanoBundleBytes)
lovelace := coins*utxoSize + cardanoMintTxFeeLovelace
return Sample{CostNative: lovelace, NativeUnit: "lovelace", GasUnits: math.NaN()}, nil
out := Sample{CostNative: lovelace, NativeUnit: "lovelace", GasUnits: math.NaN()}
cardanoCostCache.sample = out
cardanoCostCache.fetchedAt = time.Now()
return out, nil
}

// ----- Stellar ----------------------------------------------------------

// Stellar custom asset cost is 3 base reserves locked (issuer account +
// distribution account + trustline on distribution) + 2 tx fees
// Stellar custom asset cost is 5 base reserves locked (2 per new
// account for issuer + distribution, 1 for the trustline) + 2 tx fees
// (create_account + change_trust). base_reserve_in_stroops and
// base_fee_in_stroops are in /ledgers — we use the latest ledger.
// Reusing the issuer as the distribution account is an anti-pattern
Expand Down Expand Up @@ -240,8 +254,11 @@ func (s *stellarSampler) Sample(ch ChainConfig) (Sample, error) {
return Sample{}, fmt.Errorf("empty ledgers response")
}
rec := l.Embedded.Records[0]
// 3 × base_reserve (issuer + distribution + trustline) + 2 × base_fee
// (create_account + change_trust).
stroops := 3*rec.BaseReserve + 2*rec.BaseFee
// 5 × base_reserve + 2 × base_fee (create_account + change_trust).
// Protocol minimums: every new account locks 2 base reserves and
// each trustline adds 1, so issuer (2) + distribution (2) +
// trustline on distribution (1) = 5. The previous formula counted
// 3 and understated the locked capital by a full XLM.
stroops := 5*rec.BaseReserve + 2*rec.BaseFee
return Sample{CostNative: float64(stroops), NativeUnit: "stroop", GasUnits: math.NaN()}, nil
}
17 changes: 17 additions & 0 deletions harnesses/transaction-fee/cmd/script/cardano.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,25 @@ type koiosEpochParams struct {
MinFeeB float64 `json:"min_fee_b"`
}

// Koios epoch params change once per 5-day epoch, but this fetcher runs
// on the 30s harness cycle. Caching for an hour keeps the total koios
// load around 24 calls/day (both OCB harnesses combined stay far under
// the 5,000/day public tier that we exhausted before this cache).
var cardanoParamCache struct {
minFeeA, minFeeB float64
fetchedAt time.Time
}

func (f *cardanoFetcher) Sample(ch ChainConfig) ([]FeeSample, error) {
if time.Since(cardanoParamCache.fetchedAt) < time.Hour && cardanoParamCache.minFeeA > 0 {
feeLovelace := cardanoParamCache.minFeeB + (cardanoTypicalTxBytes * cardanoParamCache.minFeeA)
return []FeeSample{{Chain: ch.Slug, Tier: "single", NativeFee: feeLovelace}}, nil
}
minFeeA, minFeeB, err := f.fetchProtocolParams(ch.RPCURL)
if err == nil {
cardanoParamCache.minFeeA, cardanoParamCache.minFeeB = minFeeA, minFeeB
cardanoParamCache.fetchedAt = time.Now()
}
if err != nil {
// Fall back to known-good constants rather than emit nothing.
fmt.Printf("[cardano] koios fetch failed, using fallback constants: %v\n", err)
Expand Down
Loading