@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,507 @@
|
|
|
1
|
+
import { deepbookSwapActionPlanDataSchema } from "./deepbookSwapIntent.js";
|
|
2
|
+
import { deriveDeepbookSwapQuotePolicy } from "./deepbookQuotePolicy.js";
|
|
3
|
+
import { deepbookSwapReviewLifecycleStageLabel, newDeepbookSwapReviewLifecycle } from "./deepbookReviewLifecycle.js";
|
|
4
|
+
import { createSwapQuotePolicyEvidence } from "../../core/action/swapQuotePolicyEvidence.js";
|
|
5
|
+
import { publicHumanReadableReviewFromEvidence } from "../../core/action/humanReadableReviewEvidence.js";
|
|
6
|
+
import { publicTransactionSimulationSummaryFromEvidence } from "../../core/action/reviewTimeSimulationEvidence.js";
|
|
7
|
+
import { ReadServiceInputError } from "../../core/read/readServiceTypes.js";
|
|
8
|
+
import { resolveDeepbookPoolForSymbols } from "../../core/read/deepbookRegistry.js";
|
|
9
|
+
import { blockedAdapterLifecycleReviewResult, failReviewCheck, passReviewCheck, producerStageMissingReviewResult, refreshRequiredAdapterLifecycleReviewResult, walletReviewContractEmitMissingResult, walletReviewContractEmittedResult } from "../../core/review/reviewComputationResult.js";
|
|
10
|
+
import { assembleTransactionReviewData } from "../../core/action/walletReviewContractAssembler.js";
|
|
11
|
+
export async function computeDeepbookSwapReviewEvidence(input) {
|
|
12
|
+
const lifecycle = newDeepbookSwapReviewLifecycle(input.plan);
|
|
13
|
+
const checks = [];
|
|
14
|
+
const now = input.now ?? new Date();
|
|
15
|
+
const intentStage = await runDeepbookReviewStage({
|
|
16
|
+
lifecycle,
|
|
17
|
+
checks,
|
|
18
|
+
stage: "intent_normalized",
|
|
19
|
+
run: () => normalizeIntentStage(input.plan)
|
|
20
|
+
});
|
|
21
|
+
if (!intentStage.ok) {
|
|
22
|
+
return { result: intentStage.result };
|
|
23
|
+
}
|
|
24
|
+
const requestedIntent = intentStage.evidence;
|
|
25
|
+
const poolStage = await runDeepbookReviewStage({
|
|
26
|
+
lifecycle,
|
|
27
|
+
checks,
|
|
28
|
+
stage: "pool_resolved",
|
|
29
|
+
run: () => resolvePoolStage(requestedIntent)
|
|
30
|
+
});
|
|
31
|
+
if (!poolStage.ok) {
|
|
32
|
+
return { result: poolStage.result };
|
|
33
|
+
}
|
|
34
|
+
const poolResolution = poolStage.evidence;
|
|
35
|
+
const quoteStage = await runDeepbookReviewStage({
|
|
36
|
+
lifecycle,
|
|
37
|
+
checks,
|
|
38
|
+
stage: "quote_evidence_fetched",
|
|
39
|
+
run: () => quoteEvidenceStage(input, requestedIntent, poolResolution)
|
|
40
|
+
});
|
|
41
|
+
if (!quoteStage.ok) {
|
|
42
|
+
return { result: quoteStage.result };
|
|
43
|
+
}
|
|
44
|
+
const { quote, feeMode } = quoteStage.evidence;
|
|
45
|
+
const policyStage = await runDeepbookReviewStage({
|
|
46
|
+
lifecycle,
|
|
47
|
+
checks,
|
|
48
|
+
stage: "quote_policy_derived",
|
|
49
|
+
run: () => quotePolicyStage(input, requestedIntent, quote, feeMode, now)
|
|
50
|
+
});
|
|
51
|
+
if (!policyStage.ok) {
|
|
52
|
+
return { result: policyStage.result };
|
|
53
|
+
}
|
|
54
|
+
const quotePolicy = policyStage.evidence;
|
|
55
|
+
if (!input.transactionMaterialProducer) {
|
|
56
|
+
return {
|
|
57
|
+
result: missingProducerStageBlockedResult(lifecycle.snapshot(), checks)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const materialStage = await runDeepbookReviewStage({
|
|
61
|
+
lifecycle,
|
|
62
|
+
checks,
|
|
63
|
+
stage: "transaction_material_build_or_verify",
|
|
64
|
+
run: () => input.transactionMaterialProducer({
|
|
65
|
+
reviewSessionId: input.reviewSessionId,
|
|
66
|
+
plan: input.plan,
|
|
67
|
+
account: input.account,
|
|
68
|
+
requestedIntent,
|
|
69
|
+
poolResolution,
|
|
70
|
+
quote,
|
|
71
|
+
quotePolicy,
|
|
72
|
+
now
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
if (!materialStage.ok) {
|
|
76
|
+
return { result: materialStage.result };
|
|
77
|
+
}
|
|
78
|
+
if (!input.transactionMaterialDigestProducer) {
|
|
79
|
+
return {
|
|
80
|
+
result: missingProducerStageBlockedResult(lifecycle.snapshot(), checks)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const digestStage = await runDeepbookReviewStage({
|
|
84
|
+
lifecycle,
|
|
85
|
+
checks,
|
|
86
|
+
stage: "digest_commitment",
|
|
87
|
+
run: () => input.transactionMaterialDigestProducer({
|
|
88
|
+
materialHandle: materialStage.evidence,
|
|
89
|
+
now
|
|
90
|
+
})
|
|
91
|
+
});
|
|
92
|
+
if (!digestStage.ok) {
|
|
93
|
+
return { result: digestStage.result };
|
|
94
|
+
}
|
|
95
|
+
let swapQuotePolicy;
|
|
96
|
+
try {
|
|
97
|
+
swapQuotePolicy = createSwapQuotePolicyEvidence({
|
|
98
|
+
materialHandle: materialStage.evidence,
|
|
99
|
+
adapterId: input.plan.adapterId,
|
|
100
|
+
protocol: input.plan.protocol,
|
|
101
|
+
actionKind: input.plan.actionKind,
|
|
102
|
+
quoteEvidenceId: `deepbook_raw_quote:${input.plan.id}`,
|
|
103
|
+
quoteSource: {
|
|
104
|
+
provider: input.plan.protocol,
|
|
105
|
+
poolKey: poolResolution.poolKey,
|
|
106
|
+
direction: quotePolicy.direction,
|
|
107
|
+
fetchedAt: quotePolicy.fetchedAt,
|
|
108
|
+
sourceMoveFunction: quote.rawQuote.sourceMoveFunction
|
|
109
|
+
},
|
|
110
|
+
maxSlippageBps: quotePolicy.maxSlippageBps,
|
|
111
|
+
staleAfterMs: quotePolicy.staleAfterMs,
|
|
112
|
+
sourceAmount: quotePolicyAmount(quote.rawQuote.inputAmount),
|
|
113
|
+
expectedOutput: quotePolicyAmount(quote.rawQuote.directionalOutput),
|
|
114
|
+
minimumOutput: {
|
|
115
|
+
raw: quotePolicy.minOutRaw,
|
|
116
|
+
asset: quotePolicyAmount(quote.rawQuote.directionalOutput).asset
|
|
117
|
+
},
|
|
118
|
+
protocolFee: quotePolicyAmount(quote.rawQuote.deepRequired),
|
|
119
|
+
// The pipeline anchor `now` is captured before the network quote fetch,
|
|
120
|
+
// so anchor derivedAt at the quote timestamp when the fetch lands later.
|
|
121
|
+
derivedAt: new Date(Math.max(now.getTime(), Date.parse(quotePolicy.fetchedAt)))
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
checks.push(failReviewCheck("deepbook_quote_policy_material_binding_failed", "Quote policy material binding", error instanceof Error ? error.message : "DeepBook quote policy evidence did not match the stored transaction material.", "quote"));
|
|
126
|
+
return {
|
|
127
|
+
result: blockedAdapterLifecycleReviewResult("amount_mismatch", checks, lifecycle.snapshot())
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const privateArtifacts = {
|
|
131
|
+
transactionMaterial: materialStage.evidence,
|
|
132
|
+
transactionMaterialDigest: digestStage.evidence,
|
|
133
|
+
swapQuotePolicy
|
|
134
|
+
};
|
|
135
|
+
// Render the PTB visualization as soon as material and digest exist, so the
|
|
136
|
+
// review page can draw the transaction even when a later stage stops the
|
|
137
|
+
// review. The artifact is commitment-bound at production and carries no
|
|
138
|
+
// digest or bytes.
|
|
139
|
+
let ptbVisualization;
|
|
140
|
+
if (input.ptbVisualizationProducer) {
|
|
141
|
+
const rendered = await input.ptbVisualizationProducer({
|
|
142
|
+
transactionMaterial: materialStage.evidence,
|
|
143
|
+
transactionMaterialDigest: digestStage.evidence,
|
|
144
|
+
adapterId: input.plan.adapterId,
|
|
145
|
+
planId: input.plan.id,
|
|
146
|
+
now
|
|
147
|
+
});
|
|
148
|
+
if (rendered.status === "rendered") {
|
|
149
|
+
ptbVisualization = rendered.artifact;
|
|
150
|
+
checks.push(ptbVisualizationRenderedCheck());
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
checks.push(ptbVisualizationUnavailableCheck(rendered.reason));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const withPtb = (result) => ptbVisualization ? { ...result, ptbVisualization } : result;
|
|
157
|
+
if (!input.transactionObjectOwnershipProducer) {
|
|
158
|
+
return {
|
|
159
|
+
result: withPtb(missingProducerStageBlockedResult(lifecycle.snapshot(), checks)),
|
|
160
|
+
privateArtifacts
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const ownershipStage = await runDeepbookReviewStage({
|
|
164
|
+
lifecycle,
|
|
165
|
+
checks,
|
|
166
|
+
stage: "object_ownership",
|
|
167
|
+
run: () => input.transactionObjectOwnershipProducer({
|
|
168
|
+
materialHandle: materialStage.evidence,
|
|
169
|
+
materialDigest: digestStage.evidence,
|
|
170
|
+
now
|
|
171
|
+
})
|
|
172
|
+
});
|
|
173
|
+
if (!ownershipStage.ok) {
|
|
174
|
+
return {
|
|
175
|
+
result: withPtb(ownershipStage.result),
|
|
176
|
+
...(ownershipStage.result.status === "refresh_required" ? {} : { privateArtifacts })
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
privateArtifacts.transactionObjectOwnership = ownershipStage.evidence;
|
|
180
|
+
if (!input.humanReadableReviewProducer) {
|
|
181
|
+
return {
|
|
182
|
+
result: withPtb(missingProducerStageBlockedResult(lifecycle.snapshot(), checks)),
|
|
183
|
+
privateArtifacts
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const humanReviewStage = await runDeepbookReviewStage({
|
|
187
|
+
lifecycle,
|
|
188
|
+
checks,
|
|
189
|
+
stage: "human_readable_review",
|
|
190
|
+
run: () => input.humanReadableReviewProducer({
|
|
191
|
+
plan: input.plan,
|
|
192
|
+
account: input.account,
|
|
193
|
+
requestedIntent,
|
|
194
|
+
poolResolution,
|
|
195
|
+
quotePolicy,
|
|
196
|
+
transactionMaterial: materialStage.evidence,
|
|
197
|
+
transactionMaterialDigest: digestStage.evidence,
|
|
198
|
+
swapQuotePolicy,
|
|
199
|
+
transactionObjectOwnership: ownershipStage.evidence,
|
|
200
|
+
now
|
|
201
|
+
})
|
|
202
|
+
});
|
|
203
|
+
if (!humanReviewStage.ok) {
|
|
204
|
+
return {
|
|
205
|
+
result: withPtb(humanReviewStage.result),
|
|
206
|
+
privateArtifacts
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
privateArtifacts.humanReadableReview = humanReviewStage.evidence;
|
|
210
|
+
if (!input.reviewTimeSimulationProducer) {
|
|
211
|
+
return {
|
|
212
|
+
result: withPtb(missingProducerStageBlockedResult(lifecycle.snapshot(), checks, {
|
|
213
|
+
humanReadableReview: publicHumanReadableReviewFromEvidence(humanReviewStage.evidence)
|
|
214
|
+
})),
|
|
215
|
+
privateArtifacts
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const simulationStage = await runDeepbookReviewStage({
|
|
219
|
+
lifecycle,
|
|
220
|
+
checks,
|
|
221
|
+
stage: "review_time_simulation",
|
|
222
|
+
run: () => input.reviewTimeSimulationProducer({
|
|
223
|
+
transactionMaterial: materialStage.evidence,
|
|
224
|
+
transactionMaterialDigest: digestStage.evidence,
|
|
225
|
+
now
|
|
226
|
+
})
|
|
227
|
+
});
|
|
228
|
+
if (!simulationStage.ok) {
|
|
229
|
+
return {
|
|
230
|
+
// Keep the public projection paired with the stored private evidence:
|
|
231
|
+
// the human-readable review stays visible while simulation asks for a
|
|
232
|
+
// refresh, and the store's projection binding stays satisfied.
|
|
233
|
+
result: withPtb({
|
|
234
|
+
...simulationStage.result,
|
|
235
|
+
humanReadableReview: publicHumanReadableReviewFromEvidence(humanReviewStage.evidence)
|
|
236
|
+
}),
|
|
237
|
+
privateArtifacts
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
privateArtifacts.reviewTimeSimulation = simulationStage.evidence;
|
|
241
|
+
const assembly = assembleTransactionReviewData({
|
|
242
|
+
adapterId: input.plan.adapterId,
|
|
243
|
+
protocol: input.plan.protocol,
|
|
244
|
+
actionKind: input.plan.actionKind,
|
|
245
|
+
provenance: {
|
|
246
|
+
kind: "mcp_action_request",
|
|
247
|
+
sourceId: input.plan.id,
|
|
248
|
+
capturedAt: input.plan.createdAt
|
|
249
|
+
},
|
|
250
|
+
quotePolicy: swapQuotePolicy,
|
|
251
|
+
objectOwnership: ownershipStage.evidence,
|
|
252
|
+
humanReadableReview: humanReviewStage.evidence,
|
|
253
|
+
reviewTimeSimulation: simulationStage.evidence,
|
|
254
|
+
reviewedTransactionDigest: digestStage.evidence.transactionDigest,
|
|
255
|
+
now
|
|
256
|
+
});
|
|
257
|
+
if (assembly.status === "emitted") {
|
|
258
|
+
checks.push(contractEmittedCheck());
|
|
259
|
+
return {
|
|
260
|
+
result: walletReviewContractEmittedResult(checks, lifecycle.snapshot(), publicHumanReadableReviewFromEvidence(humanReviewStage.evidence), publicTransactionSimulationSummaryFromEvidence(simulationStage.evidence), assembly.contract, ptbVisualization),
|
|
261
|
+
privateArtifacts
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
checks.push(contractEmitDeclinedCheck(assembly.reason));
|
|
265
|
+
return {
|
|
266
|
+
result: walletReviewContractEmitMissingResult(checks, lifecycle.snapshot(), publicHumanReadableReviewFromEvidence(humanReviewStage.evidence), publicTransactionSimulationSummaryFromEvidence(simulationStage.evidence)),
|
|
267
|
+
privateArtifacts
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
async function runDeepbookReviewStage(input) {
|
|
271
|
+
const outcome = await input.run();
|
|
272
|
+
input.checks.push(...outcome.checks);
|
|
273
|
+
if (outcome.status === "completed") {
|
|
274
|
+
input.lifecycle.complete(input.stage);
|
|
275
|
+
return { ok: true, evidence: outcome.evidence };
|
|
276
|
+
}
|
|
277
|
+
const adapterLifecycle = input.lifecycle.snapshot();
|
|
278
|
+
if (outcome.status === "blocked") {
|
|
279
|
+
return {
|
|
280
|
+
ok: false,
|
|
281
|
+
result: blockedAdapterLifecycleReviewResult(outcome.blockedReason, input.checks, adapterLifecycle)
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
ok: false,
|
|
286
|
+
result: refreshRequiredAdapterLifecycleReviewResult(outcome.refreshReason, input.checks, adapterLifecycle)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function normalizeIntentStage(plan) {
|
|
290
|
+
const parsedData = deepbookSwapActionPlanDataSchema.safeParse(plan.adapterData);
|
|
291
|
+
if (!parsedData.success) {
|
|
292
|
+
return {
|
|
293
|
+
status: "blocked",
|
|
294
|
+
blockedReason: "unsupported_action",
|
|
295
|
+
checks: [
|
|
296
|
+
failReviewCheck("deepbook_requested_intent_invalid", "Requested DeepBook intent", "The action plan does not contain a valid DeepBook swap display intent.", "adapter")
|
|
297
|
+
]
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const requestedIntent = parsedData.data.requestedIntent;
|
|
301
|
+
return {
|
|
302
|
+
status: "completed",
|
|
303
|
+
evidence: requestedIntent,
|
|
304
|
+
checks: [
|
|
305
|
+
passReviewCheck("deepbook_display_intent", "Display intent", `Requested display intent is ${requestedIntent.from.amountDisplay} ${requestedIntent.from.symbol} to ${requestedIntent.to.symbol} with max slippage ${requestedIntent.maxSlippageBps} bps. This display amount is not signing input.`, "adapter")
|
|
306
|
+
]
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function resolvePoolStage(requestedIntent) {
|
|
310
|
+
try {
|
|
311
|
+
const poolResolution = resolveDeepbookPoolForSymbols({
|
|
312
|
+
sourceSymbol: requestedIntent.from.symbol,
|
|
313
|
+
targetSymbol: requestedIntent.to.symbol
|
|
314
|
+
});
|
|
315
|
+
return {
|
|
316
|
+
status: "completed",
|
|
317
|
+
evidence: poolResolution,
|
|
318
|
+
checks: [
|
|
319
|
+
passReviewCheck("deepbook_pool_resolution", "DeepBook pool", `Resolved direct DeepBook pool ${poolResolution.poolKey} for ${poolResolution.sourceSymbol} to ${poolResolution.targetSymbol}; direction is ${poolResolution.direction}.`, "registry")
|
|
320
|
+
]
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
return {
|
|
325
|
+
status: "blocked",
|
|
326
|
+
blockedReason: "asset_mismatch",
|
|
327
|
+
checks: [
|
|
328
|
+
failReviewCheck("deepbook_pool_resolution_failed", "DeepBook pool", error instanceof Error ? error.message : "DeepBook pool resolution failed.", "registry")
|
|
329
|
+
]
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async function quoteEvidenceStage(input, requestedIntent, poolResolution) {
|
|
334
|
+
try {
|
|
335
|
+
const fetchQuote = (feeMode) => input.quoteSource.quoteDeepbookDisplayAmount({
|
|
336
|
+
poolKey: poolResolution.poolKey,
|
|
337
|
+
direction: poolResolution.direction,
|
|
338
|
+
amountDisplay: requestedIntent.from.amountDisplay,
|
|
339
|
+
simulationSender: input.account,
|
|
340
|
+
feeMode
|
|
341
|
+
});
|
|
342
|
+
let feeMode = "deep";
|
|
343
|
+
let feeModeMessage = "The trading fee is quoted in DEEP. A DEEP balance was not checked for this quote.";
|
|
344
|
+
let quote = await fetchQuote("deep");
|
|
345
|
+
if (input.deepBalanceSource) {
|
|
346
|
+
const deepBalanceRaw = BigInt(await input.deepBalanceSource(input.account));
|
|
347
|
+
const deepRequiredRaw = BigInt(quote.rawQuote.deepRequired.raw);
|
|
348
|
+
if (deepBalanceRaw >= deepRequiredRaw) {
|
|
349
|
+
feeModeMessage = `The trading fee is quoted in DEEP: the checked DEEP balance (${deepBalanceRaw} raw units) covers the quoted fee (${deepRequiredRaw} raw units).`;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
feeMode = "input_coin";
|
|
353
|
+
quote = await fetchQuote("input_coin");
|
|
354
|
+
feeModeMessage = `The trading fee is quoted in the token being sent because the checked DEEP balance (${deepBalanceRaw} raw units) is below the required fee (${deepRequiredRaw} raw units). The quote includes the protocol fee penalty for this option; the receive amount already reflects it.`;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
status: "completed",
|
|
359
|
+
evidence: { quote, feeMode },
|
|
360
|
+
checks: [
|
|
361
|
+
passReviewCheck("deepbook_raw_quote_evidence", "Quote amounts (raw units)", `DeepBook quote retrieved at ${quote.fetchedAt} from ${quote.rawQuote.sourceMoveFunction}: expected receive amount before slippage ${quote.rawQuote.directionalOutput.raw} ${quote.rawQuote.directionalOutput.symbol} raw units; quoted DEEP fee ${quote.rawQuote.deepRequired.raw} raw units.`, "quote"),
|
|
362
|
+
passReviewCheck("deepbook_fee_mode", "Trading fee token", feeModeMessage, "quote")
|
|
363
|
+
]
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
return quoteSourceFailureOutcome(error);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function quotePolicyStage(_input, requestedIntent, quote, feeMode, now) {
|
|
371
|
+
let policy;
|
|
372
|
+
try {
|
|
373
|
+
policy = deriveDeepbookSwapQuotePolicy({
|
|
374
|
+
rawQuote: quote.rawQuote,
|
|
375
|
+
fetchedAt: quote.fetchedAt,
|
|
376
|
+
maxSlippageBps: requestedIntent.maxSlippageBps,
|
|
377
|
+
feeMode,
|
|
378
|
+
now
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
return {
|
|
383
|
+
status: "blocked",
|
|
384
|
+
blockedReason: "amount_mismatch",
|
|
385
|
+
checks: [
|
|
386
|
+
failReviewCheck("deepbook_quote_policy_invalid", "Quote and slippage rules", error instanceof Error ? error.message : "DeepBook quote policy could not be derived.", "quote")
|
|
387
|
+
]
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
if (policy.status === "refresh_required") {
|
|
391
|
+
return {
|
|
392
|
+
status: "refresh_required",
|
|
393
|
+
refreshReason: policy.refreshReason,
|
|
394
|
+
checks: [
|
|
395
|
+
failReviewCheck("deepbook_quote_policy_refresh_required", "Quote and slippage rules", `The quote needs updating. Reported reason: “${policy.reason}”. Quote age: ${policy.quoteAgeMs} ms; validity limit: ${policy.staleAfterMs} ms.`, "quote")
|
|
396
|
+
]
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
status: "completed",
|
|
401
|
+
evidence: policy,
|
|
402
|
+
checks: [
|
|
403
|
+
passReviewCheck("deepbook_quote_policy", "Quote and slippage rules", `Quote fee option: ${policy.feeMode}. Amounts from the quote and slippage rules (raw units): send ${policy.sourceAmountRaw}, expected receive ${policy.expectedOutRaw}, minimum receive on success ${policy.minOutRaw}, DEEP fee ${policy.deepAmountRaw}; quote age: ${policy.quoteAgeMs} ms. These amounts are for review, not signing input or permission to sign.`, "quote")
|
|
404
|
+
]
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function quotePolicyAmount(amount) {
|
|
408
|
+
return {
|
|
409
|
+
raw: amount.raw,
|
|
410
|
+
asset: {
|
|
411
|
+
symbol: amount.symbol,
|
|
412
|
+
coinType: amount.coinType,
|
|
413
|
+
decimals: amount.decimals,
|
|
414
|
+
unitSource: amount.unitSource
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function quoteSourceFailureCheck(error) {
|
|
419
|
+
if (error instanceof ReadServiceInputError) {
|
|
420
|
+
return failReviewCheck("deepbook_quote_source_failed", "Quote source", error.message, "quote");
|
|
421
|
+
}
|
|
422
|
+
return failReviewCheck("deepbook_quote_source_failed", "Quote source", error instanceof Error ? error.message : "DeepBook quote source failed.", "quote");
|
|
423
|
+
}
|
|
424
|
+
function quoteSourceFailureOutcome(error) {
|
|
425
|
+
const checks = [quoteSourceFailureCheck(error)];
|
|
426
|
+
if (error instanceof ReadServiceInputError) {
|
|
427
|
+
if (error.kind === "input_invalid") {
|
|
428
|
+
return { status: "blocked", blockedReason: "amount_mismatch", checks };
|
|
429
|
+
}
|
|
430
|
+
if (error.kind === "registry_miss") {
|
|
431
|
+
return { status: "blocked", blockedReason: "asset_mismatch", checks };
|
|
432
|
+
}
|
|
433
|
+
if (error.kind === "quote_unavailable") {
|
|
434
|
+
return { status: "refresh_required", refreshReason: "quote_unavailable", checks };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return { status: "blocked", blockedReason: "object_resolution_failed", checks };
|
|
438
|
+
}
|
|
439
|
+
function missingProducerStageCheck(adapterLifecycle) {
|
|
440
|
+
const nextMissing = adapterLifecycle.missingStages[0];
|
|
441
|
+
if (nextMissing === undefined) {
|
|
442
|
+
return contractEmitMissingCheck();
|
|
443
|
+
}
|
|
444
|
+
const nextMissingLabel = nextMissing ? deepbookSwapReviewLifecycleStageLabel(nextMissing) : undefined;
|
|
445
|
+
return {
|
|
446
|
+
id: `deepbook_${nextMissing}_missing`,
|
|
447
|
+
label: nextMissingLabel ?? "Review check",
|
|
448
|
+
status: "fail",
|
|
449
|
+
message: `This account review has not completed the required check: ${nextMissingLabel}. Wallet approval and transaction submission are unavailable.`,
|
|
450
|
+
source: "adapter"
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function missingProducerStageBlockedResult(adapterLifecycle, checks, fields = {}) {
|
|
454
|
+
const nextMissing = adapterLifecycle.missingStages[0];
|
|
455
|
+
if (nextMissing === undefined) {
|
|
456
|
+
if (!fields.humanReadableReview || !fields.simulation) {
|
|
457
|
+
throw new Error("wallet_review_contract_emit_missing requires human-readable review and simulation public evidence");
|
|
458
|
+
}
|
|
459
|
+
return walletReviewContractEmitMissingResult([...checks, contractEmitMissingCheck()], adapterLifecycle, fields.humanReadableReview, fields.simulation);
|
|
460
|
+
}
|
|
461
|
+
return producerStageMissingReviewResult([...checks, missingProducerStageCheck(adapterLifecycle)], adapterLifecycle, fields.humanReadableReview ? { humanReadableReview: fields.humanReadableReview } : {});
|
|
462
|
+
}
|
|
463
|
+
function ptbVisualizationRenderedCheck() {
|
|
464
|
+
return {
|
|
465
|
+
id: "deepbook_ptb_visualization",
|
|
466
|
+
label: "PTB visualization",
|
|
467
|
+
status: "pass",
|
|
468
|
+
message: "The transaction diagram (PTB) was created from the transaction prepared for this review. It is for inspection, not wallet approval, signing input or proof that execution will succeed.",
|
|
469
|
+
source: "adapter"
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function ptbVisualizationUnavailableCheck(reason) {
|
|
473
|
+
return {
|
|
474
|
+
id: "deepbook_ptb_visualization_unavailable",
|
|
475
|
+
label: "PTB visualization",
|
|
476
|
+
status: "warning",
|
|
477
|
+
message: `The transaction diagram (PTB) is unavailable. Reported reason: “${reason}”. The other verified review details are still available; this diagram is optional.`,
|
|
478
|
+
source: "adapter"
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function contractEmittedCheck() {
|
|
482
|
+
return {
|
|
483
|
+
id: "deepbook_wallet_review_contract_emitted",
|
|
484
|
+
label: "Transaction approval details",
|
|
485
|
+
status: "pass",
|
|
486
|
+
message: "The approval details for this exact transaction were prepared from the verified review and checked for the required format. Only your request in a Review card that allows actions can start wallet approval. Submission also requires approval in your wallet; an ordinary AI tool response cannot authorize it.",
|
|
487
|
+
source: "adapter"
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function contractEmitDeclinedCheck(reason) {
|
|
491
|
+
return {
|
|
492
|
+
id: "deepbook_wallet_review_contract_emit_missing",
|
|
493
|
+
label: "Transaction approval details",
|
|
494
|
+
status: "fail",
|
|
495
|
+
message: `The approval details could not be prepared from this review. Reported reason: “${reason}”. Wallet approval is unavailable.`,
|
|
496
|
+
source: "adapter"
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function contractEmitMissingCheck() {
|
|
500
|
+
return {
|
|
501
|
+
id: "deepbook_wallet_review_contract_emit_missing",
|
|
502
|
+
label: "Transaction approval details",
|
|
503
|
+
status: "fail",
|
|
504
|
+
message: "Simulation completed, but the required transaction approval details could not be prepared. Wallet approval is unavailable for this review.",
|
|
505
|
+
source: "adapter"
|
|
506
|
+
};
|
|
507
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DEEPBOOK_SWAP_ACTION_KIND, DEEPBOOK_SWAP_ADAPTER_ID, DEEPBOOK_SWAP_PROTOCOL } from "./deepbookSwapIntent.js";
|
|
3
|
+
export const DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGE_CATALOG_ID = "deepbook_swap_review_v1";
|
|
4
|
+
export const DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGES = [
|
|
5
|
+
"intent_normalized",
|
|
6
|
+
"pool_resolved",
|
|
7
|
+
"quote_evidence_fetched",
|
|
8
|
+
"quote_policy_derived",
|
|
9
|
+
"transaction_material_build_or_verify",
|
|
10
|
+
"digest_commitment",
|
|
11
|
+
"object_ownership",
|
|
12
|
+
"human_readable_review",
|
|
13
|
+
"review_time_simulation"
|
|
14
|
+
];
|
|
15
|
+
export const deepbookSwapReviewLifecycleStageSchema = z.enum(DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGES);
|
|
16
|
+
export const deepbookSwapReviewLifecycleSchema = z.object({
|
|
17
|
+
stageCatalogId: z.literal(DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGE_CATALOG_ID),
|
|
18
|
+
adapterId: z.literal(DEEPBOOK_SWAP_ADAPTER_ID),
|
|
19
|
+
protocol: z.literal(DEEPBOOK_SWAP_PROTOCOL),
|
|
20
|
+
actionKind: z.literal(DEEPBOOK_SWAP_ACTION_KIND),
|
|
21
|
+
completedStages: z.array(deepbookSwapReviewLifecycleStageSchema),
|
|
22
|
+
missingStages: z.array(deepbookSwapReviewLifecycleStageSchema)
|
|
23
|
+
}).strict().superRefine((lifecycle, ctx) => {
|
|
24
|
+
const expectedCompleted = DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGES.slice(0, lifecycle.completedStages.length);
|
|
25
|
+
const expectedMissing = DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGES.slice(lifecycle.completedStages.length);
|
|
26
|
+
if (!sameStringArray(lifecycle.completedStages, expectedCompleted)) {
|
|
27
|
+
ctx.addIssue({
|
|
28
|
+
code: "custom",
|
|
29
|
+
path: ["completedStages"],
|
|
30
|
+
message: "completedStages must be the canonical DeepBook swap review lifecycle prefix"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (!sameStringArray(lifecycle.missingStages, expectedMissing)) {
|
|
34
|
+
ctx.addIssue({
|
|
35
|
+
code: "custom",
|
|
36
|
+
path: ["missingStages"],
|
|
37
|
+
message: "missingStages must be the canonical DeepBook swap review lifecycle remainder"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
const DEEPBOOK_SWAP_REVIEW_STAGE_LABELS = {
|
|
42
|
+
intent_normalized: "Intent normalized",
|
|
43
|
+
pool_resolved: "DeepBook pool resolved",
|
|
44
|
+
quote_evidence_fetched: "DeepBook quote evidence fetched",
|
|
45
|
+
quote_policy_derived: "Quote policy derived",
|
|
46
|
+
transaction_material_build_or_verify: "Transaction material build or verify",
|
|
47
|
+
digest_commitment: "Digest commitment",
|
|
48
|
+
object_ownership: "Object ownership",
|
|
49
|
+
human_readable_review: "Human-readable review",
|
|
50
|
+
review_time_simulation: "Review-time simulation"
|
|
51
|
+
};
|
|
52
|
+
export function newDeepbookSwapReviewLifecycle(plan) {
|
|
53
|
+
const completedStages = [];
|
|
54
|
+
return {
|
|
55
|
+
complete(stage) {
|
|
56
|
+
const expectedStage = DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGES[completedStages.length];
|
|
57
|
+
if (stage !== expectedStage) {
|
|
58
|
+
throw new Error(`DeepBook swap review lifecycle stage '${stage}' cannot complete before '${expectedStage ?? "none"}'.`);
|
|
59
|
+
}
|
|
60
|
+
completedStages.push(stage);
|
|
61
|
+
},
|
|
62
|
+
snapshot() {
|
|
63
|
+
return deepbookSwapReviewLifecycleSchema.parse({
|
|
64
|
+
stageCatalogId: DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGE_CATALOG_ID,
|
|
65
|
+
adapterId: plan.adapterId,
|
|
66
|
+
protocol: plan.protocol,
|
|
67
|
+
actionKind: plan.actionKind,
|
|
68
|
+
completedStages: [...completedStages],
|
|
69
|
+
missingStages: DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGES.filter((stage) => !completedStages.includes(stage))
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function deepbookSwapReviewLifecycleStageLabel(stage) {
|
|
75
|
+
return DEEPBOOK_SWAP_REVIEW_STAGE_LABELS[stage];
|
|
76
|
+
}
|
|
77
|
+
export function validateDeepbookSwapReviewLifecycle(lifecycle) {
|
|
78
|
+
deepbookSwapReviewLifecycleSchema.parse(lifecycle);
|
|
79
|
+
}
|
|
80
|
+
function sameStringArray(left, right) {
|
|
81
|
+
if (left.length !== right.length) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return left.every((value, index) => value === right[index]);
|
|
85
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { accountBoundReviewRequiredCheck, signingViaLocalReviewOnlyCheck } from "../../core/review/reviewChecks.js";
|
|
4
|
+
import { canonicalDeepbookSymbol } from "../../core/read/deepbookRegistry.js";
|
|
5
|
+
export const DEEPBOOK_SWAP_ACTION_KIND = "swap";
|
|
6
|
+
export const DEEPBOOK_SWAP_ADAPTER_ID = "deepbook-swap";
|
|
7
|
+
export const DEEPBOOK_SWAP_PROTOCOL = "DeepBookV3";
|
|
8
|
+
export const deepbookSwapIntentInputSchema = z.object({
|
|
9
|
+
type: z.literal(DEEPBOOK_SWAP_ACTION_KIND),
|
|
10
|
+
from: z.object({
|
|
11
|
+
symbol: z.string().min(1),
|
|
12
|
+
amount: z.string().min(1)
|
|
13
|
+
}),
|
|
14
|
+
to: z.object({
|
|
15
|
+
symbol: z.string().min(1)
|
|
16
|
+
}),
|
|
17
|
+
maxSlippageBps: z.number().int().min(1).max(1000)
|
|
18
|
+
});
|
|
19
|
+
export const deepbookSwapRequestedIntentSchema = z.object({
|
|
20
|
+
type: z.literal(DEEPBOOK_SWAP_ACTION_KIND),
|
|
21
|
+
from: z.object({
|
|
22
|
+
symbol: z.string().min(1),
|
|
23
|
+
amountDisplay: z.string().min(1)
|
|
24
|
+
}),
|
|
25
|
+
to: z.object({
|
|
26
|
+
symbol: z.string().min(1)
|
|
27
|
+
}),
|
|
28
|
+
maxSlippageBps: z.number().int().min(1).max(1000)
|
|
29
|
+
});
|
|
30
|
+
export function createDeepbookSwapActionPlan(intent, now) {
|
|
31
|
+
const requestedIntent = normalizeDeepbookSwapRequestedIntent(intent);
|
|
32
|
+
return {
|
|
33
|
+
id: `plan_${randomUUID()}`,
|
|
34
|
+
actionKind: DEEPBOOK_SWAP_ACTION_KIND,
|
|
35
|
+
adapterId: DEEPBOOK_SWAP_ADAPTER_ID,
|
|
36
|
+
protocol: DEEPBOOK_SWAP_PROTOCOL,
|
|
37
|
+
title: `Review ${requestedIntent.from.amountDisplay} ${requestedIntent.from.symbol} to ${requestedIntent.to.symbol}`,
|
|
38
|
+
summary: "Account-bound DeepBook review evidence is computed after a wallet account is connected. The internal Review card displays the proposal and verified review evidence; when every evidence stage completes, the Review card can request user-controlled wallet approval through the backend. This MCP response contains no sign action, signing data, transaction bytes, or signing readiness.",
|
|
39
|
+
assetFlowPreview: {
|
|
40
|
+
outgoing: [
|
|
41
|
+
{ symbol: requestedIntent.from.symbol, amount: requestedIntent.from.amountDisplay, amountKind: "display_intent" }
|
|
42
|
+
],
|
|
43
|
+
expectedIncoming: [
|
|
44
|
+
{ symbol: requestedIntent.to.symbol, amount: "unknown", amountKind: "display_intent", approx: true }
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
adapterData: {
|
|
48
|
+
requestedIntent
|
|
49
|
+
},
|
|
50
|
+
createdAt: now.toISOString(),
|
|
51
|
+
preliminaryChecks: [accountBoundReviewRequiredCheck(), signingViaLocalReviewOnlyCheck()]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export const deepbookSwapActionPlanDataSchema = z.object({
|
|
55
|
+
requestedIntent: deepbookSwapRequestedIntentSchema
|
|
56
|
+
});
|
|
57
|
+
export const deepbookSwapActionPlanIdentitySchema = z.object({
|
|
58
|
+
actionKind: z.literal(DEEPBOOK_SWAP_ACTION_KIND),
|
|
59
|
+
adapterId: z.literal(DEEPBOOK_SWAP_ADAPTER_ID),
|
|
60
|
+
protocol: z.literal(DEEPBOOK_SWAP_PROTOCOL)
|
|
61
|
+
}).loose();
|
|
62
|
+
export function isDeepbookSwapActionPlanIdentity(plan) {
|
|
63
|
+
return deepbookSwapActionPlanIdentitySchema.safeParse(plan).success;
|
|
64
|
+
}
|
|
65
|
+
export function normalizeDeepbookSwapRequestedIntent(input) {
|
|
66
|
+
const fromSymbol = canonicalDeepbookSymbol(input.from.symbol) ?? input.from.symbol.trim();
|
|
67
|
+
const toSymbol = canonicalDeepbookSymbol(input.to.symbol) ?? input.to.symbol.trim();
|
|
68
|
+
return {
|
|
69
|
+
type: DEEPBOOK_SWAP_ACTION_KIND,
|
|
70
|
+
from: {
|
|
71
|
+
symbol: fromSymbol,
|
|
72
|
+
amountDisplay: input.from.amount
|
|
73
|
+
},
|
|
74
|
+
to: {
|
|
75
|
+
symbol: toSymbol
|
|
76
|
+
},
|
|
77
|
+
maxSlippageBps: input.maxSlippageBps
|
|
78
|
+
};
|
|
79
|
+
}
|