@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,362 @@
|
|
|
1
|
+
import { Trade } from "@flowx-finance/sdk";
|
|
2
|
+
import { bcs } from "@mysten/sui/bcs";
|
|
3
|
+
import { Transaction } from "@mysten/sui/transactions";
|
|
4
|
+
import { fromBase64, normalizeSuiObjectId } from "@mysten/sui/utils";
|
|
5
|
+
import { FLOWX_CLMM_MAINNET } from "../../core/read/flowxRegistry.js";
|
|
6
|
+
import { LocalTransactionMaterialStoreError } from "../../core/session/transactionMaterialStore.js";
|
|
7
|
+
import { suiTransactionDigestSchema } from "../../core/suiAddress.js";
|
|
8
|
+
import { failReviewCheck, passReviewCheck } from "../../core/review/reviewComputationResult.js";
|
|
9
|
+
const FLOWX_SWAP_GAS_BUDGET_MIST = 50000000n;
|
|
10
|
+
const SUI_COIN_TYPE = "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI";
|
|
11
|
+
const SUI_CLOCK_OBJECT_ID = "0x0000000000000000000000000000000000000000000000000000000000000006";
|
|
12
|
+
const MOVE_STDLIB_PACKAGE_ID = "0x0000000000000000000000000000000000000000000000000000000000000001";
|
|
13
|
+
const SUI_FRAMEWORK_PACKAGE_ID = "0x0000000000000000000000000000000000000000000000000000000000000002";
|
|
14
|
+
export function createFlowxSwapTransactionMaterialProducer(options) {
|
|
15
|
+
const buildBytes = options.buildSwapTransactionBytes ?? sdkTradeBytesBuilder(options.client);
|
|
16
|
+
return async (input) => {
|
|
17
|
+
if (options.network !== "mainnet" || options.chainIdentifier !== options.expectedChainIdentifier) {
|
|
18
|
+
return {
|
|
19
|
+
status: "blocked",
|
|
20
|
+
blockedReason: "network_mismatch",
|
|
21
|
+
checks: [
|
|
22
|
+
failReviewCheck("flowx_transaction_material_network_mismatch", "Transaction material network", "FlowX transaction material build requires a verified Sui mainnet gRPC endpoint and matching mainnet chain identifier.", "network")
|
|
23
|
+
]
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (input.quoteEvidence.swapXToY !== input.quotePolicy.swapXToY) {
|
|
27
|
+
return {
|
|
28
|
+
status: "blocked",
|
|
29
|
+
blockedReason: "amount_mismatch",
|
|
30
|
+
checks: [
|
|
31
|
+
failReviewCheck("flowx_transaction_material_direction_mismatch", "Transaction material quote policy", "FlowX transaction material build requires route quote evidence and quote policy to describe the same swap direction.", "adapter")
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const quoteExpiresAt = new Date(Date.parse(input.quotePolicy.fetchedAt) + input.quotePolicy.staleAfterMs);
|
|
36
|
+
if (quoteExpiresAt.getTime() <= input.now.getTime()) {
|
|
37
|
+
return {
|
|
38
|
+
status: "refresh_required",
|
|
39
|
+
refreshReason: "quote_stale",
|
|
40
|
+
checks: [
|
|
41
|
+
failReviewCheck("flowx_transaction_material_quote_expired", "Transaction material quote policy", "FlowX transaction material was not built because the quote policy expired before build.", "quote")
|
|
42
|
+
]
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const sourceAmountRaw = BigInt(input.quotePolicy.sourceAmountRaw);
|
|
47
|
+
const source = input.pairEvidence.source;
|
|
48
|
+
const requirements = source.coinType === SUI_COIN_TYPE
|
|
49
|
+
? [{ symbol: source.symbol, coinType: SUI_COIN_TYPE, decimals: source.decimals, requiredRaw: sourceAmountRaw + FLOWX_SWAP_GAS_BUDGET_MIST }]
|
|
50
|
+
: [
|
|
51
|
+
{ symbol: source.symbol, coinType: source.coinType, decimals: source.decimals, requiredRaw: sourceAmountRaw },
|
|
52
|
+
{ symbol: "SUI", coinType: SUI_COIN_TYPE, decimals: 9, requiredRaw: FLOWX_SWAP_GAS_BUDGET_MIST }
|
|
53
|
+
];
|
|
54
|
+
for (const requirement of requirements) {
|
|
55
|
+
const balance = await options.client.core.getBalance({
|
|
56
|
+
owner: input.account,
|
|
57
|
+
coinType: requirement.coinType
|
|
58
|
+
});
|
|
59
|
+
const heldRaw = BigInt(balance.balance.balance);
|
|
60
|
+
if (heldRaw < requirement.requiredRaw) {
|
|
61
|
+
const isGasOnly = requirement.coinType === SUI_COIN_TYPE && source.coinType !== SUI_COIN_TYPE;
|
|
62
|
+
return {
|
|
63
|
+
status: "blocked",
|
|
64
|
+
blockedReason: "insufficient_balance",
|
|
65
|
+
checks: [
|
|
66
|
+
failReviewCheck("flowx_transaction_material_build_failed", "Transaction material build", `Insufficient balance: this swap needs ${displayRaw(requirement.requiredRaw, requirement.decimals)} ${requirement.symbol}${isGasOnly ? " for gas" : requirement.coinType === SUI_COIN_TYPE ? " (amount + gas)" : ""}, but the account holds ${displayRaw(heldRaw, requirement.decimals)} ${requirement.symbol}. Nothing was signed and no funds moved.`, "adapter")
|
|
67
|
+
]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const transactionBytes = await buildBytes({
|
|
72
|
+
account: input.account,
|
|
73
|
+
quotePolicy: input.quotePolicy,
|
|
74
|
+
sdkRoutes: input.quoteEvidence.sdkRoutes
|
|
75
|
+
});
|
|
76
|
+
const verification = await verifyFlowxSwapMaterialBytes({
|
|
77
|
+
transactionBytes,
|
|
78
|
+
quotePolicy: input.quotePolicy
|
|
79
|
+
});
|
|
80
|
+
if (verification.status === "failed") {
|
|
81
|
+
return {
|
|
82
|
+
status: "blocked",
|
|
83
|
+
blockedReason: verification.blockedReason,
|
|
84
|
+
checks: [
|
|
85
|
+
failReviewCheck("flowx_transaction_material_bytes_verification_failed", "Transaction material verification", verification.message, "adapter")
|
|
86
|
+
]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
options.materialStore.deleteReviewSessionTransactionMaterials(input.reviewSessionId);
|
|
90
|
+
const handle = options.materialStore.recordTransactionMaterial({
|
|
91
|
+
reviewSessionId: input.reviewSessionId,
|
|
92
|
+
planId: input.plan.id,
|
|
93
|
+
account: input.account,
|
|
94
|
+
kind: "flowx_swap_transaction_data",
|
|
95
|
+
source: "say_ur_intent_built",
|
|
96
|
+
transactionBytes,
|
|
97
|
+
expiresAt: quoteExpiresAt,
|
|
98
|
+
redactedDiagnostics: {
|
|
99
|
+
protocol: input.plan.protocol,
|
|
100
|
+
adapterId: input.plan.adapterId,
|
|
101
|
+
actionKind: input.plan.actionKind,
|
|
102
|
+
poolKeys: input.quoteEvidence.pools.map((pool) => pool.poolKey),
|
|
103
|
+
swapXToY: input.quotePolicy.swapXToY,
|
|
104
|
+
quoteFetchedAt: input.quotePolicy.fetchedAt,
|
|
105
|
+
quoteExpiresAt: quoteExpiresAt.toISOString(),
|
|
106
|
+
sourceAmountRaw: input.quotePolicy.sourceAmountRaw,
|
|
107
|
+
expectedOutRaw: input.quotePolicy.expectedOutRaw,
|
|
108
|
+
minOutRaw: input.quotePolicy.minOutRaw,
|
|
109
|
+
routerSlippageUnits: input.quotePolicy.routerSlippageUnits,
|
|
110
|
+
deadlineMsEpoch: input.quotePolicy.deadlineMsEpoch
|
|
111
|
+
}
|
|
112
|
+
}, input.now);
|
|
113
|
+
return {
|
|
114
|
+
status: "completed",
|
|
115
|
+
evidence: handle,
|
|
116
|
+
checks: [
|
|
117
|
+
passReviewCheck("flowx_transaction_material_built", "Transaction material build", "Built account-bound FlowX swap transaction material locally from the quoted route and verified inside the bytes that every Move call targets pinned FlowX packages, every shared object is a pinned FlowX config object, and the router arguments carry the policy expected-output, slippage, and deadline. The unsigned bytes stay only in the local material store until quote expiry.", "adapter")
|
|
118
|
+
]
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const blockedReason = blockedReasonForBuildError(error);
|
|
123
|
+
return {
|
|
124
|
+
status: "blocked",
|
|
125
|
+
blockedReason,
|
|
126
|
+
checks: [
|
|
127
|
+
failReviewCheck("flowx_transaction_material_build_failed", "Transaction material build", buildFailureMessage(blockedReason), "adapter")
|
|
128
|
+
]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function createFlowxSwapTransactionMaterialDigestProducer(options) {
|
|
134
|
+
return async (input) => {
|
|
135
|
+
const material = options.materialStore.getTransactionMaterial(input.materialHandle, input.now);
|
|
136
|
+
if (!material) {
|
|
137
|
+
return {
|
|
138
|
+
status: "refresh_required",
|
|
139
|
+
refreshReason: "quote_stale",
|
|
140
|
+
checks: [
|
|
141
|
+
failReviewCheck("flowx_transaction_material_digest_unavailable", "Transaction material digest", "FlowX transaction material digest was not computed because the stored local material was unavailable or expired; refresh the review evidence before continuing.", "adapter")
|
|
142
|
+
]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
let transactionDigest;
|
|
146
|
+
try {
|
|
147
|
+
transactionDigest = await Transaction.from(material.transactionBytes).getDigest();
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return {
|
|
151
|
+
status: "blocked",
|
|
152
|
+
blockedReason: "object_resolution_failed",
|
|
153
|
+
checks: [
|
|
154
|
+
failReviewCheck("flowx_transaction_material_digest_failed", "Transaction material digest", "FlowX transaction material digest could not be derived from the stored local transaction bytes.", "adapter")
|
|
155
|
+
]
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const parsedDigest = suiTransactionDigestSchema.safeParse(transactionDigest);
|
|
159
|
+
if (!parsedDigest.success) {
|
|
160
|
+
return {
|
|
161
|
+
status: "blocked",
|
|
162
|
+
blockedReason: "object_resolution_failed",
|
|
163
|
+
checks: [
|
|
164
|
+
failReviewCheck("flowx_transaction_material_digest_invalid", "Transaction material digest", "FlowX transaction material digest did not match the pinned Sui SDK transaction digest format.", "adapter")
|
|
165
|
+
]
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
status: "completed",
|
|
170
|
+
evidence: {
|
|
171
|
+
materialId: material.materialId,
|
|
172
|
+
reviewSessionId: material.reviewSessionId,
|
|
173
|
+
planId: material.planId,
|
|
174
|
+
account: material.account,
|
|
175
|
+
kind: material.kind,
|
|
176
|
+
source: material.source,
|
|
177
|
+
digestKind: "sui_transaction_digest",
|
|
178
|
+
transactionDigest: parsedDigest.data,
|
|
179
|
+
computedAt: input.now.toISOString(),
|
|
180
|
+
expiresAt: material.expiresAt
|
|
181
|
+
},
|
|
182
|
+
checks: [
|
|
183
|
+
passReviewCheck("flowx_transaction_material_digest_commitment", "Transaction material digest", "Derived a Sui transaction digest from the stored local unsigned transaction material. The digest and bytes remain internal until later review stages bind object ownership, human-readable review, and simulation evidence.", "adapter")
|
|
184
|
+
]
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function sdkTradeBytesBuilder(client) {
|
|
189
|
+
return async ({ account, quotePolicy, sdkRoutes }) => {
|
|
190
|
+
const trade = new Trade({
|
|
191
|
+
network: "mainnet",
|
|
192
|
+
sender: account,
|
|
193
|
+
recipient: account,
|
|
194
|
+
amountIn: quotePolicy.sourceAmountRaw,
|
|
195
|
+
amountOut: quotePolicy.expectedOutRaw,
|
|
196
|
+
slippage: quotePolicy.routerSlippageUnits,
|
|
197
|
+
deadline: quotePolicy.deadlineMsEpoch,
|
|
198
|
+
// The route entities come from the same quoter response that produced
|
|
199
|
+
// the validated quote evidence; the byte verification below re-checks
|
|
200
|
+
// every pinned target and policy number inside the built bytes.
|
|
201
|
+
routes: sdkRoutes
|
|
202
|
+
});
|
|
203
|
+
const transaction = await trade.buildTransaction({ client });
|
|
204
|
+
transaction.setSenderIfNotSet(account);
|
|
205
|
+
transaction.setGasBudget(FLOWX_SWAP_GAS_BUDGET_MIST);
|
|
206
|
+
return transaction.build({ client });
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Re-derive the safety-relevant facts from the built transaction bytes and
|
|
211
|
+
* compare them with the pinned registry and the derived quote policy. The
|
|
212
|
+
* bytes are the only authority: a mismatch blocks the review.
|
|
213
|
+
*/
|
|
214
|
+
export async function verifyFlowxSwapMaterialBytes(input) {
|
|
215
|
+
let data;
|
|
216
|
+
try {
|
|
217
|
+
data = Transaction.from(input.transactionBytes).getData();
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return {
|
|
221
|
+
status: "failed",
|
|
222
|
+
blockedReason: "object_resolution_failed",
|
|
223
|
+
message: "FlowX transaction material bytes could not be parsed as a Sui transaction."
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const allowedPackages = new Set([
|
|
227
|
+
MOVE_STDLIB_PACKAGE_ID,
|
|
228
|
+
SUI_FRAMEWORK_PACKAGE_ID,
|
|
229
|
+
FLOWX_CLMM_MAINNET.universalRouter.packageId,
|
|
230
|
+
FLOWX_CLMM_MAINNET.universalRouter.wrappedRouterPackageId
|
|
231
|
+
].map((id) => normalizeSuiObjectId(id)));
|
|
232
|
+
const allowedSharedObjects = new Set([
|
|
233
|
+
SUI_CLOCK_OBJECT_ID,
|
|
234
|
+
FLOWX_CLMM_MAINNET.universalRouter.treasuryObjectId,
|
|
235
|
+
FLOWX_CLMM_MAINNET.universalRouter.tradeIdTrackerObjectId,
|
|
236
|
+
FLOWX_CLMM_MAINNET.universalRouter.partnerRegistryObjectId,
|
|
237
|
+
FLOWX_CLMM_MAINNET.universalRouter.versionedObjectId,
|
|
238
|
+
FLOWX_CLMM_MAINNET.poolRegistry.objectId,
|
|
239
|
+
FLOWX_CLMM_MAINNET.versioned.objectId
|
|
240
|
+
].map((id) => normalizeSuiObjectId(id)));
|
|
241
|
+
let routerBuildCall;
|
|
242
|
+
for (const command of data.commands) {
|
|
243
|
+
if (command.$kind !== "MoveCall" || !command.MoveCall) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const moveCall = command.MoveCall;
|
|
247
|
+
const packageId = normalizeSuiObjectId(moveCall.package);
|
|
248
|
+
if (!allowedPackages.has(packageId)) {
|
|
249
|
+
return {
|
|
250
|
+
status: "failed",
|
|
251
|
+
blockedReason: "object_resolution_failed",
|
|
252
|
+
message: `FlowX transaction material calls a package outside the pinned FlowX set: ${moveCall.package}::${moveCall.module}::${moveCall.function}.`
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (packageId === normalizeSuiObjectId(FLOWX_CLMM_MAINNET.universalRouter.packageId) &&
|
|
256
|
+
moveCall.module === "universal_router" &&
|
|
257
|
+
moveCall.function === "build") {
|
|
258
|
+
if (routerBuildCall !== undefined) {
|
|
259
|
+
return {
|
|
260
|
+
status: "failed",
|
|
261
|
+
blockedReason: "object_resolution_failed",
|
|
262
|
+
message: "FlowX transaction material contains more than one universal_router::build call."
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
routerBuildCall = { arguments: moveCall.arguments };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (!routerBuildCall) {
|
|
269
|
+
return {
|
|
270
|
+
status: "failed",
|
|
271
|
+
blockedReason: "object_resolution_failed",
|
|
272
|
+
message: "FlowX transaction material does not contain the universal_router::build call."
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
for (const inputEntry of data.inputs) {
|
|
276
|
+
if (inputEntry.$kind !== "Object" || !inputEntry.Object) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const objectInput = inputEntry.Object;
|
|
280
|
+
if (objectInput.$kind === "SharedObject" && objectInput.SharedObject) {
|
|
281
|
+
const objectId = normalizeSuiObjectId(objectInput.SharedObject.objectId);
|
|
282
|
+
if (!allowedSharedObjects.has(objectId)) {
|
|
283
|
+
return {
|
|
284
|
+
status: "failed",
|
|
285
|
+
blockedReason: "object_resolution_failed",
|
|
286
|
+
message: `FlowX transaction material references a shared object outside the pinned FlowX set: ${objectId}.`
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const expectations = [
|
|
292
|
+
{ label: "expected output", argumentIndex: 4, expected: BigInt(input.quotePolicy.expectedOutRaw) },
|
|
293
|
+
{ label: "slippage", argumentIndex: 5, expected: BigInt(input.quotePolicy.routerSlippageUnits) },
|
|
294
|
+
{ label: "deadline", argumentIndex: 6, expected: BigInt(input.quotePolicy.deadlineMsEpoch) }
|
|
295
|
+
];
|
|
296
|
+
for (const expectation of expectations) {
|
|
297
|
+
const actual = pureU64ArgumentValue(data, routerBuildCall.arguments, expectation.argumentIndex);
|
|
298
|
+
if (actual === undefined) {
|
|
299
|
+
return {
|
|
300
|
+
status: "failed",
|
|
301
|
+
blockedReason: "amount_mismatch",
|
|
302
|
+
message: `FlowX transaction material router ${expectation.label} argument is not a pure u64 input.`
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
if (actual !== expectation.expected) {
|
|
306
|
+
return {
|
|
307
|
+
status: "failed",
|
|
308
|
+
blockedReason: "amount_mismatch",
|
|
309
|
+
message: `FlowX transaction material router ${expectation.label} is ${actual}, but the derived quote policy requires ${expectation.expected}.`
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return { status: "ok" };
|
|
314
|
+
}
|
|
315
|
+
function pureU64ArgumentValue(data, callArguments, argumentIndex) {
|
|
316
|
+
const argument = callArguments[argumentIndex];
|
|
317
|
+
if (typeof argument !== "object" || argument === null) {
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
const argumentRecord = argument;
|
|
321
|
+
if (argumentRecord.$kind !== "Input" || typeof argumentRecord.Input !== "number") {
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
const inputEntry = data.inputs[argumentRecord.Input];
|
|
325
|
+
if (!inputEntry || inputEntry.$kind !== "Pure" || !inputEntry.Pure) {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
return BigInt(bcs.U64.parse(fromBase64(inputEntry.Pure.bytes)));
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function displayRaw(raw, decimals) {
|
|
336
|
+
const base = 10n ** BigInt(decimals);
|
|
337
|
+
const whole = raw / base;
|
|
338
|
+
const fraction = (raw % base).toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
339
|
+
return fraction ? `${whole}.${fraction}` : whole.toString();
|
|
340
|
+
}
|
|
341
|
+
function blockedReasonForBuildError(error) {
|
|
342
|
+
if (error instanceof LocalTransactionMaterialStoreError) {
|
|
343
|
+
return "object_resolution_failed";
|
|
344
|
+
}
|
|
345
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
346
|
+
if (/insufficient ?coin ?balance|insufficient balance/i.test(message)) {
|
|
347
|
+
return "insufficient_balance";
|
|
348
|
+
}
|
|
349
|
+
if (/gas/i.test(message) && /insufficient|no valid|payment|budget/i.test(message)) {
|
|
350
|
+
return "insufficient_gas";
|
|
351
|
+
}
|
|
352
|
+
return "object_resolution_failed";
|
|
353
|
+
}
|
|
354
|
+
function buildFailureMessage(blockedReason) {
|
|
355
|
+
if (blockedReason === "insufficient_balance") {
|
|
356
|
+
return "FlowX transaction material build failed before wallet handoff because the account does not have enough source or fee assets.";
|
|
357
|
+
}
|
|
358
|
+
if (blockedReason === "insufficient_gas") {
|
|
359
|
+
return "FlowX transaction material build failed before wallet handoff because a usable gas payment could not be resolved.";
|
|
360
|
+
}
|
|
361
|
+
return "FlowX transaction material build failed before wallet handoff because required account-bound objects could not be resolved.";
|
|
362
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createDeepbookSwapActionPlan, DEEPBOOK_SWAP_ACTION_KIND, DEEPBOOK_SWAP_ADAPTER_ID, DEEPBOOK_SWAP_PROTOCOL } from "./deepbook/deepbookSwapIntent.js";
|
|
3
|
+
import { createFlowxSwapActionPlan, FLOWX_SWAP_ACTION_KIND, FLOWX_SWAP_ADAPTER_ID, FLOWX_SWAP_PROTOCOL } from "./flowx/flowxSwapIntent.js";
|
|
4
|
+
/**
|
|
5
|
+
* Protocol-neutral swap intent. The optional `protocol` field carries the
|
|
6
|
+
* protocol slug (same slug vocabulary as the adapter prompt surfaces). When
|
|
7
|
+
* several protocols support the same action kind, the caller must name one -
|
|
8
|
+
* the entry point never picks a venue silently.
|
|
9
|
+
*/
|
|
10
|
+
export const swapIntentInputSchema = z.object({
|
|
11
|
+
type: z.literal("swap"),
|
|
12
|
+
from: z.object({
|
|
13
|
+
symbol: z.string().min(1),
|
|
14
|
+
amount: z.string().min(1)
|
|
15
|
+
}),
|
|
16
|
+
to: z.object({
|
|
17
|
+
symbol: z.string().min(1)
|
|
18
|
+
}),
|
|
19
|
+
maxSlippageBps: z.number().int().min(1).max(1000),
|
|
20
|
+
protocol: z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1)
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Protocol slug, required only when several protocols support the action")
|
|
25
|
+
});
|
|
26
|
+
export const INTENT_PLAN_FACTORIES = [
|
|
27
|
+
{
|
|
28
|
+
adapterId: DEEPBOOK_SWAP_ADAPTER_ID,
|
|
29
|
+
actionKind: DEEPBOOK_SWAP_ACTION_KIND,
|
|
30
|
+
protocolSlug: "deep",
|
|
31
|
+
protocol: DEEPBOOK_SWAP_PROTOCOL,
|
|
32
|
+
createPlan: (intent, now) => createDeepbookSwapActionPlan({ ...intent, type: "swap" }, now)
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
adapterId: FLOWX_SWAP_ADAPTER_ID,
|
|
36
|
+
actionKind: FLOWX_SWAP_ACTION_KIND,
|
|
37
|
+
protocolSlug: "flowx",
|
|
38
|
+
protocol: FLOWX_SWAP_PROTOCOL,
|
|
39
|
+
createPlan: (intent, now) => createFlowxSwapActionPlan({ ...intent, type: "swap" }, now)
|
|
40
|
+
}
|
|
41
|
+
];
|
|
42
|
+
export function resolveIntentPlanFactory(factories, actionKind, protocolSlug) {
|
|
43
|
+
const group = factories.filter((factory) => factory.actionKind === actionKind);
|
|
44
|
+
if (group.length === 0) {
|
|
45
|
+
return { status: "unsupported_action", actionKind };
|
|
46
|
+
}
|
|
47
|
+
const available = group.map((factory) => factory.protocolSlug);
|
|
48
|
+
if (protocolSlug !== undefined) {
|
|
49
|
+
const factory = group.find((candidate) => candidate.protocolSlug === protocolSlug.trim());
|
|
50
|
+
if (!factory) {
|
|
51
|
+
return { status: "unknown_protocol", protocolSlug, available };
|
|
52
|
+
}
|
|
53
|
+
return { status: "resolved", factory };
|
|
54
|
+
}
|
|
55
|
+
if (group.length === 1 && group[0]) {
|
|
56
|
+
return { status: "resolved", factory: group[0] };
|
|
57
|
+
}
|
|
58
|
+
return { status: "protocol_choice_required", available };
|
|
59
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { computeDeepbookSwapReviewEvidence } from "./deepbook/deepbookReviewEvidence.js";
|
|
2
|
+
import { DEEPBOOK_SWAP_ACTION_KIND, DEEPBOOK_SWAP_ADAPTER_ID, DEEPBOOK_SWAP_PROTOCOL, isDeepbookSwapActionPlanIdentity } from "./deepbook/deepbookSwapIntent.js";
|
|
3
|
+
import { DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGE_CATALOG_ID } from "./deepbook/deepbookReviewLifecycle.js";
|
|
4
|
+
import { computeFlowxSwapReviewEvidence } from "./flowx/flowxSwapReviewEvidence.js";
|
|
5
|
+
import { FLOWX_SWAP_ACTION_KIND, FLOWX_SWAP_ADAPTER_ID, FLOWX_SWAP_PROTOCOL, isFlowxSwapActionPlanIdentity } from "./flowx/flowxSwapIntent.js";
|
|
6
|
+
import { FLOWX_SWAP_REVIEW_LIFECYCLE_STAGE_CATALOG_ID } from "./flowx/flowxSwapReviewLifecycle.js";
|
|
7
|
+
import { unsupportedDeepbookSwapPlanIdentityCheck, unsupportedFlowxSwapPlanIdentityCheck } from "../core/review/reviewChecks.js";
|
|
8
|
+
import { blockedReviewResult } from "../core/review/reviewComputationResult.js";
|
|
9
|
+
export function buildSupportedReviewAdapterDescriptors(wiring) {
|
|
10
|
+
const descriptors = [
|
|
11
|
+
{
|
|
12
|
+
adapterId: DEEPBOOK_SWAP_ADAPTER_ID,
|
|
13
|
+
protocol: DEEPBOOK_SWAP_PROTOCOL,
|
|
14
|
+
actionKind: DEEPBOOK_SWAP_ACTION_KIND,
|
|
15
|
+
stageCatalogId: DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGE_CATALOG_ID,
|
|
16
|
+
computeReview: deepbookSwapEvidenceComputer(wiring.deepbook)
|
|
17
|
+
}
|
|
18
|
+
];
|
|
19
|
+
if (wiring.flowx) {
|
|
20
|
+
descriptors.push({
|
|
21
|
+
adapterId: FLOWX_SWAP_ADAPTER_ID,
|
|
22
|
+
protocol: FLOWX_SWAP_PROTOCOL,
|
|
23
|
+
actionKind: FLOWX_SWAP_ACTION_KIND,
|
|
24
|
+
stageCatalogId: FLOWX_SWAP_REVIEW_LIFECYCLE_STAGE_CATALOG_ID,
|
|
25
|
+
computeReview: flowxSwapEvidenceComputer(wiring.flowx)
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return descriptors;
|
|
29
|
+
}
|
|
30
|
+
function deepbookSwapEvidenceComputer(wiring) {
|
|
31
|
+
return async (input) => {
|
|
32
|
+
if (!isDeepbookSwapActionPlanIdentity(input.plan)) {
|
|
33
|
+
return {
|
|
34
|
+
result: blockedReviewResult("unsupported_action", [unsupportedDeepbookSwapPlanIdentityCheck()])
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return computeDeepbookSwapReviewEvidence({
|
|
38
|
+
reviewSessionId: input.reviewSessionId,
|
|
39
|
+
plan: input.plan,
|
|
40
|
+
account: input.account,
|
|
41
|
+
now: input.now,
|
|
42
|
+
quoteSource: wiring.deepbookQuoteSource,
|
|
43
|
+
deepBalanceSource: wiring.deepbookDeepBalanceSource,
|
|
44
|
+
transactionMaterialProducer: wiring.deepbookTransactionMaterialProducer,
|
|
45
|
+
transactionMaterialDigestProducer: wiring.deepbookTransactionMaterialDigestProducer,
|
|
46
|
+
transactionObjectOwnershipProducer: wiring.transactionObjectOwnershipProducer,
|
|
47
|
+
humanReadableReviewProducer: wiring.deepbookHumanReadableReviewProducer,
|
|
48
|
+
reviewTimeSimulationProducer: wiring.reviewTimeSimulationProducer,
|
|
49
|
+
ptbVisualizationProducer: wiring.ptbVisualizationProducer
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function flowxSwapEvidenceComputer(wiring) {
|
|
54
|
+
return async (input) => {
|
|
55
|
+
if (!isFlowxSwapActionPlanIdentity(input.plan)) {
|
|
56
|
+
return {
|
|
57
|
+
result: blockedReviewResult("unsupported_action", [unsupportedFlowxSwapPlanIdentityCheck()])
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return computeFlowxSwapReviewEvidence({
|
|
61
|
+
reviewSessionId: input.reviewSessionId,
|
|
62
|
+
plan: input.plan,
|
|
63
|
+
account: input.account,
|
|
64
|
+
now: input.now,
|
|
65
|
+
quoteSource: wiring.flowxQuoteSource,
|
|
66
|
+
transactionMaterialProducer: wiring.flowxTransactionMaterialProducer,
|
|
67
|
+
transactionMaterialDigestProducer: wiring.flowxTransactionMaterialDigestProducer,
|
|
68
|
+
transactionObjectOwnershipProducer: wiring.transactionObjectOwnershipProducer,
|
|
69
|
+
humanReadableReviewProducer: wiring.flowxHumanReadableReviewProducer,
|
|
70
|
+
reviewTimeSimulationProducer: wiring.reviewTimeSimulationProducer,
|
|
71
|
+
ptbVisualizationProducer: wiring.ptbVisualizationProducer
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function buildSupportedReviewAdapters(wiring) {
|
|
76
|
+
const adapters = {};
|
|
77
|
+
for (const descriptor of buildSupportedReviewAdapterDescriptors(wiring)) {
|
|
78
|
+
adapters[descriptor.adapterId] = descriptor.computeReview;
|
|
79
|
+
}
|
|
80
|
+
return adapters;
|
|
81
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function createAdapterLifecycleValidator(validatorsByStageCatalogId) {
|
|
2
|
+
return (lifecycle) => {
|
|
3
|
+
const validator = validatorsByStageCatalogId[lifecycle.stageCatalogId];
|
|
4
|
+
if (!validator) {
|
|
5
|
+
throw new Error(`Unsupported adapter lifecycle stage catalog: ${lifecycle.stageCatalogId}`);
|
|
6
|
+
}
|
|
7
|
+
validator(lifecycle);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export const rejectAdapterLifecycle = (lifecycle) => {
|
|
11
|
+
throw new Error(`Adapter lifecycle is not accepted here: ${lifecycle.stageCatalogId}`);
|
|
12
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const FORBIDDEN_EDGE_TERMS = [
|
|
2
|
+
"bytes",
|
|
3
|
+
"signature",
|
|
4
|
+
"sessiontoken",
|
|
5
|
+
"reviewtoken",
|
|
6
|
+
"wallettoken",
|
|
7
|
+
"fragmenttoken",
|
|
8
|
+
"tokenhash",
|
|
9
|
+
"privatekey",
|
|
10
|
+
"secretkey",
|
|
11
|
+
"seed",
|
|
12
|
+
"mnemonic"
|
|
13
|
+
];
|
|
14
|
+
const FORBIDDEN_PREFIX_OR_SUFFIX_TERMS = ["serialized", "signable"];
|
|
15
|
+
function isForbiddenFieldKey(key) {
|
|
16
|
+
const normalized = key.toLowerCase();
|
|
17
|
+
return (FORBIDDEN_EDGE_TERMS.some((term) => normalized === term || normalized.endsWith(term)) ||
|
|
18
|
+
FORBIDDEN_PREFIX_OR_SUFFIX_TERMS.some((term) => normalized === term || normalized.startsWith(term) || normalized.endsWith(term)));
|
|
19
|
+
}
|
|
20
|
+
export function findForbiddenMcpFields(value, path = "$") {
|
|
21
|
+
if (value === null || typeof value !== "object") {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
return value.flatMap((item, index) => findForbiddenMcpFields(item, `${path}[${index}]`));
|
|
26
|
+
}
|
|
27
|
+
return Object.entries(value).flatMap(([key, nested]) => {
|
|
28
|
+
const normalized = key.toLowerCase();
|
|
29
|
+
const currentPath = `${path}.${key}`;
|
|
30
|
+
// Deliberately conservative: MCP tool output and stored evidence should not
|
|
31
|
+
// carry raw byte, signing, token, signature, seed, or key material under
|
|
32
|
+
// direct prefix/suffix key names. Generic domain keys such as tokenSymbol
|
|
33
|
+
// and bytesPerSecond remain valid.
|
|
34
|
+
const current = isForbiddenFieldKey(normalized) ? [currentPath] : [];
|
|
35
|
+
return current.concat(findForbiddenMcpFields(nested, currentPath));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function assertNoForbiddenMcpFields(value) {
|
|
39
|
+
const forbidden = findForbiddenMcpFields(value);
|
|
40
|
+
if (forbidden.length > 0) {
|
|
41
|
+
throw new Error(`MCP payload contains forbidden field(s): ${forbidden.join(", ")}`);
|
|
42
|
+
}
|
|
43
|
+
}
|