@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,124 @@
|
|
|
1
|
+
import { assertPreSimulationHumanReadableReviewBoundaryClaims, createHumanReadableReviewEvidence, verifyHumanReadableReviewEvidence } from "./humanReadableReviewEvidence.js";
|
|
2
|
+
import { parseSuiAddress } from "../suiAddress.js";
|
|
3
|
+
export function createSwapHumanReadableReviewEvidence(input) {
|
|
4
|
+
return verifySwapHumanReadableReviewEvidence({
|
|
5
|
+
transactionMaterial: input.transactionMaterial,
|
|
6
|
+
transactionMaterialDigest: input.transactionMaterialDigest,
|
|
7
|
+
swapQuotePolicy: input.swapQuotePolicy,
|
|
8
|
+
transactionObjectOwnership: input.transactionObjectOwnership,
|
|
9
|
+
evidence: createHumanReadableReviewEvidence({
|
|
10
|
+
transactionMaterial: input.transactionMaterial,
|
|
11
|
+
transactionMaterialDigest: input.transactionMaterialDigest,
|
|
12
|
+
adapterId: input.adapterId,
|
|
13
|
+
protocol: input.protocol,
|
|
14
|
+
actionKind: input.actionKind,
|
|
15
|
+
review: input.review,
|
|
16
|
+
derivedAt: input.derivedAt
|
|
17
|
+
}),
|
|
18
|
+
now: input.derivedAt
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function verifySwapHumanReadableReviewEvidence(input) {
|
|
22
|
+
const evidence = verifyHumanReadableReviewEvidence({
|
|
23
|
+
transactionMaterial: input.transactionMaterial,
|
|
24
|
+
transactionMaterialDigest: input.transactionMaterialDigest,
|
|
25
|
+
evidence: input.evidence,
|
|
26
|
+
now: input.now
|
|
27
|
+
});
|
|
28
|
+
if (evidence.review.kind !== "swap_human_readable_review") {
|
|
29
|
+
throw new Error("swap human-readable review evidence requires a swap_human_readable_review projection");
|
|
30
|
+
}
|
|
31
|
+
if (input.swapQuotePolicy.adapterId !== evidence.adapterId ||
|
|
32
|
+
input.swapQuotePolicy.protocol !== evidence.protocol ||
|
|
33
|
+
input.swapQuotePolicy.actionKind !== evidence.actionKind) {
|
|
34
|
+
throw new Error("swap human-readable review evidence must match swap quote policy adapter identity");
|
|
35
|
+
}
|
|
36
|
+
for (const artifact of [input.swapQuotePolicy, input.transactionObjectOwnership]) {
|
|
37
|
+
if (artifact.materialId !== input.transactionMaterial.materialId ||
|
|
38
|
+
artifact.reviewSessionId !== input.transactionMaterial.reviewSessionId ||
|
|
39
|
+
artifact.planId !== input.transactionMaterial.planId ||
|
|
40
|
+
artifact.account !== input.transactionMaterial.account ||
|
|
41
|
+
artifact.expiresAt !== input.transactionMaterial.expiresAt) {
|
|
42
|
+
throw new Error("swap human-readable review evidence requires quote policy and object ownership artifacts for the same material");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (input.transactionObjectOwnership.transactionDigest !== input.transactionMaterialDigest.transactionDigest) {
|
|
46
|
+
throw new Error("swap human-readable review evidence requires object ownership bound to the same transaction digest");
|
|
47
|
+
}
|
|
48
|
+
assertSwapHumanReadableReviewProjectsQuotePolicy(evidence, input.swapQuotePolicy);
|
|
49
|
+
assertSwapHumanReadableReviewParties(evidence);
|
|
50
|
+
assertSwapHumanReadableReviewEvidenceSources(evidence);
|
|
51
|
+
assertSwapHumanReadableReviewBoundaryClaims(evidence);
|
|
52
|
+
assertPreSimulationHumanReadableReviewBoundaryClaims(evidence);
|
|
53
|
+
return evidence;
|
|
54
|
+
}
|
|
55
|
+
function assertSwapHumanReadableReviewProjectsQuotePolicy(evidence, swapQuotePolicy) {
|
|
56
|
+
assertSingleAmountProjection("outgoing", evidence.review.assetFlow.outgoing, "input", swapQuotePolicy.sourceAmount);
|
|
57
|
+
assertSingleAmountProjection("expected incoming", evidence.review.assetFlow.expectedIncoming, "expected_output", swapQuotePolicy.expectedOutput);
|
|
58
|
+
assertSingleAmountProjection("minimum incoming", evidence.review.assetFlow.minimumIncoming, "minimum_output", swapQuotePolicy.minimumOutput);
|
|
59
|
+
assertSingleAmountProjection("fee", evidence.review.assetFlow.fees, "fee", swapQuotePolicy.protocolFee);
|
|
60
|
+
if (evidence.review.targets.length !== 1) {
|
|
61
|
+
throw new Error("swap human-readable review targets must contain exactly one swap output asset target");
|
|
62
|
+
}
|
|
63
|
+
const target = evidence.review.targets[0];
|
|
64
|
+
if (target.kind !== "swap_output_asset" ||
|
|
65
|
+
target.symbol !== swapQuotePolicy.expectedOutput.asset.symbol ||
|
|
66
|
+
target.coinType !== swapQuotePolicy.expectedOutput.asset.coinType ||
|
|
67
|
+
target.protocol !== swapQuotePolicy.protocol ||
|
|
68
|
+
target.poolKey !== swapQuotePolicy.quoteSource.poolKey ||
|
|
69
|
+
target.direction !== swapQuotePolicy.quoteSource.direction) {
|
|
70
|
+
throw new Error("swap human-readable review target must match swap quote policy output asset and quote source");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function assertSwapHumanReadableReviewParties(evidence) {
|
|
74
|
+
const recipientsByRole = new Map();
|
|
75
|
+
for (const recipient of evidence.review.recipients) {
|
|
76
|
+
if (recipientsByRole.has(recipient.role)) {
|
|
77
|
+
throw new Error(`swap human-readable review recipients contains duplicate role '${recipient.role}'`);
|
|
78
|
+
}
|
|
79
|
+
const normalizedAddress = parseSuiAddress(recipient.address);
|
|
80
|
+
if (!normalizedAddress) {
|
|
81
|
+
throw new Error("swap human-readable review recipients must contain valid Sui addresses");
|
|
82
|
+
}
|
|
83
|
+
recipientsByRole.set(recipient.role, normalizedAddress);
|
|
84
|
+
}
|
|
85
|
+
if (recipientsByRole.size !== 2) {
|
|
86
|
+
throw new Error("swap human-readable review recipients must contain exactly the connected account and output recipient");
|
|
87
|
+
}
|
|
88
|
+
if (recipientsByRole.get("connected_account") !== evidence.account ||
|
|
89
|
+
recipientsByRole.get("output_recipient") !== evidence.account) {
|
|
90
|
+
throw new Error("swap human-readable review recipients must match the reviewed account");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function assertSwapHumanReadableReviewEvidenceSources(evidence) {
|
|
94
|
+
for (const source of ["quote", "wallet"]) {
|
|
95
|
+
requireFactSource(evidence.review.evidenceUsed, source);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function assertSwapHumanReadableReviewBoundaryClaims(evidence) {
|
|
99
|
+
requireGapId("unsupportedClaims", evidence.review.unsupportedClaims, "no_route_recommendation");
|
|
100
|
+
}
|
|
101
|
+
function assertSingleAmountProjection(label, amounts, role, expected) {
|
|
102
|
+
if (amounts.length !== 1) {
|
|
103
|
+
throw new Error(`swap human-readable review ${label} must contain exactly one amount`);
|
|
104
|
+
}
|
|
105
|
+
const amount = amounts[0];
|
|
106
|
+
if (amount.role !== role ||
|
|
107
|
+
amount.rawAmount !== expected.raw ||
|
|
108
|
+
amount.rawAmountSource !== "quote_policy_evidence" ||
|
|
109
|
+
amount.symbol !== expected.asset.symbol ||
|
|
110
|
+
amount.coinType !== expected.asset.coinType ||
|
|
111
|
+
amount.decimals !== expected.asset.decimals) {
|
|
112
|
+
throw new Error(`swap human-readable review ${label} amount must match swap quote policy evidence`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function requireFactSource(facts, source) {
|
|
116
|
+
if (!facts.some((fact) => fact.source === source)) {
|
|
117
|
+
throw new Error(`swap human-readable review evidenceUsed must include source '${source}'`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function requireGapId(label, gaps, id) {
|
|
121
|
+
if (!gaps.some((gap) => gap.id === id)) {
|
|
122
|
+
throw new Error(`swap human-readable review ${label} must include '${id}'`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { adapterEvidenceClaimSchema, adapterRawQuantitySchema, adapterSourceOfTruthSchema } from "./signableAdapterContract.js";
|
|
3
|
+
import { parseRawU64 } from "../numeric/rawU64.js";
|
|
4
|
+
import { normalizedSuiAddressSchema } from "../suiAddress.js";
|
|
5
|
+
import { normalizeCoinType } from "../read/coinMetadata.js";
|
|
6
|
+
export const SWAP_QUOTE_POLICY_EVIDENCE_VERSION = "swap-quote-policy-v1";
|
|
7
|
+
const BPS_DENOMINATOR = 10000n;
|
|
8
|
+
const isoUtcStringSchema = z.string().refine((value) => {
|
|
9
|
+
const parsed = new Date(value);
|
|
10
|
+
return Number.isFinite(parsed.getTime()) && parsed.toISOString() === value;
|
|
11
|
+
}, "Expected ISO 8601 UTC timestamp");
|
|
12
|
+
const coinTypeSchema = z.string().min(1).max(512).refine((value) => {
|
|
13
|
+
try {
|
|
14
|
+
normalizeCoinType(value);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}, "Expected a Sui struct tag coin type").transform((value) => normalizeCoinType(value));
|
|
21
|
+
const quotePolicyAssetSchema = z.object({
|
|
22
|
+
symbol: z.string().min(1).max(64),
|
|
23
|
+
coinType: coinTypeSchema,
|
|
24
|
+
decimals: z.number().int().min(0).max(255),
|
|
25
|
+
unitSource: z.enum(["deepbook_mainnetCoins_scalar", "flowx_pinned_registry", "sui_core_getCoinMetadata"])
|
|
26
|
+
}).strict();
|
|
27
|
+
const quotePolicyRawAmountSchema = z.object({
|
|
28
|
+
raw: z.string().min(1),
|
|
29
|
+
asset: quotePolicyAssetSchema
|
|
30
|
+
}).strict();
|
|
31
|
+
export const swapQuotePolicyEvidenceSchema = z.object({
|
|
32
|
+
evidenceVersion: z.literal(SWAP_QUOTE_POLICY_EVIDENCE_VERSION),
|
|
33
|
+
materialId: z.string().min(1),
|
|
34
|
+
reviewSessionId: z.string().min(1),
|
|
35
|
+
planId: z.string().min(1),
|
|
36
|
+
account: normalizedSuiAddressSchema,
|
|
37
|
+
kind: z.literal("swap_quote_policy"),
|
|
38
|
+
adapterId: z.string().min(1).max(120),
|
|
39
|
+
protocol: z.string().min(1).max(120),
|
|
40
|
+
actionKind: z.string().min(1).max(120),
|
|
41
|
+
network: z.literal("sui:mainnet"),
|
|
42
|
+
quoteEvidenceId: z.string().min(1).max(120),
|
|
43
|
+
quoteSource: z.object({
|
|
44
|
+
provider: z.string().min(1).max(120),
|
|
45
|
+
poolKey: z.string().min(1).max(120),
|
|
46
|
+
direction: z.enum(["base_to_quote", "quote_to_base"]),
|
|
47
|
+
fetchedAt: isoUtcStringSchema,
|
|
48
|
+
sourceMoveFunction: z.string().min(1).max(160)
|
|
49
|
+
}).strict(),
|
|
50
|
+
policySource: z.literal("adapter_policy_from_quote_evidence"),
|
|
51
|
+
maxSlippageBps: z.number().int().min(1).max(10_000),
|
|
52
|
+
staleAfterMs: z.number().int().min(1),
|
|
53
|
+
sourceAmount: quotePolicyRawAmountSchema,
|
|
54
|
+
expectedOutput: quotePolicyRawAmountSchema,
|
|
55
|
+
minimumOutput: quotePolicyRawAmountSchema,
|
|
56
|
+
protocolFee: quotePolicyRawAmountSchema,
|
|
57
|
+
derivedAt: isoUtcStringSchema,
|
|
58
|
+
expiresAt: isoUtcStringSchema
|
|
59
|
+
}).strict();
|
|
60
|
+
export function createSwapQuotePolicyEvidence(input) {
|
|
61
|
+
return verifySwapQuotePolicyEvidence({
|
|
62
|
+
transactionMaterial: input.materialHandle,
|
|
63
|
+
evidence: {
|
|
64
|
+
evidenceVersion: SWAP_QUOTE_POLICY_EVIDENCE_VERSION,
|
|
65
|
+
materialId: input.materialHandle.materialId,
|
|
66
|
+
reviewSessionId: input.materialHandle.reviewSessionId,
|
|
67
|
+
planId: input.materialHandle.planId,
|
|
68
|
+
account: input.materialHandle.account,
|
|
69
|
+
kind: "swap_quote_policy",
|
|
70
|
+
adapterId: input.adapterId,
|
|
71
|
+
protocol: input.protocol,
|
|
72
|
+
actionKind: input.actionKind,
|
|
73
|
+
network: "sui:mainnet",
|
|
74
|
+
quoteEvidenceId: input.quoteEvidenceId,
|
|
75
|
+
quoteSource: input.quoteSource,
|
|
76
|
+
policySource: "adapter_policy_from_quote_evidence",
|
|
77
|
+
maxSlippageBps: input.maxSlippageBps,
|
|
78
|
+
staleAfterMs: input.staleAfterMs,
|
|
79
|
+
sourceAmount: input.sourceAmount,
|
|
80
|
+
expectedOutput: input.expectedOutput,
|
|
81
|
+
minimumOutput: input.minimumOutput,
|
|
82
|
+
protocolFee: input.protocolFee,
|
|
83
|
+
derivedAt: input.derivedAt.toISOString(),
|
|
84
|
+
expiresAt: input.materialHandle.expiresAt
|
|
85
|
+
},
|
|
86
|
+
now: input.derivedAt
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export function parseSwapQuotePolicyEvidence(value) {
|
|
90
|
+
return swapQuotePolicyEvidenceSchema.parse(value);
|
|
91
|
+
}
|
|
92
|
+
export function verifySwapQuotePolicyEvidence(input) {
|
|
93
|
+
const evidence = parseSwapQuotePolicyEvidence(input.evidence);
|
|
94
|
+
const now = input.now ?? new Date();
|
|
95
|
+
const nowMs = now.getTime();
|
|
96
|
+
if (!Number.isFinite(nowMs)) {
|
|
97
|
+
throw new Error("now must be a valid Date");
|
|
98
|
+
}
|
|
99
|
+
if (evidence.materialId !== input.transactionMaterial.materialId ||
|
|
100
|
+
evidence.reviewSessionId !== input.transactionMaterial.reviewSessionId ||
|
|
101
|
+
evidence.planId !== input.transactionMaterial.planId ||
|
|
102
|
+
evidence.account !== input.transactionMaterial.account ||
|
|
103
|
+
evidence.expiresAt !== input.transactionMaterial.expiresAt) {
|
|
104
|
+
throw new Error("swap quote policy evidence must match material identity");
|
|
105
|
+
}
|
|
106
|
+
const fetchedAtMs = Date.parse(evidence.quoteSource.fetchedAt);
|
|
107
|
+
const derivedAtMs = Date.parse(evidence.derivedAt);
|
|
108
|
+
const expiresAtMs = Date.parse(evidence.expiresAt);
|
|
109
|
+
if (fetchedAtMs > derivedAtMs) {
|
|
110
|
+
throw new Error("swap quote policy evidence fetchedAt must not be after derivedAt");
|
|
111
|
+
}
|
|
112
|
+
if (Date.parse(evidence.quoteSource.fetchedAt) + evidence.staleAfterMs !== expiresAtMs) {
|
|
113
|
+
throw new Error("swap quote policy evidence expiresAt must equal fetchedAt plus staleAfterMs");
|
|
114
|
+
}
|
|
115
|
+
if (derivedAtMs > nowMs) {
|
|
116
|
+
throw new Error("swap quote policy evidence derivedAt must not be in the future");
|
|
117
|
+
}
|
|
118
|
+
if (expiresAtMs <= derivedAtMs || expiresAtMs <= nowMs) {
|
|
119
|
+
throw new Error("swap quote policy evidence must not be expired");
|
|
120
|
+
}
|
|
121
|
+
const expectedOutput = parseRawU64(evidence.expectedOutput.raw, "expectedOutput.raw", { positive: true });
|
|
122
|
+
const minimumOutput = parseRawU64(evidence.minimumOutput.raw, "minimumOutput.raw", { positive: true });
|
|
123
|
+
parseRawU64(evidence.sourceAmount.raw, "sourceAmount.raw", { positive: true });
|
|
124
|
+
parseRawU64(evidence.protocolFee.raw, "protocolFee.raw");
|
|
125
|
+
const computedMinimum = (expectedOutput * (BPS_DENOMINATOR - BigInt(evidence.maxSlippageBps))) / BPS_DENOMINATOR;
|
|
126
|
+
if (minimumOutput !== computedMinimum) {
|
|
127
|
+
throw new Error("swap quote policy minimumOutput.raw must match expectedOutput.raw and maxSlippageBps");
|
|
128
|
+
}
|
|
129
|
+
if (!sameAsset(evidence.expectedOutput.asset, evidence.minimumOutput.asset)) {
|
|
130
|
+
throw new Error("swap quote policy minimum output asset must match expected output asset");
|
|
131
|
+
}
|
|
132
|
+
const contractMapping = mapSwapQuotePolicyEvidenceToContractDraft(evidence);
|
|
133
|
+
if (contractMapping.status === "unsupported") {
|
|
134
|
+
throw new Error(`swap quote policy evidence is not contract-mappable: ${contractMapping.reason}`);
|
|
135
|
+
}
|
|
136
|
+
return evidence;
|
|
137
|
+
}
|
|
138
|
+
export function mapSwapQuotePolicyEvidenceToContractDraft(evidenceInput) {
|
|
139
|
+
const evidence = parseSwapQuotePolicyEvidence(evidenceInput);
|
|
140
|
+
const outputUnitSource = mapUnitSource(evidence.expectedOutput.asset.unitSource);
|
|
141
|
+
if (!outputUnitSource) {
|
|
142
|
+
return { status: "unsupported", reason: "unsupported output unit source" };
|
|
143
|
+
}
|
|
144
|
+
const quoteSourceId = "swap_quote_policy";
|
|
145
|
+
const outputUnitSourceId = "swap_quote_output_unit";
|
|
146
|
+
const outputUnitClaimId = "swap_quote_output_unit_claim";
|
|
147
|
+
const expectedAmountClaimId = "swap_quote_expected_output_claim";
|
|
148
|
+
const minimumAmountClaimId = "swap_quote_minimum_output_claim";
|
|
149
|
+
const quoteMinOutClaimId = "swap_quote_min_out_claim";
|
|
150
|
+
const slippagePolicyClaimId = "swap_quote_slippage_policy_claim";
|
|
151
|
+
const outputAsset = {
|
|
152
|
+
symbol: evidence.expectedOutput.asset.symbol,
|
|
153
|
+
coinType: evidence.expectedOutput.asset.coinType
|
|
154
|
+
};
|
|
155
|
+
const sourceOfTruth = [
|
|
156
|
+
adapterSourceOfTruthSchema.parse({
|
|
157
|
+
id: quoteSourceId,
|
|
158
|
+
kind: "quote_evidence",
|
|
159
|
+
network: "sui:mainnet",
|
|
160
|
+
source: `${evidence.protocol} quote policy for ${evidence.adapterId}`,
|
|
161
|
+
verifiedAt: evidence.derivedAt,
|
|
162
|
+
fields: ["quoteEvidenceId", "rawAmount", "minOutRaw", "asset", "amountRole", "maxSlippageBps"]
|
|
163
|
+
}),
|
|
164
|
+
adapterSourceOfTruthSchema.parse({
|
|
165
|
+
id: outputUnitSourceId,
|
|
166
|
+
kind: outputUnitSource.sourceOfTruthKind,
|
|
167
|
+
network: "sui:mainnet",
|
|
168
|
+
source: outputUnitSource.sourceDescription,
|
|
169
|
+
verifiedAt: evidence.derivedAt,
|
|
170
|
+
fields: ["coinType", "decimals"]
|
|
171
|
+
})
|
|
172
|
+
];
|
|
173
|
+
const evidenceClaims = [
|
|
174
|
+
adapterEvidenceClaimSchema.parse({
|
|
175
|
+
id: expectedAmountClaimId,
|
|
176
|
+
factKind: "raw_quantity_amount",
|
|
177
|
+
sourceEvidenceId: quoteSourceId,
|
|
178
|
+
role: "expected_output",
|
|
179
|
+
asset: outputAsset,
|
|
180
|
+
rawAmount: evidence.expectedOutput.raw
|
|
181
|
+
}),
|
|
182
|
+
adapterEvidenceClaimSchema.parse({
|
|
183
|
+
id: minimumAmountClaimId,
|
|
184
|
+
factKind: "raw_quantity_amount",
|
|
185
|
+
sourceEvidenceId: quoteSourceId,
|
|
186
|
+
role: "minimum_output",
|
|
187
|
+
asset: outputAsset,
|
|
188
|
+
rawAmount: evidence.minimumOutput.raw
|
|
189
|
+
}),
|
|
190
|
+
adapterEvidenceClaimSchema.parse({
|
|
191
|
+
id: outputUnitClaimId,
|
|
192
|
+
factKind: "unit_metadata",
|
|
193
|
+
sourceEvidenceId: outputUnitSourceId,
|
|
194
|
+
source: outputUnitSource.unitClaimSource,
|
|
195
|
+
coinType: outputAsset.coinType,
|
|
196
|
+
decimals: evidence.expectedOutput.asset.decimals
|
|
197
|
+
}),
|
|
198
|
+
adapterEvidenceClaimSchema.parse({
|
|
199
|
+
id: quoteMinOutClaimId,
|
|
200
|
+
factKind: "quote_min_out",
|
|
201
|
+
sourceEvidenceId: quoteSourceId,
|
|
202
|
+
quoteEvidenceId: evidence.quoteEvidenceId,
|
|
203
|
+
minOutRaw: evidence.minimumOutput.raw
|
|
204
|
+
}),
|
|
205
|
+
adapterEvidenceClaimSchema.parse({
|
|
206
|
+
id: slippagePolicyClaimId,
|
|
207
|
+
factKind: "slippage_policy",
|
|
208
|
+
sourceEvidenceId: quoteSourceId,
|
|
209
|
+
policySource: "adapter_policy_from_quote_evidence",
|
|
210
|
+
maxSlippageBps: evidence.maxSlippageBps,
|
|
211
|
+
minOutRaw: evidence.minimumOutput.raw
|
|
212
|
+
})
|
|
213
|
+
];
|
|
214
|
+
const rawQuantities = [
|
|
215
|
+
adapterRawQuantitySchema.parse({
|
|
216
|
+
id: "swap_quote_expected_output",
|
|
217
|
+
role: "expected_output",
|
|
218
|
+
asset: outputAsset,
|
|
219
|
+
rawAmount: evidence.expectedOutput.raw,
|
|
220
|
+
unit: {
|
|
221
|
+
decimals: evidence.expectedOutput.asset.decimals,
|
|
222
|
+
source: outputUnitSource.unitClaimSource,
|
|
223
|
+
sourceField: "expectedOutput.asset.decimals",
|
|
224
|
+
unitClaimId: outputUnitClaimId
|
|
225
|
+
},
|
|
226
|
+
amountClaimId: expectedAmountClaimId
|
|
227
|
+
}),
|
|
228
|
+
adapterRawQuantitySchema.parse({
|
|
229
|
+
id: "swap_quote_minimum_output",
|
|
230
|
+
role: "minimum_output",
|
|
231
|
+
asset: outputAsset,
|
|
232
|
+
rawAmount: evidence.minimumOutput.raw,
|
|
233
|
+
unit: {
|
|
234
|
+
decimals: evidence.expectedOutput.asset.decimals,
|
|
235
|
+
source: outputUnitSource.unitClaimSource,
|
|
236
|
+
sourceField: "minimumOutput.asset.decimals",
|
|
237
|
+
unitClaimId: outputUnitClaimId
|
|
238
|
+
},
|
|
239
|
+
amountClaimId: minimumAmountClaimId
|
|
240
|
+
})
|
|
241
|
+
];
|
|
242
|
+
return {
|
|
243
|
+
status: "mapped",
|
|
244
|
+
sourceOfTruth,
|
|
245
|
+
evidenceClaims,
|
|
246
|
+
rawQuantities
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function sameAsset(left, right) {
|
|
250
|
+
return (left.symbol === right.symbol &&
|
|
251
|
+
left.coinType === right.coinType &&
|
|
252
|
+
left.decimals === right.decimals &&
|
|
253
|
+
left.unitSource === right.unitSource);
|
|
254
|
+
}
|
|
255
|
+
function mapUnitSource(unitSource) {
|
|
256
|
+
if (unitSource === "deepbook_mainnetCoins_scalar") {
|
|
257
|
+
return {
|
|
258
|
+
sourceOfTruthKind: "pinned_sdk_registry",
|
|
259
|
+
unitClaimSource: "pinned_sdk_metadata",
|
|
260
|
+
sourceDescription: "Pinned DeepBook mainnet coin metadata scalar used for quote raw units"
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (unitSource === "flowx_pinned_registry") {
|
|
264
|
+
return {
|
|
265
|
+
sourceOfTruthKind: "pinned_sdk_registry",
|
|
266
|
+
unitClaimSource: "pinned_sdk_metadata",
|
|
267
|
+
sourceDescription: "Pinned FlowX mainnet registry (chain-verified) used for quote raw units"
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (unitSource === "sui_core_getCoinMetadata") {
|
|
271
|
+
return {
|
|
272
|
+
sourceOfTruthKind: "verified_mainnet_onchain_metadata",
|
|
273
|
+
unitClaimSource: "verified_mainnet_onchain_metadata",
|
|
274
|
+
sourceDescription: "Sui mainnet getCoinMetadata result used for quote raw units"
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isValidStructTag, normalizeStructTag } from "@mysten/sui/utils";
|
|
3
|
+
import { adapterEvidenceClaimSchema, adapterGasObjectOwnershipLinkSchema, adapterObjectOwnershipEvidenceSchema, adapterSourceOfTruthSchema, SUI_GAS_COIN_TYPE } from "./signableAdapterContract.js";
|
|
4
|
+
import { normalizedSuiAddressSchema, suiTransactionDigestSchema } from "../suiAddress.js";
|
|
5
|
+
export const TRANSACTION_OBJECT_OWNERSHIP_EVIDENCE_VERSION = "transaction-object-ownership-v1";
|
|
6
|
+
export const TRANSACTION_OBJECT_ROLES = [
|
|
7
|
+
"gas_object",
|
|
8
|
+
"imm_or_owned_object",
|
|
9
|
+
"shared_object",
|
|
10
|
+
"receiving_object"
|
|
11
|
+
];
|
|
12
|
+
export const TRANSACTION_OBJECT_OWNERSHIP_STATUSES = [
|
|
13
|
+
"owned_by_account",
|
|
14
|
+
"not_owned_by_account",
|
|
15
|
+
"shared_object",
|
|
16
|
+
"immutable_object",
|
|
17
|
+
"object_owner",
|
|
18
|
+
"consensus_address_owner",
|
|
19
|
+
"unknown_owner"
|
|
20
|
+
];
|
|
21
|
+
const isoUtcStringSchema = z.string().refine((value) => {
|
|
22
|
+
const parsed = new Date(value);
|
|
23
|
+
return Number.isFinite(parsed.getTime()) && parsed.toISOString() === value;
|
|
24
|
+
}, "Expected ISO 8601 UTC timestamp");
|
|
25
|
+
const moveStructTagSchema = z.string().min(1).max(512).refine((value) => isValidStructTag(value), "Expected valid Move struct tag").transform((value) => normalizeStructTag(value));
|
|
26
|
+
export const transactionObjectOwnershipFactSchema = z.object({
|
|
27
|
+
objectId: normalizedSuiAddressSchema,
|
|
28
|
+
roles: z.array(z.enum(TRANSACTION_OBJECT_ROLES)).min(1),
|
|
29
|
+
ownership: z.enum(TRANSACTION_OBJECT_OWNERSHIP_STATUSES),
|
|
30
|
+
ownerKind: z.string().min(1).max(80),
|
|
31
|
+
ownerAccount: normalizedSuiAddressSchema.optional(),
|
|
32
|
+
objectType: moveStructTagSchema,
|
|
33
|
+
source: z.literal("stored_transaction_data_and_mainnet_object_read")
|
|
34
|
+
}).strict().superRefine((value, ctx) => {
|
|
35
|
+
if (new Set(value.roles).size !== value.roles.length) {
|
|
36
|
+
ctx.addIssue({
|
|
37
|
+
code: "custom",
|
|
38
|
+
path: ["roles"],
|
|
39
|
+
message: "roles must be unique"
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
export const transactionObjectOwnershipEvidenceSchema = z.object({
|
|
44
|
+
evidenceVersion: z.literal(TRANSACTION_OBJECT_OWNERSHIP_EVIDENCE_VERSION),
|
|
45
|
+
materialId: z.string().min(1),
|
|
46
|
+
reviewSessionId: z.string().min(1),
|
|
47
|
+
planId: z.string().min(1),
|
|
48
|
+
account: normalizedSuiAddressSchema,
|
|
49
|
+
transactionDigest: suiTransactionDigestSchema,
|
|
50
|
+
objectCount: z.number().int().min(1),
|
|
51
|
+
objects: z.array(transactionObjectOwnershipFactSchema).min(1),
|
|
52
|
+
verifiedAt: isoUtcStringSchema,
|
|
53
|
+
expiresAt: isoUtcStringSchema
|
|
54
|
+
}).strict().superRefine((value, ctx) => {
|
|
55
|
+
if (value.objectCount !== value.objects.length) {
|
|
56
|
+
ctx.addIssue({
|
|
57
|
+
code: "custom",
|
|
58
|
+
path: ["objectCount"],
|
|
59
|
+
message: "objectCount must equal objects.length"
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (new Set(value.objects.map((object) => object.objectId)).size !== value.objects.length) {
|
|
63
|
+
ctx.addIssue({
|
|
64
|
+
code: "custom",
|
|
65
|
+
path: ["objects"],
|
|
66
|
+
message: "objects must have unique objectId values"
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const verifiedAtMs = Date.parse(value.verifiedAt);
|
|
70
|
+
const expiresAtMs = Date.parse(value.expiresAt);
|
|
71
|
+
if (expiresAtMs <= verifiedAtMs) {
|
|
72
|
+
ctx.addIssue({
|
|
73
|
+
code: "custom",
|
|
74
|
+
path: ["expiresAt"],
|
|
75
|
+
message: "expiresAt must be after verifiedAt"
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
export function parseSuiCoinObjectType(objectType) {
|
|
80
|
+
if (!isValidStructTag(objectType)) {
|
|
81
|
+
return { status: "not_coin" };
|
|
82
|
+
}
|
|
83
|
+
const normalized = normalizeStructTag(objectType);
|
|
84
|
+
const coinPrefix = `${normalizeStructTag("0x2::coin::Coin<0x2::sui::SUI>").split("<")[0]}<`;
|
|
85
|
+
if (!normalized.startsWith(coinPrefix) || !normalized.endsWith(">")) {
|
|
86
|
+
return { status: "not_coin" };
|
|
87
|
+
}
|
|
88
|
+
const coinType = normalized.slice(coinPrefix.length, -1);
|
|
89
|
+
if (!isValidStructTag(coinType)) {
|
|
90
|
+
return { status: "not_coin" };
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
status: "coin",
|
|
94
|
+
objectType: normalized,
|
|
95
|
+
coinType: normalizeStructTag(coinType)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function parseTransactionObjectOwnershipEvidence(value) {
|
|
99
|
+
return transactionObjectOwnershipEvidenceSchema.parse(value);
|
|
100
|
+
}
|
|
101
|
+
export function verifyTransactionObjectOwnershipEvidence(input) {
|
|
102
|
+
const evidence = parseTransactionObjectOwnershipEvidence(input.evidence);
|
|
103
|
+
const now = input.now ?? new Date();
|
|
104
|
+
const nowMs = now.getTime();
|
|
105
|
+
if (!Number.isFinite(nowMs)) {
|
|
106
|
+
throw new Error("now must be a valid Date");
|
|
107
|
+
}
|
|
108
|
+
if (evidence.materialId !== input.transactionMaterial.materialId ||
|
|
109
|
+
evidence.reviewSessionId !== input.transactionMaterial.reviewSessionId ||
|
|
110
|
+
evidence.planId !== input.transactionMaterial.planId ||
|
|
111
|
+
evidence.account !== input.transactionMaterial.account ||
|
|
112
|
+
evidence.expiresAt !== input.transactionMaterial.expiresAt ||
|
|
113
|
+
evidence.transactionDigest !== input.transactionMaterialDigest.transactionDigest) {
|
|
114
|
+
throw new Error("transaction object ownership evidence must match material and digest identity");
|
|
115
|
+
}
|
|
116
|
+
if (input.transactionMaterialDigest.materialId !== input.transactionMaterial.materialId ||
|
|
117
|
+
input.transactionMaterialDigest.reviewSessionId !== input.transactionMaterial.reviewSessionId ||
|
|
118
|
+
input.transactionMaterialDigest.planId !== input.transactionMaterial.planId ||
|
|
119
|
+
input.transactionMaterialDigest.account !== input.transactionMaterial.account ||
|
|
120
|
+
input.transactionMaterialDigest.expiresAt !== input.transactionMaterial.expiresAt) {
|
|
121
|
+
throw new Error("transaction material digest must match material identity before ownership evidence is accepted");
|
|
122
|
+
}
|
|
123
|
+
const materialCreatedAtMs = Date.parse(input.transactionMaterial.createdAt);
|
|
124
|
+
const verifiedAtMs = Date.parse(evidence.verifiedAt);
|
|
125
|
+
const expiresAtMs = Date.parse(evidence.expiresAt);
|
|
126
|
+
if (verifiedAtMs < materialCreatedAtMs || verifiedAtMs > nowMs || verifiedAtMs >= expiresAtMs) {
|
|
127
|
+
throw new Error("transaction object ownership verifiedAt must be between material creation, now, and material expiry");
|
|
128
|
+
}
|
|
129
|
+
if (expiresAtMs <= nowMs) {
|
|
130
|
+
throw new Error("transaction object ownership evidence must not be expired");
|
|
131
|
+
}
|
|
132
|
+
const contractMapping = mapTransactionObjectOwnershipEvidenceToContractDraft(evidence);
|
|
133
|
+
if (contractMapping.status === "unsupported") {
|
|
134
|
+
throw new Error(`transaction object ownership evidence is not contract-mappable: ${contractMapping.reason}`);
|
|
135
|
+
}
|
|
136
|
+
return evidence;
|
|
137
|
+
}
|
|
138
|
+
export function mapTransactionObjectOwnershipEvidenceToContractDraft(evidenceInput) {
|
|
139
|
+
const evidence = parseTransactionObjectOwnershipEvidence(evidenceInput);
|
|
140
|
+
const sourceEvidenceId = "tx_object_ownership_source";
|
|
141
|
+
const claims = [];
|
|
142
|
+
const objects = [];
|
|
143
|
+
const gasObjectOwnershipLinks = [];
|
|
144
|
+
for (const [index, object] of evidence.objects.entries()) {
|
|
145
|
+
const mapped = mapOwnershipFactForContract(object);
|
|
146
|
+
if (mapped.status === "unsupported") {
|
|
147
|
+
return {
|
|
148
|
+
...mapped,
|
|
149
|
+
objectId: object.objectId,
|
|
150
|
+
roles: object.roles,
|
|
151
|
+
ownership: object.ownership
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const evidenceClaimId = `tx_object_ownership:${index}`;
|
|
155
|
+
claims.push(adapterEvidenceClaimSchema.parse({
|
|
156
|
+
id: evidenceClaimId,
|
|
157
|
+
factKind: "object_ownership",
|
|
158
|
+
sourceEvidenceId,
|
|
159
|
+
objectId: object.objectId,
|
|
160
|
+
ownerAccount: evidence.account,
|
|
161
|
+
ownership: mapped.ownership
|
|
162
|
+
}));
|
|
163
|
+
objects.push({
|
|
164
|
+
objectId: object.objectId,
|
|
165
|
+
role: mapped.role,
|
|
166
|
+
ownership: mapped.ownership,
|
|
167
|
+
evidenceClaimId
|
|
168
|
+
});
|
|
169
|
+
if (mapped.role === "gas_coin" && mapped.ownership === "owned_by_account") {
|
|
170
|
+
gasObjectOwnershipLinks.push(adapterGasObjectOwnershipLinkSchema.parse({
|
|
171
|
+
objectId: object.objectId,
|
|
172
|
+
ownerAccount: evidence.account,
|
|
173
|
+
ownershipClaimId: evidenceClaimId
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (gasObjectOwnershipLinks.length === 0) {
|
|
178
|
+
return {
|
|
179
|
+
status: "unsupported",
|
|
180
|
+
reason: "contract mapping requires at least one owned Coin<SUI> gas object ownership link",
|
|
181
|
+
objectId: evidence.objects[0]?.objectId ?? "unknown",
|
|
182
|
+
roles: evidence.objects[0]?.roles ?? [],
|
|
183
|
+
ownership: evidence.objects[0]?.ownership ?? "unknown_owner"
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (!objects.some((object) => object.role !== "gas_coin")) {
|
|
187
|
+
return {
|
|
188
|
+
status: "unsupported",
|
|
189
|
+
reason: "contract mapping requires the full transaction object set, not gas-only ownership evidence",
|
|
190
|
+
objectId: evidence.objects[0]?.objectId ?? "unknown",
|
|
191
|
+
roles: evidence.objects[0]?.roles ?? [],
|
|
192
|
+
ownership: evidence.objects[0]?.ownership ?? "unknown_owner"
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
status: "mapped",
|
|
197
|
+
sourceOfTruth: adapterSourceOfTruthSchema.parse({
|
|
198
|
+
id: sourceEvidenceId,
|
|
199
|
+
kind: "wallet_account_read",
|
|
200
|
+
network: "sui:mainnet",
|
|
201
|
+
source: "Stored local transaction data object refs plus Sui mainnet object owner reads",
|
|
202
|
+
verifiedAt: evidence.verifiedAt,
|
|
203
|
+
fields: ["ownerAccount", "objects"]
|
|
204
|
+
}),
|
|
205
|
+
evidenceClaims: claims,
|
|
206
|
+
objectOwnership: adapterObjectOwnershipEvidenceSchema.parse({
|
|
207
|
+
checkedAt: evidence.verifiedAt,
|
|
208
|
+
ownerAccount: evidence.account,
|
|
209
|
+
objects
|
|
210
|
+
}),
|
|
211
|
+
gasObjectOwnershipLinks
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function mapOwnershipFactForContract(fact) {
|
|
215
|
+
if (fact.roles.includes("gas_object")) {
|
|
216
|
+
const coinObject = parseSuiCoinObjectType(fact.objectType);
|
|
217
|
+
return fact.ownership === "owned_by_account" &&
|
|
218
|
+
coinObject.status === "coin" &&
|
|
219
|
+
coinObject.coinType === SUI_GAS_COIN_TYPE
|
|
220
|
+
? { status: "mapped", role: "gas_coin", ownership: "owned_by_account" }
|
|
221
|
+
: { status: "unsupported", reason: "gas objects must be owned by the connected account and be Coin<SUI> objects" };
|
|
222
|
+
}
|
|
223
|
+
if (fact.roles.includes("shared_object")) {
|
|
224
|
+
return fact.ownership === "shared_object"
|
|
225
|
+
? { status: "mapped", role: "shared_object", ownership: "shared_object" }
|
|
226
|
+
: { status: "unsupported", reason: "shared object refs must have shared ownership facts" };
|
|
227
|
+
}
|
|
228
|
+
if (fact.roles.includes("receiving_object")) {
|
|
229
|
+
return fact.ownership === "owned_by_account"
|
|
230
|
+
? { status: "mapped", role: "receiving_object", ownership: "owned_by_account" }
|
|
231
|
+
: { status: "unsupported", reason: "receiving object refs must be owned by the connected account" };
|
|
232
|
+
}
|
|
233
|
+
if (fact.roles.includes("imm_or_owned_object")) {
|
|
234
|
+
if (fact.ownership === "immutable_object") {
|
|
235
|
+
return { status: "mapped", role: "protocol_object", ownership: "immutable_or_package" };
|
|
236
|
+
}
|
|
237
|
+
const coinObject = parseSuiCoinObjectType(fact.objectType);
|
|
238
|
+
if (fact.ownership === "owned_by_account" && coinObject.status === "coin") {
|
|
239
|
+
return { status: "mapped", role: "input_coin", ownership: "owned_by_account" };
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
status: "unsupported",
|
|
243
|
+
reason: "owned ImmOrOwnedObject refs must be Coin<T> objects before contract mapping can call them input coins"
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return { status: "unsupported", reason: "object ownership role is not contract-mappable" };
|
|
247
|
+
}
|