Skip to content
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4aaeb2f
feat: integrate Hyperliquid builder fee wallet and referral code
abretonc7s Mar 30, 2026
8820307
fix: add breadcrumb
abretonc7s Mar 30, 2026
155500e
feat: wire perps tracer to Sentry and expand infrastructure test cove…
abretonc7s Mar 30, 2026
f2c3b4e
chore: cleanup
abretonc7s Mar 30, 2026
a5f7aa1
Merge branch 'main' into feat/perps/custom-fee
abretonc7s Mar 31, 2026
4c34c70
chore: revert Sentry wiring from infrastructure to keep PR focused
abretonc7s Mar 31, 2026
56d92a5
Merge branch 'main' into feat/perps/custom-fee
gambinish Mar 31, 2026
544d218
Merge branch 'main' into feat/perps/custom-fee
gambinish Mar 31, 2026
30006d3
Merge branch 'main' into feat/perps/custom-fee
aganglada Mar 31, 2026
5a9477f
Merge branch 'main' into feat/perps/custom-fee
michalconsensys Apr 1, 2026
d3b7bf5
Merge branch 'main' into feat/perps/custom-fee
abretonc7s Apr 2, 2026
122a094
Merge branch 'main' into feat/perps/custom-fee
gambinish Apr 2, 2026
f05cabf
Update LavaMoat policies
metamaskbot Apr 2, 2026
24a86b3
fix: bump to preview package, prove myx is removed in lavamoat
gambinish Apr 2, 2026
c5de830
Merge branch 'main' into feat/perps/custom-fee
abretonc7s Apr 3, 2026
e0fe286
Merge branch 'main' into feat/perps/custom-fee
gambinish Apr 3, 2026
fa1d403
Update LavaMoat policies
metamaskbot Apr 3, 2026
9346015
fix: lint
gambinish Apr 3, 2026
a6b82c3
fix: address review comments on PR #41356
abretonc7s Apr 9, 2026
2691b57
revert: restore lavamoat policies and builds.yml to main baseline
abretonc7s Apr 9, 2026
2142757
Merge branch 'main' into feat/perps/custom-fee
abretonc7s Apr 9, 2026
fb2ab51
fix: declare MM_PERPS_HL_BUILDER_ADDRESS_* env vars in builds.yml
abretonc7s Apr 9, 2026
cc9df9a
Update LavaMoat policies
metamaskbot Apr 9, 2026
7874d3a
fix: handle missing MYXProvider.mjs and correct perps cached selectors
abretonc7s Apr 10, 2026
f53c300
fix: cast through unknown in webpack IgnorePlugin instanceof check
abretonc7s Apr 10, 2026
7a4db0c
fix: update e2e state snapshots for perps-controller v2 state shape
abretonc7s Apr 10, 2026
a110405
fix: restore lastError in e2e state snapshots — field is still in v2 …
abretonc7s Apr 10, 2026
b5b2abf
fix: remove redundant IgnorePlugin, rely on existing ignoreWarnings
abretonc7s Apr 10, 2026
4f17403
Merge remote-tracking branch 'origin/main' into feat/perps/custom-fee
abretonc7s Apr 10, 2026
88f5a1e
fix: restore IgnorePlugin for MYXProvider and update v2 test mock
abretonc7s Apr 10, 2026
60e6145
docs(webpack): clarify MYXProvider IgnorePlugin is a ts-bridge bug wo…
abretonc7s Apr 10, 2026
c4e788b
fix: resolve perps-controller to preview build excluding MYX
abretonc7s Apr 10, 2026
baf2508
fix: remove MYX workarounds now excluded by preview package
abretonc7s Apr 10, 2026
9e62ec0
Merge branch 'main' into feat/perps/custom-fee
abretonc7s Apr 10, 2026
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
8 changes: 8 additions & 0 deletions app/scripts/controllers/perps/infrastructure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@
setMeasurement: (name: string, value: number, unit: string) => {
globalThis.sentry?.setMeasurement?.(name, value, unit);
},
addBreadcrumb: (_breadcrumb: {
category: string;
message: string;
level: 'fatal' | 'error' | 'warning' | 'log' | 'info' | 'debug';
data?: Record<string, unknown>;
}) => {
// TODO: Integrate with Sentry breadcrumbs when ready

Check warning on line 188 in app/scripts/controllers/perps/infrastructure.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=metamask-extension&issues=AZ10zT7CTAyY_RG9XKXz&open=AZ10zT7CTAyY_RG9XKXz&pullRequest=41356
},
};
}

Expand Down
54 changes: 54 additions & 0 deletions app/scripts/messenger-client-init/perps-controller-init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ describe('PerpsControllerInit', () => {
beforeEach(() => {
jest.clearAllMocks();
delete process.env.MM_PERPS_BLOCKED_REGIONS;
delete process.env.MM_PERPS_HL_BUILDER_ADDRESS_MAINNET;
delete process.env.MM_PERPS_HL_BUILDER_ADDRESS_TESTNET;
});

describe('controller instantiation', () => {
Expand All @@ -177,6 +179,9 @@ describe('PerpsControllerInit', () => {
fallbackHip3Enabled: true,
fallbackHip3AllowlistMarkets: [],
fallbackBlockedRegions: [],
providerCredentials: {
hyperliquid: undefined,
},
},
deferEligibilityCheck: true,
});
Expand Down Expand Up @@ -344,6 +349,55 @@ describe('PerpsControllerInit', () => {
});
});

describe('getHyperLiquidBuilderAddresses', () => {
it('returns undefined when no env vars are set', () => {
PerpsControllerInit(getInitRequestMock());

const constructorCall = PerpsControllerMock.mock.calls[0][0];
expect(
constructorCall.clientConfig?.providerCredentials?.hyperliquid,
).toBeUndefined();
});

it('passes mainnet builder address from env var', () => {
process.env.MM_PERPS_HL_BUILDER_ADDRESS_MAINNET = '0xabc123';
PerpsControllerInit(getInitRequestMock());

const constructorCall = PerpsControllerMock.mock.calls[0][0];
expect(
constructorCall.clientConfig?.providerCredentials?.hyperliquid,
).toEqual({
builderAddressMainnet: '0xabc123',
});
});

it('passes testnet builder address from env var', () => {
process.env.MM_PERPS_HL_BUILDER_ADDRESS_TESTNET = '0xdef456';
PerpsControllerInit(getInitRequestMock());

const constructorCall = PerpsControllerMock.mock.calls[0][0];
expect(
constructorCall.clientConfig?.providerCredentials?.hyperliquid,
).toEqual({
builderAddressTestnet: '0xdef456',
});
});

it('passes both builder addresses when both env vars are set', () => {
process.env.MM_PERPS_HL_BUILDER_ADDRESS_MAINNET = '0xabc123';
process.env.MM_PERPS_HL_BUILDER_ADDRESS_TESTNET = '0xdef456';
PerpsControllerInit(getInitRequestMock());

const constructorCall = PerpsControllerMock.mock.calls[0][0];
expect(
constructorCall.clientConfig?.providerCredentials?.hyperliquid,
).toEqual({
builderAddressMainnet: '0xabc123',
builderAddressTestnet: '0xdef456',
});
});
});

describe('api method delegation', () => {
const apiToController: [string, string][] = [
['perpsInit', 'init'],
Expand Down
23 changes: 23 additions & 0 deletions app/scripts/messenger-client-init/perps-controller-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ function getFallbackBlockedRegions(): string[] {
.filter(Boolean);
}

/**
* Read HyperLiquid builder fee wallet addresses from env vars.
* Returns undefined when neither env var is set (package defaults apply).
*/
function getHyperLiquidBuilderAddresses():
| { builderAddressMainnet?: string; builderAddressTestnet?: string }
| undefined {
const mainnet =
process.env.MM_PERPS_HL_BUILDER_ADDRESS_MAINNET?.trim() || undefined;
const testnet =
process.env.MM_PERPS_HL_BUILDER_ADDRESS_TESTNET?.trim() || undefined;
if (!mainnet && !testnet) {
return undefined;
}
return {
...(mainnet ? { builderAddressMainnet: mainnet } : {}),
...(testnet ? { builderAddressTestnet: testnet } : {}),
};
}

export const PerpsControllerInit: ControllerInitFunction<
PerpsController,
PerpsControllerMessenger
Expand All @@ -50,6 +70,9 @@ export const PerpsControllerInit: ControllerInitFunction<
fallbackHip3Enabled: true,
fallbackHip3AllowlistMarkets: [],
fallbackBlockedRegions,
providerCredentials: {
hyperliquid: getHyperLiquidBuilderAddresses(),
},
},
deferEligibilityCheck: !completedOnboarding || !useExternalServices,
});
Expand Down
3 changes: 3 additions & 0 deletions builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,9 @@ env:

# Perps geoblock fallback (comma-separated region codes when remote flag unavailable)
- MM_PERPS_BLOCKED_REGIONS: null
# Hyperliquid builder fee wallet address overrides (defaults to package-defined addresses when unset)
- MM_PERPS_HL_BUILDER_ADDRESS_MAINNET: null
- MM_PERPS_HL_BUILDER_ADDRESS_TESTNET: null
# Build-time gate: omit PerpsController and perps RPC when false (mitigation for critical failures)
- PERPS_ENABLED: false
# Build-time gate: when false AssetsController is instantiated but state remains empty.
Expand Down
113 changes: 0 additions & 113 deletions development/webpack/test/webpack.config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,119 +403,6 @@ ${Object.entries(env)
);
});

it('keeps the MYX provider ignore only while the warning still exists', async () => {
const config: Configuration = getWebpackConfig();
const myxWarningPattern = /MYXProvider\.mjs/u;
const ignoreWarnings = config.ignoreWarnings ?? [];
const getPerpsControllerWarnings = async (): Promise<string[]> => {
const tempDirectory = fs.mkdtempSync(
join(tmpdir(), 'webpack-perps-controller-warning-'),
);
const repositoryRoot = resolve(__dirname, '../../..');
const entryFilePath = join(tempDirectory, 'entry.js');

fs.writeFileSync(entryFilePath, "import '@metamask/perps-controller';\n");

try {
const compiler = webpack({
mode: 'development',
context: repositoryRoot,
entry: entryFilePath,
output: {
path: join(tempDirectory, 'dist'),
filename: 'bundle.js',
clean: true,
},
resolve: {
modules: [join(repositoryRoot, 'node_modules'), 'node_modules'],
},
});

return await new Promise((resolveWarnings, reject) => {
compiler.run((error, stats) => {
const closeCompiler = (callback: () => void) => {
compiler.close((closeError) => {
if (closeError) {
reject(closeError);
return;
}

callback();
});
};

if (error) {
closeCompiler(() => reject(error));
return;
}

if (!stats) {
closeCompiler(() =>
reject(new Error('Webpack finished without returning stats.')),
);
return;
}

const errors =
stats.toJson({ all: false, errors: true }).errors ?? [];
if (errors.length > 0) {
closeCompiler(() =>
reject(
new Error(
`Webpack build failed:\n${errors
.map((item) =>
typeof item === 'string' ? item : item.message,
)
.join('\n\n')}`,
),
),
);
return;
}

const warnings =
stats.toJson({ all: false, warnings: true }).warnings ?? [];
closeCompiler(() =>
resolveWarnings(
warnings.map((item) =>
typeof item === 'string' ? item : item.message,
),
),
);
});
});
} finally {
fs.rmSync(tempDirectory, { recursive: true, force: true });
}
};

assert.ok(
ignoreWarnings.some(
(warning) =>
warning instanceof RegExp && warning.test('MYXProvider.mjs'),
),
'Expected webpack config to ignore MYXProvider.mjs while that warning still exists.',
);

// The published perps-controller package currently omits MYXProvider.mjs.
// This assertion should fail once that warning goes away, which is the cue
// to remove the ignoreWarnings entry from webpack.config.ts.
const warnings = await getPerpsControllerWarnings();
assert.ok(
warnings.some((warning) => myxWarningPattern.test(warning)),
[
'Good news: webpack no longer warns about missing MYXProvider.mjs.',
'That means the temporary workaround is probably no longer needed.',
'Cleanup steps:',
'1. Remove this test: "keeps the MYX provider ignore only while the warning still exists".',
'2. Remove /MYXProvider\\.mjs/u from ignoreWarnings in development/webpack/webpack.config.ts.',
'3. Re-run the webpack unit tests.',
'Warnings seen in this run:',
...warnings,
].join('\n'),
);
});

it('should write the `dry-run` message then call exit(0)', () => {
const exit = mock.method(process, 'exit', noop, { times: 1 });
const error = mock.method(console, 'error', noop, { times: 1 });
Expand Down
Loading
Loading