Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add custom transaction schema to formatTransaction #7227

Merged
merged 19 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2702,3 +2702,24 @@ If there are any bugs, improvements, optimizations or any new feature proposal f
- The callback function provided to the static `Web3.onNewProviderDiscovered` function expects a parameter of type `EIP6963ProvidersMapUpdateEvent` as opposed to `EIP6963AnnounceProviderEvent`. (#7242)

## [Unreleased]

### Added

#### web3-core

- Adds a new property (`customTransactionSchema`) to `Web3ConfigOptions`
- Adds a new property (`config`) to `Web3RequestManager`

#### web3-eth

- Adds the same `{transactionSchema?: ValidationSchemaInput}` that exists in `formatTransaction` to `validateTransactionForSigning`

### Changed

#### web3-eth

- Forwards the new `web3Context.config.customTransactionSchema` to `formatTransaction`

#### web3-eth-personal

- Forwards the new `web3Context.config.customTransactionSchema` to `formatTransaction`
4 changes: 4 additions & 0 deletions docs/docs/guides/web3_config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ There is list of configuration params that can be set for modifying behavior of
- [defaultCommon](/guides/web3_config/#defaultcommon)
- [defaultTransactionType](/guides/web3_config/#defaulttransactiontype)
- [defaultMaxPriorityFeePerGas](/guides/web3_config/#defaultmaxpriorityfeepergas)
- [customTransactionSchema](/guides/web3_config/#customTransactionSchema)
- [defaultReturnFormat](/guides/web3_config/#defaultreturnformat)

## Global level Config
Expand Down Expand Up @@ -411,6 +412,9 @@ The `defaultMaxPriorityFeePerGas` option is used to set the [`defaultMaxPriority

The default value of `defaultMaxPriorityFeePerGas` is 2500000000 (2.5gwei) in hexstring format.

### [customTransactionSchema](/api/web3-core/class/Web3Config#customTransactionSchema)
The `customTransactionSchema` option is used to allow [`formatTransaction`](/api/web3-eth/function/formatTransaction) to accept a custom schema to validate transactions. A use-case could be: your chain has an extra field in its transactions and you want to write a plugin that makes sending these transactions easier.

### [defaultReturnFormat](/api/web3-core/class/Web3Config#defaultReturnFormat)
The `defaultReturnFormat` option allows users to specify the format in which certain types of data should be returned by default. It is a configuration parameter that can be set at the global level and affects how data is returned across the entire library.
```ts
Expand Down
5 changes: 5 additions & 0 deletions packages/web3-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,8 @@ Documentation:
- `setConfig()` fix for `setMaxListenerWarningThreshold` fix (#5079)

## [Unreleased]

### Added

- Adds a new property (`customTransactionSchema`) to `Web3ConfigOptions`
- Adds a new property (`config`) to `Web3RequestManager`
6 changes: 6 additions & 0 deletions packages/web3-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Web3APIMethod,
Web3APIReturnType,
} from 'web3-types';
import { Schema } from 'web3-validator';

export type TransactionTypeParser = (transaction: Transaction) => HexString | undefined;

Expand Down Expand Up @@ -50,3 +51,8 @@ export interface RequestManagerMiddleware<API> {
options?: { [key: string]: unknown },
): Promise<JsonRpcResponse<ResponseType>>;
}

export type CustomTransactionSchema = {
type: string;
properties: Record<string, Schema>;
};
13 changes: 12 additions & 1 deletion packages/web3-core/src/web3_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from 'web3-types';
import { ConfigHardforkMismatchError, ConfigChainMismatchError } from 'web3-errors';
import { isNullish, toHex } from 'web3-utils';
import { TransactionTypeParser } from './types.js';
import { CustomTransactionSchema, TransactionTypeParser } from './types.js';
// eslint-disable-next-line import/no-cycle
import { TransactionBuilder } from './web3_context.js';
import { Web3EventEmitter } from './web3_event_emitter.js';
Expand Down Expand Up @@ -59,6 +59,7 @@ export interface Web3ConfigOptions {
};
transactionBuilder?: TransactionBuilder;
transactionTypeParser?: TransactionTypeParser;
customTransactionSchema?: CustomTransactionSchema;
defaultReturnFormat: DataFormat;
}

Expand Down Expand Up @@ -101,6 +102,7 @@ export abstract class Web3Config
},
transactionBuilder: undefined,
transactionTypeParser: undefined,
customTransactionSchema: undefined,
defaultReturnFormat: DEFAULT_RETURN_FORMAT,
};

Expand Down Expand Up @@ -520,6 +522,15 @@ export abstract class Web3Config
this.config.transactionTypeParser = val;
}

public get customTransactionSchema(): CustomTransactionSchema | undefined {
return this.config.customTransactionSchema;
}

public set customTransactionSchema(schema: CustomTransactionSchema | undefined) {
this._triggerConfigChange('customTransactionSchema', schema);
this.config.customTransactionSchema = schema;
}

private _triggerConfigChange<K extends keyof Web3ConfigOptions>(
config: K,
newValue: Web3ConfigOptions[K],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ exports[`Web3Context getContextObject should return correct context object 1`] =
"config": {
"blockHeaderTimeout": 10,
"contractDataInputFill": "data",
"customTransactionSchema": undefined,
"defaultAccount": undefined,
"defaultBlock": "latest",
"defaultChain": "mainnet",
Expand Down
1 change: 1 addition & 0 deletions packages/web3-core/test/unit/web3_config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const defaultConfig = {
defaultReturnFormat: DEFAULT_RETURN_FORMAT,
transactionBuilder: undefined,
transactionTypeParser: undefined,
customTransactionSchema: undefined,
};
const setValue = {
string: 'newValue',
Expand Down
4 changes: 4 additions & 0 deletions packages/web3-eth-personal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,7 @@ Documentation:
- Dependencies updated

## [Unreleased]

### Changed

- Forwards the new `web3Context.config.customTransactionSchema` to `formatTransaction`
4 changes: 2 additions & 2 deletions packages/web3-eth-personal/src/personal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export class Personal extends Web3Context<EthPersonalAPI> {
* ```
*/
public async sendTransaction(tx: Transaction, passphrase: string) {
return rpcWrappers.sendTransaction(this.requestManager, tx, passphrase);
return rpcWrappers.sendTransaction(this.requestManager, tx, passphrase, this.config);
}
/**
* Signs a transaction. This account needs to be unlocked.
Expand Down Expand Up @@ -204,7 +204,7 @@ export class Personal extends Web3Context<EthPersonalAPI> {
* ```
*/
public async signTransaction(tx: Transaction, passphrase: string) {
return rpcWrappers.signTransaction(this.requestManager, tx, passphrase);
return rpcWrappers.signTransaction(this.requestManager, tx, passphrase, this.config);
}
/**
* Calculates an Ethereum specific signature with:
Expand Down
12 changes: 9 additions & 3 deletions packages/web3-eth-personal/src/rpc_method_wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
import { Web3RequestManager } from 'web3-core';
import { Web3RequestManager, Web3ConfigOptions } from 'web3-core';
import { toChecksumAddress, utf8ToHex } from 'web3-utils';
import { formatTransaction } from 'web3-eth';
import { Address, EthPersonalAPI, ETH_DATA_FORMAT, HexString, Transaction } from 'web3-types';
Expand Down Expand Up @@ -72,8 +72,11 @@ export const sendTransaction = async (
requestManager: Web3RequestManager<EthPersonalAPI>,
tx: Transaction,
passphrase: string,
config?: Web3ConfigOptions,
) => {
const formattedTx = formatTransaction(tx, ETH_DATA_FORMAT);
const formattedTx = formatTransaction(tx, ETH_DATA_FORMAT, {
transactionSchema: config?.customTransactionSchema,
});

return personalRpcMethods.sendTransaction(requestManager, formattedTx, passphrase);
};
Expand All @@ -82,8 +85,11 @@ export const signTransaction = async (
requestManager: Web3RequestManager<EthPersonalAPI>,
tx: Transaction,
passphrase: string,
config?: Web3ConfigOptions,
) => {
const formattedTx = formatTransaction(tx, ETH_DATA_FORMAT);
const formattedTx = formatTransaction(tx, ETH_DATA_FORMAT, {
transactionSchema: config?.customTransactionSchema,
});

return personalRpcMethods.signTransaction(requestManager, formattedTx, passphrase);
};
Expand Down
12 changes: 10 additions & 2 deletions packages/web3-eth-personal/test/unit/eth_personal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,11 @@ describe('Personal', () => {
await personal.sendTransaction(tx, 'password');

expect(eth.formatTransaction).toHaveBeenCalledTimes(1);
expect(eth.formatTransaction).toHaveBeenCalledWith(tx, ETH_DATA_FORMAT);
expect(eth.formatTransaction).toHaveBeenCalledWith(
tx,
ETH_DATA_FORMAT,
expect.anything(),
);
});
});

Expand Down Expand Up @@ -215,7 +219,11 @@ describe('Personal', () => {
await personal.signTransaction(tx, 'password');

expect(eth.formatTransaction).toHaveBeenCalledTimes(1);
expect(eth.formatTransaction).toHaveBeenCalledWith(tx, ETH_DATA_FORMAT);
expect(eth.formatTransaction).toHaveBeenCalledWith(
tx,
ETH_DATA_FORMAT,
expect.anything(),
);
});
});

Expand Down
8 changes: 8 additions & 0 deletions packages/web3-eth/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,11 @@ Documentation:
- Change method `getTransactionReceipt` to not be casted as `TransactionReceipt` to give proper return type (#7159)

## [Unreleased]

### Changed

- Forwards the new `web3Context.config.customTransactionSchema` to `formatTransaction`

### Added

- Adds the same `{transactionSchema?: ValidationSchemaInput}` that exists in `formatTransaction` to `validateTransactionForSigning`
23 changes: 19 additions & 4 deletions packages/web3-eth/src/rpc_method_wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ export async function getTransaction<ReturnFormat extends DataFormat>(
return isNullish(response)
? response
: formatTransaction(response, returnFormat, {
transactionSchema: web3Context.config.customTransactionSchema,
fillInputAndData: true,
});
}
Expand All @@ -448,6 +449,7 @@ export async function getPendingTransactions<ReturnFormat extends DataFormat>(
transaction as unknown as Transaction,
returnFormat ?? web3Context.defaultReturnFormat,
{
transactionSchema: web3Context.config.customTransactionSchema,
fillInputAndData: true,
},
),
Expand Down Expand Up @@ -488,6 +490,7 @@ export async function getTransactionFromBlock<ReturnFormat extends DataFormat>(
return isNullish(response)
? response
: formatTransaction(response, returnFormat ?? web3Context.defaultReturnFormat, {
transactionSchema: web3Context.config.customTransactionSchema,
fillInputAndData: true,
});
}
Expand Down Expand Up @@ -606,6 +609,9 @@ export function sendTransaction<
to: getTransactionFromOrToAttr('to', web3Context, transaction),
},
ETH_DATA_FORMAT,
{
transactionSchema: web3Context.config.customTransactionSchema,
},
);

try {
Expand Down Expand Up @@ -847,7 +853,9 @@ export async function signTransaction<ReturnFormat extends DataFormat>(
) {
const response = await ethRpcMethods.signTransaction(
web3Context.requestManager,
formatTransaction(transaction, ETH_DATA_FORMAT),
formatTransaction(transaction, ETH_DATA_FORMAT, {
transactionSchema: web3Context.config.customTransactionSchema,
}),
);
// Some clients only return the encoded signed transaction (e.g. Ganache)
// while clients such as Geth return the desired SignedTransactionInfoAPI object
Expand All @@ -862,6 +870,7 @@ export async function signTransaction<ReturnFormat extends DataFormat>(
returnFormat,
),
tx: formatTransaction((response as SignedTransactionInfoAPI).tx, returnFormat, {
transactionSchema: web3Context.config.customTransactionSchema,
fillInputAndData: true,
}),
};
Expand All @@ -885,7 +894,9 @@ export async function call<ReturnFormat extends DataFormat>(

const response = await ethRpcMethods.call(
web3Context.requestManager,
formatTransaction(transaction, ETH_DATA_FORMAT),
formatTransaction(transaction, ETH_DATA_FORMAT, {
transactionSchema: web3Context.config.customTransactionSchema,
}),
blockNumberFormatted,
);

Expand All @@ -903,7 +914,9 @@ export async function estimateGas<ReturnFormat extends DataFormat>(
blockNumber: BlockNumberOrTag = web3Context.defaultBlock,
returnFormat: ReturnFormat,
) {
const transactionFormatted = formatTransaction(transaction, ETH_DATA_FORMAT);
const transactionFormatted = formatTransaction(transaction, ETH_DATA_FORMAT, {
transactionSchema: web3Context.config.customTransactionSchema,
});
const blockNumberFormatted = isBlockTag(blockNumber as string)
? (blockNumber as BlockTag)
: format({ format: 'uint' }, blockNumber as Numbers, ETH_DATA_FORMAT);
Expand Down Expand Up @@ -1074,7 +1087,9 @@ export async function createAccessList<ReturnFormat extends DataFormat>(

const response = (await ethRpcMethods.createAccessList(
web3Context.requestManager,
formatTransaction(transaction, ETH_DATA_FORMAT),
formatTransaction(transaction, ETH_DATA_FORMAT, {
transactionSchema: web3Context.config.customTransactionSchema,
}),
blockNumberFormatted,
)) as unknown as AccessListResult;

Expand Down
6 changes: 6 additions & 0 deletions packages/web3-eth/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
TransactionWithFromLocalWalletIndex,
TransactionWithToLocalWalletIndex,
} from 'web3-types';
import { Schema } from 'web3-validator';

export type InternalTransaction = FormatType<Transaction, typeof ETH_DATA_FORMAT>;

Expand Down Expand Up @@ -105,3 +106,8 @@ export interface TransactionMiddleware {
options?: { [key: string]: unknown },
): Promise<TransactionMiddlewareData>;
}

export type CustomTransactionSchema = {
type: string;
properties: Record<string, Schema>;
};
10 changes: 8 additions & 2 deletions packages/web3-eth/src/utils/decode_signed_transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { bytesToHex, format, hexToBytes, keccak256 } from 'web3-utils';
import { TransactionFactory } from 'web3-eth-accounts';
import { detectRawTransactionType } from './detect_transaction_type.js';
import { formatTransaction } from './format_transaction.js';
import { type CustomTransactionSchema } from '../types.js';

/**
* Decodes an [RLP](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/#top) encoded transaction.
Expand All @@ -35,7 +36,9 @@ import { formatTransaction } from './format_transaction.js';
export function decodeSignedTransaction<ReturnFormat extends DataFormat>(
encodedSignedTransaction: HexStringBytes,
returnFormat: ReturnFormat,
options: { fillInputAndData?: boolean } = { fillInputAndData: false },
options: { fillInputAndData?: boolean; transactionSchema?: CustomTransactionSchema } = {
fillInputAndData: false,
},
): SignedTransactionInfoAPI {
return {
raw: format({ format: 'bytes' }, encodedSignedTransaction, returnFormat),
Expand All @@ -48,7 +51,10 @@ export function decodeSignedTransaction<ReturnFormat extends DataFormat>(
type: detectRawTransactionType(hexToBytes(encodedSignedTransaction)),
} as TransactionSignedAPI,
returnFormat,
{ fillInputAndData: options.fillInputAndData },
{
fillInputAndData: options.fillInputAndData,
transactionSchema: options.transactionSchema,
},
),
};
}
5 changes: 3 additions & 2 deletions packages/web3-eth/src/utils/format_transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import { isNullish, ValidationSchemaInput } from 'web3-validator';
import { mergeDeep, format, bytesToHex, toHex } from 'web3-utils';
import { TransactionDataAndInputError } from 'web3-errors';

import { transactionInfoSchema, transactionSchema } from '../schemas.js';
import { transactionInfoSchema } from '../schemas.js';
import { type CustomTransactionSchema } from '../types.js';

export function formatTransaction<
ReturnFormat extends DataFormat = typeof DEFAULT_RETURN_FORMAT,
Expand All @@ -29,7 +30,7 @@ export function formatTransaction<
transaction: TransactionType,
returnFormat: ReturnFormat = DEFAULT_RETURN_FORMAT as ReturnFormat,
options: {
transactionSchema?: ValidationSchemaInput | typeof transactionSchema;
transactionSchema?: ValidationSchemaInput | CustomTransactionSchema | undefined;
fillInputAndData?: boolean;
} = {
transactionSchema: transactionInfoSchema,
Expand Down
12 changes: 8 additions & 4 deletions packages/web3-eth/src/utils/prepare_transaction_for_signing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,16 @@ export const prepareTransactionForSigning = async (
fillGasPrice,
fillGasLimit,
})) as unknown as PopulatedUnsignedTransaction;
const formattedTransaction = formatTransaction(
populatedTransaction,
ETH_DATA_FORMAT,
) as unknown as FormatType<PopulatedUnsignedTransaction, typeof ETH_DATA_FORMAT>;
const formattedTransaction = formatTransaction(populatedTransaction, ETH_DATA_FORMAT, {
transactionSchema: web3Context.config.customTransactionSchema,
}) as unknown as FormatType<PopulatedUnsignedTransaction, typeof ETH_DATA_FORMAT>;

validateTransactionForSigning(
formattedTransaction as unknown as FormatType<Transaction, typeof ETH_DATA_FORMAT>,
undefined,
{
transactionSchema: web3Context.config.customTransactionSchema,
},
);

return TransactionFactory.fromTxData(
Expand Down
Loading
Loading