@ixo/oracle-runtime-workers 0.31.0 → 0.32.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.
@@ -86,8 +86,13 @@ export interface HubActionTransport {
86
86
 
87
87
  /**
88
88
  * The protocol version this runtime declares on every operation call
89
- * (capabilities, POST operations, GET status, options). Version 5 is the
90
- * 0.7.0 contract: version 4 plus the application card (`ixo.bid.submit.v1`)
89
+ * (capabilities, POST operations, GET status, options). Version 6 is the
90
+ * 0.8.0 contract: version 5 plus 29 more `ixo.chain.tx.v1` message types
91
+ * (staking, voting, retiring impact tokens, claim collections, entities and
92
+ * identity-record links), which a 0.7.0 parser cannot read in capabilities,
93
+ * review steps or receipts, so the Hub lists and accepts them only at level
94
+ * 6 (`chainTxMessageTypesForLevel`); options answer as at version 3.
95
+ * Version 5 is the 0.7.0 contract: version 4 plus the application card (`ixo.bid.submit.v1`)
91
96
  * and the claim card (`ixo.claim.submit.v1`) with their receipts
92
97
  * (`ixo.bid.submission.v1`, `ixo.claim.submission.v1`,
93
98
  * `ixo.submission.failed.v1`), which a 0.6.0 parser cannot read, so the Hub
@@ -101,22 +106,32 @@ export interface HubActionTransport {
101
106
  * the 0.4.1 shapes. The envelopes stay tagged `ixo.auth-outcome/2` and
102
107
  * `ixo.auth-capabilities/2`, which `@ixo/action-protocol` parses.
103
108
  *
104
- * An Auth Hub older than version 5 checks `?version=` against its closed
105
- * list (1 to 4) and answers every call that declares 5 with 400
106
- * `invalid_request`, before anything else: see `HUB_UPDATE_REQUIRED`.
109
+ * An Auth Hub older than version 6 checks `?version=` against its closed
110
+ * list (1 to 5) and answers every call that declares 6 with 400
111
+ * `invalid_request`, before anything else. This runtime then speaks
112
+ * version 5 to it (every Hub action but the level-6 chain types keeps
113
+ * working while Auth Hub is rolled out) and asks for 6 again after
114
+ * `HUB_PROTOCOL_RECHECK_MS`; a Hub that refuses 5 too is told apart with
115
+ * `HUB_UPDATE_REQUIRED`.
107
116
  */
108
- const HUB_PROTOCOL_VERSION = '5';
109
- const HUB_PROTOCOL_QUERY = `version=${HUB_PROTOCOL_VERSION}`;
117
+ const HUB_PROTOCOL_VERSION = '6';
118
+ /** The lowest version this runtime works at: 6 without the level-6 chain types. */
119
+ const HUB_PROTOCOL_FALLBACK_VERSION = '5';
120
+ type HubProtocolVersion =
121
+ | typeof HUB_PROTOCOL_VERSION
122
+ | typeof HUB_PROTOCOL_FALLBACK_VERSION;
123
+ /** How long a Hub that refused version 6 is spoken to at version 5 before 6 is tried again. */
124
+ export const HUB_PROTOCOL_RECHECK_MS = 10 * 60_000;
110
125
 
111
126
  /**
112
127
  * What the model is told when Auth Hub refuses a read (capabilities,
113
128
  * options, status) with 400 `invalid_request`. A read carries nothing but
114
129
  * the protocol version (and, for options, the binding the runtime itself
115
- * checked), so that answer comes from a Hub that does not know version 5.
130
+ * checked), so that answer comes from a Hub that does not know version 6.
116
131
  * The owner object keeps every action it holds: polls and callbacks pick
117
132
  * the outcomes up once the Hub is updated.
118
133
  */
119
- export const HUB_UPDATE_REQUIRED = `Auth Hub must be updated before this oracle can use Hub actions: it does not accept protocol version ${HUB_PROTOCOL_VERSION}, which this runtime needs (invalid_request). Tell the person this is not available right now; retrying or changing the fields will not help.`;
134
+ export const HUB_UPDATE_REQUIRED = `Auth Hub must be updated before this oracle can use Hub actions: it does not accept protocol version ${HUB_PROTOCOL_FALLBACK_VERSION}, the lowest this runtime works with (invalid_request). Tell the person this is not available right now; retrying or changing the fields will not help.`;
120
135
  /** Every Hub call but a chain-transaction prepare. */
121
136
  const HUB_TIMEOUT_MS = 10_000;
122
137
  /**
@@ -131,8 +146,17 @@ function isRecord(value: unknown): value is Record<string, unknown> {
131
146
  return value !== null && typeof value === 'object' && !Array.isArray(value);
132
147
  }
133
148
 
149
+ /** A read Auth Hub refused because it does not know the declared version. */
150
+ function isUpdateRequired(error: unknown): boolean {
151
+ return (
152
+ error instanceof HubActionRefused && error.message === HUB_UPDATE_REQUIRED
153
+ );
154
+ }
155
+
134
156
  export class HubActionClient {
135
157
  private readonly origin: string;
158
+ /** Until when this Hub is spoken to at version 5 (it refused 6). */
159
+ private fallbackUntil = 0;
136
160
 
137
161
  constructor(private readonly transport: HubActionTransport) {
138
162
  const url = new URL(transport.origin);
@@ -152,6 +176,34 @@ export class HubActionClient {
152
176
  this.origin = url.origin;
153
177
  }
154
178
 
179
+ /** The protocol version the next call declares. */
180
+ get protocolVersion(): HubProtocolVersion {
181
+ return Date.now() < this.fallbackUntil
182
+ ? HUB_PROTOCOL_FALLBACK_VERSION
183
+ : HUB_PROTOCOL_VERSION;
184
+ }
185
+
186
+ /**
187
+ * A read at the current version; when Auth Hub refuses version 6 as
188
+ * unknown, the same read at version 5, remembered for
189
+ * `HUB_PROTOCOL_RECHECK_MS`. Only reads do this: a refused read changed
190
+ * nothing, while a prepare's 400 can be a real refusal of its operation.
191
+ */
192
+ private async negotiated<T>(
193
+ read: (version: HubProtocolVersion) => Promise<T>,
194
+ ): Promise<T> {
195
+ const version = this.protocolVersion;
196
+ try {
197
+ return await read(version);
198
+ } catch (error) {
199
+ if (version !== HUB_PROTOCOL_VERSION || !isUpdateRequired(error))
200
+ throw error;
201
+ const answer = await read(HUB_PROTOCOL_FALLBACK_VERSION);
202
+ this.fallbackUntil = Date.now() + HUB_PROTOCOL_RECHECK_MS;
203
+ return answer;
204
+ }
205
+ }
206
+
155
207
  /** `subject`: what a refused prepare is called in the model's words. */
156
208
  private async send(
157
209
  path: string,
@@ -221,8 +273,9 @@ export class HubActionClient {
221
273
  // amounts) without it, and stores the version per action for the
222
274
  // outcomes it pushes.
223
275
  const operation = parseOperation(input.operation);
276
+ // The version the capabilities read before every prepare settled on.
224
277
  const response = await this.send(
225
- `/api/internal/actions/operations?${HUB_PROTOCOL_QUERY}`,
278
+ `/api/internal/actions/operations?version=${this.protocolVersion}`,
226
279
  { ...input, operation },
227
280
  operation.kind === 'ixo.chain.tx.v1'
228
281
  ? HUB_CHAIN_TX_PREPARE_TIMEOUT_MS
@@ -234,10 +287,17 @@ export class HubActionClient {
234
287
  return assertHubReferenceUrl(response.reference, this.origin);
235
288
  }
236
289
 
237
- async capabilities(): Promise<ActionCapabilitiesV1> {
238
- return parseCapabilities(
239
- await this.send(
240
- `/api/internal/actions/operations/capabilities?${HUB_PROTOCOL_QUERY}`,
290
+ /** `timeoutMs`: a turn's tool list passes a short one (a slow Hub must not hold a turn). */
291
+ async capabilities(
292
+ timeoutMs = HUB_TIMEOUT_MS,
293
+ ): Promise<ActionCapabilitiesV1> {
294
+ return this.negotiated(async (version) =>
295
+ parseCapabilities(
296
+ await this.send(
297
+ `/api/internal/actions/operations/capabilities?version=${version}`,
298
+ undefined,
299
+ timeoutMs,
300
+ ),
241
301
  ),
242
302
  );
243
303
  }
@@ -249,16 +309,18 @@ export class HubActionClient {
249
309
  // Options version 3 (asked at the protocol version; version 4 answers
250
310
  // the same): version 2 plus the owner's bank tokens
251
311
  // (`transfer.tokens`), which any-token transfers are chosen from.
252
- const query = new URLSearchParams({
253
- bindingId: input.bindingId,
254
- bindingRevision: String(input.bindingRevision),
255
- version: HUB_PROTOCOL_VERSION,
312
+ const options = await this.negotiated(async (version) => {
313
+ const query = new URLSearchParams({
314
+ bindingId: input.bindingId,
315
+ bindingRevision: String(input.bindingRevision),
316
+ version,
317
+ });
318
+ return parseActionOptions(
319
+ await this.send(
320
+ `/api/internal/actions/operations/options?${query.toString()}`,
321
+ ),
322
+ );
256
323
  });
257
- const options = parseActionOptions(
258
- await this.send(
259
- `/api/internal/actions/operations/options?${query.toString()}`,
260
- ),
261
- );
262
324
  if (options.version !== 'ixo.auth-options/3')
263
325
  throw new Error('Hub action options require version 3');
264
326
  return options;
@@ -295,10 +357,12 @@ export class HubActionClient {
295
357
  ): Promise<HubActionStatus> {
296
358
  if (!/^[A-Za-z0-9_-]{32}$/.test(id))
297
359
  throw new Error('Invalid Hub action id');
298
- const raw = await this.send(
299
- `/api/internal/actions/operations/${id}?${HUB_PROTOCOL_QUERY}`,
300
- undefined,
301
- timeoutMs,
360
+ const raw = await this.negotiated((version) =>
361
+ this.send(
362
+ `/api/internal/actions/operations/${id}?version=${version}`,
363
+ undefined,
364
+ timeoutMs,
365
+ ),
302
366
  );
303
367
  if (!isRecord(raw)) throw new Error('Invalid Hub action status');
304
368
  const value = raw;
@@ -7,32 +7,28 @@ import {
7
7
  type ChainTxMessage,
8
8
  type ChainTxOperation,
9
9
  } from '@ixo/action-protocol';
10
+ import type { ChainTxOwnerField, DistributiveOmit } from '../plugin-api/types';
10
11
  import type { HubActionClient } from './client';
11
12
  import { HubActionRefused, type HubActionOrigin } from './store';
12
13
 
13
14
  /**
14
- * A chain-transaction message as the owner object completes it: bank
15
- * amounts already in base units (`bankStepsInBaseUnits`), the owner's
16
- * identity in claims messages still to add (`chainTxOperation`).
15
+ * A chain-transaction message as the owner object completes it: bank and
16
+ * stake amounts already in base units (`chainTxAmountsInBaseUnits`), the
17
+ * owner's identity (`ChainTxOwnerField`) still to add (`chainTxOperation`).
17
18
  */
18
- export type ChainTxHostMessage =
19
- | Extract<
20
- ChainTxMessage,
21
- { type: 'cosmos.bank.send' } | { type: 'ixo.token.transfer' }
22
- >
23
- | Omit<Extract<ChainTxMessage, { type: 'ixo.claims.submit' }>, 'agentDid'>
24
- | Omit<
25
- Extract<ChainTxMessage, { type: 'ixo.claims.evaluate' }>,
26
- 'agentDid' | 'agentAddress'
27
- >;
19
+ export type ChainTxHostMessage = DistributiveOmit<
20
+ ChainTxMessage,
21
+ ChainTxOwnerField
22
+ >;
28
23
 
29
24
  /**
30
25
  * The chain transaction (`ixo.chain.tx.v1`) of the model's messages: the
31
26
  * signer is the owner's account (their DID is `did:ixo:<address>`), and
32
- * the owner's identity in claims messages is the signer's: `agentDid` is
33
- * `did:ixo:<signer>` (the DID the runtime authenticates to the Hub as) and
34
- * an evaluation's `agentAddress` is the signer (the chain records the
35
- * evaluator, and pays it, by that address; Auth Hub requires both). The
27
+ * the owner's identity in the messages is the signer's: `agentDid`,
28
+ * `creatorDid`, `controllerDid` and `ownerDid` are `did:ixo:<signer>` (the
29
+ * DID the runtime authenticates to the Hub as) and an evaluation's
30
+ * `agentAddress` is the signer (the chain records the evaluator, and pays
31
+ * it, by that address; Auth Hub requires each to be the owner's). The
36
32
  * protocol parses the result; a field the runtime sets (signer, network,
37
33
  * chain id) is refused in fixed words, a field from the model's messages
38
34
  * in the protocol's (`Invalid messages[0].tokens[0].id`).
@@ -49,15 +45,26 @@ export function chainTxOperation(input: {
49
45
  const signer = input.ownerDid.startsWith('did:ixo:')
50
46
  ? input.ownerDid.slice('did:ixo:'.length)
51
47
  : '';
52
- const agentDid = `did:ixo:${signer}`;
48
+ const ownDid = `did:ixo:${signer}`;
53
49
  // Identity last: a stray identity field cannot survive.
54
- const messages: ChainTxMessage[] = input.messages.map((message) =>
55
- message.type === 'ixo.claims.submit'
56
- ? { ...message, agentDid }
57
- : message.type === 'ixo.claims.evaluate'
58
- ? { ...message, agentDid, agentAddress: signer }
59
- : message,
60
- );
50
+ const messages = input.messages.map((message): ChainTxMessage => {
51
+ switch (message.type) {
52
+ case 'ixo.claims.submit':
53
+ case 'ixo.claims.claim-intent':
54
+ case 'ixo.claims.dispute':
55
+ return { ...message, agentDid: ownDid };
56
+ case 'ixo.claims.evaluate':
57
+ return { ...message, agentDid: ownDid, agentAddress: signer };
58
+ case 'ixo.claims.create-claim-authorization':
59
+ return { ...message, creatorDid: ownDid };
60
+ case 'ixo.entity.update':
61
+ return { ...message, controllerDid: ownDid };
62
+ case 'ixo.entity.create':
63
+ return { ...message, ownerDid: ownDid };
64
+ default:
65
+ return message;
66
+ }
67
+ });
61
68
  try {
62
69
  return parseOperation({
63
70
  kind: 'ixo.chain.tx.v1',
@@ -1,8 +1,10 @@
1
- import type {
2
- ActionOperationV1,
3
- ActionReceiptV1,
4
- OptionsToken,
1
+ import {
2
+ parseChainTxRefusal,
3
+ type ActionOperationV1,
4
+ type ActionReceiptV1,
5
+ type OptionsToken,
5
6
  } from '@ixo/action-protocol';
7
+ import { chainTxRefusalWords, wordableRefusal } from './chain-tx-refusals';
6
8
  import type { ChainTxMessageInput } from '../plugin-api/types';
7
9
  import { quoteHubText } from './hub-text';
8
10
  import type { ChainTxHostMessage } from './request';
@@ -91,24 +93,67 @@ const IXO_TOKEN: TransferToken = {
91
93
  };
92
94
 
93
95
  /**
94
- * A chain transaction's bank steps in base units; the other messages pass
96
+ * A chain transaction's amounts in base units; messages without one pass
95
97
  * unchanged. Each bank step names a display `amount` of a token from the
96
98
  * owner's list (`denom`, absent = uixo): the token must be listed, the
97
99
  * amount converts with its listed decimals (`toBaseUnits`: a plain decimal,
98
100
  * no more digits after the point than the decimals, above zero; an
99
101
  * unverified token lists 0: base units), and the bank steps of one token
100
- * together stay within its listed balance. `tokens` is the owner's list
101
- * (hub_action_options `transfer.tokens`), null when the Hub listed none;
102
- * the caller reads it only when there is a bank step.
102
+ * together stay within its listed balance. Each stake step (stake, unstake,
103
+ * move stake) names a display `amount` of IXO, converted with IXO's 6
104
+ * decimals; what a stake takes from the wallet counts against the listed
105
+ * IXO balance together with the IXO bank steps, when the list has IXO
106
+ * (Auth Hub checks every balance again before the card is made). `tokens`
107
+ * is the owner's list (hub_action_options `transfer.tokens`), null when the
108
+ * Hub listed none; the caller reads it only when there is a bank or stake
109
+ * step.
103
110
  */
104
- export function bankStepsInBaseUnits(
111
+ export function chainTxAmountsInBaseUnits(
105
112
  messages: readonly ChainTxMessageInput[],
106
113
  tokens: readonly OptionsToken[] | null,
107
114
  ): ChainTxHostMessage[] {
108
115
  const spent = new Map<string, bigint>();
109
- return messages.map((message, index) => {
110
- if (message.type !== 'cosmos.bank.send') return message;
116
+ /** The bank and stake steps of one listed token together, within its listed balance. */
117
+ const spend = (
118
+ at: string,
119
+ token: OptionsToken,
120
+ amount: bigint,
121
+ what: string,
122
+ ) => {
123
+ const total = (spent.get(token.denom) ?? 0n) + amount;
124
+ spent.set(token.denom, total);
125
+ if (total > BigInt(token.balanceBaseUnits))
126
+ throw new HubActionRefused(
127
+ 'refused',
128
+ `${at}.amount: the owner holds ${describeAmount(token.balanceBaseUnits, token)}, less than ${total === amount ? what : 'what the steps of this transaction take of it together'}.`,
129
+ );
130
+ };
131
+ const converted = (at: string, amount: string, decimals: number) => {
132
+ try {
133
+ return toBaseUnits(amount, decimals);
134
+ } catch (error) {
135
+ if (error instanceof HubActionRefused)
136
+ throw new HubActionRefused('refused', `${at}.amount: ${error.message}`);
137
+ throw error;
138
+ }
139
+ };
140
+ return messages.map((message, index): ChainTxHostMessage => {
111
141
  const at = `messages[${index}]`;
142
+ if (
143
+ message.type === 'cosmos.staking.delegate' ||
144
+ message.type === 'cosmos.staking.undelegate' ||
145
+ message.type === 'cosmos.staking.redelegate'
146
+ ) {
147
+ const { amount: display, ...rest } = message;
148
+ const amount = converted(at, display, IXO_TOKEN.decimals);
149
+ // Only a stake takes IXO from the wallet: unstaking and moving stake
150
+ // work on what is already staked.
151
+ const ixo = tokens?.find((item) => item.denom === IXO_TOKEN.denom);
152
+ if (message.type === 'cosmos.staking.delegate' && ixo)
153
+ spend(at, ixo, amount, 'this stake');
154
+ return { ...rest, amountBaseUnits: amount.toString() };
155
+ }
156
+ if (message.type !== 'cosmos.bank.send') return message;
112
157
  if (!tokens?.length)
113
158
  throw new HubActionRefused(
114
159
  'refused',
@@ -124,21 +169,8 @@ export function bankStepsInBaseUnits(
124
169
  .slice(0, 10)
125
170
  .join(', ')}).`,
126
171
  );
127
- let amount: bigint;
128
- try {
129
- amount = toBaseUnits(message.amount, token.decimals);
130
- } catch (error) {
131
- if (error instanceof HubActionRefused)
132
- throw new HubActionRefused('refused', `${at}.amount: ${error.message}`);
133
- throw error;
134
- }
135
- const total = (spent.get(token.denom) ?? 0n) + amount;
136
- spent.set(token.denom, total);
137
- if (total > BigInt(token.balanceBaseUnits))
138
- throw new HubActionRefused(
139
- 'refused',
140
- `${at}.amount: the owner holds ${describeAmount(token.balanceBaseUnits, token)}, less than ${total === amount ? 'this send' : 'the bank steps of this token in this transaction'}.`,
141
- );
172
+ const amount = converted(at, message.amount, token.decimals);
173
+ spend(at, token, amount, 'this send');
142
174
  return {
143
175
  type: 'cosmos.bank.send',
144
176
  toAddress: message.toAddress,
@@ -316,6 +348,24 @@ export function hubPrepareRefusal(
316
348
  if (unfixable)
317
349
  return new HubActionRefused('refused', unfixable, undefined, status);
318
350
  if (!parsed) return null;
351
+ // A chain transaction Auth Hub would not make from what it read on chain,
352
+ // or whose simulation named one message: a fixed code, in the runtime's
353
+ // own words (the reason text is not passed on). Without a code, the
354
+ // reason is passed on quoted, as below.
355
+ const coded =
356
+ status === 400 &&
357
+ subject === 'transaction' &&
358
+ (parsed.error === 'operation_not_registered' ||
359
+ parsed.error === 'simulation_failed')
360
+ ? wordableRefusal(parseChainTxRefusal(parsed.reason))
361
+ : null;
362
+ if (coded)
363
+ return new HubActionRefused(
364
+ 'refused',
365
+ chainTxRefusalWords(coded),
366
+ undefined,
367
+ status,
368
+ );
319
369
  const reason = parsed.reason
320
370
  ? quoteHubText('reason', parsed.reason, HUB_REASON_MAX)
321
371
  : '';
@@ -32,13 +32,40 @@ import type {
32
32
  ChainTxMessage,
33
33
  } from '@ixo/action-protocol';
34
34
 
35
+ /** `Omit` applied to each member of a union (plain `Omit` merges them). */
36
+ export type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
37
+ ? Omit<T, K>
38
+ : never;
39
+
40
+ /**
41
+ * The owner's own identity in chain-transaction messages, which the host
42
+ * fills and the model never names: the filer's or disputer's `agentDid`
43
+ * (claims, reservations, disputes), an evaluation's `agentDid` and
44
+ * `agentAddress`, a claim permission's `creatorDid`, an entity update's
45
+ * `controllerDid` and a new entity's `ownerDid`: always `did:ixo:<signer>`,
46
+ * or the signer for an address.
47
+ */
48
+ export type ChainTxOwnerField =
49
+ | 'agentDid'
50
+ | 'agentAddress'
51
+ | 'creatorDid'
52
+ | 'controllerDid'
53
+ | 'ownerDid';
54
+
55
+ /** The stake steps: their amount is IXO, written in display units. */
56
+ export type ChainTxStakeType =
57
+ | 'cosmos.staking.delegate'
58
+ | 'cosmos.staking.undelegate'
59
+ | 'cosmos.staking.redelegate';
60
+
35
61
  /**
36
62
  * A chain-transaction message as the model writes it: the protocol's
37
- * message without the owner's identity, which the host fills (a claim's
38
- * `agentDid`, an evaluation's `agentDid` and `agentAddress`: the signer's).
39
- * A bank step names a display `amount` of a token from the owner's list
40
- * (`denom`, absent = uixo); the host converts it to the protocol's
41
- * `amountBaseUnits` from the listed decimals.
63
+ * message without the owner's identity (`ChainTxOwnerField`), which the
64
+ * host fills. A bank step names a display `amount` of a token from the
65
+ * owner's list (`denom`, absent = uixo); a stake step a display `amount` of
66
+ * IXO. The host converts both to the protocol's `amountBaseUnits` (the
67
+ * listed decimals; 6 for IXO). Every other amount (impact tokens, a
68
+ * collection's payments) is already in the protocol's units.
42
69
  */
43
70
  export type ChainTxMessageInput =
44
71
  | {
@@ -48,11 +75,16 @@ export type ChainTxMessageInput =
48
75
  amount: string;
49
76
  denom?: string;
50
77
  }
51
- | Extract<ChainTxMessage, { type: 'ixo.token.transfer' }>
52
- | Omit<Extract<ChainTxMessage, { type: 'ixo.claims.submit' }>, 'agentDid'>
53
- | Omit<
54
- Extract<ChainTxMessage, { type: 'ixo.claims.evaluate' }>,
55
- 'agentDid' | 'agentAddress'
78
+ | (DistributiveOmit<
79
+ Extract<ChainTxMessage, { type: ChainTxStakeType }>,
80
+ 'amountBaseUnits'
81
+ > & {
82
+ /** A plain decimal in IXO ("100" or "0.5"). */
83
+ amount: string;
84
+ })
85
+ | DistributiveOmit<
86
+ Exclude<ChainTxMessage, { type: 'cosmos.bank.send' | ChainTxStakeType }>,
87
+ ChainTxOwnerField
56
88
  >;
57
89
 
58
90
  /**
@@ -812,7 +844,13 @@ export interface RuntimeContext<TConfig = MergedConfig> {
812
844
 
813
845
  /** Host-owned action coordinator. It accepts only runtime-derived channel context. */
814
846
  hubActions?: {
815
- capabilities(): Promise<ActionCapabilitiesV1>;
847
+ /**
848
+ * The Hub's capabilities for this oracle account (cached 60 s).
849
+ * `turn`: a read on a turn's critical path (a tool list): a short
850
+ * timeout, no new read within 30 s of a failed one, and the last
851
+ * capabilities read while reads fail.
852
+ */
853
+ capabilities(opts?: { turn?: boolean }): Promise<ActionCapabilitiesV1>;
816
854
  options(input: {
817
855
  ownerDid: string;
818
856
  provider: string;
@@ -822,13 +860,16 @@ export interface RuntimeContext<TConfig = MergedConfig> {
822
860
  /**
823
861
  * One chain transaction (`ixo.chain.tx.v1`) of 1–8 messages: the host
824
862
  * adds the chain id and network (the account's), the signer (the
825
- * owner's address) and the owner's identity in claims messages
826
- * (`agentDid` = `did:ixo:<signer>`, an evaluation's `agentAddress` = the
827
- * signer) and prepares it like any operation (no fee: the Hub simulates
828
- * gas and sets it). A bank step's display `amount` is converted to base
829
- * units from the owner's token list (one options read per call, only
830
- * when there is a bank step), refused for a token not listed, more
831
- * digits than its decimals, zero or more than the listed balance.
863
+ * owner's address) and the owner's identity (`ChainTxOwnerField`:
864
+ * `did:ixo:<signer>`, an evaluation's `agentAddress` = the signer) and
865
+ * prepares it like any operation (no fee: the Hub simulates gas and
866
+ * sets it). A bank step's display `amount` is converted to base units
867
+ * from the owner's token list (one options read per call, only when
868
+ * there is a bank or stake step), refused for a token not listed, more
869
+ * digits than its decimals, zero or more than the listed balance. A
870
+ * stake step's IXO `amount` is converted with 6 decimals; what a stake
871
+ * takes from the wallet counts against the listed IXO balance with the
872
+ * IXO bank steps, when the list has IXO.
832
873
  */
833
874
  chainTransaction(input: {
834
875
  ownerDid: string;