@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.
Files changed (58) hide show
  1. package/LICENSE.md +26 -0
  2. package/README.md +144 -0
  3. package/aup/accounts/default.json +64 -0
  4. package/aup/accounts/item.json +510 -0
  5. package/aup/assets/default.json +70 -0
  6. package/aup/assets/item.json +360 -0
  7. package/aup/bridges/_addr/blocks/item.json +168 -0
  8. package/aup/bridges/default.json +79 -0
  9. package/aup/bridges/item.json +868 -0
  10. package/aup/config/default.json +806 -0
  11. package/aup/default.json +387 -0
  12. package/aup/delegates/default.json +71 -0
  13. package/aup/delegates/item.json +241 -0
  14. package/aup/factories/default.json +78 -0
  15. package/aup/factories/item.json +310 -0
  16. package/aup/stakes/default.json +76 -0
  17. package/aup/stakes/item.json +374 -0
  18. package/aup/tokens/default.json +76 -0
  19. package/aup/tokens/item.json +384 -0
  20. package/aup/transactions/default.json +102 -0
  21. package/aup/transactions/item.json +286 -0
  22. package/aup/validators/default.json +57 -0
  23. package/aup/validators/item.json +99 -0
  24. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  25. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
  26. package/dist/index.cjs +7 -0
  27. package/dist/index.d.cts +5 -0
  28. package/dist/index.d.mts +5 -0
  29. package/dist/index.mjs +4 -0
  30. package/dist/method-colors.cjs +137 -0
  31. package/dist/method-colors.mjs +136 -0
  32. package/dist/method-colors.mjs.map +1 -0
  33. package/dist/provider.cjs +2490 -0
  34. package/dist/provider.d.cts +453 -0
  35. package/dist/provider.d.cts.map +1 -0
  36. package/dist/provider.d.mts +453 -0
  37. package/dist/provider.d.mts.map +1 -0
  38. package/dist/provider.mjs +2490 -0
  39. package/dist/provider.mjs.map +1 -0
  40. package/dist/remote-source.cjs +338 -0
  41. package/dist/remote-source.d.cts +62 -0
  42. package/dist/remote-source.d.cts.map +1 -0
  43. package/dist/remote-source.d.mts +62 -0
  44. package/dist/remote-source.d.mts.map +1 -0
  45. package/dist/remote-source.mjs +339 -0
  46. package/dist/remote-source.mjs.map +1 -0
  47. package/dist/source.d.cts +77 -0
  48. package/dist/source.d.cts.map +1 -0
  49. package/dist/source.d.mts +77 -0
  50. package/dist/source.d.mts.map +1 -0
  51. package/dist/types.d.cts +572 -0
  52. package/dist/types.d.cts.map +1 -0
  53. package/dist/types.d.mts +572 -0
  54. package/dist/types.d.mts.map +1 -0
  55. package/dist/utils.cjs +581 -0
  56. package/dist/utils.mjs +557 -0
  57. package/dist/utils.mjs.map +1 -0
  58. package/package.json +61 -0
package/dist/utils.cjs ADDED
@@ -0,0 +1,581 @@
1
+ let _aigne_afs = require("@aigne/afs");
2
+ let ufo = require("ufo");
3
+ let _arcblock_did = require("@arcblock/did");
4
+ let _arcblock_did_util_cbor = require("@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
+ (0, _aigne_afs.validateCollectionQuerySpec)({ where: pruned });
136
+ const out = {};
137
+ for (const leaf of (0, _aigne_afs.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 _aigne_afs.AFSValidationError(`Invalid query spec: where.${field}: time accepts gte/lte only (got ${op})`);
143
+ if (typeof value !== "string") throw new _aigne_afs.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 _aigne_afs.AFSValidationError(`Invalid query spec: where.${field}: scalar filter accepts eq only (got ${op})`);
150
+ if (typeof value !== "string") throw new _aigne_afs.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 _aigne_afs.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 _aigne_afs.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 _aigne_afs.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: (0, ufo.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 = toList(opts.types);
249
+ const tokens = toList(opts.tokens);
250
+ return pickDefined({
251
+ addressFilter,
252
+ typeFilter: types ? { types } : 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
+ [_arcblock_did.types.RoleType.ROLE_ACCOUNT]: "account",
318
+ [_arcblock_did.types.RoleType.ROLE_APPLICATION]: "account",
319
+ [_arcblock_did.types.RoleType.ROLE_NODE]: "account",
320
+ [_arcblock_did.types.RoleType.ROLE_VALIDATOR]: "account",
321
+ [_arcblock_did.types.RoleType.ROLE_BOT]: "account",
322
+ [_arcblock_did.types.RoleType.ROLE_PASSKEY]: "account",
323
+ [_arcblock_did.types.RoleType.ROLE_TOKEN]: "token",
324
+ [_arcblock_did.types.RoleType.ROLE_ASSET]: "asset",
325
+ [_arcblock_did.types.RoleType.ROLE_FACTORY]: "factory",
326
+ [_arcblock_did.types.RoleType.ROLE_TOKEN_FACTORY]: "factory",
327
+ [_arcblock_did.types.RoleType.ROLE_STAKE]: "stake",
328
+ [_arcblock_did.types.RoleType.ROLE_ROLLUP]: "bridge",
329
+ [_arcblock_did.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 = (0, _arcblock_did.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
+ [_arcblock_did.types.RoleType.ROLE_ACCOUNT]: "Account",
350
+ [_arcblock_did.types.RoleType.ROLE_APPLICATION]: "Application",
351
+ [_arcblock_did.types.RoleType.ROLE_NODE]: "Node",
352
+ [_arcblock_did.types.RoleType.ROLE_VALIDATOR]: "Validator",
353
+ [_arcblock_did.types.RoleType.ROLE_BOT]: "Bot",
354
+ [_arcblock_did.types.RoleType.ROLE_PASSKEY]: "Passkey",
355
+ [_arcblock_did.types.RoleType.ROLE_TOKEN]: "Token",
356
+ [_arcblock_did.types.RoleType.ROLE_ASSET]: "Asset",
357
+ [_arcblock_did.types.RoleType.ROLE_FACTORY]: "Factory",
358
+ [_arcblock_did.types.RoleType.ROLE_TOKEN_FACTORY]: "Token Factory",
359
+ [_arcblock_did.types.RoleType.ROLE_STAKE]: "Stake",
360
+ [_arcblock_did.types.RoleType.ROLE_ROLLUP]: "Rollup",
361
+ [_arcblock_did.types.RoleType.ROLE_DELEGATION]: "Delegation"
362
+ };
363
+ function resolveDidRoleLabel(address) {
364
+ try {
365
+ const info = (0, _arcblock_did.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 = _arcblock_did_util_cbor.toStakeAddress;
554
+
555
+ //#endregion
556
+ exports.COLLECTION_TO_TYPES = COLLECTION_TO_TYPES;
557
+ exports.TOP_LEVEL_DIRS = TOP_LEVEL_DIRS;
558
+ exports.TYPE_TO_COLLECTION = TYPE_TO_COLLECTION;
559
+ exports.buildDirEntry = buildDirEntry;
560
+ exports.buildListResult = buildListResult;
561
+ exports.collectionKind = collectionKind;
562
+ exports.displayMoniker = displayMoniker;
563
+ exports.extractAssetFilters = extractAssetFilters;
564
+ exports.extractBridgeFilters = extractBridgeFilters;
565
+ exports.extractDelegationFilters = extractDelegationFilters;
566
+ exports.extractFactoryFilters = extractFactoryFilters;
567
+ exports.extractStakeFilters = extractStakeFilters;
568
+ exports.extractTokenFilters = extractTokenFilters;
569
+ exports.extractTxFilters = extractTxFilters;
570
+ exports.formatBignumValue = formatBignumValue;
571
+ exports.formatBridgeStatusColor = formatBridgeStatusColor;
572
+ exports.formatBridgeStatusLabel = formatBridgeStatusLabel;
573
+ exports.formatIntegerOrSentinel = formatIntegerOrSentinel;
574
+ exports.formatTxTypeName = formatTxTypeName;
575
+ exports.listOpts = listOpts;
576
+ exports.resolveDidRole = resolveDidRole;
577
+ exports.resolveDidRoleLabel = resolveDidRoleLabel;
578
+ exports.resolveTxParties = resolveTxParties;
579
+ exports.toPaging = toPaging;
580
+ exports.toStakeAddress = toStakeAddress;
581
+ exports.txStatusIntent = txStatusIntent;