@usearete/automations 0.0.0-stage → 0.44.1

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.
@@ -0,0 +1,1379 @@
1
+ import { ExecutionIntentV1, ProtocolPreparedTransaction, ProtocolTransactionFormat, RichUpdate, Update, StackDefinition, ConnectedArete, ProtocolTypeMap, ProtocolDefinition, ProtocolStepAvailability, ProtocolIntentState, ProtocolEventsOptions, ProtocolEventSubscription, ProtocolParticipantDetail } from '@usearete/sdk';
2
+
3
+ /** One top-level instruction: indexes into the message's account keys (static keys, then lookup-table writable entries, then read-only ones). */
4
+ interface CompiledInstruction {
5
+ readonly programIndex: number;
6
+ readonly accounts: readonly number[];
7
+ readonly data: Uint8Array;
8
+ }
9
+ /** One address lookup table a v0 message loads from. */
10
+ interface Lookup {
11
+ readonly table: string;
12
+ readonly writable: readonly number[];
13
+ readonly readonly: readonly number[];
14
+ }
15
+ /** A decoded wire transaction. */
16
+ interface DecodedTransaction {
17
+ /** `null` for legacy messages, else the message version. */
18
+ readonly version: 0 | 1 | null;
19
+ /** Signature slots, in key order; all zero when not signed yet. */
20
+ readonly signatures: readonly Uint8Array[];
21
+ /** Where the first signature slot starts in the wire bytes. */
22
+ readonly signatureOffset: number;
23
+ /** The byte range every signer signs, `[start, end)`. */
24
+ readonly message: {
25
+ readonly start: number;
26
+ readonly end: number;
27
+ };
28
+ readonly requiredSignatures: number;
29
+ readonly staticKeys: readonly string[];
30
+ /** Indexes of the static keys the message marks writable. */
31
+ readonly writableStatic: readonly number[];
32
+ /** The program of each top-level instruction, in order. */
33
+ readonly instructionPrograms: readonly string[];
34
+ readonly instructions: readonly CompiledInstruction[];
35
+ readonly lookups: readonly Lookup[];
36
+ }
37
+ declare class TransactionDecodeError extends Error {
38
+ constructor(message: string);
39
+ }
40
+ /** Decode a legacy, v0 or v1 wire transaction. */
41
+ declare function decodeTransaction(bytes: Uint8Array): DecodedTransaction;
42
+
43
+ /**
44
+ * Instructions that hand control of the signer's funds to someone else
45
+ * without moving a balance.
46
+ *
47
+ * The policy's caps measure balance changes, and some instructions change no
48
+ * balance yet give the funds away: a token delegation (`approve`), a new
49
+ * owner or authority (`setAuthority`, System `assign`), or lamports leaving
50
+ * an account the signer controls that is not the signer's own (a nonce or
51
+ * stake withdrawal). This module names those instructions in the System, SPL
52
+ * Token, Token-2022, upgradeable loader and stake programs when the signer
53
+ * takes part in them (signs, or is one of their accounts).
54
+ *
55
+ * Top-level instructions are read from the wire bytes. Inner (CPI)
56
+ * instructions are read from a simulation when the RPC returns them.
57
+ *
58
+ * A token account closed to an account other than the signer is not refused;
59
+ * its lamports count as SOL leaving the signer.
60
+ */
61
+
62
+ /** An instruction the signer must not sign. */
63
+ interface AuthorityFinding {
64
+ readonly programId: string;
65
+ readonly instruction: string;
66
+ /** Whether it is an inner (CPI) instruction from the simulation. */
67
+ readonly inner: boolean;
68
+ readonly reason: string;
69
+ }
70
+
71
+ /**
72
+ * A Solana JSON-RPC endpoint the policy simulates prepared transactions on.
73
+ *
74
+ * The summary calls two methods: `getMultipleAccounts` (lookup tables and
75
+ * the pre-state of written accounts) and `simulateTransaction` (with the
76
+ * written accounts' post-state and inner instructions). `call` resolves
77
+ * with the JSON-RPC `result` and rejects on a JSON-RPC `error`.
78
+ */
79
+ interface Simulator {
80
+ call(method: string, params: readonly unknown[]): Promise<unknown>;
81
+ }
82
+ interface RpcSimulatorOptions {
83
+ /** Defaults to the global `fetch`. */
84
+ readonly fetch?: typeof fetch;
85
+ /** Extra request headers, for example an RPC provider's API key. */
86
+ readonly headers?: Readonly<Record<string, string>>;
87
+ /**
88
+ * How long one call may take, request and response body together, in
89
+ * milliseconds. Defaults to 15 seconds. A call that runs out fails, so the
90
+ * transaction's effect is unknown and the policy refuses it.
91
+ */
92
+ readonly timeoutMs?: number;
93
+ }
94
+ /** A {@link Simulator} over a JSON-RPC URL. */
95
+ declare function rpcSimulator(url: string, options?: RpcSimulatorOptions): Simulator;
96
+
97
+ /**
98
+ * What a prepared step's transactions do, read from the transactions
99
+ * themselves rather than from the labels a prepare returns:
100
+ *
101
+ * - the programs each transaction invokes;
102
+ * - the accounts it writes (static keys, plus address lookup table entries,
103
+ * resolved when a simulator is available);
104
+ * - the signatures it carries and the ones still required;
105
+ * - with a simulator, the signer's SOL and token balance changes from a
106
+ * simulation of the exact bytes, the simulation error and the tail of its
107
+ * logs;
108
+ * - the instructions that would hand control of the signer's funds to
109
+ * someone else (see `authority.ts`): read from the top-level instructions
110
+ * always, and from the inner instructions when the simulation returns
111
+ * them.
112
+ *
113
+ * Without a simulator the balance changes are reported as unavailable,
114
+ * never as zero.
115
+ */
116
+
117
+ /** A prepare result, as far as the summary reads it. */
118
+ interface PreparedStepLike {
119
+ readonly intent?: unknown;
120
+ readonly transactions: readonly {
121
+ readonly index?: number;
122
+ readonly transactionBase64: string;
123
+ }[];
124
+ /** Role-labelled instructions, when the prepare returns them. */
125
+ readonly anatomy?: unknown;
126
+ /** The server's simulation metrics, when the prepare returns them. */
127
+ readonly simulation?: unknown;
128
+ }
129
+ interface TokenDelta {
130
+ readonly mint: string;
131
+ /** Base units, signed: negative when tokens leave the signer. */
132
+ readonly amount: string;
133
+ }
134
+ interface SignerEffectSummary {
135
+ /** Whether every balance change of the signer is known. */
136
+ readonly known: boolean;
137
+ readonly solLamports?: string | null;
138
+ readonly sol?: string | null;
139
+ readonly tokens?: TokenDelta[];
140
+ readonly closedAccountLamportsOut?: string | null;
141
+ readonly unresolvedLookupTables?: string[];
142
+ readonly omittedAccounts?: number;
143
+ readonly innerInstructionsChecked?: boolean;
144
+ readonly preStateSlot?: number | null;
145
+ readonly simulatedSlot?: number | null;
146
+ /** Why the simulation could not run. */
147
+ readonly error?: string;
148
+ }
149
+ interface TransactionSummary {
150
+ readonly index: number;
151
+ readonly version: 'legacy' | 'v0' | 'v1';
152
+ readonly feePayer: string | null;
153
+ readonly programs: Array<{
154
+ readonly programId: string;
155
+ readonly roles: string[];
156
+ }>;
157
+ readonly accountsWritten: string[];
158
+ readonly accountsWrittenComplete: boolean;
159
+ readonly unresolvedWritableAccounts: Array<{
160
+ readonly table: string;
161
+ readonly addressIndex: number;
162
+ }>;
163
+ readonly signatures: {
164
+ readonly required: string[];
165
+ readonly present: string[];
166
+ readonly missing: string[];
167
+ };
168
+ readonly serverSimulation: unknown;
169
+ readonly lookupTables?: Array<{
170
+ readonly table: string;
171
+ readonly writableEntries: number;
172
+ readonly readonlyEntries: number;
173
+ }>;
174
+ readonly signerEffect: SignerEffectSummary;
175
+ readonly simulation?: {
176
+ readonly err: unknown;
177
+ readonly logsTail: string[];
178
+ };
179
+ readonly unsafeInstructions: AuthorityFinding[];
180
+ }
181
+ interface PreparedSummary {
182
+ readonly signer: string;
183
+ readonly transactions: TransactionSummary[];
184
+ readonly programsInvoked: string[];
185
+ readonly signerBalanceChanges: {
186
+ readonly available: false;
187
+ readonly reason: string;
188
+ } | {
189
+ readonly available: true;
190
+ readonly complete: boolean;
191
+ readonly solLamports: string | null;
192
+ readonly sol: string | null;
193
+ readonly tokens: TokenDelta[];
194
+ readonly note: string;
195
+ };
196
+ readonly simulationErrors: Array<{
197
+ readonly transaction: number;
198
+ readonly err: unknown;
199
+ readonly dependsOnEarlierTransactions: boolean;
200
+ }>;
201
+ readonly signaturesStillRequired: string[];
202
+ }
203
+ /**
204
+ * The summary of a prepared step for `signer` (the wallet that signs).
205
+ * `simulator` adds the simulated balance changes.
206
+ */
207
+ declare function summarizePrepared(prepared: PreparedStepLike, signer: string, simulator?: Simulator | null): Promise<PreparedSummary>;
208
+
209
+ /**
210
+ * What a signer may sign for a user: the static policy.
211
+ *
212
+ * The rules, their names and the refusal shape match the Arete MCP server's
213
+ * local signer, and both implementations run the shared vectors in
214
+ * `test-vectors/signer-policy/`. The caps hold per transaction, measured by
215
+ * simulating the exact prepared bytes. Wrapped SOL counts against
216
+ * `maxSolPerTransaction`, as do the lamports of a token account closed to
217
+ * someone else. Instructions that hand control of the signer's funds to
218
+ * someone else (token approvals, authority changes, System assignments) are
219
+ * always refused, and so is any transaction whose effect on the signer
220
+ * could not be simulated.
221
+ */
222
+
223
+ /** Clusters a policy allows when it does not list any. */
224
+ declare const DEFAULT_CLUSTERS: readonly ["dev"];
225
+ /**
226
+ * Every cluster a target can be: `dev` for a deployment in a dev environment
227
+ * (a fork of mainnet), `mainnet` for a published deployment.
228
+ */
229
+ declare const KNOWN_CLUSTERS: readonly ["dev", "mainnet"];
230
+ type Cluster = (typeof KNOWN_CLUSTERS)[number];
231
+ /**
232
+ * Programs the transaction compiler adds around a step's own instructions
233
+ * (compute budget, memo, fee transfers, token accounts and wrapped SOL).
234
+ * They are always allowed; what they move is bounded by the SOL and token
235
+ * caps.
236
+ */
237
+ declare const RUNTIME_PROGRAMS: readonly ["ComputeBudget111111111111111111111111111111", "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr", "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo", "11111111111111111111111111111111", "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"];
238
+ /** The fields of a policy, as an integrator writes them. */
239
+ interface PolicyInput {
240
+ /** Deployments the signer may act in. Required, at least one. */
241
+ readonly deployments: readonly string[];
242
+ /** The most SOL one transaction may move out of the signer, in SOL. */
243
+ readonly maxSolPerTransaction: string | number;
244
+ /** Programs a transaction may invoke besides the runtime programs. Omit to allow any. */
245
+ readonly programs?: readonly string[] | null;
246
+ /**
247
+ * Base units per mint that one transaction may move out of the signer.
248
+ * A mint without a cap may not leave at all, so without this table no
249
+ * token may leave (wrapped SOL counts as SOL instead).
250
+ */
251
+ readonly maxTokenAmounts?: Readonly<Record<string, string | number | bigint>> | null;
252
+ /** Defaults to `dev`; mainnet only when listed. */
253
+ readonly clusters?: readonly Cluster[] | null;
254
+ }
255
+ /** A validated policy. */
256
+ interface SignerPolicy {
257
+ readonly deployments: readonly string[];
258
+ readonly maxLamportsPerTransaction: bigint;
259
+ readonly programs: readonly string[] | null;
260
+ readonly maxTokenAmounts: ReadonlyMap<string, bigint> | null;
261
+ readonly clusters: readonly string[];
262
+ /** Whether the input listed `clusters` (rather than the default). */
263
+ readonly clustersListed: boolean;
264
+ }
265
+ /**
266
+ * Why a step was not signed. The first six are the signing policy's rules;
267
+ * the rest are the runtime's checks around the signer.
268
+ */
269
+ type RefusalRule = 'deployments' | 'clusters' | 'programs' | 'authority' | 'maxSolPerTransaction' | 'maxTokenAmounts'
270
+ /** The prepared transactions are not what the intent committed to. */
271
+ | 'intent_mismatch'
272
+ /** The signer and the deployment share no transaction format, or the prepare used another. */
273
+ | 'transaction_format'
274
+ /** The signer changed a message, another signature, or the signers. */
275
+ | 'signer_modified'
276
+ /** The wallet's signature slot does not hold its valid signature. */
277
+ | 'signer_signature'
278
+ /** The signer cannot sign for the wallet (no key, no session signer). */
279
+ | 'signer_unavailable'
280
+ /** The wallet provider's own policy refused to sign. */
281
+ | 'provider_policy'
282
+ /** The step moves more than the consent allows in one run, or an asset it sets no limit for. */
283
+ | 'consent_limit'
284
+ /** The step would take the wallet past the consent's limit for its window. */
285
+ | 'consent_window';
286
+ /** A policy refusal: the rule and why. */
287
+ interface Refusal {
288
+ readonly rule: RefusalRule;
289
+ readonly reason: string;
290
+ }
291
+ declare class PolicyError extends Error {
292
+ constructor(message: string);
293
+ }
294
+ /** Validate a policy. Throws {@link PolicyError} naming the field at fault. */
295
+ declare function definePolicy(input: PolicyInput): SignerPolicy;
296
+ /** Whether the deployment and cluster are allowed, before preparing. `null` when they are. */
297
+ declare function checkTarget(policy: SignerPolicy, deploymentId: string, cluster: string): Refusal | null;
298
+ interface CheckPreparedInput {
299
+ /** The deployment the step was prepared in. */
300
+ readonly deploymentId: string;
301
+ /** The prepare result; its intent's deployment must match. */
302
+ readonly prepared: unknown;
303
+ /** The summary of the prepared step for the signer. */
304
+ readonly summary: PreparedSummary;
305
+ /** Whether the summary came from a simulation at all. */
306
+ readonly simulates: boolean;
307
+ }
308
+ /**
309
+ * Check a prepared step against the policy: the intent's deployment, every
310
+ * transaction's programs, the instructions that would hand over control of
311
+ * the signer's funds, and the simulated SOL and token outflows of the
312
+ * signer, per transaction. `null` when it may be signed.
313
+ */
314
+ declare function checkPrepared(policy: SignerPolicy, input: CheckPreparedInput): Refusal | null;
315
+
316
+ /**
317
+ * What a signer is given. Hand it copies and keep your own snapshot of the
318
+ * prepared transactions for `checkSignedTransactions`: `readonly` is not
319
+ * enforced at run time, and a signer that changed the objects it was given
320
+ * could change a shared baseline too. The runtime does this.
321
+ */
322
+ interface SignTransactionsInput {
323
+ /** The wallet whose signature slot to fill. */
324
+ readonly wallet: string;
325
+ /** What the prepare committed to. */
326
+ readonly intent: ExecutionIntentV1;
327
+ /** The prepared transactions, base64 wire bytes, in intent order. */
328
+ readonly transactions: readonly ProtocolPreparedTransaction[];
329
+ }
330
+ /**
331
+ * Signs prepared protocol transactions for a wallet the backend controls by
332
+ * delegation (a dev keypair, an embedded-wallet provider, a KMS).
333
+ *
334
+ * A signer fills the wallet's signature slot of each transaction and
335
+ * nothing else: it must not change the message, add instructions, touch
336
+ * other signatures, or broadcast. The runtime does not trust this: it
337
+ * re-hashes every returned message against the intent and verifies the
338
+ * wallet's signature before anything is submitted.
339
+ */
340
+ interface ProtocolSigner {
341
+ /** A short name for logs and refusals, for example `keypair` or `privy`. */
342
+ readonly kind: string;
343
+ /** Formats the signer can sign, in order of preference. */
344
+ readonly supportedTransactionFormats: readonly ProtocolTransactionFormat[];
345
+ /** Whether this signer can sign for `wallet` now. */
346
+ canSign(wallet: string): Promise<boolean>;
347
+ /** The signed transactions, base64 wire bytes, one per input in the same order. */
348
+ signTransactions(input: SignTransactionsInput): Promise<string[]>;
349
+ /**
350
+ * Whether the backend holds delegated access to `wallet`, and whether a
351
+ * provider-side policy bounds what it may sign. Granting consent warns
352
+ * when either is missing.
353
+ */
354
+ delegationStatus?(wallet: string): Promise<DelegationStatus>;
355
+ }
356
+ interface DelegationStatus {
357
+ readonly delegated: boolean;
358
+ readonly providerPolicy: boolean;
359
+ readonly detail?: string;
360
+ }
361
+ /**
362
+ * Thrown by a signer that declines to sign, for example because the wallet
363
+ * provider's own policy refused the transaction. The job ends `refused`
364
+ * with this rule instead of being retried.
365
+ */
366
+ declare const REFUSAL_BRAND: unique symbol;
367
+ declare class SignerRefusal extends Error {
368
+ readonly rule: 'provider_policy' | 'signer_unavailable';
369
+ /** Recognised across entry points: each bundle has its own copy of this class. */
370
+ readonly [REFUSAL_BRAND] = true;
371
+ constructor(message: string, rule?: 'provider_policy' | 'signer_unavailable');
372
+ }
373
+ /**
374
+ * Whether `error` is a {@link SignerRefusal}, including one thrown by a
375
+ * signer from another entry point (`@usearete/automations/privy` is a
376
+ * separate bundle, so `instanceof` would miss it).
377
+ */
378
+ declare function isSignerRefusal(error: unknown): error is SignerRefusal;
379
+
380
+ /** What the API prepares by default, then the fallback. */
381
+ declare const DEFAULT_TRANSACTION_FORMATS: readonly ProtocolTransactionFormat[];
382
+ /**
383
+ * The first format in `preferred` (the deployment's order) that the signer
384
+ * supports, or `null` when they share none.
385
+ */
386
+ declare function negotiateTransactionFormat(signerFormats: readonly ProtocolTransactionFormat[], preferred?: readonly ProtocolTransactionFormat[]): ProtocolTransactionFormat | null;
387
+
388
+ /**
389
+ * The exact-bytes checks around a delegated signer.
390
+ *
391
+ * Before signing, the prepared transactions must be the ones the intent
392
+ * committed to. After signing, every message must hash to the intent's
393
+ * `messageSha256`, every signature slot but the wallet's must be untouched,
394
+ * and the wallet's slot must hold the wallet's valid signature. A signer
395
+ * that adds a compute-budget instruction, re-encodes, or signs something
396
+ * else fails here, before anything is submitted.
397
+ */
398
+
399
+ /**
400
+ * Whether the prepared transactions are what `intent` committed to, in the
401
+ * format that was asked for, with `wallet` as a required signer of each.
402
+ * `null` when they are.
403
+ */
404
+ declare function checkPreparedAgainstIntent(input: {
405
+ readonly wallet: string;
406
+ readonly intent: ExecutionIntentV1;
407
+ readonly transactions: readonly ProtocolPreparedTransaction[];
408
+ readonly format: ProtocolTransactionFormat;
409
+ }): Refusal | null;
410
+ /**
411
+ * Whether `signed` is `prepared` with only the wallet's signature slot
412
+ * filled, by the wallet. `null` when it is.
413
+ *
414
+ * `prepared` is the baseline: pass a copy the signer never had, because a
415
+ * signer that can change the objects it was given could change the
416
+ * baseline too.
417
+ */
418
+ declare function checkSignedTransactions(input: {
419
+ readonly wallet: string;
420
+ readonly intent: ExecutionIntentV1;
421
+ readonly prepared: readonly ProtocolPreparedTransaction[];
422
+ readonly signed: readonly string[];
423
+ }): Refusal | null;
424
+
425
+ /** A duration in milliseconds, or text such as `30s`, `10m`, `24h` or `7d`. */
426
+ type Duration = number | `${number}${'ms' | 's' | 'm' | 'h' | 'd'}`;
427
+ declare function durationMs(duration: Duration): number;
428
+ /** Wall-clock time and waiting, injectable for tests. */
429
+ interface Clock {
430
+ now(): number;
431
+ sleep(ms: number): Promise<void>;
432
+ }
433
+ declare const systemClock: Clock;
434
+ type SchedulePeriod = 'minute' | 'hour' | 'day' | 'week' | 'month';
435
+ /**
436
+ * A deterministic dispatch id for a scheduled run: the same name, period
437
+ * and time give the same id, so a cron that fires twice in one period is
438
+ * deduplicated. Times are UTC.
439
+ *
440
+ * `scheduleSlot('weekly-buy', 'week')` → `weekly-buy:2026-W43`.
441
+ */
442
+ declare function scheduleSlot(name: string, period: SchedulePeriod, at?: Date | number): string;
443
+
444
+ /** Where a trigger came from. */
445
+ type TriggerSource = 'webhook' | 'stream' | 'dispatch' | 'view';
446
+ /** One accepted trigger: a webhook delivery, a stream event or a dispatch. */
447
+ interface Delivery {
448
+ /** Unique per trigger, namespaced by source (`webhook:…`, `stream:…`, `dispatch:…`, `view:…`). */
449
+ readonly id: string;
450
+ /** The handler name: an event name or a dispatch name. */
451
+ readonly name: string;
452
+ readonly source: TriggerSource;
453
+ readonly payload: unknown;
454
+ /** Milliseconds since the epoch. */
455
+ readonly receivedAt: number;
456
+ }
457
+ /** A step a handler asks for. */
458
+ interface StepRequest {
459
+ readonly step: string;
460
+ readonly wallet: string;
461
+ /** The instance, for steps that act on one; omitted for create steps. */
462
+ readonly instance?: string;
463
+ readonly args: Readonly<Record<string, unknown>>;
464
+ /** Skip the step when the same (wallet, step) completed this recently, in milliseconds. */
465
+ readonly cooldownMs?: number;
466
+ }
467
+ /** Delivery jobs run a handler; they never sign. */
468
+ type DeliveryJobState = 'queued' | 'running' | 'done' | 'failed';
469
+ /**
470
+ * Step jobs run the signing pipeline. `running`, `signed`, `submitted` and
471
+ * `submitted_unknown` are active: while one is, no other step job of the
472
+ * same wallet may start.
473
+ */
474
+ type StepJobState = 'queued' | 'running' | 'signed' | 'submitted' | 'submitted_unknown' | 'completed' | 'chain_failed' | 'expired' | 'skipped' | 'rejected' | 'refused' | 'failed';
475
+ type JobState = DeliveryJobState | StepJobState;
476
+ declare const ACTIVE_STEP_STATES: readonly StepJobState[];
477
+ declare const TERMINAL_STEP_STATES: readonly StepJobState[];
478
+ declare const TERMINAL_DELIVERY_STATES: readonly DeliveryJobState[];
479
+ /** Why a job ended where it did. */
480
+ interface JobOutcome {
481
+ /** A refusal rule, a guard or rejection code, `no_consent`, `cooldown`, or an error class. */
482
+ readonly code: string;
483
+ readonly reason?: string;
484
+ readonly detail?: unknown;
485
+ }
486
+ /** What a step job carries once it has prepared and signed. */
487
+ interface StepProgress {
488
+ /** Increments when an expired intent is prepared again. */
489
+ readonly generation: number;
490
+ readonly format?: ProtocolTransactionFormat;
491
+ readonly intent?: ExecutionIntentV1;
492
+ readonly prepared?: readonly ProtocolPreparedTransaction[];
493
+ /** The signed transactions, saved before submit so a crash never re-signs blind. */
494
+ readonly signed?: readonly string[];
495
+ /** Transaction signatures once the intent has them. */
496
+ readonly signatures?: readonly (string | null)[];
497
+ /** When the step was signed: its spending counts against windows from then. */
498
+ readonly signedAt?: number;
499
+ /** What each transaction moves out of the wallet (decimal amounts per asset), saved at signing. */
500
+ readonly outflows?: readonly {
501
+ readonly index: number;
502
+ readonly amounts: Readonly<Record<string, string>>;
503
+ }[];
504
+ }
505
+ interface JobBase {
506
+ /** Unique across both kinds: the delivery id for a delivery job, {@link stepJobId} for a step job. */
507
+ readonly id: string;
508
+ readonly attempts: number;
509
+ /** Not claimable before this time (milliseconds since the epoch). */
510
+ readonly notBefore: number;
511
+ readonly lease: {
512
+ readonly worker: string;
513
+ readonly until: number;
514
+ } | null;
515
+ readonly createdAt: number;
516
+ readonly updatedAt: number;
517
+ readonly outcome: JobOutcome | null;
518
+ }
519
+ interface DeliveryJob extends JobBase {
520
+ readonly kind: 'delivery';
521
+ readonly state: DeliveryJobState;
522
+ readonly delivery: Delivery;
523
+ }
524
+ interface StepJob extends JobBase {
525
+ readonly kind: 'step';
526
+ readonly state: StepJobState;
527
+ readonly deliveryId: string;
528
+ readonly deploymentId: string;
529
+ readonly request: StepRequest;
530
+ readonly progress: StepProgress;
531
+ }
532
+ type Job = DeliveryJob | StepJob;
533
+ /**
534
+ * A step job's id: `step:${deliveryId}:${step}:${wallet}`, then
535
+ * `:${instance}` for a step on an instance, so one trigger can run the same
536
+ * step for one wallet on several instances. The `step:` namespace and the
537
+ * encoded components keep step job ids apart from delivery ids (which start
538
+ * `webhook:`, `stream:`, `dispatch:` or `view:`) and from each other.
539
+ */
540
+ declare function stepJobId(deliveryId: string, request: Pick<StepRequest, 'step' | 'wallet' | 'instance'>): string;
541
+
542
+ /** Spending limits of a consent grant, per asset: `SOL` (in lamports) or a mint (in base units). */
543
+ interface StoredConsentLimits {
544
+ /** The most one step may move out of the wallet. An asset without one may not leave. */
545
+ readonly perExecution: Readonly<Record<string, string>>;
546
+ /** The most all steps together may move out within a rolling window. */
547
+ readonly perWindow: Readonly<Record<string, {
548
+ readonly amount: string;
549
+ readonly windowMs: number;
550
+ }>>;
551
+ }
552
+ /** The signed proof a grant was made from. */
553
+ interface ConsentProof {
554
+ /** The exact message the wallet signed. */
555
+ readonly message: string;
556
+ /** Base58 Ed25519 signature by the wallet. */
557
+ readonly signature: string;
558
+ readonly nonce: string;
559
+ readonly issuedAt: number;
560
+ }
561
+ /** A user's consent for automations to run steps for their wallet. */
562
+ interface ConsentGrant {
563
+ readonly wallet: string;
564
+ readonly deploymentId: string;
565
+ /** `null` for every instance of the deployment. */
566
+ readonly instanceId: string | null;
567
+ readonly steps: readonly string[];
568
+ readonly limits: StoredConsentLimits;
569
+ /** The user's own settings for the steps (amount, weekday, ...). */
570
+ readonly params: Readonly<Record<string, unknown>>;
571
+ /** Milliseconds since the epoch. */
572
+ readonly expiresAt: number;
573
+ readonly grantedAt: number;
574
+ readonly revokedAt: number | null;
575
+ readonly proof: ConsentProof;
576
+ }
577
+ /** What one step moved, counted against a grant's windows. */
578
+ interface LedgerEntry {
579
+ readonly jobId: string;
580
+ readonly wallet: string;
581
+ readonly deploymentId: string;
582
+ /** `SOL` or a mint. */
583
+ readonly asset: string;
584
+ /** Lamports or base units, as a decimal string. */
585
+ readonly amount: string;
586
+ readonly recordedAt: number;
587
+ /** Set when the step's intent never landed, so it no longer counts. */
588
+ readonly voidedAt: number | null;
589
+ }
590
+ interface ConsentQuery {
591
+ readonly wallet: string;
592
+ readonly deploymentId: string;
593
+ readonly step: string;
594
+ readonly instanceId: string | null;
595
+ readonly now: number;
596
+ }
597
+ /** What a view source last saw for one key. */
598
+ interface SourceState {
599
+ readonly source: string;
600
+ readonly key: string;
601
+ /** Canonical-JSON SHA-256 of the stable value. */
602
+ readonly hash: string;
603
+ /** The stable derived value (JSON). */
604
+ readonly value: unknown;
605
+ /** Increments with every stable change; delivery ids carry it. */
606
+ readonly generation: number;
607
+ /** A new value waiting out `stableFor`, if any. */
608
+ readonly pending: {
609
+ readonly hash: string;
610
+ readonly value: unknown;
611
+ readonly since: number;
612
+ } | null;
613
+ /** The newest observation order written (a decimal integer), or `null`. */
614
+ readonly observed: string | null;
615
+ /** Increments with every write; compare-and-set reads it. */
616
+ readonly version: number;
617
+ readonly updatedAt: number;
618
+ }
619
+ interface SourceAdvance {
620
+ readonly source: string;
621
+ readonly key: string;
622
+ /** The version read; `null` when there was no state yet. The write fails if it moved. */
623
+ readonly expectedVersion: number | null;
624
+ readonly next: Pick<SourceState, 'hash' | 'value' | 'pending'> & {
625
+ readonly observed?: string | null;
626
+ };
627
+ /**
628
+ * Accept a delivery in the same atomic step, with id
629
+ * `view:${encodeURIComponent(source)}:${encodeURIComponent(key)}:${generation}`
630
+ * (the generation after the write).
631
+ */
632
+ readonly deliver?: {
633
+ readonly name: string;
634
+ readonly payload: unknown;
635
+ };
636
+ readonly now: number;
637
+ }
638
+ /** A step job to create, without its bookkeeping. */
639
+ interface NewStepJob {
640
+ readonly id: string;
641
+ readonly deploymentId: string;
642
+ readonly request: StepRequest;
643
+ }
644
+ interface ClaimInput {
645
+ readonly worker: string;
646
+ readonly leaseMs: number;
647
+ readonly now: number;
648
+ }
649
+ interface TransitionInput {
650
+ /** The worker that holds the lease. */
651
+ readonly worker: string;
652
+ /** The state the job must be in; the transition fails otherwise. */
653
+ readonly from: JobState;
654
+ readonly to: JobState;
655
+ readonly now: number;
656
+ /** Merged into a step job's progress. */
657
+ readonly progress?: Partial<StepProgress>;
658
+ readonly outcome?: JobOutcome | null;
659
+ /** Keep the lease (for more work in the same claim) instead of releasing it. */
660
+ readonly keepLease?: {
661
+ readonly until: number;
662
+ };
663
+ /** Not claimable again before this time. */
664
+ readonly notBefore?: number;
665
+ readonly attempts?: number;
666
+ /**
667
+ * Ledger changes made in the same atomic step as the transition:
668
+ * `record` adds entries for this job, `void` voids its entries except
669
+ * those for `keepAssets`.
670
+ */
671
+ readonly ledger?: {
672
+ /** New entries; `recordedAt` defaults to the transition's `now`. */
673
+ readonly record?: readonly (Omit<LedgerEntry, 'jobId' | 'recordedAt' | 'voidedAt'> & {
674
+ readonly recordedAt?: number;
675
+ })[];
676
+ readonly void?: boolean;
677
+ readonly keepAssets?: readonly string[];
678
+ };
679
+ }
680
+ /**
681
+ * Where automations keep their state. A store must provide:
682
+ *
683
+ * - a unique insert for deliveries, so a trigger is accepted once;
684
+ * - exclusive job claims with a lease;
685
+ * - at most one active step job per wallet (`running`, `signed`,
686
+ * `submitted`, `submitted_unknown`);
687
+ * - compare-and-set transitions, by state and lease holder;
688
+ * - completing a delivery job and enqueuing its step jobs atomically;
689
+ * - ledger writes atomic with the job transition they belong to;
690
+ * - single-use consent nonces;
691
+ * - compare-and-set on view-source state, with its delivery in the same step.
692
+ *
693
+ * `runStoreConformance` from `@usearete/automations/testing` checks all of
694
+ * these; a custom store must pass it.
695
+ */
696
+ interface AutomationStore {
697
+ /**
698
+ * Tie the store to one deployment. The first call records it; a call for
699
+ * another deployment throws `StoreDeploymentError`. Use a separate store
700
+ * (for Postgres, a separate schema) per deployment.
701
+ */
702
+ bindDeployment(deploymentId: string): Promise<void>;
703
+ readonly deliveries: {
704
+ /**
705
+ * Record the delivery and queue its delivery job, atomically. `false`
706
+ * when a delivery with this id was already accepted, or its job still
707
+ * exists after the delivery id was pruned.
708
+ */
709
+ accept(delivery: Delivery, now: number): Promise<boolean>;
710
+ /** Forget accepted delivery ids older than `before`; returns how many. */
711
+ prune(before: number): Promise<number>;
712
+ };
713
+ readonly jobs: {
714
+ get(id: string): Promise<Job | null>;
715
+ /**
716
+ * Claim the next job: a queued delivery job; a queued step job whose
717
+ * wallet has no active step job (it becomes `running`); or an active job
718
+ * whose lease has lapsed. Jobs before their `notBefore` are skipped.
719
+ */
720
+ claimNext(input: ClaimInput): Promise<Job | null>;
721
+ /** Move a job from `from` to `to` if it is still there and leased by `worker`. */
722
+ transition(id: string, input: TransitionInput): Promise<boolean>;
723
+ /** Extend `worker`'s lease. `false` when it no longer holds it. */
724
+ renew(id: string, worker: string, until: number): Promise<boolean>;
725
+ /**
726
+ * Mark a running delivery job `done` and create its step jobs, in one
727
+ * step. Step jobs whose id exists already are left alone.
728
+ */
729
+ completeDelivery(id: string, worker: string, steps: readonly NewStepJob[], now: number): Promise<boolean>;
730
+ /** When this wallet last completed this step in this deployment, or `null`. */
731
+ lastCompleted(input: {
732
+ wallet: string;
733
+ deploymentId: string;
734
+ step: string;
735
+ }): Promise<number | null>;
736
+ list(filter?: {
737
+ kind?: Job['kind'];
738
+ state?: JobState;
739
+ }): Promise<Job[]>;
740
+ /** Delete finished jobs last updated before `before`; returns how many. */
741
+ prune(before: number): Promise<number>;
742
+ };
743
+ readonly consent: {
744
+ /** Store a grant, replacing the wallet's grant for the same deployment and instance. */
745
+ put(grant: ConsentGrant): Promise<void>;
746
+ /** The active grant covering the query, or `null`. */
747
+ find(query: ConsentQuery): Promise<ConsentGrant | null>;
748
+ /** Revoke grants (or only some of their steps). Returns how many grants changed. */
749
+ revoke(input: {
750
+ wallet: string;
751
+ deploymentId: string;
752
+ steps?: readonly string[];
753
+ /** Only the grant scoped to this instance. */
754
+ instanceId?: string;
755
+ now: number;
756
+ }): Promise<number>;
757
+ /** Every grant of a wallet, active or not. */
758
+ list(input: {
759
+ wallet: string;
760
+ }): Promise<ConsentGrant[]>;
761
+ /** Active grants in a deployment that cover `step`, for fan-out. */
762
+ active(input: {
763
+ deploymentId: string;
764
+ step?: string;
765
+ now: number;
766
+ }): Promise<ConsentGrant[]>;
767
+ /** Remember a nonce handed out with a proof message. */
768
+ issueNonce(nonce: string, expiresAt: number): Promise<void>;
769
+ /** Use an issued, unexpired nonce once. `false` when it was not issued, expired or used. */
770
+ useNonce(nonce: string, now: number): Promise<boolean>;
771
+ };
772
+ readonly sources: {
773
+ get(source: string, key: string): Promise<SourceState | null>;
774
+ /**
775
+ * Write a key's state if its version is still `expectedVersion`. The
776
+ * generation increments when the stable hash changes. Returns the new
777
+ * state, or `null` when another writer got there first.
778
+ */
779
+ advance(input: SourceAdvance): Promise<SourceState | null>;
780
+ /** The furthest cursor an append-view source recorded. */
781
+ cursor(source: string): Promise<string | null>;
782
+ /**
783
+ * Record a cursor if it is after the stored one (see `cursorAfter`), so a
784
+ * slow replica can never move a source back. Atomic.
785
+ */
786
+ putCursor(source: string, cursor: string, now: number): Promise<void>;
787
+ /** Hold a source exclusively until `until`. `false` while another holder's lease runs. */
788
+ acquireLease(source: string, holder: string, until: number, now: number): Promise<boolean>;
789
+ releaseLease(source: string, holder: string): Promise<void>;
790
+ };
791
+ readonly ledger: {
792
+ /** The sum of unvoided entries for the wallet and asset recorded at or after `since`. */
793
+ spent(input: {
794
+ wallet: string;
795
+ deploymentId: string;
796
+ asset: string;
797
+ since: number;
798
+ }): Promise<bigint>;
799
+ list(input: {
800
+ wallet: string;
801
+ }): Promise<LedgerEntry[]>;
802
+ };
803
+ }
804
+
805
+ /** An append-view cursor `{epoch}:{offset}` as numbers, or `null` when it is not one. */
806
+ declare function parseCursor(cursor: string): {
807
+ epoch: string;
808
+ offset: bigint;
809
+ } | null;
810
+ /**
811
+ * Whether `next` moves past `current`. Offsets are compared within an
812
+ * epoch; a different epoch is a new tape lifetime, which cannot be ordered
813
+ * against the old one, so it always moves on. Any cursor is after an
814
+ * unreadable one.
815
+ */
816
+ declare function cursorAfter(next: string, current: string | null): boolean;
817
+ declare class StoreDeploymentError extends Error {
818
+ constructor(bound: string, requested: string);
819
+ }
820
+
821
+ /**
822
+ * Consent: what a user signs to let automations run steps for their wallet.
823
+ *
824
+ * The proof is a human-readable, domain-bound message in the style of
825
+ * Sign-In-With-Solana. It is built from the grant's fields, so the backend
826
+ * rebuilds it from the fields it is given and checks the wallet's signature
827
+ * over that, rather than parsing a message. Raising a limit or changing a
828
+ * setting needs a new signature; revoking does not.
829
+ *
830
+ * The signature is an accountability and anti-bug record. It does not
831
+ * protect against a compromised backend; the wallet provider's policy and
832
+ * the protocol's on-chain rules do.
833
+ */
834
+
835
+ /** The asset key for SOL (wrapped SOL included), in lamports. */
836
+ declare const SOL = "SOL";
837
+ declare const CONSENT_PROOF_VERSION = "arete.automation-consent/v1";
838
+ type Amount = string | number | bigint;
839
+ /** Spending limits, per asset: `SOL` (lamports) or a mint (base units). */
840
+ interface ConsentLimits {
841
+ /** The most one step may move out of the wallet. An asset without a limit may not leave. */
842
+ readonly perExecution?: Readonly<Record<string, Amount>>;
843
+ /** The most all steps together may move out within a rolling window. */
844
+ readonly perWindow?: Readonly<Record<string, {
845
+ readonly amount: Amount;
846
+ readonly window: Duration;
847
+ }>>;
848
+ }
849
+ /** What a user consents to. */
850
+ interface ConsentFields {
851
+ readonly wallet: string;
852
+ readonly deploymentId: string;
853
+ /** One instance, or omit for every instance of the deployment. */
854
+ readonly instanceId?: string | null;
855
+ readonly steps: readonly string[];
856
+ readonly limits: ConsentLimits;
857
+ /** The user's settings for the steps, for example `{ amount: '50000000', weekday: 'fri' }`. */
858
+ readonly params?: Readonly<Record<string, unknown>>;
859
+ /** When the grant ends (a `Date` or milliseconds since the epoch). */
860
+ readonly expiresAt: Date | number;
861
+ }
862
+ interface NormalizedConsent {
863
+ readonly wallet: string;
864
+ readonly deploymentId: string;
865
+ readonly instanceId: string | null;
866
+ readonly steps: string[];
867
+ readonly limits: StoredConsentLimits;
868
+ readonly params: Record<string, unknown>;
869
+ readonly expiresAt: number;
870
+ }
871
+ type ConsentErrorCode = 'invalid_fields' | 'invalid_signature' | 'nonce_not_usable' | 'proof_stale' | 'expired' | 'no_domain';
872
+ declare class ConsentError extends Error {
873
+ readonly code: ConsentErrorCode;
874
+ constructor(code: ConsentErrorCode, message: string);
875
+ }
876
+ /** Validate and normalize consent fields. Throws {@link ConsentError}. */
877
+ declare function normalizeConsent(fields: ConsentFields): NormalizedConsent;
878
+ /** A window as the message shows it: the largest whole unit. */
879
+ declare function windowText(windowMs: number): string;
880
+ /**
881
+ * The message a wallet signs to grant consent. Lines are joined by `\n`:
882
+ *
883
+ * ```text
884
+ * <domain> asks to run protocol steps for your wallet without asking each time:
885
+ * <wallet>
886
+ *
887
+ * Deployment: <deployment id>
888
+ * Instance: <instance id, or "any">
889
+ * Steps: <steps, sorted, comma-separated>
890
+ * Per run: <amount> lamports of SOL | <amount> base units of <mint> (one line per asset, sorted)
891
+ * Per <window>: <amount> ... (one line per asset, sorted)
892
+ * Setting <key as a JSON string>: <RFC 8785 JSON of the value> (one line per key, sorted)
893
+ * Expires At: <ISO 8601 UTC>
894
+ *
895
+ * Nothing else may leave your wallet through these steps. You can revoke this at any time without signing.
896
+ *
897
+ * Version: arete.automation-consent/v1
898
+ * Nonce: <nonce>
899
+ * Issued At: <ISO 8601 UTC>
900
+ * ```
901
+ *
902
+ * Without per-run limits the message says that nothing may leave the wallet.
903
+ */
904
+ declare function consentMessage(domain: string, consent: NormalizedConsent, nonce: string, issuedAt: number): string;
905
+ /**
906
+ * What a prepared step moves out of the wallet, per asset, summed over its
907
+ * transactions: SOL as the policy measures it (wrapped SOL and token
908
+ * accounts closed to someone else included), and every mint that leaves.
909
+ * The summary must be complete; the policy refuses one that is not.
910
+ */
911
+ declare function stepOutflows(summary: PreparedSummary): Map<string, bigint>;
912
+
913
+ /**
914
+ * View sources: watch a live view and dispatch a trigger when a condition
915
+ * becomes true.
916
+ *
917
+ * Exactly-once comes from the store, not from ids derived from the view's
918
+ * data (list and state views carry no cursor or version). Each source key
919
+ * keeps its last stable value; a change is written by compare-and-set and,
920
+ * when `changed(previous, next)` holds, its delivery
921
+ * (`view:${source}:${key}:${generation}`) is accepted in the same step. So:
922
+ *
923
+ * - replicas watching one view produce one delivery per transition;
924
+ * - after a restart the first snapshot is compared with the stored value,
925
+ * so a crossing during downtime fires once and nothing fires twice;
926
+ * - every transition has its own generation, so A → B → A → B fires three
927
+ * times.
928
+ *
929
+ * A value that changed and changed back while no replica was watching is
930
+ * not seen: list and state views hold current state, not history.
931
+ *
932
+ * Replicas whose views lag each other can still disagree about the order of
933
+ * values: one stores B while another, behind, still sees A. Give the source
934
+ * `observedAt` (for example the slot a row was updated at) so an older
935
+ * observation never replaces a newer one, or run it on one replica
936
+ * (`exclusive`).
937
+ *
938
+ * A trigger means "go and look", not the truth: handlers read live state
939
+ * before returning steps, and every step is checked again before signing.
940
+ */
941
+
942
+ /** The parts of a list view a source reads. A `TypedListView` is one. */
943
+ interface ListViewLike<T> {
944
+ /** The complete current rows (the source seeds from these). */
945
+ get(): Promise<T[]>;
946
+ /** The rows the open subscription holds now; used to drop rows a reconnect removed. */
947
+ getSync?(): T[] | undefined;
948
+ watchRich(options?: {
949
+ withSnapshot?: boolean;
950
+ }): AsyncIterable<RichUpdate<T>>;
951
+ }
952
+ /** The parts of an append view a source reads: updates with `{epoch}:{offset}` cursors. */
953
+ interface AppendViewLike<T> {
954
+ watch(options?: {
955
+ after?: string;
956
+ withSnapshot?: boolean;
957
+ }): AsyncIterable<Update<T>>;
958
+ }
959
+ interface SourceOptionsBase<V> {
960
+ /** The handler name the source dispatches to. Unique per source. */
961
+ readonly name: string;
962
+ /**
963
+ * Whether a change from `previous` (undefined the first time) to `next`
964
+ * is a trigger. Defaults to every change. Put hysteresis here or in
965
+ * `derive`.
966
+ */
967
+ readonly changed?: (previous: V | undefined, next: V) => boolean;
968
+ /** A new value must hold this long before it counts. Stops one bad update from firing. */
969
+ readonly stableFor?: Duration;
970
+ }
971
+ type Observed = number | bigint | null | undefined;
972
+ /** One signal for the whole view (for example: which market pays best). */
973
+ interface AggregateSourceOptions<T, V> extends SourceOptionsBase<V> {
974
+ /** A row's identity in the view (its id), so updates and removals find the row. */
975
+ readonly key: (row: T) => string;
976
+ /** The signal, as JSON. */
977
+ readonly derive: (rows: readonly T[]) => V;
978
+ /** How recent the rows are (a slot, a sequence); an older observation is ignored. */
979
+ readonly observedAt?: (rows: readonly T[]) => Observed;
980
+ }
981
+ /** One signal per row, kept separately (for example: is this position's health low). */
982
+ interface RowSourceOptions<T, V> extends SourceOptionsBase<V> {
983
+ readonly key: (row: T) => string;
984
+ /** The row's signal, as JSON. */
985
+ readonly derive: (row: T) => V;
986
+ /** Rows to watch; others are ignored. */
987
+ readonly filter?: (row: T) => boolean;
988
+ /** How recent the row is (a slot, a sequence); an older observation is ignored. */
989
+ readonly observedAt?: (row: T) => Observed;
990
+ }
991
+ interface ListSource<T = any, V = any> {
992
+ readonly kind: 'list';
993
+ readonly name: string;
994
+ readonly view: ListViewLike<T>;
995
+ /** A row's identity in the view. */
996
+ readonly rowKey: (row: T) => string;
997
+ readonly stableForMs: number;
998
+ readonly changed: (previous: V | undefined, next: V) => boolean;
999
+ /** The keys, values and observation orders the rows give. */
1000
+ readonly signals: (rows: readonly T[]) => Array<[string, V, bigint | null]>;
1001
+ /** The key, value and order one changed row gives, or `null` for an aggregate (re-read every row). */
1002
+ readonly rowSignal: ((row: T) => [string, V, bigint | null] | undefined) | null;
1003
+ }
1004
+ interface AppendSource<T = any> {
1005
+ readonly kind: 'append';
1006
+ readonly name: string;
1007
+ readonly view: AppendViewLike<T>;
1008
+ readonly filter?: (row: T) => boolean;
1009
+ }
1010
+ type ViewSource = ListSource | AppendSource;
1011
+ /** The payload a list-view source dispatches. */
1012
+ interface ViewTrigger<V = unknown> {
1013
+ readonly source: string;
1014
+ /** The row key, or `all` for an aggregate source. */
1015
+ readonly key: string;
1016
+ readonly value: V;
1017
+ /** The stable value before; absent the first time the key is seen. */
1018
+ readonly previous?: V;
1019
+ }
1020
+ /** The payload an append-view source dispatches, one per row. */
1021
+ interface AppendTrigger<T = unknown> {
1022
+ readonly source: string;
1023
+ readonly cursor: string;
1024
+ readonly row: T;
1025
+ }
1026
+ declare const AGGREGATE_KEY = "all";
1027
+ /** A source with one signal for the whole list view, under the key `all`. */
1028
+ declare function fromView<T, V>(view: ListViewLike<T>, options: AggregateSourceOptions<T, V>): ListSource<T, V>;
1029
+ /** A source with one signal per row of a list view, keyed by `key(row)`. */
1030
+ declare function fromViewRows<T, V>(view: ListViewLike<T>, options: RowSourceOptions<T, V>): ListSource<T, V>;
1031
+ /** A source over an append view: one trigger per row, resumed from the stored cursor. */
1032
+ declare function fromAppendView<T>(view: AppendViewLike<T>, options: {
1033
+ readonly name: string;
1034
+ readonly filter?: (row: T) => boolean;
1035
+ }): AppendSource<T>;
1036
+
1037
+ /**
1038
+ * Automations as a definition: the handlers and view sources, apart from the
1039
+ * client, signer, store and secrets that run them. The same definition runs
1040
+ * under `a4 dev` (against the project's dev environment) and in production
1041
+ * (`automations.use(definition)`), so moving from one to the other changes
1042
+ * configuration only.
1043
+ */
1044
+
1045
+ /** Connects a stack's views: the dev environment's under `a4 dev`, the published stack in production. */
1046
+ type StackConnector = <T extends StackDefinition>(stack: T) => Promise<ConnectedArete<T>>;
1047
+ /** What a definition's setup registers handlers and sources on. */
1048
+ interface AutomationsSetup<TTypes extends ProtocolTypeMap = ProtocolTypeMap> {
1049
+ readonly on: Automations<TTypes>['on'];
1050
+ source(source: ViewSource, options?: {
1051
+ exclusive?: boolean;
1052
+ }): SourceHandle;
1053
+ /** Connect a stack for its views, to build view sources from. */
1054
+ readonly connect: StackConnector;
1055
+ }
1056
+ /** The policy fields a definition may set: the deployment and cluster come from where it runs. */
1057
+ type DefinitionPolicy = Omit<PolicyInput, 'deployments' | 'clusters'>;
1058
+ interface AutomationsDefinition<TTypes extends ProtocolTypeMap = ProtocolTypeMap> {
1059
+ readonly kind: 'automations';
1060
+ /** The protocol's generated definition, for its client. */
1061
+ readonly protocol: ProtocolDefinition<TTypes, any, any>;
1062
+ /**
1063
+ * The programs and caps the steps need. `a4 dev` signs under it; in
1064
+ * production, spread it into `definePolicy` with the deployment and
1065
+ * clusters. Without it `a4 dev` allows 1 SOL per transaction and no token.
1066
+ */
1067
+ readonly policy?: DefinitionPolicy;
1068
+ readonly setup: (automations: AutomationsSetup<TTypes>) => void | Promise<void>;
1069
+ }
1070
+ /**
1071
+ * Define automations for a protocol: handlers for its events and dispatched
1072
+ * triggers, and view sources. Export the definition as the module's default
1073
+ * for `a4 dev` (`[dev.automations] entry` in arete.toml).
1074
+ */
1075
+ declare function defineAutomations<TTypes extends ProtocolTypeMap>(protocol: ProtocolDefinition<TTypes, any, any>, setup: (automations: AutomationsSetup<TTypes>) => void | Promise<void>, options?: {
1076
+ policy?: DefinitionPolicy;
1077
+ }): AutomationsDefinition<TTypes>;
1078
+ /** Whether `value` is a definition, from this or another copy of the package. */
1079
+ declare function isAutomationsDefinition(value: unknown): value is AutomationsDefinition;
1080
+ /** Connects with the SDK's defaults: `ARETE_API_KEY` or the `a4` login. */
1081
+ declare const connectStack: StackConnector;
1082
+
1083
+ /** The parts of a protocol client automations use. A secret-key `ProtocolClient` is one. */
1084
+ interface AutomationClient<TTypes extends ProtocolTypeMap = ProtocolTypeMap> {
1085
+ readonly deploymentId: string;
1086
+ /** Present on a real client; only used to infer the protocol's types. */
1087
+ readonly definition?: ProtocolDefinition<TTypes, any, any>;
1088
+ readonly steps: object;
1089
+ allowedSteps(input: {
1090
+ instance: string;
1091
+ wallet: string;
1092
+ }): Promise<readonly ProtocolStepAvailability[]>;
1093
+ submit(intent: ExecutionIntentV1 | string, signedTransactions: readonly (string | Uint8Array)[]): Promise<ProtocolIntentState>;
1094
+ readonly intents: {
1095
+ get(executionId: string): Promise<ProtocolIntentState>;
1096
+ };
1097
+ events?(instanceId: string, options: ProtocolEventsOptions<TTypes>): ProtocolEventSubscription;
1098
+ /** Used to end instance-scoped consent when the instance is terminal. */
1099
+ readonly instances?: {
1100
+ get(id: string): Promise<{
1101
+ readonly terminal: boolean;
1102
+ }>;
1103
+ };
1104
+ /** Present on a real client: a handler reads a participant's current state with it. */
1105
+ readonly participants?: {
1106
+ get(input: {
1107
+ instance: string;
1108
+ wallet: string;
1109
+ signal?: AbortSignal;
1110
+ }): Promise<ProtocolParticipantDetail<TTypes>>;
1111
+ };
1112
+ }
1113
+ type EventName<TTypes extends ProtocolTypeMap> = Extract<keyof TTypes['events'], string>;
1114
+ type StepName<TTypes extends ProtocolTypeMap> = Extract<keyof TTypes['steps'], string>;
1115
+ type EventState<TTypes extends ProtocolTypeMap, E extends EventName<TTypes>> = TTypes['events'][E]['machine'] extends 'participant' ? TTypes['participantState'] : TTypes['instanceState'];
1116
+ /** An emitted protocol event, from a webhook or an instance's event stream. */
1117
+ interface AutomationEvent<TEvent extends string = string, TState extends string = string> {
1118
+ /** The emission id. */
1119
+ readonly id: string;
1120
+ readonly event: TEvent;
1121
+ readonly deploymentId: string;
1122
+ readonly instanceId: string;
1123
+ readonly participantId: string | null;
1124
+ /** The machine's state at emission; `null` for stream events, which do not carry it. */
1125
+ readonly state: TState | null;
1126
+ readonly occurredAt: string;
1127
+ readonly source: 'webhook' | 'stream';
1128
+ }
1129
+ interface TriggerInfo {
1130
+ readonly name: string;
1131
+ readonly id: string;
1132
+ readonly source: TriggerSource;
1133
+ readonly receivedAt: number;
1134
+ }
1135
+ interface StepOptions {
1136
+ /** Skip the step when the same wallet completed it this recently. */
1137
+ readonly cooldown?: Duration;
1138
+ }
1139
+ interface HandlerContext<TTypes extends ProtocolTypeMap = ProtocolTypeMap> {
1140
+ readonly client: AutomationClient<TTypes>;
1141
+ readonly trigger: TriggerInfo;
1142
+ /**
1143
+ * Active consent grants in this deployment, for fan-out: every wallet the
1144
+ * automations may act for. A starting set only; each step job checks
1145
+ * consent again.
1146
+ */
1147
+ readonly consents: {
1148
+ list(query?: {
1149
+ step?: StepName<TTypes> | (string & {});
1150
+ instanceId?: string;
1151
+ }): Promise<ConsentGrant[]>;
1152
+ };
1153
+ /** A step request for the handler to return. Handlers never prepare steps themselves. */
1154
+ step<S extends StepName<TTypes>>(step: S, request: {
1155
+ wallet: string;
1156
+ instance?: string;
1157
+ args?: Partial<TTypes['steps'][S]['args']> & Record<string, unknown>;
1158
+ }, options?: StepOptions): StepRequest;
1159
+ }
1160
+ type Handler<TPayload, TTypes extends ProtocolTypeMap> = (payload: TPayload, ctx: HandlerContext<TTypes>) => readonly StepRequest[] | void | Promise<readonly StepRequest[] | void>;
1161
+ /** Where a step job ended, for notifications. */
1162
+ interface AutomationOutcome {
1163
+ readonly jobId: string;
1164
+ readonly deliveryId: string;
1165
+ readonly deploymentId: string;
1166
+ readonly step: string;
1167
+ readonly wallet: string;
1168
+ readonly instanceId: string | null;
1169
+ readonly state: StepJobState;
1170
+ readonly code: string;
1171
+ readonly reason?: string;
1172
+ readonly detail?: unknown;
1173
+ readonly executionId: string | null;
1174
+ readonly signatures: readonly (string | null)[];
1175
+ }
1176
+ interface AutomationsOptions<TTypes extends ProtocolTypeMap = ProtocolTypeMap> {
1177
+ readonly client: AutomationClient<TTypes>;
1178
+ readonly signer: ProtocolSigner;
1179
+ readonly policy: SignerPolicy;
1180
+ /** Measures what each transaction moves out of the wallet; the policy refuses what it cannot measure. */
1181
+ readonly simulator: Simulator;
1182
+ readonly store: AutomationStore;
1183
+ /**
1184
+ * Where the deployment runs; the policy must list it. Defaults to the
1185
+ * client's: `dev` for a client `a4 dev` generated (the deployment in a dev
1186
+ * environment), else `mainnet`.
1187
+ */
1188
+ readonly cluster?: Cluster;
1189
+ /** Transaction formats in the deployment's order of preference. Defaults to v1, then v0. */
1190
+ readonly transactionFormats?: readonly ProtocolTransactionFormat[];
1191
+ /** Identifies this worker in leases. Defaults to a random id. */
1192
+ readonly worker?: string;
1193
+ /** How long a claim lasts before another worker may take the job over. Defaults to 60s. */
1194
+ readonly lease?: Duration;
1195
+ /** Attempts before a job that has not signed yet fails. Defaults to 5. */
1196
+ readonly maxAttempts?: number;
1197
+ /** How long one claim waits for a submitted intent to settle. Defaults to 30s. */
1198
+ readonly trackFor?: Duration;
1199
+ /** Between intent status reads while tracking. Defaults to 2s. */
1200
+ readonly trackInterval?: Duration;
1201
+ /** Before a pending intent is read again in a later claim. Defaults to 10s, doubling up to 5m. */
1202
+ readonly reconcileInterval?: Duration;
1203
+ readonly clock?: Clock;
1204
+ /** Consent proofs: the host name users see in the message they sign. */
1205
+ readonly consent?: {
1206
+ readonly domain: string;
1207
+ /** How long a proof message may wait to be signed and granted. Defaults to 10 minutes. */
1208
+ readonly proofTtl?: Duration;
1209
+ };
1210
+ /** Every step job that reaches a final state. */
1211
+ readonly onOutcome?: (outcome: AutomationOutcome) => void | Promise<void>;
1212
+ /** Step jobs refused by the policy or the signer checks. */
1213
+ readonly onRefused?: (outcome: AutomationOutcome) => void | Promise<void>;
1214
+ /** Step jobs the protocol rejected (a guard, an access or a state code). */
1215
+ readonly onRejected?: (outcome: AutomationOutcome) => void | Promise<void>;
1216
+ /** Errors the runtime recovers from by retrying, and hook failures. */
1217
+ readonly onError?: (error: unknown, context: {
1218
+ jobId?: string;
1219
+ phase: string;
1220
+ }) => void;
1221
+ }
1222
+ interface WebhookHandlerOptions {
1223
+ /** The endpoint's signing secret. */
1224
+ readonly secret: string | Uint8Array;
1225
+ /** Allowed clock skew in seconds; defaults to 300. */
1226
+ readonly toleranceSeconds?: number;
1227
+ }
1228
+ /** A consent proof message for the user's wallet to sign. */
1229
+ interface ConsentProofRequest {
1230
+ /** The UTF-8 message, lines joined by `\n`. */
1231
+ readonly message: string;
1232
+ /** `message` as bytes, for `wallet.signMessage`. */
1233
+ readonly bytes: Uint8Array;
1234
+ readonly nonce: string;
1235
+ /** Milliseconds since the epoch; send it back with the grant. */
1236
+ readonly issuedAt: number;
1237
+ }
1238
+ interface ConsentGrantInput extends ConsentFields {
1239
+ readonly nonce: string;
1240
+ readonly issuedAt: number;
1241
+ /** The wallet's 64-byte Ed25519 signature over the message, or its base58 text. */
1242
+ readonly signature: Uint8Array | string;
1243
+ }
1244
+ interface ConsentApi {
1245
+ /** Build the message to sign. Issues a single-use nonce. */
1246
+ proofMessage(fields: ConsentFields): Promise<ConsentProofRequest>;
1247
+ /**
1248
+ * Verify the wallet's signature over the message rebuilt from `fields`,
1249
+ * use the nonce, and store the grant. `warnings` lists what the signer
1250
+ * reports missing on the provider side (no delegation, no provider policy).
1251
+ */
1252
+ grant(input: ConsentGrantInput): Promise<{
1253
+ grant: ConsentGrant;
1254
+ warnings: string[];
1255
+ }>;
1256
+ /** Revoke without a signature. Queued and running jobs see it before signing. */
1257
+ revoke(input: {
1258
+ wallet: string;
1259
+ steps?: readonly string[];
1260
+ instanceId?: string;
1261
+ }): Promise<number>;
1262
+ list(input: {
1263
+ wallet: string;
1264
+ }): Promise<ConsentGrant[]>;
1265
+ }
1266
+ interface WebhookResult {
1267
+ readonly status: number;
1268
+ readonly body: Record<string, unknown>;
1269
+ }
1270
+ interface Automations<TTypes extends ProtocolTypeMap = ProtocolTypeMap> {
1271
+ readonly consent: ConsentApi;
1272
+ /** Handle an emitted protocol event. */
1273
+ on<E extends EventName<TTypes>>(event: E, handler: Handler<AutomationEvent<E, EventState<TTypes, E>>, TTypes>): this;
1274
+ /** Handle a dispatched trigger (a cron tick, a view source). */
1275
+ on<TPayload = unknown>(name: string, handler: Handler<TPayload, TTypes>): this;
1276
+ /**
1277
+ * Accept a trigger. `id` makes it idempotent: a repeated dispatch with the
1278
+ * same id is ignored. Resolves `false` for a duplicate.
1279
+ */
1280
+ dispatch(name: string, payload: unknown, options: {
1281
+ id: string;
1282
+ }): Promise<boolean>;
1283
+ /** A fetch-standard webhook endpoint (Workers, Next.js, Hono, Bun). */
1284
+ webhookHandler(options: WebhookHandlerOptions): (request: Request) => Promise<Response>;
1285
+ /** An Express endpoint. Mount it behind `express.raw({ type: 'application/json' })`. */
1286
+ expressWebhookHandler(options: WebhookHandlerOptions): (req: ExpressLikeRequest, res: ExpressLikeResponse) => Promise<void>;
1287
+ /** Verify and accept a webhook delivery from its raw body and headers. */
1288
+ acceptWebhook(body: string | Uint8Array, headers: WebhookHeadersLike, options: WebhookHandlerOptions): Promise<WebhookResult>;
1289
+ /** Follow one instance's event stream and accept its events. */
1290
+ follow(instanceId: string, options?: {
1291
+ lastEventId?: string;
1292
+ signal?: AbortSignal;
1293
+ }): ProtocolEventSubscription;
1294
+ /**
1295
+ * Watch a view source and accept its triggers until `stop()`. With
1296
+ * `exclusive`, only the replica holding the source's lease subscribes
1297
+ * (the compare-and-set already makes several subscribers correct; the
1298
+ * lease only saves bandwidth).
1299
+ */
1300
+ source(source: ViewSource, options?: {
1301
+ exclusive?: boolean;
1302
+ }): SourceHandle;
1303
+ /**
1304
+ * Register a definition's handlers and view sources (see
1305
+ * `defineAutomations`). `connect` opens the stacks its sources read;
1306
+ * it defaults to the SDK's own connection.
1307
+ */
1308
+ use(definition: AutomationsDefinition<TTypes>, options?: {
1309
+ connect?: StackConnector;
1310
+ }): Promise<this>;
1311
+ /** Read a list-view source's snapshot once and accept what changed (for a serverless cron). */
1312
+ poll(source: ListSource): Promise<{
1313
+ delivered: number;
1314
+ pending: number;
1315
+ }>;
1316
+ /** Claim and run one job. `false` when none was ready. */
1317
+ runOnce(): Promise<boolean>;
1318
+ /** Run jobs until none is ready (or `maxJobs` ran). Returns how many ran. */
1319
+ drain(options?: {
1320
+ maxJobs?: number;
1321
+ }): Promise<number>;
1322
+ /** Run jobs in the background until `stop()`. */
1323
+ start(options?: {
1324
+ pollInterval?: Duration;
1325
+ concurrency?: number;
1326
+ }): void;
1327
+ stop(): Promise<void>;
1328
+ }
1329
+ interface SourceHandle {
1330
+ stop(): Promise<void>;
1331
+ }
1332
+ type WebhookHeadersLike = {
1333
+ get(name: string): string | null;
1334
+ } | Record<string, string | readonly string[] | undefined>;
1335
+ interface ExpressLikeRequest {
1336
+ readonly body: unknown;
1337
+ readonly headers: Record<string, string | readonly string[] | undefined>;
1338
+ }
1339
+ interface ExpressLikeResponse {
1340
+ status(code: number): ExpressLikeResponse;
1341
+ json(body: unknown): unknown;
1342
+ }
1343
+ /** Where a client's deployment runs: `dev` when `a4 dev` generated it, else `mainnet`. */
1344
+ declare function clientCluster(client: Pick<AutomationClient, 'definition'>): Cluster;
1345
+ declare function createAutomations<TTypes extends ProtocolTypeMap = ProtocolTypeMap>(options: AutomationsOptions<TTypes>): Automations<TTypes>;
1346
+
1347
+ /**
1348
+ * An in-memory store for tests and single-process development. State is
1349
+ * lost on restart, so it must not back automations that sign real funds.
1350
+ * Each method runs to completion without awaiting, which makes every
1351
+ * operation atomic under JavaScript's single-threaded event loop.
1352
+ */
1353
+
1354
+ /**
1355
+ * A memory store's contents, for `a4 dev` to keep across restarts. Opaque:
1356
+ * write it with `v8.serialize` and read it back with `v8.deserialize`.
1357
+ */
1358
+ interface MemoryStoreState {
1359
+ readonly version: 1;
1360
+ readonly deliveries: Map<string, number>;
1361
+ readonly jobs: Map<string, Job>;
1362
+ readonly order: number;
1363
+ readonly sequence: Map<string, number>;
1364
+ readonly grants: ConsentGrant[];
1365
+ readonly deployment: string | null;
1366
+ readonly nonces: Map<string, {
1367
+ expiresAt: number;
1368
+ used: boolean;
1369
+ }>;
1370
+ readonly ledger: LedgerEntry[];
1371
+ readonly sourceStates: Map<string, SourceState>;
1372
+ readonly cursors: Map<string, string>;
1373
+ }
1374
+ declare function memoryStore(options?: {
1375
+ state?: MemoryStoreState;
1376
+ }): AutomationStore;
1377
+
1378
+ export { ACTIVE_STEP_STATES, AGGREGATE_KEY, CONSENT_PROOF_VERSION, ConsentError, DEFAULT_CLUSTERS, DEFAULT_TRANSACTION_FORMATS, KNOWN_CLUSTERS, PolicyError, RUNTIME_PROGRAMS, SOL, SignerRefusal, StoreDeploymentError, TERMINAL_DELIVERY_STATES, TERMINAL_STEP_STATES, TransactionDecodeError, checkPrepared, checkPreparedAgainstIntent, checkSignedTransactions, checkTarget, clientCluster, connectStack, consentMessage, createAutomations, cursorAfter, decodeTransaction, defineAutomations, definePolicy, durationMs, fromAppendView, fromView, fromViewRows, isAutomationsDefinition, isSignerRefusal, memoryStore, negotiateTransactionFormat, normalizeConsent, parseCursor, rpcSimulator, scheduleSlot, stepJobId, stepOutflows, summarizePrepared, systemClock, windowText };
1379
+ export type { AggregateSourceOptions, AppendSource, AppendTrigger, AppendViewLike, AuthorityFinding, AutomationClient, AutomationEvent, AutomationOutcome, AutomationStore, Automations, AutomationsDefinition, AutomationsOptions, AutomationsSetup, CheckPreparedInput, ClaimInput, Clock, Cluster, ConsentApi, ConsentErrorCode, ConsentFields, ConsentGrant, ConsentGrantInput, ConsentLimits, ConsentProof, ConsentProofRequest, ConsentQuery, DecodedTransaction, DefinitionPolicy, DelegationStatus, Delivery, DeliveryJob, DeliveryJobState, Duration, ExpressLikeRequest, ExpressLikeResponse, Handler, HandlerContext, Job, JobOutcome, JobState, LedgerEntry, ListSource, ListViewLike, NewStepJob, NormalizedConsent, PolicyInput, PreparedStepLike, PreparedSummary, ProtocolSigner, Refusal, RefusalRule, RowSourceOptions, RpcSimulatorOptions, SchedulePeriod, SignTransactionsInput, SignerEffectSummary, SignerPolicy, Simulator, SourceAdvance, SourceHandle, SourceState, StackConnector, StepJob, StepJobState, StepOptions, StepProgress, StepRequest, StoredConsentLimits, TokenDelta, TransactionSummary, TransitionInput, TriggerInfo, TriggerSource, ViewSource, ViewTrigger, WebhookHandlerOptions, WebhookResult };