Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import {
optional,
string,
type,
union,
object,
literal,
} from '@metamask/superstruct';
import { CaipChainIdStruct } from '@metamask/utils';
import { CaipChainIdStruct, StrictHexStruct } from '@metamask/utils';

const AssetSchema = type({
assetId: string(),
Expand Down Expand Up @@ -41,6 +44,13 @@ const BlockExplorerUrlsSchema = type({
fallbacks: array(string()),
});

const GasEstimationStrategySchema = union([
object({
type: literal('l1Oracle'),
l1OracleAddress: optional(StrictHexStruct),
}),
]);

const ChainConfigSchema = type({
isActive: boolean(),
isTestnet: boolean(),
Expand All @@ -49,6 +59,7 @@ const ChainConfigSchema = type({
isDeprecated: boolean(),
isDeletable: boolean(),
priority: number(),
gasEstimationStrategy: optional(GasEstimationStrategySchema),
});

/**
Expand Down Expand Up @@ -78,6 +89,8 @@ export const RegistryConfigApiResponseSchema = type({
}),
});

export type GasEstimationStrategy = Infer<typeof GasEstimationStrategySchema>;

export type RegistryNetworkConfig = Infer<typeof RegistryNetworkConfigSchema>;

export type RegistryConfigApiResponse = Infer<
Expand Down
8 changes: 3 additions & 5 deletions packages/config-registry-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type {
export type {
ConfigRegistryControllerStartPollingAction,
ConfigRegistryControllerStopPollingAction,
ConfigRegistryControllerMethodActions,
ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction,
} from './ConfigRegistryController-method-action-types.js';
export {
ConfigRegistryController,
Expand All @@ -21,17 +21,15 @@ export type {
FetchConfigResult,
RegistryNetworkConfig,
RegistryConfigApiResponse,
GasEstimationStrategy,
} from './config-registry-api-service/types.js';
export type {
ConfigRegistryApiServiceOptions,
ConfigRegistryApiServiceActions,
ConfigRegistryApiServiceEvents,
ConfigRegistryApiServiceMessenger,
} from './config-registry-api-service/config-registry-api-service.js';
export type {
ConfigRegistryApiServiceFetchConfigAction,
ConfigRegistryApiServiceMethodActions,
} from './config-registry-api-service/config-registry-api-service-method-action-types.js';
export type { ConfigRegistryApiServiceFetchConfigAction } from './config-registry-api-service/config-registry-api-service-method-action-types.js';
export type { NetworkFilterOptions } from './config-registry-api-service/filters.js';
export { ConfigRegistryApiService } from './config-registry-api-service/config-registry-api-service.js';
export { filterNetworks } from './config-registry-api-service/filters.js';
6 changes: 3 additions & 3 deletions packages/transaction-controller/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ module.exports = merge(baseConfig, {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 93.49,
functions: 93.77,
lines: 97.15,
branches: 93.51,
functions: 93.79,
lines: 97.16,
statements: 97.12,
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/transaction-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@metamask/accounts-controller": "^39.0.5",
"@metamask/approval-controller": "^9.0.2",
"@metamask/base-controller": "^9.1.0",
"@metamask/config-registry-controller": "^0.4.1",
"@metamask/controller-utils": "^12.3.0",
"@metamask/core-backend": "^7.0.0",
"@metamask/gas-fee-controller": "^26.3.0",
Expand Down
12 changes: 10 additions & 2 deletions packages/transaction-controller/src/TransactionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
StateMetadata,
} from '@metamask/base-controller';
import { BaseController } from '@metamask/base-controller';
import { ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction } from '@metamask/config-registry-controller';
import {
ApprovalType,
ORIGIN_METAMASK,
Expand Down Expand Up @@ -64,7 +65,7 @@ import {
JsonRpcError,
} from '@metamask/rpc-errors';
import type { Hex, Json } from '@metamask/utils';
import { add0x } from '@metamask/utils';
import { add0x, hexToNumber } from '@metamask/utils';
// This package purposefully relies on Node's EventEmitter module.
// eslint-disable-next-line import-x/no-nodejs-modules
import { EventEmitter } from 'events';
Expand Down Expand Up @@ -435,6 +436,7 @@ export type AllowedActions =
| AccountsControllerGetSelectedAccountAction
| AccountsControllerGetStateAction
| ApprovalControllerAddRequestAction
| ConfigRegistryControllerGetNetworkConfigByCaip2ChainIdAction
| GasFeeControllerFetchGasFeeEstimatesAction
| KeyringControllerGetStateAction
| KeyringControllerSignEip7702AuthorizationAction
Expand Down Expand Up @@ -3959,7 +3961,13 @@ export class TransactionController extends BaseController<
#getLayer1GasFeeFlows(): Layer1GasFeeFlow[] {
return [
new MantleLayer1GasFeeFlow(),
new OptimismLayer1GasFeeFlow(),
new OptimismLayer1GasFeeFlow({
getRegistryGasStrategyForChain: (chainId: Hex) =>
this.messenger.call(
'ConfigRegistryController:getNetworkConfigByCaip2ChainId',
`eip155:${hexToNumber(chainId)}`,
)?.config.gasEstimationStrategy,
}),
new ScrollLayer1GasFeeFlow(),
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ const setupController = async (
'AccountsController:getSelectedAccount',
'AccountsController:getState',
'ApprovalController:addRequest',
'ConfigRegistryController:getNetworkConfigByCaip2ChainId',
'GasFeeController:fetchGasFeeEstimates',
'KeyringController:signTransaction',
'NetworkController:findNetworkClientIdByChainId',
Expand Down Expand Up @@ -319,6 +320,11 @@ const setupController = async (
transaction.toJSON() as TypedTxData,
);

rootMessenger.registerActionHandler(
'ConfigRegistryController:getNetworkConfigByCaip2ChainId',
() => undefined,
);

const options: TransactionControllerOptions = {
disableSwaps: false,
isAutomaticGasFeeUpdateEnabled: () => true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GasEstimationStrategy } from '@metamask/config-registry-controller';
import * as ControllerUtils from '@metamask/controller-utils';
import { hexToNumber } from '@metamask/utils';
import type { Hex } from '@metamask/utils';
Expand Down Expand Up @@ -91,6 +92,24 @@ describe('OptimismLayer1GasFeeFlow', () => {
).toBe(true);
});

it('returns true when the config registry lists the gas strategy for the chain as `l1Oracle`', async () => {
const flow = new OptimismLayer1GasFeeFlow({
getRegistryGasStrategyForChain: (): GasEstimationStrategy => ({
type: 'l1Oracle',
l1OracleAddress: '0x123',
}),
});
const transactionMeta = createTransaction(CHAIN_IDS.OPTIMISM);

expect(
await flow.matchesTransaction({
transactionMeta,
messenger,
}),
).toBe(true);
expect(handleFetchMock).not.toHaveBeenCalled();
});

it('falls back to static list when remote list omits the chain', async () => {
handleFetchMock.mockResolvedValue({
fullSupport: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ export class OptimismLayer1GasFeeFlow extends OracleLayer1GasFeeFlow {
transactionMeta: TransactionMeta;
messenger: TransactionControllerMessenger;
}): Promise<boolean> {
const registryGasStrategy = this.getRegistryGasStrategyForChain?.(
transactionMeta.chainId,
);

if (registryGasStrategy?.type === 'l1Oracle') {
return true;
}

const chainIdAsNumber = hexToNumber(transactionMeta.chainId);

const supportedChains = await this.#fetchOptimismSupportedChains();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { Layer1GasFeeFlowRequest, TransactionMeta } from '../types.js';
import { rpcRequest } from '../utils/provider.js';
import { bnFromHex, padHexToEvenLength } from '../utils/utils.js';
import { OracleLayer1GasFeeFlow } from './OracleLayer1GasFeeFlow.js';
import { GasEstimationStrategy } from '../../../config-registry-controller/src/config-registry-api-service/types.js';

jest.mock('../utils/provider');

Expand Down Expand Up @@ -258,7 +259,35 @@ describe('OracleLayer1GasFeeFlow', () => {
});
});

it('uses default oracle configuration when subclasses do not override helpers', async () => {
it('uses the registry network configuration oracle address when subclasses do not override helpers', async () => {
const serializedTransactionMock = Buffer.from(
SERIALIZED_TRANSACTION_MOCK,
'hex',
);

const typedTransactionMock = createMockTypedTransaction(
serializedTransactionMock,
);

jest
.spyOn(TransactionFactory, 'fromTxData')
.mockReturnValueOnce(typedTransactionMock);

const flow = new DefaultOracleLayer1GasFeeFlow({
getRegistryGasStrategyForChain: (): GasEstimationStrategy => ({
type: 'l1Oracle',
l1OracleAddress: '0x123',
}),
});
await flow.getLayer1Fee(request);

expect(rpcRequestMock).toHaveBeenCalledTimes(1);
const { to } = getEthCallObject(rpcRequestMock.mock.calls[0]);
expect(to).toBe('0x123');
expect(typedTransactionMock.sign).not.toHaveBeenCalled();
});

it('uses default oracle configuration when subclasses do not override helpers and registry config is undefined', async () => {
const serializedTransactionMock = Buffer.from(
SERIALIZED_TRANSACTION_MOCK,
'hex',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Interface } from '@ethersproject/abi';
import type { GasEstimationStrategy } from '@metamask/config-registry-controller';
import type { Hex } from '@metamask/utils';
import { add0x, createModuleLogger } from '@metamask/utils';
import BN from 'bn.js';
Expand Down Expand Up @@ -70,20 +71,43 @@ const DEFAULT_GAS_PRICE_ORACLE_ADDRESS =

const GAS_PRICE_ORACLE_INTERFACE = new Interface(GAS_PRICE_ORACLE_ABI);

type GetRegistryGasStrategyForChain = (
chainId: Hex,
) => GasEstimationStrategy | undefined;

/**
* Layer 1 gas fee flow that obtains gas fee estimate using an oracle smart contract.
*/
export abstract class OracleLayer1GasFeeFlow implements Layer1GasFeeFlow {
/**
* Optional lookup of the Config Registry network configuration,
* used to source the layer 1 oracle address from the registry before
* falling back to bundled constants.
*/
protected readonly getRegistryGasStrategyForChain?: GetRegistryGasStrategyForChain;

constructor({
getRegistryGasStrategyForChain,
}: {
getRegistryGasStrategyForChain?: GetRegistryGasStrategyForChain;
} = {}) {
this.getRegistryGasStrategyForChain = getRegistryGasStrategyForChain;
}

/**
* Resolves the oracle address for the given chain. Subclasses can override
* this method to provide chain-specific oracle addresses. By default, this
* returns the standard OP Stack gas price oracle address.
* uses the registry-provided oracle address when present, otherwise the
* standard OP Stack gas price oracle address.
*
* @param _chainId - The chain ID to resolve the oracle address for.
* @param chainId - The chain ID to resolve the oracle address for.
* @returns The oracle address for the given chain.
*/
protected getOracleAddressForChain(_chainId: Hex): Hex {
return DEFAULT_GAS_PRICE_ORACLE_ADDRESS;
protected getOracleAddressForChain(chainId: Hex): Hex {
return (
this.getRegistryGasStrategyForChain?.(chainId)?.l1OracleAddress ??
DEFAULT_GAS_PRICE_ORACLE_ADDRESS
);
}

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/transaction-controller/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
{
"path": "../base-controller/tsconfig.build.json"
},
{
"path": "../config-registry-controller/tsconfig.build.json"
},
{
"path": "../controller-utils/tsconfig.build.json"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/transaction-controller/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
{
"path": "../base-controller"
},
{
"path": "../config-registry-controller"
},
{
"path": "../controller-utils"
},
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9035,6 +9035,7 @@ __metadata:
"@metamask/approval-controller": "npm:^9.0.2"
"@metamask/auto-changelog": "npm:^6.1.0"
"@metamask/base-controller": "npm:^9.1.0"
"@metamask/config-registry-controller": "npm:^0.4.1"
"@metamask/connectivity-controller": "npm:^0.3.0"
"@metamask/controller-utils": "npm:^12.3.0"
"@metamask/core-backend": "npm:^7.0.0"
Expand Down
Loading