From 15977f88d6fa94ac9ba27ff3e6f0433893d5c3b2 Mon Sep 17 00:00:00 2001
From: tac0turtle
Date: Mon, 19 Jan 2026 14:40:53 +0100
Subject: [PATCH 1/6] update calculator for posting strategies
---
.../components/CelestiaGasEstimator.vue | 220 +++++++++++++++---
docs/guides/celestia-gas-calculator.md | 28 ++-
docs/package.json | 2 +
3 files changed, 208 insertions(+), 42 deletions(-)
diff --git a/docs/.vitepress/components/CelestiaGasEstimator.vue b/docs/.vitepress/components/CelestiaGasEstimator.vue
index faad8909a5..b8248bcb1c 100644
--- a/docs/.vitepress/components/CelestiaGasEstimator.vue
+++ b/docs/.vitepress/components/CelestiaGasEstimator.vue
@@ -1,7 +1,7 @@
- Header cadence
+ Block production
Header size (bytes)
-
- Headers per submission
-
-
Block time
@@ -38,6 +28,101 @@
+
+ Blocks / second
+ {{ formatNumber(blocksPerSecond, 4) }}
+
+
+
+
+
+ Batching strategy
+
+ Controls how blocks are batched before submission to the DA layer. Different strategies offer trade-offs between latency, cost efficiency, and throughput.
+
+
+ Strategy
+
+ Immediate
+ Size-based
+ Time-based
+ Adaptive (Recommended)
+
+
+
+
+ Immediate: Submits as soon as any blocks are available. Best for low-latency requirements where cost is not a concern.
+
+
+ Size-based: Waits until the batch reaches a size threshold (fraction of max blob size). Best for maximizing blob utilization and minimizing costs when latency is flexible.
+
+
+ Time-based: Waits for a time interval before submitting. Provides predictable submission timing aligned with DA block times.
+
+
+ Adaptive: Balances between size and time constraints—submits when either the size threshold is reached OR the max delay expires. Recommended for most production deployments.
+
+
+
+
+
DA block time
+
+
+ seconds
+
+
+
+
+
Batch size threshold
+
+
+ of max blob ({{ formatNumber(batchSizeThreshold * 100, 0) }}%)
+
+
+
+
+
Batch max delay
+
+
+ seconds (0 = DA block time)
+
+
+
+
+ Batch minimum items
+
+
+
+
+
+ Effective max delay (s)
+ {{ formatNumber(effectiveMaxDelaySeconds, 2) }}
+
Headers / submission
{{ formatInteger(normalizedHeaderCount) }}
@@ -60,10 +145,6 @@
Submissions / minute
{{ formatNumber(submissionsPerMinute, 2) }}
-
- Blocks / second
- {{ formatNumber(blocksPerSecond, 4) }}
-
@@ -477,6 +558,7 @@ const FIRST_TX_SURCHARGE = 10_000;
const SECONDS_PER_MONTH = 30 * 24 * 60 * 60;
const SECONDS_PER_YEAR = 365 * 24 * 60 * 60;
const DATA_CHUNK_BYTES = 500 * 1024; // 500 KiB chunk limit per blob
+const MAX_BLOB_SIZE = 2 * 1024 * 1024; // 2 MiB max blob size for Celestia
const GAS_PARAMS = Object.freeze({
fixedCost: 65_000,
@@ -485,6 +567,7 @@ const GAS_PARAMS = Object.freeze({
shareSizeBytes: 482,
});
+type BatchingStrategy = "immediate" | "size" | "time" | "adaptive";
type ExecutionEnv = "evm" | "cosmos";
type EvmTxType = {
@@ -588,14 +671,12 @@ const evmMix = reactive
(
})),
);
-const headerCount = ref(15);
-const headerCountInput = computed({
- get: () => headerCount.value,
- set: (value: number) => {
- const sanitized = sanitizeInteger(value, 1);
- headerCount.value = sanitized;
- },
-});
+// Batching strategy configuration
+const batchingStrategy = ref("time");
+const daBlockTimeSeconds = ref(6); // Celestia default block time
+const batchSizeThreshold = ref(0.8); // 80% of max blob size
+const batchMaxDelaySeconds = ref(0); // 0 means use DA block time
+const batchMinItems = ref(1);
const blockTime = ref(0.25);
const blockTimeUnit = ref<"s" | "ms">("s");
@@ -624,26 +705,71 @@ const blockTimeSeconds = computed(() => {
return blockTimeUnit.value === "ms" ? value / 1000 : value;
});
-const normalizedHeaderCount = computed(() =>
- Math.max(
- 1,
- Math.round(isFinite(headerCount.value) ? headerCount.value : 1),
- ),
-);
-
-const headerBytesTotal = computed(
- () => normalizedHeaderCount.value * HEADER_BYTES,
-);
+// Effective max delay: use DA block time if batchMaxDelaySeconds is 0
+const effectiveMaxDelaySeconds = computed(() => {
+ if (batchMaxDelaySeconds.value <= 0) {
+ return daBlockTimeSeconds.value;
+ }
+ return batchMaxDelaySeconds.value;
+});
+// Calculate submission interval based on batching strategy
const submissionIntervalSeconds = computed(() => {
const blockSeconds = blockTimeSeconds.value;
- const count = normalizedHeaderCount.value;
- if (!isFinite(blockSeconds) || blockSeconds <= 0 || count <= 0) {
+ if (!isFinite(blockSeconds) || blockSeconds <= 0) {
return NaN;
}
- return blockSeconds * count;
+
+ const strategy = batchingStrategy.value;
+ const minItems = Math.max(1, batchMinItems.value);
+
+ if (strategy === "immediate") {
+ // Submit every block (respecting min items)
+ return blockSeconds * minItems;
+ }
+
+ if (strategy === "time") {
+ // Submit after max delay, but at least min items
+ const delayBlocks = Math.ceil(effectiveMaxDelaySeconds.value / blockSeconds);
+ return blockSeconds * Math.max(minItems, delayBlocks);
+ }
+
+ if (strategy === "size") {
+ // Estimate how many headers needed to reach size threshold
+ const targetBytes = MAX_BLOB_SIZE * batchSizeThreshold.value;
+ const headersToThreshold = Math.ceil(targetBytes / HEADER_BYTES);
+ return blockSeconds * Math.max(minItems, headersToThreshold);
+ }
+
+ if (strategy === "adaptive") {
+ // Adaptive: whichever comes first - size threshold or max delay
+ const delayBlocks = Math.ceil(effectiveMaxDelaySeconds.value / blockSeconds);
+ const targetBytes = MAX_BLOB_SIZE * batchSizeThreshold.value;
+ const headersToThreshold = Math.ceil(targetBytes / HEADER_BYTES);
+ // In practice, for headers, time-based usually triggers first
+ // Use the smaller of the two intervals
+ const timeBasedBlocks = Math.max(minItems, delayBlocks);
+ const sizeBasedBlocks = Math.max(minItems, headersToThreshold);
+ return blockSeconds * Math.min(timeBasedBlocks, sizeBasedBlocks);
+ }
+
+ return blockSeconds * minItems;
});
+// Calculate header count from submission interval
+const normalizedHeaderCount = computed(() => {
+ const blockSeconds = blockTimeSeconds.value;
+ const interval = submissionIntervalSeconds.value;
+ if (!isFinite(blockSeconds) || blockSeconds <= 0 || !isFinite(interval)) {
+ return 1;
+ }
+ return Math.max(1, Math.round(interval / blockSeconds));
+});
+
+const headerBytesTotal = computed(
+ () => normalizedHeaderCount.value * HEADER_BYTES,
+);
+
const submissionsPerSecond = computed(() => {
const interval = submissionIntervalSeconds.value;
if (!isFinite(interval) || interval <= 0) {
@@ -983,6 +1109,14 @@ function formatInteger(value: number) {
min-width: 120px;
}
+.field-group .unit-label {
+ display: flex;
+ align-items: center;
+ font-size: 0.85rem;
+ color: var(--vp-c-text-2);
+ white-space: nowrap;
+}
+
input,
select {
border: 1px solid var(--vp-c-divider);
@@ -1083,6 +1217,20 @@ button.ghost:hover {
color: var(--vp-c-text-2);
}
+.strategy-description {
+ margin: 0.5rem 0 1.25rem;
+ padding: 0.75rem 1rem;
+ background: var(--vp-c-bg);
+ border-radius: 8px;
+ border-left: 3px solid var(--vp-c-brand-1);
+}
+
+.strategy-description p {
+ margin: 0;
+ font-size: 0.9rem;
+ line-height: 1.5;
+}
+
strong {
font-variant-numeric: tabular-nums;
}
diff --git a/docs/guides/celestia-gas-calculator.md b/docs/guides/celestia-gas-calculator.md
index 23bef7ab8c..ed30ef5b76 100644
--- a/docs/guides/celestia-gas-calculator.md
+++ b/docs/guides/celestia-gas-calculator.md
@@ -12,13 +12,29 @@ Interactive calculator to estimate Celestia DA costs based on your rollup's bloc
## How it works
-The calculator is organized into four sections:
+The calculator is organized into five sections:
-### 1. Header cadence
+### 1. Block production
-Configure your rollup's block production rate and header batching strategy. Set how many headers you batch per submission—the tool automatically calculates the submission interval. For example, 15 headers at 250 ms block time means one submission every 3.75 seconds.
+Configure your rollup's block production rate. Set your block time (e.g., 250ms, 1s) to establish the base cadence of block production.
-### 2. Data workload
+### 2. Batching strategy
+
+Select how blocks are batched before submission to the DA layer. Four strategies are available:
+
+- **Immediate**: Submits as soon as any blocks are available. Best for low-latency requirements where cost is not a concern.
+- **Size-based**: Waits until the batch reaches a size threshold (fraction of max blob size). Best for maximizing blob utilization and minimizing costs when latency is flexible.
+- **Time-based**: Waits for a time interval before submitting. Provides predictable submission timing aligned with DA block times.
+- **Adaptive** (Recommended): Balances between size and time constraints—submits when either the size threshold is reached OR the max delay expires.
+
+Configure strategy parameters:
+
+- **DA block time**: The block time of the DA chain (default: 6s for Celestia)
+- **Batch size threshold**: For size/adaptive strategies, the fraction of max blob size to fill before submitting (default: 80%)
+- **Batch max delay**: For time/adaptive strategies, the maximum wait time before submitting (default: DA block time)
+- **Batch minimum items**: Minimum number of blocks to accumulate before submission
+
+### 3. Data workload
Model your transaction throughput and calldata usage:
@@ -29,7 +45,7 @@ The calculator translates your transaction rate and calldata into Celestia blob
For EVM workloads, data submissions are chunked into 500 KiB blobs (mirroring the batching logic in `da_submitter.go`). If a cadence produces more than 500 KiB of calldata in a window, the tool automatically simulates multiple blobs—and therefore multiple PayForBlobs transactions—so base gas and data gas scale accordingly.
-### 3. Gas parameters
+### 4. Gas parameters
Review the Celestia mainnet gas parameters used for calculations:
@@ -42,7 +58,7 @@ Set your expected gas price and optionally account for the one-time 10,000 gas s
> **Note**: Gas parameters are currently locked to Celestia mainnet defaults. Live parameter fetching and manual overrides will be added in a future update.
-### 4. Estimation
+### 5. Estimation
View comprehensive cost breakdowns including:
diff --git a/docs/package.json b/docs/package.json
index 21ef031739..b15fa649dd 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -3,7 +3,9 @@
"version": "1.0.0",
"license": "MIT",
"scripts": {
+ "predev": "[ -d node_modules ] || npm install",
"dev": "vitepress dev",
+ "prebuild": "[ -d node_modules ] || npm install",
"build": "vitepress build",
"preview": "vitepress preview"
},
From bc39aba6993611d0059d7d932eb1855b0aea3142 Mon Sep 17 00:00:00 2001
From: tac0turtle
Date: Mon, 19 Jan 2026 14:57:22 +0100
Subject: [PATCH 2/6] update calculator for strategy
---
.../components/CelestiaGasEstimator.vue | 295 +++++++++++-------
1 file changed, 190 insertions(+), 105 deletions(-)
diff --git a/docs/.vitepress/components/CelestiaGasEstimator.vue b/docs/.vitepress/components/CelestiaGasEstimator.vue
index b8248bcb1c..a517e5b897 100644
--- a/docs/.vitepress/components/CelestiaGasEstimator.vue
+++ b/docs/.vitepress/components/CelestiaGasEstimator.vue
@@ -79,17 +79,17 @@
-
Batch size threshold
+
Batch size threshold (%)
- of max blob ({{ formatNumber(batchSizeThreshold * 100, 0) }}%)
+ % of 7 MB max blob
@@ -118,34 +118,9 @@
/>
-
-
- Effective max delay (s)
- {{ formatNumber(effectiveMaxDelaySeconds, 2) }}
-
-
- Headers / submission
- {{ formatInteger(normalizedHeaderCount) }}
-
-
- Header bytes / submission
- {{ formatInteger(headerBytesTotal) }}
-
-
- Submission interval (s)
- {{
- formatNumber(submissionIntervalSeconds, 3)
- }}
-
-
- Submissions / second
- {{ formatNumber(submissionsPerSecond, 4) }}
-
-
- Submissions / minute
- {{ formatNumber(submissionsPerMinute, 2) }}
-
-
+
+ Header and data submission rates are shown in the Estimation section below, based on your data workload configuration.
+
@@ -414,6 +389,22 @@
Header costs
+
+ Header submission interval (s)
+ {{ formatNumber(headerSubmissionIntervalSeconds, 2) }}
+
+
+ Headers / submission
+ {{ formatInteger(normalizedHeaderCount) }}
+
+
+ Header bytes / submission
+ {{ formatInteger(headerBytesTotal) }}
+
+
+ Header submissions / year
+ {{ formatNumber(headerSubmissionsPerYear, 0) }}
+
Header gas / submission
{{ formatInteger(headerGas) }}
@@ -441,33 +432,43 @@
- Average calldata bytes / tx
+ Data bytes / second
{{
- formatNumber(averageCalldataBytes, 2)
+ formatNumber(dataBytesPerSecond, 2)
}}
- Data blobs / submission
- {{ formatInteger(dataBlobCount) }}
+ Data submission interval (s)
+ {{
+ formatNumber(dataSubmissionIntervalSeconds, 2)
+ }}
- Average blob size (bytes)
+ Data submissions / year
{{
- formatNumber(averageDataBlobBytes, 2)
+ formatNumber(dataSubmissionsPerYear, 0)
}}
- Data bytes / submission
+ Average calldata bytes / tx
{{
- formatNumber(dataBytesPerSubmission, 2)
+ formatNumber(averageCalldataBytes, 2)
}}
- Data shares / submission
+ Transactions / data submission
+ {{ formatNumber(transactionsPerSubmission, 0) }}
+
+
+ Data bytes / submission
{{
- formatInteger(dataSharesPerSubmission)
+ formatNumber(dataBytesPerSubmission, 0)
}}
+
+ Data blobs / submission
+ {{ formatInteger(dataBlobCount) }}
+
Data gas / submission
{{
@@ -558,7 +559,7 @@ const FIRST_TX_SURCHARGE = 10_000;
const SECONDS_PER_MONTH = 30 * 24 * 60 * 60;
const SECONDS_PER_YEAR = 365 * 24 * 60 * 60;
const DATA_CHUNK_BYTES = 500 * 1024; // 500 KiB chunk limit per blob
-const MAX_BLOB_SIZE = 2 * 1024 * 1024; // 2 MiB max blob size for Celestia
+const MAX_BLOB_SIZE = 7 * 1024 * 1024; // 7 MB max blob size (from common/consts.go)
const GAS_PARAMS = Object.freeze({
fixedCost: 65_000,
@@ -674,10 +675,19 @@ const evmMix = reactive(
// Batching strategy configuration
const batchingStrategy = ref("time");
const daBlockTimeSeconds = ref(6); // Celestia default block time
-const batchSizeThreshold = ref(0.8); // 80% of max blob size
+const batchSizeThreshold = ref(0.8); // 80% of max blob size (internal: 0.0-1.0)
const batchMaxDelaySeconds = ref(0); // 0 means use DA block time
const batchMinItems = ref(1);
+// User-facing percentage (10-100) that syncs with internal threshold (0.1-1.0)
+const batchSizeThresholdPercent = computed({
+ get: () => Math.round(batchSizeThreshold.value * 100),
+ set: (value: number) => {
+ const clamped = Math.max(10, Math.min(100, value));
+ batchSizeThreshold.value = clamped / 100;
+ },
+});
+
const blockTime = ref(0.25);
const blockTimeUnit = ref<"s" | "ms">("s");
const firstTx = ref(false);
@@ -713,8 +723,12 @@ const effectiveMaxDelaySeconds = computed(() => {
return batchMaxDelaySeconds.value;
});
-// Calculate submission interval based on batching strategy
-const submissionIntervalSeconds = computed(() => {
+// Target bytes for size-based batching
+const targetBlobBytes = computed(() => MAX_BLOB_SIZE * batchSizeThreshold.value);
+
+// ===== HEADER SUBMISSION INTERVAL =====
+// Calculate header submission interval based on batching strategy
+const headerSubmissionIntervalSeconds = computed(() => {
const blockSeconds = blockTimeSeconds.value;
if (!isFinite(blockSeconds) || blockSeconds <= 0) {
return NaN;
@@ -722,44 +736,42 @@ const submissionIntervalSeconds = computed(() => {
const strategy = batchingStrategy.value;
const minItems = Math.max(1, batchMinItems.value);
+ const headerBytesPerBlock = HEADER_BYTES;
if (strategy === "immediate") {
- // Submit every block (respecting min items)
return blockSeconds * minItems;
}
if (strategy === "time") {
- // Submit after max delay, but at least min items
const delayBlocks = Math.ceil(effectiveMaxDelaySeconds.value / blockSeconds);
return blockSeconds * Math.max(minItems, delayBlocks);
}
if (strategy === "size") {
- // Estimate how many headers needed to reach size threshold
- const targetBytes = MAX_BLOB_SIZE * batchSizeThreshold.value;
- const headersToThreshold = Math.ceil(targetBytes / HEADER_BYTES);
- return blockSeconds * Math.max(minItems, headersToThreshold);
+ // How many blocks until headers fill the target blob size
+ const blocksToThreshold = Math.ceil(targetBlobBytes.value / headerBytesPerBlock);
+ return blockSeconds * Math.max(minItems, blocksToThreshold);
}
if (strategy === "adaptive") {
- // Adaptive: whichever comes first - size threshold or max delay
const delayBlocks = Math.ceil(effectiveMaxDelaySeconds.value / blockSeconds);
- const targetBytes = MAX_BLOB_SIZE * batchSizeThreshold.value;
- const headersToThreshold = Math.ceil(targetBytes / HEADER_BYTES);
- // In practice, for headers, time-based usually triggers first
- // Use the smaller of the two intervals
- const timeBasedBlocks = Math.max(minItems, delayBlocks);
- const sizeBasedBlocks = Math.max(minItems, headersToThreshold);
- return blockSeconds * Math.min(timeBasedBlocks, sizeBasedBlocks);
+ const blocksToThreshold = Math.ceil(targetBlobBytes.value / headerBytesPerBlock);
+ return blockSeconds * Math.min(
+ Math.max(minItems, delayBlocks),
+ Math.max(minItems, blocksToThreshold)
+ );
}
return blockSeconds * minItems;
});
+// For backward compatibility, alias to submissionIntervalSeconds
+const submissionIntervalSeconds = headerSubmissionIntervalSeconds;
+
// Calculate header count from submission interval
const normalizedHeaderCount = computed(() => {
const blockSeconds = blockTimeSeconds.value;
- const interval = submissionIntervalSeconds.value;
+ const interval = headerSubmissionIntervalSeconds.value;
if (!isFinite(blockSeconds) || blockSeconds <= 0 || !isFinite(interval)) {
return 1;
}
@@ -770,19 +782,20 @@ const headerBytesTotal = computed(
() => normalizedHeaderCount.value * HEADER_BYTES,
);
-const submissionsPerSecond = computed(() => {
- const interval = submissionIntervalSeconds.value;
+const headerSubmissionsPerSecond = computed(() => {
+ const interval = headerSubmissionIntervalSeconds.value;
if (!isFinite(interval) || interval <= 0) {
return 0;
}
return 1 / interval;
});
-const submissionsPerMinute = computed(() => submissionsPerSecond.value * 60);
-
-const submissionsPerYear = computed(
- () => submissionsPerSecond.value * SECONDS_PER_YEAR,
+const submissionsPerSecond = headerSubmissionsPerSecond; // alias
+const submissionsPerMinute = computed(() => headerSubmissionsPerSecond.value * 60);
+const headerSubmissionsPerYear = computed(
+ () => headerSubmissionsPerSecond.value * SECONDS_PER_YEAR,
);
+const submissionsPerYear = headerSubmissionsPerYear; // alias
const blocksPerSecond = computed(() => {
const seconds = blockTimeSeconds.value;
@@ -876,8 +889,71 @@ const averageCalldataBytes = computed(() => {
const txPerMonth = computed(() => txPerSecond.value * SECONDS_PER_MONTH);
const txPerYear = computed(() => txPerSecond.value * SECONDS_PER_YEAR);
+// Data bytes generated per second
+const dataBytesPerSecond = computed(() => {
+ if (executionEnv.value !== "evm") {
+ return 0;
+ }
+ return txPerSecond.value * averageCalldataBytes.value;
+});
+
+// ===== DATA SUBMISSION INTERVAL =====
+// Calculate data submission interval based on batching strategy
+const dataSubmissionIntervalSeconds = computed(() => {
+ const blockSeconds = blockTimeSeconds.value;
+ const bytesPerSecond = dataBytesPerSecond.value;
+
+ if (!isFinite(blockSeconds) || blockSeconds <= 0) {
+ return NaN;
+ }
+
+ // If no data throughput, fall back to header interval
+ if (bytesPerSecond <= 0) {
+ return headerSubmissionIntervalSeconds.value;
+ }
+
+ const strategy = batchingStrategy.value;
+ const minItems = Math.max(1, batchMinItems.value);
+ const minInterval = blockSeconds * minItems;
+
+ if (strategy === "immediate") {
+ return minInterval;
+ }
+
+ if (strategy === "time") {
+ return Math.max(minInterval, effectiveMaxDelaySeconds.value);
+ }
+
+ if (strategy === "size") {
+ // Time to accumulate enough data to reach size threshold
+ const timeToThreshold = targetBlobBytes.value / bytesPerSecond;
+ return Math.max(minInterval, timeToThreshold);
+ }
+
+ if (strategy === "adaptive") {
+ // Whichever comes first: size threshold or max delay
+ const timeToThreshold = targetBlobBytes.value / bytesPerSecond;
+ return Math.max(minInterval, Math.min(timeToThreshold, effectiveMaxDelaySeconds.value));
+ }
+
+ return minInterval;
+});
+
+const dataSubmissionsPerSecond = computed(() => {
+ const interval = dataSubmissionIntervalSeconds.value;
+ if (!isFinite(interval) || interval <= 0) {
+ return 0;
+ }
+ return 1 / interval;
+});
+
+const dataSubmissionsPerYear = computed(
+ () => dataSubmissionsPerSecond.value * SECONDS_PER_YEAR,
+);
+
+// Transactions included per data submission
const transactionsPerSubmission = computed(() => {
- const interval = submissionIntervalSeconds.value;
+ const interval = dataSubmissionIntervalSeconds.value;
if (!isFinite(interval) || interval <= 0) {
return 0;
}
@@ -933,77 +1009,75 @@ const averageDataBlobBytes = computed(() => {
const gasPriceUTIA = computed(() => Math.max(gasPriceValue.value, 0));
const gasPriceTIA = computed(() => gasPriceUTIA.value / 1_000_000);
-const headerTransactionCount = computed(() =>
- normalizedHeaderCount.value > 0 ? 1 : 0,
-);
-
-const totalTransactionsPerSubmission = computed(
- () => headerTransactionCount.value + dataBlobCount.value,
-);
+// ===== HEADER COSTS =====
+// 1 PFB transaction per header submission
+const headerFixedGasPerSubmission = computed(() => GAS_PARAMS.fixedCost);
-const fixedGasPerSubmission = computed(
- () => totalTransactionsPerSubmission.value * GAS_PARAMS.fixedCost,
+const headerFeePerSubmissionTIA = computed(
+ () => (headerGas.value + headerFixedGasPerSubmission.value) * gasPriceTIA.value,
);
-const fixedFeePerSubmissionTIA = computed(
- () => fixedGasPerSubmission.value * gasPriceTIA.value,
+const headerFeePerYearTIA = computed(
+ () => headerFeePerSubmissionTIA.value * headerSubmissionsPerYear.value,
);
-const headerFeePerSubmissionTIA = computed(
- () => headerGas.value * gasPriceTIA.value,
+// ===== DATA COSTS =====
+// Each data submission may have multiple blobs (chunks), each is a separate PFB
+const dataFixedGasPerSubmission = computed(
+ () => Math.max(1, dataBlobCount.value) * GAS_PARAMS.fixedCost,
);
const dataRecurringGasPerSubmission = computed(
- () => dataGasPerSubmission.value + dataStaticGasPerSubmission.value,
+ () => dataGasPerSubmission.value + dataStaticGasPerSubmission.value + dataFixedGasPerSubmission.value,
);
const dataFeePerSubmissionTIA = computed(
() => dataRecurringGasPerSubmission.value * gasPriceTIA.value,
);
-const recurringGasPerSubmission = computed(
- () =>
- fixedGasPerSubmission.value +
- headerGas.value +
- dataRecurringGasPerSubmission.value,
+const dataFeePerYearTIA = computed(
+ () => dataFeePerSubmissionTIA.value * dataSubmissionsPerYear.value,
);
+// ===== TOTALS =====
const firstTxGas = computed(() => (firstTx.value ? FIRST_TX_SURCHARGE : 0));
const firstTxFeeTIA = computed(() => firstTxGas.value * gasPriceTIA.value);
+// Combined totals (for display, assumes one combined "submission" event)
const totalGasPerSubmission = computed(
- () => recurringGasPerSubmission.value + firstTxGas.value,
+ () => headerGas.value + headerFixedGasPerSubmission.value +
+ dataRecurringGasPerSubmission.value + firstTxGas.value,
);
const totalFeePerSubmissionTIA = computed(
() => totalGasPerSubmission.value * gasPriceTIA.value,
);
-const headerFeePerYearTIA = computed(
- () => headerFeePerSubmissionTIA.value * submissionsPerYear.value,
+// Backward compat aliases
+const fixedGasPerSubmission = computed(
+ () => headerFixedGasPerSubmission.value + dataFixedGasPerSubmission.value,
);
-
-const dataFeePerYearTIA = computed(
- () => dataFeePerSubmissionTIA.value * submissionsPerYear.value,
+const fixedFeePerSubmissionTIA = computed(
+ () => fixedGasPerSubmission.value * gasPriceTIA.value,
+);
+const totalTransactionsPerSubmission = computed(
+ () => 1 + dataBlobCount.value, // 1 header PFB + N data PFBs
);
const fixedFeePerYearTIA = computed(
- () => fixedFeePerSubmissionTIA.value * submissionsPerYear.value,
+ () => (headerFixedGasPerSubmission.value * headerSubmissionsPerYear.value +
+ dataFixedGasPerSubmission.value * dataSubmissionsPerYear.value) * gasPriceTIA.value,
);
const totalRecurringFeePerYearTIA = computed(
- () =>
- headerFeePerYearTIA.value +
- dataFeePerYearTIA.value +
- fixedFeePerYearTIA.value,
+ () => headerFeePerYearTIA.value + dataFeePerYearTIA.value,
);
const feePerSecondTIA = computed(() => {
- const interval = submissionIntervalSeconds.value;
- if (!isFinite(interval) || interval <= 0) {
- return 0;
- }
- return (recurringGasPerSubmission.value * gasPriceTIA.value) / interval;
+ // Sum of header fee rate + data fee rate
+ const headerFeePerSecond = headerFeePerSubmissionTIA.value * headerSubmissionsPerSecond.value;
+ const dataFeePerSecond = dataFeePerSubmissionTIA.value * dataSubmissionsPerSecond.value;
+ return headerFeePerSecond + dataFeePerSecond;
});
function randomizeMix() {
@@ -1183,6 +1257,17 @@ button.ghost:hover {
font-size: 0.95rem;
}
+.derived-header {
+ font-weight: 600;
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--vp-c-text-2);
+ margin-top: 0.5rem;
+ padding-top: 0.5rem;
+ border-top: 1px solid var(--vp-c-divider);
+}
+
.param-list {
margin-bottom: 1.5rem;
}
From ac013a5b3b805ef8a278c4dc155d0d07b97e1a13 Mon Sep 17 00:00:00 2001
From: tac0turtle
Date: Mon, 19 Jan 2026 15:06:18 +0100
Subject: [PATCH 3/6] address comment
---
docs/.vitepress/components/CelestiaGasEstimator.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/.vitepress/components/CelestiaGasEstimator.vue b/docs/.vitepress/components/CelestiaGasEstimator.vue
index a517e5b897..d859b68c70 100644
--- a/docs/.vitepress/components/CelestiaGasEstimator.vue
+++ b/docs/.vitepress/components/CelestiaGasEstimator.vue
@@ -1024,7 +1024,7 @@ const headerFeePerYearTIA = computed(
// ===== DATA COSTS =====
// Each data submission may have multiple blobs (chunks), each is a separate PFB
const dataFixedGasPerSubmission = computed(
- () => Math.max(1, dataBlobCount.value) * GAS_PARAMS.fixedCost,
+ () => dataBlobCount.value * GAS_PARAMS.fixedCost,
);
const dataRecurringGasPerSubmission = computed(
From be04ed94365ecdb6c7ba5ee42794b323c581b312 Mon Sep 17 00:00:00 2001
From: tac0turtle
Date: Mon, 19 Jan 2026 15:21:54 +0100
Subject: [PATCH 4/6] test action flow
---
.github/workflows/docs_deploy.yml | 3 ---
1 file changed, 3 deletions(-)
diff --git a/.github/workflows/docs_deploy.yml b/.github/workflows/docs_deploy.yml
index 541ef29f76..e3685e237c 100644
--- a/.github/workflows/docs_deploy.yml
+++ b/.github/workflows/docs_deploy.yml
@@ -13,9 +13,6 @@ on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
-# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
-permissions: write-all
-
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
From fbe9807e6dc81ed8d5e08a2a1c1fe53b53a9adc6 Mon Sep 17 00:00:00 2001
From: tac0turtle
Date: Mon, 19 Jan 2026 15:58:40 +0100
Subject: [PATCH 5/6] minor change
---
.../components/CelestiaGasEstimator.vue | 89 +++++++++----------
1 file changed, 40 insertions(+), 49 deletions(-)
diff --git a/docs/.vitepress/components/CelestiaGasEstimator.vue b/docs/.vitepress/components/CelestiaGasEstimator.vue
index d859b68c70..de5bc84455 100644
--- a/docs/.vitepress/components/CelestiaGasEstimator.vue
+++ b/docs/.vitepress/components/CelestiaGasEstimator.vue
@@ -364,14 +364,10 @@
Estimation
-
- Total gas / submission
- {{ formatInteger(totalGasPerSubmission) }}
-
-
-
Fee / submission (TIA)
+
+ Total yearly fee (TIA)
{{
- formatNumber(totalFeePerSubmissionTIA, 6)
+ formatNumber(totalRecurringFeePerYearTIA, 4)
}}
@@ -379,10 +375,12 @@
{{ formatNumber(feePerSecondTIA, 6) }}
- Total yearly fee (TIA)
- {{
- formatNumber(totalRecurringFeePerYearTIA, 4)
- }}
+ Header fee / year (TIA)
+ {{ formatNumber(headerFeePerYearTIA, 4) }}
+
+
+ Data fee / year (TIA)
+ {{ formatNumber(dataFeePerYearTIA, 4) }}
@@ -490,29 +488,31 @@
-
- Baseline gas
+
+ Fixed costs (PFB base gas)
- PFB transactions / submission
- {{
- formatInteger(totalTransactionsPerSubmission)
- }}
+ Header PFB base gas
+ {{ formatInteger(GAS_PARAMS.fixedCost) }} gas
- Base gas / submission
+ Header fixed fee / year (TIA)
{{
- formatInteger(fixedGasPerSubmission)
+ formatNumber(headerFixedGasPerSubmission * headerSubmissionsPerYear * gasPriceTIA, 4)
}}
- Base fee / submission (TIA)
+ Data blobs / submission
+ {{ formatInteger(dataBlobCount) }}
+
+
+ Data fixed fee / year (TIA)
{{
- formatNumber(fixedFeePerSubmissionTIA, 6)
+ formatNumber(dataFixedGasPerSubmission * dataSubmissionsPerYear * gasPriceTIA, 4)
}}
- Base fee / year (TIA)
+ Total fixed fee / year (TIA)
{{
formatNumber(fixedFeePerYearTIA, 4)
}}
@@ -531,18 +531,20 @@
Transactions per second
{{ formatNumber(txPerSecond, 4) }}
-
- Transactions per month
- {{ formatNumber(txPerMonth, 0) }}
-
Transactions per year
{{ formatNumber(txPerYear, 0) }}
- Submissions per year
+ Header submissions / year
+ {{
+ formatNumber(headerSubmissionsPerYear, 0)
+ }}
+
+
+ Data submissions / year
{{
- formatNumber(submissionsPerYear, 0)
+ formatNumber(dataSubmissionsPerYear, 0)
}}
@@ -1043,27 +1045,6 @@ const dataFeePerYearTIA = computed(
const firstTxGas = computed(() => (firstTx.value ? FIRST_TX_SURCHARGE : 0));
const firstTxFeeTIA = computed(() => firstTxGas.value * gasPriceTIA.value);
-// Combined totals (for display, assumes one combined "submission" event)
-const totalGasPerSubmission = computed(
- () => headerGas.value + headerFixedGasPerSubmission.value +
- dataRecurringGasPerSubmission.value + firstTxGas.value,
-);
-
-const totalFeePerSubmissionTIA = computed(
- () => totalGasPerSubmission.value * gasPriceTIA.value,
-);
-
-// Backward compat aliases
-const fixedGasPerSubmission = computed(
- () => headerFixedGasPerSubmission.value + dataFixedGasPerSubmission.value,
-);
-const fixedFeePerSubmissionTIA = computed(
- () => fixedGasPerSubmission.value * gasPriceTIA.value,
-);
-const totalTransactionsPerSubmission = computed(
- () => 1 + dataBlobCount.value, // 1 header PFB + N data PFBs
-);
-
const fixedFeePerYearTIA = computed(
() => (headerFixedGasPerSubmission.value * headerSubmissionsPerYear.value +
dataFixedGasPerSubmission.value * dataSubmissionsPerYear.value) * gasPriceTIA.value,
@@ -1585,6 +1566,16 @@ strong {
font-size: 1.1rem;
}
+.summary-item.highlight {
+ background: var(--vp-c-brand-soft);
+ border-color: var(--vp-c-brand-1);
+}
+
+.summary-item.highlight strong {
+ font-size: 1.25rem;
+ color: var(--vp-c-brand-1);
+}
+
/* Details/summary elements */
details {
margin-bottom: 1.5rem;
From 52a9a6f75f2611cd391d4a6ed67bc60f1b5951cb Mon Sep 17 00:00:00 2001
From: tac0turtle
Date: Mon, 19 Jan 2026 16:06:52 +0100
Subject: [PATCH 6/6] add token
---
.github/workflows/docs_deploy.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docs_deploy.yml b/.github/workflows/docs_deploy.yml
index e3685e237c..2b00f64960 100644
--- a/.github/workflows/docs_deploy.yml
+++ b/.github/workflows/docs_deploy.yml
@@ -46,6 +46,6 @@ jobs:
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
+ github_token: ${{ secrets.DOCS_DEPLOY_TOKEN }}
publish_dir: ./docs/.vitepress/dist
cname: ev.xyz