@stelis/say-ur-intent 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -39
- package/dist/adapters/adapterLifecycleValidators.js +7 -0
- package/dist/adapters/adapterPromptSurfaces.js +71 -0
- package/dist/adapters/deepbook/deepbookHumanReviewProducer.js +175 -0
- package/dist/adapters/deepbook/deepbookQuotePolicy.js +112 -0
- package/dist/adapters/deepbook/deepbookReviewEvidence.js +507 -0
- package/dist/adapters/deepbook/deepbookReviewLifecycle.js +85 -0
- package/dist/adapters/deepbook/deepbookSwapIntent.js +79 -0
- package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +269 -0
- package/dist/adapters/flowx/flowxSwapHumanReviewProducer.js +176 -0
- package/dist/adapters/flowx/flowxSwapIntent.js +79 -0
- package/dist/adapters/flowx/flowxSwapQuotePolicy.js +104 -0
- package/dist/adapters/flowx/flowxSwapReviewEvidence.js +468 -0
- package/dist/adapters/flowx/flowxSwapReviewLifecycle.js +85 -0
- package/dist/adapters/flowx/flowxSwapTransactionMaterialProducer.js +362 -0
- package/dist/adapters/intentPlanFactories.js +59 -0
- package/dist/adapters/reviewAdapters.js +81 -0
- package/dist/core/action/adapterLifecycleValidation.js +12 -0
- package/dist/core/action/forbiddenFields.js +43 -0
- package/dist/core/action/humanReadableReviewEvidence.js +203 -0
- package/dist/core/action/humanReadableReviewProjectionVerifier.js +29 -0
- package/dist/core/action/ptbVisualizationProducer.js +66 -0
- package/dist/core/action/reviewCheckResults.js +6 -0
- package/dist/core/action/reviewStateValidation.js +11 -0
- package/dist/core/action/reviewTimeSimulationEvidence.js +471 -0
- package/dist/core/action/schemas.js +529 -0
- package/dist/core/action/signableAdapterContract.js +993 -0
- package/dist/core/action/swapHumanReadableReviewProjection.js +124 -0
- package/dist/core/action/swapQuotePolicyEvidence.js +278 -0
- package/dist/core/action/transactionObjectOwnershipEvidence.js +247 -0
- package/dist/core/action/transactionObjectOwnershipProducer.js +329 -0
- package/dist/core/action/types.js +35 -0
- package/dist/core/action/walletReviewContractAssembler.js +282 -0
- package/dist/core/activity/activityStore.js +15 -0
- package/dist/core/activity/localDataService.js +258 -0
- package/dist/core/activity/localDataTypes.js +11 -0
- package/dist/core/activity/localDataValidation.js +396 -0
- package/dist/core/activity/schemaVersion.js +1 -0
- package/dist/core/activity/sqliteActivityStore.js +820 -0
- package/dist/core/activity/sqliteActivityStoreRows.js +430 -0
- package/dist/core/activity/sqliteActivityStoreSchema.js +258 -0
- package/dist/core/activity/sqliteActivityStoreTypes.js +5 -0
- package/dist/core/activity/suiFunctionTarget.js +43 -0
- package/dist/core/activity/transactionActivityAccountEffects.js +189 -0
- package/dist/core/activity/transactionActivityAnalysis.js +295 -0
- package/dist/core/activity/transactionActivityClassifier.js +306 -0
- package/dist/core/activity/transactionActivityDetails.js +229 -0
- package/dist/core/activity/transactionActivityProtocolRules.js +218 -0
- package/dist/core/activity/transactionActivityScanPolicy.js +170 -0
- package/dist/core/activity/transactionActivityService.js +379 -0
- package/dist/core/activity/transactionActivityTypes.js +18 -0
- package/dist/core/eventlog/sink.js +35 -0
- package/dist/core/evidence/settlementFamilies.js +87 -0
- package/dist/core/evidence/userAnswerUse.js +1 -0
- package/dist/core/numeric/rawU64.js +63 -0
- package/dist/core/preferences/preferencesStore.js +26 -0
- package/dist/core/preferences/sqlitePreferencesRepository.js +136 -0
- package/dist/core/proposal/externalProposalReview.js +347 -0
- package/dist/core/proposal/schemas.js +208 -0
- package/dist/core/proposal/types.js +35 -0
- package/dist/core/read/amounts.js +14 -0
- package/dist/core/read/coinMetadata.js +60 -0
- package/dist/core/read/deepbookRawQuoteClient.js +86 -0
- package/dist/core/read/deepbookReadHelpers.js +265 -0
- package/dist/core/read/deepbookRegistry.js +133 -0
- package/dist/core/read/flowxQuoteClient.js +117 -0
- package/dist/core/read/flowxReadHelpers.js +145 -0
- package/dist/core/read/flowxRegistry.js +174 -0
- package/dist/core/read/intentEvidenceResponseFormatting.js +228 -0
- package/dist/core/read/readResponseGuidance.js +451 -0
- package/dist/core/read/readService.js +1164 -0
- package/dist/core/read/readServiceTypes.js +59 -0
- package/dist/core/read/settlementParityFormatting.js +82 -0
- package/dist/core/read/walletReadHelpers.js +99 -0
- package/dist/core/review/reviewChecks.js +54 -0
- package/dist/core/review/reviewComputation.js +38 -0
- package/dist/core/review/reviewComputationResult.js +87 -0
- package/dist/core/session/localSession.js +31 -0
- package/dist/core/session/privateReviewArtifacts.js +73 -0
- package/dist/core/session/sessionErrors.js +9 -0
- package/dist/core/session/sessionStore.js +821 -0
- package/dist/core/session/settingsSession.js +1 -0
- package/dist/core/session/settingsSessions.js +43 -0
- package/dist/core/session/status.js +86 -0
- package/dist/core/session/transactionMaterialStore.js +205 -0
- package/dist/core/session/wait.js +102 -0
- package/dist/core/session/walletIdentity.js +103 -0
- package/dist/core/session/walletIdentitySessions.js +189 -0
- package/dist/core/suiAddress.js +18 -0
- package/dist/core/suiEndpoint.js +72 -0
- package/dist/mcp/activeAccountResponse.js +24 -0
- package/dist/mcp/prompts.js +146 -0
- package/dist/mcp/registerTool.js +19 -0
- package/dist/mcp/resources.js +72 -0
- package/dist/mcp/responseGuidance.js +381 -0
- package/dist/mcp/result.js +17 -0
- package/dist/mcp/schemas.js +8 -0
- package/dist/mcp/server.js +30 -0
- package/dist/mcp/serverInfo.js +123 -0
- package/dist/mcp/toolErrors.js +105 -0
- package/dist/mcp/toolNames.js +50 -0
- package/dist/mcp/tools/account/index.js +44 -0
- package/dist/mcp/tools/action/prepareSuiActionReview.js +120 -0
- package/dist/mcp/tools/read/commonSchemas.js +43 -0
- package/dist/mcp/tools/read/deepbookReadTools.js +453 -0
- package/dist/mcp/tools/read/flowxReadTools.js +135 -0
- package/dist/mcp/tools/read/index.js +16 -0
- package/dist/mcp/tools/read/readToolHelpers.js +68 -0
- package/dist/mcp/tools/read/reviewActivityTools.js +176 -0
- package/dist/mcp/tools/read/serverStatusTools.js +103 -0
- package/dist/mcp/tools/read/transactionActivityOutput.js +300 -0
- package/dist/mcp/tools/read/transactionActivityTools.js +544 -0
- package/dist/mcp/tools/read/walletReadTools.js +733 -0
- package/dist/mcp/tools/session/executionResultTools.js +92 -0
- package/dist/mcp/tools/session/index.js +8 -0
- package/dist/mcp/tools/session/shared.js +79 -0
- package/dist/mcp/tools/session/statusTools.js +134 -0
- package/dist/mcp/tools/session/walletIdentityTools.js +119 -0
- package/dist/mcp/tools/settings/index.js +64 -0
- package/dist/review-app/analysis.css +1 -0
- package/dist/review-app/analysis.js +1 -0
- package/dist/review-app/arc-BjIacwQm.js +1 -0
- package/dist/review-app/architecture-U656AL7Q-aSB9x1OK.js +1 -0
- package/dist/review-app/architectureDiagram-VXUJARFQ-C5W6re2I.js +36 -0
- package/dist/review-app/array-BmXUUrU6.js +1 -0
- package/dist/review-app/blockDiagram-VD42YOAC-20MLNcUm.js +122 -0
- package/dist/review-app/c4Diagram-YG6GDRKO-BZXRrcck.js +10 -0
- package/dist/review-app/channel-lk2p_CUu.js +1 -0
- package/dist/review-app/chunk-4BX2VUAB-BPITOdjX.js +1 -0
- package/dist/review-app/chunk-55IACEB6-Dz-pyw5k.js +1 -0
- package/dist/review-app/chunk-76Q3JFCE-cK_X1P_l.js +1 -0
- package/dist/review-app/chunk-ABZYJK2D-Dt4W53JI.js +81 -0
- package/dist/review-app/chunk-ATLVNIR6-fZHLXURb.js +1 -0
- package/dist/review-app/chunk-B4BG7PRW-BbgcjusC.js +165 -0
- package/dist/review-app/chunk-BJD4TVEz.js +1 -0
- package/dist/review-app/chunk-CVBHYZKI-CViawAKX.js +1 -0
- package/dist/review-app/chunk-DI55MBZ5-C5aoul-d.js +220 -0
- package/dist/review-app/chunk-FMBD7UC4-Chxmw62A.js +15 -0
- package/dist/review-app/chunk-FPAJGGOC-DDHjQ09H.js +80 -0
- package/dist/review-app/chunk-FWNWRKHM-CVVQUptk.js +1 -0
- package/dist/review-app/chunk-HN2XXSSU-yzNpjaSZ.js +1 -0
- package/dist/review-app/chunk-JA3XYJ7Z-C5ZJdU01.js +70 -0
- package/dist/review-app/chunk-JZLCHNYA-BBST4Cnk.js +54 -0
- package/dist/review-app/chunk-LBM3YZW2-CdwAPuHr.js +1 -0
- package/dist/review-app/chunk-LHMN2FUI-BtB5uDcp.js +1 -0
- package/dist/review-app/chunk-O7ZBX7Z2-pxdK4Sa3.js +1 -0
- package/dist/review-app/chunk-QN33PNHL-CbVv3uGK.js +1 -0
- package/dist/review-app/chunk-QXUST7PY-DKM2-t2c.js +7 -0
- package/dist/review-app/chunk-QZHKN3VN-C5ni2pN_.js +1 -0
- package/dist/review-app/chunk-S3R3BYOJ-BWvOhDs0.js +2 -0
- package/dist/review-app/chunk-S6J4BHB3-D9Fk0YeD.js +1 -0
- package/dist/review-app/chunk-T53DSG4Q-C1qEyzyV.js +1 -0
- package/dist/review-app/chunk-TZMSLE5B-B--7eU69.js +1 -0
- package/dist/review-app/classDiagram-2ON5EDUG-DlL1m2bp.js +1 -0
- package/dist/review-app/classDiagram-v2-WZHVMYZB-FXRskT1j.js +1 -0
- package/dist/review-app/clone-BZZb7gpZ.js +1 -0
- package/dist/review-app/cose-bilkent-S5V4N54A-CRIb8XEO.js +1 -0
- package/dist/review-app/cytoscape.esm-C7jYqDP5.js +321 -0
- package/dist/review-app/dagre-6UL2VRFP-FNCAXbdE.js +4 -0
- package/dist/review-app/dagre-Be46QtUd.js +1 -0
- package/dist/review-app/defaultLocale-BaWNtAUL.js +1 -0
- package/dist/review-app/diagram-PSM6KHXK-ylLWjiNM.js +24 -0
- package/dist/review-app/diagram-QEK2KX5R-BCDcESxs.js +43 -0
- package/dist/review-app/diagram-S2PKOQOG-Vdrc-vrO.js +24 -0
- package/dist/review-app/dist-WPc74x_f.js +1 -0
- package/dist/review-app/erDiagram-Q2GNP2WA-E5ZsUbDF.js +60 -0
- package/dist/review-app/flatten-DHf9IeNI.js +1 -0
- package/dist/review-app/flowDiagram-NV44I4VS-DBSQuj6x.js +162 -0
- package/dist/review-app/ganttDiagram-LVOFAZNH-CKUOsqwl.js +267 -0
- package/dist/review-app/gitGraph-F6HP7TQM-DsAD6qK1.js +1 -0
- package/dist/review-app/gitGraphDiagram-NY62KEGX-BCeIMWdl.js +65 -0
- package/dist/review-app/graphlib-CiX5CXxR.js +1 -0
- package/dist/review-app/http-DMvwuuFk.js +1 -0
- package/dist/review-app/identity-DY8PXc6t.js +1 -0
- package/dist/review-app/info-NVLQJR56-Dlx1nZic.js +1 -0
- package/dist/review-app/infoDiagram-F6ZHWCRC-CAuANIrz.js +2 -0
- package/dist/review-app/init-BvqephKz.js +1 -0
- package/dist/review-app/journeyDiagram-XKPGCS4Q-C-Z9phnx.js +139 -0
- package/dist/review-app/kanban-definition-3W4ZIXB7-DufgZABq.js +89 -0
- package/dist/review-app/katex-B-Z-NXXN.js +257 -0
- package/dist/review-app/line-DiIv3Jgw.js +1 -0
- package/dist/review-app/linear-Cv-UPvo1.js +1 -0
- package/dist/review-app/math-kmyYrkHL.js +1 -0
- package/dist/review-app/mermaid-parser.core-DkwUYTPl.js +4 -0
- package/dist/review-app/mindmap-definition-VGOIOE7T-TM_CqdmV.js +68 -0
- package/dist/review-app/ordinal-BliTlkoG.js +1 -0
- package/dist/review-app/packet-BFZMPI3H-DqbnU92v.js +1 -0
- package/dist/review-app/path-AEo9W6mQ.js +1 -0
- package/dist/review-app/pie-7BOR55EZ-LJzaLkgr.js +1 -0
- package/dist/review-app/pieDiagram-ADFJNKIX-BAs8OfRS.js +30 -0
- package/dist/review-app/quadrantDiagram-AYHSOK5B-CyUDZP5S.js +7 -0
- package/dist/review-app/radar-NHE76QYJ-DBpHc8_Y.js +1 -0
- package/dist/review-app/reduce-B-HuPpdd.js +1 -0
- package/dist/review-app/requirementDiagram-UZGBJVZJ-BEHix78P.js +64 -0
- package/dist/review-app/review.css +1 -0
- package/dist/review-app/review.js +43 -0
- package/dist/review-app/sankeyDiagram-TZEHDZUN-B2bKbmsm.js +10 -0
- package/dist/review-app/sequenceDiagram-WL72ISMW-DVLOORFJ.js +145 -0
- package/dist/review-app/settings.css +1 -0
- package/dist/review-app/settings.js +1 -0
- package/dist/review-app/src-Buml7cM5.js +1 -0
- package/dist/review-app/stateDiagram-FKZM4ZOC-sFGGp2kV.js +1 -0
- package/dist/review-app/stateDiagram-v2-4FDKWEC3-BHfCF4dX.js +1 -0
- package/dist/review-app/timeline-definition-IT6M3QCI-BESnBijC.js +61 -0
- package/dist/review-app/treemap-KMMF4GRG-wnVLBDeQ.js +1 -0
- package/dist/review-app/walletStatus-CcojOdGy.js +7 -0
- package/dist/review-app/xychartDiagram-PRI3JC2R-BGWVfCx4.js +7 -0
- package/dist/review-server/assets.js +48 -0
- package/dist/review-server/html.js +66 -0
- package/dist/review-server/http.js +47 -0
- package/dist/review-server/middleware/hostOrigin.js +48 -0
- package/dist/review-server/middleware/reviewToken.js +7 -0
- package/dist/review-server/reviewServerPolicy.js +10 -0
- package/dist/review-server/server.js +568 -0
- package/dist/review-server/settingsApi.js +182 -0
- package/dist/review-server/walletIdentityResponse.js +13 -0
- package/dist/runtime/config.js +103 -0
- package/dist/runtime/localSettingsService.js +198 -0
- package/dist/runtime/logger.js +50 -0
- package/dist/runtime/reviewServerAcquire.js +128 -0
- package/dist/runtime/smokeMainnetRead.js +529 -0
- package/dist/runtime/smokeMainnetReadAssertions.js +308 -0
- package/dist/runtime/start.js +295 -0
- package/dist/runtime/suiEndpoint.js +97 -0
- package/dist/runtime/suiTransactionGraphqlMapping.js +200 -0
- package/dist/runtime/suiTransactionGraphqlQueries.js +231 -0
- package/dist/runtime/suiTransactionGraphqlSource.js +148 -0
- package/docs/AGENT_BEHAVIOR.md +1 -1
- package/docs/AGENT_DEVELOPMENT_POLICY.md +20 -0
- package/docs/FRONTEND_POLICY.md +4 -3
- package/docs/MCP_SETUP.md +59 -7
- package/docs/MCP_TOOLS.md +1 -1
- package/docs/SDK_API.md +5 -1
- package/package.json +3 -2
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { actionPlanSchema } from "../action/schemas.js";
|
|
2
|
+
import { assertNoForbiddenMcpFields } from "../action/forbiddenFields.js";
|
|
3
|
+
import { ActivityStoreReadError, EXTERNAL_ACTIVITY_SCAN_MAX_LIMIT, REVIEW_ACTIVITY_LIST_DEFAULT_LIMIT, REVIEW_ACTIVITY_LIST_MAX_LIMIT, REVIEW_ACTIVITY_LOW_SAMPLE_THRESHOLD } from "./activityStore.js";
|
|
4
|
+
import { EXTERNAL_ACTIVITY_TRANSACTION_DETAIL_JSON_MAX_BYTES, externalActivityTransactionDetailJsonByteLength, externalActivityTransactionDetailSchema, externalActivityTransactionDetailsReferenceOnlyAccount } from "./transactionActivityDetails.js";
|
|
5
|
+
import { ActivityStoreError } from "./sqliteActivityStoreTypes.js";
|
|
6
|
+
export const INTERNAL_SESSION_STATUSES = [
|
|
7
|
+
"proposed",
|
|
8
|
+
"awaiting_wallet",
|
|
9
|
+
"wallet_connected",
|
|
10
|
+
"ready_for_wallet_review",
|
|
11
|
+
"refresh_required",
|
|
12
|
+
"blocked",
|
|
13
|
+
"signed_pending_result",
|
|
14
|
+
"success",
|
|
15
|
+
"failure",
|
|
16
|
+
"expired"
|
|
17
|
+
];
|
|
18
|
+
export const REVIEW_STATE_STATUSES = [
|
|
19
|
+
"ready_for_wallet_review",
|
|
20
|
+
"blocked",
|
|
21
|
+
"refresh_required"
|
|
22
|
+
];
|
|
23
|
+
export const REVIEW_TRANSITION_EVENTS = [
|
|
24
|
+
"created",
|
|
25
|
+
"opened",
|
|
26
|
+
"wallet_connected",
|
|
27
|
+
"state_computed",
|
|
28
|
+
"result_recorded",
|
|
29
|
+
"expired"
|
|
30
|
+
];
|
|
31
|
+
export const EXTERNAL_ACTIVITY_RELATIONSHIPS = ["affected", "sent"];
|
|
32
|
+
export const EXTERNAL_ACTIVITY_STATUSES = ["success", "failure", "unknown"];
|
|
33
|
+
const EXTERNAL_ACTIVITY_INCOMPLETE_REASONS = [
|
|
34
|
+
"limit_reached",
|
|
35
|
+
"ordering_unverified",
|
|
36
|
+
"cursor_invalid",
|
|
37
|
+
"provider_error"
|
|
38
|
+
];
|
|
39
|
+
export function parseIsoTimestamp(value, field) {
|
|
40
|
+
const parsed = new Date(value);
|
|
41
|
+
if (Number.isNaN(parsed.getTime()) || parsed.toISOString() !== value) {
|
|
42
|
+
throw new ActivityStoreReadError("input_invalid", `${field} must be an ISO 8601 UTC timestamp`, {
|
|
43
|
+
field
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
export function parseOptionalIsoTimestamp(value, field) {
|
|
49
|
+
if (value === undefined) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return parseIsoTimestamp(value, field);
|
|
53
|
+
}
|
|
54
|
+
export function assertDateRange(from, to) {
|
|
55
|
+
if (from && to && from > to) {
|
|
56
|
+
throw new ActivityStoreReadError("input_invalid", "from must be before or equal to to", {
|
|
57
|
+
from,
|
|
58
|
+
to
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function normalizeListLimit(limit) {
|
|
63
|
+
if (limit === undefined) {
|
|
64
|
+
return REVIEW_ACTIVITY_LIST_DEFAULT_LIMIT;
|
|
65
|
+
}
|
|
66
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > REVIEW_ACTIVITY_LIST_MAX_LIMIT) {
|
|
67
|
+
throw new ActivityStoreReadError("input_invalid", "limit must be an integer from 1 to 100", {
|
|
68
|
+
limit,
|
|
69
|
+
min: 1,
|
|
70
|
+
max: REVIEW_ACTIVITY_LIST_MAX_LIMIT
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return limit;
|
|
74
|
+
}
|
|
75
|
+
export function normalizeExternalActivityLimit(limit) {
|
|
76
|
+
if (limit === undefined) {
|
|
77
|
+
return EXTERNAL_ACTIVITY_SCAN_MAX_LIMIT;
|
|
78
|
+
}
|
|
79
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > EXTERNAL_ACTIVITY_SCAN_MAX_LIMIT) {
|
|
80
|
+
throw new ActivityStoreReadError("input_invalid", "limit must be an integer from 1 to 100", {
|
|
81
|
+
limit,
|
|
82
|
+
min: 1,
|
|
83
|
+
max: EXTERNAL_ACTIVITY_SCAN_MAX_LIMIT
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return limit;
|
|
87
|
+
}
|
|
88
|
+
export function reviewSessionWhere(accountId, from, to, status) {
|
|
89
|
+
const conditions = ["rs.account_id = ?"];
|
|
90
|
+
const params = [accountId];
|
|
91
|
+
if (from) {
|
|
92
|
+
conditions.push("rs.created_at >= ?");
|
|
93
|
+
params.push(from);
|
|
94
|
+
}
|
|
95
|
+
if (to) {
|
|
96
|
+
conditions.push("rs.created_at <= ?");
|
|
97
|
+
params.push(to);
|
|
98
|
+
}
|
|
99
|
+
if (status) {
|
|
100
|
+
conditions.push("rs.current_status = ?");
|
|
101
|
+
params.push(status);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
whereSql: `WHERE ${conditions.join(" AND ")}`,
|
|
105
|
+
params
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export function reviewActivityRowFromStorage(row) {
|
|
109
|
+
return {
|
|
110
|
+
reviewSessionId: asString(row.review_session_id),
|
|
111
|
+
planId: asString(row.plan_id),
|
|
112
|
+
actionKind: asString(row.action_kind),
|
|
113
|
+
adapterId: asString(row.adapter_id),
|
|
114
|
+
protocol: asString(row.protocol),
|
|
115
|
+
currentStatus: asInternalSessionStatus(row.current_status),
|
|
116
|
+
account: asString(row.account),
|
|
117
|
+
createdAt: asString(row.created_at),
|
|
118
|
+
updatedAt: asString(row.updated_at),
|
|
119
|
+
executionStatus: row.execution_status === null ? undefined : asString(row.execution_status),
|
|
120
|
+
txDigest: row.tx_digest === null ? undefined : asString(row.tx_digest),
|
|
121
|
+
snapshotCount: row.snapshot_count,
|
|
122
|
+
transitionCount: row.transition_count
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export function reviewActivityListResult(scope, from, to, activities, truncated, recordCount) {
|
|
126
|
+
return {
|
|
127
|
+
dataScope: dataScope(scope.account, from, to, recordCount),
|
|
128
|
+
accountSource: scope.accountSource,
|
|
129
|
+
lowSampleWarning: recordCount < REVIEW_ACTIVITY_LOW_SAMPLE_THRESHOLD,
|
|
130
|
+
lowSampleThreshold: REVIEW_ACTIVITY_LOW_SAMPLE_THRESHOLD,
|
|
131
|
+
truncated: {
|
|
132
|
+
activities: truncated,
|
|
133
|
+
snapshots: false,
|
|
134
|
+
transitions: false
|
|
135
|
+
},
|
|
136
|
+
activities
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function reviewFunnelResult(scope, from, to, summary, recordCount) {
|
|
140
|
+
return {
|
|
141
|
+
dataScope: dataScope(scope.account, from, to, recordCount),
|
|
142
|
+
accountSource: scope.accountSource,
|
|
143
|
+
lowSampleWarning: recordCount < REVIEW_ACTIVITY_LOW_SAMPLE_THRESHOLD,
|
|
144
|
+
lowSampleThreshold: REVIEW_ACTIVITY_LOW_SAMPLE_THRESHOLD,
|
|
145
|
+
truncated: {
|
|
146
|
+
activities: false,
|
|
147
|
+
snapshots: false,
|
|
148
|
+
transitions: false
|
|
149
|
+
},
|
|
150
|
+
summary
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function dataScope(account, from, to, recordCount) {
|
|
154
|
+
const scope = {
|
|
155
|
+
account,
|
|
156
|
+
recordCount
|
|
157
|
+
};
|
|
158
|
+
if (from !== undefined) {
|
|
159
|
+
scope.from = from;
|
|
160
|
+
}
|
|
161
|
+
if (to !== undefined) {
|
|
162
|
+
scope.to = to;
|
|
163
|
+
}
|
|
164
|
+
return scope;
|
|
165
|
+
}
|
|
166
|
+
export function externalActivityScanFromRow(row) {
|
|
167
|
+
return {
|
|
168
|
+
scanId: asString(row.scan_id),
|
|
169
|
+
kind: asExternalActivityScanKind(row.kind),
|
|
170
|
+
accountId: row.account_id,
|
|
171
|
+
account: asString(row.account),
|
|
172
|
+
relationship: asExternalActivityRelationship(row.relationship),
|
|
173
|
+
inputDigest: row.input_digest === null ? undefined : asString(row.input_digest),
|
|
174
|
+
fromCheckpoint: row.from_checkpoint === null ? undefined : asString(row.from_checkpoint),
|
|
175
|
+
toCheckpoint: row.to_checkpoint === null ? undefined : asString(row.to_checkpoint),
|
|
176
|
+
fromTimestamp: row.from_timestamp === null ? undefined : asString(row.from_timestamp),
|
|
177
|
+
toTimestamp: row.to_timestamp === null ? undefined : asString(row.to_timestamp),
|
|
178
|
+
limit: row.limit_count,
|
|
179
|
+
requestCursor: row.request_cursor === null ? undefined : asString(row.request_cursor),
|
|
180
|
+
responseCursor: row.response_cursor === null ? undefined : asString(row.response_cursor),
|
|
181
|
+
endpointHost: asString(row.endpoint_host),
|
|
182
|
+
chainIdentifier: asString(row.chain_identifier),
|
|
183
|
+
fetchedAt: asString(row.fetched_at),
|
|
184
|
+
storedCount: row.stored_count,
|
|
185
|
+
skippedCount: row.skipped_count,
|
|
186
|
+
hasMore: row.has_more === 1,
|
|
187
|
+
windowComplete: row.window_complete === null ? null : row.window_complete === 1,
|
|
188
|
+
incompleteReason: row.incomplete_reason === null
|
|
189
|
+
? undefined
|
|
190
|
+
: asExternalActivityIncompleteReason(row.incomplete_reason)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
export function externalActivityTransactionFromRow(row) {
|
|
194
|
+
return {
|
|
195
|
+
accountId: row.account_id,
|
|
196
|
+
account: asString(row.account),
|
|
197
|
+
digest: asString(row.digest),
|
|
198
|
+
relationship: asExternalActivityRelationship(row.relationship),
|
|
199
|
+
checkpoint: row.checkpoint === null ? undefined : asString(row.checkpoint),
|
|
200
|
+
timestamp: row.timestamp === null ? undefined : asString(row.timestamp),
|
|
201
|
+
status: asExternalActivityStatus(row.status),
|
|
202
|
+
knownSenderAccountId: row.known_sender_account_id === null ? undefined : row.known_sender_account_id,
|
|
203
|
+
firstScanId: asString(row.first_scan_id),
|
|
204
|
+
lastScanId: asString(row.last_scan_id),
|
|
205
|
+
firstFetchedAt: asString(row.first_fetched_at),
|
|
206
|
+
lastFetchedAt: asString(row.last_fetched_at),
|
|
207
|
+
lastScanIncompleteReason: row.last_scan_incomplete_reason === null
|
|
208
|
+
? undefined
|
|
209
|
+
: asExternalActivityIncompleteReason(row.last_scan_incomplete_reason),
|
|
210
|
+
details: row.detail_json === null
|
|
211
|
+
? undefined
|
|
212
|
+
: parseEvidenceJson(row.detail_json, asString(row.digest), "external_activity_detail", externalActivityTransactionDetailSchema)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export function externalActivitySummaryResult(scope, from, to, transactions, truncated, stats) {
|
|
216
|
+
const summary = {
|
|
217
|
+
transactionCount: stats.transactionCount,
|
|
218
|
+
statusCounts: stats.statusCounts,
|
|
219
|
+
relationshipCounts: stats.relationshipCounts
|
|
220
|
+
};
|
|
221
|
+
if (stats.earliestTimestamp !== undefined) {
|
|
222
|
+
summary.earliestTimestamp = stats.earliestTimestamp;
|
|
223
|
+
}
|
|
224
|
+
if (stats.latestTimestamp !== undefined) {
|
|
225
|
+
summary.latestTimestamp = stats.latestTimestamp;
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
dataScope: dataScope(scope.account, from, to, stats.transactionCount),
|
|
229
|
+
accountSource: scope.accountSource,
|
|
230
|
+
lowSampleWarning: stats.transactionCount < REVIEW_ACTIVITY_LOW_SAMPLE_THRESHOLD,
|
|
231
|
+
lowSampleThreshold: REVIEW_ACTIVITY_LOW_SAMPLE_THRESHOLD,
|
|
232
|
+
truncated,
|
|
233
|
+
summary,
|
|
234
|
+
transactions
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
export function emptyExternalActivitySummaryStats() {
|
|
238
|
+
return {
|
|
239
|
+
transactionCount: 0,
|
|
240
|
+
statusCounts: Object.fromEntries(EXTERNAL_ACTIVITY_STATUSES.map((status) => [status, 0])),
|
|
241
|
+
relationshipCounts: Object.fromEntries(EXTERNAL_ACTIVITY_RELATIONSHIPS.map((relationship) => [relationship, 0]))
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
export function emptyReviewFunnelSummary() {
|
|
245
|
+
return {
|
|
246
|
+
total: 0,
|
|
247
|
+
opened: 0,
|
|
248
|
+
walletConnected: 0,
|
|
249
|
+
stateComputed: 0,
|
|
250
|
+
currentStatusCounts: countMap(INTERNAL_SESSION_STATUSES, []),
|
|
251
|
+
everReachedReviewStateCounts: countMap(REVIEW_STATE_STATUSES, []),
|
|
252
|
+
signedPending: 0,
|
|
253
|
+
success: 0,
|
|
254
|
+
failure: 0,
|
|
255
|
+
expiredBeforeResult: 0,
|
|
256
|
+
avgCreatedToSignedSeconds: null,
|
|
257
|
+
avgOpenedToSignedSeconds: null
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
export function countMap(keys, rows) {
|
|
261
|
+
const result = Object.fromEntries(keys.map((key) => [key, 0]));
|
|
262
|
+
for (const row of rows) {
|
|
263
|
+
if (keys.includes(row.key)) {
|
|
264
|
+
result[row.key] = row.count;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
export function nullableSeconds(value) {
|
|
270
|
+
return value === null ? null : Math.round(value * 1000) / 1000;
|
|
271
|
+
}
|
|
272
|
+
export function parseEvidenceJson(value, reviewSessionId, evidenceField, schema) {
|
|
273
|
+
if (value === null) {
|
|
274
|
+
throw new ActivityStoreReadError("internal_error", "Malformed activity JSON evidence", {
|
|
275
|
+
reviewSessionId,
|
|
276
|
+
evidenceField
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
let parsed;
|
|
280
|
+
try {
|
|
281
|
+
parsed = JSON.parse(value);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
throw new ActivityStoreReadError("internal_error", "Malformed activity JSON evidence", {
|
|
285
|
+
reviewSessionId,
|
|
286
|
+
evidenceField
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (schema !== undefined) {
|
|
290
|
+
const result = schema.safeParse(parsed);
|
|
291
|
+
if (!result.success) {
|
|
292
|
+
throw new ActivityStoreReadError("internal_error", "Malformed activity JSON evidence", {
|
|
293
|
+
reviewSessionId,
|
|
294
|
+
evidenceField
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return result.data;
|
|
298
|
+
}
|
|
299
|
+
return parsed;
|
|
300
|
+
}
|
|
301
|
+
export function serializeJson(value) {
|
|
302
|
+
assertNoForbiddenMcpFields(value);
|
|
303
|
+
return JSON.stringify(value);
|
|
304
|
+
}
|
|
305
|
+
export function serializeExternalActivityTransactionDetail(details, account) {
|
|
306
|
+
const parsed = externalActivityTransactionDetailSchema.safeParse(details);
|
|
307
|
+
if (!parsed.success || !externalActivityTransactionDetailsReferenceOnlyAccount(parsed.data, account)) {
|
|
308
|
+
throw new ActivityStoreReadError("input_invalid", "Invalid external activity transaction detail", {
|
|
309
|
+
reason: "invalid_external_activity_detail_json"
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
const json = serializeJson(parsed.data);
|
|
313
|
+
if (externalActivityTransactionDetailJsonByteLength(json) > EXTERNAL_ACTIVITY_TRANSACTION_DETAIL_JSON_MAX_BYTES) {
|
|
314
|
+
throw new ActivityStoreReadError("input_invalid", "External activity transaction detail JSON is too large", {
|
|
315
|
+
reason: "external_activity_detail_too_large",
|
|
316
|
+
maxBytes: EXTERNAL_ACTIVITY_TRANSACTION_DETAIL_JSON_MAX_BYTES
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return json;
|
|
320
|
+
}
|
|
321
|
+
export function serializeOptionalJson(value) {
|
|
322
|
+
if (value === undefined) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
return serializeJson(value);
|
|
326
|
+
}
|
|
327
|
+
export function parseActionPlanEvidence(plan) {
|
|
328
|
+
const parsed = actionPlanSchema.safeParse(plan);
|
|
329
|
+
if (!parsed.success) {
|
|
330
|
+
throw new ActivityStoreError("Invalid review session action plan evidence");
|
|
331
|
+
}
|
|
332
|
+
return parsed.data;
|
|
333
|
+
}
|
|
334
|
+
export function extractRequestedIntent(plan) {
|
|
335
|
+
// Adapter convention: full plan_json stays canonical, while requestedIntent
|
|
336
|
+
// is materialized only to make activity queries avoid nested JSON paths.
|
|
337
|
+
return plan.adapterData.requestedIntent;
|
|
338
|
+
}
|
|
339
|
+
export function reasonForReviewState(state) {
|
|
340
|
+
if ("blockedReason" in state) {
|
|
341
|
+
return state.blockedReason;
|
|
342
|
+
}
|
|
343
|
+
if ("refreshReason" in state) {
|
|
344
|
+
return state.refreshReason;
|
|
345
|
+
}
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
export function asString(value) {
|
|
349
|
+
if (typeof value !== "string") {
|
|
350
|
+
throw new ActivityStoreError("Unexpected SQLite value type");
|
|
351
|
+
}
|
|
352
|
+
return value;
|
|
353
|
+
}
|
|
354
|
+
export function coinMetadataCacheRecordFromRow(row) {
|
|
355
|
+
return {
|
|
356
|
+
coinType: asString(row.coin_type),
|
|
357
|
+
chainIdentifier: asString(row.chain_identifier),
|
|
358
|
+
decimals: row.decimals,
|
|
359
|
+
symbol: asString(row.symbol),
|
|
360
|
+
name: asString(row.name),
|
|
361
|
+
fetchedAt: asString(row.fetched_at),
|
|
362
|
+
expiresAt: asString(row.expires_at)
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
export function asInternalSessionStatus(value) {
|
|
366
|
+
if (typeof value === "string" && INTERNAL_SESSION_STATUSES.includes(value)) {
|
|
367
|
+
return value;
|
|
368
|
+
}
|
|
369
|
+
throw new ActivityStoreError("Unexpected review status");
|
|
370
|
+
}
|
|
371
|
+
export function asReviewTransitionEvent(value) {
|
|
372
|
+
if (typeof value === "string" && REVIEW_TRANSITION_EVENTS.includes(value)) {
|
|
373
|
+
return value;
|
|
374
|
+
}
|
|
375
|
+
throw new ActivityStoreError("Unexpected review transition event");
|
|
376
|
+
}
|
|
377
|
+
export function asAccountSource(value) {
|
|
378
|
+
if (value === "wallet_identity" || value === "review_execution") {
|
|
379
|
+
return value;
|
|
380
|
+
}
|
|
381
|
+
throw new ActivityStoreError("Unexpected account source");
|
|
382
|
+
}
|
|
383
|
+
function asExternalActivityScanKind(value) {
|
|
384
|
+
if (value === "digest_lookup" || value === "account_scan" || value === "function_scan") {
|
|
385
|
+
return value;
|
|
386
|
+
}
|
|
387
|
+
throw new ActivityStoreError("Unexpected external activity scan kind");
|
|
388
|
+
}
|
|
389
|
+
function asExternalActivityRelationship(value) {
|
|
390
|
+
if (typeof value === "string" && EXTERNAL_ACTIVITY_RELATIONSHIPS.includes(value)) {
|
|
391
|
+
return value;
|
|
392
|
+
}
|
|
393
|
+
throw new ActivityStoreError("Unexpected external activity relationship");
|
|
394
|
+
}
|
|
395
|
+
function asExternalActivityStatus(value) {
|
|
396
|
+
if (typeof value === "string" && EXTERNAL_ACTIVITY_STATUSES.includes(value)) {
|
|
397
|
+
return value;
|
|
398
|
+
}
|
|
399
|
+
throw new ActivityStoreError("Unexpected external activity status");
|
|
400
|
+
}
|
|
401
|
+
function asExternalActivityIncompleteReason(value) {
|
|
402
|
+
if (typeof value === "string" && EXTERNAL_ACTIVITY_INCOMPLETE_REASONS.includes(value)) {
|
|
403
|
+
return value;
|
|
404
|
+
}
|
|
405
|
+
throw new ActivityStoreError("Unexpected external activity incomplete reason");
|
|
406
|
+
}
|
|
407
|
+
export function isSameReviewExecution(existing, accountId, input) {
|
|
408
|
+
return (existing.plan_id === input.planId &&
|
|
409
|
+
existing.account_id === accountId &&
|
|
410
|
+
existing.status === input.status &&
|
|
411
|
+
nullableString(existing.tx_digest) === nullableString(input.txDigest) &&
|
|
412
|
+
nullableString(existing.explorer_url) === nullableString(input.explorerUrl) &&
|
|
413
|
+
nullableString(existing.failure_reason) === nullableString(input.failureReason));
|
|
414
|
+
}
|
|
415
|
+
export function canAdvanceReviewExecution(existing, accountId, input) {
|
|
416
|
+
if (existing.plan_id !== input.planId || existing.account_id !== accountId) {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
if (existing.status !== "signed_pending_result") {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
if (input.status === "signed_pending_result") {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
const nextDigest = nullableString(input.txDigest);
|
|
426
|
+
return existing.tx_digest === null || existing.tx_digest === nextDigest;
|
|
427
|
+
}
|
|
428
|
+
function nullableString(value) {
|
|
429
|
+
return value ?? null;
|
|
430
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { EXTERNAL_ACTIVITY_SCAN_MAX_LIMIT } from "./activityStore.js";
|
|
2
|
+
import { DB_USER_VERSION } from "./schemaVersion.js";
|
|
3
|
+
import { ActivityStoreError } from "./sqliteActivityStoreTypes.js";
|
|
4
|
+
const EXTERNAL_ACTIVITY_SCAN_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_external_activity_scans_account_fetched
|
|
5
|
+
ON external_activity_scans(account_id, fetched_at)`;
|
|
6
|
+
export function configureDatabase(db) {
|
|
7
|
+
db.exec("PRAGMA journal_mode=WAL");
|
|
8
|
+
db.exec("PRAGMA synchronous=NORMAL");
|
|
9
|
+
db.exec("PRAGMA foreign_keys=ON");
|
|
10
|
+
}
|
|
11
|
+
export function initializeDatabase(db) {
|
|
12
|
+
const currentUserVersion = db.pragma("user_version", { simple: true });
|
|
13
|
+
if (currentUserVersion > DB_USER_VERSION) {
|
|
14
|
+
throw new ActivityStoreError(`Local activity database version ${currentUserVersion} is newer than this runtime supports (${DB_USER_VERSION}).`);
|
|
15
|
+
}
|
|
16
|
+
db.exec(`
|
|
17
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
18
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
19
|
+
sui_address TEXT NOT NULL UNIQUE,
|
|
20
|
+
first_seen_at TEXT NOT NULL,
|
|
21
|
+
last_used_at TEXT NOT NULL,
|
|
22
|
+
first_source TEXT NOT NULL CHECK (first_source IN ('wallet_identity', 'review_execution')),
|
|
23
|
+
last_source TEXT NOT NULL CHECK (last_source IN ('wallet_identity', 'review_execution'))
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
CREATE TABLE IF NOT EXISTS active_account_context (
|
|
27
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
28
|
+
account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL,
|
|
29
|
+
source TEXT NOT NULL CHECK (source IN ('wallet_identity', 'cleared')),
|
|
30
|
+
set_at TEXT NOT NULL,
|
|
31
|
+
wallet_name TEXT,
|
|
32
|
+
wallet_id TEXT,
|
|
33
|
+
CHECK (
|
|
34
|
+
(source = 'cleared' AND account_id IS NULL)
|
|
35
|
+
OR (source = 'wallet_identity' AND account_id IS NOT NULL)
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
CREATE TABLE IF NOT EXISTS review_sessions (
|
|
40
|
+
id TEXT PRIMARY KEY,
|
|
41
|
+
plan_id TEXT NOT NULL,
|
|
42
|
+
action_kind TEXT NOT NULL,
|
|
43
|
+
adapter_id TEXT NOT NULL,
|
|
44
|
+
protocol TEXT NOT NULL,
|
|
45
|
+
account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL,
|
|
46
|
+
current_status TEXT NOT NULL,
|
|
47
|
+
plan_json TEXT NOT NULL,
|
|
48
|
+
intent_json TEXT,
|
|
49
|
+
created_at TEXT NOT NULL,
|
|
50
|
+
updated_at TEXT NOT NULL
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_review_sessions_account_created
|
|
54
|
+
ON review_sessions(account_id, created_at);
|
|
55
|
+
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_review_sessions_status_account
|
|
57
|
+
ON review_sessions(current_status, account_id, created_at);
|
|
58
|
+
|
|
59
|
+
CREATE TABLE IF NOT EXISTS review_state_snapshots (
|
|
60
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
61
|
+
review_session_id TEXT NOT NULL REFERENCES review_sessions(id) ON DELETE RESTRICT,
|
|
62
|
+
plan_id TEXT NOT NULL,
|
|
63
|
+
account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE RESTRICT,
|
|
64
|
+
status TEXT NOT NULL CHECK (status IN ('ready_for_wallet_review', 'refresh_required', 'blocked')),
|
|
65
|
+
blocked_reason TEXT,
|
|
66
|
+
refresh_reason TEXT,
|
|
67
|
+
state_json TEXT NOT NULL,
|
|
68
|
+
updated_at TEXT NOT NULL,
|
|
69
|
+
recorded_at TEXT NOT NULL,
|
|
70
|
+
CHECK (
|
|
71
|
+
(status = 'blocked' AND blocked_reason IS NOT NULL AND refresh_reason IS NULL)
|
|
72
|
+
OR (status = 'refresh_required' AND refresh_reason IS NOT NULL AND blocked_reason IS NULL)
|
|
73
|
+
OR (status = 'ready_for_wallet_review' AND blocked_reason IS NULL AND refresh_reason IS NULL)
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_review_state_snapshots_session_recorded
|
|
78
|
+
ON review_state_snapshots(review_session_id, recorded_at);
|
|
79
|
+
|
|
80
|
+
CREATE TABLE IF NOT EXISTS review_status_transitions (
|
|
81
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
82
|
+
review_session_id TEXT NOT NULL REFERENCES review_sessions(id) ON DELETE RESTRICT,
|
|
83
|
+
event TEXT NOT NULL CHECK (
|
|
84
|
+
event IN ('created', 'opened', 'wallet_connected', 'state_computed', 'result_recorded', 'expired')
|
|
85
|
+
),
|
|
86
|
+
from_status TEXT,
|
|
87
|
+
to_status TEXT NOT NULL,
|
|
88
|
+
account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL,
|
|
89
|
+
reason TEXT,
|
|
90
|
+
transitioned_at TEXT NOT NULL
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_review_transitions_session_time
|
|
94
|
+
ON review_status_transitions(review_session_id, transitioned_at);
|
|
95
|
+
|
|
96
|
+
CREATE TABLE IF NOT EXISTS review_executions (
|
|
97
|
+
review_session_id TEXT PRIMARY KEY REFERENCES review_sessions(id) ON DELETE RESTRICT,
|
|
98
|
+
plan_id TEXT NOT NULL,
|
|
99
|
+
account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE RESTRICT,
|
|
100
|
+
status TEXT NOT NULL CHECK (status IN ('signed_pending_result', 'success', 'failure')),
|
|
101
|
+
tx_digest TEXT,
|
|
102
|
+
explorer_url TEXT,
|
|
103
|
+
failure_reason TEXT,
|
|
104
|
+
result_json TEXT NOT NULL,
|
|
105
|
+
recorded_at TEXT NOT NULL,
|
|
106
|
+
updated_at TEXT NOT NULL,
|
|
107
|
+
CHECK (
|
|
108
|
+
(status = 'failure' AND failure_reason IS NOT NULL)
|
|
109
|
+
OR (status != 'failure' AND failure_reason IS NULL)
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_review_executions_account_updated
|
|
114
|
+
ON review_executions(account_id, updated_at);
|
|
115
|
+
|
|
116
|
+
CREATE INDEX IF NOT EXISTS idx_review_executions_digest
|
|
117
|
+
ON review_executions(tx_digest);
|
|
118
|
+
|
|
119
|
+
${externalActivityScansTableSql("external_activity_scans", { ifNotExists: true })};
|
|
120
|
+
|
|
121
|
+
${EXTERNAL_ACTIVITY_SCAN_INDEX_SQL};
|
|
122
|
+
|
|
123
|
+
CREATE TABLE IF NOT EXISTS external_activity_transactions (
|
|
124
|
+
account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE RESTRICT,
|
|
125
|
+
digest TEXT NOT NULL,
|
|
126
|
+
relationship TEXT NOT NULL CHECK (relationship IN ('affected', 'sent')),
|
|
127
|
+
checkpoint TEXT,
|
|
128
|
+
timestamp TEXT,
|
|
129
|
+
status TEXT NOT NULL CHECK (status IN ('success', 'failure', 'unknown')),
|
|
130
|
+
known_sender_account_id INTEGER REFERENCES accounts(id) ON DELETE SET NULL,
|
|
131
|
+
first_scan_id TEXT NOT NULL REFERENCES external_activity_scans(scan_id) ON DELETE RESTRICT,
|
|
132
|
+
last_scan_id TEXT NOT NULL REFERENCES external_activity_scans(scan_id) ON DELETE RESTRICT,
|
|
133
|
+
first_fetched_at TEXT NOT NULL,
|
|
134
|
+
last_fetched_at TEXT NOT NULL,
|
|
135
|
+
detail_json TEXT,
|
|
136
|
+
PRIMARY KEY (account_id, digest, relationship)
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_external_activity_transactions_account_time
|
|
140
|
+
ON external_activity_transactions(account_id, timestamp);
|
|
141
|
+
|
|
142
|
+
CREATE INDEX IF NOT EXISTS idx_external_activity_transactions_digest
|
|
143
|
+
ON external_activity_transactions(digest);
|
|
144
|
+
|
|
145
|
+
CREATE TABLE IF NOT EXISTS local_settings (
|
|
146
|
+
key TEXT PRIMARY KEY,
|
|
147
|
+
value_json TEXT NOT NULL,
|
|
148
|
+
updated_at TEXT NOT NULL
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
CREATE TABLE IF NOT EXISTS coin_metadata_cache (
|
|
152
|
+
coin_type TEXT NOT NULL,
|
|
153
|
+
chain_identifier TEXT NOT NULL,
|
|
154
|
+
decimals INTEGER NOT NULL CHECK (decimals >= 0 AND decimals <= 255),
|
|
155
|
+
symbol TEXT NOT NULL,
|
|
156
|
+
name TEXT NOT NULL,
|
|
157
|
+
fetched_at TEXT NOT NULL,
|
|
158
|
+
expires_at TEXT NOT NULL,
|
|
159
|
+
PRIMARY KEY (coin_type, chain_identifier)
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
`);
|
|
163
|
+
migrateDatabase(db, currentUserVersion);
|
|
164
|
+
if (db.pragma("user_version", { simple: true }) !== DB_USER_VERSION) {
|
|
165
|
+
db.pragma(`user_version = ${DB_USER_VERSION}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function migrateDatabase(db, currentUserVersion) {
|
|
169
|
+
if (currentUserVersion < 3 && !tableHasColumn(db, "external_activity_transactions", "detail_json")) {
|
|
170
|
+
db.exec("ALTER TABLE external_activity_transactions ADD COLUMN detail_json TEXT");
|
|
171
|
+
}
|
|
172
|
+
if (currentUserVersion > 0 && currentUserVersion < 4 && tableExists(db, "external_activity_scans")) {
|
|
173
|
+
rebuildExternalActivityScansForFunctionScan(db);
|
|
174
|
+
}
|
|
175
|
+
if (currentUserVersion > 0 && currentUserVersion < 5 && tableExists(db, "active_account_context")) {
|
|
176
|
+
if (!tableHasColumn(db, "active_account_context", "wallet_name")) {
|
|
177
|
+
db.exec("ALTER TABLE active_account_context ADD COLUMN wallet_name TEXT");
|
|
178
|
+
}
|
|
179
|
+
if (!tableHasColumn(db, "active_account_context", "wallet_id")) {
|
|
180
|
+
db.exec("ALTER TABLE active_account_context ADD COLUMN wallet_id TEXT");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function tableHasColumn(db, table, column) {
|
|
185
|
+
const rows = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
186
|
+
return rows.some((row) => row.name === column);
|
|
187
|
+
}
|
|
188
|
+
function tableExists(db, table) {
|
|
189
|
+
const row = db
|
|
190
|
+
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
|
|
191
|
+
.get(table);
|
|
192
|
+
return row !== undefined;
|
|
193
|
+
}
|
|
194
|
+
function rebuildExternalActivityScansForFunctionScan(db) {
|
|
195
|
+
const previousForeignKeys = db.pragma("foreign_keys", { simple: true });
|
|
196
|
+
const rebuild = db.transaction(() => {
|
|
197
|
+
db.exec(`
|
|
198
|
+
DROP TABLE IF EXISTS external_activity_scans_new;
|
|
199
|
+
|
|
200
|
+
${externalActivityScansTableSql("external_activity_scans_new", { ifNotExists: false })};
|
|
201
|
+
|
|
202
|
+
INSERT INTO external_activity_scans_new
|
|
203
|
+
(scan_id, kind, account_id, relationship, input_digest, from_checkpoint, to_checkpoint,
|
|
204
|
+
from_timestamp, to_timestamp, limit_count, request_cursor, response_cursor, endpoint_host,
|
|
205
|
+
chain_identifier, fetched_at, stored_count, skipped_count, has_more, window_complete,
|
|
206
|
+
incomplete_reason)
|
|
207
|
+
SELECT
|
|
208
|
+
scan_id, kind, account_id, relationship, input_digest, from_checkpoint, to_checkpoint,
|
|
209
|
+
from_timestamp, to_timestamp, limit_count, request_cursor, response_cursor, endpoint_host,
|
|
210
|
+
chain_identifier, fetched_at, stored_count, skipped_count, has_more, window_complete,
|
|
211
|
+
incomplete_reason
|
|
212
|
+
FROM external_activity_scans;
|
|
213
|
+
|
|
214
|
+
DROP TABLE external_activity_scans;
|
|
215
|
+
ALTER TABLE external_activity_scans_new RENAME TO external_activity_scans;
|
|
216
|
+
${EXTERNAL_ACTIVITY_SCAN_INDEX_SQL};
|
|
217
|
+
`);
|
|
218
|
+
const foreignKeyFailures = db.prepare("PRAGMA foreign_key_check").all();
|
|
219
|
+
if (foreignKeyFailures.length > 0) {
|
|
220
|
+
throw new ActivityStoreError("Local activity database migration failed foreign key check");
|
|
221
|
+
}
|
|
222
|
+
db.pragma(`user_version = ${DB_USER_VERSION}`);
|
|
223
|
+
});
|
|
224
|
+
db.pragma("foreign_keys = OFF");
|
|
225
|
+
try {
|
|
226
|
+
rebuild();
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
db.pragma(`foreign_keys = ${previousForeignKeys === 0 ? "OFF" : "ON"}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function externalActivityScansTableSql(tableName, options) {
|
|
233
|
+
return `CREATE TABLE ${options.ifNotExists ? "IF NOT EXISTS " : ""}${tableName} (
|
|
234
|
+
scan_id TEXT PRIMARY KEY,
|
|
235
|
+
kind TEXT NOT NULL CHECK (kind IN ('digest_lookup', 'account_scan', 'function_scan')),
|
|
236
|
+
account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE RESTRICT,
|
|
237
|
+
relationship TEXT NOT NULL CHECK (relationship IN ('affected', 'sent')),
|
|
238
|
+
input_digest TEXT,
|
|
239
|
+
from_checkpoint TEXT,
|
|
240
|
+
to_checkpoint TEXT,
|
|
241
|
+
from_timestamp TEXT,
|
|
242
|
+
to_timestamp TEXT,
|
|
243
|
+
limit_count INTEGER NOT NULL CHECK (limit_count >= 1 AND limit_count <= ${EXTERNAL_ACTIVITY_SCAN_MAX_LIMIT}),
|
|
244
|
+
request_cursor TEXT,
|
|
245
|
+
response_cursor TEXT,
|
|
246
|
+
endpoint_host TEXT NOT NULL,
|
|
247
|
+
chain_identifier TEXT NOT NULL,
|
|
248
|
+
fetched_at TEXT NOT NULL,
|
|
249
|
+
stored_count INTEGER NOT NULL CHECK (stored_count >= 0),
|
|
250
|
+
skipped_count INTEGER NOT NULL CHECK (skipped_count >= 0),
|
|
251
|
+
has_more INTEGER NOT NULL CHECK (has_more IN (0, 1)),
|
|
252
|
+
window_complete INTEGER CHECK (window_complete IN (0, 1)),
|
|
253
|
+
incomplete_reason TEXT CHECK (
|
|
254
|
+
incomplete_reason IS NULL
|
|
255
|
+
OR incomplete_reason IN ('limit_reached', 'ordering_unverified', 'cursor_invalid', 'provider_error')
|
|
256
|
+
)
|
|
257
|
+
)`;
|
|
258
|
+
}
|