@buildaureon/sdk 0.1.0 → 0.1.2

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/docs/transport.md CHANGED
@@ -1,128 +1,142 @@
1
- # Transport Layer Reference
2
-
3
- This document describes the transport layer of `@buildaureon/sdk`, implemented in `src/transport/http.ts`.
4
-
5
- ---
6
-
7
- ## 1. Network Request Lifecycle
8
-
9
- Every SDK client call delegates network execution to the `requestJson` helper.
10
-
11
- ```mermaid
12
- flowchart TD
13
- Call[Client Method Call] --> Prep[Join URL & Compile Headers]
14
- Prep --> Auth[Resolve Bearer Token]
15
- Auth --> Abort[Set AbortController Timer]
16
- Abort --> Fetch[Execute fetchImpl]
17
- Fetch --> Code{HTTP Status OK?}
18
- Code -->|Yes| Parse[Parse Response JSON]
19
- Code -->|No| Map[map Status to AureonError]
20
- Map --> Retry{Is Error Retryable?}
21
- Retry -->|Yes| Sleep[Sleep retryDelayMs]
22
- Sleep --> Fetch
23
- Retry -->|No| Throw[Throw Custom Exception]
24
- ```
25
-
26
- 1. **URL Join and Query Compilation**: The transport engine normalizes the base URL and path string, then serializes any query parameters.
27
- 2. **Bearer Token Resolution**: The `getAccessToken` async function evaluates the current session token to attach the `Authorization` header.
28
- 3. **Timeout Guard Configuration**: An `AbortController` handles request timeouts, defaulting to 30 seconds.
29
- 4. **Execution and Error Mapping**: The client fires the request. If the gateway returns an error, it is converted to a typed `AureonError` subclass.
30
- 5. **Bounded Retry**: The client retries the request if it encountered a transient failure (e.g. rate limit, gateway timeout) and retry budget is remaining.
31
-
32
- ---
33
-
34
- ## 2. Standard Header Layout
35
-
36
- Every HTTP request sent by the SDK contains these default headers:
37
-
38
- | Header | Required | Typical Value | Purpose |
39
- |--------|----------|---------------|---------|
40
- | `Accept` | Yes | `application/json` | Specifies the accepted media type. |
41
- | `Content-Type` | Optional | `application/json` | Specifies the payload format (attached if a request body is present). |
42
- | `X-Aureon-SDK` | Yes | `@buildaureon/sdk/0.1.0` | Identifies client package version. |
43
- | `X-Aureon-Api-Key` | Optional | `api_key_abc123` | Sent if the client is configured with an API key. |
44
- | `Authorization` | Optional | `Bearer token_xyz` | Session JWT, attached if a token provider is active. |
45
-
46
- ---
47
-
48
- ## 3. URL Utility Implementations
49
-
50
- ### 3.1 `joinUrl`
51
- Normalizes base URLs and paths to prevent double slashes at joining boundaries:
52
- ```ts
53
- function joinUrl(base: string, path: string): string {
54
- const cleanBase = base.endsWith("/") ? base.slice(0, -1) : base;
55
- const cleanPath = path.startsWith("/") ? path.slice(1) : path;
56
- return `${cleanBase}/${cleanPath}`;
57
- }
58
- ```
59
-
60
- ### 3.2 `withQuery`
61
- Serializes query parameter dictionaries into standard URL format:
62
- ```ts
63
- function withQuery(path: string, query?: Record<string, any>): string {
64
- if (!query) return path;
65
- const parts = Object.entries(query)
66
- .filter(([_, v]) => v !== undefined && v !== null)
67
- .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
68
- if (parts.length === 0) return path;
69
- const separator = path.includes("?") ? "&" : "?";
70
- return `${path}${separator}${parts.join("&")}`;
71
- }
72
- ```
73
-
74
- ---
75
-
76
- ## 4. Timeout Budgets and Request Aborts
77
-
78
- The transport engine protects applications from hanging sockets using the Fetch API's `AbortController` signal.
79
-
80
- ```ts
81
- const controller = new AbortController();
82
- const timeoutId = setTimeout(() => controller.abort(), options.timeoutMs);
83
-
84
- try {
85
- const response = await options.fetchImpl(url, {
86
- ...init,
87
- signal: controller.signal
88
- });
89
- return response;
90
- } catch (error) {
91
- if (error instanceof Error && error.name === "AbortError") {
92
- throw new AureonTimeoutError("Request timed out", options.timeoutMs);
93
- }
94
- throw new AureonNetworkError(error instanceof Error ? error.message : String(error));
95
- } finally {
96
- clearTimeout(timeoutId);
97
- }
98
- ```
99
-
100
- * **Configuring Timeout**: Pass `timeoutMs` (in milliseconds) when instantiating `createAureonClient`.
101
- * **Custom Abort Signals**: You can pass a custom `AbortSignal` in `RequestOptions` to cancel requests manually based on user actions.
102
-
103
- ---
104
-
105
- ## 5. Retry Loop and Backoff Configurations
106
-
107
- The SDK implements a retry mechanism for transient exceptions:
108
-
109
- * **Retryable Failures**: `NETWORK_ERROR`, `TIMEOUT`, `RATE_LIMITED` (HTTP 429), and `SERVER_ERROR` (HTTP 5xx).
110
- * **Default Behavior**: `maxRetries` is 0. If you configure `maxRetries: 2` and `retryDelayMs: 500`, the client will try the request up to 3 times, waiting 500ms between attempts.
111
-
112
- ---
113
-
114
- ## 6. Built-in Logger Adapters
115
-
116
- Integrate with internal request diagnostics using the `AureonLogger` interface:
117
-
118
- ```ts
119
- export interface AureonLogger {
120
- debug(message: string, context?: Record<string, unknown>): void;
121
- info(message: string, context?: Record<string, unknown>): void;
122
- warn(message: string, context?: Record<string, unknown>): void;
123
- error(message: string, context?: Record<string, unknown>): void;
124
- }
125
- ```
126
-
127
- * `createConsoleLogger(prefix)`: Logs requests, attempts, and errors to the developer console.
128
- * `silentLogger`: Suppresses logs.
1
+ # Transport Layer Reference
2
+
3
+ HTTP transport for `@buildaureon/sdk` (`src/transport/http.ts`): headers, timeouts, retries, URL helpers, and logging.
4
+
5
+ ---
6
+
7
+ ## 1. Request lifecycle
8
+
9
+ ```mermaid
10
+ flowchart TD
11
+ Call[Client_method] --> Prep[Join_URL_and_headers]
12
+ Prep --> Auth[Resolve_API_key_and_Bearer]
13
+ Auth --> Abort[AbortController_timeout]
14
+ Abort --> Fetch[fetch_implementation]
15
+ Fetch --> Ok{HTTP_OK?}
16
+ Ok -->|Yes| Parse[Parse_JSON]
17
+ Ok -->|No| Map[Map_to_AureonError]
18
+ Map --> Retry{Retryable_and_budget?}
19
+ Retry -->|Yes| Sleep[retryDelayMs]
20
+ Sleep --> Fetch
21
+ Retry -->|No| Throw[Throw]
22
+ ```
23
+
24
+ 1. Join `baseUrl` + path; attach query string when needed.
25
+ 2. Resolve `X-Aureon-Api-Key` from `apiKey` / `getApiKey`.
26
+ 3. Resolve `Authorization: Bearer …` from `getAccessToken` / `authToken` (optional).
27
+ 4. Apply timeout via `AbortController` (default 30s).
28
+ 5. On failure, map status typed error; retry only if configured and retryable.
29
+
30
+ ---
31
+
32
+ ## 2. Headers
33
+
34
+ | Header | When | Purpose |
35
+ | --- | --- | --- |
36
+ | `Accept` | Always | `application/json` |
37
+ | `Content-Type` | Body present | `application/json` |
38
+ | `X-Aureon-SDK` | Always | Package identity / version |
39
+ | `X-Aureon-Api-Key` | Key configured | Product access + issued-key identity |
40
+ | `Authorization` | Token configured | Optional Bearer session |
41
+ | Custom `headers` | If set on client | Merged into every request |
42
+
43
+ Issued developer keys in `X-Aureon-Api-Key` are enough for control-plane identity on the live API. Bearer is optional and wins when both are present.
44
+
45
+ ---
46
+
47
+ ## 3. Client transport options
48
+
49
+ ```ts
50
+ createAureonClient({
51
+ baseUrl: "https://api.aureonlabs.network",
52
+ apiKey: process.env.AUREON_API_KEY!,
53
+ timeoutMs: 30_000, // per attempt
54
+ maxRetries: 2, // extra attempts after first failure
55
+ retryDelayMs: 500, // fixed delay between attempts
56
+ fetch: customFetch, // optional
57
+ headers: { "X-Debug": "1" },
58
+ logger: myLogger,
59
+ });
60
+ ```
61
+
62
+ | Option | Default | Notes |
63
+ | --- | --- | --- |
64
+ | `timeoutMs` | `30000` | Must be positive finite |
65
+ | `maxRetries` | `0` | Extra tries after the first failure |
66
+ | `retryDelayMs` | `250` | Fixed sleep between tries |
67
+ | `fetch` | `globalThis.fetch` | Inject for tests / unusual runtimes |
68
+
69
+ ---
70
+
71
+ ## 4. Retry policy
72
+
73
+ **Retryable:** network failures, timeouts, HTTP 429, HTTP 5xx (when mapped as retryable).
74
+
75
+ **Not retryable:** 400 validation, 401/403 auth, 404, most 409 conflicts (operator must change state).
76
+
77
+ Total attempts = `1 + maxRetries`.
78
+
79
+ ---
80
+
81
+ ## 5. URL helpers
82
+
83
+ ### `joinUrl`
84
+
85
+ Prevents double slashes when combining base + path.
86
+
87
+ ### `withQuery`
88
+
89
+ Serializes defined query params with `encodeURIComponent`. Omits `null` / `undefined`.
90
+
91
+ ---
92
+
93
+ ## 6. Timeouts and aborts
94
+
95
+ ```ts
96
+ const controller = new AbortController();
97
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
98
+ try {
99
+ return await fetchImpl(url, { ...init, signal: controller.signal });
100
+ } catch (error) {
101
+ if (error instanceof Error && error.name === "AbortError") {
102
+ throw /* AureonTimeoutError */;
103
+ }
104
+ throw /* AureonNetworkError */;
105
+ } finally {
106
+ clearTimeout(timeoutId);
107
+ }
108
+ ```
109
+
110
+ For agent loops that call restore + sync, prefer slightly higher `timeoutMs` under load rather than disabling timeouts.
111
+
112
+ ---
113
+
114
+ ## 7. Logger interface
115
+
116
+ ```ts
117
+ interface AureonLogger {
118
+ debug(message: string, context?: Record<string, unknown>): void;
119
+ info(message: string, context?: Record<string, unknown>): void;
120
+ warn(message: string, context?: Record<string, unknown>): void;
121
+ error(message: string, context?: Record<string, unknown>): void;
122
+ }
123
+ ```
124
+
125
+ Helpers may include console / silent adapters depending on package exports. Never log secrets from context.
126
+
127
+ ---
128
+
129
+ ## 8. Testing transport
130
+
131
+ - Inject a fake `fetch` that returns controlled status/body.
132
+ - Assert header presence of `X-Aureon-Api-Key` for SDK clients.
133
+ - Assert retries by counting fetch invocations with `maxRetries > 0` and 503 responses.
134
+
135
+ ---
136
+
137
+ ## 9. Related docs
138
+
139
+ - [Error model](./error-model.md)
140
+ - [Auth](./auth.md)
141
+ - [Client API](./client-api.md)
142
+ - [Security](./security.md)
@@ -1,12 +1,16 @@
1
1
  /**
2
- * Live e2e: deposit flex + Auto maintain 20% TSLA (surplus sell / deficit buy).
2
+ * Live e2e: vault deposits + Automatic maintain 20% TSLA against the hosted API.
3
3
  *
4
- * pnpm --filter @buildaureon/sdk exec tsx examples/e2e-policy-rebalance/main.ts
4
+ * Env:
5
+ * AUREON_API_KEY issued developer key (required)
6
+ * AUREON_WALLET_PRIVATE_KEY 0x… signing key (required)
7
+ * AUREON_API_URL optional (default https://api.aureonlabs.network)
8
+ * AUREON_RPC_URL optional
9
+ * AUREON_CHAIN_ID optional (default 46630)
10
+ *
11
+ * pnpm --filter @buildaureon/sdk example:e2e-policy
5
12
  */
6
13
 
7
- import { readFileSync, existsSync } from "node:fs";
8
- import { dirname, join } from "node:path";
9
- import { fileURLToPath } from "node:url";
10
14
  import {
11
15
  createPublicClient,
12
16
  createWalletClient,
@@ -21,13 +25,10 @@ import { privateKeyToAccount } from "viem/accounts";
21
25
  import {
22
26
  createAureonClient,
23
27
  createSessionTokenProvider,
28
+ DEFAULT_API_BASE_URL,
24
29
  isAureonError,
25
- LOCAL_API_BASE_URL,
26
30
  } from "../../src/index.js";
27
31
 
28
- const __dirname = dirname(fileURLToPath(import.meta.url));
29
- const BACKEND = join(__dirname, "../../../backend");
30
-
31
32
  const VAULT_ABI = [
32
33
  {
33
34
  type: "function",
@@ -41,32 +42,18 @@ const VAULT_ABI = [
41
42
  },
42
43
  ] as const;
43
44
 
44
- function loadDotEnv(path: string): Record<string, string> {
45
- if (!existsSync(path)) return {};
46
- const out: Record<string, string> = {};
47
- for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
48
- const trimmed = line.trim();
49
- if (!trimmed || trimmed.startsWith("#")) continue;
50
- const i = trimmed.indexOf("=");
51
- if (i < 0) continue;
52
- out[trimmed.slice(0, i)] = trimmed.slice(i + 1).trim();
53
- }
54
- return out;
55
- }
56
-
57
- function firstApiKey(raw?: string): string {
58
- const key = raw?.split(",")[0]?.trim();
59
- if (!key) throw new Error("AUREON_API_KEYS missing");
60
- return key;
45
+ function requireEnv(name: string): string {
46
+ const value = process.env[name]?.trim();
47
+ if (!value) throw new Error(`Set ${name}`);
48
+ return value;
61
49
  }
62
50
 
63
51
  function loadKey(): Hex {
64
- if (process.env.AUREON_E2E_KEY?.startsWith("0x")) {
65
- return process.env.AUREON_E2E_KEY as Hex;
52
+ const key = requireEnv("AUREON_WALLET_PRIVATE_KEY");
53
+ if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
54
+ throw new Error("AUREON_WALLET_PRIVATE_KEY must be a 0x-prefixed 32-byte hex key");
66
55
  }
67
- const walletPath = join(BACKEND, ".secrets/robinhood-testnet-wallet.json");
68
- const j = JSON.parse(readFileSync(walletPath, "utf8")) as { privateKey: string };
69
- return j.privateKey as Hex;
56
+ return key as Hex;
70
57
  }
71
58
 
72
59
  function log(step: string, data?: unknown) {
@@ -74,11 +61,12 @@ function log(step: string, data?: unknown) {
74
61
  }
75
62
 
76
63
  async function main() {
77
- const env = loadDotEnv(join(BACKEND, ".env"));
78
- const baseUrl = process.env.AUREON_API_URL ?? LOCAL_API_BASE_URL;
79
- const rpcUrl = env.AUREON_RPC_URL ?? "https://rpc.testnet.chain.robinhood.com";
80
- const chainId = Number(env.AUREON_CHAIN_ID ?? 46630);
81
- const apiKey = firstApiKey(process.env.AUREON_API_KEY ?? env.AUREON_API_KEYS);
64
+ const baseUrl = process.env.AUREON_API_URL?.trim() || DEFAULT_API_BASE_URL;
65
+ const rpcUrl =
66
+ process.env.AUREON_RPC_URL?.trim() ||
67
+ "https://rpc.testnet.chain.robinhood.com";
68
+ const chainId = Number(process.env.AUREON_CHAIN_ID ?? 46630);
69
+ const apiKey = requireEnv("AUREON_API_KEY");
82
70
 
83
71
  const account = privateKeyToAccount(loadKey());
84
72
  const publicClient = createPublicClient({ transport: http(rpcUrl) });
@@ -1,9 +1,14 @@
1
1
  /**
2
2
  * Follow-up: force TSLA underweight → Auto should Sell WETH → Buy TSLA.
3
+ *
4
+ * Env:
5
+ * AUREON_API_KEY issued developer key
6
+ * AUREON_WALLET_PRIVATE_KEY 0x… signing key
7
+ * AUREON_API_URL optional (default https://api.aureonlabs.network)
8
+ * AUREON_RPC_URL optional
9
+ * AUREON_CHAIN_ID optional (default 46630)
3
10
  */
4
- import { readFileSync } from "node:fs";
5
- import { join, dirname } from "node:path";
6
- import { fileURLToPath } from "node:url";
11
+
7
12
  import {
8
13
  createPublicClient,
9
14
  createWalletClient,
@@ -15,39 +20,39 @@ import { privateKeyToAccount } from "viem/accounts";
15
20
  import {
16
21
  createAureonClient,
17
22
  createSessionTokenProvider,
23
+ DEFAULT_API_BASE_URL,
18
24
  isAureonError,
19
- LOCAL_API_BASE_URL,
20
25
  } from "../../src/index.js";
21
26
 
22
- const BACKEND = join(dirname(fileURLToPath(import.meta.url)), "../../../backend");
23
- const env = Object.fromEntries(
24
- readFileSync(join(BACKEND, ".env"), "utf8")
25
- .split(/\r?\n/)
26
- .filter((l) => l && !l.startsWith("#") && l.includes("="))
27
- .map((l) => {
28
- const i = l.indexOf("=");
29
- return [l.slice(0, i), l.slice(i + 1).trim()] as const;
30
- })
31
- );
32
- const key = JSON.parse(
33
- readFileSync(join(BACKEND, ".secrets/robinhood-testnet-wallet.json"), "utf8")
34
- ).privateKey as Hex;
27
+ function requireEnv(name: string): string {
28
+ const value = process.env[name]?.trim();
29
+ if (!value) throw new Error(`Set ${name}`);
30
+ return value;
31
+ }
32
+
33
+ const key = requireEnv("AUREON_WALLET_PRIVATE_KEY") as Hex;
34
+ if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
35
+ throw new Error("AUREON_WALLET_PRIVATE_KEY must be a 0x-prefixed 32-byte hex key");
36
+ }
37
+
35
38
  const account = privateKeyToAccount(key);
36
- const rpc = env.AUREON_RPC_URL!;
37
- const chainId = Number(env.AUREON_CHAIN_ID || 46630);
39
+ const rpc =
40
+ process.env.AUREON_RPC_URL?.trim() ||
41
+ "https://rpc.testnet.chain.robinhood.com";
42
+ const chainId = Number(process.env.AUREON_CHAIN_ID || 46630);
38
43
  const publicClient = createPublicClient({ transport: http(rpc) });
39
44
  const walletClient = createWalletClient({ account, transport: http(rpc) });
40
45
  const chain = {
41
46
  id: chainId,
42
- name: "rh",
47
+ name: "Robinhood Chain Testnet",
43
48
  nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
44
49
  rpcUrls: { default: { http: [rpc] } },
45
50
  } as const;
46
51
 
47
52
  const session = createSessionTokenProvider(null);
48
53
  const aureon = createAureonClient({
49
- baseUrl: LOCAL_API_BASE_URL,
50
- apiKey: env.AUREON_API_KEYS!.split(",")[0]!.trim(),
54
+ baseUrl: process.env.AUREON_API_URL?.trim() || DEFAULT_API_BASE_URL,
55
+ apiKey: requireEnv("AUREON_API_KEY"),
51
56
  getAccessToken: session.getAccessToken,
52
57
  });
53
58
 
@@ -100,8 +105,7 @@ for (const step of d.steps) {
100
105
  }
101
106
 
102
107
  const objs = await aureon.listObjectives();
103
- const obj =
104
- objs.find((o) => o.name.includes("E2E 20% TSLA")) ?? objs[0];
108
+ const obj = objs.find((o) => o.name.includes("E2E 20% TSLA")) ?? objs[0];
105
109
  if (!obj) throw new Error("no objective");
106
110
  console.log("using objective", obj.id, obj.name);
107
111
 
@@ -1,9 +1,14 @@
1
1
  /**
2
- * Verify underweight Auto sizing lands near 20% (not 98%).
2
+ * Verify underweight Automatic sizing lands near 20% (not a blow-up fill).
3
+ *
4
+ * Env:
5
+ * AUREON_API_KEY issued developer key
6
+ * AUREON_WALLET_PRIVATE_KEY 0x… signing key
7
+ * AUREON_API_URL optional (default https://api.aureonlabs.network)
8
+ * AUREON_RPC_URL optional
9
+ * AUREON_CHAIN_ID optional (default 46630)
3
10
  */
4
- import { readFileSync } from "node:fs";
5
- import { join, dirname } from "node:path";
6
- import { fileURLToPath } from "node:url";
11
+
7
12
  import {
8
13
  createPublicClient,
9
14
  createWalletClient,
@@ -15,38 +20,38 @@ import { privateKeyToAccount } from "viem/accounts";
15
20
  import {
16
21
  createAureonClient,
17
22
  createSessionTokenProvider,
18
- LOCAL_API_BASE_URL,
23
+ DEFAULT_API_BASE_URL,
19
24
  } from "../../src/index.js";
20
25
 
21
- const BACKEND = join(dirname(fileURLToPath(import.meta.url)), "../../../backend");
22
- const env = Object.fromEntries(
23
- readFileSync(join(BACKEND, ".env"), "utf8")
24
- .split(/\r?\n/)
25
- .filter((l) => l && !l.startsWith("#") && l.includes("="))
26
- .map((l) => {
27
- const i = l.indexOf("=");
28
- return [l.slice(0, i), l.slice(i + 1).trim()] as const;
29
- })
30
- );
31
- const key = JSON.parse(
32
- readFileSync(join(BACKEND, ".secrets/robinhood-testnet-wallet.json"), "utf8")
33
- ).privateKey as Hex;
26
+ function requireEnv(name: string): string {
27
+ const value = process.env[name]?.trim();
28
+ if (!value) throw new Error(`Set ${name}`);
29
+ return value;
30
+ }
31
+
32
+ const key = requireEnv("AUREON_WALLET_PRIVATE_KEY") as Hex;
33
+ if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
34
+ throw new Error("AUREON_WALLET_PRIVATE_KEY must be a 0x-prefixed 32-byte hex key");
35
+ }
36
+
34
37
  const account = privateKeyToAccount(key);
35
- const rpc = env.AUREON_RPC_URL!;
36
- const chainId = Number(env.AUREON_CHAIN_ID || 46630);
38
+ const rpc =
39
+ process.env.AUREON_RPC_URL?.trim() ||
40
+ "https://rpc.testnet.chain.robinhood.com";
41
+ const chainId = Number(process.env.AUREON_CHAIN_ID || 46630);
37
42
  const publicClient = createPublicClient({ transport: http(rpc) });
38
43
  const walletClient = createWalletClient({ account, transport: http(rpc) });
39
44
  const chain = {
40
45
  id: chainId,
41
- name: "rh",
46
+ name: "Robinhood Chain Testnet",
42
47
  nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
43
48
  rpcUrls: { default: { http: [rpc] } },
44
49
  } as const;
45
50
 
46
51
  const session = createSessionTokenProvider(null);
47
52
  const aureon = createAureonClient({
48
- baseUrl: LOCAL_API_BASE_URL,
49
- apiKey: env.AUREON_API_KEYS!.split(",")[0]!.trim(),
53
+ baseUrl: process.env.AUREON_API_URL?.trim() || DEFAULT_API_BASE_URL,
54
+ apiKey: requireEnv("AUREON_API_KEY"),
50
55
  getAccessToken: session.getAccessToken,
51
56
  });
52
57
 
@@ -61,7 +66,6 @@ session.setToken(
61
66
  ).token
62
67
  );
63
68
 
64
- // Fresh Auto objective
65
69
  const objective = await aureon.createObjective({
66
70
  name: `SizeFix 20% TSLA ${Date.now()}`,
67
71
  kind: "balanced_portfolio",
@@ -73,7 +77,6 @@ const objective = await aureon.createObjective({
73
77
  });
74
78
  console.log("objective", objective.id);
75
79
 
76
- // Dump almost all TSLA so we're heavily underweight, keep/add WETH
77
80
  const vault = await aureon.getVault();
78
81
  console.log(
79
82
  "before",
@@ -138,7 +141,7 @@ console.log("health after", {
138
141
  });
139
142
 
140
143
  const weightPct = (h2.currentMetric ?? 0) * 100;
141
- const ok = weightPct >= 5 && weightPct <= 45; // near 20% with band (was ~98% before)
144
+ const ok = weightPct >= 5 && weightPct <= 45;
142
145
  console.log(
143
146
  ok
144
147
  ? `PASS sizing : TSLA weight ~${weightPct.toFixed(1)}% (want ~20%)`