Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Blob Transactions

Installation

Blob transactions require the brane-kzg module for KZG commitment operations:

Maven
<dependencies>
    <dependency>
        <groupId>com.github.noise-xyz.brane</groupId>
        <artifactId>brane-core</artifactId>
        <version>0.1.0-alpha</version>
    </dependency>
    <dependency>
        <groupId>com.github.noise-xyz.brane</groupId>
        <artifactId>brane-rpc</artifactId>
        <version>0.1.0-alpha</version>
    </dependency>
    <dependency>
        <groupId>com.github.noise-xyz.brane</groupId>
        <artifactId>brane-kzg</artifactId>
        <version>0.1.0-alpha</version>
    </dependency>
</dependencies>

Quick Start

import sh.brane.core.builder.Eip4844Builder;
import sh.brane.core.crypto.Kzg;
import sh.brane.core.model.BlobTransactionRequest;
import sh.brane.core.model.TransactionReceipt;
import sh.brane.core.types.Address;
import sh.brane.core.types.Wei;
import sh.brane.kzg.CKzg;
import sh.brane.rpc.Brane;
 
// 1. Load KZG trusted setup (once per application)
Kzg kzg = CKzg.loadFromClasspath();
 
// 2. Connect with signing capability
Brane.Signer client = Brane.connect("https://rpc.sepolia.org", signer);
 
// 3. Build blob transaction with raw data
byte[] data = "Hello, EIP-4844 blobs!".getBytes();
 
BlobTransactionRequest request = Eip4844Builder.create()
        .to(new Address("0x..."))
        .value(Wei.fromEther(new java.math.BigDecimal("0.001")))
        .blobData(data)
        .build(kzg);
 
// 4. Send and wait for confirmation
TransactionReceipt receipt = client.sendBlobTransactionAndWait(request);
System.out.println("Tx Hash: " + receipt.transactionHash());

Blob Size Limits

ConstantValueDescription
Blob.SIZE131,072 bytes (128 KiB)Total size of a single blob
Blob.FIELD_ELEMENTS4,096Number of field elements per blob
Blob.BYTES_PER_FIELD_ELEMENT32 bytesSize of each field element
SidecarBuilder.USABLE_BYTES_PER_BLOB126,976 bytes (~124 KiB)Usable data per blob (31 bytes per field element)
SidecarBuilder.MAX_DATA_SIZE761,848 bytes (~744 KiB)Maximum data across 6 blobs minus length prefix
BlobSidecar.MAX_BLOBS6Maximum blobs per transaction

KZG Module

The brane-kzg module provides KZG (Kate-Zaverucha-Goldberg) commitment operations required for blob transactions. It wraps the c-kzg-4844 native library.

Loading the Trusted Setup

import sh.brane.core.crypto.Kzg;
import sh.brane.kzg.CKzg;
 
// Recommended: Load bundled Ethereum mainnet trusted setup
Kzg kzg = CKzg.loadFromClasspath();
 
// Alternative: Load from a custom file path
Kzg kzg = CKzg.loadTrustedSetup("/path/to/trusted_setup.txt");

Thread Safety

The Kzg interface is thread-safe. All methods can be called concurrently from multiple threads without external synchronization.

Validating Proofs

import sh.brane.core.types.BlobSidecar;
 
// Validate all KZG proofs in a sidecar
sidecar.validate(kzg);
 
// Validate that versioned hashes match expected values
sidecar.validateHashes(transaction.blobVersionedHashes());

Building Blob Transactions

Using Raw Data

Use blobData() when you have raw bytes to encode into blobs. The builder handles encoding, blob creation, and sidecar generation:

import sh.brane.core.builder.Eip4844Builder;
import sh.brane.core.model.BlobTransactionRequest;
 
byte[] rollupData = getRollupBatchData();
 
BlobTransactionRequest request = Eip4844Builder.create()
        .to(rollupContract)
        .data(calldata)                  // Optional calldata
        .blobData(rollupData)            // Raw data to encode
        .maxFeePerGas(Wei.gwei(100))
        .maxPriorityFeePerGas(Wei.gwei(2))
        .maxFeePerBlobGas(Wei.gwei(10))
        .build(kzg);                     // KZG required for encoding

Using a Pre-Built Sidecar

Use sidecar() when you have an existing BlobSidecar (e.g., for fee bumping):

import sh.brane.core.tx.SidecarBuilder;
import sh.brane.core.types.BlobSidecar;
 
// Build sidecar separately
BlobSidecar sidecar = SidecarBuilder.from(data).build(kzg);
 
// Use in transaction
BlobTransactionRequest request = Eip4844Builder.create()
        .to(recipient)
        .sidecar(sidecar)                // Pre-built sidecar
        .maxFeePerGas(Wei.gwei(100))
        .maxPriorityFeePerGas(Wei.gwei(2))
        .maxFeePerBlobGas(Wei.gwei(10))
        .build();                        // No KZG needed

Sidecar Reuse Pattern

Reuse the same sidecar when bumping fees on a pending transaction. This ensures the versioned hashes remain consistent:

import sh.brane.core.tx.SidecarBuilder;
import sh.brane.core.types.BlobSidecar;
 
// Build sidecar once
byte[] data = getRollupBatchData();
BlobSidecar reusableSidecar = SidecarBuilder.from(data).build(kzg);
 
// Initial transaction with low fees
BlobTransactionRequest lowFeeTx = Eip4844Builder.create()
        .to(recipient)
        .nonce(currentNonce)
        .sidecar(reusableSidecar)
        .maxFeePerGas(Wei.gwei(50))
        .maxPriorityFeePerGas(Wei.gwei(1))
        .maxFeePerBlobGas(Wei.gwei(5))
        .build();
 
Hash txHash = client.sendBlobTransaction(lowFeeTx);
 
// Later: Bump fees with same nonce and sidecar
BlobTransactionRequest bumpedTx = Eip4844Builder.create()
        .to(recipient)
        .nonce(currentNonce)             // Same nonce for replacement
        .sidecar(reusableSidecar)        // Reuse same sidecar
        .maxFeePerGas(Wei.gwei(100))     // Higher fees
        .maxPriorityFeePerGas(Wei.gwei(5))
        .maxFeePerBlobGas(Wei.gwei(20))
        .build();
 
Hash replacementHash = client.sendBlobTransaction(bumpedTx);
 
// Versioned hashes are identical
assert lowFeeTx.blobVersionedHashes().equals(bumpedTx.blobVersionedHashes());

Decoding Blobs

Use BlobDecoder to extract original data from blobs. This reverses the encoding performed by SidecarBuilder:

import sh.brane.core.tx.BlobDecoder;
import sh.brane.core.types.Blob;
import java.util.List;
 
// Decode data from blobs
List<Blob> blobs = sidecar.blobs();
byte[] decodedData = BlobDecoder.decode(blobs);
 
// Verify round-trip integrity
assert Arrays.equals(originalData, decodedData);

Decoding Process

  1. Extracts 31 usable bytes from each 32-byte field element (skipping the 0x00 high byte)
  2. Reads the 8-byte big-endian length prefix from the first bytes
  3. Returns the original data bytes based on the length prefix

Error Handling

Blob transaction errors are part of the Brane exception hierarchy:

import sh.brane.core.error.KzgException;
import sh.brane.core.builder.BraneTxBuilderException;
 
try {
    BlobTransactionRequest request = Eip4844Builder.create()
            .to(recipient)
            .blobData(data)
            .build(kzg);
 
    client.sendBlobTransactionAndWait(request);
} catch (KzgException e) {
    // KZG commitment or proof failure
    System.err.println("KZG error [" + e.kind() + "]: " + e.getMessage());
} catch (BraneTxBuilderException e) {
    // Transaction building failure
    System.err.println("Builder error: " + e.getMessage());
}

KzgException Kinds

KindDescription
INVALID_BLOBInvalid blob data format or content
INVALID_PROOFKZG proof verification failed
SETUP_ERRORTrusted setup loading failed
COMMITMENT_ERRORFailed to compute KZG commitment
PROOF_ERRORFailed to compute or verify KZG proof

Common Errors

// Data too large
try {
    SidecarBuilder.from(hugeData); // > 761,848 bytes
} catch (IllegalArgumentException e) {
    // "Data size X exceeds maximum 761848 bytes"
}
 
// Empty blobs
try {
    BlobDecoder.decode(List.of());
} catch (IllegalArgumentException e) {
    // "blobs must not be empty"
}
 
// KZG setup not loaded
try {
    CKzg.loadFromClasspath();
} catch (KzgException e) {
    if (e.kind() == KzgException.Kind.SETUP_ERROR) {
        // Native library or trusted setup issue
    }
}

See Also