@ixo/oracle-runtime-workers 0.29.1 → 0.31.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 +7 -5
- package/src/attachments/channel-links.ts +135 -0
- package/src/attachments/channel.ts +203 -0
- package/src/attachments/download.ts +23 -6
- package/src/attachments/index.ts +23 -0
- package/src/attachments/pipeline.ts +120 -42
- package/src/attachments/retention.ts +11 -3
- package/src/attachments/sanitize.ts +20 -8
- package/src/attachments/types.ts +7 -0
- package/src/attachments/view.ts +24 -7
- package/src/channels/contract.ts +69 -0
- package/src/channels/turns.ts +23 -1
- package/src/core/main-agent-types.ts +2 -0
- package/src/core/runtime-context.ts +3 -0
- package/src/delivery/plan.ts +3 -1
- package/src/do/contracts.ts +3 -0
- package/src/do/turn-attachments.ts +103 -0
- package/src/do/user-oracle-do.ts +142 -42
- package/src/hub-actions/client.ts +87 -7
- package/src/hub-actions/hub-text.ts +82 -0
- package/src/hub-actions/request.ts +7 -4
- package/src/hub-actions/store.ts +5 -1
- package/src/hub-actions/transfer.ts +23 -6
- package/src/index.ts +1 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +43 -2
- package/src/plugins/attachments/view-attachment-tool.ts +7 -3
- package/src/plugins/hub-actions/hub-actions.plugin.ts +96 -17
- package/src/plugins/ixo-chain/balances.ts +141 -0
- package/src/plugins/ixo-chain/blocksync.ts +17 -8
- package/src/plugins/ixo-chain/denoms.ts +84 -36
- package/src/plugins/ixo-chain/http.ts +315 -70
- 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 +135 -54
- package/src/plugins/ixo-chain/summaries.ts +3 -2
- package/src/plugins/yoma/catalog.ts +780 -0
- package/src/plugins/yoma/clef.ts +98 -0
- package/src/plugins/yoma/drafts.ts +339 -0
- package/src/plugins/yoma/engine.ts +926 -0
- package/src/plugins/yoma/index.ts +13 -0
- package/src/plugins/yoma/judge.ts +358 -0
- package/src/plugins/yoma/permission.ts +62 -0
- package/src/plugins/yoma/reads.ts +195 -0
- package/src/plugins/yoma/submission.ts +534 -0
- package/src/plugins/yoma/text.ts +16 -0
- package/src/plugins/yoma/work.ts +190 -0
- package/src/plugins/yoma/yoma.plugin.ts +2162 -0
- package/src/sqlite/serialization.ts +2 -0
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fee_grants` and `authz_grants`: x/feegrant allowances and x/authz
|
|
3
|
+
* grants an account received or gave, each with a plain label of what it
|
|
4
|
+
* allows. One REST read per (kind, direction, account) of
|
|
5
|
+
* {@link GRANT_PAGE} rows, kept per isolate for {@link GRANTS_TTL_MS}
|
|
6
|
+
* (shared by calls asking the same at once); the total is never counted
|
|
7
|
+
* (`count_total` makes the node walk every match). An empty amount list is
|
|
8
|
+
* shown by what the chain does with it: "unlimited", "any" or "none
|
|
9
|
+
* allowed", never as an empty list.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import type { ChainCall } from '../../http';
|
|
13
|
+
import { defineKind, type KindContext, type KindResult } from '../types';
|
|
14
|
+
import { displayCoin, displayCoins, type DisplayCoin } from '../coins';
|
|
15
|
+
import { clip } from '../../summaries';
|
|
16
|
+
import {
|
|
17
|
+
ReadCache,
|
|
18
|
+
accountFor,
|
|
19
|
+
coinsOr,
|
|
20
|
+
addressArg,
|
|
21
|
+
coinSchema,
|
|
22
|
+
coinsSchema,
|
|
23
|
+
directionArg,
|
|
24
|
+
durationText,
|
|
25
|
+
expiryOf,
|
|
26
|
+
failed,
|
|
27
|
+
firstArg,
|
|
28
|
+
isPast,
|
|
29
|
+
listText,
|
|
30
|
+
setDuration,
|
|
31
|
+
shortType,
|
|
32
|
+
} from './shared';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Granters named by who runs them, per network. Mainnet
|
|
36
|
+
* `ixo1vafr2…tey6` is the IXO fee-grant service behind feegrant.ixo.world:
|
|
37
|
+
* on Oct 29 2026 it had issued 15,573 allowances, among them the fee
|
|
38
|
+
* grants of the pay-convert oracle setup's jambo and oracle accounts.
|
|
39
|
+
*/
|
|
40
|
+
export const KNOWN_GRANTERS: Record<
|
|
41
|
+
ChainCall['network'],
|
|
42
|
+
Readonly<Record<string, string>>
|
|
43
|
+
> = {
|
|
44
|
+
mainnet: {
|
|
45
|
+
ixo1vafr2dqhgz8frc7gf22njz8y2u0fue4kuetey6:
|
|
46
|
+
'IXO fee-grant service (feegrant.ixo.world)',
|
|
47
|
+
},
|
|
48
|
+
testnet: {},
|
|
49
|
+
devnet: {},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** Messages named per allowance or authorization; the rest are counted. */
|
|
53
|
+
const NAMED_MESSAGES = 10;
|
|
54
|
+
/** Constraints shown per claims authorization; the rest are counted. */
|
|
55
|
+
const CONSTRAINT_CAP = 5;
|
|
56
|
+
/** Grants read per request: the most rows a request may ask for. */
|
|
57
|
+
const GRANT_PAGE = 20;
|
|
58
|
+
/** A grants answer is reused for 30 s. */
|
|
59
|
+
export const GRANTS_TTL_MS = 30_000;
|
|
60
|
+
/** Answers kept per grant kind. */
|
|
61
|
+
const GRANTS_CACHE_LIMIT = 500;
|
|
62
|
+
|
|
63
|
+
const args = z.strictObject({
|
|
64
|
+
direction: directionArg,
|
|
65
|
+
address: addressArg,
|
|
66
|
+
first: firstArg,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const pagination = z.object({ next_key: z.string().nullish() }).nullish();
|
|
70
|
+
|
|
71
|
+
const anyTyped = z.looseObject({ '@type': z.string() });
|
|
72
|
+
|
|
73
|
+
/** The paging every grants read asks for. */
|
|
74
|
+
const PAGE = `pagination.limit=${GRANT_PAGE}`;
|
|
75
|
+
|
|
76
|
+
/** Whether rows exist past the first `first` of a page. */
|
|
77
|
+
const moreThan = (
|
|
78
|
+
rows: readonly unknown[],
|
|
79
|
+
value: z.infer<typeof pagination>,
|
|
80
|
+
first: number,
|
|
81
|
+
) => rows.length > first || !!value?.next_key;
|
|
82
|
+
|
|
83
|
+
/* ── fee grants ───────────────────────────────────────────────────────── */
|
|
84
|
+
|
|
85
|
+
const allowancesResponse = z.object({
|
|
86
|
+
allowances: z.array(
|
|
87
|
+
z.object({ granter: z.string(), grantee: z.string(), allowance: anyTyped }),
|
|
88
|
+
),
|
|
89
|
+
pagination,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const BASIC = '/cosmos.feegrant.v1beta1.BasicAllowance';
|
|
93
|
+
const PERIODIC = '/cosmos.feegrant.v1beta1.PeriodicAllowance';
|
|
94
|
+
const ALLOWED_MSG = '/cosmos.feegrant.v1beta1.AllowedMsgAllowance';
|
|
95
|
+
|
|
96
|
+
const basicAllowance = z.object({
|
|
97
|
+
spend_limit: coinsSchema.default([]),
|
|
98
|
+
expiration: z.string().nullish(),
|
|
99
|
+
});
|
|
100
|
+
const periodicAllowance = z.object({
|
|
101
|
+
basic: basicAllowance,
|
|
102
|
+
period: z.string(),
|
|
103
|
+
period_spend_limit: coinsSchema.default([]),
|
|
104
|
+
period_can_spend: coinsSchema.default([]),
|
|
105
|
+
period_reset: z.string().nullish(),
|
|
106
|
+
});
|
|
107
|
+
const allowedMsgAllowance = z.object({
|
|
108
|
+
allowance: anyTyped,
|
|
109
|
+
allowed_messages: z.array(z.string()).default([]),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const feeGrantAnswers = new ReadCache<z.infer<typeof allowancesResponse>>(
|
|
113
|
+
GRANTS_TTL_MS,
|
|
114
|
+
GRANTS_CACHE_LIMIT,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const ALLOWANCE_LABEL: Record<string, string> = {
|
|
118
|
+
BasicAllowance: 'one budget until it expires',
|
|
119
|
+
PeriodicAllowance: 'refills every period',
|
|
120
|
+
AllowedMsgAllowance: 'only for listed actions',
|
|
121
|
+
};
|
|
122
|
+
const OTHER_ALLOWANCE = 'an allowance type this reader does not know';
|
|
123
|
+
|
|
124
|
+
/** A coin limit, or what the chain does when the list is empty. */
|
|
125
|
+
type Limit<Empty extends string> = DisplayCoin[] | Empty;
|
|
126
|
+
|
|
127
|
+
type AllowanceTerms = {
|
|
128
|
+
spendLimit: Limit<'unlimited'>;
|
|
129
|
+
period?: {
|
|
130
|
+
every: string;
|
|
131
|
+
limit: Limit<'none allowed'>;
|
|
132
|
+
canSpendThisPeriod: { coins: DisplayCoin[]; nothingLeftThisPeriod?: true };
|
|
133
|
+
resetAt: string | null;
|
|
134
|
+
/** The reset time has passed: the full period limit is available on the next use. */
|
|
135
|
+
resetDue?: true;
|
|
136
|
+
};
|
|
137
|
+
expiration: string | null;
|
|
138
|
+
expired: boolean;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/** The terms of a Basic or Periodic allowance; null for any other type. */
|
|
142
|
+
async function termsOf(
|
|
143
|
+
ctx: KindContext,
|
|
144
|
+
allowance: z.infer<typeof anyTyped>,
|
|
145
|
+
): Promise<AllowanceTerms | null> {
|
|
146
|
+
if (allowance['@type'] === BASIC) {
|
|
147
|
+
const basic = basicAllowance.safeParse(allowance);
|
|
148
|
+
if (!basic.success) return null;
|
|
149
|
+
return {
|
|
150
|
+
spendLimit: await coinsOr(ctx, basic.data.spend_limit, 'unlimited'),
|
|
151
|
+
...expiryOf(basic.data.expiration),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (allowance['@type'] === PERIODIC) {
|
|
155
|
+
const periodic = periodicAllowance.safeParse(allowance);
|
|
156
|
+
if (!periodic.success) return null;
|
|
157
|
+
const { basic, period, period_reset: reset } = periodic.data;
|
|
158
|
+
// x/feegrant subtracts each fee from `period_can_spend`: an empty list
|
|
159
|
+
// is nothing left until the reset; the period limit refills it then
|
|
160
|
+
// (an empty one, which ValidateBasic refuses, would refill nothing).
|
|
161
|
+
const left = await displayCoins(ctx, periodic.data.period_can_spend);
|
|
162
|
+
return {
|
|
163
|
+
spendLimit: await coinsOr(ctx, basic.spend_limit, 'unlimited'),
|
|
164
|
+
period: {
|
|
165
|
+
every: durationText(period),
|
|
166
|
+
limit: await coinsOr(
|
|
167
|
+
ctx,
|
|
168
|
+
periodic.data.period_spend_limit,
|
|
169
|
+
'none allowed',
|
|
170
|
+
),
|
|
171
|
+
canSpendThisPeriod: left.length
|
|
172
|
+
? { coins: left }
|
|
173
|
+
: { coins: [], nothingLeftThisPeriod: true },
|
|
174
|
+
resetAt: reset ?? null,
|
|
175
|
+
...(reset && isPast(reset) ? { resetDue: true } : {}),
|
|
176
|
+
},
|
|
177
|
+
...expiryOf(basic.expiration),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function feeGrantOf(
|
|
184
|
+
ctx: KindContext,
|
|
185
|
+
grant: z.infer<typeof allowancesResponse>['allowances'][number],
|
|
186
|
+
): Promise<Record<string, unknown>> {
|
|
187
|
+
const label = KNOWN_GRANTERS[ctx.call.network][grant.granter];
|
|
188
|
+
const head = {
|
|
189
|
+
granter: grant.granter,
|
|
190
|
+
...(label ? { granterLabel: label } : {}),
|
|
191
|
+
grantee: grant.grantee,
|
|
192
|
+
};
|
|
193
|
+
const type = shortType(grant.allowance['@type']);
|
|
194
|
+
if (grant.allowance['@type'] === ALLOWED_MSG) {
|
|
195
|
+
const wrapper = allowedMsgAllowance.safeParse(grant.allowance);
|
|
196
|
+
if (wrapper.success) {
|
|
197
|
+
const messages = wrapper.data.allowed_messages.map(shortType);
|
|
198
|
+
const inner = shortType(wrapper.data.allowance['@type']);
|
|
199
|
+
const terms = await termsOf(ctx, wrapper.data.allowance);
|
|
200
|
+
return {
|
|
201
|
+
...head,
|
|
202
|
+
type,
|
|
203
|
+
innerType: inner,
|
|
204
|
+
kindLabel: `${ALLOWANCE_LABEL.AllowedMsgAllowance}; ${ALLOWANCE_LABEL[inner] ?? OTHER_ALLOWANCE}`,
|
|
205
|
+
allowedMessages: messages.slice(0, NAMED_MESSAGES),
|
|
206
|
+
...(messages.length > NAMED_MESSAGES
|
|
207
|
+
? { moreAllowedMessages: messages.length - NAMED_MESSAGES }
|
|
208
|
+
: {}),
|
|
209
|
+
...(terms ?? { expiration: null, expired: false }),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const terms = await termsOf(ctx, grant.allowance);
|
|
214
|
+
return {
|
|
215
|
+
...head,
|
|
216
|
+
type,
|
|
217
|
+
kindLabel: (terms && ALLOWANCE_LABEL[type]) || OTHER_ALLOWANCE,
|
|
218
|
+
...(terms ?? { expiration: null, expired: false }),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export const feeGrantsKind = defineKind({
|
|
223
|
+
name: 'fee_grants',
|
|
224
|
+
summary:
|
|
225
|
+
"Fee grants an account received (someone pays its fees) or gave (it pays others').",
|
|
226
|
+
explain: [
|
|
227
|
+
"A fee grant (x/feegrant) lets the granter pay the grantee's transaction fees, within limits. It never gives the granter access to the grantee's funds or lets anyone act for the grantee: the grantee still signs every transaction, only the fee is taken from the granter.",
|
|
228
|
+
'Allowance types: BasicAllowance = one budget (`spendLimit`, "unlimited" when none is set) until it expires. PeriodicAllowance = refills every `period.every`: at most `period.limit` per period, `period.canSpendThisPeriod.coins` left now (`nothingLeftThisPeriod: true` = all of it is used until the reset), reset at `period.resetAt` (`resetDue`: the reset time has passed, so the full period limit is available again on the next use), all within the overall `spendLimit`. AllowedMsgAllowance = only for the listed actions (`allowedMessages`); its inner allowance (`innerType`) sets the budget and expiry, shown unwrapped.',
|
|
229
|
+
"`expiration` null = no expiry; `expired: true` = the grant can no longer be used. A granter can revoke a grant at any time and cannot change the grantee's account in any way.",
|
|
230
|
+
"`granterLabel` names a known granter (mainnet: the IXO fee-grant service, feegrant.ixo.world). `direction`: received = grants paying this account's fees; given = grants this account pays. `more` says rows were left out (there is no total count). Amounts are display units (`amount` + `symbol`) with `amountBaseUnits`.",
|
|
231
|
+
].join('\n'),
|
|
232
|
+
source: 'chain',
|
|
233
|
+
list: true,
|
|
234
|
+
args,
|
|
235
|
+
example: { direction: 'received' },
|
|
236
|
+
plan(input, ctx) {
|
|
237
|
+
const account = accountFor(ctx, input.address);
|
|
238
|
+
if ('invalid' in account) return account;
|
|
239
|
+
const direction = input.direction ?? 'received';
|
|
240
|
+
const route = direction === 'received' ? 'allowances' : 'issued';
|
|
241
|
+
return {
|
|
242
|
+
source: 'chain',
|
|
243
|
+
async run(): Promise<KindResult> {
|
|
244
|
+
const read = await feeGrantAnswers.get(
|
|
245
|
+
ctx,
|
|
246
|
+
`${direction}|${account.address}`,
|
|
247
|
+
() =>
|
|
248
|
+
ctx.rest(
|
|
249
|
+
`/cosmos/feegrant/v1beta1/${route}/${account.address}?${PAGE}`,
|
|
250
|
+
allowancesResponse,
|
|
251
|
+
{ label: `fee grants ${direction}` },
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
if (!read.ok) return failed(read.failure);
|
|
255
|
+
const { allowances, pagination: paging } = read.value;
|
|
256
|
+
const grants = await Promise.all(
|
|
257
|
+
allowances.slice(0, ctx.first).map((grant) => feeGrantOf(ctx, grant)),
|
|
258
|
+
);
|
|
259
|
+
return {
|
|
260
|
+
ok: true,
|
|
261
|
+
subject: account.subject,
|
|
262
|
+
first: ctx.first,
|
|
263
|
+
data: {
|
|
264
|
+
address: account.address,
|
|
265
|
+
direction,
|
|
266
|
+
more: moreThan(allowances, paging, ctx.first),
|
|
267
|
+
grants,
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
/* ── authz grants ─────────────────────────────────────────────────────── */
|
|
276
|
+
|
|
277
|
+
const grantsResponse = z.object({
|
|
278
|
+
grants: z.array(
|
|
279
|
+
z.object({
|
|
280
|
+
granter: z.string(),
|
|
281
|
+
grantee: z.string(),
|
|
282
|
+
authorization: anyTyped,
|
|
283
|
+
expiration: z.string().nullish(),
|
|
284
|
+
}),
|
|
285
|
+
),
|
|
286
|
+
pagination,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const authzAnswers = new ReadCache<z.infer<typeof grantsResponse>>(
|
|
290
|
+
GRANTS_TTL_MS,
|
|
291
|
+
GRANTS_CACHE_LIMIT,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const cw20Schema = z.object({ address: z.string(), amount: z.string() });
|
|
295
|
+
/** x/claims `CW1155Payment`: a contract, its token ids (none = any) and an amount in whole tokens. */
|
|
296
|
+
const cw1155Schema = z.object({
|
|
297
|
+
address: z.string(),
|
|
298
|
+
token_id: z.array(z.string()).default([]),
|
|
299
|
+
amount: z.string(),
|
|
300
|
+
});
|
|
301
|
+
const cw20List = z.array(cw20Schema).default([]);
|
|
302
|
+
const cw1155List = z.array(cw1155Schema).default([]);
|
|
303
|
+
const quota = z.string().optional();
|
|
304
|
+
const genericAuthorization = z.object({ msg: z.string() });
|
|
305
|
+
const sendAuthorization = z.object({
|
|
306
|
+
spend_limit: coinsSchema.default([]),
|
|
307
|
+
allow_list: z.array(z.string()).default([]),
|
|
308
|
+
});
|
|
309
|
+
const validators = z.object({ address: z.array(z.string()).default([]) });
|
|
310
|
+
const stakeAuthorization = z.object({
|
|
311
|
+
max_tokens: coinSchema.nullish(),
|
|
312
|
+
allow_list: validators.nullish(),
|
|
313
|
+
deny_list: validators.nullish(),
|
|
314
|
+
authorization_type: z.string(),
|
|
315
|
+
});
|
|
316
|
+
const submitClaimAuthorization = z.object({
|
|
317
|
+
admin: z.string(),
|
|
318
|
+
constraints: z.array(
|
|
319
|
+
z.object({
|
|
320
|
+
collection_id: z.string(),
|
|
321
|
+
agent_quota: z.string(),
|
|
322
|
+
max_amount: coinsSchema.default([]),
|
|
323
|
+
max_cw20_payment: cw20List,
|
|
324
|
+
max_cw1155_payment: cw1155List,
|
|
325
|
+
intent_duration_ns: z.string().nullish(),
|
|
326
|
+
member_address: z.string().optional(),
|
|
327
|
+
}),
|
|
328
|
+
),
|
|
329
|
+
});
|
|
330
|
+
const evaluateClaimAuthorization = z.object({
|
|
331
|
+
admin: z.string(),
|
|
332
|
+
constraints: z.array(
|
|
333
|
+
z.object({
|
|
334
|
+
collection_id: z.string(),
|
|
335
|
+
claim_ids: z.array(z.string()).default([]),
|
|
336
|
+
agent_quota: z.string(),
|
|
337
|
+
before_date: z.string().nullish(),
|
|
338
|
+
max_custom_amount: coinsSchema.default([]),
|
|
339
|
+
max_custom_cw20_payment: cw20List,
|
|
340
|
+
max_custom_cw1155_payment: cw1155List,
|
|
341
|
+
}),
|
|
342
|
+
),
|
|
343
|
+
});
|
|
344
|
+
const withdrawPaymentAuthorization = z.object({
|
|
345
|
+
admin: z.string(),
|
|
346
|
+
constraints: z.array(
|
|
347
|
+
z.object({
|
|
348
|
+
claim_id: z.string(),
|
|
349
|
+
payment_type: z.string().optional(),
|
|
350
|
+
release_date: z.string().nullish(),
|
|
351
|
+
toAddress: z.string().optional(),
|
|
352
|
+
}),
|
|
353
|
+
),
|
|
354
|
+
});
|
|
355
|
+
const createClaimAuthorizationAuthorization = z.object({
|
|
356
|
+
admin: z.string(),
|
|
357
|
+
constraints: z.array(
|
|
358
|
+
z.object({
|
|
359
|
+
max_authorizations: quota,
|
|
360
|
+
max_agent_quota: quota,
|
|
361
|
+
max_amount: coinsSchema.default([]),
|
|
362
|
+
max_cw20_payment: cw20List,
|
|
363
|
+
expiration: z.string().nullish(),
|
|
364
|
+
collection_ids: z.array(z.string()).default([]),
|
|
365
|
+
allowed_auth_types: z.string().optional(),
|
|
366
|
+
max_intent_duration_ns: z.string().nullish(),
|
|
367
|
+
max_cw1155_payment: cw1155List,
|
|
368
|
+
member_address: z.string().optional(),
|
|
369
|
+
}),
|
|
370
|
+
),
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const STAKE_ACTION: Record<string, string> = {
|
|
374
|
+
AUTHORIZATION_TYPE_DELEGATE: 'delegate',
|
|
375
|
+
AUTHORIZATION_TYPE_UNDELEGATE: 'undelegate',
|
|
376
|
+
AUTHORIZATION_TYPE_REDELEGATE: 'redelegate',
|
|
377
|
+
AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION: 'cancel unbonding',
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
/** x/claims `CreateClaimAuthorizationType`. */
|
|
381
|
+
const CREATE_TYPES: Record<string, string> = {
|
|
382
|
+
ALL: 'submit and evaluate',
|
|
383
|
+
SUBMIT: 'submit',
|
|
384
|
+
EVALUATE: 'evaluate',
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
/** "collection 340" / "collections 340, 341" / the fallback for none. */
|
|
388
|
+
function collectionsText(ids: readonly string[], none: string): string {
|
|
389
|
+
if (!ids.length) return none;
|
|
390
|
+
return `${ids.length === 1 ? 'collection' : 'collections'} ${listText(ids.map((id) => clip(id, 20)))}`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** A meta-authorization's cap: 0 means none is set. */
|
|
394
|
+
const quotaText = (value: string | undefined) =>
|
|
395
|
+
value === undefined || value === '0' ? 'no cap' : value;
|
|
396
|
+
|
|
397
|
+
/** At most {@link CONSTRAINT_CAP} constraints shaped, with how many were left out. */
|
|
398
|
+
async function constraintsOf<T>(
|
|
399
|
+
list: readonly T[],
|
|
400
|
+
shape: (item: T) => Promise<Record<string, unknown>>,
|
|
401
|
+
): Promise<{
|
|
402
|
+
constraints: Record<string, unknown>[];
|
|
403
|
+
moreConstraints?: number;
|
|
404
|
+
}> {
|
|
405
|
+
return {
|
|
406
|
+
constraints: await Promise.all(list.slice(0, CONSTRAINT_CAP).map(shape)),
|
|
407
|
+
...(list.length > CONSTRAINT_CAP
|
|
408
|
+
? { moreConstraints: list.length - CONSTRAINT_CAP }
|
|
409
|
+
: {}),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** `{ [key]: value }` when there is a value, else nothing. */
|
|
414
|
+
const optional = (key: string, value: string | null) =>
|
|
415
|
+
value === null ? {} : { [key]: value };
|
|
416
|
+
|
|
417
|
+
/** Token caps shown per list. */
|
|
418
|
+
const CAP_ROWS = 5;
|
|
419
|
+
const TOKEN_IDS = 5;
|
|
420
|
+
|
|
421
|
+
/** cw20 caps as shown (the token contract and its base units), or `empty`. */
|
|
422
|
+
function cw20Of<Empty extends string>(
|
|
423
|
+
caps: z.infer<typeof cw20Schema>[],
|
|
424
|
+
empty: Empty,
|
|
425
|
+
) {
|
|
426
|
+
return caps.length
|
|
427
|
+
? caps.slice(0, CAP_ROWS).map((cap) => ({
|
|
428
|
+
contract: clip(cap.address, 80),
|
|
429
|
+
amountBaseUnits: clip(cap.amount, 40),
|
|
430
|
+
}))
|
|
431
|
+
: empty;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* cw1155 caps as shown (the token contract, its token ids or "any", and
|
|
436
|
+
* the amount in whole tokens), or `empty`.
|
|
437
|
+
*/
|
|
438
|
+
function cw1155Of<Empty extends string>(
|
|
439
|
+
caps: z.infer<typeof cw1155Schema>[],
|
|
440
|
+
empty: Empty,
|
|
441
|
+
) {
|
|
442
|
+
return caps.length
|
|
443
|
+
? caps.slice(0, CAP_ROWS).map((cap) => ({
|
|
444
|
+
contract: clip(cap.address, 80),
|
|
445
|
+
tokenIds: cap.token_id.length
|
|
446
|
+
? cap.token_id.slice(0, TOKEN_IDS).map((id) => clip(id, 80))
|
|
447
|
+
: 'any',
|
|
448
|
+
...(cap.token_id.length > TOKEN_IDS
|
|
449
|
+
? { moreTokenIds: cap.token_id.length - TOKEN_IDS }
|
|
450
|
+
: {}),
|
|
451
|
+
amount: clip(cap.amount, 40),
|
|
452
|
+
}))
|
|
453
|
+
: empty;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** What a Submit or Evaluate constraint's empty caps mean: no custom payment of that kind. */
|
|
457
|
+
const NONE = 'none allowed';
|
|
458
|
+
/** What a CreateClaimAuthorization constraint's empty caps mean: the authorizations it creates may set any. */
|
|
459
|
+
const ANY = 'any';
|
|
460
|
+
|
|
461
|
+
/** What an authorization lets the grantee do: its short type, label and limits. */
|
|
462
|
+
async function authorizationOf(
|
|
463
|
+
ctx: KindContext,
|
|
464
|
+
authorization: z.infer<typeof anyTyped>,
|
|
465
|
+
): Promise<Record<string, unknown>> {
|
|
466
|
+
const type = shortType(authorization['@type']);
|
|
467
|
+
const unknown = {
|
|
468
|
+
type,
|
|
469
|
+
kindLabel: `may act for the granter as ${type} allows (a type this reader does not know)`,
|
|
470
|
+
};
|
|
471
|
+
switch (authorization['@type']) {
|
|
472
|
+
case '/cosmos.authz.v1beta1.GenericAuthorization': {
|
|
473
|
+
const parsed = genericAuthorization.safeParse(authorization);
|
|
474
|
+
if (!parsed.success) return unknown;
|
|
475
|
+
const message = shortType(parsed.data.msg);
|
|
476
|
+
return {
|
|
477
|
+
type,
|
|
478
|
+
message,
|
|
479
|
+
kindLabel: `may send ${message} for the granter`,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
case '/cosmos.bank.v1beta1.SendAuthorization': {
|
|
483
|
+
const parsed = sendAuthorization.safeParse(authorization);
|
|
484
|
+
if (!parsed.success) return unknown;
|
|
485
|
+
// ValidateBasic requires a positive limit; an empty one would let
|
|
486
|
+
// nothing be sent.
|
|
487
|
+
const limit = await coinsOr(ctx, parsed.data.spend_limit, NONE);
|
|
488
|
+
const allowList = parsed.data.allow_list;
|
|
489
|
+
return {
|
|
490
|
+
type,
|
|
491
|
+
kindLabel:
|
|
492
|
+
limit === NONE
|
|
493
|
+
? 'may send nothing from the granter'
|
|
494
|
+
: `may send up to ${limit.map((coin) => `${coin.amount} ${coin.symbol}`).join(' + ')} from the granter`,
|
|
495
|
+
spendLimit: limit,
|
|
496
|
+
...(allowList.length
|
|
497
|
+
? { onlyTo: allowList.slice(0, 5).map((a) => clip(a, 80)) }
|
|
498
|
+
: {}),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
case '/cosmos.staking.v1beta1.StakeAuthorization': {
|
|
502
|
+
const parsed = stakeAuthorization.safeParse(authorization);
|
|
503
|
+
if (!parsed.success) return unknown;
|
|
504
|
+
const action = STAKE_ACTION[parsed.data.authorization_type] ?? 'stake';
|
|
505
|
+
const max = parsed.data.max_tokens
|
|
506
|
+
? await displayCoin(ctx, parsed.data.max_tokens)
|
|
507
|
+
: null;
|
|
508
|
+
const allow = parsed.data.allow_list?.address ?? [];
|
|
509
|
+
const deny = parsed.data.deny_list?.address ?? [];
|
|
510
|
+
return {
|
|
511
|
+
type,
|
|
512
|
+
kindLabel: `may ${action} the granter's stake${max ? ` (up to ${max.amount} ${max.symbol})` : ''}`,
|
|
513
|
+
maxTokens: max ?? 'unlimited',
|
|
514
|
+
...(allow.length ? { onlyValidators: allow.slice(0, 5) } : {}),
|
|
515
|
+
...(deny.length ? { exceptValidators: deny.slice(0, 5) } : {}),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
case '/ixo.claims.v1beta1.SubmitClaimAuthorization': {
|
|
519
|
+
const parsed = submitClaimAuthorization.safeParse(authorization);
|
|
520
|
+
if (!parsed.success) return unknown;
|
|
521
|
+
const { constraints } = parsed.data;
|
|
522
|
+
return {
|
|
523
|
+
type,
|
|
524
|
+
kindLabel: `may submit claims in ${collectionsText(
|
|
525
|
+
constraints.map((c) => c.collection_id),
|
|
526
|
+
'no collection',
|
|
527
|
+
)}`,
|
|
528
|
+
admin: parsed.data.admin,
|
|
529
|
+
...(await constraintsOf(constraints, async (c) => ({
|
|
530
|
+
collectionId: clip(c.collection_id, 20),
|
|
531
|
+
claimsLeft: c.agent_quota,
|
|
532
|
+
maxAmount: await coinsOr(ctx, c.max_amount, NONE),
|
|
533
|
+
maxCw20: cw20Of(c.max_cw20_payment, NONE),
|
|
534
|
+
maxCw1155: cw1155Of(c.max_cw1155_payment, NONE),
|
|
535
|
+
...optional('intentDuration', setDuration(c.intent_duration_ns)),
|
|
536
|
+
...(c.member_address ? { forMember: c.member_address } : {}),
|
|
537
|
+
}))),
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
case '/ixo.claims.v1beta1.EvaluateClaimAuthorization': {
|
|
541
|
+
const parsed = evaluateClaimAuthorization.safeParse(authorization);
|
|
542
|
+
if (!parsed.success) return unknown;
|
|
543
|
+
const { constraints } = parsed.data;
|
|
544
|
+
return {
|
|
545
|
+
type,
|
|
546
|
+
kindLabel: `may evaluate claims in ${collectionsText(
|
|
547
|
+
constraints.map((c) => c.collection_id),
|
|
548
|
+
'no collection',
|
|
549
|
+
)}`,
|
|
550
|
+
admin: parsed.data.admin,
|
|
551
|
+
...(await constraintsOf(constraints, async (c) => ({
|
|
552
|
+
collectionId: clip(c.collection_id, 20),
|
|
553
|
+
evaluationsLeft: c.agent_quota,
|
|
554
|
+
...(c.claim_ids.length ? { onlyClaims: c.claim_ids.length } : {}),
|
|
555
|
+
...(c.before_date ? { before: c.before_date } : {}),
|
|
556
|
+
maxCustomAmount: await coinsOr(ctx, c.max_custom_amount, NONE),
|
|
557
|
+
maxCustomCw20: cw20Of(c.max_custom_cw20_payment, NONE),
|
|
558
|
+
maxCustomCw1155: cw1155Of(c.max_custom_cw1155_payment, NONE),
|
|
559
|
+
}))),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
case '/ixo.claims.v1beta1.WithdrawPaymentAuthorization': {
|
|
563
|
+
const parsed = withdrawPaymentAuthorization.safeParse(authorization);
|
|
564
|
+
if (!parsed.success) return unknown;
|
|
565
|
+
const { constraints } = parsed.data;
|
|
566
|
+
return {
|
|
567
|
+
type,
|
|
568
|
+
kindLabel: `may withdraw payments for ${constraints.length} ${constraints.length === 1 ? 'claim' : 'claims'}`,
|
|
569
|
+
admin: parsed.data.admin,
|
|
570
|
+
...(await constraintsOf(constraints, async (c) => ({
|
|
571
|
+
claimId: clip(c.claim_id, 120),
|
|
572
|
+
...(c.payment_type
|
|
573
|
+
? { paymentType: c.payment_type.toLowerCase() }
|
|
574
|
+
: {}),
|
|
575
|
+
...(c.release_date ? { releasesAt: c.release_date } : {}),
|
|
576
|
+
...(c.toAddress ? { to: clip(c.toAddress, 80) } : {}),
|
|
577
|
+
}))),
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
case '/ixo.claims.v1beta1.CreateClaimAuthorizationAuthorization': {
|
|
581
|
+
const parsed =
|
|
582
|
+
createClaimAuthorizationAuthorization.safeParse(authorization);
|
|
583
|
+
if (!parsed.success) return unknown;
|
|
584
|
+
const { constraints } = parsed.data;
|
|
585
|
+
const kinds = [
|
|
586
|
+
...new Set(
|
|
587
|
+
constraints.map(
|
|
588
|
+
(c) => CREATE_TYPES[c.allowed_auth_types ?? 'ALL'] ?? 'claim',
|
|
589
|
+
),
|
|
590
|
+
),
|
|
591
|
+
];
|
|
592
|
+
const ids = constraints.flatMap((c) => c.collection_ids);
|
|
593
|
+
const anyCollection = constraints.some((c) => !c.collection_ids.length);
|
|
594
|
+
return {
|
|
595
|
+
type,
|
|
596
|
+
kindLabel: `may create ${kinds.join(' / ')} authorizations in ${
|
|
597
|
+
anyCollection
|
|
598
|
+
? "any of the admin's collections"
|
|
599
|
+
: collectionsText(ids, 'no collection')
|
|
600
|
+
}`,
|
|
601
|
+
admin: parsed.data.admin,
|
|
602
|
+
...(await constraintsOf(constraints, async (c) => ({
|
|
603
|
+
collections: c.collection_ids.length
|
|
604
|
+
? c.collection_ids.slice(0, 10).map((id) => clip(id, 20))
|
|
605
|
+
: "all of the admin's",
|
|
606
|
+
creates: CREATE_TYPES[c.allowed_auth_types ?? 'ALL'] ?? 'claim',
|
|
607
|
+
maxAuthorizations: quotaText(c.max_authorizations),
|
|
608
|
+
maxAgentQuota: quotaText(c.max_agent_quota),
|
|
609
|
+
maxAmount: await coinsOr(ctx, c.max_amount, ANY),
|
|
610
|
+
maxCw20: cw20Of(c.max_cw20_payment, ANY),
|
|
611
|
+
maxCw1155: cw1155Of(c.max_cw1155_payment, ANY),
|
|
612
|
+
...optional(
|
|
613
|
+
'maxIntentDuration',
|
|
614
|
+
setDuration(c.max_intent_duration_ns),
|
|
615
|
+
),
|
|
616
|
+
...expiryOf(c.expiration),
|
|
617
|
+
...(c.member_address ? { forMember: c.member_address } : {}),
|
|
618
|
+
}))),
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
default:
|
|
622
|
+
return unknown;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export const authzGrantsKind = defineKind({
|
|
627
|
+
name: 'authz_grants',
|
|
628
|
+
summary:
|
|
629
|
+
'Authz grants: who may act for an account (given) or for whom it may act (received).',
|
|
630
|
+
explain: [
|
|
631
|
+
"An authz grant (x/authz) lets the grantee send specific transactions on the granter's behalf, signed by the grantee and executed as the granter. `direction`: received = grants this account holds (it may act for others); given = grants this account made, i.e. WHO CAN ACT FOR IT — check these when the question is about account safety. The granter can revoke a grant at any time.",
|
|
632
|
+
'Types (`type`, `kindLabel`): GenericAuthorization = may send that one message type (`message`) for the granter, with no limit. SendAuthorization = may send up to `spendLimit` from the granter (`onlyTo` restricts recipients). StakeAuthorization = may delegate / undelegate / redelegate the granter\'s stake (up to `maxTokens`, "unlimited" when none is set; `onlyValidators` / `exceptValidators`).',
|
|
633
|
+
'IXO claims authorizations (granted by a collection\'s admin account, `admin`): SubmitClaimAuthorization = may submit claims in the listed collections (`claimsLeft` claims, each intent valid for `intentDuration`) asking a custom payment of at most `maxAmount` (bank coins), `maxCw20` and `maxCw1155`. EvaluateClaimAuthorization = may evaluate (approve/reject) claims there (`evaluationsLeft`, `before` a date) and set a custom payment of at most `maxCustomAmount`, `maxCustomCw20` and `maxCustomCw1155`. On both, "none allowed" = no custom payment of that kind: the collection\'s own payments apply. WithdrawPaymentAuthorization = may withdraw the payment of a claim (`paymentType`, from `releasesAt`). CreateClaimAuthorizationAuthorization = may itself create submit and/or evaluate authorizations for those collections (`maxAuthorizations`, `maxAgentQuota`; "no cap" = no limit set) whose custom payments are capped at `maxAmount`, `maxCw20` and `maxCw1155`, where "any" = the created authorizations may set any.',
|
|
634
|
+
'`expiration` null = no expiry; `expired: true` = it can no longer be used. `more` says rows were left out (there is no total count). For who can act for an account through its own signing rules (passkeys, extra keys), see `smart_account`. Amounts are display units (`amount` + `symbol`) with `amountBaseUnits`; cw20 caps are base units of the token contract, cw1155 caps whole tokens of the contract (`tokenIds` "any" = any token id).',
|
|
635
|
+
].join('\n'),
|
|
636
|
+
source: 'chain',
|
|
637
|
+
list: true,
|
|
638
|
+
args,
|
|
639
|
+
example: { direction: 'given' },
|
|
640
|
+
plan(input, ctx) {
|
|
641
|
+
const account = accountFor(ctx, input.address);
|
|
642
|
+
if ('invalid' in account) return account;
|
|
643
|
+
const direction = input.direction ?? 'received';
|
|
644
|
+
const role = direction === 'received' ? 'grantee' : 'granter';
|
|
645
|
+
return {
|
|
646
|
+
source: 'chain',
|
|
647
|
+
async run(): Promise<KindResult> {
|
|
648
|
+
const read = await authzAnswers.get(
|
|
649
|
+
ctx,
|
|
650
|
+
`${direction}|${account.address}`,
|
|
651
|
+
() =>
|
|
652
|
+
ctx.rest(
|
|
653
|
+
`/cosmos/authz/v1beta1/grants/${role}/${account.address}?${PAGE}`,
|
|
654
|
+
grantsResponse,
|
|
655
|
+
{ label: `authz grants ${direction}` },
|
|
656
|
+
),
|
|
657
|
+
);
|
|
658
|
+
if (!read.ok) return failed(read.failure);
|
|
659
|
+
const { grants: all, pagination: paging } = read.value;
|
|
660
|
+
const grants = await Promise.all(
|
|
661
|
+
all.slice(0, ctx.first).map(async (grant) => ({
|
|
662
|
+
granter: grant.granter,
|
|
663
|
+
grantee: grant.grantee,
|
|
664
|
+
...(await authorizationOf(ctx, grant.authorization)),
|
|
665
|
+
...expiryOf(grant.expiration),
|
|
666
|
+
})),
|
|
667
|
+
);
|
|
668
|
+
return {
|
|
669
|
+
ok: true,
|
|
670
|
+
subject: account.subject,
|
|
671
|
+
first: ctx.first,
|
|
672
|
+
data: {
|
|
673
|
+
address: account.address,
|
|
674
|
+
direction,
|
|
675
|
+
more: moreThan(all, paging, ctx.first),
|
|
676
|
+
grants,
|
|
677
|
+
},
|
|
678
|
+
};
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
},
|
|
682
|
+
});
|