Blob Transactions
Installation
Blob transactions require the brane-kzg module for KZG commitment operations:
<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
| Constant | Value | Description |
|---|---|---|
Blob.SIZE | 131,072 bytes (128 KiB) | Total size of a single blob |
Blob.FIELD_ELEMENTS | 4,096 | Number of field elements per blob |
Blob.BYTES_PER_FIELD_ELEMENT | 32 bytes | Size of each field element |
SidecarBuilder.USABLE_BYTES_PER_BLOB | 126,976 bytes (~124 KiB) | Usable data per blob (31 bytes per field element) |
SidecarBuilder.MAX_DATA_SIZE | 761,848 bytes (~744 KiB) | Maximum data across 6 blobs minus length prefix |
BlobSidecar.MAX_BLOBS | 6 | Maximum 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 encodingUsing 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 neededSidecar 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
- Extracts 31 usable bytes from each 32-byte field element (skipping the 0x00 high byte)
- Reads the 8-byte big-endian length prefix from the first bytes
- 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
| Kind | Description |
|---|---|
INVALID_BLOB | Invalid blob data format or content |
INVALID_PROOF | KZG proof verification failed |
SETUP_ERROR | Trusted setup loading failed |
COMMITMENT_ERROR | Failed to compute KZG commitment |
PROOF_ERROR | Failed 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
- Sending Transactions - Transaction signing with Brane.Signer
- Error Handling - Full exception hierarchy