@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,254 @@
|
|
|
1
|
+
import { SimulationError } from "@mysten/sui/client";
|
|
2
|
+
import { DeepBookClient, mainnetCoins } from "@mysten/deepbook-v3";
|
|
3
|
+
import { Transaction } from "@mysten/sui/transactions";
|
|
4
|
+
import { parseDeepbookRawU64 } from "../../core/read/deepbookReadHelpers.js";
|
|
5
|
+
import { suiTransactionDigestSchema } from "../../core/suiAddress.js";
|
|
6
|
+
import { failReviewCheck, passReviewCheck } from "../../core/review/reviewComputationResult.js";
|
|
7
|
+
const DEEPBOOK_SWAP_GAS_BUDGET_MIST = 50000000n;
|
|
8
|
+
const SUI_COIN_TYPE = "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI";
|
|
9
|
+
export function deepbookSwapBalanceRequirements(input) {
|
|
10
|
+
if (input.sourceCoinType === SUI_COIN_TYPE) {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
symbol: input.sourceSymbol,
|
|
14
|
+
coinType: SUI_COIN_TYPE,
|
|
15
|
+
decimals: input.sourceDecimals,
|
|
16
|
+
requiredRaw: input.sourceAmountRaw + DEEPBOOK_SWAP_GAS_BUDGET_MIST
|
|
17
|
+
}
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
symbol: input.sourceSymbol,
|
|
23
|
+
coinType: input.sourceCoinType,
|
|
24
|
+
decimals: input.sourceDecimals,
|
|
25
|
+
requiredRaw: input.sourceAmountRaw
|
|
26
|
+
},
|
|
27
|
+
{ symbol: "SUI", coinType: SUI_COIN_TYPE, decimals: 9, requiredRaw: DEEPBOOK_SWAP_GAS_BUDGET_MIST }
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
function displayRaw(raw, decimals) {
|
|
31
|
+
const base = 10n ** BigInt(decimals);
|
|
32
|
+
const whole = raw / base;
|
|
33
|
+
const fraction = (raw % base).toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
34
|
+
return fraction ? `${whole}.${fraction}` : whole.toString();
|
|
35
|
+
}
|
|
36
|
+
export function createDeepbookSwapTransactionMaterialProducer(options) {
|
|
37
|
+
return async (input) => {
|
|
38
|
+
if (options.network !== "mainnet" || options.chainIdentifier !== options.expectedChainIdentifier) {
|
|
39
|
+
return {
|
|
40
|
+
status: "blocked",
|
|
41
|
+
blockedReason: "network_mismatch",
|
|
42
|
+
checks: [
|
|
43
|
+
failReviewCheck("deepbook_transaction_material_network_mismatch", "Transaction material network", "DeepBook transaction material build requires a verified Sui mainnet gRPC endpoint and matching mainnet chain identifier.", "network")
|
|
44
|
+
]
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (input.poolResolution.direction !== input.quotePolicy.direction || input.quote.direction !== input.quotePolicy.direction) {
|
|
48
|
+
return {
|
|
49
|
+
status: "blocked",
|
|
50
|
+
blockedReason: "amount_mismatch",
|
|
51
|
+
checks: [
|
|
52
|
+
failReviewCheck("deepbook_transaction_material_quote_direction_mismatch", "Transaction material quote policy", "DeepBook transaction material build requires pool direction, quote evidence, and quote policy to describe the same swap direction.", "adapter")
|
|
53
|
+
]
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const quoteExpiresAt = quotePolicyExpiresAt(input.quotePolicy);
|
|
57
|
+
if (quoteExpiresAt.getTime() <= input.now.getTime()) {
|
|
58
|
+
return {
|
|
59
|
+
status: "refresh_required",
|
|
60
|
+
refreshReason: "quote_stale",
|
|
61
|
+
checks: [
|
|
62
|
+
failReviewCheck("deepbook_transaction_material_quote_expired", "Transaction material quote policy", "DeepBook transaction material was not built because the quote policy expired before build.", "quote")
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
let sourceAmountRaw;
|
|
67
|
+
let minOutRaw;
|
|
68
|
+
let deepAmountRaw;
|
|
69
|
+
try {
|
|
70
|
+
sourceAmountRaw = parseDeepbookRawU64(input.quotePolicy.sourceAmountRaw, "sourceAmountRaw", { positive: true });
|
|
71
|
+
minOutRaw = parseDeepbookRawU64(input.quotePolicy.minOutRaw, "minOutRaw", { positive: true });
|
|
72
|
+
deepAmountRaw = parseDeepbookRawU64(input.quotePolicy.deepAmountRaw, "deepAmountRaw");
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
status: "blocked",
|
|
77
|
+
blockedReason: "amount_mismatch",
|
|
78
|
+
checks: [
|
|
79
|
+
failReviewCheck("deepbook_transaction_material_raw_amount_invalid", "Transaction material quote policy", error instanceof Error ? error.message : "DeepBook transaction material requires valid raw u64 quote policy amounts.", "quote")
|
|
80
|
+
]
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const sourceQuoteAmount = input.quote.rawQuote.inputAmount;
|
|
85
|
+
const requirements = deepbookSwapBalanceRequirements({
|
|
86
|
+
sourceSymbol: sourceQuoteAmount.symbol,
|
|
87
|
+
sourceCoinType: sourceQuoteAmount.coinType,
|
|
88
|
+
sourceDecimals: sourceQuoteAmount.decimals,
|
|
89
|
+
sourceAmountRaw: sourceAmountRaw
|
|
90
|
+
});
|
|
91
|
+
for (const requirement of requirements) {
|
|
92
|
+
const balance = await options.client.core.getBalance({
|
|
93
|
+
owner: input.account,
|
|
94
|
+
coinType: requirement.coinType
|
|
95
|
+
});
|
|
96
|
+
const heldRaw = BigInt(balance.balance.balance);
|
|
97
|
+
if (heldRaw < requirement.requiredRaw) {
|
|
98
|
+
const isGasOnly = requirement.coinType === SUI_COIN_TYPE && sourceQuoteAmount.coinType !== SUI_COIN_TYPE;
|
|
99
|
+
return {
|
|
100
|
+
status: "blocked",
|
|
101
|
+
blockedReason: "insufficient_balance",
|
|
102
|
+
checks: [
|
|
103
|
+
failReviewCheck("deepbook_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")
|
|
104
|
+
]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const deepbook = new DeepBookClient({
|
|
109
|
+
client: options.client,
|
|
110
|
+
address: input.account,
|
|
111
|
+
network: options.network
|
|
112
|
+
});
|
|
113
|
+
const transaction = new Transaction();
|
|
114
|
+
transaction.setSender(input.account);
|
|
115
|
+
// The DeepBook SDK reserves a 0.25 SUI gas budget by default, which
|
|
116
|
+
// blocks small wallets whose balance is close to the swap amount. A
|
|
117
|
+
// mainnet DeepBook swap costs well under this explicit 0.05 SUI budget.
|
|
118
|
+
transaction.setGasBudget(DEEPBOOK_SWAP_GAS_BUDGET_MIST);
|
|
119
|
+
// input_coin fee mode swaps with an explicit zero DEEP coin; the Move
|
|
120
|
+
// entry point then charges the taker fee in the source coin at the
|
|
121
|
+
// protocol penalty. The explicit zero coin avoids SDK coin selection,
|
|
122
|
+
// which cannot resolve a zero balance for accounts holding no DEEP.
|
|
123
|
+
const inputFeeMode = input.quotePolicy.feeMode === "input_coin";
|
|
124
|
+
const zeroDeepCoin = inputFeeMode
|
|
125
|
+
? transaction.moveCall({
|
|
126
|
+
target: "0x2::coin::zero",
|
|
127
|
+
typeArguments: [mainnetCoins.DEEP.type]
|
|
128
|
+
})
|
|
129
|
+
: undefined;
|
|
130
|
+
const swapDeepAmountRaw = inputFeeMode ? 0n : deepAmountRaw;
|
|
131
|
+
const [baseCoinResult, quoteCoinResult, deepCoinResult] = input.quotePolicy.direction === "base_to_quote"
|
|
132
|
+
? deepbook.deepBook.swapExactBaseForQuote({
|
|
133
|
+
poolKey: input.poolResolution.poolKey,
|
|
134
|
+
amount: sourceAmountRaw,
|
|
135
|
+
minOut: minOutRaw,
|
|
136
|
+
deepAmount: swapDeepAmountRaw,
|
|
137
|
+
...(zeroDeepCoin ? { deepCoin: zeroDeepCoin } : {})
|
|
138
|
+
})(transaction)
|
|
139
|
+
: deepbook.deepBook.swapExactQuoteForBase({
|
|
140
|
+
poolKey: input.poolResolution.poolKey,
|
|
141
|
+
amount: sourceAmountRaw,
|
|
142
|
+
minOut: minOutRaw,
|
|
143
|
+
deepAmount: swapDeepAmountRaw,
|
|
144
|
+
...(zeroDeepCoin ? { deepCoin: zeroDeepCoin } : {})
|
|
145
|
+
})(transaction);
|
|
146
|
+
transaction.transferObjects([baseCoinResult, quoteCoinResult, deepCoinResult], input.account);
|
|
147
|
+
const transactionBytes = await transaction.build({ client: options.client });
|
|
148
|
+
options.materialStore.deleteReviewSessionTransactionMaterials(input.reviewSessionId);
|
|
149
|
+
const handle = options.materialStore.recordTransactionMaterial({
|
|
150
|
+
reviewSessionId: input.reviewSessionId,
|
|
151
|
+
planId: input.plan.id,
|
|
152
|
+
account: input.account,
|
|
153
|
+
kind: "deepbook_swap_transaction_data",
|
|
154
|
+
source: "sui_mcp_built",
|
|
155
|
+
transactionBytes,
|
|
156
|
+
expiresAt: quoteExpiresAt,
|
|
157
|
+
redactedDiagnostics: {
|
|
158
|
+
protocol: input.plan.protocol,
|
|
159
|
+
adapterId: input.plan.adapterId,
|
|
160
|
+
actionKind: input.plan.actionKind,
|
|
161
|
+
poolKey: input.poolResolution.poolKey,
|
|
162
|
+
direction: input.quotePolicy.direction,
|
|
163
|
+
quoteFetchedAt: input.quotePolicy.fetchedAt,
|
|
164
|
+
quoteExpiresAt: quoteExpiresAt.toISOString(),
|
|
165
|
+
sourceAmountRaw: input.quotePolicy.sourceAmountRaw,
|
|
166
|
+
minOutRaw: input.quotePolicy.minOutRaw,
|
|
167
|
+
deepAmountRaw: input.quotePolicy.deepAmountRaw
|
|
168
|
+
}
|
|
169
|
+
}, input.now);
|
|
170
|
+
return {
|
|
171
|
+
status: "completed",
|
|
172
|
+
evidence: handle,
|
|
173
|
+
checks: [
|
|
174
|
+
passReviewCheck("deepbook_transaction_material_built", "Transaction material build", "Built account-bound DeepBook swap transaction material from Sui MCP quote policy and stored the unsigned transaction bytes only in the local material store until quote expiry. This is not wallet handoff, signing data, signing readiness, or execution readiness.", "adapter")
|
|
175
|
+
]
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
return {
|
|
180
|
+
status: "blocked",
|
|
181
|
+
blockedReason: "object_resolution_failed",
|
|
182
|
+
checks: [
|
|
183
|
+
failReviewCheck("deepbook_transaction_material_build_failed", "Transaction material build", buildFailureMessage(error), "adapter")
|
|
184
|
+
]
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
export function createDeepbookSwapTransactionMaterialDigestProducer(options) {
|
|
190
|
+
return async (input) => {
|
|
191
|
+
const material = options.materialStore.getTransactionMaterial(input.materialHandle, input.now);
|
|
192
|
+
if (!material) {
|
|
193
|
+
return {
|
|
194
|
+
status: "refresh_required",
|
|
195
|
+
refreshReason: "quote_stale",
|
|
196
|
+
checks: [
|
|
197
|
+
failReviewCheck("deepbook_transaction_material_digest_unavailable", "Transaction material digest", "DeepBook transaction material digest was not computed because the stored local material was unavailable or expired; refresh the review evidence before continuing.", "adapter")
|
|
198
|
+
]
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
let transactionDigest;
|
|
202
|
+
try {
|
|
203
|
+
transactionDigest = await Transaction.from(material.transactionBytes).getDigest();
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return {
|
|
207
|
+
status: "blocked",
|
|
208
|
+
blockedReason: "object_resolution_failed",
|
|
209
|
+
checks: [
|
|
210
|
+
failReviewCheck("deepbook_transaction_material_digest_failed", "Transaction material digest", "DeepBook transaction material digest could not be derived from the stored local transaction bytes.", "adapter")
|
|
211
|
+
]
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const parsedDigest = suiTransactionDigestSchema.safeParse(transactionDigest);
|
|
215
|
+
if (!parsedDigest.success) {
|
|
216
|
+
return {
|
|
217
|
+
status: "blocked",
|
|
218
|
+
blockedReason: "object_resolution_failed",
|
|
219
|
+
checks: [
|
|
220
|
+
failReviewCheck("deepbook_transaction_material_digest_invalid", "Transaction material digest", "DeepBook transaction material digest did not match the pinned Sui SDK transaction digest format.", "adapter")
|
|
221
|
+
]
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
status: "completed",
|
|
226
|
+
evidence: {
|
|
227
|
+
materialId: material.materialId,
|
|
228
|
+
reviewSessionId: material.reviewSessionId,
|
|
229
|
+
planId: material.planId,
|
|
230
|
+
account: material.account,
|
|
231
|
+
kind: material.kind,
|
|
232
|
+
source: material.source,
|
|
233
|
+
digestKind: "sui_transaction_digest",
|
|
234
|
+
transactionDigest: parsedDigest.data,
|
|
235
|
+
computedAt: input.now.toISOString(),
|
|
236
|
+
expiresAt: material.expiresAt
|
|
237
|
+
},
|
|
238
|
+
checks: [
|
|
239
|
+
passReviewCheck("deepbook_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")
|
|
240
|
+
]
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function quotePolicyExpiresAt(policy) {
|
|
245
|
+
return new Date(Date.parse(policy.fetchedAt) + policy.staleAfterMs);
|
|
246
|
+
}
|
|
247
|
+
function buildFailureMessage(error) {
|
|
248
|
+
// The pinned SDK discards some gRPC error kinds when building a transaction.
|
|
249
|
+
// Its error text cannot establish an account-balance or gas shortfall.
|
|
250
|
+
if (error instanceof SimulationError && error.executionError) {
|
|
251
|
+
return "DeepBook transaction was rejected during build-time simulation. Refresh the review or inspect its selected constraints. Nothing was signed or submitted.";
|
|
252
|
+
}
|
|
253
|
+
return "DeepBook transaction material could not be built and verified. Nothing was signed or submitted.";
|
|
254
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createDeepbookSwapActionPlan, DEEPBOOK_SWAP_ACTION_KIND, DEEPBOOK_SWAP_ADAPTER_ID, DEEPBOOK_SWAP_PROTOCOL } from "./deepbook/deepbookSwapIntent.js";
|
|
3
|
+
/**
|
|
4
|
+
* Protocol-neutral swap intent. The optional `protocol` field carries the
|
|
5
|
+
* protocol slug (same slug vocabulary as the adapter prompt surfaces). When
|
|
6
|
+
* several protocols support the same action kind, the caller must name one -
|
|
7
|
+
* the entry point never picks a venue silently.
|
|
8
|
+
*/
|
|
9
|
+
export const swapIntentInputSchema = z.object({
|
|
10
|
+
type: z.literal("swap"),
|
|
11
|
+
from: z.object({
|
|
12
|
+
symbol: z.string().min(1),
|
|
13
|
+
amount: z.string().min(1)
|
|
14
|
+
}),
|
|
15
|
+
to: z.object({
|
|
16
|
+
symbol: z.string().min(1)
|
|
17
|
+
}),
|
|
18
|
+
maxSlippageBps: z.number().int().min(1).max(1000),
|
|
19
|
+
protocol: z
|
|
20
|
+
.string()
|
|
21
|
+
.min(1)
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Protocol slug, required only when several protocols support the action")
|
|
24
|
+
});
|
|
25
|
+
export const INTENT_PLAN_FACTORIES = [
|
|
26
|
+
{
|
|
27
|
+
adapterId: DEEPBOOK_SWAP_ADAPTER_ID,
|
|
28
|
+
actionKind: DEEPBOOK_SWAP_ACTION_KIND,
|
|
29
|
+
protocolSlug: "deep",
|
|
30
|
+
protocol: DEEPBOOK_SWAP_PROTOCOL,
|
|
31
|
+
createPlan: (intent, now) => createDeepbookSwapActionPlan({ ...intent, type: "swap" }, now)
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
export function resolveIntentPlanFactory(factories, actionKind, protocolSlug) {
|
|
35
|
+
const group = factories.filter((factory) => factory.actionKind === actionKind);
|
|
36
|
+
if (group.length === 0) {
|
|
37
|
+
return { status: "unsupported_action", actionKind };
|
|
38
|
+
}
|
|
39
|
+
const available = group.map((factory) => factory.protocolSlug);
|
|
40
|
+
if (protocolSlug !== undefined) {
|
|
41
|
+
const factory = group.find((candidate) => candidate.protocolSlug === protocolSlug.trim());
|
|
42
|
+
if (!factory) {
|
|
43
|
+
return { status: "unknown_protocol", protocolSlug, available };
|
|
44
|
+
}
|
|
45
|
+
return { status: "resolved", factory };
|
|
46
|
+
}
|
|
47
|
+
if (group.length === 1 && group[0]) {
|
|
48
|
+
return { status: "resolved", factory: group[0] };
|
|
49
|
+
}
|
|
50
|
+
return { status: "protocol_choice_required", available };
|
|
51
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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 { unsupportedDeepbookSwapPlanIdentityCheck } from "../core/review/reviewChecks.js";
|
|
5
|
+
import { blockedReviewResult } from "../core/review/reviewComputationResult.js";
|
|
6
|
+
export function buildSupportedReviewAdapterDescriptors(wiring) {
|
|
7
|
+
const descriptors = [
|
|
8
|
+
{
|
|
9
|
+
adapterId: DEEPBOOK_SWAP_ADAPTER_ID,
|
|
10
|
+
protocol: DEEPBOOK_SWAP_PROTOCOL,
|
|
11
|
+
actionKind: DEEPBOOK_SWAP_ACTION_KIND,
|
|
12
|
+
stageCatalogId: DEEPBOOK_SWAP_REVIEW_LIFECYCLE_STAGE_CATALOG_ID,
|
|
13
|
+
computeReview: deepbookSwapEvidenceComputer(wiring.deepbook)
|
|
14
|
+
}
|
|
15
|
+
];
|
|
16
|
+
return descriptors;
|
|
17
|
+
}
|
|
18
|
+
function deepbookSwapEvidenceComputer(wiring) {
|
|
19
|
+
return async (input) => {
|
|
20
|
+
if (!isDeepbookSwapActionPlanIdentity(input.plan)) {
|
|
21
|
+
return {
|
|
22
|
+
result: blockedReviewResult("unsupported_action", [unsupportedDeepbookSwapPlanIdentityCheck()])
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return computeDeepbookSwapReviewEvidence({
|
|
26
|
+
reviewSessionId: input.reviewSessionId,
|
|
27
|
+
plan: input.plan,
|
|
28
|
+
account: input.account,
|
|
29
|
+
now: input.now,
|
|
30
|
+
quoteSource: wiring.deepbookQuoteSource,
|
|
31
|
+
deepBalanceSource: wiring.deepbookDeepBalanceSource,
|
|
32
|
+
transactionMaterialProducer: wiring.deepbookTransactionMaterialProducer,
|
|
33
|
+
transactionMaterialDigestProducer: wiring.deepbookTransactionMaterialDigestProducer,
|
|
34
|
+
transactionObjectOwnershipProducer: wiring.transactionObjectOwnershipProducer,
|
|
35
|
+
humanReadableReviewProducer: wiring.deepbookHumanReadableReviewProducer,
|
|
36
|
+
reviewTimeSimulationProducer: wiring.reviewTimeSimulationProducer,
|
|
37
|
+
ptbVisualizationProducer: wiring.ptbVisualizationProducer
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function buildSupportedReviewAdapters(wiring) {
|
|
42
|
+
const adapters = {};
|
|
43
|
+
for (const descriptor of buildSupportedReviewAdapterDescriptors(wiring)) {
|
|
44
|
+
adapters[descriptor.adapterId] = descriptor.computeReview;
|
|
45
|
+
}
|
|
46
|
+
return adapters;
|
|
47
|
+
}
|
|
@@ -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,151 @@
|
|
|
1
|
+
import { mainnetPackageIds } from "@mysten/deepbook-v3";
|
|
2
|
+
import { MOVE_STDLIB_ADDRESS, normalizeSuiAddress, SUI_CLOCK_OBJECT_ID, SUI_COIN_REGISTRY_OBJECT_ID, SUI_DENY_LIST_OBJECT_ID, SUI_FRAMEWORK_ADDRESS, SUI_RANDOM_OBJECT_ID, SUI_SYSTEM_ADDRESS, SUI_SYSTEM_STATE_OBJECT_ID } from "@mysten/sui/utils";
|
|
3
|
+
import { DEEPBOOK_SOURCE_FIELD_VALUES } from "../read/deepbookSourceOwners.js";
|
|
4
|
+
/** Packages, relabeled only where they appear as a `<address>::...` path. */
|
|
5
|
+
export const PACKAGE_NAME_REGISTRY = [
|
|
6
|
+
{
|
|
7
|
+
address: mainnetPackageIds.DEEPBOOK_PACKAGE_ID,
|
|
8
|
+
name: "@deepbook/core",
|
|
9
|
+
source: DEEPBOOK_SOURCE_FIELD_VALUES.sdkMainnetPackageId
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
address: MOVE_STDLIB_ADDRESS,
|
|
13
|
+
name: "std",
|
|
14
|
+
source: "sui_sdk_move_stdlib_address"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
address: SUI_FRAMEWORK_ADDRESS,
|
|
18
|
+
name: "sui",
|
|
19
|
+
source: "sui_sdk_framework_address"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
address: SUI_SYSTEM_ADDRESS,
|
|
23
|
+
name: "sui_system",
|
|
24
|
+
source: "sui_sdk_system_address"
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
/** Well-known shared system objects, relabeled only as a bare object id. */
|
|
28
|
+
export const OBJECT_NAME_REGISTRY = [
|
|
29
|
+
{
|
|
30
|
+
address: SUI_SYSTEM_STATE_OBJECT_ID,
|
|
31
|
+
name: "SuiSystemState",
|
|
32
|
+
source: "sui_sdk_system_state_object_id"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
address: SUI_CLOCK_OBJECT_ID,
|
|
36
|
+
name: "Clock",
|
|
37
|
+
source: "sui_sdk_clock_object_id"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
address: SUI_RANDOM_OBJECT_ID,
|
|
41
|
+
name: "Random",
|
|
42
|
+
source: "sui_sdk_random_object_id"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
address: SUI_DENY_LIST_OBJECT_ID,
|
|
46
|
+
name: "DenyList",
|
|
47
|
+
source: "sui_sdk_deny_list_object_id"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
address: SUI_COIN_REGISTRY_OBJECT_ID,
|
|
51
|
+
name: "CoinRegistry",
|
|
52
|
+
source: "sui_sdk_coin_registry_object_id"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
// Address-based balances (coin reservation / fast path) reference the
|
|
56
|
+
// accumulator root object. @mysten/sui defines this id only internally as
|
|
57
|
+
// normalizeSuiAddress("0xacc") (not an exported constant), so mirror it here.
|
|
58
|
+
address: normalizeSuiAddress("0xacc"),
|
|
59
|
+
name: "AccumulatorRoot",
|
|
60
|
+
source: "sui_accumulator_root_object_id_0xacc"
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
/**
|
|
64
|
+
* Normalize a Sui address or package id to `0x` + 64 lowercase hex, or return
|
|
65
|
+
* undefined when the input is not a well-formed Sui address. Short forms are
|
|
66
|
+
* left-padded with zeros so different-width forms of the same address compare
|
|
67
|
+
* equal.
|
|
68
|
+
*/
|
|
69
|
+
function normalizeContractAddress(value) {
|
|
70
|
+
const lower = value.trim().toLowerCase();
|
|
71
|
+
const body = lower.startsWith("0x") ? lower.slice(2) : lower;
|
|
72
|
+
if (body.length === 0 || body.length > 64 || !/^[0-9a-f]+$/.test(body)) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return `0x${body.padStart(64, "0")}`;
|
|
76
|
+
}
|
|
77
|
+
function buildNameMap(entries) {
|
|
78
|
+
return new Map(entries.flatMap((entry) => {
|
|
79
|
+
const normalized = normalizeContractAddress(entry.address);
|
|
80
|
+
return normalized === undefined ? [] : [[normalized, entry.name]];
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
const PACKAGE_NAME_BY_ADDRESS = buildNameMap(PACKAGE_NAME_REGISTRY);
|
|
84
|
+
const OBJECT_NAME_BY_ADDRESS = buildNameMap(OBJECT_NAME_REGISTRY);
|
|
85
|
+
/**
|
|
86
|
+
* Replace registered addresses in PTB Mermaid label text with their display
|
|
87
|
+
* names. Mermaid node ids are synthetic (`command0`, `input0`, ...), so an
|
|
88
|
+
* address only appears inside quoted label text, and matching is context-aware:
|
|
89
|
+
*
|
|
90
|
+
* - A package is relabeled only where it is a path prefix (`<address>::`), the
|
|
91
|
+
* form a MoveCall target or type path takes — never a bare object id.
|
|
92
|
+
* - A well-known object is relabeled only where it is a bare id (not followed by
|
|
93
|
+
* `::`), the form an object input takes — never a package path.
|
|
94
|
+
*
|
|
95
|
+
* Unknown addresses (no registered name) are shortened for the display graph
|
|
96
|
+
* (leading zeros collapsed, long ids middle-elided); the full address stays in
|
|
97
|
+
* the artifact's raw `text` stream for audit and copy. Matching uses the full
|
|
98
|
+
* `0x` + 64-hex address form the PTB renderer (`@zktx.io/ptb-model`) emits; the
|
|
99
|
+
* registry keys are normalized to that same form.
|
|
100
|
+
*
|
|
101
|
+
* Registered package names may be Move Registry names like `@deepbook/core`;
|
|
102
|
+
* Mermaid v11 reads a literal `@` as node/edge metadata syntax even inside a
|
|
103
|
+
* quoted label and crashes the renderer, so an inserted `@` is written as the
|
|
104
|
+
* decimal entity `#64;`, which renders as `@` without a literal `@`. The copyable
|
|
105
|
+
* Mermaid source keeps raw addresses (it is built from the unmodified text), so
|
|
106
|
+
* this only affects the rendered graph.
|
|
107
|
+
*/
|
|
108
|
+
export function applyContractNamesToMermaid(mermaidText) {
|
|
109
|
+
let text = mermaidText;
|
|
110
|
+
// Packages: only in path position (`<address>::...`).
|
|
111
|
+
for (const [address, name] of PACKAGE_NAME_BY_ADDRESS) {
|
|
112
|
+
text = text.split(`${address}::`).join(`${mermaidSafeName(name)}::`);
|
|
113
|
+
}
|
|
114
|
+
// Objects: only as a bare id (anything but a `::` path prefix).
|
|
115
|
+
for (const [address, name] of OBJECT_NAME_BY_ADDRESS) {
|
|
116
|
+
const safe = mermaidSafeName(name);
|
|
117
|
+
text = text.replace(new RegExp(`${address}(?!::)`, "g"), () => safe);
|
|
118
|
+
}
|
|
119
|
+
// Any address still present (no registered name) is shortened for the display
|
|
120
|
+
// graph; its `::module::name` suffix, if any, is left intact. The full address
|
|
121
|
+
// is untouched in the raw `text` stream the artifact keeps for audit and copy.
|
|
122
|
+
text = text.replace(/0x[0-9a-f]{40,64}(?![0-9a-f])/gi, (address) => shortenAddressForDisplay(address));
|
|
123
|
+
return text;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Escape characters that break Mermaid label parsing. A literal `@` triggers
|
|
127
|
+
* Mermaid v11 node/edge metadata syntax (even inside quotes) and throws, so it
|
|
128
|
+
* is written as the `#64;` decimal entity, which renders as `@`. The other
|
|
129
|
+
* registered label characters (letters, `/`, `-`, `_`, `.`, `:`) are valid in
|
|
130
|
+
* Mermaid label text.
|
|
131
|
+
*/
|
|
132
|
+
function mermaidSafeName(name) {
|
|
133
|
+
return name.replace(/@/g, "#64;");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Shorten an unregistered full-length address for the display graph: collapse
|
|
137
|
+
* leading zeros so low ids read compactly (`0x...0002` -> `0x2`), and middle-elide
|
|
138
|
+
* a genuinely long id (`0x123456...cdef`). Display only — the raw `text` stream
|
|
139
|
+
* keeps the full address for audit and copy. Uses ASCII `...` (a Mermaid-valid
|
|
140
|
+
* label character) rather than a unicode ellipsis.
|
|
141
|
+
*/
|
|
142
|
+
function shortenAddressForDisplay(address) {
|
|
143
|
+
const body = address.slice(2).replace(/^0+/, "");
|
|
144
|
+
if (body.length === 0) {
|
|
145
|
+
return "0x0";
|
|
146
|
+
}
|
|
147
|
+
if (body.length <= 12) {
|
|
148
|
+
return `0x${body}`;
|
|
149
|
+
}
|
|
150
|
+
return `0x${body.slice(0, 6)}...${body.slice(-4)}`;
|
|
151
|
+
}
|
|
@@ -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
|
+
}
|