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.
@@ -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 Devnet" : "Base Sepolia").join(" + ");
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 "Send the hard limits and strategy for this chain. Include max trade size, entry filters, stop loss, take profit, max positions, and tokens/categories to avoid.";
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 the testlab launch feed. Answer yes/evet or no/hayir.";
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\nCopy and store this master key now:\n${masterKey}\n\nIt is shown once in real environments.\n\n${this.setupPromptFor("networks")}`
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 address = firstString(structured.address, asRecord(structured.wallet)?.address);
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${chainTitle(currentStrategyChain)} strategy:\n${this.setupPromptFor("strategy")}`);
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 strategyText = text.trim();
674
- if (strategyText.length < 20) {
675
- this.addAgentMessage("The strategy is too short. Include limits, entries, exits, max positions, and avoid rules.");
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
- ...(flow.currentStrategyChain ? { [flow.currentStrategyChain]: strategyText } : {}),
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 = { ...flow, chainStrategies, currentStrategyChain: nextChain };
686
- this.addAgentMessage(`${chainTitle(flow.currentStrategyChain ?? selectedChains[0])} strategy saved.\n\n${chainTitle(nextChain)} strategy:\n${this.setupPromptFor("strategy")}`);
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 limits = parseTradeLimits(combinedStrategy);
691
- this.setupFlow = { ...flow, ...limits, chainStrategies, step: "strategy-confirm", strategyText: combinedStrategy };
692
- this.addAgentMessage(`Review before live configuration:\n\n${combinedStrategy}\n\nMax SOL/trade: ${limits.maxTradeSol ?? "not specified"}\nMax USD/trade: ${limits.maxTradeUsd ?? "not specified"}\n\n${this.setupPromptFor("strategy-confirm")}`);
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.setupPromptFor("strategy"));
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.setupPromptFor("strategy"));
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 address = typeof structured.address === "string" ? structured.address : undefined;
790
- if (chain && address) {
791
- this.upsertWallet({ chain, address });
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;