@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.
Files changed (234) hide show
  1. package/README.md +4 -39
  2. package/dist/adapters/adapterLifecycleValidators.js +7 -0
  3. package/dist/adapters/adapterPromptSurfaces.js +71 -0
  4. package/dist/adapters/deepbook/deepbookHumanReviewProducer.js +175 -0
  5. package/dist/adapters/deepbook/deepbookQuotePolicy.js +112 -0
  6. package/dist/adapters/deepbook/deepbookReviewEvidence.js +507 -0
  7. package/dist/adapters/deepbook/deepbookReviewLifecycle.js +85 -0
  8. package/dist/adapters/deepbook/deepbookSwapIntent.js +79 -0
  9. package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +269 -0
  10. package/dist/adapters/flowx/flowxSwapHumanReviewProducer.js +176 -0
  11. package/dist/adapters/flowx/flowxSwapIntent.js +79 -0
  12. package/dist/adapters/flowx/flowxSwapQuotePolicy.js +104 -0
  13. package/dist/adapters/flowx/flowxSwapReviewEvidence.js +468 -0
  14. package/dist/adapters/flowx/flowxSwapReviewLifecycle.js +85 -0
  15. package/dist/adapters/flowx/flowxSwapTransactionMaterialProducer.js +362 -0
  16. package/dist/adapters/intentPlanFactories.js +59 -0
  17. package/dist/adapters/reviewAdapters.js +81 -0
  18. package/dist/core/action/adapterLifecycleValidation.js +12 -0
  19. package/dist/core/action/forbiddenFields.js +43 -0
  20. package/dist/core/action/humanReadableReviewEvidence.js +203 -0
  21. package/dist/core/action/humanReadableReviewProjectionVerifier.js +29 -0
  22. package/dist/core/action/ptbVisualizationProducer.js +66 -0
  23. package/dist/core/action/reviewCheckResults.js +6 -0
  24. package/dist/core/action/reviewStateValidation.js +11 -0
  25. package/dist/core/action/reviewTimeSimulationEvidence.js +471 -0
  26. package/dist/core/action/schemas.js +529 -0
  27. package/dist/core/action/signableAdapterContract.js +993 -0
  28. package/dist/core/action/swapHumanReadableReviewProjection.js +124 -0
  29. package/dist/core/action/swapQuotePolicyEvidence.js +278 -0
  30. package/dist/core/action/transactionObjectOwnershipEvidence.js +247 -0
  31. package/dist/core/action/transactionObjectOwnershipProducer.js +329 -0
  32. package/dist/core/action/types.js +35 -0
  33. package/dist/core/action/walletReviewContractAssembler.js +282 -0
  34. package/dist/core/activity/activityStore.js +15 -0
  35. package/dist/core/activity/localDataService.js +258 -0
  36. package/dist/core/activity/localDataTypes.js +11 -0
  37. package/dist/core/activity/localDataValidation.js +396 -0
  38. package/dist/core/activity/schemaVersion.js +1 -0
  39. package/dist/core/activity/sqliteActivityStore.js +820 -0
  40. package/dist/core/activity/sqliteActivityStoreRows.js +430 -0
  41. package/dist/core/activity/sqliteActivityStoreSchema.js +258 -0
  42. package/dist/core/activity/sqliteActivityStoreTypes.js +5 -0
  43. package/dist/core/activity/suiFunctionTarget.js +43 -0
  44. package/dist/core/activity/transactionActivityAccountEffects.js +189 -0
  45. package/dist/core/activity/transactionActivityAnalysis.js +295 -0
  46. package/dist/core/activity/transactionActivityClassifier.js +306 -0
  47. package/dist/core/activity/transactionActivityDetails.js +229 -0
  48. package/dist/core/activity/transactionActivityProtocolRules.js +218 -0
  49. package/dist/core/activity/transactionActivityScanPolicy.js +170 -0
  50. package/dist/core/activity/transactionActivityService.js +379 -0
  51. package/dist/core/activity/transactionActivityTypes.js +18 -0
  52. package/dist/core/eventlog/sink.js +35 -0
  53. package/dist/core/evidence/settlementFamilies.js +87 -0
  54. package/dist/core/evidence/userAnswerUse.js +1 -0
  55. package/dist/core/numeric/rawU64.js +63 -0
  56. package/dist/core/preferences/preferencesStore.js +26 -0
  57. package/dist/core/preferences/sqlitePreferencesRepository.js +136 -0
  58. package/dist/core/proposal/externalProposalReview.js +347 -0
  59. package/dist/core/proposal/schemas.js +208 -0
  60. package/dist/core/proposal/types.js +35 -0
  61. package/dist/core/read/amounts.js +14 -0
  62. package/dist/core/read/coinMetadata.js +60 -0
  63. package/dist/core/read/deepbookRawQuoteClient.js +86 -0
  64. package/dist/core/read/deepbookReadHelpers.js +265 -0
  65. package/dist/core/read/deepbookRegistry.js +133 -0
  66. package/dist/core/read/flowxQuoteClient.js +117 -0
  67. package/dist/core/read/flowxReadHelpers.js +145 -0
  68. package/dist/core/read/flowxRegistry.js +174 -0
  69. package/dist/core/read/intentEvidenceResponseFormatting.js +228 -0
  70. package/dist/core/read/readResponseGuidance.js +451 -0
  71. package/dist/core/read/readService.js +1164 -0
  72. package/dist/core/read/readServiceTypes.js +59 -0
  73. package/dist/core/read/settlementParityFormatting.js +82 -0
  74. package/dist/core/read/walletReadHelpers.js +99 -0
  75. package/dist/core/review/reviewChecks.js +54 -0
  76. package/dist/core/review/reviewComputation.js +38 -0
  77. package/dist/core/review/reviewComputationResult.js +87 -0
  78. package/dist/core/session/localSession.js +31 -0
  79. package/dist/core/session/privateReviewArtifacts.js +73 -0
  80. package/dist/core/session/sessionErrors.js +9 -0
  81. package/dist/core/session/sessionStore.js +821 -0
  82. package/dist/core/session/settingsSession.js +1 -0
  83. package/dist/core/session/settingsSessions.js +43 -0
  84. package/dist/core/session/status.js +86 -0
  85. package/dist/core/session/transactionMaterialStore.js +205 -0
  86. package/dist/core/session/wait.js +102 -0
  87. package/dist/core/session/walletIdentity.js +103 -0
  88. package/dist/core/session/walletIdentitySessions.js +189 -0
  89. package/dist/core/suiAddress.js +18 -0
  90. package/dist/core/suiEndpoint.js +72 -0
  91. package/dist/mcp/activeAccountResponse.js +24 -0
  92. package/dist/mcp/prompts.js +146 -0
  93. package/dist/mcp/registerTool.js +19 -0
  94. package/dist/mcp/resources.js +72 -0
  95. package/dist/mcp/responseGuidance.js +381 -0
  96. package/dist/mcp/result.js +17 -0
  97. package/dist/mcp/schemas.js +8 -0
  98. package/dist/mcp/server.js +30 -0
  99. package/dist/mcp/serverInfo.js +123 -0
  100. package/dist/mcp/toolErrors.js +105 -0
  101. package/dist/mcp/toolNames.js +50 -0
  102. package/dist/mcp/tools/account/index.js +44 -0
  103. package/dist/mcp/tools/action/prepareSuiActionReview.js +120 -0
  104. package/dist/mcp/tools/read/commonSchemas.js +43 -0
  105. package/dist/mcp/tools/read/deepbookReadTools.js +453 -0
  106. package/dist/mcp/tools/read/flowxReadTools.js +135 -0
  107. package/dist/mcp/tools/read/index.js +16 -0
  108. package/dist/mcp/tools/read/readToolHelpers.js +68 -0
  109. package/dist/mcp/tools/read/reviewActivityTools.js +176 -0
  110. package/dist/mcp/tools/read/serverStatusTools.js +103 -0
  111. package/dist/mcp/tools/read/transactionActivityOutput.js +300 -0
  112. package/dist/mcp/tools/read/transactionActivityTools.js +544 -0
  113. package/dist/mcp/tools/read/walletReadTools.js +733 -0
  114. package/dist/mcp/tools/session/executionResultTools.js +92 -0
  115. package/dist/mcp/tools/session/index.js +8 -0
  116. package/dist/mcp/tools/session/shared.js +79 -0
  117. package/dist/mcp/tools/session/statusTools.js +134 -0
  118. package/dist/mcp/tools/session/walletIdentityTools.js +119 -0
  119. package/dist/mcp/tools/settings/index.js +64 -0
  120. package/dist/review-app/analysis.css +1 -0
  121. package/dist/review-app/analysis.js +1 -0
  122. package/dist/review-app/arc-BjIacwQm.js +1 -0
  123. package/dist/review-app/architecture-U656AL7Q-aSB9x1OK.js +1 -0
  124. package/dist/review-app/architectureDiagram-VXUJARFQ-C5W6re2I.js +36 -0
  125. package/dist/review-app/array-BmXUUrU6.js +1 -0
  126. package/dist/review-app/blockDiagram-VD42YOAC-20MLNcUm.js +122 -0
  127. package/dist/review-app/c4Diagram-YG6GDRKO-BZXRrcck.js +10 -0
  128. package/dist/review-app/channel-lk2p_CUu.js +1 -0
  129. package/dist/review-app/chunk-4BX2VUAB-BPITOdjX.js +1 -0
  130. package/dist/review-app/chunk-55IACEB6-Dz-pyw5k.js +1 -0
  131. package/dist/review-app/chunk-76Q3JFCE-cK_X1P_l.js +1 -0
  132. package/dist/review-app/chunk-ABZYJK2D-Dt4W53JI.js +81 -0
  133. package/dist/review-app/chunk-ATLVNIR6-fZHLXURb.js +1 -0
  134. package/dist/review-app/chunk-B4BG7PRW-BbgcjusC.js +165 -0
  135. package/dist/review-app/chunk-BJD4TVEz.js +1 -0
  136. package/dist/review-app/chunk-CVBHYZKI-CViawAKX.js +1 -0
  137. package/dist/review-app/chunk-DI55MBZ5-C5aoul-d.js +220 -0
  138. package/dist/review-app/chunk-FMBD7UC4-Chxmw62A.js +15 -0
  139. package/dist/review-app/chunk-FPAJGGOC-DDHjQ09H.js +80 -0
  140. package/dist/review-app/chunk-FWNWRKHM-CVVQUptk.js +1 -0
  141. package/dist/review-app/chunk-HN2XXSSU-yzNpjaSZ.js +1 -0
  142. package/dist/review-app/chunk-JA3XYJ7Z-C5ZJdU01.js +70 -0
  143. package/dist/review-app/chunk-JZLCHNYA-BBST4Cnk.js +54 -0
  144. package/dist/review-app/chunk-LBM3YZW2-CdwAPuHr.js +1 -0
  145. package/dist/review-app/chunk-LHMN2FUI-BtB5uDcp.js +1 -0
  146. package/dist/review-app/chunk-O7ZBX7Z2-pxdK4Sa3.js +1 -0
  147. package/dist/review-app/chunk-QN33PNHL-CbVv3uGK.js +1 -0
  148. package/dist/review-app/chunk-QXUST7PY-DKM2-t2c.js +7 -0
  149. package/dist/review-app/chunk-QZHKN3VN-C5ni2pN_.js +1 -0
  150. package/dist/review-app/chunk-S3R3BYOJ-BWvOhDs0.js +2 -0
  151. package/dist/review-app/chunk-S6J4BHB3-D9Fk0YeD.js +1 -0
  152. package/dist/review-app/chunk-T53DSG4Q-C1qEyzyV.js +1 -0
  153. package/dist/review-app/chunk-TZMSLE5B-B--7eU69.js +1 -0
  154. package/dist/review-app/classDiagram-2ON5EDUG-DlL1m2bp.js +1 -0
  155. package/dist/review-app/classDiagram-v2-WZHVMYZB-FXRskT1j.js +1 -0
  156. package/dist/review-app/clone-BZZb7gpZ.js +1 -0
  157. package/dist/review-app/cose-bilkent-S5V4N54A-CRIb8XEO.js +1 -0
  158. package/dist/review-app/cytoscape.esm-C7jYqDP5.js +321 -0
  159. package/dist/review-app/dagre-6UL2VRFP-FNCAXbdE.js +4 -0
  160. package/dist/review-app/dagre-Be46QtUd.js +1 -0
  161. package/dist/review-app/defaultLocale-BaWNtAUL.js +1 -0
  162. package/dist/review-app/diagram-PSM6KHXK-ylLWjiNM.js +24 -0
  163. package/dist/review-app/diagram-QEK2KX5R-BCDcESxs.js +43 -0
  164. package/dist/review-app/diagram-S2PKOQOG-Vdrc-vrO.js +24 -0
  165. package/dist/review-app/dist-WPc74x_f.js +1 -0
  166. package/dist/review-app/erDiagram-Q2GNP2WA-E5ZsUbDF.js +60 -0
  167. package/dist/review-app/flatten-DHf9IeNI.js +1 -0
  168. package/dist/review-app/flowDiagram-NV44I4VS-DBSQuj6x.js +162 -0
  169. package/dist/review-app/ganttDiagram-LVOFAZNH-CKUOsqwl.js +267 -0
  170. package/dist/review-app/gitGraph-F6HP7TQM-DsAD6qK1.js +1 -0
  171. package/dist/review-app/gitGraphDiagram-NY62KEGX-BCeIMWdl.js +65 -0
  172. package/dist/review-app/graphlib-CiX5CXxR.js +1 -0
  173. package/dist/review-app/http-DMvwuuFk.js +1 -0
  174. package/dist/review-app/identity-DY8PXc6t.js +1 -0
  175. package/dist/review-app/info-NVLQJR56-Dlx1nZic.js +1 -0
  176. package/dist/review-app/infoDiagram-F6ZHWCRC-CAuANIrz.js +2 -0
  177. package/dist/review-app/init-BvqephKz.js +1 -0
  178. package/dist/review-app/journeyDiagram-XKPGCS4Q-C-Z9phnx.js +139 -0
  179. package/dist/review-app/kanban-definition-3W4ZIXB7-DufgZABq.js +89 -0
  180. package/dist/review-app/katex-B-Z-NXXN.js +257 -0
  181. package/dist/review-app/line-DiIv3Jgw.js +1 -0
  182. package/dist/review-app/linear-Cv-UPvo1.js +1 -0
  183. package/dist/review-app/math-kmyYrkHL.js +1 -0
  184. package/dist/review-app/mermaid-parser.core-DkwUYTPl.js +4 -0
  185. package/dist/review-app/mindmap-definition-VGOIOE7T-TM_CqdmV.js +68 -0
  186. package/dist/review-app/ordinal-BliTlkoG.js +1 -0
  187. package/dist/review-app/packet-BFZMPI3H-DqbnU92v.js +1 -0
  188. package/dist/review-app/path-AEo9W6mQ.js +1 -0
  189. package/dist/review-app/pie-7BOR55EZ-LJzaLkgr.js +1 -0
  190. package/dist/review-app/pieDiagram-ADFJNKIX-BAs8OfRS.js +30 -0
  191. package/dist/review-app/quadrantDiagram-AYHSOK5B-CyUDZP5S.js +7 -0
  192. package/dist/review-app/radar-NHE76QYJ-DBpHc8_Y.js +1 -0
  193. package/dist/review-app/reduce-B-HuPpdd.js +1 -0
  194. package/dist/review-app/requirementDiagram-UZGBJVZJ-BEHix78P.js +64 -0
  195. package/dist/review-app/review.css +1 -0
  196. package/dist/review-app/review.js +43 -0
  197. package/dist/review-app/sankeyDiagram-TZEHDZUN-B2bKbmsm.js +10 -0
  198. package/dist/review-app/sequenceDiagram-WL72ISMW-DVLOORFJ.js +145 -0
  199. package/dist/review-app/settings.css +1 -0
  200. package/dist/review-app/settings.js +1 -0
  201. package/dist/review-app/src-Buml7cM5.js +1 -0
  202. package/dist/review-app/stateDiagram-FKZM4ZOC-sFGGp2kV.js +1 -0
  203. package/dist/review-app/stateDiagram-v2-4FDKWEC3-BHfCF4dX.js +1 -0
  204. package/dist/review-app/timeline-definition-IT6M3QCI-BESnBijC.js +61 -0
  205. package/dist/review-app/treemap-KMMF4GRG-wnVLBDeQ.js +1 -0
  206. package/dist/review-app/walletStatus-CcojOdGy.js +7 -0
  207. package/dist/review-app/xychartDiagram-PRI3JC2R-BGWVfCx4.js +7 -0
  208. package/dist/review-server/assets.js +48 -0
  209. package/dist/review-server/html.js +66 -0
  210. package/dist/review-server/http.js +47 -0
  211. package/dist/review-server/middleware/hostOrigin.js +48 -0
  212. package/dist/review-server/middleware/reviewToken.js +7 -0
  213. package/dist/review-server/reviewServerPolicy.js +10 -0
  214. package/dist/review-server/server.js +568 -0
  215. package/dist/review-server/settingsApi.js +182 -0
  216. package/dist/review-server/walletIdentityResponse.js +13 -0
  217. package/dist/runtime/config.js +103 -0
  218. package/dist/runtime/localSettingsService.js +198 -0
  219. package/dist/runtime/logger.js +50 -0
  220. package/dist/runtime/reviewServerAcquire.js +128 -0
  221. package/dist/runtime/smokeMainnetRead.js +529 -0
  222. package/dist/runtime/smokeMainnetReadAssertions.js +308 -0
  223. package/dist/runtime/start.js +295 -0
  224. package/dist/runtime/suiEndpoint.js +97 -0
  225. package/dist/runtime/suiTransactionGraphqlMapping.js +200 -0
  226. package/dist/runtime/suiTransactionGraphqlQueries.js +231 -0
  227. package/dist/runtime/suiTransactionGraphqlSource.js +148 -0
  228. package/docs/AGENT_BEHAVIOR.md +1 -1
  229. package/docs/AGENT_DEVELOPMENT_POLICY.md +20 -0
  230. package/docs/FRONTEND_POLICY.md +4 -3
  231. package/docs/MCP_SETUP.md +59 -7
  232. package/docs/MCP_TOOLS.md +1 -1
  233. package/docs/SDK_API.md +5 -1
  234. package/package.json +3 -2
@@ -0,0 +1,1164 @@
1
+ import { COIN_METADATA_CACHE_TTL_MS, DEEPBOOK_SCALAR_UNIT_SOURCE, assertValidDecimals, decimalsFromScalar, formatRawAmount, parseDisplayAmountToRaw, normalizeCoinType } from "./coinMetadata.js";
2
+ import { createDeepBookReadClient } from "./deepbookRawQuoteClient.js";
3
+ import { createFlowxQuoteClient } from "./flowxQuoteClient.js";
4
+ import { flowxQuoteQuantitySemantics, validateFlowxRouteQuote } from "./flowxReadHelpers.js";
5
+ import { resolveFlowxSwapPair } from "./flowxRegistry.js";
6
+ import { deepbookUnitForCoinType, canonicalDeepbookSymbol, getDeepbookCoinEntryBySymbol, getKnownPool, invalidDeepbookScalar, listDeepbookTokenRegistry, PINNED_DEEPBOOK_COINS, resolveDeepbookPoolForSymbols } from "./deepbookRegistry.js";
7
+ import { sumRawAmounts } from "./amounts.js";
8
+ import { buildUsdSettlementAssetGroup, commonAssetGroupDecimals, formatSettlementAssetRawAmount, normalizeSettlementDenomination } from "../evidence/settlementFamilies.js";
9
+ import { assertDeepbookDisplayBalances, assertPositiveInteger, assertValidDeepbookMidPrice, assertValidDeepbookQuote, deepbookAccountInventorySource, deepbookDisplayQuantitySemantics, deepbookMidPriceSemantics, deepbookQuoteQuantitySemantics, normalizeManagerAddresses, normalizeOptionalManagerAddress, parseQuoteDisplayAmount, parseRawAmount, toDeepbookAccountSummary, toDeepbookDisplayQuoteFromRaw } from "./deepbookReadHelpers.js";
10
+ import { classifyWalletBalance, unavailableUnit, unitFromDeepbook, unitFromMetadataRecord, walletBalanceQuantitySemantics, withResolvedUnit, withUnavailableUnit } from "./walletReadHelpers.js";
11
+ import { deepbookAccountInventoryUserAnswerUse, deepbookMidPriceUserAnswerUse, deepbookOrderbookUserAnswerUse, deepbookQuoteUserAnswerUse, flowxQuoteUserAnswerUse, intentEvidenceUserAnswerUse, settlementAssetGroupParityUserAnswerUse, walletBalanceUserAnswerUse, walletClassificationUserAnswerUse } from "./readResponseGuidance.js";
12
+ import { intentEvidenceQuantitySemantics, intentEvidenceResponseEvidence, intentEvidenceResponseSummary, intentEvidenceSettlementAssetCoverageBoundary, intentEvidenceSupportedClaims, isIntentEvidenceTargetAssetSelectionSource, isSupportedIntentEvidenceKind } from "./intentEvidenceResponseFormatting.js";
13
+ import { roundDerivedParityPrice, settlementAssetGroupParityQuantitySemantics, settlementAssetGroupParityResponseSummary, settlementAssetGroupParityStatistics } from "./settlementParityFormatting.js";
14
+ import { DEEPBOOK_MID_PRICE_DIRECTION, DEEPBOOK_MID_PRICE_PRECISION, DEEPBOOK_MID_PRICE_TYPE, DEEPBOOK_RAW_QUOTE_QUANTITY_KIND, DEFAULT_DEEPBOOK_SIMULATION_SENDER, INTENT_EVIDENCE_TARGET_ASSET_SELECTION_SOURCES, MAX_DEEPBOOK_ACCOUNT_OPEN_ORDER_IDS, MAX_DEEPBOOK_ORDERBOOK_TICKS, MAX_WALLET_BALANCE_SCAN_PAGES, NOT_INSPECTED_ASSET_CLASSES, ReadServiceCacheError, ReadServiceInputError } from "./readServiceTypes.js";
15
+ export * from "./readServiceTypes.js";
16
+ export { listDeepbookTokenRegistry } from "./deepbookRegistry.js";
17
+ export { FLOWX_CLMM_MAINNET, FLOWX_CLMM_PROTOCOL_ID, FLOWX_CLMM_UNIT_SOURCE, assertFlowxRegistryShape, listFlowxPoolRegistry } from "./flowxRegistry.js";
18
+ export class SuiReadService {
19
+ #client;
20
+ #network;
21
+ #chainIdentifier;
22
+ #coinMetadataCache;
23
+ #now;
24
+ #deepbookFactory;
25
+ #coinMetadataTtlMs;
26
+ #deepbookCoins;
27
+ #flowxQuoteClient;
28
+ constructor(options) {
29
+ this.#client = options.client;
30
+ this.#network = options.network;
31
+ this.#chainIdentifier = options.chainIdentifier;
32
+ this.#coinMetadataCache = options.coinMetadataCache;
33
+ this.#now = options.now ?? (() => new Date());
34
+ this.#coinMetadataTtlMs = options.coinMetadataTtlMs ?? COIN_METADATA_CACHE_TTL_MS;
35
+ this.#deepbookCoins = options.deepbookCoins ?? PINNED_DEEPBOOK_COINS;
36
+ this.#deepbookFactory =
37
+ options.deepbookFactory ??
38
+ ((simulationSender, factoryOptions) => createDeepBookReadClient({
39
+ client: options.client,
40
+ simulationSender,
41
+ network: this.#network,
42
+ ...(factoryOptions?.balanceManagers === undefined
43
+ ? {}
44
+ : { balanceManagers: factoryOptions.balanceManagers })
45
+ }));
46
+ this.#flowxQuoteClient = options.flowxQuoteClient ?? createFlowxQuoteClient();
47
+ }
48
+ async quoteFlowxSwap(input) {
49
+ const pair = resolveFlowxSwapPair({
50
+ sourceSymbol: input.sourceSymbol,
51
+ targetSymbol: input.targetSymbol
52
+ });
53
+ const amountInRaw = parseQuoteDisplayAmount(input.amountDisplay, pair.source.decimals);
54
+ const quote = await this.#flowxQuoteClient.getSwapRoutes({
55
+ tokenInType: pair.source.coinType,
56
+ tokenOutType: pair.target.coinType,
57
+ amountInRaw
58
+ });
59
+ const { pools } = validateFlowxRouteQuote({ pair, requestedAmountInRaw: amountInRaw, quote });
60
+ return {
61
+ status: "ok",
62
+ pair: {
63
+ sourceSymbol: pair.source.symbol,
64
+ targetSymbol: pair.target.symbol,
65
+ sourceCoinType: pair.source.coinType,
66
+ targetCoinType: pair.target.coinType
67
+ },
68
+ amountIn: {
69
+ raw: amountInRaw,
70
+ display: formatRawAmount(amountInRaw, pair.source.decimals),
71
+ decimals: pair.source.decimals
72
+ },
73
+ amountOut: {
74
+ raw: quote.amountOutRaw,
75
+ display: formatRawAmount(quote.amountOutRaw, pair.target.decimals),
76
+ decimals: pair.target.decimals,
77
+ indicative: true
78
+ },
79
+ routeEvidence: {
80
+ kind: "flowx_aggregator_route",
81
+ routeSource: "flowx_quoter_api",
82
+ routeChosenBy: "flowx_router_not_this_server",
83
+ singleHop: true,
84
+ pools,
85
+ protocolConfigPinMatch: true
86
+ },
87
+ fetchedAt: this.#fetchedAt(),
88
+ userAnswerUse: flowxQuoteUserAnswerUse(),
89
+ quantitySemantics: flowxQuoteQuantitySemantics(),
90
+ source: {
91
+ sdk: "@flowx-finance/sdk",
92
+ transport: "https",
93
+ method: "AggregatorQuoter.getRoutes",
94
+ chainVerified: false
95
+ }
96
+ };
97
+ }
98
+ async summarizeWalletAssets(input) {
99
+ const options = { owner: input.account };
100
+ if (input.cursor !== undefined) {
101
+ options.cursor = input.cursor;
102
+ }
103
+ const result = await this.#client.core.listBalances(options);
104
+ const balances = [];
105
+ for (const balance of result.balances) {
106
+ balances.push(await this.#withUnit(balance));
107
+ }
108
+ return {
109
+ status: "ok",
110
+ account: input.account,
111
+ fetchedAt: this.#fetchedAt(),
112
+ userAnswerUse: walletBalanceUserAnswerUse(),
113
+ quantitySemantics: walletBalanceQuantitySemantics(),
114
+ source: {
115
+ sdk: "@mysten/sui",
116
+ transport: "grpc",
117
+ method: "client.core.listBalances"
118
+ },
119
+ balances,
120
+ hasNextPage: result.hasNextPage,
121
+ cursor: result.cursor
122
+ };
123
+ }
124
+ async classifyWalletAssets(input) {
125
+ const summary = await this.summarizeWalletAssets(input);
126
+ return {
127
+ status: "ok",
128
+ account: summary.account,
129
+ fetchedAt: summary.fetchedAt,
130
+ userAnswerUse: walletClassificationUserAnswerUse(),
131
+ quantitySemantics: summary.quantitySemantics,
132
+ source: summary.source,
133
+ classifiedAssets: summary.balances.map((balance) => classifyWalletBalance(balance, this.#deepbookCoins)),
134
+ uninspectedAssetClasses: NOT_INSPECTED_ASSET_CLASSES.map((assetClass) => ({ ...assetClass })),
135
+ hasNextPage: summary.hasNextPage,
136
+ cursor: summary.cursor
137
+ };
138
+ }
139
+ listSettlementAssetGroups() {
140
+ return {
141
+ status: "ok",
142
+ fetchedAt: this.#fetchedAt(),
143
+ assetGroups: [buildUsdSettlementAssetGroup(this.#deepbookCoins)]
144
+ };
145
+ }
146
+ async summarizeSettlementAssetGroupParity(input) {
147
+ let denomination;
148
+ try {
149
+ denomination = normalizeSettlementDenomination(input.denomination);
150
+ }
151
+ catch {
152
+ throw new ReadServiceInputError("input_invalid", "Unsupported settlement denomination", {
153
+ field: "denomination",
154
+ value: input.denomination,
155
+ supportedAliases: buildUsdSettlementAssetGroup(this.#deepbookCoins).aliases
156
+ });
157
+ }
158
+ const assetGroup = buildUsdSettlementAssetGroup(this.#deepbookCoins);
159
+ if (assetGroup.includedAssets.length === 0) {
160
+ throw new ReadServiceInputError("registry_miss", "No pinned USD-denominated settlement assets are available", {
161
+ assetGroupId: assetGroup.id
162
+ });
163
+ }
164
+ const referenceAsset = this.#resolveSettlementAssetGroupSymbol(input.referenceAssetSymbol ?? "USDC", assetGroup, "referenceAssetSymbol");
165
+ const deepbook = this.#deepbookFactory(input.simulationSender);
166
+ const assets = await Promise.all(assetGroup.includedAssets.map((asset) => this.#settlementAssetGroupParityAsset(asset, referenceAsset, deepbook)));
167
+ const samples = assets.filter((asset) => asset.status === "reference_asset" || asset.status === "measured");
168
+ if (samples.length === 0) {
169
+ throw new ReadServiceInputError("registry_miss", "No parity samples are available for the settlement asset group", {
170
+ assetGroupId: assetGroup.id,
171
+ referenceAssetSymbol: referenceAsset.symbol
172
+ });
173
+ }
174
+ const statistics = settlementAssetGroupParityStatistics(samples, assets.length - samples.length);
175
+ return {
176
+ status: "ok",
177
+ fetchedAt: this.#fetchedAt(),
178
+ denomination,
179
+ assetGroupId: assetGroup.id,
180
+ userAnswerUse: settlementAssetGroupParityUserAnswerUse(),
181
+ referenceAsset: {
182
+ ...referenceAsset,
183
+ role: "measurement_reference_not_settlement_choice"
184
+ },
185
+ quantitySemantics: settlementAssetGroupParityQuantitySemantics(),
186
+ evidenceSources: {
187
+ settlementAssetGroup: assetGroup.evidenceSources,
188
+ midPrice: {
189
+ sdk: "@mysten/deepbook-v3",
190
+ transport: "grpc",
191
+ simulation: "client.core.simulateTransaction",
192
+ method: "midPrice",
193
+ precision: DEEPBOOK_MID_PRICE_PRECISION
194
+ }
195
+ },
196
+ assets,
197
+ statistics,
198
+ responseSummary: settlementAssetGroupParityResponseSummary({
199
+ assetGroupId: assetGroup.id,
200
+ referenceAssetSymbol: referenceAsset.symbol,
201
+ statistics
202
+ }),
203
+ unsupportedClaims: [
204
+ "settlement_token_selection",
205
+ "fiat_usd_cash_out",
206
+ "payment_execution_readiness",
207
+ "route_recommendation",
208
+ "best_route",
209
+ "transaction_building",
210
+ "signing_readiness",
211
+ "profit_or_pnl",
212
+ "cost_basis"
213
+ ]
214
+ };
215
+ }
216
+ async previewIntentEvidence(input) {
217
+ if (!isSupportedIntentEvidenceKind(input.intentKind)) {
218
+ throw new ReadServiceInputError("input_invalid", "Unsupported intentKind", {
219
+ field: "intentKind",
220
+ value: input.intentKind
221
+ });
222
+ }
223
+ if (input.intentKind === "cover_payment_like_amount" && input.requiredDisplayAmount === undefined) {
224
+ throw new ReadServiceInputError("input_invalid", "requiredDisplayAmount is required for cover_payment_like_amount", {
225
+ field: "requiredDisplayAmount",
226
+ intentKind: input.intentKind
227
+ });
228
+ }
229
+ if (input.targetAssetSelectionSource !== undefined &&
230
+ !isIntentEvidenceTargetAssetSelectionSource(input.targetAssetSelectionSource)) {
231
+ throw new ReadServiceInputError("input_invalid", "Unsupported targetAssetSelectionSource", {
232
+ field: "targetAssetSelectionSource",
233
+ value: input.targetAssetSelectionSource,
234
+ supportedValues: [...INTENT_EVIDENCE_TARGET_ASSET_SELECTION_SOURCES]
235
+ });
236
+ }
237
+ if (input.intentKind === "summarize_settlement_asset_group_balance") {
238
+ if (input.requiredDisplayAmount !== undefined) {
239
+ throw new ReadServiceInputError("input_invalid", "requiredDisplayAmount is only supported for cover_payment_like_amount", {
240
+ field: "requiredDisplayAmount",
241
+ intentKind: input.intentKind
242
+ });
243
+ }
244
+ if (input.targetAssetSymbol !== undefined) {
245
+ throw new ReadServiceInputError("input_invalid", "targetAssetSymbol is only supported for cover_payment_like_amount", {
246
+ field: "targetAssetSymbol",
247
+ intentKind: input.intentKind
248
+ });
249
+ }
250
+ if (input.targetAssetSelectionSource !== undefined) {
251
+ throw new ReadServiceInputError("input_invalid", "targetAssetSelectionSource is only supported for cover_payment_like_amount", {
252
+ field: "targetAssetSelectionSource",
253
+ intentKind: input.intentKind
254
+ });
255
+ }
256
+ if (input.acceptedSourceAssetSymbols !== undefined) {
257
+ throw new ReadServiceInputError("input_invalid", "acceptedSourceAssetSymbols is only supported for cover_payment_like_amount", {
258
+ field: "acceptedSourceAssetSymbols",
259
+ intentKind: input.intentKind
260
+ });
261
+ }
262
+ }
263
+ if (input.intentKind === "cover_payment_like_amount") {
264
+ if (input.targetAssetSymbol !== undefined && input.targetAssetSelectionSource === undefined) {
265
+ throw new ReadServiceInputError("input_invalid", "targetAssetSelectionSource is required when targetAssetSymbol is supplied", {
266
+ field: "targetAssetSelectionSource",
267
+ requiredWith: "targetAssetSymbol",
268
+ supportedValues: [...INTENT_EVIDENCE_TARGET_ASSET_SELECTION_SOURCES]
269
+ });
270
+ }
271
+ if (input.targetAssetSymbol === undefined && input.targetAssetSelectionSource !== undefined) {
272
+ throw new ReadServiceInputError("input_invalid", "targetAssetSymbol is required when targetAssetSelectionSource is supplied", {
273
+ field: "targetAssetSymbol",
274
+ requiredWith: "targetAssetSelectionSource"
275
+ });
276
+ }
277
+ }
278
+ let denomination;
279
+ try {
280
+ denomination = normalizeSettlementDenomination(input.denomination);
281
+ }
282
+ catch {
283
+ throw new ReadServiceInputError("input_invalid", "Unsupported settlement denomination", {
284
+ field: "denomination",
285
+ value: input.denomination,
286
+ supportedAliases: buildUsdSettlementAssetGroup(this.#deepbookCoins).aliases
287
+ });
288
+ }
289
+ const assetGroup = buildUsdSettlementAssetGroup(this.#deepbookCoins);
290
+ if (assetGroup.includedAssets.length === 0) {
291
+ throw new ReadServiceInputError("registry_miss", "No pinned USD-denominated settlement assets are available", {
292
+ assetGroupId: assetGroup.id
293
+ });
294
+ }
295
+ const targetAsset = input.intentKind !== "cover_payment_like_amount" || input.targetAssetSymbol === undefined
296
+ ? undefined
297
+ : this.#resolveSettlementAssetGroupSymbol(input.targetAssetSymbol, assetGroup, "targetAssetSymbol");
298
+ const targetAssetSelectionSource = targetAsset === undefined ? undefined : input.targetAssetSelectionSource;
299
+ const acceptedSourceSymbols = input.intentKind !== "cover_payment_like_amount" || input.acceptedSourceAssetSymbols === undefined
300
+ ? undefined
301
+ : input.acceptedSourceAssetSymbols.map((symbol, index) => this.#resolveSettlementAssetGroupSymbol(symbol, assetGroup, `acceptedSourceAssetSymbols[${index}]`).symbol);
302
+ const acceptedSourceSet = acceptedSourceSymbols === undefined ? undefined : new Set(acceptedSourceSymbols);
303
+ const scan = await this.#scanWalletAssetClassificationPages(input.account);
304
+ const balances = this.#intentEvidenceAssetGroupBalances(assetGroup, scan.classifiedAssets);
305
+ const commonDecimals = commonAssetGroupDecimals(assetGroup.includedAssets);
306
+ const aggregate = this.#intentEvidenceAggregate({
307
+ requiredDisplayAmount: input.requiredDisplayAmount,
308
+ balances,
309
+ commonDecimals,
310
+ blockedReason: scan.blockedReason
311
+ });
312
+ const settlementAssetCoverage = this.#intentEvidenceSettlementAssetCoverage(aggregate);
313
+ const requiredDisplayAmount = input.requiredDisplayAmount;
314
+ let selectedTarget;
315
+ if (targetAsset !== undefined) {
316
+ if (requiredDisplayAmount === undefined) {
317
+ throw new Error("target settlement evidence requires requiredDisplayAmount");
318
+ }
319
+ if (targetAssetSelectionSource === undefined) {
320
+ throw new Error("target settlement evidence requires user selection provenance");
321
+ }
322
+ if (scan.blockedReason === undefined) {
323
+ selectedTarget = this.#intentEvidenceSelectedTarget({
324
+ targetAsset,
325
+ selectionSource: targetAssetSelectionSource,
326
+ requiredDisplayAmount,
327
+ balances
328
+ });
329
+ }
330
+ }
331
+ const candidateConversions = input.intentKind === "cover_payment_like_amount" && scan.blockedReason === undefined
332
+ ? await this.#intentEvidenceCandidateConversions({
333
+ targetAsset,
334
+ balances,
335
+ acceptedSourceSet
336
+ })
337
+ : [];
338
+ const requiredUserChoices = this.#intentEvidenceRequiredUserChoices(input.intentKind, targetAsset, candidateConversions);
339
+ const responseEvidence = intentEvidenceResponseEvidence(targetAsset, settlementAssetCoverage, candidateConversions);
340
+ const responseSummary = intentEvidenceResponseSummary({
341
+ intentKind: input.intentKind,
342
+ assetGroupId: assetGroup.id,
343
+ settlementAssetCoverage,
344
+ responseEvidenceMode: responseEvidence.mode,
345
+ requiredUserChoices
346
+ });
347
+ const { excludedAssets: _excludedAssets, ...settlementAssetGroup } = assetGroup;
348
+ return {
349
+ status: "ok",
350
+ account: input.account,
351
+ fetchedAt: this.#fetchedAt(),
352
+ userAnswerUse: intentEvidenceUserAnswerUse(settlementAssetCoverage.status, responseEvidence),
353
+ intent: {
354
+ intentKind: input.intentKind,
355
+ denomination,
356
+ ...(input.requiredDisplayAmount === undefined ? {} : { requiredDisplayAmount: input.requiredDisplayAmount }),
357
+ ...(targetAsset === undefined
358
+ ? {}
359
+ : { targetAssetSymbol: targetAsset.symbol, targetAssetSelectionSource }),
360
+ ...(acceptedSourceSymbols === undefined ? {} : { acceptedSourceAssetSymbols: acceptedSourceSymbols })
361
+ },
362
+ quantitySemantics: intentEvidenceQuantitySemantics(),
363
+ evidenceSources: {
364
+ walletBalances: {
365
+ sdk: "@mysten/sui",
366
+ transport: "grpc",
367
+ method: "client.core.listBalances"
368
+ },
369
+ settlementAssetGroup: assetGroup.evidenceSources,
370
+ quoteEvidence: "pinned_deepbook_sdk_when_target_asset_selected"
371
+ },
372
+ settlementAssetGroup,
373
+ balances,
374
+ aggregate,
375
+ settlementAssetCoverage,
376
+ ...(selectedTarget === undefined ? {} : { selectedTarget }),
377
+ candidateConversions,
378
+ blockedReasons: scan.blockedReason === undefined ? [] : [scan.blockedReason],
379
+ responseEvidence,
380
+ responseSummary,
381
+ requiredUserChoices,
382
+ supportedClaims: intentEvidenceSupportedClaims(settlementAssetCoverage, selectedTarget, candidateConversions),
383
+ unsupportedClaims: [
384
+ "settlement_token_selection",
385
+ "fiat_usd_cash_out",
386
+ "gas_reserve_or_fee_readiness",
387
+ "best_route_or_venue_comparison",
388
+ "route_dependent_payment_support",
389
+ "payment_execution_readiness",
390
+ "transaction_building",
391
+ "signing_readiness",
392
+ "profit_or_pnl",
393
+ "cost_basis"
394
+ ],
395
+ uninspectedAssetClasses: scan.uninspectedAssetClasses,
396
+ inspectedBalancePages: scan.inspectedBalancePages,
397
+ inspectedCoinBalanceCount: scan.inspectedCoinBalanceCount
398
+ };
399
+ }
400
+ async inspectDeepbookOrderbook(input) {
401
+ getKnownPool(input.poolKey);
402
+ assertPositiveInteger(input.ticks, "ticks", MAX_DEEPBOOK_ORDERBOOK_TICKS);
403
+ const deepbook = this.#deepbookFactory(input.simulationSender);
404
+ const [midPrice, poolBookParams, level2TicksFromMid] = await Promise.all([
405
+ deepbook.midPrice(input.poolKey),
406
+ deepbook.poolBookParams(input.poolKey),
407
+ deepbook.getLevel2TicksFromMid(input.poolKey, input.ticks)
408
+ ]);
409
+ const checkedMidPrice = assertValidDeepbookMidPrice(input.poolKey, midPrice);
410
+ return {
411
+ status: "ok",
412
+ poolKey: input.poolKey,
413
+ ticks: input.ticks,
414
+ fetchedAt: this.#fetchedAt(),
415
+ userAnswerUse: deepbookOrderbookUserAnswerUse(),
416
+ source: {
417
+ sdk: "@mysten/deepbook-v3",
418
+ transport: "grpc",
419
+ simulation: "client.core.simulateTransaction",
420
+ methods: ["midPrice", "poolBookParams", "getLevel2TicksFromMid"]
421
+ },
422
+ midPrice: checkedMidPrice,
423
+ poolBookParams,
424
+ level2TicksFromMid
425
+ };
426
+ }
427
+ async getDeepbookMidPrice(input) {
428
+ const pool = getKnownPool(input.poolKey);
429
+ const deepbook = this.#deepbookFactory(input.simulationSender);
430
+ const midPrice = assertValidDeepbookMidPrice(input.poolKey, await deepbook.midPrice(input.poolKey));
431
+ return {
432
+ status: "ok",
433
+ poolKey: input.poolKey,
434
+ base: pool.baseCoin,
435
+ quote: pool.quoteCoin,
436
+ userAnswerUse: deepbookMidPriceUserAnswerUse(),
437
+ priceSemantics: deepbookMidPriceSemantics(),
438
+ price: midPrice,
439
+ priceDirection: DEEPBOOK_MID_PRICE_DIRECTION,
440
+ priceType: DEEPBOOK_MID_PRICE_TYPE,
441
+ fetchedAt: this.#fetchedAt(),
442
+ source: {
443
+ sdk: "@mysten/deepbook-v3",
444
+ transport: "grpc",
445
+ simulation: "client.core.simulateTransaction",
446
+ method: "midPrice",
447
+ precision: DEEPBOOK_MID_PRICE_PRECISION
448
+ }
449
+ };
450
+ }
451
+ async quoteDeepbookAction(input) {
452
+ const pool = getKnownPool(input.poolKey);
453
+ const amount = parseRawAmount(input.amountRaw);
454
+ const feeMode = input.feeMode ?? "deep";
455
+ const deepbook = this.#deepbookFactory(input.simulationSender);
456
+ if (feeMode === "input_coin" && (!deepbook.getQuoteQuantityOutInputFeeRaw || !deepbook.getBaseQuantityOutInputFeeRaw)) {
457
+ throw new ReadServiceInputError("quote_unavailable", "Input-fee DeepBook quoting is not supported by this read client", {
458
+ poolKey: input.poolKey
459
+ });
460
+ }
461
+ const rawReturnValues = input.direction === "base_to_quote"
462
+ ? feeMode === "input_coin"
463
+ ? await deepbook.getQuoteQuantityOutInputFeeRaw(input.poolKey, amount)
464
+ : await deepbook.getQuoteQuantityOutRaw(input.poolKey, amount)
465
+ : feeMode === "input_coin"
466
+ ? await deepbook.getBaseQuantityOutInputFeeRaw(input.poolKey, amount)
467
+ : await deepbook.getBaseQuantityOutRaw(input.poolKey, amount);
468
+ const rawQuote = this.#toDeepbookRawQuoteEvidence({
469
+ poolKey: input.poolKey,
470
+ direction: input.direction,
471
+ amountRaw: input.amountRaw,
472
+ rawReturnValues,
473
+ feeMode
474
+ });
475
+ const quote = assertValidDeepbookQuote(input.poolKey, input.direction, toDeepbookDisplayQuoteFromRaw(rawReturnValues, this.#deepbookQuoteUnits(pool.baseCoin, pool.quoteCoin)));
476
+ return {
477
+ status: "ok",
478
+ poolKey: input.poolKey,
479
+ direction: input.direction,
480
+ amountRaw: input.amountRaw,
481
+ fetchedAt: this.#fetchedAt(),
482
+ userAnswerUse: deepbookQuoteUserAnswerUse("raw"),
483
+ quantitySemantics: deepbookQuoteQuantitySemantics("raw_u64"),
484
+ source: {
485
+ sdk: "@mysten/deepbook-v3",
486
+ transport: "grpc",
487
+ simulation: "client.core.simulateTransaction",
488
+ method: input.direction === "base_to_quote"
489
+ ? feeMode === "input_coin"
490
+ ? "getQuoteQuantityOutInputFee"
491
+ : "getQuoteQuantityOut"
492
+ : feeMode === "input_coin"
493
+ ? "getBaseQuantityOutInputFee"
494
+ : "getBaseQuantityOut",
495
+ returnValueEncoding: "bcs.u64"
496
+ },
497
+ quote,
498
+ rawQuote
499
+ };
500
+ }
501
+ async quoteDeepbookDisplayAmount(input) {
502
+ const pool = getKnownPool(input.poolKey);
503
+ const sourceSymbol = input.direction === "base_to_quote" ? pool.baseCoin : pool.quoteCoin;
504
+ const sourceCoin = getDeepbookCoinEntryBySymbol(sourceSymbol, this.#deepbookCoins);
505
+ const decimals = decimalsFromScalar(sourceCoin.coin.scalar);
506
+ if (decimals === undefined) {
507
+ throw invalidDeepbookScalar(sourceCoin.symbol, sourceCoin.coin.scalar);
508
+ }
509
+ const amountRaw = parseQuoteDisplayAmount(input.amountDisplay, decimals);
510
+ const rawQuote = await this.quoteDeepbookAction({
511
+ poolKey: input.poolKey,
512
+ direction: input.direction,
513
+ amountRaw,
514
+ simulationSender: input.simulationSender,
515
+ feeMode: input.feeMode
516
+ });
517
+ return {
518
+ status: "ok",
519
+ pool: {
520
+ poolKey: input.poolKey,
521
+ base: pool.baseCoin,
522
+ quote: pool.quoteCoin
523
+ },
524
+ direction: input.direction,
525
+ inputAmount: {
526
+ display: input.amountDisplay,
527
+ raw: amountRaw,
528
+ asset: {
529
+ symbol: sourceCoin.symbol,
530
+ coinType: normalizeCoinType(sourceCoin.coin.type),
531
+ decimals,
532
+ unitSource: DEEPBOOK_SCALAR_UNIT_SOURCE
533
+ }
534
+ },
535
+ fetchedAt: rawQuote.fetchedAt,
536
+ userAnswerUse: deepbookQuoteUserAnswerUse("display"),
537
+ quantitySemantics: deepbookQuoteQuantitySemantics("display_source_amount_converted_to_raw_u64"),
538
+ source: rawQuote.source,
539
+ quote: rawQuote.quote,
540
+ rawQuote: rawQuote.rawQuote
541
+ };
542
+ }
543
+ #toDeepbookRawQuoteEvidence(input) {
544
+ const pool = getKnownPool(input.poolKey);
545
+ const inputSymbol = input.direction === "base_to_quote" ? pool.baseCoin : pool.quoteCoin;
546
+ const outputSymbol = input.direction === "base_to_quote" ? pool.quoteCoin : pool.baseCoin;
547
+ const baseOut = this.#deepbookRawQuoteAmount(pool.baseCoin, input.rawReturnValues.baseOutRaw);
548
+ const quoteOut = this.#deepbookRawQuoteAmount(pool.quoteCoin, input.rawReturnValues.quoteOutRaw);
549
+ return {
550
+ kind: DEEPBOOK_RAW_QUOTE_QUANTITY_KIND,
551
+ sourceMoveFunction: input.direction === "base_to_quote"
552
+ ? input.feeMode === "input_coin"
553
+ ? "pool::get_quote_quantity_out_input_fee"
554
+ : "pool::get_quote_quantity_out"
555
+ : input.feeMode === "input_coin"
556
+ ? "pool::get_base_quantity_out_input_fee"
557
+ : "pool::get_base_quantity_out",
558
+ returnValueSourceMoveFunction: input.feeMode === "input_coin" ? "pool::get_quantity_out_input_fee" : "pool::get_quantity_out",
559
+ returnValueOrder: ["base_quantity_out", "quote_quantity_out", "deep_quantity_required"],
560
+ inputAmount: this.#deepbookRawQuoteAmount(inputSymbol, input.amountRaw),
561
+ baseOut,
562
+ quoteOut,
563
+ deepRequired: this.#deepbookRawQuoteAmount("DEEP", input.rawReturnValues.deepRequiredRaw),
564
+ directionalOutput: outputSymbol === pool.baseCoin ? baseOut : quoteOut,
565
+ boundary: {
566
+ outputBeforeSlippagePolicy: true,
567
+ notFor: [
568
+ "final_min_out",
569
+ "transaction_building",
570
+ "signing_data",
571
+ "signing_readiness",
572
+ "price_impact",
573
+ "mid_price_slippage",
574
+ "quote_vs_mid_slippage",
575
+ "effective_price",
576
+ "venue_comparison",
577
+ "best_route",
578
+ "route_recommendation",
579
+ "fiat_usd_cash_out",
580
+ "external_market_price_conversion",
581
+ "external_market_lookup",
582
+ "usd_peg_assumption",
583
+ "bank_cash_out_estimate",
584
+ "profit_or_pnl",
585
+ "cost_basis"
586
+ ]
587
+ }
588
+ };
589
+ }
590
+ #deepbookRawQuoteAmount(symbol, raw) {
591
+ const coin = getDeepbookCoinEntryBySymbol(symbol, this.#deepbookCoins);
592
+ const decimals = decimalsFromScalar(coin.coin.scalar);
593
+ if (decimals === undefined) {
594
+ throw invalidDeepbookScalar(coin.symbol, coin.coin.scalar);
595
+ }
596
+ return {
597
+ raw,
598
+ symbol: coin.symbol,
599
+ coinType: normalizeCoinType(coin.coin.type),
600
+ decimals,
601
+ unitSource: DEEPBOOK_SCALAR_UNIT_SOURCE
602
+ };
603
+ }
604
+ #deepbookQuoteUnits(baseSymbol, quoteSymbol) {
605
+ const baseCoin = getDeepbookCoinEntryBySymbol(baseSymbol, this.#deepbookCoins);
606
+ const quoteCoin = getDeepbookCoinEntryBySymbol(quoteSymbol, this.#deepbookCoins);
607
+ const deepCoin = getDeepbookCoinEntryBySymbol("DEEP", this.#deepbookCoins);
608
+ const baseDecimals = decimalsFromScalar(baseCoin.coin.scalar);
609
+ const quoteDecimals = decimalsFromScalar(quoteCoin.coin.scalar);
610
+ const deepDecimals = decimalsFromScalar(deepCoin.coin.scalar);
611
+ if (baseDecimals === undefined) {
612
+ throw invalidDeepbookScalar(baseCoin.symbol, baseCoin.coin.scalar);
613
+ }
614
+ if (quoteDecimals === undefined) {
615
+ throw invalidDeepbookScalar(quoteCoin.symbol, quoteCoin.coin.scalar);
616
+ }
617
+ if (deepDecimals === undefined) {
618
+ throw invalidDeepbookScalar(deepCoin.symbol, deepCoin.coin.scalar);
619
+ }
620
+ return {
621
+ baseDecimals,
622
+ quoteDecimals,
623
+ deepDecimals
624
+ };
625
+ }
626
+ async summarizeDeepbookAccountInventory(input) {
627
+ const normalizedManagerAddress = normalizeOptionalManagerAddress(input.managerAddress);
628
+ const pool = input.poolKey === undefined ? undefined : getKnownPool(input.poolKey);
629
+ const discoveryClient = this.#deepbookFactory(input.account);
630
+ const managerAddresses = normalizeManagerAddresses(await discoveryClient.getBalanceManagerIds(input.account));
631
+ const requested = {
632
+ ...(input.poolKey === undefined ? {} : { poolKey: input.poolKey }),
633
+ ...(normalizedManagerAddress === undefined ? {} : { managerAddress: normalizedManagerAddress })
634
+ };
635
+ const base = {
636
+ status: "ok",
637
+ account: input.account,
638
+ fetchedAt: this.#fetchedAt(),
639
+ requested,
640
+ managerAddresses,
641
+ quantitySemantics: deepbookDisplayQuantitySemantics(),
642
+ ...(input.poolKey === undefined || pool === undefined
643
+ ? {}
644
+ : {
645
+ pool: {
646
+ poolKey: input.poolKey,
647
+ base: pool.baseCoin,
648
+ quote: pool.quoteCoin
649
+ }
650
+ })
651
+ };
652
+ if (input.poolKey === undefined && normalizedManagerAddress === undefined) {
653
+ return {
654
+ ...base,
655
+ userAnswerUse: deepbookAccountInventoryUserAnswerUse("manager_discovery_only"),
656
+ detailStatus: "manager_discovery_only",
657
+ source: deepbookAccountInventorySource(["getBalanceManagerIds"])
658
+ };
659
+ }
660
+ if (input.poolKey === undefined) {
661
+ return {
662
+ ...base,
663
+ userAnswerUse: deepbookAccountInventoryUserAnswerUse("pool_key_required"),
664
+ detailStatus: "pool_key_required",
665
+ source: deepbookAccountInventorySource(["getBalanceManagerIds"])
666
+ };
667
+ }
668
+ if (normalizedManagerAddress === undefined) {
669
+ return {
670
+ ...base,
671
+ userAnswerUse: deepbookAccountInventoryUserAnswerUse("manager_address_required"),
672
+ detailStatus: "manager_address_required",
673
+ source: deepbookAccountInventorySource(["getBalanceManagerIds"])
674
+ };
675
+ }
676
+ if (!managerAddresses.includes(normalizedManagerAddress)) {
677
+ return {
678
+ ...base,
679
+ userAnswerUse: deepbookAccountInventoryUserAnswerUse("manager_address_not_discovered_for_active_account"),
680
+ detailStatus: "manager_address_not_discovered_for_active_account",
681
+ source: deepbookAccountInventorySource(["getBalanceManagerIds"])
682
+ };
683
+ }
684
+ const detailClient = this.#deepbookFactory(input.account, {
685
+ balanceManagers: {
686
+ [normalizedManagerAddress]: { address: normalizedManagerAddress }
687
+ }
688
+ });
689
+ const accountExists = await detailClient.accountExists(input.poolKey, normalizedManagerAddress);
690
+ if (!accountExists) {
691
+ return {
692
+ ...base,
693
+ userAnswerUse: deepbookAccountInventoryUserAnswerUse("account_not_found"),
694
+ detailStatus: "account_not_found",
695
+ source: deepbookAccountInventorySource(["getBalanceManagerIds", "accountExists"]),
696
+ accountExists
697
+ };
698
+ }
699
+ const [accountSummary, lockedBalances, openOrderIds] = await Promise.all([
700
+ detailClient.account(input.poolKey, normalizedManagerAddress),
701
+ detailClient.lockedBalance(input.poolKey, normalizedManagerAddress),
702
+ detailClient.accountOpenOrders(input.poolKey, normalizedManagerAddress)
703
+ ]);
704
+ const cappedOpenOrderIds = openOrderIds.slice(0, MAX_DEEPBOOK_ACCOUNT_OPEN_ORDER_IDS);
705
+ return {
706
+ ...base,
707
+ userAnswerUse: deepbookAccountInventoryUserAnswerUse("available"),
708
+ detailStatus: "available",
709
+ source: deepbookAccountInventorySource([
710
+ "getBalanceManagerIds",
711
+ "accountExists",
712
+ "account",
713
+ "lockedBalance",
714
+ "accountOpenOrders"
715
+ ]),
716
+ accountExists,
717
+ accountSummary: toDeepbookAccountSummary(accountSummary),
718
+ lockedBalances: assertDeepbookDisplayBalances(lockedBalances, "lockedBalances"),
719
+ openOrderIds: cappedOpenOrderIds,
720
+ openOrderCount: openOrderIds.length,
721
+ openOrderIdsTruncated: openOrderIds.length > cappedOpenOrderIds.length
722
+ };
723
+ }
724
+ #resolveSettlementAssetGroupSymbol(symbol, assetGroup, field) {
725
+ const canonical = canonicalDeepbookSymbol(symbol, this.#deepbookCoins);
726
+ if (canonical === undefined) {
727
+ throw new ReadServiceInputError("input_invalid", "Settlement asset symbol is not in the pinned DeepBook registry", {
728
+ field,
729
+ value: symbol
730
+ });
731
+ }
732
+ const asset = assetGroup.includedAssets.find((candidate) => candidate.symbol === canonical);
733
+ if (asset === undefined) {
734
+ throw new ReadServiceInputError("input_invalid", "Settlement asset symbol is not in the supported assetGroup", {
735
+ field,
736
+ value: symbol,
737
+ canonicalSymbol: canonical,
738
+ assetGroupId: assetGroup.id
739
+ });
740
+ }
741
+ return asset;
742
+ }
743
+ #intentEvidenceAssetGroupBalances(assetGroup, classifiedAssets) {
744
+ return assetGroup.includedAssets.map((asset) => {
745
+ const matchingBalances = classifiedAssets
746
+ .filter((classified) => {
747
+ try {
748
+ return normalizeCoinType(classified.balance.coinType) === asset.coinType;
749
+ }
750
+ catch {
751
+ return false;
752
+ }
753
+ })
754
+ .map((classified) => classified.balance.balance);
755
+ const currentRawAmount = sumRawAmounts(matchingBalances);
756
+ return {
757
+ ...asset,
758
+ currentRawAmount,
759
+ currentDisplayAmount: formatSettlementAssetRawAmount(currentRawAmount, asset.decimals),
760
+ walletBalanceEvidence: "current_wallet_coin_balance_snapshot"
761
+ };
762
+ });
763
+ }
764
+ #intentEvidenceAggregate(input) {
765
+ if (input.blockedReason !== undefined) {
766
+ return {
767
+ status: "unavailable_wallet_balance_scan_incomplete",
768
+ ...(input.requiredDisplayAmount === undefined ? {} : { requiredDisplayAmount: input.requiredDisplayAmount }),
769
+ reason: input.blockedReason
770
+ };
771
+ }
772
+ if (input.commonDecimals === undefined) {
773
+ return {
774
+ status: "unavailable_mixed_decimals",
775
+ ...(input.requiredDisplayAmount === undefined ? {} : { requiredDisplayAmount: input.requiredDisplayAmount })
776
+ };
777
+ }
778
+ const currentRawAmount = sumRawAmounts(input.balances.map((balance) => balance.currentRawAmount));
779
+ if (input.requiredDisplayAmount === undefined) {
780
+ return {
781
+ status: "available",
782
+ currentRawAmount,
783
+ currentDisplayAmount: formatSettlementAssetRawAmount(currentRawAmount, input.commonDecimals),
784
+ decimals: input.commonDecimals,
785
+ unitSource: DEEPBOOK_SCALAR_UNIT_SOURCE
786
+ };
787
+ }
788
+ const requiredRawAmount = this.#parseIntentDisplayAmount(input.requiredDisplayAmount, input.commonDecimals, "requiredDisplayAmount");
789
+ const shortfallRawAmount = BigInt(currentRawAmount) >= BigInt(requiredRawAmount)
790
+ ? "0"
791
+ : (BigInt(requiredRawAmount) - BigInt(currentRawAmount)).toString();
792
+ return {
793
+ status: "available",
794
+ requiredDisplayAmount: input.requiredDisplayAmount,
795
+ requiredRawAmount,
796
+ currentRawAmount,
797
+ currentDisplayAmount: formatSettlementAssetRawAmount(currentRawAmount, input.commonDecimals),
798
+ shortfallRawAmount,
799
+ shortfallDisplayAmount: formatSettlementAssetRawAmount(shortfallRawAmount, input.commonDecimals),
800
+ decimals: input.commonDecimals,
801
+ unitSource: DEEPBOOK_SCALAR_UNIT_SOURCE
802
+ };
803
+ }
804
+ #intentEvidenceSettlementAssetCoverage(aggregate) {
805
+ const boundary = intentEvidenceSettlementAssetCoverageBoundary();
806
+ if (aggregate.status === "unavailable_mixed_decimals") {
807
+ return {
808
+ status: "unavailable_mixed_decimals",
809
+ ...(aggregate.requiredDisplayAmount === undefined
810
+ ? {}
811
+ : { requiredDisplayAmount: aggregate.requiredDisplayAmount }),
812
+ reason: "asset_group_assets_do_not_share_verified_decimals",
813
+ boundary
814
+ };
815
+ }
816
+ if (aggregate.status === "unavailable_wallet_balance_scan_incomplete") {
817
+ return {
818
+ status: "unavailable_wallet_balance_scan_incomplete",
819
+ ...(aggregate.requiredDisplayAmount === undefined
820
+ ? {}
821
+ : { requiredDisplayAmount: aggregate.requiredDisplayAmount }),
822
+ reason: aggregate.reason,
823
+ boundary
824
+ };
825
+ }
826
+ if (aggregate.requiredDisplayAmount === undefined) {
827
+ return {
828
+ status: "balance_total_only",
829
+ currentRawAmount: aggregate.currentRawAmount,
830
+ currentDisplayAmount: aggregate.currentDisplayAmount,
831
+ decimals: aggregate.decimals,
832
+ unitSource: aggregate.unitSource,
833
+ boundary
834
+ };
835
+ }
836
+ if (aggregate.requiredRawAmount === undefined ||
837
+ aggregate.shortfallRawAmount === undefined ||
838
+ aggregate.shortfallDisplayAmount === undefined) {
839
+ throw new Error("settlement-asset coverage requires complete target amount evidence");
840
+ }
841
+ const shortfallRawAmount = aggregate.shortfallRawAmount;
842
+ return {
843
+ status: BigInt(shortfallRawAmount) === 0n ? "covered_by_settlement_asset_balance" : "shortfall_in_settlement_asset_balance",
844
+ requiredDisplayAmount: aggregate.requiredDisplayAmount,
845
+ requiredRawAmount: aggregate.requiredRawAmount,
846
+ currentRawAmount: aggregate.currentRawAmount,
847
+ currentDisplayAmount: aggregate.currentDisplayAmount,
848
+ shortfallRawAmount,
849
+ shortfallDisplayAmount: aggregate.shortfallDisplayAmount,
850
+ decimals: aggregate.decimals,
851
+ unitSource: aggregate.unitSource,
852
+ boundary
853
+ };
854
+ }
855
+ #intentEvidenceSelectedTarget(input) {
856
+ const targetBalance = input.balances.find((balance) => balance.symbol === input.targetAsset.symbol);
857
+ const currentRawAmount = targetBalance?.currentRawAmount ?? "0";
858
+ const requiredRawAmount = this.#parseIntentDisplayAmount(input.requiredDisplayAmount, input.targetAsset.decimals, "requiredDisplayAmount");
859
+ const shortfallRawAmount = BigInt(currentRawAmount) >= BigInt(requiredRawAmount)
860
+ ? "0"
861
+ : (BigInt(requiredRawAmount) - BigInt(currentRawAmount)).toString();
862
+ return {
863
+ ...input.targetAsset,
864
+ selectionSource: input.selectionSource,
865
+ requiredRawAmount,
866
+ currentRawAmount,
867
+ currentDisplayAmount: formatSettlementAssetRawAmount(currentRawAmount, input.targetAsset.decimals),
868
+ shortfallRawAmount,
869
+ shortfallDisplayAmount: formatSettlementAssetRawAmount(shortfallRawAmount, input.targetAsset.decimals)
870
+ };
871
+ }
872
+ async #settlementAssetGroupParityAsset(asset, referenceAsset, deepbook) {
873
+ if (asset.symbol === referenceAsset.symbol) {
874
+ return {
875
+ ...asset,
876
+ status: "reference_asset",
877
+ parityPrice: 1,
878
+ parityDirection: "reference_asset_per_group_asset",
879
+ reason: "reference_asset_is_measurement_baseline_not_settlement_choice"
880
+ };
881
+ }
882
+ let directPool;
883
+ try {
884
+ const resolved = resolveDeepbookPoolForSymbols({
885
+ sourceSymbol: asset.symbol,
886
+ targetSymbol: referenceAsset.symbol
887
+ });
888
+ directPool = { poolKey: resolved.poolKey, direction: resolved.direction };
889
+ }
890
+ catch {
891
+ return {
892
+ ...asset,
893
+ status: "no_direct_deepbook_pool",
894
+ reason: "No direct DeepBook mainnet pool exists between this group asset and the measurement reference asset."
895
+ };
896
+ }
897
+ let poolMidPrice;
898
+ try {
899
+ poolMidPrice = assertValidDeepbookMidPrice(directPool.poolKey, await deepbook.midPrice(directPool.poolKey));
900
+ }
901
+ catch (error) {
902
+ return {
903
+ ...asset,
904
+ status: "mid_price_unavailable",
905
+ poolKey: directPool.poolKey,
906
+ direction: directPool.direction,
907
+ reason: error instanceof Error ? error.message : "DeepBook mid-price lookup failed."
908
+ };
909
+ }
910
+ return {
911
+ ...asset,
912
+ status: "measured",
913
+ parityPrice: roundDerivedParityPrice(directPool.direction === "base_to_quote" ? poolMidPrice : 1 / poolMidPrice),
914
+ parityDirection: "reference_asset_per_group_asset",
915
+ poolKey: directPool.poolKey,
916
+ direction: directPool.direction,
917
+ poolMidPrice,
918
+ poolMidPriceDirection: DEEPBOOK_MID_PRICE_DIRECTION
919
+ };
920
+ }
921
+ async #intentEvidenceCandidateConversions(input) {
922
+ const candidates = [];
923
+ for (const balance of input.balances) {
924
+ if (balance.currentRawAmount === "0" || balance.symbol === input.targetAsset?.symbol) {
925
+ continue;
926
+ }
927
+ if (input.acceptedSourceSet !== undefined && !input.acceptedSourceSet.has(balance.symbol)) {
928
+ candidates.push({
929
+ sourceSymbol: balance.symbol,
930
+ ...(input.targetAsset === undefined ? {} : { targetSymbol: input.targetAsset.symbol }),
931
+ sourceRawAmount: balance.currentRawAmount,
932
+ sourceDisplayAmount: balance.currentDisplayAmount,
933
+ status: "filtered_by_accepted_source_assets",
934
+ reason: "The source asset was not included in acceptedSourceAssetSymbols."
935
+ });
936
+ continue;
937
+ }
938
+ if (input.targetAsset === undefined) {
939
+ candidates.push({
940
+ sourceSymbol: balance.symbol,
941
+ sourceRawAmount: balance.currentRawAmount,
942
+ sourceDisplayAmount: balance.currentDisplayAmount,
943
+ status: "target_asset_not_selected",
944
+ reason: "No target settlement asset was selected, so conversion quotes are not requested."
945
+ });
946
+ continue;
947
+ }
948
+ let directPool;
949
+ try {
950
+ const resolved = resolveDeepbookPoolForSymbols({
951
+ sourceSymbol: balance.symbol,
952
+ targetSymbol: input.targetAsset.symbol
953
+ });
954
+ directPool = { poolKey: resolved.poolKey, direction: resolved.direction };
955
+ }
956
+ catch {
957
+ candidates.push({
958
+ sourceSymbol: balance.symbol,
959
+ targetSymbol: input.targetAsset.symbol,
960
+ sourceRawAmount: balance.currentRawAmount,
961
+ sourceDisplayAmount: balance.currentDisplayAmount,
962
+ status: "no_direct_deepbook_pool",
963
+ reason: "No direct DeepBook mainnet pool exists for this source and target pair."
964
+ });
965
+ continue;
966
+ }
967
+ try {
968
+ const quote = await this.quoteDeepbookAction({
969
+ poolKey: directPool.poolKey,
970
+ direction: directPool.direction,
971
+ amountRaw: balance.currentRawAmount,
972
+ simulationSender: DEFAULT_DEEPBOOK_SIMULATION_SENDER
973
+ });
974
+ candidates.push({
975
+ sourceSymbol: balance.symbol,
976
+ targetSymbol: input.targetAsset.symbol,
977
+ sourceRawAmount: balance.currentRawAmount,
978
+ sourceDisplayAmount: balance.currentDisplayAmount,
979
+ status: "quoted",
980
+ directPool,
981
+ quote,
982
+ boundary: [
983
+ "quote_snapshot_only",
984
+ "not_final_min_out",
985
+ "not_route_recommendation",
986
+ "not_route_dependent_payment_support",
987
+ "not_payment_readiness",
988
+ "not_signing_readiness"
989
+ ]
990
+ });
991
+ }
992
+ catch (error) {
993
+ candidates.push({
994
+ sourceSymbol: balance.symbol,
995
+ targetSymbol: input.targetAsset.symbol,
996
+ sourceRawAmount: balance.currentRawAmount,
997
+ sourceDisplayAmount: balance.currentDisplayAmount,
998
+ status: "quote_unavailable",
999
+ reason: error instanceof Error ? error.message : "DeepBook quote failed for this candidate.",
1000
+ directPool
1001
+ });
1002
+ }
1003
+ }
1004
+ return candidates;
1005
+ }
1006
+ #intentEvidenceRequiredUserChoices(intentKind, targetAsset, candidateConversions) {
1007
+ if (intentKind === "summarize_settlement_asset_group_balance") {
1008
+ return [];
1009
+ }
1010
+ const choices = [];
1011
+ if (targetAsset === undefined) {
1012
+ choices.push("Choose the onchain settlement asset or merchant-accepted USD-denominated asset set before target-specific settlement evidence can be completed.");
1013
+ }
1014
+ if (candidateConversions.some((candidate) => candidate.status === "quoted")) {
1015
+ choices.push("Choose which quoted candidate assets, if any, the user wants to convert.");
1016
+ }
1017
+ return choices;
1018
+ }
1019
+ #parseIntentDisplayAmount(displayAmount, decimals, field) {
1020
+ try {
1021
+ return parseDisplayAmountToRaw(displayAmount, decimals);
1022
+ }
1023
+ catch (error) {
1024
+ throw new ReadServiceInputError("input_invalid", "requiredDisplayAmount must be an unsigned decimal string within verified decimals", {
1025
+ field,
1026
+ value: displayAmount,
1027
+ decimals,
1028
+ reason: error instanceof Error ? error.message : "unknown"
1029
+ });
1030
+ }
1031
+ }
1032
+ #fetchedAt() {
1033
+ return this.#now().toISOString();
1034
+ }
1035
+ async #withUnit(balance) {
1036
+ let normalizedCoinType;
1037
+ try {
1038
+ normalizedCoinType = normalizeCoinType(balance.coinType);
1039
+ }
1040
+ catch {
1041
+ return withUnavailableUnit(balance, "coin_type_unresolved");
1042
+ }
1043
+ const unit = await this.#resolveCoinUnitForNormalizedCoinType(normalizedCoinType);
1044
+ return withResolvedUnit(balance, unit);
1045
+ }
1046
+ async #resolveCoinUnitForNormalizedCoinType(normalizedCoinType) {
1047
+ const now = this.#now();
1048
+ let cached;
1049
+ try {
1050
+ cached = await this.#coinMetadataCache.getCoinMetadata({
1051
+ coinType: normalizedCoinType,
1052
+ chainIdentifier: this.#chainIdentifier,
1053
+ now
1054
+ });
1055
+ }
1056
+ catch (error) {
1057
+ throw new ReadServiceCacheError("read", error);
1058
+ }
1059
+ if (cached.status === "hit") {
1060
+ return unitFromMetadataRecord(cached.record, "hit");
1061
+ }
1062
+ let metadata;
1063
+ try {
1064
+ metadata = await this.#client.core.getCoinMetadata({ coinType: normalizedCoinType });
1065
+ }
1066
+ catch {
1067
+ return unavailableUnit("metadata_lookup_failed");
1068
+ }
1069
+ if (metadata.coinMetadata !== null) {
1070
+ const record = this.#cacheRecordFromMetadata(normalizedCoinType, metadata.coinMetadata, now);
1071
+ try {
1072
+ await this.#coinMetadataCache.setCoinMetadata(record);
1073
+ }
1074
+ catch (error) {
1075
+ throw new ReadServiceCacheError("write", error);
1076
+ }
1077
+ return unitFromMetadataRecord(record, cached.status === "expired" ? "expired_refetched" : "miss");
1078
+ }
1079
+ try {
1080
+ const fallback = deepbookUnitForCoinType(normalizedCoinType, this.#deepbookCoins);
1081
+ if (fallback) {
1082
+ return unitFromDeepbook(fallback);
1083
+ }
1084
+ }
1085
+ catch (error) {
1086
+ if (error instanceof ReadServiceInputError) {
1087
+ return unavailableUnit("no_verified_decimals");
1088
+ }
1089
+ throw error;
1090
+ }
1091
+ return unavailableUnit("metadata_not_found");
1092
+ }
1093
+ async #scanWalletAssetClassificationPages(account) {
1094
+ const classifiedAssets = [];
1095
+ let uninspectedAssetClasses = NOT_INSPECTED_ASSET_CLASSES.map((assetClass) => ({
1096
+ ...assetClass
1097
+ }));
1098
+ let cursor;
1099
+ const requestedCursors = new Set();
1100
+ for (let pageIndex = 0; pageIndex < MAX_WALLET_BALANCE_SCAN_PAGES; pageIndex += 1) {
1101
+ if (cursor !== undefined && cursor !== null) {
1102
+ if (requestedCursors.has(cursor)) {
1103
+ return {
1104
+ classifiedAssets,
1105
+ uninspectedAssetClasses,
1106
+ inspectedBalancePages: pageIndex,
1107
+ inspectedCoinBalanceCount: classifiedAssets.length,
1108
+ blockedReason: "wallet_balance_pagination_did_not_advance"
1109
+ };
1110
+ }
1111
+ requestedCursors.add(cursor);
1112
+ }
1113
+ const page = await this.classifyWalletAssets({ account, ...(cursor === undefined ? {} : { cursor }) });
1114
+ classifiedAssets.push(...page.classifiedAssets);
1115
+ uninspectedAssetClasses = page.uninspectedAssetClasses;
1116
+ if (!page.hasNextPage) {
1117
+ return {
1118
+ classifiedAssets,
1119
+ uninspectedAssetClasses,
1120
+ inspectedBalancePages: pageIndex + 1,
1121
+ inspectedCoinBalanceCount: classifiedAssets.length
1122
+ };
1123
+ }
1124
+ if (typeof page.cursor !== "string" ||
1125
+ page.cursor.length === 0 ||
1126
+ page.cursor === cursor ||
1127
+ requestedCursors.has(page.cursor)) {
1128
+ return {
1129
+ classifiedAssets,
1130
+ uninspectedAssetClasses,
1131
+ inspectedBalancePages: pageIndex + 1,
1132
+ inspectedCoinBalanceCount: classifiedAssets.length,
1133
+ blockedReason: "wallet_balance_pagination_did_not_advance"
1134
+ };
1135
+ }
1136
+ cursor = page.cursor;
1137
+ }
1138
+ return {
1139
+ classifiedAssets,
1140
+ uninspectedAssetClasses,
1141
+ inspectedBalancePages: MAX_WALLET_BALANCE_SCAN_PAGES,
1142
+ inspectedCoinBalanceCount: classifiedAssets.length,
1143
+ blockedReason: "wallet_balance_page_limit_exceeded"
1144
+ };
1145
+ }
1146
+ #cacheRecordFromMetadata(coinType, metadata, now) {
1147
+ const decimals = assertValidDecimals(metadata.decimals);
1148
+ return {
1149
+ coinType,
1150
+ chainIdentifier: this.#chainIdentifier,
1151
+ decimals,
1152
+ symbol: metadata.symbol,
1153
+ name: metadata.name,
1154
+ fetchedAt: now.toISOString(),
1155
+ expiresAt: new Date(now.getTime() + this.#coinMetadataTtlMs).toISOString()
1156
+ };
1157
+ }
1158
+ listDeepbookTokenRegistry() {
1159
+ return listDeepbookTokenRegistry(this.#deepbookCoins);
1160
+ }
1161
+ }
1162
+ export function createSuiReadService(options) {
1163
+ return new SuiReadService(options);
1164
+ }