balchemy 0.1.21 → 0.1.23
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 +2 -2
- package/dist/agent-store.d.ts +1 -1
- package/dist/agent-store.d.ts.map +1 -1
- package/dist/agent-store.js +3 -5
- package/dist/agent-store.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +116 -54
- package/dist/index.js.map +1 -1
- package/dist/tui/AgentBridge.d.ts +10 -0
- package/dist/tui/AgentBridge.d.ts.map +1 -1
- package/dist/tui/AgentBridge.js +286 -34
- package/dist/tui/AgentBridge.js.map +1 -1
- package/dist/tui/App.js +1 -1
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/ChatAgent.d.ts +9 -0
- package/dist/tui/ChatAgent.d.ts.map +1 -1
- package/dist/tui/ChatAgent.js +101 -6
- package/dist/tui/ChatAgent.js.map +1 -1
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +21 -4
- package/dist/wizard.js.map +1 -1
- package/package.json +2 -2
- package/templates/agent.config.yaml +0 -71
package/dist/tui/AgentBridge.js
CHANGED
|
@@ -4,7 +4,7 @@ import { AgentLoop, connectMcp } from "@balchemyai/agent-sdk";
|
|
|
4
4
|
import { ChatAgent } from "./ChatAgent.js";
|
|
5
5
|
import { buildSetupRequiredMessage, getInitialSetupStep, isSetupReady, parseNetworkSelection, parseSetupStatusSnapshot, } from "./setup-guidance.js";
|
|
6
6
|
import { buildStrategyUpdateArgs } from "./session-sync.js";
|
|
7
|
-
import { loadAgent } from "../agent-store.js";
|
|
7
|
+
import { loadAgent, saveAgent } from "../agent-store.js";
|
|
8
8
|
/** Truncate verbose API errors (429 JSON blobs, stack traces) to a readable one-liner. */
|
|
9
9
|
function truncateError(raw) {
|
|
10
10
|
// Extract HTTP status code if present
|
|
@@ -53,6 +53,23 @@ function parseJsonObject(text) {
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
function parseJsonObjectLoose(text) {
|
|
57
|
+
const direct = parseJsonObject(text);
|
|
58
|
+
if (direct)
|
|
59
|
+
return direct;
|
|
60
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
61
|
+
if (fenced?.[1]) {
|
|
62
|
+
const parsed = parseJsonObject(fenced[1].trim());
|
|
63
|
+
if (parsed)
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
const first = text.indexOf("{");
|
|
67
|
+
const last = text.lastIndexOf("}");
|
|
68
|
+
if (first >= 0 && last > first) {
|
|
69
|
+
return parseJsonObject(text.slice(first, last + 1));
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
56
73
|
function asRecord(value) {
|
|
57
74
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
58
75
|
? value
|
|
@@ -108,6 +125,25 @@ function mergeWallets(...groups) {
|
|
|
108
125
|
}
|
|
109
126
|
return wallets;
|
|
110
127
|
}
|
|
128
|
+
function mergeLatestWallets(...groups) {
|
|
129
|
+
const latest = new Map();
|
|
130
|
+
for (const group of groups) {
|
|
131
|
+
for (const wallet of group) {
|
|
132
|
+
latest.set(wallet.chain, wallet);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const ordered = [];
|
|
136
|
+
const solana = latest.get("solana");
|
|
137
|
+
const base = latest.get("base");
|
|
138
|
+
if (solana)
|
|
139
|
+
ordered.push(solana);
|
|
140
|
+
if (base)
|
|
141
|
+
ordered.push(base);
|
|
142
|
+
return ordered;
|
|
143
|
+
}
|
|
144
|
+
function walletAddressLabel(chain) {
|
|
145
|
+
return chain === "solana" ? "Solana trading wallet" : "Base trading wallet";
|
|
146
|
+
}
|
|
111
147
|
function readRecords(value) {
|
|
112
148
|
if (!Array.isArray(value))
|
|
113
149
|
return [];
|
|
@@ -278,8 +314,69 @@ function parseTradeLimits(value) {
|
|
|
278
314
|
...(maxTradeUsd !== undefined && Number.isFinite(maxTradeUsd) ? { maxTradeUsd } : {}),
|
|
279
315
|
};
|
|
280
316
|
}
|
|
317
|
+
function readStringArray(value) {
|
|
318
|
+
if (!Array.isArray(value))
|
|
319
|
+
return [];
|
|
320
|
+
return value.flatMap((item) => typeof item === "string" && item.trim() ? [item.trim()] : []);
|
|
321
|
+
}
|
|
322
|
+
function parseStrategyReviewResponse(text) {
|
|
323
|
+
const parsed = parseJsonObjectLoose(text);
|
|
324
|
+
if (!parsed)
|
|
325
|
+
return null;
|
|
326
|
+
const ready = parsed.ready === true;
|
|
327
|
+
const summary = firstString(parsed.summary, parsed.strategy, parsed.normalizedStrategy) ?? "";
|
|
328
|
+
const followUp = firstString(parsed.followUp, parsed.follow_up, parsed.question) ?? "";
|
|
329
|
+
const missing = readStringArray(parsed.missing);
|
|
330
|
+
const limits = parseTradeLimits(summary);
|
|
331
|
+
const maxTradeSol = firstNumber(parsed, ["maxTradeSol", "max_trade_sol"]) ?? limits.maxTradeSol;
|
|
332
|
+
const maxTradeUsd = firstNumber(parsed, ["maxTradeUsd", "max_trade_usd"]) ?? limits.maxTradeUsd;
|
|
333
|
+
return {
|
|
334
|
+
ready,
|
|
335
|
+
summary,
|
|
336
|
+
followUp,
|
|
337
|
+
missing,
|
|
338
|
+
...(maxTradeSol !== undefined ? { maxTradeSol } : {}),
|
|
339
|
+
...(maxTradeUsd !== undefined ? { maxTradeUsd } : {}),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function fallbackStrategyReview(chain, notes) {
|
|
343
|
+
const summary = notes.join("\n").trim();
|
|
344
|
+
const limits = parseTradeLimits(summary);
|
|
345
|
+
const lower = summary.toLowerCase();
|
|
346
|
+
const missing = [];
|
|
347
|
+
if (chain === "solana" && limits.maxTradeSol === undefined) {
|
|
348
|
+
missing.push("max SOL per trade");
|
|
349
|
+
}
|
|
350
|
+
if (chain === "base" && limits.maxTradeUsd === undefined) {
|
|
351
|
+
missing.push("max USD/USDC per trade");
|
|
352
|
+
}
|
|
353
|
+
if (!/(entry|filter|hacim|volume|mcap|market cap|holder|liquidity|giris|giriş)/i.test(summary)) {
|
|
354
|
+
missing.push("entry filters");
|
|
355
|
+
}
|
|
356
|
+
if (!/(stop|loss|zarar|sl\b)/i.test(lower)) {
|
|
357
|
+
missing.push("stop loss");
|
|
358
|
+
}
|
|
359
|
+
if (!/(take profit|profit|kar|sat|sell|tp\b|x\b)/i.test(lower)) {
|
|
360
|
+
missing.push("take profit");
|
|
361
|
+
}
|
|
362
|
+
if (!/(max.*position|position|pozisyon|concurrent|ayn[ıi] anda)/i.test(lower)) {
|
|
363
|
+
missing.push("max open positions");
|
|
364
|
+
}
|
|
365
|
+
if (!/(avoid|kaçın|kacin|yasak|alma|blacklist|tokenleri|categories|kategori)/i.test(lower)) {
|
|
366
|
+
missing.push("avoid rules");
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
ready: summary.length >= 20 && missing.length === 0,
|
|
370
|
+
summary,
|
|
371
|
+
missing,
|
|
372
|
+
followUp: missing.length > 0
|
|
373
|
+
? `I still need this before live setup: ${missing.join(", ")}. Send only the missing parts; I will merge them.`
|
|
374
|
+
: "",
|
|
375
|
+
...limits,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
281
378
|
function formatChains(chains) {
|
|
282
|
-
return chains.map((chain) => chain === "solana" ? "Solana
|
|
379
|
+
return chains.map((chain) => chain === "solana" ? "Solana" : "Base").join(" + ");
|
|
283
380
|
}
|
|
284
381
|
function chainTitle(chain) {
|
|
285
382
|
return chain === "solana" ? "Solana" : "Base";
|
|
@@ -319,9 +416,11 @@ export class AgentBridge {
|
|
|
319
416
|
pendingLoopConfig = null;
|
|
320
417
|
setupPollTimer = null;
|
|
321
418
|
setupFlow = null;
|
|
419
|
+
knownWallets = [];
|
|
322
420
|
constructor(config, setters) {
|
|
323
421
|
this.config = config;
|
|
324
422
|
this.setters = setters;
|
|
423
|
+
this.knownWallets = this.loadStoredWallets();
|
|
325
424
|
// Replay-protected fetch for MCP calls
|
|
326
425
|
this.replayFetch = async (url, init) => {
|
|
327
426
|
const headers = new Headers(init?.headers);
|
|
@@ -435,6 +534,7 @@ export class AgentBridge {
|
|
|
435
534
|
provider: resolveProviderLabel(this.config.llmProvider, this.config.llmBaseUrl),
|
|
436
535
|
model: this.config.llmModel,
|
|
437
536
|
}));
|
|
537
|
+
this.syncKnownWalletsToStatus();
|
|
438
538
|
// Input is ready now. Setup must be deterministic; do not let an LLM drive it.
|
|
439
539
|
if (setupComplete) {
|
|
440
540
|
void this.greet(true);
|
|
@@ -452,7 +552,7 @@ export class AgentBridge {
|
|
|
452
552
|
}
|
|
453
553
|
try {
|
|
454
554
|
const prompt = "Check my portfolio and status, then greet me. Tell me my balance, wallets, and current strategy. Keep it brief and do not narrate tool calls.";
|
|
455
|
-
const reply = await this.chatAgent.chat(prompt, (name, result) => {
|
|
555
|
+
const reply = await this.chatAgent.chat(this.withRuntimeContext(prompt), (name, result) => {
|
|
456
556
|
this.applyToolResult(name, result);
|
|
457
557
|
if (name !== "setup_agent") {
|
|
458
558
|
this.addSystemMessage(`Tool: ${name}`);
|
|
@@ -498,7 +598,7 @@ export class AgentBridge {
|
|
|
498
598
|
if (!this.chatAgent)
|
|
499
599
|
return;
|
|
500
600
|
try {
|
|
501
|
-
const reply = await this.chatAgent.chat(text, (name, result) => {
|
|
601
|
+
const reply = await this.chatAgent.chat(this.withRuntimeContext(text), (name, result) => {
|
|
502
602
|
this.applyToolResult(name, result);
|
|
503
603
|
this.addSystemMessage(`Tool: ${name}`);
|
|
504
604
|
}, (preview) => this.setters.confirmTrade(preview));
|
|
@@ -555,17 +655,65 @@ export class AgentBridge {
|
|
|
555
655
|
case "networks":
|
|
556
656
|
return "Which networks should this agent trade on: Solana, Base, or both?";
|
|
557
657
|
case "solana-recovery-wallet":
|
|
558
|
-
return "Paste your Solana recovery/withdrawal wallet address. This is separate from the Solana trading wallet I create for execution.";
|
|
658
|
+
return "Paste your Solana recovery/withdrawal wallet address. This is where SOL/SPL withdrawals go, and it is separate from the Solana trading wallet I create for execution.";
|
|
559
659
|
case "solana-recovery-wallet-confirm":
|
|
560
660
|
return "Paste the same Solana recovery/withdrawal wallet again to confirm it.";
|
|
561
661
|
case "slippage":
|
|
562
662
|
return "Set slippage in percent or bps. Examples: 1% = 100 bps, 3% = 300 bps, 5% = 500 bps.";
|
|
563
663
|
case "strategy":
|
|
564
|
-
return "
|
|
664
|
+
return "Describe the strategy in natural language. I will review it with AI, ask for missing risk details, and only then ask you to confirm.";
|
|
565
665
|
case "strategy-confirm":
|
|
566
666
|
return "Confirm with 'yes'/'evet', or say 'no'/'hayir' to rewrite the strategy.";
|
|
567
667
|
case "subscriptions":
|
|
568
|
-
return "Create monitoring subscriptions now? For Solana new launches I can enable
|
|
668
|
+
return "Create monitoring subscriptions now? For Solana new launches I can enable launch monitoring. Answer yes/evet or no/hayir.";
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
setupPromptForStrategyChain(chain) {
|
|
672
|
+
const maxTrade = chain === "solana"
|
|
673
|
+
? "max SOL per trade"
|
|
674
|
+
: "max USD/USDC per trade";
|
|
675
|
+
return [
|
|
676
|
+
`${chainTitle(chain)} strategy:`,
|
|
677
|
+
"Describe what you want the agent to trade and how strict it must be.",
|
|
678
|
+
`Include ${maxTrade}, entry filters, stop loss, take profit, max open positions, and avoid rules.`,
|
|
679
|
+
"You can write it messy; I will ask follow-ups if anything critical is missing.",
|
|
680
|
+
].join("\n");
|
|
681
|
+
}
|
|
682
|
+
async reviewStrategyWithAi(chain, selectedChains, notes) {
|
|
683
|
+
const fallback = fallbackStrategyReview(chain, notes);
|
|
684
|
+
if (!this.chatAgent)
|
|
685
|
+
return fallback;
|
|
686
|
+
const unit = chain === "solana" ? "SOL" : "USD/USDC";
|
|
687
|
+
const systemPrompt = [
|
|
688
|
+
"You are the strategy-coaching layer inside Balchemy CLI setup.",
|
|
689
|
+
"You do not call tools. You only decide whether the user's strategy is safe enough to configure.",
|
|
690
|
+
"Return strict JSON only. No markdown.",
|
|
691
|
+
"Required JSON shape:",
|
|
692
|
+
'{"ready":boolean,"summary":"string","missing":["string"],"followUp":"string","maxTradeSol":number|null,"maxTradeUsd":number|null}',
|
|
693
|
+
`Current chain: ${chainTitle(chain)}. Required max trade unit: ${unit}.`,
|
|
694
|
+
`Selected chains: ${formatChains(selectedChains)}.`,
|
|
695
|
+
"A ready strategy must include max trade size, entry filters, stop loss, take profit, max open positions, and avoid/blacklist rules.",
|
|
696
|
+
"If anything critical is missing, ready=false and followUp must ask only for the missing items in the same language as the user.",
|
|
697
|
+
"If ready=true, summary must be a faithful normalized version of all user notes with chain-specific limits preserved.",
|
|
698
|
+
].join("\n");
|
|
699
|
+
const userMessage = [
|
|
700
|
+
`Chain: ${chainTitle(chain)}`,
|
|
701
|
+
"User notes:",
|
|
702
|
+
notes.map((note, index) => `${index + 1}. ${note}`).join("\n"),
|
|
703
|
+
].join("\n\n");
|
|
704
|
+
try {
|
|
705
|
+
const response = await this.chatAgent.completeText(systemPrompt, userMessage);
|
|
706
|
+
const parsed = parseStrategyReviewResponse(response);
|
|
707
|
+
if (!parsed)
|
|
708
|
+
return fallback;
|
|
709
|
+
if (!parsed.ready && !parsed.followUp)
|
|
710
|
+
return fallback;
|
|
711
|
+
if (parsed.ready && parsed.summary.trim().length < 20)
|
|
712
|
+
return fallback;
|
|
713
|
+
return parsed;
|
|
714
|
+
}
|
|
715
|
+
catch (_error) {
|
|
716
|
+
return fallback;
|
|
569
717
|
}
|
|
570
718
|
}
|
|
571
719
|
async handleSetupInput(text) {
|
|
@@ -596,9 +744,12 @@ export class AgentBridge {
|
|
|
596
744
|
walletAddressConfirm: wallet,
|
|
597
745
|
});
|
|
598
746
|
const masterKey = extractMasterKey(structured);
|
|
747
|
+
if (masterKey) {
|
|
748
|
+
this.persistMasterKey(masterKey);
|
|
749
|
+
}
|
|
599
750
|
this.setupFlow = { step: "networks" };
|
|
600
751
|
this.addAgentMessage(masterKey
|
|
601
|
-
? `Developer wallet bound.\n\
|
|
752
|
+
? `Developer wallet bound.\n\nMaster key saved encrypted locally and shown here so you can copy it externally:\n${masterKey}\n\nIt is shown once in real environments.\n\n${this.setupPromptFor("networks")}`
|
|
602
753
|
: `Developer wallet bound.\n\n${this.setupPromptFor("networks")}`);
|
|
603
754
|
return;
|
|
604
755
|
}
|
|
@@ -611,7 +762,10 @@ export class AgentBridge {
|
|
|
611
762
|
const walletLines = [];
|
|
612
763
|
for (const chain of chains) {
|
|
613
764
|
const structured = await this.callSetupAgent({ action: "create_wallet", chain });
|
|
614
|
-
const
|
|
765
|
+
const wallet = readWallet({ ...structured, chain });
|
|
766
|
+
if (wallet)
|
|
767
|
+
this.upsertWallet(wallet);
|
|
768
|
+
const address = wallet?.address ?? firstString(structured.address, asRecord(structured.wallet)?.address);
|
|
615
769
|
walletLines.push(`${chainTitle(chain)} trading wallet: ${address ?? "(created)"}`);
|
|
616
770
|
}
|
|
617
771
|
this.setupFlow = {
|
|
@@ -653,7 +807,7 @@ export class AgentBridge {
|
|
|
653
807
|
this.addSystemMessage(`Solana recovery wallet stored locally for this setup. MCP did not bind it directly: ${err instanceof Error ? err.message : String(err)}`);
|
|
654
808
|
}
|
|
655
809
|
this.setupFlow = { ...flow, step: "slippage", solanaRecoveryWallet: wallet };
|
|
656
|
-
this.addAgentMessage(`Solana recovery wallet confirmed:\n${wallet}\n\n${this.setupPromptFor("slippage")}`);
|
|
810
|
+
this.addAgentMessage(`Solana recovery/withdrawal wallet confirmed:\n${wallet}\n\n${this.setupPromptFor("slippage")}`);
|
|
657
811
|
return;
|
|
658
812
|
}
|
|
659
813
|
if (flow.step === "slippage") {
|
|
@@ -666,30 +820,69 @@ export class AgentBridge {
|
|
|
666
820
|
const selectedChains = flow.selectedChains ?? ["solana"];
|
|
667
821
|
const currentStrategyChain = nextStrategyChain(selectedChains, {}) ?? selectedChains[0];
|
|
668
822
|
this.setupFlow = { ...flow, step: "strategy", slippageBps: bps, currentStrategyChain, chainStrategies: {} };
|
|
669
|
-
this.addAgentMessage(`Slippage set to ${bps} bps.\n\n${
|
|
823
|
+
this.addAgentMessage(`Slippage set to ${bps} bps.\n\n${this.setupPromptForStrategyChain(currentStrategyChain)}`);
|
|
670
824
|
return;
|
|
671
825
|
}
|
|
672
826
|
if (flow.step === "strategy") {
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
827
|
+
const strategyNote = text.trim();
|
|
828
|
+
const chain = flow.currentStrategyChain ?? (flow.selectedChains ?? ["solana"])[0];
|
|
829
|
+
if (strategyNote.length < 8) {
|
|
830
|
+
this.addAgentMessage("That is too short for live setup. Add trade size, entry filters, exits, max positions, or avoid rules.");
|
|
676
831
|
return;
|
|
677
832
|
}
|
|
833
|
+
const previousNotes = flow.strategyNotes?.[chain] ?? [];
|
|
834
|
+
const notes = [...previousNotes, strategyNote];
|
|
835
|
+
const selectedChains = flow.selectedChains ?? [chain];
|
|
836
|
+
const strategyNotes = {
|
|
837
|
+
...(flow.strategyNotes ?? {}),
|
|
838
|
+
[chain]: notes,
|
|
839
|
+
};
|
|
840
|
+
this.setupFlow = { ...flow, strategyNotes };
|
|
841
|
+
const review = await this.reviewStrategyWithAi(chain, selectedChains, notes);
|
|
842
|
+
if (!review.ready) {
|
|
843
|
+
const missingLine = review.missing.length > 0 ? `\n\nMissing: ${review.missing.join(", ")}` : "";
|
|
844
|
+
this.addAgentMessage(`${review.followUp || "I need a little more detail before this can go live."}${missingLine}`);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
const strategyText = review.summary.trim() || notes.join("\n");
|
|
848
|
+
const parsedLimits = parseTradeLimits(strategyText);
|
|
849
|
+
const maxTradeSol = chain === "solana"
|
|
850
|
+
? review.maxTradeSol ?? parsedLimits.maxTradeSol ?? flow.maxTradeSol
|
|
851
|
+
: flow.maxTradeSol;
|
|
852
|
+
const maxTradeUsd = chain === "base"
|
|
853
|
+
? review.maxTradeUsd ?? parsedLimits.maxTradeUsd ?? flow.maxTradeUsd
|
|
854
|
+
: flow.maxTradeUsd;
|
|
678
855
|
const chainStrategies = {
|
|
679
856
|
...(flow.chainStrategies ?? {}),
|
|
680
|
-
|
|
857
|
+
[chain]: strategyText,
|
|
681
858
|
};
|
|
682
|
-
const selectedChains = flow.selectedChains ?? (flow.currentStrategyChain ? [flow.currentStrategyChain] : ["solana"]);
|
|
683
859
|
const nextChain = nextStrategyChain(selectedChains, chainStrategies);
|
|
684
860
|
if (nextChain) {
|
|
685
|
-
this.setupFlow = {
|
|
686
|
-
|
|
861
|
+
this.setupFlow = {
|
|
862
|
+
...flow,
|
|
863
|
+
chainStrategies,
|
|
864
|
+
currentStrategyChain: nextChain,
|
|
865
|
+
strategyNotes,
|
|
866
|
+
...(maxTradeSol !== undefined ? { maxTradeSol } : {}),
|
|
867
|
+
...(maxTradeUsd !== undefined ? { maxTradeUsd } : {}),
|
|
868
|
+
};
|
|
869
|
+
this.addAgentMessage(`${chainTitle(chain)} strategy drafted with AI review.\n\n${this.setupPromptForStrategyChain(nextChain)}`);
|
|
687
870
|
return;
|
|
688
871
|
}
|
|
689
872
|
const combinedStrategy = buildCombinedStrategy({ ...flow, chainStrategies });
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
873
|
+
const combinedLimits = parseTradeLimits(combinedStrategy);
|
|
874
|
+
const finalMaxTradeSol = maxTradeSol ?? combinedLimits.maxTradeSol;
|
|
875
|
+
const finalMaxTradeUsd = maxTradeUsd ?? combinedLimits.maxTradeUsd;
|
|
876
|
+
this.setupFlow = {
|
|
877
|
+
...flow,
|
|
878
|
+
chainStrategies,
|
|
879
|
+
strategyNotes,
|
|
880
|
+
step: "strategy-confirm",
|
|
881
|
+
strategyText: combinedStrategy,
|
|
882
|
+
...(finalMaxTradeSol !== undefined ? { maxTradeSol: finalMaxTradeSol } : {}),
|
|
883
|
+
...(finalMaxTradeUsd !== undefined ? { maxTradeUsd: finalMaxTradeUsd } : {}),
|
|
884
|
+
};
|
|
885
|
+
this.addAgentMessage(`AI-reviewed strategy draft:\n\n${combinedStrategy}\n\nMax SOL/trade: ${finalMaxTradeSol ?? "not specified"}\nMax USD/trade: ${finalMaxTradeUsd ?? "not specified"}\n\n${this.setupPromptFor("strategy-confirm")}`);
|
|
693
886
|
return;
|
|
694
887
|
}
|
|
695
888
|
if (flow.step === "strategy-confirm") {
|
|
@@ -703,7 +896,7 @@ export class AgentBridge {
|
|
|
703
896
|
chainStrategies: {},
|
|
704
897
|
currentStrategyChain: (flow.selectedChains ?? ["solana"])[0],
|
|
705
898
|
};
|
|
706
|
-
this.addAgentMessage(this.
|
|
899
|
+
this.addAgentMessage(this.setupPromptForStrategyChain((flow.selectedChains ?? ["solana"])[0]));
|
|
707
900
|
return;
|
|
708
901
|
}
|
|
709
902
|
if (!isAffirmative(text)) {
|
|
@@ -712,7 +905,7 @@ export class AgentBridge {
|
|
|
712
905
|
}
|
|
713
906
|
if (!flow.strategyText) {
|
|
714
907
|
this.setupFlow = { ...flow, step: "strategy" };
|
|
715
|
-
this.addAgentMessage(this.
|
|
908
|
+
this.addAgentMessage(this.setupPromptForStrategyChain(flow.currentStrategyChain ?? (flow.selectedChains ?? ["solana"])[0]));
|
|
716
909
|
return;
|
|
717
910
|
}
|
|
718
911
|
await this.callSetupAgent({
|
|
@@ -777,18 +970,16 @@ export class AgentBridge {
|
|
|
777
970
|
return;
|
|
778
971
|
if (name === "setup_agent") {
|
|
779
972
|
const structured = asRecord(parsed.structured) ?? parsed;
|
|
780
|
-
const wallets = collectWallets(structured);
|
|
781
|
-
if (wallets.length > 0) {
|
|
782
|
-
this.setters.setStatus((prev) => ({
|
|
783
|
-
...prev,
|
|
784
|
-
wallets: mergeWallets(prev.wallets, wallets),
|
|
785
|
-
}));
|
|
786
|
-
}
|
|
787
973
|
if (structured.action === "create_wallet") {
|
|
788
974
|
const chain = normalizeChain(structured.chain);
|
|
789
|
-
const
|
|
790
|
-
if (
|
|
791
|
-
this.upsertWallet(
|
|
975
|
+
const wallet = chain ? readWallet({ ...structured, chain }) : null;
|
|
976
|
+
if (wallet) {
|
|
977
|
+
this.upsertWallet(wallet);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
else if (structured.action === "get_status" || structured.action === "status") {
|
|
981
|
+
for (const wallet of collectWallets(structured)) {
|
|
982
|
+
this.upsertWallet(wallet);
|
|
792
983
|
}
|
|
793
984
|
}
|
|
794
985
|
return;
|
|
@@ -810,20 +1001,81 @@ export class AgentBridge {
|
|
|
810
1001
|
?? (data ? firstNumber(data, ["totalValueUsd", "portfolioValueUsd", "portfolio_value_usd", "total_value_usd", "valueUsd"]) : undefined)
|
|
811
1002
|
?? summed.balanceUsd;
|
|
812
1003
|
const positions = collectPositions(data ?? snapshot);
|
|
1004
|
+
for (const wallet of wallets) {
|
|
1005
|
+
this.upsertWallet(wallet);
|
|
1006
|
+
}
|
|
813
1007
|
this.setters.setStatus((prev) => ({
|
|
814
1008
|
...prev,
|
|
815
1009
|
...(balanceSol !== undefined ? { balanceSol } : {}),
|
|
816
1010
|
...(balanceUsd !== undefined ? { balanceUsd } : {}),
|
|
817
|
-
...(wallets.length > 0 ? { wallets: mergeWallets(prev.wallets, wallets) } : {}),
|
|
818
1011
|
...(positions ? { activeTrades: positions } : {}),
|
|
819
1012
|
}));
|
|
820
1013
|
}
|
|
821
1014
|
upsertWallet(wallet) {
|
|
1015
|
+
this.knownWallets = mergeLatestWallets(this.knownWallets, [wallet]);
|
|
1016
|
+
this.persistWallet(wallet);
|
|
822
1017
|
this.setters.setStatus((prev) => {
|
|
823
1018
|
const wallets = prev.wallets.filter((existing) => existing.chain !== wallet.chain);
|
|
824
1019
|
return { ...prev, wallets: [...wallets, wallet] };
|
|
825
1020
|
});
|
|
826
1021
|
}
|
|
1022
|
+
loadStoredWallets() {
|
|
1023
|
+
const agent = loadAgent();
|
|
1024
|
+
if (!agent || agent.publicId !== this.config.publicId)
|
|
1025
|
+
return [];
|
|
1026
|
+
const wallets = [];
|
|
1027
|
+
if (agent.wallets?.solana)
|
|
1028
|
+
wallets.push({ chain: "solana", address: agent.wallets.solana });
|
|
1029
|
+
if (agent.wallets?.base)
|
|
1030
|
+
wallets.push({ chain: "base", address: agent.wallets.base });
|
|
1031
|
+
return wallets;
|
|
1032
|
+
}
|
|
1033
|
+
syncKnownWalletsToStatus() {
|
|
1034
|
+
if (this.knownWallets.length === 0)
|
|
1035
|
+
return;
|
|
1036
|
+
this.setters.setStatus((prev) => ({
|
|
1037
|
+
...prev,
|
|
1038
|
+
wallets: mergeLatestWallets(prev.wallets, this.knownWallets),
|
|
1039
|
+
}));
|
|
1040
|
+
}
|
|
1041
|
+
persistActiveAgent(update) {
|
|
1042
|
+
const agent = loadAgent();
|
|
1043
|
+
if (!agent || agent.publicId !== this.config.publicId)
|
|
1044
|
+
return;
|
|
1045
|
+
saveAgent(update(agent));
|
|
1046
|
+
}
|
|
1047
|
+
persistMasterKey(masterKey) {
|
|
1048
|
+
this.persistActiveAgent((agent) => ({
|
|
1049
|
+
...agent,
|
|
1050
|
+
masterKey,
|
|
1051
|
+
}));
|
|
1052
|
+
}
|
|
1053
|
+
persistWallet(wallet) {
|
|
1054
|
+
this.persistActiveAgent((agent) => {
|
|
1055
|
+
const wallets = { ...(agent.wallets ?? {}) };
|
|
1056
|
+
wallets[wallet.chain] = wallet.address;
|
|
1057
|
+
return {
|
|
1058
|
+
...agent,
|
|
1059
|
+
wallets,
|
|
1060
|
+
};
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
buildRuntimeContext() {
|
|
1064
|
+
if (this.knownWallets.length === 0)
|
|
1065
|
+
return "";
|
|
1066
|
+
return [
|
|
1067
|
+
"Known Balchemy runtime context from local encrypted CLI state:",
|
|
1068
|
+
...this.knownWallets.map((wallet) => `${walletAddressLabel(wallet.chain)}: ${wallet.address}`),
|
|
1069
|
+
"Funding rule: fund the Solana trading wallet with SOL. Fund the Base trading wallet with ETH on Base for gas and Base-chain capital as required.",
|
|
1070
|
+
"If the user asks where to fund, answer from these addresses.",
|
|
1071
|
+
].join("\n");
|
|
1072
|
+
}
|
|
1073
|
+
withRuntimeContext(userMessage) {
|
|
1074
|
+
const context = this.buildRuntimeContext();
|
|
1075
|
+
if (!context)
|
|
1076
|
+
return userMessage;
|
|
1077
|
+
return `${context}\n\nUser message:\n${userMessage}`;
|
|
1078
|
+
}
|
|
827
1079
|
async ensureDefaultSubscriptions() {
|
|
828
1080
|
if (!this.config.autoSeedSubscriptions) {
|
|
829
1081
|
return;
|