Skip to content

Commit b14ef5e

Browse files
committed
chore: Add agentic files separate from cherry pick to main
1 parent e632cc6 commit b14ef5e

4 files changed

Lines changed: 368 additions & 1 deletion

File tree

docs/perps/perps-architecture.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ The Perps feature enables perpetual futures trading in MetaMask Mobile. This doc
1616
- **[Sentry Integration](./perps-sentry-reference.md)** - Error tracking and monitoring
1717
- **[MetaMetrics Events](./perps-metametrics-reference.md)** - Analytics events
1818
- **[Protocol Documentation](./hyperliquid/)** - HyperLiquid protocol specifics
19+
- **[Account Modes & Portfolio Margin (ELI5)](./hyperliquid/account-modes-and-portfolio-margin.md)** - HL's three collateral modes and how they map to our USDC-only mobile flows
1920

2021
## Layer Architecture
2122

scripts/perps/agentic/teams/perps/evals.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,10 @@
4848
"description": "Position count + balance snapshot after a trade (compare with pre-trade output)",
4949
"expression": "Promise.all([Engine.context.PerpsController.getPositions(),Engine.context.PerpsController.getAccountState()]).then(function(r){return JSON.stringify({positionCount:r[0].length,positions:r[0].map(function(p){return{symbol:p.symbol,side:p.side,size:p.size,entryPrice:p.entryPrice,unrealizedPnl:p.unrealizedPnl}}),accountState:r[1]})})",
5050
"async": true
51+
},
52+
"hl-fixture-state": {
53+
"description": "HL fixture provisioning snapshot: balances and derived shape flags used by hl-provision-fixture flow and post-op assertions",
54+
"expression": "(function(){var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var avail=parseFloat(s.availableBalance||'0');var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var total=parseFloat(s.totalBalance||'0');var spotFold=+(trade-avail).toFixed(6);return JSON.stringify({availableBalance:avail,availableToTradeBalance:trade,totalBalance:total,spotFold:spotFold,hasPerpsBalance:avail>0.01,hasSpotBalance:spotFold>0.01,isEmpty:total<0.01})})()",
55+
"async": false
5156
}
52-
}
57+
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
{
2+
"title": "HL balance validation — phase={{phaseLabel}} expectedMode={{expectedMode}}",
3+
"description": "Captures and asserts the three balance readouts the TAT-3016 hotfix touches: (1) PerpsController.state.accountState (availableBalance vs availableToTradeBalance), (2) PerpsMarketDetails balance text (perps-market-available-balance-text — gates order-entry Long/Add-Funds CTA), (3) PerpsWithdraw balance text (perps-withdraw-available-balance-text — must show availableBalance only, never the spot fold). Screenshots per screen tagged with phaseLabel for reviewer before/after comparison. Fold invariant (trade >= avail) is asserted unconditionally. expectedMode='unified' additionally asserts the fold amount is non-trivial when spot USDC is present; expectedMode='standard' asserts withdrawable==availableBalance (no leak from fold into the withdrawable path).",
4+
"inputs": {
5+
"expectedMode": {
6+
"type": "string",
7+
"default": "unified",
8+
"description": "HL abstraction mode expected in this phase: 'unified' or 'standard'"
9+
},
10+
"phaseLabel": {
11+
"type": "string",
12+
"default": "phase",
13+
"description": "Short label used in screenshot filenames and trace annotations (e.g. 'initial-unified', 'after-flip-standard', 'restored-unified')"
14+
}
15+
},
16+
"validate": {
17+
"workflow": {
18+
"pre_conditions": [
19+
"wallet.unlocked",
20+
"perps.feature_enabled"
21+
],
22+
"entry": "refresh-state",
23+
"nodes": {
24+
"refresh-state": {
25+
"action": "eval_async",
26+
"expression": "Engine.context.PerpsController.getAccountState().then(function(r){return JSON.stringify({ok:true,availableBalance:r.availableBalance,availableToTradeBalance:r.availableToTradeBalance,totalBalance:r.totalBalance,phase:'{{phaseLabel}}'})}).catch(function(e){return JSON.stringify({ok:false,error:String(e),phase:'{{phaseLabel}}'})})",
27+
"assert": { "operator": "eq", "field": "ok", "value": true },
28+
"timeout_ms": 20000,
29+
"next": "capture-controller-state"
30+
},
31+
"capture-controller-state": {
32+
"action": "eval_ref",
33+
"ref": "perps/hl-fixture-state",
34+
"assert": {
35+
"operator": "not_null",
36+
"field": "availableToTradeBalance"
37+
},
38+
"next": "assert-fold-invariant"
39+
},
40+
"assert-fold-invariant": {
41+
"action": "eval_sync",
42+
"expression": "(function(){var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var avail=parseFloat(s.availableBalance||'0');var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var spotFold=+(trade-avail).toFixed(6);return JSON.stringify({availableBalance:avail,availableToTradeBalance:trade,spotFold:spotFold,foldInvariant:trade>=avail,phase:'{{phaseLabel}}'})})()",
43+
"assert": {
44+
"operator": "eq",
45+
"field": "foldInvariant",
46+
"value": true
47+
},
48+
"next": "nav-market-list"
49+
},
50+
"nav-market-list": {
51+
"action": "navigate",
52+
"target": "PerpsMarketListView",
53+
"next": "wait-market-list"
54+
},
55+
"wait-market-list": {
56+
"action": "wait_for",
57+
"expression": "(function(){try{var r=globalThis.__AGENTIC__&&globalThis.__AGENTIC__.getRoute();return JSON.stringify({route:r?r.name:'unknown'})}catch(e){return JSON.stringify({route:'unknown'})}})()",
58+
"assert": {
59+
"operator": "eq",
60+
"field": "route",
61+
"value": "PerpsMarketListView"
62+
},
63+
"timeout_ms": 15000,
64+
"next": "settle-market-list"
65+
},
66+
"settle-market-list": {
67+
"action": "wait_for",
68+
"expression": "(function(){var a=globalThis.__AGENTIC__;var mounted=!!(a&&a.findFiberByTestId&&a.findFiberByTestId('perps-market-available-balance-text'));return JSON.stringify({mounted:mounted})})()",
69+
"assert": { "operator": "eq", "field": "mounted", "value": true },
70+
"timeout_ms": 15000,
71+
"next": "capture-market-list-balance"
72+
},
73+
"capture-market-list-balance": {
74+
"action": "eval_sync",
75+
"expression": "(function(){try{var a=globalThis.__AGENTIC__;var display=a&&a.getTextByTestId('perps-market-available-balance-text')||'';var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var tradeFloor=Math.floor(trade);var tradeStr=tradeFloor.toString();var mentionsTrade=display.length>0&&(display.indexOf(tradeStr)>=0||display.indexOf('$0')>=0&&trade<1);return JSON.stringify({display:display,availableToTradeBalance:trade,displayMatchesTradeBalance:mentionsTrade,phase:'{{phaseLabel}}'})}catch(e){return JSON.stringify({error:String(e)})}})()",
76+
"assert": {
77+
"operator": "not_null",
78+
"field": "display"
79+
},
80+
"next": "screenshot-market-list"
81+
},
82+
"screenshot-market-list": {
83+
"action": "screenshot",
84+
"filename": "tat3016-{{phaseLabel}}-market-list.png",
85+
"next": "nav-withdraw"
86+
},
87+
"nav-withdraw": {
88+
"action": "navigate",
89+
"target": "PerpsWithdraw",
90+
"next": "wait-withdraw"
91+
},
92+
"wait-withdraw": {
93+
"action": "wait_for",
94+
"expression": "(function(){try{var r=globalThis.__AGENTIC__&&globalThis.__AGENTIC__.getRoute();return JSON.stringify({route:r?r.name:'unknown'})}catch(e){return JSON.stringify({route:'unknown'})}})()",
95+
"assert": {
96+
"operator": "eq",
97+
"field": "route",
98+
"value": "PerpsWithdraw"
99+
},
100+
"timeout_ms": 15000,
101+
"next": "settle-withdraw"
102+
},
103+
"settle-withdraw": {
104+
"action": "wait_for",
105+
"expression": "(function(){var a=globalThis.__AGENTIC__;var mounted=!!(a&&a.findFiberByTestId&&a.findFiberByTestId('perps-withdraw-available-balance-text'));return JSON.stringify({mounted:mounted})})()",
106+
"assert": { "operator": "eq", "field": "mounted", "value": true },
107+
"timeout_ms": 15000,
108+
"next": "capture-withdraw-balance"
109+
},
110+
"capture-withdraw-balance": {
111+
"action": "eval_sync",
112+
"expression": "(function(){try{var a=globalThis.__AGENTIC__;var display=a&&a.getTextByTestId('perps-withdraw-available-balance-text')||'';var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var avail=parseFloat(s.availableBalance||'0');var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var availFloor=Math.floor(avail);var availStr=availFloor.toString();var mentionsAvail=display.length>0&&(display.indexOf(availStr)>=0||(avail<1&&display.indexOf('$0')>=0));var leaksFold=trade>avail+0.5&&display.indexOf(Math.floor(trade).toString())>=0;return JSON.stringify({display:display,availableBalance:avail,availableToTradeBalance:trade,displayMatchesWithdrawable:mentionsAvail,displayLeaksFold:leaksFold,phase:'{{phaseLabel}}'})}catch(e){return JSON.stringify({error:String(e)})}})()",
113+
"assert": {
114+
"all": [
115+
{ "operator": "eq", "field": "displayMatchesWithdrawable", "value": true },
116+
{ "operator": "eq", "field": "displayLeaksFold", "value": false }
117+
]
118+
},
119+
"next": "screenshot-withdraw"
120+
},
121+
"screenshot-withdraw": {
122+
"action": "screenshot",
123+
"filename": "tat3016-{{phaseLabel}}-withdraw.png",
124+
"next": "nav-market-details"
125+
},
126+
"nav-market-details": {
127+
"action": "navigate",
128+
"target": "PerpsMarketDetails",
129+
"params": {
130+
"market": {
131+
"symbol": "BTC",
132+
"name": "BTC",
133+
"price": "0",
134+
"change24h": "0",
135+
"change24hPercent": "0",
136+
"volume": "0",
137+
"maxLeverage": "100"
138+
}
139+
},
140+
"next": "wait-long-button"
141+
},
142+
"wait-long-button": {
143+
"action": "wait_for",
144+
"expression": "(function(){var a=globalThis.__AGENTIC__;var mounted=!!(a&&a.findFiberByTestId&&a.findFiberByTestId('perps-market-details-long-button'));return JSON.stringify({mounted:mounted})})()",
145+
"assert": { "operator": "eq", "field": "mounted", "value": true },
146+
"timeout_ms": 15000,
147+
"next": "press-long"
148+
},
149+
"press-long": {
150+
"action": "press",
151+
"test_id": "perps-market-details-long-button",
152+
"next": "wait-order-form"
153+
},
154+
"wait-order-form": {
155+
"action": "wait_for",
156+
"expression": "(function(){var a=globalThis.__AGENTIC__;var btn=!!(a&&a.findFiberByTestId&&a.findFiberByTestId('perps-order-view-place-order-button'));var payWith=!!(a&&a.findFiberByTestId&&a.findFiberByTestId('pay-with-symbol'));return JSON.stringify({placeOrderMounted:btn,payWithMounted:payWith,ready:btn&&payWith})})()",
157+
"assert": { "operator": "eq", "field": "ready", "value": true },
158+
"timeout_ms": 15000,
159+
"next": "capture-order-form"
160+
},
161+
"capture-order-form": {
162+
"action": "eval_sync",
163+
"expression": "(function(){try{var a=globalThis.__AGENTIC__;var paySymbol=a&&a.getTextByTestId('pay-with-symbol')||'';var placeOrderMounted=!!(a&&a.findFiberByTestId('perps-order-view-place-order-button'));var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var expectsPerpsBalance=trade>0;var paysWithPerps=paySymbol.toLowerCase().indexOf('perps')>=0;return JSON.stringify({paySymbol:paySymbol,paysWithPerpsBalance:paysWithPerps,placeOrderButtonMounted:placeOrderMounted,defaultsCorrectly:expectsPerpsBalance?paysWithPerps:true,phase:'{{phaseLabel}}'})}catch(e){return JSON.stringify({error:String(e)})}})()",
164+
"assert": {
165+
"all": [
166+
{ "operator": "eq", "field": "defaultsCorrectly", "value": true },
167+
{ "operator": "eq", "field": "placeOrderButtonMounted", "value": true }
168+
]
169+
},
170+
"next": "screenshot-order-form"
171+
},
172+
"screenshot-order-form": {
173+
"action": "screenshot",
174+
"filename": "tat3016-{{phaseLabel}}-order-form.png",
175+
"next": "back-to-wallet"
176+
},
177+
"back-to-wallet": {
178+
"action": "navigate",
179+
"target": "Wallet",
180+
"next": "mode-switch"
181+
},
182+
"mode-switch": {
183+
"action": "switch",
184+
"cases": [
185+
{
186+
"label": "expect-unified",
187+
"when": {
188+
"operator": "eq",
189+
"field": "inputs.expectedMode",
190+
"value": "unified"
191+
},
192+
"next": "assert-unified-shape"
193+
},
194+
{
195+
"label": "expect-standard",
196+
"when": {
197+
"operator": "eq",
198+
"field": "inputs.expectedMode",
199+
"value": "standard"
200+
},
201+
"next": "assert-standard-shape"
202+
}
203+
],
204+
"default": "done"
205+
},
206+
"assert-unified-shape": {
207+
"action": "eval_sync",
208+
"expression": "(function(){var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var avail=parseFloat(s.availableBalance||'0');var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var spotFold=+(trade-avail).toFixed(6);return JSON.stringify({availableBalance:avail,availableToTradeBalance:trade,spotFold:spotFold,tradeExceedsAvail:trade>=avail,expectedMode:'unified',phase:'{{phaseLabel}}'})})()",
209+
"assert": {
210+
"operator": "eq",
211+
"field": "tradeExceedsAvail",
212+
"value": true
213+
},
214+
"next": "done"
215+
},
216+
"assert-standard-shape": {
217+
"action": "eval_sync",
218+
"expression": "(function(){var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var avail=parseFloat(s.availableBalance||'0');var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var spotFold=+(trade-avail).toFixed(6);return JSON.stringify({availableBalance:avail,availableToTradeBalance:trade,spotFold:spotFold,foldInvariant:trade>=avail,standardContract:true,expectedMode:'standard',phase:'{{phaseLabel}}'})})()",
219+
"assert": {
220+
"operator": "eq",
221+
"field": "foldInvariant",
222+
"value": true
223+
},
224+
"next": "done"
225+
},
226+
"done": {
227+
"action": "end",
228+
"status": "pass"
229+
}
230+
}
231+
}
232+
}
233+
}

0 commit comments

Comments
 (0)