@zktx.io/sui-mcp 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -42
- package/dist/core/activity/localDataService.js +8 -21
- package/dist/core/activity/localDataValidation.js +22 -46
- package/dist/core/activity/sqliteAccountStore.js +46 -0
- package/dist/core/activity/sqliteActivityStore.js +65 -147
- package/dist/core/activity/sqliteActivityStoreSchema.js +44 -28
- package/dist/core/activity/sqliteActivityStoreTypes.js +9 -0
- package/dist/core/activity/transactionActivityService.js +2 -2
- package/dist/core/preferences/preferencesStore.js +2 -2
- package/dist/core/preferences/sqlitePreferencesRepository.js +10 -0
- package/dist/core/session/cardResult.js +41 -0
- package/dist/core/session/cardSession.js +1 -1
- package/dist/core/session/cardSessionStore.js +51 -19
- package/dist/core/session/chainAccounts.js +120 -0
- package/dist/core/session/sessionStore.js +4 -8
- package/dist/core/session/settingsFileStore.js +3 -30
- package/dist/core/session/settingsOperation.js +27 -19
- package/dist/core/session/settingsWorkflow.js +161 -87
- package/dist/core/session/sqliteCardStore.js +34 -14
- package/dist/core/session/sqliteSessionStore.js +5 -5
- package/dist/core/session/sqliteSettingsWorkflowStore.js +135 -41
- package/dist/core/session/sqliteTransactionRequest.js +18 -0
- package/dist/core/session/sqliteWalletConnectionOperation.js +13 -0
- package/dist/core/session/sqliteWalletWorkflowStore.js +569 -333
- package/dist/core/session/status.js +2 -2
- package/dist/core/session/wait.js +6 -11
- package/dist/core/session/walletConnection.js +71 -46
- package/dist/core/session/walletConnectionOperations.js +24 -0
- package/dist/core/session/walletRuntime.js +7 -13
- package/dist/core/session/walletWorkflow.js +248 -99
- package/dist/core/session/workflowState.js +104 -38
- package/dist/core/session/workflowView.js +92 -101
- package/dist/mcp/activeAccountResponse.js +3 -0
- package/dist/mcp/responseGuidance.js +7 -9
- package/dist/mcp/serverInfo.js +11 -10
- package/dist/mcp/toolErrors.js +5 -3
- package/dist/mcp/toolNames.js +1 -0
- package/dist/mcp/tools/action/prepareSuiActionReview.js +9 -16
- package/dist/mcp/tools/read/readToolHelpers.js +9 -7
- package/dist/mcp/tools/session/executionResultTools.js +2 -2
- package/dist/mcp/tools/session/shared.js +2 -2
- package/dist/mcp/tools/session/statusTools.js +12 -10
- package/dist/mcp/tools/session/walletConnectionTools.js +26 -14
- package/dist/mcp/tools/settings/index.js +7 -4
- package/dist/mcp-app/account.html +7 -7
- package/dist/mcp-app/chart.html +3 -3
- package/dist/mcp-app/connect.html +13 -13
- package/dist/mcp-app/disconnect.html +3637 -0
- package/dist/mcp-app/disconnect.notices.txt +1480 -0
- package/dist/mcp-app/receipt.html +393 -393
- package/dist/mcp-app/review.html +459 -459
- package/dist/mcp-app/settings.html +7 -7
- package/dist/mcp-ui/contracts.js +3 -1
- package/dist/mcp-ui/tools.js +4 -4
- package/dist/runtime/application.js +1 -1
- package/dist/runtime/localSettingsService.js +7 -4
- package/dist/runtime/runtimeTermination.js +3 -0
- package/dist/runtime/smokeMainnetRead.js +5 -4
- package/dist/runtime/walletConnectTransport.js +16 -16
- package/dist/runtime/walletSdkChildRuntime.js +29 -12
- package/dist/runtime/walletSdkIpc.js +5 -4
- package/dist/runtime/walletSdkProcess.js +24 -8
- package/docs/AGENT_BEHAVIOR.md +82 -54
- package/docs/LOCAL_DB_ARCHITECTURE.md +100 -70
- package/docs/MCP_SETUP.md +384 -347
- package/docs/MCP_TOOLS.md +292 -177
- package/docs/SDK_API.md +15 -6
- package/docs/TRANSACTION_ACTIVITY_LOG.md +4 -0
- package/docs/UTILITY_INDEX.md +9 -7
- package/docs/WALLET_CONNECTION.md +162 -167
- package/package.json +1 -1
|
@@ -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
|
|
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,
|
|
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
|
|
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(
|
|
244
|
-
|
|
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.
|
|
248
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
|
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 =
|
|
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
|
|
733
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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", "
|
|
930
|
-
action: "
|
|
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
|
-
|
|
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) {
|
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
2
|
import { EXTERNAL_ACTIVITY_SCAN_MAX_LIMIT } from "./activityStore.js";
|
|
3
|
-
import {
|
|
3
|
+
import { ActivityDatabaseFormatError } 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -376,7 +393,7 @@ export function assertCurrentDatabaseFormat(db) {
|
|
|
376
393
|
return "empty";
|
|
377
394
|
if (signature === expectedSchemaSignature())
|
|
378
395
|
return "current";
|
|
379
|
-
throw new
|
|
396
|
+
throw new ActivityDatabaseFormatError();
|
|
380
397
|
}
|
|
381
398
|
export function initializeDatabase(db) {
|
|
382
399
|
if (assertCurrentDatabaseFormat(db) === "current")
|
|
@@ -413,13 +430,13 @@ function externalActivityScansTableSql() {
|
|
|
413
430
|
)`;
|
|
414
431
|
}
|
|
415
432
|
function settingsRevisionTriggers() {
|
|
416
|
-
const userTables = ["accounts", "
|
|
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
|
}
|
|
@@ -3,3 +3,12 @@ export class ActivityStoreError extends Error {
|
|
|
3
3
|
super(message);
|
|
4
4
|
}
|
|
5
5
|
}
|
|
6
|
+
export class ActivityDatabaseFormatError extends ActivityStoreError {
|
|
7
|
+
static recoveryMessage = "Local database format does not match this runtime. Data migration is not supported. " +
|
|
8
|
+
"The existing database was not modified or deleted. Stop Sui MCP in every app, then follow " +
|
|
9
|
+
"docs/MCP_SETUP.md#database-format-mismatch. Keep the old data and use a new empty SUI_MCP_DATA_DIR, " +
|
|
10
|
+
"or explicitly choose to delete the old local data and settings before restarting.";
|
|
11
|
+
constructor() {
|
|
12
|
+
super(ActivityDatabaseFormatError.recoveryMessage);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -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", "
|
|
284
|
-
action: "
|
|
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 });
|