@deeeed/metamask-harness 0.2.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 +161 -0
- package/README.md +140 -0
- package/bin/mm-harness +99 -0
- package/docs/CHEATSHEET.md +61 -0
- package/docs/CLI-SPEC.md +915 -0
- package/docs/MENTAL-MODEL.md +295 -0
- package/docs/architecture.md +367 -0
- package/docs/extension-runtime-commands.md +60 -0
- package/docs/harness-cli.md +43 -0
- package/docs/live-adapter-contract.md +188 -0
- package/docs/package-boundaries.md +47 -0
- package/docs/perps-flow-catalog.md +235 -0
- package/docs/recipe-libraries.md +95 -0
- package/docs/runtime-file-conventions.md +36 -0
- package/library/actions/core/perps/_controller.mjs +727 -0
- package/library/actions/core/perps/assert_orders.mjs +53 -0
- package/library/actions/core/perps/assert_positions.mjs +52 -0
- package/library/actions/core/perps/close_orders.mjs +97 -0
- package/library/actions/core/perps/close_positions.mjs +118 -0
- package/library/actions/core/perps/ensure_orders.mjs +40 -0
- package/library/actions/core/perps/ensure_positions.mjs +37 -0
- package/library/actions/core/perps/place_order.mjs +201 -0
- package/library/actions/core/perps/read_account.mjs +30 -0
- package/library/actions/core/perps/read_orders.mjs +27 -0
- package/library/actions/core/perps/read_positions.mjs +27 -0
- package/library/actions/core/perps/start_state.mjs +92 -0
- package/library/actions/core/perps/teardown_state.mjs +86 -0
- package/library/actions/extension/perps/assert_orders.mjs +11 -0
- package/library/actions/extension/perps/assert_positions.mjs +11 -0
- package/library/actions/extension/perps/close_orders.mjs +8 -0
- package/library/actions/extension/perps/close_positions.mjs +8 -0
- package/library/actions/extension/perps/ensure_orders.mjs +4 -0
- package/library/actions/extension/perps/ensure_positions.mjs +4 -0
- package/library/actions/extension/perps/perps.mjs +730 -0
- package/library/actions/extension/perps/place_order.mjs +7 -0
- package/library/actions/extension/perps/read_orders.mjs +4 -0
- package/library/actions/extension/perps/read_positions.mjs +3 -0
- package/library/actions/extension/platform/cdp.mjs +541 -0
- package/library/actions/extension/ui/navigate.mjs +44 -0
- package/library/actions/extension/wallet/ensure_unlocked.mjs +36 -0
- package/library/actions/extension/wallet/read_state.mjs +27 -0
- package/library/actions/extension/wallet/select_account.mjs +48 -0
- package/library/actions/extension/wallet/setup.mjs +35 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
- package/library/actions/mobile/bridge-runtime/cdp-bridge.cjs +686 -0
- package/library/actions/mobile/bridge-runtime/lib/cdp-eval.cjs +110 -0
- package/library/actions/mobile/bridge-runtime/lib/config.cjs +39 -0
- package/library/actions/mobile/bridge-runtime/lib/issue-capture.cjs +446 -0
- package/library/actions/mobile/bridge-runtime/lib/target-discovery.cjs +204 -0
- package/library/actions/mobile/bridge-runtime/lib/ws-client.cjs +108 -0
- package/library/actions/mobile/bridge-runtime/setup-wallet.sh +442 -0
- package/library/actions/mobile/perps/assert_orders.mjs +11 -0
- package/library/actions/mobile/perps/assert_positions.mjs +11 -0
- package/library/actions/mobile/perps/close_orders.mjs +8 -0
- package/library/actions/mobile/perps/close_positions.mjs +8 -0
- package/library/actions/mobile/perps/ensure_orders.mjs +4 -0
- package/library/actions/mobile/perps/ensure_positions.mjs +4 -0
- package/library/actions/mobile/perps/perps.mjs +709 -0
- package/library/actions/mobile/perps/place_order.mjs +7 -0
- package/library/actions/mobile/perps/read_orders.mjs +4 -0
- package/library/actions/mobile/perps/read_positions.mjs +3 -0
- package/library/actions/mobile/platform/bridge.mjs +283 -0
- package/library/actions/mobile/ui/navigate.mjs +38 -0
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +107 -0
- package/library/actions/mobile/wallet/home.mjs +35 -0
- package/library/actions/mobile/wallet/read_state.mjs +40 -0
- package/library/actions/mobile/wallet/select_account.mjs +48 -0
- package/library/actions/mobile/wallet/setup.mjs +220 -0
- package/library/flows/perps.flows.json +64 -0
- package/library/library.json +7 -0
- package/library/manifests/core.action-manifest.json +1282 -0
- package/library/manifests/extension.action-manifest.json +1749 -0
- package/library/manifests/mobile.action-manifest.json +1753 -0
- package/library/recipes/action-validation.extension.recipe.json +417 -0
- package/library/recipes/action-validation.mobile.recipe.json +422 -0
- package/library/recipes/order-lifecycle.core.recipe.json +78 -0
- package/library/recipes/perps-lifecycle.recipe.json +194 -0
- package/library/recipes/read-markets.core.recipe.json +38 -0
- package/library/recipes/smoke.extension.recipe.json +31 -0
- package/library/recipes/smoke.mobile.recipe.json +31 -0
- package/library/recipes/trading-lifecycle.core.recipe.json +76 -0
- package/orchestration/compat-overlays/README.md +19 -0
- package/orchestration/compat-overlays/mobile/README.md +13 -0
- package/orchestration/compat-overlays/mobile/rn81-message-event-source.patch +42 -0
- package/orchestration/core/cleanup.sh +37 -0
- package/orchestration/core/inject.sh +154 -0
- package/orchestration/doctor.mjs +72 -0
- package/orchestration/extension/cleanup.mjs +60 -0
- package/orchestration/extension/console-tail.mjs +228 -0
- package/orchestration/extension/ensure-browser.sh +416 -0
- package/orchestration/extension/ensure-ready.ts +185 -0
- package/orchestration/extension/extension-id.ts +107 -0
- package/orchestration/extension/inject.mjs +266 -0
- package/orchestration/extension/launch-browser.cjs +216 -0
- package/orchestration/extension/launch.sh +175 -0
- package/orchestration/extension/live.sh +320 -0
- package/orchestration/extension/pin-remote-flags.cjs +45 -0
- package/orchestration/extension/readiness.mjs +414 -0
- package/orchestration/extension/refresh-build.sh +190 -0
- package/orchestration/extension/runtime-decision.ts +445 -0
- package/orchestration/extension/runtime.ts +407 -0
- package/orchestration/extension/seed-fixture.sh +177 -0
- package/orchestration/extension/sidepanel-toggle.sh +291 -0
- package/orchestration/extension/snapshot-dist.sh +84 -0
- package/orchestration/extension/start-watch.sh +339 -0
- package/orchestration/extension/wallet-fixture-state.cjs +1086 -0
- package/orchestration/lib/activate-repo-node.sh +144 -0
- package/orchestration/lib/cli-color.mjs +84 -0
- package/orchestration/lib/cli-commands.mjs +243 -0
- package/orchestration/lib/cli-home.mjs +354 -0
- package/orchestration/lib/cli-ux.sh +252 -0
- package/orchestration/lib/cli-version.mjs +123 -0
- package/orchestration/lib/ensure-runner-deps.sh +56 -0
- package/orchestration/lib/harness-path.sh +55 -0
- package/orchestration/lib/hash-helpers.sh +44 -0
- package/orchestration/lib/json-field.sh +23 -0
- package/orchestration/lib/log-tui.mjs +304 -0
- package/orchestration/lib/open-debug.mjs +317 -0
- package/orchestration/lib/path-defaults.json +4 -0
- package/orchestration/lib/progress.mjs +107 -0
- package/orchestration/lib/recipe-paths.mjs +26 -0
- package/orchestration/lib/resolve-farmslot-ports.sh +144 -0
- package/orchestration/manifest.json +358 -0
- package/orchestration/mobile/cleanup.sh +192 -0
- package/orchestration/mobile/deps-markers.ts +21 -0
- package/orchestration/mobile/inject.sh +681 -0
- package/orchestration/mobile/launch.sh +137 -0
- package/orchestration/mobile/live.sh +125 -0
- package/orchestration/mobile/runtime-decision.ts +292 -0
- package/orchestration/porcelain/metamask-recipe +99 -0
- package/orchestration/porcelain/mm-recipe +1591 -0
- package/orchestration/porcelain/mme-recipe +1181 -0
- package/package.json +59 -0
- package/runner/extension/verify.sh +511 -0
- package/runner/mobile/verify.sh +501 -0
- package/runner/src/adapters.ts +601 -0
- package/runner/src/cli.ts +1820 -0
- package/runner/src/commands/debug.ts +44 -0
- package/runner/src/commands/fixtures.ts +99 -0
- package/runner/src/commands/launch.ts +397 -0
- package/runner/src/commands/logs.ts +60 -0
- package/runner/src/commands/shared.ts +138 -0
- package/runner/src/completions-cache.ts +86 -0
- package/runner/src/doctor.ts +203 -0
- package/runner/src/harness.ts +516 -0
- package/runner/src/heal-bounds.ts +179 -0
- package/runner/src/index.ts +6 -0
- package/runner/src/live-adapter-contract.ts +274 -0
- package/runner/src/manifest.ts +47 -0
- package/runner/src/mm-harness-cli.ts +488 -0
- package/runner/src/paths.ts +198 -0
- package/runner/src/recording-target.ts +147 -0
- package/runner/src/run-recording.ts +329 -0
- package/runner/src/runner.ts +108 -0
- package/runner/src/types.ts +57 -0
- package/scripts/completions.sh +125 -0
- package/scripts/install-completions.sh +62 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Setup wallet from a JSON fixture via CDP + AgenticService.setupWallet().
|
|
3
|
+
#
|
|
4
|
+
# Expects a FRESH app (no existing vault). For a clean environment, run:
|
|
5
|
+
# yarn a:setup:ios # full clean setup + wallet
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# setup-wallet.sh [--fixture path/to/fixture.json]
|
|
9
|
+
#
|
|
10
|
+
# Fixture JSON format:
|
|
11
|
+
# {
|
|
12
|
+
# "password": "yourpassword",
|
|
13
|
+
# "accounts": [
|
|
14
|
+
# { "type": "mnemonic", "value": "word1 word2 ...", "name": "Primary" },
|
|
15
|
+
# { "type": "privateKey", "value": "0xabc...", "name": "dev1" },
|
|
16
|
+
# { "type": "privateKey", "value": "0xdef...", "name": "dev2" }
|
|
17
|
+
# ],
|
|
18
|
+
# "settings": { "metametrics": true, "skipGtmModals": true, "skipPerpsTutorial": true, "autoLockNever": true, "deviceAuthEnabled": true }
|
|
19
|
+
# }
|
|
20
|
+
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
# Resolve the script directory to an absolute path BEFORE cd, so sourcing and
|
|
24
|
+
# helper paths work regardless of the caller's CWD (e.g. ./setup-wallet.sh).
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
26
|
+
# Run from the target product checkout; the runner invokes this with cwd=projectRoot.
|
|
27
|
+
APP_ROOT="${APP_ROOT:-$PWD}"
|
|
28
|
+
cd "$APP_ROOT"
|
|
29
|
+
|
|
30
|
+
PORT="${WATCHER_PORT:-8081}"
|
|
31
|
+
[[ "$PORT" =~ ^[0-9]+$ ]] || { echo "ERROR: WATCHER_PORT must be numeric (got: $PORT)" >&2; exit 1; }
|
|
32
|
+
SCRIPTS="$SCRIPT_DIR"
|
|
33
|
+
# Account derivation (applyWalletFixture / setupWallet) is far slower than a
|
|
34
|
+
# normal eval — a 10+ account fixture easily exceeds 30s. The value is a cap, not
|
|
35
|
+
# a delay, so a high default is harmless for the fast evals. Override via env.
|
|
36
|
+
export CDP_TIMEOUT="${CDP_TIMEOUT:-120000}"
|
|
37
|
+
export CDP_DISCOVERY_RETRIES="${CDP_DISCOVERY_RETRIES:-3}"
|
|
38
|
+
WALLET_SETUP_SETTLE_TIMEOUT="${WALLET_SETUP_SETTLE_TIMEOUT:-180}"
|
|
39
|
+
CDP="node $SCRIPTS/cdp-bridge.cjs"
|
|
40
|
+
FIXTURE_PATH=""
|
|
41
|
+
|
|
42
|
+
cdp_json_command() {
|
|
43
|
+
local mode="$1"
|
|
44
|
+
local expression="$2"
|
|
45
|
+
local output=""
|
|
46
|
+
if ! output=$($CDP "$mode" "$expression" 2>&1); then
|
|
47
|
+
echo "ERROR: CDP ${mode} failed: $output" >&2
|
|
48
|
+
return 1
|
|
49
|
+
fi
|
|
50
|
+
if ! printf '%s\n' "$output" | jq -r '.'; then
|
|
51
|
+
echo "ERROR: CDP ${mode} returned non-JSON output: $output" >&2
|
|
52
|
+
return 1
|
|
53
|
+
fi
|
|
54
|
+
}
|
|
55
|
+
cdp_eval() { cdp_json_command eval "$1"; }
|
|
56
|
+
cdp_eval_async() { cdp_json_command eval-async "$1"; }
|
|
57
|
+
|
|
58
|
+
# -- Parse args --
|
|
59
|
+
while [[ $# -gt 0 ]]; do
|
|
60
|
+
case "$1" in
|
|
61
|
+
--fixture)
|
|
62
|
+
[ $# -ge 2 ] || { echo "ERROR: --fixture requires a path argument"; exit 2; }
|
|
63
|
+
FIXTURE_PATH="$2"; shift 2 ;;
|
|
64
|
+
-h|--help) echo "Usage: setup-wallet.sh [--fixture path.json]"; exit 0 ;;
|
|
65
|
+
*) echo "Unknown arg: $1"; exit 2 ;;
|
|
66
|
+
esac
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
# -- Resolve + validate fixture --
|
|
70
|
+
[ -z "$FIXTURE_PATH" ] && FIXTURE_PATH="${WALLET_FIXTURE:-${RECIPE_RUNTIME_DIR:-temp/recipe/runtime}/wallet-fixture.json}"
|
|
71
|
+
|
|
72
|
+
if [ ! -f "$FIXTURE_PATH" ]; then
|
|
73
|
+
echo "ERROR: Fixture not found: $FIXTURE_PATH"
|
|
74
|
+
echo " create ${RECIPE_RUNTIME_DIR:-temp/recipe/runtime}/wallet-fixture.json"
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
echo "Reading fixture: $FIXTURE_PATH"
|
|
78
|
+
|
|
79
|
+
jq empty "$FIXTURE_PATH" 2>/dev/null || { echo "ERROR: Invalid JSON"; exit 1; }
|
|
80
|
+
|
|
81
|
+
PASSWORD=$(jq -r '.password // empty' "$FIXTURE_PATH")
|
|
82
|
+
[ -z "$PASSWORD" ] && { echo "ERROR: fixture missing 'password'"; exit 1; }
|
|
83
|
+
|
|
84
|
+
if jq -e '.import' "$FIXTURE_PATH" >/dev/null 2>&1 && ! jq -e '.accounts' "$FIXTURE_PATH" >/dev/null 2>&1; then
|
|
85
|
+
echo "ERROR: Old format. Use an accounts array in the wallet fixture."
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
ACCOUNT_COUNT=$(jq -r '.accounts | length // 0' "$FIXTURE_PATH" 2>/dev/null || echo 0)
|
|
90
|
+
for i in $(seq 0 $((ACCOUNT_COUNT - 1))); do
|
|
91
|
+
ACC_TYPE=$(jq -r ".accounts[$i].type // empty" "$FIXTURE_PATH")
|
|
92
|
+
ACC_VALUE=$(jq -r ".accounts[$i].value // empty" "$FIXTURE_PATH")
|
|
93
|
+
if [ -z "$ACC_TYPE" ] || { [ "$ACC_TYPE" != "mnemonic" ] && [ "$ACC_TYPE" != "privateKey" ]; }; then
|
|
94
|
+
echo "ERROR: accounts[$i].type must be 'mnemonic' or 'privateKey' (got '${ACC_TYPE:-empty}')"
|
|
95
|
+
echo " See the Recipe runner Mobile fixture format."
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
[ -z "$ACC_VALUE" ] && { echo "ERROR: accounts[$i].value is empty"; exit 1; }
|
|
99
|
+
done
|
|
100
|
+
# Expected EVM account total = sum of mnemonic counts + one per private key.
|
|
101
|
+
# A mnemonic entry with count=N materializes N HD accounts, not one, so the
|
|
102
|
+
# entry count alone (ACCOUNT_COUNT) under-counts and would let setup pass while
|
|
103
|
+
# silently missing HD accounts.
|
|
104
|
+
EXPECTED_ETH_TOTAL=$(jq -r '[.accounts[] | if .type == "mnemonic" then (.count // .numberOfAccounts // 1) else 1 end] | add // 0' "$FIXTURE_PATH")
|
|
105
|
+
echo "Fixture OK: password + ${ACCOUNT_COUNT} entry(ies), ${EXPECTED_ETH_TOTAL} expected EVM account(s)"
|
|
106
|
+
|
|
107
|
+
wait_for_cdp_bridge() {
|
|
108
|
+
local waited=0
|
|
109
|
+
local max="${CDP_READY_TIMEOUT:-90}"
|
|
110
|
+
local output=""
|
|
111
|
+
while [ "$waited" -lt "$max" ]; do
|
|
112
|
+
if output=$($CDP eval "JSON.stringify({ok:true})" 2>&1); then
|
|
113
|
+
echo "CDP bridge connected."
|
|
114
|
+
return 0
|
|
115
|
+
fi
|
|
116
|
+
sleep 1
|
|
117
|
+
waited=$((waited + 1))
|
|
118
|
+
[ "$waited" -eq 5 ] && echo "Waiting for CDP bridge..."
|
|
119
|
+
done
|
|
120
|
+
echo "ERROR: CDP not reachable after ${max}s"
|
|
121
|
+
[ -n "$output" ] && echo "$output" >&2
|
|
122
|
+
return 1
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# -- Check CDP --
|
|
126
|
+
wait_for_cdp_bridge
|
|
127
|
+
|
|
128
|
+
wait_for_engine() {
|
|
129
|
+
local waited=0
|
|
130
|
+
local max="${ENGINE_READY_TIMEOUT:-60}"
|
|
131
|
+
local engine_ok=""
|
|
132
|
+
while [ "$waited" -lt "$max" ]; do
|
|
133
|
+
engine_ok=$(cdp_eval "(function(){ var engine = globalThis.Engine; return engine && engine.context && engine.context.KeyringController ? 'ready' : 'waiting'; })()" 2>/dev/null || echo "")
|
|
134
|
+
[ "$engine_ok" = "ready" ] && return 0
|
|
135
|
+
sleep 1
|
|
136
|
+
waited=$((waited + 1))
|
|
137
|
+
[ "$waited" -eq 5 ] && echo "Waiting for Engine to initialize..."
|
|
138
|
+
done
|
|
139
|
+
echo "ERROR: Engine.context.KeyringController not available after ${max}s"
|
|
140
|
+
return 1
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# -- Wait for Engine to be ready (KeyringController must exist) --
|
|
144
|
+
wait_for_engine
|
|
145
|
+
|
|
146
|
+
# The backup subscriber reads the Engine *class* static
|
|
147
|
+
# `disableAutomaticVaultBackup`. AgenticService.setupWallet/applyWalletFixture
|
|
148
|
+
# set that static authoritatively before any account import/rename. The
|
|
149
|
+
# CDP-exposed `Engine` is the facade object, not the class, so we cannot set the
|
|
150
|
+
# static from here — only record harness intent for observability.
|
|
151
|
+
if ! DISABLE_BACKUP=$(cdp_eval "(function(){ globalThis.__AGENTIC_DISABLE_VAULT_BACKUP = true; return JSON.stringify({agenticDisableVaultBackup: globalThis.__AGENTIC_DISABLE_VAULT_BACKUP === true}); })()"); then
|
|
152
|
+
echo "WARN: could not record vault backup guard intent before wallet setup"
|
|
153
|
+
DISABLE_BACKUP='{}'
|
|
154
|
+
fi
|
|
155
|
+
echo "Vault backup guard intent: $DISABLE_BACKUP"
|
|
156
|
+
wait_for_engine
|
|
157
|
+
|
|
158
|
+
# Read fixture JSON and escape it for safe embedding in a JS string literal.
|
|
159
|
+
FIXTURE_JSON=$(jq -c '.' "$FIXTURE_PATH")
|
|
160
|
+
ESCAPED_FIXTURE=$(node -p "JSON.stringify(JSON.stringify(JSON.parse(process.argv[1])))" "$FIXTURE_JSON")
|
|
161
|
+
EXPECTED_ADDRESSES=$(node - "$FIXTURE_PATH" <<'NODE'
|
|
162
|
+
const fs = require('fs');
|
|
163
|
+
const { Wallet } = require('ethers');
|
|
164
|
+
const fixture = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
165
|
+
const addresses = [];
|
|
166
|
+
for (const account of fixture.accounts || []) {
|
|
167
|
+
if (account.type === 'mnemonic') {
|
|
168
|
+
const rawCount =
|
|
169
|
+
account.count != null
|
|
170
|
+
? account.count
|
|
171
|
+
: account.numberOfAccounts != null
|
|
172
|
+
? account.numberOfAccounts
|
|
173
|
+
: 1;
|
|
174
|
+
const count = Number(rawCount);
|
|
175
|
+
for (let i = 0; i < count; i += 1) {
|
|
176
|
+
addresses.push(
|
|
177
|
+
Wallet.fromMnemonic(account.value, `m/44'/60'/0'/0/${i}`).address.toLowerCase(),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
} else if (account.type === 'privateKey') {
|
|
181
|
+
const key = account.value.startsWith('0x') ? account.value : `0x${account.value}`;
|
|
182
|
+
addresses.push(new Wallet(key).address.toLowerCase());
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
console.log(JSON.stringify(addresses));
|
|
186
|
+
NODE
|
|
187
|
+
)
|
|
188
|
+
FIRST_EXPECTED_ADDRESS=$(EXPECTED_JSON="$EXPECTED_ADDRESSES" node <<'NODE'
|
|
189
|
+
const addresses = JSON.parse(process.env.EXPECTED_JSON || '[]');
|
|
190
|
+
process.stdout.write(addresses[0] || '');
|
|
191
|
+
NODE
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
read_wallet_state() {
|
|
195
|
+
cdp_eval "(function(){ try { var engine = globalThis.Engine; var ctx = engine && engine.context ? engine.context : {}; var accountsController = ctx.AccountsController || {}; var keyringController = ctx.KeyringController || {}; var state = accountsController.state || {}; var internalAccounts = state.internalAccounts || {}; var accountsById = internalAccounts.accounts || {}; var accs = Object.values(accountsById); var eth = accs.filter(function(a){ return String(a && a.address || '').indexOf('0x') === 0; }); var route = globalThis.__AGENTIC__ && globalThis.__AGENTIC__.getRoute ? globalThis.__AGENTIC__.getRoute() : null; var selected = null; if (typeof accountsController.getSelectedAccount === 'function') { selected = accountsController.getSelectedAccount(); } else if (internalAccounts.selectedAccount && accountsById[internalAccounts.selectedAccount]) { selected = accountsById[internalAccounts.selectedAccount]; } return JSON.stringify({ok:true, unlocked: typeof keyringController.isUnlocked === 'function' ? keyringController.isUnlocked() : null, routeName: route && route.name, selected: selected ? {name: selected.metadata && selected.metadata.name, address: selected.address} : null, total: accs.length, ethAccounts: eth.length, accounts: eth.map(function(a){ return {name: a && a.metadata && a.metadata.name, address: a && a.address}; }), first3: eth.slice(0,3).map(function(a){ return {name: a && a.metadata && a.metadata.name, address: a && a.address}; })}); } catch (e) { return JSON.stringify({ok:false, error: e && (e.stack || e.message) || String(e)}); } })()"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
missing_fixture_addresses() {
|
|
199
|
+
ACCOUNTS_JSON="$1" EXPECTED_JSON="$EXPECTED_ADDRESSES" node <<'NODE'
|
|
200
|
+
const accounts = JSON.parse(process.env.ACCOUNTS_JSON);
|
|
201
|
+
const expected = JSON.parse(process.env.EXPECTED_JSON);
|
|
202
|
+
const actual = new Set((accounts.accounts || accounts.first3 || []).map((a) => String(a.address || '').toLowerCase()));
|
|
203
|
+
const missing = expected.filter((address) => !actual.has(address));
|
|
204
|
+
console.log(JSON.stringify(missing));
|
|
205
|
+
NODE
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
wallet_state_issue() {
|
|
209
|
+
local accounts_json="$1"
|
|
210
|
+
if [ -z "$accounts_json" ]; then
|
|
211
|
+
echo "wallet state unavailable"
|
|
212
|
+
return
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
local ok unlocked eth_count missing_count
|
|
216
|
+
ok=$(echo "$accounts_json" | jq -r '.ok // false' 2>/dev/null || echo false)
|
|
217
|
+
if [ "$ok" != "true" ]; then
|
|
218
|
+
echo "$(echo "$accounts_json" | jq -r '.error // "wallet state read failed"' 2>/dev/null || echo "wallet state read failed")"
|
|
219
|
+
return
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
unlocked=$(echo "$accounts_json" | jq -r '.unlocked')
|
|
223
|
+
eth_count=$(echo "$accounts_json" | jq -r '.ethAccounts')
|
|
224
|
+
if [ "$unlocked" != "true" ]; then
|
|
225
|
+
echo "vault still locked"
|
|
226
|
+
return
|
|
227
|
+
fi
|
|
228
|
+
if [ "$eth_count" -lt "$EXPECTED_ETH_TOTAL" ]; then
|
|
229
|
+
echo "$eth_count ETH account(s), expected at least $EXPECTED_ETH_TOTAL"
|
|
230
|
+
return
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
missing_count=$(missing_fixture_addresses "$accounts_json" | jq -r 'length')
|
|
234
|
+
if [ "$missing_count" != "0" ]; then
|
|
235
|
+
echo "$missing_count expected fixture address(es) missing"
|
|
236
|
+
return
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
echo "ready"
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
wallet_state_ready() {
|
|
243
|
+
[ "$(wallet_state_issue "$1")" = "ready" ]
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
wallet_fixture_present() {
|
|
247
|
+
local accounts_json="$1"
|
|
248
|
+
local ok eth_count missing_count
|
|
249
|
+
[ -n "$accounts_json" ] || return 1
|
|
250
|
+
ok=$(echo "$accounts_json" | jq -r '.ok // false' 2>/dev/null || echo false)
|
|
251
|
+
[ "$ok" = "true" ] || return 1
|
|
252
|
+
eth_count=$(echo "$accounts_json" | jq -r '.ethAccounts // 0' 2>/dev/null || echo 0)
|
|
253
|
+
[ "$eth_count" -ge "$EXPECTED_ETH_TOTAL" ] || return 1
|
|
254
|
+
missing_count=$(missing_fixture_addresses "$accounts_json" | jq -r 'length')
|
|
255
|
+
[ "$missing_count" = "0" ]
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
PRECHECK_ACCOUNTS=$(read_wallet_state || true)
|
|
259
|
+
if wallet_state_ready "$PRECHECK_ACCOUNTS"; then
|
|
260
|
+
echo "Wallet fixture already valid; skipping wallet mutation."
|
|
261
|
+
else
|
|
262
|
+
|
|
263
|
+
wait_for_wallet_state_after_eval_timeout() {
|
|
264
|
+
local label="$1"
|
|
265
|
+
local rc="$2"
|
|
266
|
+
local elapsed=0
|
|
267
|
+
local interval=5
|
|
268
|
+
local last_accounts=""
|
|
269
|
+
local issue=""
|
|
270
|
+
|
|
271
|
+
echo "WARN: ${label} eval did not return (rc=$rc) before ${CDP_TIMEOUT}ms; validating app wallet state for up to ${WALLET_SETUP_SETTLE_TIMEOUT}s."
|
|
272
|
+
while [ "$elapsed" -le "$WALLET_SETUP_SETTLE_TIMEOUT" ]; do
|
|
273
|
+
last_accounts=$(read_wallet_state 2>/dev/null || true)
|
|
274
|
+
if wallet_state_ready "$last_accounts"; then
|
|
275
|
+
echo "Wallet state validated after ${label} eval timeout."
|
|
276
|
+
return 0
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
issue=$(wallet_state_issue "$last_accounts")
|
|
280
|
+
[ "$elapsed" -eq 0 ] && echo " Still waiting for fixture completion: $issue"
|
|
281
|
+
sleep "$interval"
|
|
282
|
+
elapsed=$((elapsed + interval))
|
|
283
|
+
done
|
|
284
|
+
|
|
285
|
+
echo "ERROR: ${label} eval failed or timed out (rc=$rc), and wallet state did not validate after ${WALLET_SETUP_SETTLE_TIMEOUT}s."
|
|
286
|
+
echo "Last observed state:"
|
|
287
|
+
if [ -n "$last_accounts" ]; then
|
|
288
|
+
echo "$last_accounts" | jq .
|
|
289
|
+
else
|
|
290
|
+
echo " unavailable"
|
|
291
|
+
fi
|
|
292
|
+
return 1
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# -- Check vault state --
|
|
296
|
+
VAULT_STATE=$(cdp_eval "(function(){ var engine = globalThis.Engine; var ctx = engine && engine.context; var keyringController = ctx && ctx.KeyringController; if (!keyringController) return JSON.stringify({ready:false, hasVault:false, isUnlocked:false}); var v = keyringController.state || {}; return JSON.stringify({ready:true, hasVault: v.vault !== undefined && v.vault !== null, isUnlocked: keyringController.isUnlocked()}); })()")
|
|
297
|
+
HAS_VAULT=$(echo "$VAULT_STATE" | jq -r '.hasVault')
|
|
298
|
+
IS_UNLOCKED=$(echo "$VAULT_STATE" | jq -r '.isUnlocked')
|
|
299
|
+
echo "Vault state: hasVault=$HAS_VAULT, isUnlocked=$IS_UNLOCKED"
|
|
300
|
+
|
|
301
|
+
if [ "$HAS_VAULT" = "true" ]; then
|
|
302
|
+
if wallet_fixture_present "$PRECHECK_ACCOUNTS"; then
|
|
303
|
+
if [ "$IS_UNLOCKED" = "true" ]; then
|
|
304
|
+
echo "Wallet fixture already present; vault is unlocked. Skipping wallet mutation."
|
|
305
|
+
else
|
|
306
|
+
echo "Wallet fixture already present; unlocking existing vault without wallet mutation."
|
|
307
|
+
$CDP unlock "$PASSWORD" >/dev/null
|
|
308
|
+
for _ in $(seq 1 30); do
|
|
309
|
+
POST_UNLOCK_ACCOUNTS=$(read_wallet_state 2>/dev/null || true)
|
|
310
|
+
if wallet_state_ready "$POST_UNLOCK_ACCOUNTS"; then
|
|
311
|
+
echo "Existing fixture vault unlocked."
|
|
312
|
+
break
|
|
313
|
+
fi
|
|
314
|
+
sleep 1
|
|
315
|
+
done
|
|
316
|
+
if ! wallet_state_ready "${POST_UNLOCK_ACCOUNTS:-}"; then
|
|
317
|
+
echo "WARN: existing fixture vault did not validate after unlock; falling back to fixture apply."
|
|
318
|
+
fi
|
|
319
|
+
fi
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
if wallet_state_ready "${POST_UNLOCK_ACCOUNTS:-$PRECHECK_ACCOUNTS}"; then
|
|
323
|
+
:
|
|
324
|
+
else
|
|
325
|
+
# Do NOT unlock here with a bare KeyringController.submitPassword(): that
|
|
326
|
+
# bypasses the real auth flow (multichain init + dispatchLogin/password state)
|
|
327
|
+
# and can leave Redux/auth state stale. applyWalletFixture tries the real
|
|
328
|
+
# Authentication.unlockWallet() path first, then recovers interrupted fixture
|
|
329
|
+
# state with a keyring fallback inside AgenticService.
|
|
330
|
+
if [ "$IS_UNLOCKED" = "true" ]; then
|
|
331
|
+
echo "Vault already unlocked."
|
|
332
|
+
else
|
|
333
|
+
echo "Vault locked — applyWalletFixture will unlock via auth flow with fixture recovery fallback."
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
echo "Applying fixture accounts/names to existing vault..."
|
|
337
|
+
set +e
|
|
338
|
+
APPLY_RESULT=$(cdp_eval_async "(function(){ var fixture = JSON.parse($ESCAPED_FIXTURE); if (!globalThis.__AGENTIC__ || typeof globalThis.__AGENTIC__.applyWalletFixture !== 'function') { return JSON.stringify({ok:false, error:'__AGENTIC__.applyWalletFixture is not installed; reload the app from Metro'}); } return globalThis.__AGENTIC__.applyWalletFixture(fixture).then(function(r){ return JSON.stringify(r); }).catch(function(e){ return JSON.stringify({ok:false, error: e.message || String(e)}); }); })()")
|
|
339
|
+
APPLY_RC=$?
|
|
340
|
+
set -e
|
|
341
|
+
if [ "$APPLY_RC" -ne 0 ] || [ -z "$APPLY_RESULT" ]; then
|
|
342
|
+
wait_for_wallet_state_after_eval_timeout "applyWalletFixture" "$APPLY_RC" || exit 1
|
|
343
|
+
echo "Wallet fixture apply result recovered from validated wallet state."
|
|
344
|
+
else
|
|
345
|
+
APPLY_OK=$(echo "$APPLY_RESULT" | jq -r '.ok')
|
|
346
|
+
if [ "$APPLY_OK" != "true" ]; then
|
|
347
|
+
APPLY_ERR=$(echo "$APPLY_RESULT" | jq -r '.error // "unknown error"')
|
|
348
|
+
echo "ERROR: applyWalletFixture failed — $APPLY_ERR"
|
|
349
|
+
exit 1
|
|
350
|
+
fi
|
|
351
|
+
echo "Wallet fixture apply result:"
|
|
352
|
+
echo "$APPLY_RESULT" | jq -r '.accounts[]? | " \(.name): \(.address)"'
|
|
353
|
+
fi
|
|
354
|
+
fi
|
|
355
|
+
else
|
|
356
|
+
# -- Call AgenticService.setupWallet() on fresh app only --
|
|
357
|
+
echo "Calling __AGENTIC__.setupWallet()..."
|
|
358
|
+
|
|
359
|
+
set +e
|
|
360
|
+
SETUP_RESULT=$(cdp_eval_async "(function(){ var fixture = JSON.parse($ESCAPED_FIXTURE); if (!globalThis.__AGENTIC__ || typeof globalThis.__AGENTIC__.setupWallet !== 'function') { return JSON.stringify({ok:false, error:'__AGENTIC__.setupWallet is not installed'}); } return globalThis.__AGENTIC__.setupWallet(fixture).then(function(r){ return JSON.stringify(r); }).catch(function(e){ return JSON.stringify({ok:false, error: e.message || String(e)}); }); })()")
|
|
361
|
+
SETUP_RC=$?
|
|
362
|
+
set -e
|
|
363
|
+
if [ "$SETUP_RC" -ne 0 ] || [ -z "$SETUP_RESULT" ]; then
|
|
364
|
+
wait_for_wallet_state_after_eval_timeout "setupWallet" "$SETUP_RC" || exit 1
|
|
365
|
+
echo "Wallet setup result recovered from validated wallet state."
|
|
366
|
+
else
|
|
367
|
+
SETUP_OK=$(echo "$SETUP_RESULT" | jq -r '.ok')
|
|
368
|
+
if [ "$SETUP_OK" != "true" ]; then
|
|
369
|
+
SETUP_ERR=$(echo "$SETUP_RESULT" | jq -r '.error // "unknown error"')
|
|
370
|
+
SETUP_STEP=$(echo "$SETUP_RESULT" | jq -r '.step // "unknown-step"')
|
|
371
|
+
echo "ERROR: setupWallet failed at ${SETUP_STEP} — $SETUP_ERR"
|
|
372
|
+
exit 1
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
echo "Wallet setup result:"
|
|
376
|
+
echo "$SETUP_RESULT" | jq -r '.accounts[]? | " \(.name): \(.address)"'
|
|
377
|
+
fi
|
|
378
|
+
sleep 2
|
|
379
|
+
fi
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
# -- Ask the app to leave auth/onboarding after unlock.
|
|
383
|
+
# HomeNav matches the product auth reset path, but some warm/onboarding states
|
|
384
|
+
# land on intermediate post-onboarding screens. Follow with WalletView so the
|
|
385
|
+
# harness proves the user-visible unlocked wallet, not just a populated vault.
|
|
386
|
+
$CDP navigate HomeNav >/dev/null 2>&1 || true
|
|
387
|
+
sleep 1
|
|
388
|
+
$CDP navigate WalletView >/dev/null 2>&1 || true
|
|
389
|
+
sleep 1
|
|
390
|
+
if [ -n "$FIRST_EXPECTED_ADDRESS" ]; then
|
|
391
|
+
cdp_eval "(function(){ try { if (globalThis.__AGENTIC__ && typeof globalThis.__AGENTIC__.switchAccount === 'function') { return JSON.stringify(globalThis.__AGENTIC__.switchAccount('$FIRST_EXPECTED_ADDRESS')); } return JSON.stringify({switched:false, error:'__AGENTIC__.switchAccount unavailable'}); } catch (e) { return JSON.stringify({switched:false, error:e && (e.message || String(e))}); } })()" >/dev/null 2>&1 || true
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
# -- Summary + hard validation --
|
|
395
|
+
ACCOUNTS=$(read_wallet_state)
|
|
396
|
+
ACCOUNTS_OK=$(echo "$ACCOUNTS" | jq -r '.ok // false')
|
|
397
|
+
if [ "$ACCOUNTS_OK" != "true" ]; then
|
|
398
|
+
echo "ERROR: Unable to read wallet state after setupWallet"
|
|
399
|
+
echo "$ACCOUNTS" | jq .
|
|
400
|
+
exit 1
|
|
401
|
+
fi
|
|
402
|
+
TOTAL=$(echo "$ACCOUNTS" | jq -r '.total')
|
|
403
|
+
ETH_COUNT=$(echo "$ACCOUNTS" | jq -r '.ethAccounts')
|
|
404
|
+
UNLOCKED=$(echo "$ACCOUNTS" | jq -r '.unlocked')
|
|
405
|
+
ROUTE_NAME=$(echo "$ACCOUNTS" | jq -r '.routeName // empty')
|
|
406
|
+
MISSING_ADDRESSES=$(missing_fixture_addresses "$ACCOUNTS")
|
|
407
|
+
if [ "$UNLOCKED" != "true" ]; then
|
|
408
|
+
echo "ERROR: Wallet setup did not unlock the vault"
|
|
409
|
+
echo "$ACCOUNTS" | jq .
|
|
410
|
+
exit 1
|
|
411
|
+
fi
|
|
412
|
+
if [ "$ETH_COUNT" -lt "$EXPECTED_ETH_TOTAL" ]; then
|
|
413
|
+
echo "ERROR: Wallet setup produced $ETH_COUNT ETH account(s), expected at least $EXPECTED_ETH_TOTAL from fixture"
|
|
414
|
+
echo "$ACCOUNTS" | jq .
|
|
415
|
+
exit 1
|
|
416
|
+
fi
|
|
417
|
+
if [ "$(echo "$MISSING_ADDRESSES" | jq 'length')" != "0" ]; then
|
|
418
|
+
echo "ERROR: Wallet setup did not import/unlock the expected fixture account(s)"
|
|
419
|
+
echo "Missing addresses:"
|
|
420
|
+
echo "$MISSING_ADDRESSES" | jq -r '.[] | " " + .'
|
|
421
|
+
echo "Actual wallet state:"
|
|
422
|
+
echo "$ACCOUNTS" | jq .
|
|
423
|
+
exit 1
|
|
424
|
+
fi
|
|
425
|
+
case "$ROUTE_NAME" in
|
|
426
|
+
Login|Onboarding|ExperienceEnhancer|FoxLoader|"")
|
|
427
|
+
echo "ERROR: Wallet setup did not reach the unlocked wallet UI (route=${ROUTE_NAME:-empty})"
|
|
428
|
+
echo "$ACCOUNTS" | jq .
|
|
429
|
+
exit 1
|
|
430
|
+
;;
|
|
431
|
+
esac
|
|
432
|
+
echo ""
|
|
433
|
+
echo "=== Wallet Ready ==="
|
|
434
|
+
echo "Route: ${ROUTE_NAME:-unknown}"
|
|
435
|
+
echo "Unlocked: $UNLOCKED"
|
|
436
|
+
echo "Accounts: $ETH_COUNT ETH (${TOTAL} total)"
|
|
437
|
+
echo "$ACCOUNTS" | jq -r '.first3[] | " \(.name): \(.address)"'
|
|
438
|
+
echo "Selected:"
|
|
439
|
+
echo "$ACCOUNTS" | jq -r '.selected | " \(.name): \(.address)"'
|
|
440
|
+
echo ""
|
|
441
|
+
echo "Done."
|
|
442
|
+
exit 0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { runAdapter } from '../platform/bridge.mjs';
|
|
2
|
+
import { assertOrders } from './perps.mjs';
|
|
3
|
+
|
|
4
|
+
function expectedOpen(input) {
|
|
5
|
+
const state = String(input.node?.state ?? input.node?.orders ?? 'open').toLowerCase();
|
|
6
|
+
if (state === 'open' || state === 'present') return true;
|
|
7
|
+
if (state === 'none' || state === 'closed' || state === 'absent') return false;
|
|
8
|
+
throw new Error(`metamask.perps.assert_orders received unsupported state: ${state}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
runAdapter((input) => assertOrders(input, expectedOpen(input)));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { runAdapter } from '../platform/bridge.mjs';
|
|
2
|
+
import { assertPositions } from './perps.mjs';
|
|
3
|
+
|
|
4
|
+
function expectedOpen(input) {
|
|
5
|
+
const state = String(input.node?.state ?? input.node?.position ?? 'open').toLowerCase();
|
|
6
|
+
if (state === 'open' || state === 'present') return true;
|
|
7
|
+
if (state === 'none' || state === 'closed' || state === 'absent') return false;
|
|
8
|
+
throw new Error(`metamask.perps.assert_positions received unsupported state: ${state}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
runAdapter((input) => assertPositions(input, expectedOpen(input)));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { runAdapter } from '../platform/bridge.mjs';
|
|
2
|
+
import { assertOrders, closeOrders } from './perps.mjs';
|
|
3
|
+
|
|
4
|
+
runAdapter(async (input) => {
|
|
5
|
+
const close = await closeOrders(input);
|
|
6
|
+
const assertion = await assertOrders(input, false);
|
|
7
|
+
return { ...assertion, close };
|
|
8
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { runAdapter } from '../platform/bridge.mjs';
|
|
2
|
+
import { assertPositions, closePositions } from './perps.mjs';
|
|
3
|
+
|
|
4
|
+
runAdapter(async (input) => {
|
|
5
|
+
const close = await closePositions(input);
|
|
6
|
+
const assertion = await assertPositions(input, false);
|
|
7
|
+
return { ...assertion, close };
|
|
8
|
+
});
|