@aigne/afs-ocap 1.12.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +26 -0
- package/README.md +144 -0
- package/aup/accounts/default.json +64 -0
- package/aup/accounts/item.json +510 -0
- package/aup/assets/default.json +70 -0
- package/aup/assets/item.json +360 -0
- package/aup/bridges/_addr/blocks/item.json +168 -0
- package/aup/bridges/default.json +79 -0
- package/aup/bridges/item.json +868 -0
- package/aup/config/default.json +806 -0
- package/aup/default.json +387 -0
- package/aup/delegates/default.json +71 -0
- package/aup/delegates/item.json +241 -0
- package/aup/factories/default.json +78 -0
- package/aup/factories/item.json +310 -0
- package/aup/stakes/default.json +76 -0
- package/aup/stakes/item.json +374 -0
- package/aup/tokens/default.json +76 -0
- package/aup/tokens/item.json +384 -0
- package/aup/transactions/default.json +102 -0
- package/aup/transactions/item.json +286 -0
- package/aup/validators/default.json +57 -0
- package/aup/validators/item.json +99 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +4 -0
- package/dist/method-colors.cjs +137 -0
- package/dist/method-colors.mjs +136 -0
- package/dist/method-colors.mjs.map +1 -0
- package/dist/provider.cjs +2490 -0
- package/dist/provider.d.cts +453 -0
- package/dist/provider.d.cts.map +1 -0
- package/dist/provider.d.mts +453 -0
- package/dist/provider.d.mts.map +1 -0
- package/dist/provider.mjs +2490 -0
- package/dist/provider.mjs.map +1 -0
- package/dist/remote-source.cjs +338 -0
- package/dist/remote-source.d.cts +62 -0
- package/dist/remote-source.d.cts.map +1 -0
- package/dist/remote-source.d.mts +62 -0
- package/dist/remote-source.d.mts.map +1 -0
- package/dist/remote-source.mjs +339 -0
- package/dist/remote-source.mjs.map +1 -0
- package/dist/source.d.cts +77 -0
- package/dist/source.d.cts.map +1 -0
- package/dist/source.d.mts +77 -0
- package/dist/source.d.mts.map +1 -0
- package/dist/types.d.cts +572 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +572 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/utils.cjs +581 -0
- package/dist/utils.mjs +557 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +61 -0
package/dist/utils.mjs
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import { AFSValidationError, normalizeWhere, validateCollectionQuerySpec } from "@aigne/afs";
|
|
2
|
+
import { joinURL } from "ufo";
|
|
3
|
+
import { toTypeInfo, types } from "@arcblock/did";
|
|
4
|
+
import { toStakeAddress } from "@arcblock/did-util/cbor";
|
|
5
|
+
|
|
6
|
+
//#region src/utils.ts
|
|
7
|
+
const TOP_LEVEL_DIRS = [
|
|
8
|
+
"config",
|
|
9
|
+
"validators",
|
|
10
|
+
"transactions",
|
|
11
|
+
"accounts",
|
|
12
|
+
"tokens",
|
|
13
|
+
"assets",
|
|
14
|
+
"factories",
|
|
15
|
+
"stakes",
|
|
16
|
+
"bridges",
|
|
17
|
+
"delegates"
|
|
18
|
+
];
|
|
19
|
+
/** Collection path segment → singular kind suffix. Single source of truth. */
|
|
20
|
+
const COLLECTION_KIND_MAP = {
|
|
21
|
+
config: "config",
|
|
22
|
+
validators: "validator",
|
|
23
|
+
transactions: "transaction",
|
|
24
|
+
accounts: "account",
|
|
25
|
+
tokens: "token",
|
|
26
|
+
assets: "asset",
|
|
27
|
+
factories: "factory",
|
|
28
|
+
stakes: "stake",
|
|
29
|
+
bridges: "bridge",
|
|
30
|
+
delegates: "delegate"
|
|
31
|
+
};
|
|
32
|
+
/** Search result type → collection path segment. */
|
|
33
|
+
const TYPE_TO_COLLECTION = {
|
|
34
|
+
tx: "transactions",
|
|
35
|
+
transaction: "transactions",
|
|
36
|
+
account: "accounts",
|
|
37
|
+
token: "tokens",
|
|
38
|
+
asset: "assets",
|
|
39
|
+
factory: "factories",
|
|
40
|
+
stake: "stakes",
|
|
41
|
+
bridge: "bridges",
|
|
42
|
+
delegate: "delegates"
|
|
43
|
+
};
|
|
44
|
+
/** Returns the full `ocap:xxx` kind for a collection name. */
|
|
45
|
+
function collectionKind(collection) {
|
|
46
|
+
return `ocap:${COLLECTION_KIND_MAP[collection] ?? collection}`;
|
|
47
|
+
}
|
|
48
|
+
function toPaging(options, defaultSize = 20) {
|
|
49
|
+
return {
|
|
50
|
+
size: options?.limit ?? defaultSize,
|
|
51
|
+
offset: options?.offset ?? 0
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Single-point narrow from `ctx.options` (typed as `RouteOptions` union in
|
|
56
|
+
* `@aigne/afs/provider`) down to OCAP's domain-specific `OcapListOptions`
|
|
57
|
+
* shape. Use this in every `@List` handler instead of casting through
|
|
58
|
+
* `as any` / `as Record<string, unknown>` — the runtime cost is identical
|
|
59
|
+
* (a bare assertion, no copy), but the type system now knows what fields
|
|
60
|
+
* the handler may read, and `toPaging` + `extract*Filters` accept the
|
|
61
|
+
* narrowed type directly without further casts.
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* Typed-where vocabulary (afs-collection-query T5.3): every field is one of
|
|
65
|
+
* OCAP's documented filter keys, with the operator(s) its extractor shape
|
|
66
|
+
* admits. ANYTHING else — unknown field, disallowed operator, non-string
|
|
67
|
+
* value — is a strict `AFSValidationError`; no unvetted token can ever
|
|
68
|
+
* reach a GQL variable. (The core grammar validator has already rejected
|
|
69
|
+
* prototype keys / malformed dot-paths before this table is consulted.)
|
|
70
|
+
*/
|
|
71
|
+
const WHERE_SCALAR_FIELDS = new Set([
|
|
72
|
+
"sender",
|
|
73
|
+
"receiver",
|
|
74
|
+
"direction",
|
|
75
|
+
"validity",
|
|
76
|
+
"issuerAddress",
|
|
77
|
+
"ownerAddress",
|
|
78
|
+
"factoryAddress",
|
|
79
|
+
"tokenAddress",
|
|
80
|
+
"from",
|
|
81
|
+
"to"
|
|
82
|
+
]);
|
|
83
|
+
const WHERE_LIST_FIELDS = new Set([
|
|
84
|
+
"types",
|
|
85
|
+
"tokens",
|
|
86
|
+
"assets",
|
|
87
|
+
"factories",
|
|
88
|
+
"accounts",
|
|
89
|
+
"rollups",
|
|
90
|
+
"stakes",
|
|
91
|
+
"delegations",
|
|
92
|
+
"addressList"
|
|
93
|
+
]);
|
|
94
|
+
/** `time` leaves map gte→startDateTime / lte→endDateTime (ISO strings). */
|
|
95
|
+
const WHERE_TIME_FIELD = "time";
|
|
96
|
+
/** A leaf whose operator value is "" or [] is the form's untouched state. */
|
|
97
|
+
function isEmptySentinelLeaf(leaf) {
|
|
98
|
+
if (typeof leaf !== "object" || leaf === null) return false;
|
|
99
|
+
const rec = leaf;
|
|
100
|
+
for (const op of [
|
|
101
|
+
"eq",
|
|
102
|
+
"in",
|
|
103
|
+
"notIn",
|
|
104
|
+
"contains",
|
|
105
|
+
"gte",
|
|
106
|
+
"lte"
|
|
107
|
+
]) if (op in rec) {
|
|
108
|
+
const v = rec[op];
|
|
109
|
+
return v === "" || Array.isArray(v) && v.length === 0;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
/** Drop empty-sentinel leaves; undefined when nothing substantive remains. */
|
|
114
|
+
function pruneEmptySentinels(where) {
|
|
115
|
+
if (typeof where !== "object" || where === null) return where;
|
|
116
|
+
const rec = where;
|
|
117
|
+
if (Array.isArray(rec.all)) {
|
|
118
|
+
const kept = rec.all.filter((leaf) => !isEmptySentinelLeaf(leaf));
|
|
119
|
+
if (kept.length === 0) return void 0;
|
|
120
|
+
return { all: kept };
|
|
121
|
+
}
|
|
122
|
+
if (typeof rec.field === "string" && isEmptySentinelLeaf(rec)) return void 0;
|
|
123
|
+
return where;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Translate a typed `where` into the legacy private option keys the 7
|
|
127
|
+
* `extract*Filters` entry points consume — the GQL filter structure the
|
|
128
|
+
* extractors emit is therefore byte-identical to the private-key path
|
|
129
|
+
* (design §6: chain-explorer / OCAP row). Empty string / empty array values
|
|
130
|
+
* skip the leaf, mirroring the extractors' own empty-sentinel semantics.
|
|
131
|
+
*/
|
|
132
|
+
function whereToOcapOptions(where) {
|
|
133
|
+
const pruned = pruneEmptySentinels(where);
|
|
134
|
+
if (pruned === void 0) return {};
|
|
135
|
+
validateCollectionQuerySpec({ where: pruned });
|
|
136
|
+
const out = {};
|
|
137
|
+
for (const leaf of normalizeWhere(pruned)) {
|
|
138
|
+
const field = leaf.field;
|
|
139
|
+
const op = Object.keys(leaf).filter((k) => k !== "field")[0];
|
|
140
|
+
const value = leaf[op];
|
|
141
|
+
if (field === WHERE_TIME_FIELD) {
|
|
142
|
+
if (op !== "gte" && op !== "lte") throw new AFSValidationError(`Invalid query spec: where.${field}: time accepts gte/lte only (got ${op})`);
|
|
143
|
+
if (typeof value !== "string") throw new AFSValidationError(`Invalid query spec: where.${field}: ISO string required`);
|
|
144
|
+
if (value === "") continue;
|
|
145
|
+
out[op === "gte" ? "startDateTime" : "endDateTime"] = value;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (WHERE_SCALAR_FIELDS.has(field)) {
|
|
149
|
+
if (op !== "eq") throw new AFSValidationError(`Invalid query spec: where.${field}: scalar filter accepts eq only (got ${op})`);
|
|
150
|
+
if (typeof value !== "string") throw new AFSValidationError(`Invalid query spec: where.${field}: string value required`);
|
|
151
|
+
if (value === "") continue;
|
|
152
|
+
out[field] = value;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (WHERE_LIST_FIELDS.has(field)) {
|
|
156
|
+
if (op !== "eq" && op !== "in") throw new AFSValidationError(`Invalid query spec: where.${field}: list filter accepts eq/in only (got ${op})`);
|
|
157
|
+
const arr = op === "in" ? value : [value];
|
|
158
|
+
if (!arr.every((v) => typeof v === "string")) throw new AFSValidationError(`Invalid query spec: where.${field}: string values required`);
|
|
159
|
+
const cleaned = arr.filter((v) => v.length > 0);
|
|
160
|
+
if (cleaned.length === 0) continue;
|
|
161
|
+
out[field] = cleaned;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
throw new AFSValidationError(`Invalid query spec: where.${field}: not an OCAP filter field`);
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
function listOpts(ctx) {
|
|
169
|
+
const raw = ctx.options;
|
|
170
|
+
if (!raw || raw.where === void 0 || raw.where === null) return raw;
|
|
171
|
+
return {
|
|
172
|
+
...raw,
|
|
173
|
+
...whereToOcapOptions(raw.where)
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function buildDirEntry(basePath, name, childrenCount) {
|
|
177
|
+
const isHidden = name === ".." || name.startsWith(".");
|
|
178
|
+
const meta = {
|
|
179
|
+
kind: collectionKind(name),
|
|
180
|
+
childrenCount: childrenCount ?? -1
|
|
181
|
+
};
|
|
182
|
+
if (isHidden) meta.visibility = "hidden";
|
|
183
|
+
return {
|
|
184
|
+
id: name,
|
|
185
|
+
path: joinURL(basePath, name),
|
|
186
|
+
meta,
|
|
187
|
+
summary: name
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function buildListResult(entries, total) {
|
|
191
|
+
return {
|
|
192
|
+
data: entries,
|
|
193
|
+
total
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/** Reverse mapping: collection path segment → search result types. */
|
|
197
|
+
const COLLECTION_TO_TYPES = {};
|
|
198
|
+
for (const [type, col] of Object.entries(TYPE_TO_COLLECTION)) {
|
|
199
|
+
if (!COLLECTION_TO_TYPES[col]) COLLECTION_TO_TYPES[col] = [];
|
|
200
|
+
COLLECTION_TO_TYPES[col].push(type);
|
|
201
|
+
}
|
|
202
|
+
function pickDefined(obj) {
|
|
203
|
+
const result = {};
|
|
204
|
+
let hasAny = false;
|
|
205
|
+
for (const [k, v] of Object.entries(obj)) if (v !== void 0 && v !== null) {
|
|
206
|
+
result[k] = v;
|
|
207
|
+
hasAny = true;
|
|
208
|
+
}
|
|
209
|
+
return hasAny ? result : void 0;
|
|
210
|
+
}
|
|
211
|
+
function extractTimeFilter(opts) {
|
|
212
|
+
const start = opts?.startDateTime ?? opts?.timeStart;
|
|
213
|
+
const end = opts?.endDateTime ?? opts?.timeEnd;
|
|
214
|
+
if (!start && !end) return void 0;
|
|
215
|
+
const filter = {};
|
|
216
|
+
if (start) filter.startDateTime = start;
|
|
217
|
+
if (end) filter.endDateTime = end;
|
|
218
|
+
return filter;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Normalize the user-supplied validity value to the GQL enum. Accepts the
|
|
222
|
+
* canonical strings (`"VALID"` / `"INVALID"` / `"BOTH"`), the lowercase
|
|
223
|
+
* variants emitted by URL params, and the empty / undefined sentinels meaning
|
|
224
|
+
* "no filter". Any other value is dropped so an arbitrary URL param can't
|
|
225
|
+
* smuggle a non-enum string through to the GQL layer.
|
|
226
|
+
*/
|
|
227
|
+
function normalizeValidity(value) {
|
|
228
|
+
if (typeof value !== "string" || !value) return void 0;
|
|
229
|
+
const upper = value.toUpperCase();
|
|
230
|
+
if (upper === "VALID" || upper === "INVALID" || upper === "BOTH") return upper;
|
|
231
|
+
}
|
|
232
|
+
function extractTxFilters(opts) {
|
|
233
|
+
if (!opts) return void 0;
|
|
234
|
+
let addressFilter;
|
|
235
|
+
if (opts.sender || opts.receiver) {
|
|
236
|
+
addressFilter = { direction: opts.direction ?? "UNION" };
|
|
237
|
+
if (opts.sender) addressFilter.sender = opts.sender;
|
|
238
|
+
if (opts.receiver) addressFilter.receiver = opts.receiver;
|
|
239
|
+
}
|
|
240
|
+
const validity = normalizeValidity(opts.validity);
|
|
241
|
+
const toList = (v) => {
|
|
242
|
+
if (Array.isArray(v)) {
|
|
243
|
+
const cleaned = v.filter((x) => typeof x === "string" && x.length > 0);
|
|
244
|
+
return cleaned.length > 0 ? cleaned : void 0;
|
|
245
|
+
}
|
|
246
|
+
if (typeof v === "string" && v.length > 0) return [v];
|
|
247
|
+
};
|
|
248
|
+
const types$1 = toList(opts.types);
|
|
249
|
+
const tokens = toList(opts.tokens);
|
|
250
|
+
return pickDefined({
|
|
251
|
+
addressFilter,
|
|
252
|
+
typeFilter: types$1 ? { types: types$1 } : void 0,
|
|
253
|
+
tokenFilter: tokens ? { tokens } : void 0,
|
|
254
|
+
assetFilter: opts.assets ? { assets: opts.assets } : void 0,
|
|
255
|
+
factoryFilter: opts.factories ? { factories: opts.factories } : void 0,
|
|
256
|
+
accountFilter: opts.accounts ? { accounts: opts.accounts } : void 0,
|
|
257
|
+
rollupFilter: opts.rollups ? { rollups: opts.rollups } : void 0,
|
|
258
|
+
stakeFilter: opts.stakes ? { stakes: opts.stakes } : void 0,
|
|
259
|
+
delegationFilter: opts.delegations ? { delegations: opts.delegations } : void 0,
|
|
260
|
+
validityFilter: validity && validity !== "BOTH" ? { validity } : void 0,
|
|
261
|
+
timeFilter: extractTimeFilter(opts)
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
function extractTokenFilters(opts) {
|
|
265
|
+
if (!opts) return void 0;
|
|
266
|
+
return pickDefined({
|
|
267
|
+
issuerAddress: opts.issuerAddress,
|
|
268
|
+
timeFilter: extractTimeFilter(opts)
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
function extractAssetFilters(opts) {
|
|
272
|
+
if (!opts) return void 0;
|
|
273
|
+
return pickDefined({
|
|
274
|
+
ownerAddress: opts.ownerAddress,
|
|
275
|
+
factoryAddress: opts.factoryAddress,
|
|
276
|
+
timeFilter: extractTimeFilter(opts)
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
function extractFactoryFilters(opts) {
|
|
280
|
+
if (!opts) return void 0;
|
|
281
|
+
return pickDefined({
|
|
282
|
+
ownerAddress: opts.ownerAddress,
|
|
283
|
+
addressList: opts.addressList,
|
|
284
|
+
timeFilter: extractTimeFilter(opts)
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
function extractStakeFilters(opts) {
|
|
288
|
+
if (!opts) return void 0;
|
|
289
|
+
let addressFilter;
|
|
290
|
+
if (opts.sender || opts.receiver) {
|
|
291
|
+
addressFilter = { direction: opts.direction ?? "UNION" };
|
|
292
|
+
if (opts.sender) addressFilter.sender = opts.sender;
|
|
293
|
+
if (opts.receiver) addressFilter.receiver = opts.receiver;
|
|
294
|
+
}
|
|
295
|
+
return pickDefined({
|
|
296
|
+
addressFilter,
|
|
297
|
+
timeFilter: extractTimeFilter(opts)
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function extractBridgeFilters(opts) {
|
|
301
|
+
if (!opts) return void 0;
|
|
302
|
+
return pickDefined({
|
|
303
|
+
tokenAddress: opts.tokenAddress,
|
|
304
|
+
timeFilter: extractTimeFilter(opts)
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
function extractDelegationFilters(opts) {
|
|
308
|
+
if (!opts) return void 0;
|
|
309
|
+
return pickDefined({
|
|
310
|
+
from: opts.from,
|
|
311
|
+
to: opts.to,
|
|
312
|
+
timeFilter: extractTimeFilter(opts)
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/** DID role code → OCAP entity type name. */
|
|
316
|
+
const ROLE_TO_ENTITY = {
|
|
317
|
+
[types.RoleType.ROLE_ACCOUNT]: "account",
|
|
318
|
+
[types.RoleType.ROLE_APPLICATION]: "account",
|
|
319
|
+
[types.RoleType.ROLE_NODE]: "account",
|
|
320
|
+
[types.RoleType.ROLE_VALIDATOR]: "account",
|
|
321
|
+
[types.RoleType.ROLE_BOT]: "account",
|
|
322
|
+
[types.RoleType.ROLE_PASSKEY]: "account",
|
|
323
|
+
[types.RoleType.ROLE_TOKEN]: "token",
|
|
324
|
+
[types.RoleType.ROLE_ASSET]: "asset",
|
|
325
|
+
[types.RoleType.ROLE_FACTORY]: "factory",
|
|
326
|
+
[types.RoleType.ROLE_TOKEN_FACTORY]: "factory",
|
|
327
|
+
[types.RoleType.ROLE_STAKE]: "stake",
|
|
328
|
+
[types.RoleType.ROLE_ROLLUP]: "bridge",
|
|
329
|
+
[types.RoleType.ROLE_DELEGATION]: "delegate"
|
|
330
|
+
};
|
|
331
|
+
/** Extract OCAP entity type from a DID address using its encoded role byte. */
|
|
332
|
+
function resolveDidRole(address) {
|
|
333
|
+
try {
|
|
334
|
+
const info = toTypeInfo(address);
|
|
335
|
+
if (info.role === void 0) return null;
|
|
336
|
+
return ROLE_TO_ENTITY[info.role] ?? null;
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Granular human-readable role label for a DID (e.g., "Validator", "Bot",
|
|
343
|
+
* "Application"). Used by the account detail page's role pill — the
|
|
344
|
+
* generic resolveDidRole returns just "account" for ROLE_VALIDATOR /
|
|
345
|
+
* ROLE_BOT / ROLE_NODE / etc., losing the actual sub-type information
|
|
346
|
+
* the user sees in legacy explorers.
|
|
347
|
+
*/
|
|
348
|
+
const ROLE_TO_LABEL = {
|
|
349
|
+
[types.RoleType.ROLE_ACCOUNT]: "Account",
|
|
350
|
+
[types.RoleType.ROLE_APPLICATION]: "Application",
|
|
351
|
+
[types.RoleType.ROLE_NODE]: "Node",
|
|
352
|
+
[types.RoleType.ROLE_VALIDATOR]: "Validator",
|
|
353
|
+
[types.RoleType.ROLE_BOT]: "Bot",
|
|
354
|
+
[types.RoleType.ROLE_PASSKEY]: "Passkey",
|
|
355
|
+
[types.RoleType.ROLE_TOKEN]: "Token",
|
|
356
|
+
[types.RoleType.ROLE_ASSET]: "Asset",
|
|
357
|
+
[types.RoleType.ROLE_FACTORY]: "Factory",
|
|
358
|
+
[types.RoleType.ROLE_TOKEN_FACTORY]: "Token Factory",
|
|
359
|
+
[types.RoleType.ROLE_STAKE]: "Stake",
|
|
360
|
+
[types.RoleType.ROLE_ROLLUP]: "Rollup",
|
|
361
|
+
[types.RoleType.ROLE_DELEGATION]: "Delegation"
|
|
362
|
+
};
|
|
363
|
+
function resolveDidRoleLabel(address) {
|
|
364
|
+
try {
|
|
365
|
+
const info = toTypeInfo(address);
|
|
366
|
+
if (info.role === void 0) return null;
|
|
367
|
+
return ROLE_TO_LABEL[info.role] ?? null;
|
|
368
|
+
} catch {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* "transfer_v3" → "Transfer V3"
|
|
374
|
+
*
|
|
375
|
+
* Display rebrand: the chain protocol calls them "rollups" in tx type names
|
|
376
|
+
* (\`create_rollup\`, \`update_rollup\`, …) but the user-facing surface uses
|
|
377
|
+
* "Bridge". Substitute the word in the rendered label so the Type column
|
|
378
|
+
* reads "Create Bridge" / "Update Bridge" without changing the on-chain
|
|
379
|
+
* type literal that GQL still uses.
|
|
380
|
+
*/
|
|
381
|
+
function formatTxTypeName(type) {
|
|
382
|
+
return type.split("_").filter(Boolean).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" ").replace(/\bRollup\b/g, "Bridge");
|
|
383
|
+
}
|
|
384
|
+
/** Map tx status code to an AUP intent string. */
|
|
385
|
+
function txStatusIntent(code) {
|
|
386
|
+
if (!code || code === "OK") return "success";
|
|
387
|
+
return "danger";
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Resolve a transaction's true sender and receiver from its nested `tx.itxJson`.
|
|
391
|
+
*
|
|
392
|
+
* v3-style (transfer_v3, acquire_asset_v3) carries an `inputs[]`/`outputs[]`
|
|
393
|
+
* payload. The true sender is `inputs[0].owner`; the outer `tx.from` holds the
|
|
394
|
+
* output owner (submitter), not the party losing tokens. Using `tx.from` as the
|
|
395
|
+
* sender makes every v3 transfer look like a self-transfer.
|
|
396
|
+
*
|
|
397
|
+
* Legacy (transfer, transfer_v2, …) has no inputs/outputs — sender comes from
|
|
398
|
+
* the outer `tx.from` and receiver from `itxJson.to` / `itxJson.receiver`.
|
|
399
|
+
*
|
|
400
|
+
* account_migrate carries the migration target in `itxJson.address` (the new
|
|
401
|
+
* DID the account is migrating to); the outer receiver is empty on chain. We
|
|
402
|
+
* surface it as the receiver so the global tx list "To" column points at the
|
|
403
|
+
* destination account instead of rendering blank.
|
|
404
|
+
*/
|
|
405
|
+
function resolveTxParties(tx) {
|
|
406
|
+
const outer = tx.tx ?? {};
|
|
407
|
+
const itxJson = outer.itxJson ?? {};
|
|
408
|
+
const inputOwner = itxJson.inputs?.[0]?.owner;
|
|
409
|
+
const outputOwner = itxJson.outputs?.[0]?.owner;
|
|
410
|
+
const nonEmpty = (v) => typeof v === "string" && v.length > 0;
|
|
411
|
+
const senderFromTop = nonEmpty(tx.sender) ? tx.sender : void 0;
|
|
412
|
+
const receiverFromTop = nonEmpty(tx.receiver) ? tx.receiver : void 0;
|
|
413
|
+
if (inputOwner || outputOwner) {
|
|
414
|
+
const sender$1 = nonEmpty(inputOwner) ? inputOwner : senderFromTop ?? (nonEmpty(outer.from) ? outer.from : void 0);
|
|
415
|
+
const receiver$1 = nonEmpty(outputOwner) ? outputOwner : receiverFromTop;
|
|
416
|
+
if (sender$1 || receiver$1) return {
|
|
417
|
+
sender: sender$1,
|
|
418
|
+
receiver: receiver$1
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
let sender = senderFromTop;
|
|
422
|
+
if (!sender && nonEmpty(outer.from)) sender = outer.from;
|
|
423
|
+
let receiver = receiverFromTop;
|
|
424
|
+
if (!receiver) {
|
|
425
|
+
if (nonEmpty(itxJson.receiver)) receiver = itxJson.receiver;
|
|
426
|
+
else if (nonEmpty(itxJson.to)) receiver = itxJson.to;
|
|
427
|
+
else if (nonEmpty(outer.receiver)) receiver = outer.receiver;
|
|
428
|
+
else if (itxJson.type_url === "fg:t:account_migrate" && nonEmpty(itxJson.address)) receiver = itxJson.address;
|
|
429
|
+
else if (typeof itxJson.type_url === "string" && STAKE_LIKE_TYPE_URLS.has(itxJson.type_url) && nonEmpty(itxJson.address)) receiver = itxJson.address;
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
sender,
|
|
433
|
+
receiver
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
const STAKE_LIKE_TYPE_URLS = new Set([
|
|
437
|
+
"fg:t:stake",
|
|
438
|
+
"fg:t:revoke_stake",
|
|
439
|
+
"fg:t:claim_stake",
|
|
440
|
+
"fg:t:return_stake",
|
|
441
|
+
"fg:t:slash_stake"
|
|
442
|
+
]);
|
|
443
|
+
/**
|
|
444
|
+
* Format a raw bignum string (possibly negative) to a human-readable decimal
|
|
445
|
+
* with a leading +/- sign. Suitable for displaying receipt change amounts.
|
|
446
|
+
*
|
|
447
|
+
* The integer part wears thousand separators so that pre-formatted strings
|
|
448
|
+
* (`_gasFeeFormatted`, `_serviceFeeFormatted`, receipt `valueFormatted`)
|
|
449
|
+
* read consistently with the browser-side `bignum` formatter (B1).
|
|
450
|
+
*
|
|
451
|
+
* Example: formatBignumValue("1000000000000000000", 18) → "+1"
|
|
452
|
+
* formatBignumValue("-500000000000000000", 18) → "-0.5"
|
|
453
|
+
* formatBignumValue("1230000000000000000000000000", 18) → "+1,230,000,000"
|
|
454
|
+
*/
|
|
455
|
+
function formatBignumValue(value, decimal) {
|
|
456
|
+
if (!value || value === "0") return "0";
|
|
457
|
+
try {
|
|
458
|
+
const negative = value.startsWith("-");
|
|
459
|
+
const padded = (negative ? value.slice(1) : value).padStart(decimal + 1, "0");
|
|
460
|
+
const intPart = padded.slice(0, padded.length - decimal) || "0";
|
|
461
|
+
const fracPart = padded.slice(padded.length - decimal).replace(/0+$/, "");
|
|
462
|
+
const intWithSep = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
463
|
+
const formatted = fracPart ? `${intWithSep}.${fracPart}` : intWithSep;
|
|
464
|
+
return negative ? `-${formatted}` : `+${formatted}`;
|
|
465
|
+
} catch {
|
|
466
|
+
return value;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* B6 — Account moniker often defaults to the account's own address when the
|
|
471
|
+
* user never set a friendly name. Surfacing it in a "Name" column next to
|
|
472
|
+
* "Address" creates a duplicate-looking row. This helper strips that
|
|
473
|
+
* duplication so the column shows "—" instead.
|
|
474
|
+
*/
|
|
475
|
+
function displayMoniker(moniker, address) {
|
|
476
|
+
if (!moniker || moniker === address) return "—";
|
|
477
|
+
return moniker;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* B3 — render a numeric integer with thousand separators, replacing the
|
|
481
|
+
* sentinel value `0` with a semantic label such as "Unlimited" / "Never".
|
|
482
|
+
*
|
|
483
|
+
* Why server-side: the value the user reads ("Unlimited") and the value
|
|
484
|
+
* the renderer formats ("1,234") are two different presentations of the
|
|
485
|
+
* same field. Rather than teach the renderer about provider-specific
|
|
486
|
+
* sentinels (factory limit=0, asset ttl=0, tx serviceFee=0), the provider
|
|
487
|
+
* resolves the sentinel and ships a plain string the renderer can drop in.
|
|
488
|
+
*/
|
|
489
|
+
function formatIntegerOrSentinel(value, zeroLabel) {
|
|
490
|
+
if (value === null || value === void 0 || value === "") return zeroLabel;
|
|
491
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
492
|
+
if (!Number.isFinite(n) || n === 0) return zeroLabel;
|
|
493
|
+
return n.toLocaleString("en-US");
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Compose bridge `closed` + `paused` into a single user-facing label so the
|
|
497
|
+
* Bridges list and the bridge detail hero surface the same information.
|
|
498
|
+
*
|
|
499
|
+
* Why combined instead of "closed only" on the list: a bridge can be both
|
|
500
|
+
* closed and paused, or active-but-paused. Hiding the paused flag on the
|
|
501
|
+
* list left users wondering why an "Active" bridge wasn't accepting traffic.
|
|
502
|
+
*
|
|
503
|
+
* Output shape:
|
|
504
|
+
* - Active (closed=false, paused=false)
|
|
505
|
+
* - Active · Paused (closed=false, paused=true)
|
|
506
|
+
* - Closed (closed=true, paused=false)
|
|
507
|
+
* - Closed · Paused (closed=true, paused=true)
|
|
508
|
+
*/
|
|
509
|
+
function formatBridgeStatusLabel(closed, paused) {
|
|
510
|
+
const base = closed ? "Closed" : "Active";
|
|
511
|
+
return paused ? `${base} · Paused` : base;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Sibling color pair for the Status colored-badge in the bridges list.
|
|
515
|
+
*
|
|
516
|
+
* Mapping mirrors `formatBridgeStatusLabel`:
|
|
517
|
+
* - Closed → red (operational dead-end, not coming back)
|
|
518
|
+
* - Paused (still open) → amber (warning, may resume)
|
|
519
|
+
* - Active → green (healthy)
|
|
520
|
+
*
|
|
521
|
+
* Uses the same `{main, light}` shape the colored-badge formatter expects.
|
|
522
|
+
*/
|
|
523
|
+
function formatBridgeStatusColor(closed, paused) {
|
|
524
|
+
if (closed) return {
|
|
525
|
+
main: "#991B1B",
|
|
526
|
+
light: "#FEE2E2"
|
|
527
|
+
};
|
|
528
|
+
if (paused) return {
|
|
529
|
+
main: "#92400E",
|
|
530
|
+
light: "#FEF3C7"
|
|
531
|
+
};
|
|
532
|
+
return {
|
|
533
|
+
main: "#0E7C5A",
|
|
534
|
+
light: "#E8F8F1"
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Re-export `toStakeAddress` from `@arcblock/did-util` so OCAP callers stay
|
|
539
|
+
* tied to the canonical chain primitive (single source of truth lives in
|
|
540
|
+
* `blockchain/did/did-util/src/shared.ts`).
|
|
541
|
+
*
|
|
542
|
+
* did-util's implementation uses `Buffer.concat([Buffer.from(x)])`. On
|
|
543
|
+
* Cloudflare Workers this still works because the arc worker enables
|
|
544
|
+
* `compatibility_flags = ["nodejs_compat"]` (`runtimes/cloudflare/wrangler.toml:25`),
|
|
545
|
+
* which exposes the Node-style `Buffer` global. The `worker-compat.test.ts`
|
|
546
|
+
* guard rail bans `node:*` imports from shipped provider sources, not the
|
|
547
|
+
* Node-compat globals, so this re-export is safe across Node, Bun, and
|
|
548
|
+
* workerd.
|
|
549
|
+
*
|
|
550
|
+
* Used by Bridge Slash listing to match `claim_block_reward` receipts back
|
|
551
|
+
* to the rollup's validator stake addresses.
|
|
552
|
+
*/
|
|
553
|
+
const toStakeAddress$1 = toStakeAddress;
|
|
554
|
+
|
|
555
|
+
//#endregion
|
|
556
|
+
export { COLLECTION_TO_TYPES, TOP_LEVEL_DIRS, TYPE_TO_COLLECTION, buildDirEntry, buildListResult, collectionKind, displayMoniker, extractAssetFilters, extractBridgeFilters, extractDelegationFilters, extractFactoryFilters, extractStakeFilters, extractTokenFilters, extractTxFilters, formatBignumValue, formatBridgeStatusColor, formatBridgeStatusLabel, formatIntegerOrSentinel, formatTxTypeName, listOpts, resolveDidRole, resolveDidRoleLabel, resolveTxParties, toPaging, toStakeAddress$1 as toStakeAddress, txStatusIntent };
|
|
557
|
+
//# sourceMappingURL=utils.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.mjs","names":["types","sender","receiver","toStakeAddress","upstreamToStakeAddress"],"sources":["../src/utils.ts"],"sourcesContent":["import type { AFSEntry, AFSListOptions, AFSWhereInput } from \"@aigne/afs\";\nimport { AFSValidationError, normalizeWhere, validateCollectionQuerySpec } from \"@aigne/afs\";\nimport type { RouteContext } from \"@aigne/afs/provider\";\nimport { toTypeInfo, types } from \"@arcblock/did\";\n// `/cbor` subpath instead of the main entry: the main entry is a barrel\n// that re-imports `./protobuf.mjs`, which transitively pulls\n// `@ocap/proto/lib/runtime.js` and its ~1 MB of generated `*_pb.js`. The\n// `cbor` entry only depends on `./shared.mjs` (where `toStakeAddress`\n// actually lives) plus `@ocap/proto/lib/schema.js` (~7 KB, no protobuf\n// runtime). This keeps the worker bundle stable even if upstream ever\n// tree-shakes the message provider chain that currently drags the same\n// protobuf files in independently.\nimport { toStakeAddress as upstreamToStakeAddress } from \"@arcblock/did-util/cbor\";\nimport { joinURL } from \"ufo\";\nimport type {\n AssetFilters,\n BridgeFilters,\n DelegationFilters,\n FactoryFilters,\n Paging,\n StakeFilters,\n TokenFilters,\n TxFilters,\n} from \"./types.js\";\n\n/**\n * Shape of the `options` payload OCAP `@List` handlers actually consume.\n *\n * The core `AFSListOptions` is a fixed-field interface (offset, limit,\n * filter, …) with no index signature, so OCAP's domain-specific filter\n * fields (sender, types, tokenAddress, …) won't fit it without casts.\n * Rather than punching a `Record<string, unknown>` hole into every\n * handler — which is what `as any` / `as Record<string, unknown>` were\n * doing throughout `provider.ts` — we collect every field OCAP reads\n * here once and let the extractors and `toPaging` consume a typed\n * union.\n *\n * Single-vs-array filter fields (types / tokens) accept both shapes:\n * the AUP single-select input emits a scalar string, the multi-select\n * emits a string[]. The `extractTx`/`extractAsset`/… helpers coerce\n * to the GQL-shaped `string[]` once.\n */\nexport interface OcapListOptions extends AFSListOptions {\n // Address-direction filters — listTransactions / listStakes\n sender?: string;\n receiver?: string;\n direction?: string;\n // Entity collection filters — listTransactions\n types?: string | string[];\n tokens?: string | string[];\n assets?: string[];\n factories?: string[];\n accounts?: string[];\n rollups?: string[];\n stakes?: string[];\n delegations?: string[];\n validity?: string;\n // Time window — every list endpoint\n startDateTime?: string;\n endDateTime?: string;\n timeStart?: string;\n timeEnd?: string;\n // Entity-specific scalars\n issuerAddress?: string;\n ownerAddress?: string;\n factoryAddress?: string;\n tokenAddress?: string;\n addressList?: string[];\n // Delegation filter (from/to are role-named, distinct from sender/receiver)\n from?: string;\n to?: string;\n}\n\nexport const TOP_LEVEL_DIRS = [\n \"config\",\n \"validators\",\n \"transactions\",\n \"accounts\",\n \"tokens\",\n \"assets\",\n \"factories\",\n \"stakes\",\n \"bridges\",\n \"delegates\",\n] as const;\n\n/** Collection path segment → singular kind suffix. Single source of truth. */\nconst COLLECTION_KIND_MAP: Record<string, string> = {\n config: \"config\",\n validators: \"validator\",\n transactions: \"transaction\",\n accounts: \"account\",\n tokens: \"token\",\n assets: \"asset\",\n factories: \"factory\",\n stakes: \"stake\",\n bridges: \"bridge\",\n delegates: \"delegate\",\n};\n\n/** Search result type → collection path segment. */\nexport const TYPE_TO_COLLECTION: Record<string, string> = {\n tx: \"transactions\",\n transaction: \"transactions\",\n account: \"accounts\",\n token: \"tokens\",\n asset: \"assets\",\n factory: \"factories\",\n stake: \"stakes\",\n bridge: \"bridges\",\n delegate: \"delegates\",\n};\n\n/** Returns the full `ocap:xxx` kind for a collection name. */\nexport function collectionKind(collection: string): string {\n return `ocap:${COLLECTION_KIND_MAP[collection] ?? collection}`;\n}\n\nexport function toPaging(options: OcapListOptions | undefined, defaultSize = 20): Paging {\n return {\n size: options?.limit ?? defaultSize,\n offset: options?.offset ?? 0,\n };\n}\n\n/**\n * Single-point narrow from `ctx.options` (typed as `RouteOptions` union in\n * `@aigne/afs/provider`) down to OCAP's domain-specific `OcapListOptions`\n * shape. Use this in every `@List` handler instead of casting through\n * `as any` / `as Record<string, unknown>` — the runtime cost is identical\n * (a bare assertion, no copy), but the type system now knows what fields\n * the handler may read, and `toPaging` + `extract*Filters` accept the\n * narrowed type directly without further casts.\n */\n/**\n * Typed-where vocabulary (afs-collection-query T5.3): every field is one of\n * OCAP's documented filter keys, with the operator(s) its extractor shape\n * admits. ANYTHING else — unknown field, disallowed operator, non-string\n * value — is a strict `AFSValidationError`; no unvetted token can ever\n * reach a GQL variable. (The core grammar validator has already rejected\n * prototype keys / malformed dot-paths before this table is consulted.)\n */\nconst WHERE_SCALAR_FIELDS = new Set([\n \"sender\",\n \"receiver\",\n \"direction\",\n \"validity\",\n \"issuerAddress\",\n \"ownerAddress\",\n \"factoryAddress\",\n \"tokenAddress\",\n \"from\",\n \"to\",\n]);\nconst WHERE_LIST_FIELDS = new Set([\n \"types\",\n \"tokens\",\n \"assets\",\n \"factories\",\n \"accounts\",\n \"rollups\",\n \"stakes\",\n \"delegations\",\n \"addressList\",\n]);\n/** `time` leaves map gte→startDateTime / lte→endDateTime (ISO strings). */\nconst WHERE_TIME_FIELD = \"time\";\n\n/** A leaf whose operator value is \"\" or [] is the form's untouched state. */\nfunction isEmptySentinelLeaf(leaf: unknown): boolean {\n if (typeof leaf !== \"object\" || leaf === null) return false;\n const rec = leaf as Record<string, unknown>;\n for (const op of [\"eq\", \"in\", \"notIn\", \"contains\", \"gte\", \"lte\"]) {\n if (op in rec) {\n const v = rec[op];\n return v === \"\" || (Array.isArray(v) && v.length === 0);\n }\n }\n return false;\n}\n\n/** Drop empty-sentinel leaves; undefined when nothing substantive remains. */\nfunction pruneEmptySentinels(where: AFSWhereInput): AFSWhereInput | undefined {\n if (typeof where !== \"object\" || where === null) return where;\n const rec = where as Record<string, unknown>;\n if (Array.isArray(rec.all)) {\n const kept = rec.all.filter((leaf) => !isEmptySentinelLeaf(leaf));\n if (kept.length === 0) return undefined;\n return { all: kept } as AFSWhereInput;\n }\n if (typeof rec.field === \"string\" && isEmptySentinelLeaf(rec)) return undefined;\n return where;\n}\n\n/**\n * Translate a typed `where` into the legacy private option keys the 7\n * `extract*Filters` entry points consume — the GQL filter structure the\n * extractors emit is therefore byte-identical to the private-key path\n * (design §6: chain-explorer / OCAP row). Empty string / empty array values\n * skip the leaf, mirroring the extractors' own empty-sentinel semantics.\n */\nexport function whereToOcapOptions(where: AFSWhereInput): Partial<OcapListOptions> {\n // Empty-sentinel pre-pass: a declarative page emits its where at event\n // time with whatever the form holds — an untouched multi-select is `in:\n // []`, an untouched select is `eq: \"\"`. Those mean \"no filter\" (the same\n // sentinel the legacy private keys used), and the core grammar would\n // reject an empty `in`, so drop them BEFORE validation.\n const pruned = pruneEmptySentinels(where);\n if (pruned === undefined) return {};\n // Layer 1: core grammar (dot-path whitelist, prototype-key blocklist,\n // operator arity). Throws AFSValidationError on violation.\n validateCollectionQuerySpec({ where: pruned });\n const out: Record<string, unknown> = {};\n for (const leaf of normalizeWhere(pruned)) {\n const field = leaf.field;\n const ops = Object.keys(leaf).filter((k) => k !== \"field\");\n const op = ops[0] as \"eq\" | \"in\" | \"notIn\" | \"contains\" | \"gte\" | \"lte\";\n const value = (leaf as Record<string, unknown>)[op];\n if (field === WHERE_TIME_FIELD) {\n if (op !== \"gte\" && op !== \"lte\") {\n throw new AFSValidationError(\n `Invalid query spec: where.${field}: time accepts gte/lte only (got ${op})`,\n );\n }\n if (typeof value !== \"string\") {\n throw new AFSValidationError(`Invalid query spec: where.${field}: ISO string required`);\n }\n if (value === \"\") continue;\n out[op === \"gte\" ? \"startDateTime\" : \"endDateTime\"] = value;\n continue;\n }\n if (WHERE_SCALAR_FIELDS.has(field)) {\n if (op !== \"eq\") {\n throw new AFSValidationError(\n `Invalid query spec: where.${field}: scalar filter accepts eq only (got ${op})`,\n );\n }\n if (typeof value !== \"string\") {\n throw new AFSValidationError(`Invalid query spec: where.${field}: string value required`);\n }\n if (value === \"\") continue; // empty sentinel = no filter (legacy parity)\n out[field] = value;\n continue;\n }\n if (WHERE_LIST_FIELDS.has(field)) {\n if (op !== \"eq\" && op !== \"in\") {\n throw new AFSValidationError(\n `Invalid query spec: where.${field}: list filter accepts eq/in only (got ${op})`,\n );\n }\n const arr = op === \"in\" ? (value as unknown[]) : [value];\n if (!arr.every((v) => typeof v === \"string\")) {\n throw new AFSValidationError(`Invalid query spec: where.${field}: string values required`);\n }\n const cleaned = (arr as string[]).filter((v) => v.length > 0);\n if (cleaned.length === 0) continue; // empty sentinel = no filter\n out[field] = cleaned;\n continue;\n }\n // Vocabulary gate — unknown fields NEVER pass toward the GQL layer.\n throw new AFSValidationError(`Invalid query spec: where.${field}: not an OCAP filter field`);\n }\n return out as Partial<OcapListOptions>;\n}\n\nexport function listOpts<T>(ctx: RouteContext<T>): OcapListOptions | undefined {\n const raw = ctx.options as (OcapListOptions & { where?: AFSWhereInput }) | undefined;\n if (!raw || raw.where === undefined || raw.where === null) {\n return raw;\n }\n // Typed where (T5.3): adapter output wins over same-key private options so\n // a page that declares `where` cannot be shadowed by stale private keys.\n return { ...raw, ...whereToOcapOptions(raw.where) };\n}\n\nexport function buildDirEntry(basePath: string, name: string, childrenCount?: number): AFSEntry {\n // Internal / parent-traversal entries (\".\", \"..\", or any dotfile like \".aup\",\n // \".did\", \".meta\") are tagged hidden so the AUP afs-list renderer auto-skips\n // them without per-blocklet `filter: { exclude: [...] }` config.\n // Explicit `afs_read /<path>/.aup` continues to work — this is a list-time\n // filter, not access control. See planning/experience-upgrade § R2.\n const isHidden = name === \"..\" || name.startsWith(\".\");\n const meta: Record<string, unknown> = {\n kind: collectionKind(name),\n childrenCount: childrenCount ?? -1,\n };\n if (isHidden) meta.visibility = \"hidden\";\n return {\n id: name,\n path: joinURL(basePath, name),\n meta,\n summary: name,\n };\n}\n\nexport function buildListResult(\n entries: AFSEntry[],\n total: number,\n): { data: AFSEntry[]; total: number } {\n return { data: entries, total };\n}\n\n/** Reverse mapping: collection path segment → search result types. */\nexport const COLLECTION_TO_TYPES: Record<string, string[]> = {};\nfor (const [type, col] of Object.entries(TYPE_TO_COLLECTION)) {\n if (!COLLECTION_TO_TYPES[col]) COLLECTION_TO_TYPES[col] = [];\n COLLECTION_TO_TYPES[col].push(type);\n}\n\n// ============ Filter Extraction ============\n\ntype Opts = OcapListOptions | undefined;\n\nfunction pickDefined<T extends Record<string, unknown>>(obj: T): T | undefined {\n const result: Record<string, unknown> = {};\n let hasAny = false;\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined && v !== null) {\n result[k] = v;\n hasAny = true;\n }\n }\n return hasAny ? (result as T) : undefined;\n}\n\nfunction extractTimeFilter(\n opts: Opts,\n): { startDateTime?: string; endDateTime?: string } | undefined {\n const start = opts?.startDateTime ?? opts?.timeStart;\n const end = opts?.endDateTime ?? opts?.timeEnd;\n if (!start && !end) return undefined;\n const filter: { startDateTime?: string; endDateTime?: string } = {};\n if (start) filter.startDateTime = start;\n if (end) filter.endDateTime = end;\n return filter;\n}\n\n/**\n * Normalize the user-supplied validity value to the GQL enum. Accepts the\n * canonical strings (`\"VALID\"` / `\"INVALID\"` / `\"BOTH\"`), the lowercase\n * variants emitted by URL params, and the empty / undefined sentinels meaning\n * \"no filter\". Any other value is dropped so an arbitrary URL param can't\n * smuggle a non-enum string through to the GQL layer.\n */\nfunction normalizeValidity(value: unknown): \"VALID\" | \"INVALID\" | \"BOTH\" | undefined {\n if (typeof value !== \"string\" || !value) return undefined;\n const upper = value.toUpperCase();\n if (upper === \"VALID\" || upper === \"INVALID\" || upper === \"BOTH\") return upper;\n return undefined;\n}\n\nexport function extractTxFilters(opts: Opts): TxFilters | undefined {\n if (!opts) return undefined;\n let addressFilter: TxFilters[\"addressFilter\"];\n if (opts.sender || opts.receiver) {\n addressFilter = { direction: opts.direction ?? \"UNION\" };\n if (opts.sender) addressFilter.sender = opts.sender;\n if (opts.receiver) addressFilter.receiver = opts.receiver;\n }\n const validity = normalizeValidity(opts.validity);\n // Accept string or string[] for list filters: single-select AUP inputs\n // emit a scalar string, multi-select inputs emit an array. The GQL layer\n // wants `string[]`, so coerce here once instead of forcing every caller to\n // wrap. Empty string / empty array → undefined (no filter).\n const toList = (v: string | string[] | undefined): string[] | undefined => {\n if (Array.isArray(v)) {\n const cleaned = v.filter((x): x is string => typeof x === \"string\" && x.length > 0);\n return cleaned.length > 0 ? cleaned : undefined;\n }\n if (typeof v === \"string\" && v.length > 0) return [v];\n return undefined;\n };\n const types = toList(opts.types);\n const tokens = toList(opts.tokens);\n return pickDefined({\n addressFilter,\n typeFilter: types ? { types } : undefined,\n tokenFilter: tokens ? { tokens } : undefined,\n assetFilter: opts.assets ? { assets: opts.assets } : undefined,\n factoryFilter: opts.factories ? { factories: opts.factories } : undefined,\n accountFilter: opts.accounts ? { accounts: opts.accounts } : undefined,\n rollupFilter: opts.rollups ? { rollups: opts.rollups } : undefined,\n stakeFilter: opts.stakes ? { stakes: opts.stakes } : undefined,\n delegationFilter: opts.delegations ? { delegations: opts.delegations } : undefined,\n // BOTH means \"no filter\"; skip emitting the field so the GQL layer doesn't\n // see a constraint it then has to evaluate.\n validityFilter: validity && validity !== \"BOTH\" ? { validity } : undefined,\n timeFilter: extractTimeFilter(opts),\n });\n}\n\nexport function extractTokenFilters(opts: Opts): TokenFilters | undefined {\n if (!opts) return undefined;\n return pickDefined({\n issuerAddress: opts.issuerAddress,\n timeFilter: extractTimeFilter(opts),\n });\n}\n\nexport function extractAssetFilters(opts: Opts): AssetFilters | undefined {\n if (!opts) return undefined;\n return pickDefined({\n ownerAddress: opts.ownerAddress,\n factoryAddress: opts.factoryAddress,\n timeFilter: extractTimeFilter(opts),\n });\n}\n\nexport function extractFactoryFilters(opts: Opts): FactoryFilters | undefined {\n if (!opts) return undefined;\n return pickDefined({\n ownerAddress: opts.ownerAddress,\n addressList: opts.addressList,\n timeFilter: extractTimeFilter(opts),\n });\n}\n\nexport function extractStakeFilters(opts: Opts): StakeFilters | undefined {\n if (!opts) return undefined;\n let addressFilter: StakeFilters[\"addressFilter\"];\n if (opts.sender || opts.receiver) {\n addressFilter = { direction: opts.direction ?? \"UNION\" };\n if (opts.sender) addressFilter.sender = opts.sender;\n if (opts.receiver) addressFilter.receiver = opts.receiver;\n }\n return pickDefined({\n addressFilter,\n timeFilter: extractTimeFilter(opts),\n });\n}\n\nexport function extractBridgeFilters(opts: Opts): BridgeFilters | undefined {\n if (!opts) return undefined;\n return pickDefined({\n tokenAddress: opts.tokenAddress,\n timeFilter: extractTimeFilter(opts),\n });\n}\n\nexport function extractDelegationFilters(opts: Opts): DelegationFilters | undefined {\n if (!opts) return undefined;\n return pickDefined({\n from: opts.from,\n to: opts.to,\n timeFilter: extractTimeFilter(opts),\n });\n}\n\n// ============ Intent Recognition ============\n\n/** DID role code → OCAP entity type name. */\nconst ROLE_TO_ENTITY: Record<number, string> = {\n [types.RoleType.ROLE_ACCOUNT]: \"account\",\n [types.RoleType.ROLE_APPLICATION]: \"account\",\n [types.RoleType.ROLE_NODE]: \"account\",\n [types.RoleType.ROLE_VALIDATOR]: \"account\",\n [types.RoleType.ROLE_BOT]: \"account\",\n [types.RoleType.ROLE_PASSKEY]: \"account\",\n [types.RoleType.ROLE_TOKEN]: \"token\",\n [types.RoleType.ROLE_ASSET]: \"asset\",\n [types.RoleType.ROLE_FACTORY]: \"factory\",\n [types.RoleType.ROLE_TOKEN_FACTORY]: \"factory\",\n [types.RoleType.ROLE_STAKE]: \"stake\",\n [types.RoleType.ROLE_ROLLUP]: \"bridge\",\n [types.RoleType.ROLE_DELEGATION]: \"delegate\",\n};\n\n/** Extract OCAP entity type from a DID address using its encoded role byte. */\nexport function resolveDidRole(address: string): string | null {\n try {\n const info = toTypeInfo(address);\n if (info.role === undefined) return null;\n return ROLE_TO_ENTITY[info.role] ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Granular human-readable role label for a DID (e.g., \"Validator\", \"Bot\",\n * \"Application\"). Used by the account detail page's role pill — the\n * generic resolveDidRole returns just \"account\" for ROLE_VALIDATOR /\n * ROLE_BOT / ROLE_NODE / etc., losing the actual sub-type information\n * the user sees in legacy explorers.\n */\nconst ROLE_TO_LABEL: Record<number, string> = {\n [types.RoleType.ROLE_ACCOUNT]: \"Account\",\n [types.RoleType.ROLE_APPLICATION]: \"Application\",\n [types.RoleType.ROLE_NODE]: \"Node\",\n [types.RoleType.ROLE_VALIDATOR]: \"Validator\",\n [types.RoleType.ROLE_BOT]: \"Bot\",\n [types.RoleType.ROLE_PASSKEY]: \"Passkey\",\n [types.RoleType.ROLE_TOKEN]: \"Token\",\n [types.RoleType.ROLE_ASSET]: \"Asset\",\n [types.RoleType.ROLE_FACTORY]: \"Factory\",\n [types.RoleType.ROLE_TOKEN_FACTORY]: \"Token Factory\",\n [types.RoleType.ROLE_STAKE]: \"Stake\",\n [types.RoleType.ROLE_ROLLUP]: \"Rollup\",\n [types.RoleType.ROLE_DELEGATION]: \"Delegation\",\n};\n\nexport function resolveDidRoleLabel(address: string): string | null {\n try {\n const info = toTypeInfo(address);\n if (info.role === undefined) return null;\n return ROLE_TO_LABEL[info.role] ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * \"transfer_v3\" → \"Transfer V3\"\n *\n * Display rebrand: the chain protocol calls them \"rollups\" in tx type names\n * (\\`create_rollup\\`, \\`update_rollup\\`, …) but the user-facing surface uses\n * \"Bridge\". Substitute the word in the rendered label so the Type column\n * reads \"Create Bridge\" / \"Update Bridge\" without changing the on-chain\n * type literal that GQL still uses.\n */\nexport function formatTxTypeName(type: string): string {\n const formatted = type\n .split(\"_\")\n .filter(Boolean)\n .map((s) => s.charAt(0).toUpperCase() + s.slice(1))\n .join(\" \");\n return formatted.replace(/\\bRollup\\b/g, \"Bridge\");\n}\n\n/** Map tx status code to an AUP intent string. */\nexport function txStatusIntent(code: string | null | undefined): string {\n if (!code || code === \"OK\") return \"success\";\n return \"danger\";\n}\n\n/**\n * Resolve a transaction's true sender and receiver from its nested `tx.itxJson`.\n *\n * v3-style (transfer_v3, acquire_asset_v3) carries an `inputs[]`/`outputs[]`\n * payload. The true sender is `inputs[0].owner`; the outer `tx.from` holds the\n * output owner (submitter), not the party losing tokens. Using `tx.from` as the\n * sender makes every v3 transfer look like a self-transfer.\n *\n * Legacy (transfer, transfer_v2, …) has no inputs/outputs — sender comes from\n * the outer `tx.from` and receiver from `itxJson.to` / `itxJson.receiver`.\n *\n * account_migrate carries the migration target in `itxJson.address` (the new\n * DID the account is migrating to); the outer receiver is empty on chain. We\n * surface it as the receiver so the global tx list \"To\" column points at the\n * destination account instead of rendering blank.\n */\nexport function resolveTxParties(tx: {\n sender?: string | null;\n receiver?: string | null;\n tx?: unknown;\n}): { sender?: string; receiver?: string } {\n const outer = (tx.tx as Record<string, unknown>) ?? {};\n const itxJson = (outer.itxJson as Record<string, unknown>) ?? {};\n const inputOwner = (itxJson.inputs as Array<{ owner?: string }> | undefined)?.[0]?.owner;\n const outputOwner = (itxJson.outputs as Array<{ owner?: string }> | undefined)?.[0]?.owner;\n\n // Empty strings on chain (\\`tx.sender = \"\"\\`, \\`tx.receiver = \"\"\\`) must be\n // treated as missing, otherwise nullish-coalesce chains stop early on the\n // empty string and hide the real value living in outer.from / itxJson.address.\n // Concrete regression: revoke_stake has outputs but no inputs and an empty\n // top-level sender — the v3 branch used to return sender=\"\" because\n // \\`tx.sender ?? undefined\\` keeps the empty string.\n const nonEmpty = (v: unknown): v is string => typeof v === \"string\" && v.length > 0;\n const senderFromTop = nonEmpty(tx.sender) ? tx.sender : undefined;\n const receiverFromTop = nonEmpty(tx.receiver) ? tx.receiver : undefined;\n\n // v3-style: inputs/outputs authoritative when at least one party shows up\n // there. Fall through to legacy resolution (outer.from / itxJson.*) when\n // the v3 branch leaves a side empty so an outputs-only tx like revoke_stake\n // still gets a sender.\n if (inputOwner || outputOwner) {\n const sender = nonEmpty(inputOwner)\n ? inputOwner\n : (senderFromTop ?? (nonEmpty(outer.from) ? (outer.from as string) : undefined));\n const receiver = nonEmpty(outputOwner) ? outputOwner : receiverFromTop;\n if (sender || receiver) return { sender, receiver };\n // Both still empty — let legacy resolution try its sources too.\n }\n\n let sender: string | undefined = senderFromTop;\n if (!sender && nonEmpty(outer.from)) sender = outer.from;\n\n let receiver: string | undefined = receiverFromTop;\n if (!receiver) {\n if (nonEmpty(itxJson.receiver)) receiver = itxJson.receiver;\n else if (nonEmpty(itxJson.to)) receiver = itxJson.to;\n else if (nonEmpty(outer.receiver)) receiver = outer.receiver;\n else if (itxJson.type_url === \"fg:t:account_migrate\" && nonEmpty(itxJson.address)) {\n receiver = itxJson.address;\n } else if (\n typeof itxJson.type_url === \"string\" &&\n STAKE_LIKE_TYPE_URLS.has(itxJson.type_url) &&\n nonEmpty(itxJson.address)\n ) {\n // Stake-class txs (stake / revoke_stake / claim_stake / return_stake /\n // slash_stake) act ON a stake DID. The chain leaves the top-level\n // receiver empty; surfacing \\`itxJson.address\\` (the stake counterparty)\n // gives the To column a meaningful destination so the row reads\n // \"[staker] → [stake DID]\" instead of \"[staker] → blank\".\n receiver = itxJson.address;\n }\n }\n\n return { sender, receiver };\n}\n\n// Stake-class type_urls — all act on \\`itxJson.address\\` (the target stake).\nconst STAKE_LIKE_TYPE_URLS = new Set([\n \"fg:t:stake\",\n \"fg:t:revoke_stake\",\n \"fg:t:claim_stake\",\n \"fg:t:return_stake\",\n \"fg:t:slash_stake\",\n]);\n\n/**\n * Format a raw bignum string (possibly negative) to a human-readable decimal\n * with a leading +/- sign. Suitable for displaying receipt change amounts.\n *\n * The integer part wears thousand separators so that pre-formatted strings\n * (`_gasFeeFormatted`, `_serviceFeeFormatted`, receipt `valueFormatted`)\n * read consistently with the browser-side `bignum` formatter (B1).\n *\n * Example: formatBignumValue(\"1000000000000000000\", 18) → \"+1\"\n * formatBignumValue(\"-500000000000000000\", 18) → \"-0.5\"\n * formatBignumValue(\"1230000000000000000000000000\", 18) → \"+1,230,000,000\"\n */\nexport function formatBignumValue(value: string, decimal: number): string {\n if (!value || value === \"0\") return \"0\";\n try {\n const negative = value.startsWith(\"-\");\n const abs = negative ? value.slice(1) : value;\n const padded = abs.padStart(decimal + 1, \"0\");\n const intPart = padded.slice(0, padded.length - decimal) || \"0\";\n const fracPart = padded.slice(padded.length - decimal).replace(/0+$/, \"\");\n // Insert thousand separators only into the integer part. Fractional digits\n // get no separator (decimal precision is meaningful end-to-end; commas\n // would be ambiguous against locales that use them as the radix marker).\n const intWithSep = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\n const formatted = fracPart ? `${intWithSep}.${fracPart}` : intWithSep;\n return negative ? `-${formatted}` : `+${formatted}`;\n } catch {\n return value;\n }\n}\n\n/**\n * B6 — Account moniker often defaults to the account's own address when the\n * user never set a friendly name. Surfacing it in a \"Name\" column next to\n * \"Address\" creates a duplicate-looking row. This helper strips that\n * duplication so the column shows \"—\" instead.\n */\nexport function displayMoniker(moniker: string | null | undefined, address: string): string {\n if (!moniker || moniker === address) return \"—\";\n return moniker;\n}\n\n/**\n * B3 — render a numeric integer with thousand separators, replacing the\n * sentinel value `0` with a semantic label such as \"Unlimited\" / \"Never\".\n *\n * Why server-side: the value the user reads (\"Unlimited\") and the value\n * the renderer formats (\"1,234\") are two different presentations of the\n * same field. Rather than teach the renderer about provider-specific\n * sentinels (factory limit=0, asset ttl=0, tx serviceFee=0), the provider\n * resolves the sentinel and ships a plain string the renderer can drop in.\n */\nexport function formatIntegerOrSentinel(\n value: number | string | null | undefined,\n zeroLabel: string,\n): string {\n if (value === null || value === undefined || value === \"\") return zeroLabel;\n const n = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(n) || n === 0) return zeroLabel;\n return n.toLocaleString(\"en-US\");\n}\n\n/**\n * Compose bridge `closed` + `paused` into a single user-facing label so the\n * Bridges list and the bridge detail hero surface the same information.\n *\n * Why combined instead of \"closed only\" on the list: a bridge can be both\n * closed and paused, or active-but-paused. Hiding the paused flag on the\n * list left users wondering why an \"Active\" bridge wasn't accepting traffic.\n *\n * Output shape:\n * - Active (closed=false, paused=false)\n * - Active · Paused (closed=false, paused=true)\n * - Closed (closed=true, paused=false)\n * - Closed · Paused (closed=true, paused=true)\n */\nexport function formatBridgeStatusLabel(closed?: boolean, paused?: boolean): string {\n const base = closed ? \"Closed\" : \"Active\";\n return paused ? `${base} · Paused` : base;\n}\n\n/**\n * Sibling color pair for the Status colored-badge in the bridges list.\n *\n * Mapping mirrors `formatBridgeStatusLabel`:\n * - Closed → red (operational dead-end, not coming back)\n * - Paused (still open) → amber (warning, may resume)\n * - Active → green (healthy)\n *\n * Uses the same `{main, light}` shape the colored-badge formatter expects.\n */\nexport function formatBridgeStatusColor(\n closed?: boolean,\n paused?: boolean,\n): { main: string; light: string } {\n if (closed) return { main: \"#991B1B\", light: \"#FEE2E2\" };\n if (paused) return { main: \"#92400E\", light: \"#FEF3C7\" };\n return { main: \"#0E7C5A\", light: \"#E8F8F1\" };\n}\n\n/**\n * Re-export `toStakeAddress` from `@arcblock/did-util` so OCAP callers stay\n * tied to the canonical chain primitive (single source of truth lives in\n * `blockchain/did/did-util/src/shared.ts`).\n *\n * did-util's implementation uses `Buffer.concat([Buffer.from(x)])`. On\n * Cloudflare Workers this still works because the arc worker enables\n * `compatibility_flags = [\"nodejs_compat\"]` (`runtimes/cloudflare/wrangler.toml:25`),\n * which exposes the Node-style `Buffer` global. The `worker-compat.test.ts`\n * guard rail bans `node:*` imports from shipped provider sources, not the\n * Node-compat globals, so this re-export is safe across Node, Bun, and\n * workerd.\n *\n * Used by Bridge Slash listing to match `claim_block_reward` receipts back\n * to the rollup's validator stake addresses.\n */\nexport const toStakeAddress = upstreamToStakeAddress;\n"],"mappings":"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"}
|