import { type SerializeAuthorizationListErrorType, serializeAuthorizationList, } from '../../../eip7702/internal/utils/serializeAuthorizationList.js' import { type BlobsToCommitmentsErrorType, blobsToCommitments, } from '../blob/blobsToCommitments.js' import { type BlobsToProofsErrorType, blobsToProofs, } from '../blob/blobsToProofs.js' import { type CommitmentsToVersionedHashesErrorType, commitmentsToVersionedHashes, } from '../blob/commitmentsToVersionedHashes.js' import { type ToBlobSidecarsErrorType, toBlobSidecars, } from '../blob/toBlobSidecars.js' import { type ConcatHexErrorType, concatHex } from '../data/concat.js' import { trim } from '../data/trim.js' import { type ToHexErrorType, bytesToHex, toHex } from '../encoding/toHex.js' import { type ToRlpErrorType, toRlp } from '../encoding/toRlp.js' import { InvalidLegacyVError, type InvalidLegacyVErrorType, } from '../errors/transaction.js' import type { ErrorType } from '../errors/utils.js' import { toSignature } from '../signature/toSignature.js' import type { Bytes, Hex } from '../types/data.js' import type { CompactSignature, Signature, Signatureish, } from '../types/signature.js' import type { TransactionType } from '../types/transaction.js' import type { TransactionEnvelope, TransactionEnvelopeEip1559, TransactionEnvelopeEip2930, TransactionEnvelopeEip4844, TransactionEnvelopeEip7702, TransactionEnvelopeGeneric, TransactionEnvelopeLegacy, TransactionSerialized, TransactionSerializedEip1559, TransactionSerializedEip2930, TransactionSerializedEip4844, TransactionSerializedEip7702, TransactionSerializedLegacy, } from '../types/transactionEnvelope.js' import type { ExactPartial, OneOf } from '../types/utils.js' import { type AssertTransactionEip1559ErrorType, type AssertTransactionEip2930ErrorType, type AssertTransactionEip4844ErrorType, type AssertTransactionEip7702ErrorType, type AssertTransactionLegacyErrorType, assertTransactionEip1559, assertTransactionEip2930, assertTransactionEip4844, assertTransactionEip7702, assertTransactionLegacy, } from './assertTransaction.js' import { type GetTransactionType, type GetTransactionTypeErrorType, getTransactionType, } from './getTransactionType.js' import { type SerializeAccessListErrorType, serializeAccessList, } from './serializeAccessList.js' export type SerializedTransactionReturnType< transaction extends TransactionEnvelope = TransactionEnvelope, /// _transactionType extends TransactionType = GetTransactionType, > = TransactionSerialized<_transactionType> export type SerializeTransactionFn< transaction extends TransactionEnvelopeGeneric = TransactionEnvelope, /// _transactionType extends TransactionType = never, > = typeof serializeTransaction< OneOf, _transactionType > export type SerializeTransactionErrorType = | GetTransactionTypeErrorType | SerializeTransactionEip1559ErrorType | SerializeTransactionEip2930ErrorType | SerializeTransactionEip4844ErrorType | SerializeTransactionEip7702ErrorType | SerializeTransactionLegacyErrorType | ErrorType export function serializeTransaction< const transaction extends TransactionEnvelope, /// _transactionType extends TransactionType = GetTransactionType, >( transaction: transaction, signature?: Signatureish | undefined, ): SerializedTransactionReturnType { const type = getTransactionType(transaction) as GetTransactionType if (type === 'eip1559') return serializeTransactionEip1559( transaction as TransactionEnvelopeEip1559, signature, ) as SerializedTransactionReturnType if (type === 'eip2930') return serializeTransactionEip2930( transaction as TransactionEnvelopeEip2930, signature, ) as SerializedTransactionReturnType if (type === 'eip4844') return serializeTransactionEip4844( transaction as TransactionEnvelopeEip4844, signature, ) as SerializedTransactionReturnType if (type === 'eip7702') return serializeTransactionEip7702( transaction as TransactionEnvelopeEip7702, signature, ) as SerializedTransactionReturnType return serializeTransactionLegacy( transaction as TransactionEnvelopeLegacy, signature, ) as SerializedTransactionReturnType } export type SerializeTransactionEip7702ErrorType = | AssertTransactionEip7702ErrorType | SerializeAuthorizationListErrorType | ConcatHexErrorType | InvalidLegacyVErrorType | ToHexErrorType | ToRlpErrorType | SerializeAccessListErrorType | ErrorType export function serializeTransactionEip7702( transaction: TransactionEnvelopeEip7702, signature?: Signatureish | undefined, ): TransactionSerializedEip7702 { const { authorizationList, chainId, gas, nonce, to, value, maxFeePerGas, maxPriorityFeePerGas, accessList, data, } = transaction assertTransactionEip7702(transaction) const serializedAccessList = serializeAccessList(accessList) const serializedAuthorizationList = serializeAuthorizationList(authorizationList) return concatHex([ '0x04', toRlp([ toHex(chainId), nonce ? toHex(nonce) : '0x', maxPriorityFeePerGas ? toHex(maxPriorityFeePerGas) : '0x', maxFeePerGas ? toHex(maxFeePerGas) : '0x', gas ? toHex(gas) : '0x', to ?? '0x', value ? toHex(value) : '0x', data ?? '0x', serializedAccessList, serializedAuthorizationList, ...toSignatureArray(signature || transaction), ]), ]) as TransactionSerializedEip7702 } export type SerializeTransactionEip4844ErrorType = | AssertTransactionEip4844ErrorType | BlobsToCommitmentsErrorType | CommitmentsToVersionedHashesErrorType | BlobsToProofsErrorType | ToBlobSidecarsErrorType | ConcatHexErrorType | InvalidLegacyVErrorType | ToHexErrorType | ToRlpErrorType | SerializeAccessListErrorType | ErrorType export function serializeTransactionEip4844( transaction: TransactionEnvelopeEip4844, signature?: Signatureish | undefined, ): TransactionSerializedEip4844 { const { chainId, gas, nonce, to, value, maxFeePerBlobGas, maxFeePerGas, maxPriorityFeePerGas, accessList, data, } = transaction assertTransactionEip4844(transaction) let blobVersionedHashes = transaction.blobVersionedHashes let sidecars = transaction.sidecars // If `blobs` are passed, we will need to compute the KZG commitments & proofs. if ( transaction.blobs && (typeof blobVersionedHashes === 'undefined' || typeof sidecars === 'undefined') ) { const blobs = ( typeof transaction.blobs[0] === 'string' ? transaction.blobs : (transaction.blobs as Bytes[]).map((x) => bytesToHex(x)) ) as Hex[] const kzg = transaction.kzg! const commitments = blobsToCommitments({ blobs, kzg, }) if (typeof blobVersionedHashes === 'undefined') blobVersionedHashes = commitmentsToVersionedHashes({ commitments, }) if (typeof sidecars === 'undefined') { const proofs = blobsToProofs({ blobs, commitments, kzg }) sidecars = toBlobSidecars({ blobs, commitments, proofs }) } } const serializedAccessList = serializeAccessList(accessList) const serializedTransaction = [ toHex(chainId), nonce ? toHex(nonce) : '0x', maxPriorityFeePerGas ? toHex(maxPriorityFeePerGas) : '0x', maxFeePerGas ? toHex(maxFeePerGas) : '0x', gas ? toHex(gas) : '0x', to ?? '0x', value ? toHex(value) : '0x', data ?? '0x', serializedAccessList, maxFeePerBlobGas ? toHex(maxFeePerBlobGas) : '0x', blobVersionedHashes ?? [], ...toSignatureArray(signature || transaction), ] as const const blobs: Hex[] = [] const commitments: Hex[] = [] const proofs: Hex[] = [] if (sidecars) for (let i = 0; i < sidecars.length; i++) { const { blob, commitment, proof } = sidecars[i] blobs.push(blob) commitments.push(commitment) proofs.push(proof) } return concatHex([ '0x03', sidecars ? // If sidecars are enabled, envelope turns into a "wrapper": toRlp([serializedTransaction, blobs, commitments, proofs]) : // If sidecars are disabled, standard envelope is used: toRlp(serializedTransaction), ]) as TransactionSerializedEip4844 } export type SerializeTransactionEip1559ErrorType = | AssertTransactionEip1559ErrorType | ConcatHexErrorType | InvalidLegacyVErrorType | ToHexErrorType | ToRlpErrorType | SerializeAccessListErrorType | ErrorType export function serializeTransactionEip1559( transaction: TransactionEnvelopeEip1559, signature?: Signatureish | undefined, ): TransactionSerializedEip1559 { const { chainId, gas, nonce, to, value, maxFeePerGas, maxPriorityFeePerGas, accessList, data, } = transaction assertTransactionEip1559(transaction) const serializedAccessList = serializeAccessList(accessList) const serializedTransaction = [ toHex(chainId), nonce ? toHex(nonce) : '0x', maxPriorityFeePerGas ? toHex(maxPriorityFeePerGas) : '0x', maxFeePerGas ? toHex(maxFeePerGas) : '0x', gas ? toHex(gas) : '0x', to ?? '0x', value ? toHex(value) : '0x', data ?? '0x', serializedAccessList, ...toSignatureArray(signature || transaction), ] return concatHex([ '0x02', toRlp(serializedTransaction), ]) as TransactionSerializedEip1559 } export type SerializeTransactionEip2930ErrorType = | AssertTransactionEip2930ErrorType | ConcatHexErrorType | InvalidLegacyVErrorType | ToHexErrorType | ToRlpErrorType | SerializeAccessListErrorType | ErrorType export function serializeTransactionEip2930( transaction: TransactionEnvelopeEip2930, signature?: Signatureish | undefined, ): TransactionSerializedEip2930 { const { chainId, gas, data, nonce, to, value, accessList, gasPrice } = transaction assertTransactionEip2930(transaction) const serializedAccessList = serializeAccessList(accessList) const serializedTransaction = [ toHex(chainId), nonce ? toHex(nonce) : '0x', gasPrice ? toHex(gasPrice) : '0x', gas ? toHex(gas) : '0x', to ?? '0x', value ? toHex(value) : '0x', data ?? '0x', serializedAccessList, ...toSignatureArray(signature || transaction), ] return concatHex([ '0x01', toRlp(serializedTransaction), ]) as TransactionSerializedEip2930 } export type SerializeTransactionLegacyErrorType = | AssertTransactionLegacyErrorType | InvalidLegacyVErrorType | ToHexErrorType | ToRlpErrorType | ErrorType export function serializeTransactionLegacy( transaction: TransactionEnvelopeLegacy, signature_?: Signatureish | undefined, ): TransactionSerializedLegacy { const { chainId = 0, gas, data, nonce, to, value, gasPrice } = transaction assertTransactionLegacy(transaction) let serializedTransaction = [ nonce ? toHex(nonce) : '0x', gasPrice ? toHex(gasPrice) : '0x', gas ? toHex(gas) : '0x', to ?? '0x', value ? toHex(value) : '0x', data ?? '0x', ] const signature = (() => { if (signature_) return toSignature(signature_) if ( typeof transaction.r === 'undefined' || typeof transaction.s === 'undefined' ) return undefined return { r: transaction.r, s: transaction.s, v: transaction.v!, } })() if (signature) { const v = (() => { // EIP-155 (inferred chainId) if (signature.v >= 35) { const inferredChainId = Math.floor((signature.v - 35) / 2) if (inferredChainId > 0) return signature.v return 27 + (signature.v === 35 ? 0 : 1) } // EIP-155 (explicit chainId) if (chainId > 0) return chainId * 2 + 35 + signature.v - 27 // Pre-EIP-155 (no chainId) const v = 27 + (signature.v === 27 ? 0 : 1) if (signature.v !== v) throw new InvalidLegacyVError({ v: signature.v }) return v })() const r = trim(signature.r) const s = trim(signature.s) serializedTransaction = [ ...serializedTransaction, toHex(v), r === '0x00' ? '0x' : r, s === '0x00' ? '0x' : s, ] } else if (chainId > 0) { serializedTransaction = [ ...serializedTransaction, toHex(chainId), '0x', '0x', ] } return toRlp(serializedTransaction) as TransactionSerializedLegacy } export function toSignatureArray( signature: | OneOf | ExactPartial> | Bytes | Hex, ) { if (typeof signature === 'object' && !Array.isArray(signature)) { const signature_ = signature as Signature if (typeof signature_.r === 'undefined') return [] if (typeof signature_.s === 'undefined') return [] if ( typeof signature_.v === 'undefined' && typeof signature_.yParity === 'undefined' ) return [] } const { yParity, ...signature_ } = toSignature(signature as Signatureish) const r = trim(signature_.r) const s = trim(signature_.s) return [ yParity ? '0x01' : '0x', r === '0x00' ? '0x' : r, s === '0x00' ? '0x' : s, ] as const }