openbroker 1.9.2 → 1.9.4
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 +21 -0
- package/README.md +16 -0
- package/SKILL.md +69 -3
- package/dist/auto/cli.js +4 -1
- package/dist/auto/examples/dca.d.ts +2 -1
- package/dist/auto/examples/dca.d.ts.map +1 -1
- package/dist/auto/examples/dca.js +19 -1
- package/dist/auto/examples/funding-arb.d.ts +2 -1
- package/dist/auto/examples/funding-arb.d.ts.map +1 -1
- package/dist/auto/examples/funding-arb.js +19 -2
- package/dist/auto/examples/grid.d.ts +2 -1
- package/dist/auto/examples/grid.d.ts.map +1 -1
- package/dist/auto/examples/grid.js +18 -2
- package/dist/auto/examples/mm-maker.d.ts +2 -1
- package/dist/auto/examples/mm-maker.d.ts.map +1 -1
- package/dist/auto/examples/mm-maker.js +18 -2
- package/dist/auto/examples/mm-spread.d.ts +2 -1
- package/dist/auto/examples/mm-spread.d.ts.map +1 -1
- package/dist/auto/examples/mm-spread.js +18 -2
- package/dist/auto/examples/price-alert.d.ts +2 -1
- package/dist/auto/examples/price-alert.d.ts.map +1 -1
- package/dist/auto/examples/price-alert.js +2 -1
- package/dist/auto/guardrails.d.ts +19 -0
- package/dist/auto/guardrails.d.ts.map +1 -0
- package/dist/auto/guardrails.js +575 -0
- package/dist/auto/guardrails.test.d.ts +2 -0
- package/dist/auto/guardrails.test.d.ts.map +1 -0
- package/dist/auto/guardrails.test.js +173 -0
- package/dist/auto/loader.d.ts +3 -3
- package/dist/auto/loader.d.ts.map +1 -1
- package/dist/auto/loader.js +25 -3
- package/dist/auto/realtime.d.ts +45 -0
- package/dist/auto/realtime.d.ts.map +1 -0
- package/dist/auto/realtime.js +177 -0
- package/dist/auto/realtime.test.d.ts +2 -0
- package/dist/auto/realtime.test.d.ts.map +1 -0
- package/dist/auto/realtime.test.js +73 -0
- package/dist/auto/runtime.d.ts +3 -3
- package/dist/auto/runtime.d.ts.map +1 -1
- package/dist/auto/runtime.js +155 -65
- package/dist/auto/types.d.ts +45 -1
- package/dist/auto/types.d.ts.map +1 -1
- package/dist/core/client.d.ts +66 -1
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +141 -4
- package/dist/core/ws.d.ts +14 -2
- package/dist/core/ws.d.ts.map +1 -1
- package/dist/core/ws.js +53 -7
- package/dist/lib.d.ts +2 -0
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -0
- package/package.json +5 -3
- package/scripts/auto/cli.ts +4 -1
- package/scripts/auto/examples/dca.ts +21 -2
- package/scripts/auto/examples/funding-arb.ts +21 -3
- package/scripts/auto/examples/grid.ts +20 -3
- package/scripts/auto/examples/mm-maker.ts +20 -3
- package/scripts/auto/examples/mm-spread.ts +20 -3
- package/scripts/auto/examples/price-alert.ts +4 -2
- package/scripts/auto/guardrails.test.ts +227 -0
- package/scripts/auto/guardrails.ts +700 -0
- package/scripts/auto/loader.ts +41 -4
- package/scripts/auto/realtime.test.ts +84 -0
- package/scripts/auto/realtime.ts +194 -0
- package/scripts/auto/runtime.ts +163 -69
- package/scripts/auto/types.ts +56 -1
- package/scripts/core/client.ts +175 -4
- package/scripts/core/ws.ts +57 -8
- package/scripts/lib.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Open Broker will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.9.3] - 2026-06-23
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Made automation reads WebSocket-first for mids, asset contexts, account state, spot balances, per-dex open orders, and lazily subscribed L2 books, with transparent REST fallback.
|
|
9
|
+
- Activated the existing all-dex context, all-dex clearinghouse, and spot-state subscriptions before strategy `onStart` hooks.
|
|
10
|
+
- Decoupled `api.every()` from REST snapshot polling. Short `--poll` values now control the disconnected fallback cadence; connected runtimes reconcile over REST at most once per minute.
|
|
11
|
+
- Cached and request-de-duplicated the REST-only predicted-funding feed for 60 seconds, retaining the last good value through transient rate limits.
|
|
12
|
+
- Limited automation startup metadata to the native universe and deferred HIP-3 metadata until a prefixed market is actually referenced.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Added realtime-cache tests covering live market/account reads, lazy order-book seeding, and disconnected REST fallback.
|
|
16
|
+
|
|
17
|
+
## [1.9.2] - 2026-06-22
|
|
18
|
+
|
|
19
|
+
### Breaking
|
|
20
|
+
- Every automation must now export a validated `guardrails` policy. Monitoring scripts use `{ mode: 'read-only' }`; trading scripts must declare explicit market, notional, exposure, leverage, margin, open-order, rate, slippage, market-order, and account-wide-cancel limits.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- Added runtime guardrail enforcement around every supported `api.client` write method, including bulk and HIP-4 outcome methods that were previously missing from dry-run and audit interception.
|
|
24
|
+
- Added fail-closed account preflight checks, explicit leverage enforcement for risk-increasing perp orders, rolling order-rate limits, audit records for blocked writes, and unit coverage for schema and runtime behavior.
|
|
25
|
+
|
|
5
26
|
## [1.9.1] - 2026-06-22
|
|
6
27
|
|
|
7
28
|
### Changed
|
package/README.md
CHANGED
|
@@ -764,6 +764,22 @@ openbroker approve-builder --max-fee "0.05%" # Custom max fee
|
|
|
764
764
|
| `--builder` | Custom builder address (advanced) | Open Broker |
|
|
765
765
|
| `--verbose` | Show debug output | — |
|
|
766
766
|
|
|
767
|
+
## Automation Runtime
|
|
768
|
+
|
|
769
|
+
Run audited TypeScript automations with required runtime guardrails:
|
|
770
|
+
|
|
771
|
+
```bash
|
|
772
|
+
openbroker auto run ./my-automation.ts --id my-auto --set coin=HYPE
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
WebSocket mode is enabled by default. Before an automation's `onStart` hooks run, OpenBroker subscribes to live mids, all-dex asset contexts, all-dex account state, spot balances, per-dex open-order snapshots, order updates, fills, and user events. L2 books are subscribed lazily per requested coin.
|
|
776
|
+
|
|
777
|
+
Existing strategies continue to use `api.client`. Inside the automation runtime, `getAllMids()`, `getMetaAndAssetCtxs()`, `getUserState()`, `getUserStateAll()`, `getSpotBalances()`, `getOpenOrders()`, and `getL2Book()` read the live WebSocket cache first and automatically fall back to REST when the socket is unavailable or a feed has not seeded. `getPredictedFundings()` has no socket equivalent, so it is request-de-duplicated, cached for 60 seconds, and serves the last successful value through transient rate limits.
|
|
778
|
+
|
|
779
|
+
`api.every(intervalMs, handler)` uses an independent scheduler and does not trigger a REST snapshot. `--poll` controls the disconnected REST fallback cadence; while WebSocket is healthy, full REST reconciliation runs no more than once per minute even when an older launch command passes a shorter value such as `--poll 5000`. Use `--no-ws` only when you intentionally need REST-only behavior.
|
|
780
|
+
|
|
781
|
+
Every automation must export `guardrails` plus its default factory. See [SKILL.md](./SKILL.md) for the complete schema, WebSocket event model, and runtime enforcement rules.
|
|
782
|
+
|
|
767
783
|
## OpenClaw Plugin
|
|
768
784
|
|
|
769
785
|
OpenBroker ships as an [OpenClaw](https://openclaw.ai) plugin. When installed via OpenClaw, it registers structured agent tools and a background position watcher — no Bash wrappers needed.
|
package/SKILL.md
CHANGED
|
@@ -213,13 +213,61 @@ Run flags:
|
|
|
213
213
|
|---|---|
|
|
214
214
|
| `--set key=value` | Repeatable typed config values |
|
|
215
215
|
| `--id <name>` | Stable automation ID |
|
|
216
|
-
| `--poll <ms>` |
|
|
216
|
+
| `--poll <ms>` | REST fallback interval, minimum 1000 ms. While WebSocket is healthy, REST reconciliation is capped at once per minute. |
|
|
217
217
|
| `--dry` | Intercept write methods |
|
|
218
218
|
| `--no-ws` | Disable WebSocket and rely on REST polling |
|
|
219
219
|
| `--allow-sleep` | Do not request OS sleep inhibition |
|
|
220
220
|
|
|
221
221
|
Bundled examples are **references, not production strategies**. Read them for API patterns, then write a purpose-built script with explicit sizing, exit logic, and failure behavior.
|
|
222
222
|
|
|
223
|
+
### Required guardrail contract
|
|
224
|
+
|
|
225
|
+
Every automation module must export both `guardrails` and a default factory. Validation runs before the factory or any `onStart` hook. A missing, malformed, or internally inconsistent policy prevents startup.
|
|
226
|
+
|
|
227
|
+
Use a read-only policy for monitoring and alerting:
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import type { AutomationAPI, AutomationGuardrails } from 'openbroker';
|
|
231
|
+
|
|
232
|
+
export const guardrails: AutomationGuardrails = { mode: 'read-only' };
|
|
233
|
+
|
|
234
|
+
export default function monitor(api: AutomationAPI) {
|
|
235
|
+
api.on('price_change', ({ coin, changePct }) => api.log.info(`${coin}: ${changePct}%`));
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Read-only mode blocks every client write. Trading policies must declare every field below:
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import type { AutomationAPI, AutomationGuardrails } from 'openbroker';
|
|
243
|
+
|
|
244
|
+
export const guardrails: AutomationGuardrails = {
|
|
245
|
+
mode: 'trading',
|
|
246
|
+
allowedMarkets: ['ETH'], // ETH, xyz:CL, spot:HYPE, or #<outcome encoding>
|
|
247
|
+
maxOrderUsd: 500,
|
|
248
|
+
maxPositionUsd: 1_000,
|
|
249
|
+
maxTotalExposureUsd: 2_500,
|
|
250
|
+
maxLeverage: 2,
|
|
251
|
+
maxMarginUsedPct: 50,
|
|
252
|
+
maxOpenOrders: 10,
|
|
253
|
+
maxOrdersPerMinute: 6,
|
|
254
|
+
maxSlippageBps: 50,
|
|
255
|
+
allowMarketOrders: true,
|
|
256
|
+
allowAccountWideCancel: false,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export default function strategy(api: AutomationAPI) {
|
|
260
|
+
// Risk-increasing perp orders must pass leverage explicitly.
|
|
261
|
+
api.onStart(() => api.client.limitOrder('ETH', true, 0.1, 2_000, 'Gtc', false, 2));
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
When allowed markets depend on `--set` values, export a factory such as `guardrails({ config }) { ... }`; the returned object is still strictly validated. Wildcard markets and unknown policy fields are rejected.
|
|
266
|
+
|
|
267
|
+
All `api.client` write methods cross the runtime policy proxy in live and `--dry` modes. Before risk-increasing orders, the runtime refreshes account positions, spot balances, prices, margin, and open orders; calculates projected per-market and total exposure; enforces leverage, margin, order-count, rate, market-order, and slippage limits; then either submits or throws `GuardrailViolation`. Blocks are logged and written to the audit trail as `guardrail_block`. Cancellations and genuinely risk-reducing orders remain available when exposure or margin is already above its cap, but market allowlists and explicit account-wide-cancel policy still apply. Administrative writes such as `approveBuilderFee` are always blocked inside automations.
|
|
268
|
+
|
|
269
|
+
Treat `api.client` as the only supported execution path. Automation files are trusted TypeScript running in-process, not an OS sandbox; direct exchange SDK imports would bypass the runtime boundary and must not be generated or accepted during review.
|
|
270
|
+
|
|
223
271
|
### Automation API essentials
|
|
224
272
|
|
|
225
273
|
- `api.client` — full Hyperliquid client.
|
|
@@ -227,9 +275,27 @@ Bundled examples are **references, not production strategies**. Read them for AP
|
|
|
227
275
|
- `api.state` — persisted state; survives restarts.
|
|
228
276
|
- `api.audit.record(...)` / `api.audit.metric(...)` — durable observability.
|
|
229
277
|
- `api.dryRun` — whether writes are intercepted.
|
|
278
|
+
- `api.guardrails` — validated policy currently enforced by the runtime.
|
|
230
279
|
|
|
231
280
|
Core events include `tick`, `price_change`, `funding_update`, `position_opened`, `position_closed`, `position_changed`, `pnl_threshold`, `margin_warning`, `order_filled`, `order_update`, and `liquidation`.
|
|
232
281
|
|
|
282
|
+
### WebSocket-first runtime
|
|
283
|
+
|
|
284
|
+
WebSocket mode is enabled by default. Before `onStart`, the runtime subscribes to:
|
|
285
|
+
|
|
286
|
+
- `allMids` for instant perp and spot prices;
|
|
287
|
+
- `allDexsAssetCtxs` for funding, mark, oracle, open-interest, and premium changes;
|
|
288
|
+
- `allDexsClearinghouseState` plus `spotState` for positions, margin, collateral, and balances;
|
|
289
|
+
- `openOrders` for the native dex and every active HIP-3 dex so guardrail order-count checks stay live;
|
|
290
|
+
- `orderUpdates`, `userFills`, and `userEvents` for order lifecycle, fills, funding payments, and liquidations;
|
|
291
|
+
- `l2Book` lazily, the first time an automation requests a book for a coin.
|
|
292
|
+
|
|
293
|
+
The normal `api.client` read methods are WebSocket-aware inside automations. `getAllMids()`, `getMetaAndAssetCtxs()`, `getUserState()`, `getUserStateAll()`, `getSpotBalances()`, `getOpenOrders()`, and `getL2Book()` return fresh socket data when available and transparently fall back to REST when the socket is disconnected, a subscription has not seeded yet, or live market data is stale. Automation code should keep using `api.client`; do not import a second exchange SDK or create a second socket.
|
|
294
|
+
|
|
295
|
+
`api.every(intervalMs, handler)` runs on an independent scheduler and no longer causes a REST snapshot. `--poll` controls the disconnected fallback cadence. While the socket is healthy, the runtime performs a REST reconciliation no more than once per minute, even when an older launch command passes `--poll 5000`. Predicted cross-venue funding has no equivalent socket feed, so `getPredictedFundings()` is de-duplicated, cached for 60 seconds, and serves the last good value if a refresh is temporarily rate-limited.
|
|
296
|
+
|
|
297
|
+
Use `--no-ws` only for debugging or networks that cannot maintain WebSockets. In that mode, `--poll` is the active REST cadence and event latency follows it.
|
|
298
|
+
|
|
233
299
|
### Monitoring and dashboard
|
|
234
300
|
|
|
235
301
|
`openbroker-monitoring` is optional but useful for long-running automations, live debugging, and post-run inspection.
|
|
@@ -251,7 +317,7 @@ These matter more than boilerplate:
|
|
|
251
317
|
|
|
252
318
|
1. **Model the strategy as a state machine.** Persist flags, streaks, targets, and recovery state with `api.state`; handlers can fire repeatedly and processes can restart.
|
|
253
319
|
2. **Use hysteresis, not one-print decisions.** Confirmation loops, separate enter/exit thresholds, and debounce logic prevent churn from noisy funding or tiny price moves.
|
|
254
|
-
3. **Use the freshest correct signal.**
|
|
320
|
+
3. **Use the freshest correct signal.** Instantaneous funding and prices are WebSocket-backed by default. Prefer `getPredictedFundings()` for cross-venue forecasts; the runtime refreshes that REST-only signal at most once per minute and retains the last good value through transient rate limits.
|
|
255
321
|
4. **Price the flip, not just the signal.** Before closing and later reopening a carry, compare expected hold cost with round-trip trading cost. The HYPE carry automation counts maker fees across both legs plus builder fees before deciding whether a mildly negative funding window is worth exiting.
|
|
256
322
|
5. **Respect settlement timing.** If the current predicted funding is still positive, a close right before hourly settlement can be economically wrong even when the broader signal weakened. Add a settlement-proximity guard when the strategy depends on funding capture.
|
|
257
323
|
6. **Sequence multi-leg hedges deliberately.** For spot-long / perp-short carry, build spot first, then short only up to spot-backed exposure; unwind spot first, then close the short reduce-only. Recover accidental one-sided exposure explicitly instead of pretending it cannot happen.
|
|
@@ -263,7 +329,7 @@ These matter more than boilerplate:
|
|
|
263
329
|
Additional practical caveats:
|
|
264
330
|
|
|
265
331
|
- Positive-funding carry and negative-funding carry are not automatically symmetric. If the hedge requires short spot and the client/runtime cannot express that safely, do not invent an unhedged mirror trade.
|
|
266
|
-
- `funding_update`
|
|
332
|
+
- `funding_update` is emitted from changed WebSocket asset contexts and may cover many assets; filter by coin early.
|
|
267
333
|
- Dust matters: if residual size falls below exchange precision or `minTradeUsd`, stop chasing it.
|
|
268
334
|
- `ALO` / post-only orders can be rejected when they would cross; treat that as an execution branch, not a surprise.
|
|
269
335
|
- Naked directional positions usually need explicit TP/SL or equivalent risk logic. Hedged multi-leg strategies need strategy-specific exits instead of cargo-cult TP/SL rules.
|
package/dist/auto/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ Options (for run):
|
|
|
29
29
|
--dry Intercept write methods (no real trades)
|
|
30
30
|
--verbose Show debug output
|
|
31
31
|
--id <name> Custom automation ID (default: filename)
|
|
32
|
-
--poll <ms>
|
|
32
|
+
--poll <ms> REST fallback interval (default: 30000 with WS, 10000 with --no-ws)
|
|
33
33
|
--no-ws Disable WebSocket; fall back to REST-only polling
|
|
34
34
|
--allow-sleep Do not request OS idle-sleep inhibition for this run
|
|
35
35
|
|
|
@@ -47,6 +47,8 @@ Scripts are loaded from:
|
|
|
47
47
|
3. Bundled examples (via --example)
|
|
48
48
|
|
|
49
49
|
Writing an automation:
|
|
50
|
+
export const guardrails = { mode: 'read-only' };
|
|
51
|
+
|
|
50
52
|
export default function(api) {
|
|
51
53
|
api.on('price_change', async ({ coin, changePct }) => {
|
|
52
54
|
api.log.info(\`\${coin} moved \${changePct.toFixed(2)}%\`);
|
|
@@ -187,6 +189,7 @@ function listCommand() {
|
|
|
187
189
|
if (automations.length === 0 && examples.length === 0) {
|
|
188
190
|
console.log('No automations found in ~/.openbroker/automations/');
|
|
189
191
|
console.log('\nCreate a .ts file there with:');
|
|
192
|
+
console.log(" export const guardrails = { mode: 'read-only' };");
|
|
190
193
|
console.log(' export default function(api) { ... }');
|
|
191
194
|
console.log('\nOr run a bundled example: openbroker auto examples');
|
|
192
195
|
return;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AutomationAPI, AutomationConfig } from '../types.js';
|
|
1
|
+
import type { AutomationAPI, AutomationConfig, AutomationGuardrailContext, AutomationGuardrails } from '../types.js';
|
|
2
2
|
export declare const config: AutomationConfig;
|
|
3
|
+
export declare function guardrails({ config: values }: AutomationGuardrailContext): AutomationGuardrails;
|
|
3
4
|
export default function dca(api: AutomationAPI): void;
|
|
4
5
|
//# sourceMappingURL=dca.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dca.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/dca.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"dca.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/dca.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAErH,eAAO,MAAM,MAAM,EAAE,gBAQpB,CAAC;AAEF,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,0BAA0B,GAAG,oBAAoB,CAiB/F;AAED,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,GAAG,EAAE,aAAa,QAyD7C"}
|
|
@@ -8,6 +8,24 @@ export const config = {
|
|
|
8
8
|
count: { type: 'number', description: 'Total number of purchases', default: 24 },
|
|
9
9
|
},
|
|
10
10
|
};
|
|
11
|
+
export function guardrails({ config: values }) {
|
|
12
|
+
const amount = Number(values.amount ?? 100);
|
|
13
|
+
const count = Number(values.count ?? 24);
|
|
14
|
+
return {
|
|
15
|
+
mode: 'trading',
|
|
16
|
+
allowedMarkets: [String(values.coin ?? 'HYPE')],
|
|
17
|
+
maxOrderUsd: amount * 1.1,
|
|
18
|
+
maxPositionUsd: amount * count * 1.1,
|
|
19
|
+
maxTotalExposureUsd: amount * count * 1.1,
|
|
20
|
+
maxLeverage: 1,
|
|
21
|
+
maxMarginUsedPct: 50,
|
|
22
|
+
maxOpenOrders: 5,
|
|
23
|
+
maxOrdersPerMinute: 5,
|
|
24
|
+
maxSlippageBps: 50,
|
|
25
|
+
allowMarketOrders: true,
|
|
26
|
+
allowAccountWideCancel: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
11
29
|
export default function dca(api) {
|
|
12
30
|
const COIN = api.state.get('coin', 'HYPE');
|
|
13
31
|
const AMOUNT_USD = api.state.get('amount', 100);
|
|
@@ -33,7 +51,7 @@ export default function dca(api) {
|
|
|
33
51
|
}
|
|
34
52
|
const size = AMOUNT_USD / price;
|
|
35
53
|
api.log.info(`[${purchased + 1}/${MAX_PURCHASES}] Buying ~$${AMOUNT_USD} of ${COIN} @ $${price.toFixed(2)}`);
|
|
36
|
-
const response = await api.client.marketOrder(COIN, true, size);
|
|
54
|
+
const response = await api.client.marketOrder(COIN, true, size, undefined, 1);
|
|
37
55
|
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
38
56
|
const status = response.response.data.statuses[0];
|
|
39
57
|
if (status?.filled) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AutomationAPI, AutomationConfig } from '../types.js';
|
|
1
|
+
import type { AutomationAPI, AutomationConfig, AutomationGuardrailContext, AutomationGuardrails } from '../types.js';
|
|
2
2
|
export declare const config: AutomationConfig;
|
|
3
|
+
export declare function guardrails({ config: values }: AutomationGuardrailContext): AutomationGuardrails;
|
|
3
4
|
export default function fundingArb(api: AutomationAPI): void;
|
|
4
5
|
//# sourceMappingURL=funding-arb.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"funding-arb.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/funding-arb.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"funding-arb.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/funding-arb.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAErH,eAAO,MAAM,MAAM,EAAE,gBASpB,CAAC;AAEF,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,0BAA0B,GAAG,oBAAoB,CAgB/F;AAED,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,GAAG,EAAE,aAAa,QAkFpD"}
|
|
@@ -9,6 +9,23 @@ export const config = {
|
|
|
9
9
|
closeAt: { type: 'number', description: 'Close when funding drops below this %', default: 5 },
|
|
10
10
|
},
|
|
11
11
|
};
|
|
12
|
+
export function guardrails({ config: values }) {
|
|
13
|
+
const sizeUsd = Number(values.sizeUsd ?? 5000);
|
|
14
|
+
return {
|
|
15
|
+
mode: 'trading',
|
|
16
|
+
allowedMarkets: [String(values.coin ?? 'HYPE')],
|
|
17
|
+
maxOrderUsd: sizeUsd * 1.1,
|
|
18
|
+
maxPositionUsd: sizeUsd * 1.1,
|
|
19
|
+
maxTotalExposureUsd: sizeUsd * 1.1,
|
|
20
|
+
maxLeverage: 1,
|
|
21
|
+
maxMarginUsedPct: 50,
|
|
22
|
+
maxOpenOrders: 5,
|
|
23
|
+
maxOrdersPerMinute: 4,
|
|
24
|
+
maxSlippageBps: 50,
|
|
25
|
+
allowMarketOrders: true,
|
|
26
|
+
allowAccountWideCancel: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
12
29
|
export default function fundingArb(api) {
|
|
13
30
|
const COIN = api.state.get('coin', 'HYPE');
|
|
14
31
|
const SIZE_USD = api.state.get('sizeUsd', 5000);
|
|
@@ -38,7 +55,7 @@ export default function fundingArb(api) {
|
|
|
38
55
|
if (shouldClose) {
|
|
39
56
|
api.log.info(`Funding dropped to ${annualizedPct.toFixed(2)}% (below ${CLOSE_AT}%), closing ${positionSide}`);
|
|
40
57
|
const closeIsBuy = positionSide === 'short';
|
|
41
|
-
await api.client.marketOrder(coin, closeIsBuy, positionSize);
|
|
58
|
+
await api.client.marketOrder(coin, closeIsBuy, positionSize, undefined, 1);
|
|
42
59
|
inPosition = false;
|
|
43
60
|
api.state.set('inPosition', false);
|
|
44
61
|
api.log.info(`Position closed. Funding collected: ~$${totalFunding.toFixed(2)}`);
|
|
@@ -56,7 +73,7 @@ export default function fundingArb(api) {
|
|
|
56
73
|
const price = parseFloat(mids[coin]);
|
|
57
74
|
const size = SIZE_USD / price;
|
|
58
75
|
api.log.info(`Funding at ${annualizedPct.toFixed(2)}% — opening ${side} ${size.toFixed(6)} ${coin}`);
|
|
59
|
-
const response = await api.client.marketOrder(coin, !shouldShort, size);
|
|
76
|
+
const response = await api.client.marketOrder(coin, !shouldShort, size, undefined, 1);
|
|
60
77
|
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
61
78
|
const status = response.response.data.statuses[0];
|
|
62
79
|
if (status?.filled) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AutomationAPI, AutomationConfig } from '../types.js';
|
|
1
|
+
import type { AutomationAPI, AutomationConfig, AutomationGuardrailContext, AutomationGuardrails } from '../types.js';
|
|
2
2
|
export declare const config: AutomationConfig;
|
|
3
|
+
export declare function guardrails({ config: values }: AutomationGuardrailContext): AutomationGuardrails;
|
|
3
4
|
export default function grid(api: AutomationAPI): void;
|
|
4
5
|
//# sourceMappingURL=grid.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/grid.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/grid.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAErH,eAAO,MAAM,MAAM,EAAE,gBAUpB,CAAC;AAEF,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,0BAA0B,GAAG,oBAAoB,CAe/F;AASD,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,GAAG,EAAE,aAAa,QA+G9C"}
|
|
@@ -10,6 +10,22 @@ export const config = {
|
|
|
10
10
|
mode: { type: 'string', description: 'Grid mode: neutral, long, or short', default: 'neutral' },
|
|
11
11
|
},
|
|
12
12
|
};
|
|
13
|
+
export function guardrails({ config: values }) {
|
|
14
|
+
return {
|
|
15
|
+
mode: 'trading',
|
|
16
|
+
allowedMarkets: [String(values.coin ?? 'HYPE')],
|
|
17
|
+
maxOrderUsd: 10_000,
|
|
18
|
+
maxPositionUsd: 25_000,
|
|
19
|
+
maxTotalExposureUsd: 25_000,
|
|
20
|
+
maxLeverage: 1,
|
|
21
|
+
maxMarginUsedPct: 50,
|
|
22
|
+
maxOpenOrders: 25,
|
|
23
|
+
maxOrdersPerMinute: 25,
|
|
24
|
+
maxSlippageBps: 50,
|
|
25
|
+
allowMarketOrders: false,
|
|
26
|
+
allowAccountWideCancel: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
13
29
|
export default function grid(api) {
|
|
14
30
|
const COIN = api.state.get('coin', 'HYPE');
|
|
15
31
|
const GRIDS = api.state.get('grids', 10);
|
|
@@ -43,7 +59,7 @@ export default function grid(api) {
|
|
|
43
59
|
if (Math.abs(price - mid) / mid < 0.001)
|
|
44
60
|
continue;
|
|
45
61
|
const level = { price, side, size: SIZE };
|
|
46
|
-
const response = await api.client.limitOrder(COIN, side === 'buy', SIZE, price, 'Gtc', false);
|
|
62
|
+
const response = await api.client.limitOrder(COIN, side === 'buy', SIZE, price, 'Gtc', false, 1);
|
|
47
63
|
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
48
64
|
const status = response.response.data.statuses[0];
|
|
49
65
|
if (status?.resting) {
|
|
@@ -84,7 +100,7 @@ export default function grid(api) {
|
|
|
84
100
|
const oppositePrice = level.side === 'buy' ? level.price + spacing : level.price - spacing;
|
|
85
101
|
if (oppositePrice < lower || oppositePrice > upper)
|
|
86
102
|
continue;
|
|
87
|
-
const response = await api.client.limitOrder(COIN, oppositeSide === 'buy', SIZE, oppositePrice, 'Gtc', false);
|
|
103
|
+
const response = await api.client.limitOrder(COIN, oppositeSide === 'buy', SIZE, oppositePrice, 'Gtc', false, 1);
|
|
88
104
|
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
89
105
|
const status = response.response.data.statuses[0];
|
|
90
106
|
if (status?.resting) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AutomationAPI, AutomationConfig } from '../types.js';
|
|
1
|
+
import type { AutomationAPI, AutomationConfig, AutomationGuardrailContext, AutomationGuardrails } from '../types.js';
|
|
2
2
|
export declare const config: AutomationConfig;
|
|
3
|
+
export declare function guardrails({ config: values }: AutomationGuardrailContext): AutomationGuardrails;
|
|
3
4
|
export default function mmMaker(api: AutomationAPI): void;
|
|
4
5
|
//# sourceMappingURL=mm-maker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mm-maker.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/mm-maker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"mm-maker.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/mm-maker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAErH,eAAO,MAAM,MAAM,EAAE,gBASpB,CAAC;AAEF,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,0BAA0B,GAAG,oBAAoB,CAe/F;AAED,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,GAAG,EAAE,aAAa,QA6GjD"}
|
|
@@ -9,6 +9,22 @@ export const config = {
|
|
|
9
9
|
skewFactor: { type: 'number', description: 'Inventory skew aggressiveness', default: 2.0 },
|
|
10
10
|
},
|
|
11
11
|
};
|
|
12
|
+
export function guardrails({ config: values }) {
|
|
13
|
+
return {
|
|
14
|
+
mode: 'trading',
|
|
15
|
+
allowedMarkets: [String(values.coin ?? 'HYPE')],
|
|
16
|
+
maxOrderUsd: 10_000,
|
|
17
|
+
maxPositionUsd: 25_000,
|
|
18
|
+
maxTotalExposureUsd: 25_000,
|
|
19
|
+
maxLeverage: 1,
|
|
20
|
+
maxMarginUsedPct: 50,
|
|
21
|
+
maxOpenOrders: 10,
|
|
22
|
+
maxOrdersPerMinute: 60,
|
|
23
|
+
maxSlippageBps: 25,
|
|
24
|
+
allowMarketOrders: false,
|
|
25
|
+
allowAccountWideCancel: false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
12
28
|
export default function mmMaker(api) {
|
|
13
29
|
const COIN = api.state.get('coin', 'HYPE');
|
|
14
30
|
const SIZE = api.state.get('size', 0.1);
|
|
@@ -85,7 +101,7 @@ export default function mmMaker(api) {
|
|
|
85
101
|
}
|
|
86
102
|
// Place ALO bid
|
|
87
103
|
if (shouldBid && !bidOid && safeBid < book.bestAsk) {
|
|
88
|
-
const resp = await api.client.limitOrder(COIN, true, SIZE, safeBid, 'Alo', false);
|
|
104
|
+
const resp = await api.client.limitOrder(COIN, true, SIZE, safeBid, 'Alo', false, 1);
|
|
89
105
|
if (resp.status === 'ok' && resp.response && typeof resp.response === 'object') {
|
|
90
106
|
const s = resp.response.data.statuses[0];
|
|
91
107
|
if (s?.resting) {
|
|
@@ -99,7 +115,7 @@ export default function mmMaker(api) {
|
|
|
99
115
|
}
|
|
100
116
|
// Place ALO ask
|
|
101
117
|
if (shouldAsk && !askOid && safeAsk > book.bestBid) {
|
|
102
|
-
const resp = await api.client.limitOrder(COIN, false, SIZE, safeAsk, 'Alo', false);
|
|
118
|
+
const resp = await api.client.limitOrder(COIN, false, SIZE, safeAsk, 'Alo', false, 1);
|
|
103
119
|
if (resp.status === 'ok' && resp.response && typeof resp.response === 'object') {
|
|
104
120
|
const s = resp.response.data.statuses[0];
|
|
105
121
|
if (s?.resting) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AutomationAPI, AutomationConfig } from '../types.js';
|
|
1
|
+
import type { AutomationAPI, AutomationConfig, AutomationGuardrailContext, AutomationGuardrails } from '../types.js';
|
|
2
2
|
export declare const config: AutomationConfig;
|
|
3
|
+
export declare function guardrails({ config: values }: AutomationGuardrailContext): AutomationGuardrails;
|
|
3
4
|
export default function mmSpread(api: AutomationAPI): void;
|
|
4
5
|
//# sourceMappingURL=mm-spread.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mm-spread.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/mm-spread.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"mm-spread.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/mm-spread.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAErH,eAAO,MAAM,MAAM,EAAE,gBASpB,CAAC;AAEF,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,0BAA0B,GAAG,oBAAoB,CAe/F;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,aAAa,QAmGlD"}
|
|
@@ -9,6 +9,22 @@ export const config = {
|
|
|
9
9
|
skewFactor: { type: 'number', description: 'Inventory skew aggressiveness', default: 2.0 },
|
|
10
10
|
},
|
|
11
11
|
};
|
|
12
|
+
export function guardrails({ config: values }) {
|
|
13
|
+
return {
|
|
14
|
+
mode: 'trading',
|
|
15
|
+
allowedMarkets: [String(values.coin ?? 'HYPE')],
|
|
16
|
+
maxOrderUsd: 10_000,
|
|
17
|
+
maxPositionUsd: 25_000,
|
|
18
|
+
maxTotalExposureUsd: 25_000,
|
|
19
|
+
maxLeverage: 1,
|
|
20
|
+
maxMarginUsedPct: 50,
|
|
21
|
+
maxOpenOrders: 10,
|
|
22
|
+
maxOrdersPerMinute: 60,
|
|
23
|
+
maxSlippageBps: 25,
|
|
24
|
+
allowMarketOrders: false,
|
|
25
|
+
allowAccountWideCancel: false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
12
28
|
export default function mmSpread(api) {
|
|
13
29
|
const COIN = api.state.get('coin', 'HYPE');
|
|
14
30
|
const SIZE = api.state.get('size', 0.1);
|
|
@@ -82,7 +98,7 @@ export default function mmSpread(api) {
|
|
|
82
98
|
}
|
|
83
99
|
// Place new quotes
|
|
84
100
|
if (shouldBid && !bidOid) {
|
|
85
|
-
const resp = await api.client.limitOrder(COIN, true, SIZE, targetBid, 'Gtc', false);
|
|
101
|
+
const resp = await api.client.limitOrder(COIN, true, SIZE, targetBid, 'Gtc', false, 1);
|
|
86
102
|
if (resp.status === 'ok' && resp.response && typeof resp.response === 'object') {
|
|
87
103
|
const s = resp.response.data.statuses[0];
|
|
88
104
|
if (s?.resting) {
|
|
@@ -92,7 +108,7 @@ export default function mmSpread(api) {
|
|
|
92
108
|
}
|
|
93
109
|
}
|
|
94
110
|
if (shouldAsk && !askOid) {
|
|
95
|
-
const resp = await api.client.limitOrder(COIN, false, SIZE, targetAsk, 'Gtc', false);
|
|
111
|
+
const resp = await api.client.limitOrder(COIN, false, SIZE, targetAsk, 'Gtc', false, 1);
|
|
96
112
|
if (resp.status === 'ok' && resp.response && typeof resp.response === 'object') {
|
|
97
113
|
const s = resp.response.data.statuses[0];
|
|
98
114
|
if (s?.resting) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AutomationAPI, AutomationConfig } from '../types.js';
|
|
1
|
+
import type { AutomationAPI, AutomationConfig, AutomationGuardrails } from '../types.js';
|
|
2
2
|
export declare const config: AutomationConfig;
|
|
3
|
+
export declare const guardrails: AutomationGuardrails;
|
|
3
4
|
export default function priceAlert(api: AutomationAPI): void;
|
|
4
5
|
//# sourceMappingURL=price-alert.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"price-alert.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/price-alert.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"price-alert.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/price-alert.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEzF,eAAO,MAAM,MAAM,EAAE,gBAQpB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,oBAA4C,CAAC;AAEtE,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,GAAG,EAAE,aAAa,QAgFpD"}
|
|
@@ -9,6 +9,7 @@ export const config = {
|
|
|
9
9
|
below: { type: 'number', description: 'Alert when price goes below this level (0 = disabled)', default: 0 },
|
|
10
10
|
},
|
|
11
11
|
};
|
|
12
|
+
export const guardrails = { mode: 'read-only' };
|
|
12
13
|
export default function priceAlert(api) {
|
|
13
14
|
const COIN = api.state.get('coin', 'BTC');
|
|
14
15
|
const THRESHOLD = api.state.get('threshold', 0.1);
|
|
@@ -73,7 +74,7 @@ export default function priceAlert(api) {
|
|
|
73
74
|
api.log.error(`LIQUIDATION: $${liquidatedNtlPos.toFixed(2)} notional, account value: $${liquidatedAccountValue.toFixed(2)}`);
|
|
74
75
|
api.publish(`LIQUIDATED: $${liquidatedNtlPos.toFixed(2)} notional, account value: $${liquidatedAccountValue.toFixed(2)}`, { name: 'liquidation-alert' });
|
|
75
76
|
});
|
|
76
|
-
// Periodic summary via
|
|
77
|
+
// Periodic summary via the independent runtime scheduler
|
|
77
78
|
api.on('tick', ({ pollCount }) => {
|
|
78
79
|
if (pollCount % 10 === 0 && alertCount > 0) {
|
|
79
80
|
api.log.info(`Summary: ${alertCount} alerts fired, last price: $${lastAlertPrice.toFixed(2)}`);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { HyperliquidClient } from '../core/client.js';
|
|
2
|
+
import type { AutomationGuardrailContext, AutomationGuardrails, AutomationGuardrailsExport, AutomationLogger } from './types.js';
|
|
3
|
+
export declare const CLIENT_WRITE_METHODS: Set<string>;
|
|
4
|
+
export declare class GuardrailViolation extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
constructor(code: string, message: string);
|
|
7
|
+
}
|
|
8
|
+
export declare function canonicalMarket(market: string): string;
|
|
9
|
+
export declare function validateAutomationGuardrails(value: unknown): AutomationGuardrails;
|
|
10
|
+
export declare function resolveAutomationGuardrails(exported: AutomationGuardrailsExport, context: AutomationGuardrailContext): AutomationGuardrails;
|
|
11
|
+
export interface GuardrailedClientOptions {
|
|
12
|
+
policy: AutomationGuardrails;
|
|
13
|
+
rawClient: HyperliquidClient;
|
|
14
|
+
log: AutomationLogger;
|
|
15
|
+
onViolation?: (error: GuardrailViolation, method: string, args: unknown[]) => void;
|
|
16
|
+
}
|
|
17
|
+
/** Wrap a client so every public write method crosses the validated policy boundary. */
|
|
18
|
+
export declare function createGuardrailedClient(executionClient: HyperliquidClient, options: GuardrailedClientOptions): HyperliquidClient;
|
|
19
|
+
//# sourceMappingURL=guardrails.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrails.d.ts","sourceRoot":"","sources":["../../scripts/auto/guardrails.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,KAAK,EACV,0BAA0B,EAC1B,oBAAoB,EACpB,0BAA0B,EAC1B,gBAAgB,EAEjB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,oBAAoB,aAQ/B,CAAC;AAiBH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAgCD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAuDjF;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,0BAA0B,EACpC,OAAO,EAAE,0BAA0B,GAClC,oBAAoB,CAUtB;AAuTD,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,GAAG,EAAE,gBAAgB,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACpF;AAED,wFAAwF;AACxF,wBAAgB,uBAAuB,CACrC,eAAe,EAAE,iBAAiB,EAClC,OAAO,EAAE,wBAAwB,GAChC,iBAAiB,CA6NnB"}
|