@stelis/say-ur-intent 0.0.0 → 0.0.2
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/README.md +4 -39
- package/dist/adapters/adapterLifecycleValidators.js +7 -0
- package/dist/adapters/adapterPromptSurfaces.js +71 -0
- package/dist/adapters/deepbook/deepbookHumanReviewProducer.js +175 -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 +269 -0
- package/dist/adapters/flowx/flowxSwapHumanReviewProducer.js +176 -0
- package/dist/adapters/flowx/flowxSwapIntent.js +79 -0
- package/dist/adapters/flowx/flowxSwapQuotePolicy.js +104 -0
- package/dist/adapters/flowx/flowxSwapReviewEvidence.js +468 -0
- package/dist/adapters/flowx/flowxSwapReviewLifecycle.js +85 -0
- package/dist/adapters/flowx/flowxSwapTransactionMaterialProducer.js +362 -0
- package/dist/adapters/intentPlanFactories.js +59 -0
- package/dist/adapters/reviewAdapters.js +81 -0
- package/dist/core/action/adapterLifecycleValidation.js +12 -0
- package/dist/core/action/forbiddenFields.js +43 -0
- package/dist/core/action/humanReadableReviewEvidence.js +203 -0
- package/dist/core/action/humanReadableReviewProjectionVerifier.js +29 -0
- package/dist/core/action/ptbVisualizationProducer.js +66 -0
- package/dist/core/action/reviewCheckResults.js +6 -0
- package/dist/core/action/reviewStateValidation.js +11 -0
- package/dist/core/action/reviewTimeSimulationEvidence.js +471 -0
- package/dist/core/action/schemas.js +529 -0
- package/dist/core/action/signableAdapterContract.js +993 -0
- package/dist/core/action/swapHumanReadableReviewProjection.js +124 -0
- package/dist/core/action/swapQuotePolicyEvidence.js +278 -0
- package/dist/core/action/transactionObjectOwnershipEvidence.js +247 -0
- package/dist/core/action/transactionObjectOwnershipProducer.js +329 -0
- package/dist/core/action/types.js +35 -0
- package/dist/core/action/walletReviewContractAssembler.js +282 -0
- package/dist/core/activity/activityStore.js +15 -0
- package/dist/core/activity/localDataService.js +258 -0
- package/dist/core/activity/localDataTypes.js +11 -0
- package/dist/core/activity/localDataValidation.js +396 -0
- package/dist/core/activity/schemaVersion.js +1 -0
- package/dist/core/activity/sqliteActivityStore.js +820 -0
- package/dist/core/activity/sqliteActivityStoreRows.js +430 -0
- package/dist/core/activity/sqliteActivityStoreSchema.js +258 -0
- package/dist/core/activity/sqliteActivityStoreTypes.js +5 -0
- package/dist/core/activity/suiFunctionTarget.js +43 -0
- package/dist/core/activity/transactionActivityAccountEffects.js +189 -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 +87 -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/deepbookRawQuoteClient.js +86 -0
- package/dist/core/read/deepbookReadHelpers.js +265 -0
- package/dist/core/read/deepbookRegistry.js +133 -0
- package/dist/core/read/flowxQuoteClient.js +117 -0
- package/dist/core/read/flowxReadHelpers.js +145 -0
- package/dist/core/read/flowxRegistry.js +174 -0
- package/dist/core/read/intentEvidenceResponseFormatting.js +228 -0
- package/dist/core/read/readResponseGuidance.js +451 -0
- package/dist/core/read/readService.js +1164 -0
- package/dist/core/read/readServiceTypes.js +59 -0
- package/dist/core/read/settlementParityFormatting.js +82 -0
- package/dist/core/read/walletReadHelpers.js +99 -0
- package/dist/core/review/reviewChecks.js +54 -0
- package/dist/core/review/reviewComputation.js +38 -0
- package/dist/core/review/reviewComputationResult.js +87 -0
- package/dist/core/session/localSession.js +31 -0
- package/dist/core/session/privateReviewArtifacts.js +73 -0
- package/dist/core/session/sessionErrors.js +9 -0
- package/dist/core/session/sessionStore.js +821 -0
- package/dist/core/session/settingsSession.js +1 -0
- package/dist/core/session/settingsSessions.js +43 -0
- package/dist/core/session/status.js +86 -0
- package/dist/core/session/transactionMaterialStore.js +205 -0
- package/dist/core/session/wait.js +102 -0
- package/dist/core/session/walletIdentity.js +103 -0
- package/dist/core/session/walletIdentitySessions.js +189 -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 +381 -0
- package/dist/mcp/result.js +17 -0
- package/dist/mcp/schemas.js +8 -0
- package/dist/mcp/server.js +30 -0
- package/dist/mcp/serverInfo.js +123 -0
- package/dist/mcp/toolErrors.js +105 -0
- package/dist/mcp/toolNames.js +50 -0
- package/dist/mcp/tools/account/index.js +44 -0
- package/dist/mcp/tools/action/prepareSuiActionReview.js +120 -0
- package/dist/mcp/tools/read/commonSchemas.js +43 -0
- package/dist/mcp/tools/read/deepbookReadTools.js +453 -0
- package/dist/mcp/tools/read/flowxReadTools.js +135 -0
- package/dist/mcp/tools/read/index.js +16 -0
- package/dist/mcp/tools/read/readToolHelpers.js +68 -0
- package/dist/mcp/tools/read/reviewActivityTools.js +176 -0
- package/dist/mcp/tools/read/serverStatusTools.js +103 -0
- package/dist/mcp/tools/read/transactionActivityOutput.js +300 -0
- package/dist/mcp/tools/read/transactionActivityTools.js +544 -0
- package/dist/mcp/tools/read/walletReadTools.js +733 -0
- package/dist/mcp/tools/session/executionResultTools.js +92 -0
- package/dist/mcp/tools/session/index.js +8 -0
- package/dist/mcp/tools/session/shared.js +79 -0
- package/dist/mcp/tools/session/statusTools.js +134 -0
- package/dist/mcp/tools/session/walletIdentityTools.js +119 -0
- package/dist/mcp/tools/settings/index.js +64 -0
- package/dist/review-app/analysis.css +1 -0
- package/dist/review-app/analysis.js +1 -0
- package/dist/review-app/arc-BjIacwQm.js +1 -0
- package/dist/review-app/architecture-U656AL7Q-aSB9x1OK.js +1 -0
- package/dist/review-app/architectureDiagram-VXUJARFQ-C5W6re2I.js +36 -0
- package/dist/review-app/array-BmXUUrU6.js +1 -0
- package/dist/review-app/blockDiagram-VD42YOAC-20MLNcUm.js +122 -0
- package/dist/review-app/c4Diagram-YG6GDRKO-BZXRrcck.js +10 -0
- package/dist/review-app/channel-lk2p_CUu.js +1 -0
- package/dist/review-app/chunk-4BX2VUAB-BPITOdjX.js +1 -0
- package/dist/review-app/chunk-55IACEB6-Dz-pyw5k.js +1 -0
- package/dist/review-app/chunk-76Q3JFCE-cK_X1P_l.js +1 -0
- package/dist/review-app/chunk-ABZYJK2D-Dt4W53JI.js +81 -0
- package/dist/review-app/chunk-ATLVNIR6-fZHLXURb.js +1 -0
- package/dist/review-app/chunk-B4BG7PRW-BbgcjusC.js +165 -0
- package/dist/review-app/chunk-BJD4TVEz.js +1 -0
- package/dist/review-app/chunk-CVBHYZKI-CViawAKX.js +1 -0
- package/dist/review-app/chunk-DI55MBZ5-C5aoul-d.js +220 -0
- package/dist/review-app/chunk-FMBD7UC4-Chxmw62A.js +15 -0
- package/dist/review-app/chunk-FPAJGGOC-DDHjQ09H.js +80 -0
- package/dist/review-app/chunk-FWNWRKHM-CVVQUptk.js +1 -0
- package/dist/review-app/chunk-HN2XXSSU-yzNpjaSZ.js +1 -0
- package/dist/review-app/chunk-JA3XYJ7Z-C5ZJdU01.js +70 -0
- package/dist/review-app/chunk-JZLCHNYA-BBST4Cnk.js +54 -0
- package/dist/review-app/chunk-LBM3YZW2-CdwAPuHr.js +1 -0
- package/dist/review-app/chunk-LHMN2FUI-BtB5uDcp.js +1 -0
- package/dist/review-app/chunk-O7ZBX7Z2-pxdK4Sa3.js +1 -0
- package/dist/review-app/chunk-QN33PNHL-CbVv3uGK.js +1 -0
- package/dist/review-app/chunk-QXUST7PY-DKM2-t2c.js +7 -0
- package/dist/review-app/chunk-QZHKN3VN-C5ni2pN_.js +1 -0
- package/dist/review-app/chunk-S3R3BYOJ-BWvOhDs0.js +2 -0
- package/dist/review-app/chunk-S6J4BHB3-D9Fk0YeD.js +1 -0
- package/dist/review-app/chunk-T53DSG4Q-C1qEyzyV.js +1 -0
- package/dist/review-app/chunk-TZMSLE5B-B--7eU69.js +1 -0
- package/dist/review-app/classDiagram-2ON5EDUG-DlL1m2bp.js +1 -0
- package/dist/review-app/classDiagram-v2-WZHVMYZB-FXRskT1j.js +1 -0
- package/dist/review-app/clone-BZZb7gpZ.js +1 -0
- package/dist/review-app/cose-bilkent-S5V4N54A-CRIb8XEO.js +1 -0
- package/dist/review-app/cytoscape.esm-C7jYqDP5.js +321 -0
- package/dist/review-app/dagre-6UL2VRFP-FNCAXbdE.js +4 -0
- package/dist/review-app/dagre-Be46QtUd.js +1 -0
- package/dist/review-app/defaultLocale-BaWNtAUL.js +1 -0
- package/dist/review-app/diagram-PSM6KHXK-ylLWjiNM.js +24 -0
- package/dist/review-app/diagram-QEK2KX5R-BCDcESxs.js +43 -0
- package/dist/review-app/diagram-S2PKOQOG-Vdrc-vrO.js +24 -0
- package/dist/review-app/dist-WPc74x_f.js +1 -0
- package/dist/review-app/erDiagram-Q2GNP2WA-E5ZsUbDF.js +60 -0
- package/dist/review-app/flatten-DHf9IeNI.js +1 -0
- package/dist/review-app/flowDiagram-NV44I4VS-DBSQuj6x.js +162 -0
- package/dist/review-app/ganttDiagram-LVOFAZNH-CKUOsqwl.js +267 -0
- package/dist/review-app/gitGraph-F6HP7TQM-DsAD6qK1.js +1 -0
- package/dist/review-app/gitGraphDiagram-NY62KEGX-BCeIMWdl.js +65 -0
- package/dist/review-app/graphlib-CiX5CXxR.js +1 -0
- package/dist/review-app/http-DMvwuuFk.js +1 -0
- package/dist/review-app/identity-DY8PXc6t.js +1 -0
- package/dist/review-app/info-NVLQJR56-Dlx1nZic.js +1 -0
- package/dist/review-app/infoDiagram-F6ZHWCRC-CAuANIrz.js +2 -0
- package/dist/review-app/init-BvqephKz.js +1 -0
- package/dist/review-app/journeyDiagram-XKPGCS4Q-C-Z9phnx.js +139 -0
- package/dist/review-app/kanban-definition-3W4ZIXB7-DufgZABq.js +89 -0
- package/dist/review-app/katex-B-Z-NXXN.js +257 -0
- package/dist/review-app/line-DiIv3Jgw.js +1 -0
- package/dist/review-app/linear-Cv-UPvo1.js +1 -0
- package/dist/review-app/math-kmyYrkHL.js +1 -0
- package/dist/review-app/mermaid-parser.core-DkwUYTPl.js +4 -0
- package/dist/review-app/mindmap-definition-VGOIOE7T-TM_CqdmV.js +68 -0
- package/dist/review-app/ordinal-BliTlkoG.js +1 -0
- package/dist/review-app/packet-BFZMPI3H-DqbnU92v.js +1 -0
- package/dist/review-app/path-AEo9W6mQ.js +1 -0
- package/dist/review-app/pie-7BOR55EZ-LJzaLkgr.js +1 -0
- package/dist/review-app/pieDiagram-ADFJNKIX-BAs8OfRS.js +30 -0
- package/dist/review-app/quadrantDiagram-AYHSOK5B-CyUDZP5S.js +7 -0
- package/dist/review-app/radar-NHE76QYJ-DBpHc8_Y.js +1 -0
- package/dist/review-app/reduce-B-HuPpdd.js +1 -0
- package/dist/review-app/requirementDiagram-UZGBJVZJ-BEHix78P.js +64 -0
- package/dist/review-app/review.css +1 -0
- package/dist/review-app/review.js +43 -0
- package/dist/review-app/sankeyDiagram-TZEHDZUN-B2bKbmsm.js +10 -0
- package/dist/review-app/sequenceDiagram-WL72ISMW-DVLOORFJ.js +145 -0
- package/dist/review-app/settings.css +1 -0
- package/dist/review-app/settings.js +1 -0
- package/dist/review-app/src-Buml7cM5.js +1 -0
- package/dist/review-app/stateDiagram-FKZM4ZOC-sFGGp2kV.js +1 -0
- package/dist/review-app/stateDiagram-v2-4FDKWEC3-BHfCF4dX.js +1 -0
- package/dist/review-app/timeline-definition-IT6M3QCI-BESnBijC.js +61 -0
- package/dist/review-app/treemap-KMMF4GRG-wnVLBDeQ.js +1 -0
- package/dist/review-app/walletStatus-CcojOdGy.js +7 -0
- package/dist/review-app/xychartDiagram-PRI3JC2R-BGWVfCx4.js +7 -0
- package/dist/review-server/assets.js +48 -0
- package/dist/review-server/html.js +66 -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 +10 -0
- package/dist/review-server/server.js +568 -0
- package/dist/review-server/settingsApi.js +182 -0
- package/dist/review-server/walletIdentityResponse.js +13 -0
- package/dist/runtime/config.js +103 -0
- package/dist/runtime/localSettingsService.js +198 -0
- package/dist/runtime/logger.js +50 -0
- package/dist/runtime/reviewServerAcquire.js +128 -0
- package/dist/runtime/smokeMainnetRead.js +529 -0
- package/dist/runtime/smokeMainnetReadAssertions.js +308 -0
- package/dist/runtime/start.js +295 -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/docs/AGENT_BEHAVIOR.md +1 -1
- package/docs/AGENT_DEVELOPMENT_POLICY.md +20 -0
- package/docs/FRONTEND_POLICY.md +4 -3
- package/docs/MCP_SETUP.md +59 -7
- package/docs/MCP_TOOLS.md +1 -1
- package/docs/SDK_API.md +5 -1
- package/package.json +3 -2
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { flowxSwapActionPlanDataSchema } from "./flowxSwapIntent.js";
|
|
2
|
+
import { deriveFlowxSwapQuotePolicy } from "./flowxSwapQuotePolicy.js";
|
|
3
|
+
import { flowxSwapReviewLifecycleStageLabel, newFlowxSwapReviewLifecycle } from "./flowxSwapReviewLifecycle.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 { parseQuoteDisplayAmount } from "../../core/read/deepbookReadHelpers.js";
|
|
8
|
+
import { validateFlowxRouteQuote } from "../../core/read/flowxReadHelpers.js";
|
|
9
|
+
import { FLOWX_CLMM_UNIT_SOURCE, resolveFlowxSwapPair } from "../../core/read/flowxRegistry.js";
|
|
10
|
+
import { ReadServiceInputError } from "../../core/read/readServiceTypes.js";
|
|
11
|
+
import { blockedAdapterLifecycleReviewResult, failReviewCheck, passReviewCheck, producerStageMissingReviewResult, refreshRequiredAdapterLifecycleReviewResult, walletReviewContractEmitMissingResult, walletReviewContractEmittedResult } from "../../core/review/reviewComputationResult.js";
|
|
12
|
+
import { assembleWalletReviewAdapterContract } from "../../core/action/walletReviewContractAssembler.js";
|
|
13
|
+
export async function computeFlowxSwapReviewEvidence(input) {
|
|
14
|
+
const lifecycle = newFlowxSwapReviewLifecycle(input.plan);
|
|
15
|
+
const checks = [];
|
|
16
|
+
const now = input.now ?? new Date();
|
|
17
|
+
const intentStage = await runFlowxReviewStage({
|
|
18
|
+
lifecycle,
|
|
19
|
+
checks,
|
|
20
|
+
stage: "intent_normalized",
|
|
21
|
+
run: () => normalizeIntentStage(input.plan)
|
|
22
|
+
});
|
|
23
|
+
if (!intentStage.ok) {
|
|
24
|
+
return { result: intentStage.result };
|
|
25
|
+
}
|
|
26
|
+
const requestedIntent = intentStage.evidence;
|
|
27
|
+
const pairStage = await runFlowxReviewStage({
|
|
28
|
+
lifecycle,
|
|
29
|
+
checks,
|
|
30
|
+
stage: "pair_resolved",
|
|
31
|
+
run: () => resolvePairStage(requestedIntent)
|
|
32
|
+
});
|
|
33
|
+
if (!pairStage.ok) {
|
|
34
|
+
return { result: pairStage.result };
|
|
35
|
+
}
|
|
36
|
+
const pairEvidence = pairStage.evidence;
|
|
37
|
+
const quoteStage = await runFlowxReviewStage({
|
|
38
|
+
lifecycle,
|
|
39
|
+
checks,
|
|
40
|
+
stage: "quote_evidence_fetched",
|
|
41
|
+
run: () => quoteEvidenceStage(input, requestedIntent, pairEvidence)
|
|
42
|
+
});
|
|
43
|
+
if (!quoteStage.ok) {
|
|
44
|
+
return { result: quoteStage.result };
|
|
45
|
+
}
|
|
46
|
+
const quoteEvidence = quoteStage.evidence;
|
|
47
|
+
const policyStage = await runFlowxReviewStage({
|
|
48
|
+
lifecycle,
|
|
49
|
+
checks,
|
|
50
|
+
stage: "quote_policy_derived",
|
|
51
|
+
run: () => quotePolicyStage(requestedIntent, quoteEvidence, now)
|
|
52
|
+
});
|
|
53
|
+
if (!policyStage.ok) {
|
|
54
|
+
return { result: policyStage.result };
|
|
55
|
+
}
|
|
56
|
+
const quotePolicy = policyStage.evidence;
|
|
57
|
+
if (!input.transactionMaterialProducer) {
|
|
58
|
+
return { result: missingProducerStageBlockedResult(lifecycle.snapshot(), checks) };
|
|
59
|
+
}
|
|
60
|
+
const materialStage = await runFlowxReviewStage({
|
|
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
|
+
pairEvidence,
|
|
70
|
+
quoteEvidence,
|
|
71
|
+
quotePolicy,
|
|
72
|
+
now
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
if (!materialStage.ok) {
|
|
76
|
+
return { result: materialStage.result };
|
|
77
|
+
}
|
|
78
|
+
if (!input.transactionMaterialDigestProducer) {
|
|
79
|
+
return { result: missingProducerStageBlockedResult(lifecycle.snapshot(), checks) };
|
|
80
|
+
}
|
|
81
|
+
const digestStage = await runFlowxReviewStage({
|
|
82
|
+
lifecycle,
|
|
83
|
+
checks,
|
|
84
|
+
stage: "digest_commitment",
|
|
85
|
+
run: () => input.transactionMaterialDigestProducer({ materialHandle: materialStage.evidence, now })
|
|
86
|
+
});
|
|
87
|
+
if (!digestStage.ok) {
|
|
88
|
+
return { result: digestStage.result };
|
|
89
|
+
}
|
|
90
|
+
const routedPool = quoteEvidence.pools[0];
|
|
91
|
+
if (!routedPool) {
|
|
92
|
+
checks.push(failReviewCheck("flowx_quote_policy_material_binding_failed", "Quote policy material binding", "FlowX route quote evidence lost its single routed pool before quote policy binding.", "quote"));
|
|
93
|
+
return { result: blockedAdapterLifecycleReviewResult("amount_mismatch", checks, lifecycle.snapshot()) };
|
|
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: `flowx_route_quote:${input.plan.id}`,
|
|
103
|
+
quoteSource: {
|
|
104
|
+
provider: input.plan.protocol,
|
|
105
|
+
poolKey: routedPool.poolKey,
|
|
106
|
+
direction: quotePolicy.swapXToY ? "base_to_quote" : "quote_to_base",
|
|
107
|
+
fetchedAt: quotePolicy.fetchedAt,
|
|
108
|
+
sourceMoveFunction: "universal_router::build"
|
|
109
|
+
},
|
|
110
|
+
maxSlippageBps: quotePolicy.maxSlippageBps,
|
|
111
|
+
staleAfterMs: quotePolicy.staleAfterMs,
|
|
112
|
+
sourceAmount: flowxQuotePolicyAmount(quotePolicy.sourceAmountRaw, pairEvidence.source),
|
|
113
|
+
expectedOutput: flowxQuotePolicyAmount(quotePolicy.expectedOutRaw, pairEvidence.target),
|
|
114
|
+
minimumOutput: flowxQuotePolicyAmount(quotePolicy.minOutRaw, pairEvidence.target),
|
|
115
|
+
// The FlowX CLMM swap fee is charged inside the pool at the pinned fee
|
|
116
|
+
// rate and is already reflected in the quoted output, so no separate
|
|
117
|
+
// fee asset leaves the account.
|
|
118
|
+
protocolFee: flowxQuotePolicyAmount("0", pairEvidence.source),
|
|
119
|
+
derivedAt: new Date(Math.max(now.getTime(), Date.parse(quotePolicy.fetchedAt)))
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
checks.push(failReviewCheck("flowx_quote_policy_material_binding_failed", "Quote policy material binding", error instanceof Error ? error.message : "FlowX quote policy evidence did not match the stored transaction material.", "quote"));
|
|
124
|
+
return { result: blockedAdapterLifecycleReviewResult("amount_mismatch", checks, lifecycle.snapshot()) };
|
|
125
|
+
}
|
|
126
|
+
const privateArtifacts = {
|
|
127
|
+
transactionMaterial: materialStage.evidence,
|
|
128
|
+
transactionMaterialDigest: digestStage.evidence,
|
|
129
|
+
swapQuotePolicy
|
|
130
|
+
};
|
|
131
|
+
let ptbVisualization;
|
|
132
|
+
if (input.ptbVisualizationProducer) {
|
|
133
|
+
const rendered = await input.ptbVisualizationProducer({
|
|
134
|
+
transactionMaterial: materialStage.evidence,
|
|
135
|
+
transactionMaterialDigest: digestStage.evidence,
|
|
136
|
+
adapterId: input.plan.adapterId,
|
|
137
|
+
planId: input.plan.id,
|
|
138
|
+
now
|
|
139
|
+
});
|
|
140
|
+
if (rendered.status === "rendered") {
|
|
141
|
+
ptbVisualization = rendered.artifact;
|
|
142
|
+
checks.push(passReviewCheck("flowx_ptb_visualization", "PTB visualization", "Rendered a Mermaid PTB visualization artifact from the stored local transaction material. Visualization only; it is not wallet authorization, not signing data, not signing readiness, and not execution readiness.", "adapter"));
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
checks.push({
|
|
146
|
+
id: "flowx_ptb_visualization_unavailable",
|
|
147
|
+
label: "PTB visualization",
|
|
148
|
+
status: "warning",
|
|
149
|
+
message: `PTB visualization is unavailable for this review: ${rendered.reason}. The emitted wallet review contract remains valid; visualization is optional review evidence.`,
|
|
150
|
+
source: "adapter"
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const withPtb = (result) => ptbVisualization ? { ...result, ptbVisualization } : result;
|
|
155
|
+
if (!input.transactionObjectOwnershipProducer) {
|
|
156
|
+
return {
|
|
157
|
+
result: withPtb(missingProducerStageBlockedResult(lifecycle.snapshot(), checks)),
|
|
158
|
+
privateArtifacts
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const ownershipStage = await runFlowxReviewStage({
|
|
162
|
+
lifecycle,
|
|
163
|
+
checks,
|
|
164
|
+
stage: "object_ownership",
|
|
165
|
+
run: () => input.transactionObjectOwnershipProducer({
|
|
166
|
+
materialHandle: materialStage.evidence,
|
|
167
|
+
materialDigest: digestStage.evidence,
|
|
168
|
+
now
|
|
169
|
+
})
|
|
170
|
+
});
|
|
171
|
+
if (!ownershipStage.ok) {
|
|
172
|
+
return {
|
|
173
|
+
result: withPtb(ownershipStage.result),
|
|
174
|
+
...(ownershipStage.result.status === "refresh_required" ? {} : { privateArtifacts })
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
privateArtifacts.transactionObjectOwnership = ownershipStage.evidence;
|
|
178
|
+
if (!input.humanReadableReviewProducer) {
|
|
179
|
+
return {
|
|
180
|
+
result: withPtb(missingProducerStageBlockedResult(lifecycle.snapshot(), checks)),
|
|
181
|
+
privateArtifacts
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const humanReviewStage = await runFlowxReviewStage({
|
|
185
|
+
lifecycle,
|
|
186
|
+
checks,
|
|
187
|
+
stage: "human_readable_review",
|
|
188
|
+
run: () => input.humanReadableReviewProducer({
|
|
189
|
+
plan: input.plan,
|
|
190
|
+
account: input.account,
|
|
191
|
+
requestedIntent,
|
|
192
|
+
pairEvidence,
|
|
193
|
+
routedPool,
|
|
194
|
+
quotePolicy,
|
|
195
|
+
transactionMaterial: materialStage.evidence,
|
|
196
|
+
transactionMaterialDigest: digestStage.evidence,
|
|
197
|
+
swapQuotePolicy,
|
|
198
|
+
transactionObjectOwnership: ownershipStage.evidence,
|
|
199
|
+
now
|
|
200
|
+
})
|
|
201
|
+
});
|
|
202
|
+
if (!humanReviewStage.ok) {
|
|
203
|
+
return { result: withPtb(humanReviewStage.result), privateArtifacts };
|
|
204
|
+
}
|
|
205
|
+
privateArtifacts.humanReadableReview = humanReviewStage.evidence;
|
|
206
|
+
if (!input.reviewTimeSimulationProducer) {
|
|
207
|
+
return {
|
|
208
|
+
result: withPtb(missingProducerStageBlockedResult(lifecycle.snapshot(), checks, {
|
|
209
|
+
humanReadableReview: publicHumanReadableReviewFromEvidence(humanReviewStage.evidence)
|
|
210
|
+
})),
|
|
211
|
+
privateArtifacts
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const simulationStage = await runFlowxReviewStage({
|
|
215
|
+
lifecycle,
|
|
216
|
+
checks,
|
|
217
|
+
stage: "review_time_simulation",
|
|
218
|
+
run: () => input.reviewTimeSimulationProducer({
|
|
219
|
+
transactionMaterial: materialStage.evidence,
|
|
220
|
+
transactionMaterialDigest: digestStage.evidence,
|
|
221
|
+
now
|
|
222
|
+
})
|
|
223
|
+
});
|
|
224
|
+
if (!simulationStage.ok) {
|
|
225
|
+
return {
|
|
226
|
+
result: withPtb({
|
|
227
|
+
...simulationStage.result,
|
|
228
|
+
humanReadableReview: publicHumanReadableReviewFromEvidence(humanReviewStage.evidence)
|
|
229
|
+
}),
|
|
230
|
+
privateArtifacts
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
privateArtifacts.reviewTimeSimulation = simulationStage.evidence;
|
|
234
|
+
const assembly = assembleWalletReviewAdapterContract({
|
|
235
|
+
adapterId: input.plan.adapterId,
|
|
236
|
+
protocol: input.plan.protocol,
|
|
237
|
+
actionKind: input.plan.actionKind,
|
|
238
|
+
provenance: {
|
|
239
|
+
kind: "mcp_action_request",
|
|
240
|
+
sourceId: input.plan.id,
|
|
241
|
+
capturedAt: input.plan.createdAt
|
|
242
|
+
},
|
|
243
|
+
quotePolicy: swapQuotePolicy,
|
|
244
|
+
objectOwnership: ownershipStage.evidence,
|
|
245
|
+
humanReadableReview: humanReviewStage.evidence,
|
|
246
|
+
reviewTimeSimulation: simulationStage.evidence,
|
|
247
|
+
transactionMaterialCommitment: digestStage.evidence.transactionDigest,
|
|
248
|
+
now
|
|
249
|
+
});
|
|
250
|
+
if (assembly.status === "emitted") {
|
|
251
|
+
checks.push(passReviewCheck("flowx_wallet_review_contract_emitted", "Wallet review contract emit", "FlowX account-bound review assembled and schema-validated a wallet review contract from verified review evidence. The local review page can now request the digest-gated handoff for user-controlled wallet signing; MCP output stays free of signing data.", "adapter"));
|
|
252
|
+
return {
|
|
253
|
+
result: walletReviewContractEmittedResult(checks, lifecycle.snapshot(), publicHumanReadableReviewFromEvidence(humanReviewStage.evidence), publicTransactionSimulationSummaryFromEvidence(simulationStage.evidence), assembly.contract, ptbVisualization),
|
|
254
|
+
privateArtifacts
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
checks.push(failReviewCheck("flowx_wallet_review_contract_emit_missing", "Wallet review contract emit", `FlowX account-bound review could not assemble the wallet review contract from the current review evidence: ${assembly.reason}. Signing stays blocked.`, "adapter"));
|
|
258
|
+
return {
|
|
259
|
+
result: walletReviewContractEmitMissingResult(checks, lifecycle.snapshot(), publicHumanReadableReviewFromEvidence(humanReviewStage.evidence), publicTransactionSimulationSummaryFromEvidence(simulationStage.evidence)),
|
|
260
|
+
privateArtifacts
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function runFlowxReviewStage(input) {
|
|
264
|
+
const outcome = await input.run();
|
|
265
|
+
input.checks.push(...outcome.checks);
|
|
266
|
+
if (outcome.status === "completed") {
|
|
267
|
+
input.lifecycle.complete(input.stage);
|
|
268
|
+
return { ok: true, evidence: outcome.evidence };
|
|
269
|
+
}
|
|
270
|
+
const adapterLifecycle = input.lifecycle.snapshot();
|
|
271
|
+
if (outcome.status === "blocked") {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
result: blockedAdapterLifecycleReviewResult(outcome.blockedReason, input.checks, adapterLifecycle)
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
ok: false,
|
|
279
|
+
result: refreshRequiredAdapterLifecycleReviewResult(outcome.refreshReason, input.checks, adapterLifecycle)
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function normalizeIntentStage(plan) {
|
|
283
|
+
const parsedData = flowxSwapActionPlanDataSchema.safeParse(plan.adapterData);
|
|
284
|
+
if (!parsedData.success) {
|
|
285
|
+
return {
|
|
286
|
+
status: "blocked",
|
|
287
|
+
blockedReason: "unsupported_action",
|
|
288
|
+
checks: [
|
|
289
|
+
failReviewCheck("flowx_requested_intent_invalid", "Requested FlowX intent", "The action plan does not contain a valid FlowX swap display intent.", "adapter")
|
|
290
|
+
]
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const requestedIntent = parsedData.data.requestedIntent;
|
|
294
|
+
return {
|
|
295
|
+
status: "completed",
|
|
296
|
+
evidence: requestedIntent,
|
|
297
|
+
checks: [
|
|
298
|
+
passReviewCheck("flowx_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")
|
|
299
|
+
]
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function resolvePairStage(requestedIntent) {
|
|
303
|
+
try {
|
|
304
|
+
const resolution = resolveFlowxSwapPair({
|
|
305
|
+
sourceSymbol: requestedIntent.from.symbol,
|
|
306
|
+
targetSymbol: requestedIntent.to.symbol
|
|
307
|
+
});
|
|
308
|
+
const evidence = {
|
|
309
|
+
source: resolution.source,
|
|
310
|
+
target: resolution.target,
|
|
311
|
+
swapXToY: resolution.swapXToY,
|
|
312
|
+
pinnedPoolCount: resolution.pools.length
|
|
313
|
+
};
|
|
314
|
+
return {
|
|
315
|
+
status: "completed",
|
|
316
|
+
evidence,
|
|
317
|
+
checks: [
|
|
318
|
+
passReviewCheck("flowx_pair_resolution", "FlowX pair", `Resolved pinned FlowX pair ${resolution.source.symbol} to ${resolution.target.symbol} (${resolution.pools.length} pinned fee-tier pools; direction ${resolution.swapXToY ? "x_to_y" : "y_to_x"}). The pool used by the swap is chosen by the FlowX router and verified against this pinned set at quote time.`, "registry")
|
|
319
|
+
]
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
return {
|
|
324
|
+
status: "blocked",
|
|
325
|
+
blockedReason: "asset_mismatch",
|
|
326
|
+
checks: [
|
|
327
|
+
failReviewCheck("flowx_pair_resolution_failed", "FlowX pair", error instanceof Error ? error.message : "FlowX pair resolution failed.", "registry")
|
|
328
|
+
]
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async function quoteEvidenceStage(input, requestedIntent, pairEvidence) {
|
|
333
|
+
try {
|
|
334
|
+
const amountInRaw = parseQuoteDisplayAmount(requestedIntent.from.amountDisplay, pairEvidence.source.decimals);
|
|
335
|
+
const fetched = await input.quoteSource.getSwapRoutesForBuild({
|
|
336
|
+
tokenInType: pairEvidence.source.coinType,
|
|
337
|
+
tokenOutType: pairEvidence.target.coinType,
|
|
338
|
+
amountInRaw
|
|
339
|
+
});
|
|
340
|
+
const { pools } = validateFlowxRouteQuote({
|
|
341
|
+
pair: {
|
|
342
|
+
source: pairEvidence.source,
|
|
343
|
+
target: pairEvidence.target,
|
|
344
|
+
swapXToY: pairEvidence.swapXToY,
|
|
345
|
+
pools: []
|
|
346
|
+
},
|
|
347
|
+
requestedAmountInRaw: amountInRaw,
|
|
348
|
+
quote: fetched.normalized
|
|
349
|
+
});
|
|
350
|
+
const evidence = {
|
|
351
|
+
amountInRaw,
|
|
352
|
+
amountOutRaw: fetched.normalized.amountOutRaw,
|
|
353
|
+
swapXToY: pairEvidence.swapXToY,
|
|
354
|
+
pools,
|
|
355
|
+
sdkRoutes: fetched.sdkRoutes,
|
|
356
|
+
fetchedAt: fetched.fetchedAt
|
|
357
|
+
};
|
|
358
|
+
return {
|
|
359
|
+
status: "completed",
|
|
360
|
+
evidence,
|
|
361
|
+
checks: [
|
|
362
|
+
passReviewCheck("flowx_route_quote_evidence", "Route quote evidence", `Fetched a FlowX route quote at ${fetched.fetchedAt}: expected output before slippage is ${evidence.amountOutRaw} ${pairEvidence.target.symbol} raw units through pinned pool ${pools.map((pool) => pool.poolKey).join(", ")}. The route was chosen by the FlowX router, not by this review; the review verified the routed pool, direction, fee, and protocol config against the pinned registry.`, "quote")
|
|
363
|
+
]
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
return quoteSourceFailureOutcome(error);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function quotePolicyStage(requestedIntent, quoteEvidence, now) {
|
|
371
|
+
let policy;
|
|
372
|
+
try {
|
|
373
|
+
policy = deriveFlowxSwapQuotePolicy({
|
|
374
|
+
amountInRaw: quoteEvidence.amountInRaw,
|
|
375
|
+
amountOutRaw: quoteEvidence.amountOutRaw,
|
|
376
|
+
swapXToY: quoteEvidence.swapXToY,
|
|
377
|
+
fetchedAt: quoteEvidence.fetchedAt,
|
|
378
|
+
maxSlippageBps: requestedIntent.maxSlippageBps,
|
|
379
|
+
now
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
return {
|
|
384
|
+
status: "blocked",
|
|
385
|
+
blockedReason: "amount_mismatch",
|
|
386
|
+
checks: [
|
|
387
|
+
failReviewCheck("flowx_quote_policy_invalid", "Quote policy", error instanceof Error ? error.message : "FlowX quote policy could not be derived.", "quote")
|
|
388
|
+
]
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (policy.status === "refresh_required") {
|
|
392
|
+
return {
|
|
393
|
+
status: "refresh_required",
|
|
394
|
+
refreshReason: policy.refreshReason,
|
|
395
|
+
checks: [
|
|
396
|
+
failReviewCheck("flowx_quote_policy_refresh_required", "Quote policy", `Quote policy requires refresh: ${policy.reason}. Quote age is ${policy.quoteAgeMs}ms with stale threshold ${policy.staleAfterMs}ms.`, "quote")
|
|
397
|
+
]
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
status: "completed",
|
|
402
|
+
evidence: policy,
|
|
403
|
+
checks: [
|
|
404
|
+
passReviewCheck("flowx_quote_policy", "Quote policy", `Derived review policy from route quote evidence: sourceAmountRaw ${policy.sourceAmountRaw}, expectedOutRaw ${policy.expectedOutRaw}, minOutRaw ${policy.minOutRaw} (router slippage ${policy.routerSlippageUnits} on the 1e6 scale), deadline ${new Date(policy.deadlineMsEpoch).toISOString()}, quoteAgeMs ${policy.quoteAgeMs}. The router enforces the minimum output on chain at settle; these values are review evidence only and are not transaction bytes, signing data, or signing readiness.`, "quote")
|
|
405
|
+
]
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function flowxQuotePolicyAmount(raw, asset) {
|
|
409
|
+
return {
|
|
410
|
+
raw,
|
|
411
|
+
asset: {
|
|
412
|
+
symbol: asset.symbol,
|
|
413
|
+
coinType: asset.coinType,
|
|
414
|
+
decimals: asset.decimals,
|
|
415
|
+
unitSource: FLOWX_CLMM_UNIT_SOURCE
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function quoteSourceFailureOutcome(error) {
|
|
420
|
+
const checks = [
|
|
421
|
+
failReviewCheck("flowx_quote_source_failed", "Quote source", error instanceof Error ? error.message : "FlowX quote source failed.", "quote")
|
|
422
|
+
];
|
|
423
|
+
if (error instanceof ReadServiceInputError) {
|
|
424
|
+
if (error.kind === "input_invalid") {
|
|
425
|
+
return { status: "blocked", blockedReason: "amount_mismatch", checks };
|
|
426
|
+
}
|
|
427
|
+
if (error.kind === "registry_miss") {
|
|
428
|
+
return { status: "blocked", blockedReason: "asset_mismatch", checks };
|
|
429
|
+
}
|
|
430
|
+
if (error.kind === "quote_unavailable") {
|
|
431
|
+
return { status: "refresh_required", refreshReason: "quote_unavailable", checks };
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return { status: "blocked", blockedReason: "object_resolution_failed", checks };
|
|
435
|
+
}
|
|
436
|
+
function missingProducerStageCheck(adapterLifecycle) {
|
|
437
|
+
const nextMissing = adapterLifecycle.missingStages[0];
|
|
438
|
+
if (nextMissing === undefined) {
|
|
439
|
+
return contractEmitMissingCheck();
|
|
440
|
+
}
|
|
441
|
+
const nextMissingLabel = flowxSwapReviewLifecycleStageLabel(nextMissing);
|
|
442
|
+
return {
|
|
443
|
+
id: `flowx_${nextMissing}_missing`,
|
|
444
|
+
label: nextMissingLabel,
|
|
445
|
+
status: "fail",
|
|
446
|
+
message: `FlowX account-bound review has not completed ${nextMissingLabel}. This is required before wallet handoff, signing, or execution, and no transaction bytes or signing readiness are available.`,
|
|
447
|
+
source: "adapter"
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function missingProducerStageBlockedResult(adapterLifecycle, checks, fields = {}) {
|
|
451
|
+
const nextMissing = adapterLifecycle.missingStages[0];
|
|
452
|
+
if (nextMissing === undefined) {
|
|
453
|
+
if (!fields.humanReadableReview || !fields.simulation) {
|
|
454
|
+
throw new Error("wallet_review_contract_emit_missing requires human-readable review and simulation public evidence");
|
|
455
|
+
}
|
|
456
|
+
return walletReviewContractEmitMissingResult([...checks, contractEmitMissingCheck()], adapterLifecycle, fields.humanReadableReview, fields.simulation);
|
|
457
|
+
}
|
|
458
|
+
return producerStageMissingReviewResult([...checks, missingProducerStageCheck(adapterLifecycle)], adapterLifecycle, fields.humanReadableReview ? { humanReadableReview: fields.humanReadableReview } : {});
|
|
459
|
+
}
|
|
460
|
+
function contractEmitMissingCheck() {
|
|
461
|
+
return {
|
|
462
|
+
id: "flowx_wallet_review_contract_emit_missing",
|
|
463
|
+
label: "Wallet review contract emit",
|
|
464
|
+
status: "fail",
|
|
465
|
+
message: "FlowX account-bound review completed review-time simulation, but this review did not assemble a wallet review contract, so signing stays blocked for this session.",
|
|
466
|
+
source: "adapter"
|
|
467
|
+
};
|
|
468
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { FLOWX_SWAP_ACTION_KIND, FLOWX_SWAP_ADAPTER_ID, FLOWX_SWAP_PROTOCOL } from "./flowxSwapIntent.js";
|
|
3
|
+
export const FLOWX_SWAP_REVIEW_LIFECYCLE_STAGE_CATALOG_ID = "flowx_swap_review_v1";
|
|
4
|
+
export const FLOWX_SWAP_REVIEW_LIFECYCLE_STAGES = [
|
|
5
|
+
"intent_normalized",
|
|
6
|
+
"pair_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 flowxSwapReviewLifecycleStageSchema = z.enum(FLOWX_SWAP_REVIEW_LIFECYCLE_STAGES);
|
|
16
|
+
export const flowxSwapReviewLifecycleSchema = z.object({
|
|
17
|
+
stageCatalogId: z.literal(FLOWX_SWAP_REVIEW_LIFECYCLE_STAGE_CATALOG_ID),
|
|
18
|
+
adapterId: z.literal(FLOWX_SWAP_ADAPTER_ID),
|
|
19
|
+
protocol: z.literal(FLOWX_SWAP_PROTOCOL),
|
|
20
|
+
actionKind: z.literal(FLOWX_SWAP_ACTION_KIND),
|
|
21
|
+
completedStages: z.array(flowxSwapReviewLifecycleStageSchema),
|
|
22
|
+
missingStages: z.array(flowxSwapReviewLifecycleStageSchema)
|
|
23
|
+
}).strict().superRefine((lifecycle, ctx) => {
|
|
24
|
+
const expectedCompleted = FLOWX_SWAP_REVIEW_LIFECYCLE_STAGES.slice(0, lifecycle.completedStages.length);
|
|
25
|
+
const expectedMissing = FLOWX_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 FlowX 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 FlowX swap review lifecycle remainder"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
const FLOWX_SWAP_REVIEW_STAGE_LABELS = {
|
|
42
|
+
intent_normalized: "Intent normalized",
|
|
43
|
+
pair_resolved: "FlowX pair resolved",
|
|
44
|
+
quote_evidence_fetched: "FlowX route quote 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 newFlowxSwapReviewLifecycle(plan) {
|
|
53
|
+
const completedStages = [];
|
|
54
|
+
return {
|
|
55
|
+
complete(stage) {
|
|
56
|
+
const expectedStage = FLOWX_SWAP_REVIEW_LIFECYCLE_STAGES[completedStages.length];
|
|
57
|
+
if (stage !== expectedStage) {
|
|
58
|
+
throw new Error(`FlowX swap review lifecycle stage '${stage}' cannot complete before '${expectedStage ?? "none"}'.`);
|
|
59
|
+
}
|
|
60
|
+
completedStages.push(stage);
|
|
61
|
+
},
|
|
62
|
+
snapshot() {
|
|
63
|
+
return flowxSwapReviewLifecycleSchema.parse({
|
|
64
|
+
stageCatalogId: FLOWX_SWAP_REVIEW_LIFECYCLE_STAGE_CATALOG_ID,
|
|
65
|
+
adapterId: plan.adapterId,
|
|
66
|
+
protocol: plan.protocol,
|
|
67
|
+
actionKind: plan.actionKind,
|
|
68
|
+
completedStages: [...completedStages],
|
|
69
|
+
missingStages: FLOWX_SWAP_REVIEW_LIFECYCLE_STAGES.filter((stage) => !completedStages.includes(stage))
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function flowxSwapReviewLifecycleStageLabel(stage) {
|
|
75
|
+
return FLOWX_SWAP_REVIEW_STAGE_LABELS[stage];
|
|
76
|
+
}
|
|
77
|
+
export function validateFlowxSwapReviewLifecycle(lifecycle) {
|
|
78
|
+
flowxSwapReviewLifecycleSchema.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
|
+
}
|