@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,286 @@
|
|
|
1
|
+
import { assertTransactionFundingMatches } from "./transactionFunding.js";
|
|
2
|
+
import { adapterEvidenceClaimSchema, adapterInputProvenanceSchema, adapterOutputBoundarySchema, adapterSimulationEvidenceSchema, adapterSourceOfTruthSchema, transactionReviewDataSchema, TRANSACTION_REVIEW_SCHEMA_VERSION, WALLET_REVIEW_REQUIRED_PROHIBITED_OUTPUTS, WALLET_REVIEW_REQUIRED_SIMULATION_FIELDS, WALLET_REVIEW_REQUIRED_HUMAN_FIELDS, adapterExpiryEvidenceSchema, adapterHumanReadableReviewSchema, adapterGasEvidenceSchema, SUI_GAS_COIN_TYPE } from "./signableAdapterContract.js";
|
|
3
|
+
import { parseReviewTimeSimulationEvidence } from "./reviewTimeSimulationEvidence.js";
|
|
4
|
+
import { parseHumanReadableReviewEvidence } from "./humanReadableReviewEvidence.js";
|
|
5
|
+
export function buildWalletReviewOutputBoundary() {
|
|
6
|
+
return adapterOutputBoundarySchema.parse({
|
|
7
|
+
runtimeStatus: "emitted_pre_handoff",
|
|
8
|
+
mcpAndReviewUiMayExpose: [
|
|
9
|
+
"human_readable_review",
|
|
10
|
+
"ptb_visualization_artifact",
|
|
11
|
+
"diagnostics",
|
|
12
|
+
"status_checks"
|
|
13
|
+
],
|
|
14
|
+
prohibited: [...WALLET_REVIEW_REQUIRED_PROHIBITED_OUTPUTS]
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function buildWalletReviewInputProvenance(input) {
|
|
18
|
+
return adapterInputProvenanceSchema.parse({
|
|
19
|
+
kind: input.kind,
|
|
20
|
+
sourceId: input.sourceId,
|
|
21
|
+
capturedAt: input.capturedAt,
|
|
22
|
+
authority: "untrusted_until_review_regenerates_and_verifies",
|
|
23
|
+
...(input.userSelectionSource ? { userSelectionSource: input.userSelectionSource } : {})
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
import { mapSwapQuotePolicyEvidenceToContractDraft, parseSwapQuotePolicyEvidence } from "./swapQuotePolicyEvidence.js";
|
|
27
|
+
import { mapTransactionObjectOwnershipEvidenceToContractDraft } from "./transactionObjectOwnershipEvidence.js";
|
|
28
|
+
export function draftWalletReviewContractEvidencePool(input) {
|
|
29
|
+
const quote = mapSwapQuotePolicyEvidenceToContractDraft(input.quotePolicy);
|
|
30
|
+
if (quote.status === "unsupported") {
|
|
31
|
+
return { status: "declined", reason: `quote policy draft unsupported: ${quote.reason}` };
|
|
32
|
+
}
|
|
33
|
+
const ownership = mapTransactionObjectOwnershipEvidenceToContractDraft(input.objectOwnership);
|
|
34
|
+
if (ownership.status === "unsupported") {
|
|
35
|
+
return { status: "declined", reason: `object ownership draft unsupported: ${ownership.reason}` };
|
|
36
|
+
}
|
|
37
|
+
const sourceReferences = [...quote.sourceReferences, ownership.sourceReferences];
|
|
38
|
+
const sourceIds = new Set();
|
|
39
|
+
for (const record of sourceReferences) {
|
|
40
|
+
if (sourceIds.has(record.id)) {
|
|
41
|
+
return { status: "declined", reason: `duplicate sourceReferences id: ${record.id}` };
|
|
42
|
+
}
|
|
43
|
+
sourceIds.add(record.id);
|
|
44
|
+
}
|
|
45
|
+
const evidenceClaims = [...quote.evidenceClaims, ...ownership.evidenceClaims];
|
|
46
|
+
const claimIds = new Set();
|
|
47
|
+
for (const claim of evidenceClaims) {
|
|
48
|
+
if (claimIds.has(claim.id)) {
|
|
49
|
+
return { status: "declined", reason: `duplicate evidence claim id: ${claim.id}` };
|
|
50
|
+
}
|
|
51
|
+
claimIds.add(claim.id);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
status: "drafted",
|
|
55
|
+
sourceReferences,
|
|
56
|
+
evidenceClaims,
|
|
57
|
+
rawQuantities: quote.rawQuantities,
|
|
58
|
+
objectOwnership: ownership.objectOwnership,
|
|
59
|
+
gasObjectOwnershipLinks: ownership.gasObjectOwnershipLinks
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function mapReviewTimeSimulationEvidenceToContractDraft(evidenceInput) {
|
|
63
|
+
const evidence = parseReviewTimeSimulationEvidence(evidenceInput);
|
|
64
|
+
const sourceId = "review_time_simulation_source";
|
|
65
|
+
const claimId = "review_time_simulation_claim";
|
|
66
|
+
const sourceReferences = adapterSourceOfTruthSchema.parse({
|
|
67
|
+
id: sourceId,
|
|
68
|
+
kind: "review_time_simulation",
|
|
69
|
+
network: "sui:mainnet",
|
|
70
|
+
source: "Sui gRPC simulation with validation enabled; returned transaction bytes and effects matched the stored transaction",
|
|
71
|
+
verifiedAt: evidence.simulatedAt,
|
|
72
|
+
fields: [...WALLET_REVIEW_REQUIRED_SIMULATION_FIELDS]
|
|
73
|
+
});
|
|
74
|
+
const evidenceClaim = adapterEvidenceClaimSchema.parse({
|
|
75
|
+
id: claimId,
|
|
76
|
+
factKind: "simulation_result",
|
|
77
|
+
sourceEvidenceId: sourceId,
|
|
78
|
+
provider: evidence.provider,
|
|
79
|
+
checksEnabled: evidence.checksEnabled,
|
|
80
|
+
simulatedAt: evidence.simulatedAt,
|
|
81
|
+
status: evidence.status,
|
|
82
|
+
requiredFields: [...evidence.requiredFields],
|
|
83
|
+
missingFields: [...evidence.missingFields]
|
|
84
|
+
});
|
|
85
|
+
const simulation = adapterSimulationEvidenceSchema.parse({
|
|
86
|
+
evidenceClaimId: claimId,
|
|
87
|
+
transactionDigest: evidence.transactionDigest,
|
|
88
|
+
provider: evidence.provider,
|
|
89
|
+
checksEnabled: evidence.checksEnabled,
|
|
90
|
+
simulatedAt: evidence.simulatedAt,
|
|
91
|
+
status: evidence.status,
|
|
92
|
+
requiredFields: [...evidence.requiredFields],
|
|
93
|
+
missingFields: [...evidence.missingFields]
|
|
94
|
+
});
|
|
95
|
+
return { status: "mapped", sourceReferences, evidenceClaim, simulation };
|
|
96
|
+
}
|
|
97
|
+
export function mapReviewTimeSimulationGasToContractDraft(evidenceInput, gasObjects) {
|
|
98
|
+
const evidence = parseReviewTimeSimulationEvidence(evidenceInput);
|
|
99
|
+
const summary = evidence.effects.gasCostSummary;
|
|
100
|
+
const gasUsed = BigInt(summary.computationCostRaw) +
|
|
101
|
+
BigInt(summary.storageCostRaw) -
|
|
102
|
+
BigInt(summary.storageRebateRaw);
|
|
103
|
+
const sourceReferences = [];
|
|
104
|
+
const evidenceClaims = [];
|
|
105
|
+
const gasAsset = { coinType: SUI_GAS_COIN_TYPE };
|
|
106
|
+
const gasUsedSourceId = "review_time_simulation_gas_used_source";
|
|
107
|
+
const gasUsedClaimId = "review_time_simulation_gas_used_claim";
|
|
108
|
+
// A storage rebate can make net gas negative. Keep the verified budget and
|
|
109
|
+
// simulation components; do not invent an unsigned zero or a spending claim.
|
|
110
|
+
if (gasUsed >= 0n) {
|
|
111
|
+
sourceReferences.push(adapterSourceOfTruthSchema.parse({
|
|
112
|
+
id: gasUsedSourceId,
|
|
113
|
+
kind: "review_time_simulation",
|
|
114
|
+
network: "sui:mainnet",
|
|
115
|
+
source: "Network fee components estimated by simulation of the stored transaction with validation enabled",
|
|
116
|
+
verifiedAt: evidence.simulatedAt,
|
|
117
|
+
fields: ["gasUsedRaw", "asset", "amountRole"]
|
|
118
|
+
}));
|
|
119
|
+
evidenceClaims.push(adapterEvidenceClaimSchema.parse({
|
|
120
|
+
id: gasUsedClaimId,
|
|
121
|
+
factKind: "raw_quantity_amount",
|
|
122
|
+
sourceEvidenceId: gasUsedSourceId,
|
|
123
|
+
role: "gas_used",
|
|
124
|
+
asset: gasAsset,
|
|
125
|
+
rawAmount: gasUsed.toString()
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
const gasBudgetRaw = evidence.transaction.gasBudgetRaw;
|
|
129
|
+
let gasBudgetClaimId;
|
|
130
|
+
if (gasBudgetRaw !== undefined) {
|
|
131
|
+
const gasBudgetSourceId = "review_time_simulation_gas_budget_source";
|
|
132
|
+
gasBudgetClaimId = "review_time_simulation_gas_budget_claim";
|
|
133
|
+
sourceReferences.push(adapterSourceOfTruthSchema.parse({
|
|
134
|
+
id: gasBudgetSourceId,
|
|
135
|
+
kind: "review_time_simulation",
|
|
136
|
+
network: "sui:mainnet",
|
|
137
|
+
source: "Network fee limit (gas budget) recorded in the transaction used for simulation",
|
|
138
|
+
verifiedAt: evidence.simulatedAt,
|
|
139
|
+
fields: ["gasBudgetRaw", "asset", "amountRole"]
|
|
140
|
+
}));
|
|
141
|
+
evidenceClaims.push(adapterEvidenceClaimSchema.parse({
|
|
142
|
+
id: gasBudgetClaimId,
|
|
143
|
+
factKind: "raw_quantity_amount",
|
|
144
|
+
sourceEvidenceId: gasBudgetSourceId,
|
|
145
|
+
role: "gas_budget",
|
|
146
|
+
asset: gasAsset,
|
|
147
|
+
rawAmount: gasBudgetRaw
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
const gas = adapterGasEvidenceSchema.parse({
|
|
151
|
+
source: "review_time_simulation",
|
|
152
|
+
checkedAt: evidence.simulatedAt,
|
|
153
|
+
...(gasUsed >= 0n ? { gasUsedRaw: gasUsed.toString(), gasUsedClaimId } : {}),
|
|
154
|
+
...(gasBudgetRaw !== undefined && gasBudgetClaimId
|
|
155
|
+
? { gasBudgetRaw, gasBudgetClaimId }
|
|
156
|
+
: {}),
|
|
157
|
+
...(gasObjects.length > 0 ? { gasObjects } : {})
|
|
158
|
+
});
|
|
159
|
+
return { status: "mapped", sourceReferences, evidenceClaims, gas };
|
|
160
|
+
}
|
|
161
|
+
export function mapSwapQuotePolicyExpiryToContractDraft(evidenceInput, now) {
|
|
162
|
+
const evidence = parseSwapQuotePolicyEvidence(evidenceInput);
|
|
163
|
+
const checkedAt = now.toISOString();
|
|
164
|
+
const status = Date.parse(evidence.expiresAt) > now.getTime() ? "current" : "expired";
|
|
165
|
+
const sourceId = "swap_quote_policy_expiry_source";
|
|
166
|
+
const claimId = "swap_quote_policy_expiry_claim";
|
|
167
|
+
const sourceReferences = adapterSourceOfTruthSchema.parse({
|
|
168
|
+
id: sourceId,
|
|
169
|
+
kind: "validated_request_fact",
|
|
170
|
+
network: "sui:mainnet",
|
|
171
|
+
source: "Quote validity period (retrieval time plus validity duration), checked when the review was prepared",
|
|
172
|
+
verifiedAt: checkedAt,
|
|
173
|
+
fields: ["checkedAt", "expiresAt"]
|
|
174
|
+
});
|
|
175
|
+
const evidenceClaim = adapterEvidenceClaimSchema.parse({
|
|
176
|
+
id: claimId,
|
|
177
|
+
factKind: "expiry_status",
|
|
178
|
+
sourceEvidenceId: sourceId,
|
|
179
|
+
checkedAt,
|
|
180
|
+
status,
|
|
181
|
+
expiresAt: evidence.expiresAt
|
|
182
|
+
});
|
|
183
|
+
const expiry = adapterExpiryEvidenceSchema.parse({
|
|
184
|
+
checkedAt,
|
|
185
|
+
status,
|
|
186
|
+
expiresAt: evidence.expiresAt,
|
|
187
|
+
evidenceClaimId: claimId
|
|
188
|
+
});
|
|
189
|
+
return { status: "mapped", sourceReferences, evidenceClaim, expiry };
|
|
190
|
+
}
|
|
191
|
+
export function mapHumanReadableReviewEvidenceToContractDraft(evidenceInput) {
|
|
192
|
+
const evidence = parseHumanReadableReviewEvidence(evidenceInput);
|
|
193
|
+
const humanReadableReview = adapterHumanReadableReviewSchema.parse({
|
|
194
|
+
fields: [...WALLET_REVIEW_REQUIRED_HUMAN_FIELDS],
|
|
195
|
+
transactionDigest: evidence.transactionDigest,
|
|
196
|
+
source: "review_model_or_adapter_equivalent",
|
|
197
|
+
purpose: "human_review_before_wallet_authorization"
|
|
198
|
+
});
|
|
199
|
+
return { status: "mapped", humanReadableReview };
|
|
200
|
+
}
|
|
201
|
+
export function assembleTransactionReviewData(input) {
|
|
202
|
+
const pool = draftWalletReviewContractEvidencePool({
|
|
203
|
+
quotePolicy: input.quotePolicy,
|
|
204
|
+
objectOwnership: input.objectOwnership
|
|
205
|
+
});
|
|
206
|
+
if (pool.status === "declined") {
|
|
207
|
+
return pool;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
assertTransactionFundingMatches(input.reviewTimeSimulation.transaction.funding, input.objectOwnership.funding);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return { status: "declined", reason: "simulation and ownership funding evidence differ" };
|
|
214
|
+
}
|
|
215
|
+
const simulation = mapReviewTimeSimulationEvidenceToContractDraft(input.reviewTimeSimulation);
|
|
216
|
+
const gas = mapReviewTimeSimulationGasToContractDraft(input.reviewTimeSimulation, pool.gasObjectOwnershipLinks);
|
|
217
|
+
if (gas.status === "unsupported") {
|
|
218
|
+
return { status: "declined", reason: `gas evidence unsupported: ${gas.reason}` };
|
|
219
|
+
}
|
|
220
|
+
const expiry = mapSwapQuotePolicyExpiryToContractDraft(input.quotePolicy, input.now);
|
|
221
|
+
const human = mapHumanReadableReviewEvidenceToContractDraft(input.humanReadableReview);
|
|
222
|
+
const sourceReferences = [
|
|
223
|
+
...pool.sourceReferences,
|
|
224
|
+
simulation.sourceReferences,
|
|
225
|
+
...gas.sourceReferences,
|
|
226
|
+
expiry.sourceReferences
|
|
227
|
+
];
|
|
228
|
+
const sourceIds = new Set();
|
|
229
|
+
for (const record of sourceReferences) {
|
|
230
|
+
if (sourceIds.has(record.id)) {
|
|
231
|
+
return { status: "declined", reason: `duplicate sourceReferences id: ${record.id}` };
|
|
232
|
+
}
|
|
233
|
+
sourceIds.add(record.id);
|
|
234
|
+
}
|
|
235
|
+
const evidenceClaims = [
|
|
236
|
+
...pool.evidenceClaims,
|
|
237
|
+
simulation.evidenceClaim,
|
|
238
|
+
...gas.evidenceClaims,
|
|
239
|
+
expiry.evidenceClaim
|
|
240
|
+
];
|
|
241
|
+
const claimIds = new Set();
|
|
242
|
+
for (const claim of evidenceClaims) {
|
|
243
|
+
if (claimIds.has(claim.id)) {
|
|
244
|
+
return { status: "declined", reason: `duplicate evidence claim id: ${claim.id}` };
|
|
245
|
+
}
|
|
246
|
+
claimIds.add(claim.id);
|
|
247
|
+
}
|
|
248
|
+
const quote = parseSwapQuotePolicyEvidence(input.quotePolicy);
|
|
249
|
+
const candidate = {
|
|
250
|
+
schemaVersion: TRANSACTION_REVIEW_SCHEMA_VERSION,
|
|
251
|
+
adapterId: input.adapterId,
|
|
252
|
+
protocol: input.protocol,
|
|
253
|
+
actionKind: input.actionKind,
|
|
254
|
+
network: "sui:mainnet",
|
|
255
|
+
inputProvenance: buildWalletReviewInputProvenance(input.provenance),
|
|
256
|
+
sourceReferences,
|
|
257
|
+
evidenceClaims,
|
|
258
|
+
rawQuantities: pool.rawQuantities,
|
|
259
|
+
gas: gas.gas,
|
|
260
|
+
expiry: expiry.expiry,
|
|
261
|
+
slippageOrMinOut: {
|
|
262
|
+
status: "required_and_verified",
|
|
263
|
+
quoteEvidenceId: quote.quoteEvidenceId,
|
|
264
|
+
quoteEvidenceClaimId: "swap_quote_min_out_claim",
|
|
265
|
+
maxSlippageBps: quote.maxSlippageBps,
|
|
266
|
+
minOutRaw: quote.minimumOutput.raw,
|
|
267
|
+
policySource: "adapter_policy_from_quote_evidence",
|
|
268
|
+
policyEvidenceClaimId: "swap_quote_slippage_policy_claim"
|
|
269
|
+
},
|
|
270
|
+
objectOwnership: pool.objectOwnership,
|
|
271
|
+
simulation: simulation.simulation,
|
|
272
|
+
humanReadableReview: human.humanReadableReview,
|
|
273
|
+
outputBoundary: buildWalletReviewOutputBoundary(),
|
|
274
|
+
reviewedTransactionDigest: input.reviewedTransactionDigest
|
|
275
|
+
};
|
|
276
|
+
const parsed = transactionReviewDataSchema.safeParse(candidate);
|
|
277
|
+
if (!parsed.success) {
|
|
278
|
+
const issue = parsed.error.issues[0];
|
|
279
|
+
const path = issue?.path.join(".") ?? "";
|
|
280
|
+
return {
|
|
281
|
+
status: "declined",
|
|
282
|
+
reason: `contract schema rejected: ${path ? `${path}: ` : ""}${issue?.message ?? "unknown issue"}`
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return { status: "emitted", contract: parsed.data };
|
|
286
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { parseSuiAddress } from "../suiAddress.js";
|
|
2
|
+
import { DEFAULT_DEEPBOOK_OFFICIAL_INDEXER_INTERVAL, deepbookOfficialIndexerIntervalDurationMs } from "../read/deepbookOfficialIndexerSource.js";
|
|
3
|
+
import { requestedAccountEffectsForTransaction } from "./transactionActivityAccountEffects.js";
|
|
4
|
+
export function buildAccountAssetTimeline(input) {
|
|
5
|
+
const account = parseSuiAddress(input.account);
|
|
6
|
+
if (account === undefined) {
|
|
7
|
+
throw new Error("Account asset timeline account must be a valid Sui address");
|
|
8
|
+
}
|
|
9
|
+
const fromMs = parseRequiredUtc(input.from, "from");
|
|
10
|
+
const toMs = parseRequiredUtc(input.to, "to");
|
|
11
|
+
if (fromMs >= toMs) {
|
|
12
|
+
throw new Error("Account asset timeline from must be before to");
|
|
13
|
+
}
|
|
14
|
+
const interval = input.interval ?? DEFAULT_DEEPBOOK_OFFICIAL_INDEXER_INTERVAL;
|
|
15
|
+
const limitations = new Set(["no_balance_anchor"]);
|
|
16
|
+
if (!input.coverage.accountKnown) {
|
|
17
|
+
limitations.add("account_not_known");
|
|
18
|
+
}
|
|
19
|
+
for (const limitation of input.coverage.limitations) {
|
|
20
|
+
limitations.add(limitation);
|
|
21
|
+
}
|
|
22
|
+
if (input.transactionsTruncated === true) {
|
|
23
|
+
limitations.add("source_transactions_truncated");
|
|
24
|
+
}
|
|
25
|
+
const bucketMs = deepbookOfficialIndexerIntervalDurationMs(interval);
|
|
26
|
+
const bars = new Map();
|
|
27
|
+
let analyzedTransactionCount = 0;
|
|
28
|
+
let skippedTransactionCount = 0;
|
|
29
|
+
for (const transaction of input.transactions) {
|
|
30
|
+
if (transaction.account !== account) {
|
|
31
|
+
skippedTransactionCount += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (transaction.timestamp === undefined) {
|
|
35
|
+
skippedTransactionCount += 1;
|
|
36
|
+
limitations.add("transaction_timestamp_unavailable");
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const timestampMs = Date.parse(transaction.timestamp);
|
|
40
|
+
if (!Number.isFinite(timestampMs)) {
|
|
41
|
+
skippedTransactionCount += 1;
|
|
42
|
+
limitations.add("transaction_timestamp_unavailable");
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (timestampMs < fromMs || timestampMs >= toMs) {
|
|
46
|
+
skippedTransactionCount += 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const effects = requestedAccountEffectsForTransaction(transactionRecordToActivityFact(transaction), account);
|
|
50
|
+
if (effects.balanceChangeCompleteness === "unavailable") {
|
|
51
|
+
limitations.add("transaction_details_unavailable");
|
|
52
|
+
}
|
|
53
|
+
if (effects.balanceChangeCompleteness === "truncated") {
|
|
54
|
+
limitations.add("provider_balance_changes_truncated");
|
|
55
|
+
}
|
|
56
|
+
analyzedTransactionCount += 1;
|
|
57
|
+
const bucketStartMs = Math.floor(timestampMs / bucketMs) * bucketMs;
|
|
58
|
+
const bucketEndMs = bucketStartMs + bucketMs;
|
|
59
|
+
for (const change of effects.balanceChanges) {
|
|
60
|
+
const amount = BigInt(change.amountRaw);
|
|
61
|
+
const key = `${bucketStartMs}\n${change.coinType}`;
|
|
62
|
+
const entry = bars.get(key) ?? {
|
|
63
|
+
bucketStartMs,
|
|
64
|
+
bucketEndMs,
|
|
65
|
+
coinType: change.coinType,
|
|
66
|
+
increaseRaw: 0n,
|
|
67
|
+
decreaseRaw: 0n,
|
|
68
|
+
netRaw: 0n,
|
|
69
|
+
digests: new Set()
|
|
70
|
+
};
|
|
71
|
+
entry.netRaw += amount;
|
|
72
|
+
if (amount > 0n) {
|
|
73
|
+
entry.increaseRaw += amount;
|
|
74
|
+
}
|
|
75
|
+
else if (amount < 0n) {
|
|
76
|
+
entry.decreaseRaw += -amount;
|
|
77
|
+
}
|
|
78
|
+
entry.digests.add(transaction.digest);
|
|
79
|
+
bars.set(key, entry);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const netFlowBars = [...bars.values()].map(netFlowBarFromAccumulator).sort(compareNetFlowBars);
|
|
83
|
+
if (netFlowBars.length === 0) {
|
|
84
|
+
limitations.add("no_observed_account_balance_changes");
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
account,
|
|
88
|
+
requestedRange: {
|
|
89
|
+
from: input.from,
|
|
90
|
+
to: input.to
|
|
91
|
+
},
|
|
92
|
+
bucket: {
|
|
93
|
+
interval,
|
|
94
|
+
durationMs: bucketMs,
|
|
95
|
+
alignment: "utc_epoch"
|
|
96
|
+
},
|
|
97
|
+
status: statusForCoverage(input.coverage),
|
|
98
|
+
balanceStatus: "unavailable_no_balance_anchor",
|
|
99
|
+
coverage: input.coverage,
|
|
100
|
+
sourceTransactionCount: input.transactions.length,
|
|
101
|
+
analyzedTransactionCount,
|
|
102
|
+
skippedTransactionCount,
|
|
103
|
+
netFlowBars,
|
|
104
|
+
balanceBars: [],
|
|
105
|
+
quantitySemantics: {
|
|
106
|
+
netFlowBars: "observed_account_scoped_raw_token_balance_changes",
|
|
107
|
+
balanceBars: "unavailable_without_balance_anchor"
|
|
108
|
+
},
|
|
109
|
+
limitations: [...limitations].sort()
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function transactionRecordToActivityFact(record) {
|
|
113
|
+
return {
|
|
114
|
+
digest: record.digest,
|
|
115
|
+
checkpoint: record.checkpoint,
|
|
116
|
+
timestamp: record.timestamp,
|
|
117
|
+
status: record.status,
|
|
118
|
+
details: record.details
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function netFlowBarFromAccumulator(value) {
|
|
122
|
+
return {
|
|
123
|
+
bucketStart: new Date(value.bucketStartMs).toISOString(),
|
|
124
|
+
bucketEnd: new Date(value.bucketEndMs).toISOString(),
|
|
125
|
+
coinType: value.coinType,
|
|
126
|
+
increaseRaw: value.increaseRaw.toString(),
|
|
127
|
+
decreaseRaw: value.decreaseRaw.toString(),
|
|
128
|
+
netRaw: value.netRaw.toString(),
|
|
129
|
+
transactionCount: value.digests.size
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function statusForCoverage(coverage) {
|
|
133
|
+
if (!coverage.accountKnown) {
|
|
134
|
+
return "account_not_known";
|
|
135
|
+
}
|
|
136
|
+
if (coverage.coverageStatus === "no_stored_scans") {
|
|
137
|
+
return "scan_needed";
|
|
138
|
+
}
|
|
139
|
+
if (coverage.coverageStatus === "partial") {
|
|
140
|
+
return "partial_coverage";
|
|
141
|
+
}
|
|
142
|
+
return "ok";
|
|
143
|
+
}
|
|
144
|
+
function parseRequiredUtc(value, field) {
|
|
145
|
+
const parsed = new Date(value);
|
|
146
|
+
if (Number.isNaN(parsed.getTime()) || parsed.toISOString() !== value) {
|
|
147
|
+
throw new Error(`Account asset timeline ${field} must be an ISO 8601 UTC timestamp`);
|
|
148
|
+
}
|
|
149
|
+
return parsed.getTime();
|
|
150
|
+
}
|
|
151
|
+
function compareNetFlowBars(a, b) {
|
|
152
|
+
const bucketComparison = a.bucketStart.localeCompare(b.bucketStart);
|
|
153
|
+
if (bucketComparison !== 0) {
|
|
154
|
+
return bucketComparison;
|
|
155
|
+
}
|
|
156
|
+
return a.coinType.localeCompare(b.coinType);
|
|
157
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { deepbookUsdcPriceHistoryQuantitySemantics, deepbookUsdcPriceHistoryResponseSummary } from "../read/deepbookReadHelpers.js";
|
|
2
|
+
import { DEEPBOOK_OFFICIAL_INDEXER_SOURCE_BASE, DEEPBOOK_OFFICIAL_INDEXER_USDC_REFERENCE } from "../read/deepbookSourceOwners.js";
|
|
3
|
+
import { DEEPBOOK_USDC_PRICE_HISTORY_UNSUPPORTED_CLAIMS } from "../read/readServiceTypes.js";
|
|
4
|
+
export async function attachDeepbookUsdcReferencesToTimeline(input) {
|
|
5
|
+
const baseSummary = emptyReferenceSummary();
|
|
6
|
+
if (input.timeline.netFlowBars.length === 0) {
|
|
7
|
+
return {
|
|
8
|
+
...input.timeline,
|
|
9
|
+
usdcReferences: { ...baseSummary, status: "no_timeline_bars" }
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const barsByCoin = groupBarsByCoin(input.timeline.netFlowBars);
|
|
13
|
+
const coinReferences = [];
|
|
14
|
+
for (const [coinType, bars] of barsByCoin) {
|
|
15
|
+
const history = await input.getPriceHistory({
|
|
16
|
+
coinType,
|
|
17
|
+
interval: input.timeline.bucket.interval,
|
|
18
|
+
start: input.timeline.requestedRange.from,
|
|
19
|
+
end: input.timeline.requestedRange.to
|
|
20
|
+
});
|
|
21
|
+
coinReferences.push(referenceForHistory(coinType, bars, history));
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
...input.timeline,
|
|
25
|
+
usdcReferences: {
|
|
26
|
+
...baseSummary,
|
|
27
|
+
status: referenceSummaryStatus(coinReferences),
|
|
28
|
+
coinReferences
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function emptyReferenceSummary() {
|
|
33
|
+
return {
|
|
34
|
+
status: "unavailable",
|
|
35
|
+
...DEEPBOOK_OFFICIAL_INDEXER_USDC_REFERENCE,
|
|
36
|
+
source: DEEPBOOK_OFFICIAL_INDEXER_SOURCE_BASE.kind,
|
|
37
|
+
chainRecomputedBySuiMcp: DEEPBOOK_OFFICIAL_INDEXER_SOURCE_BASE.chainRecomputedBySuiMcp,
|
|
38
|
+
quantitySemantics: deepbookUsdcPriceHistoryQuantitySemantics(),
|
|
39
|
+
responseSummary: deepbookUsdcPriceHistoryResponseSummary(),
|
|
40
|
+
unsupportedClaims: [...DEEPBOOK_USDC_PRICE_HISTORY_UNSUPPORTED_CLAIMS],
|
|
41
|
+
coinReferences: []
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function referenceForHistory(coinType, timelineBars, history) {
|
|
45
|
+
if (history.status === "unsupported_pair") {
|
|
46
|
+
return {
|
|
47
|
+
coinType,
|
|
48
|
+
status: "unsupported_asset",
|
|
49
|
+
reason: history.reason,
|
|
50
|
+
matchingPoolNames: history.matchingPoolNames,
|
|
51
|
+
availablePoolNames: history.availablePoolNames
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (history.status === "unsupported_range") {
|
|
55
|
+
return {
|
|
56
|
+
coinType,
|
|
57
|
+
status: "unsupported_range",
|
|
58
|
+
reason: history.reason,
|
|
59
|
+
requested: history.requested
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (history.status === "source_unavailable") {
|
|
63
|
+
return {
|
|
64
|
+
coinType,
|
|
65
|
+
status: "source_unavailable",
|
|
66
|
+
reason: history.reason,
|
|
67
|
+
pair: history.pair,
|
|
68
|
+
source: history.source
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const candlesByBucket = new Map(history.bars.map((bar) => [`${bar.start}\n${bar.end}`, bar]));
|
|
72
|
+
const barReferences = timelineBars.map((bar) => {
|
|
73
|
+
const candle = candlesByBucket.get(`${bar.bucketStart}\n${bar.bucketEnd}`);
|
|
74
|
+
return candle === undefined
|
|
75
|
+
? {
|
|
76
|
+
bucketStart: bar.bucketStart,
|
|
77
|
+
bucketEnd: bar.bucketEnd,
|
|
78
|
+
status: "missing_candle"
|
|
79
|
+
}
|
|
80
|
+
: {
|
|
81
|
+
bucketStart: bar.bucketStart,
|
|
82
|
+
bucketEnd: bar.bucketEnd,
|
|
83
|
+
status: "available",
|
|
84
|
+
candle
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
coinType,
|
|
89
|
+
status: barReferences.some((bar) => bar.status === "missing_candle")
|
|
90
|
+
? "partial"
|
|
91
|
+
: "available",
|
|
92
|
+
pair: history.pair,
|
|
93
|
+
candleAvailability: history.candleAvailability,
|
|
94
|
+
source: history.source,
|
|
95
|
+
barReferences
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function groupBarsByCoin(bars) {
|
|
99
|
+
const result = new Map();
|
|
100
|
+
for (const bar of bars) {
|
|
101
|
+
const existing = result.get(bar.coinType) ?? [];
|
|
102
|
+
existing.push(bar);
|
|
103
|
+
result.set(bar.coinType, existing);
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
function referenceSummaryStatus(coinReferences) {
|
|
108
|
+
if (coinReferences.length === 0) {
|
|
109
|
+
return "no_timeline_bars";
|
|
110
|
+
}
|
|
111
|
+
const availableCount = coinReferences.filter((entry) => entry.status === "available").length;
|
|
112
|
+
const partialCount = coinReferences.filter((entry) => entry.status === "partial").length;
|
|
113
|
+
if (availableCount === coinReferences.length) {
|
|
114
|
+
return "available";
|
|
115
|
+
}
|
|
116
|
+
if (availableCount > 0 || partialCount > 0) {
|
|
117
|
+
return "partial";
|
|
118
|
+
}
|
|
119
|
+
return "unavailable";
|
|
120
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const REVIEW_ACTIVITY_LOW_SAMPLE_THRESHOLD = 5;
|
|
2
|
+
export const REVIEW_ACTIVITY_LIST_DEFAULT_LIMIT = 20;
|
|
3
|
+
export const REVIEW_ACTIVITY_LIST_MAX_LIMIT = 100;
|
|
4
|
+
export const REVIEW_ACTIVITY_DETAIL_MAX_ITEMS = 100;
|
|
5
|
+
export const EXTERNAL_ACTIVITY_SCAN_DEFAULT_LIMIT = 100;
|
|
6
|
+
export const EXTERNAL_ACTIVITY_SCAN_MAX_LIMIT = 100;
|
|
7
|
+
export const EXTERNAL_ACTIVITY_COVERAGE_SCAN_MAX_RECORDS = 100;
|
|
8
|
+
export class ActivityStoreReadError extends Error {
|
|
9
|
+
kind;
|
|
10
|
+
details;
|
|
11
|
+
constructor(kind, message, details = {}) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.kind = kind;
|
|
14
|
+
this.details = details;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export function buildExternalActivityCoverageResult(input) {
|
|
2
|
+
const completeAffectedAccountScanIds = input.scans
|
|
3
|
+
.filter((scan) => scanCompletesAffectedAccountRange(scan, input.from, input.to))
|
|
4
|
+
.map((scan) => scan.scanId);
|
|
5
|
+
const incompleteScanIds = input.scans
|
|
6
|
+
.filter((scan) => scan.windowComplete === false || scan.incompleteReason !== undefined)
|
|
7
|
+
.map((scan) => scan.scanId);
|
|
8
|
+
const sentOnlyScanIds = input.scans
|
|
9
|
+
.filter((scan) => scan.kind === "account_scan" && scan.relationship === "sent")
|
|
10
|
+
.map((scan) => scan.scanId);
|
|
11
|
+
const limitations = new Set();
|
|
12
|
+
const scansTruncated = input.scansTruncated === true;
|
|
13
|
+
if (input.scanCount === 0) {
|
|
14
|
+
limitations.add("no_stored_activity_scans");
|
|
15
|
+
}
|
|
16
|
+
if (completeAffectedAccountScanIds.length === 0) {
|
|
17
|
+
limitations.add("no_complete_affected_account_scan");
|
|
18
|
+
}
|
|
19
|
+
if (sentOnlyScanIds.length > 0) {
|
|
20
|
+
limitations.add("sent_only_scan_not_full_account_coverage");
|
|
21
|
+
}
|
|
22
|
+
if (incompleteScanIds.length > 0) {
|
|
23
|
+
limitations.add("scan_window_incomplete");
|
|
24
|
+
}
|
|
25
|
+
if (input.scans.some((scan) => scan.fromTimestamp === undefined || scan.toTimestamp === undefined)) {
|
|
26
|
+
limitations.add("scan_window_unbounded");
|
|
27
|
+
}
|
|
28
|
+
if (scansTruncated) {
|
|
29
|
+
limitations.add("scan_records_truncated");
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
dataScope: {
|
|
33
|
+
account: input.scope.account,
|
|
34
|
+
from: input.from,
|
|
35
|
+
to: input.to,
|
|
36
|
+
recordCount: input.storedTransactionCount
|
|
37
|
+
},
|
|
38
|
+
accountSource: input.scope.accountSource,
|
|
39
|
+
accountKnown: input.scope.accountId !== undefined,
|
|
40
|
+
requestedRange: {
|
|
41
|
+
from: input.from,
|
|
42
|
+
to: input.to
|
|
43
|
+
},
|
|
44
|
+
coverageStatus: input.scanCount === 0
|
|
45
|
+
? "no_stored_scans"
|
|
46
|
+
: completeAffectedAccountScanIds.length > 0
|
|
47
|
+
? "complete"
|
|
48
|
+
: "partial",
|
|
49
|
+
scanCount: input.scanCount,
|
|
50
|
+
returnedScanCount: input.scans.length,
|
|
51
|
+
scansTruncated,
|
|
52
|
+
storedTransactionCount: input.storedTransactionCount,
|
|
53
|
+
...(input.storedTransactionRange === undefined ? {} : { storedTransactionRange: input.storedTransactionRange }),
|
|
54
|
+
coverageEvidence: {
|
|
55
|
+
completeAffectedAccountScanIds,
|
|
56
|
+
incompleteScanIds,
|
|
57
|
+
sentOnlyScanIds
|
|
58
|
+
},
|
|
59
|
+
limitations: [...limitations].sort(),
|
|
60
|
+
scans: input.scans
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function scanOverlapsRequestedRange(scan, from, to) {
|
|
64
|
+
return (scan.fromTimestamp === undefined || scan.fromTimestamp < to) &&
|
|
65
|
+
(scan.toTimestamp === undefined || scan.toTimestamp > from);
|
|
66
|
+
}
|
|
67
|
+
function scanCompletesAffectedAccountRange(scan, from, to) {
|
|
68
|
+
return scan.kind === "account_scan" &&
|
|
69
|
+
scan.relationship === "affected" &&
|
|
70
|
+
scan.windowComplete === true &&
|
|
71
|
+
scan.incompleteReason === undefined &&
|
|
72
|
+
scan.fromTimestamp !== undefined &&
|
|
73
|
+
scan.toTimestamp !== undefined &&
|
|
74
|
+
scan.fromTimestamp <= from &&
|
|
75
|
+
scan.toTimestamp >= to;
|
|
76
|
+
}
|