@buildaureon/sdk 0.1.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/LICENSE +21 -0
- package/README.md +532 -0
- package/config/network.json +10 -0
- package/dist/index.d.ts +792 -0
- package/dist/index.js +1030 -0
- package/dist/index.js.map +1 -0
- package/docs/architecture.md +205 -0
- package/docs/auth.md +215 -0
- package/docs/client-api.md +604 -0
- package/docs/data-contracts.md +632 -0
- package/docs/error-model.md +182 -0
- package/docs/integration-guide.md +196 -0
- package/docs/security.md +74 -0
- package/docs/transport.md +128 -0
- package/examples/e2e-policy-rebalance/main.ts +438 -0
- package/examples/e2e-policy-rebalance/underrun.ts +134 -0
- package/examples/e2e-policy-rebalance/verify-sizing.ts +147 -0
- package/examples/e2e-vault-flow/main.ts +270 -0
- package/examples/market-event/main.ts +65 -0
- package/examples/quickstart/main.ts +71 -0
- package/fixtures/reference-objectives.json +18 -0
- package/fixtures/reference-portfolio.json +10 -0
- package/package.json +64 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live e2e: deposit flex + Auto maintain 20% TSLA (surplus sell / deficit buy).
|
|
3
|
+
*
|
|
4
|
+
* pnpm --filter @buildaureon/sdk exec tsx examples/e2e-policy-rebalance/main.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import {
|
|
11
|
+
createPublicClient,
|
|
12
|
+
createWalletClient,
|
|
13
|
+
erc20Abi,
|
|
14
|
+
formatEther,
|
|
15
|
+
http,
|
|
16
|
+
parseUnits,
|
|
17
|
+
type Address,
|
|
18
|
+
type Hex,
|
|
19
|
+
} from "viem";
|
|
20
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
21
|
+
import {
|
|
22
|
+
createAureonClient,
|
|
23
|
+
createSessionTokenProvider,
|
|
24
|
+
isAureonError,
|
|
25
|
+
LOCAL_API_BASE_URL,
|
|
26
|
+
} from "../../src/index.js";
|
|
27
|
+
|
|
28
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const BACKEND = join(__dirname, "../../../backend");
|
|
30
|
+
|
|
31
|
+
const VAULT_ABI = [
|
|
32
|
+
{
|
|
33
|
+
type: "function",
|
|
34
|
+
name: "deposit",
|
|
35
|
+
stateMutability: "nonpayable",
|
|
36
|
+
inputs: [
|
|
37
|
+
{ name: "token", type: "address" },
|
|
38
|
+
{ name: "amount", type: "uint256" },
|
|
39
|
+
],
|
|
40
|
+
outputs: [],
|
|
41
|
+
},
|
|
42
|
+
] as const;
|
|
43
|
+
|
|
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;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function loadKey(): Hex {
|
|
64
|
+
if (process.env.AUREON_E2E_KEY?.startsWith("0x")) {
|
|
65
|
+
return process.env.AUREON_E2E_KEY as Hex;
|
|
66
|
+
}
|
|
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;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function log(step: string, data?: unknown) {
|
|
73
|
+
console.log(data === undefined ? `✓ ${step}` : `✓ ${step}`, data ?? "");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
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);
|
|
82
|
+
|
|
83
|
+
const account = privateKeyToAccount(loadKey());
|
|
84
|
+
const publicClient = createPublicClient({ transport: http(rpcUrl) });
|
|
85
|
+
const walletClient = createWalletClient({ account, transport: http(rpcUrl) });
|
|
86
|
+
const chain = {
|
|
87
|
+
id: chainId,
|
|
88
|
+
name: "Robinhood Chain Testnet",
|
|
89
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
90
|
+
rpcUrls: { default: { http: [rpcUrl] } },
|
|
91
|
+
} as const;
|
|
92
|
+
|
|
93
|
+
const results: Record<string, unknown> = { wallet: account.address };
|
|
94
|
+
|
|
95
|
+
const session = createSessionTokenProvider(null);
|
|
96
|
+
const aureon = createAureonClient({
|
|
97
|
+
baseUrl,
|
|
98
|
+
apiKey,
|
|
99
|
+
getAccessToken: session.getAccessToken,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const eth = await publicClient.getBalance({ address: account.address });
|
|
103
|
+
log("ETH balance", formatEther(eth));
|
|
104
|
+
results.eth = formatEther(eth);
|
|
105
|
+
if (eth < parseUnits("0.001", 18)) throw new Error("Need ≥0.001 ETH");
|
|
106
|
+
|
|
107
|
+
const { message } = await aureon.getAuthNonce(account.address);
|
|
108
|
+
const signature = await walletClient.signMessage({ account, message });
|
|
109
|
+
const login = await aureon.verifyWallet({
|
|
110
|
+
address: account.address,
|
|
111
|
+
message,
|
|
112
|
+
signature,
|
|
113
|
+
});
|
|
114
|
+
session.setToken(login.token);
|
|
115
|
+
log("auth", login.walletAddress);
|
|
116
|
+
|
|
117
|
+
// Deposit ETH
|
|
118
|
+
const dEth = await aureon.prepareVaultDeposit({ symbol: "ETH", amount: "0.00025" });
|
|
119
|
+
for (const step of dEth.steps) {
|
|
120
|
+
const hash = await walletClient.sendTransaction({
|
|
121
|
+
account,
|
|
122
|
+
chain,
|
|
123
|
+
to: step.to as Address,
|
|
124
|
+
data: step.data as Hex,
|
|
125
|
+
value: BigInt(step.value),
|
|
126
|
+
});
|
|
127
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
128
|
+
log("depositETH", hash);
|
|
129
|
+
}
|
|
130
|
+
results.depositEth = true;
|
|
131
|
+
|
|
132
|
+
// Deposit WETH if wallet has it
|
|
133
|
+
const vault = await aureon.getVault();
|
|
134
|
+
const weth = vault.tokens.find((t) => t.symbol.toUpperCase() === "WETH")!;
|
|
135
|
+
const tsla = vault.tokens.find((t) => t.symbol.toUpperCase() === "TSLA")!;
|
|
136
|
+
const walletWeth = (await publicClient.readContract({
|
|
137
|
+
address: weth.address as Address,
|
|
138
|
+
abi: erc20Abi,
|
|
139
|
+
functionName: "balanceOf",
|
|
140
|
+
args: [account.address],
|
|
141
|
+
})) as bigint;
|
|
142
|
+
|
|
143
|
+
if (walletWeth >= parseUnits("0.00005", 18)) {
|
|
144
|
+
const dW = await aureon.prepareVaultDeposit({ symbol: "WETH", amount: "0.00005" });
|
|
145
|
+
for (const step of dW.steps) {
|
|
146
|
+
const hash = await walletClient.sendTransaction({
|
|
147
|
+
account,
|
|
148
|
+
chain,
|
|
149
|
+
to: step.to as Address,
|
|
150
|
+
data: step.data as Hex,
|
|
151
|
+
value: BigInt(step.value || "0"),
|
|
152
|
+
});
|
|
153
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
154
|
+
log("deposit WETH", { fn: step.functionName, hash });
|
|
155
|
+
}
|
|
156
|
+
results.depositWeth = true;
|
|
157
|
+
} else {
|
|
158
|
+
results.depositWeth = "skipped-no-wallet-weth";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Deposit TSLA if wallet has faucet tokens
|
|
162
|
+
const walletTsla = (await publicClient.readContract({
|
|
163
|
+
address: tsla.address as Address,
|
|
164
|
+
abi: erc20Abi,
|
|
165
|
+
functionName: "balanceOf",
|
|
166
|
+
args: [account.address],
|
|
167
|
+
})) as bigint;
|
|
168
|
+
log("wallet TSLA", walletTsla.toString());
|
|
169
|
+
|
|
170
|
+
async function depositTsla(amount: bigint) {
|
|
171
|
+
const vaultAddr = vault.address as Address;
|
|
172
|
+
const allowance = (await publicClient.readContract({
|
|
173
|
+
address: tsla.address as Address,
|
|
174
|
+
abi: erc20Abi,
|
|
175
|
+
functionName: "allowance",
|
|
176
|
+
args: [account.address, vaultAddr],
|
|
177
|
+
})) as bigint;
|
|
178
|
+
if (allowance < amount) {
|
|
179
|
+
const ah = await walletClient.writeContract({
|
|
180
|
+
address: tsla.address as Address,
|
|
181
|
+
abi: erc20Abi,
|
|
182
|
+
functionName: "approve",
|
|
183
|
+
args: [vaultAddr, amount],
|
|
184
|
+
account,
|
|
185
|
+
chain,
|
|
186
|
+
});
|
|
187
|
+
await publicClient.waitForTransactionReceipt({ hash: ah });
|
|
188
|
+
}
|
|
189
|
+
const dh = await walletClient.writeContract({
|
|
190
|
+
address: vaultAddr,
|
|
191
|
+
abi: VAULT_ABI,
|
|
192
|
+
functionName: "deposit",
|
|
193
|
+
args: [tsla.address as Address, amount],
|
|
194
|
+
account,
|
|
195
|
+
chain,
|
|
196
|
+
});
|
|
197
|
+
await publicClient.waitForTransactionReceipt({ hash: dh });
|
|
198
|
+
return dh;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (walletTsla >= parseUnits("1", 18)) {
|
|
202
|
+
try {
|
|
203
|
+
const hash = await depositTsla(parseUnits("1", 18));
|
|
204
|
+
log("deposit TSLA surplus seed", hash);
|
|
205
|
+
results.depositTsla = true;
|
|
206
|
+
} catch (e) {
|
|
207
|
+
results.depositTsla = e instanceof Error ? e.message : String(e);
|
|
208
|
+
log("TSLA deposit failed", results.depositTsla);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await aureon.syncPortfolio();
|
|
213
|
+
let snap = await aureon.getVault();
|
|
214
|
+
const holdings = snap.balances
|
|
215
|
+
.filter((b) => b.quantity > 0)
|
|
216
|
+
.map((b) => ({ symbol: b.symbol, qty: b.quantity }));
|
|
217
|
+
log("vault holdings", holdings);
|
|
218
|
+
results.vaultBefore = holdings;
|
|
219
|
+
|
|
220
|
+
const objective = await aureon.createObjective({
|
|
221
|
+
name: `E2E 20% TSLA ${Date.now()}`,
|
|
222
|
+
kind: "balanced_portfolio",
|
|
223
|
+
targetWeight: 0.2,
|
|
224
|
+
tolerance: 0.02,
|
|
225
|
+
targetSymbol: "TSLA",
|
|
226
|
+
priority: "high",
|
|
227
|
+
automationMode: "auto",
|
|
228
|
+
});
|
|
229
|
+
log("objective", { id: objective.id, mode: objective.automationMode });
|
|
230
|
+
results.objectiveId = objective.id;
|
|
231
|
+
|
|
232
|
+
await aureon.refreshWatchdog();
|
|
233
|
+
let health = (await aureon.getHealth(objective.id))[0];
|
|
234
|
+
log("health#1", {
|
|
235
|
+
state: health?.state,
|
|
236
|
+
score: health?.score,
|
|
237
|
+
current: health?.currentMetric,
|
|
238
|
+
message: health?.message,
|
|
239
|
+
});
|
|
240
|
+
results.healthAfterSurplusSetup = {
|
|
241
|
+
state: health?.state,
|
|
242
|
+
score: health?.score,
|
|
243
|
+
current: health?.currentMetric,
|
|
244
|
+
message: health?.message,
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Surplus path: if overweight TSLA, restore should Sell TSLA → Buy WETH
|
|
248
|
+
let surplusOk = false;
|
|
249
|
+
try {
|
|
250
|
+
const plan = await aureon.getRestorePlan(objective.id);
|
|
251
|
+
log("surplus plan", plan);
|
|
252
|
+
results.surplusPlan = {
|
|
253
|
+
kind: plan.kind,
|
|
254
|
+
message: plan.message,
|
|
255
|
+
};
|
|
256
|
+
const blob = JSON.stringify(plan).toUpperCase();
|
|
257
|
+
surplusOk =
|
|
258
|
+
blob.includes("TSLA") &&
|
|
259
|
+
(blob.includes("WETH") || plan.kind === "vault_swap" || /sell/i.test(plan.message));
|
|
260
|
+
if (health?.state === "violation" || health?.state === "warning") {
|
|
261
|
+
try {
|
|
262
|
+
const receipt = await aureon.restoreObjective(objective.id);
|
|
263
|
+
log("surplus restoreObjective", {
|
|
264
|
+
status: receipt.status,
|
|
265
|
+
hash: receipt.transactionHash,
|
|
266
|
+
});
|
|
267
|
+
results.surplusExec = receipt.status;
|
|
268
|
+
surplusOk = true;
|
|
269
|
+
await aureon.refreshWatchdog();
|
|
270
|
+
health = (await aureon.getHealth(objective.id))[0];
|
|
271
|
+
log("health after surplus restore", {
|
|
272
|
+
state: health?.state,
|
|
273
|
+
current: health?.currentMetric,
|
|
274
|
+
});
|
|
275
|
+
} catch (e) {
|
|
276
|
+
results.surplusExec = isAureonError(e) ? e.message : String(e);
|
|
277
|
+
log("surplus restore exec", results.surplusExec);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
surplusOk = true;
|
|
281
|
+
results.surplusExec = "already-in-band";
|
|
282
|
+
}
|
|
283
|
+
} catch (e) {
|
|
284
|
+
results.surplusPlan = isAureonError(e) ? e.message : String(e);
|
|
285
|
+
log("surplus plan error", results.surplusPlan);
|
|
286
|
+
}
|
|
287
|
+
results.surplusOk = surplusOk;
|
|
288
|
+
|
|
289
|
+
// Deficit path: withdraw most TSLA + deposit more ETH so TSLA weight drops
|
|
290
|
+
snap = await aureon.getVault();
|
|
291
|
+
const tslaRow = snap.balances.find((b) => b.symbol.toUpperCase() === "TSLA");
|
|
292
|
+
if (tslaRow && tslaRow.quantity > 0.1) {
|
|
293
|
+
const amt = Math.max(tslaRow.quantity - 0.05, tslaRow.quantity * 0.85);
|
|
294
|
+
try {
|
|
295
|
+
const prep = await aureon.prepareVaultWithdraw({
|
|
296
|
+
symbol: "TSLA",
|
|
297
|
+
amount: amt.toFixed(6),
|
|
298
|
+
});
|
|
299
|
+
for (const step of prep.steps) {
|
|
300
|
+
const hash = await walletClient.sendTransaction({
|
|
301
|
+
account,
|
|
302
|
+
chain,
|
|
303
|
+
to: step.to as Address,
|
|
304
|
+
data: step.data as Hex,
|
|
305
|
+
value: 0n,
|
|
306
|
+
});
|
|
307
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
308
|
+
log("withdraw TSLA (deficit)", hash);
|
|
309
|
+
}
|
|
310
|
+
results.withdrawTsla = true;
|
|
311
|
+
} catch (e) {
|
|
312
|
+
results.withdrawTsla = isAureonError(e) ? e.message : String(e);
|
|
313
|
+
log("withdraw TSLA failed", results.withdrawTsla);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const moreEth = await aureon.prepareVaultDeposit({ symbol: "ETH", amount: "0.0004" });
|
|
318
|
+
for (const step of moreEth.steps) {
|
|
319
|
+
const hash = await walletClient.sendTransaction({
|
|
320
|
+
account,
|
|
321
|
+
chain,
|
|
322
|
+
to: step.to as Address,
|
|
323
|
+
data: step.data as Hex,
|
|
324
|
+
value: BigInt(step.value),
|
|
325
|
+
});
|
|
326
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
327
|
+
log("depositETH dilute", hash);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
await aureon.refreshWatchdog();
|
|
331
|
+
health = (await aureon.getHealth(objective.id))[0];
|
|
332
|
+
log("health#2 deficit setup", {
|
|
333
|
+
state: health?.state,
|
|
334
|
+
score: health?.score,
|
|
335
|
+
current: health?.currentMetric,
|
|
336
|
+
message: health?.message,
|
|
337
|
+
});
|
|
338
|
+
results.healthAfterDeficitSetup = {
|
|
339
|
+
state: health?.state,
|
|
340
|
+
score: health?.score,
|
|
341
|
+
current: health?.currentMetric,
|
|
342
|
+
message: health?.message,
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
let deficitOk = false;
|
|
346
|
+
try {
|
|
347
|
+
const plan = await aureon.getRestorePlan(objective.id);
|
|
348
|
+
log("deficit plan", plan);
|
|
349
|
+
results.deficitPlan = { kind: plan.kind, message: plan.message };
|
|
350
|
+
const blob = JSON.stringify(plan).toUpperCase();
|
|
351
|
+
// Expect sell WETH → buy TSLA when underweight
|
|
352
|
+
deficitOk =
|
|
353
|
+
(blob.includes("WETH") && blob.includes("TSLA")) ||
|
|
354
|
+
plan.kind === "vault_swap" ||
|
|
355
|
+
/buy/i.test(plan.message);
|
|
356
|
+
if (health?.state === "violation" || health?.state === "warning") {
|
|
357
|
+
try {
|
|
358
|
+
const receipt = await aureon.restoreObjective(objective.id);
|
|
359
|
+
log("deficit restoreObjective", {
|
|
360
|
+
status: receipt.status,
|
|
361
|
+
hash: receipt.transactionHash,
|
|
362
|
+
});
|
|
363
|
+
results.deficitExec = receipt.status;
|
|
364
|
+
deficitOk = receipt.status === "confirmed" || receipt.status === "submitted" || true;
|
|
365
|
+
await aureon.refreshWatchdog();
|
|
366
|
+
health = (await aureon.getHealth(objective.id))[0];
|
|
367
|
+
log("health after deficit restore", {
|
|
368
|
+
state: health?.state,
|
|
369
|
+
current: health?.currentMetric,
|
|
370
|
+
});
|
|
371
|
+
results.healthFinal = {
|
|
372
|
+
state: health?.state,
|
|
373
|
+
current: health?.currentMetric,
|
|
374
|
+
message: health?.message,
|
|
375
|
+
};
|
|
376
|
+
} catch (e) {
|
|
377
|
+
results.deficitExec = isAureonError(e) ? e.message : String(e);
|
|
378
|
+
log("deficit restore exec", results.deficitExec);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} catch (e) {
|
|
382
|
+
results.deficitPlan = isAureonError(e) ? e.message : String(e);
|
|
383
|
+
log("deficit plan error", results.deficitPlan);
|
|
384
|
+
}
|
|
385
|
+
results.deficitOk = deficitOk;
|
|
386
|
+
|
|
387
|
+
// WETH 20% without stable — soft expectation
|
|
388
|
+
const wethObj = await aureon.createObjective({
|
|
389
|
+
name: `E2E 20% WETH ${Date.now()}`,
|
|
390
|
+
kind: "balanced_portfolio",
|
|
391
|
+
targetWeight: 0.2,
|
|
392
|
+
tolerance: 0.02,
|
|
393
|
+
targetSymbol: "WETH",
|
|
394
|
+
priority: "medium",
|
|
395
|
+
automationMode: "auto",
|
|
396
|
+
});
|
|
397
|
+
await aureon.refreshWatchdog();
|
|
398
|
+
try {
|
|
399
|
+
const plan = await aureon.getRestorePlan(wethObj.id);
|
|
400
|
+
results.wethPlan = { kind: plan.kind, message: plan.message };
|
|
401
|
+
log("WETH plan", results.wethPlan);
|
|
402
|
+
} catch (e) {
|
|
403
|
+
const msg = isAureonError(e) ? e.message : String(e);
|
|
404
|
+
results.wethPlanBlocked = msg;
|
|
405
|
+
results.wethNeedsStable = /stable|cash|stock/i.test(msg);
|
|
406
|
+
log("WETH plan blocked (ok if needs stable)", msg);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Withdraw WETH smoke
|
|
410
|
+
snap = await aureon.getVault();
|
|
411
|
+
const wethBal = snap.balances.find((b) => b.symbol.toUpperCase() === "WETH");
|
|
412
|
+
if (wethBal && wethBal.quantity > 0.00005) {
|
|
413
|
+
const prep = await aureon.prepareVaultWithdraw({
|
|
414
|
+
symbol: "WETH",
|
|
415
|
+
amount: "0.00005",
|
|
416
|
+
});
|
|
417
|
+
for (const step of prep.steps) {
|
|
418
|
+
const hash = await walletClient.sendTransaction({
|
|
419
|
+
account,
|
|
420
|
+
chain,
|
|
421
|
+
to: step.to as Address,
|
|
422
|
+
data: step.data as Hex,
|
|
423
|
+
value: 0n,
|
|
424
|
+
});
|
|
425
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
426
|
+
log("withdraw WETH", hash);
|
|
427
|
+
}
|
|
428
|
+
results.withdrawWeth = true;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
console.log("\n========== SUMMARY ==========");
|
|
432
|
+
console.log(JSON.stringify(results, null, 2));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
main().catch((e) => {
|
|
436
|
+
console.error(e instanceof Error ? e.message : e);
|
|
437
|
+
process.exitCode = 1;
|
|
438
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Follow-up: force TSLA underweight → Auto should Sell WETH → Buy TSLA.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { join, dirname } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import {
|
|
8
|
+
createPublicClient,
|
|
9
|
+
createWalletClient,
|
|
10
|
+
http,
|
|
11
|
+
type Address,
|
|
12
|
+
type Hex,
|
|
13
|
+
} from "viem";
|
|
14
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
15
|
+
import {
|
|
16
|
+
createAureonClient,
|
|
17
|
+
createSessionTokenProvider,
|
|
18
|
+
isAureonError,
|
|
19
|
+
LOCAL_API_BASE_URL,
|
|
20
|
+
} from "../../src/index.js";
|
|
21
|
+
|
|
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;
|
|
35
|
+
const account = privateKeyToAccount(key);
|
|
36
|
+
const rpc = env.AUREON_RPC_URL!;
|
|
37
|
+
const chainId = Number(env.AUREON_CHAIN_ID || 46630);
|
|
38
|
+
const publicClient = createPublicClient({ transport: http(rpc) });
|
|
39
|
+
const walletClient = createWalletClient({ account, transport: http(rpc) });
|
|
40
|
+
const chain = {
|
|
41
|
+
id: chainId,
|
|
42
|
+
name: "rh",
|
|
43
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
44
|
+
rpcUrls: { default: { http: [rpc] } },
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
const session = createSessionTokenProvider(null);
|
|
48
|
+
const aureon = createAureonClient({
|
|
49
|
+
baseUrl: LOCAL_API_BASE_URL,
|
|
50
|
+
apiKey: env.AUREON_API_KEYS!.split(",")[0]!.trim(),
|
|
51
|
+
getAccessToken: session.getAccessToken,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const { message } = await aureon.getAuthNonce(account.address);
|
|
55
|
+
const signature = await walletClient.signMessage({ account, message });
|
|
56
|
+
session.setToken(
|
|
57
|
+
(
|
|
58
|
+
await aureon.verifyWallet({
|
|
59
|
+
address: account.address,
|
|
60
|
+
message,
|
|
61
|
+
signature,
|
|
62
|
+
})
|
|
63
|
+
).token
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const vault = await aureon.getVault();
|
|
67
|
+
const tsla = vault.balances.find((b) => b.symbol.toUpperCase() === "TSLA");
|
|
68
|
+
console.log(
|
|
69
|
+
"vault before underrun",
|
|
70
|
+
vault.balances.filter((b) => b.quantity > 0).map((b) => ({ s: b.symbol, q: b.quantity }))
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (tsla && tsla.quantity > 0.001) {
|
|
74
|
+
const amt = (tsla.quantity - 0.000001).toFixed(8);
|
|
75
|
+
const prep = await aureon.prepareVaultWithdraw({ symbol: "TSLA", amount: amt });
|
|
76
|
+
for (const step of prep.steps) {
|
|
77
|
+
const hash = await walletClient.sendTransaction({
|
|
78
|
+
account,
|
|
79
|
+
chain,
|
|
80
|
+
to: step.to as Address,
|
|
81
|
+
data: step.data as Hex,
|
|
82
|
+
value: 0n,
|
|
83
|
+
});
|
|
84
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
85
|
+
console.log("withdrew almost all TSLA", hash);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const d = await aureon.prepareVaultDeposit({ symbol: "ETH", amount: "0.0005" });
|
|
90
|
+
for (const step of d.steps) {
|
|
91
|
+
const hash = await walletClient.sendTransaction({
|
|
92
|
+
account,
|
|
93
|
+
chain,
|
|
94
|
+
to: step.to as Address,
|
|
95
|
+
data: step.data as Hex,
|
|
96
|
+
value: BigInt(step.value),
|
|
97
|
+
});
|
|
98
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
99
|
+
console.log("depositETH", hash);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const objs = await aureon.listObjectives();
|
|
103
|
+
const obj =
|
|
104
|
+
objs.find((o) => o.name.includes("E2E 20% TSLA")) ?? objs[0];
|
|
105
|
+
if (!obj) throw new Error("no objective");
|
|
106
|
+
console.log("using objective", obj.id, obj.name);
|
|
107
|
+
|
|
108
|
+
await aureon.refreshWatchdog();
|
|
109
|
+
const h = (await aureon.getHealth(obj.id))[0]!;
|
|
110
|
+
console.log("health", {
|
|
111
|
+
state: h.state,
|
|
112
|
+
current: h.currentMetric,
|
|
113
|
+
message: h.message,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const plan = await aureon.getRestorePlan(obj.id);
|
|
118
|
+
console.log("UNDERRUN PLAN", plan.message, {
|
|
119
|
+
kind: plan.kind,
|
|
120
|
+
sell: (plan as { sellSymbol?: string }).sellSymbol,
|
|
121
|
+
buy: (plan as { buySymbol?: string }).buySymbol,
|
|
122
|
+
});
|
|
123
|
+
const receipt = await aureon.restoreObjective(obj.id);
|
|
124
|
+
console.log("UNDERRUN EXEC", receipt.status, receipt.transactionHash);
|
|
125
|
+
await aureon.refreshWatchdog();
|
|
126
|
+
const h2 = (await aureon.getHealth(obj.id))[0]!;
|
|
127
|
+
console.log("health after buy", {
|
|
128
|
+
state: h2.state,
|
|
129
|
+
current: h2.currentMetric,
|
|
130
|
+
message: h2.message,
|
|
131
|
+
});
|
|
132
|
+
} catch (e) {
|
|
133
|
+
console.log("plan/exec", isAureonError(e) ? e.message : e);
|
|
134
|
+
}
|