@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.
- package/README.md +83 -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 +42 -26
- 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/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 +319 -339
- 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
|
@@ -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,
|
|
47
|
+
export function reviewStatusResponse({ session, request, walletAvailability, walletObservation, progress, connectionReconciliationRequired }) {
|
|
48
48
|
const pollingStatus = getExecutionPollingStatus(session, request);
|
|
49
49
|
return {
|
|
50
|
-
walletAvailability, walletObservation, progress,
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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"
|
|
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 {
|
|
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
|
|
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
|
-
|
|
28
|
-
constructor(
|
|
29
|
-
super(message);
|
|
30
|
-
this.
|
|
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,
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
export const CONNECTION_RECONCILIATION_MESSAGE = "
|
|
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:
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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),
|
|
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({
|
|
11
|
-
z.object({
|
|
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) {
|