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

Commit 70d1957

Browse files
authored
Add functionality to extend tx types (#6493)
* draft * finish * lint * add documentation * fix unit tests * add unit tests * add unit tests * increase coverage * increase coverage * fix changelog syncing * fix test
1 parent 10d1f12 commit 70d1957

File tree

18 files changed

+781
-26
lines changed

18 files changed

+781
-26
lines changed

CHANGELOG.md

+41-1
Original file line numberDiff line numberDiff line change
@@ -2089,19 +2089,59 @@ If there are any bugs, improvements, optimizations or any new feature proposal f
20892089

20902090
- Added `ALL_EVENTS` and `ALL_EVENTS_ABI` constants, `SendTransactionEventsBase` type, `decodeEventABI` method (#6410)
20912091

2092+
#### web3-eth-accounts
2093+
2094+
- Added public function `privateKeyToPublicKey`
2095+
- Added exporting `BaseTransaction` from the package (#6493)
2096+
- Added exporting `txUtils` from the package (#6493)
2097+
20922098
#### web3-types
20932099

20942100
- Interface `EventLog` was added. (#6410)
20952101

2102+
#### web3-utils
2103+
2104+
- As a replacment of the node EventEmitter, a custom `EventEmitter` has been implemented and exported. (#6398)
2105+
20962106
### Fixed
20972107

2108+
#### web3-core
2109+
2110+
- Fix the issue: "Uncaught TypeError: Class extends value undefined is not a constructor or null #6371". (#6398)
2111+
20982112
#### web3-eth
20992113

21002114
- Ensure provider.supportsSubscriptions exists before watching by subscription (#6440)
2101-
- Fixed `withdrawalsSchema.address` property type `bytes32` to `address` (#6470)
2115+
- Fixed param sent to `checkRevertBeforeSending` in `sendSignedTransaction`
2116+
2117+
#### web3-eth-accounts
2118+
2119+
- Fixed `recover` function, `v` will be normalized to value 0,1 (#6344)
2120+
2121+
#### web3-providers-http
2122+
2123+
- Fix issue lquixada/cross-fetch#78, enabling to run web3.js in service worker (#6463)
2124+
2125+
#### web3-validator
2126+
2127+
- Multi-dimensional arrays are now handled properly when parsing ABIs
21022128

21032129
### Changed
21042130

2131+
#### web3-core
2132+
2133+
- defaultTransactionType is now type 0x2 instead of 0x0 (#6282)
2134+
- Allows formatter to parse large base fee (#6456)
2135+
- The package now uses `EventEmitter` from `web3-utils` that works in node envrioment as well as in the browser. (#6398)
2136+
2137+
#### web3-eth
2138+
2139+
- Transactions will now default to type 2 transactions instead of type 0, similar to 1.x version. (#6282)
2140+
21052141
#### web3-eth-contract
21062142

21072143
- The `events` property was added to the `receipt` object (#6410)
2144+
2145+
#### web3-providers-http
2146+
2147+
- Bump cross-fetch to version 4 (#6463).

docs/docs/guides/web3_plugin_guide/plugin_authors.md

+25
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,31 @@ It is important to note that the plugin name should be structured as `@<organiza
3434

3535
When your users install your plugin, this will allow the package manager to make use of the user installed `web3` if available and if the version satisfies the version constraints instead of installing it's own version of `web3`.
3636

37+
## Add New Transaction Type
38+
39+
Furthermore, you have the flexibility to expand your range of transaction types, enhancing compatibility with the `web3.js` library.
40+
41+
42+
```typescript
43+
// create new TransactionType class which extends BaseTransaction class
44+
import { BaseTransaction } from 'web3-eth-accounts';
45+
const TRANSACTION_TYPE = 15;
46+
class SomeNewTxTypeTransaction extends BaseTransaction {
47+
// ...
48+
}
49+
50+
// create new plugin and add `SomeNewTxTypeTransaction` to the library
51+
import { Web3EthPluginBase } from 'web3';
52+
53+
class SomeNewTxTypeTransactionPlugin extends Web3PluginBase {
54+
public pluginNamespace = 'someNewTxTypeTransaction';
55+
public constructor() {
56+
super();
57+
TransactionFactory.registerTransactionType(TRANSACTION_TYPE, SomeNewTxTypeTransaction);
58+
}
59+
}
60+
```
61+
3762
## Extending `Web3PluginBase`
3863

3964
Your plugin class should `extend` the `Web3PluginBase` abstract class. This class `extends` [Web3Context](/api/web3-core/class/Web3Context) and when the user registers your plugin with a class, your plugin's `Web3Context` will point to the module's `Web3Context` giving your plugin access to things such as user configured [requestManager](/api/web3-core/class/Web3Context#requestManager) and [accountProvider](/api/web3-core/class/Web3Context#accountProvider).

packages/web3-eth-accounts/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ Documentation:
142142
### Added
143143

144144
- Added public function `privateKeyToPublicKey`
145+
- Added exporting `BaseTransaction` from the package (#6493)
146+
- Added exporting `txUtils` from the package (#6493)
145147

146148
### Fixed
147149

packages/web3-eth-accounts/src/tx/baseTransaction.ts

+21-8
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,9 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
1818
import { Numbers } from 'web3-types';
1919
import { bytesToHex } from 'web3-utils';
2020
import { MAX_INTEGER, MAX_UINT64, SECP256K1_ORDER_DIV_2, secp256k1 } from './constants.js';
21-
import {
22-
Chain,
23-
Common,
24-
Hardfork,
25-
toUint8Array,
26-
uint8ArrayToBigInt,
27-
unpadUint8Array,
28-
} from '../common/index.js';
21+
import { toUint8Array, uint8ArrayToBigInt, unpadUint8Array } from '../common/utils.js';
22+
import { Common } from '../common/common.js';
23+
import { Hardfork, Chain } from '../common/enums.js';
2924
import type {
3025
AccessListEIP2930TxData,
3126
AccessListEIP2930ValuesArray,
@@ -565,4 +560,22 @@ export abstract class BaseTransaction<TransactionObject> {
565560

566561
return { r, s, v };
567562
}
563+
564+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
565+
public static fromSerializedTx(
566+
// @ts-expect-error unused variable
567+
serialized: Uint8Array,
568+
// @ts-expect-error unused variable
569+
opts: TxOptions = {},
570+
// eslint-disable-next-line @typescript-eslint/no-empty-function
571+
): any {}
572+
573+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
574+
public static fromTxData(
575+
// @ts-expect-error unused variable
576+
txData: any,
577+
// @ts-expect-error unused variable
578+
opts: TxOptions = {},
579+
// eslint-disable-next-line @typescript-eslint/no-empty-function
580+
): any {}
568581
}

packages/web3-eth-accounts/src/tx/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ export { FeeMarketEIP1559Transaction } from './eip1559Transaction.js';
2020
export { AccessListEIP2930Transaction } from './eip2930Transaction.js';
2121
export { Transaction } from './legacyTransaction.js';
2222
export { TransactionFactory } from './transactionFactory.js';
23+
export { BaseTransaction } from './baseTransaction.js';
24+
export * as txUtils from './utils.js';
2325
export * from './types.js';

packages/web3-eth-accounts/src/tx/transactionFactory.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ GNU Lesser General Public License for more details.
1414
You should have received a copy of the GNU Lesser General Public License
1515
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
1616
*/
17+
import { Numbers } from 'web3-types';
1718
import { toUint8Array, uint8ArrayToBigInt } from '../common/utils.js';
1819
import { FeeMarketEIP1559Transaction } from './eip1559Transaction.js';
1920
import { AccessListEIP2930Transaction } from './eip2930Transaction.js';
@@ -26,13 +27,28 @@ import type {
2627
TxData,
2728
TxOptions,
2829
} from './types.js';
30+
import { BaseTransaction } from './baseTransaction.js';
31+
32+
const extraTxTypes: Map<Numbers, typeof BaseTransaction<unknown>> = new Map();
2933

3034
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
3135
export class TransactionFactory {
3236
// It is not possible to instantiate a TransactionFactory object.
3337
// eslint-disable-next-line @typescript-eslint/no-empty-function, no-useless-constructor
3438
private constructor() {}
3539

40+
public static typeToInt(txType: Numbers) {
41+
return Number(uint8ArrayToBigInt(toUint8Array(txType)));
42+
}
43+
44+
public static registerTransactionType<NewTxTypeClass extends typeof BaseTransaction<unknown>>(
45+
type: Numbers,
46+
txClass: NewTxTypeClass,
47+
) {
48+
const txType = TransactionFactory.typeToInt(type);
49+
extraTxTypes.set(txType, txClass);
50+
}
51+
3652
/**
3753
* Create a transaction from a `txData` object
3854
*
@@ -47,7 +63,7 @@ export class TransactionFactory {
4763
// Assume legacy transaction
4864
return Transaction.fromTxData(txData as TxData, txOptions);
4965
}
50-
const txType = Number(uint8ArrayToBigInt(toUint8Array(txData.type)));
66+
const txType = TransactionFactory.typeToInt(txData.type);
5167
if (txType === 0) {
5268
return Transaction.fromTxData(txData as TxData, txOptions);
5369
}
@@ -66,6 +82,11 @@ export class TransactionFactory {
6682
txOptions,
6783
);
6884
}
85+
const ExtraTransaction = extraTxTypes.get(txType);
86+
if (ExtraTransaction?.fromTxData) {
87+
return ExtraTransaction.fromTxData(txData, txOptions) as TypedTransaction;
88+
}
89+
6990
throw new Error(`Tx instantiation with type ${txType} not supported`);
7091
}
7192

@@ -86,8 +107,17 @@ export class TransactionFactory {
86107
return AccessListEIP2930Transaction.fromSerializedTx(data, txOptions);
87108
case 2:
88109
return FeeMarketEIP1559Transaction.fromSerializedTx(data, txOptions);
89-
default:
110+
default: {
111+
const ExtraTransaction = extraTxTypes.get(Number(data[0]));
112+
if (ExtraTransaction?.fromSerializedTx) {
113+
return ExtraTransaction.fromSerializedTx(
114+
data,
115+
txOptions,
116+
) as TypedTransaction;
117+
}
118+
90119
throw new Error(`TypedTransaction with ID ${data[0]} unknown`);
120+
}
91121
}
92122
} else {
93123
return Transaction.fromSerializedTx(data, txOptions);

packages/web3-eth-accounts/test/unit/account.test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
recoverTransaction,
2929
sign,
3030
signTransaction,
31-
privateKeyToPublicKey
31+
privateKeyToPublicKey,
3232
} from '../../src/account';
3333
import {
3434
invalidDecryptData,
@@ -98,8 +98,8 @@ describe('accounts', () => {
9898
it.each(validPrivateKeyToPublicKeyData)('%s', (privateKey, isCompressed, output) => {
9999
expect(privateKeyToPublicKey(privateKey, isCompressed)).toEqual(output);
100100
});
101-
})
102-
})
101+
});
102+
});
103103

104104
describe('Signing and Recovery of Transaction', () => {
105105
it.each(transactionsTestData)('sign transaction', async txData => {
@@ -228,8 +228,8 @@ describe('accounts', () => {
228228

229229
describe('valid signatures for recover', () => {
230230
it.each(validRecover)('&s', (data, signature) => {
231-
recover(data, signature)
232-
})
233-
})
231+
recover(data, signature);
232+
});
233+
});
234234
});
235235
});

packages/web3-eth-accounts/test/unit/common/utils.test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,18 @@ describe('[Utils/Parse]', () => {
4242
merge: '0x013fd1b5',
4343
};
4444

45-
it('should parse geth params file', async () => {
45+
it('should parse geth params file', () => {
4646
const params = parseGethGenesis(testnet, 'rinkeby');
4747
expect(params.genesis.nonce).toBe('0x0000000000000042');
4848
});
4949

50-
it('should throw with invalid Spurious Dragon blocks', async () => {
50+
it('should throw with invalid Spurious Dragon blocks', () => {
5151
expect(() => {
5252
parseGethGenesis(invalidSpuriousDragon, 'bad_params');
5353
}).toThrow();
5454
});
5555

56-
it('should import poa network params correctly', async () => {
56+
it('should import poa network params correctly', () => {
5757
let params = parseGethGenesis(poa, 'poa');
5858
expect(params.genesis.nonce).toBe('0x0000000000000000');
5959
expect(params.consensus).toEqual({
@@ -67,18 +67,18 @@ describe('[Utils/Parse]', () => {
6767
expect(params.hardfork).toEqual(Hardfork.London);
6868
});
6969

70-
it('should generate expected hash with london block zero and base fee per gas defined', async () => {
70+
it('should generate expected hash with london block zero and base fee per gas defined', () => {
7171
const params = parseGethGenesis(postMerge, 'post-merge');
7272
expect(params.genesis.baseFeePerGas).toEqual(postMerge.baseFeePerGas);
7373
});
7474

75-
it('should successfully parse genesis file with no extraData', async () => {
75+
it('should successfully parse genesis file with no extraData', () => {
7676
const params = parseGethGenesis(noExtraData, 'noExtraData');
7777
expect(params.genesis.extraData).toBe('0x');
7878
expect(params.genesis.timestamp).toBe('0x10');
7979
});
8080

81-
it('should successfully parse kiln genesis and set forkhash', async () => {
81+
it('should successfully parse kiln genesis and set forkhash', () => {
8282
const common = Common.fromGethGenesis(gethGenesisKiln, {
8383
chain: 'customChain',
8484
genesisHash: hexToBytes(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
This file is part of web3.js.
3+
4+
web3.js is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
web3.js is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License
15+
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
import { TransactionFactory } from '../../../src/tx/transactionFactory';
19+
import { BaseTransaction } from '../../../src/tx/baseTransaction';
20+
import { TxData, TxOptions } from '../../../src/tx';
21+
22+
describe('Register new TX', () => {
23+
it('validateCannotExceedMaxInteger()', () => {
24+
const TYPE = 20;
25+
// @ts-expect-error not implement all methods
26+
class SomeNewTxType extends BaseTransaction<any> {
27+
public constructor(txData: TxData, opts: TxOptions = {}) {
28+
super({ ...txData, type: TYPE }, opts);
29+
}
30+
public static fromTxData() {
31+
return 'new fromTxData';
32+
}
33+
public static fromSerializedTx() {
34+
return 'new fromSerializedData';
35+
}
36+
}
37+
TransactionFactory.registerTransactionType(TYPE, SomeNewTxType);
38+
const txData = {
39+
from: '0x',
40+
to: '0x',
41+
value: '0x1',
42+
type: TYPE,
43+
};
44+
expect(TransactionFactory.fromTxData(txData)).toBe('new fromTxData');
45+
expect(TransactionFactory.fromSerializedData(new Uint8Array([TYPE, 10]))).toBe(
46+
'new fromSerializedData',
47+
);
48+
});
49+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
This file is part of web3.js.
3+
4+
web3.js is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
web3.js is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License
15+
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
import { BaseTransaction } from '../../../src/tx/baseTransaction';
18+
19+
describe('[BaseTransaction]', () => {
20+
it('Initialization', () => {
21+
expect(typeof BaseTransaction.fromTxData).toBe('function');
22+
expect(typeof BaseTransaction.fromSerializedTx).toBe('function');
23+
});
24+
});

packages/web3-eth-accounts/test/unit/tx/transactionFactory.test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ describe('[TransactionFactory]: Basic functions', () => {
119119
}
120120
});
121121

122+
it('fromBlockBodyData() -> error case', () => {
123+
expect(() => {
124+
// @ts-expect-error incorrect param type
125+
TransactionFactory.fromBlockBodyData('');
126+
}).toThrow();
127+
});
128+
122129
it('fromTxData() -> success cases', () => {
123130
for (const txType of txTypes) {
124131
const tx = TransactionFactory.fromTxData({ type: txType.type }, { common });

0 commit comments

Comments
 (0)