diff --git a/benchmarks/token-deployment-cost.yml b/benchmarks/token-deployment-cost.yml index 6c00a430..38f3af8c 100644 --- a/benchmarks/token-deployment-cost.yml +++ b/benchmarks/token-deployment-cost.yml @@ -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?" @@ -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 //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." diff --git a/harnesses/token-deployment-cost/cmd/script/cosmos_cardano_stellar.go b/harnesses/token-deployment-cost/cmd/script/cosmos_cardano_stellar.go index 1aa7c5f6..061ae2fa 100644 --- a/harnesses/token-deployment-cost/cmd/script/cosmos_cardano_stellar.go +++ b/harnesses/token-deployment-cost/cmd/script/cosmos_cardano_stellar.go @@ -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 @@ -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 @@ -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 } diff --git a/harnesses/transaction-fee/cmd/script/cardano.go b/harnesses/transaction-fee/cmd/script/cardano.go index 4cf92d28..0c7bbec6 100644 --- a/harnesses/transaction-fee/cmd/script/cardano.go +++ b/harnesses/transaction-fee/cmd/script/cardano.go @@ -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)