@zktx.io/sui-mcp 0.6.2 → 0.8.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.
Files changed (87) hide show
  1. package/README.md +94 -52
  2. package/dist/adapters/adapterPromptSurfaces.js +1 -1
  3. package/dist/core/activity/localDataService.js +8 -21
  4. package/dist/core/activity/localDataValidation.js +22 -46
  5. package/dist/core/activity/sqliteAccountStore.js +46 -0
  6. package/dist/core/activity/sqliteActivityStore.js +65 -147
  7. package/dist/core/activity/sqliteActivityStoreSchema.js +42 -26
  8. package/dist/core/activity/transactionActivityService.js +2 -2
  9. package/dist/core/preferences/preferencesStore.js +2 -2
  10. package/dist/core/preferences/sqlitePreferencesRepository.js +10 -0
  11. package/dist/core/read/deepbookReadHelpers.js +1 -1
  12. package/dist/core/read/readResponseGuidance.js +6 -6
  13. package/dist/core/session/cardResult.js +41 -0
  14. package/dist/core/session/cardSession.js +1 -1
  15. package/dist/core/session/cardSessionStore.js +51 -19
  16. package/dist/core/session/chainAccounts.js +120 -0
  17. package/dist/core/session/sessionStore.js +4 -8
  18. package/dist/core/session/settingsFileProtocol.js +1 -1
  19. package/dist/core/session/settingsFileStore.js +3 -30
  20. package/dist/core/session/settingsOperation.js +27 -19
  21. package/dist/core/session/settingsWorkflow.js +161 -87
  22. package/dist/core/session/sqliteCardStore.js +34 -14
  23. package/dist/core/session/sqliteSessionStore.js +5 -5
  24. package/dist/core/session/sqliteSettingsWorkflowStore.js +135 -41
  25. package/dist/core/session/sqliteTransactionRequest.js +18 -0
  26. package/dist/core/session/sqliteWalletConnectionOperation.js +13 -0
  27. package/dist/core/session/sqliteWalletWorkflowStore.js +569 -333
  28. package/dist/core/session/status.js +2 -2
  29. package/dist/core/session/wait.js +6 -11
  30. package/dist/core/session/walletConnection.js +71 -46
  31. package/dist/core/session/walletConnectionOperations.js +24 -0
  32. package/dist/core/session/walletRuntime.js +7 -13
  33. package/dist/core/session/walletWorkflow.js +248 -99
  34. package/dist/core/session/workflowState.js +104 -38
  35. package/dist/core/session/workflowView.js +92 -101
  36. package/dist/mcp/activeAccountResponse.js +3 -0
  37. package/dist/mcp/prompts.js +10 -4
  38. package/dist/mcp/resources.js +1 -1
  39. package/dist/mcp/responseGuidance.js +7 -9
  40. package/dist/mcp/server.js +2 -2
  41. package/dist/mcp/serverInfo.js +21 -19
  42. package/dist/mcp/toolErrors.js +5 -3
  43. package/dist/mcp/toolNames.js +47 -46
  44. package/dist/mcp/tools/action/prepareSuiActionReview.js +13 -20
  45. package/dist/mcp/tools/read/deepbookReadTools.js +1 -1
  46. package/dist/mcp/tools/read/index.js +2 -2
  47. package/dist/mcp/tools/read/readToolHelpers.js +9 -7
  48. package/dist/mcp/tools/read/serverStatusTools.js +4 -5
  49. package/dist/mcp/tools/read/transactionActivityTools.js +3 -3
  50. package/dist/mcp/tools/session/executionResultTools.js +2 -2
  51. package/dist/mcp/tools/session/shared.js +2 -2
  52. package/dist/mcp/tools/session/statusTools.js +12 -10
  53. package/dist/mcp/tools/session/walletConnectionTools.js +29 -17
  54. package/dist/mcp/tools/settings/index.js +7 -4
  55. package/dist/mcp-app/account.html +7 -7
  56. package/dist/mcp-app/chart.html +3 -3
  57. package/dist/mcp-app/connect.html +13 -13
  58. package/dist/mcp-app/disconnect.html +3637 -0
  59. package/dist/mcp-app/disconnect.notices.txt +1480 -0
  60. package/dist/mcp-app/receipt.html +393 -393
  61. package/dist/mcp-app/review.html +459 -459
  62. package/dist/mcp-app/settings.html +7 -7
  63. package/dist/mcp-ui/contracts.js +3 -1
  64. package/dist/mcp-ui/tools.js +4 -4
  65. package/dist/runtime/application.js +3 -3
  66. package/dist/runtime/config.js +3 -6
  67. package/dist/runtime/localSettingsService.js +11 -8
  68. package/dist/runtime/shared/control.js +2 -2
  69. package/dist/runtime/smokeMainnetRead.js +23 -22
  70. package/dist/runtime/smokeReview.js +7 -7
  71. package/dist/runtime/start.js +1 -1
  72. package/dist/runtime/walletConnectTransport.js +16 -16
  73. package/dist/runtime/walletSdkChildRuntime.js +29 -12
  74. package/dist/runtime/walletSdkIpc.js +5 -4
  75. package/dist/runtime/walletSdkProcess.js +24 -8
  76. package/docs/AGENT_BEHAVIOR.md +149 -120
  77. package/docs/LOCAL_DB_ARCHITECTURE.md +103 -75
  78. package/docs/MCP_SETUP.md +365 -382
  79. package/docs/MCP_TOOLS.md +387 -271
  80. package/docs/SDK_API.md +23 -11
  81. package/docs/SIGNABLE_ADAPTER_CONTRACT.md +6 -6
  82. package/docs/TRANSACTION_ACTIVITY_LOG.md +11 -7
  83. package/docs/UTILITY_INDEX.md +66 -64
  84. package/docs/WALLET_CONNECTION.md +162 -166
  85. package/package.json +1 -1
  86. package/protocols/deepbook-margin.md +1 -1
  87. package/protocols/deepbook-v3.md +2 -2
@@ -1,5 +1,7 @@
1
+ import { readStoredTransactionRequest } from "../session/sqliteTransactionRequest.js";
2
+ import { publicConnectionOperation } from "../session/walletConnectionOperations.js";
1
3
  import { decideReviewEvaluation, needsReviewMaterial } from "../session/reviewValidity.js";
2
- import { workflowEligibility } from "../session/workflowState.js";
4
+ import { fixedReviewTarget, workflowEligibility } from "../session/workflowState.js";
3
5
  import { SessionStoreError } from "../session/sessionErrors.js";
4
6
  import { z } from "zod";
5
7
  import { TRANSACTION_REQUEST_STATUSES, transactionRequestStatusSchema } from "../session/transactionRequest.js";
@@ -8,11 +10,14 @@ import { chmodSync, existsSync, mkdirSync } from "node:fs";
8
10
  import { homedir } from "node:os";
9
11
  import { dirname, resolve } from "node:path";
10
12
  import Database from "better-sqlite3";
11
- import { SqliteWalletWorkflowStore, readStoredTransactionRequest } from "../session/sqliteWalletWorkflowStore.js";
13
+ import { SqliteWalletWorkflowStore } from "../session/sqliteWalletWorkflowStore.js";
12
14
  import { assertNoForbiddenMcpFields } from "../action/forbiddenFields.js";
13
15
  import { parseLifecycleValidatedReviewState } from "../action/reviewStateValidation.js";
14
16
  import { actionPlanSchema } from "../action/schemas.js";
15
17
  import { parseSuiAddress } from "../suiAddress.js";
18
+ import { SqliteAccountStore } from "./sqliteAccountStore.js";
19
+ import { SUI_MAINNET_WALLET_CHAIN, fixedChainAccount } from "../session/chainAccounts.js";
20
+ import { walletConnectionSchema } from "../session/walletConnection.js";
16
21
  import { SqlitePreferencesRepository } from "../preferences/sqlitePreferencesRepository.js";
17
22
  import { SqliteTransactionMaterialStore } from "../session/sqliteTransactionMaterialStore.js";
18
23
  import { SqliteCardRecordStore } from "../session/sqliteCardStore.js";
@@ -25,9 +30,8 @@ import { buildExternalActivityTransactionStreamResult, canonicalExternalActivity
25
30
  import { ActivityStoreReadError, EXTERNAL_ACTIVITY_COVERAGE_SCAN_MAX_RECORDS, REVIEW_ACTIVITY_DETAIL_MAX_ITEMS, REVIEW_ACTIVITY_LOW_SAMPLE_THRESHOLD } from "./activityStore.js";
26
31
  import { assertCurrentDatabaseFormat, configureDatabase, initializeDatabase } from "./sqliteActivityStoreSchema.js";
27
32
  import { ActivityStoreError } from "./sqliteActivityStoreTypes.js";
28
- import { EXTERNAL_ACTIVITY_RELATIONSHIPS, EXTERNAL_ACTIVITY_STATUSES, INTERNAL_SESSION_STATUSES, REVIEW_STATE_STATUSES, asAccountSource, asInternalSessionStatus, asReviewTransitionEvent, asString, assertDateRange, coinMetadataCacheRecordFromRow, countMap, emptyExternalActivitySummaryStats, emptyReviewFunnelSummary, externalActivityScanFromRow, externalActivitySummaryResult, externalActivityTransactionFromRow, extractRequestedIntent, normalizeExternalActivityLimit, normalizeListLimit, nullableSeconds, parseActionPlanEvidence, parseEvidenceJson, parseIsoTimestamp, parseOptionalIsoTimestamp, reasonForReviewState, reviewActivityListResult, reviewActivityRowFromStorage, reviewFunnelResult, reviewSessionWhere, serializeExternalActivityTransactionDetail, serializeJson, serializeOptionalJson } from "./sqliteActivityStoreRows.js";
33
+ import { EXTERNAL_ACTIVITY_RELATIONSHIPS, EXTERNAL_ACTIVITY_STATUSES, INTERNAL_SESSION_STATUSES, REVIEW_STATE_STATUSES, asInternalSessionStatus, asReviewTransitionEvent, assertDateRange, coinMetadataCacheRecordFromRow, countMap, emptyExternalActivitySummaryStats, emptyReviewFunnelSummary, externalActivityScanFromRow, externalActivitySummaryResult, externalActivityTransactionFromRow, extractRequestedIntent, normalizeExternalActivityLimit, normalizeListLimit, nullableSeconds, parseActionPlanEvidence, parseEvidenceJson, parseIsoTimestamp, parseOptionalIsoTimestamp, reasonForReviewState, reviewActivityListResult, reviewActivityRowFromStorage, reviewFunnelResult, reviewSessionWhere, serializeExternalActivityTransactionDetail, serializeJson, serializeOptionalJson } from "./sqliteActivityStoreRows.js";
29
34
  export { ActivityStoreError };
30
- const ACTIVE_ACCOUNT_SINGLETON_ID = 1;
31
35
  export const DATA_DIR_ENV = "SUI_MCP_DATA_DIR";
32
36
  export const ACTIVITY_DATABASE_FILENAME = "sui-mcp.sqlite";
33
37
  function restrictPathPermissions(path, mode) {
@@ -39,6 +43,7 @@ function restrictPathPermissions(path, mode) {
39
43
  }
40
44
  export class SqliteActivityStore {
41
45
  db;
46
+ chainAccounts;
42
47
  validateAdapterLifecycle;
43
48
  constructor(options) {
44
49
  this.validateAdapterLifecycle = options.validateAdapterLifecycle;
@@ -60,6 +65,7 @@ export class SqliteActivityStore {
60
65
  }
61
66
  const database = new Database(options.databasePath);
62
67
  this.db = options.guardDatabase?.(database) ?? database;
68
+ this.chainAccounts = new SqliteAccountStore(this.db);
63
69
  try {
64
70
  initializeDatabase(this.db);
65
71
  configureDatabase(this.db);
@@ -81,47 +87,7 @@ export class SqliteActivityStore {
81
87
  }
82
88
  return this.getAccountByAddressSync(normalized);
83
89
  }
84
- async setActiveAccount(address, source, now = new Date(), wallet) {
85
- return this.setActiveAccountSync(address, source, now, wallet);
86
- }
87
- setActiveAccountSync(address, source, now, wallet) {
88
- const timestamp = now.toISOString();
89
- return this.db.transaction(() => {
90
- const account = this.upsertAccountSync(address, source, timestamp);
91
- this.db
92
- .prepare(`INSERT INTO active_account_context (id, account_id, source, set_at, wallet_name, wallet_id)
93
- VALUES (?, ?, ?, ?, ?, ?)
94
- ON CONFLICT(id) DO UPDATE SET
95
- account_id = excluded.account_id,
96
- source = excluded.source,
97
- set_at = excluded.set_at,
98
- wallet_name = excluded.wallet_name,
99
- wallet_id = excluded.wallet_id`)
100
- .run(ACTIVE_ACCOUNT_SINGLETON_ID, account.id, source, timestamp, wallet?.name ?? null, wallet?.id ?? null);
101
- return {
102
- accountId: account.id,
103
- address: account.address,
104
- source,
105
- setAt: timestamp,
106
- ...(wallet?.name ? { walletName: wallet.name } : {}),
107
- ...(wallet?.id ? { walletId: wallet.id } : {})
108
- };
109
- })();
110
- }
111
- async getActiveAccount() {
112
- return this.getActiveAccountSync();
113
- }
114
- async clearActiveAccount(now = new Date()) { this.clearActiveAccountSync(now); }
115
- clearActiveAccountSync(now = new Date()) {
116
- this.db
117
- .prepare(`INSERT INTO active_account_context (id, account_id, source, set_at)
118
- VALUES (?, NULL, 'cleared', ?)
119
- ON CONFLICT(id) DO UPDATE SET
120
- account_id = NULL,
121
- source = 'cleared',
122
- set_at = excluded.set_at`)
123
- .run(ACTIVE_ACCOUNT_SINGLETON_ID, now.toISOString());
124
- }
90
+ async getActiveAccount() { return this.getActiveAccountSync(); }
125
91
  async recordReviewSession(input) {
126
92
  const plan = parseActionPlanEvidence(input.plan);
127
93
  const planJson = serializeJson(plan);
@@ -188,9 +154,6 @@ export class SqliteActivityStore {
188
154
  : null;
189
155
  if (accountId !== null) {
190
156
  this.assertReviewSessionAccount(input.reviewSessionId, accountId);
191
- if (input.event === "wallet_connected") {
192
- this.assertActiveAccountSync(accountId, input.reviewSessionId);
193
- }
194
157
  }
195
158
  this.insertReviewTransition({ ...input, accountId });
196
159
  this.db
@@ -240,12 +203,16 @@ export class SqliteActivityStore {
240
203
  const { material: checked, clock } = live.publication;
241
204
  const at = clock(), handle = checked.artifacts.transactionMaterial;
242
205
  const material = handle && this.createTransactionMaterialStore().getTransactionMaterial(handle, at);
243
- const connection = live.expected?.walletConnectionId ? this.db.prepare("SELECT revision,status,owner_id,connection_json FROM live_wallet_connections WHERE id=?")
244
- .get(live.expected.walletConnectionId) : undefined;
206
+ const connection = live.expected?.walletConnectionId ? this.db.prepare(`SELECT c.status,c.owner_id,c.run_id,c.connection_json,r.preparation_run_id FROM live_wallet_connections c
207
+ JOIN live_review_sessions r ON r.wallet_connection_id=c.id WHERE c.id=? AND r.id=? AND c.current_slot=1 AND c.data_epoch=(SELECT data_epoch FROM settings_control WHERE id=1)`)
208
+ .get(live.expected.walletConnectionId, live.expected.id) : undefined;
209
+ const wallet = connection && walletConnectionSchema.parse(JSON.parse(connection.connection_json));
245
210
  if (Date.parse(live.next.expiresAt) <= at.getTime() || !handle || !material || !sameHandle(material, handle) ||
246
211
  !Buffer.from(material.transactionBytes).equals(checked.transactionBytes) || !connection || connection.status !== "connected" ||
247
- connection.owner_id !== live.expected?.ownerId || connection.revision !== live.expected?.walletConnectionRevision ||
248
- Date.parse(JSON.parse(connection.connection_json).expiresAt) <= at.getTime()) {
212
+ connection.owner_id !== live.expected?.ownerId || !connection.run_id || connection.run_id !== connection.preparation_run_id || !wallet ||
213
+ wallet.grantRevisions[SUI_MAINNET_WALLET_CHAIN] !== live.expected?.walletGrantRevision ||
214
+ fixedChainAccount(wallet, SUI_MAINNET_WALLET_CHAIN) !== parsedState.account ||
215
+ Date.parse(wallet.expiresAt) <= at.getTime()) {
249
216
  throw new SessionStoreError("invalid_session_transition", "Review material or wallet selection changed before publication.");
250
217
  }
251
218
  }
@@ -290,12 +257,12 @@ export class SqliteActivityStore {
290
257
  where += " AND e.status=?";
291
258
  params.push(z.enum(["success", "failure"]).parse(filter.executionStatus));
292
259
  }
293
- const joins = `FROM review_sessions rs JOIN accounts a ON a.id=rs.account_id
260
+ const joins = `FROM review_sessions rs JOIN accounts a ON a.chain_id='${SUI_MAINNET_WALLET_CHAIN}' AND a.id=rs.account_id
294
261
  LEFT JOIN review_requests r ON r.attempt_id=rs.current_attempt_id AND r.review_session_id=rs.id AND r.account_id=rs.account_id
295
262
  LEFT JOIN review_executions e ON e.attempt_id=r.attempt_id`;
296
263
  const count = this.db.prepare(`SELECT COUNT(*) AS count ${joins} ${where}`).get(...params).count;
297
264
  const rows = this.db.prepare(`SELECT rs.id AS review_session_id, rs.plan_id, rs.action_kind, rs.adapter_id, rs.protocol,
298
- rs.current_status, rs.current_attempt_id, a.sui_address AS account, rs.created_at, rs.updated_at,
265
+ rs.current_status, rs.current_attempt_id, a.address AS account, rs.created_at, rs.updated_at,
299
266
  e.status AS execution_status, e.tx_digest,
300
267
  (SELECT COUNT(*) FROM review_state_snapshots s WHERE s.review_session_id=rs.id) AS snapshot_count,
301
268
  (SELECT COUNT(*) FROM review_status_transitions t WHERE t.review_session_id=rs.id) AS transition_count
@@ -355,8 +322,8 @@ export class SqliteActivityStore {
355
322
  (r.request_status!='completed' OR e.tx_digest!=r.transaction_digest OR json_valid(e.result_json)=0))) LIMIT 1`).get(...params);
356
323
  if (inconsistent)
357
324
  throw new ActivityStoreReadError("internal_error", "Stored review request evidence is inconsistent.", { reason: "invalid_stored_evidence" });
358
- for (const row of this.db.prepare(`SELECT rs.id,rs.current_attempt_id,a.sui_address AS account
359
- FROM review_sessions rs JOIN accounts a ON a.id=rs.account_id ${where} AND rs.current_attempt_id IS NOT NULL`)
325
+ for (const row of this.db.prepare(`SELECT rs.id,rs.current_attempt_id,a.address AS account
326
+ FROM review_sessions rs JOIN accounts a ON a.chain_id='${SUI_MAINNET_WALLET_CHAIN}' AND a.id=rs.account_id ${where} AND rs.current_attempt_id IS NOT NULL`)
360
327
  .all(...params)) {
361
328
  this.readRequestEvidence(row.current_attempt_id, row.id, row.account);
362
329
  }
@@ -375,15 +342,15 @@ export class SqliteActivityStore {
375
342
  }
376
343
  async getReviewSessionDetail(input) {
377
344
  const scope = this.resolveReviewActivityScope({ account: input.account });
378
- const row = scope.accountId === undefined ? undefined : this.db.prepare(`SELECT rs.*, a.sui_address AS account
379
- FROM review_sessions rs JOIN accounts a ON a.id=rs.account_id WHERE rs.id=? AND rs.account_id=?`)
345
+ const row = scope.accountId === undefined ? undefined : this.db.prepare(`SELECT rs.*, a.address AS account
346
+ FROM review_sessions rs JOIN accounts a ON a.chain_id='${SUI_MAINNET_WALLET_CHAIN}' AND a.id=rs.account_id WHERE rs.id=? AND rs.account_id=?`)
380
347
  .get(input.reviewSessionId, scope.accountId);
381
348
  if (!row)
382
349
  throw new ActivityStoreReadError("session_not_found", "Review session not found", { reviewSessionId: input.reviewSessionId });
383
- const snapshots = this.db.prepare(`SELECT s.*,a.sui_address AS account FROM review_state_snapshots s JOIN accounts a ON a.id=s.account_id
350
+ const snapshots = this.db.prepare(`SELECT s.*,a.address AS account FROM review_state_snapshots s JOIN accounts a ON a.chain_id='${SUI_MAINNET_WALLET_CHAIN}' AND a.id=s.account_id
384
351
  WHERE s.review_session_id=? AND s.account_id=? ORDER BY s.recorded_at,s.id LIMIT ?`)
385
352
  .all(input.reviewSessionId, scope.accountId, REVIEW_ACTIVITY_DETAIL_MAX_ITEMS + 1);
386
- const transitions = this.db.prepare(`SELECT t.*,a.sui_address AS account FROM review_status_transitions t LEFT JOIN accounts a ON a.id=t.account_id
353
+ const transitions = this.db.prepare(`SELECT t.*,a.address AS account FROM review_status_transitions t LEFT JOIN accounts a ON a.chain_id='${SUI_MAINNET_WALLET_CHAIN}' AND a.id=t.account_id
387
354
  WHERE t.review_session_id=? AND (t.account_id IS NULL OR t.account_id=?) ORDER BY t.transitioned_at,t.id LIMIT ?`)
388
355
  .all(input.reviewSessionId, scope.accountId, REVIEW_ACTIVITY_DETAIL_MAX_ITEMS + 1);
389
356
  const requestIds = this.db.prepare(`SELECT attempt_id FROM review_requests WHERE review_session_id=? AND account_id=? ORDER BY created_at,attempt_id LIMIT ?`)
@@ -510,13 +477,13 @@ export class SqliteActivityStore {
510
477
  .prepare(`SELECT COUNT(*) AS count FROM external_activity_scans eas ${scanWhereSql}`)
511
478
  .get(...scanParams).count;
512
479
  const scanRows = this.db
513
- .prepare(`SELECT eas.scan_id, eas.kind, eas.account_id, a.sui_address AS account, eas.relationship,
480
+ .prepare(`SELECT eas.scan_id, eas.kind, eas.account_id, a.address AS account, eas.relationship,
514
481
  eas.input_digest, eas.from_checkpoint, eas.to_checkpoint, eas.from_timestamp,
515
482
  eas.to_timestamp, eas.limit_count, eas.request_cursor, eas.response_cursor,
516
483
  eas.endpoint_host, eas.chain_identifier, eas.fetched_at, eas.stored_count,
517
484
  eas.skipped_count, eas.has_more, eas.window_complete, eas.incomplete_reason
518
485
  FROM external_activity_scans eas
519
- JOIN accounts a ON a.id = eas.account_id
486
+ JOIN accounts a ON a.chain_id='${SUI_MAINNET_WALLET_CHAIN}' AND a.id = eas.account_id
520
487
  ${scanWhereSql}
521
488
  ORDER BY eas.fetched_at DESC, eas.scan_id DESC
522
489
  LIMIT ?`)
@@ -606,13 +573,13 @@ export class SqliteActivityStore {
606
573
  ${whereSql}`)
607
574
  .get(...params);
608
575
  const rows = this.db
609
- .prepare(`SELECT eat.account_id, a.sui_address AS account, eat.digest, eat.relationship,
576
+ .prepare(`SELECT eat.account_id, a.address AS account, eat.digest, eat.relationship,
610
577
  eat.checkpoint, eat.timestamp, eat.status, eat.known_sender_account_id,
611
578
  eat.first_scan_id, eat.last_scan_id, eat.first_fetched_at, eat.last_fetched_at,
612
579
  last_scan.incomplete_reason AS last_scan_incomplete_reason,
613
580
  eat.detail_json
614
581
  FROM external_activity_transactions eat
615
- JOIN accounts a ON a.id = eat.account_id
582
+ JOIN accounts a ON a.chain_id='${SUI_MAINNET_WALLET_CHAIN}' AND a.id = eat.account_id
616
583
  LEFT JOIN external_activity_scans last_scan ON last_scan.scan_id = eat.last_scan_id
617
584
  ${whereSql}
618
585
  ORDER BY
@@ -658,7 +625,7 @@ export class SqliteActivityStore {
658
625
  return new SqlitePrivateReviewArtifactStore(this.db);
659
626
  }
660
627
  createWalletWorkflowStore(ownerId, clock = () => new Date(), readSdkSnapshot = () => undefined) {
661
- const records = new SqliteWalletWorkflowStore(this.db, ownerId, (address, id, name, now) => this.setActiveAccountSync(address, "wallet_connection", now, { id, name }), clock, this.createTransactionMaterialStore(), (input, at) => this.reconcileWorkflowState(input, records, ownerId, at === undefined ? clock : () => at), () => this.getActiveAccountSync(), readSdkSnapshot);
628
+ const records = new SqliteWalletWorkflowStore(this.db, ownerId, this.chainAccounts, clock, this.createTransactionMaterialStore(), (input, at) => this.reconcileWorkflowState(input, records, ownerId, at === undefined ? clock : () => at), readSdkSnapshot);
662
629
  return records;
663
630
  }
664
631
  finalizeReviewEvaluation(candidate, clock) {
@@ -701,7 +668,7 @@ export class SqliteActivityStore {
701
668
  const cards = this.createCardRecordStore();
702
669
  let record = input.expectedCard && records.currentCard(input.expectedCard);
703
670
  const reviewId = input.purpose === "review" ? input.reviewSessionId : undefined;
704
- if (record && (input.purpose === "review" ? record.state.kind !== "review" || record.state.input.reviewSessionId !== reviewId : record.state.kind !== "connect")) {
671
+ if (record && (input.purpose === "review" ? record.state.kind !== "review" || record.state.input.reviewSessionId !== reviewId : !["connect", "disconnect"].includes(record.state.kind))) {
705
672
  throw new SessionStoreError("session_mismatch", "The workflow target does not match this card.");
706
673
  }
707
674
  const target = reviewId ? records.reviewReadTarget(reviewId, record) : undefined;
@@ -724,20 +691,35 @@ export class SqliteActivityStore {
724
691
  else
725
692
  records.advanceRequestDeadlines(now, target.attemptId);
726
693
  if (record)
727
- record = cards.evaluate(record, () => now).record;
694
+ record = records.expireCard(record, now);
728
695
  const session = evaluated?.session;
729
696
  const request = target?.attemptId ? records.request(target.attemptId) : undefined;
730
697
  const authority = request && records.authority(request.attemptId);
731
698
  const details = request?.execution && records.executionDetails(request.attemptId);
732
- const connection = record?.state.kind === "connect" && record.operationId ? records.connection(record.operationId) : undefined;
733
- const connectionFacts = records.readConnectionFacts(now), targetAccount = session?.account ?? connectionFacts.activeAccount;
699
+ const operationRecord = ["connect", "disconnect"].includes(record?.scope ?? "") && record ? records.cardOperation(record) : undefined;
700
+ const operation = operationRecord && publicConnectionOperation(operationRecord);
701
+ const connectionFacts = records.readConnectionFacts(now), reviewTarget = session ? fixedReviewTarget(session, record?.state.input) : undefined;
702
+ const targetAccount = reviewTarget?.account;
703
+ if (record?.scope === "connect" || record?.scope === "disconnect")
704
+ record = records.reconcileWalletCard(record, connectionFacts);
705
+ if (record?.scope === "review" && record.state.state === "ready") {
706
+ const expired = session?.status === "expired" || !!session && Date.parse(session.expiresAt) <= now.getTime();
707
+ const targetReplaced = !!reviewTarget && !!connectionFacts.connection && connectionFacts.connection.connectionId !== reviewTarget.connectionId;
708
+ const targetAbsent = !!reviewTarget && connectionFacts.connection?.connectionId === reviewTarget.connectionId && connectionFacts.connectionPresence.confirmed &&
709
+ connectionFacts.connectionPresence.observation?.status === "absent";
710
+ if (expired || targetAbsent || targetReplaced) {
711
+ const error = expired ? undefined : targetAbsent
712
+ ? "The wallet session for this review was confirmed absent. Ask in chat for a new transaction review; connect a wallet first if needed."
713
+ : "The original wallet connection is no longer current. Ask in chat for a new transaction review.";
714
+ record = records.endCard(record, expired ? "expired" : "failed", now, error);
715
+ }
716
+ }
734
717
  const facts = { ...connectionFacts, ownerId, record, session, request, authority,
735
718
  hasReviewInput: !!session && session.status !== "expired" && Date.parse(session.expiresAt) > now.getTime() &&
736
719
  cards.hasReviewInput(session.id, ownerId, now),
737
720
  walletAvailability: input.walletAvailability,
738
- runtimeRecovery: record && records.walletRecovery(record),
721
+ operation,
739
722
  walletDependent: !target || target.walletDependent,
740
- connection: connection ? records.connectionView(connection.connection) : undefined,
741
723
  boundReview: request && (record?.operationId || record?.scope === "review_manage") ? records.requestReview(request.attemptId) : undefined,
742
724
  busyForAccount: !!targetAccount && records.busyForAccount(targetAccount, now),
743
725
  receipt: details ? details.data : undefined, receiptDisplay: details ? details.receiptDisplay : undefined };
@@ -809,33 +791,7 @@ export class SqliteActivityStore {
809
791
  .get(id);
810
792
  }
811
793
  upsertAccountSync(address, source, timestamp) {
812
- const normalized = parseSuiAddress(address);
813
- if (!normalized) {
814
- throw new ActivityStoreError("Invalid Sui account address");
815
- }
816
- this.db
817
- .prepare(`INSERT INTO accounts (sui_address, first_seen_at, last_used_at, first_source, last_source)
818
- VALUES (?, ?, ?, ?, ?)
819
- ON CONFLICT(sui_address) DO UPDATE SET
820
- last_used_at = excluded.last_used_at,
821
- last_source = excluded.last_source`)
822
- .run(normalized, timestamp, timestamp, source, source);
823
- const row = this.db
824
- .prepare(`SELECT id, sui_address, first_seen_at, last_used_at, first_source, last_source
825
- FROM accounts
826
- WHERE sui_address = ?`)
827
- .get(normalized);
828
- if (!row) {
829
- throw new ActivityStoreError(`Account was not recorded: ${normalized}`);
830
- }
831
- return {
832
- id: row.id,
833
- address: asString(row.sui_address),
834
- firstSeenAt: asString(row.first_seen_at),
835
- lastUsedAt: asString(row.last_used_at),
836
- firstSource: asAccountSource(row.first_source),
837
- lastSource: asAccountSource(row.last_source)
838
- };
794
+ return this.chainAccounts.upsert(SUI_MAINNET_WALLET_CHAIN, address, source, timestamp);
839
795
  }
840
796
  parseReviewStateEvidenceJson(value, reviewSessionId, evidenceField) {
841
797
  const parsed = parseEvidenceJson(value, reviewSessionId, evidenceField);
@@ -850,48 +806,34 @@ export class SqliteActivityStore {
850
806
  }
851
807
  }
852
808
  getAccountByAddressSync(address) {
853
- const row = this.db
854
- .prepare(`SELECT id, sui_address, first_seen_at, last_used_at, first_source, last_source
855
- FROM accounts
856
- WHERE sui_address = ?`)
857
- .get(address);
858
- return row
859
- ? {
860
- id: row.id,
861
- address: asString(row.sui_address),
862
- firstSeenAt: asString(row.first_seen_at),
863
- lastUsedAt: asString(row.last_used_at),
864
- firstSource: asAccountSource(row.first_source),
865
- lastSource: asAccountSource(row.last_source)
866
- }
867
- : undefined;
809
+ return this.chainAccounts.get(SUI_MAINNET_WALLET_CHAIN, address);
868
810
  }
869
811
  accountIdExists(accountId) {
870
- const row = this.db.prepare("SELECT id FROM accounts WHERE id = ?").get(accountId);
812
+ const row = this.db.prepare("SELECT id FROM accounts WHERE id = ? AND chain_id=?").get(accountId, SUI_MAINNET_WALLET_CHAIN);
871
813
  return row !== undefined;
872
814
  }
873
815
  externalActivityScanById(scanId) {
874
816
  const row = this.db
875
- .prepare(`SELECT eas.scan_id, eas.kind, eas.account_id, a.sui_address AS account, eas.relationship,
817
+ .prepare(`SELECT eas.scan_id, eas.kind, eas.account_id, a.address AS account, eas.relationship,
876
818
  eas.input_digest, eas.from_checkpoint, eas.to_checkpoint, eas.from_timestamp,
877
819
  eas.to_timestamp, eas.limit_count, eas.request_cursor, eas.response_cursor,
878
820
  eas.endpoint_host, eas.chain_identifier, eas.fetched_at, eas.stored_count,
879
821
  eas.skipped_count, eas.has_more, eas.window_complete, eas.incomplete_reason
880
822
  FROM external_activity_scans eas
881
- JOIN accounts a ON a.id = eas.account_id
823
+ JOIN accounts a ON a.chain_id='${SUI_MAINNET_WALLET_CHAIN}' AND a.id = eas.account_id
882
824
  WHERE eas.scan_id = ?`)
883
825
  .get(scanId);
884
826
  return row ? externalActivityScanFromRow(row) : undefined;
885
827
  }
886
828
  externalActivityTransactions(whereSql, params) {
887
829
  const rows = this.db
888
- .prepare(`SELECT eat.account_id, a.sui_address AS account, eat.digest, eat.relationship,
830
+ .prepare(`SELECT eat.account_id, a.address AS account, eat.digest, eat.relationship,
889
831
  eat.checkpoint, eat.timestamp, eat.status, eat.known_sender_account_id,
890
832
  eat.first_scan_id, eat.last_scan_id, eat.first_fetched_at, eat.last_fetched_at,
891
833
  last_scan.incomplete_reason AS last_scan_incomplete_reason,
892
834
  eat.detail_json
893
835
  FROM external_activity_transactions eat
894
- JOIN accounts a ON a.id = eat.account_id
836
+ JOIN accounts a ON a.chain_id='${SUI_MAINNET_WALLET_CHAIN}' AND a.id = eat.account_id
895
837
  LEFT JOIN external_activity_scans last_scan ON last_scan.scan_id = eat.last_scan_id
896
838
  ${whereSql}
897
839
  ORDER BY
@@ -916,8 +858,8 @@ export class SqliteActivityStore {
916
858
  throw new ActivityStoreReadError("input_invalid", "Invalid account filter", { field: "account" });
917
859
  }
918
860
  const row = this.db
919
- .prepare("SELECT id FROM accounts WHERE sui_address = ?")
920
- .get(normalized);
861
+ .prepare("SELECT id FROM accounts WHERE chain_id=? AND address=?")
862
+ .get(SUI_MAINNET_WALLET_CHAIN, normalized);
921
863
  return {
922
864
  account: normalized,
923
865
  accountId: row?.id,
@@ -926,8 +868,8 @@ export class SqliteActivityStore {
926
868
  }
927
869
  const active = this.getActiveAccountSync();
928
870
  if (!active) {
929
- throw new ActivityStoreReadError("active_account_not_set", "Active account read context is not set", {
930
- action: "connect_wallet_connection"
871
+ throw new ActivityStoreReadError("active_account_not_set", "No saved read address is set. Provide an explicit Sui account for this read.", {
872
+ action: "provide_account"
931
873
  });
932
874
  }
933
875
  return {
@@ -937,23 +879,7 @@ export class SqliteActivityStore {
937
879
  };
938
880
  }
939
881
  getActiveAccountSync() {
940
- const row = this.db
941
- .prepare(`SELECT a.id AS account_id, a.sui_address AS address, c.source AS source, c.set_at AS set_at,
942
- c.wallet_name AS wallet_name, c.wallet_id AS wallet_id
943
- FROM active_account_context c
944
- JOIN accounts a ON a.id = c.account_id
945
- WHERE c.id = ? AND c.account_id IS NOT NULL`)
946
- .get(ACTIVE_ACCOUNT_SINGLETON_ID);
947
- return row
948
- ? {
949
- accountId: row.account_id,
950
- address: asString(row.address),
951
- source: "wallet_connection",
952
- setAt: asString(row.set_at),
953
- ...(row.wallet_name ? { walletName: row.wallet_name } : {}),
954
- ...(row.wallet_id ? { walletId: row.wallet_id } : {})
955
- }
956
- : undefined;
882
+ return this.chainAccounts.currentReadAccount(SUI_MAINNET_WALLET_CHAIN);
957
883
  }
958
884
  assertReviewSessionAccount(reviewSessionId, accountId) {
959
885
  const row = this.db
@@ -966,14 +892,6 @@ export class SqliteActivityStore {
966
892
  throw new ActivityStoreError(`Review session already belongs to a different account: ${reviewSessionId}`);
967
893
  }
968
894
  }
969
- assertActiveAccountSync(accountId, reviewSessionId) {
970
- const row = this.db
971
- .prepare("SELECT account_id FROM active_account_context WHERE id = ? AND source = 'wallet_connection'")
972
- .get(ACTIVE_ACCOUNT_SINGLETON_ID);
973
- if (!row || row.account_id !== accountId) {
974
- throw new ActivityStoreError(`Review session active account changed before commit: ${reviewSessionId}`);
975
- }
976
- }
977
895
  }
978
896
  function assertNonEmptyHalfOpenRange(from, to) {
979
897
  if (from >= to) {
@@ -2,30 +2,21 @@ import Database from "better-sqlite3";
2
2
  import { EXTERNAL_ACTIVITY_SCAN_MAX_LIMIT } from "./activityStore.js";
3
3
  import { ActivityStoreError } from "./sqliteActivityStoreTypes.js";
4
4
  import { SETTINGS_RESET_PREPARATION_SQL } from "../session/settingsPreparationSql.js";
5
+ import { WALLET_CHAINS } from "../session/chainAccounts.js";
5
6
  import { LIVE_REVIEW_SESSION_INSERT_TRIGGER, LIVE_REVIEW_SESSION_UPDATE_TRIGGER, LIVE_REVIEW_SESSION_WRITE_CONTRACT_VERSION } from "../session/liveReviewSessionContract.js";
6
7
  const EXTERNAL_ACTIVITY_SCAN_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_external_activity_scans_account_fetched
7
8
  ON external_activity_scans(account_id, fetched_at)`;
9
+ const WALLET_CHAIN_SQL = WALLET_CHAINS.map((chain) => `'${chain}'`).join(",");
8
10
  const CURRENT_SCHEMA_SQL = `
9
11
  CREATE TABLE IF NOT EXISTS accounts (
10
12
  id INTEGER PRIMARY KEY AUTOINCREMENT,
11
- sui_address TEXT NOT NULL UNIQUE,
13
+ chain_id TEXT NOT NULL CHECK (chain_id IN (${WALLET_CHAIN_SQL})),
14
+ address TEXT NOT NULL,
12
15
  first_seen_at TEXT NOT NULL,
13
16
  last_used_at TEXT NOT NULL,
14
17
  first_source TEXT NOT NULL CHECK (first_source IN ('wallet_connection', 'review_execution')),
15
- last_source TEXT NOT NULL CHECK (last_source IN ('wallet_connection', 'review_execution'))
16
- );
17
-
18
- CREATE TABLE IF NOT EXISTS active_account_context (
19
- id INTEGER PRIMARY KEY CHECK (id = 1),
20
- account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL,
21
- source TEXT NOT NULL CHECK (source IN ('wallet_connection', 'cleared')),
22
- set_at TEXT NOT NULL,
23
- wallet_name TEXT,
24
- wallet_id TEXT,
25
- CHECK (
26
- (source = 'cleared' AND account_id IS NULL)
27
- OR (source = 'wallet_connection' AND account_id IS NOT NULL)
28
- )
18
+ last_source TEXT NOT NULL CHECK (last_source IN ('wallet_connection', 'review_execution')),
19
+ UNIQUE (chain_id,address)
29
20
  );
30
21
 
31
22
  CREATE TABLE IF NOT EXISTS review_sessions (
@@ -136,15 +127,38 @@ const CURRENT_SCHEMA_SQL = `
136
127
  status TEXT NOT NULL,
137
128
  revision INTEGER NOT NULL CHECK (revision >= 0),
138
129
  topic TEXT UNIQUE,
139
- sdk_pending INTEGER NOT NULL CHECK (sdk_pending IN (0,1)),
130
+ current_slot INTEGER UNIQUE CHECK (current_slot IS NULL OR current_slot=1),
131
+ data_epoch INTEGER NOT NULL CHECK (data_epoch >= 0),
132
+ read_address_cleared INTEGER NOT NULL DEFAULT 0 CHECK (read_address_cleared IN (0,1)),
140
133
  connection_json TEXT NOT NULL
141
134
  );
135
+ CREATE TABLE IF NOT EXISTS wallet_connection_operations (
136
+ operation_id TEXT PRIMARY KEY,
137
+ card_id TEXT NOT NULL UNIQUE,
138
+ owner_id TEXT NOT NULL,
139
+ run_id TEXT NOT NULL,
140
+ data_epoch INTEGER NOT NULL CHECK (data_epoch >= 0),
141
+ action TEXT NOT NULL CHECK (action IN ('connect','disconnect')),
142
+ connection_id TEXT NOT NULL REFERENCES live_wallet_connections(id) ON DELETE RESTRICT,
143
+ status TEXT NOT NULL CHECK (status IN ('running','completed','failed','stopped')),
144
+ input_json TEXT NOT NULL,
145
+ result_json TEXT,
146
+ sdk_pending INTEGER NOT NULL CHECK (sdk_pending IN (0,1)),
147
+ created_at TEXT NOT NULL,
148
+ updated_at TEXT NOT NULL,
149
+ expires_at TEXT NOT NULL
150
+ );
151
+
152
+ CREATE INDEX IF NOT EXISTS idx_wallet_connection_pending ON wallet_connection_operations(connection_id,sdk_pending);
153
+ CREATE TRIGGER IF NOT EXISTS wallet_connection_result_immutable
154
+ BEFORE UPDATE ON wallet_connection_operations WHEN OLD.result_json IS NOT NULL AND NEW.result_json IS NOT OLD.result_json
155
+ BEGIN SELECT RAISE(ABORT,'Account management results are immutable'); END;
142
156
  CREATE TABLE IF NOT EXISTS live_request_authority (
143
157
  attempt_id TEXT PRIMARY KEY REFERENCES review_requests(attempt_id) ON DELETE RESTRICT,
144
158
  owner_id TEXT NOT NULL,
145
159
  run_id TEXT,
146
160
  connection_id TEXT NOT NULL REFERENCES live_wallet_connections(id) ON DELETE RESTRICT,
147
- connection_revision INTEGER NOT NULL,
161
+ grant_revision INTEGER NOT NULL,
148
162
  can_submit INTEGER NOT NULL CHECK (can_submit IN (0,1)),
149
163
  sdk_pending INTEGER NOT NULL CHECK (sdk_pending IN (0,1)),
150
164
  submit_pending INTEGER NOT NULL CHECK (submit_pending IN (0,1)),
@@ -225,7 +239,7 @@ const CURRENT_SCHEMA_SQL = `
225
239
  preparation_run_id TEXT,
226
240
  preparation_error TEXT,
227
241
  wallet_connection_id TEXT,
228
- wallet_connection_revision INTEGER,
242
+ wallet_grant_revision INTEGER,
229
243
  current_attempt_id TEXT,
230
244
  plans_json TEXT NOT NULL,
231
245
  review_state_json TEXT,
@@ -246,7 +260,7 @@ const CURRENT_SCHEMA_SQL = `
246
260
  id TEXT PRIMARY KEY,
247
261
  owner_id TEXT NOT NULL,
248
262
  token_hash TEXT NOT NULL,
249
- kind TEXT NOT NULL CHECK (kind IN ('account', 'receipt', 'chart', 'connect', 'review', 'settings')),
263
+ kind TEXT NOT NULL CHECK (kind IN ('account', 'receipt', 'chart', 'connect', 'disconnect', 'review', 'settings')),
250
264
  state TEXT NOT NULL CHECK (state IN ('ready', 'running', 'closed')),
251
265
  reason TEXT CHECK (reason IN ('completed', 'expired', 'failed', 'server_restarted', 'cancelled')),
252
266
  error TEXT,
@@ -254,7 +268,7 @@ const CURRENT_SCHEMA_SQL = `
254
268
  created_at TEXT NOT NULL,
255
269
  expires_at TEXT NOT NULL,
256
270
  input_json TEXT NOT NULL,
257
- scope TEXT NOT NULL DEFAULT 'read' CHECK (scope IN ('read', 'connect', 'review', 'review_manage', 'settings')),
271
+ scope TEXT NOT NULL DEFAULT 'read' CHECK (scope IN ('read', 'connect', 'disconnect', 'review', 'review_manage', 'settings')),
258
272
  operation_id TEXT,
259
273
  accepted_input_json TEXT,
260
274
  result_json TEXT,
@@ -294,7 +308,6 @@ const CURRENT_SCHEMA_SQL = `
294
308
  next_run_id TEXT,
295
309
  supersedes_operation_id TEXT,
296
310
  superseded_by_operation_id TEXT,
297
- checkpoint TEXT,
298
311
  CHECK ((phase='terminal' AND result_json IS NOT NULL) OR (phase!='terminal' AND result_json IS NULL))
299
312
  );
300
313
  CREATE TRIGGER IF NOT EXISTS settings_immutable_result
@@ -320,6 +333,10 @@ const CURRENT_SCHEMA_SQL = `
320
333
  CREATE TRIGGER IF NOT EXISTS live_read_cards_insert_revision
321
334
  BEFORE INSERT ON live_read_cards WHEN NEW.revision != 0
322
335
  BEGIN SELECT RAISE(ABORT, 'card insert requires revision zero'); END;
336
+ CREATE TRIGGER IF NOT EXISTS live_card_terminal_immutable
337
+ BEFORE UPDATE ON live_read_cards WHEN OLD.state='closed'
338
+ BEGIN SELECT RAISE(ABORT, 'Closed card results and permissions are immutable'); END;
339
+
323
340
  CREATE TRIGGER IF NOT EXISTS live_read_cards_update_revision
324
341
  BEFORE UPDATE ON live_read_cards WHEN NEW.revision != OLD.revision + 1
325
342
  BEGIN SELECT RAISE(ABORT, 'card update requires the next revision'); END;
@@ -413,13 +430,13 @@ function externalActivityScansTableSql() {
413
430
  )`;
414
431
  }
415
432
  function settingsRevisionTriggers() {
416
- const userTables = ["accounts", "active_account_context", "review_sessions", "review_state_snapshots",
433
+ const userTables = ["accounts", "review_sessions", "review_state_snapshots",
417
434
  "review_status_transitions", "review_executions", "review_requests", "external_activity_scans",
418
435
  "external_activity_transactions", "local_settings"];
419
- const walletTables = ["live_wallet_connections", "live_review_sessions", "live_request_authority"];
436
+ const walletTables = ["live_wallet_connections", "wallet_connection_operations", "live_review_sessions", "live_request_authority"];
420
437
  const invalidate = (condition) => `
421
438
  UPDATE live_read_cards SET revision=revision+1
422
- WHERE id IN (SELECT card_id FROM settings_operations WHERE phase='prepared' AND (${condition}));
439
+ WHERE state!='closed' AND id IN (SELECT card_id FROM settings_operations WHERE phase='prepared' AND (${condition}));
423
440
  ${SETTINGS_RESET_PREPARATION_SQL}
424
441
  WHERE phase='prepared' AND (${condition});`;
425
442
  return [...new Set([...userTables, ...walletTables])].flatMap((table) => ["INSERT", "UPDATE", "DELETE"].map((event) => `
@@ -428,7 +445,6 @@ function settingsRevisionTriggers() {
428
445
  ${userTables.includes(table) ? "UPDATE settings_control SET user_data_revision=user_data_revision+1 WHERE id=1;" : ""}
429
446
  ${invalidate([...(userTables.includes(table) ? ["kind IN ('import_local_data','reset_local_data')"] : []),
430
447
  ...(walletTables.includes(table) || table === "review_requests" ? ["kind='restart_wallet_service'"] : []),
431
- ...(table === "local_settings" ? ["kind IN ('set_endpoint','restore_endpoint')"] : []),
432
- ...(table === "active_account_context" ? ["kind='clear_read_address'"] : [])].join(" OR ") || "0")}
448
+ ...(table === "local_settings" ? ["kind IN ('set_endpoint','restore_endpoint')", `kind='set_wallet_networks' AND ${event === "DELETE" ? "OLD" : "NEW"}.key='walletConnectionChains'`] : [])].join(" OR ") || "0")}
433
449
  END;`)).join("\n");
434
450
  }
@@ -280,8 +280,8 @@ export class TransactionActivityService {
280
280
  async resolveActiveAccount() {
281
281
  const active = await this.options.activityStore.getActiveAccount();
282
282
  if (!active) {
283
- throw new TransactionActivityError("active_account_not_set", "Active account read context is not set", {
284
- action: "connect_wallet_connection"
283
+ throw new TransactionActivityError("active_account_not_set", "No saved read address is set. Provide an explicit Sui account for this read.", {
284
+ action: "provide_account"
285
285
  });
286
286
  }
287
287
  return { account: active.address, accountSource: "active_account_context" };
@@ -1,4 +1,4 @@
1
- export const LOCAL_SETTING_KEYS = ["suiGrpcUrl", "suiGraphqlUrl"];
1
+ export const LOCAL_SETTING_KEYS = ["suiGrpcUrl", "suiGraphqlUrl", "walletConnectionChains"];
2
2
  export const SETTINGS_APPLIES_AFTER_RESTART = "mcp_server_restart";
3
3
  export class PreferencesStoreError extends Error {
4
4
  kind;
@@ -19,7 +19,7 @@ export class LocalSettingsError extends Error {
19
19
  }
20
20
  }
21
21
  export function assertLocalSettingKey(key) {
22
- if (key === "suiGrpcUrl" || key === "suiGraphqlUrl") {
22
+ if (key === "suiGrpcUrl" || key === "suiGraphqlUrl" || key === "walletConnectionChains") {
23
23
  return key;
24
24
  }
25
25
  throw new PreferencesStoreError("unknown_key", "Unknown local setting key", { key });
@@ -1,5 +1,6 @@
1
1
  import { PreferencesStoreError, assertLocalSettingKey } from "./preferencesStore.js";
2
2
  import { parseGraphqlUrl, parseGrpcUrl } from "../suiEndpoint.js";
3
+ import { DEFAULT_WALLET_CHAINS, parseWalletChains } from "../session/chainAccounts.js";
3
4
  export class SqlitePreferencesRepository {
4
5
  db;
5
6
  constructor(db) {
@@ -11,6 +12,11 @@ export class SqlitePreferencesRepository {
11
12
  ON CONFLICT(key) DO NOTHING`);
12
13
  insert.run("suiGrpcUrl", encodeSettingValue("suiGrpcUrl", defaults.suiGrpcUrl), now.toISOString());
13
14
  insert.run("suiGraphqlUrl", encodeSettingValue("suiGraphqlUrl", defaults.suiGraphqlUrl), now.toISOString());
15
+ insert.run("walletConnectionChains", encodeSettingValue("walletConnectionChains", DEFAULT_WALLET_CHAINS), now.toISOString());
16
+ }
17
+ getWalletConnectionChainsSync() {
18
+ const record = this.getLocalSettingSync("walletConnectionChains");
19
+ return record ? parseWalletChains(record.value) : [...DEFAULT_WALLET_CHAINS];
14
20
  }
15
21
  async getSuiGrpcUrl() {
16
22
  const record = await this.getLocalSetting("suiGrpcUrl");
@@ -92,6 +98,8 @@ function rowToLocalSetting(row) {
92
98
  };
93
99
  }
94
100
  function encodeSettingValue(key, value) {
101
+ if (key === "walletConnectionChains")
102
+ return JSON.stringify(parseWalletChains(value));
95
103
  if (key === "suiGrpcUrl") {
96
104
  if (typeof value !== "string") {
97
105
  throw new PreferencesStoreError("invalid_value", "suiGrpcUrl setting value must be a string", { key });
@@ -114,6 +122,8 @@ function decodeSettingValue(key, valueJson) {
114
122
  catch {
115
123
  throw new PreferencesStoreError("malformed_json", "Malformed local setting JSON", { key });
116
124
  }
125
+ if (key === "walletConnectionChains")
126
+ return parseWalletChains(parsed);
117
127
  if (key === "suiGrpcUrl") {
118
128
  return asSuiGrpcUrlValue(parsed);
119
129
  }
@@ -67,7 +67,7 @@ export function deepbookQuoteQuantitySemantics(inputAmountKind) {
67
67
  canUseForPaymentAnswer: false,
68
68
  canUseForShortfallAnswer: false,
69
69
  doNotCombineWithPaymentAnswer: true,
70
- requiredPaymentAnswerTool: "read.preview_intent_evidence",
70
+ requiredPaymentAnswerTool: "read_preview_intent_evidence",
71
71
  paymentAnswerUseBlockedReason: "quote_output_is_price_reference_not_payment_answer",
72
72
  requiredPaymentAnswerField: "responseSummary",
73
73
  fiatUsdCashOutAvailable: false,