UNPKG

viem

Version:

TypeScript Interface for Ethereum

350 lines • 13.8 kB
import { InvalidAddressError, } from '../../errors/address.js'; import { InvalidLegacyVError, InvalidSerializedTransactionError, } from '../../errors/transaction.js'; import { isAddress } from '../address/isAddress.js'; import { toBlobSidecars } from '../blob/toBlobSidecars.js'; import { isHex } from '../data/isHex.js'; import { padHex } from '../data/pad.js'; import { trim } from '../data/trim.js'; import { hexToBigInt, hexToNumber, } from '../encoding/fromHex.js'; import { fromRlp } from '../encoding/fromRlp.js'; import { isHash } from '../hash/isHash.js'; import { assertTransactionEIP1559, assertTransactionEIP2930, assertTransactionEIP4844, assertTransactionEIP7702, assertTransactionLegacy, } from './assertTransaction.js'; import { getSerializedTransactionType, } from './getSerializedTransactionType.js'; export function parseTransaction(serializedTransaction) { const type = getSerializedTransactionType(serializedTransaction); if (type === 'eip1559') return parseTransactionEIP1559(serializedTransaction); if (type === 'eip2930') return parseTransactionEIP2930(serializedTransaction); if (type === 'eip4844') return parseTransactionEIP4844(serializedTransaction); if (type === 'eip7702') return parseTransactionEIP7702(serializedTransaction); return parseTransactionLegacy(serializedTransaction); } function parseTransactionEIP7702(serializedTransaction) { 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), type: 'eip7702', }; 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); if (authorizationList.length !== 0 && authorizationList !== '0x') transaction.authorizationList = parseAuthorizationList(authorizationList); assertTransactionEIP7702(transaction); const signature = transactionArray.length === 13 ? parseEIP155Signature(transactionArray) : undefined; return { ...signature, ...transaction }; } function parseTransactionEIP4844(serializedTransaction) { 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, chainId: hexToNumber(chainId), type: 'eip4844', }; 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); if (blobs && commitments && proofs) transaction.sidecars = toBlobSidecars({ blobs: blobs, commitments: commitments, proofs: proofs, }); assertTransactionEIP4844(transaction); const signature = transactionArray.length === 14 ? parseEIP155Signature(transactionArray) : undefined; return { ...signature, ...transaction }; } function parseTransactionEIP1559(serializedTransaction) { 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 = { chainId: hexToNumber(chainId), 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); assertTransactionEIP1559(transaction); const signature = transactionArray.length === 12 ? parseEIP155Signature(transactionArray) : undefined; return { ...signature, ...transaction }; } function parseTransactionEIP2930(serializedTransaction) { 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 = { chainId: hexToNumber(chainId), 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); assertTransactionEIP2930(transaction); const signature = transactionArray.length === 11 ? parseEIP155Signature(transactionArray) : undefined; return { ...signature, ...transaction }; } function parseTransactionLegacy(serializedTransaction) { 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 = { 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' ? hexToBigInt(chainIdOrV_) : 0n; if (s === '0x' && r === '0x') { if (chainIdOrV > 0) transaction.chainId = Number(chainIdOrV); return transaction; } const v = chainIdOrV; const chainId = Number((v - 35n) / 2n); if (chainId > 0) transaction.chainId = chainId; else if (v !== 27n && v !== 28n) throw new InvalidLegacyVError({ v }); transaction.v = v; transaction.s = s; transaction.r = r; transaction.yParity = v % 2n === 0n ? 1 : 0; return transaction; } export function toTransactionArray(serializedTransaction) { return fromRlp(`0x${serializedTransaction.slice(4)}`, 'hex'); } export function parseAccessList(accessList_) { const accessList = []; for (let i = 0; i < accessList_.length; i++) { const [address, storageKeys] = accessList_[i]; if (!isAddress(address, { strict: false })) throw new InvalidAddressError({ address }); accessList.push({ address: address, storageKeys: storageKeys.map((key) => (isHash(key) ? key : trim(key))), }); } return accessList; } function parseAuthorizationList(serializedAuthorizationList) { const authorizationList = []; for (let i = 0; i < serializedAuthorizationList.length; i++) { const [chainId, address, nonce, yParity, r, s] = serializedAuthorizationList[i]; authorizationList.push({ address, chainId: hexToNumber(chainId), nonce: hexToNumber(nonce), ...parseEIP155Signature([yParity, r, s]), }); } return authorizationList; } function parseEIP155Signature(transactionArray) { const signature = transactionArray.slice(-3); const v = signature[0] === '0x' || hexToBigInt(signature[0]) === 0n ? 27n : 28n; return { r: padHex(signature[1], { size: 32 }), s: padHex(signature[2], { size: 32 }), v, yParity: v === 27n ? 0 : 1, }; } //# sourceMappingURL=parseTransaction.js.map