@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,88 @@
1
+ export const MAX_DEEPBOOK_ORDERBOOK_TICKS = 50;
2
+ export const MAX_DEEPBOOK_ACCOUNT_OPEN_ORDER_IDS = 100;
3
+ export const DEEPBOOK_MID_PRICE_TYPE = "deepbook_mid_price";
4
+ export const DEEPBOOK_MID_PRICE_DIRECTION = "quote_per_base";
5
+ export const DEEPBOOK_MID_PRICE_PRECISION = "deepbook_v3_to_fixed_9_js_number";
6
+ export const DEEPBOOK_MID_PRICE_SEMANTICS_KIND = "deepbook_mid_price_snapshot";
7
+ export const WALLET_BALANCE_QUANTITY_KIND = "sui_wallet_balance_snapshot";
8
+ export const DEEPBOOK_ACCOUNT_QUANTITY_KIND = "deepbook_display_number";
9
+ export const DEEPBOOK_QUOTE_QUANTITY_KIND = "deepbook_quote_display_amount";
10
+ export const DEEPBOOK_RAW_QUOTE_QUANTITY_KIND = "deepbook_quote_raw_u64";
11
+ export const DEEPBOOK_USDC_PRICE_HISTORY_QUANTITY_KIND = "deepbook_official_indexer_candles";
12
+ export const INTENT_EVIDENCE_QUANTITY_KIND = "sui_intent_evidence_report";
13
+ export const SETTLEMENT_ASSET_GROUP_PARITY_QUANTITY_KIND = "settlement_asset_group_parity_snapshot";
14
+ export const SUI_USD_SETTLEMENT_ASSET_GROUP_ID = "SUI_USD_SETTLEMENT_ASSETS";
15
+ export const MAX_WALLET_BALANCE_SCAN_PAGES = 20;
16
+ export const MAX_DEEPBOOK_USDC_PRICE_HISTORY_BARS = 1_008;
17
+ export const DEEPBOOK_USDC_PRICE_HISTORY_UNSUPPORTED_CLAIMS = [
18
+ "fiat_usd_cash_out",
19
+ "usd_peg_assumption",
20
+ "global_market_price",
21
+ "historical_mid_price",
22
+ "live_quote",
23
+ "route_recommendation",
24
+ "best_route",
25
+ "transaction_building",
26
+ "signing_readiness",
27
+ "profit_or_pnl",
28
+ "cost_basis",
29
+ "independent_chain_recomputation"
30
+ ];
31
+ export const DEEPBOOK_USDC_CANDLE_AVAILABILITY = [
32
+ "available",
33
+ "no_candles_in_range"
34
+ ];
35
+ export const DEEPBOOK_USDC_PRICE_HISTORY_UNSUPPORTED_PAIR_REASONS = [
36
+ "selector_not_in_official_indexer",
37
+ "selector_resolves_to_multiple_usdc_pools"
38
+ ];
39
+ export const DEEPBOOK_USDC_PRICE_HISTORY_SOURCE_UNAVAILABLE_REASONS = [
40
+ "official_indexer_not_configured",
41
+ "pool_list_unavailable",
42
+ "candle_fetch_failed",
43
+ "official_indexer_invalid_payload"
44
+ ];
45
+ // Internal placeholder for sender-independent DeepBook market reads. The pinned Sui gRPC
46
+ // resolver uses the same dummy sender when no transaction sender is provided.
47
+ export const DEFAULT_DEEPBOOK_SIMULATION_SENDER = "0x0000000000000000000000000000000000000000000000000000000000000000";
48
+ export const NOT_INSPECTED_ASSET_CLASSES = [
49
+ {
50
+ assetClass: "staked_or_locked_asset",
51
+ reason: "requires_separate_stake_read_not_inspected"
52
+ },
53
+ {
54
+ assetClass: "deepbook_balance_manager_or_open_order",
55
+ reason: "requires_separate_deepbook_account_read_not_inspected"
56
+ },
57
+ {
58
+ assetClass: "lp_vault_or_position",
59
+ reason: "requires_separate_protocol_read_not_inspected"
60
+ },
61
+ {
62
+ assetClass: "nft_or_object_asset",
63
+ reason: "requires_separate_object_read_not_inspected"
64
+ }
65
+ ];
66
+ export const INTENT_EVIDENCE_TARGET_ASSET_SELECTION_SOURCES = [
67
+ "user_explicit",
68
+ "prior_user_explicit_context"
69
+ ];
70
+ export class ReadServiceInputError extends Error {
71
+ kind;
72
+ details;
73
+ constructor(kind, message, details) {
74
+ super(message);
75
+ this.kind = kind;
76
+ this.details = details;
77
+ }
78
+ }
79
+ export class ReadServiceCacheError extends Error {
80
+ kind = "metadata_cache_unavailable";
81
+ details;
82
+ cause;
83
+ constructor(operation, cause) {
84
+ super("Coin metadata cache is unavailable");
85
+ this.details = { resource: "coin_metadata_cache", operation };
86
+ this.cause = cause;
87
+ }
88
+ }
@@ -0,0 +1,82 @@
1
+ import { SETTLEMENT_ASSET_GROUP_PARITY_QUANTITY_KIND } from "./readServiceTypes.js";
2
+ export function settlementAssetGroupParityQuantitySemantics() {
3
+ return {
4
+ kind: SETTLEMENT_ASSET_GROUP_PARITY_QUANTITY_KIND,
5
+ allowedUse: "settlement_asset_group_internal_parity_evidence",
6
+ referenceAssetRole: "measurement_reference_not_settlement_choice",
7
+ priceSource: "deepbook_mid_price_snapshot",
8
+ fiatUsdCashOutAvailable: false,
9
+ externalMarketPriceConversionAvailable: false,
10
+ externalMarketLookupAvailable: false,
11
+ usdPegAssumptionAvailable: false,
12
+ settlementTokenSelectionAvailable: false,
13
+ paymentExecutionReadinessAvailable: false,
14
+ routeRecommendationAvailable: false,
15
+ profitAndLossAvailable: false,
16
+ costBasisAvailable: false,
17
+ notFor: [
18
+ "settlement_token_selection",
19
+ "fiat_usd_cash_out",
20
+ "external_market_price_conversion",
21
+ "external_market_lookup",
22
+ "usd_peg_assumption",
23
+ "bank_cash_out_estimate",
24
+ "payment_execution_readiness",
25
+ "route_recommendation",
26
+ "best_route",
27
+ "transaction_building",
28
+ "signing_readiness",
29
+ "profit_or_pnl",
30
+ "cost_basis"
31
+ ]
32
+ };
33
+ }
34
+ export function settlementAssetGroupParityStatistics(samples, unavailableAssetCount) {
35
+ const sorted = [...samples].sort((left, right) => left.priceInReferenceAsset - right.priceInReferenceAsset);
36
+ const min = sorted[0];
37
+ const max = sorted[sorted.length - 1];
38
+ const middle = Math.floor(sorted.length / 2);
39
+ const median = sorted.length % 2 === 1
40
+ ? sorted[middle].priceInReferenceAsset
41
+ : (sorted[middle - 1].priceInReferenceAsset + sorted[middle].priceInReferenceAsset) / 2;
42
+ const mean = samples.reduce((sum, sample) => sum + sample.priceInReferenceAsset, 0) / samples.length;
43
+ return {
44
+ status: "available",
45
+ sampleCount: samples.length,
46
+ unavailableAssetCount,
47
+ parityDirection: "reference_asset_per_group_asset",
48
+ calculation: "computed_from_available_direct_deepbook_mid_price_snapshots",
49
+ min: { symbol: min.symbol, priceInReferenceAsset: roundPriceInReferenceAsset(min.priceInReferenceAsset) },
50
+ max: { symbol: max.symbol, priceInReferenceAsset: roundPriceInReferenceAsset(max.priceInReferenceAsset) },
51
+ mean: { priceInReferenceAsset: roundPriceInReferenceAsset(mean) },
52
+ median: { priceInReferenceAsset: roundPriceInReferenceAsset(median) }
53
+ };
54
+ }
55
+ export function settlementAssetGroupParityResponseSummary(input) {
56
+ return {
57
+ questionKind: "settlement_asset_group_parity",
58
+ conclusionKind: "parity_statistics_available",
59
+ assetGroupId: input.assetGroupId,
60
+ referenceAssetSymbol: input.referenceAssetSymbol,
61
+ referenceAssetRole: "measurement_reference_not_settlement_choice",
62
+ parityDirection: input.statistics.parityDirection,
63
+ min: input.statistics.min,
64
+ max: input.statistics.max,
65
+ mean: input.statistics.mean,
66
+ median: input.statistics.median,
67
+ excludedFromConclusion: [
68
+ "settlement_token_selection",
69
+ "fiat_usd_cash_out",
70
+ "payment_execution_readiness",
71
+ "route_recommendation",
72
+ "best_route",
73
+ "transaction_building",
74
+ "signing_readiness",
75
+ "profit_or_pnl",
76
+ "cost_basis"
77
+ ]
78
+ };
79
+ }
80
+ export function roundPriceInReferenceAsset(price) {
81
+ return Number(price.toFixed(9));
82
+ }
@@ -0,0 +1,99 @@
1
+ import { DEEPBOOK_SCALAR_UNIT_SOURCE, DISPLAY_AMOUNT_SOURCE, SUI_METADATA_UNIT_SOURCE, formatRawAmount, normalizeCoinType } from "./coinMetadata.js";
2
+ import { findDeepbookCoinEntry } from "./deepbookRegistry.js";
3
+ import { WALLET_BALANCE_QUANTITY_KIND } from "./readServiceTypes.js";
4
+ export const SUI_COIN_TYPE = normalizeCoinType("0x2::sui::SUI");
5
+ export function walletBalanceQuantitySemantics() {
6
+ return {
7
+ kind: WALLET_BALANCE_QUANTITY_KIND,
8
+ allowedUse: "current_coin_balance_snapshot",
9
+ currentBalanceSnapshot: true,
10
+ transactionHistoryAvailable: false,
11
+ transactionReceiptProofAvailable: false,
12
+ transactionBalanceDeltaAvailable: false,
13
+ acquisitionSourceAvailable: false,
14
+ objectProvenanceAvailable: false,
15
+ fiatUsdCashOutAvailable: false,
16
+ profitAndLossAvailable: false,
17
+ costBasisAvailable: false,
18
+ notFor: [
19
+ "transaction_history",
20
+ "transaction_receipt_proof",
21
+ "specific_transaction_balance_delta",
22
+ "acquisition_source",
23
+ "object_provenance",
24
+ "fiat_usd_cash_out",
25
+ "profit_or_pnl",
26
+ "cost_basis",
27
+ "signing_data"
28
+ ]
29
+ };
30
+ }
31
+ export function classifyWalletBalance(balance, coins) {
32
+ const roles = [];
33
+ let normalizedCoinType;
34
+ try {
35
+ normalizedCoinType = normalizeCoinType(balance.coinType);
36
+ }
37
+ catch {
38
+ normalizedCoinType = undefined;
39
+ }
40
+ if (normalizedCoinType === SUI_COIN_TYPE) {
41
+ roles.push("gas_candidate");
42
+ }
43
+ if (normalizedCoinType && findDeepbookCoinEntry(normalizedCoinType, coins)) {
44
+ roles.push("deepbook_registered");
45
+ }
46
+ return {
47
+ balance,
48
+ classification: {
49
+ assetClass: "coin_balance",
50
+ balanceStatus: balance.balance === "0" ? "zero" : "nonzero",
51
+ roles
52
+ }
53
+ };
54
+ }
55
+ export function unitFromMetadataRecord(record, cacheStatus) {
56
+ return {
57
+ status: "available",
58
+ source: SUI_METADATA_UNIT_SOURCE,
59
+ decimals: record.decimals,
60
+ symbol: record.symbol,
61
+ name: record.name,
62
+ cacheStatus
63
+ };
64
+ }
65
+ export function unitFromDeepbook(unit) {
66
+ return {
67
+ status: "available",
68
+ source: DEEPBOOK_SCALAR_UNIT_SOURCE,
69
+ decimals: unit.decimals,
70
+ symbol: unit.symbol,
71
+ name: unit.name
72
+ };
73
+ }
74
+ export function withUnavailableUnit(balance, reason) {
75
+ return withResolvedUnit(balance, unavailableUnit(reason));
76
+ }
77
+ export function unavailableUnit(reason) {
78
+ return {
79
+ status: "unavailable",
80
+ reason
81
+ };
82
+ }
83
+ export function withResolvedUnit(balance, unit) {
84
+ if (unit.status === "unavailable") {
85
+ return {
86
+ ...balance,
87
+ unit
88
+ };
89
+ }
90
+ return {
91
+ ...balance,
92
+ unit,
93
+ display: {
94
+ amount: formatRawAmount(balance.balance, unit.decimals),
95
+ symbol: unit.symbol,
96
+ source: DISPLAY_AMOUNT_SOURCE
97
+ }
98
+ };
99
+ }
@@ -0,0 +1,45 @@
1
+ export function adapterNotImplementedCheck() {
2
+ return {
3
+ id: "adapter_not_implemented",
4
+ label: "Supported transaction type",
5
+ status: "fail",
6
+ message: "No installed protocol adapter can review this transaction type. Wallet approval is unavailable.",
7
+ source: "adapter"
8
+ };
9
+ }
10
+ export function accountBoundReviewRequiredCheck() {
11
+ return {
12
+ id: "account_bound_review_required",
13
+ label: "Review for your account",
14
+ status: "warning",
15
+ message: "This transaction has not been checked for a connected wallet account. Wallet approval and submission are unavailable.",
16
+ source: "adapter"
17
+ };
18
+ }
19
+ export function signingViaLocalReviewOnlyCheck() {
20
+ return {
21
+ id: "signing_via_local_review_only",
22
+ label: "Wallet signing",
23
+ status: "warning",
24
+ message: "Wallet signing requires completed review evidence, an explicit user action in the internal Review card and approval in the wallet; the backend verifies the returned transaction digest and signer. MCP responses never contain signing data, transaction bytes, or signing readiness.",
25
+ source: "adapter"
26
+ };
27
+ }
28
+ export function unsupportedDeepbookSwapPlanIdentityCheck() {
29
+ return {
30
+ id: "deepbook_swap_plan_identity_invalid",
31
+ label: "DeepBook plan identity",
32
+ status: "fail",
33
+ message: "The proposed transaction does not match the supported DeepBook swap format.",
34
+ source: "adapter"
35
+ };
36
+ }
37
+ export function externalProposalReviewOnlyCheck() {
38
+ return {
39
+ id: "external_proposal_review_only",
40
+ label: "View-only proposal",
41
+ status: "fail",
42
+ message: "This external proposal is for viewing only. It does not prepare a transaction, request a signature or allow wallet actions.",
43
+ source: "adapter"
44
+ };
45
+ }
@@ -0,0 +1,38 @@
1
+ import { rejectAdapterLifecycle } from "../action/adapterLifecycleValidation.js";
2
+ import { adapterNotImplementedCheck, externalProposalReviewOnlyCheck } from "./reviewChecks.js";
3
+ import { blockedReviewResult, mapReviewComputationResultToState } from "./reviewComputationResult.js";
4
+ export async function computeReviewState(input, deps = {}) {
5
+ const computed = await computeReviewStateWithPrivateArtifacts(input, deps);
6
+ return computed.state;
7
+ }
8
+ export async function computeReviewStateWithPrivateArtifacts(input, deps = {}) {
9
+ const computed = await computeReviewOutput(input, deps);
10
+ const state = mapReviewComputationResultToState({
11
+ reviewSessionId: input.reviewSessionId,
12
+ planId: input.plan.id,
13
+ account: input.account,
14
+ now: input.now
15
+ }, computed.result, deps.validateAdapterLifecycle ?? rejectAdapterLifecycle);
16
+ return {
17
+ state,
18
+ ...(computed.privateArtifacts ? { privateArtifacts: computed.privateArtifacts } : {})
19
+ };
20
+ }
21
+ export async function computeReviewResult(input, deps = {}) {
22
+ return (await computeReviewOutput(input, deps)).result;
23
+ }
24
+ async function computeReviewOutput(input, deps = {}) {
25
+ if (input.plan.adapterId === "external-proposal-review") {
26
+ return {
27
+ result: blockedReviewResult("proposal_review_only", input.plan.reviewModel?.blockingChecks ?? [externalProposalReviewOnlyCheck()])
28
+ };
29
+ }
30
+ const computeAdapterEvidence = deps.adapters?.[input.plan.adapterId];
31
+ if (computeAdapterEvidence) {
32
+ return computeAdapterEvidence(input);
33
+ }
34
+ return {
35
+ result: blockedReviewResult("adapter_not_implemented", [adapterNotImplementedCheck()])
36
+ };
37
+ }
38
+ export { adapterNotImplementedCheck, externalProposalReviewOnlyCheck };
@@ -0,0 +1,87 @@
1
+ import { parseLifecycleValidatedReviewState } from "../action/reviewStateValidation.js";
2
+ export function mapReviewComputationResultToState(identity, result, validateAdapterLifecycle) {
3
+ const base = {
4
+ planId: identity.planId,
5
+ reviewSessionId: identity.reviewSessionId,
6
+ account: identity.account,
7
+ checks: result.checks,
8
+ updatedAt: result.updatedAt ?? (identity.now ?? new Date()).toISOString(),
9
+ ...(result.assetFlowActual ? { assetFlowActual: result.assetFlowActual } : {}),
10
+ ...(result.beforeAfterBalance ? { beforeAfterBalance: result.beforeAfterBalance } : {}),
11
+ ...(result.simulation ? { simulation: result.simulation } : {}),
12
+ ...(result.humanReadableReview ? { humanReadableReview: result.humanReadableReview } : {}),
13
+ ...(result.transactionReviewData
14
+ ? { transactionReviewData: result.transactionReviewData }
15
+ : {}),
16
+ ...(result.ptbVisualization ? { ptbVisualization: result.ptbVisualization } : {}),
17
+ ...(result.adapterLifecycle ? { adapterLifecycle: result.adapterLifecycle } : {})
18
+ };
19
+ if (result.status === "ready_for_wallet_review") {
20
+ return parseMappedReviewState({
21
+ ...base,
22
+ status: "ready_for_wallet_review"
23
+ }, validateAdapterLifecycle);
24
+ }
25
+ if (result.status === "refresh_required") {
26
+ return parseMappedReviewState({
27
+ ...base,
28
+ status: "refresh_required",
29
+ refreshReason: result.refreshReason
30
+ }, validateAdapterLifecycle);
31
+ }
32
+ return parseMappedReviewState({
33
+ ...base,
34
+ status: "blocked",
35
+ blockedReason: result.blockedReason
36
+ }, validateAdapterLifecycle);
37
+ }
38
+ function parseMappedReviewState(state, validateAdapterLifecycle) {
39
+ return parseLifecycleValidatedReviewState(state, validateAdapterLifecycle);
40
+ }
41
+ export function blockedReviewResult(blockedReason, checks, fields = {}) {
42
+ return {
43
+ status: "blocked",
44
+ blockedReason,
45
+ checks,
46
+ ...fields
47
+ };
48
+ }
49
+ export function blockedAdapterLifecycleReviewResult(blockedReason, checks, adapterLifecycle) {
50
+ return blockedReviewResult(blockedReason, checks, { adapterLifecycle });
51
+ }
52
+ export function producerStageMissingReviewResult(checks, adapterLifecycle, fields = {}) {
53
+ return blockedReviewResult("producer_stage_missing", checks, {
54
+ adapterLifecycle,
55
+ ...fields
56
+ });
57
+ }
58
+ export function walletReviewContractEmitMissingResult(checks, adapterLifecycle, humanReadableReview, simulation) {
59
+ return blockedReviewResult("wallet_review_contract_emit_missing", checks, {
60
+ adapterLifecycle,
61
+ humanReadableReview,
62
+ simulation
63
+ });
64
+ }
65
+ export function walletReviewContractEmittedResult(checks, adapterLifecycle, humanReadableReview, simulation, transactionReviewData, ptbVisualization) {
66
+ return {
67
+ status: "ready_for_wallet_review",
68
+ checks,
69
+ adapterLifecycle,
70
+ humanReadableReview,
71
+ simulation,
72
+ transactionReviewData,
73
+ ...(ptbVisualization ? { ptbVisualization } : {})
74
+ };
75
+ }
76
+ export function refreshRequiredReviewResult(refreshReason, checks, fields = {}) {
77
+ return {
78
+ status: "refresh_required",
79
+ refreshReason,
80
+ checks,
81
+ ...fields
82
+ };
83
+ }
84
+ export function refreshRequiredAdapterLifecycleReviewResult(refreshReason, checks, adapterLifecycle) {
85
+ return refreshRequiredReviewResult(refreshReason, checks, { adapterLifecycle });
86
+ }
87
+ export { failReviewCheck, passReviewCheck } from "../action/reviewCheckResults.js";
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ export const walletDisplaySchema = z.object({
3
+ connectionId: z.string().min(1), pairingUri: z.string().startsWith("wc:"), expiresAt: z.string().datetime()
4
+ }).strict();
5
+ export const cardKindSchema = z.enum(["account", "receipt", "chart", "connect", "review"]);
6
+ export const cardStateSchema = z.object({
7
+ cardId: z.string().min(1), kind: cardKindSchema,
8
+ state: z.enum(["ready", "running", "closed"]),
9
+ revision: z.number().int().nonnegative(),
10
+ createdAt: z.string().datetime(), expiresAt: z.string().datetime(),
11
+ input: z.record(z.string(), z.unknown()),
12
+ data: z.unknown().optional(),
13
+ reason: z.enum(["completed", "expired", "failed", "server_restarted", "cancelled"]).optional(),
14
+ error: z.string().optional()
15
+ }).strict();
16
+ export const cardSnapshotSchema = cardStateSchema.extend({
17
+ pollAfterMs: z.number().int().positive(),
18
+ inputRemainingMs: z.number().int().nonnegative()
19
+ });
20
+ // Private display values from a receipt already read by the backend. These are
21
+ // individual on-chain inputs, never a serialized transaction or a signature.
22
+ export const receiptDisplaySchema = z.object({
23
+ // Sui syntax is validated by the backend producer/projection. The View checks
24
+ // equality with its selected digest and does not load an SDK to recompute it.
25
+ transactionDigest: z.string().min(1),
26
+ pureInputs: z.array(z.object({
27
+ index: z.number().int().nonnegative(),
28
+ bytes: z.string().regex(/^0x(?:[0-9a-f]{2})*$/i)
29
+ }).strict()).refine((items) => new Set(items.map((item) => item.index)).size === items.length, "Duplicate input index."),
30
+ ptbGraph: z.object({ mermaid: z.object({ text: z.string(), namedText: z.string() }).strict() }).strict().optional()
31
+ }).strict();
@@ -0,0 +1,204 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { parseCardInput } from "../read/readCardInputs.js";
3
+ import { projectReadCardResult } from "../read/readCardResult.js";
4
+ import { WALLET_CONNECTION_POLL_SECONDS, WalletUnavailableError } from "./walletConnection.js";
5
+ import { EXECUTION_POLLING_INTERVAL_SECONDS } from "./status.js";
6
+ import { createLocalSessionBase, DEFAULT_SESSION_TTL_MS, tokenMatchesHash } from "./localSession.js";
7
+ import { assertNoForbiddenMcpFields } from "../action/forbiddenFields.js";
8
+ import { cardSnapshotSchema } from "./cardSession.js";
9
+ import { WorkflowConflict } from "./sqliteWalletWorkflowStore.js";
10
+ import { parseWorkflowAction } from "./workflowView.js";
11
+ export class CardError extends Error {
12
+ }
13
+ export class CardStore {
14
+ options;
15
+ stopped = false;
16
+ constructor(options) {
17
+ this.options = options;
18
+ this.assertCurrent();
19
+ options.records.recover(options.ownerId, this.now());
20
+ }
21
+ now() { return this.options.now?.() ?? new Date(); }
22
+ assertCurrent() {
23
+ if (this.stopped)
24
+ throw new CardError("This card cannot be checked right now.");
25
+ this.options.assertCurrent?.();
26
+ }
27
+ async create(kind, input, execute = false) {
28
+ this.assertCurrent();
29
+ const business = kind === "connect" || kind === "review";
30
+ if (business && (!this.options.workflow || execute))
31
+ throw new CardError("This type of card is unavailable.");
32
+ const parsed = execute ? parseCardInput(kind, input) : undefined;
33
+ let preparation = { status: "ready" };
34
+ if (business) {
35
+ preparation = await this.options.workflow.prepare(kind, input);
36
+ this.assertCurrent();
37
+ }
38
+ else if (!execute && this.options.prepare) {
39
+ try {
40
+ preparation = await this.options.prepare(kind);
41
+ }
42
+ catch {
43
+ preparation = { status: "failed", error: "The choices for this card could not be loaded." };
44
+ }
45
+ // Preparation may finish after reset/import or shutdown. Never create a
46
+ // card in replacement data from that stale continuation.
47
+ this.assertCurrent();
48
+ }
49
+ assertNoForbiddenMcpFields({ input, preparation });
50
+ const { base, token } = createLocalSessionBase(this.now(), DEFAULT_SESSION_TTL_MS);
51
+ const record = { ownerId: this.options.ownerId, tokenHash: base.tokenHash,
52
+ scope: kind === "connect" ? "connect" : kind === "review" ? input.mode === "manage" ? "review_manage" : "review" : "read",
53
+ ...(kind === "review" && input.mode === "manage" && typeof input.attemptId === "string" ? { operationId: input.attemptId } : {}),
54
+ state: { cardId: base.id, kind, state: preparation.status === "failed" ? "closed" : "ready", revision: 0,
55
+ createdAt: base.createdAt, expiresAt: base.expiresAt, input,
56
+ ...(preparation.status === "failed" ? { reason: "failed", error: preparation.error } :
57
+ preparation.data === undefined ? {} : { data: preparation.data }) } };
58
+ this.options.records.create(record);
59
+ if (preparation.status === "failed")
60
+ this.reportFailure(record, "input_preparation");
61
+ const result = parsed === undefined ? await this.describe(record, false) : await this.submit({
62
+ cardId: base.id, permission: token, revision: 0, input: parsed
63
+ });
64
+ return { ...result, permission: token };
65
+ }
66
+ async read(input) { return this.describe(this.require(input), true); }
67
+ async readSaved(cardId) { return (await this.describe(this.current(cardId), false)).snapshot; }
68
+ async describe(record, uiObservation) {
69
+ if (record.state.kind !== "connect" && record.state.kind !== "review")
70
+ return this.response(record);
71
+ if (!this.options.workflow)
72
+ throw new CardError("Wallet actions are unavailable.");
73
+ const view = await this.options.workflow.describe(record, uiObservation);
74
+ const current = view.evaluated.record;
75
+ if (!current)
76
+ throw new CardError("Saved card data is unavailable.");
77
+ const snapshot = cardSnapshotSchema.parse({ ...current.state, data: view.data,
78
+ inputRemainingMs: view.evaluated.inputRemainingMs,
79
+ pollAfterMs: (current.state.kind === "review" ? EXECUTION_POLLING_INTERVAL_SECONDS : WALLET_CONNECTION_POLL_SECONDS) * 1000 });
80
+ assertNoForbiddenMcpFields(snapshot);
81
+ return { snapshot, ...(view.walletDisplay ? { walletDisplay: view.walletDisplay } : {}),
82
+ ...(view.receiptDisplay ? { receiptDisplay: view.receiptDisplay, displayAttemptId: view.displayAttemptId } : {}) };
83
+ }
84
+ async act(input) {
85
+ const record = this.require(input);
86
+ if ((record.state.kind !== "connect" && record.state.kind !== "review") || !this.options.workflow) {
87
+ return this.conflict(record, "This card does not accept wallet workflow actions.");
88
+ }
89
+ let parsed;
90
+ try {
91
+ parsed = parseWorkflowAction(input.input);
92
+ }
93
+ catch {
94
+ return { ...await this.describe(record, false), error: { code: "invalid_card_input", message: "The requested wallet action is not supported by this card." } };
95
+ }
96
+ if (record.acceptedInput !== undefined && isDeepStrictEqual(record.acceptedInput, parsed))
97
+ return this.describe(record, false);
98
+ if (record.state.revision !== input.revision)
99
+ return { ...await this.describe(record, false),
100
+ error: { code: "card_conflict", message: "This request does not match the card's current state." } };
101
+ try {
102
+ await this.options.workflow.act(record, parsed);
103
+ }
104
+ catch (error) {
105
+ if (!(error instanceof WorkflowConflict) && !(error instanceof WalletUnavailableError))
106
+ throw error;
107
+ return { ...await this.describe(this.current(input.cardId), false), error: {
108
+ code: error instanceof WalletUnavailableError ? "wallet_unavailable" : "card_conflict", message: error.message
109
+ } };
110
+ }
111
+ return this.describe(this.current(input.cardId), false);
112
+ }
113
+ async submit(input) {
114
+ const record = this.require(input);
115
+ if (record.state.kind === "connect" || record.state.kind === "review")
116
+ throw new CardError("This card does not accept data selections.");
117
+ let parsed;
118
+ try {
119
+ parsed = parseCardInput(record.state.kind, input.input);
120
+ }
121
+ catch {
122
+ return { ...this.response(record), error: { code: "invalid_card_input", message: "The selected information is not valid for this card." } };
123
+ }
124
+ if (record.acceptedInput !== undefined) {
125
+ if (isDeepStrictEqual(record.acceptedInput, parsed))
126
+ return this.response(record);
127
+ return this.conflict(record, "This card already accepted a different selection.");
128
+ }
129
+ if (record.state.state !== "ready" || record.ownerId !== this.options.ownerId || record.state.revision !== input.revision) {
130
+ return this.conflict(record, "This selection does not match the current card.");
131
+ }
132
+ const admitted = this.options.records.admit(record, parsed, this.options.ownerId, () => this.now());
133
+ if (!admitted)
134
+ return this.conflict(this.require(input), "The card changed or expired before this selection was accepted.");
135
+ let source;
136
+ try {
137
+ source = await this.options.execute(record.state.kind, parsed);
138
+ }
139
+ catch {
140
+ return this.fail(admitted, "read_service", "The requested data could not be read.");
141
+ }
142
+ this.assertCurrent();
143
+ let projected;
144
+ try {
145
+ projected = projectReadCardResult(record.state.kind, parsed, source);
146
+ }
147
+ catch {
148
+ return this.fail(admitted, "result_validation", "The returned data could not be prepared for this card.");
149
+ }
150
+ const complete = { ...admitted, ...("receiptDisplay" in projected ? { receiptDisplay: projected.receiptDisplay } : {}),
151
+ state: { ...admitted.state, data: projected.data, state: "closed", reason: "completed", revision: admitted.state.revision + 1 } };
152
+ // Do not report success or replay the source call if this commit fails.
153
+ try {
154
+ this.options.records.replace(admitted, complete);
155
+ }
156
+ catch {
157
+ return this.fail(admitted, "result_storage", "The result could not be saved. This request will not be repeated automatically.");
158
+ }
159
+ return this.response(this.current(record.state.cardId));
160
+ }
161
+ stop() { this.stopped = true; }
162
+ fail(record, stage, message) {
163
+ this.assertCurrent();
164
+ this.reportFailure(record, stage);
165
+ this.options.records.replace(record, { ...record, state: { ...record.state,
166
+ state: "closed", reason: "failed", error: message, revision: record.state.revision + 1 } });
167
+ return this.response(this.current(record.state.cardId));
168
+ }
169
+ reportFailure(record, stage) {
170
+ try {
171
+ this.options.logger?.error("Card read failed", { cardId: record.state.cardId, kind: record.state.kind, stage });
172
+ }
173
+ catch { /* Logging does not own the operation's state. */ }
174
+ }
175
+ conflict(record, message) {
176
+ return { ...this.response(record), error: { code: "card_conflict", message } };
177
+ }
178
+ current(id) {
179
+ this.assertCurrent();
180
+ const record = this.options.records.get(id);
181
+ if (!record)
182
+ throw new CardError("Saved card data is unavailable.");
183
+ return record;
184
+ }
185
+ require(input) {
186
+ this.assertCurrent();
187
+ const record = this.options.records.get(input.cardId);
188
+ if (!record || !tokenMatchesHash(record.tokenHash, input.permission))
189
+ throw new CardError("Card access is unavailable.");
190
+ return record;
191
+ }
192
+ snapshot(record, evaluatedAt) {
193
+ const snapshot = cardSnapshotSchema.parse({ ...record.state,
194
+ pollAfterMs: WALLET_CONNECTION_POLL_SECONDS * 1000,
195
+ inputRemainingMs: record.state.state === "ready" ? Math.max(0, Date.parse(record.state.expiresAt) - Date.parse(evaluatedAt)) : 0 });
196
+ assertNoForbiddenMcpFields(snapshot);
197
+ return snapshot;
198
+ }
199
+ response(record) {
200
+ const evaluated = this.options.records.evaluate(record, () => this.now());
201
+ record = evaluated.record;
202
+ return { snapshot: this.snapshot(record, evaluated.evaluatedAt), ...(record.receiptDisplay === undefined ? {} : { receiptDisplay: record.receiptDisplay }) };
203
+ }
204
+ }