@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
package/README.md CHANGED
@@ -16,7 +16,7 @@ cause another signing or submission request. Request failures and unknown
16
16
  outcomes are separate from independently observed chain success/failure.
17
17
 
18
18
  Account, Receipt and Chart cards provide read-only evidence independently of
19
- signing. Connect, Review and Settings also run inside the MCP host. Settings
19
+ signing. Connect, Disconnect, Review and Settings also run inside the MCP host. Settings
20
20
  provides local data controls and explicit connection-service recovery. Human-readable review is not a guarantee of safety or
21
21
  of complete interpretation of every transaction detail; the raw PTB structure
22
22
  and addresses remain inspectable.
@@ -27,14 +27,72 @@ Users can ask ordinary questions:
27
27
  - "How much are my USD-denominated assets together?"
28
28
  - "What is the shortfall?"
29
29
 
30
- This document describes `@zktx.io/sui-mcp@0.6.2`. Confirm that version is
31
- published before using npm installation commands. For a local build, follow
32
- [Developer Checkout Setup](docs/MCP_SETUP.md#developer-checkout-setup).
30
+ ## Install
31
+
32
+ Install once with npm, then register the command in each AI app. You need [Node.js 22 or newer](https://nodejs.org/en/download), including npm. No Git checkout, build, custom installation folder, database server, Sui API key or endpoint setup is required for the normal path.
33
+
34
+ This guide describes **0.8.0**. First confirm it is published, then install that exact version:
35
+
36
+ ```sh
37
+ npm view @zktx.io/sui-mcp@0.8.0 version
38
+ npm install -g @zktx.io/sui-mcp@0.8.0
39
+ ```
40
+
41
+ The first command must return `0.8.0`. If it is unavailable, follow the [version availability guidance](docs/MCP_SETUP.md#npm-command-returns-404); do not drop the version and assume an older release matches this guide. Contributors can use the optional [developer checkout](docs/MCP_SETUP.md#developer-checkout-setup).
42
+
43
+ Register the installed command in your app. This is the complete default configuration for Claude Desktop; Cursor's example is in the setup guide:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "sui-mcp": {
49
+ "command": "sui-mcp"
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ Codex uses TOML in `~/.codex/config.toml` instead:
56
+
57
+ ```toml
58
+ [mcp_servers.sui-mcp]
59
+ command = "sui-mcp"
60
+ ```
61
+
62
+ Keep existing settings and other servers when adding these entries. Choose your app's walkthrough for the file location, registration and startup confirmation:
63
+
64
+ - [Codex Desktop / CLI](docs/MCP_SETUP.md#codex)
65
+ - [Claude Desktop](docs/MCP_SETUP.md#claude-desktop)
66
+ - [Claude Code](docs/MCP_SETUP.md#claude-code)
67
+ - [Cursor](docs/MCP_SETUP.md#cursor)
68
+
69
+ After the app loads the registration, ask: **"Use Sui MCP to show its package name, server name, version and network."** Expect `@zktx.io/sui-mcp`, `sui-mcp`, `0.8.0` and `mainnet`. Then ask: **"Use Sui MCP to list the available DeepBook pools on Sui mainnet."** This first read needs no wallet or transaction. The [first-use tutorial](docs/MCP_SETUP.md#first-use-flow) explains expected results, optional cards and wallet connection. If the app cannot find `sui-mcp`, use [Command not found](docs/MCP_SETUP.md#command-not-found).
70
+
71
+ ### Using several apps at once
72
+
73
+ Codex, Claude Desktop and other local MCP apps can use the same installation at the same time on one computer under the same OS user. Register `sui-mcp` in each app. With defaults, they share the local backend, settings, records and **one current wallet connection**; no separate wallet pairing per app is needed. Changing that connection in one app affects the others, while completed cards keep their original results.
74
+
75
+ All apps must use the same package release and configuration. Updates require stopping all participating Sui MCP connections before restarting the shared backend. See [Using Multiple Apps](docs/MCP_SETUP.md#using-multiple-apps) for sharing and update conditions. Interactive wallet, Review and Settings workflows require MCP Apps card support; ordinary reads can be used without it.
76
+
77
+ ### Package identity and local data
78
+
79
+ The npm package is `@zktx.io/sui-mcp`, and its repository is
80
+ `zktx-mcp/sui-mcp`. Confirm the requested version is available before changing
81
+ your client registration.
82
+
83
+ Use the full scoped package name: the unscoped `sui-mcp` package is a different
84
+ project. Use `sui-mcp` as the default MCP registration name. A client-defined registration
85
+ alias is a local label, not server identity or authorization. Compatible
86
+ updates preserve the current data directory. Incompatible databases and backups
87
+ are rejected without conversion, and files are left in place. Data from a
88
+ different package is not discovered or imported. See the
89
+ [setup guide](docs/MCP_SETUP.md#switching-the-installed-package) for installation
90
+ and shared-server restart instructions.
33
91
 
34
92
  ## Product Scope in Two Layers
35
93
 
36
- - **Implemented:** Sui mainnet read evidence, non-signable external proposal review, and account-bound DeepBook swap review using protocol-agnostic adapters. Six internal cards use the same SQLite state and permission model. The backend verifies stored material, ownership, quote/policy provenance, human-readable facts, simulation and PTB evidence before an explicit user request. WalletConnect carries bytes only between the backend and wallet; ordinary MCP responses expose facts, request status and observed execution results. Settings owns endpoint and read-address controls, local backup/import/reset, and explicit connection-service recovery. Settings operations require scoped user confirmation; ordinary MCP calls only create the card or read settings.
37
- - **Never (permanently unsupported at every layer):** no private-key custody, no MCP or AI autonomous execution, no forwarding of opaque external transaction bytes to a wallet, no silent settlement-token or route choice, no fiat cash-out, no P&L, no peg guarantee.
94
+ - **Implemented:** Sui mainnet read evidence, non-signable external proposal review, and account-bound DeepBook swap review using protocol-agnostic adapters. Account, Receipt, Chart, Connect, Disconnect, Review and Settings cards use the same SQLite state and scoped permission model. The backend verifies stored material, ownership, quote/policy provenance, human-readable facts, simulation and PTB evidence before an explicit user request. WalletConnect carries bytes only between the backend and wallet; ordinary MCP responses expose facts, request status and observed execution results. Settings owns endpoint and read-address controls, local backup/import/reset, and explicit connection-service recovery. Settings operations require scoped user confirmation; ordinary MCP calls only create the card or read settings.
95
+ - **Safety boundaries:** no private-key custody, no MCP or AI autonomous execution, no forwarding of opaque external transaction bytes to a wallet, and no peg guarantee. The current product does not silently choose settlement tokens or routes. It does not provide fiat cash-out, P&L, tax or cost-basis support.
38
96
 
39
97
  DeepBook provides the current scoped Sui conversion, price, and orderbook
40
98
  evidence through the shared protocol adapter contracts. Wallet and Sui balance reads
@@ -92,22 +150,33 @@ The current release can run as a local stdio MCP server and expose mainnet Sui D
92
150
  It also includes:
93
151
 
94
152
  - a local review server bound to `127.0.0.1`;
95
- - a local SQLite store for active account read context and review evidence;
153
+ - a local SQLite store for chain-qualified address history and review evidence;
96
154
  - MCP output checks that reject forbidden executable, signing, token, seed, and
97
155
  key-material field names from responses.
98
156
 
99
157
  ## Screens
100
158
 
101
- **Wallet connection.** Connect one Sui mainnet wallet and use one of its approved
102
- addresses. A manage card shows Connect wallet when no connection is active;
103
- opening it alone does not pair. A connect-intent card starts pairing after its
104
- View confirms permission and current state. Each disconnection requires the
105
- user to confirm its exact target. Connection is not transaction approval.
106
-
107
-
108
- **Review and result.** The internal Review card automatically prepares and renews verified conditions for one selected wallet/account. Users inspect its transaction graph, amounts and costs, then choose Request wallet approval and approve in their wallet. One wallet connection is used at a time; choose among its approved addresses when needed. Conflicting saved connections must be resolved through targeted disconnection. Failed preparation has an explicit retry. Receipt and completed Review show the executed transaction graph, actual asset changes and net network fee. Supplementary facts are in one Details slide; earlier estimates in Review remain distinct from actual chain results. Missing graph or receipt data is marked unavailable. Cards use a single document layout without nested card panels.
109
-
110
- **DeepBook USDC chart.** `ui.open_chart` opens an internal MCP Apps card with
159
+ **Wallet connection.** One WalletConnect connection provides one fixed address
160
+ per approved chain. Settings chooses networks for the next connection, defaulting
161
+ to Sui. The wallet may return a subset. Sui, Ethereum and Solana mainnet addresses
162
+ are supported; financial actions remain Sui-only. A chain returning multiple
163
+ addresses is unavailable for that connection; there is no account selector.
164
+ Changing the wallet, address or network scope requires disconnecting and connecting
165
+ again. Connect and Disconnect are separate purpose cards. Connect starts one
166
+ pairing after authenticated View confirmation; Disconnect asks for one exact-target
167
+ confirmation. Ended cards show a fixed address-free result and accept no further
168
+ operation. Current addresses are available through read-only connection status.
169
+ Connection is not transaction approval.
170
+
171
+ **Review and result.** The Review card prepares and renews verified conditions for
172
+ its fixed Sui account and connection. Disconnecting and reconnecting cannot retarget an
173
+ existing review. A replacement connection requires a new review. Users inspect the graph,
174
+ amounts and costs, then choose Request wallet approval and approve in their wallet.
175
+ Failed preparation has an explicit retry. Receipt and completed Review show the
176
+ executed graph, observed asset changes and net network fee. Details supplements
177
+ the same facts. Missing graph or receipt data is marked unavailable.
178
+
179
+ **DeepBook USDC chart.** `ui_open_chart` opens an internal MCP Apps card with
111
180
  official DeepBookV3 Indexer candles and volume for one selected USDC pool.
112
181
  The saved result shows the selected pair, interval, UTC period and source time. Limited and empty results are identified.
113
182
 
@@ -156,11 +225,11 @@ These product behaviors are out of scope in the current release by design:
156
225
 
157
226
  ### Quote Response Limits
158
227
 
159
- Quote tools such as `read.quote_deepbook_action` and
160
- `read.quote_deepbook_display_amount` return scoped quote facts and raw quote
228
+ Quote tools such as `read_quote_deepbook_action` and
229
+ `read_quote_deepbook_display_amount` return scoped quote facts and raw quote
161
230
  evidence only.
162
231
 
163
- Their semantics mark quote output as price evidence, not payment coverage or shortfall evidence. Coverage and shortfall answers come from `read.preview_intent_evidence.responseSummary`.
232
+ Their semantics mark quote output as price evidence, not payment coverage or shortfall evidence. Coverage and shortfall answers come from `read_preview_intent_evidence.responseSummary`.
164
233
 
165
234
  For quote responses alone, these conclusions are unsupported:
166
235
 
@@ -180,33 +249,6 @@ For quote responses alone, these conclusions are unsupported:
180
249
  - cost basis is not available;
181
250
  - actionable signing data is not available.
182
251
 
183
- ## Install
184
-
185
- Use the checkout setup above for unpublished changes. A local commit or successful
186
- package check does not publish a release.
187
-
188
- This release uses npm package `@zktx.io/sui-mcp` and MCP Registry name
189
- `io.github.zktx-mcp/sui-mcp`. Once the requested version is published,
190
- install through your client's registry support or run `npx -y @zktx.io/sui-mcp`.
191
- For per-client configuration (Claude Code, Claude Desktop, Codex, Cursor) and
192
- running from a local checkout, see [docs/MCP_SETUP.md](docs/MCP_SETUP.md).
193
-
194
- ### Package identity and local data
195
-
196
- The npm package is `@zktx.io/sui-mcp`, and its repository is
197
- `zktx-mcp/sui-mcp`. Confirm the requested version is available before changing
198
- your client registration.
199
-
200
- Use the full scoped package name: the unscoped `sui-mcp` package is a different
201
- project. Keep the MCP registration name `sui-mcp`, without a suffix. Compatible
202
- updates preserve the current data directory. Incompatible databases and backups
203
- are rejected without conversion, and files are left in place. Data from a
204
- different package is not discovered or imported. See the
205
- [setup guide](docs/MCP_SETUP.md#switching-the-installed-package) for installation
206
- and shared-server restart instructions.
207
-
208
- After the MCP server is connected, use [docs/MCP_SETUP.md](docs/MCP_SETUP.md#first-use-flow) for first-use setup, [docs/MCP_TOOLS.md](docs/MCP_TOOLS.md) for API fields and statuses, and [docs/AGENT_BEHAVIOR.md](docs/AGENT_BEHAVIOR.md) for user-question flow and response wording.
209
-
210
252
  ## Mainnet-Only Product Surface
211
253
 
212
254
  Product docs, registry, AI responses, UX copy, and signable actions are mainnet-only.
@@ -217,11 +259,11 @@ Unsupported protocol experiments are not product functionality and are not inclu
217
259
 
218
260
  The canonical MCP API reference lives in [docs/MCP_TOOLS.md](docs/MCP_TOOLS.md).
219
261
 
220
- The server also exposes `read.get_server_status`. It returns the package version, evidence policy version, `implementedToolsCount`, and implemented tool list at runtime.
262
+ The server also exposes `read_get_server_status`. It returns the package version, evidence policy version, `implementedToolsCount`, and implemented tool list at runtime.
221
263
 
222
- DeepBook pool-price context is exposed through `read.get_deepbook_mid_price`. Use `docs/MCP_TOOLS.md` for the response fields and unsupported conclusions.
264
+ DeepBook pool-price context is exposed through `read_get_deepbook_mid_price`. Use `docs/MCP_TOOLS.md` for the response fields and unsupported conclusions.
223
265
 
224
- Tool names use dot prefixes because the MCP spec recommends ASCII letters, digits, underscore, hyphen, and dot for tool names. `action.prepare_sui_action_review` returns a `reviewSessionId` and an internal `card`; it does not return executable transaction bytes.
266
+ Tool names use `domain_verb_object`. Names identify an operation; its schema, card visibility and permission checks define its authority. `action_start_review` returns a `reviewSessionId` and an internal `card`; it does not request a signature, submit a transaction or return executable transaction bytes.
225
267
 
226
268
  ## Documentation Map
227
269
 
@@ -237,7 +279,7 @@ Runtime-facing MCP resources currently include:
237
279
  - `protocols/deepbook-v3.md`;
238
280
  - `protocols/deepbook-margin.md`.
239
281
 
240
- Protocol resources are explanatory references. Current support is declared by `read.get_server_status`, `read.list_supported_protocols`, concrete tool schemas, and concrete tool responses, not by protocol Markdown alone.
282
+ Protocol resources are explanatory references. Current support is declared by `read_get_server_status`, `read_list_supported_protocols`, concrete tool schemas, and concrete tool responses, not by protocol Markdown alone.
241
283
 
242
284
  Development-only or release-review documents can define contributor rules and checks.
243
285
 
@@ -248,7 +290,7 @@ AI client answer behavior must be mirrored in runtime-facing instructions, resou
248
290
  - `docs/MCP_TOOLS.md`: API reference: tool contracts, response fields, statuses, follow-up fields, and output boundaries.
249
291
  - `docs/AGENT_BEHAVIOR.md`: Answer playbook: user-question flows, tool selection, and response wording boundaries.
250
292
  - `docs/WALLET_CONNECTION.md`: Wallet connection reference: active read context, private SDK ownership and user-approved transaction requests.
251
- - `protocols/deepbook-v3.md`: Protocol reference only; use MCP tool responses and read.list_supported_protocols for current support.
293
+ - `protocols/deepbook-v3.md`: Protocol reference only; use MCP tool responses and read_list_supported_protocols for current support.
252
294
  - `protocols/deepbook-margin.md`: Protocol reference only; no margin MCP read tools or signable actions are exposed in this release.
253
295
  - `docs/TRANSACTION_ACTIVITY_LOG.md`: transaction activity evidence, storage, scan, and summary boundaries.
254
296
  - `docs/UTILITY_INDEX.md`: manual maintainer and developer utilities. Utility rows are not MCP tools unless they explicitly name an MCP tool, and source-checkout scripts are not packaged product commands.
@@ -35,6 +35,6 @@ export const ADAPTER_PROMPT_SURFACES = [
35
35
  description: 'Prepare a reviewable DeepBook mainnet swap from a one-line intent, e.g. "10 sui to usdc".',
36
36
  intentArgDescription: 'Swap intent in one line, any language, e.g. "10 sui to usdc" or "10 수이 usdc로 환전"',
37
37
  exampleIntents: ["10 sui to usdc", "10 수이 usdc로 환전"],
38
- toolName: "action.prepare_sui_action_review"
38
+ toolName: "action_start_review"
39
39
  }
40
40
  ];
@@ -1,8 +1,9 @@
1
+ import { DEFAULT_WALLET_CHAINS } from "../session/chainAccounts.js";
1
2
  import { SETTINGS_DOWNLOAD_FILE_SQL, SETTINGS_FILE_CLEANUP_SQL } from "../session/settingsFileIdentity.js";
2
3
  import { hasUnsettledWalletWork } from "../session/sqliteWalletWorkflowStore.js";
3
4
  import { SuiEndpointError, parseGraphqlUrl, parseGrpcUrl } from "../suiEndpoint.js";
4
5
  import { LOCAL_DATA_EXPORT_FORMAT, LOCAL_DATA_NETWORK, LocalDataError } from "./localDataTypes.js";
5
- import { activeAccountChange, activeAccountAddress, countsForPayload, invalidBackup, maxNumber, parseLocalDataEnvelope, suiGraphqlUrlFromPayload, suiGrpcUrlFromPayload } from "./localDataValidation.js";
6
+ import { countsForPayload, invalidBackup, maxNumber, parseLocalDataEnvelope, suiGraphqlUrlFromPayload, suiGrpcUrlFromPayload } from "./localDataValidation.js";
6
7
  export * from "./localDataTypes.js";
7
8
  export class SqliteLocalDataService {
8
9
  db;
@@ -32,7 +33,6 @@ export class SqliteLocalDataService {
32
33
  exportedAt: now.toISOString(),
33
34
  data: {
34
35
  accounts: this.selectAll("accounts"),
35
- activeAccountContext: this.selectAll("active_account_context"),
36
36
  reviewSessions: this.selectAll("review_sessions"),
37
37
  reviewStateSnapshots: this.selectAll("review_state_snapshots"),
38
38
  reviewStatusTransitions: this.selectAll("review_status_transitions"),
@@ -55,8 +55,6 @@ export class SqliteLocalDataService {
55
55
  });
56
56
  return this.db.transaction(() => {
57
57
  this.options.advanceRequestDeadlines(now);
58
- const currentAccounts = this.selectAll("accounts");
59
- const currentContext = this.selectAll("active_account_context");
60
58
  return {
61
59
  status: "valid",
62
60
  format: envelope.format,
@@ -65,9 +63,6 @@ export class SqliteLocalDataService {
65
63
  currentCounts: this.getDataCountsSync(),
66
64
  incomingCounts: countsForPayload(envelope.data),
67
65
  willReplace: true,
68
- activeAccountChange: activeAccountChange(currentAccounts, currentContext, envelope.data.accounts, envelope.data.activeAccountContext),
69
- currentReadAddress: activeAccountAddress(currentAccounts, currentContext),
70
- incomingReadAddress: activeAccountAddress(envelope.data.accounts, envelope.data.activeAccountContext),
71
66
  restartRequiredAfterImport: true,
72
67
  };
73
68
  }).immediate();
@@ -89,7 +84,6 @@ export class SqliteLocalDataService {
89
84
  this.resetLocalDataTables(preserveCardId);
90
85
  if (envelope) {
91
86
  this.insertAccounts(envelope.data.accounts);
92
- this.insertActiveAccountContext(envelope.data.activeAccountContext);
93
87
  this.insertReviewSessions(envelope.data.reviewSessions);
94
88
  this.insertReviewStateSnapshots(envelope.data.reviewStateSnapshots);
95
89
  this.insertReviewRequests(envelope.data.reviewRequests);
@@ -121,6 +115,7 @@ export class SqliteLocalDataService {
121
115
  }
122
116
  else
123
117
  this.db.prepare("DELETE FROM live_read_cards").run();
118
+ this.db.prepare("DELETE FROM wallet_connection_operations WHERE sdk_pending=0 AND status!='running' AND card_id NOT IN (SELECT id FROM live_read_cards)").run();
124
119
  for (const table of [
125
120
  "live_execution_details",
126
121
  "live_request_authority",
@@ -135,7 +130,6 @@ export class SqliteLocalDataService {
135
130
  "review_requests",
136
131
  "review_sessions",
137
132
  "coin_metadata_cache",
138
- "active_account_context",
139
133
  "accounts",
140
134
  "local_settings"
141
135
  ]) {
@@ -148,6 +142,7 @@ export class SqliteLocalDataService {
148
142
  VALUES (?, ?, ?)`);
149
143
  statement.run("suiGrpcUrl", JSON.stringify(parseGrpcUrl(this.options.suiGrpcUrl)), now.toISOString());
150
144
  statement.run("suiGraphqlUrl", JSON.stringify(parseGraphqlUrl(this.options.suiGraphqlUrl)), now.toISOString());
145
+ statement.run("walletConnectionChains", JSON.stringify(DEFAULT_WALLET_CHAINS), now.toISOString());
151
146
  }
152
147
  getDataCountsSync() {
153
148
  return {
@@ -170,18 +165,10 @@ export class SqliteLocalDataService {
170
165
  return this.db.prepare(`SELECT * FROM ${table} ORDER BY rowid ASC`).all();
171
166
  }
172
167
  insertAccounts(rows) {
173
- const statement = this.db.prepare(`INSERT INTO accounts (id, sui_address, first_seen_at, last_used_at, first_source, last_source)
174
- VALUES (?, ?, ?, ?, ?, ?)`);
175
- for (const row of rows) {
176
- statement.run(row.id, row.sui_address, row.first_seen_at, row.last_used_at, row.first_source, row.last_source);
177
- }
178
- }
179
- insertActiveAccountContext(rows) {
180
- const statement = this.db.prepare(`INSERT INTO active_account_context (id, account_id, source, set_at, wallet_name, wallet_id)
181
- VALUES (?, ?, ?, ?, ?, ?)`);
182
- for (const row of rows) {
183
- statement.run(row.id, row.account_id, row.source, row.set_at, row.wallet_name ?? null, row.wallet_id ?? null);
184
- }
168
+ const statement = this.db.prepare(`INSERT INTO accounts (id,chain_id,address,first_seen_at,last_used_at,first_source,last_source)
169
+ VALUES (?,?,?,?,?,?,?)`);
170
+ for (const row of rows)
171
+ statement.run(row.id, row.chain_id, row.address, row.first_seen_at, row.last_used_at, row.first_source, row.last_source);
185
172
  }
186
173
  insertReviewSessions(rows) {
187
174
  const statement = this.db.prepare(`INSERT INTO review_sessions
@@ -1,10 +1,11 @@
1
+ import { walletChainSchema, walletChainsSchema, normalizeChainAddress, SUI_MAINNET_WALLET_CHAIN } from "../session/chainAccounts.js";
2
+ import { LOCAL_SETTING_KEYS } from "../preferences/preferencesStore.js";
1
3
  import { transactionRequestStatusSchema, transactionRequestSchema } from "../session/transactionRequest.js";
2
4
  import { REVIEW_TRANSITION_EVENTS } from "./sqliteActivityStoreRows.js";
3
5
  import { z } from "zod";
4
6
  import { assertNoForbiddenMcpFields } from "../action/forbiddenFields.js";
5
7
  import { parseLifecycleValidatedReviewState } from "../action/reviewStateValidation.js";
6
8
  import { actionPlanSchema, transactionExecutionSummarySchema, internalSessionStatusSchema } from "../action/schemas.js";
7
- import { parseSuiAddress } from "../suiAddress.js";
8
9
  import { parseGraphqlUrl, parseGrpcUrl } from "../suiEndpoint.js";
9
10
  import { EXTERNAL_ACTIVITY_TRANSACTION_DETAIL_JSON_MAX_BYTES, externalActivityTransactionDetailJsonByteLength, externalActivityTransactionDetailSchema, externalActivityTransactionDetailsReferenceOnlyAccount } from "./transactionActivityDetails.js";
10
11
  import { LOCAL_DATA_EXPORT_FORMAT, LOCAL_DATA_NETWORK, LocalDataError } from "./localDataTypes.js";
@@ -15,20 +16,14 @@ const isoTimestamp = z.string().refine((value) => {
15
16
  const nullableString = z.string().nullable();
16
17
  const accountRowSchema = z.object({
17
18
  id: z.number().int().positive(),
18
- sui_address: z.string().refine((value) => parseSuiAddress(value) === value, "must be a normalized Sui address"),
19
+ chain_id: walletChainSchema,
20
+ address: z.string(),
19
21
  first_seen_at: isoTimestamp,
20
22
  last_used_at: isoTimestamp,
21
23
  first_source: z.enum(["wallet_connection", "review_execution"]),
22
24
  last_source: z.enum(["wallet_connection", "review_execution"])
23
- }).strict();
24
- const activeAccountContextRowSchema = z.object({
25
- id: z.literal(1),
26
- account_id: z.number().int().positive().nullable(),
27
- source: z.enum(["wallet_connection", "cleared"]),
28
- set_at: isoTimestamp,
29
- wallet_name: z.string().min(1).max(200).nullish(),
30
- wallet_id: z.string().min(1).max(200).nullish()
31
- }).strict();
25
+ }).strict().refine((row) => normalizeChainAddress(row.chain_id, row.address) === row.address &&
26
+ (row.chain_id === SUI_MAINNET_WALLET_CHAIN || row.first_source !== "review_execution" && row.last_source !== "review_execution"), "Account identity and source must match the supported chain");
32
27
  const reviewSessionRowSchema = z.object({
33
28
  id: z.string().min(1),
34
29
  plan_id: z.string().min(1),
@@ -126,13 +121,12 @@ const externalActivityTransactionRowSchema = z.object({
126
121
  detail_json: z.string().nullable()
127
122
  }).strict();
128
123
  const localSettingRowSchema = z.object({
129
- key: z.enum(["suiGrpcUrl", "suiGraphqlUrl"]),
124
+ key: z.enum(LOCAL_SETTING_KEYS),
130
125
  value_json: z.string(),
131
126
  updated_at: isoTimestamp
132
127
  }).strict();
133
128
  const payloadSchema = z.object({
134
129
  accounts: z.array(accountRowSchema),
135
- activeAccountContext: z.array(activeAccountContextRowSchema).max(1),
136
130
  reviewSessions: z.array(reviewSessionRowSchema),
137
131
  reviewStateSnapshots: z.array(reviewStateSnapshotRowSchema),
138
132
  reviewStatusTransitions: z.array(reviewStatusTransitionRowSchema),
@@ -140,7 +134,7 @@ const payloadSchema = z.object({
140
134
  reviewRequests: z.array(reviewRequestRowSchema),
141
135
  externalActivityScans: z.array(externalActivityScanRowSchema),
142
136
  externalActivityTransactions: z.array(externalActivityTransactionRowSchema),
143
- localSettings: z.array(localSettingRowSchema).length(2)
137
+ localSettings: z.array(localSettingRowSchema).length(LOCAL_SETTING_KEYS.length)
144
138
  }).strict();
145
139
  const envelopeSchema = z.object({
146
140
  format: z.literal(LOCAL_DATA_EXPORT_FORMAT),
@@ -160,7 +154,7 @@ export function parseLocalDataEnvelope(input, options) {
160
154
  }
161
155
  function validatePayloadSemantics(data, validateAdapterLifecycle) {
162
156
  ensureUnique(data.accounts.map((row) => row.id), "duplicate_account_id");
163
- ensureUnique(data.accounts.map((row) => row.sui_address), "duplicate_account_address");
157
+ ensureUnique(data.accounts.map((row) => `${row.chain_id}:${row.address}`), "duplicate_account_address");
164
158
  ensureUnique(data.reviewSessions.map((row) => row.id), "duplicate_review_session_id");
165
159
  ensureUnique(data.reviewStateSnapshots.map((row) => row.id), "duplicate_review_state_snapshot_id");
166
160
  ensureUnique(data.reviewStatusTransitions.map((row) => row.id), "duplicate_review_transition_id");
@@ -170,26 +164,18 @@ function validatePayloadSemantics(data, validateAdapterLifecycle) {
170
164
  ensureUnique(data.localSettings.map((row) => row.key), "duplicate_local_setting_key");
171
165
  ensureUnique(data.reviewRequests.map((row) => row.attempt_id), "duplicate_review_request_id");
172
166
  ensureUnique(data.reviewRequests.map((row) => `${row.review_session_id}:${row.review_revision}`), "duplicate_review_request_revision");
173
- const accountIds = new Set(data.accounts.map((row) => row.id));
174
- const accountAddressesById = new Map(data.accounts.map((row) => [row.id, row.sui_address]));
175
- for (const row of data.activeAccountContext) {
176
- if (row.source === "cleared" && row.account_id !== null) {
177
- throw invalidBackup("invalid_active_account_context");
178
- }
179
- if (row.source === "wallet_connection" && (row.account_id === null || !accountIds.has(row.account_id))) {
180
- throw invalidBackup("invalid_active_account_context");
181
- }
182
- }
167
+ const suiAccountIds = new Set(data.accounts.filter((row) => row.chain_id === SUI_MAINNET_WALLET_CHAIN).map((row) => row.id));
168
+ const accountAddressesById = new Map(data.accounts.map((row) => [row.id, row.address]));
183
169
  for (const row of data.reviewSessions) {
184
170
  validateJsonColumn(row.plan_json, "plan_json", actionPlanSchema);
185
171
  validateOptionalJsonColumn(row.intent_json, "intent_json");
186
- if (row.account_id !== null && !accountIds.has(row.account_id)) {
172
+ if (row.account_id !== null && !suiAccountIds.has(row.account_id)) {
187
173
  throw invalidBackup("invalid_review_session_account");
188
174
  }
189
175
  }
190
176
  const reviewSessionIds = new Set(data.reviewSessions.map((row) => row.id));
191
177
  for (const row of data.reviewStateSnapshots) {
192
- if (!reviewSessionIds.has(row.review_session_id) || !accountIds.has(row.account_id)) {
178
+ if (!reviewSessionIds.has(row.review_session_id) || !suiAccountIds.has(row.account_id)) {
193
179
  throw invalidBackup("invalid_review_state_snapshot_reference");
194
180
  }
195
181
  if (row.status === "blocked" && (row.blocked_reason === null || row.refresh_reason !== null)) {
@@ -242,7 +228,7 @@ function validatePayloadSemantics(data, validateAdapterLifecycle) {
242
228
  throw invalidBackup("invalid_review_open_time");
243
229
  }
244
230
  for (const row of data.reviewStatusTransitions) {
245
- if (!reviewSessionIds.has(row.review_session_id) || (row.account_id !== null && !accountIds.has(row.account_id)))
231
+ if (!reviewSessionIds.has(row.review_session_id) || (row.account_id !== null && !suiAccountIds.has(row.account_id)))
246
232
  throw invalidBackup("invalid_review_transition_reference");
247
233
  const requestEvent = ["request_admitted", "request_status_changed", "signature_verified", "chain_result_recorded"].includes(row.event);
248
234
  if ((row.domain === "request") !== requestEvent)
@@ -270,7 +256,7 @@ function validatePayloadSemantics(data, validateAdapterLifecycle) {
270
256
  }
271
257
  const scanIds = new Set(data.externalActivityScans.map((row) => row.scan_id));
272
258
  for (const row of data.externalActivityScans) {
273
- if (!accountIds.has(row.account_id)) {
259
+ if (!suiAccountIds.has(row.account_id)) {
274
260
  throw invalidBackup("invalid_external_activity_scan_account");
275
261
  }
276
262
  if (row.from_timestamp !== null && row.to_timestamp !== null && row.from_timestamp > row.to_timestamp) {
@@ -281,10 +267,10 @@ function validatePayloadSemantics(data, validateAdapterLifecycle) {
281
267
  }
282
268
  }
283
269
  for (const row of data.externalActivityTransactions) {
284
- if (!accountIds.has(row.account_id) || !scanIds.has(row.first_scan_id) || !scanIds.has(row.last_scan_id)) {
270
+ if (!suiAccountIds.has(row.account_id) || !scanIds.has(row.first_scan_id) || !scanIds.has(row.last_scan_id)) {
285
271
  throw invalidBackup("invalid_external_activity_transaction_reference");
286
272
  }
287
- if (row.known_sender_account_id !== null && !accountIds.has(row.known_sender_account_id)) {
273
+ if (row.known_sender_account_id !== null && !suiAccountIds.has(row.known_sender_account_id)) {
288
274
  throw invalidBackup("invalid_external_activity_sender_reference");
289
275
  }
290
276
  if (row.detail_json !== null) {
@@ -310,6 +296,11 @@ function validatePayloadSemantics(data, validateAdapterLifecycle) {
310
296
  }
311
297
  for (const row of data.localSettings) {
312
298
  const value = validateJsonColumn(row.value_json, "local_settings.value_json");
299
+ if (row.key === "walletConnectionChains") {
300
+ if (!walletChainsSchema.safeParse(value).success)
301
+ throw invalidBackup("invalid_wallet_connection_chains");
302
+ continue;
303
+ }
313
304
  if (typeof value !== "string") {
314
305
  throw invalidBackup(row.key === "suiGrpcUrl" ? "invalid_sui_grpc_url" : "invalid_sui_graphql_url");
315
306
  }
@@ -417,21 +408,6 @@ export function countsForPayload(data) {
417
408
  localSettings: data.localSettings.length
418
409
  };
419
410
  }
420
- export function activeAccountChange(currentAccounts, current, incomingAccounts, incoming) {
421
- const currentAccount = activeAccountAddress(currentAccounts, current);
422
- const incomingAccount = activeAccountAddress(incomingAccounts, incoming);
423
- if (currentAccount === incomingAccount) {
424
- return "unchanged";
425
- }
426
- return incomingAccount === null ? "cleared" : "set";
427
- }
428
- export function activeAccountAddress(accounts, context) {
429
- const accountId = context[0]?.account_id ?? null;
430
- if (accountId === null) {
431
- return null;
432
- }
433
- return accounts.find((account) => account.id === accountId)?.sui_address ?? null;
434
- }
435
411
  export function maxNumber(values) {
436
412
  return values.reduce((max, value) => Math.max(max, value), 0);
437
413
  }
@@ -0,0 +1,46 @@
1
+ import { normalizeChainAddress, fixedChainAccount } from "../session/chainAccounts.js";
2
+ import { walletConnectionSchema } from "../session/walletConnection.js";
3
+ import { ActivityStoreError } from "./sqliteActivityStoreTypes.js";
4
+ export class SqliteAccountStore {
5
+ db;
6
+ constructor(db) {
7
+ this.db = db;
8
+ }
9
+ get(chainId, address) {
10
+ const normalized = normalizeChainAddress(chainId, address);
11
+ if (!normalized)
12
+ throw new ActivityStoreError("Invalid chain account address");
13
+ const row = this.db.prepare("SELECT * FROM accounts WHERE chain_id=? AND address=?").get(chainId, normalized);
14
+ return row && this.account(row);
15
+ }
16
+ upsert(chainId, address, source, timestamp) {
17
+ const normalized = normalizeChainAddress(chainId, address);
18
+ if (!normalized)
19
+ throw new ActivityStoreError("Invalid chain account address");
20
+ this.db.prepare(`INSERT INTO accounts (chain_id,address,first_seen_at,last_used_at,first_source,last_source)
21
+ VALUES (?,?,?,?,?,?) ON CONFLICT(chain_id,address) DO UPDATE SET last_used_at=excluded.last_used_at,last_source=excluded.last_source`)
22
+ .run(chainId, normalized, timestamp, timestamp, source, source);
23
+ return this.get(chainId, normalized);
24
+ }
25
+ currentReadAccount(chainId) {
26
+ const row = this.db.prepare(`SELECT connection_json FROM live_wallet_connections
27
+ WHERE current_slot=1 AND read_address_cleared=0 AND data_epoch=(SELECT data_epoch FROM settings_control WHERE id=1)`)
28
+ .get();
29
+ if (!row)
30
+ return undefined;
31
+ const connection = walletConnectionSchema.parse(JSON.parse(row.connection_json));
32
+ const address = fixedChainAccount(connection, chainId);
33
+ if (!address)
34
+ return undefined;
35
+ const account = this.get(chainId, address);
36
+ if (!account)
37
+ return undefined;
38
+ return { accountId: account.id, chainId, address, revision: connection.revision, source: "wallet_connection",
39
+ setAt: connection.approvedAt, connectionId: connection.connectionId,
40
+ ...(connection.walletName ? { walletName: connection.walletName } : {}) };
41
+ }
42
+ account(row) {
43
+ return { id: row.id, chainId: row.chain_id, address: row.address, firstSeenAt: row.first_seen_at,
44
+ lastUsedAt: row.last_used_at, firstSource: row.first_source, lastSource: row.last_source };
45
+ }
46
+ }