import type { SerializedAuthorizationList, SignedAuthorizationList, } from '../../../eip7702/internal/types/authorization.js' import { type IsAddressErrorType, isAddress } from '../address/isAddress.js' import { toBlobSidecars } from '../blob/toBlobSidecars.js' import { type IsHexErrorType, isHex } from '../data/isHex.js' import { type PadHexErrorType, padHex } from '../data/pad.js' import { trim } from '../data/trim.js' import { type HexToBigIntErrorType, type HexToNumberErrorType, hexToBigInt, hexToNumber, } from '../encoding/fromHex.js' import { type FromRlpErrorType, fromRlp } from '../encoding/fromRlp.js' import type { RecursiveArray } from '../encoding/toRlp.js' import { InvalidAddressError, type InvalidAddressErrorType, } from '../errors/address.js' import { InvalidLegacyVError, type InvalidLegacyVErrorType, InvalidSerializedTransactionError, type InvalidSerializedTransactionErrorType, } from '../errors/transaction.js' import type { ErrorType } from '../errors/utils.js' import { isHash } from '../hash/isHash.js' import type { Hex } from '../types/data.js' import type { Signature } from '../types/signature.js' import type { AccessList, TransactionType } from '../types/transaction.js' import type { TransactionEnvelope, TransactionEnvelopeEip1559, TransactionEnvelopeEip2930, TransactionEnvelopeEip4844, TransactionEnvelopeEip7702, TransactionEnvelopeLegacy, TransactionSerialized, TransactionSerializedEip1559, TransactionSerializedEip2930, TransactionSerializedEip4844, TransactionSerializedEip7702, TransactionSerializedGeneric, } from '../types/transactionEnvelope.js' import type { IsNarrowable, Mutable } 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 GetSerializedTransactionType, type GetSerializedTransactionTypeErrorType, getSerializedTransactionType, } from './getSerializedTransactionType.js' export type ParseTransactionReturnType< serialized extends TransactionSerializedGeneric = TransactionSerialized, type extends TransactionType = GetSerializedTransactionType, > = IsNarrowable extends true ? | (type extends 'eip1559' ? TransactionEnvelopeEip1559 : never) | (type extends 'eip2930' ? TransactionEnvelopeEip2930 : never) | (type extends 'eip4844' ? TransactionEnvelopeEip4844 : never) | (type extends 'eip7702' ? TransactionEnvelopeEip7702 : never) | (type extends 'legacy' ? TransactionEnvelopeLegacy : never) : TransactionEnvelope export type ParseTransactionErrorType = | GetSerializedTransactionTypeErrorType | ParseTransactionEip1559ErrorType | ParseTransactionEip2930ErrorType | ParseTransactionEip4844ErrorType | ParseTransactionEip7702ErrorType | ParseTransactionLegacyErrorType export function parseTransaction< const serialized extends TransactionSerializedGeneric, >(serializedTransaction: serialized): ParseTransactionReturnType { const type = getSerializedTransactionType(serializedTransaction) if (type === 'eip1559') return parseTransactionEip1559( serializedTransaction as TransactionSerializedEip1559, ) as ParseTransactionReturnType if (type === 'eip2930') return parseTransactionEip2930( serializedTransaction as TransactionSerializedEip2930, ) as ParseTransactionReturnType if (type === 'eip4844') return parseTransactionEip4844( serializedTransaction as TransactionSerializedEip4844, ) as ParseTransactionReturnType if (type === 'eip7702') return parseTransactionEip7702( serializedTransaction as TransactionSerializedEip7702, ) as ParseTransactionReturnType return parseTransactionLegacy( serializedTransaction, ) as ParseTransactionReturnType } export type ParseTransactionEip7702ErrorType = | ToTransactionArrayErrorType | AssertTransactionEip7702ErrorType | ToTransactionArrayErrorType | HexToBigIntErrorType | HexToNumberErrorType | InvalidLegacyVErrorType | InvalidSerializedTransactionErrorType | IsHexErrorType | ParseAuthorizationListErrorType | ParseEip155SignatureErrorType | ErrorType export function parseTransactionEip7702( serializedTransaction: TransactionSerializedEip7702, ): TransactionEnvelopeEip7702 { const transactionArray = toTransactionArray(serializedTransaction) const [ chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, authorizationList, v, r, s, ] = transactionArray if (transactionArray.length !== 10 && transactionArray.length !== 13) throw new InvalidSerializedTransactionError({ attributes: { chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, authorizationList, ...(transactionArray.length > 9 ? { v, r, s, } : {}), }, serializedTransaction, type: 'eip7702', }) const transaction = { chainId: hexToNumber(chainId as Hex), type: 'eip7702', } as TransactionEnvelopeEip7702 if (isHex(to) && to !== '0x') transaction.to = to if (isHex(gas) && gas !== '0x') transaction.gas = hexToBigInt(gas) if (isHex(data) && data !== '0x') transaction.data = data if (isHex(nonce) && nonce !== '0x') transaction.nonce = hexToNumber(nonce) if (isHex(value) && value !== '0x') transaction.value = hexToBigInt(value) if (isHex(maxFeePerGas) && maxFeePerGas !== '0x') transaction.maxFeePerGas = hexToBigInt(maxFeePerGas) if (isHex(maxPriorityFeePerGas) && maxPriorityFeePerGas !== '0x') transaction.maxPriorityFeePerGas = hexToBigInt(maxPriorityFeePerGas) if (accessList.length !== 0 && accessList !== '0x') transaction.accessList = parseAccessList(accessList as RecursiveArray) if (authorizationList.length !== 0 && authorizationList !== '0x') transaction.authorizationList = parseAuthorizationList( authorizationList as SerializedAuthorizationList, ) assertTransactionEip7702(transaction) const signature = transactionArray.length === 13 ? parseEip155Signature(transactionArray as RecursiveArray) : undefined return { ...signature, ...transaction } } export type ParseTransactionEip4844ErrorType = | ToTransactionArrayErrorType | AssertTransactionEip4844ErrorType | ToTransactionArrayErrorType | HexToBigIntErrorType | HexToNumberErrorType | InvalidLegacyVErrorType | InvalidSerializedTransactionErrorType | IsHexErrorType | ParseEip155SignatureErrorType | ErrorType export function parseTransactionEip4844( serializedTransaction: TransactionSerializedEip4844, ): TransactionEnvelopeEip4844 { const transactionOrWrapperArray = toTransactionArray(serializedTransaction) const hasNetworkWrapper = transactionOrWrapperArray.length === 4 const transactionArray = hasNetworkWrapper ? transactionOrWrapperArray[0] : transactionOrWrapperArray const wrapperArray = hasNetworkWrapper ? transactionOrWrapperArray.slice(1) : [] const [ chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, maxFeePerBlobGas, blobVersionedHashes, v, r, s, ] = transactionArray const [blobs, commitments, proofs] = wrapperArray if (!(transactionArray.length === 11 || transactionArray.length === 14)) throw new InvalidSerializedTransactionError({ attributes: { chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, ...(transactionArray.length > 9 ? { v, r, s, } : {}), }, serializedTransaction, type: 'eip4844', }) const transaction = { blobVersionedHashes: blobVersionedHashes as Hex[], chainId: hexToNumber(chainId as Hex), type: 'eip4844', } as TransactionEnvelopeEip4844 if (isHex(to) && to !== '0x') transaction.to = to if (isHex(gas) && gas !== '0x') transaction.gas = hexToBigInt(gas) if (isHex(data) && data !== '0x') transaction.data = data if (isHex(nonce) && nonce !== '0x') transaction.nonce = hexToNumber(nonce) if (isHex(value) && value !== '0x') transaction.value = hexToBigInt(value) if (isHex(maxFeePerBlobGas) && maxFeePerBlobGas !== '0x') transaction.maxFeePerBlobGas = hexToBigInt(maxFeePerBlobGas) if (isHex(maxFeePerGas) && maxFeePerGas !== '0x') transaction.maxFeePerGas = hexToBigInt(maxFeePerGas) if (isHex(maxPriorityFeePerGas) && maxPriorityFeePerGas !== '0x') transaction.maxPriorityFeePerGas = hexToBigInt(maxPriorityFeePerGas) if (accessList.length !== 0 && accessList !== '0x') transaction.accessList = parseAccessList(accessList as RecursiveArray) if (blobs && commitments && proofs) transaction.sidecars = toBlobSidecars({ blobs: blobs as Hex[], commitments: commitments as Hex[], proofs: proofs as Hex[], }) assertTransactionEip4844(transaction) const signature = transactionArray.length === 14 ? parseEip155Signature(transactionArray as RecursiveArray) : undefined return { ...signature, ...transaction } } export type ParseTransactionEip1559ErrorType = | ToTransactionArrayErrorType | AssertTransactionEip1559ErrorType | ToTransactionArrayErrorType | HexToBigIntErrorType | HexToNumberErrorType | InvalidLegacyVErrorType | InvalidSerializedTransactionErrorType | IsHexErrorType | ParseEip155SignatureErrorType | ParseAccessListErrorType | ErrorType export function parseTransactionEip1559( serializedTransaction: TransactionSerializedEip1559, ): TransactionEnvelopeEip1559 { const transactionArray = toTransactionArray(serializedTransaction) const [ chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, v, r, s, ] = transactionArray if (!(transactionArray.length === 9 || transactionArray.length === 12)) throw new InvalidSerializedTransactionError({ attributes: { chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, ...(transactionArray.length > 9 ? { v, r, s, } : {}), }, serializedTransaction, type: 'eip1559', }) const transaction: TransactionEnvelopeEip1559 = { chainId: hexToNumber(chainId as Hex), type: 'eip1559', } if (isHex(to) && to !== '0x') transaction.to = to if (isHex(gas) && gas !== '0x') transaction.gas = hexToBigInt(gas) if (isHex(data) && data !== '0x') transaction.data = data if (isHex(nonce) && nonce !== '0x') transaction.nonce = hexToNumber(nonce) if (isHex(value) && value !== '0x') transaction.value = hexToBigInt(value) if (isHex(maxFeePerGas) && maxFeePerGas !== '0x') transaction.maxFeePerGas = hexToBigInt(maxFeePerGas) if (isHex(maxPriorityFeePerGas) && maxPriorityFeePerGas !== '0x') transaction.maxPriorityFeePerGas = hexToBigInt(maxPriorityFeePerGas) if (accessList.length !== 0 && accessList !== '0x') transaction.accessList = parseAccessList(accessList as RecursiveArray) assertTransactionEip1559(transaction) const signature = transactionArray.length === 12 ? parseEip155Signature(transactionArray) : undefined return { ...signature, ...transaction } } export type ParseTransactionEip2930ErrorType = | ToTransactionArrayErrorType | AssertTransactionEip2930ErrorType | ToTransactionArrayErrorType | HexToBigIntErrorType | HexToNumberErrorType | InvalidLegacyVErrorType | InvalidSerializedTransactionErrorType | IsHexErrorType | ParseEip155SignatureErrorType | ParseAccessListErrorType | ErrorType export function parseTransactionEip2930( serializedTransaction: TransactionSerializedEip2930, ): TransactionEnvelopeEip2930 { const transactionArray = toTransactionArray(serializedTransaction) const [chainId, nonce, gasPrice, gas, to, value, data, accessList, v, r, s] = transactionArray if (!(transactionArray.length === 8 || transactionArray.length === 11)) throw new InvalidSerializedTransactionError({ attributes: { chainId, nonce, gasPrice, gas, to, value, data, accessList, ...(transactionArray.length > 8 ? { v, r, s, } : {}), }, serializedTransaction, type: 'eip2930', }) const transaction: TransactionEnvelopeEip2930 = { chainId: hexToNumber(chainId as Hex), type: 'eip2930', } if (isHex(to) && to !== '0x') transaction.to = to if (isHex(gas) && gas !== '0x') transaction.gas = hexToBigInt(gas) if (isHex(data) && data !== '0x') transaction.data = data if (isHex(nonce) && nonce !== '0x') transaction.nonce = hexToNumber(nonce) if (isHex(value) && value !== '0x') transaction.value = hexToBigInt(value) if (isHex(gasPrice) && gasPrice !== '0x') transaction.gasPrice = hexToBigInt(gasPrice) if (accessList.length !== 0 && accessList !== '0x') transaction.accessList = parseAccessList(accessList as RecursiveArray) assertTransactionEip2930(transaction) const signature = transactionArray.length === 11 ? parseEip155Signature(transactionArray) : undefined return { ...signature, ...transaction } } export type ParseTransactionLegacyErrorType = | AssertTransactionLegacyErrorType | FromRlpErrorType | HexToBigIntErrorType | HexToNumberErrorType | InvalidLegacyVErrorType | InvalidSerializedTransactionErrorType | IsHexErrorType | ErrorType export function parseTransactionLegacy( serializedTransaction: Hex, ): TransactionEnvelopeLegacy { const transactionArray = fromRlp(serializedTransaction, 'hex') const [nonce, gasPrice, gas, to, value, data, chainIdOrV_, r, s] = transactionArray if (!(transactionArray.length === 6 || transactionArray.length === 9)) throw new InvalidSerializedTransactionError({ attributes: { nonce, gasPrice, gas, to, value, data, ...(transactionArray.length > 6 ? { v: chainIdOrV_, r, s, } : {}), }, serializedTransaction, type: 'legacy', }) const transaction: TransactionEnvelopeLegacy = { type: 'legacy', } if (isHex(to) && to !== '0x') transaction.to = to if (isHex(gas) && gas !== '0x') transaction.gas = hexToBigInt(gas) if (isHex(data) && data !== '0x') transaction.data = data if (isHex(nonce) && nonce !== '0x') transaction.nonce = hexToNumber(nonce) if (isHex(value) && value !== '0x') transaction.value = hexToBigInt(value) if (isHex(gasPrice) && gasPrice !== '0x') transaction.gasPrice = hexToBigInt(gasPrice) assertTransactionLegacy(transaction) if (transactionArray.length === 6) return transaction const chainIdOrV = isHex(chainIdOrV_) && chainIdOrV_ !== '0x' ? hexToNumber(chainIdOrV_ as Hex) : 0 if (s === '0x' && r === '0x') { if (chainIdOrV > 0) transaction.chainId = Number(chainIdOrV) return transaction } const v = chainIdOrV const chainId: number | undefined = Math.floor((v - 35) / 2) if (chainId > 0) transaction.chainId = chainId else if (v !== 27 && v !== 28) throw new InvalidLegacyVError({ v }) transaction.v = v transaction.s = s as Hex transaction.r = r as Hex transaction.yParity = v % 2 === 0 ? 1 : 0 return transaction } export type ToTransactionArrayErrorType = FromRlpErrorType | ErrorType export function toTransactionArray(serializedTransaction: string) { return fromRlp(`0x${serializedTransaction.slice(4)}` as Hex, 'hex') } export type ParseAccessListErrorType = | InvalidAddressErrorType | IsAddressErrorType | ErrorType export function parseAccessList(accessList_: RecursiveArray): AccessList { const accessList: Mutable = [] for (let i = 0; i < accessList_.length; i++) { const [address, storageKeys] = accessList_[i] as [Hex, Hex[]] if (!isAddress(address, { strict: false })) throw new InvalidAddressError({ address }) accessList.push({ address: address, storageKeys: storageKeys.map((key) => (isHash(key) ? key : trim(key))), }) } return accessList } type ParseAuthorizationListErrorType = | HexToNumberErrorType | ParseEip155SignatureErrorType | ErrorType function parseAuthorizationList( serializedAuthorizationList: SerializedAuthorizationList, ): SignedAuthorizationList { const authorizationList: Mutable = [] for (let i = 0; i < serializedAuthorizationList.length; i++) { const [chainId, contractAddress, nonce, yParity, r, s] = serializedAuthorizationList[i] authorizationList.push({ chainId: hexToNumber(chainId), contractAddress, nonce: hexToBigInt(nonce), ...parseEip155Signature([yParity, r, s]), }) } return authorizationList } type ParseEip155SignatureErrorType = | HexToBigIntErrorType | PadHexErrorType | ErrorType function parseEip155Signature( transactionArray: RecursiveArray, ): Signature & { yParity: number } { const signature = transactionArray.slice(-3) const v = signature[0] === '0x' || hexToNumber(signature[0] as Hex) === 0 ? 27 : 28 return { r: padHex(signature[1] as Hex, { size: 32 }), s: padHex(signature[2] as Hex, { size: 32 }), v, yParity: v === 27 ? 0 : 1, } }