@ixo/oracle-runtime-workers 0.29.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/package.json +3 -3
  2. package/src/plugins/ixo-chain/balances.ts +141 -0
  3. package/src/plugins/ixo-chain/blocksync.ts +11 -6
  4. package/src/plugins/ixo-chain/denoms.ts +84 -36
  5. package/src/plugins/ixo-chain/http.ts +300 -63
  6. package/src/plugins/ixo-chain/ixo-chain.plugin.ts +182 -301
  7. package/src/plugins/ixo-chain/kinds/chain/account.ts +516 -0
  8. package/src/plugins/ixo-chain/kinds/chain/claims.ts +287 -0
  9. package/src/plugins/ixo-chain/kinds/chain/economics.ts +237 -0
  10. package/src/plugins/ixo-chain/kinds/chain/governance.ts +455 -0
  11. package/src/plugins/ixo-chain/kinds/chain/grants.ts +682 -0
  12. package/src/plugins/ixo-chain/kinds/chain/index.ts +23 -0
  13. package/src/plugins/ixo-chain/kinds/chain/liquid-staking.ts +279 -0
  14. package/src/plugins/ixo-chain/kinds/chain/shared.ts +286 -0
  15. package/src/plugins/ixo-chain/kinds/chain/staking.ts +220 -0
  16. package/src/plugins/ixo-chain/kinds/coins.ts +112 -0
  17. package/src/plugins/ixo-chain/kinds/core.ts +246 -0
  18. package/src/plugins/ixo-chain/kinds/indexed/agent-deposits.ts +124 -0
  19. package/src/plugins/ixo-chain/kinds/indexed/dao-proposals.ts +379 -0
  20. package/src/plugins/ixo-chain/kinds/indexed/daos.ts +319 -0
  21. package/src/plugins/ixo-chain/kinds/indexed/disputes.ts +159 -0
  22. package/src/plugins/ixo-chain/kinds/indexed/evaluations.ts +172 -0
  23. package/src/plugins/ixo-chain/kinds/indexed/index.ts +21 -0
  24. package/src/plugins/ixo-chain/kinds/indexed/liquid-stake-history.ts +160 -0
  25. package/src/plugins/ixo-chain/kinds/indexed/member-budgets.ts +175 -0
  26. package/src/plugins/ixo-chain/kinds/indexed/shared.ts +211 -0
  27. package/src/plugins/ixo-chain/kinds/indexed/token-history.ts +205 -0
  28. package/src/plugins/ixo-chain/kinds/registry.ts +43 -0
  29. package/src/plugins/ixo-chain/kinds/types.ts +156 -0
  30. package/src/plugins/ixo-chain/lookup.ts +470 -0
  31. package/src/plugins/ixo-chain/overview.ts +1060 -0
  32. package/src/plugins/ixo-chain/queries.ts +170 -138
  33. package/src/plugins/ixo-chain/rest.ts +129 -54
  34. package/src/plugins/ixo-chain/summaries.ts +3 -2
@@ -0,0 +1,319 @@
1
+ /**
2
+ * `daos`: the DAO DAO DAOs an account belongs to, through each kind of
3
+ * membership the index keeps: native-token staking, cw20 staking and cw4
4
+ * group membership, as one alias holding one sub-field per kind of
5
+ * membership (Blocksync's `query` field nests the root query type). Each
6
+ * reaches its DAO through relations, so one document answers it.
7
+ *
8
+ * Index (ixo-blocksync main, `20250611142429900_daodao_epochs.sql`):
9
+ * `idx_dao_native_staker_address`, `idx_dao_cw20_staker_address` and
10
+ * `idx_dao_cw4_member_address` serve the account; the voting module is a
11
+ * primary key, `idx_dao_voting_module_staking_contract` serves cw20,
12
+ * `idx_dao_core_voting_module` the DAO. A cw4 group's voting modules are
13
+ * found by `dao_voting_module.group_contract_address`, which no index
14
+ * leads; that table holds one row per DAO, and a member of three groups
15
+ * answered in 0.27 s on mainnet on Oct 29 2026. cw721 (NFT) staking is not read.
16
+ * Each list is ordered by its primary key (voting module or contract
17
+ * address).
18
+ */
19
+ import { z } from 'zod';
20
+ import { displayCoin } from '../coins';
21
+ import { defineKind, type KindContext, type KindResult } from '../types';
22
+ import { accountArg, firstArg } from '../../queries';
23
+ import { text } from '../../summaries';
24
+ import {
25
+ PAGE,
26
+ accountFor,
27
+ amountText,
28
+ chainCoin,
29
+ compact,
30
+ connection,
31
+ equalTo,
32
+ idText,
33
+ listResult,
34
+ longText,
35
+ num,
36
+ opt,
37
+ str,
38
+ unexpected,
39
+ } from './shared';
40
+
41
+ /** Voting modules read per cw4 group or cw20 staking contract (several DAOs can share one). */
42
+ const MODULES_PER_CONTRACT = 3;
43
+
44
+ const args = z.strictObject({
45
+ address: accountArg('Member'),
46
+ first: firstArg,
47
+ });
48
+
49
+ /**
50
+ * The membership sub-fields of `address`, `perList` rows each, with
51
+ * `core` as the selection set of each DAO core reached.
52
+ */
53
+ export function membershipFields(
54
+ address: string,
55
+ perList: number,
56
+ core: string,
57
+ ): string {
58
+ const cores = `daoCoresByVotingModule(first: 1) { nodes { ${core} } }`;
59
+ const staker = equalTo('stakerAddress', address);
60
+ const member = equalTo('memberAddress', address);
61
+ const modules = (relation: string, fields: string) =>
62
+ `${relation}(first: ${MODULES_PER_CONTRACT}) { nodes { ${fields} ${cores} } }`;
63
+ return [
64
+ `native: daoNativeStakers(filter: ${staker}, first: ${perList}, orderBy: PRIMARY_KEY_ASC) { ${PAGE} nodes { stakedAmount daoVotingModuleByVotingModuleAddress { address moduleType nativeDenom totalWeight ${cores} } } }`,
65
+ `cw20: daoCw20Stakers(filter: ${staker}, first: ${perList}, orderBy: PRIMARY_KEY_ASC) { ${PAGE} nodes { stakedAmount daoCw20StakingContractByStakingContract { ${modules('daoVotingModulesByStakingContract', 'address moduleType tokenAddress totalWeight')} } } }`,
66
+ `cw4: daoCw4Members(filter: ${member}, first: ${perList}, orderBy: PRIMARY_KEY_ASC) { ${PAGE} nodes { weight daoCw4GroupContractByGroupContractAddress { ${modules('daoVotingModulesByGroupContractAddress', 'address moduleType totalWeight')} } } }`,
67
+ ].join(' ');
68
+ }
69
+
70
+ const moduleNode = <T extends z.ZodType>(core: T) =>
71
+ z.looseObject({
72
+ address: str,
73
+ moduleType: opt,
74
+ nativeDenom: opt,
75
+ tokenAddress: opt,
76
+ totalWeight: num,
77
+ daoCoresByVotingModule: z.object({ nodes: z.array(core) }),
78
+ });
79
+
80
+ /** The answer of {@link membershipFields} with `core` as each DAO core's shape. */
81
+ export function membershipSchema<T extends z.ZodType>(core: T) {
82
+ const module = moduleNode(core);
83
+ const modules = z.object({ nodes: z.array(module) });
84
+ return z.looseObject({
85
+ native: connection(
86
+ z.looseObject({
87
+ stakedAmount: num,
88
+ daoVotingModuleByVotingModuleAddress: module.nullish(),
89
+ }),
90
+ ),
91
+ cw20: connection(
92
+ z.looseObject({
93
+ stakedAmount: num,
94
+ daoCw20StakingContractByStakingContract: z
95
+ .looseObject({ daoVotingModulesByStakingContract: modules })
96
+ .nullish(),
97
+ }),
98
+ ),
99
+ cw4: connection(
100
+ z.looseObject({
101
+ weight: z.number().nullish(),
102
+ daoCw4GroupContractByGroupContractAddress: z
103
+ .looseObject({ daoVotingModulesByGroupContractAddress: modules })
104
+ .nullish(),
105
+ }),
106
+ ),
107
+ });
108
+ }
109
+
110
+ /** One DAO reached through one membership. */
111
+ interface Membership<C> {
112
+ core: C;
113
+ membership: 'native-staked' | 'cw20-staked' | 'cw4-member';
114
+ votingModule: string;
115
+ moduleType?: string;
116
+ /** Staked base units (native or cw20), or the cw4 weight. */
117
+ weight?: string;
118
+ nativeDenom?: string;
119
+ tokenAddress?: string;
120
+ totalWeight?: string;
121
+ }
122
+
123
+ type Page = { pageInfo?: { hasNextPage: boolean } };
124
+ type Amount = string | number | null | undefined;
125
+ interface ModuleRow<C> {
126
+ address: string;
127
+ moduleType?: string | null;
128
+ nativeDenom?: string | null;
129
+ tokenAddress?: string | null;
130
+ totalWeight?: Amount;
131
+ daoCoresByVotingModule: { nodes: C[] };
132
+ }
133
+ /** The parsed answer of {@link membershipFields} (see {@link membershipSchema}). */
134
+ export interface MembershipRows<C> {
135
+ native: Page & {
136
+ nodes: {
137
+ stakedAmount?: Amount;
138
+ daoVotingModuleByVotingModuleAddress?: ModuleRow<C> | null;
139
+ }[];
140
+ };
141
+ cw20: Page & {
142
+ nodes: {
143
+ stakedAmount?: Amount;
144
+ daoCw20StakingContractByStakingContract?: {
145
+ daoVotingModulesByStakingContract: { nodes: ModuleRow<C>[] };
146
+ } | null;
147
+ }[];
148
+ };
149
+ cw4: Page & {
150
+ nodes: {
151
+ weight?: number | null;
152
+ daoCw4GroupContractByGroupContractAddress?: {
153
+ daoVotingModulesByGroupContractAddress: { nodes: ModuleRow<C>[] };
154
+ } | null;
155
+ }[];
156
+ };
157
+ }
158
+
159
+ /** Every DAO the memberships reach, native stakes first, then cw20 stakes, then cw4 groups. */
160
+ export function membershipsOf<C>(answer: MembershipRows<C>): Membership<C>[] {
161
+ const out: Membership<C>[] = [];
162
+ const add = (
163
+ membership: Membership<C>['membership'],
164
+ module: ModuleRow<C>,
165
+ weight: Amount,
166
+ ) => {
167
+ for (const core of module.daoCoresByVotingModule.nodes)
168
+ out.push({
169
+ core,
170
+ membership,
171
+ votingModule: module.address,
172
+ moduleType: module.moduleType ?? undefined,
173
+ weight: amountText(weight),
174
+ nativeDenom: module.nativeDenom ?? undefined,
175
+ tokenAddress: module.tokenAddress ?? undefined,
176
+ totalWeight: amountText(module.totalWeight),
177
+ });
178
+ };
179
+ for (const row of answer.native.nodes)
180
+ if (row.daoVotingModuleByVotingModuleAddress)
181
+ add(
182
+ 'native-staked',
183
+ row.daoVotingModuleByVotingModuleAddress,
184
+ row.stakedAmount,
185
+ );
186
+ for (const row of answer.cw20.nodes)
187
+ for (const module of row.daoCw20StakingContractByStakingContract
188
+ ?.daoVotingModulesByStakingContract.nodes ?? [])
189
+ add('cw20-staked', module, row.stakedAmount);
190
+ for (const row of answer.cw4.nodes)
191
+ for (const module of row.daoCw4GroupContractByGroupContractAddress
192
+ ?.daoVotingModulesByGroupContractAddress.nodes ?? [])
193
+ add('cw4-member', module, row.weight);
194
+ return out;
195
+ }
196
+
197
+ /** Whether any membership list holds rows past those read. */
198
+ export function moreMemberships<C>(answer: MembershipRows<C>): boolean {
199
+ return [answer.native, answer.cw20, answer.cw4].some(
200
+ (list) => list.pageInfo?.hasNextPage === true,
201
+ );
202
+ }
203
+
204
+ const DIGITS = /^\d+$/;
205
+
206
+ /** `part / whole` as a percentage with two decimals, rounded down; undefined unless both are whole numbers. */
207
+ function shareOf(part?: string, whole?: string): string | undefined {
208
+ if (!part || !whole || !DIGITS.test(part) || !DIGITS.test(whole))
209
+ return undefined;
210
+ const total = BigInt(whole);
211
+ if (total === 0n) return undefined;
212
+ const basisPoints = (BigInt(part) * 10_000n) / total;
213
+ return `${basisPoints / 100n}.${String(basisPoints % 100n).padStart(2, '0')}%`;
214
+ }
215
+
216
+ const coreNode = z.looseObject({
217
+ address: str,
218
+ name: opt,
219
+ description: opt,
220
+ });
221
+
222
+ /** What a DAO row shows of its core. */
223
+ type CoreText = {
224
+ address: string;
225
+ name?: string | null;
226
+ description?: string | null;
227
+ };
228
+
229
+ async function daoOf(ctx: KindContext, entry: Membership<CoreText>) {
230
+ const native =
231
+ entry.membership === 'native-staked' && entry.nativeDenom
232
+ ? chainCoin({ denom: entry.nativeDenom, amount: entry.weight })
233
+ : undefined;
234
+ const staked = native
235
+ ? ((await displayCoin(ctx, native)) ?? undefined)
236
+ : entry.membership === 'cw20-staked' && entry.weight
237
+ ? compact({
238
+ token: idText(entry.tokenAddress),
239
+ amountBaseUnits: entry.weight,
240
+ })
241
+ : undefined;
242
+ const name = text(entry.core.name);
243
+ const description = longText(entry.core.description);
244
+ const cw4 = entry.membership === 'cw4-member';
245
+ return compact({
246
+ dao: idText(entry.core.address),
247
+ name,
248
+ description,
249
+ nameIsSelfChosen: name || description ? true : undefined,
250
+ membership: entry.membership,
251
+ // A cw4 group's admin adds members without their consent.
252
+ addedByGroupAdmin: cw4 ? true : undefined,
253
+ staked,
254
+ weight: cw4 ? entry.weight : undefined,
255
+ votingPowerShare: shareOf(entry.weight, entry.totalWeight),
256
+ votingModule: idText(entry.votingModule),
257
+ moduleType: text(entry.moduleType, 80),
258
+ });
259
+ }
260
+
261
+ /**
262
+ * The `daos` answer's data from the membership answer: the first `n`
263
+ * DAOs, and `more` when the index holds further memberships.
264
+ */
265
+ export async function daosData<C extends CoreText>(
266
+ ctx: KindContext,
267
+ answer: MembershipRows<C>,
268
+ n: number,
269
+ ): Promise<Record<string, unknown>> {
270
+ const all = membershipsOf(answer);
271
+ return compact({
272
+ items: await Promise.all(all.slice(0, n).map((entry) => daoOf(ctx, entry))),
273
+ more: all.length > n || moreMemberships(answer) ? true : undefined,
274
+ });
275
+ }
276
+
277
+ const answerSchema = membershipSchema(coreNode);
278
+
279
+ export const daos = defineKind({
280
+ name: 'daos',
281
+ summary:
282
+ 'DAOs an account (default: the user) is a member of, how (staked or group member) and its share of the voting power.',
283
+ explain: [
284
+ 'A DAO (DAO DAO on the IXO chain) decides by proposals and votes; its voting module sets who votes and with what weight.',
285
+ '`membership`: `cw4-member` (a group member with a fixed `weight`), `cw20-staked` (voting power from cw20 tokens staked in the DAO, `staked.amountBaseUnits` of the `staked.token` contract) or `native-staked` (from staked bank tokens, e.g. IXO; `staked` is a bank coin, `amount` in display units beside `symbol`).',
286
+ '`votingPowerShare` is the account’s weight or stake over the voting module’s total, rounded down; staked tokens count only while staked, and unstaking may take a waiting period.',
287
+ 'Each: dao (the DAO core contract address), name, description, membership, staked or weight, votingPowerShare, votingModule.',
288
+ '`addedByGroupAdmin: true` (every cw4-member row): a cw4 group’s admin can add any address to the group without its consent, so membership alone does not mean the account joined, agreed or knows of the DAO.',
289
+ '`nameIsSelfChosen: true`: the name and description are whatever the DAO set for itself, never a verified identity or an endorsement; say so before relying on them.',
290
+ 'NFT (cw721) staking is not indexed here. Ask `dao_proposals` for what the DAOs are voting on.',
291
+ ].join(' '),
292
+ source: 'blocksync',
293
+ list: true,
294
+ args,
295
+ example: {},
296
+ plan(input, ctx) {
297
+ const member = accountFor(input.address, ctx.identity, 'address');
298
+ if ('invalid' in member) return member;
299
+ const n = ctx.first;
300
+ const subfields = membershipFields(
301
+ member.address,
302
+ n,
303
+ 'address name description',
304
+ );
305
+ return {
306
+ source: 'blocksync',
307
+ field: (alias) => `${alias}: query { ${subfields} }`,
308
+ async shape(raw): Promise<KindResult> {
309
+ const parsed = answerSchema.safeParse(raw);
310
+ if (!parsed.success) return { ok: false, ...unexpected };
311
+ return listResult(
312
+ await daosData(ctx, parsed.data, n),
313
+ n,
314
+ member.subject,
315
+ );
316
+ },
317
+ };
318
+ },
319
+ });
@@ -0,0 +1,159 @@
1
+ /**
2
+ * `disputes`: x/claims disputes against one claim, or those an account
3
+ * filed (default: the user), with their resolution.
4
+ *
5
+ * Index (ixo-blocksync main): `Dispute_subjectId_idx` ("subjectId") and
6
+ * `Dispute_disputerAddress_idx` ("disputerAddress"); ordered by the
7
+ * primary key `id` (filing order), newest first. The resolution is the
8
+ * 1:1 `DisputeResolution` row (primary key "disputeId"). The filing's and
9
+ * the resolution's `data` (evidence links and proofs) are not read.
10
+ */
11
+ import { z } from 'zod';
12
+ import { COINS_NOTE } from '../coins';
13
+ import { defineKind, type KindContext, type KindResult } from '../types';
14
+ import { accountArg, firstArg } from '../../queries';
15
+ import {
16
+ PAGE,
17
+ accountFor,
18
+ bankCoins,
19
+ claimIdArg,
20
+ compact,
21
+ connection,
22
+ equalTo,
23
+ idText,
24
+ listResult,
25
+ opt,
26
+ str,
27
+ unexpected,
28
+ } from './shared';
29
+
30
+ /** x/claims `DisputeStatus`. */
31
+ const STATUS = ['open', 'awarded', 'dismissed'] as const;
32
+ /** x/claims `DisputeTargetRole`: the party of the claim disputed. */
33
+ const TARGET = ['unspecified', 'submitter', 'evaluator'] as const;
34
+
35
+ const args = z
36
+ .strictObject({
37
+ claimId: claimIdArg('Every dispute against this claim.'),
38
+ address: accountArg('Disputer (who filed)'),
39
+ first: firstArg,
40
+ })
41
+ .refine(
42
+ (value) => value.claimId === undefined || value.address === undefined,
43
+ { message: 'give claimId or address, not both' },
44
+ );
45
+
46
+ const RESOLUTION_FIELDS =
47
+ 'resolvedAt adjudicatorDid adjudicatorAddress intendedPenalty actualPenaltyPaid winnerAddress winnerAmount loserAddress adjudicatorAmount';
48
+ const FIELDS = `id subjectId type targetRole status disputerAddress disputerDid disputeDeposit submittedAt resolution { ${RESOLUTION_FIELDS} }`;
49
+
50
+ const resolutionNode = z.looseObject({
51
+ resolvedAt: opt,
52
+ adjudicatorDid: opt,
53
+ adjudicatorAddress: opt,
54
+ intendedPenalty: z.unknown().optional(),
55
+ actualPenaltyPaid: z.unknown().optional(),
56
+ winnerAddress: opt,
57
+ winnerAmount: z.unknown().optional(),
58
+ loserAddress: opt,
59
+ adjudicatorAmount: z.unknown().optional(),
60
+ });
61
+ const node = z.looseObject({
62
+ id: z.union([z.string(), z.number()]),
63
+ subjectId: str,
64
+ type: z.number().nullish(),
65
+ targetRole: z.number().nullish(),
66
+ status: z.number().nullish(),
67
+ disputerAddress: opt,
68
+ disputerDid: opt,
69
+ disputeDeposit: z.unknown().optional(),
70
+ submittedAt: opt,
71
+ resolution: resolutionNode.nullish(),
72
+ });
73
+
74
+ function named<T extends readonly string[]>(
75
+ names: T,
76
+ value: number | null | undefined,
77
+ ): string | undefined {
78
+ if (value === null || value === undefined) return undefined;
79
+ return names[value] ?? `unknown (${value})`;
80
+ }
81
+
82
+ async function disputeOf(ctx: KindContext, row: z.infer<typeof node>) {
83
+ const resolution = row.resolution;
84
+ return compact({
85
+ id: idText(String(row.id)),
86
+ claimId: idText(row.subjectId),
87
+ against: named(TARGET, row.targetRole),
88
+ status: named(STATUS, row.status),
89
+ type: row.type ?? undefined,
90
+ disputer: idText(row.disputerAddress),
91
+ disputerDid: idText(row.disputerDid),
92
+ deposit: await bankCoins(ctx, row.disputeDeposit),
93
+ submittedAt: idText(row.submittedAt),
94
+ resolution: resolution
95
+ ? compact({
96
+ resolvedAt: idText(resolution.resolvedAt),
97
+ adjudicator: idText(resolution.adjudicatorAddress),
98
+ adjudicatorDid: idText(resolution.adjudicatorDid),
99
+ intendedPenalty: await bankCoins(ctx, resolution.intendedPenalty),
100
+ penaltyPaid: await bankCoins(ctx, resolution.actualPenaltyPaid),
101
+ winner: idText(resolution.winnerAddress),
102
+ winnerAmount: await bankCoins(ctx, resolution.winnerAmount),
103
+ loser: idText(resolution.loserAddress),
104
+ adjudicatorAmount: await bankCoins(ctx, resolution.adjudicatorAmount),
105
+ })
106
+ : undefined,
107
+ });
108
+ }
109
+
110
+ export const disputes = defineKind({
111
+ name: 'disputes',
112
+ summary:
113
+ 'Disputes against a claim, or those an account filed (default: the user), with how each was resolved.',
114
+ explain: [
115
+ 'A dispute challenges one party of a claim: `against` is `submitter` (the agent that submitted it) or `evaluator` (the agent that evaluated it). The disputer locks a deposit; an adjudicator the collection names then rules.',
116
+ 'Status: `open` (awaiting a ruling; the disputed agent cannot submit or withdraw its deposit in that collection meanwhile), `awarded` (upheld: the disputed party was in the wrong, its deposit is slashed, the disputer gets its deposit back and most of the penalty) or `dismissed` (the disputed party was vindicated; the disputer’s deposit becomes the penalty).',
117
+ '`resolution`: when and by whom, the intended and actually paid penalty (capped by what the loser had), the winner, the loser, and the shares paid out.',
118
+ '`type` is a number the client defines. Newest first. Evidence documents are not shown.',
119
+ COINS_NOTE,
120
+ ].join(' '),
121
+ source: 'blocksync',
122
+ list: true,
123
+ args,
124
+ example: {
125
+ claimId: 'bafkreibqh2o3cx2bhnvxhoknbashrse3lhxh5rjohyqgy5yoqqc5jhqgpm',
126
+ },
127
+ plan(input, ctx) {
128
+ const n = ctx.first;
129
+ let filter: string;
130
+ let subject: 'user' | 'other' | undefined;
131
+ if (input.claimId !== undefined)
132
+ filter = equalTo('subjectId', input.claimId);
133
+ else {
134
+ const disputer = accountFor(input.address, ctx.identity, 'address');
135
+ if ('invalid' in disputer) return disputer;
136
+ filter = equalTo('disputerAddress', disputer.address);
137
+ subject = disputer.subject;
138
+ }
139
+ return {
140
+ source: 'blocksync',
141
+ field: (alias) =>
142
+ `${alias}: disputes(filter: ${filter}, first: ${n}, orderBy: ID_DESC) { ${PAGE} nodes { ${FIELDS} } }`,
143
+ async shape(raw): Promise<KindResult> {
144
+ const parsed = connection(node).safeParse(raw);
145
+ if (!parsed.success) return { ok: false, ...unexpected };
146
+ return listResult(
147
+ {
148
+ items: await Promise.all(
149
+ parsed.data.nodes.map((row) => disputeOf(ctx, row)),
150
+ ),
151
+ more: parsed.data.pageInfo?.hasNextPage ? true : undefined,
152
+ },
153
+ n,
154
+ subject,
155
+ );
156
+ },
157
+ };
158
+ },
159
+ });
@@ -0,0 +1,172 @@
1
+ /**
2
+ * `evaluations`: x/claims evaluations, by claim, by collection or by the
3
+ * agent that evaluated (default: the user).
4
+ *
5
+ * Index (ixo-blocksync main): `Evaluation_claimId_evaluationDate_idx`
6
+ * ("claimId", "evaluationDate" DESC) and
7
+ * `Evaluation_collectionId_evaluationDate_id_idx` ("collectionId",
8
+ * "evaluationDate", "id") serve the claim and collection reads in
9
+ * `EVALUATION_DATE_DESC` order; `Evaluation_agentAddress_idx`
10
+ * ("agentAddress") serves the evaluator read, ordered by the primary key
11
+ * `id` (indexing order).
12
+ */
13
+ import { z } from 'zod';
14
+ import { COINS_NOTE } from '../coins';
15
+ import { defineKind, type KindContext, type KindResult } from '../types';
16
+ import { accountArg, firstArg } from '../../queries';
17
+ import {
18
+ PAGE,
19
+ accountFor,
20
+ bankCoins,
21
+ claimIdArg,
22
+ collectionIdArg,
23
+ compact,
24
+ connection,
25
+ cw1155Amounts,
26
+ cw20Amounts,
27
+ equalTo,
28
+ idText,
29
+ listResult,
30
+ nonEmpty,
31
+ num,
32
+ opt,
33
+ str,
34
+ unexpected,
35
+ } from './shared';
36
+
37
+ /** x/claims `EvaluationStatus`. */
38
+ const STATUS = [
39
+ 'pending',
40
+ 'approved',
41
+ 'rejected',
42
+ 'disputed',
43
+ 'invalidated',
44
+ 'flagged',
45
+ ] as const;
46
+
47
+ const args = z
48
+ .strictObject({
49
+ claimId: claimIdArg('Every evaluation of this claim (its history).'),
50
+ collectionId: collectionIdArg(
51
+ 'Every evaluation in this collection, e.g. "8250".',
52
+ ),
53
+ evaluator: accountArg('The agent that evaluated'),
54
+ first: firstArg,
55
+ })
56
+ .refine(
57
+ (value) =>
58
+ [value.claimId, value.collectionId, value.evaluator].filter(
59
+ (given) => given !== undefined,
60
+ ).length <= 1,
61
+ { message: 'give at most one of claimId, collectionId and evaluator' },
62
+ );
63
+
64
+ // `cw1155Payment` is on the mainnet index's `Evaluation` (introspected on
65
+ // Oct 29 2026), as the chain's `[{ address, token_id, amount }]`.
66
+ const FIELDS =
67
+ 'claimId collectionId oracle agentDid agentAddress status reason verificationProof amount cw20Payment cw1155Payment evaluationDate';
68
+
69
+ const node = z.looseObject({
70
+ claimId: str,
71
+ collectionId: str,
72
+ oracle: opt,
73
+ agentDid: opt,
74
+ agentAddress: opt,
75
+ status: z.number().nullish(),
76
+ reason: num,
77
+ verificationProof: opt,
78
+ amount: z.unknown().optional(),
79
+ cw20Payment: z.unknown().optional(),
80
+ cw1155Payment: z.unknown().optional(),
81
+ evaluationDate: opt,
82
+ });
83
+
84
+ function statusOf(value: number | null | undefined): string | undefined {
85
+ if (value === null || value === undefined) return undefined;
86
+ return STATUS[value] ?? `unknown (${value})`;
87
+ }
88
+
89
+ async function evaluationOf(ctx: KindContext, row: z.infer<typeof node>) {
90
+ const coins = await bankCoins(ctx, row.amount);
91
+ const cw20 = nonEmpty(cw20Amounts(row.cw20Payment));
92
+ const cw1155 = nonEmpty(cw1155Amounts(row.cw1155Payment));
93
+ return compact({
94
+ claimId: idText(row.claimId),
95
+ collectionId: idText(row.collectionId),
96
+ status: statusOf(row.status),
97
+ evaluationDate: idText(row.evaluationDate),
98
+ evaluatorAddress: idText(row.agentAddress),
99
+ evaluatorDid: idText(row.agentDid),
100
+ oracle: idText(row.oracle),
101
+ reasonCode:
102
+ row.reason === null || row.reason === undefined
103
+ ? undefined
104
+ : idText(String(row.reason)),
105
+ verificationProof: idText(row.verificationProof),
106
+ coins,
107
+ cw20,
108
+ cw1155,
109
+ // x/claims pays the collection's default approval payment only when an
110
+ // evaluation sets none of the three; any one set replaces all of it.
111
+ defaultPayment: !coins && !cw20 && !cw1155 ? true : undefined,
112
+ });
113
+ }
114
+
115
+ export const evaluations = defineKind({
116
+ name: 'evaluations',
117
+ summary:
118
+ 'Claim evaluations: the history of one claim, a collection, or what an agent evaluated (default: the user).',
119
+ explain: [
120
+ 'An evaluation is the verdict an evaluation agent, acting for an oracle, gives a claim: approved claims get paid from the collection, rejected ones do not.',
121
+ 'Status: pending, approved, rejected, disputed, invalidated, or flagged (the evaluator declined to decide yet; the claim can be evaluated again, and only a later approved/rejected/invalidated is final).',
122
+ 'A claim can have several evaluations (flagged, then decided); with claimId they are newest first.',
123
+ 'Each: claimId, collectionId, status, evaluationDate, evaluatorAddress/evaluatorDid (the agent that evaluated), oracle (the oracle entity it evaluated for), reasonCode (a number the evaluator defines), verificationProof (the CID of the evaluation credential).',
124
+ '`coins` (bank coins), `cw20` (base units per token contract) and `cw1155` (whole units of the listed token ids per contract) are the approval payment the evaluation set: together they replace the collection’s default, so one absent beside another pays nothing of its kind.',
125
+ '`defaultPayment: true`: it set none of them, so an approval pays the collection’s default approval payment (see claim_collection payments). A claim with an intent is paid its intent amounts, which the evaluation then carries.',
126
+ COINS_NOTE,
127
+ 'For the evaluations of claims an account submitted, ask `claims` (each carries its current evaluation) or this kind with a claimId.',
128
+ 'Give at most one of claimId, collectionId, evaluator; with none, the user’s own evaluations.',
129
+ ].join(' '),
130
+ source: 'blocksync',
131
+ list: true,
132
+ args,
133
+ example: { collectionId: '8250', first: 5 },
134
+ plan(input, ctx) {
135
+ const n = ctx.first;
136
+ let filter: string;
137
+ let orderBy: string;
138
+ let subject: 'user' | 'other' | undefined;
139
+ if (input.claimId !== undefined) {
140
+ filter = equalTo('claimId', input.claimId);
141
+ orderBy = 'EVALUATION_DATE_DESC';
142
+ } else if (input.collectionId !== undefined) {
143
+ filter = equalTo('collectionId', input.collectionId);
144
+ orderBy = 'EVALUATION_DATE_DESC';
145
+ } else {
146
+ const evaluator = accountFor(input.evaluator, ctx.identity, 'evaluator');
147
+ if ('invalid' in evaluator) return evaluator;
148
+ filter = equalTo('agentAddress', evaluator.address);
149
+ orderBy = 'ID_DESC';
150
+ subject = evaluator.subject;
151
+ }
152
+ return {
153
+ source: 'blocksync',
154
+ field: (alias) =>
155
+ `${alias}: evaluations(filter: ${filter}, first: ${n}, orderBy: ${orderBy}) { ${PAGE} nodes { ${FIELDS} } }`,
156
+ async shape(raw): Promise<KindResult> {
157
+ const parsed = connection(node).safeParse(raw);
158
+ if (!parsed.success) return { ok: false, ...unexpected };
159
+ return listResult(
160
+ {
161
+ items: await Promise.all(
162
+ parsed.data.nodes.map((row) => evaluationOf(ctx, row)),
163
+ ),
164
+ more: parsed.data.pageInfo?.hasNextPage ? true : undefined,
165
+ },
166
+ n,
167
+ subject,
168
+ );
169
+ },
170
+ };
171
+ },
172
+ });
@@ -0,0 +1,21 @@
1
+ import type { KindDef } from '../types';
2
+ import { agentDeposits } from './agent-deposits';
3
+ import { daoProposals } from './dao-proposals';
4
+ import { daos } from './daos';
5
+ import { disputes } from './disputes';
6
+ import { evaluations } from './evaluations';
7
+ import { liquidStakeHistory } from './liquid-stake-history';
8
+ import { memberBudgets } from './member-budgets';
9
+ import { tokenHistory } from './token-history';
10
+
11
+ /** Kinds answered by the Blocksync index, in catalogue order. */
12
+ export const INDEXED_KINDS: readonly KindDef[] = [
13
+ tokenHistory,
14
+ evaluations,
15
+ memberBudgets,
16
+ agentDeposits,
17
+ disputes,
18
+ daos,
19
+ daoProposals,
20
+ liquidStakeHistory,
21
+ ];