Try it Live
Run Blob examples in the interactive playground
L2 Rollup Data Posting
Basic L2 Batch
Copy
Ask AI
import { Blob } from 'tevm';
class L2Rollup {
async postBatch(transactions: Transaction[]) {
// 1. Serialize batch data
const batchData = this.serializeBatch(transactions);
// 2. Create blob
const blob = Blob.fromData(batchData);
// 3. Generate KZG commitment and proof
const commitment = Blob.toCommitment(blob);
const proof = Blob.toProof(blob);
const versionedHash = Blob.Commitment.toVersionedHash(commitment);
// 4. Create blob transaction
const tx = await this.createBlobTransaction({
blobVersionedHashes: [versionedHash],
blobs: [blob],
commitments: [commitment],
proofs: [proof],
});
// 5. Send transaction
const receipt = await this.sendTransaction(tx);
// 6. Store blob data locally (for ~18 day retention)
await this.storeBlob(receipt.blockNumber, blob);
return receipt;
}
private serializeBatch(transactions: Transaction[]): Uint8Array {
return new TextEncoder().encode(JSON.stringify(transactions));
}
private async storeBlob(blockNumber: number, blob: Blob) {
const data = Blob.toData(blob);
await this.db.put(`blob:${blockNumber}`, data);
}
}
Multi-Blob Batch
Copy
Ask AI
import { Blob } from 'tevm';
class L2Rollup {
async postLargeBatch(transactions: Transaction[]) {
const batchData = this.serializeBatch(transactions);
// Check blob count
const blobCount = Blob.estimateBlobCount(batchData);
if (blobCount > Blob.MAX_PER_TRANSACTION) {
throw new Error(`Batch too large: ${blobCount} blobs`);
}
// Split into blobs
const blobs = Blob.splitData(batchData);
// Generate commitments, proofs, hashes
const commitments = blobs.map(b => Blob.toCommitment(b));
const proofs = blobs.map(b => Blob.toProof(b));
const versionedHashes = commitments.map(c =>
Blob.Commitment.toVersionedHash(c)
);
// Batch verify before sending
if (!Blob.verifyBatch(blobs, commitments, proofs)) {
throw new Error('Proof verification failed');
}
// Create transaction
const tx = await this.createBlobTransaction({
blobVersionedHashes: versionedHashes,
blobs: blobs,
commitments: commitments,
proofs: proofs,
});
return await this.sendTransaction(tx);
}
}
Cost Optimization
Dynamic Blob vs Calldata
Copy
Ask AI
import { Blob } from 'tevm';
class CostOptimizer {
async selectDataAvailability(
data: Uint8Array,
provider: any
): Promise<'blob' | 'calldata'> {
// Get current gas prices
const blobBaseFee = await provider.getBlobBaseFee();
const executionBaseFee = (await provider.getFeeData()).gasPrice;
// Calculate blob cost
const blobCount = Blob.estimateBlobCount(data);
const blobGas = Blob.calculateGas(blobCount);
const blobCost = BigInt(blobGas) * blobBaseFee;
// Calculate calldata cost
const calldataGas = BigInt(data.length) * 16n; // 16 gas per byte
const calldataCost = calldataGas * executionBaseFee;
// Choose cheaper option
if (blobCost < calldataCost) {
console.log(`Using blobs (${((1 - Number(blobCost) / Number(calldataCost)) * 100).toFixed(1)}% cheaper)`);
return 'blob';
} else {
console.log('Using calldata (blobs more expensive)');
return 'calldata';
}
}
async postData(data: Uint8Array, provider: any) {
const method = await this.selectDataAvailability(data, provider);
if (method === 'blob') {
return await this.postAsBlob(data);
} else {
return await this.postAsCalldata(data);
}
}
}
Gas Price Monitoring
Copy
Ask AI
import { Blob } from 'tevm';
class GasMonitor {
private readonly TARGET_COST_GWEI = 10_000_000n; // 0.01 ETH
async waitForFavorableGas(
data: Uint8Array,
provider: any,
maxWaitMs: number = 300_000 // 5 minutes
): Promise<void> {
const startTime = Date.now();
const blobCount = Blob.estimateBlobCount(data);
while (Date.now() - startTime < maxWaitMs) {
const blobBaseFee = await provider.getBlobBaseFee();
const cost = Blob.calculateGas(blobCount, blobBaseFee);
if (cost / 10n**9n <= this.TARGET_COST_GWEI) {
console.log(`Favorable gas: ${cost / 10n**9n} gwei`);
return;
}
console.log(`Waiting... current cost: ${cost / 10n**9n} gwei`);
await new Promise(resolve => setTimeout(resolve, 12_000)); // Wait 1 block
}
throw new Error('Timeout waiting for favorable gas');
}
}
Data Recovery
L2 Node Sync
Copy
Ask AI
import { Blob } from 'tevm';
class L2Sync {
async syncFromBeacon(
startBlock: number,
endBlock: number,
beaconRPC: string
) {
console.log(`Syncing blocks ${startBlock}-${endBlock}`);
for (let block = startBlock; block <= endBlock; block++) {
try {
// Fetch blob sidecars from beacon chain
const blobs = await this.fetchBlobSidecars(block, beaconRPC);
if (blobs.length === 0) {
console.log(`Block ${block}: no blobs`);
continue;
}
// Reconstruct data
const data = Blob.joinData(blobs);
// Process L2 batch
await this.processBatch(block, data);
console.log(`Block ${block}: processed ${blobs.length} blobs`);
} catch (e) {
console.error(`Block ${block}: failed to sync`, e);
throw e;
}
}
}
private async fetchBlobSidecars(
block: number,
beaconRPC: string
): Promise<Blob[]> {
const response = await fetch(
`${beaconRPC}/eth/v1/beacon/blob_sidecars/${block}`
);
const json = await response.json();
return json.data.map((sidecar: any) =>
Blob(Hex.toBytes(sidecar.blob))
);
}
}
Historical Data Access
Copy
Ask AI
import { Blob } from 'tevm';
class HistoricalDataProvider {
private db: Database;
private beaconRPC: string;
private readonly RETENTION_DAYS = 18;
async getData(blockNumber: number): Promise<Uint8Array> {
// Try local database first
const cached = await this.db.get(`blob:${blockNumber}`);
if (cached) {
return cached;
}
// Check if within retention period
const blockAge = await this.getBlockAge(blockNumber);
if (blockAge > this.RETENTION_DAYS) {
throw new Error(
`Block ${blockNumber} beyond retention period (${blockAge} days old)`
);
}
// Fetch from beacon chain
console.log(`Fetching block ${blockNumber} from beacon chain`);
const blobs = await this.fetchFromBeacon(blockNumber);
const data = Blob.joinData(blobs);
// Cache locally
await this.db.put(`blob:${blockNumber}`, data);
return data;
}
private async getBlockAge(blockNumber: number): Promise<number> {
const currentBlock = await this.getCurrentBlock();
const blocksDiff = currentBlock - blockNumber;
const daysDiff = (blocksDiff * 12) / 86400; // 12 sec per block
return daysDiff;
}
}
Batch Submission Strategies
Adaptive Batching
Copy
Ask AI
import { Blob } from 'tevm';
class AdaptiveBatcher {
private pending: Transaction[] = [];
private readonly MAX_WAIT_MS = 60_000; // 1 minute
private readonly MIN_BATCH_SIZE = 50_000; // 50 KB
async addTransaction(tx: Transaction) {
this.pending.push(tx);
const batchSize = this.estimateBatchSize(this.pending);
// Submit if batch is large enough
if (batchSize >= this.MIN_BATCH_SIZE) {
await this.submitBatch();
}
}
async submitBatch() {
if (this.pending.length === 0) return;
const batchData = this.serializeBatch(this.pending);
const blobCount = Blob.estimateBlobCount(batchData);
console.log(
`Submitting batch: ${this.pending.length} txs, ${blobCount} blobs`
);
// Create and send blob transaction
const blobs = Blob.splitData(batchData);
// ... (generate commitments, proofs, send)
this.pending = [];
}
private estimateBatchSize(txs: Transaction[]): number {
return new TextEncoder().encode(JSON.stringify(txs)).length;
}
}
Multi-Transaction Splitting
Copy
Ask AI
import { Blob } from 'tevm';
class BatchSplitter {
async splitIntoBatches(
transactions: Transaction[]
): Promise<Transaction[][]> {
const batches: Transaction[][] = [];
let currentBatch: Transaction[] = [];
let currentSize = 0;
for (const tx of transactions) {
const txSize = this.estimateSize(tx);
const newSize = currentSize + txSize;
// Check if adding tx would exceed max blobs
const blobCount = Blob.estimateBlobCount(newSize);
if (blobCount > Blob.MAX_PER_TRANSACTION) {
// Start new batch
batches.push(currentBatch);
currentBatch = [tx];
currentSize = txSize;
} else {
// Add to current batch
currentBatch.push(tx);
currentSize = newSize;
}
}
if (currentBatch.length > 0) {
batches.push(currentBatch);
}
return batches;
}
private estimateSize(tx: Transaction): number {
return new TextEncoder().encode(JSON.stringify(tx)).length;
}
}
Error Handling
Retry Logic
Copy
Ask AI
import { Blob } from 'tevm';
class ResilientSubmitter {
async submitWithRetry(
blobs: Blob[],
maxRetries: number = 3
): Promise<TransactionReceipt> {
let lastError: Error | undefined;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
// Generate fresh commitments/proofs
const commitments = blobs.map(b => Blob.toCommitment(b));
const proofs = blobs.map(b => Blob.toProof(b));
// Verify before sending
if (!Blob.verifyBatch(blobs, commitments, proofs)) {
throw new Error('Proof verification failed');
}
const versionedHashes = commitments.map(c =>
Blob.Commitment.toVersionedHash(c)
);
// Create and send transaction
const tx = await this.createBlobTransaction({
blobVersionedHashes: versionedHashes,
blobs: blobs,
commitments: commitments,
proofs: proofs,
});
const receipt = await this.sendTransaction(tx);
console.log(`Submitted successfully: ${receipt.transactionHash}`);
return receipt;
} catch (e) {
lastError = e as Error;
console.error(`Attempt ${attempt + 1} failed:`, e);
if (attempt < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5s
}
}
}
throw new Error(`Failed after ${maxRetries} attempts: ${lastError?.message}`);
}
}
Validation Guards
Copy
Ask AI
import { Blob } from 'tevm';
class BlobValidator {
validateBeforeSending(
blobs: Blob[],
commitments: Commitment[],
proofs: Proof[],
versionedHashes: VersionedHash[]
): void {
// Check blob count
if (blobs.length === 0) {
throw new Error('No blobs provided');
}
if (blobs.length > Blob.MAX_PER_TRANSACTION) {
throw new Error(
`Too many blobs: ${blobs.length} (max ${Blob.MAX_PER_TRANSACTION})`
);
}
// Check array lengths match
if (
blobs.length !== commitments.length ||
blobs.length !== proofs.length ||
blobs.length !== versionedHashes.length
) {
throw new Error('Array length mismatch');
}
// Validate each blob
for (let i = 0; i < blobs.length; i++) {
if (!Blob.isValid(blobs[i])) {
throw new Error(`Invalid blob at index ${i}`);
}
if (!Blob.Commitment.isValid(commitments[i])) {
throw new Error(`Invalid commitment at index ${i}`);
}
if (!Blob.Proof.isValid(proofs[i])) {
throw new Error(`Invalid proof at index ${i}`);
}
if (!Blob.isValidVersion(versionedHashes[i])) {
throw new Error(`Invalid versioned hash at index ${i}`);
}
}
// Batch verify proofs
if (!Blob.verifyBatch(blobs, commitments, proofs)) {
throw new Error('Proof verification failed');
}
// Verify versioned hashes
for (let i = 0; i < blobs.length; i++) {
const computed = Blob.Commitment.toVersionedHash(commitments[i]);
if (!Blob.equals(computed, versionedHashes[i])) {
throw new Error(`Versioned hash mismatch at index ${i}`);
}
}
}
}
Monitoring
Cost Tracking
Copy
Ask AI
import { Blob } from 'tevm';
class CostTracker {
private totalBlobCost = 0n;
private totalCalldataCost = 0n;
async recordBlobTransaction(
blobCount: number,
blobBaseFee: bigint
): Promise<void> {
const cost = Blob.calculateGas(blobCount, blobBaseFee);
this.totalBlobCost += cost;
console.log(`Blob tx cost: ${cost / 10n**9n} gwei`);
console.log(`Total blob cost: ${this.totalBlobCost / 10n**18n} ETH`);
// Calculate equivalent calldata cost
const dataBytes = Blob.SIZE * blobCount;
const calldataGas = BigInt(dataBytes) * 16n;
const calldataCost = calldataGas * 30_000_000_000n; // Assume 30 gwei
this.totalCalldataCost += calldataCost;
const savings = ((1 - Number(this.totalBlobCost) / Number(this.totalCalldataCost)) * 100).toFixed(1);
console.log(`Total savings vs calldata: ${savings}%`);
}
}
Resources
- L2 Best Practices - L2 scaling strategies
- Blob Explorer - Explore blob transactions
- EIP-4844 FAQ - Common questions
See Also
- Fundamentals - Blob basics
- EIP-4844 - Specification details
- calculateGas - Gas cost calculation

