@distrohelena/canton-explorer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +9 -0
- package/LICENSE +184 -0
- package/README.md +63 -0
- package/bin/canton-explorer.js +68 -0
- package/config/nodes.example.json +35 -0
- package/dist/public/assets/index-NfJYgVwb.css +1 -0
- package/dist/public/assets/index-ybPCCjei.js +1 -0
- package/dist/public/cantonexplorer.png +0 -0
- package/dist/public/favicon.svg +1 -0
- package/dist/public/icons.svg +24 -0
- package/dist/public/index.html +14 -0
- package/dist/scripts/list-local-parties.d.ts +1 -0
- package/dist/scripts/list-local-parties.js +64 -0
- package/dist/scripts/list-local-parties.js.map +1 -0
- package/dist/src/api/nodes.controller.d.ts +181 -0
- package/dist/src/api/nodes.controller.js +873 -0
- package/dist/src/api/nodes.controller.js.map +1 -0
- package/dist/src/app-bootstrap.d.ts +22 -0
- package/dist/src/app-bootstrap.js +52 -0
- package/dist/src/app-bootstrap.js.map +1 -0
- package/dist/src/app.module.d.ts +2 -0
- package/dist/src/app.module.js +48 -0
- package/dist/src/app.module.js.map +1 -0
- package/dist/src/cache/node-cache.service.d.ts +54 -0
- package/dist/src/cache/node-cache.service.js +154 -0
- package/dist/src/cache/node-cache.service.js.map +1 -0
- package/dist/src/config/node-config.schema.d.ts +100 -0
- package/dist/src/config/node-config.schema.js +83 -0
- package/dist/src/config/node-config.schema.js.map +1 -0
- package/dist/src/config/node-config.service.d.ts +7 -0
- package/dist/src/config/node-config.service.js +36 -0
- package/dist/src/config/node-config.service.js.map +1 -0
- package/dist/src/domain/node-health.d.ts +9 -0
- package/dist/src/domain/node-health.js +16 -0
- package/dist/src/domain/node-health.js.map +1 -0
- package/dist/src/domain/node.types.d.ts +563 -0
- package/dist/src/domain/node.types.js +3 -0
- package/dist/src/domain/node.types.js.map +1 -0
- package/dist/src/grpc/grpc-client.factory.d.ts +297 -0
- package/dist/src/grpc/grpc-client.factory.js +67 -0
- package/dist/src/grpc/grpc-client.factory.js.map +1 -0
- package/dist/src/grpc/grpc-error.util.d.ts +7 -0
- package/dist/src/grpc/grpc-error.util.js +31 -0
- package/dist/src/grpc/grpc-error.util.js.map +1 -0
- package/dist/src/grpc/grpc-operations.service.d.ts +96 -0
- package/dist/src/grpc/grpc-operations.service.js +844 -0
- package/dist/src/grpc/grpc-operations.service.js.map +1 -0
- package/dist/src/grpc/shared-secret-jwt.d.ts +6 -0
- package/dist/src/grpc/shared-secret-jwt.js +22 -0
- package/dist/src/grpc/shared-secret-jwt.js.map +1 -0
- package/dist/src/main.d.ts +1 -0
- package/dist/src/main.js +6 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/namespaces/namespace-fingerprint.service.d.ts +22 -0
- package/dist/src/namespaces/namespace-fingerprint.service.js +125 -0
- package/dist/src/namespaces/namespace-fingerprint.service.js.map +1 -0
- package/dist/src/orchestrator/node-poller.service.d.ts +26 -0
- package/dist/src/orchestrator/node-poller.service.js +224 -0
- package/dist/src/orchestrator/node-poller.service.js.map +1 -0
- package/dist/src/packages/daml-decoder.types.d.ts +69 -0
- package/dist/src/packages/daml-decoder.types.js +3 -0
- package/dist/src/packages/daml-decoder.types.js.map +1 -0
- package/dist/src/packages/daml-lf-archive-v2.descriptor.pb +0 -0
- package/dist/src/packages/daml-lf-archive.descriptor.pb +0 -0
- package/dist/src/packages/daml-lf-loader.d.ts +3 -0
- package/dist/src/packages/daml-lf-loader.js +51 -0
- package/dist/src/packages/daml-lf-loader.js.map +1 -0
- package/dist/src/packages/daml-lf-raw.util.d.ts +12 -0
- package/dist/src/packages/daml-lf-raw.util.js +117 -0
- package/dist/src/packages/daml-lf-raw.util.js.map +1 -0
- package/dist/src/packages/daml-lf-value.descriptor.pb +0 -0
- package/dist/src/packages/daml-value-decoder.service.d.ts +39 -0
- package/dist/src/packages/daml-value-decoder.service.js +463 -0
- package/dist/src/packages/daml-value-decoder.service.js.map +1 -0
- package/dist/src/packages/package-cache.service.d.ts +53 -0
- package/dist/src/packages/package-cache.service.js +288 -0
- package/dist/src/packages/package-cache.service.js.map +1 -0
- package/dist/src/packages/package-registry.service.d.ts +44 -0
- package/dist/src/packages/package-registry.service.js +625 -0
- package/dist/src/packages/package-registry.service.js.map +1 -0
- package/dist/src/packages/package-sync.service.d.ts +22 -0
- package/dist/src/packages/package-sync.service.js +129 -0
- package/dist/src/packages/package-sync.service.js.map +1 -0
- package/dist/src/packages/pqs-package.service.d.ts +9 -0
- package/dist/src/packages/pqs-package.service.js +118 -0
- package/dist/src/packages/pqs-package.service.js.map +1 -0
- package/dist/src/pqs/pqs-client.factory.d.ts +7 -0
- package/dist/src/pqs/pqs-client.factory.js +37 -0
- package/dist/src/pqs/pqs-client.factory.js.map +1 -0
- package/dist/src/pqs/pqs-summary.service.d.ts +229 -0
- package/dist/src/pqs/pqs-summary.service.js +4652 -0
- package/dist/src/pqs/pqs-summary.service.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +97 -0
|
@@ -0,0 +1,4652 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.PqsSummaryService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const node_config_schema_1 = require("../config/node-config.schema");
|
|
18
|
+
const node_config_service_1 = require("../config/node-config.service");
|
|
19
|
+
const pqs_client_factory_1 = require("./pqs-client.factory");
|
|
20
|
+
const daml_value_decoder_service_1 = require("../packages/daml-value-decoder.service");
|
|
21
|
+
const package_cache_service_1 = require("../packages/package-cache.service");
|
|
22
|
+
const package_registry_service_1 = require("../packages/package-registry.service");
|
|
23
|
+
const grpc_operations_service_1 = require("../grpc/grpc-operations.service");
|
|
24
|
+
const package_sync_service_1 = require("../packages/package-sync.service");
|
|
25
|
+
const SEARCH_GROUP_LIMIT = 25;
|
|
26
|
+
const CANTON_COIN_TOKEN_ID = 'canton-coin';
|
|
27
|
+
const CANTON_COIN_TOKEN_NAME = 'Canton Coin';
|
|
28
|
+
const CANTON_COIN_TRANSFER_TEMPLATE_ID = 'Splice.AmuletTransferInstruction:AmuletTransferInstruction';
|
|
29
|
+
const CANTON_COIN_AMULET_TEMPLATE_ID = 'Splice.Amulet:Amulet';
|
|
30
|
+
const CIP56_HOLDING_TEMPLATE_ID = 'Splice.Api.Token.HoldingV1:Holding';
|
|
31
|
+
const CIP56_TRANSFER_TEMPLATE_ID = 'Splice.Api.Token.TransferInstructionV1:Transfer';
|
|
32
|
+
const CIP112_TEMPLATE_ID_LIKE_PATTERN = '%.CIP112:%';
|
|
33
|
+
const TOKEN_DISCOVERY_TEMPLATE_IDS = [
|
|
34
|
+
CANTON_COIN_TRANSFER_TEMPLATE_ID,
|
|
35
|
+
CANTON_COIN_AMULET_TEMPLATE_ID,
|
|
36
|
+
CIP56_HOLDING_TEMPLATE_ID,
|
|
37
|
+
CIP56_TRANSFER_TEMPLATE_ID,
|
|
38
|
+
];
|
|
39
|
+
const TOKEN_DISCOVERY_TEMPLATE_PATTERNS = [CIP112_TEMPLATE_ID_LIKE_PATTERN];
|
|
40
|
+
const TOKEN_DISCOVERY_NON_CIP112_TEMPLATE_IDS = [
|
|
41
|
+
CANTON_COIN_TRANSFER_TEMPLATE_ID,
|
|
42
|
+
CANTON_COIN_AMULET_TEMPLATE_ID,
|
|
43
|
+
CIP56_HOLDING_TEMPLATE_ID,
|
|
44
|
+
CIP56_TRANSFER_TEMPLATE_ID,
|
|
45
|
+
];
|
|
46
|
+
const TOKEN_DISCOVERY_NON_CIP112_TEMPLATE_PATTERNS = [];
|
|
47
|
+
const TOKEN_HOLDER_TEMPLATE_IDS = [
|
|
48
|
+
CANTON_COIN_AMULET_TEMPLATE_ID,
|
|
49
|
+
CIP56_HOLDING_TEMPLATE_ID,
|
|
50
|
+
];
|
|
51
|
+
const TOKEN_HOLDER_TEMPLATE_PATTERNS = [CIP112_TEMPLATE_ID_LIKE_PATTERN];
|
|
52
|
+
const TOKEN_HOLDER_NON_CIP112_TEMPLATE_IDS = [
|
|
53
|
+
CANTON_COIN_AMULET_TEMPLATE_ID,
|
|
54
|
+
CIP56_HOLDING_TEMPLATE_ID,
|
|
55
|
+
];
|
|
56
|
+
const TOKEN_HOLDER_NON_CIP112_TEMPLATE_PATTERNS = [];
|
|
57
|
+
const TOKEN_TRANSFER_TEMPLATE_IDS = [
|
|
58
|
+
CANTON_COIN_TRANSFER_TEMPLATE_ID,
|
|
59
|
+
CANTON_COIN_AMULET_TEMPLATE_ID,
|
|
60
|
+
CIP56_TRANSFER_TEMPLATE_ID,
|
|
61
|
+
];
|
|
62
|
+
const INFERRED_HOLDING_V2_SOURCE = 'pqs_inferred_holding_v2';
|
|
63
|
+
const TOKEN_TRANSFER_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
64
|
+
const TOKEN_TRANSFER_CACHE_LIMIT = 250;
|
|
65
|
+
const ACTIVE_QUERY = `
|
|
66
|
+
select
|
|
67
|
+
current_database() as pqs_database,
|
|
68
|
+
count(*)::int as active_contract_count,
|
|
69
|
+
max(created_at_offset) as latest_offset,
|
|
70
|
+
max(created_effective_at)::text as latest_event_at,
|
|
71
|
+
(
|
|
72
|
+
select count(*)::int
|
|
73
|
+
from participant.lapi_update_meta
|
|
74
|
+
) as total_update_count
|
|
75
|
+
from active()
|
|
76
|
+
`;
|
|
77
|
+
function compareGlobalMergedUpdates(left, right) {
|
|
78
|
+
const leftRecordTimeMs = Date.parse(left.recordTime ?? '');
|
|
79
|
+
const rightRecordTimeMs = Date.parse(right.recordTime ?? '');
|
|
80
|
+
const leftHasRecordTime = Number.isFinite(leftRecordTimeMs);
|
|
81
|
+
const rightHasRecordTime = Number.isFinite(rightRecordTimeMs);
|
|
82
|
+
if (leftHasRecordTime && rightHasRecordTime && leftRecordTimeMs !== rightRecordTimeMs) {
|
|
83
|
+
return rightRecordTimeMs - leftRecordTimeMs;
|
|
84
|
+
}
|
|
85
|
+
if (leftHasRecordTime !== rightHasRecordTime) {
|
|
86
|
+
return leftHasRecordTime ? -1 : 1;
|
|
87
|
+
}
|
|
88
|
+
if (left.nodeId !== right.nodeId) {
|
|
89
|
+
return left.nodeId.localeCompare(right.nodeId);
|
|
90
|
+
}
|
|
91
|
+
if (left.eventOffset !== right.eventOffset) {
|
|
92
|
+
return right.eventOffset.localeCompare(left.eventOffset);
|
|
93
|
+
}
|
|
94
|
+
return right.updateId.localeCompare(left.updateId);
|
|
95
|
+
}
|
|
96
|
+
function encodeGlobalUpdateCursor(update) {
|
|
97
|
+
return Buffer.from(JSON.stringify(update), 'utf8').toString('base64url');
|
|
98
|
+
}
|
|
99
|
+
function decodeGlobalUpdateCursor(cursor) {
|
|
100
|
+
if (!cursor || !cursor.trim()) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
105
|
+
if ((decoded.recordTime === null || typeof decoded.recordTime === 'string') &&
|
|
106
|
+
typeof decoded.nodeId === 'string' &&
|
|
107
|
+
typeof decoded.eventOffset === 'string' &&
|
|
108
|
+
typeof decoded.updateId === 'string') {
|
|
109
|
+
return {
|
|
110
|
+
recordTime: decoded.recordTime ?? null,
|
|
111
|
+
nodeId: decoded.nodeId,
|
|
112
|
+
eventOffset: decoded.eventOffset,
|
|
113
|
+
updateId: decoded.updateId,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
function compareGlobalMergedContracts(left, right) {
|
|
123
|
+
const leftRecordTimeMs = Date.parse(left.recordTime ?? '');
|
|
124
|
+
const rightRecordTimeMs = Date.parse(right.recordTime ?? '');
|
|
125
|
+
const leftHasRecordTime = Number.isFinite(leftRecordTimeMs);
|
|
126
|
+
const rightHasRecordTime = Number.isFinite(rightRecordTimeMs);
|
|
127
|
+
if (leftHasRecordTime && rightHasRecordTime && leftRecordTimeMs !== rightRecordTimeMs) {
|
|
128
|
+
return rightRecordTimeMs - leftRecordTimeMs;
|
|
129
|
+
}
|
|
130
|
+
if (leftHasRecordTime !== rightHasRecordTime) {
|
|
131
|
+
return leftHasRecordTime ? -1 : 1;
|
|
132
|
+
}
|
|
133
|
+
if (left.nodeId !== right.nodeId) {
|
|
134
|
+
return left.nodeId.localeCompare(right.nodeId);
|
|
135
|
+
}
|
|
136
|
+
return right.contractId.localeCompare(left.contractId);
|
|
137
|
+
}
|
|
138
|
+
function encodeGlobalContractCursor(contract) {
|
|
139
|
+
return Buffer.from(JSON.stringify(contract), 'utf8').toString('base64url');
|
|
140
|
+
}
|
|
141
|
+
function decodeGlobalContractCursor(cursor) {
|
|
142
|
+
if (!cursor || !cursor.trim()) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
147
|
+
if ((decoded.recordTime === null || typeof decoded.recordTime === 'string') &&
|
|
148
|
+
typeof decoded.nodeId === 'string' &&
|
|
149
|
+
typeof decoded.contractId === 'string') {
|
|
150
|
+
return {
|
|
151
|
+
recordTime: decoded.recordTime ?? null,
|
|
152
|
+
nodeId: decoded.nodeId,
|
|
153
|
+
contractId: decoded.contractId,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
function compareSearchUpdates(left, right) {
|
|
163
|
+
if (left.exact !== right.exact) {
|
|
164
|
+
return left.exact ? -1 : 1;
|
|
165
|
+
}
|
|
166
|
+
const leftRecordTimeMs = Date.parse(left.recordTime ?? '');
|
|
167
|
+
const rightRecordTimeMs = Date.parse(right.recordTime ?? '');
|
|
168
|
+
const leftHasRecordTime = Number.isFinite(leftRecordTimeMs);
|
|
169
|
+
const rightHasRecordTime = Number.isFinite(rightRecordTimeMs);
|
|
170
|
+
if (leftHasRecordTime && rightHasRecordTime && leftRecordTimeMs !== rightRecordTimeMs) {
|
|
171
|
+
return rightRecordTimeMs - leftRecordTimeMs;
|
|
172
|
+
}
|
|
173
|
+
if (leftHasRecordTime !== rightHasRecordTime) {
|
|
174
|
+
return leftHasRecordTime ? -1 : 1;
|
|
175
|
+
}
|
|
176
|
+
if (left.label !== right.label) {
|
|
177
|
+
return left.label.localeCompare(right.label);
|
|
178
|
+
}
|
|
179
|
+
return right.eventOffset.localeCompare(left.eventOffset);
|
|
180
|
+
}
|
|
181
|
+
function compareSearchContracts(left, right) {
|
|
182
|
+
if (left.exact !== right.exact) {
|
|
183
|
+
return left.exact ? -1 : 1;
|
|
184
|
+
}
|
|
185
|
+
const leftRecordTimeMs = Date.parse(left.createdRecordTime ?? '');
|
|
186
|
+
const rightRecordTimeMs = Date.parse(right.createdRecordTime ?? '');
|
|
187
|
+
const leftHasRecordTime = Number.isFinite(leftRecordTimeMs);
|
|
188
|
+
const rightHasRecordTime = Number.isFinite(rightRecordTimeMs);
|
|
189
|
+
if (leftHasRecordTime && rightHasRecordTime && leftRecordTimeMs !== rightRecordTimeMs) {
|
|
190
|
+
return rightRecordTimeMs - leftRecordTimeMs;
|
|
191
|
+
}
|
|
192
|
+
if (leftHasRecordTime !== rightHasRecordTime) {
|
|
193
|
+
return leftHasRecordTime ? -1 : 1;
|
|
194
|
+
}
|
|
195
|
+
if (left.label !== right.label) {
|
|
196
|
+
return left.label.localeCompare(right.label);
|
|
197
|
+
}
|
|
198
|
+
return left.contractId.localeCompare(right.contractId);
|
|
199
|
+
}
|
|
200
|
+
function compareGlobalTokenTransfers(left, right) {
|
|
201
|
+
const leftRecordTimeMs = Date.parse(left.recordTime ?? '');
|
|
202
|
+
const rightRecordTimeMs = Date.parse(right.recordTime ?? '');
|
|
203
|
+
const leftHasRecordTime = Number.isFinite(leftRecordTimeMs);
|
|
204
|
+
const rightHasRecordTime = Number.isFinite(rightRecordTimeMs);
|
|
205
|
+
if (leftHasRecordTime && rightHasRecordTime && leftRecordTimeMs !== rightRecordTimeMs) {
|
|
206
|
+
return rightRecordTimeMs - leftRecordTimeMs;
|
|
207
|
+
}
|
|
208
|
+
if (leftHasRecordTime !== rightHasRecordTime) {
|
|
209
|
+
return leftHasRecordTime ? -1 : 1;
|
|
210
|
+
}
|
|
211
|
+
if (left.updateId !== right.updateId) {
|
|
212
|
+
return right.updateId.localeCompare(left.updateId);
|
|
213
|
+
}
|
|
214
|
+
if (left.tokenId !== right.tokenId) {
|
|
215
|
+
return right.tokenId.localeCompare(left.tokenId);
|
|
216
|
+
}
|
|
217
|
+
if ((left.sender ?? '') !== (right.sender ?? '')) {
|
|
218
|
+
return (right.sender ?? '').localeCompare(left.sender ?? '');
|
|
219
|
+
}
|
|
220
|
+
if ((left.receiver ?? '') !== (right.receiver ?? '')) {
|
|
221
|
+
return (right.receiver ?? '').localeCompare(left.receiver ?? '');
|
|
222
|
+
}
|
|
223
|
+
if ((left.rowId ?? '') !== (right.rowId ?? '')) {
|
|
224
|
+
return (right.rowId ?? '').localeCompare(left.rowId ?? '');
|
|
225
|
+
}
|
|
226
|
+
return (right.amount ?? '').localeCompare(left.amount ?? '');
|
|
227
|
+
}
|
|
228
|
+
function encodeGlobalTokenTransferCursor(transfer) {
|
|
229
|
+
return Buffer.from(JSON.stringify({
|
|
230
|
+
rowId: transfer.rowId,
|
|
231
|
+
recordTime: transfer.recordTime,
|
|
232
|
+
updateId: transfer.updateId,
|
|
233
|
+
tokenId: transfer.tokenId,
|
|
234
|
+
amount: transfer.amount,
|
|
235
|
+
sender: transfer.sender,
|
|
236
|
+
receiver: transfer.receiver,
|
|
237
|
+
}), 'utf8').toString('base64url');
|
|
238
|
+
}
|
|
239
|
+
function decodeGlobalTokenTransferCursor(cursor) {
|
|
240
|
+
if (!cursor || !cursor.trim()) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
245
|
+
if ((decoded.rowId === undefined || typeof decoded.rowId === 'string') &&
|
|
246
|
+
(decoded.recordTime === null || typeof decoded.recordTime === 'string') &&
|
|
247
|
+
typeof decoded.updateId === 'string' &&
|
|
248
|
+
typeof decoded.tokenId === 'string' &&
|
|
249
|
+
(decoded.amount === null || typeof decoded.amount === 'string') &&
|
|
250
|
+
(decoded.sender === null || typeof decoded.sender === 'string') &&
|
|
251
|
+
(decoded.receiver === null || typeof decoded.receiver === 'string')) {
|
|
252
|
+
return {
|
|
253
|
+
rowId: decoded.rowId,
|
|
254
|
+
recordTime: decoded.recordTime ?? null,
|
|
255
|
+
updateId: decoded.updateId,
|
|
256
|
+
tokenId: decoded.tokenId,
|
|
257
|
+
amount: decoded.amount ?? null,
|
|
258
|
+
sender: decoded.sender ?? null,
|
|
259
|
+
receiver: decoded.receiver ?? null,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
function compareGlobalTokenHolders(left, right) {
|
|
269
|
+
const leftValue = left.amount === null ? Number.NEGATIVE_INFINITY : Number(left.amount);
|
|
270
|
+
const rightValue = right.amount === null ? Number.NEGATIVE_INFINITY : Number(right.amount);
|
|
271
|
+
const leftValid = Number.isFinite(leftValue);
|
|
272
|
+
const rightValid = Number.isFinite(rightValue);
|
|
273
|
+
if (leftValid && rightValid && leftValue !== rightValue) {
|
|
274
|
+
return rightValue - leftValue;
|
|
275
|
+
}
|
|
276
|
+
if (leftValid !== rightValid) {
|
|
277
|
+
return leftValid ? -1 : 1;
|
|
278
|
+
}
|
|
279
|
+
return left.partyId.localeCompare(right.partyId);
|
|
280
|
+
}
|
|
281
|
+
function compareGlobalTokens(left, right) {
|
|
282
|
+
if (left.name !== right.name) {
|
|
283
|
+
return left.name.localeCompare(right.name);
|
|
284
|
+
}
|
|
285
|
+
return left.tokenId.localeCompare(right.tokenId);
|
|
286
|
+
}
|
|
287
|
+
function encodeGlobalTokenCursor(token) {
|
|
288
|
+
return Buffer.from(JSON.stringify({
|
|
289
|
+
tokenId: token.tokenId,
|
|
290
|
+
name: token.name,
|
|
291
|
+
}), 'utf8').toString('base64url');
|
|
292
|
+
}
|
|
293
|
+
function decodeGlobalTokenCursor(cursor) {
|
|
294
|
+
if (!cursor || !cursor.trim()) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
const decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
299
|
+
if (typeof decoded.tokenId === 'string' && typeof decoded.name === 'string') {
|
|
300
|
+
return {
|
|
301
|
+
tokenId: decoded.tokenId,
|
|
302
|
+
name: decoded.name,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
function encodeGlobalTokenHolderCursor(holder) {
|
|
312
|
+
return Buffer.from(JSON.stringify({
|
|
313
|
+
partyId: holder.partyId,
|
|
314
|
+
amount: holder.amount,
|
|
315
|
+
}), 'utf8').toString('base64url');
|
|
316
|
+
}
|
|
317
|
+
function decodeGlobalTokenHolderCursor(cursor) {
|
|
318
|
+
if (!cursor || !cursor.trim()) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
323
|
+
if (typeof decoded.partyId === 'string' && (decoded.amount === null || typeof decoded.amount === 'string')) {
|
|
324
|
+
return {
|
|
325
|
+
partyId: decoded.partyId,
|
|
326
|
+
amount: decoded.amount ?? null,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
function compareSearchParties(left, right) {
|
|
336
|
+
if (left.exact !== right.exact) {
|
|
337
|
+
return left.exact ? -1 : 1;
|
|
338
|
+
}
|
|
339
|
+
return left.partyId.localeCompare(right.partyId);
|
|
340
|
+
}
|
|
341
|
+
function compareSearchPackageIds(left, right) {
|
|
342
|
+
if (left.exact !== right.exact) {
|
|
343
|
+
return left.exact ? -1 : 1;
|
|
344
|
+
}
|
|
345
|
+
const leftName = left.name ?? '';
|
|
346
|
+
const rightName = right.name ?? '';
|
|
347
|
+
if (leftName !== rightName) {
|
|
348
|
+
return leftName.localeCompare(rightName);
|
|
349
|
+
}
|
|
350
|
+
const leftVersion = left.version ?? '';
|
|
351
|
+
const rightVersion = right.version ?? '';
|
|
352
|
+
if (leftVersion !== rightVersion) {
|
|
353
|
+
return rightVersion.localeCompare(leftVersion);
|
|
354
|
+
}
|
|
355
|
+
return left.packageId.localeCompare(right.packageId);
|
|
356
|
+
}
|
|
357
|
+
function compareSearchPackageNames(left, right) {
|
|
358
|
+
if (left.exact !== right.exact) {
|
|
359
|
+
return left.exact ? -1 : 1;
|
|
360
|
+
}
|
|
361
|
+
return left.name.localeCompare(right.name);
|
|
362
|
+
}
|
|
363
|
+
const PARTICIPANT_FALLBACK_QUERY = `
|
|
364
|
+
select
|
|
365
|
+
current_database() as pqs_database,
|
|
366
|
+
(
|
|
367
|
+
select count(*)::int
|
|
368
|
+
from participant.par_active_contracts
|
|
369
|
+
) as active_contract_count,
|
|
370
|
+
(
|
|
371
|
+
select max(event_offset)::text
|
|
372
|
+
from participant.lapi_update_meta
|
|
373
|
+
) as latest_offset,
|
|
374
|
+
(
|
|
375
|
+
select to_char(
|
|
376
|
+
to_timestamp(max(record_time) / 1000000.0) at time zone 'UTC',
|
|
377
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
378
|
+
)
|
|
379
|
+
from participant.lapi_update_meta
|
|
380
|
+
) as latest_event_at,
|
|
381
|
+
(
|
|
382
|
+
select count(*)::int
|
|
383
|
+
from participant.lapi_update_meta
|
|
384
|
+
) as total_update_count
|
|
385
|
+
`;
|
|
386
|
+
function activityBucketsQuery(days, bucketMinutes) {
|
|
387
|
+
const normalizedDays = Number.isFinite(days) && days > 0 ? Math.trunc(days) : 30;
|
|
388
|
+
const normalizedBucketMinutes = Number.isFinite(bucketMinutes) && bucketMinutes > 0 ? Math.trunc(bucketMinutes) : 15;
|
|
389
|
+
const bucketSeconds = normalizedBucketMinutes * 60;
|
|
390
|
+
const minRecordTimeMicros = Math.floor((Date.now() - normalizedDays * 24 * 60 * 60 * 1000) * 1000);
|
|
391
|
+
return `
|
|
392
|
+
select
|
|
393
|
+
to_char(
|
|
394
|
+
to_timestamp(floor((record_time / 1000000.0) / ${bucketSeconds}) * ${bucketSeconds}) at time zone 'UTC',
|
|
395
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
396
|
+
) as bucket_timestamp,
|
|
397
|
+
count(*)::int as activity_value,
|
|
398
|
+
max(event_offset)::text as latest_offset
|
|
399
|
+
from participant.lapi_update_meta
|
|
400
|
+
where record_time >= ${minRecordTimeMicros}
|
|
401
|
+
group by 1
|
|
402
|
+
order by 1 asc
|
|
403
|
+
`;
|
|
404
|
+
}
|
|
405
|
+
function normalizePartyFilters(parties) {
|
|
406
|
+
if (!parties) {
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
return Array.from(new Set(parties
|
|
410
|
+
.map((party) => party.trim())
|
|
411
|
+
.filter((party) => party.length > 0)));
|
|
412
|
+
}
|
|
413
|
+
function normalizePartyFilterMode(partyMode) {
|
|
414
|
+
return partyMode === 'and' ? 'and' : 'or';
|
|
415
|
+
}
|
|
416
|
+
function normalizeTemplateFilterValue(templateId) {
|
|
417
|
+
return templateId.trim().replace(/^t\|#[^:]+:/, '');
|
|
418
|
+
}
|
|
419
|
+
function normalizeTemplateFilters(templates) {
|
|
420
|
+
if (!templates) {
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
return Array.from(new Set(templates
|
|
424
|
+
.map((templateId) => normalizeTemplateFilterValue(templateId))
|
|
425
|
+
.filter((templateId) => templateId.length > 0)));
|
|
426
|
+
}
|
|
427
|
+
function normalizeTokenTextFilters(values) {
|
|
428
|
+
if (!values) {
|
|
429
|
+
return [];
|
|
430
|
+
}
|
|
431
|
+
return Array.from(new Set(values
|
|
432
|
+
.map((value) => value.trim().toLowerCase())
|
|
433
|
+
.filter((value) => value.length > 0)));
|
|
434
|
+
}
|
|
435
|
+
function normalizeTokenIssuerFilters(values) {
|
|
436
|
+
if (!values) {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
return Array.from(new Set(values
|
|
440
|
+
.map((value) => value.trim())
|
|
441
|
+
.filter((value) => value.length > 0)));
|
|
442
|
+
}
|
|
443
|
+
function buildUpdatePartyExistsCondition(partyId) {
|
|
444
|
+
const createMatch = partyWitnessArrayMatchCondition('create_event.tree_event_witnesses', partyId);
|
|
445
|
+
const consumingMatch = partyWitnessArrayMatchCondition('exercise_event.tree_event_witnesses', partyId);
|
|
446
|
+
const nonConsumingMatch = partyWitnessArrayMatchCondition('exercise_event.tree_event_witnesses', partyId);
|
|
447
|
+
return `(
|
|
448
|
+
exists (
|
|
449
|
+
select 1
|
|
450
|
+
from participant.lapi_events_create create_event
|
|
451
|
+
where create_event.update_id = update_meta.update_id
|
|
452
|
+
and ${createMatch}
|
|
453
|
+
)
|
|
454
|
+
or exists (
|
|
455
|
+
select 1
|
|
456
|
+
from participant.lapi_events_consuming_exercise exercise_event
|
|
457
|
+
where exercise_event.update_id = update_meta.update_id
|
|
458
|
+
and ${consumingMatch}
|
|
459
|
+
)
|
|
460
|
+
or exists (
|
|
461
|
+
select 1
|
|
462
|
+
from participant.lapi_events_non_consuming_exercise exercise_event
|
|
463
|
+
where exercise_event.update_id = update_meta.update_id
|
|
464
|
+
and ${nonConsumingMatch}
|
|
465
|
+
)
|
|
466
|
+
)`;
|
|
467
|
+
}
|
|
468
|
+
function buildNormalizedUpdatePartyExistsCondition(partyId) {
|
|
469
|
+
const partyMatch = partyScalarMatchCondition('party_string.external_string', partyId);
|
|
470
|
+
return `(
|
|
471
|
+
exists (
|
|
472
|
+
select 1
|
|
473
|
+
from participant.lapi_events_activate_contract activate_event
|
|
474
|
+
join participant.lapi_filter_activate_witness witness_filter
|
|
475
|
+
on witness_filter.event_sequential_id = activate_event.event_sequential_id
|
|
476
|
+
join participant.lapi_string_interning party_string
|
|
477
|
+
on party_string.internal_id = witness_filter.party_id
|
|
478
|
+
where activate_event.update_id = update_meta.update_id
|
|
479
|
+
and ${partyMatch}
|
|
480
|
+
)
|
|
481
|
+
or exists (
|
|
482
|
+
select 1
|
|
483
|
+
from participant.lapi_events_deactivate_contract deactivate_event
|
|
484
|
+
join participant.lapi_filter_deactivate_witness witness_filter
|
|
485
|
+
on witness_filter.event_sequential_id = deactivate_event.event_sequential_id
|
|
486
|
+
join participant.lapi_string_interning party_string
|
|
487
|
+
on party_string.internal_id = witness_filter.party_id
|
|
488
|
+
where deactivate_event.update_id = update_meta.update_id
|
|
489
|
+
and ${partyMatch}
|
|
490
|
+
)
|
|
491
|
+
or exists (
|
|
492
|
+
select 1
|
|
493
|
+
from participant.lapi_events_various_witnessed various_event
|
|
494
|
+
join participant.lapi_filter_various_witness witness_filter
|
|
495
|
+
on witness_filter.event_sequential_id = various_event.event_sequential_id
|
|
496
|
+
join participant.lapi_string_interning party_string
|
|
497
|
+
on party_string.internal_id = witness_filter.party_id
|
|
498
|
+
where various_event.update_id = update_meta.update_id
|
|
499
|
+
and ${partyMatch}
|
|
500
|
+
)
|
|
501
|
+
)`;
|
|
502
|
+
}
|
|
503
|
+
function buildUpdateTemplateExistsCondition(templateId) {
|
|
504
|
+
const normalizedTemplateId = normalizeTemplateFilterValue(templateId);
|
|
505
|
+
const quotedTemplateId = `'${escapeSqlLiteral(normalizedTemplateId)}'`;
|
|
506
|
+
return `(
|
|
507
|
+
exists (
|
|
508
|
+
select 1
|
|
509
|
+
from (
|
|
510
|
+
select regexp_replace(coalesce(create_event.template_id::text, ''), '^t\\\\|#[^:]+:', '') as template_id
|
|
511
|
+
from participant.lapi_events_create create_event
|
|
512
|
+
where create_event.update_id = update_meta.update_id
|
|
513
|
+
|
|
514
|
+
union all
|
|
515
|
+
|
|
516
|
+
select regexp_replace(coalesce(exercise_event.template_id::text, ''), '^t\\\\|#[^:]+:', '') as template_id
|
|
517
|
+
from participant.lapi_events_consuming_exercise exercise_event
|
|
518
|
+
where exercise_event.update_id = update_meta.update_id
|
|
519
|
+
|
|
520
|
+
union all
|
|
521
|
+
|
|
522
|
+
select regexp_replace(coalesce(exercise_event.template_id::text, ''), '^t\\\\|#[^:]+:', '') as template_id
|
|
523
|
+
from participant.lapi_events_non_consuming_exercise exercise_event
|
|
524
|
+
where exercise_event.update_id = update_meta.update_id
|
|
525
|
+
) update_event_templates
|
|
526
|
+
where update_event_templates.template_id = ${quotedTemplateId}
|
|
527
|
+
)
|
|
528
|
+
)`;
|
|
529
|
+
}
|
|
530
|
+
function buildNormalizedUpdateTemplateExistsCondition(templateId) {
|
|
531
|
+
const normalizedTemplateId = normalizeTemplateFilterValue(templateId);
|
|
532
|
+
const quotedTemplateId = `'${escapeSqlLiteral(normalizedTemplateId)}'`;
|
|
533
|
+
return `(
|
|
534
|
+
exists (
|
|
535
|
+
select 1
|
|
536
|
+
from (
|
|
537
|
+
select regexp_replace(coalesce(contract.template_id::text, ''), '^t\\\\|#[^:]+:', '') as template_id
|
|
538
|
+
from participant.lapi_events_activate_contract activate_event
|
|
539
|
+
left join participant.par_contracts contract
|
|
540
|
+
on contract.internal_contract_id = activate_event.internal_contract_id
|
|
541
|
+
where activate_event.update_id = update_meta.update_id
|
|
542
|
+
|
|
543
|
+
union all
|
|
544
|
+
|
|
545
|
+
select regexp_replace(coalesce(contract.template_id::text, ''), '^t\\\\|#[^:]+:', '') as template_id
|
|
546
|
+
from participant.lapi_events_various_witnessed various_event
|
|
547
|
+
left join participant.par_contracts contract
|
|
548
|
+
on contract.internal_contract_id = various_event.internal_contract_id
|
|
549
|
+
where various_event.update_id = update_meta.update_id
|
|
550
|
+
and various_event.event_type = 6
|
|
551
|
+
|
|
552
|
+
union all
|
|
553
|
+
|
|
554
|
+
select regexp_replace(coalesce(template_string.external_string, ''), '^t\\\\|#[^:]+:', '') as template_id
|
|
555
|
+
from participant.lapi_events_deactivate_contract deactivate_event
|
|
556
|
+
left join participant.lapi_string_interning template_string
|
|
557
|
+
on template_string.internal_id = deactivate_event.template_id
|
|
558
|
+
where deactivate_event.update_id = update_meta.update_id
|
|
559
|
+
|
|
560
|
+
union all
|
|
561
|
+
|
|
562
|
+
select regexp_replace(coalesce(template_string.external_string, ''), '^t\\\\|#[^:]+:', '') as template_id
|
|
563
|
+
from participant.lapi_events_various_witnessed various_event
|
|
564
|
+
left join participant.lapi_string_interning template_string
|
|
565
|
+
on template_string.internal_id = various_event.template_id
|
|
566
|
+
where various_event.update_id = update_meta.update_id
|
|
567
|
+
and various_event.event_type in (5, 7)
|
|
568
|
+
) update_event_templates
|
|
569
|
+
where update_event_templates.template_id = ${quotedTemplateId}
|
|
570
|
+
)
|
|
571
|
+
)`;
|
|
572
|
+
}
|
|
573
|
+
function buildUpdatesFilterClause(parties, templates, partyMode) {
|
|
574
|
+
const partyConditions = normalizePartyFilters(parties).map((party) => buildUpdatePartyExistsCondition(party));
|
|
575
|
+
const templateConditions = normalizeTemplateFilters(templates).map((templateId) => buildUpdateTemplateExistsCondition(templateId));
|
|
576
|
+
const groups = [];
|
|
577
|
+
if (partyConditions.length > 0) {
|
|
578
|
+
const partyJoiner = normalizePartyFilterMode(partyMode) === 'and' ? '\n and ' : '\n or ';
|
|
579
|
+
groups.push(partyConditions.length === 1
|
|
580
|
+
? partyConditions[0]
|
|
581
|
+
: `(\n ${partyConditions.join(partyJoiner)}\n )`);
|
|
582
|
+
}
|
|
583
|
+
if (templateConditions.length > 0) {
|
|
584
|
+
groups.push(templateConditions.length === 1
|
|
585
|
+
? templateConditions[0]
|
|
586
|
+
: `(\n ${templateConditions.join('\n or ')}\n )`);
|
|
587
|
+
}
|
|
588
|
+
if (groups.length === 0) {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
return groups.length === 1 ? groups[0] : `(\n ${groups.join('\n and ')}\n )`;
|
|
592
|
+
}
|
|
593
|
+
function buildNormalizedUpdatesFilterClause(parties, templates, partyMode) {
|
|
594
|
+
const partyConditions = normalizePartyFilters(parties).map((party) => buildNormalizedUpdatePartyExistsCondition(party));
|
|
595
|
+
const templateConditions = normalizeTemplateFilters(templates).map((templateId) => buildNormalizedUpdateTemplateExistsCondition(templateId));
|
|
596
|
+
const groups = [];
|
|
597
|
+
if (partyConditions.length > 0) {
|
|
598
|
+
const partyJoiner = normalizePartyFilterMode(partyMode) === 'and' ? '\n and ' : '\n or ';
|
|
599
|
+
groups.push(partyConditions.length === 1
|
|
600
|
+
? partyConditions[0]
|
|
601
|
+
: `(\n ${partyConditions.join(partyJoiner)}\n )`);
|
|
602
|
+
}
|
|
603
|
+
if (templateConditions.length > 0) {
|
|
604
|
+
groups.push(templateConditions.length === 1
|
|
605
|
+
? templateConditions[0]
|
|
606
|
+
: `(\n ${templateConditions.join('\n or ')}\n )`);
|
|
607
|
+
}
|
|
608
|
+
if (groups.length === 0) {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
return groups.length === 1 ? groups[0] : `(\n ${groups.join('\n and ')}\n )`;
|
|
612
|
+
}
|
|
613
|
+
function recentUpdatesQuery(limit, before, after, parties, templates, partyMode, hideSplice) {
|
|
614
|
+
const normalizedBefore = normalizeEventOffsetCursor(before);
|
|
615
|
+
const normalizedAfter = normalizeEventOffsetCursor(after);
|
|
616
|
+
const filterClause = buildUpdatesFilterClause(parties, templates, partyMode);
|
|
617
|
+
const hideSpliceClause = hideSplice ? buildHideSpliceOffsetsClause() : null;
|
|
618
|
+
const queryLimit = limit + 1;
|
|
619
|
+
const afterFilters = [
|
|
620
|
+
normalizedAfter ? `update_meta.event_offset::numeric > ${normalizedAfter}` : null,
|
|
621
|
+
filterClause,
|
|
622
|
+
hideSpliceClause,
|
|
623
|
+
].filter((value) => Boolean(value));
|
|
624
|
+
const olderFilters = [
|
|
625
|
+
normalizedBefore ? `update_meta.event_offset::numeric < ${normalizedBefore}` : null,
|
|
626
|
+
filterClause,
|
|
627
|
+
hideSpliceClause,
|
|
628
|
+
].filter((value) => Boolean(value));
|
|
629
|
+
if (normalizedAfter && !normalizedBefore) {
|
|
630
|
+
const whereClause = afterFilters.length > 0 ? `where ${afterFilters.join('\n and ')}` : '';
|
|
631
|
+
return `
|
|
632
|
+
select
|
|
633
|
+
update_meta.update_id::text as update_id,
|
|
634
|
+
update_meta.event_offset::text as event_offset,
|
|
635
|
+
to_char(
|
|
636
|
+
to_timestamp(update_meta.record_time / 1000000.0) at time zone 'UTC',
|
|
637
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
638
|
+
) as record_time
|
|
639
|
+
from participant.lapi_update_meta update_meta
|
|
640
|
+
${whereClause}
|
|
641
|
+
order by update_meta.event_offset::numeric asc
|
|
642
|
+
limit ${queryLimit}
|
|
643
|
+
`;
|
|
644
|
+
}
|
|
645
|
+
const whereClause = olderFilters.length > 0 ? `where ${olderFilters.join('\n and ')}` : '';
|
|
646
|
+
return `
|
|
647
|
+
select
|
|
648
|
+
update_meta.update_id::text as update_id,
|
|
649
|
+
update_meta.event_offset::text as event_offset,
|
|
650
|
+
to_char(
|
|
651
|
+
to_timestamp(update_meta.record_time / 1000000.0) at time zone 'UTC',
|
|
652
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
653
|
+
) as record_time
|
|
654
|
+
from participant.lapi_update_meta update_meta
|
|
655
|
+
${whereClause}
|
|
656
|
+
order by update_meta.event_offset::numeric desc
|
|
657
|
+
limit ${queryLimit}
|
|
658
|
+
`;
|
|
659
|
+
}
|
|
660
|
+
function searchUpdatesQuery(searchQuery, limit) {
|
|
661
|
+
const quotedQuery = escapeSqlLiteral(searchQuery);
|
|
662
|
+
return `
|
|
663
|
+
select
|
|
664
|
+
update_meta.update_id::text as update_id,
|
|
665
|
+
update_meta.event_offset::text as event_offset,
|
|
666
|
+
to_char(
|
|
667
|
+
to_timestamp(update_meta.record_time / 1000000.0) at time zone 'UTC',
|
|
668
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
669
|
+
) as record_time
|
|
670
|
+
from participant.lapi_update_meta update_meta
|
|
671
|
+
where update_meta.event_offset::text like '${quotedQuery}%'
|
|
672
|
+
or regexp_replace(update_meta.update_id::text, '^\\\\x', '') like '${quotedQuery}%'
|
|
673
|
+
order by update_meta.record_time desc, update_meta.event_offset::numeric desc
|
|
674
|
+
limit ${limit}
|
|
675
|
+
`;
|
|
676
|
+
}
|
|
677
|
+
function normalizedRecentUpdatesQuery(limit, before, after, parties, templates, partyMode, hideSplice) {
|
|
678
|
+
const normalizedBefore = normalizeEventOffsetCursor(before);
|
|
679
|
+
const normalizedAfter = normalizeEventOffsetCursor(after);
|
|
680
|
+
const filterClause = buildNormalizedUpdatesFilterClause(parties, templates, partyMode);
|
|
681
|
+
const hideSpliceClause = hideSplice ? buildNormalizedHideSpliceOffsetsClause() : null;
|
|
682
|
+
const queryLimit = limit + 1;
|
|
683
|
+
const afterFilters = [
|
|
684
|
+
normalizedAfter ? `update_meta.event_offset::numeric > ${normalizedAfter}` : null,
|
|
685
|
+
filterClause,
|
|
686
|
+
hideSpliceClause,
|
|
687
|
+
].filter((value) => Boolean(value));
|
|
688
|
+
const olderFilters = [
|
|
689
|
+
normalizedBefore ? `update_meta.event_offset::numeric < ${normalizedBefore}` : null,
|
|
690
|
+
filterClause,
|
|
691
|
+
hideSpliceClause,
|
|
692
|
+
].filter((value) => Boolean(value));
|
|
693
|
+
if (normalizedAfter && !normalizedBefore) {
|
|
694
|
+
const whereClause = afterFilters.length > 0 ? `where ${afterFilters.join('\n and ')}` : '';
|
|
695
|
+
return `
|
|
696
|
+
select
|
|
697
|
+
encode(update_meta.update_id, 'hex') as update_id,
|
|
698
|
+
update_meta.event_offset::text as event_offset,
|
|
699
|
+
to_char(
|
|
700
|
+
to_timestamp(update_meta.record_time / 1000000.0) at time zone 'UTC',
|
|
701
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
702
|
+
) as record_time
|
|
703
|
+
from participant.lapi_update_meta update_meta
|
|
704
|
+
${whereClause}
|
|
705
|
+
order by update_meta.event_offset::numeric asc
|
|
706
|
+
limit ${queryLimit}
|
|
707
|
+
`;
|
|
708
|
+
}
|
|
709
|
+
const whereClause = olderFilters.length > 0 ? `where ${olderFilters.join('\n and ')}` : '';
|
|
710
|
+
return `
|
|
711
|
+
select
|
|
712
|
+
encode(update_meta.update_id, 'hex') as update_id,
|
|
713
|
+
update_meta.event_offset::text as event_offset,
|
|
714
|
+
to_char(
|
|
715
|
+
to_timestamp(update_meta.record_time / 1000000.0) at time zone 'UTC',
|
|
716
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
717
|
+
) as record_time
|
|
718
|
+
from participant.lapi_update_meta update_meta
|
|
719
|
+
${whereClause}
|
|
720
|
+
order by update_meta.event_offset::numeric desc
|
|
721
|
+
limit ${queryLimit}
|
|
722
|
+
`;
|
|
723
|
+
}
|
|
724
|
+
function normalizedSearchUpdatesQuery(searchQuery, limit) {
|
|
725
|
+
const quotedQuery = escapeSqlLiteral(searchQuery);
|
|
726
|
+
return `
|
|
727
|
+
select
|
|
728
|
+
encode(update_meta.update_id, 'hex') as update_id,
|
|
729
|
+
update_meta.event_offset::text as event_offset,
|
|
730
|
+
to_char(
|
|
731
|
+
to_timestamp(update_meta.record_time / 1000000.0) at time zone 'UTC',
|
|
732
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
733
|
+
) as record_time
|
|
734
|
+
from participant.lapi_update_meta update_meta
|
|
735
|
+
where update_meta.event_offset::text like '${quotedQuery}%'
|
|
736
|
+
or encode(update_meta.update_id, 'hex') like '${quotedQuery}%'
|
|
737
|
+
order by update_meta.record_time desc, update_meta.event_offset::numeric desc
|
|
738
|
+
limit ${limit}
|
|
739
|
+
`;
|
|
740
|
+
}
|
|
741
|
+
function buildHideSpliceOffsetsClause() {
|
|
742
|
+
return `
|
|
743
|
+
exists (
|
|
744
|
+
select 1
|
|
745
|
+
from (
|
|
746
|
+
select create_event.template_id::text as template_id
|
|
747
|
+
from participant.lapi_events_create create_event
|
|
748
|
+
where create_event.update_id::text = update_meta.update_id::text
|
|
749
|
+
|
|
750
|
+
union all
|
|
751
|
+
|
|
752
|
+
select exercise_event.template_id::text as template_id
|
|
753
|
+
from participant.lapi_events_consuming_exercise exercise_event
|
|
754
|
+
where exercise_event.update_id::text = update_meta.update_id::text
|
|
755
|
+
|
|
756
|
+
union all
|
|
757
|
+
|
|
758
|
+
select exercise_event.template_id::text as template_id
|
|
759
|
+
from participant.lapi_events_non_consuming_exercise exercise_event
|
|
760
|
+
where exercise_event.update_id::text = update_meta.update_id::text
|
|
761
|
+
) update_event_templates
|
|
762
|
+
where update_event_templates.template_id is null
|
|
763
|
+
or update_event_templates.template_id not like 'Splice.%'
|
|
764
|
+
)
|
|
765
|
+
`;
|
|
766
|
+
}
|
|
767
|
+
function buildNormalizedHideSpliceOffsetsClause() {
|
|
768
|
+
return `
|
|
769
|
+
exists (
|
|
770
|
+
select 1
|
|
771
|
+
from (
|
|
772
|
+
select regexp_replace(coalesce(contract.template_id::text, ''), '^t\\\\|#[^:]+:', '') as template_id
|
|
773
|
+
from participant.lapi_events_activate_contract activate_event
|
|
774
|
+
left join participant.par_contracts contract
|
|
775
|
+
on contract.internal_contract_id = activate_event.internal_contract_id
|
|
776
|
+
where encode(activate_event.update_id, 'hex') = encode(update_meta.update_id, 'hex')
|
|
777
|
+
|
|
778
|
+
union all
|
|
779
|
+
|
|
780
|
+
select regexp_replace(coalesce(contract.template_id::text, ''), '^t\\\\|#[^:]+:', '') as template_id
|
|
781
|
+
from participant.lapi_events_various_witnessed various_event
|
|
782
|
+
left join participant.par_contracts contract
|
|
783
|
+
on contract.internal_contract_id = various_event.internal_contract_id
|
|
784
|
+
where encode(various_event.update_id, 'hex') = encode(update_meta.update_id, 'hex')
|
|
785
|
+
and various_event.event_type = 6
|
|
786
|
+
|
|
787
|
+
union all
|
|
788
|
+
|
|
789
|
+
select regexp_replace(coalesce(template_string.external_string, ''), '^t\\\\|#[^:]+:', '') as template_id
|
|
790
|
+
from participant.lapi_events_deactivate_contract deactivate_event
|
|
791
|
+
left join participant.lapi_string_interning template_string
|
|
792
|
+
on template_string.internal_id = deactivate_event.template_id
|
|
793
|
+
where encode(deactivate_event.update_id, 'hex') = encode(update_meta.update_id, 'hex')
|
|
794
|
+
|
|
795
|
+
union all
|
|
796
|
+
|
|
797
|
+
select regexp_replace(coalesce(template_string.external_string, ''), '^t\\\\|#[^:]+:', '') as template_id
|
|
798
|
+
from participant.lapi_events_various_witnessed various_event
|
|
799
|
+
left join participant.lapi_string_interning template_string
|
|
800
|
+
on template_string.internal_id = various_event.template_id
|
|
801
|
+
where encode(various_event.update_id, 'hex') = encode(update_meta.update_id, 'hex')
|
|
802
|
+
and various_event.event_type in (5, 7)
|
|
803
|
+
) update_event_templates
|
|
804
|
+
where update_event_templates.template_id = ''
|
|
805
|
+
or update_event_templates.template_id not like 'Splice.%'
|
|
806
|
+
)
|
|
807
|
+
`;
|
|
808
|
+
}
|
|
809
|
+
function buildQuotedPartyIdentifiers(partyId) {
|
|
810
|
+
const trimmed = partyId.trim();
|
|
811
|
+
if (!trimmed) {
|
|
812
|
+
return [];
|
|
813
|
+
}
|
|
814
|
+
const normalized = trimmed.replace(/^p\|/, '');
|
|
815
|
+
const identifiers = new Set([trimmed, normalized]);
|
|
816
|
+
if (normalized) {
|
|
817
|
+
identifiers.add(`p|${normalized}`);
|
|
818
|
+
}
|
|
819
|
+
return Array.from(identifiers).map((identifier) => `'${escapeSqlLiteral(identifier)}'`);
|
|
820
|
+
}
|
|
821
|
+
function partyWitnessArrayMatchCondition(arrayExpression, partyId) {
|
|
822
|
+
const quotedIdentifiers = buildQuotedPartyIdentifiers(partyId);
|
|
823
|
+
if (quotedIdentifiers.length === 0) {
|
|
824
|
+
return 'false';
|
|
825
|
+
}
|
|
826
|
+
return `array[${quotedIdentifiers.join(', ')}]::text[] && ${arrayExpression}::text[]`;
|
|
827
|
+
}
|
|
828
|
+
function partyScalarMatchCondition(columnExpression, partyId) {
|
|
829
|
+
const quotedIdentifiers = buildQuotedPartyIdentifiers(partyId);
|
|
830
|
+
if (quotedIdentifiers.length === 0) {
|
|
831
|
+
return 'false';
|
|
832
|
+
}
|
|
833
|
+
return `${columnExpression} in (${quotedIdentifiers.join(', ')})`;
|
|
834
|
+
}
|
|
835
|
+
function partyRecentUpdatesQuery(partyId, limit) {
|
|
836
|
+
const witnessMatch = partyWitnessArrayMatchCondition('create_event.tree_event_witnesses', partyId);
|
|
837
|
+
const consumingWitnessMatch = partyWitnessArrayMatchCondition('exercise_event.tree_event_witnesses', partyId);
|
|
838
|
+
const nonConsumingWitnessMatch = partyWitnessArrayMatchCondition('exercise_event.tree_event_witnesses', partyId);
|
|
839
|
+
return `
|
|
840
|
+
select
|
|
841
|
+
update_meta.update_id::text as update_id,
|
|
842
|
+
update_meta.event_offset::text as event_offset,
|
|
843
|
+
to_char(
|
|
844
|
+
to_timestamp(update_meta.record_time / 1000000.0) at time zone 'UTC',
|
|
845
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
846
|
+
) as record_time
|
|
847
|
+
from participant.lapi_update_meta update_meta
|
|
848
|
+
where update_meta.update_id::text in (
|
|
849
|
+
select create_event.update_id::text
|
|
850
|
+
from participant.lapi_events_create create_event
|
|
851
|
+
where ${witnessMatch}
|
|
852
|
+
|
|
853
|
+
union
|
|
854
|
+
|
|
855
|
+
select exercise_event.update_id::text
|
|
856
|
+
from participant.lapi_events_consuming_exercise exercise_event
|
|
857
|
+
where ${consumingWitnessMatch}
|
|
858
|
+
|
|
859
|
+
union
|
|
860
|
+
|
|
861
|
+
select exercise_event.update_id::text
|
|
862
|
+
from participant.lapi_events_non_consuming_exercise exercise_event
|
|
863
|
+
where ${nonConsumingWitnessMatch}
|
|
864
|
+
)
|
|
865
|
+
order by update_meta.record_time desc
|
|
866
|
+
limit ${limit}
|
|
867
|
+
`;
|
|
868
|
+
}
|
|
869
|
+
function normalizedPartyRecentUpdatesQuery(partyId, limit) {
|
|
870
|
+
const partyMatch = partyScalarMatchCondition('party_string.external_string', partyId);
|
|
871
|
+
return `
|
|
872
|
+
with matching_updates as (
|
|
873
|
+
select distinct activate_event.update_id
|
|
874
|
+
from participant.lapi_events_activate_contract activate_event
|
|
875
|
+
join participant.lapi_filter_activate_witness witness_filter
|
|
876
|
+
on witness_filter.event_sequential_id = activate_event.event_sequential_id
|
|
877
|
+
join participant.lapi_string_interning party_string
|
|
878
|
+
on party_string.internal_id = witness_filter.party_id
|
|
879
|
+
where ${partyMatch}
|
|
880
|
+
|
|
881
|
+
union
|
|
882
|
+
|
|
883
|
+
select distinct deactivate_event.update_id
|
|
884
|
+
from participant.lapi_events_deactivate_contract deactivate_event
|
|
885
|
+
join participant.lapi_filter_deactivate_witness witness_filter
|
|
886
|
+
on witness_filter.event_sequential_id = deactivate_event.event_sequential_id
|
|
887
|
+
join participant.lapi_string_interning party_string
|
|
888
|
+
on party_string.internal_id = witness_filter.party_id
|
|
889
|
+
where ${partyMatch}
|
|
890
|
+
|
|
891
|
+
union
|
|
892
|
+
|
|
893
|
+
select distinct various_event.update_id
|
|
894
|
+
from participant.lapi_events_various_witnessed various_event
|
|
895
|
+
join participant.lapi_filter_various_witness witness_filter
|
|
896
|
+
on witness_filter.event_sequential_id = various_event.event_sequential_id
|
|
897
|
+
join participant.lapi_string_interning party_string
|
|
898
|
+
on party_string.internal_id = witness_filter.party_id
|
|
899
|
+
where ${partyMatch}
|
|
900
|
+
)
|
|
901
|
+
select
|
|
902
|
+
encode(update_meta.update_id, 'hex') as update_id,
|
|
903
|
+
update_meta.event_offset::text as event_offset,
|
|
904
|
+
to_char(
|
|
905
|
+
to_timestamp(update_meta.record_time / 1000000.0) at time zone 'UTC',
|
|
906
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
907
|
+
) as record_time
|
|
908
|
+
from participant.lapi_update_meta update_meta
|
|
909
|
+
join matching_updates
|
|
910
|
+
on matching_updates.update_id = update_meta.update_id
|
|
911
|
+
order by update_meta.record_time desc
|
|
912
|
+
limit ${limit}
|
|
913
|
+
`;
|
|
914
|
+
}
|
|
915
|
+
function recentUpdatePartiesQuery(updateIds) {
|
|
916
|
+
const quotedIds = updateIds.map((updateId) => `'${escapeSqlLiteral(updateId)}'`).join(', ');
|
|
917
|
+
return `
|
|
918
|
+
select
|
|
919
|
+
update_id::text as update_id,
|
|
920
|
+
array_agg(distinct party order by party) as parties
|
|
921
|
+
from (
|
|
922
|
+
select
|
|
923
|
+
update_id,
|
|
924
|
+
unnest(tree_event_witnesses) as party
|
|
925
|
+
from participant.lapi_events_create
|
|
926
|
+
where update_id in (${quotedIds})
|
|
927
|
+
|
|
928
|
+
union
|
|
929
|
+
|
|
930
|
+
select
|
|
931
|
+
update_id,
|
|
932
|
+
unnest(tree_event_witnesses) as party
|
|
933
|
+
from participant.lapi_events_consuming_exercise
|
|
934
|
+
where update_id in (${quotedIds})
|
|
935
|
+
|
|
936
|
+
union
|
|
937
|
+
|
|
938
|
+
select
|
|
939
|
+
update_id,
|
|
940
|
+
unnest(tree_event_witnesses) as party
|
|
941
|
+
from participant.lapi_events_non_consuming_exercise
|
|
942
|
+
where update_id in (${quotedIds})
|
|
943
|
+
) update_parties
|
|
944
|
+
group by update_id
|
|
945
|
+
`;
|
|
946
|
+
}
|
|
947
|
+
function normalizedRecentUpdatePartiesQuery(updateIds) {
|
|
948
|
+
const quotedIds = updateIds
|
|
949
|
+
.map((updateId) => `'${escapeSqlLiteral(normalizeByteaHex(updateId))}'`)
|
|
950
|
+
.join(', ');
|
|
951
|
+
return `
|
|
952
|
+
select
|
|
953
|
+
update_id,
|
|
954
|
+
array_agg(distinct party order by party) as parties
|
|
955
|
+
from (
|
|
956
|
+
select
|
|
957
|
+
encode(activate_event.update_id, 'hex') as update_id,
|
|
958
|
+
party_string.external_string as party
|
|
959
|
+
from participant.lapi_events_activate_contract activate_event
|
|
960
|
+
join participant.lapi_filter_activate_witness witness_filter
|
|
961
|
+
on witness_filter.event_sequential_id = activate_event.event_sequential_id
|
|
962
|
+
join participant.lapi_string_interning party_string
|
|
963
|
+
on party_string.internal_id = witness_filter.party_id
|
|
964
|
+
where encode(activate_event.update_id, 'hex') in (${quotedIds})
|
|
965
|
+
|
|
966
|
+
union
|
|
967
|
+
|
|
968
|
+
select
|
|
969
|
+
encode(deactivate_event.update_id, 'hex') as update_id,
|
|
970
|
+
party_string.external_string as party
|
|
971
|
+
from participant.lapi_events_deactivate_contract deactivate_event
|
|
972
|
+
join participant.lapi_filter_deactivate_witness witness_filter
|
|
973
|
+
on witness_filter.event_sequential_id = deactivate_event.event_sequential_id
|
|
974
|
+
join participant.lapi_string_interning party_string
|
|
975
|
+
on party_string.internal_id = witness_filter.party_id
|
|
976
|
+
where encode(deactivate_event.update_id, 'hex') in (${quotedIds})
|
|
977
|
+
|
|
978
|
+
union
|
|
979
|
+
|
|
980
|
+
select
|
|
981
|
+
encode(various_event.update_id, 'hex') as update_id,
|
|
982
|
+
party_string.external_string as party
|
|
983
|
+
from participant.lapi_events_various_witnessed various_event
|
|
984
|
+
join participant.lapi_filter_various_witness witness_filter
|
|
985
|
+
on witness_filter.event_sequential_id = various_event.event_sequential_id
|
|
986
|
+
join participant.lapi_string_interning party_string
|
|
987
|
+
on party_string.internal_id = witness_filter.party_id
|
|
988
|
+
where encode(various_event.update_id, 'hex') in (${quotedIds})
|
|
989
|
+
) update_parties
|
|
990
|
+
group by update_id
|
|
991
|
+
`;
|
|
992
|
+
}
|
|
993
|
+
function activePartiesQuery() {
|
|
994
|
+
return `
|
|
995
|
+
select
|
|
996
|
+
array_agg(distinct party order by party) as parties
|
|
997
|
+
from (
|
|
998
|
+
select unnest(tree_event_witnesses) as party
|
|
999
|
+
from participant.lapi_events_create
|
|
1000
|
+
|
|
1001
|
+
union
|
|
1002
|
+
|
|
1003
|
+
select unnest(tree_event_witnesses) as party
|
|
1004
|
+
from participant.lapi_events_consuming_exercise
|
|
1005
|
+
|
|
1006
|
+
union
|
|
1007
|
+
|
|
1008
|
+
select unnest(tree_event_witnesses) as party
|
|
1009
|
+
from participant.lapi_events_non_consuming_exercise
|
|
1010
|
+
) observed_parties
|
|
1011
|
+
`;
|
|
1012
|
+
}
|
|
1013
|
+
function normalizedActivePartiesQuery() {
|
|
1014
|
+
return `
|
|
1015
|
+
select
|
|
1016
|
+
array_agg(distinct party order by party) as parties
|
|
1017
|
+
from (
|
|
1018
|
+
select party_string.external_string as party
|
|
1019
|
+
from participant.lapi_filter_activate_witness witness_filter
|
|
1020
|
+
join participant.lapi_string_interning party_string
|
|
1021
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1022
|
+
|
|
1023
|
+
union
|
|
1024
|
+
|
|
1025
|
+
select party_string.external_string as party
|
|
1026
|
+
from participant.lapi_filter_deactivate_witness witness_filter
|
|
1027
|
+
join participant.lapi_string_interning party_string
|
|
1028
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1029
|
+
|
|
1030
|
+
union
|
|
1031
|
+
|
|
1032
|
+
select party_string.external_string as party
|
|
1033
|
+
from participant.lapi_filter_various_witness witness_filter
|
|
1034
|
+
join participant.lapi_string_interning party_string
|
|
1035
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1036
|
+
) observed_parties
|
|
1037
|
+
`;
|
|
1038
|
+
}
|
|
1039
|
+
function partyRecentContractsQuery(partyId, limit, before, after) {
|
|
1040
|
+
const witnessMatch = partyWitnessArrayMatchCondition('create_event.tree_event_witnesses', partyId);
|
|
1041
|
+
const normalizedBefore = normalizeEventOffsetCursor(before);
|
|
1042
|
+
const normalizedAfter = normalizeEventOffsetCursor(after);
|
|
1043
|
+
const queryLimit = limit + 1;
|
|
1044
|
+
const afterClause = normalizedAfter ? `and create_event.event_offset::numeric > ${normalizedAfter}` : '';
|
|
1045
|
+
const beforeClause = normalizedBefore ? `and create_event.event_offset::numeric < ${normalizedBefore}` : '';
|
|
1046
|
+
if (normalizedAfter && !normalizedBefore) {
|
|
1047
|
+
return `
|
|
1048
|
+
select
|
|
1049
|
+
create_event.contract_id::text as contract_id,
|
|
1050
|
+
create_event.template_id::text as template_id,
|
|
1051
|
+
null::text as package_id,
|
|
1052
|
+
to_char(
|
|
1053
|
+
to_timestamp(update_meta.record_time / 1000000.0) at time zone 'UTC',
|
|
1054
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1055
|
+
) as record_time,
|
|
1056
|
+
create_event.event_offset::text as created_event_offset
|
|
1057
|
+
from participant.lapi_events_create create_event
|
|
1058
|
+
join participant.lapi_update_meta update_meta
|
|
1059
|
+
on update_meta.update_id = create_event.update_id
|
|
1060
|
+
where ${witnessMatch}
|
|
1061
|
+
${afterClause}
|
|
1062
|
+
order by create_event.event_offset::numeric asc
|
|
1063
|
+
limit ${queryLimit}
|
|
1064
|
+
`;
|
|
1065
|
+
}
|
|
1066
|
+
return `
|
|
1067
|
+
select
|
|
1068
|
+
create_event.contract_id::text as contract_id,
|
|
1069
|
+
create_event.template_id::text as template_id,
|
|
1070
|
+
null::text as package_id,
|
|
1071
|
+
to_char(
|
|
1072
|
+
to_timestamp(update_meta.record_time / 1000000.0) at time zone 'UTC',
|
|
1073
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1074
|
+
) as record_time
|
|
1075
|
+
,
|
|
1076
|
+
create_event.event_offset::text as created_event_offset
|
|
1077
|
+
from participant.lapi_events_create create_event
|
|
1078
|
+
join participant.lapi_update_meta update_meta
|
|
1079
|
+
on update_meta.update_id = create_event.update_id
|
|
1080
|
+
where ${witnessMatch}
|
|
1081
|
+
${beforeClause}
|
|
1082
|
+
order by create_event.event_offset::numeric desc
|
|
1083
|
+
limit ${queryLimit}
|
|
1084
|
+
`;
|
|
1085
|
+
}
|
|
1086
|
+
function normalizedPartyRecentContractsQuery(partyId, limit, before, after) {
|
|
1087
|
+
const partyMatch = partyScalarMatchCondition('party_string.external_string', partyId);
|
|
1088
|
+
const normalizedBefore = normalizeEventOffsetCursor(before);
|
|
1089
|
+
const normalizedAfter = normalizeEventOffsetCursor(after);
|
|
1090
|
+
const queryLimit = limit + 1;
|
|
1091
|
+
const afterFilter = normalizedAfter && !normalizedBefore
|
|
1092
|
+
? `where created_event_offset::numeric > ${normalizedAfter}`
|
|
1093
|
+
: normalizedBefore
|
|
1094
|
+
? `where created_event_offset::numeric < ${normalizedBefore}`
|
|
1095
|
+
: '';
|
|
1096
|
+
const sortDirection = normalizedAfter && !normalizedBefore ? 'asc' : 'desc';
|
|
1097
|
+
return `
|
|
1098
|
+
select
|
|
1099
|
+
contract_id,
|
|
1100
|
+
template_id,
|
|
1101
|
+
package_id,
|
|
1102
|
+
record_time,
|
|
1103
|
+
created_event_offset
|
|
1104
|
+
from (
|
|
1105
|
+
select
|
|
1106
|
+
encode(contract.contract_id, 'hex') as contract_id,
|
|
1107
|
+
contract.template_id::text as template_id,
|
|
1108
|
+
package_string.external_string as package_id,
|
|
1109
|
+
to_char(
|
|
1110
|
+
to_timestamp(activate_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1111
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1112
|
+
) as record_time,
|
|
1113
|
+
activate_event.event_sequential_id as sort_event_sequential_id,
|
|
1114
|
+
activate_event.event_offset::text as created_event_offset
|
|
1115
|
+
from participant.lapi_events_activate_contract activate_event
|
|
1116
|
+
join participant.lapi_filter_activate_witness witness_filter
|
|
1117
|
+
on witness_filter.event_sequential_id = activate_event.event_sequential_id
|
|
1118
|
+
join participant.lapi_string_interning party_string
|
|
1119
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1120
|
+
left join participant.par_contracts contract
|
|
1121
|
+
on contract.internal_contract_id = activate_event.internal_contract_id
|
|
1122
|
+
left join participant.lapi_string_interning package_string
|
|
1123
|
+
on package_string.internal_id = activate_event.representative_package_id
|
|
1124
|
+
where ${partyMatch}
|
|
1125
|
+
|
|
1126
|
+
union all
|
|
1127
|
+
|
|
1128
|
+
select
|
|
1129
|
+
encode(contract.contract_id, 'hex') as contract_id,
|
|
1130
|
+
contract.template_id::text as template_id,
|
|
1131
|
+
package_string.external_string as package_id,
|
|
1132
|
+
to_char(
|
|
1133
|
+
to_timestamp(various_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1134
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1135
|
+
) as record_time,
|
|
1136
|
+
various_event.event_sequential_id as sort_event_sequential_id,
|
|
1137
|
+
various_event.event_offset::text as created_event_offset
|
|
1138
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1139
|
+
join participant.lapi_filter_various_witness witness_filter
|
|
1140
|
+
on witness_filter.event_sequential_id = various_event.event_sequential_id
|
|
1141
|
+
join participant.lapi_string_interning party_string
|
|
1142
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1143
|
+
left join participant.par_contracts contract
|
|
1144
|
+
on contract.internal_contract_id = various_event.internal_contract_id
|
|
1145
|
+
left join participant.lapi_string_interning package_string
|
|
1146
|
+
on package_string.internal_id = various_event.package_id
|
|
1147
|
+
where ${partyMatch}
|
|
1148
|
+
and various_event.event_type = 6
|
|
1149
|
+
) recent_contracts
|
|
1150
|
+
${afterFilter}
|
|
1151
|
+
order by created_event_offset::numeric ${sortDirection}
|
|
1152
|
+
limit ${queryLimit}
|
|
1153
|
+
`;
|
|
1154
|
+
}
|
|
1155
|
+
function singleUpdateQuery(eventOffset) {
|
|
1156
|
+
const quotedOffset = `'${escapeSqlLiteral(eventOffset)}'`;
|
|
1157
|
+
return `
|
|
1158
|
+
select
|
|
1159
|
+
update_meta.update_id::text as update_id,
|
|
1160
|
+
update_meta.event_offset::text as event_offset,
|
|
1161
|
+
to_char(
|
|
1162
|
+
to_timestamp(update_meta.record_time / 1000000.0) at time zone 'UTC',
|
|
1163
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1164
|
+
) as record_time_iso,
|
|
1165
|
+
to_jsonb(update_meta) as meta
|
|
1166
|
+
from participant.lapi_update_meta update_meta
|
|
1167
|
+
where update_meta.event_offset::text = ${quotedOffset}
|
|
1168
|
+
order by update_meta.record_time desc
|
|
1169
|
+
limit 1
|
|
1170
|
+
`;
|
|
1171
|
+
}
|
|
1172
|
+
function updateEventsQuery(updateId) {
|
|
1173
|
+
const quotedId = `'${escapeSqlLiteral(updateId)}'`;
|
|
1174
|
+
return `
|
|
1175
|
+
select
|
|
1176
|
+
event_kind,
|
|
1177
|
+
event_id,
|
|
1178
|
+
contract_id,
|
|
1179
|
+
template_id,
|
|
1180
|
+
package_id,
|
|
1181
|
+
choice,
|
|
1182
|
+
witnesses,
|
|
1183
|
+
contract_instance,
|
|
1184
|
+
exercise_argument,
|
|
1185
|
+
exercise_result,
|
|
1186
|
+
raw
|
|
1187
|
+
from (
|
|
1188
|
+
select
|
|
1189
|
+
'create'::text as event_kind,
|
|
1190
|
+
create_event.event_id::text as event_id,
|
|
1191
|
+
create_event.contract_id::text as contract_id,
|
|
1192
|
+
create_event.template_id::text as template_id,
|
|
1193
|
+
null::text as package_id,
|
|
1194
|
+
null::text as choice,
|
|
1195
|
+
create_event.tree_event_witnesses as witnesses,
|
|
1196
|
+
null::bytea as contract_instance,
|
|
1197
|
+
null::bytea as exercise_argument,
|
|
1198
|
+
null::bytea as exercise_result,
|
|
1199
|
+
to_jsonb(create_event) as raw
|
|
1200
|
+
from participant.lapi_events_create create_event
|
|
1201
|
+
where create_event.update_id::text = ${quotedId}
|
|
1202
|
+
|
|
1203
|
+
union all
|
|
1204
|
+
|
|
1205
|
+
select
|
|
1206
|
+
'consuming_exercise'::text as event_kind,
|
|
1207
|
+
exercise_event.event_id::text as event_id,
|
|
1208
|
+
exercise_event.contract_id::text as contract_id,
|
|
1209
|
+
exercise_event.template_id::text as template_id,
|
|
1210
|
+
null::text as package_id,
|
|
1211
|
+
exercise_event.choice::text as choice,
|
|
1212
|
+
exercise_event.tree_event_witnesses as witnesses,
|
|
1213
|
+
null::bytea as contract_instance,
|
|
1214
|
+
null::bytea as exercise_argument,
|
|
1215
|
+
null::bytea as exercise_result,
|
|
1216
|
+
to_jsonb(exercise_event) as raw
|
|
1217
|
+
from participant.lapi_events_consuming_exercise exercise_event
|
|
1218
|
+
where exercise_event.update_id::text = ${quotedId}
|
|
1219
|
+
|
|
1220
|
+
union all
|
|
1221
|
+
|
|
1222
|
+
select
|
|
1223
|
+
'non_consuming_exercise'::text as event_kind,
|
|
1224
|
+
exercise_event.event_id::text as event_id,
|
|
1225
|
+
exercise_event.contract_id::text as contract_id,
|
|
1226
|
+
exercise_event.template_id::text as template_id,
|
|
1227
|
+
null::text as package_id,
|
|
1228
|
+
exercise_event.choice::text as choice,
|
|
1229
|
+
exercise_event.tree_event_witnesses as witnesses,
|
|
1230
|
+
null::bytea as contract_instance,
|
|
1231
|
+
null::bytea as exercise_argument,
|
|
1232
|
+
null::bytea as exercise_result,
|
|
1233
|
+
to_jsonb(exercise_event) as raw
|
|
1234
|
+
from participant.lapi_events_non_consuming_exercise exercise_event
|
|
1235
|
+
where exercise_event.update_id::text = ${quotedId}
|
|
1236
|
+
) update_events
|
|
1237
|
+
order by event_id asc, event_kind asc, contract_id asc, template_id asc
|
|
1238
|
+
`;
|
|
1239
|
+
}
|
|
1240
|
+
function normalizedUpdateEventsQuery(updateId) {
|
|
1241
|
+
const quotedId = `'${escapeSqlLiteral(normalizeByteaHex(updateId))}'`;
|
|
1242
|
+
return `
|
|
1243
|
+
with activate_witnesses as (
|
|
1244
|
+
select
|
|
1245
|
+
witness_filter.event_sequential_id,
|
|
1246
|
+
array_agg(distinct party_string.external_string order by party_string.external_string) as witnesses
|
|
1247
|
+
from participant.lapi_filter_activate_witness witness_filter
|
|
1248
|
+
join participant.lapi_string_interning party_string
|
|
1249
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1250
|
+
group by witness_filter.event_sequential_id
|
|
1251
|
+
),
|
|
1252
|
+
deactivate_witnesses as (
|
|
1253
|
+
select
|
|
1254
|
+
witness_filter.event_sequential_id,
|
|
1255
|
+
array_agg(distinct party_string.external_string order by party_string.external_string) as witnesses
|
|
1256
|
+
from participant.lapi_filter_deactivate_witness witness_filter
|
|
1257
|
+
join participant.lapi_string_interning party_string
|
|
1258
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1259
|
+
group by witness_filter.event_sequential_id
|
|
1260
|
+
),
|
|
1261
|
+
various_witnesses as (
|
|
1262
|
+
select
|
|
1263
|
+
witness_filter.event_sequential_id,
|
|
1264
|
+
array_agg(distinct party_string.external_string order by party_string.external_string) as witnesses
|
|
1265
|
+
from participant.lapi_filter_various_witness witness_filter
|
|
1266
|
+
join participant.lapi_string_interning party_string
|
|
1267
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1268
|
+
group by witness_filter.event_sequential_id
|
|
1269
|
+
)
|
|
1270
|
+
select
|
|
1271
|
+
event_kind,
|
|
1272
|
+
event_id,
|
|
1273
|
+
contract_id,
|
|
1274
|
+
template_id,
|
|
1275
|
+
package_id,
|
|
1276
|
+
choice,
|
|
1277
|
+
witnesses,
|
|
1278
|
+
contract_instance,
|
|
1279
|
+
exercise_argument,
|
|
1280
|
+
exercise_result,
|
|
1281
|
+
raw
|
|
1282
|
+
from (
|
|
1283
|
+
select
|
|
1284
|
+
'create'::text as event_kind,
|
|
1285
|
+
'#0:' || activate_event.node_id::text as event_id,
|
|
1286
|
+
encode(contract.contract_id, 'hex') as contract_id,
|
|
1287
|
+
contract.template_id::text as template_id,
|
|
1288
|
+
package_string.external_string as package_id,
|
|
1289
|
+
null::text as choice,
|
|
1290
|
+
coalesce(activate_witnesses.witnesses, array[]::text[]) as witnesses,
|
|
1291
|
+
contract.instance as contract_instance,
|
|
1292
|
+
null::bytea as exercise_argument,
|
|
1293
|
+
null::bytea as exercise_result,
|
|
1294
|
+
jsonb_build_object(
|
|
1295
|
+
'source_table', 'participant.lapi_events_activate_contract',
|
|
1296
|
+
'update_id', encode(activate_event.update_id, 'hex'),
|
|
1297
|
+
'event_offset', activate_event.event_offset,
|
|
1298
|
+
'event_type', activate_event.event_type,
|
|
1299
|
+
'event_sequential_id', activate_event.event_sequential_id,
|
|
1300
|
+
'node_id', activate_event.node_id,
|
|
1301
|
+
'contract_id', encode(contract.contract_id, 'hex'),
|
|
1302
|
+
'template_id', contract.template_id,
|
|
1303
|
+
'package_id', package_string.external_string
|
|
1304
|
+
) as raw,
|
|
1305
|
+
activate_event.event_sequential_id as sort_event_sequential_id
|
|
1306
|
+
from participant.lapi_events_activate_contract activate_event
|
|
1307
|
+
left join participant.par_contracts contract
|
|
1308
|
+
on contract.internal_contract_id = activate_event.internal_contract_id
|
|
1309
|
+
left join participant.lapi_string_interning package_string
|
|
1310
|
+
on package_string.internal_id = activate_event.representative_package_id
|
|
1311
|
+
left join activate_witnesses
|
|
1312
|
+
on activate_witnesses.event_sequential_id = activate_event.event_sequential_id
|
|
1313
|
+
where encode(activate_event.update_id, 'hex') = ${quotedId}
|
|
1314
|
+
|
|
1315
|
+
union all
|
|
1316
|
+
|
|
1317
|
+
select
|
|
1318
|
+
'create'::text as event_kind,
|
|
1319
|
+
'#0:' || various_event.node_id::text as event_id,
|
|
1320
|
+
encode(contract.contract_id, 'hex') as contract_id,
|
|
1321
|
+
contract.template_id::text as template_id,
|
|
1322
|
+
package_string.external_string as package_id,
|
|
1323
|
+
null::text as choice,
|
|
1324
|
+
coalesce(various_witnesses.witnesses, array[]::text[]) as witnesses,
|
|
1325
|
+
contract.instance as contract_instance,
|
|
1326
|
+
null::bytea as exercise_argument,
|
|
1327
|
+
null::bytea as exercise_result,
|
|
1328
|
+
jsonb_build_object(
|
|
1329
|
+
'source_table', 'participant.lapi_events_various_witnessed',
|
|
1330
|
+
'update_id', encode(various_event.update_id, 'hex'),
|
|
1331
|
+
'event_offset', various_event.event_offset,
|
|
1332
|
+
'event_type', various_event.event_type,
|
|
1333
|
+
'event_sequential_id', various_event.event_sequential_id,
|
|
1334
|
+
'node_id', various_event.node_id,
|
|
1335
|
+
'contract_id', encode(contract.contract_id, 'hex'),
|
|
1336
|
+
'template_id', contract.template_id,
|
|
1337
|
+
'package_id', package_string.external_string
|
|
1338
|
+
) as raw,
|
|
1339
|
+
various_event.event_sequential_id as sort_event_sequential_id
|
|
1340
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1341
|
+
left join participant.par_contracts contract
|
|
1342
|
+
on contract.internal_contract_id = various_event.internal_contract_id
|
|
1343
|
+
left join participant.lapi_string_interning package_string
|
|
1344
|
+
on package_string.internal_id = various_event.package_id
|
|
1345
|
+
left join various_witnesses
|
|
1346
|
+
on various_witnesses.event_sequential_id = various_event.event_sequential_id
|
|
1347
|
+
where encode(various_event.update_id, 'hex') = ${quotedId}
|
|
1348
|
+
and various_event.event_type = 6
|
|
1349
|
+
|
|
1350
|
+
union all
|
|
1351
|
+
|
|
1352
|
+
select
|
|
1353
|
+
'consuming_exercise'::text as event_kind,
|
|
1354
|
+
'#0:' || deactivate_event.node_id::text as event_id,
|
|
1355
|
+
encode(deactivate_event.contract_id, 'hex') as contract_id,
|
|
1356
|
+
template_string.external_string as template_id,
|
|
1357
|
+
package_string.external_string as package_id,
|
|
1358
|
+
choice_string.external_string as choice,
|
|
1359
|
+
coalesce(deactivate_witnesses.witnesses, array[]::text[]) as witnesses,
|
|
1360
|
+
null::bytea as contract_instance,
|
|
1361
|
+
deactivate_event.exercise_argument as exercise_argument,
|
|
1362
|
+
deactivate_event.exercise_result as exercise_result,
|
|
1363
|
+
jsonb_build_object(
|
|
1364
|
+
'source_table', 'participant.lapi_events_deactivate_contract',
|
|
1365
|
+
'update_id', encode(deactivate_event.update_id, 'hex'),
|
|
1366
|
+
'event_offset', deactivate_event.event_offset,
|
|
1367
|
+
'event_type', deactivate_event.event_type,
|
|
1368
|
+
'event_sequential_id', deactivate_event.event_sequential_id,
|
|
1369
|
+
'node_id', deactivate_event.node_id,
|
|
1370
|
+
'contract_id', encode(deactivate_event.contract_id, 'hex'),
|
|
1371
|
+
'template_id', template_string.external_string,
|
|
1372
|
+
'package_id', package_string.external_string,
|
|
1373
|
+
'choice', choice_string.external_string
|
|
1374
|
+
) as raw,
|
|
1375
|
+
deactivate_event.event_sequential_id as sort_event_sequential_id
|
|
1376
|
+
from participant.lapi_events_deactivate_contract deactivate_event
|
|
1377
|
+
left join participant.lapi_string_interning template_string
|
|
1378
|
+
on template_string.internal_id = deactivate_event.template_id
|
|
1379
|
+
left join participant.lapi_string_interning package_string
|
|
1380
|
+
on package_string.internal_id = deactivate_event.package_id
|
|
1381
|
+
left join participant.lapi_string_interning choice_string
|
|
1382
|
+
on choice_string.internal_id = deactivate_event.exercise_choice
|
|
1383
|
+
left join deactivate_witnesses
|
|
1384
|
+
on deactivate_witnesses.event_sequential_id = deactivate_event.event_sequential_id
|
|
1385
|
+
where encode(deactivate_event.update_id, 'hex') = ${quotedId}
|
|
1386
|
+
|
|
1387
|
+
union all
|
|
1388
|
+
|
|
1389
|
+
select
|
|
1390
|
+
case
|
|
1391
|
+
when various_event.consuming then 'consuming_exercise'::text
|
|
1392
|
+
else 'non_consuming_exercise'::text
|
|
1393
|
+
end as event_kind,
|
|
1394
|
+
'#0:' || various_event.node_id::text as event_id,
|
|
1395
|
+
encode(various_event.contract_id, 'hex') as contract_id,
|
|
1396
|
+
template_string.external_string as template_id,
|
|
1397
|
+
package_string.external_string as package_id,
|
|
1398
|
+
choice_string.external_string as choice,
|
|
1399
|
+
coalesce(various_witnesses.witnesses, array[]::text[]) as witnesses,
|
|
1400
|
+
null::bytea as contract_instance,
|
|
1401
|
+
various_event.exercise_argument as exercise_argument,
|
|
1402
|
+
various_event.exercise_result as exercise_result,
|
|
1403
|
+
jsonb_build_object(
|
|
1404
|
+
'source_table', 'participant.lapi_events_various_witnessed',
|
|
1405
|
+
'update_id', encode(various_event.update_id, 'hex'),
|
|
1406
|
+
'event_offset', various_event.event_offset,
|
|
1407
|
+
'event_type', various_event.event_type,
|
|
1408
|
+
'event_sequential_id', various_event.event_sequential_id,
|
|
1409
|
+
'node_id', various_event.node_id,
|
|
1410
|
+
'consuming', various_event.consuming,
|
|
1411
|
+
'contract_id', encode(various_event.contract_id, 'hex'),
|
|
1412
|
+
'template_id', template_string.external_string,
|
|
1413
|
+
'package_id', package_string.external_string,
|
|
1414
|
+
'choice', choice_string.external_string
|
|
1415
|
+
) as raw,
|
|
1416
|
+
various_event.event_sequential_id as sort_event_sequential_id
|
|
1417
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1418
|
+
left join participant.lapi_string_interning template_string
|
|
1419
|
+
on template_string.internal_id = various_event.template_id
|
|
1420
|
+
left join participant.lapi_string_interning package_string
|
|
1421
|
+
on package_string.internal_id = various_event.package_id
|
|
1422
|
+
left join participant.lapi_string_interning choice_string
|
|
1423
|
+
on choice_string.internal_id = various_event.exercise_choice
|
|
1424
|
+
left join various_witnesses
|
|
1425
|
+
on various_witnesses.event_sequential_id = various_event.event_sequential_id
|
|
1426
|
+
where encode(various_event.update_id, 'hex') = ${quotedId}
|
|
1427
|
+
and various_event.event_type in (5, 7)
|
|
1428
|
+
) update_events
|
|
1429
|
+
order by sort_event_sequential_id asc, event_kind asc
|
|
1430
|
+
`;
|
|
1431
|
+
}
|
|
1432
|
+
function rewardCouponInstanceQuery(updateId) {
|
|
1433
|
+
const quotedId = `'${escapeSqlLiteral(normalizeByteaHex(updateId))}'`;
|
|
1434
|
+
return `
|
|
1435
|
+
select
|
|
1436
|
+
coupon_contract_id,
|
|
1437
|
+
contract_instance
|
|
1438
|
+
from (
|
|
1439
|
+
select
|
|
1440
|
+
encode(contract.contract_id, 'hex') as coupon_contract_id,
|
|
1441
|
+
contract.instance as contract_instance,
|
|
1442
|
+
activate_event.event_sequential_id as sort_event_sequential_id
|
|
1443
|
+
from participant.lapi_events_activate_contract activate_event
|
|
1444
|
+
join participant.par_contracts contract
|
|
1445
|
+
on contract.internal_contract_id = activate_event.internal_contract_id
|
|
1446
|
+
where encode(activate_event.update_id, 'hex') = ${quotedId}
|
|
1447
|
+
and contract.template_id = 'Splice.Amulet:SvRewardCoupon'
|
|
1448
|
+
|
|
1449
|
+
union all
|
|
1450
|
+
|
|
1451
|
+
select
|
|
1452
|
+
encode(contract.contract_id, 'hex') as coupon_contract_id,
|
|
1453
|
+
contract.instance as contract_instance,
|
|
1454
|
+
various_event.event_sequential_id as sort_event_sequential_id
|
|
1455
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1456
|
+
join participant.par_contracts contract
|
|
1457
|
+
on contract.internal_contract_id = various_event.internal_contract_id
|
|
1458
|
+
where encode(various_event.update_id, 'hex') = ${quotedId}
|
|
1459
|
+
and various_event.event_type = 6
|
|
1460
|
+
and contract.template_id = 'Splice.Amulet:SvRewardCoupon'
|
|
1461
|
+
) reward_coupon_events
|
|
1462
|
+
order by sort_event_sequential_id desc
|
|
1463
|
+
limit 1
|
|
1464
|
+
`;
|
|
1465
|
+
}
|
|
1466
|
+
function contractDetailQuery(contractId) {
|
|
1467
|
+
const quotedId = `'${escapeSqlLiteral(normalizeByteaHex(contractId))}'`;
|
|
1468
|
+
return `
|
|
1469
|
+
with contract_row as (
|
|
1470
|
+
select
|
|
1471
|
+
internal_contract_id,
|
|
1472
|
+
encode(contract_id, 'hex') as contract_id,
|
|
1473
|
+
package_id,
|
|
1474
|
+
template_id,
|
|
1475
|
+
instance
|
|
1476
|
+
from participant.par_contracts
|
|
1477
|
+
where encode(contract_id, 'hex') = ${quotedId}
|
|
1478
|
+
limit 1
|
|
1479
|
+
),
|
|
1480
|
+
create_events as (
|
|
1481
|
+
select
|
|
1482
|
+
encode(activate_event.update_id, 'hex') as update_id,
|
|
1483
|
+
activate_event.event_offset::text as event_offset,
|
|
1484
|
+
to_char(
|
|
1485
|
+
to_timestamp(activate_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1486
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1487
|
+
) as record_time,
|
|
1488
|
+
activate_event.event_sequential_id
|
|
1489
|
+
from participant.lapi_events_activate_contract activate_event
|
|
1490
|
+
join contract_row contract_row
|
|
1491
|
+
on contract_row.internal_contract_id = activate_event.internal_contract_id
|
|
1492
|
+
|
|
1493
|
+
union all
|
|
1494
|
+
|
|
1495
|
+
select
|
|
1496
|
+
encode(various_event.update_id, 'hex') as update_id,
|
|
1497
|
+
various_event.event_offset::text as event_offset,
|
|
1498
|
+
to_char(
|
|
1499
|
+
to_timestamp(various_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1500
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1501
|
+
) as record_time,
|
|
1502
|
+
various_event.event_sequential_id
|
|
1503
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1504
|
+
join contract_row contract_row
|
|
1505
|
+
on contract_row.internal_contract_id = various_event.internal_contract_id
|
|
1506
|
+
where various_event.event_type = 6
|
|
1507
|
+
),
|
|
1508
|
+
archive_events as (
|
|
1509
|
+
select
|
|
1510
|
+
encode(deactivate_event.update_id, 'hex') as update_id,
|
|
1511
|
+
deactivate_event.event_offset::text as event_offset,
|
|
1512
|
+
to_char(
|
|
1513
|
+
to_timestamp(deactivate_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1514
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1515
|
+
) as record_time,
|
|
1516
|
+
deactivate_event.event_sequential_id
|
|
1517
|
+
from participant.lapi_events_deactivate_contract deactivate_event
|
|
1518
|
+
join contract_row contract_row
|
|
1519
|
+
on contract_row.internal_contract_id = deactivate_event.internal_contract_id
|
|
1520
|
+
|
|
1521
|
+
union all
|
|
1522
|
+
|
|
1523
|
+
select
|
|
1524
|
+
encode(various_event.update_id, 'hex') as update_id,
|
|
1525
|
+
various_event.event_offset::text as event_offset,
|
|
1526
|
+
to_char(
|
|
1527
|
+
to_timestamp(various_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1528
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1529
|
+
) as record_time,
|
|
1530
|
+
various_event.event_sequential_id
|
|
1531
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1532
|
+
join contract_row contract_row
|
|
1533
|
+
on contract_row.internal_contract_id = various_event.internal_contract_id
|
|
1534
|
+
where various_event.event_type in (5, 7)
|
|
1535
|
+
and various_event.consuming
|
|
1536
|
+
)
|
|
1537
|
+
select
|
|
1538
|
+
contract_row.contract_id,
|
|
1539
|
+
contract_row.template_id,
|
|
1540
|
+
contract_row.package_id,
|
|
1541
|
+
contract_row.instance as contract_instance,
|
|
1542
|
+
created_event.update_id as created_update_id,
|
|
1543
|
+
created_event.event_offset as created_event_offset,
|
|
1544
|
+
created_event.record_time as created_record_time,
|
|
1545
|
+
archived_event.update_id as archived_update_id,
|
|
1546
|
+
archived_event.event_offset as archived_event_offset,
|
|
1547
|
+
archived_event.record_time as archived_record_time
|
|
1548
|
+
from contract_row
|
|
1549
|
+
left join lateral (
|
|
1550
|
+
select
|
|
1551
|
+
update_id,
|
|
1552
|
+
event_offset,
|
|
1553
|
+
record_time
|
|
1554
|
+
from create_events
|
|
1555
|
+
order by event_sequential_id asc
|
|
1556
|
+
limit 1
|
|
1557
|
+
) created_event on true
|
|
1558
|
+
left join lateral (
|
|
1559
|
+
select
|
|
1560
|
+
update_id,
|
|
1561
|
+
event_offset,
|
|
1562
|
+
record_time
|
|
1563
|
+
from archive_events
|
|
1564
|
+
order by event_sequential_id asc
|
|
1565
|
+
limit 1
|
|
1566
|
+
) archived_event on true
|
|
1567
|
+
`;
|
|
1568
|
+
}
|
|
1569
|
+
function tokenRowsQuery(limit, templateIds, templatePatterns = []) {
|
|
1570
|
+
const normalizedLimit = Number.isFinite(limit) && Number(limit) > 0 ? Math.trunc(limit) : TOKEN_TRANSFER_CACHE_LIMIT;
|
|
1571
|
+
const quotedTemplateIds = templateIds
|
|
1572
|
+
.map((templateId) => `'${escapeSqlLiteral(templateId)}'`)
|
|
1573
|
+
.join(',\n ');
|
|
1574
|
+
const patternClauses = templatePatterns.map((pattern) => `contract.template_id::text like '${escapeSqlLiteral(pattern)}'`);
|
|
1575
|
+
const templateFilterClause = [
|
|
1576
|
+
quotedTemplateIds ? `contract.template_id in (\n ${quotedTemplateIds}\n )` : null,
|
|
1577
|
+
...patternClauses,
|
|
1578
|
+
]
|
|
1579
|
+
.filter((clause) => clause !== null)
|
|
1580
|
+
.join('\n or ');
|
|
1581
|
+
return `
|
|
1582
|
+
select
|
|
1583
|
+
contract_id,
|
|
1584
|
+
update_id,
|
|
1585
|
+
event_offset,
|
|
1586
|
+
record_time,
|
|
1587
|
+
template_id,
|
|
1588
|
+
package_id,
|
|
1589
|
+
contract_instance
|
|
1590
|
+
from (
|
|
1591
|
+
select
|
|
1592
|
+
encode(contract.contract_id, 'hex') as contract_id,
|
|
1593
|
+
encode(activate_event.update_id, 'hex') as update_id,
|
|
1594
|
+
activate_event.event_offset::text as event_offset,
|
|
1595
|
+
to_char(
|
|
1596
|
+
to_timestamp(activate_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1597
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1598
|
+
) as record_time,
|
|
1599
|
+
contract.template_id::text as template_id,
|
|
1600
|
+
package_string.external_string as package_id,
|
|
1601
|
+
contract.instance as contract_instance
|
|
1602
|
+
from participant.lapi_events_activate_contract activate_event
|
|
1603
|
+
join participant.par_contracts contract
|
|
1604
|
+
on contract.internal_contract_id = activate_event.internal_contract_id
|
|
1605
|
+
left join participant.lapi_string_interning package_string
|
|
1606
|
+
on package_string.internal_id = activate_event.representative_package_id
|
|
1607
|
+
where ${templateFilterClause}
|
|
1608
|
+
|
|
1609
|
+
union all
|
|
1610
|
+
|
|
1611
|
+
select
|
|
1612
|
+
encode(contract.contract_id, 'hex') as contract_id,
|
|
1613
|
+
encode(various_event.update_id, 'hex') as update_id,
|
|
1614
|
+
various_event.event_offset::text as event_offset,
|
|
1615
|
+
to_char(
|
|
1616
|
+
to_timestamp(various_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1617
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1618
|
+
) as record_time,
|
|
1619
|
+
contract.template_id::text as template_id,
|
|
1620
|
+
package_string.external_string as package_id,
|
|
1621
|
+
contract.instance as contract_instance
|
|
1622
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1623
|
+
join participant.par_contracts contract
|
|
1624
|
+
on contract.internal_contract_id = various_event.internal_contract_id
|
|
1625
|
+
left join participant.lapi_string_interning package_string
|
|
1626
|
+
on package_string.internal_id = various_event.package_id
|
|
1627
|
+
where various_event.event_type = 6
|
|
1628
|
+
and (${templateFilterClause})
|
|
1629
|
+
) token_transfer_rows
|
|
1630
|
+
order by event_offset::numeric desc
|
|
1631
|
+
limit ${normalizedLimit}
|
|
1632
|
+
`;
|
|
1633
|
+
}
|
|
1634
|
+
function recentCip112MovementUpdateIdsQuery(limit) {
|
|
1635
|
+
const normalizedLimit = Number.isFinite(limit) && Number(limit) > 0 ? Math.trunc(limit) : TOKEN_TRANSFER_CACHE_LIMIT;
|
|
1636
|
+
return `
|
|
1637
|
+
/* cip112_movement_update_ids */
|
|
1638
|
+
with relevant_updates as (
|
|
1639
|
+
select
|
|
1640
|
+
encode(activate_event.update_id, 'hex') as update_id,
|
|
1641
|
+
activate_event.event_offset::text as event_offset,
|
|
1642
|
+
to_char(
|
|
1643
|
+
to_timestamp(activate_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1644
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1645
|
+
) as record_time
|
|
1646
|
+
from participant.lapi_events_activate_contract activate_event
|
|
1647
|
+
join participant.par_contracts contract
|
|
1648
|
+
on contract.internal_contract_id = activate_event.internal_contract_id
|
|
1649
|
+
where contract.template_id::text like '${escapeSqlLiteral(CIP112_TEMPLATE_ID_LIKE_PATTERN)}'
|
|
1650
|
+
|
|
1651
|
+
union all
|
|
1652
|
+
|
|
1653
|
+
select
|
|
1654
|
+
encode(deactivate_event.update_id, 'hex') as update_id,
|
|
1655
|
+
deactivate_event.event_offset::text as event_offset,
|
|
1656
|
+
to_char(
|
|
1657
|
+
to_timestamp(deactivate_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1658
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1659
|
+
) as record_time
|
|
1660
|
+
from participant.lapi_events_deactivate_contract deactivate_event
|
|
1661
|
+
left join participant.lapi_string_interning template_string
|
|
1662
|
+
on template_string.internal_id = deactivate_event.template_id
|
|
1663
|
+
where template_string.external_string like '${escapeSqlLiteral(CIP112_TEMPLATE_ID_LIKE_PATTERN)}'
|
|
1664
|
+
|
|
1665
|
+
union all
|
|
1666
|
+
|
|
1667
|
+
select
|
|
1668
|
+
encode(various_event.update_id, 'hex') as update_id,
|
|
1669
|
+
various_event.event_offset::text as event_offset,
|
|
1670
|
+
to_char(
|
|
1671
|
+
to_timestamp(various_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1672
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1673
|
+
) as record_time
|
|
1674
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1675
|
+
left join participant.par_contracts contract
|
|
1676
|
+
on contract.internal_contract_id = various_event.internal_contract_id
|
|
1677
|
+
left join participant.lapi_string_interning template_string
|
|
1678
|
+
on template_string.internal_id = various_event.template_id
|
|
1679
|
+
where (
|
|
1680
|
+
various_event.event_type = 6
|
|
1681
|
+
and contract.template_id::text like '${escapeSqlLiteral(CIP112_TEMPLATE_ID_LIKE_PATTERN)}'
|
|
1682
|
+
) or (
|
|
1683
|
+
various_event.event_type in (5, 7)
|
|
1684
|
+
and template_string.external_string like '${escapeSqlLiteral(CIP112_TEMPLATE_ID_LIKE_PATTERN)}'
|
|
1685
|
+
)
|
|
1686
|
+
)
|
|
1687
|
+
select
|
|
1688
|
+
update_id,
|
|
1689
|
+
max(event_offset::numeric)::text as event_offset,
|
|
1690
|
+
max(record_time) as record_time
|
|
1691
|
+
from relevant_updates
|
|
1692
|
+
group by update_id
|
|
1693
|
+
order by max(event_offset::numeric) desc
|
|
1694
|
+
limit ${normalizedLimit}
|
|
1695
|
+
`;
|
|
1696
|
+
}
|
|
1697
|
+
function buildActiveContractsFilterClause(parties, templates, partyMode, hideSplice) {
|
|
1698
|
+
const partyConditions = normalizePartyFilters(parties).map((party) => partyWitnessArrayMatchCondition('create_events.witnesses', party));
|
|
1699
|
+
const templateExpression = "regexp_replace(coalesce(contract.template_id::text, ''), '^t\\\\|#[^:]+:', '')";
|
|
1700
|
+
const templateConditions = normalizeTemplateFilters(templates).map((templateId) => {
|
|
1701
|
+
const quotedTemplateId = `'${escapeSqlLiteral(templateId)}'`;
|
|
1702
|
+
return `${templateExpression} = ${quotedTemplateId}`;
|
|
1703
|
+
});
|
|
1704
|
+
const groups = [];
|
|
1705
|
+
if (partyConditions.length > 0) {
|
|
1706
|
+
const partyJoiner = normalizePartyFilterMode(partyMode) === 'and' ? '\n and ' : '\n or ';
|
|
1707
|
+
groups.push(partyConditions.length === 1
|
|
1708
|
+
? partyConditions[0]
|
|
1709
|
+
: `(\n ${partyConditions.join(partyJoiner)}\n )`);
|
|
1710
|
+
}
|
|
1711
|
+
if (templateConditions.length > 0) {
|
|
1712
|
+
groups.push(templateConditions.length === 1
|
|
1713
|
+
? templateConditions[0]
|
|
1714
|
+
: `(\n ${templateConditions.join('\n or ')}\n )`);
|
|
1715
|
+
}
|
|
1716
|
+
if (hideSplice) {
|
|
1717
|
+
groups.push(`${templateExpression} not like 'Splice.%'`);
|
|
1718
|
+
}
|
|
1719
|
+
if (groups.length === 0) {
|
|
1720
|
+
return null;
|
|
1721
|
+
}
|
|
1722
|
+
return groups.length === 1 ? groups[0] : `(\n ${groups.join('\n and ')}\n )`;
|
|
1723
|
+
}
|
|
1724
|
+
function activeContractsQuery(limit, before, after, parties, templates, partyMode, hideSplice) {
|
|
1725
|
+
const normalizedBefore = normalizeEventOffsetCursor(before);
|
|
1726
|
+
const normalizedAfter = normalizeEventOffsetCursor(after);
|
|
1727
|
+
const queryLimit = limit + 1;
|
|
1728
|
+
const useAfterCursor = Boolean(normalizedAfter && !normalizedBefore);
|
|
1729
|
+
const cursorFilter = useAfterCursor
|
|
1730
|
+
? `create_event_offset::numeric > ${normalizedAfter}`
|
|
1731
|
+
: normalizedBefore
|
|
1732
|
+
? `create_event_offset::numeric < ${normalizedBefore}`
|
|
1733
|
+
: null;
|
|
1734
|
+
const orderDirection = useAfterCursor ? 'asc' : 'desc';
|
|
1735
|
+
const filterClause = buildActiveContractsFilterClause(parties, templates, partyMode, hideSplice);
|
|
1736
|
+
const whereConditions = [
|
|
1737
|
+
`not exists (
|
|
1738
|
+
select 1
|
|
1739
|
+
from archived_contracts archived_contract
|
|
1740
|
+
where archived_contract.internal_contract_id = create_events.internal_contract_id
|
|
1741
|
+
)`,
|
|
1742
|
+
cursorFilter,
|
|
1743
|
+
filterClause,
|
|
1744
|
+
].filter((value) => Boolean(value));
|
|
1745
|
+
const whereClause = whereConditions.length > 0 ? `where ${whereConditions.join('\n and ')}` : '';
|
|
1746
|
+
return `
|
|
1747
|
+
with activate_witnesses as (
|
|
1748
|
+
select
|
|
1749
|
+
witness_filter.event_sequential_id,
|
|
1750
|
+
array_agg(distinct party_string.external_string order by party_string.external_string) as witnesses
|
|
1751
|
+
from participant.lapi_filter_activate_witness witness_filter
|
|
1752
|
+
join participant.lapi_string_interning party_string
|
|
1753
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1754
|
+
group by witness_filter.event_sequential_id
|
|
1755
|
+
),
|
|
1756
|
+
various_witnesses as (
|
|
1757
|
+
select
|
|
1758
|
+
witness_filter.event_sequential_id,
|
|
1759
|
+
array_agg(distinct party_string.external_string order by party_string.external_string) as witnesses
|
|
1760
|
+
from participant.lapi_filter_various_witness witness_filter
|
|
1761
|
+
join participant.lapi_string_interning party_string
|
|
1762
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1763
|
+
group by witness_filter.event_sequential_id
|
|
1764
|
+
),
|
|
1765
|
+
create_events as (
|
|
1766
|
+
select
|
|
1767
|
+
activate_event.internal_contract_id,
|
|
1768
|
+
activate_event.event_offset::text as create_event_offset,
|
|
1769
|
+
to_char(
|
|
1770
|
+
to_timestamp(activate_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1771
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1772
|
+
) as created_record_time,
|
|
1773
|
+
coalesce(activate_witnesses.witnesses, array[]::text[]) as witnesses
|
|
1774
|
+
from participant.lapi_events_activate_contract activate_event
|
|
1775
|
+
left join activate_witnesses
|
|
1776
|
+
on activate_witnesses.event_sequential_id = activate_event.event_sequential_id
|
|
1777
|
+
|
|
1778
|
+
union all
|
|
1779
|
+
|
|
1780
|
+
select
|
|
1781
|
+
various_event.internal_contract_id,
|
|
1782
|
+
various_event.event_offset::text as create_event_offset,
|
|
1783
|
+
to_char(
|
|
1784
|
+
to_timestamp(various_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1785
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1786
|
+
) as created_record_time,
|
|
1787
|
+
coalesce(various_witnesses.witnesses, array[]::text[]) as witnesses
|
|
1788
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1789
|
+
left join various_witnesses
|
|
1790
|
+
on various_witnesses.event_sequential_id = various_event.event_sequential_id
|
|
1791
|
+
where various_event.event_type = 6
|
|
1792
|
+
),
|
|
1793
|
+
archived_contracts as (
|
|
1794
|
+
select distinct deactivate_event.internal_contract_id
|
|
1795
|
+
from participant.lapi_events_deactivate_contract deactivate_event
|
|
1796
|
+
|
|
1797
|
+
union
|
|
1798
|
+
|
|
1799
|
+
select distinct various_event.internal_contract_id
|
|
1800
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1801
|
+
where various_event.event_type in (5, 7)
|
|
1802
|
+
and various_event.consuming
|
|
1803
|
+
)
|
|
1804
|
+
select
|
|
1805
|
+
encode(contract.contract_id, 'hex') as contract_id,
|
|
1806
|
+
regexp_replace(coalesce(contract.template_id::text, ''), '^t\\\\|#[^:]+:', '') as template_id,
|
|
1807
|
+
create_events.created_record_time,
|
|
1808
|
+
create_events.create_event_offset as created_event_offset
|
|
1809
|
+
from create_events
|
|
1810
|
+
join participant.par_contracts contract
|
|
1811
|
+
on contract.internal_contract_id = create_events.internal_contract_id
|
|
1812
|
+
${whereClause}
|
|
1813
|
+
order by create_event_offset::numeric ${orderDirection}
|
|
1814
|
+
limit ${queryLimit}
|
|
1815
|
+
`;
|
|
1816
|
+
}
|
|
1817
|
+
function searchContractsQuery(searchQuery, limit) {
|
|
1818
|
+
const quotedQuery = escapeSqlLiteral(searchQuery);
|
|
1819
|
+
return `
|
|
1820
|
+
with activate_witnesses as (
|
|
1821
|
+
select
|
|
1822
|
+
witness_filter.event_sequential_id,
|
|
1823
|
+
array_agg(distinct party_string.external_string order by party_string.external_string) as witnesses
|
|
1824
|
+
from participant.lapi_filter_activate_witness witness_filter
|
|
1825
|
+
join participant.lapi_string_interning party_string
|
|
1826
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1827
|
+
group by witness_filter.event_sequential_id
|
|
1828
|
+
),
|
|
1829
|
+
various_witnesses as (
|
|
1830
|
+
select
|
|
1831
|
+
witness_filter.event_sequential_id,
|
|
1832
|
+
array_agg(distinct party_string.external_string order by party_string.external_string) as witnesses
|
|
1833
|
+
from participant.lapi_filter_various_witness witness_filter
|
|
1834
|
+
join participant.lapi_string_interning party_string
|
|
1835
|
+
on party_string.internal_id = witness_filter.party_id
|
|
1836
|
+
group by witness_filter.event_sequential_id
|
|
1837
|
+
),
|
|
1838
|
+
create_events as (
|
|
1839
|
+
select
|
|
1840
|
+
activate_event.internal_contract_id,
|
|
1841
|
+
activate_event.event_offset::text as create_event_offset,
|
|
1842
|
+
to_char(
|
|
1843
|
+
to_timestamp(activate_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1844
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1845
|
+
) as created_record_time,
|
|
1846
|
+
coalesce(activate_witnesses.witnesses, array[]::text[]) as witnesses
|
|
1847
|
+
from participant.lapi_events_activate_contract activate_event
|
|
1848
|
+
left join activate_witnesses
|
|
1849
|
+
on activate_witnesses.event_sequential_id = activate_event.event_sequential_id
|
|
1850
|
+
|
|
1851
|
+
union all
|
|
1852
|
+
|
|
1853
|
+
select
|
|
1854
|
+
various_event.internal_contract_id,
|
|
1855
|
+
various_event.event_offset::text as create_event_offset,
|
|
1856
|
+
to_char(
|
|
1857
|
+
to_timestamp(various_event.record_time / 1000000.0) at time zone 'UTC',
|
|
1858
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
1859
|
+
) as created_record_time,
|
|
1860
|
+
coalesce(various_witnesses.witnesses, array[]::text[]) as witnesses
|
|
1861
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1862
|
+
left join various_witnesses
|
|
1863
|
+
on various_witnesses.event_sequential_id = various_event.event_sequential_id
|
|
1864
|
+
where various_event.event_type = 6
|
|
1865
|
+
),
|
|
1866
|
+
archived_contracts as (
|
|
1867
|
+
select distinct deactivate_event.internal_contract_id
|
|
1868
|
+
from participant.lapi_events_deactivate_contract deactivate_event
|
|
1869
|
+
|
|
1870
|
+
union
|
|
1871
|
+
|
|
1872
|
+
select distinct various_event.internal_contract_id
|
|
1873
|
+
from participant.lapi_events_various_witnessed various_event
|
|
1874
|
+
where various_event.event_type in (5, 7)
|
|
1875
|
+
and various_event.consuming
|
|
1876
|
+
)
|
|
1877
|
+
select
|
|
1878
|
+
encode(contract.contract_id, 'hex') as contract_id,
|
|
1879
|
+
regexp_replace(coalesce(contract.template_id::text, ''), '^t\\\\|#[^:]+:', '') as template_id,
|
|
1880
|
+
create_events.created_record_time
|
|
1881
|
+
from create_events
|
|
1882
|
+
join participant.par_contracts contract
|
|
1883
|
+
on contract.internal_contract_id = create_events.internal_contract_id
|
|
1884
|
+
where encode(contract.contract_id, 'hex') like '${quotedQuery}%'
|
|
1885
|
+
and not exists (
|
|
1886
|
+
select 1
|
|
1887
|
+
from archived_contracts archived_contract
|
|
1888
|
+
where archived_contract.internal_contract_id = create_events.internal_contract_id
|
|
1889
|
+
)
|
|
1890
|
+
order by create_events.created_record_time desc nulls last, contract_id asc
|
|
1891
|
+
limit ${limit}
|
|
1892
|
+
`;
|
|
1893
|
+
}
|
|
1894
|
+
function escapeSqlLiteral(value) {
|
|
1895
|
+
return value.replaceAll("'", "''");
|
|
1896
|
+
}
|
|
1897
|
+
function normalizeEventOffsetCursor(value) {
|
|
1898
|
+
if (!value) {
|
|
1899
|
+
return null;
|
|
1900
|
+
}
|
|
1901
|
+
const trimmed = value.trim();
|
|
1902
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
1903
|
+
return null;
|
|
1904
|
+
}
|
|
1905
|
+
return trimmed.replace(/^0+(?=\d)/, '');
|
|
1906
|
+
}
|
|
1907
|
+
function normalizeByteaHex(value) {
|
|
1908
|
+
return value.startsWith('\\x') ? value.slice(2) : value;
|
|
1909
|
+
}
|
|
1910
|
+
let PqsSummaryService = class PqsSummaryService {
|
|
1911
|
+
clientFactory;
|
|
1912
|
+
damlValueDecoder;
|
|
1913
|
+
packageCacheService;
|
|
1914
|
+
packageRegistryService;
|
|
1915
|
+
nodeConfigService;
|
|
1916
|
+
grpcOperationsService;
|
|
1917
|
+
packageSyncService;
|
|
1918
|
+
observedTokensByNode = new Map();
|
|
1919
|
+
tokenHoldersByNode = new Map();
|
|
1920
|
+
tokenTransfersByNode = new Map();
|
|
1921
|
+
constructor(clientFactory, damlValueDecoder, packageCacheService, packageRegistryService, nodeConfigService, grpcOperationsService, packageSyncService) {
|
|
1922
|
+
this.clientFactory = clientFactory;
|
|
1923
|
+
this.damlValueDecoder = damlValueDecoder;
|
|
1924
|
+
this.packageCacheService = packageCacheService;
|
|
1925
|
+
this.packageRegistryService = packageRegistryService;
|
|
1926
|
+
this.nodeConfigService = nodeConfigService;
|
|
1927
|
+
this.grpcOperationsService = grpcOperationsService;
|
|
1928
|
+
this.packageSyncService = packageSyncService;
|
|
1929
|
+
}
|
|
1930
|
+
async fetchSummary(node) {
|
|
1931
|
+
const client = this.clientFactory.getClient(node);
|
|
1932
|
+
const result = await this.querySummary(client.query.bind(client));
|
|
1933
|
+
const row = result.rows[0] ?? this.defaultRow();
|
|
1934
|
+
return {
|
|
1935
|
+
ledgerLabel: node.ledgerLabel ?? node.label,
|
|
1936
|
+
pqsDatabase: row.pqs_database,
|
|
1937
|
+
activeContractCount: Number(row.active_contract_count ?? 0),
|
|
1938
|
+
latestOffset: row.latest_offset ?? null,
|
|
1939
|
+
latestEventAt: row.latest_event_at ?? null,
|
|
1940
|
+
totalUpdateCount: Number(row.total_update_count ?? 0),
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
async search(query) {
|
|
1944
|
+
const trimmedQuery = query.trim();
|
|
1945
|
+
if (!trimmedQuery) {
|
|
1946
|
+
return {
|
|
1947
|
+
query: '',
|
|
1948
|
+
updates: this.emptySearchGroup(),
|
|
1949
|
+
contracts: this.emptySearchGroup(),
|
|
1950
|
+
parties: this.emptySearchGroup(),
|
|
1951
|
+
packages: {
|
|
1952
|
+
packageIds: this.emptySearchGroup(),
|
|
1953
|
+
packageNames: this.emptySearchGroup(),
|
|
1954
|
+
},
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
const nodes = this.nodeConfigService?.list() ?? [];
|
|
1958
|
+
const updates = await this.searchUpdates(nodes, trimmedQuery);
|
|
1959
|
+
const contracts = await this.searchContracts(nodes, trimmedQuery);
|
|
1960
|
+
const parties = await this.searchParties(nodes, trimmedQuery);
|
|
1961
|
+
const packages = await this.searchPackages(trimmedQuery);
|
|
1962
|
+
return {
|
|
1963
|
+
query: trimmedQuery,
|
|
1964
|
+
updates,
|
|
1965
|
+
contracts,
|
|
1966
|
+
parties,
|
|
1967
|
+
packages,
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
async fetchRecentUpdates(node, options = 25) {
|
|
1971
|
+
const client = this.clientFactory.getClient(node);
|
|
1972
|
+
const normalizedLimit = typeof options === 'number'
|
|
1973
|
+
? Number.isFinite(options) && options > 0
|
|
1974
|
+
? Math.trunc(options)
|
|
1975
|
+
: 25
|
|
1976
|
+
: Number.isFinite(options.limit) && Number(options.limit) > 0
|
|
1977
|
+
? Math.trunc(Number(options.limit))
|
|
1978
|
+
: 25;
|
|
1979
|
+
const before = typeof options === 'object' ? options.before : undefined;
|
|
1980
|
+
const after = typeof options === 'object' ? options.after : undefined;
|
|
1981
|
+
const parties = typeof options === 'object' ? options.parties : undefined;
|
|
1982
|
+
const templates = typeof options === 'object' ? options.templates : undefined;
|
|
1983
|
+
const partyMode = typeof options === 'object' ? (options.partyMode ?? options.mode) : undefined;
|
|
1984
|
+
const hideSplice = typeof options === 'object' ? options.hideSplice === true : false;
|
|
1985
|
+
const useAfterCursor = Boolean(after && !before);
|
|
1986
|
+
const query = client.query.bind(client);
|
|
1987
|
+
const rawMetaRows = await this.queryRecentUpdateMetaRows(query, normalizedLimit, before, after, parties, templates, partyMode, hideSplice);
|
|
1988
|
+
const hasMoreInQuery = rawMetaRows.length > normalizedLimit;
|
|
1989
|
+
const trimmedMetaRows = rawMetaRows.slice(0, normalizedLimit);
|
|
1990
|
+
const orderedUpdates = (useAfterCursor ? [...trimmedMetaRows].reverse() : trimmedMetaRows).map((row) => ({
|
|
1991
|
+
eventOffset: this.extractEventOffset(row),
|
|
1992
|
+
rawUpdateId: row.update_id,
|
|
1993
|
+
updateId: this.normalizeUpdateId(row.update_id),
|
|
1994
|
+
recordTime: row.record_time ?? null,
|
|
1995
|
+
}));
|
|
1996
|
+
if (orderedUpdates.length === 0) {
|
|
1997
|
+
return {
|
|
1998
|
+
nodeId: node.id,
|
|
1999
|
+
label: node.label,
|
|
2000
|
+
limit: normalizedLimit,
|
|
2001
|
+
nextBefore: null,
|
|
2002
|
+
nextAfter: null,
|
|
2003
|
+
updates: [],
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
const partiesByUpdateId = await this.fetchPartiesByUpdateId(query, orderedUpdates.map((update) => update.rawUpdateId));
|
|
2007
|
+
return {
|
|
2008
|
+
nodeId: node.id,
|
|
2009
|
+
label: node.label,
|
|
2010
|
+
limit: normalizedLimit,
|
|
2011
|
+
nextBefore: useAfterCursor || hasMoreInQuery
|
|
2012
|
+
? orderedUpdates[orderedUpdates.length - 1]?.eventOffset ?? null
|
|
2013
|
+
: null,
|
|
2014
|
+
nextAfter: useAfterCursor
|
|
2015
|
+
? hasMoreInQuery
|
|
2016
|
+
? orderedUpdates[0]?.eventOffset ?? null
|
|
2017
|
+
: null
|
|
2018
|
+
: before
|
|
2019
|
+
? orderedUpdates[0]?.eventOffset ?? null
|
|
2020
|
+
: null,
|
|
2021
|
+
updates: orderedUpdates.map((update) => ({
|
|
2022
|
+
eventOffset: update.eventOffset,
|
|
2023
|
+
updateId: update.updateId,
|
|
2024
|
+
recordTime: update.recordTime,
|
|
2025
|
+
parties: partiesByUpdateId.get(update.updateId) ?? [],
|
|
2026
|
+
})),
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
async fetchGlobalRecentUpdates(nodes, limit = 25, options) {
|
|
2030
|
+
const normalizedLimit = Number.isFinite(limit) && Number(limit) > 0 ? Math.trunc(Number(limit)) : 25;
|
|
2031
|
+
const beforeCursor = decodeGlobalUpdateCursor(options?.before);
|
|
2032
|
+
const afterCursor = beforeCursor === null ? decodeGlobalUpdateCursor(options?.after) : null;
|
|
2033
|
+
const parties = options?.parties;
|
|
2034
|
+
const templates = options?.templates;
|
|
2035
|
+
const partyMode = options?.partyMode ?? options?.mode;
|
|
2036
|
+
const hideSplice = options?.hideSplice === true;
|
|
2037
|
+
const useAfterCursor = Boolean(afterCursor && !beforeCursor);
|
|
2038
|
+
const pageSize = Math.max(normalizedLimit * 2, normalizedLimit + 1);
|
|
2039
|
+
const nodeStates = nodes.map((node) => ({
|
|
2040
|
+
node,
|
|
2041
|
+
nextBefore: undefined,
|
|
2042
|
+
exhausted: false,
|
|
2043
|
+
}));
|
|
2044
|
+
const mergedUpdatesByKey = new Map();
|
|
2045
|
+
const filterUpdates = (updates) => {
|
|
2046
|
+
if (beforeCursor !== null) {
|
|
2047
|
+
return updates.filter((update) => compareGlobalMergedUpdates(update, beforeCursor) > 0);
|
|
2048
|
+
}
|
|
2049
|
+
if (afterCursor !== null) {
|
|
2050
|
+
return updates.filter((update) => compareGlobalMergedUpdates(update, afterCursor) < 0);
|
|
2051
|
+
}
|
|
2052
|
+
return updates;
|
|
2053
|
+
};
|
|
2054
|
+
let filteredUpdates = [];
|
|
2055
|
+
let hasMoreInDirection = false;
|
|
2056
|
+
while (true) {
|
|
2057
|
+
const nodesToFetch = nodeStates.filter((state) => !state.exhausted);
|
|
2058
|
+
if (nodesToFetch.length === 0) {
|
|
2059
|
+
break;
|
|
2060
|
+
}
|
|
2061
|
+
const responses = await Promise.all(nodesToFetch.map((state) => this.fetchRecentUpdates(state.node, {
|
|
2062
|
+
limit: pageSize,
|
|
2063
|
+
parties,
|
|
2064
|
+
templates,
|
|
2065
|
+
partyMode,
|
|
2066
|
+
hideSplice,
|
|
2067
|
+
before: state.nextBefore,
|
|
2068
|
+
})));
|
|
2069
|
+
responses.forEach((response, index) => {
|
|
2070
|
+
const state = nodesToFetch[index];
|
|
2071
|
+
for (const update of response.updates) {
|
|
2072
|
+
mergedUpdatesByKey.set(`${state.node.id}:${update.eventOffset}:${update.updateId}`, {
|
|
2073
|
+
nodeId: state.node.id,
|
|
2074
|
+
label: state.node.label,
|
|
2075
|
+
eventOffset: update.eventOffset,
|
|
2076
|
+
updateId: update.updateId,
|
|
2077
|
+
recordTime: update.recordTime,
|
|
2078
|
+
parties: update.parties,
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
state.nextBefore = response.nextBefore ?? undefined;
|
|
2082
|
+
state.exhausted = response.nextBefore === null;
|
|
2083
|
+
});
|
|
2084
|
+
const sortedUpdates = Array.from(mergedUpdatesByKey.values()).sort(compareGlobalMergedUpdates);
|
|
2085
|
+
filteredUpdates = filterUpdates(sortedUpdates);
|
|
2086
|
+
hasMoreInDirection = filteredUpdates.length > normalizedLimit;
|
|
2087
|
+
if (hasMoreInDirection || nodeStates.every((state) => state.exhausted)) {
|
|
2088
|
+
break;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
const updates = filteredUpdates.slice(0, normalizedLimit);
|
|
2092
|
+
return {
|
|
2093
|
+
limit: normalizedLimit,
|
|
2094
|
+
nextBefore: updates.length > 0 && (useAfterCursor || hasMoreInDirection)
|
|
2095
|
+
? encodeGlobalUpdateCursor(updates[updates.length - 1])
|
|
2096
|
+
: null,
|
|
2097
|
+
nextAfter: updates.length === 0
|
|
2098
|
+
? null
|
|
2099
|
+
: useAfterCursor
|
|
2100
|
+
? hasMoreInDirection
|
|
2101
|
+
? encodeGlobalUpdateCursor(updates[0])
|
|
2102
|
+
: null
|
|
2103
|
+
: beforeCursor
|
|
2104
|
+
? encodeGlobalUpdateCursor(updates[0])
|
|
2105
|
+
: null,
|
|
2106
|
+
updates,
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
emptySearchGroup() {
|
|
2110
|
+
return {
|
|
2111
|
+
items: [],
|
|
2112
|
+
displayedCount: 0,
|
|
2113
|
+
truncated: false,
|
|
2114
|
+
status: 'ok',
|
|
2115
|
+
warnings: [],
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
finalizeSearchGroup(options) {
|
|
2119
|
+
const totalCount = options.totalCount ?? options.items.length;
|
|
2120
|
+
return {
|
|
2121
|
+
items: options.items,
|
|
2122
|
+
displayedCount: options.items.length,
|
|
2123
|
+
truncated: totalCount > options.items.length,
|
|
2124
|
+
status: options.status ?? 'ok',
|
|
2125
|
+
warnings: options.warnings ?? [],
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
resolveNodeSearchStatus(settled, subject, nodes) {
|
|
2129
|
+
const warnings = settled.flatMap((result, index) => result.status === 'rejected'
|
|
2130
|
+
? [`Failed to search ${subject} on ${nodes[index]?.label ?? nodes[index]?.id ?? 'unknown node'}`]
|
|
2131
|
+
: []);
|
|
2132
|
+
const hasSuccess = settled.some((result) => result.status === 'fulfilled');
|
|
2133
|
+
return {
|
|
2134
|
+
status: warnings.length === 0 ? 'ok' : hasSuccess ? 'partial' : 'failed',
|
|
2135
|
+
warnings,
|
|
2136
|
+
hasSuccess,
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
isExactMatch(query, values) {
|
|
2140
|
+
return values.some((value) => typeof value === 'string' && value === query);
|
|
2141
|
+
}
|
|
2142
|
+
hasPrefixMatch(query, values) {
|
|
2143
|
+
return values.some((value) => typeof value === 'string' && value.startsWith(query));
|
|
2144
|
+
}
|
|
2145
|
+
async searchUpdates(nodes, query) {
|
|
2146
|
+
if (nodes.length === 0) {
|
|
2147
|
+
return this.emptySearchGroup();
|
|
2148
|
+
}
|
|
2149
|
+
const settled = await Promise.allSettled(nodes.map((node) => this.searchUpdatesForNode(node, query, SEARCH_GROUP_LIMIT + 1)));
|
|
2150
|
+
const deduped = new Map();
|
|
2151
|
+
for (const result of settled) {
|
|
2152
|
+
if (result.status !== 'fulfilled') {
|
|
2153
|
+
continue;
|
|
2154
|
+
}
|
|
2155
|
+
for (const item of result.value) {
|
|
2156
|
+
const key = `${item.nodeId}:${item.eventOffset}`;
|
|
2157
|
+
const existing = deduped.get(key);
|
|
2158
|
+
if (!existing || (item.exact && !existing.exact)) {
|
|
2159
|
+
deduped.set(key, item);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
const ordered = Array.from(deduped.values()).sort(compareSearchUpdates);
|
|
2164
|
+
const { status, warnings } = this.resolveNodeSearchStatus(settled, 'updates', nodes);
|
|
2165
|
+
return this.finalizeSearchGroup({
|
|
2166
|
+
items: ordered.slice(0, SEARCH_GROUP_LIMIT).map(({ exact: _exact, ...item }) => item),
|
|
2167
|
+
totalCount: ordered.length,
|
|
2168
|
+
status,
|
|
2169
|
+
warnings,
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
async searchUpdatesForNode(node, query, limit) {
|
|
2173
|
+
const client = this.clientFactory.getClient(node);
|
|
2174
|
+
const rows = await this.querySearchUpdateMetaRows(client.query.bind(client), query, limit);
|
|
2175
|
+
const dedupedRows = new Map();
|
|
2176
|
+
for (const row of rows) {
|
|
2177
|
+
const eventOffset = this.extractEventOffset(row);
|
|
2178
|
+
if (!dedupedRows.has(eventOffset)) {
|
|
2179
|
+
dedupedRows.set(eventOffset, row);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
const rawRows = Array.from(dedupedRows.values());
|
|
2183
|
+
const rawUpdateIds = rawRows.map((row) => row.update_id);
|
|
2184
|
+
const partiesByUpdateId = rawUpdateIds.length > 0
|
|
2185
|
+
? await this.fetchPartiesByUpdateId(client.query.bind(client), rawUpdateIds)
|
|
2186
|
+
: new Map();
|
|
2187
|
+
return rawRows
|
|
2188
|
+
.map((row) => {
|
|
2189
|
+
const normalizedUpdateId = this.normalizeUpdateId(row.update_id);
|
|
2190
|
+
const eventOffset = this.extractEventOffset(row);
|
|
2191
|
+
const exact = this.isExactMatch(query, [eventOffset, normalizedUpdateId]);
|
|
2192
|
+
const matches = exact || this.hasPrefixMatch(query, [eventOffset, normalizedUpdateId]);
|
|
2193
|
+
if (!matches) {
|
|
2194
|
+
return null;
|
|
2195
|
+
}
|
|
2196
|
+
return {
|
|
2197
|
+
nodeId: node.id,
|
|
2198
|
+
label: node.label,
|
|
2199
|
+
eventOffset,
|
|
2200
|
+
updateId: normalizedUpdateId,
|
|
2201
|
+
recordTime: row.record_time ?? null,
|
|
2202
|
+
parties: partiesByUpdateId.get(normalizedUpdateId) ?? [],
|
|
2203
|
+
exact,
|
|
2204
|
+
};
|
|
2205
|
+
})
|
|
2206
|
+
.filter((item) => item !== null);
|
|
2207
|
+
}
|
|
2208
|
+
async searchContracts(nodes, query) {
|
|
2209
|
+
if (nodes.length === 0) {
|
|
2210
|
+
return this.emptySearchGroup();
|
|
2211
|
+
}
|
|
2212
|
+
const settled = await Promise.allSettled(nodes.map((node) => this.searchContractsForNode(node, query, SEARCH_GROUP_LIMIT + 1)));
|
|
2213
|
+
const deduped = new Map();
|
|
2214
|
+
for (const result of settled) {
|
|
2215
|
+
if (result.status !== 'fulfilled') {
|
|
2216
|
+
continue;
|
|
2217
|
+
}
|
|
2218
|
+
for (const item of result.value) {
|
|
2219
|
+
deduped.set(`${item.nodeId}:${item.contractId}`, item);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
const ordered = Array.from(deduped.values()).sort(compareSearchContracts);
|
|
2223
|
+
const { status, warnings } = this.resolveNodeSearchStatus(settled, 'contracts', nodes);
|
|
2224
|
+
return this.finalizeSearchGroup({
|
|
2225
|
+
items: ordered.slice(0, SEARCH_GROUP_LIMIT).map(({ exact: _exact, ...item }) => item),
|
|
2226
|
+
totalCount: ordered.length,
|
|
2227
|
+
status,
|
|
2228
|
+
warnings,
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
async searchContractsForNode(node, query, limit) {
|
|
2232
|
+
const client = this.clientFactory.getClient(node);
|
|
2233
|
+
const result = await client.query(searchContractsQuery(query, limit));
|
|
2234
|
+
const rows = result.rows ?? [];
|
|
2235
|
+
return rows
|
|
2236
|
+
.filter((row) => this.hasPrefixMatch(query, [row.contract_id]))
|
|
2237
|
+
.map((row) => ({
|
|
2238
|
+
nodeId: node.id,
|
|
2239
|
+
label: node.label,
|
|
2240
|
+
contractId: row.contract_id,
|
|
2241
|
+
templateId: this.normalizeTemplateIdentifier(row.template_id),
|
|
2242
|
+
createdRecordTime: row.created_record_time ?? null,
|
|
2243
|
+
exact: this.isExactMatch(query, [row.contract_id]),
|
|
2244
|
+
}));
|
|
2245
|
+
}
|
|
2246
|
+
async searchParties(nodes, query) {
|
|
2247
|
+
if (nodes.length === 0) {
|
|
2248
|
+
return this.emptySearchGroup();
|
|
2249
|
+
}
|
|
2250
|
+
const normalizedQuery = this.normalizePartyIdentifier(query);
|
|
2251
|
+
const settled = await Promise.allSettled(nodes.map(async (node) => ({
|
|
2252
|
+
node,
|
|
2253
|
+
parties: await this.fetchActivePartiesForNode(node),
|
|
2254
|
+
})));
|
|
2255
|
+
const merged = new Map();
|
|
2256
|
+
for (const result of settled) {
|
|
2257
|
+
if (result.status !== 'fulfilled') {
|
|
2258
|
+
continue;
|
|
2259
|
+
}
|
|
2260
|
+
for (const partyId of result.value.parties) {
|
|
2261
|
+
if (!partyId.startsWith(normalizedQuery)) {
|
|
2262
|
+
continue;
|
|
2263
|
+
}
|
|
2264
|
+
const existing = merged.get(partyId);
|
|
2265
|
+
if (existing) {
|
|
2266
|
+
existing.nodeIds = Array.from(new Set([...existing.nodeIds, result.value.node.id])).sort();
|
|
2267
|
+
continue;
|
|
2268
|
+
}
|
|
2269
|
+
merged.set(partyId, {
|
|
2270
|
+
partyId,
|
|
2271
|
+
nodeIds: [result.value.node.id],
|
|
2272
|
+
exact: partyId === normalizedQuery,
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
const ordered = Array.from(merged.values()).sort(compareSearchParties);
|
|
2277
|
+
const { status, warnings } = this.resolveNodeSearchStatus(settled, 'parties', nodes);
|
|
2278
|
+
return this.finalizeSearchGroup({
|
|
2279
|
+
items: ordered.slice(0, SEARCH_GROUP_LIMIT).map(({ exact: _exact, ...item }) => item),
|
|
2280
|
+
totalCount: ordered.length,
|
|
2281
|
+
status,
|
|
2282
|
+
warnings,
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
async searchPackages(query) {
|
|
2286
|
+
const packages = this.packageCacheService?.listPackages() ?? [];
|
|
2287
|
+
const packageIdMatches = packages
|
|
2288
|
+
.filter((pkg) => pkg.packageId.startsWith(query))
|
|
2289
|
+
.map((pkg) => ({
|
|
2290
|
+
packageId: pkg.packageId,
|
|
2291
|
+
name: pkg.name,
|
|
2292
|
+
version: pkg.version,
|
|
2293
|
+
exact: pkg.packageId === query,
|
|
2294
|
+
}))
|
|
2295
|
+
.sort(compareSearchPackageIds);
|
|
2296
|
+
const groupedNames = new Map();
|
|
2297
|
+
for (const pkg of packages) {
|
|
2298
|
+
if (!pkg.name || !pkg.name.startsWith(query)) {
|
|
2299
|
+
continue;
|
|
2300
|
+
}
|
|
2301
|
+
const existing = groupedNames.get(pkg.name);
|
|
2302
|
+
if (existing) {
|
|
2303
|
+
existing.packages.push({ packageId: pkg.packageId, version: pkg.version });
|
|
2304
|
+
continue;
|
|
2305
|
+
}
|
|
2306
|
+
groupedNames.set(pkg.name, {
|
|
2307
|
+
name: pkg.name,
|
|
2308
|
+
packages: [{ packageId: pkg.packageId, version: pkg.version }],
|
|
2309
|
+
exact: pkg.name === query,
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
const packageNameMatches = Array.from(groupedNames.values())
|
|
2313
|
+
.map((entry) => ({
|
|
2314
|
+
...entry,
|
|
2315
|
+
packages: [...entry.packages].sort((left, right) => (right.version ?? '').localeCompare(left.version ?? '') ||
|
|
2316
|
+
left.packageId.localeCompare(right.packageId)),
|
|
2317
|
+
}))
|
|
2318
|
+
.sort(compareSearchPackageNames);
|
|
2319
|
+
return {
|
|
2320
|
+
packageIds: this.finalizeSearchGroup({
|
|
2321
|
+
items: packageIdMatches
|
|
2322
|
+
.slice(0, SEARCH_GROUP_LIMIT)
|
|
2323
|
+
.map(({ exact: _exact, ...item }) => item),
|
|
2324
|
+
totalCount: packageIdMatches.length,
|
|
2325
|
+
}),
|
|
2326
|
+
packageNames: this.finalizeSearchGroup({
|
|
2327
|
+
items: packageNameMatches
|
|
2328
|
+
.slice(0, SEARCH_GROUP_LIMIT)
|
|
2329
|
+
.map(({ exact: _exact, ...item }) => item),
|
|
2330
|
+
totalCount: packageNameMatches.length,
|
|
2331
|
+
}),
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
async querySearchUpdateMetaRows(queryFn, searchQuery, limit) {
|
|
2335
|
+
try {
|
|
2336
|
+
const result = await queryFn(searchUpdatesQuery(searchQuery, limit));
|
|
2337
|
+
return result.rows ?? [];
|
|
2338
|
+
}
|
|
2339
|
+
catch (error) {
|
|
2340
|
+
if (!this.shouldFallbackToNormalizedEventTables(error)) {
|
|
2341
|
+
throw error;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
const fallbackResult = await queryFn(normalizedSearchUpdatesQuery(searchQuery, limit));
|
|
2345
|
+
return fallbackResult.rows ?? [];
|
|
2346
|
+
}
|
|
2347
|
+
async queryRecentUpdateMetaRows(query, limit, before, after, parties, templates, mode, hideSplice) {
|
|
2348
|
+
try {
|
|
2349
|
+
const result = await query(recentUpdatesQuery(limit, before, after, parties, templates, mode, hideSplice));
|
|
2350
|
+
return result.rows ?? [];
|
|
2351
|
+
}
|
|
2352
|
+
catch (error) {
|
|
2353
|
+
if (!this.shouldFallbackToNormalizedEventTables(error)) {
|
|
2354
|
+
throw error;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
const fallbackResult = await query(normalizedRecentUpdatesQuery(limit, before, after, parties, templates, mode, hideSplice));
|
|
2358
|
+
return fallbackResult.rows ?? [];
|
|
2359
|
+
}
|
|
2360
|
+
async fetchNodeContracts(node, options) {
|
|
2361
|
+
const client = this.clientFactory.getClient(node);
|
|
2362
|
+
const normalizedLimit = typeof options?.limit === 'number' && Number.isFinite(options.limit) && options.limit > 0
|
|
2363
|
+
? Math.trunc(options.limit)
|
|
2364
|
+
: 25;
|
|
2365
|
+
const before = options?.before;
|
|
2366
|
+
const after = options?.after;
|
|
2367
|
+
const parties = options?.parties;
|
|
2368
|
+
const templates = options?.templates;
|
|
2369
|
+
const partyMode = options?.partyMode;
|
|
2370
|
+
const hideSplice = options?.hideSplice === true;
|
|
2371
|
+
const useAfterCursor = Boolean(after && !before);
|
|
2372
|
+
const result = await client.query(activeContractsQuery(normalizedLimit, before, after, parties, templates, partyMode, hideSplice));
|
|
2373
|
+
const rawRows = result.rows ?? [];
|
|
2374
|
+
const hasMoreInQuery = rawRows.length > normalizedLimit;
|
|
2375
|
+
const trimmedRows = rawRows.slice(0, normalizedLimit);
|
|
2376
|
+
const orderedContracts = (useAfterCursor ? [...trimmedRows].reverse() : trimmedRows).map((row) => ({
|
|
2377
|
+
contractId: row.contract_id,
|
|
2378
|
+
templateId: this.normalizeTemplateIdentifier(row.template_id),
|
|
2379
|
+
createdRecordTime: row.created_record_time ?? null,
|
|
2380
|
+
createdEventOffset: this.normalizeOptionalScalar(row.created_event_offset),
|
|
2381
|
+
}));
|
|
2382
|
+
return {
|
|
2383
|
+
nodeId: node.id,
|
|
2384
|
+
label: node.label,
|
|
2385
|
+
limit: normalizedLimit,
|
|
2386
|
+
nextBefore: orderedContracts.length > 0 && (useAfterCursor || hasMoreInQuery)
|
|
2387
|
+
? orderedContracts[orderedContracts.length - 1]?.createdEventOffset ?? null
|
|
2388
|
+
: null,
|
|
2389
|
+
nextAfter: orderedContracts.length === 0
|
|
2390
|
+
? null
|
|
2391
|
+
: useAfterCursor
|
|
2392
|
+
? hasMoreInQuery
|
|
2393
|
+
? orderedContracts[0]?.createdEventOffset ?? null
|
|
2394
|
+
: null
|
|
2395
|
+
: before
|
|
2396
|
+
? orderedContracts[0]?.createdEventOffset ?? null
|
|
2397
|
+
: null,
|
|
2398
|
+
contracts: orderedContracts.map((contract) => ({
|
|
2399
|
+
contractId: contract.contractId,
|
|
2400
|
+
templateId: contract.templateId,
|
|
2401
|
+
createdRecordTime: contract.createdRecordTime,
|
|
2402
|
+
})),
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
async fetchActivityBuckets(node, days = 30, bucketMinutes = 15) {
|
|
2406
|
+
const client = this.clientFactory.getClient(node);
|
|
2407
|
+
const result = await client.query(activityBucketsQuery(days, bucketMinutes));
|
|
2408
|
+
const rows = result.rows ?? [];
|
|
2409
|
+
return rows
|
|
2410
|
+
.filter((row) => typeof row.bucket_timestamp === 'string')
|
|
2411
|
+
.map((row) => ({
|
|
2412
|
+
timestamp: row.bucket_timestamp,
|
|
2413
|
+
activityValue: Number(row.activity_value ?? 0),
|
|
2414
|
+
latestOffset: row.latest_offset ?? null,
|
|
2415
|
+
}));
|
|
2416
|
+
}
|
|
2417
|
+
async fetchPackageDetail(packageId) {
|
|
2418
|
+
const metadata = this.packageCacheService?.getPackageMetadata(packageId) ?? null;
|
|
2419
|
+
if (!metadata) {
|
|
2420
|
+
throw new Error('Package not found');
|
|
2421
|
+
}
|
|
2422
|
+
const seenOnNodes = (this.packageCacheService?.listNodesForPackage(packageId) ?? []).map((row) => ({
|
|
2423
|
+
nodeId: row.nodeId,
|
|
2424
|
+
packageName: row.packageName,
|
|
2425
|
+
packageVersion: row.packageVersion,
|
|
2426
|
+
seenAt: row.seenAt,
|
|
2427
|
+
}));
|
|
2428
|
+
const inspection = this.packageRegistryService
|
|
2429
|
+
? await this.packageRegistryService.inspectPackage(packageId)
|
|
2430
|
+
: { ok: false, reason: 'missing_package' };
|
|
2431
|
+
if (!inspection.ok) {
|
|
2432
|
+
return {
|
|
2433
|
+
packageId: metadata.packageId,
|
|
2434
|
+
name: metadata.name,
|
|
2435
|
+
version: metadata.version,
|
|
2436
|
+
uploadedAt: metadata.uploadedAt,
|
|
2437
|
+
packageSize: metadata.packageSize,
|
|
2438
|
+
status: inspection.reason === 'missing_package' ? 'missing_package' : 'invalid_package',
|
|
2439
|
+
seenOnNodes,
|
|
2440
|
+
moduleCount: 0,
|
|
2441
|
+
templateCount: 0,
|
|
2442
|
+
dataTypeCount: 0,
|
|
2443
|
+
modules: [],
|
|
2444
|
+
templates: [],
|
|
2445
|
+
dataTypes: [],
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
return {
|
|
2449
|
+
packageId: metadata.packageId,
|
|
2450
|
+
name: metadata.name,
|
|
2451
|
+
version: metadata.version,
|
|
2452
|
+
uploadedAt: metadata.uploadedAt,
|
|
2453
|
+
packageSize: metadata.packageSize,
|
|
2454
|
+
status: 'decoded',
|
|
2455
|
+
seenOnNodes,
|
|
2456
|
+
moduleCount: inspection.definition.moduleCount,
|
|
2457
|
+
templateCount: inspection.definition.templateCount,
|
|
2458
|
+
dataTypeCount: inspection.definition.dataTypeCount,
|
|
2459
|
+
modules: [...inspection.definition.modules].sort((left, right) => left.localeCompare(right)),
|
|
2460
|
+
templates: [...inspection.definition.templates].sort((left, right) => left.templateId.localeCompare(right.templateId)),
|
|
2461
|
+
dataTypes: [...inspection.definition.dataTypes].sort((left, right) => left.typeId.localeCompare(right.typeId)),
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
async fetchPackagesByName(packageName) {
|
|
2465
|
+
const packages = this.packageCacheService?.listPackagesByName(packageName) ?? [];
|
|
2466
|
+
if (packages.length === 0) {
|
|
2467
|
+
throw new Error('Package family not found');
|
|
2468
|
+
}
|
|
2469
|
+
return {
|
|
2470
|
+
name: packageName,
|
|
2471
|
+
packages,
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
async fetchNodePackages(node) {
|
|
2475
|
+
const rows = this.packageCacheService?.listPackagesForNode(node.id) ?? [];
|
|
2476
|
+
const groupedPackages = new Map();
|
|
2477
|
+
for (const row of rows) {
|
|
2478
|
+
const packageName = row.packageName ?? row.packageId;
|
|
2479
|
+
const group = groupedPackages.get(packageName) ?? [];
|
|
2480
|
+
group.push({
|
|
2481
|
+
packageId: row.packageId,
|
|
2482
|
+
version: row.packageVersion,
|
|
2483
|
+
uploadedAt: row.uploadedAt,
|
|
2484
|
+
seenAt: row.seenAt,
|
|
2485
|
+
});
|
|
2486
|
+
groupedPackages.set(packageName, group);
|
|
2487
|
+
}
|
|
2488
|
+
return {
|
|
2489
|
+
nodeId: node.id,
|
|
2490
|
+
label: node.label,
|
|
2491
|
+
packagesByName: Array.from(groupedPackages.entries()).map(([packageName, packages]) => ({
|
|
2492
|
+
packageName,
|
|
2493
|
+
packages,
|
|
2494
|
+
})),
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
async fetchTemplates() {
|
|
2498
|
+
const packageIds = (this.packageCacheService?.listPackages() ?? []).map((pkg) => pkg.packageId);
|
|
2499
|
+
return this.buildTemplateFilterResponse(packageIds);
|
|
2500
|
+
}
|
|
2501
|
+
async fetchNodeTemplates(node) {
|
|
2502
|
+
const packageIds = Array.from(new Set((this.packageCacheService?.listPackagesForNode(node.id) ?? []).map((pkg) => pkg.packageId)));
|
|
2503
|
+
return this.buildTemplateFilterResponse(packageIds);
|
|
2504
|
+
}
|
|
2505
|
+
async fetchActiveParties(nodes) {
|
|
2506
|
+
return {
|
|
2507
|
+
nodes: await Promise.all(nodes.map(async (node) => ({
|
|
2508
|
+
nodeId: node.id,
|
|
2509
|
+
label: node.label,
|
|
2510
|
+
mode: node.mode,
|
|
2511
|
+
parties: await this.fetchActivePartiesForNode(node),
|
|
2512
|
+
}))),
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
async fetchPartyDetail(nodes, partyId) {
|
|
2516
|
+
const normalizedPartyId = this.normalizePartyIdentifier(partyId);
|
|
2517
|
+
const grpcOperationsService = this.grpcOperationsService;
|
|
2518
|
+
const activePartiesByNode = await Promise.all(nodes.map(async (node) => ({
|
|
2519
|
+
node,
|
|
2520
|
+
parties: await this.fetchActivePartiesForNode(node),
|
|
2521
|
+
})));
|
|
2522
|
+
const recentUpdatesByNode = await Promise.all(nodes.map(async (node) => ({
|
|
2523
|
+
node,
|
|
2524
|
+
updates: await this.fetchPartyRecentUpdatesForNode(node, normalizedPartyId, 10),
|
|
2525
|
+
})));
|
|
2526
|
+
const recentContractsByNode = await Promise.all(nodes.map(async (node) => ({
|
|
2527
|
+
node,
|
|
2528
|
+
contracts: await this.fetchPartyRecentContractsForNode(node, normalizedPartyId, 10),
|
|
2529
|
+
})));
|
|
2530
|
+
const localPartiesByNode = grpcOperationsService
|
|
2531
|
+
? await Promise.all(nodes.map(async (node) => {
|
|
2532
|
+
try {
|
|
2533
|
+
return {
|
|
2534
|
+
node,
|
|
2535
|
+
parties: await grpcOperationsService.listLocalParties(node),
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
catch {
|
|
2539
|
+
return {
|
|
2540
|
+
node,
|
|
2541
|
+
parties: [],
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
}))
|
|
2545
|
+
: [];
|
|
2546
|
+
const localPartyPresenceByNodeId = new Map(localPartiesByNode.map(({ node, parties }) => [node.id, parties.includes(normalizedPartyId)]));
|
|
2547
|
+
const nodesById = new Map();
|
|
2548
|
+
for (const { node, updates } of recentUpdatesByNode) {
|
|
2549
|
+
if (updates.length === 0) {
|
|
2550
|
+
continue;
|
|
2551
|
+
}
|
|
2552
|
+
const existing = nodesById.get(node.id);
|
|
2553
|
+
nodesById.set(node.id, {
|
|
2554
|
+
nodeId: node.id,
|
|
2555
|
+
label: node.label,
|
|
2556
|
+
recentUpdateCount: updates.length,
|
|
2557
|
+
recentContractCount: existing?.recentContractCount ?? 0,
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
for (const { node, contracts } of recentContractsByNode) {
|
|
2561
|
+
if (contracts.length === 0) {
|
|
2562
|
+
continue;
|
|
2563
|
+
}
|
|
2564
|
+
const existing = nodesById.get(node.id);
|
|
2565
|
+
nodesById.set(node.id, {
|
|
2566
|
+
nodeId: node.id,
|
|
2567
|
+
label: node.label,
|
|
2568
|
+
recentUpdateCount: existing?.recentUpdateCount ?? 0,
|
|
2569
|
+
recentContractCount: contracts.length,
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
for (const { node, parties } of activePartiesByNode) {
|
|
2573
|
+
if (!parties.includes(normalizedPartyId)) {
|
|
2574
|
+
continue;
|
|
2575
|
+
}
|
|
2576
|
+
const existing = nodesById.get(node.id);
|
|
2577
|
+
nodesById.set(node.id, {
|
|
2578
|
+
nodeId: node.id,
|
|
2579
|
+
label: node.label,
|
|
2580
|
+
recentUpdateCount: existing?.recentUpdateCount ?? 0,
|
|
2581
|
+
recentContractCount: existing?.recentContractCount ?? 0,
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2584
|
+
for (const { node, parties } of localPartiesByNode) {
|
|
2585
|
+
if (!parties.includes(normalizedPartyId)) {
|
|
2586
|
+
continue;
|
|
2587
|
+
}
|
|
2588
|
+
const existing = nodesById.get(node.id);
|
|
2589
|
+
nodesById.set(node.id, {
|
|
2590
|
+
nodeId: node.id,
|
|
2591
|
+
label: node.label,
|
|
2592
|
+
recentUpdateCount: existing?.recentUpdateCount ?? 0,
|
|
2593
|
+
recentContractCount: existing?.recentContractCount ?? 0,
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
const recentUpdates = recentUpdatesByNode
|
|
2597
|
+
.flatMap(({ updates }) => updates)
|
|
2598
|
+
.sort((left, right) => Date.parse(right.recordTime ?? '') - Date.parse(left.recordTime ?? ''))
|
|
2599
|
+
.slice(0, 10);
|
|
2600
|
+
const recentContracts = recentContractsByNode
|
|
2601
|
+
.flatMap(({ contracts }) => contracts)
|
|
2602
|
+
.sort((left, right) => Date.parse(right.recordTime ?? '') - Date.parse(left.recordTime ?? ''))
|
|
2603
|
+
.slice(0, 10);
|
|
2604
|
+
const observedNodes = Array.from(nodesById.values()).sort((left, right) => left.label.localeCompare(right.label));
|
|
2605
|
+
if (observedNodes.length === 0 && recentUpdates.length === 0 && recentContracts.length === 0) {
|
|
2606
|
+
throw new Error('Party not found');
|
|
2607
|
+
}
|
|
2608
|
+
const partyTopologyByNode = grpcOperationsService
|
|
2609
|
+
? (await Promise.all(observedNodes.map(async (observedNode) => {
|
|
2610
|
+
const node = nodes.find((candidate) => candidate.id === observedNode.nodeId);
|
|
2611
|
+
if (!node) {
|
|
2612
|
+
return null;
|
|
2613
|
+
}
|
|
2614
|
+
const topology = await grpcOperationsService.fetchPartyTopology(node, normalizedPartyId);
|
|
2615
|
+
return {
|
|
2616
|
+
...topology,
|
|
2617
|
+
isLocalParty: localPartyPresenceByNodeId.get(node.id) ?? topology.isLocalParty ?? null,
|
|
2618
|
+
};
|
|
2619
|
+
})))
|
|
2620
|
+
.filter((entry) => entry !== null)
|
|
2621
|
+
.sort((left, right) => left.label.localeCompare(right.label))
|
|
2622
|
+
: [];
|
|
2623
|
+
return {
|
|
2624
|
+
partyId: normalizedPartyId,
|
|
2625
|
+
nodeCount: observedNodes.length,
|
|
2626
|
+
recentUpdateCount: recentUpdates.length,
|
|
2627
|
+
recentContractCount: recentContracts.length,
|
|
2628
|
+
nodes: observedNodes,
|
|
2629
|
+
recentUpdates,
|
|
2630
|
+
recentContracts,
|
|
2631
|
+
partyTopologyByNode,
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
async fetchNamespaceDetail(nodes, namespaceId) {
|
|
2635
|
+
const normalizedNamespaceId = namespaceId.trim();
|
|
2636
|
+
const grpcOperationsService = this.grpcOperationsService;
|
|
2637
|
+
const activePartiesByNode = await Promise.all(nodes.map(async (node) => ({
|
|
2638
|
+
node,
|
|
2639
|
+
parties: (await this.fetchActivePartiesForNode(node)).filter((partyId) => this.extractNamespaceIdentifier(partyId) === normalizedNamespaceId),
|
|
2640
|
+
})));
|
|
2641
|
+
const localPartiesByNode = grpcOperationsService
|
|
2642
|
+
? await Promise.all(nodes.map(async (node) => {
|
|
2643
|
+
try {
|
|
2644
|
+
return {
|
|
2645
|
+
node,
|
|
2646
|
+
parties: (await grpcOperationsService.listLocalParties(node)).filter((partyId) => this.extractNamespaceIdentifier(partyId) === normalizedNamespaceId),
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
catch {
|
|
2650
|
+
return {
|
|
2651
|
+
node,
|
|
2652
|
+
parties: [],
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
}))
|
|
2656
|
+
: [];
|
|
2657
|
+
const partiesById = new Map();
|
|
2658
|
+
const nodesById = new Map();
|
|
2659
|
+
for (const { node, parties } of [...activePartiesByNode, ...localPartiesByNode]) {
|
|
2660
|
+
for (const partyId of parties) {
|
|
2661
|
+
const nodeIds = partiesById.get(partyId) ?? new Set();
|
|
2662
|
+
nodeIds.add(node.id);
|
|
2663
|
+
partiesById.set(partyId, nodeIds);
|
|
2664
|
+
if (!nodesById.has(node.id)) {
|
|
2665
|
+
nodesById.set(node.id, {
|
|
2666
|
+
nodeId: node.id,
|
|
2667
|
+
label: node.label,
|
|
2668
|
+
recentUpdateCount: 0,
|
|
2669
|
+
recentContractCount: 0,
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
const matchingParties = Array.from(partiesById.keys()).sort((left, right) => left.localeCompare(right));
|
|
2675
|
+
if (matchingParties.length === 0) {
|
|
2676
|
+
throw new Error('Namespace not found');
|
|
2677
|
+
}
|
|
2678
|
+
const recentUpdatesResponse = await this.fetchGlobalRecentUpdates(nodes, 10, {
|
|
2679
|
+
parties: matchingParties,
|
|
2680
|
+
partyMode: 'or',
|
|
2681
|
+
});
|
|
2682
|
+
const recentContractsResponse = await this.fetchGlobalContracts(nodes, 10, {
|
|
2683
|
+
parties: matchingParties,
|
|
2684
|
+
partyMode: 'or',
|
|
2685
|
+
});
|
|
2686
|
+
for (const update of recentUpdatesResponse.updates) {
|
|
2687
|
+
const existing = nodesById.get(update.nodeId);
|
|
2688
|
+
nodesById.set(update.nodeId, {
|
|
2689
|
+
nodeId: update.nodeId,
|
|
2690
|
+
label: update.label,
|
|
2691
|
+
recentUpdateCount: (existing?.recentUpdateCount ?? 0) + 1,
|
|
2692
|
+
recentContractCount: existing?.recentContractCount ?? 0,
|
|
2693
|
+
});
|
|
2694
|
+
for (const partyId of update.parties.filter((partyId) => this.extractNamespaceIdentifier(partyId) === normalizedNamespaceId)) {
|
|
2695
|
+
const nodeIds = partiesById.get(partyId) ?? new Set();
|
|
2696
|
+
nodeIds.add(update.nodeId);
|
|
2697
|
+
partiesById.set(partyId, nodeIds);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
for (const contract of recentContractsResponse.contracts) {
|
|
2701
|
+
const existing = nodesById.get(contract.nodeId);
|
|
2702
|
+
nodesById.set(contract.nodeId, {
|
|
2703
|
+
nodeId: contract.nodeId,
|
|
2704
|
+
label: contract.label,
|
|
2705
|
+
recentUpdateCount: existing?.recentUpdateCount ?? 0,
|
|
2706
|
+
recentContractCount: (existing?.recentContractCount ?? 0) + 1,
|
|
2707
|
+
});
|
|
2708
|
+
}
|
|
2709
|
+
const observedNodes = Array.from(nodesById.values()).sort((left, right) => left.label.localeCompare(right.label));
|
|
2710
|
+
const topologyByNode = grpcOperationsService
|
|
2711
|
+
? await this.fetchNamespaceTopologyByNode(nodes, observedNodes.map((node) => node.nodeId), partiesById)
|
|
2712
|
+
: [];
|
|
2713
|
+
return {
|
|
2714
|
+
namespaceId: normalizedNamespaceId,
|
|
2715
|
+
partyCount: matchingParties.length,
|
|
2716
|
+
nodeCount: observedNodes.length,
|
|
2717
|
+
recentUpdateCount: recentUpdatesResponse.updates.length,
|
|
2718
|
+
recentContractCount: recentContractsResponse.contracts.length,
|
|
2719
|
+
nodes: observedNodes,
|
|
2720
|
+
recentUpdates: recentUpdatesResponse.updates,
|
|
2721
|
+
recentContracts: recentContractsResponse.contracts.map((contract) => ({
|
|
2722
|
+
...contract,
|
|
2723
|
+
packageId: null,
|
|
2724
|
+
packageName: null,
|
|
2725
|
+
packageVersion: null,
|
|
2726
|
+
})),
|
|
2727
|
+
topologyByNode,
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
async fetchNamespaceParties(nodes, namespaceId, options) {
|
|
2731
|
+
const normalizedNamespaceId = namespaceId.trim();
|
|
2732
|
+
const matchingParties = new Set();
|
|
2733
|
+
const activePartiesByNode = await Promise.all(nodes.map(async (node) => (await this.fetchActivePartiesForNode(node)).filter((partyId) => this.extractNamespaceIdentifier(partyId) === normalizedNamespaceId)));
|
|
2734
|
+
for (const parties of activePartiesByNode) {
|
|
2735
|
+
for (const partyId of parties) {
|
|
2736
|
+
matchingParties.add(partyId);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
if (this.grpcOperationsService) {
|
|
2740
|
+
const localPartiesByNode = await Promise.all(nodes.map(async (node) => {
|
|
2741
|
+
try {
|
|
2742
|
+
return (await this.grpcOperationsService?.listLocalParties(node))?.filter((partyId) => this.extractNamespaceIdentifier(partyId) === normalizedNamespaceId) ?? [];
|
|
2743
|
+
}
|
|
2744
|
+
catch {
|
|
2745
|
+
return [];
|
|
2746
|
+
}
|
|
2747
|
+
}));
|
|
2748
|
+
for (const parties of localPartiesByNode) {
|
|
2749
|
+
for (const partyId of parties) {
|
|
2750
|
+
matchingParties.add(partyId);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
const orderedParties = Array.from(matchingParties).sort((left, right) => left.localeCompare(right));
|
|
2755
|
+
if (orderedParties.length === 0) {
|
|
2756
|
+
throw new Error('Namespace not found');
|
|
2757
|
+
}
|
|
2758
|
+
const normalizedLimit = typeof options?.limit === 'number' && Number.isFinite(options.limit) && options.limit > 0
|
|
2759
|
+
? Math.trunc(options.limit)
|
|
2760
|
+
: 25;
|
|
2761
|
+
const pagedParties = this.paginateNamespacePartyIds(orderedParties, {
|
|
2762
|
+
limit: normalizedLimit,
|
|
2763
|
+
before: options?.before,
|
|
2764
|
+
after: options?.after,
|
|
2765
|
+
});
|
|
2766
|
+
return {
|
|
2767
|
+
namespaceId: normalizedNamespaceId,
|
|
2768
|
+
partyCount: orderedParties.length,
|
|
2769
|
+
limit: normalizedLimit,
|
|
2770
|
+
nextBefore: pagedParties.nextBefore,
|
|
2771
|
+
nextAfter: pagedParties.nextAfter,
|
|
2772
|
+
parties: pagedParties.items.map((partyId) => ({
|
|
2773
|
+
partyId,
|
|
2774
|
+
})),
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
async fetchPartyUpdates(nodes, partyId, options) {
|
|
2778
|
+
return this.fetchGlobalRecentUpdates(nodes, options?.limit ?? 25, {
|
|
2779
|
+
before: options?.before,
|
|
2780
|
+
after: options?.after,
|
|
2781
|
+
parties: [this.normalizePartyIdentifier(partyId)],
|
|
2782
|
+
templates: options?.templates,
|
|
2783
|
+
partyMode: options?.partyMode,
|
|
2784
|
+
hideSplice: options?.hideSplice,
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
async fetchGlobalContracts(nodes, limit = 25, options) {
|
|
2788
|
+
const normalizedLimit = typeof limit === 'number' && Number.isFinite(limit) && limit > 0
|
|
2789
|
+
? Math.trunc(limit)
|
|
2790
|
+
: 25;
|
|
2791
|
+
const beforeCursor = decodeGlobalContractCursor(options?.before);
|
|
2792
|
+
const afterCursor = beforeCursor === null ? decodeGlobalContractCursor(options?.after) : null;
|
|
2793
|
+
const useAfterCursor = Boolean(afterCursor && !beforeCursor);
|
|
2794
|
+
const pageSize = Math.max(normalizedLimit * 2, normalizedLimit + 1);
|
|
2795
|
+
const nodeStates = nodes.map((node) => ({
|
|
2796
|
+
node,
|
|
2797
|
+
nextBefore: undefined,
|
|
2798
|
+
exhausted: false,
|
|
2799
|
+
}));
|
|
2800
|
+
const mergedContractsByKey = new Map();
|
|
2801
|
+
const activePartyFilters = normalizePartyFilters(options?.parties);
|
|
2802
|
+
const activeTemplateFilters = normalizeTemplateFilters(options?.templates);
|
|
2803
|
+
const partyMode = normalizePartyFilterMode(options?.partyMode);
|
|
2804
|
+
const hideSplice = options?.hideSplice === true;
|
|
2805
|
+
const filterContracts = (contracts) => contracts.filter((contract) => {
|
|
2806
|
+
if (beforeCursor !== null && compareGlobalMergedContracts(contract, beforeCursor) <= 0) {
|
|
2807
|
+
return false;
|
|
2808
|
+
}
|
|
2809
|
+
if (afterCursor !== null && compareGlobalMergedContracts(contract, afterCursor) >= 0) {
|
|
2810
|
+
return false;
|
|
2811
|
+
}
|
|
2812
|
+
const normalizedTemplateId = this.normalizeTemplateIdentifier(contract.templateId);
|
|
2813
|
+
if (activeTemplateFilters.length > 0 &&
|
|
2814
|
+
(!normalizedTemplateId || !activeTemplateFilters.includes(normalizedTemplateId))) {
|
|
2815
|
+
return false;
|
|
2816
|
+
}
|
|
2817
|
+
if (hideSplice && normalizedTemplateId?.startsWith('Splice.')) {
|
|
2818
|
+
return false;
|
|
2819
|
+
}
|
|
2820
|
+
return true;
|
|
2821
|
+
});
|
|
2822
|
+
let filteredContracts = [];
|
|
2823
|
+
let hasMoreInDirection = false;
|
|
2824
|
+
while (true) {
|
|
2825
|
+
const nodesToFetch = nodeStates.filter((state) => !state.exhausted);
|
|
2826
|
+
if (nodesToFetch.length === 0) {
|
|
2827
|
+
break;
|
|
2828
|
+
}
|
|
2829
|
+
const responses = await Promise.all(nodesToFetch.map((state) => this.fetchNodeContracts(state.node, {
|
|
2830
|
+
limit: pageSize,
|
|
2831
|
+
before: state.nextBefore,
|
|
2832
|
+
parties: activePartyFilters.length > 0 ? activePartyFilters : undefined,
|
|
2833
|
+
templates: activeTemplateFilters.length > 0 ? activeTemplateFilters : undefined,
|
|
2834
|
+
partyMode,
|
|
2835
|
+
hideSplice,
|
|
2836
|
+
})));
|
|
2837
|
+
responses.forEach((response, index) => {
|
|
2838
|
+
const state = nodesToFetch[index];
|
|
2839
|
+
for (const contract of response.contracts) {
|
|
2840
|
+
mergedContractsByKey.set(`${state.node.id}:${contract.contractId}`, {
|
|
2841
|
+
nodeId: state.node.id,
|
|
2842
|
+
label: state.node.label,
|
|
2843
|
+
contractId: contract.contractId,
|
|
2844
|
+
templateId: contract.templateId,
|
|
2845
|
+
recordTime: contract.createdRecordTime,
|
|
2846
|
+
});
|
|
2847
|
+
}
|
|
2848
|
+
state.nextBefore = response.nextBefore ?? undefined;
|
|
2849
|
+
state.exhausted = response.nextBefore === null;
|
|
2850
|
+
});
|
|
2851
|
+
const sortedContracts = Array.from(mergedContractsByKey.values()).sort(compareGlobalMergedContracts);
|
|
2852
|
+
filteredContracts = filterContracts(sortedContracts);
|
|
2853
|
+
hasMoreInDirection = filteredContracts.length > normalizedLimit;
|
|
2854
|
+
if (hasMoreInDirection || nodeStates.every((state) => state.exhausted)) {
|
|
2855
|
+
break;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
const contracts = filteredContracts.slice(0, normalizedLimit);
|
|
2859
|
+
return {
|
|
2860
|
+
limit: normalizedLimit,
|
|
2861
|
+
nextBefore: contracts.length > 0 && (useAfterCursor || hasMoreInDirection)
|
|
2862
|
+
? encodeGlobalContractCursor(contracts[contracts.length - 1])
|
|
2863
|
+
: null,
|
|
2864
|
+
nextAfter: contracts.length === 0
|
|
2865
|
+
? null
|
|
2866
|
+
: useAfterCursor
|
|
2867
|
+
? hasMoreInDirection
|
|
2868
|
+
? encodeGlobalContractCursor(contracts[0])
|
|
2869
|
+
: null
|
|
2870
|
+
: beforeCursor
|
|
2871
|
+
? encodeGlobalContractCursor(contracts[0])
|
|
2872
|
+
: null,
|
|
2873
|
+
contracts,
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
async fetchPartyContracts(nodes, partyId, options) {
|
|
2877
|
+
const normalizedPartyId = this.normalizePartyIdentifier(partyId);
|
|
2878
|
+
const normalizedLimit = typeof options?.limit === 'number' && Number.isFinite(options.limit) && options.limit > 0
|
|
2879
|
+
? Math.trunc(options.limit)
|
|
2880
|
+
: 25;
|
|
2881
|
+
const beforeCursor = decodeGlobalContractCursor(options?.before);
|
|
2882
|
+
const afterCursor = beforeCursor === null ? decodeGlobalContractCursor(options?.after) : null;
|
|
2883
|
+
const useAfterCursor = Boolean(afterCursor && !beforeCursor);
|
|
2884
|
+
const pageSize = Math.max(normalizedLimit * 2, normalizedLimit + 1);
|
|
2885
|
+
const nodeStates = nodes.map((node) => ({
|
|
2886
|
+
node,
|
|
2887
|
+
nextBefore: undefined,
|
|
2888
|
+
exhausted: false,
|
|
2889
|
+
}));
|
|
2890
|
+
const mergedContractsByKey = new Map();
|
|
2891
|
+
const activeTemplateFilters = normalizeTemplateFilters(options?.templates);
|
|
2892
|
+
const hideSplice = options?.hideSplice === true;
|
|
2893
|
+
const filterContracts = (contracts) => {
|
|
2894
|
+
return contracts.filter((contract) => {
|
|
2895
|
+
if (beforeCursor !== null && compareGlobalMergedContracts(contract, beforeCursor) <= 0) {
|
|
2896
|
+
return false;
|
|
2897
|
+
}
|
|
2898
|
+
if (afterCursor !== null && compareGlobalMergedContracts(contract, afterCursor) >= 0) {
|
|
2899
|
+
return false;
|
|
2900
|
+
}
|
|
2901
|
+
const normalizedTemplateId = this.normalizeTemplateIdentifier(contract.templateId);
|
|
2902
|
+
if (activeTemplateFilters.length > 0 &&
|
|
2903
|
+
(!normalizedTemplateId || !activeTemplateFilters.includes(normalizedTemplateId))) {
|
|
2904
|
+
return false;
|
|
2905
|
+
}
|
|
2906
|
+
if (hideSplice && normalizedTemplateId?.startsWith('Splice.')) {
|
|
2907
|
+
return false;
|
|
2908
|
+
}
|
|
2909
|
+
return true;
|
|
2910
|
+
});
|
|
2911
|
+
};
|
|
2912
|
+
let filteredContracts = [];
|
|
2913
|
+
let hasMoreInDirection = false;
|
|
2914
|
+
while (true) {
|
|
2915
|
+
const nodesToFetch = nodeStates.filter((state) => !state.exhausted);
|
|
2916
|
+
if (nodesToFetch.length === 0) {
|
|
2917
|
+
break;
|
|
2918
|
+
}
|
|
2919
|
+
const responses = await Promise.all(nodesToFetch.map((state) => this.fetchPartyContractsForNode(state.node, normalizedPartyId, {
|
|
2920
|
+
limit: pageSize,
|
|
2921
|
+
before: state.nextBefore,
|
|
2922
|
+
})));
|
|
2923
|
+
responses.forEach((response, index) => {
|
|
2924
|
+
const state = nodesToFetch[index];
|
|
2925
|
+
for (const contract of response.contracts) {
|
|
2926
|
+
mergedContractsByKey.set(`${contract.nodeId}:${contract.contractId}`, contract);
|
|
2927
|
+
}
|
|
2928
|
+
state.nextBefore = response.nextBefore ?? undefined;
|
|
2929
|
+
state.exhausted = response.nextBefore === null;
|
|
2930
|
+
});
|
|
2931
|
+
const sortedContracts = Array.from(mergedContractsByKey.values()).sort(compareGlobalMergedContracts);
|
|
2932
|
+
filteredContracts = filterContracts(sortedContracts);
|
|
2933
|
+
hasMoreInDirection = filteredContracts.length > normalizedLimit;
|
|
2934
|
+
if (hasMoreInDirection || nodeStates.every((state) => state.exhausted)) {
|
|
2935
|
+
break;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
const contracts = filteredContracts.slice(0, normalizedLimit);
|
|
2939
|
+
return {
|
|
2940
|
+
limit: normalizedLimit,
|
|
2941
|
+
nextBefore: contracts.length > 0 && (useAfterCursor || hasMoreInDirection)
|
|
2942
|
+
? encodeGlobalContractCursor(contracts[contracts.length - 1])
|
|
2943
|
+
: null,
|
|
2944
|
+
nextAfter: contracts.length === 0
|
|
2945
|
+
? null
|
|
2946
|
+
: useAfterCursor
|
|
2947
|
+
? hasMoreInDirection
|
|
2948
|
+
? encodeGlobalContractCursor(contracts[0])
|
|
2949
|
+
: null
|
|
2950
|
+
: beforeCursor
|
|
2951
|
+
? encodeGlobalContractCursor(contracts[0])
|
|
2952
|
+
: null,
|
|
2953
|
+
contracts,
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
async fetchTokens(nodes, limit = 25, options) {
|
|
2957
|
+
const refreshResults = await Promise.allSettled(nodes.map(async (node) => ({
|
|
2958
|
+
node,
|
|
2959
|
+
tokens: await this.loadCachedObservedTokens(node),
|
|
2960
|
+
})));
|
|
2961
|
+
const successfulTokens = refreshResults
|
|
2962
|
+
.filter((result) => result.status === 'fulfilled')
|
|
2963
|
+
.flatMap((result) => result.value.tokens);
|
|
2964
|
+
if (successfulTokens.length === 0) {
|
|
2965
|
+
const firstFailure = refreshResults.find((result) => result.status === 'rejected');
|
|
2966
|
+
if (firstFailure) {
|
|
2967
|
+
throw firstFailure.reason;
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
const dedupedTokens = new Map();
|
|
2971
|
+
for (const token of successfulTokens) {
|
|
2972
|
+
if (!dedupedTokens.has(token.tokenId)) {
|
|
2973
|
+
dedupedTokens.set(token.tokenId, token);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
const filteredTokens = this.filterTokens(Array.from(dedupedTokens.values()), options);
|
|
2977
|
+
return this.paginateTokens(filteredTokens.sort(compareGlobalTokens), limit, options);
|
|
2978
|
+
}
|
|
2979
|
+
async fetchLatestTokenTransfers(nodes, limit = 25, options) {
|
|
2980
|
+
const mergedTransfers = this.filterTokenTransfersByParties(await this.loadMergedTokenTransfers(nodes), options);
|
|
2981
|
+
return this.paginateTokenTransfers(mergedTransfers, limit, options);
|
|
2982
|
+
}
|
|
2983
|
+
async fetchTokenTransfers(nodes, tokenId, limit = 25, options) {
|
|
2984
|
+
const normalizedTokenId = this.normalizeTokenId(tokenId);
|
|
2985
|
+
await this.findObservedToken(nodes, normalizedTokenId);
|
|
2986
|
+
const mergedTransfers = this.filterTokenTransfersByParties(await this.loadMergedTokenTransfers(nodes), options);
|
|
2987
|
+
const directTransfers = mergedTransfers.filter((transfer) => transfer.tokenId === normalizedTokenId);
|
|
2988
|
+
if (directTransfers.length > 0) {
|
|
2989
|
+
return this.paginateTokenTransfers(directTransfers, limit, options);
|
|
2990
|
+
}
|
|
2991
|
+
const recoveredTransfers = this.filterTokenTransfersByParties(await this.recoverTokenTransfersFromActiveHolders(nodes, normalizedTokenId), options);
|
|
2992
|
+
return this.paginateTokenTransfers(recoveredTransfers, limit, options);
|
|
2993
|
+
}
|
|
2994
|
+
async fetchTokenTransferDetail(nodes, transferId) {
|
|
2995
|
+
const normalizedTransferId = this.normalizeUpdateId(transferId);
|
|
2996
|
+
const transfer = (await this.loadMergedTokenTransfers(nodes))
|
|
2997
|
+
.sort(compareGlobalTokenTransfers)
|
|
2998
|
+
.find((entry) => entry.rowId === normalizedTransferId || entry.updateId === normalizedTransferId);
|
|
2999
|
+
if (!transfer) {
|
|
3000
|
+
throw new Error('Token transfer not found');
|
|
3001
|
+
}
|
|
3002
|
+
return transfer;
|
|
3003
|
+
}
|
|
3004
|
+
async fetchTokenDetail(nodes, tokenId) {
|
|
3005
|
+
const normalizedTokenId = this.normalizeTokenId(tokenId);
|
|
3006
|
+
const token = await this.findObservedToken(nodes, normalizedTokenId);
|
|
3007
|
+
const transfersResponse = await this.fetchTokenTransfers(nodes, normalizedTokenId, 25);
|
|
3008
|
+
return {
|
|
3009
|
+
token,
|
|
3010
|
+
transfers: transfersResponse.transfers,
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
async fetchTokenHolders(nodes, tokenId, limit = 25, options) {
|
|
3014
|
+
const normalizedTokenId = this.normalizeTokenId(tokenId);
|
|
3015
|
+
await this.findObservedToken(nodes, normalizedTokenId);
|
|
3016
|
+
const refreshResults = await Promise.allSettled(nodes.map(async (node) => ({
|
|
3017
|
+
node,
|
|
3018
|
+
holders: await this.loadCachedTokenHolders(node),
|
|
3019
|
+
})));
|
|
3020
|
+
const successfulHolders = refreshResults
|
|
3021
|
+
.filter((result) => result.status === 'fulfilled')
|
|
3022
|
+
.flatMap((result) => result.value.holders)
|
|
3023
|
+
.filter((holder) => holder.tokenId === normalizedTokenId);
|
|
3024
|
+
if (successfulHolders.length === 0) {
|
|
3025
|
+
const firstFailure = refreshResults.find((result) => result.status === 'rejected');
|
|
3026
|
+
if (firstFailure) {
|
|
3027
|
+
throw firstFailure.reason;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
return this.paginateTokenHolders(normalizedTokenId, this.mergeTokenHolders(successfulHolders), limit, options);
|
|
3031
|
+
}
|
|
3032
|
+
paginateTokenHolders(tokenId, holders, limit = 25, options) {
|
|
3033
|
+
const normalizedLimit = Number.isFinite(limit) && Number(limit) > 0 ? Math.trunc(Number(limit)) : 25;
|
|
3034
|
+
const beforeCursor = decodeGlobalTokenHolderCursor(options?.before);
|
|
3035
|
+
const afterCursor = beforeCursor === null ? decodeGlobalTokenHolderCursor(options?.after) : null;
|
|
3036
|
+
const useAfterCursor = Boolean(afterCursor && !beforeCursor);
|
|
3037
|
+
const filteredHolders = holders.filter((holder) => {
|
|
3038
|
+
if (beforeCursor !== null) {
|
|
3039
|
+
return compareGlobalTokenHolders(holder, beforeCursor) > 0;
|
|
3040
|
+
}
|
|
3041
|
+
if (afterCursor !== null) {
|
|
3042
|
+
return compareGlobalTokenHolders(holder, afterCursor) < 0;
|
|
3043
|
+
}
|
|
3044
|
+
return true;
|
|
3045
|
+
});
|
|
3046
|
+
const hasMoreInDirection = filteredHolders.length > normalizedLimit;
|
|
3047
|
+
const pagedHolders = filteredHolders.slice(0, normalizedLimit);
|
|
3048
|
+
return {
|
|
3049
|
+
tokenId,
|
|
3050
|
+
limit: normalizedLimit,
|
|
3051
|
+
nextBefore: pagedHolders.length > 0 && (useAfterCursor || hasMoreInDirection)
|
|
3052
|
+
? encodeGlobalTokenHolderCursor(pagedHolders[pagedHolders.length - 1])
|
|
3053
|
+
: null,
|
|
3054
|
+
nextAfter: pagedHolders.length === 0
|
|
3055
|
+
? null
|
|
3056
|
+
: useAfterCursor
|
|
3057
|
+
? hasMoreInDirection
|
|
3058
|
+
? encodeGlobalTokenHolderCursor(pagedHolders[0])
|
|
3059
|
+
: null
|
|
3060
|
+
: beforeCursor
|
|
3061
|
+
? encodeGlobalTokenHolderCursor(pagedHolders[0])
|
|
3062
|
+
: null,
|
|
3063
|
+
holders: pagedHolders,
|
|
3064
|
+
};
|
|
3065
|
+
}
|
|
3066
|
+
paginateTokens(tokens, limit = 25, options) {
|
|
3067
|
+
const normalizedLimit = Number.isFinite(limit) && Number(limit) > 0 ? Math.trunc(Number(limit)) : 25;
|
|
3068
|
+
const beforeCursor = decodeGlobalTokenCursor(options?.before);
|
|
3069
|
+
const afterCursor = beforeCursor === null ? decodeGlobalTokenCursor(options?.after) : null;
|
|
3070
|
+
const useAfterCursor = Boolean(afterCursor && !beforeCursor);
|
|
3071
|
+
const filteredTokens = tokens.filter((token) => {
|
|
3072
|
+
if (beforeCursor !== null) {
|
|
3073
|
+
return compareGlobalTokens(token, beforeCursor) > 0;
|
|
3074
|
+
}
|
|
3075
|
+
if (afterCursor !== null) {
|
|
3076
|
+
return compareGlobalTokens(token, afterCursor) < 0;
|
|
3077
|
+
}
|
|
3078
|
+
return true;
|
|
3079
|
+
});
|
|
3080
|
+
const hasMoreInDirection = filteredTokens.length > normalizedLimit;
|
|
3081
|
+
const pagedTokens = filteredTokens.slice(0, normalizedLimit);
|
|
3082
|
+
return {
|
|
3083
|
+
limit: normalizedLimit,
|
|
3084
|
+
nextBefore: pagedTokens.length > 0 && (useAfterCursor || hasMoreInDirection)
|
|
3085
|
+
? encodeGlobalTokenCursor(pagedTokens[pagedTokens.length - 1])
|
|
3086
|
+
: null,
|
|
3087
|
+
nextAfter: pagedTokens.length === 0
|
|
3088
|
+
? null
|
|
3089
|
+
: useAfterCursor
|
|
3090
|
+
? hasMoreInDirection
|
|
3091
|
+
? encodeGlobalTokenCursor(pagedTokens[0])
|
|
3092
|
+
: null
|
|
3093
|
+
: beforeCursor
|
|
3094
|
+
? encodeGlobalTokenCursor(pagedTokens[0])
|
|
3095
|
+
: null,
|
|
3096
|
+
tokens: pagedTokens,
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
filterTokens(tokens, options) {
|
|
3100
|
+
const activeNameFilters = normalizeTokenTextFilters(options?.names);
|
|
3101
|
+
const activeExcludedNameFilters = normalizeTokenTextFilters(options?.excludeNames);
|
|
3102
|
+
const activeIssuerFilters = normalizeTokenIssuerFilters(options?.issuers);
|
|
3103
|
+
if (activeNameFilters.length === 0
|
|
3104
|
+
&& activeExcludedNameFilters.length === 0
|
|
3105
|
+
&& activeIssuerFilters.length === 0) {
|
|
3106
|
+
return tokens;
|
|
3107
|
+
}
|
|
3108
|
+
return tokens.filter((token) => {
|
|
3109
|
+
const tokenName = token.name.trim().toLowerCase();
|
|
3110
|
+
const tokenSymbol = token.symbol?.trim().toLowerCase() ?? '';
|
|
3111
|
+
const matchesNameFilter = activeNameFilters.length === 0
|
|
3112
|
+
|| activeNameFilters.some((filterValue) => tokenName.includes(filterValue) || tokenSymbol.includes(filterValue));
|
|
3113
|
+
if (!matchesNameFilter) {
|
|
3114
|
+
return false;
|
|
3115
|
+
}
|
|
3116
|
+
const matchesExcludedNameFilter = activeExcludedNameFilters.some((filterValue) => tokenName.includes(filterValue) || tokenSymbol.includes(filterValue));
|
|
3117
|
+
if (matchesExcludedNameFilter) {
|
|
3118
|
+
return false;
|
|
3119
|
+
}
|
|
3120
|
+
if (activeIssuerFilters.length === 0) {
|
|
3121
|
+
return true;
|
|
3122
|
+
}
|
|
3123
|
+
return token.issuer !== null && activeIssuerFilters.includes(token.issuer);
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
async loadMergedTokenTransfers(nodes) {
|
|
3127
|
+
const refreshResults = await Promise.allSettled(nodes.map(async (node) => ({
|
|
3128
|
+
node,
|
|
3129
|
+
transfers: await this.loadCachedTokenTransfers(node),
|
|
3130
|
+
})));
|
|
3131
|
+
const successfulTransfers = refreshResults
|
|
3132
|
+
.filter((result) => result.status === 'fulfilled')
|
|
3133
|
+
.flatMap((result) => result.value.transfers);
|
|
3134
|
+
if (successfulTransfers.length === 0) {
|
|
3135
|
+
const firstFailure = refreshResults.find((result) => result.status === 'rejected');
|
|
3136
|
+
if (firstFailure) {
|
|
3137
|
+
throw firstFailure.reason;
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
return this.mergeTokenTransfers(successfulTransfers);
|
|
3141
|
+
}
|
|
3142
|
+
async recoverTokenTransfersFromActiveHolders(nodes, tokenId) {
|
|
3143
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
3144
|
+
const refreshResults = await Promise.allSettled(nodes.map(async (node) => ({
|
|
3145
|
+
node,
|
|
3146
|
+
holders: await this.loadCachedTokenHolders(node),
|
|
3147
|
+
})));
|
|
3148
|
+
const candidateHolders = refreshResults
|
|
3149
|
+
.filter((result) => result.status === 'fulfilled')
|
|
3150
|
+
.flatMap((result) => result.value.holders)
|
|
3151
|
+
.filter((holder) => holder.tokenId === tokenId && holder.contractId !== null);
|
|
3152
|
+
const recoveredTransfers = [];
|
|
3153
|
+
const seenContracts = new Set();
|
|
3154
|
+
for (const holder of candidateHolders) {
|
|
3155
|
+
const node = nodeById.get(holder.nodeId);
|
|
3156
|
+
if (!node || !holder.contractId) {
|
|
3157
|
+
continue;
|
|
3158
|
+
}
|
|
3159
|
+
const dedupeKey = `${holder.nodeId}\u0000${holder.contractId}`;
|
|
3160
|
+
if (seenContracts.has(dedupeKey)) {
|
|
3161
|
+
continue;
|
|
3162
|
+
}
|
|
3163
|
+
seenContracts.add(dedupeKey);
|
|
3164
|
+
const client = this.clientFactory.getClient(node);
|
|
3165
|
+
const contractResult = await client.query(contractDetailQuery(holder.contractId));
|
|
3166
|
+
const contractRow = contractResult.rows[0];
|
|
3167
|
+
if (!contractRow || typeof contractRow.created_update_id !== 'string') {
|
|
3168
|
+
continue;
|
|
3169
|
+
}
|
|
3170
|
+
const events = await this.fetchEventsByUpdateId(node, client.query.bind(client), contractRow.created_update_id);
|
|
3171
|
+
const inferredTransfers = this.inferCip112TokenMovements(node, {
|
|
3172
|
+
update_id: contractRow.created_update_id,
|
|
3173
|
+
event_offset: contractRow.created_event_offset,
|
|
3174
|
+
record_time: contractRow.created_record_time,
|
|
3175
|
+
}, events).filter((transfer) => transfer.tokenId === tokenId);
|
|
3176
|
+
recoveredTransfers.push(...inferredTransfers);
|
|
3177
|
+
}
|
|
3178
|
+
return this.mergeTokenTransfers(recoveredTransfers);
|
|
3179
|
+
}
|
|
3180
|
+
paginateTokenTransfers(transfers, limit = 25, options) {
|
|
3181
|
+
const normalizedLimit = Number.isFinite(limit) && Number(limit) > 0 ? Math.trunc(Number(limit)) : 25;
|
|
3182
|
+
const beforeCursor = decodeGlobalTokenTransferCursor(options?.before);
|
|
3183
|
+
const afterCursor = beforeCursor === null ? decodeGlobalTokenTransferCursor(options?.after) : null;
|
|
3184
|
+
const useAfterCursor = Boolean(afterCursor && !beforeCursor);
|
|
3185
|
+
const filteredTransfers = transfers
|
|
3186
|
+
.sort(compareGlobalTokenTransfers)
|
|
3187
|
+
.filter((transfer) => {
|
|
3188
|
+
if (beforeCursor !== null) {
|
|
3189
|
+
return compareGlobalTokenTransfers(transfer, beforeCursor) > 0;
|
|
3190
|
+
}
|
|
3191
|
+
if (afterCursor !== null) {
|
|
3192
|
+
return compareGlobalTokenTransfers(transfer, afterCursor) < 0;
|
|
3193
|
+
}
|
|
3194
|
+
return true;
|
|
3195
|
+
});
|
|
3196
|
+
const hasMoreInDirection = filteredTransfers.length > normalizedLimit;
|
|
3197
|
+
const pagedTransfers = filteredTransfers.slice(0, normalizedLimit);
|
|
3198
|
+
return {
|
|
3199
|
+
limit: normalizedLimit,
|
|
3200
|
+
nextBefore: pagedTransfers.length > 0 && (useAfterCursor || hasMoreInDirection)
|
|
3201
|
+
? encodeGlobalTokenTransferCursor(pagedTransfers[pagedTransfers.length - 1])
|
|
3202
|
+
: null,
|
|
3203
|
+
nextAfter: pagedTransfers.length === 0
|
|
3204
|
+
? null
|
|
3205
|
+
: useAfterCursor
|
|
3206
|
+
? hasMoreInDirection
|
|
3207
|
+
? encodeGlobalTokenTransferCursor(pagedTransfers[0])
|
|
3208
|
+
: null
|
|
3209
|
+
: beforeCursor
|
|
3210
|
+
? encodeGlobalTokenTransferCursor(pagedTransfers[0])
|
|
3211
|
+
: null,
|
|
3212
|
+
transfers: pagedTransfers,
|
|
3213
|
+
};
|
|
3214
|
+
}
|
|
3215
|
+
filterTokenTransfersByParties(transfers, options) {
|
|
3216
|
+
const activeFromParties = normalizePartyFilters(options?.fromParties);
|
|
3217
|
+
const activeToParties = normalizePartyFilters(options?.toParties);
|
|
3218
|
+
const amountGt = options?.amountGt?.trim() || null;
|
|
3219
|
+
const amountLt = options?.amountLt?.trim() || null;
|
|
3220
|
+
if (activeFromParties.length === 0 &&
|
|
3221
|
+
activeToParties.length === 0 &&
|
|
3222
|
+
amountGt === null &&
|
|
3223
|
+
amountLt === null) {
|
|
3224
|
+
return transfers;
|
|
3225
|
+
}
|
|
3226
|
+
return transfers.filter((transfer) => {
|
|
3227
|
+
const matchesFrom = activeFromParties.length === 0 ||
|
|
3228
|
+
(transfer.sender !== null && activeFromParties.includes(transfer.sender));
|
|
3229
|
+
const matchesTo = activeToParties.length === 0 ||
|
|
3230
|
+
(transfer.receiver !== null && activeToParties.includes(transfer.receiver));
|
|
3231
|
+
const matchesAmountGt = amountGt === null ||
|
|
3232
|
+
(transfer.amount !== null && this.compareNumericStrings(transfer.amount, amountGt) > 0);
|
|
3233
|
+
const matchesAmountLt = amountLt === null ||
|
|
3234
|
+
(transfer.amount !== null && this.compareNumericStrings(transfer.amount, amountLt) < 0);
|
|
3235
|
+
return matchesFrom && matchesTo && matchesAmountGt && matchesAmountLt;
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
async fetchUpdateDetail(node, eventOffset) {
|
|
3239
|
+
const client = this.clientFactory.getClient(node);
|
|
3240
|
+
const detailResult = await client.query(singleUpdateQuery(eventOffset));
|
|
3241
|
+
const detailRows = detailResult.rows ?? [];
|
|
3242
|
+
const detailRow = detailRows[0];
|
|
3243
|
+
if (!detailRow) {
|
|
3244
|
+
throw new Error('Update not found');
|
|
3245
|
+
}
|
|
3246
|
+
const rawUpdateId = this.extractRawUpdateId(detailRow);
|
|
3247
|
+
const matchedEventOffset = this.extractEventOffset(detailRow);
|
|
3248
|
+
const canonicalUpdateId = this.normalizeUpdateId(rawUpdateId);
|
|
3249
|
+
const partiesByUpdateId = await this.fetchPartiesByUpdateId(client.query.bind(client), [rawUpdateId]);
|
|
3250
|
+
const events = await this.fetchEventsByUpdateId(node, client.query.bind(client), rawUpdateId);
|
|
3251
|
+
const exerciseData = this.shouldResolveRewardCoupon(events)
|
|
3252
|
+
? await this.fetchRewardCouponDetails(client.query.bind(client), rawUpdateId)
|
|
3253
|
+
: null;
|
|
3254
|
+
return {
|
|
3255
|
+
nodeId: node.id,
|
|
3256
|
+
label: node.label,
|
|
3257
|
+
eventOffset: matchedEventOffset,
|
|
3258
|
+
updateId: canonicalUpdateId,
|
|
3259
|
+
recordTime: this.extractIsoRecordTime(detailRow),
|
|
3260
|
+
parties: partiesByUpdateId.get(canonicalUpdateId) ?? [],
|
|
3261
|
+
meta: this.extractMeta(detailRow),
|
|
3262
|
+
events: this.attachRewardCouponDetails(events, exerciseData),
|
|
3263
|
+
};
|
|
3264
|
+
}
|
|
3265
|
+
async fetchContractDetail(node, contractId) {
|
|
3266
|
+
const client = this.clientFactory.getClient(node);
|
|
3267
|
+
const result = await client.query(contractDetailQuery(contractId));
|
|
3268
|
+
const row = result.rows[0];
|
|
3269
|
+
if (!row) {
|
|
3270
|
+
throw new Error('Contract not found');
|
|
3271
|
+
}
|
|
3272
|
+
const templateId = this.normalizeTemplateIdentifier(row.template_id);
|
|
3273
|
+
const packageId = typeof row.package_id === 'string' ? row.package_id : null;
|
|
3274
|
+
const packageMetadata = packageId && this.packageCacheService ? this.packageCacheService.getPackage(packageId) : null;
|
|
3275
|
+
return {
|
|
3276
|
+
nodeId: node.id,
|
|
3277
|
+
label: node.label,
|
|
3278
|
+
contractId: row.contract_id,
|
|
3279
|
+
templateId,
|
|
3280
|
+
packageId,
|
|
3281
|
+
packageName: packageMetadata?.name ?? null,
|
|
3282
|
+
packageVersion: packageMetadata?.version ?? null,
|
|
3283
|
+
createdUpdateId: typeof row.created_update_id === 'string' ? row.created_update_id : null,
|
|
3284
|
+
createdEventOffset: this.normalizeOptionalScalar(row.created_event_offset),
|
|
3285
|
+
createdRecordTime: typeof row.created_record_time === 'string' ? row.created_record_time : null,
|
|
3286
|
+
archivedUpdateId: typeof row.archived_update_id === 'string' ? row.archived_update_id : null,
|
|
3287
|
+
archivedEventOffset: this.normalizeOptionalScalar(row.archived_event_offset),
|
|
3288
|
+
archivedRecordTime: typeof row.archived_record_time === 'string' ? row.archived_record_time : null,
|
|
3289
|
+
contractData: await this.decodeContractData(node, packageId, templateId, row.contract_instance),
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3292
|
+
async querySummary(query) {
|
|
3293
|
+
try {
|
|
3294
|
+
return await query(ACTIVE_QUERY);
|
|
3295
|
+
}
|
|
3296
|
+
catch (error) {
|
|
3297
|
+
if (!this.shouldFallbackToParticipantTables(error)) {
|
|
3298
|
+
throw error;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
return query(PARTICIPANT_FALLBACK_QUERY);
|
|
3302
|
+
}
|
|
3303
|
+
async fetchPartyRecentUpdatesForNode(node, partyId, limit) {
|
|
3304
|
+
const client = this.clientFactory.getClient(node);
|
|
3305
|
+
let rows = [];
|
|
3306
|
+
try {
|
|
3307
|
+
const result = await client.query(partyRecentUpdatesQuery(partyId, limit));
|
|
3308
|
+
rows = result.rows ?? [];
|
|
3309
|
+
}
|
|
3310
|
+
catch (error) {
|
|
3311
|
+
if (!this.shouldFallbackToNormalizedEventTables(error)) {
|
|
3312
|
+
throw error;
|
|
3313
|
+
}
|
|
3314
|
+
const result = await client.query(normalizedPartyRecentUpdatesQuery(partyId, limit));
|
|
3315
|
+
rows = result.rows ?? [];
|
|
3316
|
+
}
|
|
3317
|
+
const rawUpdateIds = rows.map((row) => row.update_id);
|
|
3318
|
+
const partiesByUpdateId = rawUpdateIds.length > 0
|
|
3319
|
+
? await this.fetchPartiesByUpdateId(client.query.bind(client), rawUpdateIds)
|
|
3320
|
+
: new Map();
|
|
3321
|
+
return rows.map((row) => {
|
|
3322
|
+
const updateId = this.normalizeUpdateId(row.update_id);
|
|
3323
|
+
return {
|
|
3324
|
+
nodeId: node.id,
|
|
3325
|
+
label: node.label,
|
|
3326
|
+
eventOffset: this.extractEventOffset(row),
|
|
3327
|
+
updateId,
|
|
3328
|
+
recordTime: typeof row.record_time === 'string' ? row.record_time : null,
|
|
3329
|
+
parties: partiesByUpdateId.get(updateId) ?? [],
|
|
3330
|
+
};
|
|
3331
|
+
});
|
|
3332
|
+
}
|
|
3333
|
+
async fetchPartyRecentContractsForNode(node, partyId, limit) {
|
|
3334
|
+
const response = await this.fetchPartyContractsForNode(node, partyId, { limit });
|
|
3335
|
+
return response.contracts;
|
|
3336
|
+
}
|
|
3337
|
+
async fetchPartyContractsForNode(node, partyId, options) {
|
|
3338
|
+
const client = this.clientFactory.getClient(node);
|
|
3339
|
+
const normalizedLimit = typeof options?.limit === 'number' && Number.isFinite(options.limit) && options.limit > 0
|
|
3340
|
+
? Math.trunc(options.limit)
|
|
3341
|
+
: 25;
|
|
3342
|
+
const before = options?.before;
|
|
3343
|
+
const after = options?.after;
|
|
3344
|
+
const useAfterCursor = Boolean(after && !before);
|
|
3345
|
+
let rows = [];
|
|
3346
|
+
try {
|
|
3347
|
+
const result = await client.query(partyRecentContractsQuery(partyId, normalizedLimit, before, after));
|
|
3348
|
+
rows = result.rows ?? [];
|
|
3349
|
+
}
|
|
3350
|
+
catch (error) {
|
|
3351
|
+
if (!this.shouldFallbackToNormalizedEventTables(error)) {
|
|
3352
|
+
throw error;
|
|
3353
|
+
}
|
|
3354
|
+
const result = await client.query(normalizedPartyRecentContractsQuery(partyId, normalizedLimit, before, after));
|
|
3355
|
+
rows = result.rows ?? [];
|
|
3356
|
+
}
|
|
3357
|
+
const hasMoreInQuery = rows.length > normalizedLimit;
|
|
3358
|
+
const trimmedRows = rows.slice(0, normalizedLimit);
|
|
3359
|
+
const orderedContracts = (useAfterCursor ? [...trimmedRows].reverse() : trimmedRows)
|
|
3360
|
+
.filter((row) => typeof row.contract_id === 'string')
|
|
3361
|
+
.map((row) => {
|
|
3362
|
+
const packageId = this.normalizePackageIdentifier(row.package_id ?? null);
|
|
3363
|
+
const packageMetadata = packageId && this.packageCacheService ? this.packageCacheService.getPackage(packageId) : null;
|
|
3364
|
+
return {
|
|
3365
|
+
nodeId: node.id,
|
|
3366
|
+
label: node.label,
|
|
3367
|
+
contractId: row.contract_id,
|
|
3368
|
+
templateId: this.normalizeTemplateIdentifier(row.template_id),
|
|
3369
|
+
packageId,
|
|
3370
|
+
packageName: packageMetadata?.name ?? null,
|
|
3371
|
+
packageVersion: packageMetadata?.version ?? null,
|
|
3372
|
+
recordTime: typeof row.record_time === 'string' ? row.record_time : null,
|
|
3373
|
+
createdEventOffset: this.normalizeOptionalScalar(row.created_event_offset),
|
|
3374
|
+
};
|
|
3375
|
+
});
|
|
3376
|
+
return {
|
|
3377
|
+
nextBefore: orderedContracts.length > 0 && (useAfterCursor || hasMoreInQuery)
|
|
3378
|
+
? orderedContracts[orderedContracts.length - 1]?.createdEventOffset ?? null
|
|
3379
|
+
: null,
|
|
3380
|
+
nextAfter: orderedContracts.length === 0
|
|
3381
|
+
? null
|
|
3382
|
+
: useAfterCursor
|
|
3383
|
+
? hasMoreInQuery
|
|
3384
|
+
? orderedContracts[0]?.createdEventOffset ?? null
|
|
3385
|
+
: null
|
|
3386
|
+
: before
|
|
3387
|
+
? orderedContracts[0]?.createdEventOffset ?? null
|
|
3388
|
+
: null,
|
|
3389
|
+
contracts: orderedContracts.map(({ createdEventOffset: _, ...contract }) => contract),
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
shouldFallbackToParticipantTables(error) {
|
|
3393
|
+
if (!(error instanceof Error)) {
|
|
3394
|
+
return false;
|
|
3395
|
+
}
|
|
3396
|
+
const code = error.code;
|
|
3397
|
+
return code === '42883' || error.message.includes('function active() does not exist');
|
|
3398
|
+
}
|
|
3399
|
+
shouldFallbackToNormalizedEventTables(error) {
|
|
3400
|
+
if (!(error instanceof Error)) {
|
|
3401
|
+
return false;
|
|
3402
|
+
}
|
|
3403
|
+
const code = error.code;
|
|
3404
|
+
return code === '42P01' || error.message.includes('does not exist');
|
|
3405
|
+
}
|
|
3406
|
+
async fetchActivePartiesForNode(node) {
|
|
3407
|
+
const client = this.clientFactory.getClient(node);
|
|
3408
|
+
try {
|
|
3409
|
+
const result = await client.query(activePartiesQuery());
|
|
3410
|
+
const row = result.rows[0];
|
|
3411
|
+
return this.normalizeParties(row?.parties ?? null);
|
|
3412
|
+
}
|
|
3413
|
+
catch (error) {
|
|
3414
|
+
if (!this.shouldFallbackToNormalizedEventTables(error)) {
|
|
3415
|
+
throw error;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
const fallbackResult = await client.query(normalizedActivePartiesQuery());
|
|
3419
|
+
const fallbackRow = fallbackResult.rows[0];
|
|
3420
|
+
return this.normalizeParties(fallbackRow?.parties ?? null);
|
|
3421
|
+
}
|
|
3422
|
+
async fetchPartiesByUpdateId(query, updateIds) {
|
|
3423
|
+
try {
|
|
3424
|
+
const result = await query(recentUpdatePartiesQuery(updateIds));
|
|
3425
|
+
const rows = result.rows ?? [];
|
|
3426
|
+
return new Map(rows.map((row) => [
|
|
3427
|
+
this.normalizeUpdateId(row.update_id),
|
|
3428
|
+
this.normalizeParties(row.parties),
|
|
3429
|
+
]));
|
|
3430
|
+
}
|
|
3431
|
+
catch (error) {
|
|
3432
|
+
if (!this.shouldFallbackToNormalizedEventTables(error)) {
|
|
3433
|
+
return new Map();
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
try {
|
|
3437
|
+
const fallbackResult = await query(normalizedRecentUpdatePartiesQuery(updateIds));
|
|
3438
|
+
const fallbackRows = fallbackResult.rows ?? [];
|
|
3439
|
+
return new Map(fallbackRows.map((row) => [
|
|
3440
|
+
this.normalizeUpdateId(row.update_id),
|
|
3441
|
+
this.normalizeParties(row.parties),
|
|
3442
|
+
]));
|
|
3443
|
+
}
|
|
3444
|
+
catch {
|
|
3445
|
+
return new Map();
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
async loadCachedObservedTokens(node) {
|
|
3449
|
+
const cached = this.observedTokensByNode.get(node.id);
|
|
3450
|
+
if (cached && Date.now() - cached.cachedAt < TOKEN_TRANSFER_CACHE_TTL_MS) {
|
|
3451
|
+
return cached.tokens;
|
|
3452
|
+
}
|
|
3453
|
+
const useGrpcHoldingViews = node.mode === 'pqs_with_grpc' && this.grpcOperationsService !== undefined;
|
|
3454
|
+
const [pqsTokens, grpcTokens] = await Promise.all([
|
|
3455
|
+
this.fetchObservedTokensForNode(node, TOKEN_TRANSFER_CACHE_LIMIT, {
|
|
3456
|
+
includeCip112: !useGrpcHoldingViews,
|
|
3457
|
+
}),
|
|
3458
|
+
useGrpcHoldingViews
|
|
3459
|
+
? this.grpcOperationsService.fetchHoldingV2Tokens(node)
|
|
3460
|
+
: Promise.resolve([]),
|
|
3461
|
+
]);
|
|
3462
|
+
const dedupedTokens = new Map();
|
|
3463
|
+
for (const token of [...pqsTokens, ...grpcTokens]) {
|
|
3464
|
+
if (!dedupedTokens.has(token.tokenId)) {
|
|
3465
|
+
dedupedTokens.set(token.tokenId, token);
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
const tokens = Array.from(dedupedTokens.values()).sort(compareGlobalTokens);
|
|
3469
|
+
this.observedTokensByNode.set(node.id, {
|
|
3470
|
+
cachedAt: Date.now(),
|
|
3471
|
+
tokens,
|
|
3472
|
+
});
|
|
3473
|
+
return tokens;
|
|
3474
|
+
}
|
|
3475
|
+
async fetchObservedTokensForNode(node, limit, options) {
|
|
3476
|
+
const client = this.clientFactory.getClient(node);
|
|
3477
|
+
const includeCip112 = options?.includeCip112 ?? true;
|
|
3478
|
+
const result = await client.query(tokenRowsQuery(limit, includeCip112 ? TOKEN_DISCOVERY_TEMPLATE_IDS : TOKEN_DISCOVERY_NON_CIP112_TEMPLATE_IDS, includeCip112 ? TOKEN_DISCOVERY_TEMPLATE_PATTERNS : TOKEN_DISCOVERY_NON_CIP112_TEMPLATE_PATTERNS));
|
|
3479
|
+
const rows = result.rows ?? [];
|
|
3480
|
+
const dedupedTokens = new Map();
|
|
3481
|
+
for (const row of rows) {
|
|
3482
|
+
const token = await this.normalizeObservedTokenRow(node, row);
|
|
3483
|
+
if (token && !dedupedTokens.has(token.tokenId)) {
|
|
3484
|
+
dedupedTokens.set(token.tokenId, token);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
return Array.from(dedupedTokens.values()).sort((left, right) => left.name.localeCompare(right.name) || left.tokenId.localeCompare(right.tokenId));
|
|
3488
|
+
}
|
|
3489
|
+
async findObservedToken(nodes, tokenId) {
|
|
3490
|
+
const refreshResults = await Promise.allSettled(nodes.map(async (node) => ({
|
|
3491
|
+
node,
|
|
3492
|
+
tokens: await this.loadCachedObservedTokens(node),
|
|
3493
|
+
})));
|
|
3494
|
+
const successfulTokens = refreshResults
|
|
3495
|
+
.filter((result) => result.status === 'fulfilled')
|
|
3496
|
+
.flatMap((result) => result.value.tokens);
|
|
3497
|
+
if (successfulTokens.length === 0) {
|
|
3498
|
+
const firstFailure = refreshResults.find((result) => result.status === 'rejected');
|
|
3499
|
+
if (firstFailure) {
|
|
3500
|
+
throw firstFailure.reason;
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
const token = successfulTokens.find((candidate) => candidate.tokenId === tokenId);
|
|
3504
|
+
if (!token) {
|
|
3505
|
+
throw new Error('Token not found');
|
|
3506
|
+
}
|
|
3507
|
+
return token;
|
|
3508
|
+
}
|
|
3509
|
+
async normalizeObservedTokenRow(node, row) {
|
|
3510
|
+
const templateId = this.normalizeTemplateIdentifier(row.template_id);
|
|
3511
|
+
if (templateId === CANTON_COIN_TRANSFER_TEMPLATE_ID ||
|
|
3512
|
+
templateId === CANTON_COIN_AMULET_TEMPLATE_ID) {
|
|
3513
|
+
return {
|
|
3514
|
+
tokenId: CANTON_COIN_TOKEN_ID,
|
|
3515
|
+
name: CANTON_COIN_TOKEN_NAME,
|
|
3516
|
+
symbol: null,
|
|
3517
|
+
issuer: null,
|
|
3518
|
+
source: 'pqs',
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
if (!this.isSupportedObservedTokenTemplate(templateId)) {
|
|
3522
|
+
return null;
|
|
3523
|
+
}
|
|
3524
|
+
const decoded = await this.decodeContractData(node, this.normalizePackageIdentifier(row.package_id), templateId, row.contract_instance);
|
|
3525
|
+
return this.extractObservedTokenSummary(templateId, decoded);
|
|
3526
|
+
}
|
|
3527
|
+
async loadCachedTokenHolders(node) {
|
|
3528
|
+
const cached = this.tokenHoldersByNode.get(node.id);
|
|
3529
|
+
if (cached && Date.now() - cached.cachedAt < TOKEN_TRANSFER_CACHE_TTL_MS) {
|
|
3530
|
+
return cached.holders;
|
|
3531
|
+
}
|
|
3532
|
+
const useGrpcHoldingViews = node.mode === 'pqs_with_grpc' && this.grpcOperationsService !== undefined;
|
|
3533
|
+
const [pqsHolders, grpcHolders] = await Promise.all([
|
|
3534
|
+
this.fetchTokenHoldersForNode(node, TOKEN_TRANSFER_CACHE_LIMIT, {
|
|
3535
|
+
includeCip112: !useGrpcHoldingViews,
|
|
3536
|
+
}),
|
|
3537
|
+
useGrpcHoldingViews
|
|
3538
|
+
? this.grpcOperationsService.fetchHoldingV2TokenHolders(node)
|
|
3539
|
+
: Promise.resolve([]),
|
|
3540
|
+
]);
|
|
3541
|
+
const holders = [
|
|
3542
|
+
...pqsHolders,
|
|
3543
|
+
...grpcHolders.map((holder) => ({
|
|
3544
|
+
contractId: holder.contractId,
|
|
3545
|
+
nodeId: holder.nodeId,
|
|
3546
|
+
label: holder.label,
|
|
3547
|
+
tokenId: holder.tokenId,
|
|
3548
|
+
partyId: holder.partyId,
|
|
3549
|
+
amount: holder.amount,
|
|
3550
|
+
})),
|
|
3551
|
+
];
|
|
3552
|
+
this.tokenHoldersByNode.set(node.id, {
|
|
3553
|
+
cachedAt: Date.now(),
|
|
3554
|
+
holders,
|
|
3555
|
+
});
|
|
3556
|
+
return holders;
|
|
3557
|
+
}
|
|
3558
|
+
async fetchTokenHoldersForNode(node, limit, options) {
|
|
3559
|
+
const client = this.clientFactory.getClient(node);
|
|
3560
|
+
const includeCip112 = options?.includeCip112 ?? true;
|
|
3561
|
+
const result = await client.query(tokenRowsQuery(limit, includeCip112 ? TOKEN_HOLDER_TEMPLATE_IDS : TOKEN_HOLDER_NON_CIP112_TEMPLATE_IDS, includeCip112 ? TOKEN_HOLDER_TEMPLATE_PATTERNS : TOKEN_HOLDER_NON_CIP112_TEMPLATE_PATTERNS));
|
|
3562
|
+
const rows = result.rows ?? [];
|
|
3563
|
+
const holders = [];
|
|
3564
|
+
for (const row of rows) {
|
|
3565
|
+
const holder = await this.normalizeTokenHolderRow(node, row);
|
|
3566
|
+
if (holder) {
|
|
3567
|
+
holders.push(holder);
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
return holders;
|
|
3571
|
+
}
|
|
3572
|
+
async normalizeTokenHolderRow(node, row) {
|
|
3573
|
+
const templateId = this.normalizeTemplateIdentifier(row.template_id);
|
|
3574
|
+
const decoded = await this.decodeContractData(node, this.normalizePackageIdentifier(row.package_id), templateId, row.contract_instance);
|
|
3575
|
+
if (decoded?.status !== 'decoded' || !this.isRecordValue(decoded.value)) {
|
|
3576
|
+
return null;
|
|
3577
|
+
}
|
|
3578
|
+
if (templateId === CIP56_HOLDING_TEMPLATE_ID || this.isCip112TemplateId(templateId)) {
|
|
3579
|
+
const tokenSummary = this.extractObservedTokenSummary(templateId, decoded);
|
|
3580
|
+
const partyId = this.readScalarField(decoded.value, 'owner');
|
|
3581
|
+
if (!tokenSummary || !partyId) {
|
|
3582
|
+
return null;
|
|
3583
|
+
}
|
|
3584
|
+
return {
|
|
3585
|
+
contractId: this.normalizeOptionalScalar(row.contract_id),
|
|
3586
|
+
nodeId: node.id,
|
|
3587
|
+
label: node.label,
|
|
3588
|
+
tokenId: tokenSummary.tokenId,
|
|
3589
|
+
partyId,
|
|
3590
|
+
amount: this.readScalarField(decoded.value, 'amount'),
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
if (templateId === CANTON_COIN_AMULET_TEMPLATE_ID) {
|
|
3594
|
+
const partyId = this.readScalarField(decoded.value, 'owner');
|
|
3595
|
+
if (!partyId) {
|
|
3596
|
+
return null;
|
|
3597
|
+
}
|
|
3598
|
+
return {
|
|
3599
|
+
contractId: this.normalizeOptionalScalar(row.contract_id),
|
|
3600
|
+
nodeId: node.id,
|
|
3601
|
+
label: node.label,
|
|
3602
|
+
tokenId: CANTON_COIN_TOKEN_ID,
|
|
3603
|
+
partyId,
|
|
3604
|
+
amount: this.readNestedScalarField(decoded.value, ['amount', 'initialAmount']),
|
|
3605
|
+
};
|
|
3606
|
+
}
|
|
3607
|
+
return null;
|
|
3608
|
+
}
|
|
3609
|
+
async loadCachedTokenTransfers(node) {
|
|
3610
|
+
const cached = this.tokenTransfersByNode.get(node.id);
|
|
3611
|
+
if (cached && Date.now() - cached.cachedAt < TOKEN_TRANSFER_CACHE_TTL_MS) {
|
|
3612
|
+
return cached.transfers;
|
|
3613
|
+
}
|
|
3614
|
+
const transfers = await this.fetchTokenTransfersForNode(node, TOKEN_TRANSFER_CACHE_LIMIT);
|
|
3615
|
+
this.tokenTransfersByNode.set(node.id, {
|
|
3616
|
+
cachedAt: Date.now(),
|
|
3617
|
+
transfers,
|
|
3618
|
+
});
|
|
3619
|
+
return transfers;
|
|
3620
|
+
}
|
|
3621
|
+
async fetchTokenTransfersForNode(node, limit) {
|
|
3622
|
+
const client = this.clientFactory.getClient(node);
|
|
3623
|
+
const result = await client.query(tokenRowsQuery(limit, TOKEN_TRANSFER_TEMPLATE_IDS));
|
|
3624
|
+
const rows = result.rows ?? [];
|
|
3625
|
+
const transfers = [];
|
|
3626
|
+
for (const row of rows) {
|
|
3627
|
+
const transfer = await this.normalizeTokenTransferRow(node, row);
|
|
3628
|
+
if (transfer) {
|
|
3629
|
+
transfers.push(transfer);
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
const inferredTransfers = await this.fetchInferredCip112TransfersForNode(node, limit);
|
|
3633
|
+
return [...transfers, ...inferredTransfers].sort(compareGlobalTokenTransfers);
|
|
3634
|
+
}
|
|
3635
|
+
async normalizeTokenTransferRow(node, row) {
|
|
3636
|
+
const templateId = this.normalizeTemplateIdentifier(row.template_id);
|
|
3637
|
+
const decoded = await this.decodeContractData(node, this.normalizePackageIdentifier(row.package_id), templateId, row.contract_instance);
|
|
3638
|
+
const decodedTransfer = this.extractTokenMovement(templateId, decoded);
|
|
3639
|
+
if (!decodedTransfer) {
|
|
3640
|
+
return null;
|
|
3641
|
+
}
|
|
3642
|
+
return {
|
|
3643
|
+
nodeId: node.id,
|
|
3644
|
+
label: node.label,
|
|
3645
|
+
tokenId: decodedTransfer.tokenId,
|
|
3646
|
+
tokenName: decodedTransfer.tokenName,
|
|
3647
|
+
amount: decodedTransfer.amount,
|
|
3648
|
+
sender: decodedTransfer.sender,
|
|
3649
|
+
receiver: decodedTransfer.receiver,
|
|
3650
|
+
eventOffset: this.normalizeOptionalScalar(row.event_offset) ?? '',
|
|
3651
|
+
updateId: this.normalizeUpdateId(row.update_id),
|
|
3652
|
+
recordTime: typeof row.record_time === 'string' ? row.record_time : null,
|
|
3653
|
+
};
|
|
3654
|
+
}
|
|
3655
|
+
async fetchInferredCip112TransfersForNode(node, limit) {
|
|
3656
|
+
const client = this.clientFactory.getClient(node);
|
|
3657
|
+
const result = await client.query(recentCip112MovementUpdateIdsQuery(limit));
|
|
3658
|
+
const rows = result.rows ?? [];
|
|
3659
|
+
const transfers = [];
|
|
3660
|
+
for (const row of rows) {
|
|
3661
|
+
const rawUpdateId = typeof row.update_id === 'string' ? row.update_id : null;
|
|
3662
|
+
if (!rawUpdateId || !this.isHexTokenMovementUpdateId(rawUpdateId)) {
|
|
3663
|
+
continue;
|
|
3664
|
+
}
|
|
3665
|
+
const events = await this.fetchEventsByUpdateId(node, client.query.bind(client), rawUpdateId);
|
|
3666
|
+
transfers.push(...this.inferCip112TokenMovements(node, row, events));
|
|
3667
|
+
}
|
|
3668
|
+
return transfers;
|
|
3669
|
+
}
|
|
3670
|
+
inferCip112TokenMovements(node, update, events) {
|
|
3671
|
+
const createEvents = events.filter((event) => event.eventKind === 'create' &&
|
|
3672
|
+
this.isCip112TemplateId(event.templateId) &&
|
|
3673
|
+
event.createData?.status === 'decoded' &&
|
|
3674
|
+
this.isRecordValue(event.createData.value));
|
|
3675
|
+
const transferExercises = events.filter((event) => event.eventKind === 'consuming_exercise' &&
|
|
3676
|
+
this.isCip112TemplateId(event.templateId) &&
|
|
3677
|
+
typeof event.choice === 'string' &&
|
|
3678
|
+
event.choice.startsWith('Transfer'));
|
|
3679
|
+
const mintExercises = events.filter((event) => event.eventKind === 'non_consuming_exercise' &&
|
|
3680
|
+
this.isCip112TemplateId(event.templateId) &&
|
|
3681
|
+
typeof event.choice === 'string' &&
|
|
3682
|
+
event.choice.startsWith('Mint'));
|
|
3683
|
+
const transfers = [];
|
|
3684
|
+
const shareOwnerFallback = createEvents
|
|
3685
|
+
.filter((event) => this.isShareLikeCip112Template(event.templateId))
|
|
3686
|
+
.map((event) => event.createData?.status === 'decoded' && this.isRecordValue(event.createData.value)
|
|
3687
|
+
? this.readScalarField(event.createData.value, 'owner')
|
|
3688
|
+
: null)
|
|
3689
|
+
.find((owner) => Boolean(owner));
|
|
3690
|
+
if (transferExercises.length > 0) {
|
|
3691
|
+
for (const event of createEvents) {
|
|
3692
|
+
const movementType = 'Create';
|
|
3693
|
+
const transfer = this.buildInferredCip112Movement(node, update, event, movementType, !this.isShareLikeCip112Template(event.templateId) ? shareOwnerFallback : null);
|
|
3694
|
+
if (transfer) {
|
|
3695
|
+
transfers.push(transfer);
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
if (mintExercises.length > 0) {
|
|
3700
|
+
for (const event of createEvents) {
|
|
3701
|
+
if (this.isShareLikeCip112Template(event.templateId)) {
|
|
3702
|
+
continue;
|
|
3703
|
+
}
|
|
3704
|
+
const transfer = this.buildInferredCip112Movement(node, update, event, 'Create');
|
|
3705
|
+
if (transfer) {
|
|
3706
|
+
transfers.push(transfer);
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
return transfers;
|
|
3711
|
+
}
|
|
3712
|
+
buildInferredCip112Movement(node, update, event, movementType, receiverFallback = null) {
|
|
3713
|
+
if (event.createData?.status !== 'decoded') {
|
|
3714
|
+
return null;
|
|
3715
|
+
}
|
|
3716
|
+
const record = event.createData.value;
|
|
3717
|
+
if (!this.isRecordValue(record)) {
|
|
3718
|
+
return null;
|
|
3719
|
+
}
|
|
3720
|
+
const token = this.extractObservedTokenSummary(event.templateId, event.createData);
|
|
3721
|
+
if (!token) {
|
|
3722
|
+
return null;
|
|
3723
|
+
}
|
|
3724
|
+
return {
|
|
3725
|
+
rowId: this.buildTokenMovementRowId(typeof update.update_id === 'string' ? update.update_id : this.normalizeUpdateId(''), event.eventId, event.templateId, movementType),
|
|
3726
|
+
movementType,
|
|
3727
|
+
source: INFERRED_HOLDING_V2_SOURCE,
|
|
3728
|
+
nodeId: node.id,
|
|
3729
|
+
label: node.label,
|
|
3730
|
+
tokenId: token.tokenId,
|
|
3731
|
+
tokenName: token.name,
|
|
3732
|
+
amount: this.readScalarField(record, 'amount'),
|
|
3733
|
+
sender: this.readScalarField(record, 'issuer'),
|
|
3734
|
+
receiver: this.readScalarField(record, 'owner') ?? receiverFallback,
|
|
3735
|
+
eventOffset: this.normalizeOptionalScalar(update.event_offset) ?? '',
|
|
3736
|
+
updateId: typeof update.update_id === 'string' ? this.normalizeUpdateId(update.update_id) : '',
|
|
3737
|
+
recordTime: typeof update.record_time === 'string' ? update.record_time : null,
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
mergeTokenTransfers(transfers) {
|
|
3741
|
+
const deduped = new Map();
|
|
3742
|
+
for (const transfer of transfers) {
|
|
3743
|
+
const key = this.buildTokenTransferDedupKey(transfer);
|
|
3744
|
+
const existing = deduped.get(key);
|
|
3745
|
+
const observedNode = {
|
|
3746
|
+
nodeId: transfer.nodeId,
|
|
3747
|
+
label: transfer.label,
|
|
3748
|
+
eventOffset: transfer.eventOffset,
|
|
3749
|
+
};
|
|
3750
|
+
if (!existing) {
|
|
3751
|
+
deduped.set(key, {
|
|
3752
|
+
rowId: transfer.rowId,
|
|
3753
|
+
movementType: transfer.movementType,
|
|
3754
|
+
source: transfer.source,
|
|
3755
|
+
tokenId: transfer.tokenId,
|
|
3756
|
+
tokenName: transfer.tokenName,
|
|
3757
|
+
amount: transfer.amount,
|
|
3758
|
+
sender: transfer.sender,
|
|
3759
|
+
receiver: transfer.receiver,
|
|
3760
|
+
updateId: transfer.updateId,
|
|
3761
|
+
recordTime: transfer.recordTime,
|
|
3762
|
+
nodes: [observedNode],
|
|
3763
|
+
});
|
|
3764
|
+
continue;
|
|
3765
|
+
}
|
|
3766
|
+
if (!existing.nodes.some((node) => node.nodeId === observedNode.nodeId)) {
|
|
3767
|
+
existing.nodes.push(observedNode);
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
return Array.from(deduped.values()).map((transfer) => ({
|
|
3771
|
+
...transfer,
|
|
3772
|
+
nodes: [...transfer.nodes].sort((left, right) => left.label.localeCompare(right.label)),
|
|
3773
|
+
}));
|
|
3774
|
+
}
|
|
3775
|
+
mergeTokenHolders(holders) {
|
|
3776
|
+
const merged = new Map();
|
|
3777
|
+
for (const holder of holders) {
|
|
3778
|
+
const holderKey = `${holder.tokenId}\u0000${holder.partyId}`;
|
|
3779
|
+
const existing = merged.get(holderKey);
|
|
3780
|
+
const observedNode = {
|
|
3781
|
+
nodeId: holder.nodeId,
|
|
3782
|
+
label: holder.label,
|
|
3783
|
+
};
|
|
3784
|
+
const dedupeContractId = holder.contractId ??
|
|
3785
|
+
JSON.stringify([holder.tokenId, holder.partyId, holder.amount ?? '', holder.nodeId]);
|
|
3786
|
+
if (!existing) {
|
|
3787
|
+
merged.set(holderKey, {
|
|
3788
|
+
partyId: holder.partyId,
|
|
3789
|
+
amount: holder.amount,
|
|
3790
|
+
nodes: [observedNode],
|
|
3791
|
+
observedContractIds: new Set([dedupeContractId]),
|
|
3792
|
+
});
|
|
3793
|
+
continue;
|
|
3794
|
+
}
|
|
3795
|
+
if (!existing.nodes.some((node) => node.nodeId === observedNode.nodeId)) {
|
|
3796
|
+
existing.nodes.push(observedNode);
|
|
3797
|
+
}
|
|
3798
|
+
if (!existing.observedContractIds.has(dedupeContractId)) {
|
|
3799
|
+
existing.amount = this.addNumericStrings(existing.amount, holder.amount);
|
|
3800
|
+
existing.observedContractIds.add(dedupeContractId);
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
return Array.from(merged.values())
|
|
3804
|
+
.map(({ observedContractIds: _observedContractIds, ...holder }) => ({
|
|
3805
|
+
...holder,
|
|
3806
|
+
nodes: [...holder.nodes].sort((left, right) => left.label.localeCompare(right.label)),
|
|
3807
|
+
}))
|
|
3808
|
+
.sort((left, right) => {
|
|
3809
|
+
const amountComparison = this.compareNumericStrings(left.amount, right.amount);
|
|
3810
|
+
if (amountComparison !== 0) {
|
|
3811
|
+
return -amountComparison;
|
|
3812
|
+
}
|
|
3813
|
+
return left.partyId.localeCompare(right.partyId);
|
|
3814
|
+
});
|
|
3815
|
+
}
|
|
3816
|
+
buildTokenTransferDedupKey(transfer) {
|
|
3817
|
+
if (transfer.rowId) {
|
|
3818
|
+
return transfer.rowId;
|
|
3819
|
+
}
|
|
3820
|
+
return JSON.stringify([
|
|
3821
|
+
transfer.updateId,
|
|
3822
|
+
transfer.tokenId,
|
|
3823
|
+
transfer.amount ?? '',
|
|
3824
|
+
transfer.sender ?? '',
|
|
3825
|
+
transfer.receiver ?? '',
|
|
3826
|
+
transfer.recordTime ?? '',
|
|
3827
|
+
]);
|
|
3828
|
+
}
|
|
3829
|
+
normalizeTokenId(tokenId) {
|
|
3830
|
+
return tokenId.trim();
|
|
3831
|
+
}
|
|
3832
|
+
isCip112TemplateId(templateId) {
|
|
3833
|
+
return typeof templateId === 'string' && templateId.includes('.CIP112:');
|
|
3834
|
+
}
|
|
3835
|
+
isSupportedObservedTokenTemplate(templateId) {
|
|
3836
|
+
return (templateId === CIP56_HOLDING_TEMPLATE_ID ||
|
|
3837
|
+
templateId === CIP56_TRANSFER_TEMPLATE_ID ||
|
|
3838
|
+
this.isCip112TemplateId(templateId));
|
|
3839
|
+
}
|
|
3840
|
+
isShareLikeCip112Template(templateId) {
|
|
3841
|
+
return typeof templateId === 'string' && templateId.includes('.ShareToken.CIP112:');
|
|
3842
|
+
}
|
|
3843
|
+
isHexTokenMovementUpdateId(updateId) {
|
|
3844
|
+
return /^[0-9a-f]+$/i.test(updateId);
|
|
3845
|
+
}
|
|
3846
|
+
buildTokenMovementRowId(updateId, eventId, templateId, movementType) {
|
|
3847
|
+
return [updateId, eventId ?? '', templateId ?? '', movementType].join(':');
|
|
3848
|
+
}
|
|
3849
|
+
compareNumericStrings(left, right) {
|
|
3850
|
+
const leftValue = left === null ? Number.NEGATIVE_INFINITY : Number(left);
|
|
3851
|
+
const rightValue = right === null ? Number.NEGATIVE_INFINITY : Number(right);
|
|
3852
|
+
const leftValid = Number.isFinite(leftValue);
|
|
3853
|
+
const rightValid = Number.isFinite(rightValue);
|
|
3854
|
+
if (leftValid && rightValid && leftValue !== rightValue) {
|
|
3855
|
+
return leftValue - rightValue;
|
|
3856
|
+
}
|
|
3857
|
+
if (leftValid !== rightValid) {
|
|
3858
|
+
return leftValid ? 1 : -1;
|
|
3859
|
+
}
|
|
3860
|
+
return (left ?? '').localeCompare(right ?? '');
|
|
3861
|
+
}
|
|
3862
|
+
addNumericStrings(left, right) {
|
|
3863
|
+
if (left === null) {
|
|
3864
|
+
return right;
|
|
3865
|
+
}
|
|
3866
|
+
if (right === null) {
|
|
3867
|
+
return left;
|
|
3868
|
+
}
|
|
3869
|
+
const scale = Math.max(this.numericScale(left), this.numericScale(right));
|
|
3870
|
+
const factor = 10n ** BigInt(scale);
|
|
3871
|
+
const leftValue = this.parseScaledDecimal(left, scale);
|
|
3872
|
+
const rightValue = this.parseScaledDecimal(right, scale);
|
|
3873
|
+
const sum = leftValue + rightValue;
|
|
3874
|
+
const negative = sum < 0n;
|
|
3875
|
+
const absolute = negative ? -sum : sum;
|
|
3876
|
+
const whole = absolute / factor;
|
|
3877
|
+
const fractional = absolute % factor;
|
|
3878
|
+
if (scale === 0) {
|
|
3879
|
+
return `${negative ? '-' : ''}${whole.toString()}`;
|
|
3880
|
+
}
|
|
3881
|
+
return `${negative ? '-' : ''}${whole.toString()}.${fractional
|
|
3882
|
+
.toString()
|
|
3883
|
+
.padStart(scale, '0')}`;
|
|
3884
|
+
}
|
|
3885
|
+
numericScale(value) {
|
|
3886
|
+
const normalized = value.trim();
|
|
3887
|
+
const decimalIndex = normalized.indexOf('.');
|
|
3888
|
+
return decimalIndex === -1 ? 0 : normalized.length - decimalIndex - 1;
|
|
3889
|
+
}
|
|
3890
|
+
parseScaledDecimal(value, scale) {
|
|
3891
|
+
const normalized = value.trim();
|
|
3892
|
+
const negative = normalized.startsWith('-');
|
|
3893
|
+
const unsigned = negative ? normalized.slice(1) : normalized;
|
|
3894
|
+
const [wholePart, fractionalPart = ''] = unsigned.split('.');
|
|
3895
|
+
const paddedFractional = `${fractionalPart}${'0'.repeat(scale)}`.slice(0, scale);
|
|
3896
|
+
const digits = `${wholePart || '0'}${paddedFractional}`;
|
|
3897
|
+
const parsed = BigInt(digits || '0');
|
|
3898
|
+
return negative ? -parsed : parsed;
|
|
3899
|
+
}
|
|
3900
|
+
normalizeUpdateId(updateId) {
|
|
3901
|
+
return updateId.startsWith('\\x') ? updateId.slice(2) : updateId;
|
|
3902
|
+
}
|
|
3903
|
+
extractTokenMovement(templateId, decoded) {
|
|
3904
|
+
if (!decoded || decoded.status !== 'decoded' || !this.isRecordValue(decoded.value)) {
|
|
3905
|
+
return null;
|
|
3906
|
+
}
|
|
3907
|
+
if (templateId === CANTON_COIN_TRANSFER_TEMPLATE_ID) {
|
|
3908
|
+
const transferRecord = this.findRecordField(decoded.value, 'transfer');
|
|
3909
|
+
if (!transferRecord) {
|
|
3910
|
+
return null;
|
|
3911
|
+
}
|
|
3912
|
+
return {
|
|
3913
|
+
tokenId: CANTON_COIN_TOKEN_ID,
|
|
3914
|
+
tokenName: CANTON_COIN_TOKEN_NAME,
|
|
3915
|
+
sender: this.readScalarField(transferRecord, 'sender'),
|
|
3916
|
+
receiver: this.readScalarField(transferRecord, 'receiver'),
|
|
3917
|
+
amount: this.readScalarField(transferRecord, 'amount'),
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3920
|
+
if (templateId === CANTON_COIN_AMULET_TEMPLATE_ID) {
|
|
3921
|
+
return {
|
|
3922
|
+
tokenId: CANTON_COIN_TOKEN_ID,
|
|
3923
|
+
tokenName: CANTON_COIN_TOKEN_NAME,
|
|
3924
|
+
sender: this.readScalarField(decoded.value, 'dso'),
|
|
3925
|
+
receiver: this.readScalarField(decoded.value, 'owner'),
|
|
3926
|
+
amount: this.readNestedScalarField(decoded.value, ['amount', 'initialAmount']),
|
|
3927
|
+
};
|
|
3928
|
+
}
|
|
3929
|
+
if (templateId === CIP56_TRANSFER_TEMPLATE_ID) {
|
|
3930
|
+
const tokenSummary = this.extractCip56TokenSummary(decoded);
|
|
3931
|
+
if (!tokenSummary) {
|
|
3932
|
+
return null;
|
|
3933
|
+
}
|
|
3934
|
+
return {
|
|
3935
|
+
tokenId: tokenSummary.tokenId,
|
|
3936
|
+
tokenName: tokenSummary.name,
|
|
3937
|
+
sender: this.readScalarField(decoded.value, 'sender'),
|
|
3938
|
+
receiver: this.readScalarField(decoded.value, 'receiver'),
|
|
3939
|
+
amount: this.readScalarField(decoded.value, 'amount'),
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
return null;
|
|
3943
|
+
}
|
|
3944
|
+
extractCip56TokenSummary(decoded) {
|
|
3945
|
+
if (!decoded || decoded.status !== 'decoded' || !this.isRecordValue(decoded.value)) {
|
|
3946
|
+
return null;
|
|
3947
|
+
}
|
|
3948
|
+
const intrinsicId = this.readNestedScalarField(decoded.value, ['instrumentId', 'id']);
|
|
3949
|
+
if (!intrinsicId) {
|
|
3950
|
+
return null;
|
|
3951
|
+
}
|
|
3952
|
+
const issuer = this.readNestedScalarField(decoded.value, ['instrumentId', 'admin']);
|
|
3953
|
+
const tokenId = this.buildObservedTokenId(intrinsicId, issuer);
|
|
3954
|
+
return {
|
|
3955
|
+
tokenId,
|
|
3956
|
+
name: this.readConfiguredDecodedTokenMetadata(decoded.value, 'name') ?? intrinsicId,
|
|
3957
|
+
symbol: this.readConfiguredDecodedTokenMetadata(decoded.value, 'symbol'),
|
|
3958
|
+
issuer,
|
|
3959
|
+
source: 'pqs',
|
|
3960
|
+
};
|
|
3961
|
+
}
|
|
3962
|
+
extractObservedTokenSummary(templateId, decoded) {
|
|
3963
|
+
if (!templateId) {
|
|
3964
|
+
return null;
|
|
3965
|
+
}
|
|
3966
|
+
if (this.isCip112TemplateId(templateId)) {
|
|
3967
|
+
return this.extractCip112TokenSummary(templateId, decoded);
|
|
3968
|
+
}
|
|
3969
|
+
return this.extractCip56TokenSummary(decoded);
|
|
3970
|
+
}
|
|
3971
|
+
extractCip112TokenSummary(templateId, decoded) {
|
|
3972
|
+
if (!decoded || decoded.status !== 'decoded' || !this.isRecordValue(decoded.value)) {
|
|
3973
|
+
return null;
|
|
3974
|
+
}
|
|
3975
|
+
const configuredSymbol = this.readConfiguredDecodedTokenMetadata(decoded.value, 'symbol');
|
|
3976
|
+
const symbol = configuredSymbol ?? this.readScalarField(decoded.value, 'symbol');
|
|
3977
|
+
const instrumentIdText = this.readScalarField(decoded.value, 'instrumentIdText');
|
|
3978
|
+
const instrumentId = this.readNestedScalarField(decoded.value, ['instrumentId', 'id']);
|
|
3979
|
+
const vaultIdentityId = this.readNestedScalarField(decoded.value, ['vaultIdentity', 'id']);
|
|
3980
|
+
const shareLikeIntrinsicId = templateId !== null && this.isShareLikeCip112Template(templateId) && vaultIdentityId
|
|
3981
|
+
? this.normalizeShareLikeIntrinsicTokenId(vaultIdentityId)
|
|
3982
|
+
: null;
|
|
3983
|
+
const intrinsicId = instrumentId ?? shareLikeIntrinsicId ?? symbol ?? instrumentIdText;
|
|
3984
|
+
if (!intrinsicId) {
|
|
3985
|
+
return null;
|
|
3986
|
+
}
|
|
3987
|
+
const issuer = this.readNestedScalarField(decoded.value, ['instrumentId', 'admin'])
|
|
3988
|
+
?? this.readNestedScalarField(decoded.value, ['vaultIdentity', 'admin'])
|
|
3989
|
+
?? this.readScalarField(decoded.value, 'vaultParty')
|
|
3990
|
+
?? this.readScalarField(decoded.value, 'issuer');
|
|
3991
|
+
const tokenId = this.buildObservedTokenId(intrinsicId, issuer);
|
|
3992
|
+
return {
|
|
3993
|
+
tokenId,
|
|
3994
|
+
name: this.readConfiguredDecodedTokenMetadata(decoded.value, 'name')
|
|
3995
|
+
?? this.readScalarField(decoded.value, 'name')
|
|
3996
|
+
?? instrumentIdText
|
|
3997
|
+
?? symbol
|
|
3998
|
+
?? intrinsicId,
|
|
3999
|
+
symbol,
|
|
4000
|
+
issuer,
|
|
4001
|
+
source: 'pqs',
|
|
4002
|
+
};
|
|
4003
|
+
}
|
|
4004
|
+
buildObservedTokenId(intrinsicId, issuer) {
|
|
4005
|
+
const normalizedIntrinsicId = intrinsicId.trim();
|
|
4006
|
+
const normalizedIssuer = issuer?.trim() ?? '';
|
|
4007
|
+
if (normalizedIssuer.length === 0) {
|
|
4008
|
+
return normalizedIntrinsicId;
|
|
4009
|
+
}
|
|
4010
|
+
return `${normalizedIssuer}::${normalizedIntrinsicId}`;
|
|
4011
|
+
}
|
|
4012
|
+
normalizeShareLikeIntrinsicTokenId(intrinsicId) {
|
|
4013
|
+
const normalizedIntrinsicId = intrinsicId.trim();
|
|
4014
|
+
if (normalizedIntrinsicId.endsWith(':share')) {
|
|
4015
|
+
return normalizedIntrinsicId;
|
|
4016
|
+
}
|
|
4017
|
+
return `${normalizedIntrinsicId}:share`;
|
|
4018
|
+
}
|
|
4019
|
+
readConfiguredDecodedTokenMetadata(value, field) {
|
|
4020
|
+
const keys = this.getTokenMetadataConfig()[field === 'name' ? 'nameKeys' : 'symbolKeys'];
|
|
4021
|
+
for (const key of keys) {
|
|
4022
|
+
const metadataValue = this.readTextMapEntryField(value, ['meta', 'values'], key);
|
|
4023
|
+
if (metadataValue) {
|
|
4024
|
+
return metadataValue;
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
return null;
|
|
4028
|
+
}
|
|
4029
|
+
getTokenMetadataConfig() {
|
|
4030
|
+
return this.nodeConfigService?.getTokenMetadataConfig() ?? {
|
|
4031
|
+
nameKeys: [...node_config_schema_1.DEFAULT_TOKEN_METADATA_CONFIG.nameKeys],
|
|
4032
|
+
symbolKeys: [...node_config_schema_1.DEFAULT_TOKEN_METADATA_CONFIG.symbolKeys],
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
4035
|
+
async fetchEventsByUpdateId(node, query, rawUpdateId) {
|
|
4036
|
+
try {
|
|
4037
|
+
const result = await query(updateEventsQuery(rawUpdateId));
|
|
4038
|
+
const rows = result.rows ?? [];
|
|
4039
|
+
return Promise.all(rows.map((row) => this.normalizeEventRow(node, row)));
|
|
4040
|
+
}
|
|
4041
|
+
catch (error) {
|
|
4042
|
+
if (!this.shouldFallbackToNormalizedEventTables(error)) {
|
|
4043
|
+
throw error;
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
const fallbackResult = await query(normalizedUpdateEventsQuery(rawUpdateId));
|
|
4047
|
+
const rows = fallbackResult.rows ?? [];
|
|
4048
|
+
return Promise.all(rows.map((row) => this.normalizeEventRow(node, row)));
|
|
4049
|
+
}
|
|
4050
|
+
shouldResolveRewardCoupon(events) {
|
|
4051
|
+
return events.some((event) => event.choice === 'ReceiveSvRewardCoupon');
|
|
4052
|
+
}
|
|
4053
|
+
async fetchRewardCouponDetails(query, rawUpdateId) {
|
|
4054
|
+
try {
|
|
4055
|
+
const result = await query(rewardCouponInstanceQuery(rawUpdateId));
|
|
4056
|
+
const row = result.rows[0];
|
|
4057
|
+
if (!row || !Buffer.isBuffer(row.contract_instance)) {
|
|
4058
|
+
return null;
|
|
4059
|
+
}
|
|
4060
|
+
const decoded = this.decodeRewardCouponContractInstance(row.contract_instance);
|
|
4061
|
+
if (!decoded || decoded.status !== 'decoded' || !this.isRecordValue(decoded.value)) {
|
|
4062
|
+
return null;
|
|
4063
|
+
}
|
|
4064
|
+
return {
|
|
4065
|
+
argument: { status: 'not_available' },
|
|
4066
|
+
result: {
|
|
4067
|
+
status: 'decoded',
|
|
4068
|
+
value: {
|
|
4069
|
+
kind: 'record',
|
|
4070
|
+
fields: typeof row.coupon_contract_id === 'string'
|
|
4071
|
+
? [
|
|
4072
|
+
...decoded.value.fields,
|
|
4073
|
+
{
|
|
4074
|
+
label: 'couponContractId',
|
|
4075
|
+
value: { kind: 'contract_id', value: row.coupon_contract_id },
|
|
4076
|
+
},
|
|
4077
|
+
]
|
|
4078
|
+
: decoded.value.fields,
|
|
4079
|
+
},
|
|
4080
|
+
},
|
|
4081
|
+
};
|
|
4082
|
+
}
|
|
4083
|
+
catch {
|
|
4084
|
+
return null;
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
extractRawUpdateId(row) {
|
|
4088
|
+
if (typeof row.update_id === 'string') {
|
|
4089
|
+
return row.update_id;
|
|
4090
|
+
}
|
|
4091
|
+
const meta = row.meta;
|
|
4092
|
+
if (meta && typeof meta.update_id === 'string') {
|
|
4093
|
+
return meta.update_id;
|
|
4094
|
+
}
|
|
4095
|
+
throw new Error('Update metadata is missing update_id');
|
|
4096
|
+
}
|
|
4097
|
+
extractEventOffset(row) {
|
|
4098
|
+
if (typeof row.event_offset === 'string') {
|
|
4099
|
+
return row.event_offset;
|
|
4100
|
+
}
|
|
4101
|
+
if (typeof row.event_offset === 'number') {
|
|
4102
|
+
return String(row.event_offset);
|
|
4103
|
+
}
|
|
4104
|
+
const meta = 'meta' in row ? row.meta : null;
|
|
4105
|
+
if (meta && typeof meta.event_offset === 'string') {
|
|
4106
|
+
return meta.event_offset;
|
|
4107
|
+
}
|
|
4108
|
+
if (meta && typeof meta.event_offset === 'number') {
|
|
4109
|
+
return String(meta.event_offset);
|
|
4110
|
+
}
|
|
4111
|
+
throw new Error('Update metadata is missing event_offset');
|
|
4112
|
+
}
|
|
4113
|
+
extractIsoRecordTime(row) {
|
|
4114
|
+
if (typeof row.record_time_iso === 'string') {
|
|
4115
|
+
return row.record_time_iso;
|
|
4116
|
+
}
|
|
4117
|
+
return typeof row.record_time === 'string' ? row.record_time : null;
|
|
4118
|
+
}
|
|
4119
|
+
extractMeta(row) {
|
|
4120
|
+
if (row.meta && typeof row.meta === 'object' && !Array.isArray(row.meta)) {
|
|
4121
|
+
return row.meta;
|
|
4122
|
+
}
|
|
4123
|
+
const { record_time_iso: _recordTimeIso, ...meta } = row;
|
|
4124
|
+
return meta;
|
|
4125
|
+
}
|
|
4126
|
+
async normalizeEventRow(node, row) {
|
|
4127
|
+
const templateId = this.normalizeTemplateIdentifier(row.template_id);
|
|
4128
|
+
const packageId = this.normalizePackageIdentifier(row.package_id ?? null);
|
|
4129
|
+
const rawChoice = typeof row.choice === 'string' ? row.choice : null;
|
|
4130
|
+
return {
|
|
4131
|
+
eventKind: row.event_kind,
|
|
4132
|
+
eventId: typeof row.event_id === 'string' ? row.event_id : null,
|
|
4133
|
+
contractId: typeof row.contract_id === 'string' ? row.contract_id : null,
|
|
4134
|
+
packageId,
|
|
4135
|
+
templateId,
|
|
4136
|
+
choice: this.normalizeChoiceIdentifier(row.choice),
|
|
4137
|
+
witnesses: this.normalizeParties(row.witnesses),
|
|
4138
|
+
createData: row.event_kind === 'create'
|
|
4139
|
+
? await this.decodeContractData(node, packageId, templateId, row.contract_instance ?? null)
|
|
4140
|
+
: null,
|
|
4141
|
+
exerciseData: row.event_kind === 'create'
|
|
4142
|
+
? null
|
|
4143
|
+
: await this.decodeExerciseData(node, {
|
|
4144
|
+
packageId,
|
|
4145
|
+
templateId,
|
|
4146
|
+
rawChoice,
|
|
4147
|
+
exerciseArgument: row.exercise_argument ?? null,
|
|
4148
|
+
exerciseResult: row.exercise_result ?? null,
|
|
4149
|
+
}),
|
|
4150
|
+
raw: row.raw && typeof row.raw === 'object' && !Array.isArray(row.raw) ? row.raw : {},
|
|
4151
|
+
};
|
|
4152
|
+
}
|
|
4153
|
+
attachRewardCouponDetails(events, exerciseData) {
|
|
4154
|
+
if (!exerciseData) {
|
|
4155
|
+
return events;
|
|
4156
|
+
}
|
|
4157
|
+
return events.map((event) => event.choice === 'ReceiveSvRewardCoupon'
|
|
4158
|
+
? {
|
|
4159
|
+
...event,
|
|
4160
|
+
exerciseData: {
|
|
4161
|
+
argument: event.exerciseData?.argument ?? exerciseData.argument,
|
|
4162
|
+
result: exerciseData.result,
|
|
4163
|
+
},
|
|
4164
|
+
}
|
|
4165
|
+
: event);
|
|
4166
|
+
}
|
|
4167
|
+
decodeRewardCouponContractInstance(contractInstance) {
|
|
4168
|
+
const contractPayload = this.getFirstLengthDelimitedField(contractInstance, 2);
|
|
4169
|
+
const createArgument = contractPayload
|
|
4170
|
+
? this.getFirstLengthDelimitedField(contractPayload, 4)
|
|
4171
|
+
: null;
|
|
4172
|
+
const rewardCouponRecord = createArgument
|
|
4173
|
+
? this.getFirstLengthDelimitedField(createArgument, 13)
|
|
4174
|
+
: null;
|
|
4175
|
+
const fields = rewardCouponRecord
|
|
4176
|
+
? this.getAllLengthDelimitedFields(rewardCouponRecord, 1)
|
|
4177
|
+
: [];
|
|
4178
|
+
if (fields.length < 5) {
|
|
4179
|
+
return null;
|
|
4180
|
+
}
|
|
4181
|
+
const rewardRound = this.unwrapScalarVarint(fields.at(-2) ?? null);
|
|
4182
|
+
const rewardAmount = this.unwrapScalarVarint(fields.at(-1) ?? null);
|
|
4183
|
+
if (rewardRound === null || rewardAmount === null) {
|
|
4184
|
+
return null;
|
|
4185
|
+
}
|
|
4186
|
+
return {
|
|
4187
|
+
status: 'decoded',
|
|
4188
|
+
value: {
|
|
4189
|
+
kind: 'record',
|
|
4190
|
+
fields: [
|
|
4191
|
+
{ label: 'rewardRound', value: rewardRound },
|
|
4192
|
+
{ label: 'rewardAmount', value: rewardAmount },
|
|
4193
|
+
],
|
|
4194
|
+
},
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
async decodeContractData(node, packageId, templateId, contractInstance) {
|
|
4198
|
+
if (!Buffer.isBuffer(contractInstance) || !templateId) {
|
|
4199
|
+
return null;
|
|
4200
|
+
}
|
|
4201
|
+
if (templateId === 'Splice.Amulet:SvRewardCoupon') {
|
|
4202
|
+
return this.decodeRewardCouponContractInstance(contractInstance);
|
|
4203
|
+
}
|
|
4204
|
+
if (!this.damlValueDecoder) {
|
|
4205
|
+
return null;
|
|
4206
|
+
}
|
|
4207
|
+
const initialDecode = await this.damlValueDecoder.decodeContractInstance({
|
|
4208
|
+
packageId,
|
|
4209
|
+
templateId,
|
|
4210
|
+
contractInstance,
|
|
4211
|
+
});
|
|
4212
|
+
return this.retryContractDecodeAfterPackageRefresh(node, packageId, templateId, contractInstance, initialDecode);
|
|
4213
|
+
}
|
|
4214
|
+
async decodeExerciseData(node, input) {
|
|
4215
|
+
if (!this.damlValueDecoder) {
|
|
4216
|
+
return null;
|
|
4217
|
+
}
|
|
4218
|
+
const initialDecode = await this.damlValueDecoder.decodeExerciseValue(input);
|
|
4219
|
+
return this.retryExerciseDecodeAfterPackageRefresh(node, input, initialDecode);
|
|
4220
|
+
}
|
|
4221
|
+
async retryContractDecodeAfterPackageRefresh(node, packageId, templateId, contractInstance, initialDecode) {
|
|
4222
|
+
const reason = initialDecode?.status === 'invalid_data' ? initialDecode.reason : null;
|
|
4223
|
+
if (!this.shouldRetryPackageRefresh(node, packageId, reason)) {
|
|
4224
|
+
return initialDecode;
|
|
4225
|
+
}
|
|
4226
|
+
await this.refreshPackageForNode(node, packageId);
|
|
4227
|
+
return this.damlValueDecoder?.decodeContractInstance({
|
|
4228
|
+
packageId,
|
|
4229
|
+
templateId,
|
|
4230
|
+
contractInstance,
|
|
4231
|
+
}) ?? initialDecode;
|
|
4232
|
+
}
|
|
4233
|
+
async retryExerciseDecodeAfterPackageRefresh(node, input, initialDecode) {
|
|
4234
|
+
const reason = this.extractPackageRefreshReason(initialDecode);
|
|
4235
|
+
if (!this.shouldRetryPackageRefresh(node, input.packageId, reason)) {
|
|
4236
|
+
return initialDecode;
|
|
4237
|
+
}
|
|
4238
|
+
await this.refreshPackageForNode(node, input.packageId);
|
|
4239
|
+
return this.damlValueDecoder?.decodeExerciseValue(input) ?? initialDecode;
|
|
4240
|
+
}
|
|
4241
|
+
extractPackageRefreshReason(decoded) {
|
|
4242
|
+
const reasons = [
|
|
4243
|
+
decoded?.argument.status === 'invalid_data' ? decoded.argument.reason : null,
|
|
4244
|
+
decoded?.result.status === 'invalid_data' ? decoded.result.reason : null,
|
|
4245
|
+
];
|
|
4246
|
+
return reasons.find((reason) => reason !== null) ?? null;
|
|
4247
|
+
}
|
|
4248
|
+
shouldRetryPackageRefresh(node, packageId, reason) {
|
|
4249
|
+
return Boolean(node &&
|
|
4250
|
+
packageId &&
|
|
4251
|
+
this.packageSyncService &&
|
|
4252
|
+
(reason === 'missing_package' || reason === 'invalid_package'));
|
|
4253
|
+
}
|
|
4254
|
+
async refreshPackageForNode(node, packageId) {
|
|
4255
|
+
await this.packageSyncService?.syncPackagesById(node, [packageId]);
|
|
4256
|
+
this.packageRegistryService?.invalidatePackage(packageId);
|
|
4257
|
+
}
|
|
4258
|
+
normalizePackageIdentifier(packageId) {
|
|
4259
|
+
if (typeof packageId !== 'string') {
|
|
4260
|
+
return null;
|
|
4261
|
+
}
|
|
4262
|
+
return packageId.replace(/^i\|/, '');
|
|
4263
|
+
}
|
|
4264
|
+
decodeKnownContractData(templateId, contractInstance) {
|
|
4265
|
+
if (!Buffer.isBuffer(contractInstance) || !templateId) {
|
|
4266
|
+
return null;
|
|
4267
|
+
}
|
|
4268
|
+
if (templateId === 'Splice.Amulet:SvRewardCoupon') {
|
|
4269
|
+
return this.decodeRewardCouponContractInstance(contractInstance);
|
|
4270
|
+
}
|
|
4271
|
+
return null;
|
|
4272
|
+
}
|
|
4273
|
+
isRecordValue(value) {
|
|
4274
|
+
return typeof value === 'object' && value !== null && 'kind' in value && value.kind === 'record';
|
|
4275
|
+
}
|
|
4276
|
+
findRecordField(value, label) {
|
|
4277
|
+
const field = value.fields.find((candidate) => candidate.label === label)?.value;
|
|
4278
|
+
return field && this.isRecordValue(field) ? field : null;
|
|
4279
|
+
}
|
|
4280
|
+
readScalarField(value, label) {
|
|
4281
|
+
const field = value.fields.find((candidate) => candidate.label === label)?.value;
|
|
4282
|
+
if (field === null || field === undefined) {
|
|
4283
|
+
return null;
|
|
4284
|
+
}
|
|
4285
|
+
if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') {
|
|
4286
|
+
return String(field);
|
|
4287
|
+
}
|
|
4288
|
+
if (typeof field === 'object' && field !== null && 'kind' in field && field.kind === 'contract_id') {
|
|
4289
|
+
return field.value;
|
|
4290
|
+
}
|
|
4291
|
+
return null;
|
|
4292
|
+
}
|
|
4293
|
+
readNestedScalarField(value, path) {
|
|
4294
|
+
let current = value;
|
|
4295
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
4296
|
+
if (!current || !this.isRecordValue(current)) {
|
|
4297
|
+
return null;
|
|
4298
|
+
}
|
|
4299
|
+
const field = current.fields.find((candidate) => candidate.label === path[index])?.value;
|
|
4300
|
+
if (field === undefined) {
|
|
4301
|
+
return null;
|
|
4302
|
+
}
|
|
4303
|
+
if (index === path.length - 1) {
|
|
4304
|
+
if (typeof field === 'string' ||
|
|
4305
|
+
typeof field === 'number' ||
|
|
4306
|
+
typeof field === 'boolean') {
|
|
4307
|
+
return String(field);
|
|
4308
|
+
}
|
|
4309
|
+
if (typeof field === 'object' && field !== null && 'kind' in field && field.kind === 'contract_id') {
|
|
4310
|
+
return field.value;
|
|
4311
|
+
}
|
|
4312
|
+
return null;
|
|
4313
|
+
}
|
|
4314
|
+
current = field;
|
|
4315
|
+
}
|
|
4316
|
+
return null;
|
|
4317
|
+
}
|
|
4318
|
+
readTextMapEntryField(value, path, key) {
|
|
4319
|
+
let current = value;
|
|
4320
|
+
for (const segment of path) {
|
|
4321
|
+
if (!current || !this.isRecordValue(current)) {
|
|
4322
|
+
return null;
|
|
4323
|
+
}
|
|
4324
|
+
const field = current.fields.find((candidate) => candidate.label === segment)?.value;
|
|
4325
|
+
if (field === undefined) {
|
|
4326
|
+
return null;
|
|
4327
|
+
}
|
|
4328
|
+
current = field;
|
|
4329
|
+
}
|
|
4330
|
+
if (!current ||
|
|
4331
|
+
typeof current !== 'object' ||
|
|
4332
|
+
!('kind' in current) ||
|
|
4333
|
+
current.kind !== 'text_map') {
|
|
4334
|
+
return null;
|
|
4335
|
+
}
|
|
4336
|
+
const entry = current.entries.find((candidate) => candidate.key === key)?.value;
|
|
4337
|
+
if (entry === null || entry === undefined) {
|
|
4338
|
+
return null;
|
|
4339
|
+
}
|
|
4340
|
+
if (typeof entry === 'string' || typeof entry === 'number' || typeof entry === 'boolean') {
|
|
4341
|
+
return String(entry);
|
|
4342
|
+
}
|
|
4343
|
+
if (typeof entry === 'object' &&
|
|
4344
|
+
entry !== null &&
|
|
4345
|
+
'kind' in entry &&
|
|
4346
|
+
entry.kind === 'contract_id') {
|
|
4347
|
+
return entry.value;
|
|
4348
|
+
}
|
|
4349
|
+
return null;
|
|
4350
|
+
}
|
|
4351
|
+
getFirstLengthDelimitedField(message, fieldNumber) {
|
|
4352
|
+
return this.parseProtobufFields(message).find((field) => field.fieldNumber === fieldNumber && field.buffer)?.buffer ?? null;
|
|
4353
|
+
}
|
|
4354
|
+
getAllLengthDelimitedFields(message, fieldNumber) {
|
|
4355
|
+
return this.parseProtobufFields(message)
|
|
4356
|
+
.filter((field) => field.fieldNumber === fieldNumber && Boolean(field.buffer))
|
|
4357
|
+
.map((field) => field.buffer);
|
|
4358
|
+
}
|
|
4359
|
+
unwrapScalarVarint(message) {
|
|
4360
|
+
let current = message;
|
|
4361
|
+
while (current) {
|
|
4362
|
+
const fields = this.parseProtobufFields(current);
|
|
4363
|
+
if (fields.length !== 1) {
|
|
4364
|
+
return null;
|
|
4365
|
+
}
|
|
4366
|
+
const [field] = fields;
|
|
4367
|
+
if (typeof field.value === 'number') {
|
|
4368
|
+
return field.value;
|
|
4369
|
+
}
|
|
4370
|
+
current = field.buffer ?? null;
|
|
4371
|
+
}
|
|
4372
|
+
return null;
|
|
4373
|
+
}
|
|
4374
|
+
parseProtobufFields(message) {
|
|
4375
|
+
const fields = [];
|
|
4376
|
+
let offset = 0;
|
|
4377
|
+
while (offset < message.length) {
|
|
4378
|
+
const tag = this.readVarint(message, offset);
|
|
4379
|
+
offset = tag.next;
|
|
4380
|
+
const fieldNumber = Number(tag.value >> 3n);
|
|
4381
|
+
const wireType = Number(tag.value & 0x07n);
|
|
4382
|
+
if (wireType === 0) {
|
|
4383
|
+
const value = this.readVarint(message, offset);
|
|
4384
|
+
offset = value.next;
|
|
4385
|
+
fields.push({ fieldNumber, value: Number(value.value) });
|
|
4386
|
+
continue;
|
|
4387
|
+
}
|
|
4388
|
+
if (wireType === 2) {
|
|
4389
|
+
const length = this.readVarint(message, offset);
|
|
4390
|
+
offset = length.next;
|
|
4391
|
+
const end = offset + Number(length.value);
|
|
4392
|
+
if (end > message.length) {
|
|
4393
|
+
break;
|
|
4394
|
+
}
|
|
4395
|
+
fields.push({ fieldNumber, buffer: message.subarray(offset, end) });
|
|
4396
|
+
offset = end;
|
|
4397
|
+
continue;
|
|
4398
|
+
}
|
|
4399
|
+
if (wireType === 1) {
|
|
4400
|
+
offset += 8;
|
|
4401
|
+
continue;
|
|
4402
|
+
}
|
|
4403
|
+
if (wireType === 5) {
|
|
4404
|
+
offset += 4;
|
|
4405
|
+
continue;
|
|
4406
|
+
}
|
|
4407
|
+
break;
|
|
4408
|
+
}
|
|
4409
|
+
return fields;
|
|
4410
|
+
}
|
|
4411
|
+
readVarint(buffer, offset) {
|
|
4412
|
+
let value = 0n;
|
|
4413
|
+
let shift = 0n;
|
|
4414
|
+
let next = offset;
|
|
4415
|
+
while (next < buffer.length) {
|
|
4416
|
+
const byte = BigInt(buffer[next]);
|
|
4417
|
+
value |= (byte & 0x7fn) << shift;
|
|
4418
|
+
next += 1;
|
|
4419
|
+
if ((byte & 0x80n) === 0n) {
|
|
4420
|
+
return { value, next };
|
|
4421
|
+
}
|
|
4422
|
+
shift += 7n;
|
|
4423
|
+
}
|
|
4424
|
+
return { value, next };
|
|
4425
|
+
}
|
|
4426
|
+
async buildTemplateFilterResponse(packageIds) {
|
|
4427
|
+
const templates = new Set();
|
|
4428
|
+
for (const packageId of packageIds) {
|
|
4429
|
+
const inspection = this.packageRegistryService
|
|
4430
|
+
? await this.packageRegistryService.inspectPackage(packageId)
|
|
4431
|
+
: { ok: false, reason: 'missing_package' };
|
|
4432
|
+
if (!inspection.ok) {
|
|
4433
|
+
continue;
|
|
4434
|
+
}
|
|
4435
|
+
for (const template of inspection.definition.templates) {
|
|
4436
|
+
if (template.templateId.trim()) {
|
|
4437
|
+
templates.add(template.templateId);
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
return {
|
|
4442
|
+
templates: Array.from(templates)
|
|
4443
|
+
.sort((left, right) => left.localeCompare(right))
|
|
4444
|
+
.map((templateId) => ({ templateId })),
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
normalizeOptionalScalar(value) {
|
|
4448
|
+
if (typeof value === 'string') {
|
|
4449
|
+
return value;
|
|
4450
|
+
}
|
|
4451
|
+
if (typeof value === 'number') {
|
|
4452
|
+
return String(value);
|
|
4453
|
+
}
|
|
4454
|
+
return null;
|
|
4455
|
+
}
|
|
4456
|
+
async fetchNamespaceTopologyByNode(nodes, observedNodeIds, partiesById) {
|
|
4457
|
+
const grpcOperationsService = this.grpcOperationsService;
|
|
4458
|
+
if (!grpcOperationsService) {
|
|
4459
|
+
return [];
|
|
4460
|
+
}
|
|
4461
|
+
const observedNodeIdSet = new Set(observedNodeIds);
|
|
4462
|
+
const topologyEntries = await Promise.all(nodes
|
|
4463
|
+
.filter((node) => observedNodeIdSet.has(node.id))
|
|
4464
|
+
.map(async (node) => {
|
|
4465
|
+
if (node.mode !== 'pqs_with_grpc') {
|
|
4466
|
+
return {
|
|
4467
|
+
nodeId: node.id,
|
|
4468
|
+
label: node.label,
|
|
4469
|
+
status: 'grpc_not_configured',
|
|
4470
|
+
errorMessage: null,
|
|
4471
|
+
partyToParticipants: [],
|
|
4472
|
+
partyToKeyMappings: [],
|
|
4473
|
+
};
|
|
4474
|
+
}
|
|
4475
|
+
const nodeParties = Array.from(partiesById.entries())
|
|
4476
|
+
.filter(([, nodeIds]) => nodeIds.has(node.id))
|
|
4477
|
+
.map(([partyId]) => partyId);
|
|
4478
|
+
return this.mergeNamespaceNodeTopologies(node, await Promise.all(nodeParties.map(async (partyId) => grpcOperationsService.fetchPartyTopology(node, partyId))));
|
|
4479
|
+
}));
|
|
4480
|
+
return topologyEntries.sort((left, right) => left.label.localeCompare(right.label));
|
|
4481
|
+
}
|
|
4482
|
+
mergeNamespaceNodeTopologies(node, topologies) {
|
|
4483
|
+
const successfulTopologies = topologies.filter((entry) => entry.status === 'ok');
|
|
4484
|
+
if (successfulTopologies.length > 0) {
|
|
4485
|
+
return {
|
|
4486
|
+
nodeId: node.id,
|
|
4487
|
+
label: node.label,
|
|
4488
|
+
status: 'ok',
|
|
4489
|
+
errorMessage: null,
|
|
4490
|
+
partyToParticipants: this.dedupePartyTopologyParticipants(successfulTopologies.flatMap((entry) => entry.partyToParticipants)),
|
|
4491
|
+
partyToKeyMappings: this.dedupePartyTopologyKeys(successfulTopologies.flatMap((entry) => entry.partyToKeyMappings)),
|
|
4492
|
+
};
|
|
4493
|
+
}
|
|
4494
|
+
const grpcError = topologies.find((entry) => entry.status === 'grpc_error');
|
|
4495
|
+
if (grpcError) {
|
|
4496
|
+
return {
|
|
4497
|
+
nodeId: node.id,
|
|
4498
|
+
label: node.label,
|
|
4499
|
+
status: 'grpc_error',
|
|
4500
|
+
errorMessage: grpcError.errorMessage,
|
|
4501
|
+
partyToParticipants: [],
|
|
4502
|
+
partyToKeyMappings: [],
|
|
4503
|
+
};
|
|
4504
|
+
}
|
|
4505
|
+
return {
|
|
4506
|
+
nodeId: node.id,
|
|
4507
|
+
label: node.label,
|
|
4508
|
+
status: 'grpc_not_configured',
|
|
4509
|
+
errorMessage: null,
|
|
4510
|
+
partyToParticipants: [],
|
|
4511
|
+
partyToKeyMappings: [],
|
|
4512
|
+
};
|
|
4513
|
+
}
|
|
4514
|
+
dedupePartyTopologyParticipants(participants) {
|
|
4515
|
+
const deduped = new Map();
|
|
4516
|
+
for (const participant of participants) {
|
|
4517
|
+
const key = [
|
|
4518
|
+
participant.participantId ?? '',
|
|
4519
|
+
participant.participantUid ?? '',
|
|
4520
|
+
participant.permission ?? '',
|
|
4521
|
+
participant.threshold ?? '',
|
|
4522
|
+
participant.synchronizerIds.join('|'),
|
|
4523
|
+
].join('::');
|
|
4524
|
+
if (!deduped.has(key)) {
|
|
4525
|
+
deduped.set(key, participant);
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
return Array.from(deduped.values());
|
|
4529
|
+
}
|
|
4530
|
+
dedupePartyTopologyKeys(keyMappings) {
|
|
4531
|
+
const deduped = new Map();
|
|
4532
|
+
for (const keyMapping of keyMappings) {
|
|
4533
|
+
const key = [
|
|
4534
|
+
keyMapping.keyFingerprint ?? '',
|
|
4535
|
+
keyMapping.publicKey ?? '',
|
|
4536
|
+
keyMapping.purpose ?? '',
|
|
4537
|
+
keyMapping.keyType ?? '',
|
|
4538
|
+
keyMapping.keyFormat ?? '',
|
|
4539
|
+
keyMapping.keySpec ?? '',
|
|
4540
|
+
keyMapping.threshold ?? '',
|
|
4541
|
+
keyMapping.synchronizerIds.join('|'),
|
|
4542
|
+
].join('::');
|
|
4543
|
+
if (!deduped.has(key)) {
|
|
4544
|
+
deduped.set(key, keyMapping);
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
return Array.from(deduped.values());
|
|
4548
|
+
}
|
|
4549
|
+
extractNamespaceIdentifier(partyId) {
|
|
4550
|
+
const normalizedPartyId = this.normalizePartyIdentifier(partyId);
|
|
4551
|
+
const separatorIndex = normalizedPartyId.lastIndexOf('::');
|
|
4552
|
+
if (separatorIndex < 0) {
|
|
4553
|
+
return null;
|
|
4554
|
+
}
|
|
4555
|
+
const namespaceId = normalizedPartyId.slice(separatorIndex + 2).trim();
|
|
4556
|
+
return namespaceId.length > 0 ? namespaceId : null;
|
|
4557
|
+
}
|
|
4558
|
+
paginateNamespacePartyIds(partyIds, options) {
|
|
4559
|
+
const limit = Math.max(1, Math.trunc(options.limit));
|
|
4560
|
+
if (options.after) {
|
|
4561
|
+
const endIndex = partyIds.findIndex((value) => value === options.after);
|
|
4562
|
+
const normalizedEndIndex = endIndex >= 0 ? endIndex : partyIds.length;
|
|
4563
|
+
const startIndex = Math.max(0, normalizedEndIndex - limit);
|
|
4564
|
+
const items = partyIds.slice(startIndex, normalizedEndIndex);
|
|
4565
|
+
return {
|
|
4566
|
+
items,
|
|
4567
|
+
nextBefore: normalizedEndIndex < partyIds.length && items.length > 0 ? items[items.length - 1] ?? null : null,
|
|
4568
|
+
nextAfter: startIndex > 0 && items.length > 0 ? items[0] ?? null : null,
|
|
4569
|
+
};
|
|
4570
|
+
}
|
|
4571
|
+
const startIndex = options.before
|
|
4572
|
+
? (() => {
|
|
4573
|
+
const index = partyIds.findIndex((value) => value === options.before);
|
|
4574
|
+
return index >= 0 ? index + 1 : 0;
|
|
4575
|
+
})()
|
|
4576
|
+
: 0;
|
|
4577
|
+
const items = partyIds.slice(startIndex, startIndex + limit);
|
|
4578
|
+
return {
|
|
4579
|
+
items,
|
|
4580
|
+
nextBefore: startIndex + limit < partyIds.length && items.length > 0 ? items[items.length - 1] ?? null : null,
|
|
4581
|
+
nextAfter: startIndex > 0 && items.length > 0 ? items[0] ?? null : null,
|
|
4582
|
+
};
|
|
4583
|
+
}
|
|
4584
|
+
normalizeTemplateIdentifier(templateId) {
|
|
4585
|
+
if (typeof templateId !== 'string') {
|
|
4586
|
+
return null;
|
|
4587
|
+
}
|
|
4588
|
+
return templateId.replace(/^t\|#[^:]+:/, '');
|
|
4589
|
+
}
|
|
4590
|
+
normalizeChoiceIdentifier(choice) {
|
|
4591
|
+
if (typeof choice !== 'string') {
|
|
4592
|
+
return null;
|
|
4593
|
+
}
|
|
4594
|
+
const normalized = choice.replace(/^c\|/, '');
|
|
4595
|
+
const separatorIndex = normalized.lastIndexOf('_');
|
|
4596
|
+
return separatorIndex >= 0 ? normalized.slice(separatorIndex + 1) : normalized;
|
|
4597
|
+
}
|
|
4598
|
+
normalizeParties(parties) {
|
|
4599
|
+
if (Array.isArray(parties)) {
|
|
4600
|
+
return parties
|
|
4601
|
+
.filter((party) => typeof party === 'string')
|
|
4602
|
+
.map((party) => this.normalizePartyIdentifier(party))
|
|
4603
|
+
.filter(Boolean);
|
|
4604
|
+
}
|
|
4605
|
+
if (typeof parties !== 'string') {
|
|
4606
|
+
return [];
|
|
4607
|
+
}
|
|
4608
|
+
const trimmed = parties.trim();
|
|
4609
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
4610
|
+
return trimmed ? [trimmed] : [];
|
|
4611
|
+
}
|
|
4612
|
+
return trimmed
|
|
4613
|
+
.slice(1, -1)
|
|
4614
|
+
.split(',')
|
|
4615
|
+
.map((party) => this.normalizePartyIdentifier(party.trim().replace(/^"(.*)"$/, '$1')))
|
|
4616
|
+
.filter(Boolean);
|
|
4617
|
+
}
|
|
4618
|
+
normalizePartyIdentifier(party) {
|
|
4619
|
+
const trimmed = party.trim();
|
|
4620
|
+
if (!trimmed) {
|
|
4621
|
+
return '';
|
|
4622
|
+
}
|
|
4623
|
+
return trimmed.replace(/^p\|/, '');
|
|
4624
|
+
}
|
|
4625
|
+
defaultRow() {
|
|
4626
|
+
return {
|
|
4627
|
+
pqs_database: 'unknown',
|
|
4628
|
+
active_contract_count: 0,
|
|
4629
|
+
latest_offset: null,
|
|
4630
|
+
latest_event_at: null,
|
|
4631
|
+
total_update_count: 0,
|
|
4632
|
+
};
|
|
4633
|
+
}
|
|
4634
|
+
};
|
|
4635
|
+
exports.PqsSummaryService = PqsSummaryService;
|
|
4636
|
+
exports.PqsSummaryService = PqsSummaryService = __decorate([
|
|
4637
|
+
(0, common_1.Injectable)(),
|
|
4638
|
+
__param(1, (0, common_1.Optional)()),
|
|
4639
|
+
__param(2, (0, common_1.Optional)()),
|
|
4640
|
+
__param(3, (0, common_1.Optional)()),
|
|
4641
|
+
__param(4, (0, common_1.Optional)()),
|
|
4642
|
+
__param(5, (0, common_1.Optional)()),
|
|
4643
|
+
__param(6, (0, common_1.Optional)()),
|
|
4644
|
+
__metadata("design:paramtypes", [pqs_client_factory_1.PqsClientFactory,
|
|
4645
|
+
daml_value_decoder_service_1.DamlValueDecoderService,
|
|
4646
|
+
package_cache_service_1.PackageCacheService,
|
|
4647
|
+
package_registry_service_1.PackageRegistryService,
|
|
4648
|
+
node_config_service_1.NodeConfigService,
|
|
4649
|
+
grpc_operations_service_1.GrpcOperationsService,
|
|
4650
|
+
package_sync_service_1.PackageSyncService])
|
|
4651
|
+
], PqsSummaryService);
|
|
4652
|
+
//# sourceMappingURL=pqs-summary.service.js.map
|