@aigne/afs-ocap 1.12.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +26 -0
- package/README.md +144 -0
- package/aup/accounts/default.json +64 -0
- package/aup/accounts/item.json +510 -0
- package/aup/assets/default.json +70 -0
- package/aup/assets/item.json +360 -0
- package/aup/bridges/_addr/blocks/item.json +168 -0
- package/aup/bridges/default.json +79 -0
- package/aup/bridges/item.json +868 -0
- package/aup/config/default.json +806 -0
- package/aup/default.json +387 -0
- package/aup/delegates/default.json +71 -0
- package/aup/delegates/item.json +241 -0
- package/aup/factories/default.json +78 -0
- package/aup/factories/item.json +310 -0
- package/aup/stakes/default.json +76 -0
- package/aup/stakes/item.json +374 -0
- package/aup/tokens/default.json +76 -0
- package/aup/tokens/item.json +384 -0
- package/aup/transactions/default.json +102 -0
- package/aup/transactions/item.json +286 -0
- package/aup/validators/default.json +57 -0
- package/aup/validators/item.json +99 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +4 -0
- package/dist/method-colors.cjs +137 -0
- package/dist/method-colors.mjs +136 -0
- package/dist/method-colors.mjs.map +1 -0
- package/dist/provider.cjs +2490 -0
- package/dist/provider.d.cts +453 -0
- package/dist/provider.d.cts.map +1 -0
- package/dist/provider.d.mts +453 -0
- package/dist/provider.d.mts.map +1 -0
- package/dist/provider.mjs +2490 -0
- package/dist/provider.mjs.map +1 -0
- package/dist/remote-source.cjs +338 -0
- package/dist/remote-source.d.cts +62 -0
- package/dist/remote-source.d.cts.map +1 -0
- package/dist/remote-source.d.mts +62 -0
- package/dist/remote-source.d.mts.map +1 -0
- package/dist/remote-source.mjs +339 -0
- package/dist/remote-source.mjs.map +1 -0
- package/dist/source.d.cts +77 -0
- package/dist/source.d.cts.map +1 -0
- package/dist/source.d.mts +77 -0
- package/dist/source.d.mts.map +1 -0
- package/dist/types.d.cts +572 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +572 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/utils.cjs +581 -0
- package/dist/utils.mjs +557 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +61 -0
|
@@ -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
|