# Some Tx Refactoring Conceptual Ideas for Jochem Author: Holger, 2024-11-07 So this is how I could imagine a tx type data class ```ts // I would keep calling this with the "core" name, no `data` attached // or something, since this IS the tx class FeeMarket1559Tx implements 1559CompatibleTx, 2718CompatibleTx,... { public readonly type: TransactionType public readonly nonce: bigint public readonly gasLimit: bigint // ... public readonly v?: bigint public readonly r?: bigint public readonly s?: bigint public readonly chainId: bigint public readonly accessList: AccessListBytes // ... // This would be something for (another) step 4 or so, but the more I think // about this the more I get convinced that we just do not want Common // attached to a tx *at all*. A tx can just exist for itself as a fee market // tx and be perfectly happy with it (and what makes an Ethereum dev happier // than happy txs? 😂). The Common info just always comes from the context // e.g. a block the tx is included within. The VM which executes the tx. I // would assume that if we remove common here, this will somewhat naturally // solve structural problems. Beside common *also* make txs really heavy, // this is a performance burner on its own (have to copy in client over and // over) // Again: // For step 4 though. 🙂 Makes a lot of sense to not deal with this now. public readonly common: Common } ``` Some notes/additional thoughts: - ~~I would first-round think this does not need additional TypeScript interfaces or anything (basically `FeeMarket1559Tx` is already somewhat as simple as a type definition)~~  Update: this *does* need interfaces I realized further down the line to be able to tell consuming methods what a certain type of tx is composed of (added these, e.g. `1559CompatibleTx`, notes on this further down) - I would also tend to not do anything with inheritance here and in doubt take some mild redundancy, e.g. all v, r, s containing txs just have these three properties. Tx types are just made/setup to be independent by design, and this would reflect this Going further: the data interfaces we have now like `EIP1559CompatibleTx` can still be used (renamed to `1559CompatibleTx`) and are now a lot better compatible with `FeeMarket1559Tx`, but they should fully abandon inheritance to allow for fully modular composition (so no `extends EIP2930CompatibleTx` or the like). And then we might have something like type-specific worker classes, one-time instantiated: ```ts class FeeMarket1559TxWorker { // So, the respective tx is always passed in here sign(tx: FeeMarket1559Tx, privateKey: Uint8Array): FeeMarket1559Tx { // Do not have the full picture here yet how to decompose the base // functionality into capabilities, maybe non-EIP-named but still atomic // capabilities like `ECDSASignatureCompatibleTx` (so that's the related // data interface) } getUpfrontCost(tx: FeeMarket1559Tx, baseFee: bigint = BIGINT_0): bigint { // This will call into the respective capability methods as before return EIP1559.getUpfrontCost(tx, baseFee) } } ``` Then, if I got everything right, the capabilities itself could be easily typed with the data interfaces from above: ```ts export function getEffectivePriorityFee( // Guess this might be necessary (not here but in general), just tested // that typing like `1559CompatibleTx & 2718CompatibleTx` seems possible, // so a type where compatibility with 1559 *and* 2718 is needed tx: 1559CompatibleTx, baseFee: bigint | undefined, ): bigint ``` I think the capabilities also need interfaces for the methods supported by each capability: ```ts interface 1559CompatibleWorker { getEffectivePriorityFee(tx: 1559CompatibleTx, baseFee: bigint | undefined): bigint // ... } interface 2718CompatibleWorker { getHashedMessageToSign(tx: 2718CompatibleTx): Uint8Array // ... } ``` And these interfaces can then be used to type the tx type specific worker from above (had not yet added this above to not overload the example): ```ts class FeeMarket1559TxWorker implements 1559CompatibleWorker, 2718CompatibleWorker { } ``` I would then think we then also needs a generic `TxWorker`, to make it as comfortable as we have to work with txs when the type can be diverse. I will give it a try to sketch this out: ```ts // Guess this will just need to implement all the worker classes class TxWorker implements 1559CompatibleWorker, 2718CompatibleWorker,... { // This whole class basically holds "forwards" (to capability methods) for // all existing methods from capabilities // The only thing different here is the typing // We will - respectively should - see this in code upstream if we apply // a method in a context where this could be called with a not-compatible // tx type. This will the directly break (aka: "notify" us) on the // TypeScript level getUpfrontCost(tx: 1559CompatibleTx, baseFee: bigint = BIGINT_0): bigint { // This will call into the respective capability methods as before return EIP1559.getUpfrontCost(this, baseFee) } // ... } ``` Not fully sure if this `TxWorker` setup fully "holds" (but it might). Guess I will leave it there/here nevertheless. Enough food for thought/discussion/inspiration. 🙂