@ethereumjs/tx
Version:
Implementation of the various Ethereum Transaction Types
591 lines (525 loc) • 17 kB
text/typescript
import { bytesToBigInt, toBytes } from '@ethereumjs/util'
import type { Common, Hardfork, ParamsDict } from '@ethereumjs/common'
import type {
Address,
AddressLike,
BigIntLike,
BytesLike,
EOACode7702AuthorizationList,
EOACode7702AuthorizationListBytes,
PrefixedHexString,
} from '@ethereumjs/util'
import type { FeeMarket1559Tx } from './1559/tx.ts'
import type { AccessList2930Tx } from './2930/tx.ts'
import type { Blob4844Tx } from './4844/tx.ts'
import type { EOACode7702Tx } from './7702/tx.ts'
import type { LegacyTx } from './legacy/tx.ts'
export type Capability = (typeof Capability)[keyof typeof Capability]
/**
* Can be used in conjunction with {@link Transaction[TransactionType].supports}
* to query on tx capabilities
*/
export const Capability = {
/**
* Tx supports EIP-155 replay protection
* See: [155](https://eips.ethereum.org/EIPS/eip-155) Replay Attack Protection EIP
*/
EIP155ReplayProtection: 155,
/**
* Tx supports EIP-1559 gas fee market mechanism
* See: [1559](https://eips.ethereum.org/EIPS/eip-1559) Fee Market EIP
*/
EIP1559FeeMarket: 1559,
/**
* Tx is a typed transaction as defined in EIP-2718
* See: [2718](https://eips.ethereum.org/EIPS/eip-2718) Transaction Type EIP
*/
EIP2718TypedTransaction: 2718,
/**
* Tx supports access list generation as defined in EIP-2930
* See: [2930](https://eips.ethereum.org/EIPS/eip-2930) Access Lists EIP
*/
EIP2930AccessLists: 2930,
/**
* Tx supports setting EOA code
* See [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)
*/
EIP7702EOACode: 7702,
}
/**
* The options for initializing a {@link Transaction}.
*/
export interface TxOptions {
/**
* A {@link Common} object defining the chain and hardfork for the transaction.
*
* Object will be internally copied so that tx behavior don't incidentally
* change on future HF changes.
*
* Default: {@link Common} object set to `mainnet` and the default hardfork as defined in the {@link Common} class.
*
* Current default hardfork: `istanbul`
*/
common?: Common
/**
* Tx parameters sorted by EIP can be found in the exported `paramsTx` dictionary,
* which is internally passed to the associated `@ethereumjs/common` instance which
* manages parameter selection based on the hardfork and EIP settings.
*
* This option allows providing a custom set of parameters. Note that parameters
* get fully overwritten, so you need to extend the default parameter dict
* to provide the full parameter set.
*
* It is recommended to deep-clone the params object for this to avoid side effects:
*
* ```ts
* const params = JSON.parse(JSON.stringify(paramsTx))
* params['1']['txGas'] = 30000 // 21000
* ```
*/
params?: ParamsDict
/**
* A transaction object by default gets frozen along initialization. This gives you
* strong additional security guarantees on the consistency of the tx parameters.
* It also enables tx hash caching when the `hash()` method is called multiple times.
*
* If you need to deactivate the tx freeze - e.g. because you want to subclass tx and
* add additional properties - it is strongly encouraged that you do the freeze yourself
* within your code instead.
*
* Default: true
*/
freeze?: boolean
/**
* Allows unlimited contract code-size init while debugging. This (partially) disables EIP-3860.
* Gas cost for initcode size analysis will still be charged. Use with caution.
*/
allowUnlimitedInitCodeSize?: boolean
}
export function isAccessListBytes(input: AccessListBytes | AccessList): input is AccessListBytes {
if (input.length === 0) {
return true
}
const firstItem = input[0]
if (Array.isArray(firstItem)) {
return true
}
return false
}
export function isAccessList(input: AccessListBytes | AccessList): input is AccessList {
return !isAccessListBytes(input) // This is exactly the same method, except the output is negated.
}
export interface TransactionCache {
hash?: Uint8Array
dataFee?: {
value: bigint
hardfork: string | Hardfork
}
senderPubKey?: Uint8Array
// TODO: re-add these cache items for the JSON
// See: https://github.com/ethereumjs/ethereumjs-monorepo/issues/3932
//accessListJSON?: AccessList
//authorityListJSON?: EOACode7702AuthorizationList
}
export type TransactionType = (typeof TransactionType)[keyof typeof TransactionType]
export const TransactionType = {
Legacy: 0,
AccessListEIP2930: 1,
FeeMarketEIP1559: 2,
BlobEIP4844: 3,
EOACodeEIP7702: 4,
} as const
export interface Transaction {
[]: LegacyTx
[]: FeeMarket1559Tx
[]: AccessList2930Tx
[]: Blob4844Tx
[]: EOACode7702Tx
}
export type TypedTransaction = Transaction[TransactionType]
export function isLegacyTx(tx: TypedTransaction): tx is LegacyTx {
return tx.type === TransactionType.Legacy
}
export function isAccessList2930Tx(tx: TypedTransaction): tx is AccessList2930Tx {
return tx.type === TransactionType.AccessListEIP2930
}
export function isFeeMarket1559Tx(tx: TypedTransaction): tx is FeeMarket1559Tx {
return tx.type === TransactionType.FeeMarketEIP1559
}
export function isBlob4844Tx(tx: TypedTransaction): tx is Blob4844Tx {
return tx.type === TransactionType.BlobEIP4844
}
export function isEOACode7702Tx(tx: TypedTransaction): tx is EOACode7702Tx {
return tx.type === TransactionType.EOACodeEIP7702
}
export interface TransactionInterface<T extends TransactionType = TransactionType> {
readonly common: Common
readonly nonce: bigint
readonly gasLimit: bigint
readonly to?: Address
readonly value: bigint
readonly data: Uint8Array
readonly v?: bigint
readonly r?: bigint
readonly s?: bigint
readonly cache: TransactionCache
supports(capability: Capability): boolean
type: TransactionType
txOptions: TxOptions
getIntrinsicGas(): bigint
getDataGas(): bigint
getUpfrontCost(): bigint
toCreationAddress(): boolean
raw(): TxValuesArray[T]
serialize(): Uint8Array
getMessageToSign(): Uint8Array | Uint8Array[]
getHashedMessageToSign(): Uint8Array
hash(): Uint8Array
getMessageToVerifySignature(): Uint8Array
getValidationErrors(): string[]
isSigned(): boolean
isValid(): boolean
verifySignature(): boolean
getSenderAddress(): Address
getSenderPublicKey(): Uint8Array
sign(privateKey: Uint8Array, extraEntropy?: Uint8Array | boolean): Transaction[T]
toJSON(): JSONTx
errorStr(): string
addSignature(
v: bigint,
r: Uint8Array | bigint,
s: Uint8Array | bigint,
convertV?: boolean,
): Transaction[T]
}
export interface LegacyTxInterface<T extends TransactionType = TransactionType>
extends TransactionInterface<T> {}
export interface EIP2718CompatibleTx<T extends TransactionType = TransactionType>
extends TransactionInterface<T> {
readonly chainId: bigint
getMessageToSign(): Uint8Array
}
export interface EIP2930CompatibleTx<T extends TransactionType = TransactionType>
extends EIP2718CompatibleTx<T> {
readonly accessList: AccessListBytes
}
export interface EIP1559CompatibleTx<T extends TransactionType = TransactionType>
extends EIP2930CompatibleTx<T> {
readonly maxPriorityFeePerGas: bigint
readonly maxFeePerGas: bigint
}
export interface EIP4844CompatibleTx<T extends TransactionType = TransactionType>
extends EIP1559CompatibleTx<T> {
readonly maxFeePerBlobGas: bigint
blobVersionedHashes: Uint8Array[]
blobs?: Uint8Array[]
kzgCommitments?: Uint8Array[]
kzgProofs?: Uint8Array[]
serializeNetworkWrapper(): Uint8Array
numBlobs(): number
}
export interface EIP7702CompatibleTx<T extends TransactionType = TransactionType>
extends EIP1559CompatibleTx<T> {
// ChainID, Address, [nonce], y_parity, r, s
readonly authorizationList: EOACode7702AuthorizationListBytes
}
export interface TxData {
[]: LegacyTxData
[]: AccessList2930TxData
[]: FeeMarketEIP1559TxData
[]: BlobEIP4844TxData
[]: EOACode7702TxData
}
export type TypedTxData = TxData[TransactionType]
export function isLegacyTxData(txData: TypedTxData): txData is LegacyTxData {
const txType = Number(bytesToBigInt(toBytes(txData.type)))
return txType === TransactionType.Legacy
}
export function isAccessList2930TxData(txData: TypedTxData): txData is AccessList2930TxData {
const txType = Number(bytesToBigInt(toBytes(txData.type)))
return txType === TransactionType.AccessListEIP2930
}
export function isFeeMarket1559TxData(txData: TypedTxData): txData is FeeMarketEIP1559TxData {
const txType = Number(bytesToBigInt(toBytes(txData.type)))
return txType === TransactionType.FeeMarketEIP1559
}
export function isBlob4844TxData(txData: TypedTxData): txData is BlobEIP4844TxData {
const txType = Number(bytesToBigInt(toBytes(txData.type)))
return txType === TransactionType.BlobEIP4844
}
export function isEOACode7702TxData(txData: TypedTxData): txData is EOACode7702TxData {
const txType = Number(bytesToBigInt(toBytes(txData.type)))
return txType === TransactionType.EOACodeEIP7702
}
/**
* Legacy {@link Transaction} Data
*/
export type LegacyTxData = {
/**
* The transaction's nonce.
*/
nonce?: BigIntLike
/**
* The transaction's gas price.
*/
gasPrice?: BigIntLike | null
/**
* The transaction's gas limit.
*/
gasLimit?: BigIntLike
/**
* The transaction's the address is sent to.
*/
to?: AddressLike | ''
/**
* The amount of Ether sent.
*/
value?: BigIntLike
/**
* This will contain the data of the message or the init of a contract.
*/
data?: BytesLike | ''
/**
* EC recovery ID.
*/
v?: BigIntLike
/**
* EC signature parameter.
*/
r?: BigIntLike
/**
* EC signature parameter.
*/
s?: BigIntLike
/**
* The transaction type
*/
type?: BigIntLike
}
/**
* {@link AccessList2930Tx} data.
*/
export interface AccessList2930TxData extends LegacyTxData {
/**
* The transaction's chain ID
*/
chainId?: BigIntLike
/**
* The access list which contains the addresses/storage slots which the transaction wishes to access
*/
accessList?: AccessListBytes | AccessList | null
}
/**
* {@link FeeMarket1559Tx} data.
*/
export interface FeeMarketEIP1559TxData extends AccessList2930TxData {
/**
* The transaction's gas price, inherited from {@link Transaction}. This property is not used for EIP1559
* transactions and should always be undefined for this specific transaction type.
*/
gasPrice?: never | null
/**
* The maximum inclusion fee per gas (this fee is given to the miner)
*/
maxPriorityFeePerGas?: BigIntLike
/**
* The maximum total fee
*/
maxFeePerGas?: BigIntLike
}
/**
* {@link Blob4844Tx} data.
*/
export interface BlobEIP4844TxData extends FeeMarketEIP1559TxData {
/**
* The versioned hashes used to validate the blobs attached to a transaction
*/
blobVersionedHashes?: BytesLike[]
/**
* The maximum fee per blob gas paid for the transaction
*/
maxFeePerBlobGas?: BigIntLike
/**
* The blobs associated with a transaction
*/
blobs?: BytesLike[]
/**
* The KZG commitments corresponding to the versioned hashes for each blob
*/
kzgCommitments?: BytesLike[]
/**
* The KZG proofs associated with the transaction
*/
kzgProofs?: BytesLike[]
/**
* An array of arbitrary strings that blobs are to be constructed from
*/
blobsData?: string[]
}
/**
* {@link EOACode7702Tx} data.
*/
export interface EOACode7702TxData extends FeeMarketEIP1559TxData {
authorizationList?: EOACode7702AuthorizationListBytes | EOACode7702AuthorizationList | never
}
export interface TxValuesArray {
[]: LegacyTxValuesArray
[]: AccessList2930TxValuesArray
[]: FeeMarketEIP1559TxValuesArray
[]: BlobEIP4844TxValuesArray
[]: EOACode7702TxValuesArray
}
/**
* Bytes values array for a legacy {@link Transaction}
*/
type LegacyTxValuesArray = Uint8Array[]
/**
* Bytes values array for an {@link AccessList2930Tx}
*/
type AccessList2930TxValuesArray = [
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
AccessListBytes,
Uint8Array?,
Uint8Array?,
Uint8Array?,
]
/**
* Bytes values array for a {@link FeeMarket1559Tx}
*/
type FeeMarketEIP1559TxValuesArray = [
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
AccessListBytes,
Uint8Array?,
Uint8Array?,
Uint8Array?,
]
/**
* Bytes values array for a {@link EOACode7702Transaction}
*/
type EOACode7702TxValuesArray = [
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
AccessListBytes,
EOACode7702AuthorizationListBytes,
Uint8Array?,
Uint8Array?,
Uint8Array?,
]
/**
* Bytes values array for a {@link Blob4844Tx}
*/
type BlobEIP4844TxValuesArray = [
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
Uint8Array,
AccessListBytes,
Uint8Array,
Uint8Array[],
Uint8Array?,
Uint8Array?,
Uint8Array?,
]
export type BlobEIP4844NetworkValuesArray = [
BlobEIP4844TxValuesArray,
Uint8Array[],
Uint8Array[],
Uint8Array[],
]
type JSONAccessListItem = { address: string; storageKeys: string[] }
/**
* Generic interface for all tx types with a
* JSON representation of a transaction.
*
* Note that all values are marked as optional
* and not all the values are present on all tx types
* (an EIP1559 tx e.g. lacks a `gasPrice`).
*/
export interface JSONTx {
nonce?: PrefixedHexString
gasPrice?: PrefixedHexString
gasLimit?: PrefixedHexString
to?: PrefixedHexString
data?: PrefixedHexString
v?: PrefixedHexString
r?: PrefixedHexString
s?: PrefixedHexString
value?: PrefixedHexString
chainId?: PrefixedHexString
accessList?: JSONAccessListItem[] // TODO should this not be AccessList?
authorizationList?: EOACode7702AuthorizationList
type?: PrefixedHexString
maxPriorityFeePerGas?: PrefixedHexString
maxFeePerGas?: PrefixedHexString
maxFeePerBlobGas?: PrefixedHexString
blobVersionedHashes?: PrefixedHexString[]
yParity?: PrefixedHexString
}
export type JSONBlobTxNetworkWrapper = JSONTx & {
blobs: PrefixedHexString[]
kzgCommitments: PrefixedHexString[]
kzgProofs: PrefixedHexString[]
}
/*
* Based on https://ethereum.org/en/developers/docs/apis/json-rpc/
*/
export interface JSONRPCTx {
blockHash: string | null // DATA, 32 Bytes - hash of the block where this transaction was in. null when it's pending.
blockNumber: string | null // QUANTITY - block number where this transaction was in. null when it's pending.
from: string // DATA, 20 Bytes - address of the sender.
gas: string // QUANTITY - gas provided by the sender.
gasPrice: string // QUANTITY - gas price provided by the sender in wei. If EIP-1559 tx, defaults to maxFeePerGas.
maxFeePerGas?: string // QUANTITY - max total fee per gas provided by the sender in wei.
maxPriorityFeePerGas?: string // QUANTITY - max priority fee per gas provided by the sender in wei.
type: string // QUANTITY - EIP-2718 Typed Transaction type
accessList?: JSONTx['accessList'] // EIP-2930 access list
chainId?: string // Chain ID that this transaction is valid on.
hash: string // DATA, 32 Bytes - hash of the transaction.
input: string // DATA - the data send along with the transaction.
nonce: string // QUANTITY - the number of transactions made by the sender prior to this one.
to: string | null /// DATA, 20 Bytes - address of the receiver. null when it's a contract creation transaction.
transactionIndex: string | null // QUANTITY - integer of the transactions index position in the block. null when it's pending.
value: string // QUANTITY - value transferred in Wei.
v: string // QUANTITY - ECDSA recovery id
r: string // DATA, 32 Bytes - ECDSA signature r
s: string // DATA, 32 Bytes - ECDSA signature s
maxFeePerBlobGas?: string // QUANTITY - max data fee for blob transactions
blobVersionedHashes?: string[] // DATA - array of 32 byte versioned hashes for blob transactions
yParity?: string // DATA - parity of the y-coordinate of the public key
}
/*
* Access List types
*/
export type AccessListItem = {
address: PrefixedHexString
storageKeys: PrefixedHexString[]
}
/*
* An Access List as a tuple of [address: Uint8Array, storageKeys: Uint8Array[]]
*/
export type AccessListBytesItem = [Uint8Array, Uint8Array[]]
export type AccessListBytes = AccessListBytesItem[]
export type AccessList = AccessListItem[]