@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.
- package/LICENSE +21 -0
- package/LICENSES/@mysten-bcs-2.0.5-Apache-2.0.txt +205 -0
- package/LICENSES/@mysten-sui-2.17.0-Apache-2.0.txt +201 -0
- package/LICENSES/@mysten-utils-0.3.3-Apache-2.0.txt +205 -0
- package/LICENSES/fancy-canvas-2.1.0-MIT.txt +7 -0
- package/LICENSES/lightweight-charts-5.2.0-NOTICE.txt +2 -0
- package/README.md +288 -0
- package/dist/adapters/adapterLifecycleValidators.js +5 -0
- package/dist/adapters/adapterPromptSurfaces.js +60 -0
- package/dist/adapters/deepbook/deepbookHumanReviewProducer.js +178 -0
- package/dist/adapters/deepbook/deepbookQuotePolicy.js +112 -0
- package/dist/adapters/deepbook/deepbookReviewEvidence.js +507 -0
- package/dist/adapters/deepbook/deepbookReviewLifecycle.js +85 -0
- package/dist/adapters/deepbook/deepbookSwapIntent.js +79 -0
- package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +254 -0
- package/dist/adapters/intentPlanFactories.js +51 -0
- package/dist/adapters/reviewAdapters.js +47 -0
- package/dist/core/action/adapterLifecycleValidation.js +12 -0
- package/dist/core/action/contractNameRegistry.js +151 -0
- package/dist/core/action/forbiddenFields.js +43 -0
- package/dist/core/action/humanReadableReviewEvidence.js +193 -0
- package/dist/core/action/humanReadableReviewProjectionVerifier.js +29 -0
- package/dist/core/action/ptbVisualizationProducer.js +71 -0
- package/dist/core/action/receiptPtbGraph.js +29 -0
- package/dist/core/action/reviewCheckResults.js +6 -0
- package/dist/core/action/reviewStateValidation.js +11 -0
- package/dist/core/action/reviewTimeSimulationEvidence.js +470 -0
- package/dist/core/action/schemas.js +491 -0
- package/dist/core/action/signableAdapterContract.js +998 -0
- package/dist/core/action/suiChainReceiptEvidence.js +106 -0
- package/dist/core/action/suiChainReceiptMapping.js +107 -0
- package/dist/core/action/suiChainReceiptReader.js +211 -0
- package/dist/core/action/suiChainReceiptVerifier.js +178 -0
- package/dist/core/action/swapHumanReadableReviewProjection.js +130 -0
- package/dist/core/action/swapQuotePolicyEvidence.js +271 -0
- package/dist/core/action/transactionFunding.js +244 -0
- package/dist/core/action/transactionObjectOwnershipEvidence.js +256 -0
- package/dist/core/action/transactionObjectOwnershipProducer.js +323 -0
- package/dist/core/action/types.js +33 -0
- package/dist/core/action/walletReviewContractAssembler.js +286 -0
- package/dist/core/activity/accountAssetTimeline.js +157 -0
- package/dist/core/activity/accountAssetTimelineUsdcReferences.js +120 -0
- package/dist/core/activity/activityStore.js +16 -0
- package/dist/core/activity/externalActivityCoverage.js +76 -0
- package/dist/core/activity/externalActivityTransactionStream.js +82 -0
- package/dist/core/activity/localDataService.js +296 -0
- package/dist/core/activity/localDataTypes.js +11 -0
- package/dist/core/activity/localDataValidation.js +439 -0
- package/dist/core/activity/schemaVersion.js +2 -0
- package/dist/core/activity/sqliteActivityStore.js +1068 -0
- package/dist/core/activity/sqliteActivityStoreRows.js +389 -0
- package/dist/core/activity/sqliteActivityStoreSchema.js +367 -0
- package/dist/core/activity/sqliteActivityStoreTypes.js +5 -0
- package/dist/core/activity/suiFunctionTarget.js +43 -0
- package/dist/core/activity/transactionActivityAccountEffects.js +186 -0
- package/dist/core/activity/transactionActivityAnalysis.js +295 -0
- package/dist/core/activity/transactionActivityClassifier.js +306 -0
- package/dist/core/activity/transactionActivityDetails.js +229 -0
- package/dist/core/activity/transactionActivityProtocolRules.js +218 -0
- package/dist/core/activity/transactionActivityScanPolicy.js +170 -0
- package/dist/core/activity/transactionActivityService.js +379 -0
- package/dist/core/activity/transactionActivityTypes.js +18 -0
- package/dist/core/eventlog/sink.js +35 -0
- package/dist/core/evidence/settlementFamilies.js +85 -0
- package/dist/core/evidence/userAnswerUse.js +1 -0
- package/dist/core/numeric/rawU64.js +63 -0
- package/dist/core/preferences/preferencesStore.js +26 -0
- package/dist/core/preferences/sqlitePreferencesRepository.js +136 -0
- package/dist/core/proposal/externalProposalReview.js +347 -0
- package/dist/core/proposal/schemas.js +208 -0
- package/dist/core/proposal/types.js +35 -0
- package/dist/core/read/amounts.js +14 -0
- package/dist/core/read/coinMetadata.js +60 -0
- package/dist/core/read/deepbookOfficialIndexerSource.js +373 -0
- package/dist/core/read/deepbookRawQuoteClient.js +86 -0
- package/dist/core/read/deepbookReadHelpers.js +321 -0
- package/dist/core/read/deepbookRegistry.js +157 -0
- package/dist/core/read/deepbookSourceOwners.js +159 -0
- package/dist/core/read/deepbookUsdcChartService.js +342 -0
- package/dist/core/read/intentEvidenceResponseFormatting.js +228 -0
- package/dist/core/read/readCardInputs.js +19 -0
- package/dist/core/read/readCardResult.js +40 -0
- package/dist/core/read/readResponseGuidance.js +402 -0
- package/dist/core/read/readService.js +1639 -0
- package/dist/core/read/readServiceTypes.js +88 -0
- package/dist/core/read/settlementParityFormatting.js +82 -0
- package/dist/core/read/walletReadHelpers.js +99 -0
- package/dist/core/review/reviewChecks.js +45 -0
- package/dist/core/review/reviewComputation.js +38 -0
- package/dist/core/review/reviewComputationResult.js +87 -0
- package/dist/core/session/cardSession.js +31 -0
- package/dist/core/session/cardSessionStore.js +204 -0
- package/dist/core/session/keyedRecordStore.js +18 -0
- package/dist/core/session/liveReviewSessionContract.js +3 -0
- package/dist/core/session/localSession.js +31 -0
- package/dist/core/session/privateReviewArtifacts.js +75 -0
- package/dist/core/session/reviewValidity.js +49 -0
- package/dist/core/session/sessionErrors.js +9 -0
- package/dist/core/session/sessionRecordStore.js +40 -0
- package/dist/core/session/sessionStore.js +655 -0
- package/dist/core/session/settingsSession.js +1 -0
- package/dist/core/session/settingsSessions.js +45 -0
- package/dist/core/session/sqliteCardStore.js +88 -0
- package/dist/core/session/sqliteSessionStore.js +213 -0
- package/dist/core/session/sqliteTransactionMaterialStore.js +64 -0
- package/dist/core/session/sqliteWalletWorkflowStore.js +821 -0
- package/dist/core/session/status.js +60 -0
- package/dist/core/session/transactionMaterialStore.js +221 -0
- package/dist/core/session/transactionRequest.js +72 -0
- package/dist/core/session/wait.js +79 -0
- package/dist/core/session/walletConnection.js +92 -0
- package/dist/core/session/walletRuntime.js +38 -0
- package/dist/core/session/walletWorkflow.js +608 -0
- package/dist/core/session/workflowState.js +80 -0
- package/dist/core/session/workflowView.js +150 -0
- package/dist/core/suiAddress.js +18 -0
- package/dist/core/suiEndpoint.js +72 -0
- package/dist/mcp/activeAccountResponse.js +24 -0
- package/dist/mcp/prompts.js +146 -0
- package/dist/mcp/registerTool.js +19 -0
- package/dist/mcp/resources.js +72 -0
- package/dist/mcp/responseGuidance.js +326 -0
- package/dist/mcp/result.js +17 -0
- package/dist/mcp/schemas.js +11 -0
- package/dist/mcp/server.js +32 -0
- package/dist/mcp/serverInfo.js +131 -0
- package/dist/mcp/toolErrors.js +118 -0
- package/dist/mcp/toolNames.js +58 -0
- package/dist/mcp/tools/account/index.js +44 -0
- package/dist/mcp/tools/action/prepareSuiActionReview.js +126 -0
- package/dist/mcp/tools/read/commonSchemas.js +43 -0
- package/dist/mcp/tools/read/deepbookReadTools.js +686 -0
- package/dist/mcp/tools/read/index.js +14 -0
- package/dist/mcp/tools/read/readToolHelpers.js +97 -0
- package/dist/mcp/tools/read/reviewActivityTools.js +179 -0
- package/dist/mcp/tools/read/serverStatusTools.js +100 -0
- package/dist/mcp/tools/read/transactionActivityOutput.js +371 -0
- package/dist/mcp/tools/read/transactionActivityTools.js +858 -0
- package/dist/mcp/tools/read/walletReadTools.js +734 -0
- package/dist/mcp/tools/session/executionResultTools.js +44 -0
- package/dist/mcp/tools/session/index.js +8 -0
- package/dist/mcp/tools/session/shared.js +36 -0
- package/dist/mcp/tools/session/statusTools.js +69 -0
- package/dist/mcp/tools/session/walletConnectionTools.js +69 -0
- package/dist/mcp/tools/settings/index.js +66 -0
- package/dist/mcp-app/account.html +3133 -0
- package/dist/mcp-app/account.notices.txt +973 -0
- package/dist/mcp-app/chart.html +3354 -0
- package/dist/mcp-app/chart.notices.txt +1194 -0
- package/dist/mcp-app/connect.html +3179 -0
- package/dist/mcp-app/connect.notices.txt +1012 -0
- package/dist/mcp-app/receipt.html +8430 -0
- package/dist/mcp-app/receipt.notices.txt +3549 -0
- package/dist/mcp-app/review.html +8430 -0
- package/dist/mcp-app/review.notices.txt +3549 -0
- package/dist/mcp-ui/contracts.js +31 -0
- package/dist/mcp-ui/readCards.js +28 -0
- package/dist/mcp-ui/tools.js +134 -0
- package/dist/review-app/brand-dark.svg +1 -0
- package/dist/review-app/brand-light.svg +1 -0
- package/dist/review-app/favicon.svg +1 -0
- package/dist/review-app/settings.css +1 -0
- package/dist/review-app/settings.js +1 -0
- package/dist/review-app/ui.css +809 -0
- package/dist/review-server/assets.js +48 -0
- package/dist/review-server/html.js +45 -0
- package/dist/review-server/http.js +47 -0
- package/dist/review-server/middleware/hostOrigin.js +48 -0
- package/dist/review-server/middleware/reviewToken.js +7 -0
- package/dist/review-server/reviewServerPolicy.js +1 -0
- package/dist/review-server/server.js +72 -0
- package/dist/review-server/settingsApi.js +158 -0
- package/dist/runtime/application.js +203 -0
- package/dist/runtime/config.js +97 -0
- package/dist/runtime/localSettingsService.js +198 -0
- package/dist/runtime/logger.js +66 -0
- package/dist/runtime/reviewDependencies.js +58 -0
- package/dist/runtime/reviewServerAcquire.js +93 -0
- package/dist/runtime/runtimeTermination.js +60 -0
- package/dist/runtime/shared/authenticatedFetch.js +127 -0
- package/dist/runtime/shared/control.js +93 -0
- package/dist/runtime/shared/dataAccess.js +66 -0
- package/dist/runtime/shared/mcpHttp.js +84 -0
- package/dist/runtime/shared/ownerLease.js +50 -0
- package/dist/runtime/shared/server.js +98 -0
- package/dist/runtime/shared/stdio.js +113 -0
- package/dist/runtime/smokeMainnetRead.js +391 -0
- package/dist/runtime/smokeMainnetReadAssertions.js +306 -0
- package/dist/runtime/smokeReview.js +40 -0
- package/dist/runtime/start.js +90 -0
- package/dist/runtime/suiEndpoint.js +97 -0
- package/dist/runtime/suiTransactionGraphqlMapping.js +200 -0
- package/dist/runtime/suiTransactionGraphqlQueries.js +231 -0
- package/dist/runtime/suiTransactionGraphqlSource.js +148 -0
- package/dist/runtime/walletConnectConfig.js +3 -0
- package/dist/runtime/walletConnectStorage.js +99 -0
- package/dist/runtime/walletConnectTransport.js +128 -0
- package/dist/runtime/walletSdkChild.js +3 -0
- package/dist/runtime/walletSdkChildRuntime.js +160 -0
- package/dist/runtime/walletSdkIpc.js +26 -0
- package/dist/runtime/walletSdkProcess.js +358 -0
- package/docs/AGENT_BEHAVIOR.md +524 -0
- package/docs/AGENT_DEVELOPMENT_POLICY.md +815 -0
- package/docs/FRONTEND_POLICY.md +534 -0
- package/docs/LOCAL_DB_ARCHITECTURE.md +381 -0
- package/docs/MCP_SETUP.md +742 -0
- package/docs/MCP_TOOLS.md +1318 -0
- package/docs/SDK_API.md +206 -0
- package/docs/SIGNABLE_ADAPTER_CONTRACT.md +396 -0
- package/docs/TRANSACTION_ACTIVITY_LOG.md +194 -0
- package/docs/UTILITY_INDEX.md +275 -0
- package/docs/WALLET_CONNECTION.md +238 -0
- package/docs/golden-scenarios/BEHAVIOR_MATRIX.md +166 -0
- package/docs/golden-scenarios/INTENT_EVIDENCE_GOLDEN_ANSWERS.md +161 -0
- package/docs/golden-scenarios/INTENT_EVIDENCE_MATRIX.md +259 -0
- package/package.json +79 -0
- package/protocols/deepbook-margin.md +15 -0
- 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
|
+
}
|