@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,48 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, extname, resolve, sep } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { sendJson } from "./http.js";
|
|
5
|
+
export async function serveReviewAsset(response, assetsDir, requestPath) {
|
|
6
|
+
let decoded;
|
|
7
|
+
try {
|
|
8
|
+
decoded = decodeURIComponent(requestPath);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
sendJson(response, 404, { error: "not_found" });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const root = resolve(assetsDir);
|
|
15
|
+
const filePath = resolve(root, decoded);
|
|
16
|
+
if (filePath !== root && !filePath.startsWith(`${root}${sep}`)) {
|
|
17
|
+
sendJson(response, 404, { error: "not_found" });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const content = await readFile(filePath);
|
|
22
|
+
response.writeHead(200, {
|
|
23
|
+
"content-type": contentTypeForAsset(filePath),
|
|
24
|
+
// Entry assets keep stable names across rebuilds; never let the browser
|
|
25
|
+
// serve a stale bundle for a loopback dev/review surface.
|
|
26
|
+
"cache-control": "no-store"
|
|
27
|
+
});
|
|
28
|
+
response.end(content);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
sendJson(response, 404, { error: "not_found" });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function contentTypeForAsset(filePath) {
|
|
35
|
+
switch (extname(filePath)) {
|
|
36
|
+
case ".js":
|
|
37
|
+
return "text/javascript; charset=utf-8";
|
|
38
|
+
case ".css":
|
|
39
|
+
return "text/css; charset=utf-8";
|
|
40
|
+
case ".svg":
|
|
41
|
+
return "image/svg+xml";
|
|
42
|
+
default:
|
|
43
|
+
return "application/octet-stream";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function defaultReviewAssetsDir() {
|
|
47
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "../review-app");
|
|
48
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Page HTML shells served by the review server. Each page is a minimal document:
|
|
2
|
+
// the shared <head> (charset, viewport, title, favicon, the page stylesheet) and
|
|
3
|
+
// a mount element plus the page's module script. The shared UI module renders the
|
|
4
|
+
// header, navigation, and content into the mount on the public pages it has
|
|
5
|
+
// already migrated; pages not yet migrated keep their existing body. `data-theme`
|
|
6
|
+
// defaults to light on <html> so there is no unstyled flash before the theme
|
|
7
|
+
// helper applies the stored or preferred theme.
|
|
8
|
+
function pageDocument(options) {
|
|
9
|
+
const uiLink = options.ui ? ` <link rel="stylesheet" href="/review-assets/ui.css">\n` : "";
|
|
10
|
+
return `<!doctype html>
|
|
11
|
+
<html lang="en" data-theme="light">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="utf-8">
|
|
14
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
15
|
+
<title>${options.title}</title>
|
|
16
|
+
<link rel="icon" type="image/svg+xml" href="/review-assets/favicon.svg">
|
|
17
|
+
${uiLink} <link rel="stylesheet" href="/review-assets/${options.css}">
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
${options.body}
|
|
21
|
+
<script type="module" src="/review-assets/${options.js}"></script>
|
|
22
|
+
</body>
|
|
23
|
+
</html>`;
|
|
24
|
+
}
|
|
25
|
+
export function settingsHtml(sessionId) {
|
|
26
|
+
return pageDocument({
|
|
27
|
+
title: "Sui MCP Settings",
|
|
28
|
+
css: "settings.css",
|
|
29
|
+
js: "settings.js",
|
|
30
|
+
ui: true,
|
|
31
|
+
body: ` <div id="settings-app" data-settings-session-id="${escapeHtml(sessionId)}"></div>`
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function escapeHtml(value) {
|
|
35
|
+
return value.replace(/[&<>"']/g, (char) => {
|
|
36
|
+
const replacements = {
|
|
37
|
+
"&": "&",
|
|
38
|
+
"<": "<",
|
|
39
|
+
">": ">",
|
|
40
|
+
'"': """,
|
|
41
|
+
"'": "'"
|
|
42
|
+
};
|
|
43
|
+
return replacements[char] ?? char;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const MAX_JSON_BODY_BYTES = 64 * 1024;
|
|
2
|
+
export class HttpError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
constructor(status, code) {
|
|
6
|
+
super(code);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function sendHtml(response, body, headers = {}, status = 200) {
|
|
12
|
+
response.writeHead(status, { "content-type": "text/html; charset=utf-8", ...headers });
|
|
13
|
+
response.end(body);
|
|
14
|
+
}
|
|
15
|
+
export function sendJson(response, status, body) {
|
|
16
|
+
response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
17
|
+
response.end(JSON.stringify(body));
|
|
18
|
+
}
|
|
19
|
+
export async function readJsonBody(request, maxBytes = MAX_JSON_BODY_BYTES) {
|
|
20
|
+
const chunks = [];
|
|
21
|
+
let totalBytes = 0;
|
|
22
|
+
for await (const chunk of request) {
|
|
23
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
24
|
+
totalBytes += buffer.length;
|
|
25
|
+
if (totalBytes > maxBytes) {
|
|
26
|
+
throw new HttpError(413, "payload_too_large");
|
|
27
|
+
}
|
|
28
|
+
chunks.push(buffer);
|
|
29
|
+
}
|
|
30
|
+
if (chunks.length === 0) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
34
|
+
let parsed;
|
|
35
|
+
try {
|
|
36
|
+
parsed = JSON.parse(raw);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new HttpError(400, "invalid_json");
|
|
40
|
+
}
|
|
41
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
42
|
+
? parsed
|
|
43
|
+
: raiseHttpError(400, "invalid_body_shape");
|
|
44
|
+
}
|
|
45
|
+
export function raiseHttpError(status, code) {
|
|
46
|
+
throw new HttpError(status, code);
|
|
47
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export function validateHostOrigin(request, policy) {
|
|
2
|
+
const hostHeader = request.headers.host;
|
|
3
|
+
const host = parseHost(hostHeader);
|
|
4
|
+
if (!host || !policy.allowedHostnames.includes(host.hostname)) {
|
|
5
|
+
return { ok: false, status: 400, reason: "invalid_host" };
|
|
6
|
+
}
|
|
7
|
+
const originHeader = request.headers.origin;
|
|
8
|
+
if (!originHeader) {
|
|
9
|
+
return { ok: true };
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const origin = new URL(originHeader);
|
|
13
|
+
if (origin.username || origin.password || origin.pathname !== "/" || origin.search || origin.hash) {
|
|
14
|
+
return { ok: false, status: 403, reason: "invalid_origin" };
|
|
15
|
+
}
|
|
16
|
+
if (origin.protocol !== "http:" && origin.protocol !== "https:") {
|
|
17
|
+
return { ok: false, status: 403, reason: "invalid_origin" };
|
|
18
|
+
}
|
|
19
|
+
if (!policy.allowedHostnames.includes(origin.hostname)) {
|
|
20
|
+
return { ok: false, status: 403, reason: "invalid_origin" };
|
|
21
|
+
}
|
|
22
|
+
if (originPort(origin) !== host.port) {
|
|
23
|
+
return { ok: false, status: 403, reason: "origin_port_mismatch" };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { ok: false, status: 403, reason: "invalid_origin" };
|
|
28
|
+
}
|
|
29
|
+
return { ok: true };
|
|
30
|
+
}
|
|
31
|
+
function parseHost(hostHeader) {
|
|
32
|
+
if (!hostHeader) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const url = new URL(`http://${hostHeader}`);
|
|
37
|
+
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return { hostname: url.hostname, port: url.port || "80" };
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function originPort(origin) {
|
|
47
|
+
return origin.port || (origin.protocol === "https:" ? "443" : "80");
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ALLOWED_HOSTNAMES = ["127.0.0.1", "localhost"];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { validateHostOrigin } from "./middleware/hostOrigin.js";
|
|
3
|
+
import { defaultReviewAssetsDir, serveReviewAsset } from "./assets.js";
|
|
4
|
+
import { settingsHtml } from "./html.js";
|
|
5
|
+
import { HttpError, sendHtml, sendJson } from "./http.js";
|
|
6
|
+
import { ALLOWED_HOSTNAMES } from "./reviewServerPolicy.js";
|
|
7
|
+
import { routeSettingsApi } from "./settingsApi.js";
|
|
8
|
+
export function createReviewRequestHandler(options) {
|
|
9
|
+
return async (request, response) => {
|
|
10
|
+
try {
|
|
11
|
+
const source = validateHostOrigin(request, { allowedHostnames: ALLOWED_HOSTNAMES });
|
|
12
|
+
if (!source.ok) {
|
|
13
|
+
sendJson(response, source.status, { error: source.reason });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const url = new URL(request.url ?? "/", "http://localhost");
|
|
17
|
+
if (url.searchParams.has("token")) {
|
|
18
|
+
sendJson(response, 400, { error: "token_query_not_supported" });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const settings = /^\/settings\/([^/]+)$/.exec(url.pathname);
|
|
22
|
+
if (request.method === "GET" && settings?.[1]) {
|
|
23
|
+
sendHtml(response, settingsHtml(settings[1]), { "content-security-policy": [
|
|
24
|
+
"default-src 'none'", "base-uri 'none'", "connect-src 'self'", "script-src 'self'",
|
|
25
|
+
"style-src 'self'", "img-src 'self' data:", "form-action 'none'"
|
|
26
|
+
].join("; ") });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const asset = /^\/review-assets\/(.+)$/.exec(url.pathname);
|
|
30
|
+
if (request.method === "GET" && asset?.[1]) {
|
|
31
|
+
await serveReviewAsset(response, options.reviewAssetsDir ?? defaultReviewAssetsDir(), asset[1]);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const route = /^\/api\/settings\/([^/]+)(?:\/(.*))?$/.exec(url.pathname);
|
|
35
|
+
if (route?.[1]) {
|
|
36
|
+
const routes = {
|
|
37
|
+
"": "status", "clear-active-account": "clearActiveAccount", "sui-grpc-url": "setSuiGrpcUrl",
|
|
38
|
+
"sui-grpc-url/restore-default": "restoreDefaultSuiGrpcUrl", "sui-graphql-url": "setSuiGraphqlUrl",
|
|
39
|
+
"sui-graphql-url/restore-default": "restoreDefaultSuiGraphqlUrl", "local-data/export": "exportLocalData",
|
|
40
|
+
"local-data/import/preview": "previewImport", "local-data/import": "importLocalData", "local-data/reset": "resetLocalData"
|
|
41
|
+
};
|
|
42
|
+
const match = routes[route[2] ?? ""];
|
|
43
|
+
if (match) {
|
|
44
|
+
await routeSettingsApi(request, response, options, url, { [match]: route[1] });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
sendJson(response, 404, { error: "not_found" });
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof HttpError) {
|
|
52
|
+
sendJson(response, error.status, { error: error.code });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
options.logger.error("settings request failed", { stage: "settings_http" });
|
|
56
|
+
sendJson(response, 500, { error: "internal_error" });
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function createReviewHttpServer(options) {
|
|
61
|
+
const handler = createReviewRequestHandler(options);
|
|
62
|
+
const server = createServer((request, response) => { void handler(request, response); });
|
|
63
|
+
return {
|
|
64
|
+
start(port) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
server.once("error", reject);
|
|
67
|
+
server.listen(port, options.host, () => resolve({ host: options.host, port: server.address().port,
|
|
68
|
+
close: () => new Promise((done, failed) => { server.close((error) => error ? failed(error) : done()); }) }));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { LocalDataError } from "../core/activity/localDataService.js";
|
|
2
|
+
import { LocalSettingsError } from "../core/preferences/preferencesStore.js";
|
|
3
|
+
import { readReviewToken } from "./middleware/reviewToken.js";
|
|
4
|
+
import { HttpError, MAX_JSON_BODY_BYTES, readJsonBody, sendJson } from "./http.js";
|
|
5
|
+
const MAX_SETTINGS_BODY_BYTES = MAX_JSON_BODY_BYTES;
|
|
6
|
+
const MAX_IMPORT_BODY_BYTES = 16 * 1024 * 1024;
|
|
7
|
+
export async function routeSettingsApi(request, response, options, _url, matches) {
|
|
8
|
+
// Host/Origin and a query token are rejected once by the global guards in
|
|
9
|
+
// routeRequest before this handler runs (see the "validate once" guards
|
|
10
|
+
// there), so this handler does not re-check them.
|
|
11
|
+
const settings = requireSettingsDeps(options);
|
|
12
|
+
const sessionId = matches.status ??
|
|
13
|
+
matches.clearActiveAccount ??
|
|
14
|
+
matches.setSuiGrpcUrl ??
|
|
15
|
+
matches.restoreDefaultSuiGrpcUrl ??
|
|
16
|
+
matches.setSuiGraphqlUrl ??
|
|
17
|
+
matches.restoreDefaultSuiGraphqlUrl ??
|
|
18
|
+
matches.exportLocalData ??
|
|
19
|
+
matches.previewImport ??
|
|
20
|
+
matches.importLocalData ??
|
|
21
|
+
matches.resetLocalData;
|
|
22
|
+
if (!sessionId) {
|
|
23
|
+
throw new HttpError(404, "not_found");
|
|
24
|
+
}
|
|
25
|
+
const token = readReviewToken(request.headers);
|
|
26
|
+
if (!token || !(await options.store.validateSettingsToken(sessionId, token))) {
|
|
27
|
+
sendJson(response, 401, { error: "invalid_settings_token" });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (request.method === "GET" && matches.status) {
|
|
31
|
+
const [localSettings, activeAccount, dataCounts] = await Promise.all([
|
|
32
|
+
mapLocalSettingsError(() => settings.localSettings.getLocalSettings()),
|
|
33
|
+
settings.activityStore.getActiveAccount(),
|
|
34
|
+
settings.localData.getDataCounts()
|
|
35
|
+
]);
|
|
36
|
+
sendJson(response, 200, {
|
|
37
|
+
settingsSessionId: sessionId,
|
|
38
|
+
server: settings.serverInfo,
|
|
39
|
+
localSettings,
|
|
40
|
+
activeAccount: activeAccount
|
|
41
|
+
? {
|
|
42
|
+
account: activeAccount.address,
|
|
43
|
+
source: activeAccount.source,
|
|
44
|
+
setAt: activeAccount.setAt
|
|
45
|
+
}
|
|
46
|
+
: undefined,
|
|
47
|
+
dataCounts,
|
|
48
|
+
restartRequired: localSettings.suiGrpcUrl.appliesAfter === "mcp_server_restart" ||
|
|
49
|
+
localSettings.suiGraphqlUrl.appliesAfter === "mcp_server_restart"
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (request.method === "POST" && matches.clearActiveAccount) {
|
|
54
|
+
await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
55
|
+
await settings.activityStore.clearActiveAccount();
|
|
56
|
+
sendJson(response, 200, { status: "cleared" });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (request.method === "POST" && matches.setSuiGrpcUrl) {
|
|
60
|
+
const body = await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
61
|
+
const urlValue = typeof body.url === "string" ? body.url : undefined;
|
|
62
|
+
if (!urlValue) {
|
|
63
|
+
sendJson(response, 400, { error: "input_invalid" });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const result = await mapLocalSettingsError(() => settings.localSettings.setSuiGrpcUrl(urlValue));
|
|
67
|
+
sendJson(response, 200, result);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (request.method === "POST" && matches.restoreDefaultSuiGrpcUrl) {
|
|
71
|
+
await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
72
|
+
const result = await mapLocalSettingsError(() => settings.localSettings.resetSuiGrpcUrl());
|
|
73
|
+
sendJson(response, 200, result);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (request.method === "POST" && matches.setSuiGraphqlUrl) {
|
|
77
|
+
const body = await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
78
|
+
const urlValue = typeof body.url === "string" ? body.url : undefined;
|
|
79
|
+
if (!urlValue) {
|
|
80
|
+
sendJson(response, 400, { error: "input_invalid" });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const result = await mapLocalSettingsError(() => settings.localSettings.setSuiGraphqlUrl(urlValue));
|
|
84
|
+
sendJson(response, 200, result);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (request.method === "POST" && matches.restoreDefaultSuiGraphqlUrl) {
|
|
88
|
+
await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
89
|
+
const result = await mapLocalSettingsError(() => settings.localSettings.resetSuiGraphqlUrl());
|
|
90
|
+
sendJson(response, 200, result);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (request.method === "GET" && matches.exportLocalData) {
|
|
94
|
+
const envelope = await mapLocalDataError(() => settings.localData.exportLocalData());
|
|
95
|
+
sendJson(response, 200, envelope);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (request.method === "POST" && matches.previewImport) {
|
|
99
|
+
const body = await readJsonBody(request, MAX_IMPORT_BODY_BYTES);
|
|
100
|
+
const preview = await mapLocalDataError(() => settings.localData.previewImportLocalData(body));
|
|
101
|
+
sendJson(response, 200, preview);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (request.method === "POST" && matches.importLocalData) {
|
|
105
|
+
const body = await readJsonBody(request, MAX_IMPORT_BODY_BYTES);
|
|
106
|
+
const result = await mapLocalDataError(() => settings.localData.importLocalDataReplace(body));
|
|
107
|
+
sendJson(response, 200, result);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (request.method === "POST" && matches.resetLocalData) {
|
|
111
|
+
await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
112
|
+
const result = await mapLocalDataError(() => settings.localData.resetLocalData());
|
|
113
|
+
sendJson(response, 200, result);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
sendJson(response, 404, { error: "not_found" });
|
|
117
|
+
}
|
|
118
|
+
function requireSettingsDeps(options) {
|
|
119
|
+
if (!options.activityStore || !options.localSettings || !options.localData || !options.serverInfo) {
|
|
120
|
+
throw new HttpError(404, "not_found");
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
activityStore: options.activityStore,
|
|
124
|
+
localSettings: options.localSettings,
|
|
125
|
+
localData: options.localData,
|
|
126
|
+
serverInfo: options.serverInfo
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async function mapLocalSettingsError(operation) {
|
|
130
|
+
try {
|
|
131
|
+
return await operation();
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (error instanceof LocalSettingsError) {
|
|
135
|
+
throw new HttpError(statusForLocalSettingsError(error), error.kind);
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function statusForLocalSettingsError(error) {
|
|
141
|
+
return error.kind === "input_invalid" ? 400 : 500;
|
|
142
|
+
}
|
|
143
|
+
async function mapLocalDataError(operation) {
|
|
144
|
+
try {
|
|
145
|
+
return await operation();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
if (error instanceof LocalDataError) {
|
|
149
|
+
if (error.details.reason === "wallet_request_unsettled")
|
|
150
|
+
throw new HttpError(409, "wallet_request_unsettled");
|
|
151
|
+
throw new HttpError(statusForLocalDataError(error), error.kind);
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function statusForLocalDataError(error) {
|
|
157
|
+
return error.kind === "input_invalid" ? 400 : 500;
|
|
158
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { createRuntimeReviewDependencies } from "./reviewDependencies.js";
|
|
2
|
+
import { acquireDataDirectoryOwner } from "./shared/ownerLease.js";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { WalletSdkProcess } from "./walletSdkProcess.js";
|
|
6
|
+
import { WALLETCONNECT_PROJECT_ID } from "./walletConnectConfig.js";
|
|
7
|
+
import { WalletWorkflow } from "../core/session/walletWorkflow.js";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { SqliteActivityStore } from "../core/activity/sqliteActivityStore.js";
|
|
10
|
+
import { validateSupportedAdapterLifecycle } from "../adapters/adapterLifecycleValidators.js";
|
|
11
|
+
import { ADAPTER_PROMPT_SURFACES } from "../adapters/adapterPromptSurfaces.js";
|
|
12
|
+
import { TransactionActivityService } from "../core/activity/transactionActivityService.js";
|
|
13
|
+
import { createSuiReadService } from "../core/read/readService.js";
|
|
14
|
+
import { verifySuiChainReceipt } from "../core/action/suiChainReceiptVerifier.js";
|
|
15
|
+
import { readPublicChainReceipt } from "../core/action/suiChainReceiptReader.js";
|
|
16
|
+
import { LocalSessionStore } from "../core/session/sessionStore.js";
|
|
17
|
+
import { createMcpServer } from "../mcp/server.js";
|
|
18
|
+
import { SERVER_NAME, SERVER_NETWORK, SERVER_VERSION } from "../mcp/serverInfo.js";
|
|
19
|
+
import { createReviewRequestHandler } from "../review-server/server.js";
|
|
20
|
+
import { DEFAULT_SUI_GRAPHQL_URL, DEFAULT_SUI_GRPC_URL, composeRuntimeConfig } from "./config.js";
|
|
21
|
+
import { DeepbookOfficialIndexerSource } from "../core/read/deepbookOfficialIndexerSource.js";
|
|
22
|
+
import { RuntimeLocalSettingsService } from "./localSettingsService.js";
|
|
23
|
+
import { verifyMainnetGraphqlEndpoint, verifyMainnetGrpcEndpoint } from "./suiEndpoint.js";
|
|
24
|
+
import { GraphqlSuiTransactionActivitySource } from "./suiTransactionGraphqlSource.js";
|
|
25
|
+
import { createReadCardStore } from "../mcp-ui/readCards.js";
|
|
26
|
+
import { createDeepbookUsdcChartService } from "../core/read/deepbookUsdcChartService.js";
|
|
27
|
+
import { RuntimeDataAccess } from "./shared/dataAccess.js";
|
|
28
|
+
import { createInternalMcpHandler } from "./shared/mcpHttp.js";
|
|
29
|
+
export async function createRuntimeApplication(bootConfig, logger, ownerId = randomUUID()) {
|
|
30
|
+
const access = new RuntimeDataAccess();
|
|
31
|
+
const ownership = acquireDataDirectoryOwner(bootConfig.activityDatabasePath);
|
|
32
|
+
let walletRuntime;
|
|
33
|
+
let activityStore;
|
|
34
|
+
let cards;
|
|
35
|
+
let workflow;
|
|
36
|
+
let chart;
|
|
37
|
+
try {
|
|
38
|
+
const store = new SqliteActivityStore({
|
|
39
|
+
databasePath: bootConfig.activityDatabasePath,
|
|
40
|
+
guardDatabase: access.guardDatabase,
|
|
41
|
+
validateAdapterLifecycle: validateSupportedAdapterLifecycle
|
|
42
|
+
});
|
|
43
|
+
activityStore = store;
|
|
44
|
+
const preferencesRepository = store.createPreferencesRepository();
|
|
45
|
+
await preferencesRepository.ensureDefaultLocalSettings({
|
|
46
|
+
suiGrpcUrl: DEFAULT_SUI_GRPC_URL,
|
|
47
|
+
suiGraphqlUrl: DEFAULT_SUI_GRAPHQL_URL
|
|
48
|
+
});
|
|
49
|
+
const storedSuiGrpcUrl = await preferencesRepository.getSuiGrpcUrl();
|
|
50
|
+
const storedSuiGraphqlUrl = await preferencesRepository.getSuiGraphqlUrl();
|
|
51
|
+
const config = composeRuntimeConfig({
|
|
52
|
+
bootConfig,
|
|
53
|
+
env: process.env,
|
|
54
|
+
storedSuiGrpcUrl: storedSuiGrpcUrl?.value,
|
|
55
|
+
storedSuiGraphqlUrl: storedSuiGraphqlUrl?.value,
|
|
56
|
+
defaultSuiGrpcUrl: DEFAULT_SUI_GRPC_URL,
|
|
57
|
+
defaultSuiGraphqlUrl: DEFAULT_SUI_GRAPHQL_URL
|
|
58
|
+
});
|
|
59
|
+
const { client: suiClient, chainIdentifier } = await verifyMainnetGrpcEndpoint({
|
|
60
|
+
url: config.grpcUrl,
|
|
61
|
+
expectedChainIdentifier: config.expectedChainIdentifier
|
|
62
|
+
});
|
|
63
|
+
const localSettings = new RuntimeLocalSettingsService({
|
|
64
|
+
preferencesRepository,
|
|
65
|
+
env: process.env,
|
|
66
|
+
defaultSuiGrpcUrl: DEFAULT_SUI_GRPC_URL,
|
|
67
|
+
defaultSuiGraphqlUrl: DEFAULT_SUI_GRAPHQL_URL,
|
|
68
|
+
bootSuiGrpcUrl: config.suiGrpcUrl,
|
|
69
|
+
bootSuiGraphqlUrl: config.suiGraphqlUrl
|
|
70
|
+
});
|
|
71
|
+
const workflowRecords = store.createWalletWorkflowStore(ownerId);
|
|
72
|
+
const localData = store.createLocalDataService({
|
|
73
|
+
advanceRequestDeadlines: (now) => workflowRecords.advanceRequestDeadlines(now),
|
|
74
|
+
onDataReplaced: () => { access.dataReplaced(); workflow?.dataReplaced(); chart?.clearCache(); },
|
|
75
|
+
suiGrpcUrl: DEFAULT_SUI_GRPC_URL,
|
|
76
|
+
suiGraphqlUrl: DEFAULT_SUI_GRAPHQL_URL,
|
|
77
|
+
verifySuiGrpcUrl: async (url) => {
|
|
78
|
+
await verifyMainnetGrpcEndpoint({
|
|
79
|
+
url,
|
|
80
|
+
expectedChainIdentifier: config.expectedChainIdentifier
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
verifySuiGraphqlUrl: async (url) => {
|
|
84
|
+
await verifyMainnetGraphqlEndpoint({
|
|
85
|
+
url,
|
|
86
|
+
expectedChainIdentifier: config.expectedChainIdentifier
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
const transactionMaterialStore = store.createTransactionMaterialStore();
|
|
91
|
+
const sessions = new LocalSessionStore({
|
|
92
|
+
activityStore: store,
|
|
93
|
+
transactionMaterialStore,
|
|
94
|
+
logger,
|
|
95
|
+
validateAdapterLifecycle: validateSupportedAdapterLifecycle,
|
|
96
|
+
sessions: store.createSessionRecordStore(),
|
|
97
|
+
artifacts: store.createPrivateReviewArtifactStore(),
|
|
98
|
+
ownerId,
|
|
99
|
+
settingsStore: store.createSettingsRecordStore()
|
|
100
|
+
});
|
|
101
|
+
const readService = createSuiReadService({
|
|
102
|
+
client: suiClient,
|
|
103
|
+
network: config.network,
|
|
104
|
+
chainIdentifier,
|
|
105
|
+
coinMetadataCache: store.createCoinMetadataCache(),
|
|
106
|
+
deepbookOfficialIndexerSource: new DeepbookOfficialIndexerSource()
|
|
107
|
+
});
|
|
108
|
+
const chainReceiptVerifier = (input) => verifySuiChainReceipt({
|
|
109
|
+
client: suiClient,
|
|
110
|
+
network: config.network,
|
|
111
|
+
expectedChainIdentifier: config.expectedChainIdentifier
|
|
112
|
+
}, input);
|
|
113
|
+
const publicChainReceiptReader = (input) => readPublicChainReceipt({
|
|
114
|
+
client: suiClient,
|
|
115
|
+
network: config.network,
|
|
116
|
+
expectedChainIdentifier: config.expectedChainIdentifier,
|
|
117
|
+
resolveCoinUnit: (coinType) => readService.resolveCoinUnit(coinType)
|
|
118
|
+
}, input);
|
|
119
|
+
const reviewComputationDeps = createRuntimeReviewDependencies({ client: suiClient, chainIdentifier,
|
|
120
|
+
expectedChainIdentifier: config.expectedChainIdentifier, materialStore: transactionMaterialStore, readService });
|
|
121
|
+
const httpHandler = createReviewRequestHandler({ host: config.reviewHost, store: sessions, logger,
|
|
122
|
+
activityStore: store, localSettings, localData,
|
|
123
|
+
serverInfo: { name: SERVER_NAME, version: SERVER_VERSION, network: SERVER_NETWORK } });
|
|
124
|
+
const cardRecords = store.createCardRecordStore();
|
|
125
|
+
let metadata;
|
|
126
|
+
try {
|
|
127
|
+
metadata = JSON.parse(await readFile(new URL("../../package.json", import.meta.url), "utf8"));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
logger.error("WalletConnect initialization unavailable", { stage: "initialization_failed" });
|
|
131
|
+
}
|
|
132
|
+
walletRuntime = new WalletSdkProcess({ projectId: WALLETCONNECT_PROJECT_ID, dataDirectory: dirname(bootConfig.activityDatabasePath),
|
|
133
|
+
...(metadata ? { metadata: { name: metadata.name, description: metadata.description, url: metadata.homepage } } : {}) });
|
|
134
|
+
workflow = new WalletWorkflow({ records: workflowRecords, sessions, ownerId,
|
|
135
|
+
runtime: walletRuntime, computation: reviewComputationDeps, verifyReceipt: chainReceiptVerifier, readReceipt: publicChainReceiptReader, signatureClient: suiClient,
|
|
136
|
+
assertCurrent: access.assertCurrent, bindExternalEvent: (work) => access.bind(work), logger,
|
|
137
|
+
verifyNetwork: async () => {
|
|
138
|
+
const actual = await suiClient.core.getChainIdentifier();
|
|
139
|
+
if (actual.chainIdentifier !== config.expectedChainIdentifier)
|
|
140
|
+
throw new Error("Sui mainnet verification failed.");
|
|
141
|
+
},
|
|
142
|
+
submitTransaction: (transaction, signature) => suiClient.core.executeTransaction({ transaction, signatures: [signature] })
|
|
143
|
+
});
|
|
144
|
+
await workflow.start();
|
|
145
|
+
chart = createDeepbookUsdcChartService({ assertCurrent: access.assertCurrent });
|
|
146
|
+
cards = createReadCardStore({ readService, publicChainReceiptReader, chart, records: cardRecords, ownerId, assertCurrent: access.assertCurrent, logger, workflow });
|
|
147
|
+
const transactionActivityService = new TransactionActivityService({
|
|
148
|
+
activityStore: store,
|
|
149
|
+
source: new GraphqlSuiTransactionActivitySource({
|
|
150
|
+
url: config.graphqlUrl,
|
|
151
|
+
expectedChainIdentifier: config.expectedChainIdentifier
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
const mcp = createInternalMcpHandler(() => createMcpServer({
|
|
155
|
+
cards: { store: cards },
|
|
156
|
+
workflow,
|
|
157
|
+
promptSurfaces: ADAPTER_PROMPT_SURFACES,
|
|
158
|
+
sessions,
|
|
159
|
+
activityStore: store,
|
|
160
|
+
reviewBaseUrl: `http://${config.reviewHost}:${config.reviewPort}`,
|
|
161
|
+
readService,
|
|
162
|
+
transactionActivityService,
|
|
163
|
+
localSettings,
|
|
164
|
+
logger
|
|
165
|
+
}));
|
|
166
|
+
access.ready();
|
|
167
|
+
return {
|
|
168
|
+
handleMcp: (request, response) => access.run(() => mcp.handle(request, response)),
|
|
169
|
+
handleHttp: (request, response) => access.run(() => httpHandler(request, response)),
|
|
170
|
+
async close() {
|
|
171
|
+
workflow?.stop();
|
|
172
|
+
access.close();
|
|
173
|
+
cards?.stop();
|
|
174
|
+
chart?.clearCache();
|
|
175
|
+
try {
|
|
176
|
+
await walletRuntime.close();
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
try {
|
|
180
|
+
await mcp.close();
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
store.close();
|
|
184
|
+
ownership.close();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
workflow?.stop();
|
|
192
|
+
access.close();
|
|
193
|
+
cards?.stop();
|
|
194
|
+
try {
|
|
195
|
+
await walletRuntime?.close();
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
activityStore?.close();
|
|
199
|
+
ownership.close();
|
|
200
|
+
}
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|