@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
@@ -0,0 +1,2490 @@
1
+ import { getTxColor, getTxGroup } from "./method-colors.mjs";
2
+ import { OcapRemoteSource } from "./remote-source.mjs";
3
+ import { 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, txStatusIntent } from "./utils.mjs";
4
+ import { __decorate } from "./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs";
5
+ import { AFSNotFoundError, AFS_EVENT_TYPES } from "@aigne/afs";
6
+ import { AFSBaseProvider, Explain, List, Meta, Read, Search, Stat } from "@aigne/afs/provider";
7
+ import { joinURL } from "ufo";
8
+ import { z } from "zod";
9
+
10
+ //#region src/provider.ts
11
+ /**
12
+ * Build the `content` payload for a token detail entry. Centralizes three
13
+ * pieces of derivation that the AUP page consumes:
14
+ *
15
+ * 1. `genesisTime` / `renaissanceTime` — the chain wire delivers these
16
+ * in two shapes (top-level for some token types, nested under a
17
+ * `context` mixin for others). We normalize to top-level here so
18
+ * the recipe can bind one stable path; this is a multi-source
19
+ * fallback, NOT a workaround for AUP nesting limits (AUP binds
20
+ * arbitrary `a.b.c` paths via `_surfaceBindField`). Stake / delegate
21
+ * detail handlers don't need this because their wire shape is
22
+ * consistent.
23
+ * 2. `metadata.value` — issuer-supplied free-form info, sent as a JSON
24
+ * string. Parsed into `_issuerName` (display name) and `_communityUrl`
25
+ * so the page doesn't have to JSON-parse on the client.
26
+ * 3. `_typeLabel` — humanized rendering of `state.type`. The chain stores
27
+ * `Token` / `BondingCurveToken` / `CreditToken`; users want
28
+ * "Standard / Bonding Curve / Credit Token". Falls back to deriving
29
+ * from `tokenFactoryAddress` when the type field is empty (older
30
+ * tokens minted before the type discriminator existed).
31
+ *
32
+ * Pure function — kept at module scope so the unit tests can exercise the
33
+ * derivation without spinning up a provider.
34
+ */
35
+ function enrichTokenContent(state) {
36
+ const ctxBlock = state.context;
37
+ let metadataValue;
38
+ const rawMeta = state.metadata?.value;
39
+ if (typeof rawMeta === "string") try {
40
+ const parsed = JSON.parse(rawMeta);
41
+ if (parsed && typeof parsed === "object") metadataValue = parsed;
42
+ } catch {}
43
+ else if (rawMeta && typeof rawMeta === "object") metadataValue = rawMeta;
44
+ const issuerName = typeof metadataValue?.issuer === "string" ? metadataValue.issuer : void 0;
45
+ const communityUrl = typeof metadataValue?.communityUrl === "string" ? metadataValue.communityUrl : void 0;
46
+ const typeLabel = (() => {
47
+ switch (state.type) {
48
+ case "CreditToken": return "Credit Token";
49
+ case "BondingCurveToken": return "Bonding Curve Token";
50
+ case "Token": return "Standard Token";
51
+ default: return state.tokenFactoryAddress ? "Bonding Curve Token" : "Standard Token";
52
+ }
53
+ })();
54
+ return {
55
+ ...state,
56
+ genesisTime: state.genesisTime ?? ctxBlock?.genesisTime,
57
+ renaissanceTime: state.renaissanceTime ?? ctxBlock?.renaissanceTime,
58
+ _issuerName: issuerName,
59
+ _communityUrl: communityUrl,
60
+ _typeLabel: typeLabel
61
+ };
62
+ }
63
+ /**
64
+ * Build the `_nftDisplay` payload that the AUP `nft-display` renderer
65
+ * binds to. The chain stores the rendering hint as `display.{type,
66
+ * content}`; for `type === "url"` we pre-attach the asset's `assetId`
67
+ * (and `vcId` when present) so the renderer doesn't have to know about
68
+ * the URL convention. Other types (uri / svg / svg_gzipped) need no
69
+ * URL preprocessing — the renderer dispatches on `type` and consumes
70
+ * `content` directly.
71
+ *
72
+ * Returns `null` (rather than an empty object) when the chain doesn't
73
+ * have a display descriptor — the AUP page binds `${content._nftDisplay}`
74
+ * and renders a "no display" placeholder for null.
75
+ */
76
+ function deriveNftDisplay(state) {
77
+ const d = state.display;
78
+ if (!d || !d.type || !d.content) return null;
79
+ const out = {
80
+ type: d.type,
81
+ content: d.content
82
+ };
83
+ if (d.vcId) out.vcId = d.vcId;
84
+ if (d.type === "url") try {
85
+ const u = new URL(d.content);
86
+ if (!u.searchParams.has("assetId")) u.searchParams.set("assetId", state.address);
87
+ if (d.vcId && !u.searchParams.has("vcId")) u.searchParams.set("vcId", d.vcId);
88
+ out.resolvedUrl = u.href;
89
+ } catch {}
90
+ return out;
91
+ }
92
+ /**
93
+ * Build per-tx-type label overrides for the "Type Info" card on the
94
+ * transaction detail page. The card surfaces inner-tx subject fields
95
+ * (`_itxAddress`, `_itxMoniker`, `_itxFactory`, `_itxRollup`,
96
+ * `_itxBlockHash`, `_itxBlockHeight`) whose meaning is tx-type-specific —
97
+ * "Address" alone is too vague when it could be a token, asset, factory,
98
+ * stake, or account DID. Per-type labels make the row read clearly:
99
+ * "Token DID" / "Asset DID" / "Rollup Block Hash" etc.
100
+ *
101
+ * Returns a flat object whose keys the AUP page binds via
102
+ * `${content._typeContext.<key>}`. All keys carry a sensible fallback
103
+ * (the same generic label the card used to ship with) so unknown tx
104
+ * types still render without holes; the rest of the row's hide-when-
105
+ * empty behavior keeps irrelevant rows out of the DOM regardless of
106
+ * label content.
107
+ */
108
+ function deriveTypeContext(rawType) {
109
+ const t = (rawType || "").toLowerCase();
110
+ const out = {
111
+ addressLabel: "Address",
112
+ addressPath: "accounts/{value}",
113
+ monikerLabel: "Moniker",
114
+ factoryLabel: "Factory",
115
+ rollupLabel: "Bridge",
116
+ blockHashLabel: "Block Hash",
117
+ blockHeightLabel: "Block Height"
118
+ };
119
+ switch (t) {
120
+ case "create_token":
121
+ case "createtoken": return {
122
+ ...out,
123
+ addressLabel: "Token DID",
124
+ addressPath: "tokens/{value}",
125
+ monikerLabel: "Token Name",
126
+ factoryLabel: "Token Factory"
127
+ };
128
+ case "create_token_factory":
129
+ case "createtokenfactory":
130
+ case "update_token_factory":
131
+ case "updatetokenfactory": return {
132
+ ...out,
133
+ addressLabel: "Token Factory DID",
134
+ addressPath: "factories/{value}",
135
+ monikerLabel: "Token Factory Name"
136
+ };
137
+ case "mint_token":
138
+ case "minttoken":
139
+ case "burn_token":
140
+ case "burntoken":
141
+ case "deposit_token_v2":
142
+ case "deposittokenv2":
143
+ case "withdraw_token_v2":
144
+ case "withdrawtokenv2": return {
145
+ ...out,
146
+ factoryLabel: "Token Factory"
147
+ };
148
+ case "create_factory":
149
+ case "createfactory": return {
150
+ ...out,
151
+ addressLabel: "NFT Factory DID",
152
+ addressPath: "factories/{value}",
153
+ monikerLabel: "NFT Factory Name"
154
+ };
155
+ case "create_asset":
156
+ case "createasset":
157
+ case "mint_asset":
158
+ case "mintasset":
159
+ case "update_asset":
160
+ case "updateasset":
161
+ case "consume_asset":
162
+ case "consumeasset":
163
+ case "acquire_asset_v2":
164
+ case "acquireassetv2":
165
+ case "acquire_asset_v3":
166
+ case "acquireassetv3": return {
167
+ ...out,
168
+ addressLabel: "Asset DID",
169
+ addressPath: "assets/{value}",
170
+ monikerLabel: "Asset Name",
171
+ factoryLabel: "NFT Factory"
172
+ };
173
+ case "stake":
174
+ case "revoke_stake":
175
+ case "revokestake":
176
+ case "claim_stake":
177
+ case "claimstake":
178
+ case "slash_stake":
179
+ case "slashstake":
180
+ case "return_stake":
181
+ case "returnstake": return {
182
+ ...out,
183
+ addressLabel: "Stake DID",
184
+ addressPath: "stakes/{value}"
185
+ };
186
+ case "create_rollup":
187
+ case "createrollup":
188
+ case "update_rollup":
189
+ case "updaterollup":
190
+ case "join_rollup":
191
+ case "joinrollup":
192
+ case "leave_rollup":
193
+ case "leaverollup":
194
+ case "pause_rollup":
195
+ case "pauserollup":
196
+ case "resume_rollup":
197
+ case "resumerollup":
198
+ case "close_rollup":
199
+ case "closerollup":
200
+ case "migrate_rollup":
201
+ case "migraterollup":
202
+ case "migrate_rollup_contract":
203
+ case "migraterollupcontract":
204
+ case "migrate_rollup_token":
205
+ case "migraterolluptoken": return {
206
+ ...out,
207
+ rollupLabel: "Bridge Address"
208
+ };
209
+ case "create_rollup_block":
210
+ case "createrollupblock":
211
+ case "claim_block_reward":
212
+ case "claimblockreward": return {
213
+ ...out,
214
+ rollupLabel: "Bridge Address",
215
+ blockHashLabel: "Bridge Block Hash",
216
+ blockHeightLabel: "Bridge Block Height"
217
+ };
218
+ case "declare":
219
+ case "account_migrate":
220
+ case "accountmigrate":
221
+ case "delegate":
222
+ case "revoke_delegate":
223
+ case "revokedelegate": return {
224
+ ...out,
225
+ addressLabel: "Account DID"
226
+ };
227
+ default: return out;
228
+ }
229
+ }
230
+ var AFSOCAP = class AFSOCAP extends AFSBaseProvider {
231
+ static schema() {
232
+ return z.object({
233
+ endpoint: z.string().describe("OCAP endpoint URL"),
234
+ name: z.string().optional().describe("Provider name"),
235
+ description: z.string().optional().describe("Provider description"),
236
+ accessMode: z.enum(["readonly", "readwrite"]).optional().describe("Access mode"),
237
+ pageSize: z.number().optional().describe("Default page size")
238
+ });
239
+ }
240
+ static manifest() {
241
+ return {
242
+ name: "ocap",
243
+ description: "OCAP blockchain — browse transactions, accounts, tokens, factories, and more.\n- Read-only access to blockchain state\n- Path structure: `/transactions/{hash}`, `/accounts/{address}`, `/tokens/{address}`",
244
+ uriTemplate: "ocap://{endpoint+}",
245
+ category: "blockchain",
246
+ schema: z.object({ endpoint: z.string() }),
247
+ tags: [
248
+ "blockchain",
249
+ "ocap",
250
+ "arcblock"
251
+ ],
252
+ capabilityTags: [
253
+ "read-only",
254
+ "search",
255
+ "auth:none",
256
+ "remote"
257
+ ],
258
+ security: {
259
+ riskLevel: "sandboxed",
260
+ resourceAccess: []
261
+ },
262
+ capabilities: { filesystem: {
263
+ read: true,
264
+ write: false
265
+ } }
266
+ };
267
+ }
268
+ static treeSchema() {
269
+ return {
270
+ operations: [
271
+ "list",
272
+ "read",
273
+ "search",
274
+ "stat",
275
+ "explain"
276
+ ],
277
+ tree: {
278
+ "/": { kind: "ocap:chain" },
279
+ "/transactions": { kind: "ocap:transaction" },
280
+ "/transactions/{hash}": { kind: "ocap:transaction" },
281
+ "/transactions/{hash}/receipts": { kind: "ocap:receipt" },
282
+ "/transactions/{hash}/receipts/{idx}": { kind: "ocap:receipt" },
283
+ "/accounts": { kind: "ocap:account" },
284
+ "/accounts/{address}": { kind: "ocap:account" },
285
+ "/accounts/{address}/tokens": { kind: "ocap:token" },
286
+ "/accounts/{address}/assets": { kind: "ocap:asset" },
287
+ "/accounts/{address}/stakes": { kind: "ocap:stake" },
288
+ "/accounts/{address}/stakes/sent": { kind: "ocap:stake" },
289
+ "/accounts/{address}/stakes/received": { kind: "ocap:stake" },
290
+ "/accounts/{address}/delegates": { kind: "ocap:delegate" },
291
+ "/accounts/{address}/transactions": { kind: "ocap:transaction" },
292
+ "/accounts/{address}/migration": { kind: "ocap:account:migration" },
293
+ "/accounts/{address}/token-flows": { kind: "ocap:token-flow" },
294
+ "/tokens": { kind: "ocap:token" },
295
+ "/tokens/{address}": { kind: "ocap:token" },
296
+ "/tokens/{address}/holders": { kind: "ocap:account" },
297
+ "/tokens/{address}/transactions": { kind: "ocap:transaction" },
298
+ "/factories": { kind: "ocap:factory" },
299
+ "/factories/{address}": { kind: "ocap:factory" },
300
+ "/factories/{address}/assets": { kind: "ocap:asset" },
301
+ "/factories/{address}/transactions": { kind: "ocap:transaction" },
302
+ "/assets": { kind: "ocap:asset" },
303
+ "/assets/{address}": { kind: "ocap:asset" },
304
+ "/assets/{address}/transactions": { kind: "ocap:transaction" },
305
+ "/assets/{address}/stakes": { kind: "ocap:stake" },
306
+ "/bridges": { kind: "ocap:bridge" },
307
+ "/bridges/{address}": { kind: "ocap:bridge" },
308
+ "/bridges/{address}/transactions": { kind: "ocap:transaction" },
309
+ "/bridges/{address}/deposits": { kind: "ocap:transaction" },
310
+ "/bridges/{address}/withdrawals": { kind: "ocap:transaction" },
311
+ "/bridges/{address}/governance": { kind: "ocap:transaction" },
312
+ "/bridges/{address}/slashes": { kind: "ocap:transaction" },
313
+ "/bridges/{address}/nodes": { kind: "ocap:rollup-node" },
314
+ "/bridges/{address}/stakes": { kind: "ocap:stake" },
315
+ "/bridges/{address}/blocks": { kind: "ocap:rollup-block" },
316
+ "/bridges/{address}/blocks/{hash}": { kind: "ocap:rollup-block" },
317
+ "/stakes": { kind: "ocap:stake" },
318
+ "/stakes/{address}": { kind: "ocap:stake" },
319
+ "/stakes/{address}/transactions": { kind: "ocap:transaction" },
320
+ "/stakes/{address}/tokens": { kind: "ocap:token" },
321
+ "/stakes/{address}/assets": { kind: "ocap:asset" },
322
+ "/delegates": { kind: "ocap:delegate" },
323
+ "/delegates/{address}": { kind: "ocap:delegate" },
324
+ "/delegates/{address}/transactions": { kind: "ocap:transaction" },
325
+ "/delegates/{address}/permissions": { kind: "ocap:delegate_permission" },
326
+ "/validators": { kind: "ocap:validator" },
327
+ "/validators/{address}": { kind: "ocap:validator" },
328
+ "/config": { kind: "ocap:config" }
329
+ },
330
+ auth: { type: "none" },
331
+ bestFor: [
332
+ "blockchain exploration",
333
+ "transaction analysis",
334
+ "account lookup"
335
+ ],
336
+ notFor: ["write operations", "binary data"]
337
+ };
338
+ }
339
+ static async load({ config } = {}) {
340
+ return new AFSOCAP(AFSOCAP.schema().parse(config));
341
+ }
342
+ _name;
343
+ description;
344
+ accessMode;
345
+ get name() {
346
+ return this._name;
347
+ }
348
+ source;
349
+ defaultPageSize;
350
+ _nameResolved = false;
351
+ priceCache = /* @__PURE__ */ new Map();
352
+ static PRICE_TTL_MS = 6e4;
353
+ static PRICE_API = "https://token-data.arcblock.io/api/token-price-by-symbol?symbols=";
354
+ fetchImpl = (url) => fetch(url);
355
+ chainCache = /* @__PURE__ */ new Map();
356
+ static CHAIN_TTL_MS = 3e4;
357
+ nativeTokenPromise = null;
358
+ constructor(options) {
359
+ super();
360
+ if (options.source) this.source = options.source;
361
+ else if (options.endpoint) this.source = new OcapRemoteSource(options.endpoint);
362
+ else throw new Error("AFSOCAP requires either 'source' or 'endpoint' option");
363
+ this._name = options.name || "ocap";
364
+ this.description = options.description;
365
+ this.accessMode = options.accessMode ?? "readonly";
366
+ this.defaultPageSize = options.pageSize ?? 20;
367
+ }
368
+ /**
369
+ * Probed during onMount(); true when the underlying source exposes a
370
+ * working `subscribe(topic, cb)`. Read by `getOperationsDeclaration()`
371
+ * so capability discovery reflects runtime reality (worker bundles
372
+ * without `@arcblock/ws` advertise live:false even when the rest of
373
+ * the read API works fine).
374
+ */
375
+ _liveCapable = false;
376
+ _unsubTxCreate = null;
377
+ onMount(_root, _mountPath) {
378
+ if (typeof this.source.subscribe !== "function") {
379
+ this._liveCapable = false;
380
+ return;
381
+ }
382
+ try {
383
+ this._unsubTxCreate = this.source.subscribe("tx.create", (payload) => {
384
+ this.handleTxCreatePayload(payload);
385
+ });
386
+ this._liveCapable = true;
387
+ } catch {
388
+ this._liveCapable = false;
389
+ this._unsubTxCreate = null;
390
+ }
391
+ }
392
+ onUnmount() {
393
+ if (this._unsubTxCreate) {
394
+ try {
395
+ this._unsubTxCreate();
396
+ } catch {}
397
+ this._unsubTxCreate = null;
398
+ }
399
+ this._liveCapable = false;
400
+ }
401
+ /**
402
+ * Bridge a phoenix `tx.create` payload onto the AFS event bus as an
403
+ * `afs:append /transactions/<hash>` event. Path traversal is impossible
404
+ * because the hash is bracketed with `joinURL` after validation; we drop
405
+ * the event entirely if the hash isn't a plain string.
406
+ */
407
+ handleTxCreatePayload(payload) {
408
+ const p = payload;
409
+ if (!p || typeof p.hash !== "string" || p.hash.length === 0 || p.hash.includes("/")) return;
410
+ this.emit({
411
+ type: AFS_EVENT_TYPES.APPEND,
412
+ path: joinURL("/transactions", p.hash),
413
+ data: {
414
+ hash: p.hash,
415
+ type: typeof p.type === "string" ? p.type : void 0,
416
+ time: typeof p.time === "string" ? p.time : void 0,
417
+ sender: typeof p.sender === "string" ? p.sender : void 0,
418
+ receiver: typeof p.receiver === "string" ? p.receiver : void 0
419
+ }
420
+ });
421
+ }
422
+ getOperationsDeclaration() {
423
+ return {
424
+ ...super.getOperationsDeclaration(),
425
+ subscribe: {
426
+ supported: true,
427
+ features: {
428
+ live: this._liveCapable,
429
+ eventTypes: "append,write",
430
+ maxRateHz: 100,
431
+ scope: "global"
432
+ }
433
+ }
434
+ };
435
+ }
436
+ async resolveNameOnce() {
437
+ if (this._nameResolved || this._name !== "ocap") return;
438
+ this._nameResolved = true;
439
+ const info = await this.memoize("getChainInfo", AFSOCAP.CHAIN_TTL_MS, () => this.source.getChainInfo());
440
+ if (info.moniker) this._name = info.moniker;
441
+ }
442
+ /**
443
+ * Memoize an async source call by `key` for `ttlMs`. Concurrent callers
444
+ * share the in-flight Promise; rejections evict the entry so the next
445
+ * call retries. Used by chain-level aggregate queries that are read by
446
+ * multiple handlers within the same homepage load.
447
+ */
448
+ memoize(key, ttlMs, fn) {
449
+ const now = Date.now();
450
+ const cached = this.chainCache.get(key);
451
+ if (cached && cached.expiresAt > now) return cached.promise;
452
+ const promise = fn();
453
+ this.chainCache.set(key, {
454
+ promise,
455
+ expiresAt: now + ttlMs
456
+ });
457
+ promise.catch(() => {
458
+ if (this.chainCache.get(key)?.promise === promise) this.chainCache.delete(key);
459
+ });
460
+ return promise;
461
+ }
462
+ /**
463
+ * Resolve the chain's native gas/staking token (ABT on ArcBlock-family
464
+ * chains, with a fallback to the newest token when ABT isn't present —
465
+ * test chains, custom genesis). The result is cached for the lifetime of
466
+ * the provider; failures are not cached so the next caller retries.
467
+ *
468
+ * Returns `null` when no native token is discoverable (empty chain).
469
+ */
470
+ resolveNativeToken() {
471
+ if (this.nativeTokenPromise) return this.nativeTokenPromise;
472
+ const promise = (async () => {
473
+ try {
474
+ const items = (await this.memoize("listTokens:size50", AFSOCAP.CHAIN_TTL_MS, () => this.source.listTokens({
475
+ size: 50,
476
+ offset: 0
477
+ })))?.items ?? [];
478
+ const native = items.find((t) => t.symbol === "ABT") ?? items[0];
479
+ if (!native?.address || !native.symbol) return null;
480
+ return {
481
+ symbol: native.symbol,
482
+ address: native.address
483
+ };
484
+ } catch {
485
+ return null;
486
+ }
487
+ })();
488
+ this.nativeTokenPromise = promise;
489
+ promise.then((result) => {
490
+ if (!result && this.nativeTokenPromise === promise) this.nativeTokenPromise = null;
491
+ });
492
+ return promise;
493
+ }
494
+ /**
495
+ * Symbol-keyed price fetch. Extracted from `readTokenPrice` so callers
496
+ * that already know the symbol (e.g. microbar after resolveNativeToken)
497
+ * can skip the `getTokenState` round-trip just to look up the symbol.
498
+ *
499
+ * Returns the normalized payload on success, a `{...stale, error}` object
500
+ * when fetch fails but a cache entry exists, or `null` when fetch fails
501
+ * with no cached fallback.
502
+ */
503
+ async getPriceBySymbol(symbol) {
504
+ const cacheKey = symbol;
505
+ const now = Date.now();
506
+ const cached = this.priceCache.get(cacheKey);
507
+ if (cached && cached.expiresAt > now) return {
508
+ ...cached.value,
509
+ fromCache: true
510
+ };
511
+ const url = `${AFSOCAP.PRICE_API}${encodeURIComponent(symbol)}`;
512
+ let payload = null;
513
+ let fetchError = null;
514
+ try {
515
+ const res = await this.fetchImpl(url);
516
+ if (!res.ok) fetchError = "price api returned !ok";
517
+ else payload = await res.json();
518
+ } catch (err) {
519
+ fetchError = err instanceof Error ? err.message : String(err);
520
+ }
521
+ if (payload) {
522
+ const value = this.normalizePricePayload(payload, symbol, now);
523
+ this.priceCache.set(cacheKey, {
524
+ value,
525
+ expiresAt: now + AFSOCAP.PRICE_TTL_MS
526
+ });
527
+ return value;
528
+ }
529
+ if (cached) return {
530
+ ...cached.value,
531
+ stale: true,
532
+ error: fetchError ?? "fetch failed"
533
+ };
534
+ return null;
535
+ }
536
+ resolveNavPath(address) {
537
+ if (!address) return void 0;
538
+ const entityType = resolveDidRole(address);
539
+ if (!entityType) return void 0;
540
+ const collection = TYPE_TO_COLLECTION[entityType];
541
+ if (!collection) return void 0;
542
+ return joinURL("/", collection, address);
543
+ }
544
+ /**
545
+ * Phase 0.2 A.6 — Inject the request's account address as `_currentAccount`
546
+ * on every entry's content. Used by /accounts/:addr/{transactions,stakes/*,
547
+ * assets,token-flows} so the AUP `direction-pill` formatter can compute
548
+ * IN/OUT/SELF without each blocklet reaching for ctx.params.
549
+ *
550
+ * Trust boundary: the address comes from the route param (set by the AFS
551
+ * core router from the URL path), NOT from ctx.options (which is
552
+ * caller-controlled). The object spread ordering also guarantees a
553
+ * caller-supplied `content._currentAccount` cannot override us.
554
+ */
555
+ withCurrentAccount(entries, currentAccount) {
556
+ if (!currentAccount) return entries;
557
+ return entries.map((e) => ({
558
+ ...e,
559
+ content: {
560
+ ...e.content ?? {},
561
+ _currentAccount: currentAccount
562
+ }
563
+ }));
564
+ }
565
+ /**
566
+ * Phase 0.6 polish — single source of truth for tx content enrichment.
567
+ *
568
+ * Both `buildTxEntries` (list path) and `readTransaction` (detail path)
569
+ * call this so columns and key-value-list rows that bind to the same
570
+ * keys (e.g. `_gasFeeRaw`) render identically. Detail-only fields like
571
+ * `_itxJson` / `_receiverPath` / `_prevHash` stay in `readTransaction`
572
+ * so the list payload doesn't grow with rendering metadata.
573
+ */
574
+ enrichTxContent(tx, itxJson) {
575
+ const itx = tx.tx || {};
576
+ const itxInputs = itxJson.inputs;
577
+ const itxOutputs = itxJson.outputs;
578
+ const itxOutputOwner = itxOutputs?.[0]?.owner;
579
+ const itxTo = itxJson.to;
580
+ const token = itxJson.token;
581
+ const itxTokens = itxJson.tokens;
582
+ const tokenSymbols = tx.tokenSymbols;
583
+ const txReceipts = tx.receipts ?? [];
584
+ const isNoTokenValueTx = new Set([
585
+ "mint_asset",
586
+ "create_asset",
587
+ "update_asset",
588
+ "consume_asset",
589
+ "acquire_asset_v2",
590
+ "acquire_asset_v3",
591
+ "MintAsset",
592
+ "MintAssetTx",
593
+ "CreateAsset",
594
+ "CreateAssetTx",
595
+ "UpdateAsset",
596
+ "UpdateAssetTx",
597
+ "ConsumeAsset",
598
+ "ConsumeAssetTx",
599
+ "AcquireAssetV2",
600
+ "AcquireAssetV2Tx",
601
+ "AcquireAssetV3",
602
+ "AcquireAssetV3Tx"
603
+ ]).has(tx.type || "");
604
+ const tokenInfoByAddr = /* @__PURE__ */ new Map();
605
+ for (const t of tokenSymbols ?? []) if (typeof t.address === "string" && typeof t.symbol === "string" && typeof t.decimal === "number") tokenInfoByAddr.set(t.address, {
606
+ symbol: t.symbol,
607
+ decimal: t.decimal
608
+ });
609
+ const firstTokenSymbol = (tokenSymbols ?? [])[0];
610
+ const fallbackTokenInfo = {
611
+ symbol: typeof firstTokenSymbol?.symbol === "string" ? firstTokenSymbol.symbol : "ABT",
612
+ decimal: typeof firstTokenSymbol?.decimal === "number" ? firstTokenSymbol.decimal : 18
613
+ };
614
+ const fallbackTokenAddr = (() => {
615
+ const first = tokenInfoByAddr.keys().next().value;
616
+ return typeof first === "string" ? first : "native";
617
+ })();
618
+ const legs = [];
619
+ function pushTokenLeg(target, value) {
620
+ if (typeof value !== "string" || value === "") return;
621
+ const addr = typeof target === "string" && target.length > 0 ? target : fallbackTokenAddr;
622
+ const info = tokenInfoByAddr.get(addr) ?? fallbackTokenInfo;
623
+ legs.push({
624
+ kind: "token",
625
+ target: addr,
626
+ symbol: info.symbol,
627
+ decimal: info.decimal,
628
+ value
629
+ });
630
+ }
631
+ const SUBJECT_RECEIPT_ACTIONS = new Set([
632
+ "mint",
633
+ "burn",
634
+ "stake",
635
+ "claim",
636
+ "fee",
637
+ "transfer",
638
+ "consume"
639
+ ]);
640
+ const FEE_LIKE_RECEIPT_ACTIONS = new Set([
641
+ "gas",
642
+ "service-fee",
643
+ "service_fee"
644
+ ]);
645
+ for (const r of txReceipts) for (const ch of r.changes ?? []) {
646
+ if (typeof ch.action !== "string") continue;
647
+ if (FEE_LIKE_RECEIPT_ACTIONS.has(ch.action)) continue;
648
+ if (!SUBJECT_RECEIPT_ACTIONS.has(ch.action)) continue;
649
+ if (typeof ch.value !== "string" || ch.value === "") continue;
650
+ const target = typeof ch.target === "string" ? ch.target : "";
651
+ if (!target && tokenInfoByAddr.size > 0) continue;
652
+ if (target && tokenInfoByAddr.has(target)) {
653
+ const info = tokenInfoByAddr.get(target);
654
+ legs.push({
655
+ kind: "token",
656
+ target,
657
+ symbol: info.symbol,
658
+ decimal: info.decimal,
659
+ value: ch.value
660
+ });
661
+ } else if (target && tokenInfoByAddr.size > 0) legs.push({
662
+ kind: "asset",
663
+ target
664
+ });
665
+ else legs.push({
666
+ kind: "token",
667
+ target: target || fallbackTokenAddr,
668
+ symbol: fallbackTokenInfo.symbol,
669
+ decimal: fallbackTokenInfo.decimal,
670
+ value: ch.value
671
+ });
672
+ }
673
+ if (legs.length === 0 && !isNoTokenValueTx) {
674
+ if (token && typeof token.value === "string") pushTokenLeg(token.address, token.value);
675
+ for (const t of itxTokens ?? []) pushTokenLeg(t.address, t.value);
676
+ const sideTotals = (side) => {
677
+ const totals = /* @__PURE__ */ new Map();
678
+ for (const entry of side ?? []) for (const t of entry.tokens ?? []) {
679
+ if (typeof t.value !== "string" || t.value === "") continue;
680
+ let v;
681
+ try {
682
+ v = BigInt(t.value);
683
+ } catch (_) {
684
+ continue;
685
+ }
686
+ const addr = typeof t.address === "string" && t.address.length > 0 ? t.address : fallbackTokenAddr;
687
+ totals.set(addr, (totals.get(addr) ?? BigInt(0)) + v);
688
+ }
689
+ return totals;
690
+ };
691
+ const inTotals = sideTotals(itxInputs);
692
+ const outTotals = sideTotals(itxOutputs);
693
+ for (const addr of new Set([...inTotals.keys(), ...outTotals.keys()])) {
694
+ const inV = inTotals.get(addr) ?? BigInt(0);
695
+ const outV = outTotals.get(addr) ?? BigInt(0);
696
+ pushTokenLeg(addr, (inV > outV ? inV : outV).toString());
697
+ }
698
+ const assetDids = /* @__PURE__ */ new Set();
699
+ const collectAssetDids = (assets) => {
700
+ if (!Array.isArray(assets)) return;
701
+ for (const a of assets) {
702
+ const addr = typeof a === "string" ? a : a?.address;
703
+ if (typeof addr === "string" && addr.length > 0) assetDids.add(addr);
704
+ }
705
+ };
706
+ for (const entry of itxInputs ?? []) collectAssetDids(entry.assets);
707
+ for (const entry of itxOutputs ?? []) collectAssetDids(entry.assets);
708
+ collectAssetDids(itxJson.assets);
709
+ for (const did of assetDids) legs.push({
710
+ kind: "asset",
711
+ target: did
712
+ });
713
+ if (legs.length === 0 && typeof itxJson.value === "string" && itxJson.value !== "") pushTokenLeg(void 0, itxJson.value);
714
+ }
715
+ const tokenGroups = /* @__PURE__ */ new Map();
716
+ const tokenOrder = [];
717
+ let assetCount = 0;
718
+ for (const leg of legs) if (leg.kind === "token") {
719
+ let v;
720
+ try {
721
+ v = BigInt(leg.value);
722
+ } catch (_) {
723
+ continue;
724
+ }
725
+ const positive = v > BigInt(0) ? v : BigInt(0);
726
+ const existing = tokenGroups.get(leg.target);
727
+ if (existing) existing.total += positive;
728
+ else {
729
+ tokenGroups.set(leg.target, {
730
+ kind: "token",
731
+ target: leg.target,
732
+ symbol: leg.symbol,
733
+ decimal: leg.decimal,
734
+ total: positive
735
+ });
736
+ tokenOrder.push(leg.target);
737
+ }
738
+ } else assetCount++;
739
+ const groups = [];
740
+ for (const k of tokenOrder) {
741
+ const g = tokenGroups.get(k);
742
+ if (g && g.total > BigInt(0)) groups.push(g);
743
+ }
744
+ if (assetCount > 0) groups.push({
745
+ kind: "asset",
746
+ count: assetCount
747
+ });
748
+ const principal = groups[0];
749
+ let txValue;
750
+ let txDecimal;
751
+ let txSymbol;
752
+ if (principal && principal.kind === "token") {
753
+ txValue = principal.total.toString();
754
+ txDecimal = principal.decimal;
755
+ txSymbol = principal.symbol;
756
+ } else if (principal && principal.kind === "asset") {
757
+ txValue = String(principal.count);
758
+ txDecimal = 0;
759
+ txSymbol = "Asset";
760
+ }
761
+ const _valueLegCount = groups.length;
762
+ const _valueLegCountSuffix = _valueLegCount > 1 ? ` · +${_valueLegCount - 1} more` : "";
763
+ const gasFeeRaw = String(itx.gasFee ?? "0");
764
+ const gasPaidRaw = String(itx.gasPaid ?? "0");
765
+ const serviceFeeRaw = String(itx.serviceFee ?? "0");
766
+ const gasExempted = gasFeeRaw !== "0" && (gasPaidRaw === "0" || gasPaidRaw === "");
767
+ const methodGroup = getTxGroup(tx.type || "");
768
+ const methodColor = getTxColor(tx.type || "");
769
+ return {
770
+ _typeName: formatTxTypeName(tx.type || ""),
771
+ _statusIntent: txStatusIntent(tx.code),
772
+ _statusLabel: tx.code === "OK" ? "Success" : tx.code || "",
773
+ _resolvedReceiver: tx.receiver || itxTo || itxOutputOwner || null,
774
+ _itxTokenSymbol: tokenSymbols?.[0]?.symbol,
775
+ _itxTokenDecimal: tokenSymbols?.[0]?.decimal,
776
+ _itxTokenAddress: tokenSymbols?.[0]?.address ?? "",
777
+ _itxAmount: token?.value,
778
+ _itxBlockHeight: itxJson.height ?? itxJson.blockHeight,
779
+ value: txValue,
780
+ decimal: txDecimal,
781
+ symbol: txSymbol,
782
+ _valueLegCount: txValue !== void 0 ? _valueLegCount : void 0,
783
+ _valueLegCountSuffix,
784
+ _gasFeeRaw: gasFeeRaw,
785
+ _gasExempted: gasExempted,
786
+ _gasStyle: gasExempted ? {
787
+ textDecoration: "line-through",
788
+ opacity: "0.55"
789
+ } : {},
790
+ _gasExemptedNote: gasExempted ? "Saved by staking ABT" : "",
791
+ _serviceFeeRaw: serviceFeeRaw,
792
+ _methodGroup: methodGroup,
793
+ _methodColor: methodColor
794
+ };
795
+ }
796
+ buildTxEntries(result) {
797
+ return result.items.map((tx) => {
798
+ const itxJson = tx.tx?.itxJson || {};
799
+ const parties = resolveTxParties(tx);
800
+ if (parties.sender) tx.sender = parties.sender;
801
+ if (parties.receiver) tx.receiver = parties.receiver;
802
+ return {
803
+ ...this.buildEntry(joinURL("/transactions", tx.hash), {
804
+ content: {
805
+ ...tx,
806
+ ...this.enrichTxContent(tx, itxJson)
807
+ },
808
+ meta: { kind: tx.type ? `ocap:transaction:${tx.type}` : "ocap:transaction" }
809
+ }),
810
+ summary: `${tx.type}: ${tx.sender} → ${tx.receiver}`
811
+ };
812
+ });
813
+ }
814
+ buildBlockEntries(rollupAddr, result) {
815
+ return result.items.map((b) => ({
816
+ ...this.buildEntry(joinURL("/bridges", rollupAddr, "blocks", b.hash), {
817
+ content: {
818
+ ...b,
819
+ tokenDecimal: b.tokenInfo?.decimal
820
+ },
821
+ meta: { kind: "ocap:rollup-block" }
822
+ }),
823
+ summary: `Block #${b.height} • ${b.txs?.length ?? 0} txs`
824
+ }));
825
+ }
826
+ async readRoot(_ctx) {
827
+ await this.resolveNameOnce();
828
+ const minPage = {
829
+ size: 1,
830
+ offset: 0
831
+ };
832
+ const ttl = AFSOCAP.CHAIN_TTL_MS;
833
+ const [info, forgeStats, tokens, assets, factories, bridges, stakes] = await Promise.all([
834
+ this.memoize("getChainInfo", ttl, () => this.source.getChainInfo()),
835
+ this.memoize("getForgeStats", ttl, () => this.source.getForgeStats()).catch(() => null),
836
+ this.memoize("listTokens:size1", ttl, () => this.source.listTokens(minPage)).catch(() => ({ total: 0 })),
837
+ this.memoize("listAssets:size1", ttl, () => this.source.listAssets(minPage)).catch(() => ({ total: 0 })),
838
+ this.memoize("listFactories:size1", ttl, () => this.source.listFactories(minPage)).catch(() => ({ total: 0 })),
839
+ this.memoize("listRollups:size1", ttl, () => this.source.listRollups(minPage)).catch(() => ({ total: 0 })),
840
+ this.memoize("listStakes:size1", ttl, () => this.source.listStakes(minPage)).catch(() => ({ total: 0 }))
841
+ ]);
842
+ const accountCount = this.computeAccountCount(forgeStats);
843
+ const numTxs = this.parseSafeCount(info.totalTxs);
844
+ return this.buildEntry("/", {
845
+ content: {
846
+ ...info,
847
+ accountCount,
848
+ tokenCount: tokens.total,
849
+ assetCount: assets.total,
850
+ factoryCount: factories.total,
851
+ bridgeCount: bridges.total,
852
+ stakeCount: stakes.total,
853
+ numTxs
854
+ },
855
+ meta: {
856
+ kind: "ocap:chain",
857
+ childrenCount: TOP_LEVEL_DIRS.length
858
+ }
859
+ });
860
+ }
861
+ /**
862
+ * Phase 0.5 B.1 — chainInfo.totalTxs is a string from GraphQL ("5000").
863
+ * Same regex + BigInt clamp story as `computeAccountCount`: reject anything
864
+ * that isn't a base-10 unsigned integer to keep "1e308" and negatives out
865
+ * of the renderer, then clamp to MAX_SAFE_INTEGER so JSON serialization
866
+ * stays lossless.
867
+ */
868
+ parseSafeCount(raw) {
869
+ if (typeof raw !== "string" || raw.length === 0) return 0;
870
+ if (!/^\d+$/.test(raw)) return 0;
871
+ const big = BigInt(raw);
872
+ if (big > BigInt(Number.MAX_SAFE_INTEGER)) return Number.MAX_SAFE_INTEGER;
873
+ return Number(big);
874
+ }
875
+ /**
876
+ * Phase 0.2 A.10 — Convert the last `numDeclareTxs` entry (a string from
877
+ * GraphQL) to a JS number, with explicit BigInt-then-bounds guard so a
878
+ * malicious / corrupt payload like "1e308" or "9".repeat(40) cannot wrap
879
+ * around to Infinity or lose precision via Number() coercion.
880
+ *
881
+ * Returns 0 when the source is unavailable; callers can detect "no data"
882
+ * vs "0 declare txs" by checking forgeStats null upstream.
883
+ */
884
+ computeAccountCount(forgeStats) {
885
+ const arr = forgeStats?.forgeStats?.numDeclareTxs;
886
+ if (!Array.isArray(arr) || arr.length === 0) return 0;
887
+ const last = arr[arr.length - 1];
888
+ if (typeof last !== "string" || last.length === 0) return 0;
889
+ if (!/^\d+$/.test(last)) return 0;
890
+ const big = BigInt(last);
891
+ if (big > BigInt(Number.MAX_SAFE_INTEGER)) return Number.MAX_SAFE_INTEGER;
892
+ return Number(big);
893
+ }
894
+ /**
895
+ * Phase 1.1 — `/_microbar` aggregator powering the chain-explorer header
896
+ * micro-bar. The renderer can't fan out to multiple sources cheaply, so
897
+ * the provider produces a single object containing chain.height +
898
+ * sync flag + native-token price + 24h change.
899
+ *
900
+ * Price fetch is best-effort (Promise.allSettled): a slow upstream MUST
901
+ * NOT block chain navigation. When unavailable, tokens[i].price is null
902
+ * and tokens[i].stale is true so the renderer can show a dim "—".
903
+ */
904
+ async readMicrobar(ctx) {
905
+ const native = await this.resolveNativeToken();
906
+ const [info, priceValue] = await Promise.all([this.memoize("getChainInfo", AFSOCAP.CHAIN_TTL_MS, () => this.source.getChainInfo()), native ? this.getPriceBySymbol(native.symbol).catch(() => null) : Promise.resolve(null)]);
907
+ const tokenEntries = [];
908
+ if (native) if (priceValue) tokenEntries.push({
909
+ symbol: priceValue.symbol ?? native.symbol,
910
+ address: native.address,
911
+ price: priceValue.price ?? null,
912
+ change24h: priceValue.change24h ?? null,
913
+ currency: priceValue.currency ?? "USD",
914
+ stale: Boolean(priceValue.stale),
915
+ fetchedAt: priceValue.fetchedAt ?? null
916
+ });
917
+ else tokenEntries.push({
918
+ symbol: native.symbol,
919
+ address: native.address,
920
+ price: null,
921
+ change24h: null,
922
+ currency: "USD",
923
+ stale: true,
924
+ fetchedAt: null
925
+ });
926
+ const flat = tokenEntries[0];
927
+ return this.buildEntry(ctx.path, {
928
+ content: {
929
+ chainHeight: this.parseSafeCount(info.blockHeight),
930
+ synced: Boolean(info.synced),
931
+ moniker: info.moniker || "",
932
+ nativeSymbol: flat?.symbol ?? null,
933
+ nativePrice: flat?.price ?? null,
934
+ nativeChange24h: flat?.change24h ?? null,
935
+ nativeCurrency: flat?.currency ?? "USD",
936
+ nativeStale: flat ? Boolean(flat.stale) : false,
937
+ tokens: tokenEntries
938
+ },
939
+ meta: {
940
+ kind: "ocap:microbar",
941
+ visibility: "hidden"
942
+ }
943
+ });
944
+ }
945
+ async readConfig(_ctx) {
946
+ const tx = (await this.source.getConfig()).transaction ?? {};
947
+ const txSize = tx.maxTxSize ?? {};
948
+ const txFee = tx.txFee ?? {};
949
+ const txGas = tx.txGas ?? {};
950
+ const txStake = tx.txStake ?? {};
951
+ const supported = tx.supportedTxs ?? [];
952
+ const strip = (s) => s.replace(/^fg:t:/, "");
953
+ const group = (keys) => keys.filter((k) => supported.includes(k)).map(strip).join(", ") || "—";
954
+ const content = {
955
+ maxAssetSize: tx.maxAssetSize,
956
+ maxListSize: tx.maxListSize,
957
+ maxMultisig: tx.maxMultisig,
958
+ defaultMaxTxSize: txSize.default,
959
+ gasPrice: txGas.price,
960
+ gasCreateState: txGas.createState,
961
+ gasUpdateState: txGas.updateState,
962
+ gasDataStorage: txGas.dataStorage,
963
+ gasMinStake: txGas.minStake,
964
+ gasMaxStake: txGas.maxStake,
965
+ gasStakeLockPeriod: txGas.stakeLockPeriod,
966
+ feeDefault: txFee.default ?? 0,
967
+ feeCreateAsset: txFee["fg:t:create_asset"] ?? 0,
968
+ feeMintAsset: txFee["fg:t:mint_asset"] ?? 0,
969
+ feeCreateFactory: txFee["fg:t:create_factory"] ?? 0,
970
+ feeCreateToken: txFee["fg:t:create_token"] ?? 0,
971
+ feeCreateRollup: txFee["fg:t:create_rollup"] ?? 0,
972
+ feeUpdateTokenFactory: txFee["fg:t:update_token_factory"] ?? 0,
973
+ stakeCreateToken: txStake.createToken,
974
+ stakeCreateCreditToken: txStake.createCreditToken,
975
+ stakeCreateTokenLockPeriod: txStake.createTokenLockPeriod,
976
+ txGroupAccount: group([
977
+ "fg:t:account_migrate",
978
+ "fg:t:delegate",
979
+ "fg:t:revoke_delegate"
980
+ ]),
981
+ txGroupTrade: group([
982
+ "fg:t:transfer",
983
+ "fg:t:transfer_v2",
984
+ "fg:t:transfer_v3",
985
+ "fg:t:exchange_v2"
986
+ ]),
987
+ txGroupAsset: group([
988
+ "fg:t:create_asset",
989
+ "fg:t:update_asset",
990
+ "fg:t:consume_asset",
991
+ "fg:t:acquire_asset_v2",
992
+ "fg:t:acquire_asset_v3",
993
+ "fg:t:mint_asset",
994
+ "fg:t:create_factory"
995
+ ]),
996
+ txGroupToken: group([
997
+ "fg:t:create_token",
998
+ "fg:t:deposit_token_v2",
999
+ "fg:t:withdraw_token_v2",
1000
+ "fg:t:create_token_factory",
1001
+ "fg:t:update_token_factory",
1002
+ "fg:t:mint_token",
1003
+ "fg:t:burn_token"
1004
+ ]),
1005
+ txGroupGovernance: group([
1006
+ "fg:t:stake",
1007
+ "fg:t:revoke_stake",
1008
+ "fg:t:claim_stake",
1009
+ "fg:t:slash_stake",
1010
+ "fg:t:return_stake"
1011
+ ]),
1012
+ txGroupRollup: group([
1013
+ "fg:t:create_rollup",
1014
+ "fg:t:update_rollup",
1015
+ "fg:t:pause_rollup",
1016
+ "fg:t:resume_rollup",
1017
+ "fg:t:close_rollup",
1018
+ "fg:t:join_rollup",
1019
+ "fg:t:leave_rollup",
1020
+ "fg:t:create_rollup_block",
1021
+ "fg:t:migrate_rollup",
1022
+ "fg:t:claim_block_reward"
1023
+ ])
1024
+ };
1025
+ return this.buildEntry("/config", {
1026
+ content,
1027
+ meta: { kind: "ocap:config" }
1028
+ });
1029
+ }
1030
+ async listValidators(_ctx) {
1031
+ return { data: (await this.source.getValidatorsInfo()).map((v) => ({
1032
+ ...this.buildEntry(joinURL("/validators", v.address), {
1033
+ content: v,
1034
+ meta: { kind: "ocap:validator" }
1035
+ }),
1036
+ summary: `${v.name || v.address} • voting power: ${v.votingPower}`
1037
+ })) };
1038
+ }
1039
+ async readValidator(ctx) {
1040
+ const v = (await this.source.getValidatorsInfo()).find((x) => x.address === ctx.params.address);
1041
+ if (!v) throw new AFSNotFoundError(ctx.path);
1042
+ return {
1043
+ ...this.buildEntry(ctx.path, {
1044
+ content: v,
1045
+ meta: { kind: "ocap:validator" }
1046
+ }),
1047
+ summary: `${v.name || v.address} • voting power: ${v.votingPower}`
1048
+ };
1049
+ }
1050
+ async listTransactions(ctx) {
1051
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1052
+ const filters = extractTxFilters(listOpts(ctx));
1053
+ const result = await this.source.listTransactions(paging, filters);
1054
+ return buildListResult(this.buildTxEntries(result), result.total);
1055
+ }
1056
+ async readTransaction(ctx) {
1057
+ const tx = await this.source.getTx(ctx.params.hash);
1058
+ if (!tx) throw new AFSNotFoundError(ctx.path);
1059
+ const itx = tx.tx || {};
1060
+ const itxJson = itx.itxJson || {};
1061
+ if (!tx.type && typeof itxJson.type_url === "string") tx.type = itxJson.type_url.split(":").pop() || "";
1062
+ const parties = resolveTxParties(tx);
1063
+ if (parties.sender) tx.sender = parties.sender;
1064
+ if (parties.receiver) tx.receiver = parties.receiver;
1065
+ const { prevHash, nextHash } = await this.findTxNeighbours(tx.time, tx.hash);
1066
+ const shared = this.enrichTxContent(tx, itxJson);
1067
+ const resolvedReceiver = shared._resolvedReceiver || "";
1068
+ return {
1069
+ ...this.buildEntry(ctx.path, {
1070
+ content: {
1071
+ ...tx,
1072
+ ...shared,
1073
+ _prevHash: prevHash,
1074
+ _nextHash: nextHash,
1075
+ _receiverPath: this.resolveNavPath(resolvedReceiver),
1076
+ _itxJson: itxJson,
1077
+ _itxTo: itxJson.to,
1078
+ _itxAddress: itxJson.address,
1079
+ _itxMoniker: itxJson.moniker,
1080
+ _itxFactory: itxJson.factory,
1081
+ _itxTokenFactory: itxJson.tokenFactory || itxJson.token_factory,
1082
+ _itxRollup: itxJson.rollup,
1083
+ _itxBlockHash: itxJson.hash ?? itxJson.blockHash,
1084
+ _itxInputOwner: itxJson.inputs?.[0]?.owner,
1085
+ _itxOutputOwner: itxJson.outputs?.[0]?.owner,
1086
+ _typeContext: deriveTypeContext(tx.type),
1087
+ _hasReceipts: Array.isArray(tx.receipts) && tx.receipts.some((r) => (r.changes?.length ?? 0) > 0),
1088
+ _nonce: itx.nonce,
1089
+ _gasFeeFormatted: (() => {
1090
+ const fee = String(itx.gasFee ?? "0");
1091
+ if (!fee || fee === "0") return "0 ABT";
1092
+ return `${formatBignumValue(fee, 18).replace(/^\+/, "")} ABT`;
1093
+ })(),
1094
+ _serviceFeeFormatted: (() => {
1095
+ const fee = String(itx.serviceFee ?? "0");
1096
+ if (!fee || fee === "0") return "0 ABT";
1097
+ return `${formatBignumValue(fee, 18).replace(/^\+/, "")} ABT`;
1098
+ })(),
1099
+ _serviceFeeDisplay: (() => {
1100
+ const fee = String(itx.serviceFee ?? "0");
1101
+ if (!fee || fee === "0") return "Free";
1102
+ return `${formatBignumValue(fee, 18).replace(/^\+/, "")} ABT`;
1103
+ })()
1104
+ },
1105
+ meta: { kind: tx.type ? `ocap:transaction:${tx.type}` : "ocap:transaction" }
1106
+ }),
1107
+ summary: `${tx.type || "tx"}: ${tx.sender} → ${tx.receiver}`
1108
+ };
1109
+ }
1110
+ async readTxReceiptsDir(ctx) {
1111
+ return this.buildEntry(ctx.path, { meta: {
1112
+ kind: "ocap:receipt",
1113
+ childrenCount: -1
1114
+ } });
1115
+ }
1116
+ async listTxReceipts(ctx) {
1117
+ return { data: await this._buildReceiptEntries(ctx.params.hash, ctx.path) };
1118
+ }
1119
+ async readTxReceiptEntry(ctx) {
1120
+ const entry = (await this._buildReceiptEntries(ctx.params.hash, joinURL(ctx.path, "..")))[Number(ctx.params.idx)];
1121
+ if (!entry) throw new AFSNotFoundError(ctx.path);
1122
+ return entry;
1123
+ }
1124
+ /**
1125
+ * Phase 1.6 — find adjacent tx hashes by querying a tight time window
1126
+ * around the current tx and selecting the immediate neighbours after
1127
+ * sorting by time desc. Returns nulls for missing neighbours (newest /
1128
+ * oldest tx). Failure of listTransactions must NOT propagate — the
1129
+ * detail page still needs to render even when the indexer is down.
1130
+ */
1131
+ async findTxNeighbours(txTime, currentHash) {
1132
+ if (!txTime || !currentHash) return {
1133
+ prevHash: null,
1134
+ nextHash: null
1135
+ };
1136
+ const t = Date.parse(txTime);
1137
+ if (!Number.isFinite(t)) return {
1138
+ prevHash: null,
1139
+ nextHash: null
1140
+ };
1141
+ const WINDOW_MS = 6e4;
1142
+ const startDateTime = new Date(t - WINDOW_MS).toISOString();
1143
+ const endDateTime = new Date(t + WINDOW_MS).toISOString();
1144
+ try {
1145
+ const sorted = ((await this.source.listTransactions({
1146
+ size: 50,
1147
+ offset: 0
1148
+ }, { timeFilter: {
1149
+ startDateTime,
1150
+ endDateTime
1151
+ } }))?.items ?? []).slice().sort((a, b) => {
1152
+ const dt = Date.parse(b.time) - Date.parse(a.time);
1153
+ if (dt !== 0) return dt;
1154
+ return a.hash < b.hash ? -1 : 1;
1155
+ });
1156
+ const idx = sorted.findIndex((it) => it.hash === currentHash);
1157
+ if (idx < 0) return {
1158
+ prevHash: null,
1159
+ nextHash: null
1160
+ };
1161
+ const next = sorted.slice(0, idx).find((it) => it.hash !== currentHash);
1162
+ return {
1163
+ prevHash: sorted.slice(idx + 1).find((it) => it.hash !== currentHash)?.hash ?? null,
1164
+ nextHash: next?.hash ?? null
1165
+ };
1166
+ } catch {
1167
+ return {
1168
+ prevHash: null,
1169
+ nextHash: null
1170
+ };
1171
+ }
1172
+ }
1173
+ async _buildReceiptEntries(hash, basePath) {
1174
+ const tx = await this.source.getTx(hash);
1175
+ if (!tx) return [];
1176
+ const receipts = tx.receipts ?? [];
1177
+ const tokenSymbols = tx.tokenSymbols ?? [];
1178
+ const symbolMap = new Map(tokenSymbols.map((t) => [t.address, t]));
1179
+ const unknownTargets = /* @__PURE__ */ new Set();
1180
+ for (const r of receipts) for (const c of r.changes ?? []) if (c.target && !symbolMap.has(c.target)) unknownTargets.add(c.target);
1181
+ const assetMap = /* @__PURE__ */ new Map();
1182
+ if (unknownTargets.size > 0) await Promise.all(Array.from(unknownTargets).map(async (addr) => {
1183
+ try {
1184
+ const a = await this.source.getAssetState(addr);
1185
+ if (a) assetMap.set(addr, a);
1186
+ } catch {}
1187
+ }));
1188
+ const entries = [];
1189
+ let idx = 0;
1190
+ for (const receipt of receipts) for (const change of receipt.changes ?? []) {
1191
+ const target = change.target ?? "";
1192
+ const rawValue = change.value ?? "0";
1193
+ const tokenInfo = symbolMap.get(target);
1194
+ const assetInfo = assetMap.get(target);
1195
+ let targetKind;
1196
+ let symbol;
1197
+ let valueFormatted;
1198
+ if (tokenInfo) {
1199
+ targetKind = "token";
1200
+ symbol = tokenInfo.symbol;
1201
+ valueFormatted = formatBignumValue(rawValue, tokenInfo.decimal);
1202
+ } else if (assetInfo) {
1203
+ targetKind = "asset";
1204
+ symbol = assetInfo.moniker || "Asset";
1205
+ valueFormatted = rawValue;
1206
+ } else {
1207
+ targetKind = "unknown";
1208
+ symbol = "—";
1209
+ valueFormatted = rawValue;
1210
+ }
1211
+ entries.push(this.buildEntry(joinURL(basePath, String(idx)), {
1212
+ content: {
1213
+ account: receipt.address ?? "",
1214
+ action: change.action ?? "",
1215
+ target,
1216
+ targetKind,
1217
+ symbol,
1218
+ value: rawValue,
1219
+ valueFormatted,
1220
+ _tokenLink: targetKind === "token" ? target : ""
1221
+ },
1222
+ meta: { kind: "ocap:receipt" }
1223
+ }));
1224
+ idx++;
1225
+ }
1226
+ return entries;
1227
+ }
1228
+ async listAccounts(ctx) {
1229
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1230
+ const result = await this.source.listTopAccounts(paging);
1231
+ return buildListResult(result.items.map((a) => ({
1232
+ ...this.buildEntry(joinURL("/accounts", a.address), {
1233
+ content: {
1234
+ ...a,
1235
+ _monikerDisplay: displayMoniker(a.moniker, a.address)
1236
+ },
1237
+ meta: {
1238
+ kind: "ocap:account",
1239
+ childrenCount: 7
1240
+ }
1241
+ }),
1242
+ summary: `${a.moniker || a.address} • ${a.balance}`
1243
+ })), result.total);
1244
+ }
1245
+ async readAccount(ctx) {
1246
+ const state = await this.source.getAccountState(ctx.params.address);
1247
+ if (!state) throw new AFSNotFoundError(ctx.path);
1248
+ const content = {
1249
+ ...state,
1250
+ migratedToFirst: state.migratedTo?.[0],
1251
+ migratedFromFirst: state.migratedFrom?.[0],
1252
+ _roleLabel: resolveDidRoleLabel(ctx.params.address) ?? "Account",
1253
+ _hasMigration: Array.isArray(state.migratedFrom) && state.migratedFrom.length > 0 || Array.isArray(state.migratedTo) && state.migratedTo.length > 0
1254
+ };
1255
+ return {
1256
+ ...this.buildEntry(ctx.path, {
1257
+ content,
1258
+ meta: {
1259
+ kind: "ocap:account",
1260
+ childrenCount: 7
1261
+ }
1262
+ }),
1263
+ summary: `${state.moniker || state.address} • ${state.balance}`
1264
+ };
1265
+ }
1266
+ async listAccountChildren(ctx) {
1267
+ if (ctx.params.address.startsWith(".")) throw new AFSNotFoundError(ctx.path);
1268
+ return { data: [
1269
+ "tokens",
1270
+ "assets",
1271
+ "stakes",
1272
+ "delegates",
1273
+ "transactions",
1274
+ "migration",
1275
+ "token-flows"
1276
+ ].map((sub) => buildDirEntry(ctx.path, sub)) };
1277
+ }
1278
+ async readAccountTokensDir(ctx) {
1279
+ return this.buildEntry(ctx.path, { meta: {
1280
+ kind: "ocap:token",
1281
+ childrenCount: -1
1282
+ } });
1283
+ }
1284
+ async readAccountAssetsDir(ctx) {
1285
+ return this.buildEntry(ctx.path, { meta: {
1286
+ kind: "ocap:asset",
1287
+ childrenCount: -1
1288
+ } });
1289
+ }
1290
+ async readAccountTxDir(ctx) {
1291
+ return this.buildEntry(ctx.path, { meta: {
1292
+ kind: "ocap:transaction",
1293
+ childrenCount: -1
1294
+ } });
1295
+ }
1296
+ async listAccountTokens(ctx) {
1297
+ return { data: (await this.source.getAccountTokens(ctx.params.addr)).map((t) => ({
1298
+ ...this.buildEntry(joinURL("/tokens", t.address), {
1299
+ content: t,
1300
+ meta: { kind: "ocap:token" }
1301
+ }),
1302
+ summary: `${t.symbol} • ${t.balance}`
1303
+ })) };
1304
+ }
1305
+ async metaAccountTokens(ctx) {
1306
+ return this.buildEntry(ctx.path, { meta: {
1307
+ kind: "ocap:token",
1308
+ childrenCount: -1
1309
+ } });
1310
+ }
1311
+ async readAccountToken(ctx) {
1312
+ const t = (await this.source.getAccountTokens(ctx.params.addr)).find((x) => x.address === ctx.params.token);
1313
+ if (!t) throw new AFSNotFoundError(ctx.path);
1314
+ return {
1315
+ ...this.buildEntry(ctx.path, {
1316
+ content: t,
1317
+ meta: { kind: "ocap:token" }
1318
+ }),
1319
+ summary: `${t.symbol} • ${t.balance}`
1320
+ };
1321
+ }
1322
+ async listAccountAssets(ctx) {
1323
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1324
+ const userFilters = extractAssetFilters(listOpts(ctx));
1325
+ const result = await this.source.listAssets(paging, {
1326
+ ...userFilters,
1327
+ ownerAddress: ctx.params.addr
1328
+ });
1329
+ const entries = result.items.map((a) => ({
1330
+ ...this.buildEntry(joinURL("/assets", a.address), {
1331
+ content: a,
1332
+ meta: {
1333
+ kind: "ocap:asset",
1334
+ childrenCount: 2
1335
+ }
1336
+ }),
1337
+ summary: `${a.moniker || a.address} • owner: ${a.owner}`
1338
+ }));
1339
+ return buildListResult(this.withCurrentAccount(entries, ctx.params.addr), result.total);
1340
+ }
1341
+ async listAccountTransactions(ctx) {
1342
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1343
+ const userFilters = extractTxFilters(listOpts(ctx));
1344
+ const result = await this.source.listTransactions(paging, {
1345
+ ...userFilters,
1346
+ accountFilter: { accounts: [ctx.params.addr] }
1347
+ });
1348
+ return buildListResult(this.withCurrentAccount(this.buildTxEntries(result), ctx.params.addr), result.total);
1349
+ }
1350
+ async readAccountStakesDir(ctx) {
1351
+ return this.buildEntry(ctx.path, { meta: {
1352
+ kind: "ocap:stake",
1353
+ childrenCount: -1
1354
+ } });
1355
+ }
1356
+ async listAccountStakes(ctx) {
1357
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1358
+ const userFilters = extractStakeFilters(listOpts(ctx));
1359
+ const result = await this.source.listStakes(paging, {
1360
+ ...userFilters,
1361
+ addressFilter: {
1362
+ sender: ctx.params.addr,
1363
+ receiver: ctx.params.addr,
1364
+ direction: "UNION"
1365
+ }
1366
+ });
1367
+ const entries = result.items.map((s) => ({
1368
+ ...this.buildEntry(joinURL("/stakes", s.address), {
1369
+ content: s,
1370
+ meta: { kind: "ocap:stake" }
1371
+ }),
1372
+ summary: `${s.sender} → ${s.receiver}`
1373
+ }));
1374
+ return buildListResult(this.withCurrentAccount(entries, ctx.params.addr), result.total);
1375
+ }
1376
+ async readAccountSentStakesDir(ctx) {
1377
+ return this.buildEntry(ctx.path, { meta: {
1378
+ kind: "ocap:stake",
1379
+ childrenCount: -1
1380
+ } });
1381
+ }
1382
+ async listAccountSentStakes(ctx) {
1383
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1384
+ const userFilters = extractStakeFilters(listOpts(ctx));
1385
+ const result = await this.source.listStakes(paging, {
1386
+ ...userFilters,
1387
+ addressFilter: { sender: ctx.params.addr }
1388
+ });
1389
+ const entries = result.items.map((s) => ({
1390
+ ...this.buildEntry(joinURL("/stakes", s.address), {
1391
+ content: s,
1392
+ meta: { kind: "ocap:stake" }
1393
+ }),
1394
+ summary: `${s.sender} → ${s.receiver}`
1395
+ }));
1396
+ return buildListResult(this.withCurrentAccount(entries, ctx.params.addr), result.total);
1397
+ }
1398
+ async readAccountReceivedStakesDir(ctx) {
1399
+ return this.buildEntry(ctx.path, { meta: {
1400
+ kind: "ocap:stake",
1401
+ childrenCount: -1
1402
+ } });
1403
+ }
1404
+ async listAccountReceivedStakes(ctx) {
1405
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1406
+ const userFilters = extractStakeFilters(listOpts(ctx));
1407
+ const result = await this.source.listStakes(paging, {
1408
+ ...userFilters,
1409
+ addressFilter: { receiver: ctx.params.addr }
1410
+ });
1411
+ const items = result.items.map((s) => ({
1412
+ ...this.buildEntry(joinURL("/stakes", s.address), {
1413
+ content: s,
1414
+ meta: { kind: "ocap:stake" }
1415
+ }),
1416
+ summary: `${s.sender} → ${s.receiver}`
1417
+ }));
1418
+ return buildListResult(this.withCurrentAccount(items, ctx.params.addr), result.total);
1419
+ }
1420
+ async readAccountMigrationDir(ctx) {
1421
+ return this.buildEntry(ctx.path, { meta: {
1422
+ kind: "ocap:account:migration",
1423
+ childrenCount: -1
1424
+ } });
1425
+ }
1426
+ async listAccountMigration(ctx) {
1427
+ const state = await this.source.getAccountState(ctx.params.addr);
1428
+ if (!state) throw new AFSNotFoundError(ctx.path);
1429
+ const from = Array.isArray(state.migratedFrom) ? state.migratedFrom : [];
1430
+ const to = Array.isArray(state.migratedTo) ? state.migratedTo : [];
1431
+ const ordered = [
1432
+ ...from.map((a) => ({
1433
+ address: a,
1434
+ role: "from"
1435
+ })),
1436
+ {
1437
+ address: state.address,
1438
+ role: "current"
1439
+ },
1440
+ ...to.map((a) => ({
1441
+ address: a,
1442
+ role: "to"
1443
+ }))
1444
+ ];
1445
+ const lastIdx = ordered.length - 1;
1446
+ const entries = ordered.map((step, idx) => ({
1447
+ ...this.buildEntry(joinURL("/accounts", step.address), {
1448
+ content: {
1449
+ address: step.address,
1450
+ _migrationRole: step.role,
1451
+ _isCurrent: step.role === "current",
1452
+ _isLatest: idx === lastIdx,
1453
+ _index: idx,
1454
+ _currentAccount: ctx.params.addr
1455
+ },
1456
+ meta: { kind: "ocap:account:migration:step" }
1457
+ }),
1458
+ summary: `${step.role}: ${step.address}`
1459
+ }));
1460
+ return buildListResult(entries, entries.length);
1461
+ }
1462
+ async readAccountTokenFlowsDir(ctx) {
1463
+ return this.buildEntry(ctx.path, { meta: {
1464
+ kind: "ocap:token-flow",
1465
+ childrenCount: -1
1466
+ } });
1467
+ }
1468
+ async listAccountTokenFlows(ctx) {
1469
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1470
+ const opts = ctx.options ?? {};
1471
+ const tokenAddress = typeof opts.tokenAddress === "string" ? opts.tokenAddress : void 0;
1472
+ const depth = typeof opts.depth === "number" ? opts.depth : void 0;
1473
+ const dirRaw = typeof opts.direction === "string" ? opts.direction.toUpperCase() : void 0;
1474
+ const direction = dirRaw === "IN" || dirRaw === "OUT" || dirRaw === "ALL" ? dirRaw : void 0;
1475
+ const result = await this.source.listTokenFlows(ctx.params.addr, paging, {
1476
+ tokenAddress,
1477
+ depth,
1478
+ direction
1479
+ });
1480
+ const items = result.items.map((flow) => {
1481
+ const isIn = flow.to === ctx.params.addr;
1482
+ const counterparty = isIn ? flow.from : flow.to;
1483
+ return {
1484
+ ...this.buildEntry(joinURL(ctx.path, flow.hash), {
1485
+ content: {
1486
+ ...flow,
1487
+ _direction: isIn ? "in" : "out",
1488
+ _counterparty: counterparty
1489
+ },
1490
+ meta: { kind: "ocap:token-flow:entry" }
1491
+ }),
1492
+ summary: `${isIn ? "in" : "out"}: ${counterparty} • ${flow.value}`
1493
+ };
1494
+ });
1495
+ return buildListResult(this.withCurrentAccount(items, ctx.params.addr), result.total);
1496
+ }
1497
+ async readAccountDelegatesDir(ctx) {
1498
+ return this.buildEntry(ctx.path, { meta: {
1499
+ kind: "ocap:delegate",
1500
+ childrenCount: -1
1501
+ } });
1502
+ }
1503
+ async listAccountDelegates(ctx) {
1504
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1505
+ const userFilters = extractDelegationFilters(listOpts(ctx));
1506
+ const result = await this.source.listDelegations(paging, {
1507
+ ...userFilters,
1508
+ from: ctx.params.addr
1509
+ });
1510
+ return buildListResult(result.items.map((d) => ({
1511
+ ...this.buildEntry(joinURL("/delegates", d.address), {
1512
+ content: d,
1513
+ meta: { kind: "ocap:delegate" }
1514
+ }),
1515
+ summary: `${d.from} → ${d.to}`
1516
+ })), result.total);
1517
+ }
1518
+ async listTokens(ctx) {
1519
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1520
+ const filters = extractTokenFilters(listOpts(ctx));
1521
+ const result = await this.source.listTokens(paging, filters);
1522
+ return buildListResult(result.items.map((t) => ({
1523
+ ...this.buildEntry(joinURL("/tokens", t.address), {
1524
+ content: t,
1525
+ meta: {
1526
+ kind: "ocap:token",
1527
+ childrenCount: 2
1528
+ }
1529
+ }),
1530
+ summary: `${t.name} (${t.symbol}) • supply: ${t.totalSupply}`
1531
+ })), result.total);
1532
+ }
1533
+ async readToken(ctx) {
1534
+ const state = await this.source.getTokenState(ctx.params.address);
1535
+ if (!state) throw new AFSNotFoundError(ctx.path);
1536
+ const content = enrichTokenContent(state);
1537
+ return {
1538
+ ...this.buildEntry(ctx.path, {
1539
+ content,
1540
+ meta: {
1541
+ kind: "ocap:token",
1542
+ childrenCount: 3
1543
+ }
1544
+ }),
1545
+ summary: `${state.name} (${state.symbol}) • supply: ${state.totalSupply}`
1546
+ };
1547
+ }
1548
+ async listTokenChildren(ctx) {
1549
+ if (ctx.params.address.startsWith(".")) throw new AFSNotFoundError(ctx.path);
1550
+ return { data: [
1551
+ "holders",
1552
+ "transactions",
1553
+ "price"
1554
+ ].map((sub) => buildDirEntry(ctx.path, sub)) };
1555
+ }
1556
+ /**
1557
+ * Phase 0.2 A.7 — Pull `{price, change24h}` out of the token-data
1558
+ * upstream shape into top-level fields. The upstream API returns a
1559
+ * paginated `{ symbol, data: [{ price: {USD, CNY}, percent_change_24h:
1560
+ * {USD, CNY}, ... }] }`. AUP nodes binding `${content.price}` should
1561
+ * just see a number — they shouldn't have to navigate the upstream
1562
+ * pagination wrapper.
1563
+ *
1564
+ * Also surfaces `currency: "USD"` so dark-launch testing or future
1565
+ * locale work can switch to other currencies. Falls back to the raw
1566
+ * shape (with the wrapper) when the upstream returns something
1567
+ * unexpected — partial structure is better than throwing on a price
1568
+ * lookup that the UI uses opportunistically.
1569
+ */
1570
+ normalizePricePayload(payload, symbol, now) {
1571
+ const fetchedAt = new Date(now).toISOString();
1572
+ const first = (payload.data ?? [])[0];
1573
+ if (!first) return {
1574
+ ...payload,
1575
+ symbol,
1576
+ fetchedAt
1577
+ };
1578
+ const priceMap = first.price ?? {};
1579
+ const changeMap = first.percent_change_24h ?? {};
1580
+ return {
1581
+ ...payload,
1582
+ symbol,
1583
+ price: priceMap.USD ?? null,
1584
+ change24h: changeMap.USD ?? null,
1585
+ currency: "USD",
1586
+ fetchedAt
1587
+ };
1588
+ }
1589
+ /**
1590
+ * Phase 0.2 A.7 — Token price endpoint with 60s in-memory cache + stale
1591
+ * fallback on upstream failure. Backed by token-data.arcblock.io which
1592
+ * accepts only symbols (not addresses), so we resolve the token's symbol
1593
+ * via getTokenState first.
1594
+ *
1595
+ * Cache lifecycle:
1596
+ * - Fresh hit (within TTL) → return cached value immediately
1597
+ * - Miss → fetch upstream → cache → return
1598
+ * - Fetch failure WITH cached value → return cached + { stale: true, error }
1599
+ * - Fetch failure WITHOUT cached value → throw AFSError
1600
+ */
1601
+ async readTokenPrice(ctx) {
1602
+ const addr = ctx.params.addr;
1603
+ if (!addr || addr.startsWith(".") || addr.includes("/")) throw new AFSNotFoundError(ctx.path);
1604
+ const state = await this.source.getTokenState(addr);
1605
+ if (!state) throw new AFSNotFoundError(ctx.path);
1606
+ if (!state.symbol) throw new AFSNotFoundError(joinURL(ctx.path, "no-symbol"));
1607
+ const value = await this.getPriceBySymbol(state.symbol);
1608
+ if (!value) throw new AFSNotFoundError(ctx.path);
1609
+ return this.buildEntry(ctx.path, {
1610
+ content: value,
1611
+ meta: { kind: "ocap:token:price" }
1612
+ });
1613
+ }
1614
+ async readTokenHoldersDir(ctx) {
1615
+ return this.buildEntry(ctx.path, { meta: {
1616
+ kind: "ocap:account",
1617
+ childrenCount: -1
1618
+ } });
1619
+ }
1620
+ async listTokenHolders(ctx) {
1621
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1622
+ const tokenAddr = ctx.params.addr;
1623
+ const result = await this.source.listTopAccounts(paging, { tokenAddress: tokenAddr });
1624
+ return buildListResult(result.items.map((a) => {
1625
+ const tokenEntry = a.tokens?.find((t) => t.address === tokenAddr);
1626
+ const tokenBalance = tokenEntry?.balance ?? "0";
1627
+ const tokenDecimal = tokenEntry?.decimal;
1628
+ const tokenSymbol = tokenEntry?.symbol;
1629
+ return {
1630
+ ...this.buildEntry(joinURL("/accounts", a.address), {
1631
+ content: {
1632
+ ...a,
1633
+ _monikerDisplay: displayMoniker(a.moniker, a.address),
1634
+ _tokenBalance: tokenBalance,
1635
+ _tokenDecimal: tokenDecimal,
1636
+ _tokenSymbol: tokenSymbol
1637
+ },
1638
+ meta: { kind: "ocap:account" }
1639
+ }),
1640
+ summary: `${a.moniker || a.address} • ${tokenBalance}`
1641
+ };
1642
+ }), result.total);
1643
+ }
1644
+ async readTokenTxDir(ctx) {
1645
+ return this.buildEntry(ctx.path, { meta: {
1646
+ kind: "ocap:transaction",
1647
+ childrenCount: -1
1648
+ } });
1649
+ }
1650
+ async listTokenTransactions(ctx) {
1651
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1652
+ const userFilters = extractTxFilters(listOpts(ctx));
1653
+ const result = await this.source.listTransactions(paging, {
1654
+ ...userFilters,
1655
+ tokenFilter: { tokens: [ctx.params.addr] }
1656
+ });
1657
+ return buildListResult(this.buildTxEntries(result), result.total);
1658
+ }
1659
+ async listAssets(ctx) {
1660
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1661
+ const filters = extractAssetFilters(listOpts(ctx));
1662
+ const result = await this.source.listAssets(paging, filters);
1663
+ return buildListResult(result.items.map((a) => ({
1664
+ ...this.buildEntry(joinURL("/assets", a.address), {
1665
+ content: a,
1666
+ meta: {
1667
+ kind: "ocap:asset",
1668
+ childrenCount: 2
1669
+ }
1670
+ }),
1671
+ summary: `${a.moniker || a.address} • owner: ${a.owner}`
1672
+ })), result.total);
1673
+ }
1674
+ async readAsset(ctx) {
1675
+ const state = await this.source.getAssetState(ctx.params.address);
1676
+ if (!state) throw new AFSNotFoundError(ctx.path);
1677
+ const ttlRaw = state.ttl;
1678
+ return {
1679
+ ...this.buildEntry(ctx.path, {
1680
+ content: {
1681
+ ...state,
1682
+ _issuerPath: this.resolveNavPath(state.issuer),
1683
+ _ttlDisplay: formatIntegerOrSentinel(ttlRaw, "Never"),
1684
+ _nftDisplay: deriveNftDisplay(state)
1685
+ },
1686
+ meta: {
1687
+ kind: "ocap:asset",
1688
+ childrenCount: 2
1689
+ }
1690
+ }),
1691
+ summary: `${state.moniker || state.address} • owner: ${state.owner}`
1692
+ };
1693
+ }
1694
+ async listAssetChildren(ctx) {
1695
+ if (ctx.params.address.startsWith(".")) throw new AFSNotFoundError(ctx.path);
1696
+ return { data: ["transactions", "stakes"].map((sub) => buildDirEntry(ctx.path, sub)) };
1697
+ }
1698
+ async readAssetTxDir(ctx) {
1699
+ return this.buildEntry(ctx.path, { meta: {
1700
+ kind: "ocap:transaction",
1701
+ childrenCount: -1
1702
+ } });
1703
+ }
1704
+ async listAssetTransactions(ctx) {
1705
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1706
+ const userFilters = extractTxFilters(listOpts(ctx));
1707
+ const result = await this.source.listTransactions(paging, {
1708
+ ...userFilters,
1709
+ assetFilter: { assets: [ctx.params.addr] }
1710
+ });
1711
+ return buildListResult(this.buildTxEntries(result), result.total);
1712
+ }
1713
+ async readAssetStakesDir(ctx) {
1714
+ return this.buildEntry(ctx.path, { meta: {
1715
+ kind: "ocap:stake",
1716
+ childrenCount: -1
1717
+ } });
1718
+ }
1719
+ async listAssetStakes(ctx) {
1720
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1721
+ const userFilters = extractStakeFilters(listOpts(ctx));
1722
+ const result = await this.source.listStakes(paging, {
1723
+ ...userFilters,
1724
+ addressFilter: {
1725
+ sender: ctx.params.addr,
1726
+ receiver: ctx.params.addr,
1727
+ direction: "UNION"
1728
+ }
1729
+ });
1730
+ return buildListResult(result.items.map((s) => ({
1731
+ ...this.buildEntry(joinURL("/stakes", s.address), {
1732
+ content: s,
1733
+ meta: { kind: "ocap:stake" }
1734
+ }),
1735
+ summary: `${s.sender} → ${s.receiver}`
1736
+ })), result.total);
1737
+ }
1738
+ async listFactories(ctx) {
1739
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1740
+ const filters = extractFactoryFilters(listOpts(ctx));
1741
+ const result = await this.source.listFactories(paging, filters);
1742
+ return buildListResult(result.items.map((f) => ({
1743
+ ...this.buildEntry(joinURL("/factories", f.address), {
1744
+ content: {
1745
+ ...f,
1746
+ _limitDisplay: formatIntegerOrSentinel(f.limit, "Unlimited")
1747
+ },
1748
+ meta: {
1749
+ kind: "ocap:factory",
1750
+ childrenCount: 2
1751
+ }
1752
+ }),
1753
+ summary: `${f.name} • minted: ${f.numMinted}/${f.limit}`
1754
+ })), result.total);
1755
+ }
1756
+ async readFactory(ctx) {
1757
+ const state = await this.source.getFactoryState(ctx.params.address);
1758
+ if (!state) throw new AFSNotFoundError(ctx.path);
1759
+ return {
1760
+ ...this.buildEntry(ctx.path, {
1761
+ content: {
1762
+ ...state,
1763
+ _limitDisplay: formatIntegerOrSentinel(state.limit, "Unlimited")
1764
+ },
1765
+ meta: {
1766
+ kind: "ocap:factory",
1767
+ childrenCount: 2
1768
+ }
1769
+ }),
1770
+ summary: `${state.name} • minted: ${state.numMinted}/${state.limit}`
1771
+ };
1772
+ }
1773
+ async listFactoryChildren(ctx) {
1774
+ if (ctx.params.address.startsWith(".")) throw new AFSNotFoundError(ctx.path);
1775
+ return { data: ["assets", "transactions"].map((sub) => buildDirEntry(ctx.path, sub)) };
1776
+ }
1777
+ async readFactoryAssetsDir(ctx) {
1778
+ return this.buildEntry(ctx.path, { meta: {
1779
+ kind: "ocap:asset",
1780
+ childrenCount: -1
1781
+ } });
1782
+ }
1783
+ async listFactoryAssets(ctx) {
1784
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1785
+ const userFilters = extractAssetFilters(listOpts(ctx));
1786
+ const result = await this.source.listAssets(paging, {
1787
+ ...userFilters,
1788
+ factoryAddress: ctx.params.addr
1789
+ });
1790
+ return buildListResult(result.items.map((a) => ({
1791
+ ...this.buildEntry(joinURL("/assets", a.address), {
1792
+ content: a,
1793
+ meta: {
1794
+ kind: "ocap:asset",
1795
+ childrenCount: 2
1796
+ }
1797
+ }),
1798
+ summary: `${a.moniker || a.address} • owner: ${a.owner}`
1799
+ })), result.total);
1800
+ }
1801
+ async readFactoryTxDir(ctx) {
1802
+ return this.buildEntry(ctx.path, { meta: {
1803
+ kind: "ocap:transaction",
1804
+ childrenCount: -1
1805
+ } });
1806
+ }
1807
+ async listFactoryTransactions(ctx) {
1808
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1809
+ const userFilters = extractTxFilters(listOpts(ctx));
1810
+ const result = await this.source.listTransactions(paging, {
1811
+ ...userFilters,
1812
+ factoryFilter: { factories: [ctx.params.addr] }
1813
+ });
1814
+ return buildListResult(this.buildTxEntries(result), result.total);
1815
+ }
1816
+ async listStakes(ctx) {
1817
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1818
+ const filters = extractStakeFilters(listOpts(ctx));
1819
+ const result = await this.source.listStakes(paging, filters);
1820
+ return buildListResult(result.items.map((s) => ({
1821
+ ...this.buildEntry(joinURL("/stakes", s.address), {
1822
+ content: s,
1823
+ meta: {
1824
+ kind: "ocap:stake",
1825
+ childrenCount: 3
1826
+ }
1827
+ }),
1828
+ summary: `${s.sender} → ${s.receiver}`
1829
+ })), result.total);
1830
+ }
1831
+ async readStake(ctx) {
1832
+ const state = await this.source.getStakeState(ctx.params.address);
1833
+ if (!state) throw new AFSNotFoundError(ctx.path);
1834
+ const content = {
1835
+ ...state,
1836
+ firstSlasher: state.slashers?.[0] ?? ""
1837
+ };
1838
+ return {
1839
+ ...this.buildEntry(ctx.path, {
1840
+ content,
1841
+ meta: {
1842
+ kind: "ocap:stake",
1843
+ childrenCount: 3
1844
+ }
1845
+ }),
1846
+ summary: `${state.sender} → ${state.receiver}`
1847
+ };
1848
+ }
1849
+ async listStakeChildren(ctx) {
1850
+ if (ctx.params.address.startsWith(".")) throw new AFSNotFoundError(ctx.path);
1851
+ return { data: [
1852
+ buildDirEntry(ctx.path, "transactions"),
1853
+ buildDirEntry(ctx.path, "tokens"),
1854
+ buildDirEntry(ctx.path, "assets")
1855
+ ] };
1856
+ }
1857
+ async readStakeTxDir(ctx) {
1858
+ return this.buildEntry(ctx.path, { meta: {
1859
+ kind: "ocap:transaction",
1860
+ childrenCount: -1
1861
+ } });
1862
+ }
1863
+ async listStakeTransactions(ctx) {
1864
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1865
+ const userFilters = extractTxFilters(listOpts(ctx));
1866
+ const result = await this.source.listTransactions(paging, {
1867
+ ...userFilters,
1868
+ stakeFilter: { stakes: [ctx.params.addr] }
1869
+ });
1870
+ return buildListResult(this.buildTxEntries(result), result.total);
1871
+ }
1872
+ async readStakeTokensDir(ctx) {
1873
+ return this.buildEntry(ctx.path, { meta: {
1874
+ kind: "ocap:token",
1875
+ childrenCount: -1
1876
+ } });
1877
+ }
1878
+ async listStakeTokens(ctx) {
1879
+ const state = await this.source.getStakeState(ctx.params.addr);
1880
+ if (!state) throw new AFSNotFoundError(ctx.path);
1881
+ const tokenMap = /* @__PURE__ */ new Map();
1882
+ for (const t of state.tokens ?? []) tokenMap.set(t.address, {
1883
+ address: t.address,
1884
+ symbol: t.symbol ?? "",
1885
+ decimal: t.decimal ?? 18,
1886
+ unit: t.unit ?? "",
1887
+ staked: t.value ?? "0",
1888
+ revoked: "0"
1889
+ });
1890
+ for (const t of state.revokedTokens ?? []) {
1891
+ const existing = tokenMap.get(t.address);
1892
+ if (existing) existing.revoked = t.value ?? "0";
1893
+ else tokenMap.set(t.address, {
1894
+ address: t.address,
1895
+ symbol: t.symbol ?? "",
1896
+ decimal: t.decimal ?? 18,
1897
+ unit: t.unit ?? "",
1898
+ staked: "0",
1899
+ revoked: t.value ?? "0"
1900
+ });
1901
+ }
1902
+ return { data: [...tokenMap.values()].map((t) => this.buildEntry(joinURL("/tokens", t.address), {
1903
+ content: t,
1904
+ meta: { kind: "ocap:token" }
1905
+ })) };
1906
+ }
1907
+ async readStakeAssetsDir(ctx) {
1908
+ return this.buildEntry(ctx.path, { meta: {
1909
+ kind: "ocap:asset",
1910
+ childrenCount: -1
1911
+ } });
1912
+ }
1913
+ async listStakeAssets(ctx) {
1914
+ const state = await this.source.getStakeState(ctx.params.addr);
1915
+ if (!state) throw new AFSNotFoundError(ctx.path);
1916
+ const stakedSet = new Set(state.assets ?? []);
1917
+ const revokedSet = new Set(state.revokedAssets ?? []);
1918
+ return { data: [...new Set([...stakedSet, ...revokedSet])].map((addr) => this.buildEntry(joinURL("/assets", addr), {
1919
+ content: {
1920
+ address: addr,
1921
+ staked: stakedSet.has(addr) ? "Yes" : "",
1922
+ revoked: revokedSet.has(addr) ? "Yes" : ""
1923
+ },
1924
+ meta: { kind: "ocap:asset" }
1925
+ })) };
1926
+ }
1927
+ async listBridges(ctx) {
1928
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1929
+ const filters = extractBridgeFilters(listOpts(ctx));
1930
+ const result = await this.source.listRollups(paging, filters);
1931
+ return buildListResult(result.items.map((r) => ({
1932
+ ...this.buildEntry(joinURL("/bridges", r.address), {
1933
+ content: {
1934
+ ...r,
1935
+ _statusLabel: formatBridgeStatusLabel(r.closed, r.paused),
1936
+ _statusColor: formatBridgeStatusColor(r.closed, r.paused)
1937
+ },
1938
+ meta: {
1939
+ kind: "ocap:bridge",
1940
+ childrenCount: 8
1941
+ }
1942
+ }),
1943
+ summary: `${r.tokenAddress} bridge`
1944
+ })), result.total);
1945
+ }
1946
+ async readBridge(ctx) {
1947
+ const state = await this.source.getRollupState(ctx.params.address);
1948
+ if (!state) throw new AFSNotFoundError(ctx.path);
1949
+ return {
1950
+ ...this.buildEntry(ctx.path, {
1951
+ content: {
1952
+ ...state,
1953
+ tokenDecimal: state.tokenInfo?.decimal,
1954
+ _statusLabel: formatBridgeStatusLabel(state.closed, state.paused),
1955
+ _statusColor: formatBridgeStatusColor(state.closed, state.paused)
1956
+ },
1957
+ meta: {
1958
+ kind: "ocap:bridge",
1959
+ childrenCount: 8
1960
+ }
1961
+ }),
1962
+ summary: `${state.tokenAddress} bridge`
1963
+ };
1964
+ }
1965
+ async listBridgeChildren(ctx) {
1966
+ if (ctx.params.address.startsWith(".")) throw new AFSNotFoundError(ctx.path);
1967
+ return { data: [
1968
+ buildDirEntry(ctx.path, "deposits"),
1969
+ buildDirEntry(ctx.path, "withdrawals"),
1970
+ buildDirEntry(ctx.path, "blocks"),
1971
+ buildDirEntry(ctx.path, "nodes"),
1972
+ buildDirEntry(ctx.path, "stakes"),
1973
+ buildDirEntry(ctx.path, "governance"),
1974
+ buildDirEntry(ctx.path, "slashes"),
1975
+ buildDirEntry(ctx.path, "transactions")
1976
+ ] };
1977
+ }
1978
+ async readBridgeTxDir(ctx) {
1979
+ return this.buildEntry(ctx.path, { meta: {
1980
+ kind: "ocap:transaction",
1981
+ childrenCount: -1
1982
+ } });
1983
+ }
1984
+ async listBridgeTransactions(ctx) {
1985
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
1986
+ const userFilters = extractTxFilters(listOpts(ctx));
1987
+ const result = await this.source.listTransactions(paging, {
1988
+ ...userFilters,
1989
+ rollupFilter: { rollups: [ctx.params.addr] }
1990
+ });
1991
+ return buildListResult(this.buildTxEntries(result), result.total);
1992
+ }
1993
+ async readBridgeStakeDir(ctx) {
1994
+ return this.buildEntry(ctx.path, { meta: {
1995
+ kind: "ocap:stake",
1996
+ childrenCount: -1
1997
+ } });
1998
+ }
1999
+ async listBridgeStakes(ctx) {
2000
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
2001
+ const userFilters = extractStakeFilters(listOpts(ctx));
2002
+ const result = await this.source.listStakes(paging, {
2003
+ ...userFilters,
2004
+ addressFilter: {
2005
+ ...userFilters?.addressFilter,
2006
+ receiver: ctx.params.addr
2007
+ }
2008
+ });
2009
+ return buildListResult(result.items.map((s) => ({
2010
+ ...this.buildEntry(joinURL("/stakes", s.address), {
2011
+ content: s,
2012
+ meta: {
2013
+ kind: "ocap:stake",
2014
+ childrenCount: 3
2015
+ }
2016
+ }),
2017
+ summary: `${s.sender} → ${s.receiver}`
2018
+ })), result.total);
2019
+ }
2020
+ async readBridgeDepositsDir(ctx) {
2021
+ return this.buildEntry(ctx.path, { meta: {
2022
+ kind: "ocap:transaction",
2023
+ childrenCount: -1
2024
+ } });
2025
+ }
2026
+ async listBridgeDeposits(ctx) {
2027
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
2028
+ const userFilters = extractTxFilters(listOpts(ctx));
2029
+ const result = await this.source.listTransactions(paging, {
2030
+ ...userFilters,
2031
+ rollupFilter: { rollups: [ctx.params.addr] },
2032
+ typeFilter: { types: ["deposit_token_v2", "deposit_token"] }
2033
+ });
2034
+ return buildListResult(this.buildTxEntries(result), result.total);
2035
+ }
2036
+ async readBridgeWithdrawalsDir(ctx) {
2037
+ return this.buildEntry(ctx.path, { meta: {
2038
+ kind: "ocap:transaction",
2039
+ childrenCount: -1
2040
+ } });
2041
+ }
2042
+ async listBridgeWithdrawals(ctx) {
2043
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
2044
+ const userFilters = extractTxFilters(listOpts(ctx));
2045
+ const result = await this.source.listTransactions(paging, {
2046
+ ...userFilters,
2047
+ rollupFilter: { rollups: [ctx.params.addr] },
2048
+ typeFilter: { types: ["withdraw_token_v2", "withdraw_token"] }
2049
+ });
2050
+ return buildListResult(this.buildTxEntries(result), result.total);
2051
+ }
2052
+ async readBridgeGovernanceDir(ctx) {
2053
+ return this.buildEntry(ctx.path, { meta: {
2054
+ kind: "ocap:transaction",
2055
+ childrenCount: -1
2056
+ } });
2057
+ }
2058
+ async listBridgeGovernance(ctx) {
2059
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
2060
+ const userFilters = extractTxFilters(listOpts(ctx));
2061
+ const result = await this.source.listTransactions(paging, {
2062
+ ...userFilters,
2063
+ rollupFilter: { rollups: [ctx.params.addr] },
2064
+ typeFilter: { types: [
2065
+ "create_rollup",
2066
+ "update_rollup",
2067
+ "pause_rollup",
2068
+ "resume_rollup",
2069
+ "close_rollup",
2070
+ "migrate_rollup",
2071
+ "join_rollup",
2072
+ "leave_rollup"
2073
+ ] }
2074
+ });
2075
+ return buildListResult(this.buildTxEntries(result), result.total);
2076
+ }
2077
+ async readBridgeSlashesDir(ctx) {
2078
+ return this.buildEntry(ctx.path, { meta: {
2079
+ kind: "ocap:transaction",
2080
+ childrenCount: -1
2081
+ } });
2082
+ }
2083
+ async listBridgeSlashes(ctx) {
2084
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
2085
+ const userFilters = extractTxFilters(listOpts(ctx));
2086
+ const validators = (await this.source.getRollupState(ctx.params.addr).catch(() => null))?.validators ?? [];
2087
+ if (validators.length === 0) return buildListResult([], 0);
2088
+ const stakeAddrToValidator = /* @__PURE__ */ new Map();
2089
+ for (const v of validators) {
2090
+ if (!v.address) continue;
2091
+ const stakeAddr = toStakeAddress(v.address, ctx.params.addr);
2092
+ stakeAddrToValidator.set(stakeAddr, v.address);
2093
+ }
2094
+ const stakeAddrs = Array.from(stakeAddrToValidator.keys());
2095
+ const result = await this.source.listTransactions(paging, {
2096
+ ...userFilters,
2097
+ accountFilter: { accounts: stakeAddrs },
2098
+ typeFilter: { types: ["claim_block_reward"] }
2099
+ });
2100
+ const slashEntries = [];
2101
+ for (const tx of result.items) {
2102
+ const receipts = tx.receipts ?? [];
2103
+ let slashStakeAddr;
2104
+ let slashAmount;
2105
+ for (const r of receipts) {
2106
+ const hit = (r.changes ?? []).find((c) => c.action === "slash");
2107
+ if (hit) {
2108
+ slashStakeAddr = r.address;
2109
+ slashAmount = hit.value;
2110
+ break;
2111
+ }
2112
+ }
2113
+ if (!slashStakeAddr) continue;
2114
+ const slashedNode = slashAmount ? slashAmount && stakeAddrToValidator.get(slashStakeAddr || "") || "" : "";
2115
+ const itxJson = tx.tx?.itxJson || {};
2116
+ const parties = resolveTxParties(tx);
2117
+ if (parties.sender) tx.sender = parties.sender;
2118
+ if (parties.receiver) tx.receiver = parties.receiver;
2119
+ slashEntries.push({
2120
+ ...this.buildEntry(joinURL("/transactions", tx.hash), {
2121
+ content: {
2122
+ ...tx,
2123
+ ...this.enrichTxContent(tx, itxJson),
2124
+ _slashStakeAddress: slashStakeAddr,
2125
+ _slashedNode: slashedNode,
2126
+ _slashAmount: slashAmount,
2127
+ _slashBlockHeight: itxJson.blockHeight,
2128
+ _slashBlockHash: itxJson.blockHash
2129
+ },
2130
+ meta: { kind: tx.type ? `ocap:transaction:${tx.type}` : "ocap:transaction" }
2131
+ }),
2132
+ summary: `slash ${slashedNode}`
2133
+ });
2134
+ }
2135
+ return buildListResult(slashEntries, result.total);
2136
+ }
2137
+ async readBridgeNodesDir(ctx) {
2138
+ return this.buildEntry(ctx.path, { meta: {
2139
+ kind: "ocap:rollup-node",
2140
+ childrenCount: -1
2141
+ } });
2142
+ }
2143
+ async listBridgeNodes(ctx) {
2144
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
2145
+ const [result, rollupState] = await Promise.all([this.source.listRollupValidators(ctx.params.addr, paging), this.source.getRollupState(ctx.params.addr).catch(() => null)]);
2146
+ const activeSet = new Set((rollupState?.validators || []).map((v) => v.address).filter((a) => typeof a === "string"));
2147
+ return buildListResult(result.items.map((v) => {
2148
+ const isActive = activeSet.has(v.address);
2149
+ return {
2150
+ ...this.buildEntry(joinURL("/accounts", v.address), {
2151
+ content: {
2152
+ ...v,
2153
+ _isActive: isActive,
2154
+ _status: isActive ? "Active" : "Inactive",
2155
+ _statusColor: isActive ? {
2156
+ main: "#0E7C5A",
2157
+ light: "#E8F8F1"
2158
+ } : {
2159
+ main: "#92400E",
2160
+ light: "#FEF3C7"
2161
+ }
2162
+ },
2163
+ meta: { kind: "ocap:rollup-node" }
2164
+ }),
2165
+ summary: `${v.moniker || v.address} (${v.proposedBlockCount ?? 0} blocks)`
2166
+ };
2167
+ }), result.total);
2168
+ }
2169
+ async readBridgeBlocksDir(ctx) {
2170
+ return this.buildEntry(ctx.path, { meta: {
2171
+ kind: "ocap:rollup-block",
2172
+ childrenCount: -1
2173
+ } });
2174
+ }
2175
+ async metaBridgeBlocks(ctx) {
2176
+ return this.buildEntry(ctx.path, { meta: {
2177
+ kind: "ocap:rollup-block",
2178
+ childrenCount: -1
2179
+ } });
2180
+ }
2181
+ async listBridgeBlocks(ctx) {
2182
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
2183
+ const result = await this.source.listRollupBlocks(ctx.params.addr, paging);
2184
+ return buildListResult(this.buildBlockEntries(ctx.params.addr, result), result.total);
2185
+ }
2186
+ async readBridgeBlock(ctx) {
2187
+ if (ctx.params.hash.startsWith(".")) throw new AFSNotFoundError(ctx.path);
2188
+ const [block, rollup] = await Promise.all([this.source.getRollupBlock(ctx.params.hash, ctx.params.addr), this.source.getRollupState(ctx.params.addr).catch(() => null)]);
2189
+ if (!block) throw new AFSNotFoundError(ctx.path);
2190
+ return {
2191
+ ...this.buildEntry(ctx.path, {
2192
+ content: {
2193
+ ...block,
2194
+ tokenDecimal: rollup?.tokenInfo?.decimal
2195
+ },
2196
+ meta: { kind: "ocap:rollup-block" }
2197
+ }),
2198
+ summary: `Block #${block.height} • ${block.txs?.length ?? 0} txs`
2199
+ };
2200
+ }
2201
+ async listDelegates(ctx) {
2202
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
2203
+ const filters = extractDelegationFilters(listOpts(ctx));
2204
+ const result = await this.source.listDelegations(paging, filters);
2205
+ return buildListResult(result.items.map((d) => ({
2206
+ ...this.buildEntry(joinURL("/delegates", d.address), {
2207
+ content: d,
2208
+ meta: {
2209
+ kind: "ocap:delegate",
2210
+ childrenCount: 1
2211
+ }
2212
+ }),
2213
+ summary: `${d.from} → ${d.to}`
2214
+ })), result.total);
2215
+ }
2216
+ async readDelegate(ctx) {
2217
+ const state = await this.source.getDelegateState(ctx.params.address);
2218
+ if (!state) throw new AFSNotFoundError(ctx.path);
2219
+ const content = {
2220
+ ...state,
2221
+ dataJson: JSON.stringify(state.data ?? {}, null, 2),
2222
+ opsCount: (state.ops || []).length
2223
+ };
2224
+ return {
2225
+ ...this.buildEntry(ctx.path, {
2226
+ content,
2227
+ meta: {
2228
+ kind: "ocap:delegate",
2229
+ childrenCount: 2
2230
+ }
2231
+ }),
2232
+ summary: `${state.from} → ${state.to}`
2233
+ };
2234
+ }
2235
+ async listDelegateChildren(ctx) {
2236
+ if (ctx.params.address.startsWith(".")) throw new AFSNotFoundError(ctx.path);
2237
+ return { data: [buildDirEntry(ctx.path, "transactions"), buildDirEntry(ctx.path, "permissions")] };
2238
+ }
2239
+ async readDelegateTxDir(ctx) {
2240
+ return this.buildEntry(ctx.path, { meta: {
2241
+ kind: "ocap:transaction",
2242
+ childrenCount: -1
2243
+ } });
2244
+ }
2245
+ async listDelegateTransactions(ctx) {
2246
+ const paging = toPaging(listOpts(ctx), this.defaultPageSize);
2247
+ const userFilters = extractTxFilters(listOpts(ctx));
2248
+ const result = await this.source.listTransactions(paging, {
2249
+ ...userFilters,
2250
+ delegationFilter: { delegations: [ctx.params.addr] }
2251
+ });
2252
+ return buildListResult(this.buildTxEntries(result), result.total);
2253
+ }
2254
+ async readDelegatePermissionsDir(ctx) {
2255
+ return this.buildEntry(ctx.path, { meta: {
2256
+ kind: "ocap:delegate_permission",
2257
+ childrenCount: -1
2258
+ } });
2259
+ }
2260
+ async listDelegatePermissions(ctx) {
2261
+ const state = await this.source.getDelegateState(ctx.params.addr);
2262
+ if (!state) throw new AFSNotFoundError(ctx.path);
2263
+ return { data: (state.ops || []).map((op) => ({
2264
+ ...this.buildEntry(joinURL(ctx.path, encodeURIComponent(op.key)), {
2265
+ content: {
2266
+ typeUrl: op.key,
2267
+ rule: op.value?.rule || "",
2268
+ numTxs: op.value?.numTxs ?? 0,
2269
+ numTxsDelta: op.value?.numTxsDelta ?? 0,
2270
+ balance: op.value?.balance || "0",
2271
+ balanceDelta: op.value?.balanceDelta || "0"
2272
+ },
2273
+ meta: { kind: "ocap:delegate_permission" }
2274
+ }),
2275
+ summary: op.key
2276
+ })) };
2277
+ }
2278
+ async searchHandler(ctx, query, options) {
2279
+ const paging = toPaging({
2280
+ limit: options?.limit,
2281
+ offset: options?.offset
2282
+ }, this.defaultPageSize);
2283
+ const result = await this.source.search(query, paging);
2284
+ let items = result.items;
2285
+ const scope = ctx.params.path?.split("/")[0];
2286
+ if (scope) {
2287
+ const allowedTypes = COLLECTION_TO_TYPES[scope];
2288
+ if (allowedTypes) items = items.filter((r) => allowedTypes.includes(r.type));
2289
+ }
2290
+ return {
2291
+ data: items.map((r) => ({
2292
+ ...this.buildEntry(joinURL("/", TYPE_TO_COLLECTION[r.type] ?? `${r.type}s`, r.id), {
2293
+ content: r,
2294
+ meta: {
2295
+ kind: `ocap:${r.type}`,
2296
+ ...r.tags?.length ? { tags: r.tags } : {},
2297
+ ...r.timestamp ? { timestamp: r.timestamp } : {}
2298
+ }
2299
+ }),
2300
+ summary: r.description || r.subtitle
2301
+ })),
2302
+ message: `Found ${result.total} results${result.hasNext ? " (more available)" : ""}`
2303
+ };
2304
+ }
2305
+ async explainHandler(ctx) {
2306
+ const normalizedPath = joinURL("/", ctx.params.path ?? "");
2307
+ const format = ctx.options?.format || "markdown";
2308
+ const segments = normalizedPath.split("/").filter(Boolean);
2309
+ const lines = [];
2310
+ if (format === "markdown") if (segments.length === 0) {
2311
+ const info = await this.source.getChainInfo();
2312
+ lines.push(`# ${info.moniker}`);
2313
+ lines.push("");
2314
+ lines.push(`**Chain ID:** ${info.id}`);
2315
+ lines.push(`**Block Height:** ${info.blockHeight}`);
2316
+ lines.push(`**Total Txs:** ${info.totalTxs}`);
2317
+ lines.push(`**Synced:** ${info.synced}`);
2318
+ lines.push("");
2319
+ lines.push("## Available Paths");
2320
+ for (const dir of TOP_LEVEL_DIRS) lines.push(`- \`/${dir}\``);
2321
+ } else {
2322
+ lines.push(`# ${normalizedPath}`);
2323
+ lines.push("");
2324
+ lines.push(`**Type:** ${segments[0]}`);
2325
+ if (segments.length > 1) lines.push(`**ID:** ${segments[1]}`);
2326
+ }
2327
+ else if (segments.length === 0) {
2328
+ const info = await this.source.getChainInfo();
2329
+ lines.push(`${info.moniker} (chain)`);
2330
+ lines.push(`Height: ${info.blockHeight}, Txs: ${info.totalTxs}`);
2331
+ } else lines.push(`${normalizedPath} (${segments[0]})`);
2332
+ return {
2333
+ content: lines.join("\n"),
2334
+ format
2335
+ };
2336
+ }
2337
+ async readCollection(ctx) {
2338
+ if (!TOP_LEVEL_DIRS.includes(ctx.params.collection)) throw new AFSNotFoundError(ctx.path);
2339
+ return this.buildEntry(ctx.path, { meta: {
2340
+ kind: collectionKind(ctx.params.collection),
2341
+ childrenCount: -1
2342
+ } });
2343
+ }
2344
+ async listCatchAll(ctx) {
2345
+ const normalizedPath = joinURL("/", ctx.params.path ?? "");
2346
+ if (normalizedPath === "/") return { data: TOP_LEVEL_DIRS.map((dir) => buildDirEntry("/", dir)) };
2347
+ const segments = normalizedPath.split("/").filter(Boolean);
2348
+ if (segments.length >= 1 && TOP_LEVEL_DIRS.includes(segments[0])) return { data: [] };
2349
+ throw new AFSNotFoundError(normalizedPath);
2350
+ }
2351
+ async readCapabilities(_ctx) {
2352
+ const operations = this.getOperationsDeclaration();
2353
+ return this.buildEntry("/.meta/.capabilities", {
2354
+ content: { operations },
2355
+ meta: { kind: "afs:capabilities" }
2356
+ });
2357
+ }
2358
+ async statHandler(ctx) {
2359
+ const normalizedPath = joinURL("/", ctx.params.path ?? "");
2360
+ const segments = normalizedPath.split("/").filter(Boolean);
2361
+ if (segments.length >= 1 && !TOP_LEVEL_DIRS.includes(segments[0])) throw new AFSNotFoundError(ctx.path);
2362
+ const meta = {};
2363
+ if (segments.length === 0) {
2364
+ meta.kind = "ocap:chain";
2365
+ meta.childrenCount = TOP_LEVEL_DIRS.length;
2366
+ } else if (segments.length === 1) {
2367
+ meta.kind = collectionKind(segments[0]);
2368
+ meta.childrenCount = -1;
2369
+ } else meta.kind = collectionKind(segments[0]);
2370
+ const { content: _, ...statData } = this.buildEntry(normalizedPath, { meta });
2371
+ return { data: statData };
2372
+ }
2373
+ async metaHandler(ctx) {
2374
+ const segments = joinURL("/", ctx.params.path ?? "").split("/").filter(Boolean);
2375
+ if (segments.length >= 1 && !TOP_LEVEL_DIRS.includes(segments[0])) throw new AFSNotFoundError(ctx.path);
2376
+ const meta = {};
2377
+ if (segments.length === 0) {
2378
+ meta.kind = "ocap:chain";
2379
+ meta.childrenCount = TOP_LEVEL_DIRS.length;
2380
+ } else if (segments.length === 1) {
2381
+ meta.kind = collectionKind(segments[0]);
2382
+ meta.childrenCount = -1;
2383
+ } else meta.kind = collectionKind(segments[0]);
2384
+ return this.buildEntry(ctx.path, { meta });
2385
+ }
2386
+ };
2387
+ __decorate([Read("/")], AFSOCAP.prototype, "readRoot", null);
2388
+ __decorate([Read("/_microbar")], AFSOCAP.prototype, "readMicrobar", null);
2389
+ __decorate([Read("/config")], AFSOCAP.prototype, "readConfig", null);
2390
+ __decorate([List("/validators")], AFSOCAP.prototype, "listValidators", null);
2391
+ __decorate([Read("/validators/:address")], AFSOCAP.prototype, "readValidator", null);
2392
+ __decorate([List("/transactions")], AFSOCAP.prototype, "listTransactions", null);
2393
+ __decorate([Read("/transactions/:hash")], AFSOCAP.prototype, "readTransaction", null);
2394
+ __decorate([Read("/transactions/:hash/receipts")], AFSOCAP.prototype, "readTxReceiptsDir", null);
2395
+ __decorate([List("/transactions/:hash/receipts")], AFSOCAP.prototype, "listTxReceipts", null);
2396
+ __decorate([Read("/transactions/:hash/receipts/:idx")], AFSOCAP.prototype, "readTxReceiptEntry", null);
2397
+ __decorate([List("/accounts")], AFSOCAP.prototype, "listAccounts", null);
2398
+ __decorate([Read("/accounts/:address")], AFSOCAP.prototype, "readAccount", null);
2399
+ __decorate([List("/accounts/:address")], AFSOCAP.prototype, "listAccountChildren", null);
2400
+ __decorate([Read("/accounts/:addr/tokens")], AFSOCAP.prototype, "readAccountTokensDir", null);
2401
+ __decorate([Read("/accounts/:addr/assets")], AFSOCAP.prototype, "readAccountAssetsDir", null);
2402
+ __decorate([Read("/accounts/:addr/transactions")], AFSOCAP.prototype, "readAccountTxDir", null);
2403
+ __decorate([List("/accounts/:addr/tokens")], AFSOCAP.prototype, "listAccountTokens", null);
2404
+ __decorate([Meta("/accounts/:addr/tokens")], AFSOCAP.prototype, "metaAccountTokens", null);
2405
+ __decorate([Read("/accounts/:addr/tokens/:token")], AFSOCAP.prototype, "readAccountToken", null);
2406
+ __decorate([List("/accounts/:addr/assets")], AFSOCAP.prototype, "listAccountAssets", null);
2407
+ __decorate([List("/accounts/:addr/transactions")], AFSOCAP.prototype, "listAccountTransactions", null);
2408
+ __decorate([Read("/accounts/:addr/stakes")], AFSOCAP.prototype, "readAccountStakesDir", null);
2409
+ __decorate([List("/accounts/:addr/stakes")], AFSOCAP.prototype, "listAccountStakes", null);
2410
+ __decorate([Read("/accounts/:addr/stakes/sent")], AFSOCAP.prototype, "readAccountSentStakesDir", null);
2411
+ __decorate([List("/accounts/:addr/stakes/sent")], AFSOCAP.prototype, "listAccountSentStakes", null);
2412
+ __decorate([Read("/accounts/:addr/stakes/received")], AFSOCAP.prototype, "readAccountReceivedStakesDir", null);
2413
+ __decorate([List("/accounts/:addr/stakes/received")], AFSOCAP.prototype, "listAccountReceivedStakes", null);
2414
+ __decorate([Read("/accounts/:addr/migration")], AFSOCAP.prototype, "readAccountMigrationDir", null);
2415
+ __decorate([List("/accounts/:addr/migration")], AFSOCAP.prototype, "listAccountMigration", null);
2416
+ __decorate([Read("/accounts/:addr/token-flows")], AFSOCAP.prototype, "readAccountTokenFlowsDir", null);
2417
+ __decorate([List("/accounts/:addr/token-flows")], AFSOCAP.prototype, "listAccountTokenFlows", null);
2418
+ __decorate([Read("/accounts/:addr/delegates")], AFSOCAP.prototype, "readAccountDelegatesDir", null);
2419
+ __decorate([List("/accounts/:addr/delegates")], AFSOCAP.prototype, "listAccountDelegates", null);
2420
+ __decorate([List("/tokens")], AFSOCAP.prototype, "listTokens", null);
2421
+ __decorate([Read("/tokens/:address")], AFSOCAP.prototype, "readToken", null);
2422
+ __decorate([List("/tokens/:address")], AFSOCAP.prototype, "listTokenChildren", null);
2423
+ __decorate([Read("/tokens/:addr/price")], AFSOCAP.prototype, "readTokenPrice", null);
2424
+ __decorate([Read("/tokens/:addr/holders")], AFSOCAP.prototype, "readTokenHoldersDir", null);
2425
+ __decorate([List("/tokens/:addr/holders")], AFSOCAP.prototype, "listTokenHolders", null);
2426
+ __decorate([Read("/tokens/:addr/transactions")], AFSOCAP.prototype, "readTokenTxDir", null);
2427
+ __decorate([List("/tokens/:addr/transactions")], AFSOCAP.prototype, "listTokenTransactions", null);
2428
+ __decorate([List("/assets")], AFSOCAP.prototype, "listAssets", null);
2429
+ __decorate([Read("/assets/:address")], AFSOCAP.prototype, "readAsset", null);
2430
+ __decorate([List("/assets/:address")], AFSOCAP.prototype, "listAssetChildren", null);
2431
+ __decorate([Read("/assets/:addr/transactions")], AFSOCAP.prototype, "readAssetTxDir", null);
2432
+ __decorate([List("/assets/:addr/transactions")], AFSOCAP.prototype, "listAssetTransactions", null);
2433
+ __decorate([Read("/assets/:addr/stakes")], AFSOCAP.prototype, "readAssetStakesDir", null);
2434
+ __decorate([List("/assets/:addr/stakes")], AFSOCAP.prototype, "listAssetStakes", null);
2435
+ __decorate([List("/factories")], AFSOCAP.prototype, "listFactories", null);
2436
+ __decorate([Read("/factories/:address")], AFSOCAP.prototype, "readFactory", null);
2437
+ __decorate([List("/factories/:address")], AFSOCAP.prototype, "listFactoryChildren", null);
2438
+ __decorate([Read("/factories/:addr/assets")], AFSOCAP.prototype, "readFactoryAssetsDir", null);
2439
+ __decorate([List("/factories/:addr/assets")], AFSOCAP.prototype, "listFactoryAssets", null);
2440
+ __decorate([Read("/factories/:addr/transactions")], AFSOCAP.prototype, "readFactoryTxDir", null);
2441
+ __decorate([List("/factories/:addr/transactions")], AFSOCAP.prototype, "listFactoryTransactions", null);
2442
+ __decorate([List("/stakes")], AFSOCAP.prototype, "listStakes", null);
2443
+ __decorate([Read("/stakes/:address")], AFSOCAP.prototype, "readStake", null);
2444
+ __decorate([List("/stakes/:address")], AFSOCAP.prototype, "listStakeChildren", null);
2445
+ __decorate([Read("/stakes/:addr/transactions")], AFSOCAP.prototype, "readStakeTxDir", null);
2446
+ __decorate([List("/stakes/:addr/transactions")], AFSOCAP.prototype, "listStakeTransactions", null);
2447
+ __decorate([Read("/stakes/:addr/tokens")], AFSOCAP.prototype, "readStakeTokensDir", null);
2448
+ __decorate([List("/stakes/:addr/tokens")], AFSOCAP.prototype, "listStakeTokens", null);
2449
+ __decorate([Read("/stakes/:addr/assets")], AFSOCAP.prototype, "readStakeAssetsDir", null);
2450
+ __decorate([List("/stakes/:addr/assets")], AFSOCAP.prototype, "listStakeAssets", null);
2451
+ __decorate([List("/bridges")], AFSOCAP.prototype, "listBridges", null);
2452
+ __decorate([Read("/bridges/:address")], AFSOCAP.prototype, "readBridge", null);
2453
+ __decorate([List("/bridges/:address")], AFSOCAP.prototype, "listBridgeChildren", null);
2454
+ __decorate([Read("/bridges/:addr/transactions")], AFSOCAP.prototype, "readBridgeTxDir", null);
2455
+ __decorate([List("/bridges/:addr/transactions")], AFSOCAP.prototype, "listBridgeTransactions", null);
2456
+ __decorate([Read("/bridges/:addr/stakes")], AFSOCAP.prototype, "readBridgeStakeDir", null);
2457
+ __decorate([List("/bridges/:addr/stakes")], AFSOCAP.prototype, "listBridgeStakes", null);
2458
+ __decorate([Read("/bridges/:addr/deposits")], AFSOCAP.prototype, "readBridgeDepositsDir", null);
2459
+ __decorate([List("/bridges/:addr/deposits")], AFSOCAP.prototype, "listBridgeDeposits", null);
2460
+ __decorate([Read("/bridges/:addr/withdrawals")], AFSOCAP.prototype, "readBridgeWithdrawalsDir", null);
2461
+ __decorate([List("/bridges/:addr/withdrawals")], AFSOCAP.prototype, "listBridgeWithdrawals", null);
2462
+ __decorate([Read("/bridges/:addr/governance")], AFSOCAP.prototype, "readBridgeGovernanceDir", null);
2463
+ __decorate([List("/bridges/:addr/governance")], AFSOCAP.prototype, "listBridgeGovernance", null);
2464
+ __decorate([Read("/bridges/:addr/slashes")], AFSOCAP.prototype, "readBridgeSlashesDir", null);
2465
+ __decorate([List("/bridges/:addr/slashes")], AFSOCAP.prototype, "listBridgeSlashes", null);
2466
+ __decorate([Read("/bridges/:addr/nodes")], AFSOCAP.prototype, "readBridgeNodesDir", null);
2467
+ __decorate([List("/bridges/:addr/nodes")], AFSOCAP.prototype, "listBridgeNodes", null);
2468
+ __decorate([Read("/bridges/:addr/blocks")], AFSOCAP.prototype, "readBridgeBlocksDir", null);
2469
+ __decorate([Meta("/bridges/:addr/blocks")], AFSOCAP.prototype, "metaBridgeBlocks", null);
2470
+ __decorate([List("/bridges/:addr/blocks")], AFSOCAP.prototype, "listBridgeBlocks", null);
2471
+ __decorate([Read("/bridges/:addr/blocks/:hash")], AFSOCAP.prototype, "readBridgeBlock", null);
2472
+ __decorate([List("/delegates")], AFSOCAP.prototype, "listDelegates", null);
2473
+ __decorate([Read("/delegates/:address")], AFSOCAP.prototype, "readDelegate", null);
2474
+ __decorate([List("/delegates/:address")], AFSOCAP.prototype, "listDelegateChildren", null);
2475
+ __decorate([Read("/delegates/:addr/transactions")], AFSOCAP.prototype, "readDelegateTxDir", null);
2476
+ __decorate([List("/delegates/:addr/transactions")], AFSOCAP.prototype, "listDelegateTransactions", null);
2477
+ __decorate([Read("/delegates/:addr/permissions")], AFSOCAP.prototype, "readDelegatePermissionsDir", null);
2478
+ __decorate([List("/delegates/:addr/permissions")], AFSOCAP.prototype, "listDelegatePermissions", null);
2479
+ __decorate([Search("/:path*")], AFSOCAP.prototype, "searchHandler", null);
2480
+ __decorate([Explain("/:path*")], AFSOCAP.prototype, "explainHandler", null);
2481
+ __decorate([Read("/:collection")], AFSOCAP.prototype, "readCollection", null);
2482
+ __decorate([List("/:path*")], AFSOCAP.prototype, "listCatchAll", null);
2483
+ __decorate([Read("/.meta/.capabilities")], AFSOCAP.prototype, "readCapabilities", null);
2484
+ __decorate([Stat("/:path*")], AFSOCAP.prototype, "statHandler", null);
2485
+ __decorate([Meta("/:path*")], AFSOCAP.prototype, "metaHandler", null);
2486
+ var provider_default = AFSOCAP;
2487
+
2488
+ //#endregion
2489
+ export { AFSOCAP, provider_default as default };
2490
+ //# sourceMappingURL=provider.mjs.map