@deeeed/metamask-harness 0.78.0 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +1 -1
- package/adapters/extension/ensure-browser.sh +2 -2
- package/adapters/extension/inject.mjs +0 -1
- package/adapters/extension/launch-browser.cjs +39 -613
- package/adapters/extension/readiness.mjs +2 -5
- package/adapters/extension/reattach.sh +2 -2
- package/adapters/extension/stamp-runtime-title.cjs +1 -1
- package/adapters/manifest.json +2 -34
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +57 -1358
- package/adapters/mobile/bridge-runtime/teaching.cjs +20 -0
- package/adapters/mobile/inject.sh +16 -0
- package/adapters/mobile/launch-console-forwarder.cjs +1 -2
- package/adapters/mobile/open-device.sh +2 -2
- package/adapters/mobile/reload-app.mjs +4 -4
- package/adapters/mobile/reset-app-data.sh +1 -1
- package/adapters/mobile/start-console-forwarder.sh +3 -2
- package/adapters/mobile/start-metro.sh +17 -12
- package/adapters/mobile/wait-for-bridge.cjs +2 -2
- package/adapters/shared/ensure-runner-deps.sh +2 -2
- package/adapters/shared/harness-source-fingerprint.mjs +13 -8
- package/adapters/shared/open-debug.mjs +2 -2
- package/adapters/shared/runner-deps-ready.mjs +1 -1
- package/adapters/shared/sync-wallet-fixture.sh +8 -5
- package/adapters/terminal/cleanup.mjs +2 -1
- package/adapters/terminal/inject.mjs +2 -1
- package/adapters/terminal/lib/cdp-client.mjs +1 -1
- package/bin/mm-harness +9 -28
- package/dist/adapters/checkout-files.js +16 -0
- package/dist/adapters/console-capture.js +14 -1
- package/dist/adapters/core/actions.js +4 -3
- package/dist/adapters/core/overlay.js +1 -1
- package/dist/adapters/core/readiness.js +28 -1
- package/dist/adapters/core/run.js +3 -3
- package/dist/adapters/core/surface.js +22 -5
- package/dist/adapters/extension/browser-pid.js +56 -0
- package/dist/adapters/extension/console-capture.js +1 -1
- package/dist/adapters/extension/doctor-checks.js +1 -1
- package/dist/adapters/extension/ensure-ready.js +5 -4
- package/dist/adapters/extension/extension-id.js +1 -1
- package/dist/adapters/extension/feature-flags.js +1 -1
- package/dist/adapters/extension/fixtures.js +1 -2
- package/dist/adapters/extension/launch.js +4 -5
- package/dist/adapters/extension/launched-browser.js +1 -2
- package/dist/adapters/extension/network-observer.js +1 -1
- package/dist/adapters/extension/overlay.js +1 -1
- package/dist/adapters/extension/product-config.js +1 -1
- package/dist/adapters/extension/readiness.js +1 -1
- package/dist/adapters/extension/reload.js +1 -1
- package/dist/adapters/extension/run.js +24 -10
- package/dist/adapters/extension/runtime-decision.js +3 -2
- package/dist/adapters/extension/runtime.js +9 -5
- package/dist/adapters/extension/surface.js +20 -5
- package/dist/adapters/extension/verify.js +2 -2
- package/dist/adapters/harness-freshness.js +2 -1
- package/dist/adapters/mobile/android-gfxinfo-performance.js +1 -3
- package/dist/adapters/mobile/app-identity.js +10 -0
- package/dist/adapters/mobile/doctor-checks.js +1 -1
- package/dist/adapters/mobile/fixtures.js +1 -1
- package/dist/adapters/mobile/launch.js +27 -28
- package/dist/adapters/mobile/lifecycle.js +1 -1
- package/dist/adapters/mobile/metro-env.js +9 -59
- package/dist/adapters/mobile/performance-observer.js +1 -1
- package/dist/adapters/mobile/prepare-target.js +1 -1
- package/dist/adapters/mobile/prepare.js +4 -5
- package/dist/adapters/mobile/provision.js +1 -1
- package/dist/adapters/mobile/readiness.js +1 -1
- package/dist/adapters/mobile/recording-target.js +15 -0
- package/dist/adapters/mobile/release-artifact-state.js +1 -1
- package/dist/adapters/mobile/reload.js +1 -1
- package/dist/adapters/mobile/run.js +9 -8
- package/dist/adapters/mobile/runtime-decision.js +5 -7
- package/dist/adapters/mobile/source-freshness.js +22 -126
- package/dist/adapters/mobile/surface.js +31 -6
- package/dist/adapters/mobile/ui.js +16 -5
- package/dist/adapters/mobile/verify.js +2 -3
- package/dist/adapters/performance/cdp-trace.js +1 -1
- package/dist/adapters/performance/device-platform.js +1 -1
- package/dist/adapters/registry.js +2 -14
- package/dist/adapters/slot-ports.js +1 -1
- package/dist/adapters/surface.js +1 -0
- package/dist/adapters/terminal/console-capture.js +1 -1
- package/dist/adapters/terminal/launch.js +1 -1
- package/dist/adapters/terminal/run.js +25 -2
- package/dist/adapters/terminal/surface.js +12 -4
- package/dist/adapters.js +8 -4
- package/dist/advisor-policy.js +1 -1
- package/dist/checkout-view.js +1 -2
- package/dist/cli.js +28 -15
- package/dist/command-contract.js +1 -26
- package/dist/commands/call.js +10 -24
- package/dist/commands/check.js +3 -4
- package/dist/commands/checklist.js +1 -1
- package/dist/commands/completion-candidates.js +2 -10
- package/dist/commands/config.js +2 -2
- package/dist/commands/core-readiness.js +12 -7
- package/dist/commands/debug.js +3 -3
- package/dist/commands/device-target.js +5 -5
- package/dist/commands/doctor.js +2 -14
- package/dist/commands/domain.js +2 -2
- package/dist/commands/ensure-ready.js +1 -7
- package/dist/commands/fixtures.js +5 -6
- package/dist/commands/help.js +3 -4
- package/dist/commands/launch/device.js +10 -17
- package/dist/commands/launch/extension.js +3 -9
- package/dist/commands/launch/mobile.js +1 -1
- package/dist/commands/list-executables.js +1 -3
- package/dist/commands/logs.js +3 -3
- package/dist/commands/manifest.js +1 -12
- package/dist/commands/mobile-device-view.js +4 -2
- package/dist/commands/overlay.js +110 -0
- package/dist/commands/pin.js +1 -1
- package/dist/commands/pr-body.js +2 -2
- package/dist/commands/prepare.js +6 -11
- package/dist/commands/provision.js +3 -12
- package/dist/commands/recipe-advice.js +1 -2
- package/dist/commands/recipe-quality.js +2 -3
- package/dist/commands/resolve-extension.js +1 -7
- package/dist/commands/review.js +4 -4
- package/dist/commands/run-engine.js +12 -51
- package/dist/commands/run.js +4 -25
- package/dist/commands/runtime-decision.js +1 -6
- package/dist/commands/runtime-health.js +1 -7
- package/dist/commands/runtime-launch.js +1 -13
- package/dist/commands/self-test.js +2 -6
- package/dist/commands/status-watch.js +1 -2
- package/dist/commands/status.js +2 -5
- package/dist/commands/task-init.js +2 -3
- package/dist/commands/tutorial.js +2 -2
- package/dist/commands/update.js +1 -1
- package/dist/completions-cache.js +1 -1
- package/dist/console-classifier.js +19 -0
- package/dist/discovery.js +1 -1
- package/dist/doctor.js +3 -4
- package/dist/funding-execution-context.js +15 -1
- package/dist/host.js +32 -0
- package/dist/index.js +1 -6
- package/dist/live-tsx.js +49 -0
- package/dist/mm-harness-cli.js +2 -4
- package/dist/network-observation.js +4 -4
- package/dist/paths.js +48 -78
- package/dist/performance-observation.js +2 -2
- package/dist/progress.js +1 -1
- package/dist/recipe-assistance-context.js +2 -4
- package/dist/recipe-security.js +2 -59
- package/dist/review/knowledge.js +3 -3
- package/dist/runner.js +5 -3
- package/dist/runtime-context.js +1 -1
- package/dist/task-view.js +3 -4
- package/docs/CONTRIBUTING.md +3 -3
- package/docs/cli-contract.md +2 -2
- package/docs/feature-guard.md +90 -0
- package/library/README.md +3 -1
- package/library/actions/core/perps/_controller.mjs +153 -130
- package/library/actions/core/perps/quote_fees.mjs +58 -0
- package/library/actions/core/wallet/_fixture-accounts.mjs +69 -0
- package/library/actions/core/wallet/list_accounts.mjs +14 -57
- package/library/actions/extension/feature_flags/state.mjs +1 -1
- package/library/actions/extension/platform/cdp.mjs +2 -8
- package/library/actions/harness-exports.mjs +1 -6
- package/library/actions/mobile/app/network-control.mjs +6 -8
- package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +1 -1
- package/library/actions/mobile/platform/bridge.mjs +8 -8
- package/library/actions/mobile/platform/native-session-name.mjs +21 -1
- package/library/actions/mobile/platform/native-session.mjs +3 -4
- package/library/actions/mobile/platform/observe-ui.mjs +12 -6
- package/library/actions/mobile/ui/native-navigation.mjs +4 -3
- package/library/actions/mobile/wallet/native-ui.mjs +3 -2
- package/library/actions/shared/analytics/_adapter.mjs +1 -1
- package/library/actions/shared/console-findings.mjs +1 -1
- package/library/actions/terminal/platform/runtime.mjs +2 -1
- package/library/manifests/core.action-manifest.json +83 -13
- package/package.json +8 -8
- package/adapters/extension/lib/slot-title.cjs +0 -292
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +0 -472
- package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +0 -125
- package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +0 -1055
- package/adapters/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -110
- package/adapters/mobile/bridge-runtime/lib/config.cjs +0 -61
- package/adapters/mobile/bridge-runtime/lib/console-format.cjs +0 -53
- package/adapters/mobile/bridge-runtime/lib/devtools-proxy.cjs +0 -177
- package/adapters/mobile/bridge-runtime/lib/issue-capture.cjs +0 -446
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +0 -84
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +0 -403
- package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +0 -116
- package/adapters/mobile/coalesce-metro-log.cjs +0 -24
- package/adapters/mobile/launch-metro.cjs +0 -77
- package/adapters/mobile/metro-config.cjs +0 -98
- package/adapters/mobile/metro-log-generation.cjs +0 -106
- package/dist/adapters/mobile/frame-metrics.js +0 -118
- package/dist/adapters/mobile/video-recorder.js +0 -289
- package/dist/artifact-files.js +0 -92
- package/dist/checkout-lock.js +0 -149
- package/dist/cli-color.js +0 -98
- package/dist/command-journal.js +0 -308
- package/dist/commands/last.js +0 -60
- package/dist/commands/launch/index.js +0 -109
- package/dist/commands/parse-args.js +0 -279
- package/dist/commands/reload.js +0 -41
- package/dist/commands/run-report.js +0 -241
- package/dist/commands/shared.js +0 -323
- package/dist/commands/stop.js +0 -60
- package/dist/devices.js +0 -100
- package/dist/execution-provenance.js +0 -332
- package/dist/harness.js +0 -451
- package/dist/heal-bounds.js +0 -137
- package/dist/json-stream.js +0 -60
- package/dist/leaf-invoke.js +0 -19
- package/dist/library-provenance.js +0 -33
- package/dist/live-adapter-contract.js +0 -380
- package/dist/recording-target.js +0 -120
- package/dist/run-diagnostics.js +0 -364
- package/dist/run-recording.js +0 -481
- package/dist/runtime-proof.js +0 -90
- package/library/actions/mobile/platform/tool-paths.mjs +0 -123
|
@@ -5,9 +5,8 @@ import { readFile, writeFile } from 'node:fs/promises';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
7
|
|
|
8
|
-
import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
|
|
9
|
-
|
|
10
8
|
import { walletFixturePath } from '../../harness-exports.mjs';
|
|
9
|
+
import { readFixtureAccounts } from '../wallet/_fixture-accounts.mjs';
|
|
11
10
|
|
|
12
11
|
function controllerEntry(projectRoot) {
|
|
13
12
|
return pathToFileURL(path.join(projectRoot, 'packages/perps-controller/src/index.ts')).href;
|
|
@@ -27,7 +26,7 @@ function messengerEntry(projectRoot) {
|
|
|
27
26
|
// a persistent auto-reconnecting HyperLiquid WebSocket (HyperLiquidClientService
|
|
28
27
|
// .initialize → wsTransport.ready()); these logs proved the order returns over
|
|
29
28
|
// HTTP while that open socket kept the event loop alive — see disconnectAndExit.
|
|
30
|
-
function buildInfrastructure(stubbed, input) {
|
|
29
|
+
function buildInfrastructure(stubbed, input, rewardsSource) {
|
|
31
30
|
const noop = () => undefined;
|
|
32
31
|
const terminalApi = resolveTerminalApi(input);
|
|
33
32
|
return {
|
|
@@ -69,153 +68,169 @@ function buildInfrastructure(stubbed, input) {
|
|
|
69
68
|
removeItem: async () => undefined,
|
|
70
69
|
},
|
|
71
70
|
...(terminalApi ? { terminalApi } : {}),
|
|
72
|
-
rewards: {
|
|
71
|
+
rewards: {
|
|
72
|
+
getPerpsDiscountForAccount: (caipAccountId, baseFeeBips) =>
|
|
73
|
+
rewardsDiscount(rewardsSource.messenger, caipAccountId, baseFeeBips),
|
|
74
|
+
},
|
|
73
75
|
};
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
// ---
|
|
78
|
+
// --- Rewards ---
|
|
77
79
|
//
|
|
78
|
-
// The
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
80
|
+
// The clients answer the rewards dependency through the messenger's
|
|
81
|
+
// RewardsController:getPerpsDiscountForAccount (extension perps-controller-init,
|
|
82
|
+
// mobile mobileInfrastructure). Core has no RewardsController, so the handler
|
|
83
|
+
// exists when the node supplies rewards_discount_bips, or when a driver
|
|
84
|
+
// registers one on rootMessenger. Without a handler the answer is null: the
|
|
85
|
+
// controller's "no discount known yet".
|
|
86
|
+
//
|
|
87
|
+
// The controller asks for the discount with the account's CAIP id, built from
|
|
88
|
+
// the wallet's selected network (NetworkController:getState and
|
|
89
|
+
// getNetworkClientById). A supplied discount therefore comes with the network a
|
|
90
|
+
// fresh client wallet starts on, Ethereum mainnet; without one the headless
|
|
91
|
+
// controller keeps having no NetworkController, as before.
|
|
84
92
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
* @param projectRoot - Absolute path to the project root.
|
|
90
|
-
* @param accountName - The `name` field to match in fixture.accounts.
|
|
91
|
-
*/
|
|
92
|
-
async function loadFixtureAccount(projectRoot, accountName) {
|
|
93
|
-
const fixturePath = walletFixturePath(projectRoot);
|
|
94
|
-
let raw;
|
|
93
|
+
const REWARDS_DISCOUNT_ACTION = 'RewardsController:getPerpsDiscountForAccount';
|
|
94
|
+
|
|
95
|
+
async function rewardsDiscount(rootMessenger, caipAccountId, baseFeeBips) {
|
|
96
|
+
if (!rootMessenger) return null;
|
|
95
97
|
try {
|
|
96
|
-
|
|
98
|
+
return await rootMessenger.call(REWARDS_DISCOUNT_ACTION, caipAccountId, baseFeeBips);
|
|
97
99
|
} catch (error) {
|
|
98
|
-
if (error?.
|
|
100
|
+
if (String(error?.message ?? '').includes(`${REWARDS_DISCOUNT_ACTION} has not been registered`)) return null;
|
|
99
101
|
throw error;
|
|
100
102
|
}
|
|
101
|
-
const fixture = JSON.parse(raw);
|
|
102
|
-
if (!Array.isArray(fixture.accounts) || fixture.accounts.length === 0) {
|
|
103
|
-
throw new Error(`wallet-fixture.json at ${fixturePath} has no accounts array.`);
|
|
104
|
-
}
|
|
105
|
-
const entry = fixture.accounts.find((a) => a?.name === accountName);
|
|
106
|
-
if (!entry) {
|
|
107
|
-
const names = fixture.accounts.map((a) => a?.name).filter(Boolean).join(', ');
|
|
108
|
-
throw new Error(
|
|
109
|
-
`wallet-fixture.json has no account named "${accountName}". Available: ${names}.`,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
if (typeof entry.value !== 'string' || entry.value.trim().length === 0) {
|
|
113
|
-
throw new Error(`wallet-fixture.json account "${accountName}" has no value.`);
|
|
114
|
-
}
|
|
115
|
-
if (entry.type !== 'mnemonic' && entry.type !== 'privateKey') {
|
|
116
|
-
throw new Error(
|
|
117
|
-
`wallet-fixture.json account "${accountName}" type must be mnemonic or privateKey, got "${entry.type}".`,
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
return entry;
|
|
121
103
|
}
|
|
122
104
|
|
|
123
105
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
106
|
+
* The rewards discount a node supplies, in basis points (0-10000), or undefined.
|
|
107
|
+
*
|
|
108
|
+
* @param input - Adapter input (node.rewards_discount_bips).
|
|
127
109
|
*/
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const normalized = raw.startsWith('0x') ? raw : `0x${raw}`;
|
|
135
|
-
if (!/^0x[0-9a-fA-F]{64}$/u.test(normalized)) {
|
|
136
|
-
throw new Error(
|
|
137
|
-
`wallet-fixture.json privateKey account "${entry.name}" is not a 32-byte hex key.`,
|
|
138
|
-
);
|
|
110
|
+
export function requestedRewardsDiscountBips(input) {
|
|
111
|
+
const raw = optionalParam(input.node, 'rewards_discount_bips', 'rewardsDiscountBips');
|
|
112
|
+
if (raw === undefined) return undefined;
|
|
113
|
+
const bips = Number(raw);
|
|
114
|
+
if (!Number.isInteger(bips) || bips < 0 || bips > 10000) {
|
|
115
|
+
throw new Error(`rewards_discount_bips must be an integer from 0 through 10000; got ${raw}.`);
|
|
139
116
|
}
|
|
140
|
-
return
|
|
117
|
+
return bips;
|
|
141
118
|
}
|
|
142
119
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
120
|
+
const REWARDS_NETWORK_ACTIONS = ['NetworkController:getState', 'NetworkController:getNetworkClientById'];
|
|
121
|
+
|
|
122
|
+
function registerRewardsHandler(rootMessenger, childMessenger, discountBips) {
|
|
123
|
+
if (discountBips === undefined) return;
|
|
124
|
+
rootMessenger.registerActionHandler(REWARDS_DISCOUNT_ACTION, async () => discountBips);
|
|
125
|
+
rootMessenger.registerActionHandler('NetworkController:getState', () => ({
|
|
126
|
+
selectedNetworkClientId: 'mainnet',
|
|
127
|
+
}));
|
|
128
|
+
rootMessenger.registerActionHandler('NetworkController:getNetworkClientById', (networkClientId) => {
|
|
129
|
+
if (networkClientId !== 'mainnet') throw new Error(`No network client with id ${networkClientId}.`);
|
|
130
|
+
return { configuration: { chainId: '0x1' } };
|
|
131
|
+
});
|
|
132
|
+
rootMessenger.delegate({ actions: REWARDS_NETWORK_ACTIONS, messenger: childMessenger });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- Account selection from wallet-fixture.json ---
|
|
136
|
+
//
|
|
137
|
+
// The core adapter reads the same wallet-fixture.json the mobile/extension
|
|
138
|
+
// adapters use (recipeRuntimePath/wallet-fixture.json), expanded the way
|
|
139
|
+
// list_accounts shows it (dev1..devN from a mnemonic with count N). A node
|
|
140
|
+
// selects an account by name (`account_name`, or a non-address `account`) or by
|
|
141
|
+
// 0x address (`account`, `address`, `userAddress`); dev1 when it names none.
|
|
142
|
+
// An unknown name is refused, and an address is never swapped for another
|
|
143
|
+
// account: one outside the fixture is read as given, and cannot sign.
|
|
144
|
+
|
|
145
|
+
const ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/u;
|
|
146
|
+
|
|
147
|
+
function trimmedString(value) {
|
|
148
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
162
149
|
}
|
|
163
150
|
|
|
164
151
|
/**
|
|
165
|
-
* Resolve the
|
|
166
|
-
* The wallet fixture account selected by name is the only signing source.
|
|
152
|
+
* Resolve the account a node selects.
|
|
167
153
|
*
|
|
168
|
-
* @param input - Adapter input (context.projectRoot, node
|
|
169
|
-
* @returns {
|
|
154
|
+
* @param input - Adapter input (context.projectRoot, node account fields).
|
|
155
|
+
* @returns { name, address, signer } — name and signer are null for an address outside the fixture.
|
|
170
156
|
*/
|
|
171
|
-
async function
|
|
157
|
+
export async function selectAccount(input) {
|
|
158
|
+
const node = input.node ?? {};
|
|
172
159
|
const projectRoot = input.context?.projectRoot;
|
|
173
|
-
const
|
|
160
|
+
const accountField = trimmedString(node.account);
|
|
161
|
+
const names = [trimmedString(node.account_name), accountField && !ADDRESS_PATTERN.test(accountField) ? accountField : undefined]
|
|
162
|
+
.filter((value) => value !== undefined);
|
|
163
|
+
const addresses = [node.account, node.address, node.userAddress]
|
|
164
|
+
.map(trimmedString)
|
|
165
|
+
.filter((value) => value !== undefined && ADDRESS_PATTERN.test(value));
|
|
166
|
+
if (new Set(names).size > 1) {
|
|
167
|
+
throw new Error(`core perps account selectors disagree: ${[...new Set(names)].join(' vs ')}; select one account.`);
|
|
168
|
+
}
|
|
169
|
+
if (new Set(addresses.map((value) => value.toLowerCase())).size > 1) {
|
|
170
|
+
throw new Error(`core perps account selectors disagree: ${addresses.join(' vs ')}; select one account.`);
|
|
171
|
+
}
|
|
172
|
+
const [name] = names;
|
|
173
|
+
const [address] = addresses;
|
|
174
|
+
const loaded = projectRoot ? await readFixtureAccounts(projectRoot) : null;
|
|
174
175
|
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
if (!loaded) {
|
|
177
|
+
if (!name && address) return { name: null, address, signer: null };
|
|
178
|
+
throw new Error(
|
|
179
|
+
`core perps account "${name ?? 'dev1'}" needs a wallet fixture at ` +
|
|
180
|
+
`${projectRoot ? walletFixturePath(projectRoot) : 'the checkout'}; ` +
|
|
181
|
+
'a read-only action can name a 0x address via account= instead.\n' +
|
|
182
|
+
'Next: run mm-harness fixtures set',
|
|
183
|
+
);
|
|
181
184
|
}
|
|
182
185
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
const available = loaded.accounts.map((account) => account.name).join(', ');
|
|
187
|
+
const byName = (wanted) => loaded.accounts.find((account) => account.name === wanted);
|
|
188
|
+
if (name) {
|
|
189
|
+
const matches = loaded.accounts.filter((account) => account.name === name);
|
|
190
|
+
if (matches.length > 1) {
|
|
191
|
+
throw new Error(`wallet fixture has ${matches.length} accounts named "${name}"; rename one in ${loaded.fixturePath}.`);
|
|
192
|
+
}
|
|
193
|
+
const [match] = matches;
|
|
194
|
+
if (!match) {
|
|
195
|
+
throw new Error(`wallet fixture has no account named "${name}". Available: ${available}.`);
|
|
196
|
+
}
|
|
197
|
+
if (address && match.address.toLowerCase() !== address.toLowerCase()) {
|
|
198
|
+
throw new Error(`fixture account "${name}" is ${match.address}, not ${address}; select one of them.`);
|
|
199
|
+
}
|
|
200
|
+
return match;
|
|
201
|
+
}
|
|
202
|
+
if (address) {
|
|
203
|
+
return loaded.accounts.find((account) => account.address.toLowerCase() === address.toLowerCase()) ??
|
|
204
|
+
{ name: null, address, signer: null };
|
|
205
|
+
}
|
|
206
|
+
const fallback = byName('dev1');
|
|
207
|
+
if (!fallback) {
|
|
208
|
+
throw new Error(`wallet fixture has no account named "dev1" (the default). Available: ${available}; pass account=<name>.`);
|
|
209
|
+
}
|
|
210
|
+
return fallback;
|
|
187
211
|
}
|
|
188
212
|
|
|
189
213
|
/**
|
|
190
|
-
* Resolve the
|
|
191
|
-
*
|
|
214
|
+
* Resolve the viem signer and EVM address for writes: a fixture account,
|
|
215
|
+
* selected by name or by its address.
|
|
216
|
+
*
|
|
217
|
+
* @param input - Adapter input (context.projectRoot, node account fields).
|
|
218
|
+
* @returns { account: ViemAccount, address: string }
|
|
192
219
|
*/
|
|
193
|
-
async function
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (projectRoot) {
|
|
198
|
-
// No try/catch: loadFixtureAccount returns null when no fixture is present
|
|
199
|
-
// (the only recoverable case) and THROWS on a malformed / missing-named /
|
|
200
|
-
// empty / bad-type fixture. Those must fail loudly — swallowing them would
|
|
201
|
-
// let a read silently run against a different address than the writes use.
|
|
202
|
-
const entry = await loadFixtureAccount(projectRoot, accountName);
|
|
203
|
-
if (entry) {
|
|
204
|
-
const account = viemAccountFromFixtureEntry(entry);
|
|
205
|
-
return account.address;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Read-only calls may name an address directly.
|
|
210
|
-
const fromNode = input.node?.account ?? input.node?.address ?? input.node?.userAddress;
|
|
211
|
-
const address = String(fromNode ?? '').trim();
|
|
212
|
-
if (!/^0x[0-9a-fA-F]{40}$/u.test(address)) {
|
|
220
|
+
async function resolveSignerFromFixture(input) {
|
|
221
|
+
const selected = await selectAccount(input);
|
|
222
|
+
if (!selected.signer) {
|
|
213
223
|
throw new Error(
|
|
214
|
-
`core perps
|
|
215
|
-
|
|
224
|
+
`core perps writes sign with a wallet-fixture.json account; ${selected.address} is not one of them.\n` +
|
|
225
|
+
'Next: pass account=<fixture account name>, or an address listed by metamask.wallet.list_accounts',
|
|
216
226
|
);
|
|
217
227
|
}
|
|
218
|
-
return address;
|
|
228
|
+
return { account: selected.signer, address: selected.address };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** The address a read acts on (no signing required). */
|
|
232
|
+
async function requireAccountAddress(input) {
|
|
233
|
+
return (await selectAccount(input)).address;
|
|
219
234
|
}
|
|
220
235
|
|
|
221
236
|
let cached = null;
|
|
@@ -275,13 +290,15 @@ export async function getCoreController(input) {
|
|
|
275
290
|
const network = resolveNetwork(input);
|
|
276
291
|
const terminalApi = resolveTerminalApi(input);
|
|
277
292
|
const terminalApiKey = JSON.stringify(terminalApi ?? null);
|
|
293
|
+
const rewardsDiscountBips = requestedRewardsDiscountBips(input);
|
|
278
294
|
|
|
279
295
|
if (
|
|
280
296
|
cached &&
|
|
281
297
|
cached.projectRoot === projectRoot &&
|
|
282
298
|
cached.network === network &&
|
|
283
299
|
cached.accountAddress.toLowerCase() === accountAddress.toLowerCase() &&
|
|
284
|
-
cached.terminalApiKey === terminalApiKey
|
|
300
|
+
cached.terminalApiKey === terminalApiKey &&
|
|
301
|
+
cached.rewardsDiscountBips === rewardsDiscountBips
|
|
285
302
|
) {
|
|
286
303
|
return { ...cached, accountAddress };
|
|
287
304
|
}
|
|
@@ -307,7 +324,8 @@ export async function getCoreController(input) {
|
|
|
307
324
|
);
|
|
308
325
|
|
|
309
326
|
const stubbed = new Set();
|
|
310
|
-
const
|
|
327
|
+
const rewardsSource = {};
|
|
328
|
+
const infrastructure = buildInfrastructure(stubbed, input, rewardsSource);
|
|
311
329
|
|
|
312
330
|
// Root + child messenger pair, mirroring the real app wiring (and the core
|
|
313
331
|
// repo's own test harness in
|
|
@@ -323,6 +341,8 @@ export async function getCoreController(input) {
|
|
|
323
341
|
parent: rootMessenger,
|
|
324
342
|
});
|
|
325
343
|
registerSelectedAccountHandler(rootMessenger, messenger, accountAddress);
|
|
344
|
+
registerRewardsHandler(rootMessenger, messenger, rewardsDiscountBips);
|
|
345
|
+
rewardsSource.messenger = rootMessenger;
|
|
326
346
|
|
|
327
347
|
// Default testnet. Mainnet only on explicit node.network: "mainnet" — mainnet
|
|
328
348
|
// reads are safe; mainnet mutations use real funds (gated in getCoreControllerWithSigner).
|
|
@@ -365,6 +385,7 @@ export async function getCoreController(input) {
|
|
|
365
385
|
network,
|
|
366
386
|
accountAddress,
|
|
367
387
|
terminalApiKey,
|
|
388
|
+
rewardsDiscountBips,
|
|
368
389
|
};
|
|
369
390
|
return { ...cached, accountAddress };
|
|
370
391
|
}
|
|
@@ -450,6 +471,9 @@ function registerSignerHandlers(rootMessenger, childMessenger, account) {
|
|
|
450
471
|
'KeyringController:signTypedMessage received no typed data to sign.',
|
|
451
472
|
);
|
|
452
473
|
}
|
|
474
|
+
if (msgParams?.from && String(msgParams.from).toLowerCase() !== account.address.toLowerCase()) {
|
|
475
|
+
throw new Error(`KeyringController:signTypedMessage asked to sign for ${msgParams.from}; the selected account is ${account.address}.`);
|
|
476
|
+
}
|
|
453
477
|
const { domain, types, primaryType, message } = typedData;
|
|
454
478
|
// Strip the EIP712Domain entry if present: viem derives the domain types
|
|
455
479
|
// from `domain` itself and rejects a duplicate EIP712Domain in `types`.
|
|
@@ -486,16 +510,16 @@ function registerSignerHandlers(rootMessenger, childMessenger, account) {
|
|
|
486
510
|
* @returns { controller, projectRoot, network, accountAddress, signerAddress }.
|
|
487
511
|
*/
|
|
488
512
|
export async function getCoreControllerWithSigner(input) {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
/^0x[0-9a-fA-F]{40}$/u.test(requestedName.trim())
|
|
493
|
-
)) {
|
|
513
|
+
// A selection given only as empty values must not quietly sign as dev1.
|
|
514
|
+
const selectors = ['account_name', 'account', 'address', 'userAddress'].filter((key) => input.node?.[key] != null);
|
|
515
|
+
if (selectors.length > 0 && selectors.every((key) => trimmedString(input.node[key]) === undefined)) {
|
|
494
516
|
throw new Error(
|
|
495
|
-
'Core signing requires a non-empty fixture account name
|
|
496
|
-
'Next: pass account=<
|
|
517
|
+
'Core signing requires a non-empty fixture account name or address.\n' +
|
|
518
|
+
'Next: pass account=<fixture account name>, or omit it to sign as dev1.',
|
|
497
519
|
);
|
|
498
520
|
}
|
|
521
|
+
// Refuse an account that cannot sign before any controller I/O.
|
|
522
|
+
const { account, address: signerAddress } = await resolveSignerFromFixture(input);
|
|
499
523
|
const base = await getCoreController(input);
|
|
500
524
|
const { controller, messenger: childMessenger, rootMessenger, accountAddress } = base;
|
|
501
525
|
if (!rootMessenger || !childMessenger) {
|
|
@@ -517,7 +541,6 @@ export async function getCoreControllerWithSigner(input) {
|
|
|
517
541
|
);
|
|
518
542
|
}
|
|
519
543
|
|
|
520
|
-
const { account, address: signerAddress } = await resolveSignerFromFixture(input);
|
|
521
544
|
registerSignerHandlers(rootMessenger, childMessenger, account);
|
|
522
545
|
|
|
523
546
|
// Bring up the active provider. placeOrder/closePosition call
|
|
@@ -741,7 +764,7 @@ export function redactOrder(order) {
|
|
|
741
764
|
};
|
|
742
765
|
}
|
|
743
766
|
|
|
744
|
-
// --- Adapter IO (mirrors the live-adapter contract in
|
|
767
|
+
// --- Adapter IO (mirrors the live-adapter contract in @farmslot/recipe-cli/harness live-adapter-contract) ---
|
|
745
768
|
|
|
746
769
|
async function loadInput() {
|
|
747
770
|
const inputPath = process.argv[2] || process.env.METAMASK_RECIPE_ADAPTER_INPUT;
|
|
@@ -766,7 +789,7 @@ const fmtError = (e) => e?.stack ?? e?.message ?? String(e);
|
|
|
766
789
|
* wsTransport.ready(). Orders themselves go over the HTTP ExchangeClient and
|
|
767
790
|
* RETURN normally, but that open WebSocket keeps the Node event loop alive, so
|
|
768
791
|
* the adapter process never exits. The runner only resolves on the child's
|
|
769
|
-
* `close` event (
|
|
792
|
+
* `close` event (@farmslot/recipe-cli/harness live-adapter-contract runProcess), so it waits out
|
|
770
793
|
* live_adapter_timeout_ms, SIGTERMs the child, and reports `fail` even though
|
|
771
794
|
* the trade filled and the output was computed. READ adapters use the
|
|
772
795
|
* standalone HTTP-only InfoClient (utils/standaloneInfoClient) — no WebSocket —
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Quote an order's fees through the controller's calculateFees: the preview the
|
|
2
|
+
// clients show, with the rewards and subscription sources the fee resolver uses.
|
|
3
|
+
// Read-only: nothing is signed or placed.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
configuredSymbols,
|
|
7
|
+
getCoreController,
|
|
8
|
+
isDirectRun,
|
|
9
|
+
optionalBooleanParam,
|
|
10
|
+
optionalParam,
|
|
11
|
+
requestedRewardsDiscountBips,
|
|
12
|
+
runAdapter,
|
|
13
|
+
} from './_controller.mjs';
|
|
14
|
+
|
|
15
|
+
const ORDER_TYPES = ['market', 'limit'];
|
|
16
|
+
|
|
17
|
+
export async function quoteFees(input) {
|
|
18
|
+
const symbols = configuredSymbols(input, []);
|
|
19
|
+
if (symbols.length !== 1) {
|
|
20
|
+
throw new Error(`metamask.perps.quote_fees requires exactly one market; got ${JSON.stringify(symbols)}.`);
|
|
21
|
+
}
|
|
22
|
+
const orderType = (optionalParam(input.node, 'order_type', 'orderType') ?? 'market').toLowerCase();
|
|
23
|
+
if (!ORDER_TYPES.includes(orderType)) {
|
|
24
|
+
throw new Error(`metamask.perps.quote_fees order_type must be ${ORDER_TYPES.join(' or ')}; got ${orderType}.`);
|
|
25
|
+
}
|
|
26
|
+
const isMaker = optionalBooleanParam(input.node, 'is_maker', 'isMaker');
|
|
27
|
+
const amount = optionalParam(input.node, 'amount');
|
|
28
|
+
if (amount !== undefined && !(Number(amount) > 0)) {
|
|
29
|
+
throw new Error(`metamask.perps.quote_fees amount must be a positive USD notional; got ${amount}.`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { controller, accountAddress, network } = await getCoreController(input);
|
|
33
|
+
// calculateFees routes to the active provider, which init() brings up.
|
|
34
|
+
await controller.init();
|
|
35
|
+
const fees = await controller.calculateFees({
|
|
36
|
+
symbol: symbols[0],
|
|
37
|
+
orderType,
|
|
38
|
+
...(isMaker === undefined ? {} : { isMaker }),
|
|
39
|
+
...(amount === undefined ? {} : { amount }),
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
action: input.action,
|
|
43
|
+
source: 'perps-controller-calculateFees',
|
|
44
|
+
network,
|
|
45
|
+
account: accountAddress,
|
|
46
|
+
market: symbols[0],
|
|
47
|
+
orderType,
|
|
48
|
+
isMaker: isMaker ?? null,
|
|
49
|
+
amount: amount ?? null,
|
|
50
|
+
rewardsDiscountBips: requestedRewardsDiscountBips(input) ?? null,
|
|
51
|
+
// Core has no RewardsController: a discount here is the one the node supplied.
|
|
52
|
+
rewardsSource: requestedRewardsDiscountBips(input) === undefined ? null : 'node.rewards_discount_bips',
|
|
53
|
+
fees,
|
|
54
|
+
proofPath: 'perps-controller-calculateFees',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (isDirectRun(import.meta.url)) runAdapter(quoteFees);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// The checkout's wallet fixture expanded into accounts: a mnemonic entry with
|
|
2
|
+
// count N yields N accounts at address indexes 0..N-1, named dev1..devN from the
|
|
3
|
+
// entry's name. list_accounts shows them; Core Perps actions select from them.
|
|
4
|
+
|
|
5
|
+
import { readFile } from 'node:fs/promises';
|
|
6
|
+
import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
|
|
7
|
+
|
|
8
|
+
import { walletFixturePath } from '../../harness-exports.mjs';
|
|
9
|
+
|
|
10
|
+
function fixtureAccountName(base, index, count) {
|
|
11
|
+
if (count === 1) return base || 'account1';
|
|
12
|
+
const match = /^(.*?)(\d+)$/u.exec(base || 'account1');
|
|
13
|
+
return match
|
|
14
|
+
? `${match[1]}${Number(match[2]) + index}`
|
|
15
|
+
: `${base || 'account'}${index + 1}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function mnemonicCount(entry, fixturePath, index) {
|
|
19
|
+
const count = Number(entry.count ?? entry.numberOfAccounts ?? 1);
|
|
20
|
+
if (!Number.isInteger(count) || count < 1 || count > 100) {
|
|
21
|
+
throw new Error(`${fixturePath} accounts[${index}] mnemonic count must be an integer from 1 through 100.`);
|
|
22
|
+
}
|
|
23
|
+
return count;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Expand fixture entries into `{ name, address, signer }`. `signer` is the viem
|
|
28
|
+
* account holding the key: use it to sign, never serialize it.
|
|
29
|
+
*/
|
|
30
|
+
export function deriveFixtureAccounts(fixture, fixturePath) {
|
|
31
|
+
if (!Array.isArray(fixture.accounts) || fixture.accounts.length === 0) {
|
|
32
|
+
throw new Error(`wallet fixture at ${fixturePath} has no accounts array.`);
|
|
33
|
+
}
|
|
34
|
+
return fixture.accounts.flatMap((entry, fixtureIndex) => {
|
|
35
|
+
if (!entry || typeof entry !== 'object' || typeof entry.value !== 'string' || !entry.value.trim()) {
|
|
36
|
+
throw new Error(`${fixturePath} accounts[${fixtureIndex}] must contain a non-empty value.`);
|
|
37
|
+
}
|
|
38
|
+
if (entry.type === 'mnemonic') {
|
|
39
|
+
const count = mnemonicCount(entry, fixturePath, fixtureIndex);
|
|
40
|
+
return Array.from({ length: count }, (_, addressIndex) => {
|
|
41
|
+
const signer = mnemonicToAccount(entry.value.trim(), { addressIndex });
|
|
42
|
+
return { name: fixtureAccountName(entry.name, addressIndex, count), address: signer.address, signer };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (entry.type === 'privateKey') {
|
|
46
|
+
const raw = entry.value.trim();
|
|
47
|
+
const normalized = raw.startsWith('0x') ? raw : `0x${raw}`;
|
|
48
|
+
if (!/^0x[0-9a-fA-F]{64}$/u.test(normalized)) {
|
|
49
|
+
throw new Error(`${fixturePath} accounts[${fixtureIndex}] privateKey is not a 32-byte hex key.`);
|
|
50
|
+
}
|
|
51
|
+
const signer = privateKeyToAccount(normalized);
|
|
52
|
+
return [{ name: entry.name || `account${fixtureIndex + 1}`, address: signer.address, signer }];
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`${fixturePath} accounts[${fixtureIndex}].type must be mnemonic or privateKey.`);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The checkout's fixture accounts, or null when it has no wallet fixture. */
|
|
59
|
+
export async function readFixtureAccounts(projectRoot) {
|
|
60
|
+
const fixturePath = walletFixturePath(projectRoot);
|
|
61
|
+
let raw;
|
|
62
|
+
try {
|
|
63
|
+
raw = await readFile(fixturePath, 'utf8');
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error?.code === 'ENOENT') return null;
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
return { fixturePath, accounts: deriveFixtureAccounts(JSON.parse(raw), fixturePath) };
|
|
69
|
+
}
|
|
@@ -3,72 +3,29 @@
|
|
|
3
3
|
|
|
4
4
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
|
-
import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
|
|
7
6
|
|
|
8
7
|
import { walletFixturePath } from '../../harness-exports.mjs';
|
|
9
|
-
|
|
10
|
-
function fixtureAccountName(base, index, count) {
|
|
11
|
-
if (count === 1) return base || 'account1';
|
|
12
|
-
const match = /^(.*?)(\d+)$/u.exec(base || 'account1');
|
|
13
|
-
return match
|
|
14
|
-
? `${match[1]}${Number(match[2]) + index}`
|
|
15
|
-
: `${base || 'account'}${index + 1}`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function mnemonicCount(entry, fixturePath, index) {
|
|
19
|
-
const count = Number(entry.count ?? entry.numberOfAccounts ?? 1);
|
|
20
|
-
if (!Number.isInteger(count) || count < 1 || count > 100) {
|
|
21
|
-
throw new Error(`${fixturePath} accounts[${index}] mnemonic count must be an integer from 1 through 100.`);
|
|
22
|
-
}
|
|
23
|
-
return count;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function deriveFixtureAccounts(fixture, fixturePath) {
|
|
27
|
-
if (!Array.isArray(fixture.accounts) || fixture.accounts.length === 0) {
|
|
28
|
-
throw new Error(`wallet fixture at ${fixturePath} has no accounts array.`);
|
|
29
|
-
}
|
|
30
|
-
return fixture.accounts.flatMap((entry, fixtureIndex) => {
|
|
31
|
-
if (!entry || typeof entry !== 'object' || typeof entry.value !== 'string' || !entry.value.trim()) {
|
|
32
|
-
throw new Error(`${fixturePath} accounts[${fixtureIndex}] must contain a non-empty value.`);
|
|
33
|
-
}
|
|
34
|
-
if (entry.type === 'mnemonic') {
|
|
35
|
-
const count = mnemonicCount(entry, fixturePath, fixtureIndex);
|
|
36
|
-
return Array.from({ length: count }, (_, addressIndex) => {
|
|
37
|
-
const account = mnemonicToAccount(entry.value.trim(), { addressIndex });
|
|
38
|
-
return {
|
|
39
|
-
id: `fixture:${fixtureAccountName(entry.name, addressIndex, count)}`,
|
|
40
|
-
address: account.address,
|
|
41
|
-
name: fixtureAccountName(entry.name, addressIndex, count),
|
|
42
|
-
type: 'eip155:eoa',
|
|
43
|
-
};
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
if (entry.type === 'privateKey') {
|
|
47
|
-
const raw = entry.value.trim();
|
|
48
|
-
const normalized = raw.startsWith('0x') ? raw : `0x${raw}`;
|
|
49
|
-
if (!/^0x[0-9a-fA-F]{64}$/u.test(normalized)) {
|
|
50
|
-
throw new Error(`${fixturePath} accounts[${fixtureIndex}] privateKey is not a 32-byte hex key.`);
|
|
51
|
-
}
|
|
52
|
-
const account = privateKeyToAccount(normalized);
|
|
53
|
-
const name = entry.name || `account${fixtureIndex + 1}`;
|
|
54
|
-
return [{ id: `fixture:${name}`, address: account.address, name, type: 'eip155:eoa' }];
|
|
55
|
-
}
|
|
56
|
-
throw new Error(`${fixturePath} accounts[${fixtureIndex}].type must be mnemonic or privateKey.`);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
8
|
+
import { readFixtureAccounts } from './_fixture-accounts.mjs';
|
|
59
9
|
|
|
60
10
|
async function listAccounts(input) {
|
|
61
11
|
const scope = input.node?.scope == null ? 'named' : String(input.node.scope);
|
|
62
12
|
if (scope !== 'selected' && scope !== 'named' && scope !== 'evm' && scope !== 'all') {
|
|
63
13
|
throw new Error('metamask.wallet.list_accounts scope must be selected, named, evm, or all.');
|
|
64
14
|
}
|
|
65
|
-
const
|
|
66
|
-
|
|
15
|
+
const loaded = await readFixtureAccounts(input.context.projectRoot);
|
|
16
|
+
if (!loaded) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`no wallet fixture at ${walletFixturePath(input.context.projectRoot)}.\nNext: run mm-harness fixtures set`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
67
21
|
const selectedName = String(input.node?.account_name ?? input.node?.account ?? 'dev1');
|
|
68
|
-
const accounts =
|
|
69
|
-
.map((
|
|
70
|
-
|
|
71
|
-
|
|
22
|
+
const accounts = loaded.accounts
|
|
23
|
+
.map(({ name, address }) => ({
|
|
24
|
+
id: `fixture:${name}`,
|
|
25
|
+
address,
|
|
26
|
+
name,
|
|
27
|
+
type: 'eip155:eoa',
|
|
28
|
+
selected: name === selectedName,
|
|
72
29
|
}))
|
|
73
30
|
.filter((account) => {
|
|
74
31
|
if (scope === 'selected') return account.selected;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import fs from 'node:fs';
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
|
|
18
|
-
import { recipeRuntimeDir } from '
|
|
18
|
+
import { recipeRuntimeDir } from '@farmslot/recipe-cli/harness';
|
|
19
19
|
|
|
20
20
|
export const EXTENSION_SET_UNSUPPORTED_REASON =
|
|
21
21
|
'The extension exposes no runtime remote feature-flag override RPC: RemoteFeatureFlagController.setFlagOverride is never registered in metamask-controller.js getApi(), so stateHooks.submitRequestToBackground cannot reach it. Overrides are pinned into the per-run dist snapshot manifest at launch.';
|
|
@@ -20,14 +20,8 @@ import { spawn } from 'node:child_process';
|
|
|
20
20
|
import { createRequire } from 'node:module';
|
|
21
21
|
import path from 'node:path';
|
|
22
22
|
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
importRecipeHarnessRuntimeCdp,
|
|
26
|
-
extensionIdPath,
|
|
27
|
-
recipeHarnessPath,
|
|
28
|
-
walletFixturePath,
|
|
29
|
-
captureActiveRecipeRecordingSnapshot,
|
|
30
|
-
} from '../../harness-exports.mjs';
|
|
23
|
+
import { captureActiveRecipeRecordingSnapshot, recipeHarnessPath } from '@farmslot/recipe-cli/harness';
|
|
24
|
+
import { importRecipeHarnessRuntimeBrowserExtension, importRecipeHarnessRuntimeCdp, extensionIdPath, walletFixturePath } from '../../harness-exports.mjs';
|
|
31
25
|
|
|
32
26
|
// Resolve the Farmslot harness through normal package dependencies by default.
|
|
33
27
|
// Local Farmslot source is only a dev override handled by the harness paths module.
|