@zktx.io/sui-mcp 0.7.0 → 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 (69) hide show
  1. package/README.md +83 -42
  2. package/dist/core/activity/localDataService.js +8 -21
  3. package/dist/core/activity/localDataValidation.js +22 -46
  4. package/dist/core/activity/sqliteAccountStore.js +46 -0
  5. package/dist/core/activity/sqliteActivityStore.js +65 -147
  6. package/dist/core/activity/sqliteActivityStoreSchema.js +42 -26
  7. package/dist/core/activity/transactionActivityService.js +2 -2
  8. package/dist/core/preferences/preferencesStore.js +2 -2
  9. package/dist/core/preferences/sqlitePreferencesRepository.js +10 -0
  10. package/dist/core/session/cardResult.js +41 -0
  11. package/dist/core/session/cardSession.js +1 -1
  12. package/dist/core/session/cardSessionStore.js +51 -19
  13. package/dist/core/session/chainAccounts.js +120 -0
  14. package/dist/core/session/sessionStore.js +4 -8
  15. package/dist/core/session/settingsFileStore.js +3 -30
  16. package/dist/core/session/settingsOperation.js +27 -19
  17. package/dist/core/session/settingsWorkflow.js +161 -87
  18. package/dist/core/session/sqliteCardStore.js +34 -14
  19. package/dist/core/session/sqliteSessionStore.js +5 -5
  20. package/dist/core/session/sqliteSettingsWorkflowStore.js +135 -41
  21. package/dist/core/session/sqliteTransactionRequest.js +18 -0
  22. package/dist/core/session/sqliteWalletConnectionOperation.js +13 -0
  23. package/dist/core/session/sqliteWalletWorkflowStore.js +569 -333
  24. package/dist/core/session/status.js +2 -2
  25. package/dist/core/session/wait.js +6 -11
  26. package/dist/core/session/walletConnection.js +71 -46
  27. package/dist/core/session/walletConnectionOperations.js +24 -0
  28. package/dist/core/session/walletRuntime.js +7 -13
  29. package/dist/core/session/walletWorkflow.js +248 -99
  30. package/dist/core/session/workflowState.js +104 -38
  31. package/dist/core/session/workflowView.js +92 -101
  32. package/dist/mcp/activeAccountResponse.js +3 -0
  33. package/dist/mcp/responseGuidance.js +7 -9
  34. package/dist/mcp/serverInfo.js +11 -10
  35. package/dist/mcp/toolErrors.js +5 -3
  36. package/dist/mcp/toolNames.js +1 -0
  37. package/dist/mcp/tools/action/prepareSuiActionReview.js +9 -16
  38. package/dist/mcp/tools/read/readToolHelpers.js +9 -7
  39. package/dist/mcp/tools/session/executionResultTools.js +2 -2
  40. package/dist/mcp/tools/session/shared.js +2 -2
  41. package/dist/mcp/tools/session/statusTools.js +12 -10
  42. package/dist/mcp/tools/session/walletConnectionTools.js +26 -14
  43. package/dist/mcp/tools/settings/index.js +7 -4
  44. package/dist/mcp-app/account.html +7 -7
  45. package/dist/mcp-app/chart.html +3 -3
  46. package/dist/mcp-app/connect.html +13 -13
  47. package/dist/mcp-app/disconnect.html +3637 -0
  48. package/dist/mcp-app/disconnect.notices.txt +1480 -0
  49. package/dist/mcp-app/receipt.html +393 -393
  50. package/dist/mcp-app/review.html +459 -459
  51. package/dist/mcp-app/settings.html +7 -7
  52. package/dist/mcp-ui/contracts.js +3 -1
  53. package/dist/mcp-ui/tools.js +4 -4
  54. package/dist/runtime/application.js +1 -1
  55. package/dist/runtime/localSettingsService.js +7 -4
  56. package/dist/runtime/smokeMainnetRead.js +5 -4
  57. package/dist/runtime/walletConnectTransport.js +16 -16
  58. package/dist/runtime/walletSdkChildRuntime.js +29 -12
  59. package/dist/runtime/walletSdkIpc.js +5 -4
  60. package/dist/runtime/walletSdkProcess.js +24 -8
  61. package/docs/AGENT_BEHAVIOR.md +82 -54
  62. package/docs/LOCAL_DB_ARCHITECTURE.md +100 -70
  63. package/docs/MCP_SETUP.md +319 -339
  64. package/docs/MCP_TOOLS.md +292 -177
  65. package/docs/SDK_API.md +15 -6
  66. package/docs/TRANSACTION_ACTIVITY_LOG.md +4 -0
  67. package/docs/UTILITY_INDEX.md +9 -7
  68. package/docs/WALLET_CONNECTION.md +162 -167
  69. package/package.json +1 -1
@@ -44,10 +44,10 @@ export function executionPollingHint() {
44
44
  recommendedIntervalSeconds: EXECUTION_POLLING_INTERVAL_SECONDS
45
45
  };
46
46
  }
47
- export function reviewStatusResponse({ session, request, walletAvailability, walletObservation, progress, connectionConflict, connectionReconciliationRequired }) {
47
+ export function reviewStatusResponse({ session, request, walletAvailability, walletObservation, progress, connectionReconciliationRequired }) {
48
48
  const pollingStatus = getExecutionPollingStatus(session, request);
49
49
  return {
50
- walletAvailability, walletObservation, progress, connectionConflict, connectionReconciliationRequired,
50
+ walletAvailability, walletObservation, progress, connectionReconciliationRequired,
51
51
  reviewSessionId: session.id, status: session.status, reviewRevision: session.reviewRevision,
52
52
  account: session.account, plans: session.plans, reviewState: session.reviewState,
53
53
  pollingStatus, statusCategory: executionStatusCategory(pollingStatus), pollingHint: executionPollingHint(),
@@ -1,6 +1,5 @@
1
1
  import { EXECUTION_POLLING_INTERVAL_SECONDS, getExecutionPollingStatus, executionStatusCategory, isWaitStoppingExecutionStatus } from "./status.js";
2
2
  import { SessionStoreError } from "./sessionStore.js";
3
- import { workflowViewSchema } from "./workflowView.js";
4
3
  import { WALLET_CONNECTION_POLL_SECONDS } from "./walletConnection.js";
5
4
  export const DEFAULT_WAIT_TIMEOUT_MS = 45_000;
6
5
  export const MAX_WAIT_TIMEOUT_MS = 55_000;
@@ -15,20 +14,16 @@ export class WaitRequestAbortedError extends Error {
15
14
  export async function waitForWalletConnection(cards, cardId, options = {}) {
16
15
  const timeout = options.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
17
16
  const started = Date.now(), sleep = options.sleep ?? sleepMs;
18
- let snapshot;
19
17
  while (true) {
20
18
  assertNotAborted(options.signal);
21
- snapshot = await cards.readSaved(cardId);
22
- const view = workflowViewSchema.parse(snapshot.data);
23
- if (snapshot.kind !== "connect")
24
- throw new SessionStoreError("input_invalid", "A connection card ID is required.");
25
- if (view.progress.status === "unavailable")
26
- return { waitOutcome: "unavailable", snapshot };
27
- if (!view.observe)
28
- return { waitOutcome: "status_reached", snapshot };
19
+ const state = await cards.readWalletOperation(cardId), operation = state.operation;
20
+ if (!operation || operation.status !== "running" && !operation.settlementPending)
21
+ return { ...state, waitOutcome: "status_reached" };
22
+ if (state.walletAvailability.status !== "available")
23
+ return { ...state, waitOutcome: "unavailable" };
29
24
  const remaining = timeout - (Date.now() - started);
30
25
  if (remaining <= 0)
31
- return { waitOutcome: "timed_out", snapshot };
26
+ return { ...state, waitOutcome: "timed_out" };
32
27
  await sleep(Math.min(remaining, WALLET_CONNECTION_POLL_SECONDS * 1000), options.signal);
33
28
  }
34
29
  }
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
- import { suiAddressStringSchema } from "../suiAddress.js";
2
+ import { walletChainResultsSchema, fixedChainAccount, walletChainSchema, walletChainsSchema, walletGrantsSchema, walletGrantAccount, walletGrantRevisionsSchema, SUI_MAINNET_WALLET_CHAIN } from "./chainAccounts.js";
3
3
  import { transactionRequestStatusSchema } from "./transactionRequest.js";
4
- export const SUI_MAINNET_WALLET_CHAIN = "sui:mainnet";
4
+ export { SUI_MAINNET_WALLET_CHAIN } from "./chainAccounts.js";
5
5
  export const SUI_SIGN_TRANSACTION_METHOD = "sui_signTransaction";
6
6
  export const WALLET_CONNECTION_POLL_SECONDS = 5;
7
7
  export const walletRunIdSchema = z.string().uuid();
@@ -24,10 +24,10 @@ export function walletUnavailable(reason, walletRunId) {
24
24
  return { status: "unavailable", reason, message, ...(walletRunId === undefined ? {} : { walletRunId }) };
25
25
  }
26
26
  export class WalletUnavailableError extends Error {
27
- reason;
28
- constructor(reason, message = walletUnavailable(reason).message) {
29
- super(message);
30
- this.reason = reason;
27
+ availability;
28
+ constructor(availability) {
29
+ super(availability.message);
30
+ this.availability = availability;
31
31
  }
32
32
  }
33
33
  export const workflowProgressSchema = z.discriminatedUnion("status", [
@@ -49,7 +49,7 @@ export const walletConnectionStatusSchema = z.enum([
49
49
  export const walletRecoveryImpactSchema = z.object({
50
50
  runId: z.string().uuid(),
51
51
  connections: z.array(z.object({ connectionId: z.string().min(1), revision: z.number().int().nonnegative(),
52
- status: walletConnectionStatusSchema, accounts: z.array(suiAddressStringSchema), walletName: z.string().optional(),
52
+ status: walletConnectionStatusSchema, chainResults: walletChainResultsSchema, walletName: z.string().optional(),
53
53
  sdkPending: z.boolean(), disconnectPending: z.boolean() }).strict()),
54
54
  preparations: z.array(z.object({ id: z.string().min(1), revision: z.number().int().nonnegative(),
55
55
  review_revision: z.number().int().nonnegative(), preparation_id: z.string().nullable(), wallet_connection_id: z.string().nullable() }).strict()),
@@ -58,47 +58,72 @@ export const walletRecoveryImpactSchema = z.object({
58
58
  sdk_pending: z.number().int().min(0).max(1), submit_pending: z.number().int().min(0).max(1), lookup_pending: z.number().int().min(0).max(1) }).strict())
59
59
  }).strict();
60
60
  export const walletConnectionSchema = z.object({
61
- connectionId: z.string().min(1),
62
- status: walletConnectionStatusSchema,
63
- revision: z.number().int().nonnegative(),
64
- accounts: z.array(suiAddressStringSchema),
65
- methods: z.array(z.string()),
66
- chain: z.literal(SUI_MAINNET_WALLET_CHAIN),
67
- createdAt: z.string().datetime(),
68
- updatedAt: z.string().datetime(),
69
- expiresAt: z.string().datetime(),
70
- walletName: z.string().optional(),
71
- reason: z.string().optional()
72
- }).strict();
73
- export const connectionConflictSchema = z.object({
74
- reason: z.literal("multiple_connections"),
75
- connectionIds: z.array(z.string().min(1)).min(2).refine((ids) => new Set(ids).size === ids.length)
76
- }).strict();
77
- export const CONNECTION_CONFLICT_MESSAGE = "More than one wallet connection is saved. Disconnect the connections you no longer need before continuing.";
78
- export const CONNECTION_RECONCILIATION_MESSAGE = "A wallet connection in the connection service is not recorded here. Remove the retained connection in your wallet app, then check its status. If needed, open Settings and use Troubleshooting to restart the connection service and inspect it again. Restarting does not approve an unrecorded connection.";
61
+ connectionId: z.string().min(1), status: walletConnectionStatusSchema,
62
+ revision: z.number().int().nonnegative(), requestedChains: walletChainsSchema,
63
+ chainResults: walletChainResultsSchema, approvedAt: z.string().datetime().optional(),
64
+ grants: walletGrantsSchema, grantRevisions: walletGrantRevisionsSchema,
65
+ lifecycleRevision: z.number().int().nonnegative(),
66
+ createdAt: z.string().datetime(), updatedAt: z.string().datetime(), expiresAt: z.string().datetime(),
67
+ walletName: z.string().optional(), reason: z.string().optional()
68
+ }).strict().superRefine((connection, context) => {
69
+ if (connection.chainResults.some((item) => !connection.requestedChains.includes(item.chainId)) ||
70
+ connection.approvedAt && connection.chainResults.length !== connection.requestedChains.length ||
71
+ !connection.approvedAt && connection.chainResults.length) {
72
+ context.addIssue({ code: "custom", message: "Approval results must match the exact requested networks." });
73
+ }
74
+ });
75
+ export const connectionChainStateSchema = z.object({ chainId: walletChainSchema,
76
+ status: z.enum(["available", "multiple_accounts", "not_approved", "invalid_response", "unavailable"]), address: z.string().optional(),
77
+ suiSigningScopeApproved: z.boolean(), reason: z.string().optional() }).strict();
78
+ export const CONNECTION_RECONCILIATION_MESSAGE = "An unexpected wallet session is retained by the connection service. Remove it in your wallet app, then check status. Only one wallet connection can be used here.";
79
79
  export const assetReadAccountSchema = z.discriminatedUnion("status", [
80
- z.object({ status: z.literal("available"), account: suiAddressStringSchema }).strict(),
81
- z.object({ status: z.literal("address_required") }).strict()
80
+ z.object({ status: z.literal("available"), account: z.string() }).strict(),
81
+ z.object({ status: z.literal("address_required"), reason: z.literal("read_address_cleared").optional() }).strict()
82
82
  ]);
83
- export function walletConnectionSelection(connections, at, presence, stored) {
84
- const connected = connections.filter((item) => item.status === "connected" && Date.parse(item.expiresAt) > at);
85
- const pending = presence.pendingConnectionIds.length > 0 || connections.some((item) => item.pendingAction || item.status === "awaiting_approval");
86
- const presentIds = presence.connections.map((item) => item.connectionId);
87
- const occupied = new Set([...connected.map((item) => item.connectionId), ...presentIds]);
88
- const reconciliation = presence.confirmed && presence.unrecorded && !pending;
89
- const connection = presence.confirmed && !presence.unrecorded && occupied.size === 1 && connected.length === 1 &&
90
- presence.connections.some((item) => item.connectionId === connected[0].connectionId && Date.parse(item.expiresAt) > at) && !pending ? connected[0] : undefined;
91
- return {
92
- ...(occupied.size > 1 ? { conflict: { reason: "multiple_connections",
93
- connectionIds: [...occupied].sort() } } : {}),
94
- ...(reconciliation ? { connectionReconciliationRequired: true } : {}),
95
- manageableConnectionIds: connections.filter((item) => !presence.pendingConnectionIds.includes(item.connectionId) && !item.pendingAction && item.status !== "awaiting_approval" &&
96
- (item.status === "connected" || presence.confirmed && presentIds.includes(item.connectionId))).map((item) => item.connectionId),
97
- ...(connection ? { connection } : {}),
98
- pairingAllowed: presence.confirmed && !presence.unrecorded && occupied.size === 0 && !pending,
99
- assetReadAccount: connection && stored?.walletId === connection.connectionId && connection.accounts.includes(stored.address)
100
- ? { status: "available", account: stored.address } : { status: "address_required" }
101
- };
83
+ function connectionSessionPresence(connection, presence) {
84
+ if (!presence.confirmed || !presence.managed)
85
+ return "unconfirmed";
86
+ if (presence.hasRecordedSession)
87
+ return presence.observation?.status ?? "unconfirmed";
88
+ return !connection?.approvedAt && !presence.pending && !presence.unrecorded ? "absent" : "unconfirmed";
89
+ }
90
+ export function usableWalletConnection(connection, at, presence, connectionId, chainId, address) {
91
+ if (!connection || connection.connectionId !== connectionId || !presence.hasRecordedSession || !presence.confirmed || !presence.managed ||
92
+ !presence.dataCurrent || presence.pending || presence.observation?.status !== "present")
93
+ return undefined;
94
+ const fixed = fixedChainAccount(connection, chainId);
95
+ return fixed && (address === undefined || address === fixed) && connection.status === "connected" &&
96
+ Date.parse(connection.expiresAt) > at && Date.parse(presence.observation.expiresAt) > at &&
97
+ walletGrantAccount(connection.grants, chainId, fixed) ? connection : undefined;
98
+ }
99
+ export function walletConnectionEligibility(connection, at, presence, readAccount, readAddressCleared = false) {
100
+ const usable = connection && usableWalletConnection(connection, at, presence, connection.connectionId, SUI_MAINNET_WALLET_CHAIN);
101
+ const sessionPresence = connectionSessionPresence(connection, presence);
102
+ const manageable = !!(connection && !presence.pending && ["present", "unusable"].includes(sessionPresence));
103
+ return { ...(presence.confirmed && presence.unrecorded ? { connectionReconciliationRequired: true } : {}),
104
+ ...(usable ? { connection: usable } : {}), manageable,
105
+ pairingAllowed: presence.confirmed && presence.managed && !presence.unrecorded && !presence.pending && sessionPresence === "absent",
106
+ assetReadAccount: usable && readAccount && readAccount === fixedChainAccount(usable, SUI_MAINNET_WALLET_CHAIN) && !readAddressCleared ? { status: "available", account: readAccount }
107
+ : { status: "address_required", ...(readAddressCleared ? { reason: "read_address_cleared" } : {}) } };
108
+ }
109
+ export function connectionHasSuiSigningScope(connection, account) {
110
+ const grant = walletGrantAccount(connection.grants, SUI_MAINNET_WALLET_CHAIN, account);
111
+ return fixedChainAccount(connection, SUI_MAINNET_WALLET_CHAIN) === account &&
112
+ !!grant?.methods.includes(SUI_SIGN_TRANSACTION_METHOD) && ["accountsChanged", "chainChanged"].every((event) => grant.events.includes(event));
113
+ }
114
+ export function projectWalletConnection(connection, at, presence, available) {
115
+ const sessionPresence = available ? connectionSessionPresence(connection, presence) : "unconfirmed";
116
+ return connection && { ...connection, sessionPresence, chainStates: connection.chainResults.map((result) => {
117
+ const address = result.status === "bound" ? result.address : undefined;
118
+ const usable = !!(available && address && usableWalletConnection(connection, at, presence, connection.connectionId, result.chainId, address));
119
+ return { chainId: result.chainId, status: result.status === "bound" ? usable ? "available" : "unavailable" : result.status,
120
+ ...(address ? { address } : {}), suiSigningScopeApproved: result.chainId === SUI_MAINNET_WALLET_CHAIN && !!address && connectionHasSuiSigningScope(connection, address),
121
+ ...(!usable ? { reason: sessionPresence === "absent" ? "The wallet session is no longer present. Connect again to use this network."
122
+ : result.status === "multiple_accounts" ? "The wallet returned several addresses. Disconnect and connect again with one account for this network."
123
+ : result.status === "not_approved" ? "The wallet did not approve this network."
124
+ : result.status === "invalid_response" ? "The wallet returned invalid account information."
125
+ : "This fixed address is not currently usable through this connection." } : {}) };
126
+ }) };
102
127
  }
103
128
  export class WalletUserRejectedError extends Error {
104
129
  constructor() { super("The wallet request was rejected by the user."); }
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ import { walletChainsSchema } from "./chainAccounts.js";
3
+ import { walletRunIdSchema } from "./walletConnection.js";
4
+ const operationId = z.string().uuid();
5
+ const command = { operationId, walletRunId: walletRunIdSchema };
6
+ export const walletConnectionActionSchema = z.discriminatedUnion("action", [
7
+ z.object({ action: z.literal("connect"), ...command, chains: walletChainsSchema }).strict(),
8
+ z.object({ action: z.literal("disconnect"), ...command, connectionId: z.string().min(1), impactId: z.string().min(1) }).strict()
9
+ ]);
10
+ export const walletConnectionCardInputSchema = z.object({}).strict();
11
+ export const walletConnectionOperationSchema = z.object({
12
+ operationId, cardId: z.string().min(1), action: z.enum(["connect", "disconnect"]),
13
+ status: z.enum(["running", "completed", "failed", "stopped"]), connectionId: z.string().min(1),
14
+ createdAt: z.string().datetime(), updatedAt: z.string().datetime(), expiresAt: z.string().datetime(),
15
+ settlementPending: z.boolean(), message: z.string().optional(),
16
+ endReason: z.enum(["user_stopped", "expired", "run_ended"]).optional()
17
+ }).strict();
18
+ export const pendingWalletConnectionOperationSchema = walletConnectionOperationSchema.pick({
19
+ operationId: true, action: true, status: true, settlementPending: true
20
+ });
21
+ export function publicConnectionOperation(record) {
22
+ const { ownerId: _owner, runId: _run, dataEpoch: _epoch, input: _input, ...result } = record;
23
+ return result;
24
+ }
@@ -1,27 +1,21 @@
1
1
  import { z } from "zod";
2
- import { suiAddressStringSchema } from "../suiAddress.js";
3
2
  import { SUI_MAINNET_WALLET_CHAIN, walletRunIdSchema } from "./walletConnection.js";
3
+ import { walletGrantsSchema, walletGrantRevisionsSchema } from "./chainAccounts.js";
4
4
  export { walletRunIdSchema, walletStartStageSchema } from "./walletConnection.js";
5
5
  export const walletSessionSchema = z.object({
6
- topic: z.string().min(1), accounts: z.array(suiAddressStringSchema).min(1), methods: z.array(z.string()),
7
- chain: z.literal(SUI_MAINNET_WALLET_CHAIN), expiresAt: z.string().datetime(), walletName: z.string().optional()
6
+ topic: z.string().min(1), grants: walletGrantsSchema, expiresAt: z.string().datetime(), walletName: z.string().optional()
8
7
  }).strict();
8
+ const observationIdentity = { topic: z.string().min(1), lifecycleRevision: z.number().int().positive(), grantRevisions: walletGrantRevisionsSchema };
9
9
  export const walletSessionObservationSchema = z.discriminatedUnion("status", [
10
- z.object({ topic: z.string().min(1), version: z.number().int().positive(), status: z.literal("present"), session: walletSessionSchema }).strict(),
11
- z.object({ topic: z.string().min(1), version: z.number().int().positive(), status: z.enum(["absent", "unusable"]) }).strict()
10
+ z.object({ ...observationIdentity, status: z.literal("present"), session: walletSessionSchema }).strict(),
11
+ z.object({ ...observationIdentity, status: z.enum(["absent", "unusable"]) }).strict()
12
12
  ]);
13
+ export const suiWalletRevisionSchema = z.object({ chainId: z.literal(SUI_MAINNET_WALLET_CHAIN),
14
+ lifecycleRevision: z.number().int().positive(), grantRevision: z.number().int().nonnegative() });
13
15
  export const walletObservationSchema = z.object({
14
16
  runId: walletRunIdSchema, sequence: z.number().int().positive(), observedAt: z.string().datetime()
15
17
  }).strict();
16
18
  export const walletSnapshotSchema = walletObservationSchema.extend({ sessions: z.array(walletSessionObservationSchema) }).strict();
17
- const recoveryBase = z.object({ kind: z.literal("wallet_service_recovery"), priorRunId: walletRunIdSchema,
18
- admittedAt: z.string().datetime(), updatedAt: z.string().datetime() });
19
- export const walletRecoverySchema = z.union([
20
- recoveryBase.extend({ phase: z.literal("stopping") }).strict(),
21
- recoveryBase.extend({ phase: z.literal("starting"), nextRunId: walletRunIdSchema }).strict(),
22
- recoveryBase.extend({ outcome: z.enum(["available", "failed", "superseded", "server_restarted"]),
23
- nextRunId: walletRunIdSchema.optional(), message: z.string().optional() }).strict()
24
- ]);
25
19
  export class WalletRunInterruptedError extends Error {
26
20
  runId;
27
21
  constructor(runId) {