@ixo/oracle-runtime-workers 0.29.0 → 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.
- package/package.json +2 -2
- package/src/plugins/hub-actions/hub-actions.plugin.ts +73 -24
- package/src/plugins/ixo-chain/balances.ts +141 -0
- package/src/plugins/ixo-chain/blocksync.ts +11 -6
- package/src/plugins/ixo-chain/denoms.ts +84 -36
- package/src/plugins/ixo-chain/http.ts +300 -63
- package/src/plugins/ixo-chain/ixo-chain.plugin.ts +182 -301
- package/src/plugins/ixo-chain/kinds/chain/account.ts +516 -0
- package/src/plugins/ixo-chain/kinds/chain/claims.ts +287 -0
- package/src/plugins/ixo-chain/kinds/chain/economics.ts +237 -0
- package/src/plugins/ixo-chain/kinds/chain/governance.ts +455 -0
- package/src/plugins/ixo-chain/kinds/chain/grants.ts +682 -0
- package/src/plugins/ixo-chain/kinds/chain/index.ts +23 -0
- package/src/plugins/ixo-chain/kinds/chain/liquid-staking.ts +279 -0
- package/src/plugins/ixo-chain/kinds/chain/shared.ts +286 -0
- package/src/plugins/ixo-chain/kinds/chain/staking.ts +220 -0
- package/src/plugins/ixo-chain/kinds/coins.ts +112 -0
- package/src/plugins/ixo-chain/kinds/core.ts +246 -0
- package/src/plugins/ixo-chain/kinds/indexed/agent-deposits.ts +124 -0
- package/src/plugins/ixo-chain/kinds/indexed/dao-proposals.ts +379 -0
- package/src/plugins/ixo-chain/kinds/indexed/daos.ts +319 -0
- package/src/plugins/ixo-chain/kinds/indexed/disputes.ts +159 -0
- package/src/plugins/ixo-chain/kinds/indexed/evaluations.ts +172 -0
- package/src/plugins/ixo-chain/kinds/indexed/index.ts +21 -0
- package/src/plugins/ixo-chain/kinds/indexed/liquid-stake-history.ts +160 -0
- package/src/plugins/ixo-chain/kinds/indexed/member-budgets.ts +175 -0
- package/src/plugins/ixo-chain/kinds/indexed/shared.ts +211 -0
- package/src/plugins/ixo-chain/kinds/indexed/token-history.ts +205 -0
- package/src/plugins/ixo-chain/kinds/registry.ts +43 -0
- package/src/plugins/ixo-chain/kinds/types.ts +156 -0
- package/src/plugins/ixo-chain/lookup.ts +470 -0
- package/src/plugins/ixo-chain/overview.ts +1060 -0
- package/src/plugins/ixo-chain/queries.ts +170 -138
- package/src/plugins/ixo-chain/rest.ts +129 -54
- package/src/plugins/ixo-chain/summaries.ts +3 -2
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agent_deposits`: an account's performance deposits in claim
|
|
3
|
+
* collections (x/claims `AgentDepositBalance`).
|
|
4
|
+
*
|
|
5
|
+
* Index (ixo-blocksync main): `AgentDepositBalance_agentAddress_idx`
|
|
6
|
+
* ("agentAddress") serves the account; the primary key ("collectionId",
|
|
7
|
+
* "agentAddress") serves a collection with it and gives the order
|
|
8
|
+
* (collection id as text).
|
|
9
|
+
*/
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { literal } from '../../queries';
|
|
12
|
+
import { COINS_NOTE, displayCoins, type DisplayCoin } from '../coins';
|
|
13
|
+
import { defineKind, type KindContext, type KindResult } from '../types';
|
|
14
|
+
import { accountArg, firstArg } from '../../queries';
|
|
15
|
+
import {
|
|
16
|
+
PAGE,
|
|
17
|
+
accountFor,
|
|
18
|
+
chainCoins,
|
|
19
|
+
collectionIdArg,
|
|
20
|
+
compact,
|
|
21
|
+
connection,
|
|
22
|
+
idText,
|
|
23
|
+
listResult,
|
|
24
|
+
opt,
|
|
25
|
+
reached,
|
|
26
|
+
str,
|
|
27
|
+
unexpected,
|
|
28
|
+
} from './shared';
|
|
29
|
+
|
|
30
|
+
const args = z.strictObject({
|
|
31
|
+
address: accountArg('Agent'),
|
|
32
|
+
collectionId: collectionIdArg('Only this collection, e.g. "8250".'),
|
|
33
|
+
first: firstArg,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const FIELDS = 'collectionId agentAddress amount withdrawableAt updatedAt';
|
|
37
|
+
|
|
38
|
+
const node = z.looseObject({
|
|
39
|
+
collectionId: str,
|
|
40
|
+
agentAddress: str,
|
|
41
|
+
amount: z.unknown().optional(),
|
|
42
|
+
withdrawableAt: opt,
|
|
43
|
+
updatedAt: opt,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A deposit's balance (x/claims keeps it as coins): `coin` when it holds
|
|
48
|
+
* one denom, `coins` when several, `"none"` when the record holds none.
|
|
49
|
+
* An amount that is no coin is left out.
|
|
50
|
+
*/
|
|
51
|
+
async function balanceOf(
|
|
52
|
+
ctx: KindContext,
|
|
53
|
+
amount: unknown,
|
|
54
|
+
): Promise<{ coin?: DisplayCoin | 'none'; coins?: DisplayCoin[] }> {
|
|
55
|
+
const shown = await displayCoins(ctx, chainCoins(amount));
|
|
56
|
+
const [only] = shown;
|
|
57
|
+
if (only && shown.length === 1) return { coin: only };
|
|
58
|
+
if (shown.length) return { coins: shown };
|
|
59
|
+
return Array.isArray(amount) && amount.length === 0 ? { coin: 'none' } : {};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function depositOf(
|
|
63
|
+
ctx: KindContext,
|
|
64
|
+
row: z.infer<typeof node>,
|
|
65
|
+
now: number,
|
|
66
|
+
) {
|
|
67
|
+
return compact({
|
|
68
|
+
collectionId: idText(row.collectionId),
|
|
69
|
+
agent: idText(row.agentAddress),
|
|
70
|
+
...(await balanceOf(ctx, row.amount)),
|
|
71
|
+
withdrawableAt: idText(row.withdrawableAt),
|
|
72
|
+
lockEnded: reached(row.withdrawableAt, now),
|
|
73
|
+
updatedAt: idText(row.updatedAt),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const agentDeposits = defineKind({
|
|
78
|
+
name: 'agent_deposits',
|
|
79
|
+
summary:
|
|
80
|
+
'Performance deposits an agent (default: the user) holds in claim collections, and when each can be withdrawn.',
|
|
81
|
+
explain: [
|
|
82
|
+
'A performance deposit is stake an agent (a service agent, an evaluator or a disputer) puts into a collection’s escrow so it has something to lose: a dispute the agent loses is paid out of it.',
|
|
83
|
+
'Each: collectionId, agent, `coin` (the balance; `coins` when it holds several denoms, `"none"` when empty), withdrawableAt and `lockEnded`.',
|
|
84
|
+
'Every top-up moves `withdrawableAt` to at least now plus the collection’s minimum deposit period; a collection without one has no lock (a zero date).',
|
|
85
|
+
'`lockEnded: true` means that time has passed. Withdrawing also needs no open dispute against the agent in that collection (see `disputes`), which this record does not show.',
|
|
86
|
+
'A fully withdrawn or slashed deposit is removed, so it no longer appears. Ordered by collection id as text.',
|
|
87
|
+
COINS_NOTE,
|
|
88
|
+
].join(' '),
|
|
89
|
+
source: 'blocksync',
|
|
90
|
+
list: true,
|
|
91
|
+
args,
|
|
92
|
+
example: {},
|
|
93
|
+
plan(input, ctx) {
|
|
94
|
+
const agent = accountFor(input.address, ctx.identity, 'address');
|
|
95
|
+
if ('invalid' in agent) return agent;
|
|
96
|
+
const n = ctx.first;
|
|
97
|
+
const filter = literal({
|
|
98
|
+
agentAddress: { equalTo: agent.address },
|
|
99
|
+
...(input.collectionId !== undefined
|
|
100
|
+
? { collectionId: { equalTo: input.collectionId } }
|
|
101
|
+
: {}),
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
source: 'blocksync',
|
|
105
|
+
field: (alias) =>
|
|
106
|
+
`${alias}: agentDepositBalances(filter: ${filter}, first: ${n}, orderBy: PRIMARY_KEY_ASC) { ${PAGE} nodes { ${FIELDS} } }`,
|
|
107
|
+
async shape(raw): Promise<KindResult> {
|
|
108
|
+
const parsed = connection(node).safeParse(raw);
|
|
109
|
+
if (!parsed.success) return { ok: false, ...unexpected };
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
return listResult(
|
|
112
|
+
{
|
|
113
|
+
items: await Promise.all(
|
|
114
|
+
parsed.data.nodes.map((row) => depositOf(ctx, row, now)),
|
|
115
|
+
),
|
|
116
|
+
more: parsed.data.pageInfo?.hasNextPage ? true : undefined,
|
|
117
|
+
},
|
|
118
|
+
n,
|
|
119
|
+
agent.subject,
|
|
120
|
+
);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
});
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dao_proposals`: the proposals of one DAO, or of every DAO an account
|
|
3
|
+
* belongs to (default: the user), each with that account's vote.
|
|
4
|
+
*
|
|
5
|
+
* One alias either way: one DAO is `daoCore(address)`; the account's DAOs
|
|
6
|
+
* are its memberships (see `daos.ts`) inside Blocksync's nested `query`
|
|
7
|
+
* field. From a DAO core every hop is a relation on an indexed column:
|
|
8
|
+
* `idx_dao_proposal_module_dao` (proposal modules of a DAO), the
|
|
9
|
+
* `dao_proposal` primary key ("proposal_module", "id") and the `dao_vote`
|
|
10
|
+
* primary key ("proposal_module", "proposal_id", "voter"). The `status`
|
|
11
|
+
* filter leads no index; it only narrows one proposal module's
|
|
12
|
+
* proposals. Each module's proposals are read newest id first; the
|
|
13
|
+
* answer orders them all by start height, newest first.
|
|
14
|
+
*
|
|
15
|
+
* Votes cast before the DAO DAO indexing cutoff (the first height whose
|
|
16
|
+
* contract state the archive node can query; ixo-blocksync
|
|
17
|
+
* `src/constants/daodao_cutoff.ts`) are not indexed, so a missing vote
|
|
18
|
+
* on a proposal that started before it is unknown, never "not voted".
|
|
19
|
+
*/
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import type { ChainCall } from '../../http';
|
|
22
|
+
import { isBech32, literal, type Subject } from '../../queries';
|
|
23
|
+
import { isRecord } from '../../summaries';
|
|
24
|
+
import { defineKind, type KindResult } from '../types';
|
|
25
|
+
import {
|
|
26
|
+
daosData,
|
|
27
|
+
membershipFields,
|
|
28
|
+
membershipSchema,
|
|
29
|
+
membershipsOf,
|
|
30
|
+
moreMemberships,
|
|
31
|
+
} from './daos';
|
|
32
|
+
import { accountArg, firstArg } from '../../queries';
|
|
33
|
+
import { text } from '../../summaries';
|
|
34
|
+
import {
|
|
35
|
+
CONTRACT_ADDRESS,
|
|
36
|
+
PAGE,
|
|
37
|
+
accountFor,
|
|
38
|
+
amountText,
|
|
39
|
+
compact,
|
|
40
|
+
connection,
|
|
41
|
+
equalTo,
|
|
42
|
+
idText,
|
|
43
|
+
listResult,
|
|
44
|
+
num,
|
|
45
|
+
opt,
|
|
46
|
+
str,
|
|
47
|
+
unexpected,
|
|
48
|
+
} from './shared';
|
|
49
|
+
|
|
50
|
+
/** First height whose DAO DAO votes Blocksync indexes, per network. */
|
|
51
|
+
const DAODAO_VOTES_FROM: Record<ChainCall['network'], number> = {
|
|
52
|
+
mainnet: 9_269_290,
|
|
53
|
+
testnet: 9_284_120,
|
|
54
|
+
devnet: 5_251_750,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Proposal modules read per DAO (single choice, multiple choice…). */
|
|
58
|
+
const MODULES_PER_DAO = 3;
|
|
59
|
+
/** DAOs read per kind of membership when no DAO is named. */
|
|
60
|
+
const DAOS_PER_MEMBERSHIP = 3;
|
|
61
|
+
|
|
62
|
+
const args = z.strictObject({
|
|
63
|
+
dao: z
|
|
64
|
+
.string()
|
|
65
|
+
.regex(
|
|
66
|
+
CONTRACT_ADDRESS,
|
|
67
|
+
'a DAO core contract address (ixo1…, 62 characters)',
|
|
68
|
+
)
|
|
69
|
+
.optional()
|
|
70
|
+
.describe(
|
|
71
|
+
"A DAO's core contract address. Default: every DAO of `address`.",
|
|
72
|
+
),
|
|
73
|
+
address: accountArg('Member whose DAOs and votes are read'),
|
|
74
|
+
status: z.enum(['open', 'all']).optional().describe('open (default) or all.'),
|
|
75
|
+
first: firstArg,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const voteNode = z.looseObject({ vote: opt, power: num, votedAt: opt });
|
|
79
|
+
const proposalNode = z.looseObject({
|
|
80
|
+
id: z.union([z.string(), z.number()]),
|
|
81
|
+
title: opt,
|
|
82
|
+
status: opt,
|
|
83
|
+
proposer: opt,
|
|
84
|
+
startHeight: z.number().nullish(),
|
|
85
|
+
expiration: z.unknown().optional(),
|
|
86
|
+
totalPower: opt,
|
|
87
|
+
votes: z.unknown().optional(),
|
|
88
|
+
daoVotesByProposalModuleAndProposalId: z
|
|
89
|
+
.object({ nodes: z.array(voteNode) })
|
|
90
|
+
.optional(),
|
|
91
|
+
});
|
|
92
|
+
const moduleNode = z.looseObject({
|
|
93
|
+
address: str,
|
|
94
|
+
prefix: opt,
|
|
95
|
+
daoProposalsByProposalModule: connection(proposalNode),
|
|
96
|
+
});
|
|
97
|
+
const coreNode = z.looseObject({
|
|
98
|
+
address: str,
|
|
99
|
+
name: opt,
|
|
100
|
+
daoProposalModulesByDaoAddress: connection(moduleNode),
|
|
101
|
+
});
|
|
102
|
+
type Core = z.infer<typeof coreNode>;
|
|
103
|
+
const membershipsAnswer = membershipSchema(coreNode);
|
|
104
|
+
|
|
105
|
+
/** cw-utils `Expiration`: `{ at_time: "<ns>" }`, `{ at_height: n }` or `{ never: {} }`. */
|
|
106
|
+
function expirationOf(value: unknown, now: number) {
|
|
107
|
+
if (!isRecord(value)) return {};
|
|
108
|
+
if (typeof value.at_time === 'string' && /^\d{1,20}$/.test(value.at_time)) {
|
|
109
|
+
const ms = Number(BigInt(value.at_time) / 1_000_000n);
|
|
110
|
+
return {
|
|
111
|
+
votingEndsAt: new Date(ms).toISOString(),
|
|
112
|
+
votingEnded: ms <= now ? true : undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (typeof value.at_height === 'number')
|
|
116
|
+
return { votingEndsAtHeight: value.at_height };
|
|
117
|
+
if ('never' in value) return { votingEndsAt: 'never' };
|
|
118
|
+
return {};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** A single-choice tally (`{ yes, no, abstain }` weights); undefined for any other shape. */
|
|
122
|
+
function tallyOf(value: unknown) {
|
|
123
|
+
if (!isRecord(value)) return undefined;
|
|
124
|
+
const yes = amountText(value.yes);
|
|
125
|
+
const no = amountText(value.no);
|
|
126
|
+
const abstain = amountText(value.abstain);
|
|
127
|
+
return yes && no && abstain ? { yes, no, abstain } : undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
type Row = { startHeight: number; id: string; item: Record<string, unknown> };
|
|
131
|
+
|
|
132
|
+
function rowsOf(
|
|
133
|
+
core: Core,
|
|
134
|
+
voter: string | undefined,
|
|
135
|
+
votesFrom: number,
|
|
136
|
+
now: number,
|
|
137
|
+
): { rows: Row[]; more: boolean } {
|
|
138
|
+
const rows: Row[] = [];
|
|
139
|
+
const modules = core.daoProposalModulesByDaoAddress;
|
|
140
|
+
let more = modules.pageInfo?.hasNextPage === true;
|
|
141
|
+
for (const module of modules.nodes) {
|
|
142
|
+
const proposals = module.daoProposalsByProposalModule;
|
|
143
|
+
if (proposals.pageInfo?.hasNextPage) more = true;
|
|
144
|
+
for (const proposal of proposals.nodes) {
|
|
145
|
+
const id = String(proposal.id);
|
|
146
|
+
const vote = proposal.daoVotesByProposalModuleAndProposalId?.nodes[0];
|
|
147
|
+
const indexedFromStart =
|
|
148
|
+
typeof proposal.startHeight === 'number' &&
|
|
149
|
+
proposal.startHeight >= votesFrom;
|
|
150
|
+
rows.push({
|
|
151
|
+
startHeight: proposal.startHeight ?? -1,
|
|
152
|
+
id,
|
|
153
|
+
item: compact({
|
|
154
|
+
dao: idText(core.address),
|
|
155
|
+
daoName: text(core.name),
|
|
156
|
+
id: idText(id),
|
|
157
|
+
label: idText(`${module.prefix ?? ''}${id}`),
|
|
158
|
+
proposalModule: idText(module.address),
|
|
159
|
+
title: text(proposal.title),
|
|
160
|
+
status: text(proposal.status, 40),
|
|
161
|
+
...expirationOf(proposal.expiration, now),
|
|
162
|
+
proposer: idText(proposal.proposer),
|
|
163
|
+
tally: tallyOf(proposal.votes),
|
|
164
|
+
totalPower: amountText(proposal.totalPower),
|
|
165
|
+
yourVote: vote
|
|
166
|
+
? compact({
|
|
167
|
+
vote: text(vote.vote, 40),
|
|
168
|
+
power: amountText(vote.power),
|
|
169
|
+
votedAt: idText(vote.votedAt),
|
|
170
|
+
})
|
|
171
|
+
: undefined,
|
|
172
|
+
notVoted: voter && !vote && indexedFromStart ? true : undefined,
|
|
173
|
+
voteUnknown: voter && !vote && !indexedFromStart ? true : undefined,
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { rows, more };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The selection set of a DAO core: its name (and description when
|
|
183
|
+
* `described`), its proposal modules and each one's newest `n` proposals
|
|
184
|
+
* (open ones only when `open`), with `voter`'s vote.
|
|
185
|
+
*/
|
|
186
|
+
function coreFields(
|
|
187
|
+
n: number,
|
|
188
|
+
open: boolean,
|
|
189
|
+
voter: string | undefined,
|
|
190
|
+
described = false,
|
|
191
|
+
) {
|
|
192
|
+
const filter = open
|
|
193
|
+
? `filter: ${literal({ status: { equalTo: 'open' } })}, `
|
|
194
|
+
: '';
|
|
195
|
+
const votes = voter
|
|
196
|
+
? ` daoVotesByProposalModuleAndProposalId(filter: ${equalTo('voter', voter)}, first: 1) { nodes { vote power votedAt } }`
|
|
197
|
+
: '';
|
|
198
|
+
return `address name${described ? ' description' : ''} daoProposalModulesByDaoAddress(first: ${MODULES_PER_DAO}, orderBy: PRIMARY_KEY_ASC) { ${PAGE} nodes { address prefix daoProposalsByProposalModule(${filter}first: ${n}, orderBy: PRIMARY_KEY_DESC) { ${PAGE} nodes { id title status proposer startHeight expiration totalPower votes${votes} } } } }`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Rows by start height, newest first (a proposal without one last), then by id. */
|
|
202
|
+
const byRecency = (a: Row, b: Row) =>
|
|
203
|
+
b.startHeight - a.startHeight ||
|
|
204
|
+
b.id.localeCompare(a.id, 'en', { numeric: true });
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The answer's data from the DAO cores read (each DAO once): every
|
|
208
|
+
* proposal newest start first, cut to `n`, and `more` when there are more
|
|
209
|
+
* proposals or DAOs (`moreDaos`).
|
|
210
|
+
*/
|
|
211
|
+
function proposalsData(
|
|
212
|
+
cores: Core[],
|
|
213
|
+
moreDaos: boolean,
|
|
214
|
+
voter: string | undefined,
|
|
215
|
+
votesFrom: number,
|
|
216
|
+
n: number,
|
|
217
|
+
): Record<string, unknown> {
|
|
218
|
+
const now = Date.now();
|
|
219
|
+
const seen = new Set<string>();
|
|
220
|
+
const rows: Row[] = [];
|
|
221
|
+
let more = moreDaos;
|
|
222
|
+
for (const entry of cores) {
|
|
223
|
+
if (seen.has(entry.address)) continue;
|
|
224
|
+
seen.add(entry.address);
|
|
225
|
+
const read = rowsOf(entry, voter, votesFrom, now);
|
|
226
|
+
rows.push(...read.rows);
|
|
227
|
+
if (read.more) more = true;
|
|
228
|
+
}
|
|
229
|
+
rows.sort(byRecency);
|
|
230
|
+
return compact({
|
|
231
|
+
items: rows.slice(0, n).map((row) => row.item),
|
|
232
|
+
more: more || rows.length > n ? true : undefined,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export const daoProposals = defineKind({
|
|
237
|
+
name: 'dao_proposals',
|
|
238
|
+
summary:
|
|
239
|
+
"Proposals of a DAO, or of the user's DAOs, open by default, with the user's vote on each.",
|
|
240
|
+
explain: [
|
|
241
|
+
'A DAO proposal is a set of actions the DAO takes if members vote it through before `votingEndsAt`; members vote yes, no or abstain (or an option number on a multiple-choice proposal) with their voting power.',
|
|
242
|
+
'Each: dao, daoName, id and `label` (the DAO’s prefix and id, e.g. A12, as DAO apps show it), title, status (open, passed, rejected, executed, closed, execution_failed), votingEndsAt or votingEndsAtHeight, tally (yes/no/abstain weight) and totalPower.',
|
|
243
|
+
'`votingEnded: true` on an `open` proposal: its voting time is over and the index keeps `open` until it is executed or closed, so it may already have passed or failed.',
|
|
244
|
+
'`yourVote` (vote, power, votedAt), or `notVoted: true`, or `voteUnknown: true` when the proposal started before the index kept votes (votes cast before then are missing).',
|
|
245
|
+
'Without `dao`, the DAOs of `address` (default: the user), at most 3 per kind of membership (see `daos`); `more: true` when there are more proposals or DAOs. Newest start first.',
|
|
246
|
+
'Titles are written by the proposer and DAO names by the DAO: what they claim, never an endorsement or a verified fact.',
|
|
247
|
+
].join(' '),
|
|
248
|
+
source: 'blocksync',
|
|
249
|
+
list: true,
|
|
250
|
+
args,
|
|
251
|
+
example: { status: 'open' },
|
|
252
|
+
plan(input, ctx) {
|
|
253
|
+
const n = ctx.first;
|
|
254
|
+
const open = (input.status ?? 'open') === 'open';
|
|
255
|
+
const votesFrom = DAODAO_VOTES_FROM[ctx.call.network];
|
|
256
|
+
const answer = (
|
|
257
|
+
cores: Core[],
|
|
258
|
+
moreDaos: boolean,
|
|
259
|
+
voter: string | undefined,
|
|
260
|
+
subject: Subject | undefined,
|
|
261
|
+
): KindResult =>
|
|
262
|
+
listResult(
|
|
263
|
+
proposalsData(cores, moreDaos, voter, votesFrom, n),
|
|
264
|
+
n,
|
|
265
|
+
subject,
|
|
266
|
+
);
|
|
267
|
+
const { dao } = input;
|
|
268
|
+
if (dao !== undefined) {
|
|
269
|
+
if (!isBech32(dao, 'ixo'))
|
|
270
|
+
return {
|
|
271
|
+
invalid:
|
|
272
|
+
'dao is not a valid ixo1… address: its checksum does not match. Check it for a typo.',
|
|
273
|
+
};
|
|
274
|
+
// One DAO's proposals need no account: without one (a user with no
|
|
275
|
+
// IXO address, none given) they are read without votes.
|
|
276
|
+
const member =
|
|
277
|
+
input.address === undefined && !ctx.identity.address
|
|
278
|
+
? undefined
|
|
279
|
+
: accountFor(input.address, ctx.identity, 'address');
|
|
280
|
+
if (member && 'invalid' in member) return member;
|
|
281
|
+
const core = coreFields(n, open, member?.address);
|
|
282
|
+
return {
|
|
283
|
+
source: 'blocksync',
|
|
284
|
+
field: (alias) =>
|
|
285
|
+
`${alias}: daoCore(address: ${literal(dao)}) { ${core} }`,
|
|
286
|
+
shape(raw): KindResult {
|
|
287
|
+
if (raw === null)
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
reason: 'not_found',
|
|
291
|
+
detail: 'No DAO with that address is indexed.',
|
|
292
|
+
retryable: false,
|
|
293
|
+
};
|
|
294
|
+
const parsed = coreNode.safeParse(raw);
|
|
295
|
+
if (!parsed.success) return { ok: false, ...unexpected };
|
|
296
|
+
return answer([parsed.data], false, member?.address, member?.subject);
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const member = accountFor(input.address, ctx.identity, 'address');
|
|
301
|
+
if ('invalid' in member) return member;
|
|
302
|
+
const subfields = membershipFields(
|
|
303
|
+
member.address,
|
|
304
|
+
DAOS_PER_MEMBERSHIP,
|
|
305
|
+
coreFields(n, open, member.address),
|
|
306
|
+
);
|
|
307
|
+
return {
|
|
308
|
+
source: 'blocksync',
|
|
309
|
+
field: (alias) => `${alias}: query { ${subfields} }`,
|
|
310
|
+
shape(raw): KindResult {
|
|
311
|
+
const parsed = membershipsAnswer.safeParse(raw);
|
|
312
|
+
if (!parsed.success) return { ok: false, ...unexpected };
|
|
313
|
+
return answer(
|
|
314
|
+
membershipsOf(parsed.data).map((entry) => entry.core),
|
|
315
|
+
moreMemberships(parsed.data),
|
|
316
|
+
member.address,
|
|
317
|
+
member.subject,
|
|
318
|
+
);
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const describedCore = coreNode.extend({ description: opt });
|
|
325
|
+
const describedMemberships = membershipSchema(describedCore);
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* The `daos` answer and the open `dao_proposals` answer of one account
|
|
329
|
+
* from ONE membership read, each DAO core read once with its description
|
|
330
|
+
* and its open proposals: what `get_account_overview` asks for its DAO
|
|
331
|
+
* section instead of both kinds (which would each read the memberships).
|
|
332
|
+
* `first` is the DAOs per kind of membership and the proposals per
|
|
333
|
+
* proposal module, as in each kind. Not a `chain_lookup` kind.
|
|
334
|
+
*/
|
|
335
|
+
export const daosWithOpenProposals = defineKind({
|
|
336
|
+
name: 'daos_with_open_proposals',
|
|
337
|
+
summary:
|
|
338
|
+
"An account's DAOs and their open proposals with its votes, from one membership read.",
|
|
339
|
+
explain:
|
|
340
|
+
'The `daos` answer as `daos` and the `dao_proposals` answer as `proposals`.',
|
|
341
|
+
source: 'blocksync',
|
|
342
|
+
list: true,
|
|
343
|
+
args: z.strictObject({ address: accountArg('Member'), first: firstArg }),
|
|
344
|
+
example: {},
|
|
345
|
+
plan(input, ctx) {
|
|
346
|
+
const member = accountFor(input.address, ctx.identity, 'address');
|
|
347
|
+
if ('invalid' in member) return member;
|
|
348
|
+
const n = ctx.first;
|
|
349
|
+
const votesFrom = DAODAO_VOTES_FROM[ctx.call.network];
|
|
350
|
+
const subfields = membershipFields(
|
|
351
|
+
member.address,
|
|
352
|
+
n,
|
|
353
|
+
coreFields(n, true, member.address, true),
|
|
354
|
+
);
|
|
355
|
+
return {
|
|
356
|
+
source: 'blocksync',
|
|
357
|
+
field: (alias) => `${alias}: query { ${subfields} }`,
|
|
358
|
+
async shape(raw): Promise<KindResult> {
|
|
359
|
+
const parsed = describedMemberships.safeParse(raw);
|
|
360
|
+
if (!parsed.success) return { ok: false, ...unexpected };
|
|
361
|
+
return {
|
|
362
|
+
ok: true,
|
|
363
|
+
data: {
|
|
364
|
+
daos: await daosData(ctx, parsed.data, n),
|
|
365
|
+
proposals: proposalsData(
|
|
366
|
+
membershipsOf(parsed.data).map((entry) => entry.core),
|
|
367
|
+
moreMemberships(parsed.data),
|
|
368
|
+
member.address,
|
|
369
|
+
votesFrom,
|
|
370
|
+
n,
|
|
371
|
+
),
|
|
372
|
+
},
|
|
373
|
+
first: n,
|
|
374
|
+
subject: member.subject,
|
|
375
|
+
};
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
},
|
|
379
|
+
});
|