Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit cc99825

Browse files
feat: make getEthereumjsTxDataFromTransaction not trim extra fields (#7282)
* feat: make getEthereumjsTxDataFromTransaction not trim extra fields * fix: lint issues * fix: tests * fix: revert the changes and just spread extra fields * chore: changelog
1 parent c602fc6 commit cc99825

File tree

6 files changed

+122
-19
lines changed

6 files changed

+122
-19
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2728,3 +2728,9 @@ If there are any bugs, improvements, optimizations or any new feature proposal f
27282728
- The callback function provided to the static `Web3.onNewProviderDiscovered` function expects a parameter of type `EIP6963ProvidersMapUpdateEvent` as opposed to `EIP6963AnnounceProviderEvent`. (#7242)
27292729

27302730
## [Unreleased]
2731+
2732+
### Changed
2733+
2734+
#### web3-eth
2735+
2736+
- Allow `getEthereumjsTxDataFrom` to return additional fields that may be passed if using a `customTransactionSchema`.

packages/web3-eth/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,7 @@ Documentation:
280280
- Adds the same `{transactionSchema?: ValidationSchemaInput}` that exists in `formatTransaction` to `validateTransactionForSigning`
281281

282282
## [Unreleased]
283+
284+
### Changed
285+
286+
- Allow `getEthereumjsTxDataFrom` to return additional fields that may be passed if using a `customTransactionSchema`.

packages/web3-eth/src/utils/prepare_transaction_for_signing.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { transactionBuilder } from './transaction_builder.js';
3737
const getEthereumjsTxDataFromTransaction = (
3838
transaction: FormatType<PopulatedUnsignedTransaction, typeof ETH_DATA_FORMAT>,
3939
) => ({
40+
...transaction,
4041
nonce: transaction.nonce,
4142
gasPrice: transaction.gasPrice,
4243
gasLimit: transaction.gasLimit ?? transaction.gas,

packages/web3-eth/test/unit/prepare_transaction_for_signing.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ import {
2828
FeeMarketEIP1559Transaction,
2929
Transaction,
3030
Hardfork,
31+
TypedTransaction,
32+
TransactionFactory,
3133
} from 'web3-eth-accounts';
3234
import { prepareTransactionForSigning } from '../../src/utils/prepare_transaction_for_signing';
3335
import { validTransactions } from '../fixtures/prepare_transaction_for_signing';
36+
import { transactionSchema } from '../../src/schemas';
37+
import { CustomFieldTransaction } from '../fixtures/format_transaction';
3438

3539
describe('prepareTransactionForSigning', () => {
3640
const web3Context = new Web3Context<EthExecutionAPI>({
@@ -317,4 +321,52 @@ describe('prepareTransactionForSigning', () => {
317321
},
318322
);
319323
});
324+
325+
it('should not remove extra fields when using a custom schema', async () => {
326+
const context = new Web3Context<EthExecutionAPI>({
327+
provider: new HttpProvider('http://127.0.0.1'),
328+
config: {
329+
defaultNetworkId: '0x1',
330+
customTransactionSchema: {
331+
type: 'object',
332+
properties: {
333+
...transactionSchema.properties,
334+
feeCurrency: { format: 'address' },
335+
},
336+
},
337+
},
338+
});
339+
340+
async function transactionBuilder<ReturnType = TransactionType>(options: {
341+
transaction: TransactionType;
342+
web3Context: Web3Context<EthExecutionAPI & Web3NetAPI>;
343+
privateKey?: HexString | Uint8Array;
344+
fillGasPrice?: boolean;
345+
fillGasLimit?: boolean;
346+
}): Promise<ReturnType> {
347+
const tx = { ...options.transaction };
348+
return tx as unknown as ReturnType;
349+
}
350+
351+
context.transactionBuilder = transactionBuilder;
352+
353+
const spy = jest.spyOn(TransactionFactory, 'fromTxData');
354+
(await prepareTransactionForSigning(
355+
{
356+
chainId: 1458,
357+
nonce: 1,
358+
gasPrice: BigInt(20000000000),
359+
gasLimit: BigInt(21000),
360+
to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55',
361+
from: '0x2c7536E3605D9C16a7a3D7b1898e529396a65c23',
362+
value: '1000000000',
363+
input: '',
364+
feeCurrency: '0x1234567890123456789012345678901234567890',
365+
} as CustomFieldTransaction,
366+
context,
367+
)) as TypedTransaction & { feeCurrency: string };
368+
369+
// @ts-expect-error feeCurrency is a custom field for testing here
370+
expect(spy.mock.lastCall[0].feeCurrency).toBe('0x1234567890123456789012345678901234567890');
371+
});
320372
});

packages/web3/test/fixtures/tx-type-15/index.ts

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ import {
3232
AccessList,
3333
AccessListUint8Array,
3434
FeeMarketEIP1559TxData,
35-
FeeMarketEIP1559ValuesArray,
3635
JsonTx,
3736
TxOptions,
37+
TxValuesArray,
3838
} from 'web3-eth-accounts';
3939

4040
const { getAccessListData, getAccessListJSON, getDataFeeEIP2930, verifyAccessList } = txUtils;
@@ -43,19 +43,40 @@ const MAX_INTEGER = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffff
4343
export const TRANSACTION_TYPE = 15;
4444
const TRANSACTION_TYPE_UINT8ARRAY = hexToBytes(TRANSACTION_TYPE.toString(16).padStart(2, '0'));
4545

46+
type CustomFieldTxValuesArray = [
47+
Uint8Array,
48+
Uint8Array,
49+
Uint8Array,
50+
Uint8Array,
51+
Uint8Array,
52+
Uint8Array,
53+
Uint8Array,
54+
Uint8Array,
55+
AccessListUint8Array,
56+
Uint8Array,
57+
Uint8Array?,
58+
Uint8Array?,
59+
Uint8Array?,
60+
];
61+
62+
type SomeNewTxTypeTxData = FeeMarketEIP1559TxData & {
63+
customTestField: bigint;
64+
};
65+
4666
/**
4767
* Typed transaction with a new gas fee market mechanism
4868
*
4969
* - TransactionType: 2
5070
* - EIP: [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)
5171
*/
5272
// eslint-disable-next-line no-use-before-define
53-
export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Transaction> {
73+
export class SomeNewTxTypeTransaction extends BaseTransaction<SomeNewTxTypeTransaction> {
5474
public readonly chainId: bigint;
5575
public readonly accessList: AccessListUint8Array;
5676
public readonly AccessListJSON: AccessList;
5777
public readonly maxPriorityFeePerGas: bigint;
5878
public readonly maxFeePerGas: bigint;
79+
public readonly customTestField: bigint;
5980

6081
public readonly common: Common;
6182

@@ -77,7 +98,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
7798
* - `chainId` will be set automatically if not provided
7899
* - All parameters are optional and have some basic default values
79100
*/
80-
public static fromTxData(txData: FeeMarketEIP1559TxData, opts: TxOptions = {}) {
101+
public static fromTxData(txData: SomeNewTxTypeTxData, opts: TxOptions = {}) {
81102
return new SomeNewTxTypeTransaction(txData, opts);
82103
}
83104

@@ -110,10 +131,10 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
110131
* Format: `[chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data,
111132
* accessList, signatureYParity, signatureR, signatureS]`
112133
*/
113-
public static fromValuesArray(values: FeeMarketEIP1559ValuesArray, opts: TxOptions = {}) {
114-
if (values.length !== 9 && values.length !== 12) {
134+
public static fromValuesArray(values: CustomFieldTxValuesArray, opts: TxOptions = {}) {
135+
if (values.length !== 10 && values.length !== 13) {
115136
throw new Error(
116-
'Invalid EIP-1559 transaction. Only expecting 9 values (for unsigned tx) or 12 values (for signed tx).',
137+
'Invalid CUSTOM TEST transaction. Only expecting 10 values (for unsigned tx) or 13 values (for signed tx).',
117138
);
118139
}
119140

@@ -127,6 +148,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
127148
value,
128149
data,
129150
accessList,
151+
customTestField,
130152
v,
131153
r,
132154
s,
@@ -144,7 +166,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
144166
s,
145167
});
146168

147-
return new FeeMarketEIP1559Transaction(
169+
return new SomeNewTxTypeTransaction(
148170
{
149171
chainId: uint8ArrayToBigInt(chainId),
150172
nonce,
@@ -155,6 +177,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
155177
value,
156178
data,
157179
accessList: accessList ?? [],
180+
customTestField: uint8ArrayToBigInt(customTestField),
158181
v: v !== undefined ? uint8ArrayToBigInt(v) : undefined, // EIP2930 supports v's with value 0 (empty Uint8Array)
159182
r,
160183
s,
@@ -170,12 +193,13 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
170193
* the static factory methods to assist in creating a Transaction object from
171194
* varying data types.
172195
*/
173-
public constructor(txData: FeeMarketEIP1559TxData, opts: TxOptions = {}) {
196+
public constructor(txData: SomeNewTxTypeTxData, opts: TxOptions = {}) {
174197
super({ ...txData, type: TRANSACTION_TYPE }, opts);
175-
const { chainId, accessList, maxFeePerGas, maxPriorityFeePerGas } = txData;
198+
const { chainId, accessList, maxFeePerGas, maxPriorityFeePerGas, customTestField } = txData;
176199

177200
this.common = this._getCommon(opts.common, chainId);
178201
this.chainId = this.common.chainId();
202+
this.customTestField = customTestField;
179203

180204
if (!this.common.isActivatedEIP(1559)) {
181205
throw new Error('EIP-1559 not enabled on Common');
@@ -272,7 +296,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
272296
* signature parameters `v`, `r` and `s` for encoding. For an EIP-155 compliant
273297
* representation for external signing use {@link FeeMarketEIP1559Transaction.getMessageToSign}.
274298
*/
275-
public raw(): FeeMarketEIP1559ValuesArray {
299+
public raw(): TxValuesArray {
276300
return [
277301
bigIntToUnpaddedUint8Array(this.chainId),
278302
bigIntToUnpaddedUint8Array(this.nonce),
@@ -283,10 +307,11 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
283307
bigIntToUnpaddedUint8Array(this.value),
284308
this.data,
285309
this.accessList,
310+
bigIntToUnpaddedUint8Array(this.customTestField),
286311
this.v !== undefined ? bigIntToUnpaddedUint8Array(this.v) : Uint8Array.from([]),
287312
this.r !== undefined ? bigIntToUnpaddedUint8Array(this.r) : Uint8Array.from([]),
288313
this.s !== undefined ? bigIntToUnpaddedUint8Array(this.s) : Uint8Array.from([]),
289-
];
314+
] as TxValuesArray;
290315
}
291316

292317
/**
@@ -384,7 +409,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
384409
public _processSignature(v: bigint, r: Uint8Array, s: Uint8Array) {
385410
const opts = { ...this.txOptions, common: this.common };
386411

387-
return FeeMarketEIP1559Transaction.fromTxData(
412+
return SomeNewTxTypeTransaction.fromTxData(
388413
{
389414
chainId: this.chainId,
390415
nonce: this.nonce,
@@ -395,6 +420,7 @@ export class SomeNewTxTypeTransaction extends BaseTransaction<FeeMarketEIP1559Tr
395420
value: this.value,
396421
data: this.data,
397422
accessList: this.accessList,
423+
customTestField: this.customTestField,
398424
v: v - BigInt(27), // This looks extremely hacky: /util actually adds 27 to the value, the recovery bit is either 0 or 1.
399425
r: uint8ArrayToBigInt(r),
400426
s: uint8ArrayToBigInt(s),

packages/web3/test/integration/web3-plugin-add-tx.test.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
1818
/* eslint-disable @typescript-eslint/no-magic-numbers */
1919

2020
import { Transaction, Web3Account } from 'web3-eth-accounts';
21+
import { transactionSchema } from 'web3-eth';
2122
import { SupportedProviders, Web3, Web3PluginBase } from '../../src';
2223
import {
2324
createAccount,
@@ -51,23 +52,36 @@ describe('Add New Tx as a Plugin', () => {
5152
});
5253
it('should receive correct type of tx', async () => {
5354
web3.registerPlugin(new Eip4844Plugin());
55+
web3.config.customTransactionSchema = {
56+
type: 'object',
57+
properties: {
58+
...transactionSchema.properties,
59+
customField: { format: 'string' },
60+
},
61+
};
62+
web3.eth.config.customTransactionSchema = web3.config.customTransactionSchema;
5463
const tx = {
5564
from: account1.address,
5665
to: account2.address,
5766
value: '0x1',
5867
type: TRANSACTION_TYPE,
5968
maxPriorityFeePerGas: BigInt(5000000),
6069
maxFeePerGas: BigInt(5000000),
70+
customField: BigInt(42),
6171
};
6272
const sub = web3.eth.sendTransaction(tx);
6373

64-
const waitForEvent: Promise<Transaction> = new Promise(resolve => {
65-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
66-
sub.on('sending', txData => {
67-
resolve(txData as unknown as Transaction);
68-
});
69-
});
70-
expect(Number((await waitForEvent).type)).toBe(TRANSACTION_TYPE);
74+
const waitForEvent: Promise<Transaction & { customField: bigint }> = new Promise(
75+
resolve => {
76+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
77+
sub.on('sending', txData => {
78+
resolve(txData as unknown as Transaction & { customField: bigint });
79+
});
80+
},
81+
);
82+
const { type, customField } = await waitForEvent;
83+
expect(Number(type)).toBe(TRANSACTION_TYPE);
84+
expect(BigInt(customField)).toBe(BigInt(42));
7185
await expect(sub).rejects.toThrow();
7286
});
7387
});

0 commit comments

Comments
 (0)