diff --git a/packages/tx/src/1559/tx.ts b/packages/tx/src/1559/tx.ts index 00423967c81..753baa65454 100644 --- a/packages/tx/src/1559/tx.ts +++ b/packages/tx/src/1559/tx.ts @@ -12,7 +12,7 @@ import * as EIP1559 from '../capabilities/eip1559.js' import * as EIP2718 from '../capabilities/eip2718.js' import * as EIP2930 from '../capabilities/eip2930.js' import * as Legacy from '../capabilities/legacy.js' -import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' +import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../capabilities/generic.js' import { TransactionType } from '../types.js' import { AccessLists } from '../util.js' diff --git a/packages/tx/src/2930/tx.ts b/packages/tx/src/2930/tx.ts index f827d2518ff..1f965f87e09 100644 --- a/packages/tx/src/2930/tx.ts +++ b/packages/tx/src/2930/tx.ts @@ -9,8 +9,8 @@ import { import * as EIP2718 from '../capabilities/eip2718.js' import * as EIP2930 from '../capabilities/eip2930.js' +import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../capabilities/generic.js' import * as Legacy from '../capabilities/legacy.js' -import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { TransactionType } from '../types.js' import { AccessLists } from '../util.js' @@ -72,7 +72,7 @@ export class AccessList2930Tx implements TransactionInterface = { // represents the constructor of baseTransaction // Note: have to use `Mutable` to write to readonly props. Only call this in constructor of txs. export function sharedConstructor( - tx: Mutable, + tx: Mutable, txData: TxData[TransactionType], opts: TxOptions = {}, ) { @@ -122,7 +122,7 @@ export function sharedConstructor( } } -export function getBaseJSON(tx: TransactionInterface) { +export function getBaseJSON(tx: L1TxInterface) { return { type: bigIntToHex(BigInt(tx.type)), nonce: bigIntToHex(tx.nonce), diff --git a/packages/tx/src/capabilities/legacy.ts b/packages/tx/src/capabilities/legacy.ts index be560641687..394219dce16 100644 --- a/packages/tx/src/capabilities/legacy.ts +++ b/packages/tx/src/capabilities/legacy.ts @@ -1,22 +1,36 @@ +import { RLP } from '@ethereumjs/rlp' import { Address, BIGINT_0, + BIGINT_2, + BIGINT_8, SECP256K1_ORDER_DIV_2, + bigIntToHex, bigIntToUnpaddedBytes, + bytesToBigInt, bytesToHex, ecrecover, ecsign, publicToAddress, + toBytes, unpadBytes, } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' +import { getBaseJSON } from '../capabilities/generic.js' +import { createLegacyTx } from '../legacy/constructors.js' import { Capability, TransactionType } from '../types.js' -import type { LegacyTxInterface, Transaction } from '../types.js' +import type { JSONTx, LegacyTxInterface, TxValuesArray } from '../types.js' + +export function errorStr(tx: LegacyTxInterface) { + let errorStr = getSharedErrorPostfix(tx) + errorStr += ` gasPrice=${tx.gasPrice}` + return errorStr +} export function errorMsg(tx: LegacyTxInterface, msg: string) { - return `${msg} (${tx.errorStr()})` + return `${msg} (${errorStr(tx)})` } export function isSigned(tx: LegacyTxInterface): boolean { @@ -68,9 +82,9 @@ export function getDataGas(tx: LegacyTxInterface, extraCost?: bigint): bigint { */ export function getIntrinsicGas(tx: LegacyTxInterface): bigint { const txFee = tx.common.param('txGas') - let fee = tx.getDataGas() + let fee = getDataGas(tx) if (txFee) fee += txFee - if (tx.common.gteHardfork('homestead') && tx.toCreationAddress()) { + if (tx.common.gteHardfork('homestead') && toCreationAddress(tx)) { const txCreationFee = tx.common.param('txCreationGas') if (txCreationFee) fee += txCreationFee } @@ -82,7 +96,7 @@ export function toCreationAddress(tx: LegacyTxInterface): boolean { } export function hash(tx: LegacyTxInterface): Uint8Array { - if (!tx.isSigned()) { + if (!isSigned(tx)) { const msg = errorMsg(tx, 'Cannot call hash method if transaction is not signed') throw new Error(msg) } @@ -91,12 +105,12 @@ export function hash(tx: LegacyTxInterface): Uint8Array { if (Object.isFrozen(tx)) { if (!tx.cache.hash) { - tx.cache.hash = keccakFunction(tx.serialize()) + tx.cache.hash = keccakFunction(serialize(tx)) } return tx.cache.hash } - return keccakFunction(tx.serialize()) + return keccakFunction(serialize(tx)) } /** @@ -119,7 +133,7 @@ export function getSenderPublicKey(tx: LegacyTxInterface): Uint8Array { return tx.cache.senderPubKey } - const msgHash = tx.getMessageToVerifySignature() + const msgHash = getMessageToVerifySignature(tx) const { v, r, s } = tx @@ -132,7 +146,9 @@ export function getSenderPublicKey(tx: LegacyTxInterface): Uint8Array { v!, bigIntToUnpaddedBytes(r!), bigIntToUnpaddedBytes(s!), - tx.supports(Capability.EIP155ReplayProtection) ? tx.common.chainId() : undefined, + tx.activeCapabilities.includes(Capability.EIP155ReplayProtection) + ? tx.common.chainId() + : undefined, ) if (Object.isFrozen(tx)) { tx.cache.senderPubKey = sender @@ -163,12 +179,12 @@ export function getEffectivePriorityFee(gasPrice: bigint, baseFee: bigint | unde export function getValidationErrors(tx: LegacyTxInterface): string[] { const errors = [] - if (tx.isSigned() && !tx.verifySignature()) { + if (isSigned(tx) && !verifySignature(tx)) { errors.push('Invalid Signature') } - if (tx.getIntrinsicGas() > tx.gasLimit) { - errors.push(`gasLimit is too low. given ${tx.gasLimit}, need at least ${tx.getIntrinsicGas()}`) + if (getIntrinsicGas(tx) > tx.gasLimit) { + errors.push(`gasLimit is too low. given ${tx.gasLimit}, need at least ${getIntrinsicGas(tx)}`) } return errors @@ -179,7 +195,7 @@ export function getValidationErrors(tx: LegacyTxInterface): string[] { * @returns {boolean} true if the transaction is valid, false otherwise */ export function isValid(tx: LegacyTxInterface): boolean { - const errors = tx.getValidationErrors() + const errors = getValidationErrors(tx) return errors.length === 0 } @@ -190,7 +206,7 @@ export function isValid(tx: LegacyTxInterface): boolean { export function verifySignature(tx: LegacyTxInterface): boolean { try { // Main signature verification is done in `getSenderPublicKey()` - const publicKey = tx.getSenderPublicKey() + const publicKey = getSenderPublicKey(tx) return unpadBytes(publicKey).length !== 0 } catch (e: any) { return false @@ -201,7 +217,7 @@ export function verifySignature(tx: LegacyTxInterface): boolean { * Returns the sender's address */ export function getSenderAddress(tx: LegacyTxInterface): Address { - return new Address(publicToAddress(tx.getSenderPublicKey())) + return new Address(publicToAddress(getSenderPublicKey(tx))) } /** @@ -213,7 +229,7 @@ export function getSenderAddress(tx: LegacyTxInterface): Address { * const signedTx = tx.sign(privateKey) * ``` */ -export function sign(tx: LegacyTxInterface, privateKey: Uint8Array): Transaction[TransactionType] { +export function sign(tx: LegacyTxInterface, privateKey: Uint8Array): LegacyTxInterface { if (privateKey.length !== 32) { // TODO figure out this errorMsg logic how this diverges on other txs const msg = errorMsg(tx, 'Private key must be 32 bytes in length.') @@ -230,17 +246,17 @@ export function sign(tx: LegacyTxInterface, privateKey: Uint8Array): Transaction if ( tx.type === TransactionType.Legacy && tx.common.gteHardfork('spuriousDragon') && - !tx.supports(Capability.EIP155ReplayProtection) + !tx.activeCapabilities.includes(Capability.EIP155ReplayProtection) ) { // cast as any to edit the protected `activeCapabilities` ;(tx as any).activeCapabilities.push(Capability.EIP155ReplayProtection) hackApplied = true } - const msgHash = tx.getHashedMessageToSign() + const msgHash = getHashedMessageToSign(tx) const ecSignFunction = tx.common.customCrypto?.ecsign ?? ecsign const { v, r, s } = ecSignFunction(msgHash, privateKey) - const signedTx = tx.addSignature(v, r, s, true) + const signedTx = addSignature(tx, v, r, s, true) // Hack part 2 if (hackApplied) { @@ -257,17 +273,17 @@ export function sign(tx: LegacyTxInterface, privateKey: Uint8Array): Transaction // TODO maybe move this to shared methods (util.ts in features) export function getSharedErrorPostfix(tx: LegacyTxInterface) { - let hash = '' + let hashStr = '' try { - hash = tx.isSigned() ? bytesToHex(tx.hash()) : 'not available (unsigned)' + hashStr = isSigned(tx) ? bytesToHex(hash(tx)) : 'not available (unsigned)' } catch (e: any) { - hash = 'error' + hashStr = 'error' } - let isSigned = '' + let isSignedStr = '' try { - isSigned = tx.isSigned().toString() + isSignedStr = isSigned(tx).toString() } catch (e: any) { - hash = 'error' + hashStr = 'error' } let hf = '' try { @@ -276,8 +292,147 @@ export function getSharedErrorPostfix(tx: LegacyTxInterface) { hf = 'error' } - let postfix = `tx type=${tx.type} hash=${hash} nonce=${tx.nonce} value=${tx.value} ` - postfix += `signed=${isSigned} hf=${hf}` + let postfix = `tx type=${tx.type} hash=${hashStr} nonce=${tx.nonce} value=${tx.value} ` + postfix += `signed=${isSignedStr} hf=${hf}` return postfix } + +/** + * Returns the serialized encoding of the legacy transaction. + * + * Format: `rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s])` + * + * For an unsigned tx this method uses the empty Uint8Array values for the + * signature parameters `v`, `r` and `s` for encoding. For an EIP-155 compliant + * representation for external signing use {@link Transaction.getMessageToSign}. + */ +export function serialize(tx: LegacyTxInterface): Uint8Array { + return RLP.encode(raw(tx)) +} + +/** + * Returns a Uint8Array Array of the raw Bytes of the legacy transaction, in order. + * + * Format: `[nonce, gasPrice, gasLimit, to, value, data, v, r, s]` + * + * For legacy txs this is also the correct format to add transactions + * to a block with {@link createBlockFromBytesArray} (use the `serialize()` method + * for typed txs). + * + * For an unsigned tx this method returns the empty Bytes values + * for the signature parameters `v`, `r` and `s`. For an EIP-155 compliant + * representation have a look at {@link Transaction.getMessageToSign}. + */ +export function raw(tx: LegacyTxInterface): TxValuesArray[TransactionType.Legacy] { + return [ + bigIntToUnpaddedBytes(tx.nonce), + bigIntToUnpaddedBytes(tx.gasPrice), + bigIntToUnpaddedBytes(tx.gasLimit), + tx.to !== undefined ? tx.to.bytes : new Uint8Array(0), + bigIntToUnpaddedBytes(tx.value), + tx.data, + tx.v !== undefined ? bigIntToUnpaddedBytes(tx.v) : new Uint8Array(0), + tx.r !== undefined ? bigIntToUnpaddedBytes(tx.r) : new Uint8Array(0), + tx.s !== undefined ? bigIntToUnpaddedBytes(tx.s) : new Uint8Array(0), + ] +} + +/** + * Returns the raw unsigned tx, which can be used + * to sign the transaction (e.g. for sending to a hardware wallet). + * + * Note: the raw message message format for the legacy tx is not RLP encoded + * and you might need to do yourself with: + * + * ```javascript + * import { RLP } from '@ethereumjs/rlp' + * const message = tx.getMessageToSign() + * const serializedMessage = RLP.encode(message)) // use this for the HW wallet input + * ``` + */ +export function getMessageToSign(tx: LegacyTxInterface): Uint8Array[] { + const message = [ + bigIntToUnpaddedBytes(tx.nonce), + bigIntToUnpaddedBytes(tx.gasPrice), + bigIntToUnpaddedBytes(tx.gasLimit), + tx.to !== undefined ? tx.to.bytes : new Uint8Array(0), + bigIntToUnpaddedBytes(tx.value), + tx.data, + ] + + if (tx.activeCapabilities.includes(Capability.EIP155ReplayProtection)) { + message.push(bigIntToUnpaddedBytes(tx.common.chainId())) + message.push(unpadBytes(toBytes(0))) + message.push(unpadBytes(toBytes(0))) + } + + return message +} + +export function getHashedMessageToSign(tx: LegacyTxInterface) { + const message = getMessageToSign(tx) + const keccakFunction = tx.common.customCrypto.keccak256 ?? keccak256 + return keccakFunction(RLP.encode(message)) +} + +/** + * The up front amount that an account must have for this transaction to be valid + */ +export function getUpfrontCost(tx: LegacyTxInterface): bigint { + return tx.gasLimit * tx.gasPrice + tx.value +} + +/** + * Computes a sha3-256 hash which can be used to verify the signature + */ +export function getMessageToVerifySignature(tx: LegacyTxInterface) { + if (!isSigned(tx)) { + const msg = errorMsg(tx, 'This transaction is not signed') + throw new Error(msg) + } + return getHashedMessageToSign(tx) +} + +export function addSignature( + tx: LegacyTxInterface, + v: bigint, + r: Uint8Array | bigint, + s: Uint8Array | bigint, + convertV: boolean = false, +): LegacyTxInterface { + r = toBytes(r) + s = toBytes(s) + if (convertV && tx.activeCapabilities.includes(Capability.EIP155ReplayProtection)) { + v += tx.common.chainId() * BIGINT_2 + BIGINT_8 + } + + const opts = { ...tx.txOptions, common: tx.common } + + return createLegacyTx( + { + nonce: tx.nonce, + gasPrice: tx.gasPrice, + gasLimit: tx.gasLimit, + to: tx.to, + value: tx.value, + data: tx.data, + v, + r: bytesToBigInt(r), + s: bytesToBigInt(s), + }, + opts, + ) +} + +/** + * Returns an object with the JSON representation of the transaction. + */ +export function toJSON(tx: LegacyTxInterface): JSONTx { + // TODO this is just copied. Make this execution-api compliant + + const baseJSON = getBaseJSON(tx) as JSONTx + baseJSON.gasPrice = bigIntToHex(tx.gasPrice) + + return baseJSON +} diff --git a/packages/tx/src/legacy/tx.ts b/packages/tx/src/legacy/tx.ts index a576f4f7ff7..5357ab5f7e3 100644 --- a/packages/tx/src/legacy/tx.ts +++ b/packages/tx/src/legacy/tx.ts @@ -1,29 +1,16 @@ -import { RLP } from '@ethereumjs/rlp' -import { - BIGINT_2, - BIGINT_8, - MAX_INTEGER, - bigIntToHex, - bigIntToUnpaddedBytes, - bytesToBigInt, - toBytes, - unpadBytes, -} from '@ethereumjs/util' +import { BIGINT_2, MAX_INTEGER, bytesToBigInt, toBytes } from '@ethereumjs/util' import { keccak256 } from 'ethereum-cryptography/keccak.js' +import { sharedConstructor, valueBoundaryCheck } from '../capabilities/generic.js' import * as Legacy from '../capabilities/legacy.js' -import { getBaseJSON, sharedConstructor, valueBoundaryCheck } from '../features/util.js' import { paramsTx } from '../index.js' import { Capability, TransactionType } from '../types.js' -import { createLegacyTx } from './constructors.js' - import type { TxData as AllTypesTxData, TxValuesArray as AllTypesTxValuesArray, - JSONTx, + LegacyTxInterface, TransactionCache, - TransactionInterface, TxOptions, } from '../types.js' import type { Common } from '@ethereumjs/common' @@ -78,7 +65,7 @@ function validateVAndExtractChainID(common: Common, _v?: bigint): BigInt | undef /** * An Ethereum non-typed (legacy) transaction */ -export class LegacyTx implements TransactionInterface { +export class LegacyTx implements LegacyTxInterface { /* Tx public data fields */ public type: number = TransactionType.Legacy // Legacy tx type @@ -110,7 +97,7 @@ export class LegacyTx implements TransactionInterface { * e.g. 1559 (fee market) and 2930 (access lists) * for FeeMarket1559Tx objects */ - protected activeCapabilities: number[] = [] + public activeCapabilities: number[] = [] /** * This constructor takes the values, validates them, assigns them and freezes the object. @@ -142,7 +129,7 @@ export class LegacyTx implements TransactionInterface { } if (this.common.gteHardfork('spuriousDragon')) { - if (!this.isSigned()) { + if (!Legacy.isSigned(this)) { this.activeCapabilities.push(Capability.EIP155ReplayProtection) } else { // EIP155 spec: @@ -162,243 +149,4 @@ export class LegacyTx implements TransactionInterface { Object.freeze(this) } } - - /** - * Checks if a tx type defining capability is active - * on a tx, for example the EIP-1559 fee market mechanism - * or the EIP-2930 access list feature. - * - * Note that this is different from the tx type itself, - * so EIP-2930 access lists can very well be active - * on an EIP-1559 tx for example. - * - * This method can be useful for feature checks if the - * tx type is unknown (e.g. when instantiated with - * the tx factory). - * - * See `Capabilities` in the `types` module for a reference - * on all supported capabilities. - */ - supports(capability: Capability) { - return this.activeCapabilities.includes(capability) - } - - isSigned(): boolean { - return Legacy.isSigned(this) - } - - getEffectivePriorityFee(baseFee?: bigint): bigint { - return Legacy.getEffectivePriorityFee(this.gasPrice, baseFee) - } - - /** - * Returns a Uint8Array Array of the raw Bytes of the legacy transaction, in order. - * - * Format: `[nonce, gasPrice, gasLimit, to, value, data, v, r, s]` - * - * For legacy txs this is also the correct format to add transactions - * to a block with {@link createBlockFromBytesArray} (use the `serialize()` method - * for typed txs). - * - * For an unsigned tx this method returns the empty Bytes values - * for the signature parameters `v`, `r` and `s`. For an EIP-155 compliant - * representation have a look at {@link Transaction.getMessageToSign}. - */ - raw(): TxValuesArray { - return [ - bigIntToUnpaddedBytes(this.nonce), - bigIntToUnpaddedBytes(this.gasPrice), - bigIntToUnpaddedBytes(this.gasLimit), - this.to !== undefined ? this.to.bytes : new Uint8Array(0), - bigIntToUnpaddedBytes(this.value), - this.data, - this.v !== undefined ? bigIntToUnpaddedBytes(this.v) : new Uint8Array(0), - this.r !== undefined ? bigIntToUnpaddedBytes(this.r) : new Uint8Array(0), - this.s !== undefined ? bigIntToUnpaddedBytes(this.s) : new Uint8Array(0), - ] - } - - /** - * Returns the serialized encoding of the legacy transaction. - * - * Format: `rlp([nonce, gasPrice, gasLimit, to, value, data, v, r, s])` - * - * For an unsigned tx this method uses the empty Uint8Array values for the - * signature parameters `v`, `r` and `s` for encoding. For an EIP-155 compliant - * representation for external signing use {@link Transaction.getMessageToSign}. - */ - serialize(): Uint8Array { - return RLP.encode(this.raw()) - } - - /** - * Returns the raw unsigned tx, which can be used - * to sign the transaction (e.g. for sending to a hardware wallet). - * - * Note: the raw message message format for the legacy tx is not RLP encoded - * and you might need to do yourself with: - * - * ```javascript - * import { RLP } from '@ethereumjs/rlp' - * const message = tx.getMessageToSign() - * const serializedMessage = RLP.encode(message)) // use this for the HW wallet input - * ``` - */ - getMessageToSign(): Uint8Array[] { - const message = [ - bigIntToUnpaddedBytes(this.nonce), - bigIntToUnpaddedBytes(this.gasPrice), - bigIntToUnpaddedBytes(this.gasLimit), - this.to !== undefined ? this.to.bytes : new Uint8Array(0), - bigIntToUnpaddedBytes(this.value), - this.data, - ] - - if (this.supports(Capability.EIP155ReplayProtection)) { - message.push(bigIntToUnpaddedBytes(this.common.chainId())) - message.push(unpadBytes(toBytes(0))) - message.push(unpadBytes(toBytes(0))) - } - - return message - } - - /** - * Returns the hashed serialized unsigned tx, which can be used - * to sign the transaction (e.g. for sending to a hardware wallet). - */ - getHashedMessageToSign() { - const message = this.getMessageToSign() - return this.keccakFunction(RLP.encode(message)) - } - - /** - * The amount of gas paid for the data in this tx - */ - getDataGas(): bigint { - return Legacy.getDataGas(this) - } - - // TODO figure out if this is necessary - /** - * If the tx's `to` is to the creation address - */ - toCreationAddress(): boolean { - return Legacy.toCreationAddress(this) - } - - /** - * The minimum gas limit which the tx to have to be valid. - * This covers costs as the standard fee (21000 gas), the data fee (paid for each calldata byte), - * the optional creation fee (if the transaction creates a contract), and if relevant the gas - * to be paid for access lists (EIP-2930) and authority lists (EIP-7702). - */ - getIntrinsicGas(): bigint { - return Legacy.getIntrinsicGas(this) - } - /** - * The up front amount that an account must have for this transaction to be valid - */ - getUpfrontCost(): bigint { - return this.gasLimit * this.gasPrice + this.value - } - - /** - * Computes a sha3-256 hash of the serialized tx. - * - * This method can only be used for signed txs (it throws otherwise). - * Use {@link Transaction.getMessageToSign} to get a tx hash for the purpose of signing. - */ - hash(): Uint8Array { - return Legacy.hash(this) - } - - /** - * Computes a sha3-256 hash which can be used to verify the signature - */ - getMessageToVerifySignature() { - if (!this.isSigned()) { - const msg = Legacy.errorMsg(this, 'This transaction is not signed') - throw new Error(msg) - } - return this.getHashedMessageToSign() - } - - /** - * Returns the public key of the sender - */ - getSenderPublicKey(): Uint8Array { - return Legacy.getSenderPublicKey(this) - } - - addSignature( - v: bigint, - r: Uint8Array | bigint, - s: Uint8Array | bigint, - convertV: boolean = false, - ): LegacyTx { - r = toBytes(r) - s = toBytes(s) - if (convertV && this.supports(Capability.EIP155ReplayProtection)) { - v += this.common.chainId() * BIGINT_2 + BIGINT_8 - } - - const opts = { ...this.txOptions, common: this.common } - - return createLegacyTx( - { - nonce: this.nonce, - gasPrice: this.gasPrice, - gasLimit: this.gasLimit, - to: this.to, - value: this.value, - data: this.data, - v, - r: bytesToBigInt(r), - s: bytesToBigInt(s), - }, - opts, - ) - } - - /** - * Returns an object with the JSON representation of the transaction. - */ - toJSON(): JSONTx { - // TODO this is just copied. Make this execution-api compliant - - const baseJSON = getBaseJSON(this) as JSONTx - baseJSON.gasPrice = bigIntToHex(this.gasPrice) - - return baseJSON - } - - getValidationErrors(): string[] { - return Legacy.getValidationErrors(this) - } - - isValid(): boolean { - return Legacy.isValid(this) - } - - verifySignature(): boolean { - return Legacy.verifySignature(this) - } - - getSenderAddress(): Address { - return Legacy.getSenderAddress(this) - } - - sign(privateKey: Uint8Array): LegacyTx { - return Legacy.sign(this, privateKey) - } - - /** - * Return a compact error string representation of the object - */ - public errorStr() { - let errorStr = Legacy.getSharedErrorPostfix(this) - errorStr += ` gasPrice=${this.gasPrice}` - return errorStr - } } diff --git a/packages/tx/src/types.ts b/packages/tx/src/types.ts index 16f442da16c..4c7d6b82fd0 100644 --- a/packages/tx/src/types.ts +++ b/packages/tx/src/types.ts @@ -24,6 +24,19 @@ export enum Capability { */ EIP155ReplayProtection = 155, + /** + * The tx supports the "legacy gas market": it has a `gasPrice` property + */ + LegacyGasMarket = 'LegacyGasMarket', + + /** + * The tx supports the "fee gas market": it has the `maxPriorityFeePerGas` and `maxFeePerGas` properties + */ + FeeGasMarket = 'FeeGasMarket', + + // Below here are tx-specfic Capabilities, used to distinguish transactions from other transactions + // These are used in methods such as `raw` + /** * Tx supports EIP-1559 gas fee market mechanism * See: [1559](https://eips.ethereum.org/EIPS/eip-1559) Fee Market EIP @@ -42,6 +55,12 @@ export enum Capability { */ EIP2930AccessLists = 2930, + /** + * Tx supports blobs generation as defined in EIP-4844 + * See: [4844](https://eips.ethereum.org/EIPS/eip-4844) Access Lists EIP + */ + EIP4844Blobs = 4844, + /** * Tx supports setting EOA code * See [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) @@ -165,26 +184,69 @@ export interface Transaction { export type TypedTransaction = Transaction[TransactionType] -export function isLegacyTx(tx: TypedTransaction): tx is LegacyTx { +export function isLegacyTx(tx: TxInterface): tx is LegacyTxInterface { return tx.type === TransactionType.Legacy } -export function isAccessList2930Tx(tx: TypedTransaction): tx is AccessList2930Tx { +export function isAccessList2930Tx(tx: TxInterface): tx is EIP2930CompatibleTx { return tx.type === TransactionType.AccessListEIP2930 } -export function isFeeMarket1559Tx(tx: TypedTransaction): tx is FeeMarket1559Tx { +export function isFeeMarket1559Tx(tx: TxInterface): tx is EIP1559CompatibleTx { return tx.type === TransactionType.FeeMarketEIP1559 } -export function isBlob4844Tx(tx: TypedTransaction): tx is Blob4844Tx { +export function isBlob4844Tx(tx: TxInterface): tx is EIP4844CompatibleTx { return tx.type === TransactionType.BlobEIP4844 } -export function isEOACode7702Tx(tx: TypedTransaction): tx is EOACode7702Tx { +export function isEOACode7702Tx(tx: TxInterface): tx is EIP7702CompatibleTx { return tx.type === TransactionType.EOACodeEIP7702 } +// Temp interface to replace TransactionInterface +export interface TxInterface { + readonly type: number + readonly cache: TransactionCache + readonly txOptions?: any // Placeholder for the saved "txOptions" when constructing a tx + readonly common: Common // TODO: remove Common from tx interfaces + // TODO: make this a Set. `supports()` method is removed in favour of just using a set. + readonly activeCapabilities: Capability[] // Necessary to determine the capabilities of the transaction in the respective methods +} + +// NOTE: this type should be removed and covers the "shared" properties of all L1 txs currently +// TODO: figure out how to handle this in a much cleaner way +export interface L1TxInterface extends TxInterface { + readonly nonce: bigint + readonly gasLimit: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint +} + +export interface ECDSASignableInterface extends TxInterface { + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint +} + +export interface LegacyGasMarketInterface extends TxInterface { + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly value: bigint +} + +export interface FeeGasMarketInterface extends TxInterface { + readonly maxPriorityFeePerGas: bigint + readonly maxFeePerGas: bigint + readonly gasLimit: bigint + readonly value: bigint +} + +// TODO: this interface has to be removed or purged! export interface TransactionInterface { readonly common: Common readonly nonce: bigint @@ -196,7 +258,6 @@ export interface TransactionInterface - extends TransactionInterface {} +// Interface of a transaction which supports `to: undefined`, which is used to create a contract +export interface ContractCreationInterface extends TxInterface { + readonly to?: Address +} + +// Interface of a transaction which supports `to: Address`, which calls that address +// It is not possible to create a contract with this tx. +export interface ToInterface extends TxInterface { + readonly to: Address +} + +export interface LegacyTxInterface extends TxInterface { + readonly nonce: bigint + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint +} +/* export interface EIP2718CompatibleTx extends TransactionInterface { readonly chainId: bigint getMessageToSign(): Uint8Array -} +}*/ -export interface EIP2930CompatibleTx - extends EIP2718CompatibleTx { +export interface EIP2930CompatibleTx // TODO (among the other types like `EIP1559CompatibleTx` below: rename this?) + extends TxInterface { + readonly nonce: bigint + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly chainId: bigint readonly accessList: AccessListBytes - readonly AccessListJSON: AccessList + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint } -export interface EIP1559CompatibleTx - extends EIP2930CompatibleTx { +export interface EIP1559CompatibleTx extends TxInterface { + readonly nonce: bigint + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly chainId: bigint + readonly accessList: AccessListBytes readonly maxPriorityFeePerGas: bigint readonly maxFeePerGas: bigint + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint } -export interface EIP4844CompatibleTx - extends EIP1559CompatibleTx { +export interface EIP4844CompatibleTx extends TxInterface { + readonly nonce: bigint + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly chainId: bigint + readonly accessList: AccessListBytes + readonly maxPriorityFeePerGas: bigint + readonly maxFeePerGas: bigint readonly maxFeePerBlobGas: bigint + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint + + // Network wrapper format blobVersionedHashes: Uint8Array[] blobs?: Uint8Array[] kzgCommitments?: Uint8Array[] kzgProofs?: Uint8Array[] - serializeNetworkWrapper(): Uint8Array - numBlobs(): number + //serializeNetworkWrapper(): Uint8Array + //numBlobs(): number } -export interface EIP7702CompatibleTx - extends EIP1559CompatibleTx { +export interface EIP7702CompatibleTx extends TxInterface { // ChainID, Address, [nonce], y_parity, r, s + readonly nonce: bigint + readonly gasLimit: bigint + readonly gasPrice: bigint + readonly to?: Address + readonly value: bigint + readonly data: Uint8Array + readonly chainId: bigint + readonly accessList: AccessListBytes readonly authorizationList: AuthorizationListBytes + readonly maxPriorityFeePerGas: bigint + readonly maxFeePerGas: bigint + readonly v?: bigint + readonly r?: bigint + readonly s?: bigint } export interface TxData {