@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,470 @@
1
+ import { GrpcTypes } from "@mysten/sui/grpc";
2
+ import { describeTransactionFunding, assertTransactionFundingMatches, transactionFundingDescriptionSchema } from "./transactionFunding.js";
3
+ import { Transaction } from "@mysten/sui/transactions";
4
+ import { z } from "zod";
5
+ import { assertNoForbiddenMcpFields } from "./forbiddenFields.js";
6
+ import { REVIEW_TIME_SIMULATION_PROVIDER, WALLET_REVIEW_REQUIRED_SIMULATION_FIELDS } from "./signableAdapterContract.js";
7
+ import { failReviewCheck, passReviewCheck } from "./reviewCheckResults.js";
8
+ import { makeRawU64StringSchema, makeSignedRawIntegerStringSchema, parseRawU64 } from "../numeric/rawU64.js";
9
+ import { normalizedSuiAddressSchema, parseSuiAddress, suiTransactionDigestSchema } from "../suiAddress.js";
10
+ import { normalizeCoinType } from "../read/coinMetadata.js";
11
+ import { LocalTransactionMaterialStoreError, verifyLocalTransactionMaterialArtifacts } from "../session/transactionMaterialStore.js";
12
+ export const REVIEW_TIME_SIMULATION_EVIDENCE_VERSION = "review-time-simulation-v2";
13
+ const INVALID_SIMULATION_EVIDENCE_MESSAGE = "Review-time simulation returned incomplete or unverifiable evidence. Nothing was signed or submitted.";
14
+ const isoUtcStringSchema = z.string().refine((value) => {
15
+ const parsed = new Date(value);
16
+ return Number.isFinite(parsed.getTime()) && parsed.toISOString() === value;
17
+ }, "Expected ISO 8601 UTC timestamp");
18
+ const simulationRequiredFieldSchema = z.enum(WALLET_REVIEW_REQUIRED_SIMULATION_FIELDS);
19
+ const simulationBalanceChangeSchema = z.object({
20
+ address: normalizedSuiAddressSchema,
21
+ coinType: z.string().min(1).max(512).refine((value) => {
22
+ try {
23
+ return normalizeCoinType(value) === value;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }, "Expected a normalized Sui struct tag coin type"),
29
+ amount: makeSignedRawIntegerStringSchema("balanceChanges[].amount")
30
+ }).strict();
31
+ const simulationObjectChangeSchema = z.object({
32
+ objectId: normalizedSuiAddressSchema,
33
+ objectType: z.string().min(1).max(512).optional(),
34
+ inputState: z.string().min(1).max(80),
35
+ outputState: z.string().min(1).max(80),
36
+ idOperation: z.string().min(1).max(80)
37
+ }).strict();
38
+ const simulationTransactionSummarySchema = z.object({
39
+ sender: normalizedSuiAddressSchema,
40
+ funding: transactionFundingDescriptionSchema,
41
+ gasPaymentCount: z.number().int().min(0),
42
+ inputCount: z.number().int().min(0),
43
+ commandCount: z.number().int().min(0),
44
+ gasBudgetRaw: makeRawU64StringSchema("gasBudgetRaw").optional(),
45
+ gasPriceRaw: makeRawU64StringSchema("gasPriceRaw").optional()
46
+ }).strict().superRefine((value, ctx) => {
47
+ if (value.sender !== value.funding.sender || value.gasPaymentCount !== value.funding.gasPayments.length ||
48
+ value.gasBudgetRaw !== value.funding.gasBudgetRaw || value.gasPriceRaw !== value.funding.gasPriceRaw ||
49
+ (value.gasPaymentCount === 0) !== (value.funding.gasMode === "address_balance")) {
50
+ ctx.addIssue({ code: "custom", message: "Simulation summary must match transaction funding" });
51
+ }
52
+ });
53
+ const simulationGasCostSummarySchema = z.object({
54
+ computationCostRaw: makeRawU64StringSchema("computationCostRaw"),
55
+ storageCostRaw: makeRawU64StringSchema("storageCostRaw"),
56
+ storageRebateRaw: makeRawU64StringSchema("storageRebateRaw"),
57
+ nonRefundableStorageFeeRaw: makeRawU64StringSchema("nonRefundableStorageFeeRaw")
58
+ }).strict();
59
+ export const reviewTimeSimulationEvidenceSchema = z.object({
60
+ evidenceVersion: z.literal(REVIEW_TIME_SIMULATION_EVIDENCE_VERSION),
61
+ materialId: z.string().min(1),
62
+ reviewSessionId: z.string().min(1),
63
+ planId: z.string().min(1),
64
+ account: normalizedSuiAddressSchema,
65
+ transactionDigest: suiTransactionDigestSchema,
66
+ kind: z.literal("review_time_simulation"),
67
+ provider: z.literal(REVIEW_TIME_SIMULATION_PROVIDER),
68
+ network: z.literal("sui:mainnet"),
69
+ checksEnabled: z.literal(true),
70
+ requiredFields: z.array(simulationRequiredFieldSchema).min(WALLET_REVIEW_REQUIRED_SIMULATION_FIELDS.length),
71
+ missingFields: z.array(simulationRequiredFieldSchema).default([]),
72
+ status: z.literal("success"),
73
+ simulatedAt: isoUtcStringSchema,
74
+ expiresAt: isoUtcStringSchema,
75
+ effects: z.object({
76
+ transactionDigest: suiTransactionDigestSchema,
77
+ gasCostSummary: simulationGasCostSummarySchema,
78
+ changedObjectCount: z.number().int().min(0)
79
+ }).strict(),
80
+ balanceChanges: z.array(simulationBalanceChangeSchema),
81
+ objectChanges: z.array(simulationObjectChangeSchema),
82
+ transaction: simulationTransactionSummarySchema
83
+ }).strict().superRefine((value, ctx) => {
84
+ if (!WALLET_REVIEW_REQUIRED_SIMULATION_FIELDS.every((field) => value.requiredFields.includes(field))) {
85
+ ctx.addIssue({
86
+ code: "custom",
87
+ path: ["requiredFields"],
88
+ message: "review-time simulation evidence must include every required simulation field"
89
+ });
90
+ }
91
+ if (value.missingFields.length !== 0) {
92
+ ctx.addIssue({
93
+ code: "custom",
94
+ path: ["missingFields"],
95
+ message: "successful review-time simulation evidence must not have missing fields"
96
+ });
97
+ }
98
+ if (value.effects.transactionDigest !== value.transactionDigest) {
99
+ ctx.addIssue({
100
+ code: "custom",
101
+ path: ["effects", "transactionDigest"],
102
+ message: "simulation effects transaction digest must match evidence transactionDigest"
103
+ });
104
+ }
105
+ if (value.effects.changedObjectCount !== value.objectChanges.length) {
106
+ ctx.addIssue({
107
+ code: "custom",
108
+ path: ["effects", "changedObjectCount"],
109
+ message: "simulation changedObjectCount must match objectChanges length"
110
+ });
111
+ }
112
+ });
113
+ export function parseReviewTimeSimulationEvidence(value) {
114
+ return reviewTimeSimulationEvidenceSchema.parse(value);
115
+ }
116
+ export function verifyReviewTimeSimulationEvidence(input) {
117
+ const evidence = parseReviewTimeSimulationEvidence(input.evidence);
118
+ assertTransactionFundingMatches(describeTransactionFunding(Transaction.from(input.transactionBytes).getData(), input.transactionMaterial.account, evidence.transaction.funding.chainIdentifier), evidence.transaction.funding);
119
+ const now = input.now ?? new Date();
120
+ const nowMs = now.getTime();
121
+ if (!Number.isFinite(nowMs)) {
122
+ throw new Error("now must be a valid Date");
123
+ }
124
+ if (evidence.materialId !== input.transactionMaterial.materialId ||
125
+ evidence.reviewSessionId !== input.transactionMaterial.reviewSessionId ||
126
+ evidence.planId !== input.transactionMaterial.planId ||
127
+ evidence.account !== input.transactionMaterial.account ||
128
+ evidence.expiresAt !== input.transactionMaterial.expiresAt ||
129
+ evidence.transactionDigest !== input.transactionMaterialDigest.transactionDigest) {
130
+ throw new Error("review-time simulation evidence must match material and digest identity");
131
+ }
132
+ if (input.transactionMaterialDigest.materialId !== input.transactionMaterial.materialId ||
133
+ input.transactionMaterialDigest.reviewSessionId !== input.transactionMaterial.reviewSessionId ||
134
+ input.transactionMaterialDigest.planId !== input.transactionMaterial.planId ||
135
+ input.transactionMaterialDigest.account !== input.transactionMaterial.account ||
136
+ input.transactionMaterialDigest.expiresAt !== input.transactionMaterial.expiresAt) {
137
+ throw new Error("transaction material digest must match material identity before review-time simulation evidence is accepted");
138
+ }
139
+ const materialCreatedAtMs = Date.parse(input.transactionMaterial.createdAt);
140
+ const simulatedAtMs = Date.parse(evidence.simulatedAt);
141
+ const expiresAtMs = Date.parse(evidence.expiresAt);
142
+ if (simulatedAtMs < materialCreatedAtMs || simulatedAtMs > nowMs || simulatedAtMs >= expiresAtMs) {
143
+ throw new Error("review-time simulation simulatedAt must be between material creation, now, and material expiry");
144
+ }
145
+ if (expiresAtMs <= nowMs) {
146
+ throw new Error("review-time simulation evidence must not be expired");
147
+ }
148
+ if (evidence.transaction.sender !== evidence.account) {
149
+ throw new Error("review-time simulation transaction sender must match reviewed account");
150
+ }
151
+ assertNoForbiddenMcpFields(evidence);
152
+ return evidence;
153
+ }
154
+ export function publicTransactionSimulationSummaryFromEvidence(evidenceInput) {
155
+ const evidence = parseReviewTimeSimulationEvidence(evidenceInput);
156
+ const summary = {
157
+ provider: evidence.provider,
158
+ checksEnabled: evidence.checksEnabled,
159
+ success: true,
160
+ gasCostSummary: { ...evidence.effects.gasCostSummary },
161
+ balanceChanges: evidence.balanceChanges.map((change) => ({ ...change })),
162
+ objectChanges: evidence.objectChanges.map((change) => ({ ...change }))
163
+ };
164
+ assertNoForbiddenMcpFields(summary);
165
+ return summary;
166
+ }
167
+ export function createReviewTimeSimulationProducer(options) {
168
+ return async (input) => {
169
+ if (options.network !== "mainnet" || options.chainIdentifier !== options.expectedChainIdentifier) {
170
+ return {
171
+ status: "blocked",
172
+ blockedReason: "network_mismatch",
173
+ checks: [
174
+ failReviewCheck("review_time_simulation_network_mismatch", "Review-time simulation network", "Review-time simulation requires a verified Sui mainnet gRPC endpoint and matching mainnet chain identifier.", "network")
175
+ ]
176
+ };
177
+ }
178
+ let parsed;
179
+ try {
180
+ parsed = await verifyLocalTransactionMaterialArtifacts({
181
+ materialStore: options.materialStore,
182
+ transactionMaterial: input.transactionMaterial,
183
+ transactionMaterialDigest: input.transactionMaterialDigest,
184
+ now: input.now
185
+ });
186
+ }
187
+ catch (error) {
188
+ const message = error instanceof Error ? error.message : "Stored transaction material could not be verified.";
189
+ const check = failReviewCheck("review_time_simulation_material_unavailable", "Review-time simulation material", message, "adapter");
190
+ if (error instanceof LocalTransactionMaterialStoreError && /expired|unavailable/i.test(message)) {
191
+ return { status: "refresh_required", refreshReason: "quote_stale", checks: [check] };
192
+ }
193
+ return { status: "blocked", blockedReason: "object_resolution_failed", checks: [check] };
194
+ }
195
+ const material = options.materialStore.getTransactionMaterial(parsed.transactionMaterial, input.now);
196
+ if (!material) {
197
+ return {
198
+ status: "refresh_required",
199
+ refreshReason: "quote_stale",
200
+ checks: [
201
+ failReviewCheck("review_time_simulation_material_unavailable", "Review-time simulation material", "Review-time simulation could not run because the stored local transaction material is unavailable or expired.", "adapter")
202
+ ]
203
+ };
204
+ }
205
+ let simulationResult;
206
+ try {
207
+ const result = await options.client.transactionExecutionService.simulateTransaction({
208
+ transaction: { bcs: { value: material.transactionBytes } },
209
+ checks: GrpcTypes.SimulateTransactionRequest_TransactionChecks.ENABLED,
210
+ // With false, the node may inject mock gas for empty payments. A fully
211
+ // specified BCS transaction stays immutable: verify returned bytes below.
212
+ doGasSelection: true,
213
+ readMask: { paths: ["transaction.transaction.bcs", "transaction.effects", "transaction.balance_changes",
214
+ "transaction.objects.objects.object_id", "transaction.objects.objects.object_type"] }
215
+ });
216
+ simulationResult = result.response.transaction;
217
+ }
218
+ catch (error) {
219
+ const classification = classifySimulationException(error);
220
+ if (classification.status === "refresh_required") {
221
+ return {
222
+ status: "refresh_required",
223
+ refreshReason: "simulation_transient_failure",
224
+ checks: [
225
+ failReviewCheck("review_time_simulation_transient_failure", "Review-time simulation", "Sui simulation did not return a result because the connection failed or timed out.", "simulation")
226
+ ]
227
+ };
228
+ }
229
+ return {
230
+ status: "blocked",
231
+ blockedReason: classification.blockedReason,
232
+ checks: [
233
+ failReviewCheck("review_time_simulation_exception_blocked", "Review-time simulation", "Simulation could not be completed. This error was not classified as a temporary connection problem. The transaction, simulation request or adapter needs to be checked before wallet approval can be offered.", "simulation")
234
+ ]
235
+ };
236
+ }
237
+ const simulatedTransaction = simulationResult;
238
+ if (!simulatedTransaction?.effects?.status || typeof simulatedTransaction.effects.status.success !== "boolean") {
239
+ return {
240
+ status: "blocked",
241
+ blockedReason: "object_resolution_failed",
242
+ checks: [failReviewCheck("review_time_simulation_result_invalid", "Review-time simulation", INVALID_SIMULATION_EVIDENCE_MESSAGE, "simulation")]
243
+ };
244
+ }
245
+ if (simulatedTransaction.effects.status.success === false) {
246
+ const failureKind = simulatedTransaction.effects.status.error?.kind;
247
+ const failureReason = failureKind === GrpcTypes.ExecutionError_ExecutionErrorKind.INSUFFICIENT_GAS
248
+ ? "The checked simulation reported insufficient gas."
249
+ : failureKind === GrpcTypes.ExecutionError_ExecutionErrorKind.INSUFFICIENT_COIN_BALANCE
250
+ ? "The checked simulation reported insufficient coin balance."
251
+ : "The checked simulation rejected the transaction. No transaction was signed or submitted.";
252
+ const checks = [
253
+ failReviewCheck("review_time_simulation_result_failed", "Review-time simulation", `Review-time simulation did not succeed: ${failureReason}`, "simulation")
254
+ ];
255
+ return {
256
+ status: "blocked",
257
+ blockedReason: blockedReasonForSimulationFailure(failureKind),
258
+ checks
259
+ };
260
+ }
261
+ let recomputedTransactionDigest;
262
+ try {
263
+ recomputedTransactionDigest = await Transaction.from(material.transactionBytes).getDigest();
264
+ }
265
+ catch {
266
+ return {
267
+ status: "blocked",
268
+ blockedReason: "object_resolution_failed",
269
+ checks: [
270
+ failReviewCheck("review_time_simulation_result_invalid", "Review-time simulation", "Stored transaction material bytes could not produce a Sui transaction digest for the simulation binding.", "simulation")
271
+ ]
272
+ };
273
+ }
274
+ let evidence;
275
+ try {
276
+ evidence = createReviewTimeSimulationEvidenceFromTransaction({
277
+ transactionMaterial: parsed.transactionMaterial,
278
+ transactionMaterialDigest: parsed.transactionMaterialDigest,
279
+ recomputedTransactionDigest,
280
+ chainIdentifier: options.chainIdentifier,
281
+ transaction: simulatedTransaction,
282
+ submittedBytes: material.transactionBytes,
283
+ simulatedAt: input.now
284
+ });
285
+ verifyReviewTimeSimulationEvidence({
286
+ transactionMaterial: parsed.transactionMaterial,
287
+ transactionMaterialDigest: parsed.transactionMaterialDigest,
288
+ evidence,
289
+ transactionBytes: material.transactionBytes,
290
+ now: input.now
291
+ });
292
+ }
293
+ catch {
294
+ return {
295
+ status: "blocked",
296
+ blockedReason: "object_resolution_failed",
297
+ checks: [
298
+ failReviewCheck("review_time_simulation_result_invalid", "Review-time simulation", INVALID_SIMULATION_EVIDENCE_MESSAGE, "simulation")
299
+ ]
300
+ };
301
+ }
302
+ return {
303
+ status: "completed",
304
+ evidence,
305
+ checks: [
306
+ passReviewCheck("review_time_simulation_evidence", "Review-time simulation", "Simulated this exact stored transaction with validation checks enabled. The effects, balance changes and objects describe that simulation, not an actual transaction result. They do not authorize wallet approval or guarantee execution.", "simulation")
307
+ ]
308
+ };
309
+ };
310
+ }
311
+ function createReviewTimeSimulationEvidenceFromTransaction(input) {
312
+ const tx = input.transaction;
313
+ if (input.recomputedTransactionDigest !== input.transactionMaterialDigest.transactionDigest ||
314
+ (tx.digest !== undefined && tx.digest !== input.recomputedTransactionDigest)) {
315
+ throw new Error("simulation transaction digest must match stored material");
316
+ }
317
+ const returnedBytes = tx.transaction?.bcs?.value;
318
+ if (!(returnedBytes instanceof Uint8Array) || !Buffer.from(input.submittedBytes).equals(returnedBytes)) {
319
+ throw new Error("simulation must return exactly the submitted transaction bytes");
320
+ }
321
+ if (!tx.effects || tx.effects.transactionDigest !== input.recomputedTransactionDigest) {
322
+ throw new Error("simulated effects digest must match the stored material digest");
323
+ }
324
+ if (!Array.isArray(tx.balanceChanges) || !tx.objects || !Array.isArray(tx.objects.objects)) {
325
+ throw new Error("simulation is missing balance changes or object type evidence");
326
+ }
327
+ const objectTypes = new Map(tx.objects.objects.map((object) => {
328
+ if (typeof object.objectType !== "string" || !object.objectType)
329
+ throw new Error("simulation object type is missing");
330
+ return [normalizeSimulationAddress(object.objectId, "object type id"), object.objectType];
331
+ }));
332
+ const gas = tx.effects.gasUsed;
333
+ const objectChanges = tx.effects.changedObjects.map((change) => ({
334
+ objectId: normalizeSimulationAddress(change.objectId, "object change id"),
335
+ ...(objectTypes.has(change.objectId) ? { objectType: objectTypes.get(change.objectId) } : {}),
336
+ inputState: simulationEnum(GrpcTypes.ChangedObject_InputObjectState, change.inputState),
337
+ outputState: simulationEnum(GrpcTypes.ChangedObject_OutputObjectState, change.outputState),
338
+ idOperation: simulationEnum(GrpcTypes.ChangedObject_IdOperation, change.idOperation)
339
+ }));
340
+ return reviewTimeSimulationEvidenceSchema.parse({
341
+ evidenceVersion: REVIEW_TIME_SIMULATION_EVIDENCE_VERSION,
342
+ materialId: input.transactionMaterial.materialId, reviewSessionId: input.transactionMaterial.reviewSessionId,
343
+ planId: input.transactionMaterial.planId, account: input.transactionMaterial.account,
344
+ transactionDigest: input.recomputedTransactionDigest, kind: "review_time_simulation",
345
+ provider: REVIEW_TIME_SIMULATION_PROVIDER, network: "sui:mainnet", checksEnabled: true,
346
+ requiredFields: [...WALLET_REVIEW_REQUIRED_SIMULATION_FIELDS], missingFields: [], status: "success",
347
+ simulatedAt: input.simulatedAt.toISOString(), expiresAt: input.transactionMaterial.expiresAt,
348
+ effects: { transactionDigest: tx.effects.transactionDigest, gasCostSummary: summarizeGasCost({
349
+ computationCost: gas?.computationCost?.toString(), storageCost: gas?.storageCost?.toString(),
350
+ storageRebate: gas?.storageRebate?.toString(), nonRefundableStorageFee: gas?.nonRefundableStorageFee?.toString()
351
+ }), changedObjectCount: objectChanges.length },
352
+ balanceChanges: tx.balanceChanges.map((change) => ({ address: normalizeSimulationAddress(change.address, "balance change address"),
353
+ coinType: normalizeCoinType(change.coinType), amount: change.amount })), objectChanges,
354
+ transaction: summarizeSimulatedTransaction(Transaction.from(returnedBytes).getData(), input.transactionMaterial.account, input.chainIdentifier)
355
+ });
356
+ }
357
+ // Preserve the established simulation labels while reading the SDK's public
358
+ // protobuf enums. Missing state is malformed evidence, not a guessed state.
359
+ function simulationEnum(values, value) {
360
+ if (value === undefined || values[value] === undefined)
361
+ throw new Error("simulation object state is missing");
362
+ const name = values[value];
363
+ if (name === "ACCUMULATOR_WRITE")
364
+ return "AccumulatorWriteV1";
365
+ if (name === "ID_OPERATION_UNKNOWN")
366
+ return "None";
367
+ return name.split("_").map((word) => word[0] + word.slice(1).toLowerCase()).join("");
368
+ }
369
+ function summarizeGasCost(gasUsed) {
370
+ const computationCost = parseRawU64(gasUsed.computationCost, "computationCost");
371
+ const storageCost = parseRawU64(gasUsed.storageCost, "storageCost");
372
+ const storageRebate = parseRawU64(gasUsed.storageRebate, "storageRebate");
373
+ const nonRefundableStorageFee = parseRawU64(gasUsed.nonRefundableStorageFee, "nonRefundableStorageFee");
374
+ return {
375
+ computationCostRaw: computationCost.toString(),
376
+ storageCostRaw: storageCost.toString(),
377
+ storageRebateRaw: storageRebate.toString(),
378
+ nonRefundableStorageFeeRaw: nonRefundableStorageFee.toString()
379
+ };
380
+ }
381
+ function summarizeSimulatedTransaction(transaction, account, chainIdentifier) {
382
+ if (transaction.sender === null || transaction.sender === undefined) {
383
+ throw new Error("simulation transaction sender is missing");
384
+ }
385
+ const sender = normalizeSimulationAddress(transaction.sender, "transaction sender");
386
+ const gasData = transaction.gasData;
387
+ return {
388
+ sender,
389
+ funding: describeTransactionFunding(transaction, account, chainIdentifier),
390
+ gasPaymentCount: Array.isArray(gasData.payment) ? gasData.payment.length : 0,
391
+ inputCount: transaction.inputs.length,
392
+ commandCount: transaction.commands.length,
393
+ ...(gasData.budget === null || gasData.budget === undefined
394
+ ? {}
395
+ : { gasBudgetRaw: parseRawU64(String(gasData.budget), "gasBudgetRaw").toString() }),
396
+ ...(gasData.price === null || gasData.price === undefined
397
+ ? {}
398
+ : { gasPriceRaw: parseRawU64(String(gasData.price), "gasPriceRaw").toString() })
399
+ };
400
+ }
401
+ function normalizeSimulationAddress(value, label) {
402
+ const normalized = parseSuiAddress(value);
403
+ if (!normalized) {
404
+ throw new Error(`simulation ${label} must be a valid Sui address`);
405
+ }
406
+ return normalized;
407
+ }
408
+ function blockedReasonForSimulationFailure(kind) {
409
+ if (kind === GrpcTypes.ExecutionError_ExecutionErrorKind.INSUFFICIENT_GAS) {
410
+ return "insufficient_gas";
411
+ }
412
+ if (kind === GrpcTypes.ExecutionError_ExecutionErrorKind.INSUFFICIENT_COIN_BALANCE) {
413
+ return "insufficient_balance";
414
+ }
415
+ return "object_resolution_failed";
416
+ }
417
+ function classifySimulationException(error) {
418
+ if (isTransientSimulationException(error)) {
419
+ return { status: "refresh_required" };
420
+ }
421
+ return { status: "blocked", blockedReason: "object_resolution_failed" };
422
+ }
423
+ function isTransientSimulationException(error) {
424
+ const code = errorCode(error);
425
+ if (code === "UNAVAILABLE" ||
426
+ code === "DEADLINE_EXCEEDED" ||
427
+ code === "RESOURCE_EXHAUSTED" ||
428
+ code === "ECONNRESET" ||
429
+ code === "ECONNREFUSED" ||
430
+ code === "ETIMEDOUT" ||
431
+ code === "ENOTFOUND" ||
432
+ code === "EAI_AGAIN" ||
433
+ code === 4 ||
434
+ code === 8 ||
435
+ code === 14) {
436
+ return true;
437
+ }
438
+ const message = errorMessage(error).toLowerCase();
439
+ if (message.length === 0) {
440
+ return false;
441
+ }
442
+ return [
443
+ /\b(grpc|rpc|transport|network|endpoint|connection|connect|socket|fetch)\b[\s\S]{0,80}\b(unavailable|timeout|timed out|deadline|reset|refused|failed|hang up|exhausted)\b/,
444
+ /\b(unavailable|timeout|timed out|deadline exceeded|temporarily unavailable|service unavailable|gateway timeout|too many requests)\b[\s\S]{0,80}\b(grpc|rpc|transport|network|endpoint|connection|connect|socket|fetch)\b/,
445
+ /\b(econnreset|econnrefused|etimedout|enotfound|eai_again|socket hang up|fetch failed|network error|deadline exceeded|request timeout|service unavailable|gateway timeout|too many requests)\b/
446
+ ].some((pattern) => pattern.test(message));
447
+ }
448
+ function errorCode(error) {
449
+ if (typeof error !== "object" || error === null) {
450
+ return undefined;
451
+ }
452
+ const value = error.code ??
453
+ error.status;
454
+ return typeof value === "string" || typeof value === "number" ? value : undefined;
455
+ }
456
+ function errorMessage(error) {
457
+ if (error instanceof Error) {
458
+ return error.message;
459
+ }
460
+ if (typeof error === "string") {
461
+ return error;
462
+ }
463
+ if (typeof error === "object" && error !== null) {
464
+ const message = error.message;
465
+ if (typeof message === "string") {
466
+ return message;
467
+ }
468
+ }
469
+ return "";
470
+ }