Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 113 additions & 37 deletions packages/tx/src/4844/constructors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CELLS_PER_EXT_BLOB,
EthereumJSErrorWithoutCode,
bigIntToHex,
blobsToCellProofs,
blobsToCells,
blobsToCommitments,
blobsToProofs,
Expand Down Expand Up @@ -86,16 +87,40 @@ const validateBlobTransactionNetworkWrapper = (
}

/**
* Instantiate a transaction from a data dictionary.
* Instantiate a Blob4844Tx transaction from a data dictionary.
*
* Format: { chainId, nonce, gasPrice, gasLimit, to, value, data, accessList,
* v, r, s, blobs, kzgCommitments, blobVersionedHashes, kzgProofs }
* If blobs are provided the tx will be instantiated in the "Network Wrapper" format,
* otherwise in the canonical form represented on-chain.
*
* @param txData - Transaction data object containing:
* - `chainId` - Chain ID (will be set automatically if not provided)
* - `nonce` - Transaction nonce
* - `maxPriorityFeePerGas` - Maximum priority fee per gas (EIP-1559)
* - `maxFeePerGas` - Maximum fee per gas (EIP-1559)
* - `gasLimit` - Gas limit for the transaction
* - `to` - Recipient address (optional for contract creation)
* - `value` - Value to transfer in wei
* - `data` - Transaction data
* - `accessList` - Access list for EIP-2930 (optional)
* - `maxFeePerBlobGas` - Maximum fee per blob gas (EIP-4844)
* - `blobVersionedHashes` - Versioned hashes for blob validation
* - `v`, `r`, `s` - Signature components (for signed transactions)
* - `blobs` - Raw blob data (optional, will derive commitments/proofs)
* - `blobsData` - Array of strings to construct blobs from (optional)
* - `kzgCommitments` - KZG commitments (optional, derived from blobs if not provided)
* - `kzgProofs` - KZG proofs (optional, derived from blobs if not provided)
* - `networkWrapperVersion` - Network wrapper version (0=EIP-4844, 1=EIP-7594)
* @param opts - Transaction options including Common instance with KZG initialized
* @returns A new Blob4844Tx instance
*
* @throws {EthereumJSErrorWithoutCode} If KZG is not initialized in Common
* @throws {EthereumJSErrorWithoutCode} If both blobsData and blobs are provided
*
* Notes:
* - `chainId` will be set automatically if not provided
* - All parameters are optional and have some basic default values
* - `blobs` cannot be supplied as well as `kzgCommitments`, `blobVersionedHashes`, `kzgProofs`
* - If `blobs` is passed in, `kzgCommitments`, `blobVersionedHashes`, `kzgProofs` will be derived by the constructor
* - Requires a Common instance with `customCrypto.kzg` initialized
* - Cannot provide both `blobsData` and `blobs` simultaneously
* - If `blobs` or `blobsData` is provided, `kzgCommitments`, `blobVersionedHashes`, and `kzgProofs` will be automatically derived
* - KZG proof type depends on EIP-7594 activation: per-Blob proofs (EIP-4844) or per-Cell proofs (EIP-7594)
*/
export function createBlob4844Tx(txData: TxData, opts?: TxOptions) {
if (opts?.common?.customCrypto?.kzg === undefined) {
Expand All @@ -110,23 +135,6 @@ export function createBlob4844Tx(txData: TxData, opts?: TxOptions) {
'cannot have both raw blobs data and encoded blobs in constructor',
)
}
if (txData.blobsData !== undefined) {
if (txData.kzgCommitments !== undefined) {
throw EthereumJSErrorWithoutCode(
'cannot have both raw blobs data and KZG commitments in constructor',
)
}
if (txData.blobVersionedHashes !== undefined) {
throw EthereumJSErrorWithoutCode(
'cannot have both raw blobs data and versioned hashes in constructor',
)
}
if (txData.kzgProofs !== undefined) {
throw EthereumJSErrorWithoutCode(
'cannot have both raw blobs data and KZG proofs in constructor',
)
}
}
if (txData.blobsData !== undefined || txData.blobs !== undefined) {
txData.blobs ??= getBlobs(
txData.blobsData!.reduce((acc, cur) => acc + cur),
Expand All @@ -135,21 +143,52 @@ export function createBlob4844Tx(txData: TxData, opts?: TxOptions) {
txData.blobVersionedHashes ??= commitmentsToVersionedHashes(
txData.kzgCommitments as PrefixedHexString[],
)
txData.kzgProofs ??= blobsToProofs(
kzg,
txData.blobs as PrefixedHexString[],
txData.kzgCommitments as PrefixedHexString[],
)
if (opts!.common!.isActivatedEIP(7594)) {
txData.kzgProofs ??= blobsToCellProofs(kzg, txData.blobs as PrefixedHexString[])
} else {
txData.kzgProofs ??= blobsToProofs(
kzg,
txData.blobs as PrefixedHexString[],
txData.kzgCommitments as PrefixedHexString[],
)
}
}

return new Blob4844Tx(txData, opts)
}

/**
* Create a transaction from an array of byte encoded values ordered according to the devp2p network encoding - format noted below.
* Create a Blob4844Tx transaction from an array of byte encoded values ordered according to the devp2p network encoding.
* Only canonical format supported, otherwise use `createBlob4844TxFromSerializedNetworkWrapper()`.
*
* @param values - Array of byte encoded values containing:
* - `chainId` - Chain ID as Uint8Array
* - `nonce` - Transaction nonce as Uint8Array
* - `maxPriorityFeePerGas` - Maximum priority fee per gas (EIP-1559) as Uint8Array
* - `maxFeePerGas` - Maximum fee per gas (EIP-1559) as Uint8Array
* - `gasLimit` - Gas limit for the transaction as Uint8Array
* - `to` - Recipient address as Uint8Array (optional for contract creation)
* - `value` - Value to transfer in wei as Uint8Array
* - `data` - Transaction data as Uint8Array
* - `accessList` - Access list for EIP-2930 as Uint8Array (optional)
* - `maxFeePerBlobGas` - Maximum fee per blob gas (EIP-4844) as Uint8Array
* - `blobVersionedHashes` - Versioned hashes for blob validation as Uint8Array[]
* - `v` - Signature recovery ID as Uint8Array (for signed transactions)
* - `r` - Signature r component as Uint8Array (for signed transactions)
* - `s` - Signature s component as Uint8Array (for signed transactions)
* @param opts - Transaction options including Common instance with KZG initialized
* @returns A new Blob4844Tx instance
*
* @throws {EthereumJSErrorWithoutCode} If KZG is not initialized in Common
* @throws {EthereumJSErrorWithoutCode} If values array length is not 11 (unsigned) or 14 (signed)
*
* Format: `[chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data,
* accessList, signatureYParity, signatureR, signatureS]`
* accessList, maxFeePerBlobGas, blobVersionedHashes, v, r, s]`
*
* Notes:
* - Requires a Common instance with `customCrypto.kzg` initialized
* - Supports both unsigned (11 values) and signed (14 values) transaction formats
* - All numeric values must be provided as Uint8Array byte representations
*/
export function createBlob4844TxFromBytesArray(values: TxValuesArray, opts: TxOptions = {}) {
if (opts.common?.customCrypto?.kzg === undefined) {
Expand Down Expand Up @@ -216,10 +255,25 @@ export function createBlob4844TxFromBytesArray(values: TxValuesArray, opts: TxOp
}

/**
* Instantiate a transaction from a RLP serialized tx.
* Instantiate a Blob4844Tx transaction from an RLP serialized transaction.
* Only canonical format supported, otherwise use `createBlob4844TxFromSerializedNetworkWrapper()`.
*
* @param serialized - RLP serialized transaction data as Uint8Array
* @param opts - Transaction options including Common instance with KZG initialized
* @returns A new Blob4844Tx instance
*
* @throws {EthereumJSErrorWithoutCode} If KZG is not initialized in Common
* @throws {EthereumJSErrorWithoutCode} If serialized data is not a valid EIP-4844 transaction
* @throws {EthereumJSErrorWithoutCode} If RLP decoded data is not an array
*
* Format: `0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data,
* access_list, max_fee_per_data_gas, blob_versioned_hashes, y_parity, r, s])`
* access_list, max_fee_per_blob_gas, blob_versioned_hashes, y_parity, r, s])`
*
* Notes:
* - Requires a Common instance with `customCrypto.kzg` initialized
* - Transaction type byte must be 0x03 (BlobEIP4844)
* - RLP payload must decode to an array of transaction fields
* - Delegates to `createBlob4844TxFromBytesArray` for actual construction
*/
export function createBlob4844TxFromRLP(serialized: Uint8Array, opts: TxOptions = {}) {
if (opts.common?.customCrypto?.kzg === undefined) {
Expand All @@ -246,10 +300,32 @@ export function createBlob4844TxFromRLP(serialized: Uint8Array, opts: TxOptions
}

/**
* Creates a transaction from the network encoding of a blob transaction (with blobs/commitments/proof)
* @param serialized a buffer representing a serialized BlobTransactionNetworkWrapper
* @param opts any TxOptions defined
* @returns a Blob4844Tx
* Creates a Blob4844Tx transaction from the network encoding of a blob transaction wrapper.
* This function handles the "Network Wrapper" format that includes blobs, commitments, and proofs.
*
* @param serialized - Serialized BlobTransactionNetworkWrapper as Uint8Array
* @param opts - Transaction options including Common instance with KZG initialized
* @returns A new Blob4844Tx instance with network wrapper data
*
* @throws {EthereumJSErrorWithoutCode} If Common instance is not provided
* @throws {EthereumJSErrorWithoutCode} If KZG is not initialized in Common
* @throws {EthereumJSErrorWithoutCode} If serialized data is not a valid EIP-4844 transaction
* @throws {Error} If network wrapper has invalid number of values (not 4 or 5)
* @throws {Error} If transaction has no valid `to` address
* @throws {Error} If network wrapper version is invalid
* @throws {EthereumJSErrorWithoutCode} If KZG verification fails
* @throws {EthereumJSErrorWithoutCode} If versioned hashes don't match commitments
*
* Network Wrapper Formats:
* - EIP-4844: `0x03 || rlp([tx_values, blobs, kzg_commitments, kzg_proofs])` (4 values)
* - EIP-7594: `0x03 || rlp([tx_values, network_wrapper_version, blobs, kzg_commitments, kzg_proofs])` (5 values)
*
* Notes:
* - Requires a Common instance with `customCrypto.kzg` initialized
* - Validates KZG proofs against blobs and commitments
* - Verifies versioned hashes match computed commitments
* - Supports both EIP-4844 and EIP-7594 network wrapper formats
* - Transaction is frozen by default unless `opts.freeze` is set to false
*/
export function createBlob4844TxFromSerializedNetworkWrapper(
serialized: Uint8Array,
Expand Down
53 changes: 47 additions & 6 deletions packages/tx/src/4844/tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export type NetworkWrapperType = (typeof NetworkWrapperType)[keyof typeof Networ
*
* - TransactionType: 3
* - EIP: [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844)
*
* This tx type has two "modes": the plain canonical format only contains `blobVersionedHashes`.
* If blobs are passed in the tx automatically switches to "Network Wrapper" format and the
* `networkWrapperVersion` will be set or validated.
*/
export class Blob4844Tx implements TransactionInterface<typeof TransactionType.BlobEIP4844> {
public type = TransactionType.BlobEIP4844 // 4844 tx type
Expand All @@ -76,12 +80,21 @@ export class Blob4844Tx implements TransactionInterface<typeof TransactionType.B
public readonly v?: bigint
public readonly r?: bigint
public readonly s?: bigint

// End of Tx data part

/**
* This property is set if the tx is in "Network Wrapper" format.
*
* Possible values:
* - 0 (EIP-4844)
* - 1 (EIP-4844 + EIP-7594)
*/
networkWrapperVersion?: NetworkWrapperType
blobs?: PrefixedHexString[] // This property should only be populated when the transaction is in the "Network Wrapper" format
kzgCommitments?: PrefixedHexString[] // This property should only be populated when the transaction is in the "Network Wrapper" format
kzgProofs?: PrefixedHexString[] // This property should only be populated when the transaction is in the "Network Wrapper" format

// "Network Wrapper" Format
blobs?: PrefixedHexString[] // EIP-4844 + EIP-7594
kzgCommitments?: PrefixedHexString[] // EIP-4844 + EIP-7594
kzgProofs?: PrefixedHexString[] // EIP-4844: per-Blob proofs, EIP-7594: per-Cell proofs

public readonly common!: Common

Expand Down Expand Up @@ -231,15 +244,15 @@ export class Blob4844Tx implements TransactionInterface<typeof TransactionType.B
case NetworkWrapperType.EIP7594:
if (!this.common.isActivatedEIP(7594)) {
throw EthereumJSErrorWithoutCode(
'EIP-7594 not enabled on Common for EIP7594 network wrapper version',
'EIP-7594 not enabled on Common for EIP-7594 network wrapper version',
)
}
break

case NetworkWrapperType.EIP4844:
if (this.common.isActivatedEIP(7594)) {
throw EthereumJSErrorWithoutCode(
'EIP-7594 is active on Common for EIP4844 network wrapper version',
'EIP-7594 is active on Common for EIP-4844 network wrapper version',
)
}
break
Expand All @@ -252,10 +265,38 @@ export class Blob4844Tx implements TransactionInterface<typeof TransactionType.B
}

this.blobs = txData.blobs?.map((blob) => toType(blob, TypeOutput.PrefixedHexString))

if (this.networkWrapperVersion === undefined && this.blobs !== undefined) {
if (this.common.isActivatedEIP(7594)) {
this.networkWrapperVersion = 1
} else {
this.networkWrapperVersion = 0
}
}
if (this.networkWrapperVersion !== undefined && this.blobs === undefined) {
const msg = Legacy.errorMsg(
this,
'tx is not allowed to be in network wrapper format if no blob list is provided',
)
throw EthereumJSErrorWithoutCode(msg)
}

this.kzgCommitments = txData.kzgCommitments?.map((commitment) =>
toType(commitment, TypeOutput.PrefixedHexString),
)
this.kzgProofs = txData.kzgProofs?.map((proof) => toType(proof, TypeOutput.PrefixedHexString))

if (this.blobs !== undefined) {
if (this.kzgCommitments === undefined) {
const msg = Legacy.errorMsg(this, 'kzgCommitments are mandatory if blobs are provided')
throw EthereumJSErrorWithoutCode(msg)
}
if (this.kzgProofs === undefined) {
const msg = Legacy.errorMsg(this, 'kzgProofs are mandatory if blobs are provided')
throw EthereumJSErrorWithoutCode(msg)
}
}

const freeze = opts?.freeze ?? true
if (freeze) {
Object.freeze(this)
Expand Down
2 changes: 1 addition & 1 deletion packages/tx/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ export interface BlobEIP4844TxData extends FeeMarketEIP1559TxData {
*/
kzgCommitments?: BytesLike[]
/**
* The KZG proofs associated with the transaction
* The KZG proofs associated with the transaction (EIP-4844: per-Blob proofs, EIP-7594: per-Cell proofs)
*/
kzgProofs?: BytesLike[]
/**
Expand Down
Loading
Loading