@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,306 @@
|
|
|
1
|
+
export class SmokeResponseShapeError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "SmokeResponseShapeError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function assertSmokeOkStatus(payload, toolName) {
|
|
8
|
+
const data = objectValue(payload.data);
|
|
9
|
+
if (payload.ok !== true || data?.status !== "ok") {
|
|
10
|
+
throw new SmokeResponseShapeError(`Tool ${toolName} did not return ok status: ${JSON.stringify(payload)}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function summarizeActivitySmokePayload(payload, toolName, options) {
|
|
14
|
+
assertSmokeOkStatus(payload, toolName);
|
|
15
|
+
const data = requiredObject(payload.data, `${toolName}.data`);
|
|
16
|
+
const source = requiredObject(data.source, `${toolName}.data.source`);
|
|
17
|
+
const persistence = requiredObject(data.persistence, `${toolName}.data.persistence`);
|
|
18
|
+
const requestedAccount = requiredObject(data.requestedAccount, `${toolName}.data.requestedAccount`);
|
|
19
|
+
const transactions = requiredArray(data.transactions, `${toolName}.data.transactions`);
|
|
20
|
+
const account = requiredString(data.account, `${toolName}.data.account`);
|
|
21
|
+
const accountSource = requiredAccountSource(data.accountSource, `${toolName}.data.accountSource`);
|
|
22
|
+
const relationship = requiredRelationship(data.relationship, `${toolName}.data.relationship`);
|
|
23
|
+
const functionTarget = options.requireFunction
|
|
24
|
+
? requiredString(data.function, `${toolName}.data.function`)
|
|
25
|
+
: undefined;
|
|
26
|
+
const sourceTransport = requiredLiteral(source.transport, "graphql", `${toolName}.data.source.transport`);
|
|
27
|
+
const sourceMethod = requiredLiteral(source.method, "Query.transactions", `${toolName}.data.source.method`);
|
|
28
|
+
const hasMore = requiredBoolean(data.hasMore, `${toolName}.data.hasMore`);
|
|
29
|
+
const windowComplete = requiredBooleanOrNull(data.windowComplete, `${toolName}.data.windowComplete`);
|
|
30
|
+
const orderingVerified = requiredBoolean(data.orderingVerified, `${toolName}.data.orderingVerified`);
|
|
31
|
+
const persistenceStored = requiredBoolean(persistence.stored, `${toolName}.data.persistence.stored`);
|
|
32
|
+
const requestedAccountBalanceChangeCompleteness = requiredBalanceChangeCompleteness(requestedAccount.balanceChangeCompleteness, `${toolName}.data.requestedAccount.balanceChangeCompleteness`);
|
|
33
|
+
const requestedAccountCoinFlows = requiredArray(requestedAccount.coinFlows, `${toolName}.data.requestedAccount.coinFlows`);
|
|
34
|
+
const requestedAccountSentCount = requiredNonnegativeInteger(requestedAccount.sentCount, `${toolName}.data.requestedAccount.sentCount`);
|
|
35
|
+
const requestedAccountAffectedOnlyCount = requiredNonnegativeInteger(requestedAccount.affectedOnlyCount, `${toolName}.data.requestedAccount.affectedOnlyCount`);
|
|
36
|
+
const requestedAccountTransactionFacts = requiredArray(data.requestedAccountTransactionFacts, `${toolName}.data.requestedAccountTransactionFacts`);
|
|
37
|
+
const accountFactSummary = summarizeRequestedAccountTransactionFacts(requestedAccountTransactionFacts, transactions.length, toolName);
|
|
38
|
+
const compactSummary = summarizeCompactResponseSupport(transactions, toolName);
|
|
39
|
+
let analysisLimitationCount;
|
|
40
|
+
let analysisCoinFlowCount;
|
|
41
|
+
if (accountSource !== options.expectedAccountSource) {
|
|
42
|
+
throw new SmokeResponseShapeError(`${toolName}.data.accountSource expected ${options.expectedAccountSource}, got ${accountSource}`);
|
|
43
|
+
}
|
|
44
|
+
if (options.expectedRelationship !== undefined && relationship !== options.expectedRelationship) {
|
|
45
|
+
throw new SmokeResponseShapeError(`${toolName}.data.relationship expected ${options.expectedRelationship}, got ${relationship}`);
|
|
46
|
+
}
|
|
47
|
+
if (options.requireAnalysis) {
|
|
48
|
+
const analysis = requiredObject(data.analysis, `${toolName}.data.analysis`);
|
|
49
|
+
const overview = requiredObject(analysis.overview, `${toolName}.data.analysis.overview`);
|
|
50
|
+
const limitations = requiredArray(analysis.limitations, `${toolName}.data.analysis.limitations`);
|
|
51
|
+
const coinFlows = requiredArray(analysis.coinFlows, `${toolName}.data.analysis.coinFlows`);
|
|
52
|
+
const analysisTransactionCount = requiredNonnegativeInteger(overview.transactionCount, `${toolName}.data.analysis.overview.transactionCount`);
|
|
53
|
+
if (analysisTransactionCount !== transactions.length) {
|
|
54
|
+
throw new SmokeResponseShapeError(`${toolName}.data.analysis.overview.transactionCount does not match transactions.length`);
|
|
55
|
+
}
|
|
56
|
+
analysisLimitationCount = limitations.length;
|
|
57
|
+
analysisCoinFlowCount = coinFlows.length;
|
|
58
|
+
}
|
|
59
|
+
if (options.expectNoDetails === true && compactSummary.fullDetailsReturned) {
|
|
60
|
+
throw new SmokeResponseShapeError(`${toolName}.data.transactions must not include full details`);
|
|
61
|
+
}
|
|
62
|
+
if (options.expectNoDetails === true && compactSummary.compactReturned) {
|
|
63
|
+
throw new SmokeResponseShapeError(`${toolName}.data.transactions must not include compact transaction aggregates`);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
status: transactions.length === 0 ? "empty_page" : "ok",
|
|
67
|
+
rowCount: transactions.length,
|
|
68
|
+
emptyAccepted: transactions.length === 0,
|
|
69
|
+
transactionCount: transactions.length,
|
|
70
|
+
account,
|
|
71
|
+
accountSource,
|
|
72
|
+
relationship,
|
|
73
|
+
...(functionTarget === undefined ? {} : { functionTargetPresent: true }),
|
|
74
|
+
sourceTransport,
|
|
75
|
+
sourceMethod,
|
|
76
|
+
hasMore,
|
|
77
|
+
windowComplete,
|
|
78
|
+
orderingVerified,
|
|
79
|
+
persistenceStored,
|
|
80
|
+
requestedAccountBalanceChangeCompleteness,
|
|
81
|
+
requestedAccountCoinFlowCount: requestedAccountCoinFlows.length,
|
|
82
|
+
requestedAccountSentCount,
|
|
83
|
+
requestedAccountAffectedOnlyCount,
|
|
84
|
+
...accountFactSummary,
|
|
85
|
+
...compactSummary,
|
|
86
|
+
...(analysisLimitationCount === undefined ? {} : { analysisLimitationCount }),
|
|
87
|
+
...(analysisCoinFlowCount === undefined ? {} : { analysisCoinFlowCount })
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function summarizeRequestedAccountTransactionFacts(facts, transactionCount, toolName) {
|
|
91
|
+
if (facts.length !== transactionCount) {
|
|
92
|
+
throw new SmokeResponseShapeError(`${toolName}.data.requestedAccountTransactionFacts length does not match transactions.length`);
|
|
93
|
+
}
|
|
94
|
+
let balanceChangeRows = 0;
|
|
95
|
+
facts.forEach((factValue, factIndex) => {
|
|
96
|
+
const fact = requiredObject(factValue, `${toolName}.data.requestedAccountTransactionFacts[${factIndex}]`);
|
|
97
|
+
const requestedAccountEffect = requiredObject(fact.requestedAccountEffect, `${toolName}.data.requestedAccountTransactionFacts[${factIndex}].requestedAccountEffect`);
|
|
98
|
+
const evidence = requiredAccountBalanceChangeEvidence(fact.accountBalanceChangeEvidence, `${toolName}.data.requestedAccountTransactionFacts[${factIndex}].accountBalanceChangeEvidence`);
|
|
99
|
+
const inferencePolicy = requiredAccountBalanceChangeInferencePolicy(fact.accountBalanceChangeInferencePolicy, `${toolName}.data.requestedAccountTransactionFacts[${factIndex}].accountBalanceChangeInferencePolicy`);
|
|
100
|
+
const completeness = requiredBalanceChangeCompleteness(fact.accountBalanceChangeCompleteness, `${toolName}.data.requestedAccountTransactionFacts[${factIndex}].accountBalanceChangeCompleteness`);
|
|
101
|
+
requiredLiteral(requestedAccountEffect.scope, "requested_account", `${toolName}.data.requestedAccountTransactionFacts[${factIndex}].requestedAccountEffect.scope`);
|
|
102
|
+
const balanceChanges = requiredArray(fact.accountBalanceChanges, `${toolName}.data.requestedAccountTransactionFacts[${factIndex}].accountBalanceChanges`);
|
|
103
|
+
balanceChangeRows += balanceChanges.length;
|
|
104
|
+
const nestedBalanceChanges = requiredArray(requestedAccountEffect.balanceChanges, `${toolName}.data.requestedAccountTransactionFacts[${factIndex}].requestedAccountEffect.balanceChanges`);
|
|
105
|
+
if (nestedBalanceChanges.length !== balanceChanges.length) {
|
|
106
|
+
throw new SmokeResponseShapeError(`${toolName}.data.requestedAccountTransactionFacts[${factIndex}] accountBalanceChanges length must match requestedAccountEffect.balanceChanges length`);
|
|
107
|
+
}
|
|
108
|
+
if (completeness === "truncated" && evidence !== "incomplete_account_balance_changes") {
|
|
109
|
+
throw new SmokeResponseShapeError(`${toolName}.data.requestedAccountTransactionFacts[${factIndex}] truncated account evidence must be incomplete_account_balance_changes`);
|
|
110
|
+
}
|
|
111
|
+
if (completeness === "unavailable" && evidence !== "account_balance_changes_unavailable") {
|
|
112
|
+
throw new SmokeResponseShapeError(`${toolName}.data.requestedAccountTransactionFacts[${factIndex}] unavailable account evidence must be account_balance_changes_unavailable`);
|
|
113
|
+
}
|
|
114
|
+
if (completeness === "complete" && balanceChanges.length === 0 && evidence !== "no_account_balance_changes_returned") {
|
|
115
|
+
throw new SmokeResponseShapeError(`${toolName}.data.requestedAccountTransactionFacts[${factIndex}] empty complete account evidence must be no_account_balance_changes_returned`);
|
|
116
|
+
}
|
|
117
|
+
if (completeness === "complete" && balanceChanges.length > 0 && evidence !== "account_balance_changes_returned") {
|
|
118
|
+
throw new SmokeResponseShapeError(`${toolName} complete account rows require account_balance_changes_returned`);
|
|
119
|
+
}
|
|
120
|
+
if (requestedAccountEffect.balanceChangeEvidence !== evidence || requestedAccountEffect.balanceChangeCompleteness !== completeness) {
|
|
121
|
+
throw new SmokeResponseShapeError(`${toolName} nested account evidence and completeness must match flat fields`);
|
|
122
|
+
}
|
|
123
|
+
const expectedInferencePolicy = expectedAccountBalanceChangeInferencePolicy(evidence);
|
|
124
|
+
if (inferencePolicy !== expectedInferencePolicy) {
|
|
125
|
+
throw new SmokeResponseShapeError(`${toolName}.data.requestedAccountTransactionFacts[${factIndex}].accountBalanceChangeInferencePolicy is inconsistent with account evidence`);
|
|
126
|
+
}
|
|
127
|
+
if (requestedAccountEffect.accountBalanceChangeInferencePolicy !== inferencePolicy) {
|
|
128
|
+
throw new SmokeResponseShapeError(`${toolName}.data.requestedAccountTransactionFacts[${factIndex}].requestedAccountEffect.accountBalanceChangeInferencePolicy does not match flat field`);
|
|
129
|
+
}
|
|
130
|
+
const transactionContext = objectValue(fact.transactionContext);
|
|
131
|
+
if (transactionContext !== undefined && Object.hasOwn(transactionContext, "balanceChanges")) {
|
|
132
|
+
throw new SmokeResponseShapeError(`${toolName}.data.requestedAccountTransactionFacts[${factIndex}].transactionContext must not include transaction-wide balanceChanges`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
requestedAccountTransactionFactCount: facts.length,
|
|
137
|
+
requestedAccountTransactionFactBalanceChangeRowCount: balanceChangeRows
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function summarizeCompactResponseSupport(transactions, toolName) {
|
|
141
|
+
let fullDetailsReturned = false;
|
|
142
|
+
let compactReturned = false;
|
|
143
|
+
let compactBalanceChangeRowCount = 0;
|
|
144
|
+
let compactAggregatedBalanceChangeRowCount = 0;
|
|
145
|
+
let transactionContextCount = 0;
|
|
146
|
+
let requestedAccountEffectBalanceChangeRowCount = 0;
|
|
147
|
+
let requestedAccountEffectTruncatedTransactionCount = 0;
|
|
148
|
+
transactions.forEach((transactionValue, transactionIndex) => {
|
|
149
|
+
const transaction = requiredObject(transactionValue, `${toolName}.data.transactions[${transactionIndex}]`);
|
|
150
|
+
if (Object.hasOwn(transaction, "details")) {
|
|
151
|
+
fullDetailsReturned = true;
|
|
152
|
+
}
|
|
153
|
+
const transactionContext = objectValue(transaction.transactionContext);
|
|
154
|
+
if (transactionContext !== undefined) {
|
|
155
|
+
transactionContextCount += 1;
|
|
156
|
+
if (transactionContext.factScope !== "transaction") {
|
|
157
|
+
throw new Error(`${toolName}.data.transactions[${transactionIndex}].transactionContext.factScope must be "transaction"`);
|
|
158
|
+
}
|
|
159
|
+
if (transactionContext.requestedAccountScoped !== false) {
|
|
160
|
+
throw new Error(`${toolName}.data.transactions[${transactionIndex}].transactionContext.requestedAccountScoped must be false`);
|
|
161
|
+
}
|
|
162
|
+
if (Object.hasOwn(transactionContext, "balanceChanges")) {
|
|
163
|
+
throw new SmokeResponseShapeError(`${toolName}.data.transactions[${transactionIndex}].transactionContext must not include transaction-wide balanceChanges`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const requestedAccountEffect = objectValue(transaction.requestedAccountEffect);
|
|
167
|
+
if (requestedAccountEffect !== undefined) {
|
|
168
|
+
requiredLiteral(requestedAccountEffect.scope, "requested_account", `${toolName}.data.transactions[${transactionIndex}].requestedAccountEffect.scope`);
|
|
169
|
+
const completeness = requiredBalanceChangeCompleteness(requestedAccountEffect.balanceChangeCompleteness, `${toolName}.data.transactions[${transactionIndex}].requestedAccountEffect.balanceChangeCompleteness`);
|
|
170
|
+
if (completeness === "truncated") {
|
|
171
|
+
requestedAccountEffectTruncatedTransactionCount += 1;
|
|
172
|
+
}
|
|
173
|
+
requestedAccountEffectBalanceChangeRowCount += requiredArray(requestedAccountEffect.balanceChanges, `${toolName}.data.transactions[${transactionIndex}].requestedAccountEffect.balanceChanges`).length;
|
|
174
|
+
}
|
|
175
|
+
const compact = objectValue(transaction.compact);
|
|
176
|
+
if (compact === undefined) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
compactReturned = true;
|
|
180
|
+
if (compact.factScope !== "transaction") {
|
|
181
|
+
throw new Error(`${toolName}.data.transactions[${transactionIndex}].compact.factScope must be "transaction"`);
|
|
182
|
+
}
|
|
183
|
+
if (compact.requestedAccountScoped !== false) {
|
|
184
|
+
throw new Error(`${toolName}.data.transactions[${transactionIndex}].compact.requestedAccountScoped must be false`);
|
|
185
|
+
}
|
|
186
|
+
const balanceChanges = requiredArray(compact.balanceChanges, `${toolName}.data.transactions[${transactionIndex}].compact.balanceChanges`);
|
|
187
|
+
compactBalanceChangeRowCount += balanceChanges.length;
|
|
188
|
+
balanceChanges.forEach((changeValue, changeIndex) => {
|
|
189
|
+
const change = requiredObject(changeValue, `${toolName}.data.transactions[${transactionIndex}].compact.balanceChanges[${changeIndex}]`);
|
|
190
|
+
if (change.count === undefined) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const count = requiredPositiveInteger(change.count, `${toolName}.data.transactions[${transactionIndex}].compact.balanceChanges[${changeIndex}].count`);
|
|
194
|
+
if (count > 1) {
|
|
195
|
+
compactAggregatedBalanceChangeRowCount += 1;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
fullDetailsReturned,
|
|
201
|
+
compactReturned,
|
|
202
|
+
compactBalanceChangeRowCount,
|
|
203
|
+
compactAggregatedBalanceChangeRowCount,
|
|
204
|
+
transactionContextCount,
|
|
205
|
+
requestedAccountEffectBalanceChangeRowCount,
|
|
206
|
+
requestedAccountEffectTruncatedTransactionCount
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function requiredObject(value, path) {
|
|
210
|
+
const object = objectValue(value);
|
|
211
|
+
if (object === undefined) {
|
|
212
|
+
throw new SmokeResponseShapeError(`${path} must be an object`);
|
|
213
|
+
}
|
|
214
|
+
return object;
|
|
215
|
+
}
|
|
216
|
+
function objectValue(value) {
|
|
217
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
218
|
+
? value
|
|
219
|
+
: undefined;
|
|
220
|
+
}
|
|
221
|
+
function requiredArray(value, path) {
|
|
222
|
+
if (!Array.isArray(value)) {
|
|
223
|
+
throw new SmokeResponseShapeError(`${path} must be an array`);
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
function requiredString(value, path) {
|
|
228
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
229
|
+
throw new SmokeResponseShapeError(`${path} must be a non-empty string`);
|
|
230
|
+
}
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
233
|
+
function requiredBoolean(value, path) {
|
|
234
|
+
if (typeof value !== "boolean") {
|
|
235
|
+
throw new SmokeResponseShapeError(`${path} must be a boolean`);
|
|
236
|
+
}
|
|
237
|
+
return value;
|
|
238
|
+
}
|
|
239
|
+
function requiredBooleanOrNull(value, path) {
|
|
240
|
+
if (typeof value !== "boolean" && value !== null) {
|
|
241
|
+
throw new SmokeResponseShapeError(`${path} must be a boolean or null`);
|
|
242
|
+
}
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
function requiredNonnegativeInteger(value, path) {
|
|
246
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
247
|
+
throw new SmokeResponseShapeError(`${path} must be a nonnegative integer`);
|
|
248
|
+
}
|
|
249
|
+
return value;
|
|
250
|
+
}
|
|
251
|
+
function requiredPositiveInteger(value, path) {
|
|
252
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
253
|
+
throw new SmokeResponseShapeError(`${path} must be a positive integer`);
|
|
254
|
+
}
|
|
255
|
+
return value;
|
|
256
|
+
}
|
|
257
|
+
function requiredAccountSource(value, path) {
|
|
258
|
+
if (value === "active_account_context" || value === "explicit_filter") {
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
throw new SmokeResponseShapeError(`${path} must be active_account_context or explicit_filter`);
|
|
262
|
+
}
|
|
263
|
+
function requiredRelationship(value, path) {
|
|
264
|
+
if (value === "affected" || value === "sent") {
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
267
|
+
throw new SmokeResponseShapeError(`${path} must be affected or sent`);
|
|
268
|
+
}
|
|
269
|
+
function requiredBalanceChangeCompleteness(value, path) {
|
|
270
|
+
if (value === "complete" || value === "truncated" || value === "unavailable") {
|
|
271
|
+
return value;
|
|
272
|
+
}
|
|
273
|
+
throw new SmokeResponseShapeError(`${path} must be complete, truncated, or unavailable`);
|
|
274
|
+
}
|
|
275
|
+
function requiredAccountBalanceChangeEvidence(value, path) {
|
|
276
|
+
if (value === "account_balance_changes_returned" ||
|
|
277
|
+
value === "no_account_balance_changes_returned" ||
|
|
278
|
+
value === "incomplete_account_balance_changes" ||
|
|
279
|
+
value === "account_balance_changes_unavailable") {
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
throw new SmokeResponseShapeError(`${path} must be a valid account balance-change evidence marker`);
|
|
283
|
+
}
|
|
284
|
+
function requiredAccountBalanceChangeInferencePolicy(value, path) {
|
|
285
|
+
if (value === "use_returned_account_balance_changes" ||
|
|
286
|
+
value === "no_account_balance_changes_in_complete_details" ||
|
|
287
|
+
value === "do_not_infer_from_transaction_context") {
|
|
288
|
+
return value;
|
|
289
|
+
}
|
|
290
|
+
throw new SmokeResponseShapeError(`${path} must be a valid account balance-change inference policy`);
|
|
291
|
+
}
|
|
292
|
+
function expectedAccountBalanceChangeInferencePolicy(evidence) {
|
|
293
|
+
if (evidence === "account_balance_changes_returned") {
|
|
294
|
+
return "use_returned_account_balance_changes";
|
|
295
|
+
}
|
|
296
|
+
if (evidence === "no_account_balance_changes_returned") {
|
|
297
|
+
return "no_account_balance_changes_in_complete_details";
|
|
298
|
+
}
|
|
299
|
+
return "do_not_infer_from_transaction_context";
|
|
300
|
+
}
|
|
301
|
+
function requiredLiteral(value, expected, path) {
|
|
302
|
+
if (value === expected) {
|
|
303
|
+
return expected;
|
|
304
|
+
}
|
|
305
|
+
throw new SmokeResponseShapeError(`${path} must be ${expected}`);
|
|
306
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { INTENT_PLAN_FACTORIES, resolveIntentPlanFactory, swapIntentInputSchema } from "../adapters/intentPlanFactories.js";
|
|
2
|
+
import { computeReviewStateWithPrivateArtifacts } from "../core/review/reviewComputation.js";
|
|
3
|
+
export class SmokeReviewConfigError extends Error {
|
|
4
|
+
}
|
|
5
|
+
const requiredSwapInputs = ["SMOKE_SWAP_PROTOCOL", "SMOKE_SWAP_FROM_SYMBOL", "SMOKE_SWAP_TO_SYMBOL", "SMOKE_SWAP_AMOUNT_DISPLAY"];
|
|
6
|
+
/** Optional read-only review computation. This never establishes a wallet
|
|
7
|
+
* connection; the caller owns the explicit temporary read-account fixture. */
|
|
8
|
+
export async function computeSmokeReview(options) {
|
|
9
|
+
const { env, sessions, computation, account } = options;
|
|
10
|
+
const rawSlippage = env.SMOKE_SWAP_MAX_SLIPPAGE_BPS;
|
|
11
|
+
if (rawSlippage !== undefined && (!Number.isInteger(Number(rawSlippage)) || Number(rawSlippage) < 1)) {
|
|
12
|
+
throw new SmokeReviewConfigError("SMOKE_SWAP_MAX_SLIPPAGE_BPS must be a positive integer.");
|
|
13
|
+
}
|
|
14
|
+
const missing = requiredSwapInputs.filter((name) => env[name] === undefined);
|
|
15
|
+
if (missing.length === requiredSwapInputs.length)
|
|
16
|
+
return { status: "not_run", notRunReason: "missing_env", requiredEnv: [...requiredSwapInputs] };
|
|
17
|
+
if (missing.length)
|
|
18
|
+
throw new SmokeReviewConfigError(`Partial account-bound swap smoke configuration; missing: ${missing.join(", ")}`);
|
|
19
|
+
// Preserve the utility's existing 50 bps CLI default; it is not a product
|
|
20
|
+
// recommendation or a silently selected protocol/settlement asset.
|
|
21
|
+
const parsed = swapIntentInputSchema.safeParse({ type: "swap", protocol: env.SMOKE_SWAP_PROTOCOL,
|
|
22
|
+
from: { symbol: env.SMOKE_SWAP_FROM_SYMBOL, amount: env.SMOKE_SWAP_AMOUNT_DISPLAY },
|
|
23
|
+
to: { symbol: env.SMOKE_SWAP_TO_SYMBOL }, maxSlippageBps: Number(env.SMOKE_SWAP_MAX_SLIPPAGE_BPS ?? "50") });
|
|
24
|
+
if (!parsed.success)
|
|
25
|
+
throw new SmokeReviewConfigError("The optional swap smoke inputs are invalid.");
|
|
26
|
+
const { protocol, ...intent } = parsed.data;
|
|
27
|
+
const resolution = resolveIntentPlanFactory(INTENT_PLAN_FACTORIES, "swap", protocol);
|
|
28
|
+
if (resolution.status !== "resolved")
|
|
29
|
+
throw new SmokeReviewConfigError("SMOKE_SWAP_PROTOCOL must select a supported swap adapter.");
|
|
30
|
+
const now = options.now ?? new Date(), plan = resolution.factory.createPlan(intent, now);
|
|
31
|
+
const created = await sessions.createReviewSession([plan], now);
|
|
32
|
+
await sessions.recordWalletConnected(created.session.id, account, now);
|
|
33
|
+
const computed = await computeReviewStateWithPrivateArtifacts({ reviewSessionId: created.session.id, plan, account, now }, computation);
|
|
34
|
+
await sessions.recordReviewStateWithArtifacts(created.session.id, computed.state, computed.privateArtifacts, now);
|
|
35
|
+
return { status: "ok", reviewStatus: computed.state.status, blockedReason: computed.state.blockedReason,
|
|
36
|
+
completedStageCount: computed.state.adapterLifecycle?.completedStages.length ?? 0,
|
|
37
|
+
missingStages: computed.state.adapterLifecycle?.missingStages ?? [],
|
|
38
|
+
reviewDataEmitted: computed.state.transactionReviewData !== undefined,
|
|
39
|
+
failedCheckIds: computed.state.checks.filter((check) => check.status === "fail").map((check) => check.id) };
|
|
40
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { loadBootConfig } from "./config.js";
|
|
4
|
+
import { createRuntimeApplication } from "./application.js";
|
|
5
|
+
import { createStderrLogger, flushStderr } from "./logger.js";
|
|
6
|
+
import { startOrDeferReviewServer } from "./reviewServerAcquire.js";
|
|
7
|
+
import { loadControlIdentity } from "./shared/control.js";
|
|
8
|
+
import { probeAuthenticatedServer } from "./shared/authenticatedFetch.js";
|
|
9
|
+
import { startSharedServer } from "./shared/server.js";
|
|
10
|
+
import { startSharedStdio } from "./shared/stdio.js";
|
|
11
|
+
import { RuntimeTermination } from "./runtimeTermination.js";
|
|
12
|
+
import { WALLETCONNECT_PROJECT_ID } from "./walletConnectConfig.js";
|
|
13
|
+
async function main() {
|
|
14
|
+
const logger = createStderrLogger("runtime");
|
|
15
|
+
let shared;
|
|
16
|
+
let bridge;
|
|
17
|
+
let ready, failed;
|
|
18
|
+
const backendReady = new Promise((resolve, reject) => { ready = resolve; failed = reject; });
|
|
19
|
+
void backendReady.catch(() => { });
|
|
20
|
+
const termination = new RuntimeTermination({ logger, flush: flushStderr, exit: (code) => process.exit(code),
|
|
21
|
+
close: async () => {
|
|
22
|
+
failed(new Error("Runtime closed before startup completed."));
|
|
23
|
+
const results = await Promise.allSettled([bridge?.close(), shared?.close()]);
|
|
24
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
25
|
+
if (failure?.status === "rejected")
|
|
26
|
+
throw failure.reason;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
let stage = "configuration";
|
|
30
|
+
const stdio = new StdioServerTransport();
|
|
31
|
+
const closeForClient = () => { void termination.request({ kind: "client" }); };
|
|
32
|
+
process.stdin.once("end", closeForClient);
|
|
33
|
+
process.stdin.once("close", closeForClient);
|
|
34
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
35
|
+
process.once(signal, () => { void termination.request({ kind: "signal", code: signal === "SIGINT" ? 130 : 143 }); });
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const bootConfig = loadBootConfig();
|
|
39
|
+
stage = "control_identity";
|
|
40
|
+
const controlReady = loadControlIdentity(bootConfig.activityDatabasePath, {
|
|
41
|
+
network: bootConfig.network, chainIdentifier: bootConfig.expectedChainIdentifier,
|
|
42
|
+
walletConnectProjectId: WALLETCONNECT_PROJECT_ID,
|
|
43
|
+
grpcOverride: process.env.SUI_GRPC_URL ?? null, graphqlOverride: process.env.SUI_GRAPHQL_URL ?? null
|
|
44
|
+
});
|
|
45
|
+
void controlReady.catch(() => { });
|
|
46
|
+
// The SDK owns stdin from the start, including fragmented initialization
|
|
47
|
+
// and EOF while backend startup is pending. No second reader buffers bytes.
|
|
48
|
+
stage = "stdio_start";
|
|
49
|
+
bridge = await startSharedStdio({ stdio, port: bootConfig.reviewPort, control: controlReady, ready: backendReady,
|
|
50
|
+
onError: (error) => logger.error("MCP transport failed", { error: error.message }) });
|
|
51
|
+
const protocolClosed = stdio.onclose;
|
|
52
|
+
stdio.onclose = () => { protocolClosed?.(); closeForClient(); };
|
|
53
|
+
if (termination.requested) {
|
|
54
|
+
await bridge.close();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
stage = "control_identity";
|
|
58
|
+
const control = await controlReady;
|
|
59
|
+
if (termination.requested)
|
|
60
|
+
return;
|
|
61
|
+
stage = "shared_server_start";
|
|
62
|
+
shared = await startOrDeferReviewServer((port) => startSharedServer({
|
|
63
|
+
port, control,
|
|
64
|
+
createApplication: (instanceId) => createRuntimeApplication(bootConfig, logger, instanceId),
|
|
65
|
+
onError: (error) => logger.error("shared server request failed", { error: error instanceof Error ? error.message : "unknown error" })
|
|
66
|
+
}), bootConfig.reviewPort, {
|
|
67
|
+
probeIdentity: async (port) => {
|
|
68
|
+
try {
|
|
69
|
+
return await probeAuthenticatedServer(port, control);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
delay: (ms) => new Promise((resolve) => { const timer = setTimeout(resolve, ms); timer.unref(); }),
|
|
76
|
+
currentPid: process.pid, serviceName: "sui-mcp", logger,
|
|
77
|
+
onFailure: (error) => { void termination.request({ kind: "failure", stage: "owner_acquisition", error }); }
|
|
78
|
+
});
|
|
79
|
+
if (termination.requested) {
|
|
80
|
+
await shared.close();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
ready();
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
failed(error);
|
|
87
|
+
await termination.request({ kind: "failure", stage, error });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
void main();
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
2
|
+
import { SuiGraphQLClient } from "@mysten/sui/graphql";
|
|
3
|
+
import { MAX_SUI_GRAPHQL_URL_LENGTH, MAX_SUI_GRPC_URL_LENGTH, SuiEndpointError, parseGraphqlUrl, parseGrpcUrl } from "../core/suiEndpoint.js";
|
|
4
|
+
export { MAX_SUI_GRAPHQL_URL_LENGTH, MAX_SUI_GRPC_URL_LENGTH, SuiEndpointError, parseGraphqlUrl, parseGrpcUrl };
|
|
5
|
+
export const SUI_MAINNET_CHAIN_IDENTIFIER = "4btiuiMPvEENsttpZC7CZ53DruC3MAgfznDbASZ7DR6S";
|
|
6
|
+
export const DEFAULT_SUI_GRPC_URL = "https://fullnode.mainnet.sui.io:443";
|
|
7
|
+
export const DEFAULT_SUI_GRAPHQL_URL = "https://graphql.mainnet.sui.io/graphql";
|
|
8
|
+
export const DEFAULT_SUI_GRPC_ENDPOINT_VERIFY_TIMEOUT_MS = 10_000;
|
|
9
|
+
export const DEFAULT_SUI_GRAPHQL_ENDPOINT_VERIFY_TIMEOUT_MS = 10_000;
|
|
10
|
+
export async function verifyMainnetGrpcEndpoint(input) {
|
|
11
|
+
const url = parseGrpcUrl(input.url);
|
|
12
|
+
const expectedChainIdentifier = input.expectedChainIdentifier ?? SUI_MAINNET_CHAIN_IDENTIFIER;
|
|
13
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_SUI_GRPC_ENDPOINT_VERIFY_TIMEOUT_MS;
|
|
14
|
+
const client = new SuiGrpcClient({
|
|
15
|
+
baseUrl: url,
|
|
16
|
+
network: "mainnet"
|
|
17
|
+
});
|
|
18
|
+
let timeout;
|
|
19
|
+
try {
|
|
20
|
+
const chainIdentifier = await Promise.race([
|
|
21
|
+
client.core.getChainIdentifier().then((result) => result.chainIdentifier),
|
|
22
|
+
new Promise((_, reject) => {
|
|
23
|
+
timeout = setTimeout(() => {
|
|
24
|
+
reject(new SuiEndpointError("endpoint_timeout", `Sui gRPC endpoint verification timed out after ${timeoutMs}ms`, { timeoutMs, url }));
|
|
25
|
+
}, timeoutMs);
|
|
26
|
+
})
|
|
27
|
+
]);
|
|
28
|
+
if (chainIdentifier !== expectedChainIdentifier) {
|
|
29
|
+
throw new SuiEndpointError("chain_identifier_mismatch", `Sui chain identifier mismatch: expected mainnet, got ${chainIdentifier}`, { expectedChainIdentifier, chainIdentifier, url });
|
|
30
|
+
}
|
|
31
|
+
return { client, chainIdentifier };
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error instanceof SuiEndpointError) {
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
throw new SuiEndpointError("endpoint_unreachable", "Could not verify Sui mainnet gRPC endpoint", {
|
|
38
|
+
error: error instanceof Error ? error.message : String(error),
|
|
39
|
+
url
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
if (timeout) {
|
|
44
|
+
clearTimeout(timeout);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function verifyMainnetGraphqlEndpoint(input) {
|
|
49
|
+
const url = parseGraphqlUrl(input.url);
|
|
50
|
+
const expectedChainIdentifier = input.expectedChainIdentifier ?? SUI_MAINNET_CHAIN_IDENTIFIER;
|
|
51
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_SUI_GRAPHQL_ENDPOINT_VERIFY_TIMEOUT_MS;
|
|
52
|
+
const client = new SuiGraphQLClient({
|
|
53
|
+
url,
|
|
54
|
+
network: "mainnet",
|
|
55
|
+
...(input.fetch ? { fetch: input.fetch } : {})
|
|
56
|
+
});
|
|
57
|
+
let timeout;
|
|
58
|
+
try {
|
|
59
|
+
const chainIdentifier = await Promise.race([
|
|
60
|
+
client.query({
|
|
61
|
+
query: "query SuiMcpChainIdentifier { chainIdentifier }",
|
|
62
|
+
variables: {}
|
|
63
|
+
}).then((result) => {
|
|
64
|
+
const value = result.data?.chainIdentifier;
|
|
65
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
66
|
+
throw new SuiEndpointError("endpoint_unreachable", "Sui GraphQL endpoint did not return chainIdentifier", {
|
|
67
|
+
url
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}),
|
|
72
|
+
new Promise((_, reject) => {
|
|
73
|
+
timeout = setTimeout(() => {
|
|
74
|
+
reject(new SuiEndpointError("endpoint_timeout", `Sui GraphQL endpoint verification timed out after ${timeoutMs}ms`, { timeoutMs, url }));
|
|
75
|
+
}, timeoutMs);
|
|
76
|
+
})
|
|
77
|
+
]);
|
|
78
|
+
if (chainIdentifier !== expectedChainIdentifier) {
|
|
79
|
+
throw new SuiEndpointError("chain_identifier_mismatch", `Sui chain identifier mismatch: expected mainnet, got ${chainIdentifier}`, { expectedChainIdentifier, chainIdentifier, url });
|
|
80
|
+
}
|
|
81
|
+
return { client, chainIdentifier };
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof SuiEndpointError) {
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
throw new SuiEndpointError("endpoint_unreachable", "Could not verify Sui mainnet GraphQL endpoint", {
|
|
88
|
+
error: error instanceof Error ? error.message : String(error),
|
|
89
|
+
url
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
if (timeout) {
|
|
94
|
+
clearTimeout(timeout);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|