@distrohelena/canton-explorer 0.1.3 → 0.1.5

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 (31) hide show
  1. package/dist/public/assets/DebuggerView-VieEr0b3.js +2 -0
  2. package/dist/public/assets/index-BmT5Hrud.js +1 -0
  3. package/dist/public/assets/index-DnEheBxd.css +1 -0
  4. package/dist/public/assets/{editor.api-8w-mShFJ.js → monaco-runtime-DiIHw4v2.js} +1 -1
  5. package/dist/public/index.html +2 -2
  6. package/dist/src/api/nodes.controller.d.ts +2 -2
  7. package/dist/src/api/nodes.controller.js +20 -8
  8. package/dist/src/api/nodes.controller.js.map +1 -1
  9. package/dist/src/cache/node-cache.service.js +23 -9
  10. package/dist/src/cache/node-cache.service.js.map +1 -1
  11. package/dist/src/debugger/debugger.service.d.ts +1 -0
  12. package/dist/src/debugger/debugger.service.js +9 -0
  13. package/dist/src/debugger/debugger.service.js.map +1 -1
  14. package/dist/src/domain/node-health.js +1 -1
  15. package/dist/src/domain/node-health.js.map +1 -1
  16. package/dist/src/domain/node.types.d.ts +2 -0
  17. package/dist/src/grpc/daml-lf-archive.d.ts +2 -0
  18. package/dist/src/grpc/daml-lf-archive.js +104 -0
  19. package/dist/src/grpc/daml-lf-archive.js.map +1 -0
  20. package/dist/src/grpc/grpc-operations.service.d.ts +3 -0
  21. package/dist/src/grpc/grpc-operations.service.js +80 -28
  22. package/dist/src/grpc/grpc-operations.service.js.map +1 -1
  23. package/dist/src/pqs/pqs-summary.service.d.ts +21 -1
  24. package/dist/src/pqs/pqs-summary.service.js +492 -93
  25. package/dist/src/pqs/pqs-summary.service.js.map +1 -1
  26. package/dist/tsconfig.build.tsbuildinfo +1 -1
  27. package/package.json +1 -1
  28. package/dist/public/assets/DebuggerView-DnuybIZF.js +0 -2
  29. package/dist/public/assets/index-Yd5H2RaX.js +0 -1
  30. package/dist/public/assets/index-fkZAWLbW.css +0 -1
  31. /package/dist/public/assets/{editor-C5uazxST.css → monaco-runtime-C5uazxST.css} +0 -0
@@ -28,6 +28,13 @@ const CANTON_COIN_TOKEN_ID = 'canton-coin';
28
28
  const CANTON_COIN_TOKEN_NAME = 'Canton Coin';
29
29
  const CANTON_COIN_TRANSFER_TEMPLATE_ID = 'Splice.AmuletTransferInstruction:AmuletTransferInstruction';
30
30
  const CANTON_COIN_AMULET_TEMPLATE_ID = 'Splice.Amulet:Amulet';
31
+ const AMULET_RULES_TEMPLATE_ID = 'Splice.AmuletRules:AmuletRules';
32
+ const NATIVE_AMULET_SUPPORT_TEMPLATE_IDS = [
33
+ CANTON_COIN_AMULET_TEMPLATE_ID,
34
+ AMULET_RULES_TEMPLATE_ID,
35
+ 'Splice.Amulet:ValidatorRight',
36
+ ];
37
+ const NATIVE_AMULET_INTRINSIC_ID = 'Amulet';
31
38
  const CIP56_HOLDING_TEMPLATE_ID = 'Splice.Api.Token.HoldingV1:Holding';
32
39
  const CIP56_TRANSFER_TEMPLATE_ID = 'Splice.Api.Token.TransferInstructionV1:Transfer';
33
40
  const CIP112_TEMPLATE_ID_LIKE_PATTERN = '%.CIP112:%';
@@ -855,13 +862,14 @@ function singleUpdateQuery(node, eventOffset) {
855
862
  limit 1
856
863
  `;
857
864
  }
858
- function updateEventsQuery(node, updateId) {
865
+ function updateEventsByUpdateIdsQuery(node, updateIds) {
859
866
  const relations = pqsCoreRelations(node);
860
- const quotedId = `'${escapeSqlLiteral(updateId)}'`;
867
+ const quotedIds = updateIds.map((updateId) => `'${escapeSqlLiteral(updateId)}'`).join(', ');
861
868
  const contractTemplateId = contractTemplateIdentifierExpression('contract_tpe_row');
862
869
  const exerciseTemplateId = contractTemplateIdentifierExpression('exercise_contract_tpe_row');
863
870
  return `
864
871
  select
872
+ update_id,
865
873
  event_kind,
866
874
  event_id,
867
875
  contract_id,
@@ -875,6 +883,7 @@ function updateEventsQuery(node, updateId) {
875
883
  raw
876
884
  from (
877
885
  select
886
+ tx.transaction_id::text as update_id,
878
887
  'create'::text as event_kind,
879
888
  event_row.event_id::text as event_id,
880
889
  contract_row.contract_id::text as contract_id,
@@ -902,11 +911,12 @@ function updateEventsQuery(node, updateId) {
902
911
  on contract_tpe_row.pk = contract_row.tpe_pk
903
912
  left join ${relations.packages} package_row
904
913
  on package_row.pk = contract_row.package_pk
905
- where tx.transaction_id = ${quotedId}
914
+ where tx.transaction_id in (${quotedIds})
906
915
 
907
916
  union all
908
917
 
909
918
  select
919
+ tx.transaction_id::text as update_id,
910
920
  case
911
921
  when exercise_tpe_row.consuming then 'consuming_exercise'::text
912
922
  else 'non_consuming_exercise'::text
@@ -940,11 +950,14 @@ function updateEventsQuery(node, updateId) {
940
950
  on exercise_tpe_row.pk = exercise_row.tpe_pk
941
951
  left join ${relations.packages} package_row
942
952
  on package_row.pk = exercise_row.package_pk
943
- where tx.transaction_id = ${quotedId}
953
+ where tx.transaction_id in (${quotedIds})
944
954
  ) update_events
945
- order by event_id asc nulls last, event_kind asc, contract_id asc, template_id asc
955
+ order by update_id asc nulls last, event_id asc nulls last, event_kind asc, contract_id asc, template_id asc
946
956
  `;
947
957
  }
958
+ function updateEventsQuery(node, updateId) {
959
+ return updateEventsByUpdateIdsQuery(node, [updateId]);
960
+ }
948
961
  function rewardCouponInstanceQuery(node, updateId) {
949
962
  const relations = pqsCoreRelations(node);
950
963
  const quotedId = `'${escapeSqlLiteral(normalizeByteaHex(updateId))}'`;
@@ -970,9 +983,11 @@ function rewardCouponInstanceQuery(node, updateId) {
970
983
  limit 1
971
984
  `;
972
985
  }
973
- function contractDetailQuery(node, contractId) {
986
+ function contractDetailsQuery(node, contractIds) {
974
987
  const relations = pqsCoreRelations(node);
975
- const quotedId = `'${escapeSqlLiteral(normalizeByteaHex(contractId))}'`;
988
+ const quotedIds = contractIds
989
+ .map((contractId) => `'${escapeSqlLiteral(normalizeByteaHex(contractId))}'`)
990
+ .join(', ');
976
991
  const contractTemplateId = contractTemplateIdentifierExpression('contract_tpe_row');
977
992
  return `
978
993
  with selected_contract as (
@@ -988,8 +1003,7 @@ function contractDetailQuery(node, contractId) {
988
1003
  on contract_tpe_row.pk = contract_row.tpe_pk
989
1004
  left join ${relations.packages} package_row
990
1005
  on package_row.pk = contract_row.package_pk
991
- where contract_row.contract_id = ${quotedId}
992
- limit 1
1006
+ where contract_row.contract_id in (${quotedIds})
993
1007
  )
994
1008
  select
995
1009
  contract_row.contract_id,
@@ -1009,6 +1023,9 @@ function contractDetailQuery(node, contractId) {
1009
1023
  on archived_tx.ix = contract_row.archived_at_ix
1010
1024
  `;
1011
1025
  }
1026
+ function contractDetailQuery(node, contractId) {
1027
+ return contractDetailsQuery(node, [contractId]);
1028
+ }
1012
1029
  function tokenRowsQuery(node, limit, templateIds, templatePatterns = []) {
1013
1030
  const relations = pqsCoreRelations(node);
1014
1031
  const normalizedLimit = Number.isFinite(limit) && Number(limit) > 0 ? Math.trunc(limit) : TOKEN_TRANSFER_CACHE_LIMIT;
@@ -1054,6 +1071,21 @@ function tokenRowsQuery(node, limit, templateIds, templatePatterns = []) {
1054
1071
  limit ${normalizedLimit}
1055
1072
  `;
1056
1073
  }
1074
+ function nativeAmuletSupportQuery(node) {
1075
+ const relations = pqsCoreRelations(node);
1076
+ const templateIds = NATIVE_AMULET_SUPPORT_TEMPLATE_IDS
1077
+ .map((templateId) => `'${escapeSqlLiteral(templateId)}'`)
1078
+ .join(',\n ');
1079
+ const templateId = contractTemplateIdentifierExpression('contract_tpe_row');
1080
+ return `
1081
+ select ${templateId} as template_id
1082
+ from ${relations.contractTpe} contract_tpe_row
1083
+ where ${templateId} in (
1084
+ ${templateIds}
1085
+ )
1086
+ limit 1
1087
+ `;
1088
+ }
1057
1089
  function recentCip112MovementUpdateIdsQuery(node, limit) {
1058
1090
  const relations = pqsCoreRelations(node);
1059
1091
  const normalizedLimit = Number.isFinite(limit) && Number(limit) > 0 ? Math.trunc(limit) : TOKEN_TRANSFER_CACHE_LIMIT;
@@ -1205,12 +1237,19 @@ let PqsSummaryService = class PqsSummaryService {
1205
1237
  const rawMetaRows = await this.queryRecentUpdateMetaRows(node, query, normalizedLimit, before, after, parties, templates, partyMode, hideSplice);
1206
1238
  const hasMoreInQuery = rawMetaRows.length > normalizedLimit;
1207
1239
  const trimmedMetaRows = rawMetaRows.slice(0, normalizedLimit);
1208
- const orderedUpdates = (useAfterCursor ? [...trimmedMetaRows].reverse() : trimmedMetaRows).map((row) => ({
1209
- eventOffset: this.extractEventOffset(row),
1210
- rawUpdateId: row.update_id,
1211
- updateId: this.normalizeUpdateId(row.update_id),
1212
- recordTime: row.record_time ?? null,
1213
- }));
1240
+ const orderedUpdates = (useAfterCursor ? [...trimmedMetaRows].reverse() : trimmedMetaRows).flatMap((row) => {
1241
+ if (typeof row.update_id !== 'string') {
1242
+ return [];
1243
+ }
1244
+ return [
1245
+ {
1246
+ eventOffset: this.extractEventOffset(row),
1247
+ rawUpdateId: row.update_id,
1248
+ updateId: this.normalizeUpdateId(row.update_id),
1249
+ recordTime: row.record_time ?? null,
1250
+ },
1251
+ ];
1252
+ });
1214
1253
  if (orderedUpdates.length === 0) {
1215
1254
  return {
1216
1255
  nodeId: node.id,
@@ -1276,7 +1315,7 @@ let PqsSummaryService = class PqsSummaryService {
1276
1315
  if (nodesToFetch.length === 0) {
1277
1316
  break;
1278
1317
  }
1279
- const responses = await Promise.all(nodesToFetch.map((state) => this.fetchRecentUpdates(state.node, {
1318
+ const settled = await Promise.allSettled(nodesToFetch.map((state) => this.fetchRecentUpdates(state.node, {
1280
1319
  limit: pageSize,
1281
1320
  parties,
1282
1321
  templates,
@@ -1284,8 +1323,25 @@ let PqsSummaryService = class PqsSummaryService {
1284
1323
  hideSplice,
1285
1324
  before: state.nextBefore,
1286
1325
  })));
1287
- responses.forEach((response, index) => {
1326
+ const successfulResponses = [];
1327
+ settled.forEach((result, index) => {
1288
1328
  const state = nodesToFetch[index];
1329
+ if (!state) {
1330
+ return;
1331
+ }
1332
+ if (result.status !== 'fulfilled') {
1333
+ state.exhausted = true;
1334
+ return;
1335
+ }
1336
+ successfulResponses.push({
1337
+ state,
1338
+ response: result.value,
1339
+ });
1340
+ });
1341
+ if (successfulResponses.length === 0) {
1342
+ throw settled.find((result) => result.status === 'rejected')?.reason;
1343
+ }
1344
+ successfulResponses.forEach(({ response, state }) => {
1289
1345
  for (const update of response.updates) {
1290
1346
  mergedUpdatesByKey.set(`${state.node.id}:${update.eventOffset}:${update.updateId}`, {
1291
1347
  nodeId: state.node.id,
@@ -1398,12 +1454,17 @@ let PqsSummaryService = class PqsSummaryService {
1398
1454
  }
1399
1455
  }
1400
1456
  const rawRows = Array.from(dedupedRows.values());
1401
- const rawUpdateIds = rawRows.map((row) => row.update_id);
1457
+ const rawUpdateIds = rawRows
1458
+ .map((row) => row.update_id)
1459
+ .filter((updateId) => typeof updateId === 'string');
1402
1460
  const partiesByUpdateId = rawUpdateIds.length > 0
1403
1461
  ? await this.fetchPartiesByUpdateId(node, client.query.bind(client), rawUpdateIds)
1404
1462
  : new Map();
1405
1463
  return rawRows
1406
1464
  .map((row) => {
1465
+ if (typeof row.update_id !== 'string') {
1466
+ return null;
1467
+ }
1407
1468
  const normalizedUpdateId = this.normalizeUpdateId(row.update_id);
1408
1469
  const eventOffset = this.extractEventOffset(row);
1409
1470
  const exact = this.isExactMatch(query, [eventOffset, normalizedUpdateId]);
@@ -1704,29 +1765,52 @@ let PqsSummaryService = class PqsSummaryService {
1704
1765
  }
1705
1766
  async fetchActiveParties(nodes) {
1706
1767
  return {
1707
- nodes: await Promise.all(nodes.map(async (node) => ({
1768
+ nodes: await Promise.all(nodes.map(async (node) => this.buildActivePartiesEntry(node))),
1769
+ };
1770
+ }
1771
+ async buildActivePartiesEntry(node) {
1772
+ try {
1773
+ return {
1708
1774
  nodeId: node.id,
1709
1775
  label: node.label,
1710
1776
  mode: node.mode,
1711
1777
  parties: await this.fetchActivePartiesForNode(node),
1712
- }))),
1713
- };
1778
+ activePartiesStatus: 'ok',
1779
+ activePartiesError: null,
1780
+ };
1781
+ }
1782
+ catch (error) {
1783
+ return {
1784
+ nodeId: node.id,
1785
+ label: node.label,
1786
+ mode: node.mode,
1787
+ parties: [],
1788
+ activePartiesStatus: 'pqs_error',
1789
+ activePartiesError: error instanceof Error ? error.message : 'Unknown error',
1790
+ };
1791
+ }
1714
1792
  }
1715
1793
  async fetchPartyDetail(nodes, partyId) {
1716
1794
  const normalizedPartyId = this.normalizePartyIdentifier(partyId);
1717
1795
  const grpcOperationsService = this.grpcOperationsService;
1718
- const activePartiesByNode = await Promise.all(nodes.map(async (node) => ({
1796
+ const activePartiesByNode = (await Promise.allSettled(nodes.map(async (node) => ({
1719
1797
  node,
1720
1798
  parties: await this.fetchActivePartiesForNode(node),
1721
- })));
1722
- const recentUpdatesByNode = await Promise.all(nodes.map(async (node) => ({
1799
+ }))))
1800
+ .filter((result) => result.status === 'fulfilled')
1801
+ .map((result) => result.value);
1802
+ const recentUpdatesByNode = (await Promise.allSettled(nodes.map(async (node) => ({
1723
1803
  node,
1724
1804
  updates: await this.fetchPartyRecentUpdatesForNode(node, normalizedPartyId, 10),
1725
- })));
1726
- const recentContractsByNode = await Promise.all(nodes.map(async (node) => ({
1805
+ }))))
1806
+ .filter((result) => result.status === 'fulfilled')
1807
+ .map((result) => result.value);
1808
+ const recentContractsByNode = (await Promise.allSettled(nodes.map(async (node) => ({
1727
1809
  node,
1728
1810
  contracts: await this.fetchPartyRecentContractsForNode(node, normalizedPartyId, 10),
1729
- })));
1811
+ }))))
1812
+ .filter((result) => result.status === 'fulfilled')
1813
+ .map((result) => result.value);
1730
1814
  const localPartiesByNode = grpcOperationsService
1731
1815
  ? await Promise.all(nodes.map(async (node) => {
1732
1816
  try {
@@ -1834,10 +1918,12 @@ let PqsSummaryService = class PqsSummaryService {
1834
1918
  async fetchNamespaceDetail(nodes, namespaceId) {
1835
1919
  const normalizedNamespaceId = namespaceId.trim();
1836
1920
  const grpcOperationsService = this.grpcOperationsService;
1837
- const activePartiesByNode = await Promise.all(nodes.map(async (node) => ({
1921
+ const activePartiesByNode = (await Promise.allSettled(nodes.map(async (node) => ({
1838
1922
  node,
1839
1923
  parties: (await this.fetchActivePartiesForNode(node)).filter((partyId) => this.extractNamespaceIdentifier(partyId) === normalizedNamespaceId),
1840
- })));
1924
+ }))))
1925
+ .filter((result) => result.status === 'fulfilled')
1926
+ .map((result) => result.value);
1841
1927
  const localPartiesByNode = grpcOperationsService
1842
1928
  ? await Promise.all(nodes.map(async (node) => {
1843
1929
  try {
@@ -1930,7 +2016,9 @@ let PqsSummaryService = class PqsSummaryService {
1930
2016
  async fetchNamespaceParties(nodes, namespaceId, options) {
1931
2017
  const normalizedNamespaceId = namespaceId.trim();
1932
2018
  const matchingParties = new Set();
1933
- const activePartiesByNode = await Promise.all(nodes.map(async (node) => (await this.fetchActivePartiesForNode(node)).filter((partyId) => this.extractNamespaceIdentifier(partyId) === normalizedNamespaceId)));
2019
+ const activePartiesByNode = (await Promise.allSettled(nodes.map(async (node) => (await this.fetchActivePartiesForNode(node)).filter((partyId) => this.extractNamespaceIdentifier(partyId) === normalizedNamespaceId))))
2020
+ .filter((result) => result.status === 'fulfilled')
2021
+ .map((result) => result.value);
1934
2022
  for (const parties of activePartiesByNode) {
1935
2023
  for (const partyId of parties) {
1936
2024
  matchingParties.add(partyId);
@@ -2026,7 +2114,7 @@ let PqsSummaryService = class PqsSummaryService {
2026
2114
  if (nodesToFetch.length === 0) {
2027
2115
  break;
2028
2116
  }
2029
- const responses = await Promise.all(nodesToFetch.map((state) => this.fetchNodeContracts(state.node, {
2117
+ const settled = await Promise.allSettled(nodesToFetch.map((state) => this.fetchNodeContracts(state.node, {
2030
2118
  limit: pageSize,
2031
2119
  before: state.nextBefore,
2032
2120
  parties: activePartyFilters.length > 0 ? activePartyFilters : undefined,
@@ -2034,8 +2122,25 @@ let PqsSummaryService = class PqsSummaryService {
2034
2122
  partyMode,
2035
2123
  hideSplice,
2036
2124
  })));
2037
- responses.forEach((response, index) => {
2125
+ const successfulResponses = [];
2126
+ settled.forEach((result, index) => {
2038
2127
  const state = nodesToFetch[index];
2128
+ if (!state) {
2129
+ return;
2130
+ }
2131
+ if (result.status !== 'fulfilled') {
2132
+ state.exhausted = true;
2133
+ return;
2134
+ }
2135
+ successfulResponses.push({
2136
+ state,
2137
+ response: result.value,
2138
+ });
2139
+ });
2140
+ if (successfulResponses.length === 0) {
2141
+ throw settled.find((result) => result.status === 'rejected')?.reason;
2142
+ }
2143
+ successfulResponses.forEach(({ response, state }) => {
2039
2144
  for (const contract of response.contracts) {
2040
2145
  mergedContractsByKey.set(`${state.node.id}:${contract.contractId}`, {
2041
2146
  nodeId: state.node.id,
@@ -2116,12 +2221,29 @@ let PqsSummaryService = class PqsSummaryService {
2116
2221
  if (nodesToFetch.length === 0) {
2117
2222
  break;
2118
2223
  }
2119
- const responses = await Promise.all(nodesToFetch.map((state) => this.fetchPartyContractsForNode(state.node, normalizedPartyId, {
2224
+ const settled = await Promise.allSettled(nodesToFetch.map((state) => this.fetchPartyContractsForNode(state.node, normalizedPartyId, {
2120
2225
  limit: pageSize,
2121
2226
  before: state.nextBefore,
2122
2227
  })));
2123
- responses.forEach((response, index) => {
2228
+ const successfulResponses = [];
2229
+ settled.forEach((result, index) => {
2124
2230
  const state = nodesToFetch[index];
2231
+ if (!state) {
2232
+ return;
2233
+ }
2234
+ if (result.status !== 'fulfilled') {
2235
+ state.exhausted = true;
2236
+ return;
2237
+ }
2238
+ successfulResponses.push({
2239
+ state,
2240
+ response: result.value,
2241
+ });
2242
+ });
2243
+ if (successfulResponses.length === 0) {
2244
+ throw settled.find((result) => result.status === 'rejected')?.reason;
2245
+ }
2246
+ successfulResponses.forEach(({ response, state }) => {
2125
2247
  for (const contract of response.contracts) {
2126
2248
  mergedContractsByKey.set(`${contract.nodeId}:${contract.contractId}`, contract);
2127
2249
  }
@@ -2177,18 +2299,18 @@ let PqsSummaryService = class PqsSummaryService {
2177
2299
  return this.paginateTokens(filteredTokens.sort(compareGlobalTokens), limit, options);
2178
2300
  }
2179
2301
  async fetchLatestTokenTransfers(nodes, limit = 25, options) {
2180
- const mergedTransfers = this.filterTokenTransfersByParties(await this.loadMergedTokenTransfers(nodes), options);
2302
+ const mergedTransfers = this.filterTokenTransfers(await this.loadMergedTokenTransfers(nodes), options);
2181
2303
  return this.paginateTokenTransfers(mergedTransfers, limit, options);
2182
2304
  }
2183
2305
  async fetchTokenTransfers(nodes, tokenId, limit = 25, options) {
2184
2306
  const normalizedTokenId = this.normalizeTokenId(tokenId);
2185
2307
  await this.findObservedToken(nodes, normalizedTokenId);
2186
- const mergedTransfers = this.filterTokenTransfersByParties(await this.loadMergedTokenTransfers(nodes), options);
2308
+ const mergedTransfers = this.filterTokenTransfers(await this.loadMergedTokenTransfers(nodes), options);
2187
2309
  const directTransfers = mergedTransfers.filter((transfer) => transfer.tokenId === normalizedTokenId);
2188
2310
  if (directTransfers.length > 0) {
2189
2311
  return this.paginateTokenTransfers(directTransfers, limit, options);
2190
2312
  }
2191
- const recoveredTransfers = this.filterTokenTransfersByParties(await this.recoverTokenTransfersFromActiveHolders(nodes, normalizedTokenId), options);
2313
+ const recoveredTransfers = this.filterTokenTransfers(await this.recoverTokenTransfersFromActiveHolders(nodes, normalizedTokenId), options);
2192
2314
  return this.paginateTokenTransfers(recoveredTransfers, limit, options);
2193
2315
  }
2194
2316
  async fetchTokenTransferDetail(nodes, transferId) {
@@ -2328,10 +2450,11 @@ let PqsSummaryService = class PqsSummaryService {
2328
2450
  node,
2329
2451
  transfers: await this.loadCachedTokenTransfers(node),
2330
2452
  })));
2331
- const successfulTransfers = refreshResults
2453
+ const successfulRefreshes = refreshResults.filter((result) => result.status === 'fulfilled');
2454
+ const successfulTransfers = successfulRefreshes
2332
2455
  .filter((result) => result.status === 'fulfilled')
2333
2456
  .flatMap((result) => result.value.transfers);
2334
- if (successfulTransfers.length === 0) {
2457
+ if (successfulRefreshes.length === 0) {
2335
2458
  const firstFailure = refreshResults.find((result) => result.status === 'rejected');
2336
2459
  if (firstFailure) {
2337
2460
  throw firstFailure.reason;
@@ -2351,9 +2474,9 @@ let PqsSummaryService = class PqsSummaryService {
2351
2474
  .filter((holder) => holder.tokenId === tokenId && holder.contractId !== null);
2352
2475
  const recoveredTransfers = [];
2353
2476
  const seenContracts = new Set();
2477
+ const contractIdsByNode = new Map();
2354
2478
  for (const holder of candidateHolders) {
2355
- const node = nodeById.get(holder.nodeId);
2356
- if (!node || !holder.contractId) {
2479
+ if (!nodeById.has(holder.nodeId) || !holder.contractId) {
2357
2480
  continue;
2358
2481
  }
2359
2482
  const dedupeKey = `${holder.nodeId}\u0000${holder.contractId}`;
@@ -2361,19 +2484,38 @@ let PqsSummaryService = class PqsSummaryService {
2361
2484
  continue;
2362
2485
  }
2363
2486
  seenContracts.add(dedupeKey);
2364
- const client = this.clientFactory.getClient(node);
2365
- const contractResult = await client.query(contractDetailQuery(node, holder.contractId));
2366
- const contractRow = contractResult.rows[0];
2367
- if (!contractRow || typeof contractRow.created_update_id !== 'string') {
2487
+ const nodeContractIds = contractIdsByNode.get(holder.nodeId);
2488
+ if (nodeContractIds) {
2489
+ nodeContractIds.push(holder.contractId);
2490
+ }
2491
+ else {
2492
+ contractIdsByNode.set(holder.nodeId, [holder.contractId]);
2493
+ }
2494
+ }
2495
+ for (const [nodeId, contractIds] of contractIdsByNode) {
2496
+ const node = nodeById.get(nodeId);
2497
+ if (!node) {
2368
2498
  continue;
2369
2499
  }
2370
- const events = await this.fetchEventsByUpdateId(node, client.query.bind(client), contractRow.created_update_id);
2371
- const inferredTransfers = this.inferCip112TokenMovements(node, {
2372
- update_id: contractRow.created_update_id,
2373
- event_offset: contractRow.created_event_offset,
2374
- record_time: contractRow.created_record_time,
2375
- }, events).filter((transfer) => transfer.tokenId === tokenId);
2376
- recoveredTransfers.push(...inferredTransfers);
2500
+ const client = this.clientFactory.getClient(node);
2501
+ const contractRows = await this.fetchContractDetailsByIds(node, client.query.bind(client), contractIds);
2502
+ const eventsByUpdateId = await this.fetchEventsByUpdateIds(node, client.query.bind(client), contractRows
2503
+ .map((contractRow) => contractRow.created_update_id)
2504
+ .filter((updateId) => typeof updateId === 'string'));
2505
+ for (const contractRow of contractRows) {
2506
+ if (typeof contractRow.created_update_id !== 'string') {
2507
+ continue;
2508
+ }
2509
+ const events = eventsByUpdateId.get(contractRow.created_update_id) ?? [];
2510
+ const inferredTransfers = this.extractCip112TokenMovements(node, {
2511
+ update_id: contractRow.created_update_id,
2512
+ event_offset: contractRow.created_event_offset,
2513
+ record_time: contractRow.created_record_time,
2514
+ }, events, {
2515
+ canonicalShareTokenId: tokenId,
2516
+ }).filter((transfer) => transfer.tokenId === tokenId);
2517
+ recoveredTransfers.push(...inferredTransfers);
2518
+ }
2377
2519
  }
2378
2520
  return this.mergeTokenTransfers(recoveredTransfers);
2379
2521
  }
@@ -2412,13 +2554,15 @@ let PqsSummaryService = class PqsSummaryService {
2412
2554
  transfers: pagedTransfers,
2413
2555
  };
2414
2556
  }
2415
- filterTokenTransfersByParties(transfers, options) {
2557
+ filterTokenTransfers(transfers, options) {
2416
2558
  const activeFromParties = normalizePartyFilters(options?.fromParties);
2417
2559
  const activeToParties = normalizePartyFilters(options?.toParties);
2560
+ const activeMovementTypes = this.normalizeTokenTransferMovementTypeFilters(options?.movementTypes);
2418
2561
  const amountGt = options?.amountGt?.trim() || null;
2419
2562
  const amountLt = options?.amountLt?.trim() || null;
2420
2563
  if (activeFromParties.length === 0 &&
2421
2564
  activeToParties.length === 0 &&
2565
+ activeMovementTypes.length === 0 &&
2422
2566
  amountGt === null &&
2423
2567
  amountLt === null) {
2424
2568
  return transfers;
@@ -2428,11 +2572,13 @@ let PqsSummaryService = class PqsSummaryService {
2428
2572
  (transfer.sender !== null && activeFromParties.includes(transfer.sender));
2429
2573
  const matchesTo = activeToParties.length === 0 ||
2430
2574
  (transfer.receiver !== null && activeToParties.includes(transfer.receiver));
2575
+ const matchesMovementType = activeMovementTypes.length === 0 ||
2576
+ activeMovementTypes.includes(this.effectiveTokenTransferMovementType(transfer).toLowerCase());
2431
2577
  const matchesAmountGt = amountGt === null ||
2432
2578
  (transfer.amount !== null && this.compareNumericStrings(transfer.amount, amountGt) > 0);
2433
2579
  const matchesAmountLt = amountLt === null ||
2434
2580
  (transfer.amount !== null && this.compareNumericStrings(transfer.amount, amountLt) < 0);
2435
- return matchesFrom && matchesTo && matchesAmountGt && matchesAmountLt;
2581
+ return matchesFrom && matchesTo && matchesMovementType && matchesAmountGt && matchesAmountLt;
2436
2582
  });
2437
2583
  }
2438
2584
  async fetchUpdateDetail(node, eventOffset) {
@@ -2493,20 +2639,27 @@ let PqsSummaryService = class PqsSummaryService {
2493
2639
  const client = this.clientFactory.getClient(node);
2494
2640
  const result = await client.query(pqsPartyRecentUpdatesQuery(node, partyId, limit));
2495
2641
  const rows = result.rows ?? [];
2496
- const rawUpdateIds = rows.map((row) => row.update_id);
2642
+ const rawUpdateIds = rows
2643
+ .map((row) => row.update_id)
2644
+ .filter((updateId) => typeof updateId === 'string');
2497
2645
  const partiesByUpdateId = rawUpdateIds.length > 0
2498
2646
  ? await this.fetchPartiesByUpdateId(node, client.query.bind(client), rawUpdateIds)
2499
2647
  : new Map();
2500
- return rows.map((row) => {
2648
+ return rows.flatMap((row) => {
2649
+ if (typeof row.update_id !== 'string') {
2650
+ return [];
2651
+ }
2501
2652
  const updateId = this.normalizeUpdateId(row.update_id);
2502
- return {
2503
- nodeId: node.id,
2504
- label: node.label,
2505
- eventOffset: this.extractEventOffset(row),
2506
- updateId,
2507
- recordTime: typeof row.record_time === 'string' ? row.record_time : null,
2508
- parties: partiesByUpdateId.get(updateId) ?? [],
2509
- };
2653
+ return [
2654
+ {
2655
+ nodeId: node.id,
2656
+ label: node.label,
2657
+ eventOffset: this.extractEventOffset(row),
2658
+ updateId,
2659
+ recordTime: typeof row.record_time === 'string' ? row.record_time : null,
2660
+ parties: partiesByUpdateId.get(updateId) ?? [],
2661
+ },
2662
+ ];
2510
2663
  });
2511
2664
  }
2512
2665
  async fetchPartyRecentContractsForNode(node, partyId, limit) {
@@ -2586,16 +2739,15 @@ let PqsSummaryService = class PqsSummaryService {
2586
2739
  return cached.tokens;
2587
2740
  }
2588
2741
  const useGrpcHoldingViews = node.mode === 'pqs_with_grpc' && this.grpcOperationsService !== undefined;
2589
- const [pqsTokens, grpcTokens] = await Promise.all([
2590
- this.fetchObservedTokensForNode(node, TOKEN_TRANSFER_CACHE_LIMIT, {
2591
- includeCip112: !useGrpcHoldingViews,
2592
- }),
2742
+ const [pqsTokens, grpcTokens, builtinTokens] = await Promise.all([
2743
+ this.fetchObservedTokensForNode(node, TOKEN_TRANSFER_CACHE_LIMIT),
2593
2744
  useGrpcHoldingViews
2594
2745
  ? this.grpcOperationsService.fetchHoldingV2Tokens(node)
2595
2746
  : Promise.resolve([]),
2747
+ this.fetchBuiltinTokensForNode(node),
2596
2748
  ]);
2597
2749
  const dedupedTokens = new Map();
2598
- for (const token of [...pqsTokens, ...grpcTokens]) {
2750
+ for (const token of [...pqsTokens, ...grpcTokens, ...builtinTokens]) {
2599
2751
  if (!dedupedTokens.has(token.tokenId)) {
2600
2752
  dedupedTokens.set(token.tokenId, token);
2601
2753
  }
@@ -2607,6 +2759,31 @@ let PqsSummaryService = class PqsSummaryService {
2607
2759
  });
2608
2760
  return tokens;
2609
2761
  }
2762
+ async fetchBuiltinTokensForNode(node) {
2763
+ const client = this.clientFactory.getClient(node);
2764
+ const result = await client.query(nativeAmuletSupportQuery(node));
2765
+ const rows = Array.isArray(result.rows) ? result.rows : [];
2766
+ const hasNativeAmuletSupport = rows.some((row) => {
2767
+ if (!row || typeof row !== 'object') {
2768
+ return false;
2769
+ }
2770
+ const templateId = row.template_id;
2771
+ return (typeof templateId === 'string' &&
2772
+ NATIVE_AMULET_SUPPORT_TEMPLATE_IDS.includes(this.normalizeTemplateIdentifier(templateId)));
2773
+ });
2774
+ if (!hasNativeAmuletSupport) {
2775
+ return [];
2776
+ }
2777
+ return [
2778
+ {
2779
+ tokenId: CANTON_COIN_TOKEN_ID,
2780
+ name: CANTON_COIN_TOKEN_NAME,
2781
+ symbol: null,
2782
+ issuer: null,
2783
+ source: 'pqs',
2784
+ },
2785
+ ];
2786
+ }
2610
2787
  async fetchObservedTokensForNode(node, limit, options) {
2611
2788
  const client = this.clientFactory.getClient(node);
2612
2789
  const includeCip112 = options?.includeCip112 ?? true;
@@ -2792,17 +2969,93 @@ let PqsSummaryService = class PqsSummaryService {
2792
2969
  const result = await client.query(recentCip112MovementUpdateIdsQuery(node, limit));
2793
2970
  const rows = result.rows ?? [];
2794
2971
  const transfers = [];
2972
+ const rawUpdateIds = rows
2973
+ .map((row) => (typeof row.update_id === 'string' ? row.update_id : null))
2974
+ .filter((updateId) => Boolean(updateId && this.isHexTokenMovementUpdateId(updateId)));
2975
+ const eventsByUpdateId = await this.fetchEventsByUpdateIds(node, client.query.bind(client), rawUpdateIds);
2795
2976
  for (const row of rows) {
2796
2977
  const rawUpdateId = typeof row.update_id === 'string' ? row.update_id : null;
2797
2978
  if (!rawUpdateId || !this.isHexTokenMovementUpdateId(rawUpdateId)) {
2798
2979
  continue;
2799
2980
  }
2800
- const events = await this.fetchEventsByUpdateId(node, client.query.bind(client), rawUpdateId);
2801
- transfers.push(...this.inferCip112TokenMovements(node, row, events));
2981
+ const events = eventsByUpdateId.get(rawUpdateId) ?? [];
2982
+ transfers.push(...this.extractCip112TokenMovements(node, row, events));
2983
+ }
2984
+ return transfers;
2985
+ }
2986
+ extractCip112TokenMovements(node, update, events, options) {
2987
+ const standardTransfers = this.extractStandardCip112TokenMovements(node, update, events, options);
2988
+ if (standardTransfers.length > 0) {
2989
+ return standardTransfers;
2990
+ }
2991
+ return this.inferCip112TokenMovements(node, update, events, options);
2992
+ }
2993
+ extractStandardCip112TokenMovements(node, update, events, options) {
2994
+ const createTokensById = new Map();
2995
+ for (const event of events) {
2996
+ if (event.eventKind !== 'create' ||
2997
+ !this.isCip112TemplateId(event.templateId) ||
2998
+ event.createData?.status !== 'decoded') {
2999
+ continue;
3000
+ }
3001
+ const token = this.extractObservedTokenSummary(event.templateId, event.createData);
3002
+ if (token) {
3003
+ createTokensById.set(token.tokenId, token);
3004
+ }
3005
+ }
3006
+ const transfers = [];
3007
+ const eventLogExercises = events.filter((event) => event.eventKind === 'non_consuming_exercise' &&
3008
+ (event.choice === 'HoldingsChange' || event.choice === 'EventLog_HoldingsChange') &&
3009
+ event.exerciseData?.argument.status === 'decoded' &&
3010
+ this.isRecordValue(event.exerciseData.argument.value));
3011
+ for (const event of eventLogExercises) {
3012
+ const argument = event.exerciseData?.argument;
3013
+ if (!argument || argument.status !== 'decoded' || !this.isRecordValue(argument.value)) {
3014
+ continue;
3015
+ }
3016
+ const account = this.findRecordField(argument.value, 'account');
3017
+ const transferLegs = this.readListField(argument.value, 'transferLegSides');
3018
+ const admin = this.readScalarField(argument.value, 'admin');
3019
+ if (!account || !admin || transferLegs.length === 0) {
3020
+ continue;
3021
+ }
3022
+ for (const leg of transferLegs) {
3023
+ if (!this.isRecordValue(leg)) {
3024
+ continue;
3025
+ }
3026
+ const side = this.readConstructorField(leg, 'side');
3027
+ if (side !== 'SenderSide') {
3028
+ continue;
3029
+ }
3030
+ const transferLegId = this.readScalarField(leg, 'transferLegId');
3031
+ const amount = this.readScalarField(leg, 'amount');
3032
+ const instrumentId = this.readScalarField(leg, 'instrumentId');
3033
+ const otherSideAccount = this.findRecordField(leg, 'otherside');
3034
+ if (!transferLegId || !instrumentId || !otherSideAccount) {
3035
+ continue;
3036
+ }
3037
+ const token = this.resolveEventLogTokenSummary(admin, instrumentId, createTokensById, options?.canonicalShareTokenId ?? null);
3038
+ const movementType = this.classifyEventLogMovementType(account, otherSideAccount, admin, instrumentId);
3039
+ transfers.push({
3040
+ rowId: this.buildTokenTransferLegRowId(typeof update.update_id === 'string' ? this.normalizeUpdateId(update.update_id) : '', event.eventId, transferLegId, movementType),
3041
+ movementType,
3042
+ source: 'pqs',
3043
+ nodeId: node.id,
3044
+ label: node.label,
3045
+ tokenId: token.tokenId,
3046
+ tokenName: token.name,
3047
+ amount,
3048
+ sender: this.readAccountParty(account),
3049
+ receiver: this.readAccountParty(otherSideAccount),
3050
+ eventOffset: this.normalizeOptionalScalar(update.event_offset) ?? '',
3051
+ updateId: typeof update.update_id === 'string' ? this.normalizeUpdateId(update.update_id) : '',
3052
+ recordTime: typeof update.record_time === 'string' ? update.record_time : null,
3053
+ });
3054
+ }
2802
3055
  }
2803
3056
  return transfers;
2804
3057
  }
2805
- inferCip112TokenMovements(node, update, events) {
3058
+ inferCip112TokenMovements(node, update, events, options) {
2806
3059
  const createEvents = events.filter((event) => event.eventKind === 'create' &&
2807
3060
  this.isCip112TemplateId(event.templateId) &&
2808
3061
  event.createData?.status === 'decoded' &&
@@ -2822,10 +3075,11 @@ let PqsSummaryService = class PqsSummaryService {
2822
3075
  ? this.readScalarField(event.createData.value, 'owner')
2823
3076
  : null)
2824
3077
  .find((owner) => Boolean(owner));
2825
- if (transferExercises.length > 0) {
3078
+ const shouldEmitCreateMovements = transferExercises.length > 0 || (transferExercises.length === 0 && mintExercises.length === 0);
3079
+ if (shouldEmitCreateMovements) {
2826
3080
  for (const event of createEvents) {
2827
3081
  const movementType = 'Create';
2828
- const transfer = this.buildInferredCip112Movement(node, update, event, movementType, !this.isShareLikeCip112Template(event.templateId) ? shareOwnerFallback : null);
3082
+ const transfer = this.buildInferredCip112Movement(node, update, event, movementType, !this.isShareLikeCip112Template(event.templateId) ? shareOwnerFallback : null, options?.canonicalShareTokenId);
2829
3083
  if (transfer) {
2830
3084
  transfers.push(transfer);
2831
3085
  }
@@ -2844,7 +3098,7 @@ let PqsSummaryService = class PqsSummaryService {
2844
3098
  }
2845
3099
  return transfers;
2846
3100
  }
2847
- buildInferredCip112Movement(node, update, event, movementType, receiverFallback = null) {
3101
+ buildInferredCip112Movement(node, update, event, movementType, receiverFallback = null, canonicalShareTokenId = null) {
2848
3102
  if (event.createData?.status !== 'decoded') {
2849
3103
  return null;
2850
3104
  }
@@ -2856,13 +3110,16 @@ let PqsSummaryService = class PqsSummaryService {
2856
3110
  if (!token) {
2857
3111
  return null;
2858
3112
  }
3113
+ const tokenId = canonicalShareTokenId && this.isShareLikeCip112Template(event.templateId)
3114
+ ? canonicalShareTokenId
3115
+ : token.tokenId;
2859
3116
  return {
2860
3117
  rowId: this.buildTokenMovementRowId(typeof update.update_id === 'string' ? update.update_id : this.normalizeUpdateId(''), event.eventId, event.templateId, movementType),
2861
3118
  movementType,
2862
3119
  source: INFERRED_HOLDING_V2_SOURCE,
2863
3120
  nodeId: node.id,
2864
3121
  label: node.label,
2865
- tokenId: token.tokenId,
3122
+ tokenId,
2866
3123
  tokenName: token.name,
2867
3124
  amount: this.readScalarField(record, 'amount'),
2868
3125
  sender: movementType === 'Mint' ? this.readScalarField(record, 'issuer') : null,
@@ -2964,6 +3221,17 @@ let PqsSummaryService = class PqsSummaryService {
2964
3221
  normalizeTokenId(tokenId) {
2965
3222
  return tokenId.trim();
2966
3223
  }
3224
+ normalizeTokenTransferMovementTypeFilters(movementTypes) {
3225
+ return Array.from(new Set((movementTypes ?? [])
3226
+ .map((movementType) => movementType.trim().toLowerCase())
3227
+ .filter((movementType) => movementType.length > 0)));
3228
+ }
3229
+ effectiveTokenTransferMovementType(transfer) {
3230
+ const normalizedMovementType = transfer.movementType?.trim();
3231
+ return normalizedMovementType && normalizedMovementType.length > 0
3232
+ ? normalizedMovementType
3233
+ : 'Transfer';
3234
+ }
2967
3235
  isCip112TemplateId(templateId) {
2968
3236
  return typeof templateId === 'string' && templateId.includes('.CIP112:');
2969
3237
  }
@@ -2981,6 +3249,33 @@ let PqsSummaryService = class PqsSummaryService {
2981
3249
  buildTokenMovementRowId(updateId, eventId, templateId, movementType) {
2982
3250
  return [updateId, eventId ?? '', templateId ?? '', movementType].join(':');
2983
3251
  }
3252
+ buildTokenTransferLegRowId(updateId, eventId, transferLegId, movementType) {
3253
+ return [updateId, eventId ?? '', transferLegId, movementType].join(':');
3254
+ }
3255
+ resolveEventLogTokenSummary(admin, instrumentId, tokensById, canonicalShareTokenId) {
3256
+ const fallbackToken = this.buildObservedTokenSummary(instrumentId, admin, this.isNativeAmuletIntrinsicId(instrumentId) ? CANTON_COIN_TOKEN_NAME : instrumentId, null);
3257
+ const tokenId = canonicalShareTokenId && fallbackToken.tokenId === this.normalizeShareLikeIntrinsicTokenId(instrumentId)
3258
+ ? canonicalShareTokenId
3259
+ : fallbackToken.tokenId;
3260
+ return tokensById.get(tokenId) ?? tokensById.get(fallbackToken.tokenId) ?? {
3261
+ ...fallbackToken,
3262
+ tokenId,
3263
+ };
3264
+ }
3265
+ classifyEventLogMovementType(senderAccount, receiverAccount, admin, instrumentId) {
3266
+ if (this.isSyntheticEventLogAccount(senderAccount, admin, `${instrumentId}:mint`)) {
3267
+ return 'Mint';
3268
+ }
3269
+ if (this.isSyntheticEventLogAccount(receiverAccount, admin, `${instrumentId}:burn`)) {
3270
+ return 'Burn';
3271
+ }
3272
+ return 'Transfer';
3273
+ }
3274
+ isSyntheticEventLogAccount(account, provider, expectedId) {
3275
+ return (this.readOptionalScalarField(account, 'owner') === null &&
3276
+ this.readOptionalScalarField(account, 'provider') === provider &&
3277
+ this.readScalarField(account, 'id') === expectedId);
3278
+ }
2984
3279
  compareNumericStrings(left, right) {
2985
3280
  const leftValue = left === null ? Number.NEGATIVE_INFINITY : Number(left);
2986
3281
  const rightValue = right === null ? Number.NEGATIVE_INFINITY : Number(right);
@@ -3033,6 +3328,9 @@ let PqsSummaryService = class PqsSummaryService {
3033
3328
  return negative ? -parsed : parsed;
3034
3329
  }
3035
3330
  normalizeUpdateId(updateId) {
3331
+ if (typeof updateId !== 'string') {
3332
+ return '';
3333
+ }
3036
3334
  return updateId.startsWith('\\x') ? updateId.slice(2) : updateId;
3037
3335
  }
3038
3336
  extractTokenMovement(templateId, decoded) {
@@ -3085,14 +3383,7 @@ let PqsSummaryService = class PqsSummaryService {
3085
3383
  return null;
3086
3384
  }
3087
3385
  const issuer = this.readNestedScalarField(decoded.value, ['instrumentId', 'admin']);
3088
- const tokenId = this.buildObservedTokenId(intrinsicId, issuer);
3089
- return {
3090
- tokenId,
3091
- name: this.readConfiguredDecodedTokenMetadata(decoded.value, 'name') ?? intrinsicId,
3092
- symbol: this.readConfiguredDecodedTokenMetadata(decoded.value, 'symbol'),
3093
- issuer,
3094
- source: 'pqs',
3095
- };
3386
+ return this.buildObservedTokenSummary(intrinsicId, issuer, this.readConfiguredDecodedTokenMetadata(decoded.value, 'name') ?? intrinsicId, this.readConfiguredDecodedTokenMetadata(decoded.value, 'symbol'));
3096
3387
  }
3097
3388
  extractObservedTokenSummary(templateId, decoded) {
3098
3389
  if (!templateId) {
@@ -3123,14 +3414,25 @@ let PqsSummaryService = class PqsSummaryService {
3123
3414
  ?? this.readNestedScalarField(decoded.value, ['vaultIdentity', 'admin'])
3124
3415
  ?? this.readScalarField(decoded.value, 'vaultParty')
3125
3416
  ?? this.readScalarField(decoded.value, 'issuer');
3126
- const tokenId = this.buildObservedTokenId(intrinsicId, issuer);
3417
+ return this.buildObservedTokenSummary(intrinsicId, issuer, this.readConfiguredDecodedTokenMetadata(decoded.value, 'name')
3418
+ ?? this.readScalarField(decoded.value, 'name')
3419
+ ?? instrumentIdText
3420
+ ?? symbol
3421
+ ?? intrinsicId, symbol);
3422
+ }
3423
+ buildObservedTokenSummary(intrinsicId, issuer, fallbackName, symbol) {
3424
+ if (this.isNativeAmuletIntrinsicId(intrinsicId)) {
3425
+ return {
3426
+ tokenId: CANTON_COIN_TOKEN_ID,
3427
+ name: CANTON_COIN_TOKEN_NAME,
3428
+ symbol: null,
3429
+ issuer: null,
3430
+ source: 'pqs',
3431
+ };
3432
+ }
3127
3433
  return {
3128
- tokenId,
3129
- name: this.readConfiguredDecodedTokenMetadata(decoded.value, 'name')
3130
- ?? this.readScalarField(decoded.value, 'name')
3131
- ?? instrumentIdText
3132
- ?? symbol
3133
- ?? intrinsicId,
3434
+ tokenId: this.buildObservedTokenId(intrinsicId, issuer),
3435
+ name: fallbackName,
3134
3436
  symbol,
3135
3437
  issuer,
3136
3438
  source: 'pqs',
@@ -3144,6 +3446,9 @@ let PqsSummaryService = class PqsSummaryService {
3144
3446
  }
3145
3447
  return `${normalizedIssuer}::${normalizedIntrinsicId}`;
3146
3448
  }
3449
+ isNativeAmuletIntrinsicId(intrinsicId) {
3450
+ return intrinsicId.trim() === NATIVE_AMULET_INTRINSIC_ID;
3451
+ }
3147
3452
  normalizeShareLikeIntrinsicTokenId(intrinsicId) {
3148
3453
  const normalizedIntrinsicId = intrinsicId.trim();
3149
3454
  if (normalizedIntrinsicId.endsWith(':share')) {
@@ -3168,9 +3473,43 @@ let PqsSummaryService = class PqsSummaryService {
3168
3473
  };
3169
3474
  }
3170
3475
  async fetchEventsByUpdateId(node, query, rawUpdateId) {
3171
- const result = await query(updateEventsQuery(node, rawUpdateId));
3476
+ const eventsByUpdateId = await this.fetchEventsByUpdateIds(node, query, [rawUpdateId]);
3477
+ return eventsByUpdateId.get(rawUpdateId) ?? [];
3478
+ }
3479
+ async fetchEventsByUpdateIds(node, query, rawUpdateIds) {
3480
+ if (rawUpdateIds.length === 0) {
3481
+ return new Map();
3482
+ }
3483
+ const result = await query(updateEventsByUpdateIdsQuery(node, rawUpdateIds));
3172
3484
  const rows = result.rows ?? [];
3173
- return Promise.all(rows.map((row) => this.normalizeEventRow(node, row)));
3485
+ const onlyRequestedUpdateId = rawUpdateIds.length === 1 ? rawUpdateIds[0] : null;
3486
+ const normalizedRows = await Promise.all(rows.map(async (row) => ({
3487
+ updateId: typeof row.update_id === 'string'
3488
+ ? row.update_id
3489
+ : onlyRequestedUpdateId,
3490
+ event: await this.normalizeEventRow(node, row),
3491
+ })));
3492
+ const eventsByUpdateId = new Map();
3493
+ for (const { updateId, event } of normalizedRows) {
3494
+ if (!updateId) {
3495
+ continue;
3496
+ }
3497
+ const existingEvents = eventsByUpdateId.get(updateId);
3498
+ if (existingEvents) {
3499
+ existingEvents.push(event);
3500
+ }
3501
+ else {
3502
+ eventsByUpdateId.set(updateId, [event]);
3503
+ }
3504
+ }
3505
+ return eventsByUpdateId;
3506
+ }
3507
+ async fetchContractDetailsByIds(node, query, contractIds) {
3508
+ if (contractIds.length === 0) {
3509
+ return [];
3510
+ }
3511
+ const result = await query(contractDetailsQuery(node, contractIds));
3512
+ return result.rows ?? [];
3174
3513
  }
3175
3514
  shouldResolveRewardCoupon(events) {
3176
3515
  return events.some((event) => event.choice === 'ReceiveSvRewardCoupon');
@@ -3467,6 +3806,66 @@ let PqsSummaryService = class PqsSummaryService {
3467
3806
  }
3468
3807
  return null;
3469
3808
  }
3809
+ readOptionalScalarField(value, label) {
3810
+ const field = value.fields.find((candidate) => candidate.label === label)?.value;
3811
+ if (field === null || field === undefined) {
3812
+ return null;
3813
+ }
3814
+ if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') {
3815
+ return String(field);
3816
+ }
3817
+ if (typeof field === 'object' &&
3818
+ field !== null &&
3819
+ 'kind' in field &&
3820
+ field.kind === 'contract_id') {
3821
+ return field.value;
3822
+ }
3823
+ if (typeof field !== 'object' || !('kind' in field) || field.kind !== 'optional') {
3824
+ return null;
3825
+ }
3826
+ const innerValue = field.value;
3827
+ if (typeof innerValue === 'string' ||
3828
+ typeof innerValue === 'number' ||
3829
+ typeof innerValue === 'boolean') {
3830
+ return String(innerValue);
3831
+ }
3832
+ if (typeof innerValue === 'object' &&
3833
+ innerValue !== null &&
3834
+ 'kind' in innerValue &&
3835
+ innerValue.kind === 'contract_id') {
3836
+ return innerValue.value;
3837
+ }
3838
+ return null;
3839
+ }
3840
+ readConstructorField(value, label) {
3841
+ const field = value.fields.find((candidate) => candidate.label === label)?.value;
3842
+ if (field === null || field === undefined) {
3843
+ return null;
3844
+ }
3845
+ if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') {
3846
+ return String(field);
3847
+ }
3848
+ if (typeof field !== 'object' || !('kind' in field)) {
3849
+ return null;
3850
+ }
3851
+ if (field.kind === 'enum') {
3852
+ return field.constructor;
3853
+ }
3854
+ if (field.kind === 'variant') {
3855
+ return field.constructor;
3856
+ }
3857
+ return null;
3858
+ }
3859
+ readListField(value, label) {
3860
+ const field = value.fields.find((candidate) => candidate.label === label)?.value;
3861
+ if (!field || typeof field !== 'object' || !('kind' in field) || field.kind !== 'list') {
3862
+ return [];
3863
+ }
3864
+ return field.items;
3865
+ }
3866
+ readAccountParty(value) {
3867
+ return this.readOptionalScalarField(value, 'owner') ?? this.readOptionalScalarField(value, 'provider');
3868
+ }
3470
3869
  readTextMapEntryField(value, path, key) {
3471
3870
  let current = value;
3472
3871
  for (const segment of path) {