@zktx.io/sui-mcp 0.5.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 (218) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSES/@mysten-bcs-2.0.5-Apache-2.0.txt +205 -0
  3. package/LICENSES/@mysten-sui-2.17.0-Apache-2.0.txt +201 -0
  4. package/LICENSES/@mysten-utils-0.3.3-Apache-2.0.txt +205 -0
  5. package/LICENSES/fancy-canvas-2.1.0-MIT.txt +7 -0
  6. package/LICENSES/lightweight-charts-5.2.0-NOTICE.txt +2 -0
  7. package/README.md +288 -0
  8. package/dist/adapters/adapterLifecycleValidators.js +5 -0
  9. package/dist/adapters/adapterPromptSurfaces.js +60 -0
  10. package/dist/adapters/deepbook/deepbookHumanReviewProducer.js +178 -0
  11. package/dist/adapters/deepbook/deepbookQuotePolicy.js +112 -0
  12. package/dist/adapters/deepbook/deepbookReviewEvidence.js +507 -0
  13. package/dist/adapters/deepbook/deepbookReviewLifecycle.js +85 -0
  14. package/dist/adapters/deepbook/deepbookSwapIntent.js +79 -0
  15. package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +254 -0
  16. package/dist/adapters/intentPlanFactories.js +51 -0
  17. package/dist/adapters/reviewAdapters.js +47 -0
  18. package/dist/core/action/adapterLifecycleValidation.js +12 -0
  19. package/dist/core/action/contractNameRegistry.js +151 -0
  20. package/dist/core/action/forbiddenFields.js +43 -0
  21. package/dist/core/action/humanReadableReviewEvidence.js +193 -0
  22. package/dist/core/action/humanReadableReviewProjectionVerifier.js +29 -0
  23. package/dist/core/action/ptbVisualizationProducer.js +71 -0
  24. package/dist/core/action/receiptPtbGraph.js +29 -0
  25. package/dist/core/action/reviewCheckResults.js +6 -0
  26. package/dist/core/action/reviewStateValidation.js +11 -0
  27. package/dist/core/action/reviewTimeSimulationEvidence.js +470 -0
  28. package/dist/core/action/schemas.js +491 -0
  29. package/dist/core/action/signableAdapterContract.js +998 -0
  30. package/dist/core/action/suiChainReceiptEvidence.js +106 -0
  31. package/dist/core/action/suiChainReceiptMapping.js +107 -0
  32. package/dist/core/action/suiChainReceiptReader.js +211 -0
  33. package/dist/core/action/suiChainReceiptVerifier.js +178 -0
  34. package/dist/core/action/swapHumanReadableReviewProjection.js +130 -0
  35. package/dist/core/action/swapQuotePolicyEvidence.js +271 -0
  36. package/dist/core/action/transactionFunding.js +244 -0
  37. package/dist/core/action/transactionObjectOwnershipEvidence.js +256 -0
  38. package/dist/core/action/transactionObjectOwnershipProducer.js +323 -0
  39. package/dist/core/action/types.js +33 -0
  40. package/dist/core/action/walletReviewContractAssembler.js +286 -0
  41. package/dist/core/activity/accountAssetTimeline.js +157 -0
  42. package/dist/core/activity/accountAssetTimelineUsdcReferences.js +120 -0
  43. package/dist/core/activity/activityStore.js +16 -0
  44. package/dist/core/activity/externalActivityCoverage.js +76 -0
  45. package/dist/core/activity/externalActivityTransactionStream.js +82 -0
  46. package/dist/core/activity/localDataService.js +296 -0
  47. package/dist/core/activity/localDataTypes.js +11 -0
  48. package/dist/core/activity/localDataValidation.js +439 -0
  49. package/dist/core/activity/schemaVersion.js +2 -0
  50. package/dist/core/activity/sqliteActivityStore.js +1068 -0
  51. package/dist/core/activity/sqliteActivityStoreRows.js +389 -0
  52. package/dist/core/activity/sqliteActivityStoreSchema.js +367 -0
  53. package/dist/core/activity/sqliteActivityStoreTypes.js +5 -0
  54. package/dist/core/activity/suiFunctionTarget.js +43 -0
  55. package/dist/core/activity/transactionActivityAccountEffects.js +186 -0
  56. package/dist/core/activity/transactionActivityAnalysis.js +295 -0
  57. package/dist/core/activity/transactionActivityClassifier.js +306 -0
  58. package/dist/core/activity/transactionActivityDetails.js +229 -0
  59. package/dist/core/activity/transactionActivityProtocolRules.js +218 -0
  60. package/dist/core/activity/transactionActivityScanPolicy.js +170 -0
  61. package/dist/core/activity/transactionActivityService.js +379 -0
  62. package/dist/core/activity/transactionActivityTypes.js +18 -0
  63. package/dist/core/eventlog/sink.js +35 -0
  64. package/dist/core/evidence/settlementFamilies.js +85 -0
  65. package/dist/core/evidence/userAnswerUse.js +1 -0
  66. package/dist/core/numeric/rawU64.js +63 -0
  67. package/dist/core/preferences/preferencesStore.js +26 -0
  68. package/dist/core/preferences/sqlitePreferencesRepository.js +136 -0
  69. package/dist/core/proposal/externalProposalReview.js +347 -0
  70. package/dist/core/proposal/schemas.js +208 -0
  71. package/dist/core/proposal/types.js +35 -0
  72. package/dist/core/read/amounts.js +14 -0
  73. package/dist/core/read/coinMetadata.js +60 -0
  74. package/dist/core/read/deepbookOfficialIndexerSource.js +373 -0
  75. package/dist/core/read/deepbookRawQuoteClient.js +86 -0
  76. package/dist/core/read/deepbookReadHelpers.js +321 -0
  77. package/dist/core/read/deepbookRegistry.js +157 -0
  78. package/dist/core/read/deepbookSourceOwners.js +159 -0
  79. package/dist/core/read/deepbookUsdcChartService.js +342 -0
  80. package/dist/core/read/intentEvidenceResponseFormatting.js +228 -0
  81. package/dist/core/read/readCardInputs.js +19 -0
  82. package/dist/core/read/readCardResult.js +40 -0
  83. package/dist/core/read/readResponseGuidance.js +402 -0
  84. package/dist/core/read/readService.js +1639 -0
  85. package/dist/core/read/readServiceTypes.js +88 -0
  86. package/dist/core/read/settlementParityFormatting.js +82 -0
  87. package/dist/core/read/walletReadHelpers.js +99 -0
  88. package/dist/core/review/reviewChecks.js +45 -0
  89. package/dist/core/review/reviewComputation.js +38 -0
  90. package/dist/core/review/reviewComputationResult.js +87 -0
  91. package/dist/core/session/cardSession.js +31 -0
  92. package/dist/core/session/cardSessionStore.js +204 -0
  93. package/dist/core/session/keyedRecordStore.js +18 -0
  94. package/dist/core/session/liveReviewSessionContract.js +3 -0
  95. package/dist/core/session/localSession.js +31 -0
  96. package/dist/core/session/privateReviewArtifacts.js +75 -0
  97. package/dist/core/session/reviewValidity.js +49 -0
  98. package/dist/core/session/sessionErrors.js +9 -0
  99. package/dist/core/session/sessionRecordStore.js +40 -0
  100. package/dist/core/session/sessionStore.js +655 -0
  101. package/dist/core/session/settingsSession.js +1 -0
  102. package/dist/core/session/settingsSessions.js +45 -0
  103. package/dist/core/session/sqliteCardStore.js +88 -0
  104. package/dist/core/session/sqliteSessionStore.js +213 -0
  105. package/dist/core/session/sqliteTransactionMaterialStore.js +64 -0
  106. package/dist/core/session/sqliteWalletWorkflowStore.js +821 -0
  107. package/dist/core/session/status.js +60 -0
  108. package/dist/core/session/transactionMaterialStore.js +221 -0
  109. package/dist/core/session/transactionRequest.js +72 -0
  110. package/dist/core/session/wait.js +79 -0
  111. package/dist/core/session/walletConnection.js +92 -0
  112. package/dist/core/session/walletRuntime.js +38 -0
  113. package/dist/core/session/walletWorkflow.js +608 -0
  114. package/dist/core/session/workflowState.js +80 -0
  115. package/dist/core/session/workflowView.js +150 -0
  116. package/dist/core/suiAddress.js +18 -0
  117. package/dist/core/suiEndpoint.js +72 -0
  118. package/dist/mcp/activeAccountResponse.js +24 -0
  119. package/dist/mcp/prompts.js +146 -0
  120. package/dist/mcp/registerTool.js +19 -0
  121. package/dist/mcp/resources.js +72 -0
  122. package/dist/mcp/responseGuidance.js +326 -0
  123. package/dist/mcp/result.js +17 -0
  124. package/dist/mcp/schemas.js +11 -0
  125. package/dist/mcp/server.js +32 -0
  126. package/dist/mcp/serverInfo.js +131 -0
  127. package/dist/mcp/toolErrors.js +118 -0
  128. package/dist/mcp/toolNames.js +58 -0
  129. package/dist/mcp/tools/account/index.js +44 -0
  130. package/dist/mcp/tools/action/prepareSuiActionReview.js +126 -0
  131. package/dist/mcp/tools/read/commonSchemas.js +43 -0
  132. package/dist/mcp/tools/read/deepbookReadTools.js +686 -0
  133. package/dist/mcp/tools/read/index.js +14 -0
  134. package/dist/mcp/tools/read/readToolHelpers.js +97 -0
  135. package/dist/mcp/tools/read/reviewActivityTools.js +179 -0
  136. package/dist/mcp/tools/read/serverStatusTools.js +100 -0
  137. package/dist/mcp/tools/read/transactionActivityOutput.js +371 -0
  138. package/dist/mcp/tools/read/transactionActivityTools.js +858 -0
  139. package/dist/mcp/tools/read/walletReadTools.js +734 -0
  140. package/dist/mcp/tools/session/executionResultTools.js +44 -0
  141. package/dist/mcp/tools/session/index.js +8 -0
  142. package/dist/mcp/tools/session/shared.js +36 -0
  143. package/dist/mcp/tools/session/statusTools.js +69 -0
  144. package/dist/mcp/tools/session/walletConnectionTools.js +69 -0
  145. package/dist/mcp/tools/settings/index.js +66 -0
  146. package/dist/mcp-app/account.html +3133 -0
  147. package/dist/mcp-app/account.notices.txt +973 -0
  148. package/dist/mcp-app/chart.html +3354 -0
  149. package/dist/mcp-app/chart.notices.txt +1194 -0
  150. package/dist/mcp-app/connect.html +3179 -0
  151. package/dist/mcp-app/connect.notices.txt +1012 -0
  152. package/dist/mcp-app/receipt.html +8430 -0
  153. package/dist/mcp-app/receipt.notices.txt +3549 -0
  154. package/dist/mcp-app/review.html +8430 -0
  155. package/dist/mcp-app/review.notices.txt +3549 -0
  156. package/dist/mcp-ui/contracts.js +31 -0
  157. package/dist/mcp-ui/readCards.js +28 -0
  158. package/dist/mcp-ui/tools.js +134 -0
  159. package/dist/review-app/brand-dark.svg +1 -0
  160. package/dist/review-app/brand-light.svg +1 -0
  161. package/dist/review-app/favicon.svg +1 -0
  162. package/dist/review-app/settings.css +1 -0
  163. package/dist/review-app/settings.js +1 -0
  164. package/dist/review-app/ui.css +809 -0
  165. package/dist/review-server/assets.js +48 -0
  166. package/dist/review-server/html.js +45 -0
  167. package/dist/review-server/http.js +47 -0
  168. package/dist/review-server/middleware/hostOrigin.js +48 -0
  169. package/dist/review-server/middleware/reviewToken.js +7 -0
  170. package/dist/review-server/reviewServerPolicy.js +1 -0
  171. package/dist/review-server/server.js +72 -0
  172. package/dist/review-server/settingsApi.js +158 -0
  173. package/dist/runtime/application.js +203 -0
  174. package/dist/runtime/config.js +97 -0
  175. package/dist/runtime/localSettingsService.js +198 -0
  176. package/dist/runtime/logger.js +66 -0
  177. package/dist/runtime/reviewDependencies.js +58 -0
  178. package/dist/runtime/reviewServerAcquire.js +93 -0
  179. package/dist/runtime/runtimeTermination.js +60 -0
  180. package/dist/runtime/shared/authenticatedFetch.js +127 -0
  181. package/dist/runtime/shared/control.js +93 -0
  182. package/dist/runtime/shared/dataAccess.js +66 -0
  183. package/dist/runtime/shared/mcpHttp.js +84 -0
  184. package/dist/runtime/shared/ownerLease.js +50 -0
  185. package/dist/runtime/shared/server.js +98 -0
  186. package/dist/runtime/shared/stdio.js +113 -0
  187. package/dist/runtime/smokeMainnetRead.js +391 -0
  188. package/dist/runtime/smokeMainnetReadAssertions.js +306 -0
  189. package/dist/runtime/smokeReview.js +40 -0
  190. package/dist/runtime/start.js +90 -0
  191. package/dist/runtime/suiEndpoint.js +97 -0
  192. package/dist/runtime/suiTransactionGraphqlMapping.js +200 -0
  193. package/dist/runtime/suiTransactionGraphqlQueries.js +231 -0
  194. package/dist/runtime/suiTransactionGraphqlSource.js +148 -0
  195. package/dist/runtime/walletConnectConfig.js +3 -0
  196. package/dist/runtime/walletConnectStorage.js +99 -0
  197. package/dist/runtime/walletConnectTransport.js +128 -0
  198. package/dist/runtime/walletSdkChild.js +3 -0
  199. package/dist/runtime/walletSdkChildRuntime.js +160 -0
  200. package/dist/runtime/walletSdkIpc.js +26 -0
  201. package/dist/runtime/walletSdkProcess.js +358 -0
  202. package/docs/AGENT_BEHAVIOR.md +524 -0
  203. package/docs/AGENT_DEVELOPMENT_POLICY.md +815 -0
  204. package/docs/FRONTEND_POLICY.md +534 -0
  205. package/docs/LOCAL_DB_ARCHITECTURE.md +381 -0
  206. package/docs/MCP_SETUP.md +742 -0
  207. package/docs/MCP_TOOLS.md +1318 -0
  208. package/docs/SDK_API.md +206 -0
  209. package/docs/SIGNABLE_ADAPTER_CONTRACT.md +396 -0
  210. package/docs/TRANSACTION_ACTIVITY_LOG.md +194 -0
  211. package/docs/UTILITY_INDEX.md +275 -0
  212. package/docs/WALLET_CONNECTION.md +238 -0
  213. package/docs/golden-scenarios/BEHAVIOR_MATRIX.md +166 -0
  214. package/docs/golden-scenarios/INTENT_EVIDENCE_GOLDEN_ANSWERS.md +161 -0
  215. package/docs/golden-scenarios/INTENT_EVIDENCE_MATRIX.md +259 -0
  216. package/package.json +79 -0
  217. package/protocols/deepbook-margin.md +15 -0
  218. package/protocols/deepbook-v3.md +24 -0
@@ -0,0 +1,608 @@
1
+ import { SessionStoreError } from "./sessionErrors.js";
2
+ import { Transaction } from "@mysten/sui/transactions";
3
+ import { verifyTransactionSignature } from "@mysten/sui/verify";
4
+ import { computeReviewStateWithPrivateArtifacts } from "../review/reviewComputation.js";
5
+ import { assertNoForbiddenMcpFields } from "../action/forbiddenFields.js";
6
+ import { projectReadCardResult } from "../read/readCardResult.js";
7
+ import { WorkflowConflict } from "./sqliteWalletWorkflowStore.js";
8
+ import { isOwnedConnectedWallet, SUI_SIGN_TRANSACTION_METHOD, WalletUserRejectedError, WalletUnavailableError } from "./walletConnection.js";
9
+ import { isInitialChainObservation } from "./transactionRequest.js";
10
+ import { projectConnectionView, projectReviewView } from "./workflowView.js";
11
+ import { WalletRunInterruptedError } from "./walletRuntime.js";
12
+ import { reviewProgress } from "./status.js";
13
+ export class WalletWorkflow {
14
+ options;
15
+ stopped = false;
16
+ started = false;
17
+ pairings = new Map();
18
+ observing = new Map();
19
+ preparing = new Set();
20
+ pendingWrites = new Map();
21
+ eventListener;
22
+ constructor(options) {
23
+ this.options = options;
24
+ }
25
+ now() { return this.options.now?.() ?? new Date(); }
26
+ assertCurrent() {
27
+ if (this.stopped)
28
+ throw new Error("Wallet workflow owner stopped.");
29
+ this.options.assertCurrent();
30
+ }
31
+ walletAvailability() {
32
+ this.assertCurrent();
33
+ return this.options.runtime.availability();
34
+ }
35
+ assertWalletAvailable(run) {
36
+ this.assertCurrent();
37
+ run?.assertCurrent();
38
+ const availability = this.walletAvailability();
39
+ if (availability.status !== "available")
40
+ throw new WalletUnavailableError(availability.status === "unavailable" ? availability.reason : "wallet_state_unavailable", availability.message);
41
+ }
42
+ report(stage) { try {
43
+ this.options.logger.error("Wallet operation failed", { stage });
44
+ }
45
+ catch { /* SQLite owns outcomes. */ } }
46
+ async start() {
47
+ this.assertCurrent();
48
+ if (this.started)
49
+ throw new WorkflowConflict("The wallet service has already started.");
50
+ this.started = true;
51
+ this.options.records.recover(this.now());
52
+ this.bindEvents();
53
+ this.options.runtime.start((event) => this.eventListener?.(event));
54
+ }
55
+ bindEvents() {
56
+ this.eventListener = this.options.bindExternalEvent((event) => this.runtimeEvent(event));
57
+ }
58
+ runtimeEvent(event) {
59
+ this.assertCurrent();
60
+ if (event.runId !== this.options.runtime.runId)
61
+ return;
62
+ const runtime = this.options.runtime;
63
+ if (event.type === "snapshot") {
64
+ this.options.records.applyWalletObservation(event.snapshot, event.previous, event.ready);
65
+ if (event.ready)
66
+ runtime.publishReady(event.runId);
67
+ }
68
+ else if (event.type === "stage") {
69
+ this.options.records.publishWalletState();
70
+ }
71
+ else if (event.type === "lost") {
72
+ this.pairings.clear();
73
+ this.retainWrite({ kind: "runtime_failure", runId: event.runId, observedAt: this.now().toISOString() });
74
+ }
75
+ }
76
+ dataReplaced() {
77
+ this.pairings.clear();
78
+ this.pendingWrites.clear();
79
+ const status = this.options.runtime.availability().status;
80
+ if (status === "initializing" || status === "recovering")
81
+ this.options.runtime.dataReplaced();
82
+ this.bindEvents();
83
+ }
84
+ observed(topic) {
85
+ return this.options.runtime.snapshot()?.sessions.find((item) => item.topic === topic);
86
+ }
87
+ observation() {
88
+ const snapshot = this.options.runtime.snapshot();
89
+ return snapshot && { runId: snapshot.runId, sequence: snapshot.sequence, observedAt: snapshot.observedAt };
90
+ }
91
+ completeCallback(completion) {
92
+ this.retainWrite(completion);
93
+ }
94
+ retainWrite(completion) {
95
+ // An old data generation cannot add repair work to the replacement data.
96
+ try {
97
+ this.assertCurrent();
98
+ }
99
+ catch {
100
+ return;
101
+ }
102
+ const key = completion.kind === "runtime_failure" ? `runtime:${completion.runId}` :
103
+ completion.kind === "request" ? `request:${completion.id}:${completion.field}` : `connection:${completion.cardId}:${completion.runId}`;
104
+ if (!this.pendingWrites.has(key))
105
+ this.pendingWrites.set(key, completion);
106
+ try {
107
+ this.settlePendingWrite(key, this.pendingWrites.get(key));
108
+ }
109
+ catch {
110
+ this.report("callback_record");
111
+ }
112
+ }
113
+ settlePendingWrite(key, completion) {
114
+ if (completion.kind === "request")
115
+ this.options.records.settle(completion.id, completion.field, false);
116
+ else if (completion.kind === "connection")
117
+ this.options.records.settleConnection(completion.id, completion);
118
+ else if (completion.runId === this.options.runtime.runId)
119
+ this.options.records.recordWalletFailure(completion);
120
+ this.pendingWrites.delete(key);
121
+ }
122
+ synchronizeWalletState(target) {
123
+ this.assertCurrent();
124
+ const walletDependent = !target || target.walletDependent;
125
+ let failure;
126
+ for (const [key, completion] of this.pendingWrites) {
127
+ if (!walletDependent && (completion.kind !== "request" || completion.id !== target?.attemptId))
128
+ continue;
129
+ try {
130
+ this.settlePendingWrite(key, completion);
131
+ }
132
+ catch (error) {
133
+ failure = error;
134
+ }
135
+ }
136
+ // A completed callback's pending flag is housekeeping, not authority to
137
+ // read recorded facts. Retain failed repairs for the next applicable read.
138
+ if (failure && walletDependent)
139
+ throw failure;
140
+ if (walletDependent)
141
+ this.options.runtime.synchronize();
142
+ }
143
+ async prepare(kind, input) {
144
+ this.assertCurrent();
145
+ if (kind === "connect")
146
+ return { status: "ready" };
147
+ if (typeof input.reviewSessionId !== "string")
148
+ throw new WorkflowConflict("The requested review was not specified.");
149
+ const session = await this.options.sessions.getReviewSession(input.reviewSessionId, () => this.now());
150
+ this.assertCurrent();
151
+ if (!session)
152
+ throw new WorkflowConflict("The review session is unavailable.");
153
+ if (input.mode === "manage") {
154
+ if (typeof input.attemptId !== "string")
155
+ throw new WorkflowConflict("The transaction request to check was not specified.");
156
+ const request = this.options.records.request(input.attemptId);
157
+ if (!request || request.reviewSessionId !== session.id)
158
+ throw new WorkflowConflict("This transaction request does not belong to the specified review.");
159
+ }
160
+ return { status: "ready" };
161
+ }
162
+ readConnectionContext() {
163
+ this.assertCurrent();
164
+ this.synchronizeWalletState();
165
+ this.recoverDisconnects();
166
+ const state = this.options.records.evaluate({ walletAvailability: this.walletAvailability(), walletObservation: this.observation() });
167
+ // Current-owner rows are published by the same SDK observation/admission
168
+ // transactions that gate availability. Use their evaluated selection in
169
+ // every consumer instead of deriving another default from address matches.
170
+ return { connections: state.connections, connectionConflict: state.connectionConflict, walletAvailability: state.walletAvailability, walletObservation: this.observation(),
171
+ assetReadAccount: state.assetReadAccount ?? { status: "address_required" } };
172
+ }
173
+ recoverDisconnects() {
174
+ const available = this.walletAvailability().status === "available";
175
+ for (const record of this.options.records.connections()) {
176
+ if (record.ownerId !== this.options.ownerId || record.sdkPending)
177
+ continue;
178
+ const pending = this.options.records.pendingDisconnect(record.connection.connectionId);
179
+ if (!pending)
180
+ continue;
181
+ // Disabled transports also return undefined; that is not session absence.
182
+ const absent = available && !!record.topic && this.observed(record.topic)?.status === "absent";
183
+ this.options.records.recoverDisconnect(pending.state.cardId, record.connection.connectionId, record.connection.revision, absent);
184
+ }
185
+ }
186
+ async evaluateReview(id, expectedCard, uiObservation = false, expectedAttempt) {
187
+ this.assertCurrent();
188
+ const readTarget = this.options.records.reviewReadTarget(id, expectedCard);
189
+ if (expectedAttempt && readTarget.attemptId !== expectedAttempt)
190
+ throw new WorkflowConflict("The current transaction request changed. Check this review again.");
191
+ this.synchronizeWalletState(readTarget);
192
+ const preparation = this.options.sessions.readReviewSession(id)?.preparationId;
193
+ if (preparation && !this.preparing.has(preparation))
194
+ this.options.records.failPreparation(id, preparation, this.now());
195
+ const candidate = await this.options.sessions.inspectReview(id, this.now());
196
+ this.assertCurrent();
197
+ const evaluated = this.options.records.evaluate({ reviewSessionId: id, candidate, expectedCard, readTarget,
198
+ walletAvailability: this.walletAvailability(), walletObservation: this.observation(), uiObservation });
199
+ this.options.sessions.recordEvaluationEvents(evaluated.events);
200
+ return evaluated;
201
+ }
202
+ async readReview(id, explicitResult = false) {
203
+ if (!this.options.sessions.readReviewSession(id))
204
+ return undefined;
205
+ let state = await this.evaluateReview(id);
206
+ if (state.request) {
207
+ this.observe(state.request.attemptId, explicitResult);
208
+ if (explicitResult) {
209
+ await this.observing.get(state.request.attemptId);
210
+ this.assertCurrent();
211
+ // An awaited receipt may have committed new facts. Return one freshly
212
+ // evaluated snapshot; ordinary status reads do not await external I/O.
213
+ state = await this.evaluateReview(id, undefined, false, state.request.attemptId);
214
+ }
215
+ }
216
+ if (!state.session)
217
+ return undefined;
218
+ return { session: state.session, request: state.request, hasReviewInput: state.hasReviewInput, connectionConflict: state.connectionConflict, walletAvailability: state.walletAvailability, walletObservation: this.observation(),
219
+ progress: reviewProgress(!!state.session.preparationId, state.request, state.walletAvailability, { stopped: state.authority?.observation_stopped === 1, pending: state.authority?.lookup_pending === 1 }) };
220
+ }
221
+ async describe(record, uiObservation = false) {
222
+ this.assertCurrent();
223
+ if (record.state.kind === "connect")
224
+ this.readConnectionContext();
225
+ const evaluated = record.state.kind === "review"
226
+ ? await this.evaluateReview(String(record.state.input.reviewSessionId), record, uiObservation)
227
+ : this.options.records.evaluate({ expectedCard: record, walletAvailability: this.walletAvailability(), walletObservation: this.observation() });
228
+ this.assertCurrent();
229
+ if (record.state.kind === "connect") {
230
+ const data = projectConnectionView(evaluated);
231
+ const pairing = evaluated.connection && evaluated.walletAvailability.status === "available" ? this.pairings.get(evaluated.connection.connectionId) : undefined;
232
+ return { evaluated, data, ...(pairing && evaluated.connection?.status === "awaiting_approval" ? { walletDisplay: pairing } : {}) };
233
+ }
234
+ const data = projectReviewView(evaluated);
235
+ assertNoForbiddenMcpFields(data);
236
+ if (uiObservation && evaluated.request && isInitialChainObservation(evaluated.request.requestStatus) && !evaluated.authority?.observation_stopped)
237
+ this.observe(evaluated.request.attemptId);
238
+ return { evaluated, data, ...(evaluated.receiptDisplay && evaluated.request ? { receiptDisplay: evaluated.receiptDisplay, displayAttemptId: evaluated.request.attemptId } : {}) };
239
+ }
240
+ async act(record, action) {
241
+ this.assertCurrent();
242
+ const runtime = this.options.runtime;
243
+ if ("walletRunId" in action && action.walletRunId !== runtime.runId)
244
+ throw new WorkflowConflict("The wallet service changed. Confirm the current selection again.");
245
+ if (action.action === "request_signature" && record.scope === "review") {
246
+ const recovered = this.options.records.recoverRequest(record, action, { reviewSessionId: String(record.state.input.reviewSessionId),
247
+ reviewRevision: action.reviewRevision, account: action.account, connectionId: action.connectionId });
248
+ if (recovered)
249
+ return;
250
+ }
251
+ if (["connect", "disconnect", "use_account", "prepare_review", "request_signature"].includes(action.action))
252
+ this.assertWalletAvailable();
253
+ switch (action.action) {
254
+ case "cancel":
255
+ this.options.records.cancelInput(record);
256
+ return;
257
+ case "connect": {
258
+ const run = runtime.bind();
259
+ const admitted = this.options.records.admitConnection(record, action);
260
+ void this.connect(admitted.connection.connectionId, run, record.state.cardId).catch(() => this.report("connection"));
261
+ return;
262
+ }
263
+ case "use_account": {
264
+ if (!action.account)
265
+ throw new WorkflowConflict("No account from the connected wallet was selected.");
266
+ const run = runtime.bind();
267
+ await this.checkWallet(run, action.connectionId, action.account, record.state.cardId);
268
+ this.assertWalletAvailable(run);
269
+ this.options.records.useAccount(record, action, action.connectionId, action.account);
270
+ return;
271
+ }
272
+ case "disconnect": {
273
+ const run = runtime.bind();
274
+ const selected = this.options.records.connection(action.connectionId);
275
+ if (!selected?.topic)
276
+ throw new WorkflowConflict("The selected wallet connection is unavailable.");
277
+ await run.checkSession(selected.topic, record.state.cardId);
278
+ this.assertWalletAvailable(run);
279
+ const target = this.options.records.admitDisconnect(record, action, action.connectionId);
280
+ void (async () => {
281
+ try {
282
+ if (target.topic)
283
+ await run.disconnect(target.topic, record.state.cardId);
284
+ this.assertWalletAvailable(run);
285
+ this.options.records.finishDisconnect(record.state.cardId, action.connectionId, true, this.now());
286
+ }
287
+ catch {
288
+ if (!this.stopped)
289
+ this.options.records.finishDisconnect(record.state.cardId, action.connectionId, false, this.now());
290
+ }
291
+ finally {
292
+ this.completeCallback({ kind: "connection", id: action.connectionId, cardId: record.state.cardId, action: "disconnect", runId: run.runId });
293
+ }
294
+ })().catch(() => this.report("disconnection"));
295
+ return;
296
+ }
297
+ case "stop_connection": {
298
+ const id = this.options.records.manage(record, action.action);
299
+ this.pairings.delete(id);
300
+ return;
301
+ }
302
+ case "prepare_review": {
303
+ const id = String(record.state.input.reviewSessionId);
304
+ const run = runtime.bind();
305
+ const { connection } = await this.checkWallet(run, action.connectionId, action.account, record.state.cardId);
306
+ this.assertWalletAvailable(run);
307
+ const preparation = this.options.records.beginReviewPreparation(record, action.reviewRevision, connection, action.account);
308
+ this.preparing.add(preparation);
309
+ void this.compute(id, preparation, action.account, connection.connectionId, run)
310
+ .finally(() => this.preparing.delete(preparation)).catch(() => this.report("review_computation"));
311
+ return;
312
+ }
313
+ case "request_signature": {
314
+ const run = runtime.bind();
315
+ const id = String(record.state.input.reviewSessionId);
316
+ const { connection } = await this.checkWallet(run, action.connectionId, action.account, record.state.cardId);
317
+ this.assertWalletAvailable(run);
318
+ const session = this.options.sessions.readReviewSession(id);
319
+ if (!session || session.reviewRevision !== action.reviewRevision || !session.plans[0])
320
+ throw new WorkflowConflict("This request does not match the current review.");
321
+ let material;
322
+ try {
323
+ material = await this.options.sessions.prepareReviewedTransaction(id, session.plans[0].id, action.account, this.now());
324
+ }
325
+ catch (error) {
326
+ if (!(error instanceof SessionStoreError))
327
+ throw error;
328
+ if (this.options.records.recoverRequest(record, action, { reviewSessionId: id, reviewRevision: action.reviewRevision,
329
+ account: action.account, connectionId: action.connectionId }))
330
+ return;
331
+ await this.evaluateReview(id);
332
+ throw new WorkflowConflict("The transaction details needed for this approval request are no longer available.");
333
+ }
334
+ this.assertWalletAvailable(run);
335
+ let admitted;
336
+ try {
337
+ admitted = this.options.records.admitRequest(record, action, material, connection, run.runId);
338
+ }
339
+ catch (error) {
340
+ if (error instanceof WorkflowConflict)
341
+ this.options.sessions.recordEvaluationEvents(error.events);
342
+ throw error;
343
+ }
344
+ if (admitted.created)
345
+ void this.sign(admitted.request, action.connectionId, material.transactionBytesBase64, run).catch(() => this.report("signature_request"));
346
+ return;
347
+ }
348
+ case "stop_waiting":
349
+ this.options.records.manage(record, action.action);
350
+ return;
351
+ case "read_result": {
352
+ const id = this.options.records.manage(record, action.action);
353
+ this.observe(id, true);
354
+ return;
355
+ }
356
+ }
357
+ }
358
+ requireWallet(id, account) {
359
+ this.assertWalletAvailable();
360
+ const current = this.options.records.connection(id);
361
+ if (!this.options.records.isUsableConnection(id, this.now()) || !isOwnedConnectedWallet(current, this.options.ownerId, !!this.options.records.pendingDisconnect(id)) ||
362
+ !current.connection.accounts.includes(account) || Date.parse(current.connection.expiresAt) <= this.now().getTime()) {
363
+ throw new WorkflowConflict("The selected Sui wallet connection is unavailable.");
364
+ }
365
+ return current.connection;
366
+ }
367
+ async checkWallet(run, id, account, operationId) {
368
+ this.assertWalletAvailable(run);
369
+ const stored = this.options.records.connection(id);
370
+ if (!stored?.topic)
371
+ throw new WorkflowConflict("The selected wallet connection is unavailable.");
372
+ const observed = await run.checkSession(stored.topic, operationId);
373
+ this.assertWalletAvailable(run);
374
+ if (observed.status !== "present" || !observed.session.accounts.includes(account))
375
+ throw new WorkflowConflict("The selected Sui wallet account is unavailable.");
376
+ return { connection: this.requireWallet(id, account), observed };
377
+ }
378
+ async connect(id, run, cardId) {
379
+ let unrecordedOutcome = "failed";
380
+ try {
381
+ const result = await run.connect(id);
382
+ let displayFailure;
383
+ try {
384
+ this.assertWalletAvailable(run);
385
+ const current = this.options.records.connection(id);
386
+ if (current?.connection.status === "awaiting_approval") {
387
+ this.pairings.set(id, { connectionId: id, pairingUri: result.uri, expiresAt: result.expiresAt });
388
+ this.options.records.updateConnection(id, { expiresAt: result.expiresAt }, this.now());
389
+ }
390
+ }
391
+ catch (error) {
392
+ displayFailure = error;
393
+ }
394
+ // The approval remains pending even if setup/QR recording fails or the
395
+ // wallet dependency is disabled. Settle only after that promise ends.
396
+ const session = await result.approval;
397
+ if (displayFailure)
398
+ throw displayFailure;
399
+ this.assertWalletAvailable(run);
400
+ const awaiting = this.options.records.connection(id);
401
+ if (!awaiting || awaiting.connection.status !== "awaiting_approval" || Date.parse(awaiting.connection.expiresAt) <= this.now().getTime()) {
402
+ await run.disconnect(session.topic, id);
403
+ return;
404
+ }
405
+ this.options.records.updateConnection(id, { status: "connected", accounts: session.accounts, methods: session.methods,
406
+ expiresAt: session.expiresAt, walletName: session.walletName }, this.now(), session.topic, false);
407
+ }
408
+ catch (error) {
409
+ unrecordedOutcome = error instanceof WalletRunInterruptedError ? "stopped" : error instanceof WalletUserRejectedError ? "rejected" : "failed";
410
+ if (!this.stopped) {
411
+ const current = this.options.records.connection(id);
412
+ if (current?.connection.status === "awaiting_approval")
413
+ this.options.records.updateConnection(id, { status: error instanceof WalletRunInterruptedError ? "stopped" : error instanceof WalletUserRejectedError ? "rejected" : "failed",
414
+ reason: this.walletAvailability().status !== "available" ? "The wallet connection could not be confirmed because the wallet service is unavailable." :
415
+ error instanceof WalletUserRejectedError ? error.message : "Wallet connection could not be confirmed." }, this.now(), undefined, false);
416
+ }
417
+ }
418
+ finally {
419
+ this.completeCallback({ kind: "connection", id, runId: run.runId, cardId, action: "connect", unrecordedOutcome });
420
+ this.pairings.delete(id);
421
+ }
422
+ }
423
+ async compute(id, preparation, account, connectionId, run) {
424
+ try {
425
+ await this.options.sessions.recordWalletConnected(id, account, this.now());
426
+ this.assertWalletAvailable(run);
427
+ const session = await this.options.sessions.getReviewSession(id, () => this.now());
428
+ if (!session?.plans[0])
429
+ throw new WorkflowConflict("Review session is unavailable.");
430
+ const computed = await computeReviewStateWithPrivateArtifacts({ reviewSessionId: id, plan: session.plans[0], account, now: this.now() }, this.options.computation);
431
+ const { connection: currentConnection } = await this.checkWallet(run, connectionId, account, preparation);
432
+ this.assertWalletAvailable(run);
433
+ if (!this.options.records.isPreparing(id, preparation) || session.walletConnectionRevision !== currentConnection.revision)
434
+ return;
435
+ await this.options.sessions.recordReviewStateWithArtifacts(id, computed.state, computed.privateArtifacts, this.now(), { id: preparation, connectionId, connectionRevision: currentConnection.revision });
436
+ }
437
+ catch {
438
+ if (!this.stopped)
439
+ this.options.records.failPreparation(id, preparation, this.now());
440
+ }
441
+ }
442
+ async sign(request, connectionId, bytesBase64, run) {
443
+ try {
444
+ await this.performSignature(request, connectionId, bytesBase64, run);
445
+ }
446
+ finally {
447
+ this.completeCallback({ kind: "request", id: request.attemptId, field: "sdk_pending" });
448
+ }
449
+ }
450
+ async performSignature(request, connectionId, bytesBase64, run) {
451
+ let response;
452
+ try {
453
+ this.assertWalletAvailable(run);
454
+ const connection = this.options.records.connection(connectionId);
455
+ if (!connection?.topic || !connection.connection.methods.includes(SUI_SIGN_TRANSACTION_METHOD))
456
+ throw new WorkflowConflict("Wallet does not support Sui sign-only requests.");
457
+ const proof = await this.checkWallet(run, connectionId, request.account, request.attemptId);
458
+ this.assertWalletAvailable(run);
459
+ response = await run.sign({ topic: connection.topic, account: request.account, transactionBytesBase64: bytesBase64,
460
+ sessionVersion: proof.observed.version }, request.attemptId);
461
+ this.assertWalletAvailable(run);
462
+ }
463
+ catch {
464
+ if (!this.stopped)
465
+ this.options.records.transitionRequest(request.attemptId, "request_failed", this.now(), { reason: this.walletAvailability().status !== "available" ? "Wallet operations became unavailable before submission. The returned signature will not be submitted." : "The wallet rejected the request or did not return a signature." });
466
+ return;
467
+ }
468
+ const current = this.options.records.request(request.attemptId);
469
+ if (!current)
470
+ return;
471
+ this.options.records.advanceRequestDeadlines(this.now(), current.attemptId);
472
+ if (this.options.records.request(request.attemptId)?.requestStatus !== "awaiting_signature")
473
+ return;
474
+ let bytes;
475
+ try {
476
+ bytes = Buffer.from(response.transactionBytes, "base64");
477
+ if (await Transaction.from(bytes).getDigest() !== request.transactionDigest) {
478
+ throw new WorkflowConflict("The returned transaction does not match the reviewed transaction. Nothing was submitted.");
479
+ }
480
+ await verifyTransactionSignature(bytes, response.signature, { address: request.account,
481
+ ...(this.options.signatureClient ? { client: this.options.signatureClient } : {}) });
482
+ this.assertWalletAvailable(run);
483
+ await this.options.verifyNetwork();
484
+ this.assertWalletAvailable(run);
485
+ const current = await this.checkWallet(run, connectionId, request.account, request.attemptId);
486
+ this.assertWalletAvailable(run);
487
+ if (current.observed.version !== response.sessionVersion)
488
+ throw new WorkflowConflict("The wallet session changed before submission. Nothing was submitted.");
489
+ this.options.records.advanceRequestDeadlines(this.now(), request.attemptId);
490
+ if (this.options.records.request(request.attemptId)?.requestStatus !== "awaiting_signature")
491
+ return;
492
+ this.options.records.transitionRequest(request.attemptId, "awaiting_signature", this.now(), { signatureVerified: true });
493
+ if (!this.options.records.beginSubmission(request.attemptId))
494
+ return;
495
+ }
496
+ catch (error) {
497
+ // SDK verification can itself depend on a remote source. An exception is
498
+ // not proof of a mismatched transaction or signer.
499
+ if (!this.stopped)
500
+ this.options.records.transitionRequest(request.attemptId, "request_failed", this.now(), {
501
+ reason: this.walletAvailability().status !== "available" ? "Wallet operations became unavailable before submission. The returned signature will not be submitted."
502
+ : error instanceof WorkflowConflict ? error.message : "Submission checks could not be completed. Nothing was submitted."
503
+ });
504
+ return;
505
+ }
506
+ try {
507
+ await this.options.submitTransaction(bytes, response.signature);
508
+ }
509
+ catch { /* A lost submit response is not proof of failure. Observe the same digest. */ }
510
+ finally {
511
+ this.completeCallback({ kind: "request", id: request.attemptId, field: "submit_pending" });
512
+ }
513
+ this.assertCurrent();
514
+ try {
515
+ this.options.records.finishSubmission(request.attemptId);
516
+ }
517
+ catch {
518
+ this.report("submission_record");
519
+ return;
520
+ }
521
+ this.observe(request.attemptId);
522
+ }
523
+ observe(id, explicit = false) {
524
+ this.assertCurrent();
525
+ if (this.observing.has(id))
526
+ return;
527
+ const current = this.options.records.request(id);
528
+ if (!current || !(isInitialChainObservation(current.requestStatus) || explicit && current.requestStatus === "outcome_unknown"))
529
+ return;
530
+ // Starting another lookup needs its own previous callback to be settled.
531
+ // Stored terminal facts above never depend on this write succeeding.
532
+ for (const [key, write] of this.pendingWrites)
533
+ if (write.kind === "request" && write.id === id && write.field === "lookup_pending") {
534
+ this.settlePendingWrite(key, write);
535
+ }
536
+ const request = this.options.records.beginObservation(id, explicit);
537
+ if (!request)
538
+ return;
539
+ const reading = (async () => {
540
+ try {
541
+ const result = await this.options.verifyReceipt({ txDigest: request.transactionDigest, reviewedTransactionDigest: request.transactionDigest, account: request.account, now: this.now() });
542
+ this.assertCurrent();
543
+ if (result.status === "verified_success" || result.status === "verified_failure") {
544
+ let details;
545
+ if (this.options.readReceipt) {
546
+ try {
547
+ const receipt = await this.options.readReceipt({ digest: request.transactionDigest, now: this.now() });
548
+ this.assertCurrent();
549
+ if (receipt.status === "found")
550
+ details = projectReadCardResult("receipt", { digest: request.transactionDigest }, receipt);
551
+ }
552
+ catch { /* Keep verified chain facts if the additional display read is unavailable. */ }
553
+ }
554
+ this.assertCurrent();
555
+ this.options.records.transitionRequest(id, "completed", this.now(), { execution: {
556
+ reviewSessionId: request.reviewSessionId, attemptId: id, planId: request.planId,
557
+ status: result.status === "verified_success" ? "success" : "failure", txDigest: request.transactionDigest,
558
+ chainReceipt: result.receipt, recordedAt: this.now().toISOString(),
559
+ ...(result.status === "verified_failure" ? { failureReason: "chain_execution_failed" } : {})
560
+ }, ...(details ? { details } : {}) });
561
+ }
562
+ else if (result.status === "verification_failed") {
563
+ this.options.records.transitionRequest(id, "outcome_unknown", this.now(), { reason: "The chain result could not be verified for this request." });
564
+ }
565
+ }
566
+ catch {
567
+ if (!this.stopped)
568
+ this.options.records.transitionRequest(id, "outcome_unknown", this.now(), { reason: "The chain result is unavailable. No transaction was sent again." });
569
+ }
570
+ finally {
571
+ this.completeCallback({ kind: "request", id, field: "lookup_pending" });
572
+ this.observing.delete(id);
573
+ }
574
+ })();
575
+ this.observing.set(id, reading);
576
+ void reading.catch(() => this.report("chain_observation"));
577
+ }
578
+ pendingConnections() {
579
+ this.assertCurrent();
580
+ this.synchronizeWalletState();
581
+ this.recoverDisconnects();
582
+ const rows = [];
583
+ for (const card of this.options.records.pendingConnectionCards(this.now())) {
584
+ const evaluated = this.options.records.evaluate({ expectedCard: card, walletAvailability: this.walletAvailability(), walletObservation: this.observation() });
585
+ if (evaluated.record?.state.state === "closed")
586
+ continue;
587
+ const data = projectConnectionView(evaluated);
588
+ if (data.runtimeRecovery)
589
+ continue;
590
+ if (!data.connection)
591
+ rows.push({ cardId: card.state.cardId, status: "input_required", lastActivityAt: card.state.createdAt, progress: data.progress });
592
+ else if (data.connection.status === "awaiting_approval")
593
+ rows.push({ cardId: card.state.cardId,
594
+ connectionId: data.connection.connectionId, status: "awaiting_approval", lastActivityAt: data.connection.updatedAt, progress: data.progress });
595
+ else if (data.connection.pendingAction === "disconnect")
596
+ rows.push({ cardId: card.state.cardId,
597
+ connectionId: data.connection.connectionId, status: "disconnect_pending", lastActivityAt: data.connection.updatedAt, progress: data.progress });
598
+ }
599
+ return rows;
600
+ }
601
+ stop() {
602
+ if (this.stopped)
603
+ return;
604
+ this.stopped = true;
605
+ this.pairings.clear();
606
+ this.pendingWrites.clear();
607
+ }
608
+ }