@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,256 @@
1
+ import { Transaction } from "@mysten/sui/transactions";
2
+ import { transactionFundingEvidenceSchema, describeTransactionFunding, assertTransactionFundingMatches } from "./transactionFunding.js";
3
+ import { z } from "zod";
4
+ import { isValidStructTag, normalizeStructTag } from "@mysten/sui/utils";
5
+ import { adapterEvidenceClaimSchema, adapterGasObjectOwnershipLinkSchema, adapterObjectOwnershipEvidenceSchema, adapterSourceOfTruthSchema, SUI_GAS_COIN_TYPE } from "./signableAdapterContract.js";
6
+ import { normalizedSuiAddressSchema, suiTransactionDigestSchema } from "../suiAddress.js";
7
+ export const TRANSACTION_OBJECT_OWNERSHIP_EVIDENCE_VERSION = "transaction-object-ownership-v2";
8
+ export const TRANSACTION_OBJECT_ROLES = [
9
+ "gas_object",
10
+ "imm_or_owned_object",
11
+ "shared_object",
12
+ "receiving_object"
13
+ ];
14
+ export const TRANSACTION_OBJECT_OWNERSHIP_STATUSES = [
15
+ "owned_by_account",
16
+ "not_owned_by_account",
17
+ "shared_object",
18
+ "immutable_object",
19
+ "object_owner",
20
+ "consensus_address_owner",
21
+ "unknown_owner"
22
+ ];
23
+ const isoUtcStringSchema = z.string().refine((value) => {
24
+ const parsed = new Date(value);
25
+ return Number.isFinite(parsed.getTime()) && parsed.toISOString() === value;
26
+ }, "Expected ISO 8601 UTC timestamp");
27
+ const moveStructTagSchema = z.string().min(1).max(512).refine((value) => isValidStructTag(value), "Expected valid Move struct tag").transform((value) => normalizeStructTag(value));
28
+ export const transactionObjectOwnershipFactSchema = z.object({
29
+ objectId: normalizedSuiAddressSchema,
30
+ roles: z.array(z.enum(TRANSACTION_OBJECT_ROLES)).min(1),
31
+ ownership: z.enum(TRANSACTION_OBJECT_OWNERSHIP_STATUSES),
32
+ ownerKind: z.string().min(1).max(80),
33
+ ownerAccount: normalizedSuiAddressSchema.optional(),
34
+ objectType: moveStructTagSchema,
35
+ source: z.literal("stored_transaction_data_and_mainnet_object_read")
36
+ }).strict().superRefine((value, ctx) => {
37
+ if (new Set(value.roles).size !== value.roles.length) {
38
+ ctx.addIssue({
39
+ code: "custom",
40
+ path: ["roles"],
41
+ message: "roles must be unique"
42
+ });
43
+ }
44
+ });
45
+ export const transactionObjectOwnershipEvidenceSchema = z.object({
46
+ evidenceVersion: z.literal(TRANSACTION_OBJECT_OWNERSHIP_EVIDENCE_VERSION),
47
+ materialId: z.string().min(1),
48
+ reviewSessionId: z.string().min(1),
49
+ planId: z.string().min(1),
50
+ account: normalizedSuiAddressSchema,
51
+ transactionDigest: suiTransactionDigestSchema,
52
+ funding: transactionFundingEvidenceSchema,
53
+ objectCount: z.number().int().min(1),
54
+ objects: z.array(transactionObjectOwnershipFactSchema).min(1),
55
+ verifiedAt: isoUtcStringSchema,
56
+ expiresAt: isoUtcStringSchema
57
+ }).strict().superRefine((value, ctx) => {
58
+ if (value.account !== value.funding.sender) {
59
+ ctx.addIssue({ code: "custom", path: ["funding", "sender"], message: "Funding must match the ownership account" });
60
+ }
61
+ if (value.objectCount !== value.objects.length) {
62
+ ctx.addIssue({
63
+ code: "custom",
64
+ path: ["objectCount"],
65
+ message: "objectCount must equal objects.length"
66
+ });
67
+ }
68
+ if (new Set(value.objects.map((object) => object.objectId)).size !== value.objects.length) {
69
+ ctx.addIssue({
70
+ code: "custom",
71
+ path: ["objects"],
72
+ message: "objects must have unique objectId values"
73
+ });
74
+ }
75
+ const verifiedAtMs = Date.parse(value.verifiedAt);
76
+ const expiresAtMs = Date.parse(value.expiresAt);
77
+ if (expiresAtMs <= verifiedAtMs) {
78
+ ctx.addIssue({
79
+ code: "custom",
80
+ path: ["expiresAt"],
81
+ message: "expiresAt must be after verifiedAt"
82
+ });
83
+ }
84
+ });
85
+ export function parseSuiCoinObjectType(objectType) {
86
+ if (!isValidStructTag(objectType)) {
87
+ return { status: "not_coin" };
88
+ }
89
+ const normalized = normalizeStructTag(objectType);
90
+ const coinPrefix = `${normalizeStructTag("0x2::coin::Coin<0x2::sui::SUI>").split("<")[0]}<`;
91
+ if (!normalized.startsWith(coinPrefix) || !normalized.endsWith(">")) {
92
+ return { status: "not_coin" };
93
+ }
94
+ const coinType = normalized.slice(coinPrefix.length, -1);
95
+ if (!isValidStructTag(coinType)) {
96
+ return { status: "not_coin" };
97
+ }
98
+ return {
99
+ status: "coin",
100
+ objectType: normalized,
101
+ coinType: normalizeStructTag(coinType)
102
+ };
103
+ }
104
+ export function parseTransactionObjectOwnershipEvidence(value) {
105
+ return transactionObjectOwnershipEvidenceSchema.parse(value);
106
+ }
107
+ export function verifyTransactionObjectOwnershipEvidence(input) {
108
+ const evidence = parseTransactionObjectOwnershipEvidence(input.evidence);
109
+ assertTransactionFundingMatches(describeTransactionFunding(Transaction.from(input.transactionBytes).getData(), input.transactionMaterial.account, evidence.funding.chainIdentifier), evidence.funding);
110
+ const now = input.now ?? new Date();
111
+ const nowMs = now.getTime();
112
+ if (!Number.isFinite(nowMs)) {
113
+ throw new Error("now must be a valid Date");
114
+ }
115
+ if (evidence.materialId !== input.transactionMaterial.materialId ||
116
+ evidence.reviewSessionId !== input.transactionMaterial.reviewSessionId ||
117
+ evidence.planId !== input.transactionMaterial.planId ||
118
+ evidence.account !== input.transactionMaterial.account ||
119
+ evidence.expiresAt !== input.transactionMaterial.expiresAt ||
120
+ evidence.transactionDigest !== input.transactionMaterialDigest.transactionDigest) {
121
+ throw new Error("transaction object ownership evidence must match material and digest identity");
122
+ }
123
+ if (input.transactionMaterialDigest.materialId !== input.transactionMaterial.materialId ||
124
+ input.transactionMaterialDigest.reviewSessionId !== input.transactionMaterial.reviewSessionId ||
125
+ input.transactionMaterialDigest.planId !== input.transactionMaterial.planId ||
126
+ input.transactionMaterialDigest.account !== input.transactionMaterial.account ||
127
+ input.transactionMaterialDigest.expiresAt !== input.transactionMaterial.expiresAt) {
128
+ throw new Error("transaction material digest must match material identity before ownership evidence is accepted");
129
+ }
130
+ const materialCreatedAtMs = Date.parse(input.transactionMaterial.createdAt);
131
+ const verifiedAtMs = Date.parse(evidence.verifiedAt);
132
+ const expiresAtMs = Date.parse(evidence.expiresAt);
133
+ if (verifiedAtMs < materialCreatedAtMs || verifiedAtMs > nowMs || verifiedAtMs >= expiresAtMs) {
134
+ throw new Error("transaction object ownership verifiedAt must be between material creation, now, and material expiry");
135
+ }
136
+ if (expiresAtMs <= nowMs) {
137
+ throw new Error("transaction object ownership evidence must not be expired");
138
+ }
139
+ const contractMapping = mapTransactionObjectOwnershipEvidenceToContractDraft(evidence);
140
+ if (contractMapping.status === "unsupported") {
141
+ throw new Error(`transaction object ownership evidence is not contract-mappable: ${contractMapping.reason}`);
142
+ }
143
+ return evidence;
144
+ }
145
+ export function mapTransactionObjectOwnershipEvidenceToContractDraft(evidenceInput) {
146
+ const evidence = parseTransactionObjectOwnershipEvidence(evidenceInput);
147
+ const sourceEvidenceId = "tx_object_ownership_source";
148
+ const claims = [];
149
+ const objects = [];
150
+ const gasObjectOwnershipLinks = [];
151
+ for (const [index, object] of evidence.objects.entries()) {
152
+ const mapped = mapOwnershipFactForContract(object);
153
+ if (mapped.status === "unsupported") {
154
+ return {
155
+ ...mapped,
156
+ objectId: object.objectId,
157
+ roles: object.roles,
158
+ ownership: object.ownership
159
+ };
160
+ }
161
+ const evidenceClaimId = `tx_object_ownership:${index}`;
162
+ claims.push(adapterEvidenceClaimSchema.parse({
163
+ id: evidenceClaimId,
164
+ factKind: "object_ownership",
165
+ sourceEvidenceId,
166
+ objectId: object.objectId,
167
+ ownerAccount: evidence.account,
168
+ ownership: mapped.ownership
169
+ }));
170
+ objects.push({
171
+ objectId: object.objectId,
172
+ role: mapped.role,
173
+ ownership: mapped.ownership,
174
+ evidenceClaimId
175
+ });
176
+ if (mapped.role === "gas_coin" && mapped.ownership === "owned_by_account") {
177
+ gasObjectOwnershipLinks.push(adapterGasObjectOwnershipLinkSchema.parse({
178
+ objectId: object.objectId,
179
+ ownerAccount: evidence.account,
180
+ ownershipClaimId: evidenceClaimId
181
+ }));
182
+ }
183
+ }
184
+ if (gasObjectOwnershipLinks.length !== evidence.funding.gasObjectIds.length ||
185
+ gasObjectOwnershipLinks.some((link) => !evidence.funding.gasObjectIds.includes(link.objectId)) ||
186
+ (gasObjectOwnershipLinks.length === 0 && evidence.funding.gasMode === "coin_objects")) {
187
+ return {
188
+ status: "unsupported",
189
+ reason: "contract gas ownership links must cover exactly the real gas objects in the verified funding evidence",
190
+ objectId: evidence.objects[0]?.objectId ?? "unknown",
191
+ roles: evidence.objects[0]?.roles ?? [],
192
+ ownership: evidence.objects[0]?.ownership ?? "unknown_owner"
193
+ };
194
+ }
195
+ if (!objects.some((object) => object.role !== "gas_coin")) {
196
+ return {
197
+ status: "unsupported",
198
+ reason: "contract mapping requires the full transaction object set, not gas-only ownership evidence",
199
+ objectId: evidence.objects[0]?.objectId ?? "unknown",
200
+ roles: evidence.objects[0]?.roles ?? [],
201
+ ownership: evidence.objects[0]?.ownership ?? "unknown_owner"
202
+ };
203
+ }
204
+ return {
205
+ status: "mapped",
206
+ sourceReferences: adapterSourceOfTruthSchema.parse({
207
+ id: sourceEvidenceId,
208
+ kind: "wallet_account_read",
209
+ network: "sui:mainnet",
210
+ source: "Stored local transaction data object refs plus Sui mainnet object owner reads",
211
+ verifiedAt: evidence.verifiedAt,
212
+ fields: ["ownerAccount", "objects"]
213
+ }),
214
+ evidenceClaims: claims,
215
+ objectOwnership: adapterObjectOwnershipEvidenceSchema.parse({
216
+ checkedAt: evidence.verifiedAt,
217
+ ownerAccount: evidence.account,
218
+ objects
219
+ }),
220
+ gasObjectOwnershipLinks
221
+ };
222
+ }
223
+ function mapOwnershipFactForContract(fact) {
224
+ if (fact.roles.includes("gas_object")) {
225
+ const coinObject = parseSuiCoinObjectType(fact.objectType);
226
+ return fact.ownership === "owned_by_account" &&
227
+ coinObject.status === "coin" &&
228
+ coinObject.coinType === SUI_GAS_COIN_TYPE
229
+ ? { status: "mapped", role: "gas_coin", ownership: "owned_by_account" }
230
+ : { status: "unsupported", reason: "gas objects must be owned by the connected account and be Coin<SUI> objects" };
231
+ }
232
+ if (fact.roles.includes("shared_object")) {
233
+ return fact.ownership === "shared_object"
234
+ ? { status: "mapped", role: "shared_object", ownership: "shared_object" }
235
+ : { status: "unsupported", reason: "shared object refs must have shared ownership facts" };
236
+ }
237
+ if (fact.roles.includes("receiving_object")) {
238
+ return fact.ownership === "owned_by_account"
239
+ ? { status: "mapped", role: "receiving_object", ownership: "owned_by_account" }
240
+ : { status: "unsupported", reason: "receiving object refs must be owned by the connected account" };
241
+ }
242
+ if (fact.roles.includes("imm_or_owned_object")) {
243
+ if (fact.ownership === "immutable_object") {
244
+ return { status: "mapped", role: "protocol_object", ownership: "immutable_or_package" };
245
+ }
246
+ const coinObject = parseSuiCoinObjectType(fact.objectType);
247
+ if (fact.ownership === "owned_by_account" && coinObject.status === "coin") {
248
+ return { status: "mapped", role: "input_coin", ownership: "owned_by_account" };
249
+ }
250
+ return {
251
+ status: "unsupported",
252
+ reason: "owned ImmOrOwnedObject refs must be Coin<T> objects before contract mapping can call them input coins"
253
+ };
254
+ }
255
+ return { status: "unsupported", reason: "object ownership role is not contract-mappable" };
256
+ }
@@ -0,0 +1,323 @@
1
+ import { describeTransactionFunding, readTransactionFunding, TransactionFundingError } from "./transactionFunding.js";
2
+ import { Transaction } from "@mysten/sui/transactions";
3
+ import { mapTransactionObjectOwnershipEvidenceToContractDraft, parseTransactionObjectOwnershipEvidence, TRANSACTION_OBJECT_OWNERSHIP_EVIDENCE_VERSION } from "./transactionObjectOwnershipEvidence.js";
4
+ import { failReviewCheck, passReviewCheck } from "./reviewCheckResults.js";
5
+ import { parseSuiAddress } from "../suiAddress.js";
6
+ import { LocalTransactionMaterialStoreError, verifyLocalTransactionMaterialArtifacts } from "../session/transactionMaterialStore.js";
7
+ export function createTransactionObjectOwnershipProducer(options) {
8
+ return async (input) => {
9
+ if (options.network !== "mainnet" || options.chainIdentifier !== options.expectedChainIdentifier) {
10
+ return {
11
+ status: "blocked",
12
+ blockedReason: "network_mismatch",
13
+ checks: [
14
+ failReviewCheck("transaction_object_ownership_network_mismatch", "Object ownership network", "Object ownership evidence requires a verified Sui mainnet object source and matching mainnet chain identifier.", "network")
15
+ ]
16
+ };
17
+ }
18
+ let materialHandle;
19
+ let materialDigest;
20
+ try {
21
+ const verified = await verifyLocalTransactionMaterialArtifacts({
22
+ materialStore: options.materialStore,
23
+ transactionMaterial: input.materialHandle,
24
+ transactionMaterialDigest: input.materialDigest,
25
+ now: input.now
26
+ });
27
+ materialHandle = verified.transactionMaterial;
28
+ materialDigest = verified.transactionMaterialDigest;
29
+ }
30
+ catch (error) {
31
+ const message = error instanceof Error ? error.message : "transaction material is unavailable";
32
+ const check = failReviewCheck("transaction_object_ownership_material_unavailable", "Object ownership material", "Object ownership evidence was not produced because the stored local transaction material was unavailable, stale, or not bound to its internal digest.", "adapter");
33
+ if (message.includes("expired") || message.includes("unavailable")) {
34
+ return {
35
+ status: "refresh_required",
36
+ refreshReason: "quote_stale",
37
+ checks: [check]
38
+ };
39
+ }
40
+ return {
41
+ status: "blocked",
42
+ blockedReason: "object_resolution_failed",
43
+ checks: [check]
44
+ };
45
+ }
46
+ const material = options.materialStore.getTransactionMaterial(materialHandle, input.now);
47
+ if (!material) {
48
+ return {
49
+ status: "refresh_required",
50
+ refreshReason: "quote_stale",
51
+ checks: [
52
+ failReviewCheck("transaction_object_ownership_material_unavailable", "Object ownership material", "Object ownership evidence was not produced because the stored local transaction material was unavailable or expired; refresh the review evidence before continuing.", "adapter")
53
+ ]
54
+ };
55
+ }
56
+ let objectRefs;
57
+ let funding;
58
+ try {
59
+ const description = describeTransactionFunding(Transaction.from(material.transactionBytes).getData(), materialHandle.account, options.chainIdentifier);
60
+ funding = await readTransactionFunding(description, options.fundingSource);
61
+ objectRefs = extractObjectRefsFromStoredTransactionBytes(material.transactionBytes, funding.gasObjectIds);
62
+ }
63
+ catch (error) {
64
+ if (error instanceof TransactionFundingError)
65
+ return { status: "blocked",
66
+ blockedReason: error.kind === "insufficient_balance" || error.kind === "insufficient_gas" ? error.kind : "object_resolution_failed",
67
+ checks: [failReviewCheck("transaction_funding_" + error.kind, "Transaction funding", error.message, error.kind === "unavailable" ? "network" : "wallet")] };
68
+ return {
69
+ status: "blocked",
70
+ blockedReason: "object_resolution_failed",
71
+ checks: [
72
+ failReviewCheck("transaction_object_ownership_refs_unavailable", "Object ownership refs", "Transaction funding and object ownership could not be verified because the stored material has invalid or unsupported account, gas, withdrawal, expiration or object references.", "adapter")
73
+ ]
74
+ };
75
+ }
76
+ const objectRoles = mergeObjectRoles(objectRefs);
77
+ if (objectRoles.size === 0) {
78
+ return {
79
+ status: "blocked",
80
+ blockedReason: "object_resolution_failed",
81
+ checks: [
82
+ failReviewCheck("transaction_object_ownership_refs_empty", "Object ownership refs", "Object ownership evidence requires at least one resolved transaction object reference.", "adapter")
83
+ ]
84
+ };
85
+ }
86
+ const facts = [];
87
+ for (const [objectId, roles] of objectRoles) {
88
+ let response;
89
+ try {
90
+ response = await options.objectSource.getObject({ objectId });
91
+ }
92
+ catch {
93
+ return objectReadBlocked();
94
+ }
95
+ if (parseSuiObjectId(response.object.objectId) !== objectId) {
96
+ return objectReadBlocked();
97
+ }
98
+ const fact = classifyObjectOwnership({
99
+ objectId,
100
+ roles,
101
+ owner: response.object.owner,
102
+ objectType: response.object.type,
103
+ account: materialHandle.account
104
+ });
105
+ if (!fact) {
106
+ return objectReadBlocked();
107
+ }
108
+ facts.push(fact);
109
+ }
110
+ const invalidFact = facts.find((fact) => !isAcceptableOwnershipFact(fact));
111
+ if (invalidFact) {
112
+ const blockedReason = "object_resolution_failed";
113
+ return {
114
+ status: "blocked",
115
+ blockedReason,
116
+ checks: [
117
+ failReviewCheck("transaction_object_ownership_unverified", "Object ownership", "An object's ownership could not be confirmed, or an account-owned transaction or gas object belongs to a different account.", invalidFact.roles.includes("gas_object") ? "wallet" : "adapter")
118
+ ]
119
+ };
120
+ }
121
+ let evidence;
122
+ try {
123
+ evidence = parseTransactionObjectOwnershipEvidence({
124
+ evidenceVersion: TRANSACTION_OBJECT_OWNERSHIP_EVIDENCE_VERSION,
125
+ materialId: materialHandle.materialId,
126
+ reviewSessionId: materialHandle.reviewSessionId,
127
+ planId: materialHandle.planId,
128
+ account: materialHandle.account,
129
+ transactionDigest: materialDigest.transactionDigest,
130
+ funding,
131
+ objectCount: facts.length,
132
+ objects: facts,
133
+ verifiedAt: input.now.toISOString(),
134
+ expiresAt: materialHandle.expiresAt
135
+ });
136
+ }
137
+ catch {
138
+ return objectReadBlocked();
139
+ }
140
+ const contractMapping = mapTransactionObjectOwnershipEvidenceToContractDraft(evidence);
141
+ if (contractMapping.status === "unsupported") {
142
+ return {
143
+ status: "blocked",
144
+ blockedReason: "object_resolution_failed",
145
+ checks: [
146
+ failReviewCheck("transaction_object_ownership_contract_mapping_unsupported", "Object ownership contract mapping", `The object ownership checks could not be used for this transaction review. Reported reason: “${contractMapping.reason}”.`, contractMapping.roles.includes("gas_object") ? "wallet" : "adapter")
147
+ ]
148
+ };
149
+ }
150
+ return {
151
+ status: "completed",
152
+ evidence,
153
+ checks: [
154
+ passReviewCheck("transaction_object_ownership_verified", "Object ownership", "Checked the transaction objects against Sui mainnet. The gas objects and account-owned coins belong to the connected account; shared or immutable protocol objects are recorded separately. These ownership checks do not authorize wallet approval or guarantee execution.", "wallet")
155
+ ]
156
+ };
157
+ };
158
+ }
159
+ function extractObjectRefsFromStoredTransactionBytes(transactionBytes, gasObjectIds) {
160
+ const transaction = Transaction.from(transactionBytes);
161
+ const data = transaction.getData();
162
+ const refs = [];
163
+ for (const objectId of gasObjectIds)
164
+ refs.push({ objectId, role: "gas_object" });
165
+ for (const input of data.inputs) {
166
+ const inputKind = enumKind(input, [
167
+ "Object",
168
+ "Pure",
169
+ "UnresolvedPure",
170
+ "UnresolvedObject",
171
+ "FundsWithdrawal"
172
+ ]);
173
+ if (!inputKind) {
174
+ throw new LocalTransactionMaterialStoreError("unknown transaction input kind");
175
+ }
176
+ if (inputKind === "Pure" || inputKind === "UnresolvedPure" || inputKind === "FundsWithdrawal") {
177
+ continue;
178
+ }
179
+ if (inputKind === "UnresolvedObject") {
180
+ throw new LocalTransactionMaterialStoreError("transaction object refs must be resolved");
181
+ }
182
+ const objectInput = input.Object;
183
+ const objectKind = enumKind(objectInput, [
184
+ "ImmOrOwnedObject",
185
+ "SharedObject",
186
+ "Receiving"
187
+ ]);
188
+ if (!objectKind) {
189
+ throw new LocalTransactionMaterialStoreError("unknown transaction object input kind");
190
+ }
191
+ if (objectKind === "ImmOrOwnedObject") {
192
+ refs.push({
193
+ objectId: readObjectId(objectInput.ImmOrOwnedObject),
194
+ role: "imm_or_owned_object"
195
+ });
196
+ }
197
+ else if (objectKind === "SharedObject") {
198
+ refs.push({
199
+ objectId: readObjectId(objectInput.SharedObject),
200
+ role: "shared_object"
201
+ });
202
+ }
203
+ else {
204
+ refs.push({
205
+ objectId: readObjectId(objectInput.Receiving),
206
+ role: "receiving_object"
207
+ });
208
+ }
209
+ }
210
+ return refs;
211
+ }
212
+ function enumKind(value, allowedKinds) {
213
+ if (!isRecord(value)) {
214
+ return undefined;
215
+ }
216
+ if ("$kind" in value) {
217
+ return typeof value.$kind === "string" && allowedKinds.includes(value.$kind)
218
+ ? value.$kind
219
+ : undefined;
220
+ }
221
+ const matchingKeys = allowedKinds.filter((kind) => kind in value);
222
+ return matchingKeys.length === 1 ? matchingKeys[0] : undefined;
223
+ }
224
+ function readObjectId(value) {
225
+ if (!isRecord(value) || typeof value.objectId !== "string") {
226
+ throw new LocalTransactionMaterialStoreError("transaction object ref is missing objectId");
227
+ }
228
+ return parseSuiObjectId(value.objectId);
229
+ }
230
+ function parseSuiObjectId(value) {
231
+ const objectId = parseSuiAddress(value);
232
+ if (!objectId) {
233
+ throw new LocalTransactionMaterialStoreError("transaction object ref has invalid Sui object id");
234
+ }
235
+ return objectId;
236
+ }
237
+ function mergeObjectRoles(refs) {
238
+ const merged = new Map();
239
+ for (const ref of refs) {
240
+ const roles = merged.get(ref.objectId) ?? [];
241
+ if (!roles.includes(ref.role)) {
242
+ roles.push(ref.role);
243
+ }
244
+ merged.set(ref.objectId, roles);
245
+ }
246
+ return merged;
247
+ }
248
+ function classifyObjectOwnership(input) {
249
+ if (!isRecord(input.owner) || typeof input.owner.$kind !== "string") {
250
+ return undefined;
251
+ }
252
+ if (typeof input.objectType !== "string") {
253
+ return undefined;
254
+ }
255
+ if (input.owner.$kind === "AddressOwner") {
256
+ const ownerAccount = parseSuiAddress(typeof input.owner.AddressOwner === "string" ? input.owner.AddressOwner : "");
257
+ if (!ownerAccount) {
258
+ return undefined;
259
+ }
260
+ return {
261
+ objectId: input.objectId,
262
+ roles: input.roles,
263
+ ownership: ownerAccount === input.account ? "owned_by_account" : "not_owned_by_account",
264
+ ownerKind: "AddressOwner",
265
+ ownerAccount,
266
+ objectType: input.objectType,
267
+ source: "stored_transaction_data_and_mainnet_object_read"
268
+ };
269
+ }
270
+ if (input.owner.$kind === "Shared") {
271
+ return ownershipFact(input, "shared_object", "Shared");
272
+ }
273
+ if (input.owner.$kind === "Immutable") {
274
+ return ownershipFact(input, "immutable_object", "Immutable");
275
+ }
276
+ if (input.owner.$kind === "ObjectOwner") {
277
+ return ownershipFact(input, "object_owner", "ObjectOwner");
278
+ }
279
+ if (input.owner.$kind === "ConsensusAddressOwner") {
280
+ return ownershipFact(input, "consensus_address_owner", "ConsensusAddressOwner");
281
+ }
282
+ if (input.owner.$kind === "Unknown") {
283
+ return ownershipFact(input, "unknown_owner", "Unknown");
284
+ }
285
+ return undefined;
286
+ }
287
+ function ownershipFact(input, ownership, ownerKind) {
288
+ return {
289
+ objectId: input.objectId,
290
+ roles: input.roles,
291
+ ownership,
292
+ ownerKind,
293
+ objectType: input.objectType,
294
+ source: "stored_transaction_data_and_mainnet_object_read"
295
+ };
296
+ }
297
+ function isAcceptableOwnershipFact(fact) {
298
+ if (fact.ownership === "unknown_owner" || fact.ownership === "object_owner" || fact.ownership === "consensus_address_owner") {
299
+ return false;
300
+ }
301
+ if (fact.roles.includes("gas_object")) {
302
+ return fact.ownership === "owned_by_account";
303
+ }
304
+ if (fact.roles.includes("imm_or_owned_object") || fact.roles.includes("receiving_object")) {
305
+ return fact.ownership === "owned_by_account" ||
306
+ (fact.roles.includes("imm_or_owned_object") && fact.ownership === "immutable_object");
307
+ }
308
+ return fact.roles.every((role) => role === "shared_object")
309
+ ? fact.ownership === "shared_object" || fact.ownership === "immutable_object"
310
+ : false;
311
+ }
312
+ function objectReadBlocked() {
313
+ return {
314
+ status: "blocked",
315
+ blockedReason: "object_resolution_failed",
316
+ checks: [
317
+ failReviewCheck("transaction_object_ownership_read_failed", "Object ownership read", "Object ownership evidence was not produced because a transaction object owner could not be read or classified from mainnet object state.", "network")
318
+ ]
319
+ };
320
+ }
321
+ function isRecord(value) {
322
+ return typeof value === "object" && value !== null;
323
+ }
@@ -0,0 +1,33 @@
1
+ export { SUI_CHAIN_RECEIPT_REQUIRED_INCLUDE } from "./suiChainReceiptEvidence.js";
2
+ export const BLOCKED_REASONS = [
3
+ "adapter_not_implemented",
4
+ "producer_stage_missing",
5
+ "wallet_review_contract_emit_missing",
6
+ "network_mismatch",
7
+ "insufficient_balance",
8
+ "insufficient_gas",
9
+ "allowlist_violation",
10
+ "asset_mismatch",
11
+ "amount_mismatch",
12
+ "wallet_mismatch",
13
+ "unsupported_action",
14
+ "object_resolution_failed",
15
+ "proposal_review_only"
16
+ ];
17
+ export const REFRESH_REASONS = [
18
+ "review_evidence_stale",
19
+ "quote_stale",
20
+ "quote_unavailable",
21
+ "simulation_transient_failure",
22
+ "review_update_failed",
23
+ "wallet_connection_changed"
24
+ ];
25
+ export const REVIEW_PREPARATION_STATUSES = ["proposed", "awaiting_wallet", "wallet_connected", "ready_for_wallet_review", "refresh_required", "blocked", "expired"];
26
+ export const REVIEW_MATERIAL_DERIVED_FIELDS = ["humanReadableReview", "simulation", "transactionReviewData",
27
+ "ptbVisualization", "assetFlowActual", "beforeAfterBalance"];
28
+ // Display wording for the three computed review states, not wallet authority.
29
+ export const REVIEW_UI_LABELS = {
30
+ ready_for_wallet_review: "Ready for your review",
31
+ refresh_required: "Review needs updating",
32
+ blocked: "Review blocked"
33
+ };