@molpha/sdk 0.2.0-dev-20261002062009 → 0.2.0-dev-20261005191253

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/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Address } from '@solana/kit';
2
2
  import { Wallet, Idl, AnchorProvider } from '@anchor-lang/core';
3
- import { U as UpstreamQuote, A as APIConfig, a as AssetDomain, b as UpstreamTerms, E as EvmSigner, R as RegistrySelectionConfig, S as Signer, N as NodeKeyVerifier, c as Node, d as NodesInfo, e as SourcePaymentOptions, f as Attestation, g as SolanaAccountMeta, h as SolanaConnection, i as SolanaAddress, j as NodeKeyVerifierArgs, k as AggregationConfig, M as MolphaWallet } from './wallet-HfGXPpOp.js';
4
- export { l as AttestationPayload, m as EncKeyBundle, n as NumericConfig, o as RegistryInfo, p as SchnorrSignature, q as gatewaySignerFromWallet, s as signerFromKeypair } from './wallet-HfGXPpOp.js';
3
+ import { U as UpstreamQuote, A as APIConfig, a as AssetDomain, b as UpstreamTerms, E as EvmSigner, S as Signer, c as SourcePaymentOptions, R as RegistrySelectionConfig, N as Node, d as NodeKeyVerifier, e as NodesInfo, f as Attestation, g as SolanaAccountMeta, h as SolanaConnection, i as SolanaAddress, j as NodeKeyVerifierArgs, k as AggregationConfig, M as MolphaWallet } from './wallet--IGHoCtG.js';
4
+ export { l as AttestationPayload, m as EncKeyBundle, n as NumericConfig, o as RegistryInfo, p as SchnorrSignature, q as gatewaySignerFromWallet, s as signerFromKeypair } from './wallet--IGHoCtG.js';
5
5
  import BN from 'bn.js';
6
6
 
7
7
  /**
@@ -27,6 +27,12 @@ interface GatewayInfo {
27
27
  gatewayAuthority: string;
28
28
  /** Program id the gateway settles against; must match the client's when present. */
29
29
  programId?: string;
30
+ /** The gateway's round timestamp grid in milliseconds (`round.tick_ms`), when advertised. */
31
+ tickMs?: number;
32
+ /** Longest the gateway waits for a round, in seconds (`node.agg_wait_seconds`), when advertised. */
33
+ roundTimeoutSeconds?: number;
34
+ /** Rounds the gateway runs at once (`limits.max_inflight_rounds`); 0 or absent: not advertised. */
35
+ maxInflightRounds?: number;
30
36
  }
31
37
  declare function normalizeEndpoint(input: GatewayEndpointInput): GatewayEndpoint;
32
38
  /** 32-byte form of a base58 address. */
@@ -48,8 +54,8 @@ interface RequestAuthFields {
48
54
  sourceId: Uint8Array | string;
49
55
  /** u8 — the per-request quorum. */
50
56
  signaturesRequired: number;
51
- /** u64 unix seconds. */
52
- timestamp: number | bigint;
57
+ /** u64 unix SECONDS at signing (the auth freshness stamp, not the round's timestamp). */
58
+ authTimestamp: number | bigint;
53
59
  }
54
60
  /** Borsh encoding of `RequestAuth` — the hash body, without the domain. */
55
61
  declare function encodeRequestAuth(fields: RequestAuthFields): Uint8Array;
@@ -117,15 +123,16 @@ interface RequestSignedDataOptions {
117
123
  */
118
124
  subscriptionOwner?: string;
119
125
  /**
120
- * Solana pubkey (base58) of the consumer authority that signs gateway auth.
126
+ * Solana pubkey (base58) of the consumer: the subscription owner, or a delegate of it.
121
127
  * Overrides the gateway's `defaultConsumerAuthority` when set.
122
128
  */
123
129
  consumerAuthority?: string;
124
130
  /**
125
- * Signs `hashRequestAuth({ programId, gateway, sourceId, signaturesRequired, timestamp })`.
131
+ * Signs `hashRequestAuth({ programId, gateway, sourceId, signaturesRequired, authTimestamp })`.
126
132
  * The hash binds the gateway's on-chain account, so it is computed — and the signer
127
- * invoked — once per endpoint actually tried. Overrides the gateway's `defaultSigner`
128
- * when set. When both are omitted, sends an all-zero authSig (dev only).
133
+ * invoked — once per endpoint actually tried, with a fresh `authTimestamp` each time.
134
+ * Overrides the gateway's `defaultSigner` when set. When both are omitted, sends an all-zero
135
+ * authSig (a gateway rejects it with 401; for tests and local tooling only).
129
136
  */
130
137
  signer?: Signer;
131
138
  encrypt?: {
@@ -144,9 +151,23 @@ interface RequestSignedDataOptions {
144
151
  sourcePayment?: SourcePaymentOptions;
145
152
  /** Max accepted value age in seconds. Default 60. */
146
153
  maxAge?: number;
147
- /** Each retry re-rolls the timestamp. Default 15. */
154
+ /**
155
+ * Max attempts. Each retry waits for a later gateway tick, plus jitter, and for longer after a
156
+ * busy answer (`Retry-After`) or a failure that is not a conflict (capped exponential backoff);
157
+ * see {@link retryDelayMs}. Default {@link DEFAULT_MAX_RETRIES}.
158
+ */
148
159
  maxRetries?: number;
149
- /** Per-request timeout in ms. Default 5000. */
160
+ /**
161
+ * The gateway's tick grid in milliseconds (its `round.tick_ms`). A retry waits for the start of
162
+ * the next tick so it is a new round, not a duplicate of the last. Default: the gateway's
163
+ * advertised `tickMs` (read once, on the first retry), else 1000.
164
+ */
165
+ tickMs?: number;
166
+ /**
167
+ * Per-request timeout in ms. Default {@link DEFAULT_ROUND_TIMEOUT_MS}, above the gateway's own
168
+ * wait for a round: a shorter timeout abandons rounds the gateway is still running and the retry
169
+ * then starts another.
170
+ */
150
171
  timeoutMs?: number;
151
172
  /**
152
173
  * Pre-fetched round inputs. Any field present here skips its network/on-chain
@@ -177,8 +198,9 @@ interface MolphaGatewayOptions {
177
198
  /** Solana pubkey (base58) of the consumer authority used when a request omits it. */
178
199
  defaultConsumerAuthority?: string;
179
200
  /**
180
- * Molpha program id the request authorization is bound to. Defaults to the vendored
181
- * `MOLPHA_PROGRAM_ID`; must match the gateway's deployment.
201
+ * Molpha program id the request authorization is bound to (and that `fetchGatewayInfo`
202
+ * checks the gateway settles against). Defaults to the vendored `MOLPHA_PROGRAM_ID`;
203
+ * must match the gateway's deployment.
182
204
  */
183
205
  programId?: string;
184
206
  /**
@@ -191,15 +213,53 @@ interface MolphaGatewayOptions {
191
213
  * may use gateway-provided node keys without authentication. Defaults to false.
192
214
  */
193
215
  allowUnverifiedNodeKeysForPrivateApi?: boolean;
216
+ /** Source of randomness in [0, 1) for retry jitter; defaults to `Math.random` (tests only). */
217
+ random?: () => number;
194
218
  }
195
219
  /**
196
220
  * The slow-changing inputs a `requestSignedData` round binds to. Fetch once with
197
221
  * {@link MolphaGateway.prepareContext} and reuse across many rounds.
198
222
  */
199
223
  interface RoundContext extends RegistrySelectionConfig {
200
- /** Full node set used to encrypt private API secrets for the selected nodes. */
224
+ /** Full registry node set: private API secrets are encrypted for every node in it. */
201
225
  nodes: Node[];
202
226
  }
227
+ /** Milliseconds from `nowMs` to the start of the next tick, plus a millisecond of margin. */
228
+ declare function msUntilNextTick(nowMs: number, tickMs: number): number;
229
+ /**
230
+ * Default per-request timeout: above the gateway's own wait for a round (`node.agg_wait_seconds`,
231
+ * 30 s by default), so the client does not abandon a round the gateway is still running and then
232
+ * start a second one. A gateway advertises its value as `roundTimeoutSeconds` in `GET /v1/info`.
233
+ */
234
+ declare const DEFAULT_ROUND_TIMEOUT_MS = 35000;
235
+ /** Default number of attempts. */
236
+ declare const DEFAULT_MAX_RETRIES = 6;
237
+ /**
238
+ * Why an attempt failed, as far as it decides how long to wait before the next one:
239
+ * - `conflict`: HTTP 409, this consumer already has a round for the source in this tick;
240
+ * - `busy`: HTTP 503 or 429, the gateway is at capacity or a node set is unavailable, optionally
241
+ * with the gateway's `Retry-After`;
242
+ * - `error`: anything else that may be transient (timeout, network, other 5xx).
243
+ */
244
+ type RetryCause = {
245
+ kind: "conflict";
246
+ } | {
247
+ kind: "busy";
248
+ retryAfterMs?: number;
249
+ } | {
250
+ kind: "error";
251
+ };
252
+ /**
253
+ * How long to wait before the next attempt, in ms. Every delay reaches at least the start of the
254
+ * next tick (a retry inside the tick that just failed would be a duplicate round), then adds jitter
255
+ * so clients that failed together do not retry together:
256
+ * - conflict: the next tick plus up to half a tick;
257
+ * - busy: the later of the next tick and the gateway's `Retry-After` (a tick if absent), plus up to
258
+ * a tick;
259
+ * - error: capped exponential backoff (250 ms, 500 ms, ... 5 s) with half of it randomized.
260
+ * `failures` is the number of attempts that have failed so far (1 after the first).
261
+ */
262
+ declare function retryDelayMs(cause: RetryCause, failures: number, nowMs: number, tickMs: number, random?: () => number): number;
203
263
  /** Default gateway base URL when `endpoints` is omitted. */
204
264
  declare const DEFAULT_GATEWAY_ENDPOINT = "https://dev-gateway.molpha.io/";
205
265
  /** Thrown for terminal gateway errors (400/401) — never retried. */
@@ -220,12 +280,17 @@ declare class MolphaGateway {
220
280
  private readonly programIdBytes;
221
281
  /** Gateway PDA per endpoint URL; resolved once per client lifetime. */
222
282
  private readonly gatewayPdas;
283
+ /** Advertised timing per endpoint URL, read lazily on the first retry. */
284
+ private readonly advertisedInfo;
285
+ private readonly random;
223
286
  constructor(endpoints?: GatewayEndpointInput | GatewayEndpointInput[], getRegistrySelectionConfig?: () => Promise<RegistrySelectionConfig>, defaultSigner?: Signer,
224
287
  /**
225
- * Either a default subscription owner (base58) or gateway options. A string
226
- * keeps the previous positional form used by standalone callers/tests.
288
+ * Either a default subscription owner (base58) or gateway options. A string is the
289
+ * shorthand for `{ defaultSubscriptionOwner }` used by standalone callers/tests.
227
290
  */
228
291
  defaultSubscriptionOwnerOrOptions?: string | MolphaGatewayOptions, defaultConsumerAuthority?: string);
292
+ /** The configured gateway base URLs, in failover order. */
293
+ endpointUrls(): string[];
229
294
  /** Tries endpoints in order; returns the first node list it can fetch. */
230
295
  getNodes(): Promise<Node[]>;
231
296
  /**
@@ -233,7 +298,7 @@ declare class MolphaGateway {
233
298
  * selection policy, which sizes the eligible set a paid source must fund.
234
299
  *
235
300
  * The `registry` block is advisory: it is whatever the gateway read from the
236
- * chain, and is absent on older gateways or when that read failed. Prefer the
301
+ * chain, and is absent when that read failed. Prefer the
237
302
  * on-chain read (`MolphaSolanaClient.getRegistrySelectionConfig`) whenever a
238
303
  * Solana connection is available — `requestSignedData` already does.
239
304
  */
@@ -257,12 +322,23 @@ declare class MolphaGateway {
257
322
  prepareContext(): Promise<RoundContext>;
258
323
  /**
259
324
  * Request a threshold-signed data update from the gateway, with retry +
260
- * failover. Per attempt a fresh timestamp yields a fresh selection bitmap; the
261
- * body is POSTed to each endpoint in order until one `completed`s.
325
+ * failover. The body is POSTed to each endpoint in order until one `completed`s.
326
+ *
327
+ * The round's `sourceId` is derived from `apiConfig`. The request carries no round timestamp:
328
+ * the gateway assigns the round's `timestamp` (unix milliseconds) from its own clock
329
+ * on a tick grid, and the committee follows from it. The result carries the assigned
330
+ * timestamp and the signers' bitmap. A retry waits for a later tick, because a round that was
331
+ * already reserved or dispatched cannot run again in the same one (HTTP 409).
262
332
  *
263
- * The round's `sourceId` is derived from `apiConfig`. The request authorization
264
- * binds the program id and each gateway's on-chain account, so the auth signature
265
- * is recomputed for every endpoint tried.
333
+ * The request is authorized by a `RequestAuth` signature (`authSig`) over the program id, the
334
+ * endpoint's Gateway PDA, the source id, the quorum and `authTimestamp` (unix seconds, read
335
+ * from the local clock for every attempt). It only proves the caller may spend the
336
+ * subscription's rounds; it is not part of the round, so the signature is recomputed for every
337
+ * endpoint tried.
338
+ *
339
+ * Because the committee is unknown until the gateway stamps the round, private API
340
+ * secrets are encrypted for **every** node of the registry; the gateway forwards only the
341
+ * selected nodes' envelopes.
266
342
  *
267
343
  * By default this fetches the registry selection config up front and the node
268
344
  * set only when the round needs it (private API encryption, or when the registry
@@ -282,6 +358,12 @@ declare class MolphaGateway {
282
358
  * encryption) or when nothing else can tell us the node count.
283
359
  */
284
360
  private resolveContext;
361
+ /**
362
+ * The gateway's advertised tick grid, read once per endpoint and only when a retry needs it. Any
363
+ * failure (an old gateway without `/v1/info`, a timeout) means "not advertised", so the caller
364
+ * falls back to the default; a failure is not cached.
365
+ */
366
+ private advertisedTickMs;
285
367
  /** Gateway PDA bytes for an endpoint, cached per URL. A failed lookup is not cached. */
286
368
  private resolveGatewayPda;
287
369
  private lookupGatewayPda;
@@ -374,6 +456,17 @@ declare function planIdFromVariant(variant: Record<string, unknown>): PlanId;
374
456
  * `Program` over the vendored IDL (program `3d01170`, "Epoch settlements").
375
457
  */
376
458
 
459
+ /**
460
+ * Compute units to request for a `submit_attestation` carrying `signerCount` signatures.
461
+ *
462
+ * The program's LiteSVM benchmark measures the whole transaction at about `43k + 9.1k` units per
463
+ * signer (119k at 8 signers, 155k at 12, 208k at 18), so the old flat 1.4M request was 7-10 times
464
+ * what a typical aggregate uses. That matters once a priority fee is attached, because it is
465
+ * priced per requested unit, and for how the scheduler packs blocks. The estimate adds 15% and a
466
+ * fixed 10k: the program's selection check costs a little more on some registries than the
467
+ * benchmark's, and a limit that is hit fails the transaction for good.
468
+ */
469
+ declare function estimateSubmitComputeUnits(signerCount: number): number;
377
470
  type Commitment$1 = NonNullable<ConstructorParameters<typeof AnchorProvider>[2]>["commitment"];
378
471
  interface SubscribeResult {
379
472
  signature: string;
@@ -392,8 +485,7 @@ interface PlanInfo {
392
485
  isActive: boolean;
393
486
  }
394
487
  /**
395
- * On-chain `Subscription`. The program no longer tracks usage: `used_rounds`,
396
- * `prepaid_usdc` and the locked `price` were removed (round quota is counted by the
488
+ * On-chain `Subscription`. The program does not track usage (round quota is counted by the
397
489
  * gateway's off-chain outbox), so there is no on-chain "rounds used" figure to read.
398
490
  */
399
491
  interface SubscriptionInfo {
@@ -428,8 +520,8 @@ interface FeedAccount {
428
520
  hash: Record<string, never>;
429
521
  };
430
522
  submitter: Address;
431
- /** u64 unix seconds. */
432
- canonicalTimestamp: BN;
523
+ /** u64 unix MILLISECONDS (the round's gateway-assigned timestamp). */
524
+ timestamp: BN;
433
525
  signaturesRequired: number;
434
526
  signersBitmap: number[];
435
527
  registryVersion: number;
@@ -443,7 +535,7 @@ interface SubmitAttestationArgs {
443
535
  sourceId: number[];
444
536
  registryVersion: number;
445
537
  signaturesRequired: number;
446
- canonicalTimestamp: BN;
538
+ timestamp: BN;
447
539
  };
448
540
  signature: {
449
541
  aggSigS: number[];
@@ -459,7 +551,18 @@ interface SubmitAttestationArgs {
459
551
  };
460
552
  }
461
553
  interface SubmitAttestationOptions {
554
+ /**
555
+ * Compute-unit limit. Defaults to {@link estimateSubmitComputeUnits} for the aggregate's signer
556
+ * count, not the 1.4M maximum.
557
+ */
462
558
  computeUnitLimit?: number;
559
+ /**
560
+ * Priority fee in micro-lamports per compute unit. A number is used as given; `"auto"` takes the
561
+ * 75th percentile of the fees recently paid by transactions that wrote the feed, capped at 1
562
+ * lamport per unit. Omitted: no priority fee, which is right on a quiet cluster. Raise it (or
563
+ * use `"auto"`) when submits are dropped under load.
564
+ */
565
+ priorityFeeMicroLamports?: number | "auto";
463
566
  /**
464
567
  * Precomputed signer coalition key. When omitted, the client fetches signer `Node`
465
568
  * accounts and sums their secp256k1 keys ({@link computeCoalitionKey}).
@@ -482,6 +585,10 @@ declare class MolphaSolanaClient {
482
585
  private readonly program;
483
586
  private readonly provider;
484
587
  readonly programId: Address;
588
+ private readonly registryCache;
589
+ private readonly nodeKeyCache;
590
+ private readonly coalitionCache;
591
+ private priorityFeeCache?;
485
592
  private constructor();
486
593
  static create(opts: CreateClientOpts): MolphaSolanaClient;
487
594
  private get wallet();
@@ -549,8 +656,12 @@ declare class MolphaSolanaClient {
549
656
  * `keccak256(rawValue)`; {@link buildSubmitAttestationArgs} validates that before send.
550
657
  */
551
658
  submitAttestation(attestation: Attestation, opts?: SubmitAttestationOptions): Promise<SubmitResult>;
659
+ /** The registry for `version`, reused for {@link REGISTRY_CACHE_MS}. */
660
+ private fetchRegistryCached;
661
+ private resolvePriorityFee;
552
662
  /** Sum of the signers' keys, read from their on-chain `Node` accounts (one batched fetch). */
553
663
  private computeSignerCoalitionKey;
664
+ private sumSignerKeys;
554
665
  /**
555
666
  * Read the feed written by `submitter` (default: this wallet) for
556
667
  * `(sourceId, signaturesRequired)`, or `null` before its first submit.
@@ -709,8 +820,8 @@ interface AttestationMessageFields {
709
820
  signersBitmap: string | Uint8Array;
710
821
  /** 32-byte packed value (hex or bytes). */
711
822
  value: string | Uint8Array;
712
- /** Unix seconds (u64). */
713
- canonicalTimestamp: number | bigint;
823
+ /** Unix milliseconds (u64), assigned by the gateway. */
824
+ timestamp: number | bigint;
714
825
  }
715
826
  /** Compute the attestation message hash (32 bytes). */
716
827
  declare function attestationMessageHash(fields: AttestationMessageFields): Uint8Array;
@@ -726,9 +837,17 @@ declare function normalizeSecp256k1PublicKeyHex(publicKeyHex: string, label?: st
726
837
  declare function secp256k1PublicKeyFromCoordinates(x: Uint8Array, y: Uint8Array, label?: string): string;
727
838
 
728
839
  /**
729
- * `seed = keccak256(keccak256("MOLPHA_SELECTION_V1") || sourceId || be32(rv) || be64(ts))`.
840
+ * Width in milliseconds of the window selection reads from the timestamp. The timestamp
841
+ * is unix milliseconds; the seed uses `floor(timestamp / SELECTION_WINDOW_MS)`, so
842
+ * sub-second precision never changes the committee. Changing it is a consensus break and must
843
+ * bump the selection prefix.
730
844
  */
731
- declare function deriveSelectionSeed(sourceId: Uint8Array, registryVersion: number, canonicalTimestamp: number | bigint): Uint8Array;
845
+ declare const SELECTION_WINDOW_MS = 1000n;
846
+ /**
847
+ * `seed = keccak256(keccak256("MOLPHA_SELECTION_V1") || sourceId || be32(rv) || be64(ts / 1000))`,
848
+ * where `ts` is the timestamp in unix MILLISECONDS (its 1 s window index is hashed).
849
+ */
850
+ declare function deriveSelectionSeed(sourceId: Uint8Array, registryVersion: number, timestamp: number | bigint): Uint8Array;
732
851
  /** `min(signaturesRequired + redundancyBuffer, nodeCount)`. */
733
852
  declare function effectiveSelectionSize(signaturesRequired: number, redundancyBuffer: number, nodeCount: number): number;
734
853
  /** Is bit `bit` set in the 32-byte big-endian `bitmap`? */
@@ -743,10 +862,24 @@ declare function selectedIndices(bitmap: Uint8Array, nodeCount: number): number[
743
862
  declare function deriveGroupBitmap(seed: Uint8Array, nodeCount: number, groupSize: number): Uint8Array;
744
863
  /**
745
864
  * Convenience orchestrator: derive the selection bitmap end-to-end.
746
- * `redundancy` defaults to 0; `ts` defaults to the current unix second.
865
+ * `redundancy` defaults to 0; `ts` is the timestamp in unix MILLISECONDS and defaults
866
+ * to now. The gateway assigns the real timestamp of a round, so this is for tests and tools.
747
867
  */
748
868
  declare function deriveSelectionBitmap(sourceId: Uint8Array, registryVersion: number, nodeCount: number, signaturesRequired: number, redundancy?: number, ts?: number | bigint): Uint8Array;
749
869
 
870
+ /**
871
+ * `timestamp` is unix MILLISECONDS, assigned by the gateway. Chain clocks (Solana `Clock`,
872
+ * EVM `block.timestamp`), epoch windows and `maxAge`/staleness are measured in SECONDS, so
873
+ * anything that compares the two must go through {@link timestampSeconds}.
874
+ */
875
+ /** `timestamp` (unix ms) as whole unix seconds, floored. */
876
+ declare function timestampSeconds(timestamp: number | bigint): number;
877
+ /**
878
+ * Age in whole seconds of a timestamp against `nowSeconds`, saturating at 0 so a
879
+ * timestamp slightly ahead of the local clock never goes negative.
880
+ */
881
+ declare function timestampAgeSeconds(timestamp: number | bigint, nowSeconds: number): number;
882
+
750
883
  /**
751
884
  * Result codes returned by Molpha's stateless verifier contracts (`verify` on EVM and
752
885
  * Starknet). Shared across VMs: the same attestation yields the same code on every chain.
@@ -761,11 +894,11 @@ declare const VERIFY_CODES: {
761
894
  readonly FEED_WITNESS: 1;
762
895
  /** The payload's `registryVersion` does not exist on this verifier. */
763
896
  readonly BAD_REGISTRY_VERSION: 2;
764
- /** Structurally invalid input, or a `canonicalTimestamp` in the future when `maxAge != 0`. */
897
+ /** Structurally invalid input, or a `timestamp` in the future when `maxAge != 0`. */
765
898
  readonly MALFORMED: 3;
766
- /** `canonicalTimestamp` predates the registry version's activation. */
899
+ /** `timestamp` predates the registry version's activation. */
767
900
  readonly NOT_YET_ACTIVE: 4;
768
- /** The registry version was superseded more than the grace window before `canonicalTimestamp`. */
901
+ /** The registry version was superseded more than the grace window before `timestamp`. */
769
902
  readonly VERSION_EXPIRED: 5;
770
903
  /** Reserved. Never returned. */
771
904
  readonly COMPROMISED_QUORUM: 6;
@@ -823,7 +956,7 @@ declare const MOLPHA_VERIFIER_ABI: readonly [{
823
956
  readonly type: "uint8";
824
957
  readonly internalType: "uint8";
825
958
  }, {
826
- readonly name: "canonicalTimestamp";
959
+ readonly name: "timestamp";
827
960
  readonly type: "uint64";
828
961
  readonly internalType: "uint64";
829
962
  }];
@@ -1071,8 +1204,8 @@ interface EvmAttestationPayload {
1071
1204
  registryVersion: number;
1072
1205
  /** `uint8`. */
1073
1206
  signaturesRequired: number;
1074
- /** `uint64`, unix seconds. */
1075
- canonicalTimestamp: bigint;
1207
+ /** `uint64`, unix MILLISECONDS (`timestamp / 1000` is the seconds the verifier's `maxAge` uses). */
1208
+ timestamp: bigint;
1076
1209
  }
1077
1210
  /** `IVerifier.SchnorrSignature`. */
1078
1211
  interface EvmSchnorrSignature {
@@ -1136,7 +1269,7 @@ declare function buildEvmVerifierArgs(attestation: Attestation, options: BuildEv
1136
1269
  * ABI calldata for `verify(attestation, maxAge)`: the 4-byte selector followed by nine
1137
1270
  * 32-byte words. Both structs are static, so they encode inline with no offsets:
1138
1271
  *
1139
- * `value, sourceId, registryVersion, signaturesRequired, canonicalTimestamp, signature,
1272
+ * `value, sourceId, registryVersion, signaturesRequired, timestamp, signature,
1140
1273
  * commitment, signersBitmap, maxAge`
1141
1274
  *
1142
1275
  * For a raw `eth_call` (`{ to: verifier, data }`).
@@ -1182,8 +1315,8 @@ interface StarknetAttestationPayload {
1182
1315
  registry_version: number;
1183
1316
  /** `u8`. */
1184
1317
  signatures_required: number;
1185
- /** `u64`, unix seconds. */
1186
- canonical_timestamp: number;
1318
+ /** `u64`, unix MILLISECONDS (`/ 1000` is the seconds the verifier's `max_age` uses). */
1319
+ timestamp: number;
1187
1320
  }
1188
1321
  /** Starknet calldata shape for `SchnorrSignature`. */
1189
1322
  interface StarknetSchnorrSignature {
@@ -1243,7 +1376,7 @@ declare function buildStarknetVerifierArgs(attestation: Attestation, options: Bu
1243
1376
  * Flat felt calldata for `verify(attestation, max_age)`, in Cairo `Serde` order — 13 felts:
1244
1377
  *
1245
1378
  * `value.low, value.high, source_id.low, source_id.high, registry_version,
1246
- * signatures_required, canonical_timestamp, signature.low, signature.high, commitment,
1379
+ * signatures_required, timestamp, signature.low, signature.high, commitment,
1247
1380
  * signers_bitmap.low, signers_bitmap.high, max_age`
1248
1381
  *
1249
1382
  * For a raw `starknet_call` with `entry_point_selector = selector("verify")`.
@@ -1290,6 +1423,98 @@ declare const subscriptionPda: (owner: SolanaAddress, programId: SolanaAddress)
1290
1423
  */
1291
1424
  declare function feedPda(sourceId: Uint8Array, signaturesRequired: number, submitter: SolanaAddress, programId: SolanaAddress): Address;
1292
1425
 
1426
+ /**
1427
+ * Drive many feeds through the gateway (and optionally on to Solana) without overloading it.
1428
+ *
1429
+ * The consumer is the party that pushes updates and pays for them, so the cost and the burst
1430
+ * behaviour of a large set of feeds is the consumer's to control. {@link requestMany} does the parts
1431
+ * that matter at volume:
1432
+ *
1433
+ * - the registry inputs are read once and shared by every request, not once per feed;
1434
+ * - concurrency is bounded and adapts: it starts from the gateway's advertised capacity, halves
1435
+ * when the gateway answers busy (503/429) and creeps back up while requests succeed (AIMD);
1436
+ * - request starts are spread over a window by a hash of each feed's source id, so feeds that fall
1437
+ * due together do not arrive together, and the same feed always lands at the same offset;
1438
+ * - submitting to Solana runs on its own, smaller limiter, pipelined behind the requests;
1439
+ * - a failed submit never discards the signed attestation the gateway returned (and quota paid for).
1440
+ *
1441
+ * Each request still has the single-request retry policy (backoff with jitter, `Retry-After`, a stale
1442
+ * registry refreshed once; see {@link retryDelayMs}).
1443
+ */
1444
+
1445
+ /** One feed to update. */
1446
+ interface BulkFeed {
1447
+ apiConfig: APIConfig;
1448
+ signaturesRequired: number;
1449
+ /** Echoed in the result; for the caller's own bookkeeping. */
1450
+ label?: string;
1451
+ }
1452
+ interface BulkOptions {
1453
+ /**
1454
+ * Most gateway requests in flight. The limit adapts below this ceiling. Default: half the gateway's
1455
+ * advertised `maxInflightRounds` (at most 64), or 32 when it advertises none.
1456
+ */
1457
+ concurrency?: number;
1458
+ /**
1459
+ * Spread request starts over this many ms by a hash of the feed's source id. Feeds that are due on
1460
+ * the same tick otherwise arrive together and queue at the gateway. Default 0 (no spreading).
1461
+ */
1462
+ spreadMs?: number;
1463
+ /** Submit each attestation to Solana after its round. Default true. */
1464
+ submit?: boolean;
1465
+ /** Most Solana submits in flight. Default 8. */
1466
+ submitConcurrency?: number;
1467
+ /**
1468
+ * Options applied to every request (subscription owner, signer, `maxRetries`, `timeoutMs`, ...).
1469
+ * `apiConfig`, `signaturesRequired` and `context` are set per feed.
1470
+ */
1471
+ request?: Omit<RequestSignedDataOptions, "apiConfig" | "signaturesRequired" | "context">;
1472
+ /** Called as each feed finishes, in completion order. */
1473
+ onResult?: (result: BulkResult) => void;
1474
+ /** Stops starting new feeds; those already started finish. */
1475
+ signal?: AbortSignal;
1476
+ }
1477
+ interface BulkResult {
1478
+ /** Position in the input array. */
1479
+ index: number;
1480
+ label?: string;
1481
+ /** The gateway round succeeded (submitting is reported separately in `submitError`). */
1482
+ ok: boolean;
1483
+ /** The signed attestation, whenever the round succeeded, including when the submit failed. */
1484
+ attestation?: Attestation;
1485
+ /** Transaction signature and feed account, when submitted. */
1486
+ signature?: string;
1487
+ feedAddress?: Address;
1488
+ /** Why the round failed. */
1489
+ error?: unknown;
1490
+ /** Why submitting failed after a successful round; `attestation` is still valid. */
1491
+ submitError?: unknown;
1492
+ }
1493
+ /**
1494
+ * An adaptive concurrency limit: additive increase on success, multiplicative decrease on a busy
1495
+ * answer. Waiters are served in arrival order.
1496
+ */
1497
+ declare class AdaptiveLimiter {
1498
+ private readonly max;
1499
+ private readonly now;
1500
+ private limit_;
1501
+ private inFlight;
1502
+ private successes;
1503
+ private lastCut;
1504
+ private readonly waiters;
1505
+ constructor(initial: number, max: number, now?: () => number);
1506
+ get limit(): number;
1507
+ acquire(): Promise<void>;
1508
+ /** Release a slot. `busy` reports that the gateway answered at capacity. */
1509
+ release(outcome: "ok" | "busy" | "other"): void;
1510
+ }
1511
+ /** Deterministic start offset for a feed: the same source id always lands at the same point. */
1512
+ declare function spreadOffsetMs(sourceIdHex: string, spreadMs: number): number;
1513
+ /**
1514
+ * Update many feeds. Results are returned in input order; a feed that fails does not stop the others.
1515
+ */
1516
+ declare function requestMany(sdk: MolphaSDK, feeds: BulkFeed[], opts?: BulkOptions): Promise<BulkResult[]>;
1517
+
1293
1518
  /**
1294
1519
  * Vendored Molpha Anchor IDL (`target/idl/molpha.json` from the program repo).
1295
1520
  *
@@ -1327,6 +1552,20 @@ interface MolphaSDKOptions {
1327
1552
  idl?: Idl;
1328
1553
  commitment?: Commitment;
1329
1554
  }
1555
+ /**
1556
+ * Thrown by {@link MolphaSDK.requestAndSubmit} when the gateway round succeeded but submitting the
1557
+ * attestation to Solana failed. The round consumed subscription quota and the signed attestation is
1558
+ * valid, so it is carried here: submit `error.result` again (it stays valid until its freshness
1559
+ * window passes, and re-submitting it is safe) instead of requesting, and paying for, a new round.
1560
+ */
1561
+ declare class SubmitFailedError extends Error {
1562
+ /** The signed attestation the gateway returned. */
1563
+ readonly result: Attestation;
1564
+ readonly cause: unknown;
1565
+ constructor(
1566
+ /** The signed attestation the gateway returned. */
1567
+ result: Attestation, cause: unknown);
1568
+ }
1330
1569
  declare class MolphaSDK {
1331
1570
  readonly gateway: MolphaGateway;
1332
1571
  readonly solana: MolphaSolanaClient;
@@ -1342,4 +1581,4 @@ declare class MolphaSDK {
1342
1581
  }>;
1343
1582
  }
1344
1583
 
1345
- export { APIConfig, AggregationConfig, AggregationConfigError, AssetDomain, Attestation, type AttestationMessageFields, type BuildEvmVerifierArgsOptions, type BuildStarknetVerifierArgsOptions, type CoalitionKey, DEFAULT_GATEWAY_ENDPOINT, EIP712_DOMAIN_TYPEHASH, type Eip712Domain, type EvmAttestation, type EvmAttestationPayload, type EvmSchnorrSignature, EvmSigner, type EvmVerifierArgs, type EvmVerifyResult, type FeedAccount, type GatewayEndpoint, type GatewayEndpointInput, GatewayError, type GatewayInfo, INT256_MAX, INT256_MIN, MESSAGE_PREFIX, MIN_TOLERANCE_SIGNATURES, MOLPHA_IDL, MOLPHA_PROGRAM_ADDRESS, MOLPHA_PROGRAM_ID, MOLPHA_VERIFIER_ABI, MOLPHA_VERIFIER_ADDRESS, MOLPHA_VERIFIER_STARKNET_ADDRESSES, MOLPHA_VERIFIER_STARKNET_SEPOLIA, type MolphaEvmNetwork, MolphaGateway, type MolphaGatewayOptions, MolphaSDK, type MolphaSDKOptions, MolphaSolanaClient, type MolphaStarknetNetwork, MolphaWallet, Node, NodeKeyVerifier, NodeKeyVerifierArgs, NodesInfo, type PlanId, type PlanInfo, PlanType, REQUEST_AUTH_DOMAIN, RegistrySelectionConfig, type RegistryStateView, type RegistryView, type RequestAuthFields, type RequestSignedDataOptions, type RoundContext, type Secp256k1KeyInput, Signer, SourcePaymentOptions, type StarknetAttestation, type StarknetAttestationPayload, type StarknetFeltLike, type StarknetSchnorrSignature, type StarknetVerifierArgs, type StarknetVerifyResult, type SubmitAttestationArgs, type SubmitAttestationOptions, type SubmitResult, type SubscribeResult, type SubscriptionInfo, TRANSFER_WITH_AUTHORIZATION_TYPEHASH, type TransferAuthorization, UpstreamPaymentRequiredError, UpstreamQuote, UpstreamTerms, VERIFY_CODES, type VerifyCode, type VerifyCodeName, addressToBytes, assertAggregationQuorum, attestationMessageHash, attestationMessageHashFromAttestation, base64ToBytes, bigIntFromBytesBe, bitmapBitSet, bitmapToIndices, buildEvmVerifierArgs, buildStarknetVerifierArgs, buildSubmitAttestationArgs, bytesToBase64, bytesToHex, bytesToHex0x, canonicalizeAPIConfig, canonicalizeAggregation, commitmentAddressToStarknetFelt, computeCoalitionKey, concatBytes, createEvmSignerFromPrivateKey, decodeInt256, deriveGatewayPda, deriveGatewayPdaAddress, deriveGroupBitmap, deriveSelectionBitmap, deriveSelectionSeed, deriveSourceId, deriveSourceIdString, domainSeparator, effectiveSelectionSize, eligibleSetSize, encodeEvmVerifyCalldata, encodeInt256, encodeInt256Decimal, encodeRequestAuth, encodeStarknetVerifyCalldata, ensureLength, evmAddressFromPrivateKey, feedPda, formatInt256Decimal, gatewayPda, getMolphaStarknetVerifierAddress, hashRequestAuth, hexToBytes, nodePda, normalizeEndpoint, normalizeSecp256k1PublicKeyHex, parseEvmVerifyResult, parseGatewayInfo, parseStarknetVerifyResult, parseUpstreamQuote, planIdFromVariant, planPda, planVariant, probeSource, protocolConfigPda, registryPda, registryStatePda, resolveRemainingAccounts, secp256k1PublicKeyFromCoordinates, selectedIndices, signSourcePayments, signersBitmapToDecimal, signersBitmapToStarknetUint256, signersBitmapToUint256, subscriptionPda, toChecksumAddress, toFixedBytes, toFixedHex, transferWithAuthorizationDigest, transferWithAuthorizationHash, u256beFromBigInt, u32be, u32le, u64be, u64le, u8, utf8, validateSuppliedTerms, verifyCodeName };
1584
+ export { APIConfig, AdaptiveLimiter, AggregationConfig, AggregationConfigError, AssetDomain, Attestation, type AttestationMessageFields, type BuildEvmVerifierArgsOptions, type BuildStarknetVerifierArgsOptions, type BulkFeed, type BulkOptions, type BulkResult, type CoalitionKey, DEFAULT_GATEWAY_ENDPOINT, DEFAULT_MAX_RETRIES, DEFAULT_ROUND_TIMEOUT_MS, EIP712_DOMAIN_TYPEHASH, type Eip712Domain, type EvmAttestation, type EvmAttestationPayload, type EvmSchnorrSignature, EvmSigner, type EvmVerifierArgs, type EvmVerifyResult, type FeedAccount, type GatewayEndpoint, type GatewayEndpointInput, GatewayError, type GatewayInfo, INT256_MAX, INT256_MIN, MESSAGE_PREFIX, MIN_TOLERANCE_SIGNATURES, MOLPHA_IDL, MOLPHA_PROGRAM_ADDRESS, MOLPHA_PROGRAM_ID, MOLPHA_VERIFIER_ABI, MOLPHA_VERIFIER_ADDRESS, MOLPHA_VERIFIER_STARKNET_ADDRESSES, MOLPHA_VERIFIER_STARKNET_SEPOLIA, type MolphaEvmNetwork, MolphaGateway, type MolphaGatewayOptions, MolphaSDK, type MolphaSDKOptions, MolphaSolanaClient, type MolphaStarknetNetwork, MolphaWallet, Node, NodeKeyVerifier, NodeKeyVerifierArgs, NodesInfo, type PlanId, type PlanInfo, PlanType, REQUEST_AUTH_DOMAIN, RegistrySelectionConfig, type RegistryStateView, type RegistryView, type RequestAuthFields, type RequestSignedDataOptions, type RetryCause, type RoundContext, SELECTION_WINDOW_MS, type Secp256k1KeyInput, Signer, SourcePaymentOptions, type StarknetAttestation, type StarknetAttestationPayload, type StarknetFeltLike, type StarknetSchnorrSignature, type StarknetVerifierArgs, type StarknetVerifyResult, type SubmitAttestationArgs, type SubmitAttestationOptions, SubmitFailedError, type SubmitResult, type SubscribeResult, type SubscriptionInfo, TRANSFER_WITH_AUTHORIZATION_TYPEHASH, type TransferAuthorization, UpstreamPaymentRequiredError, UpstreamQuote, UpstreamTerms, VERIFY_CODES, type VerifyCode, type VerifyCodeName, addressToBytes, assertAggregationQuorum, attestationMessageHash, attestationMessageHashFromAttestation, base64ToBytes, bigIntFromBytesBe, bitmapBitSet, bitmapToIndices, buildEvmVerifierArgs, buildStarknetVerifierArgs, buildSubmitAttestationArgs, bytesToBase64, bytesToHex, bytesToHex0x, canonicalizeAPIConfig, canonicalizeAggregation, commitmentAddressToStarknetFelt, computeCoalitionKey, concatBytes, createEvmSignerFromPrivateKey, decodeInt256, deriveGatewayPda, deriveGatewayPdaAddress, deriveGroupBitmap, deriveSelectionBitmap, deriveSelectionSeed, deriveSourceId, deriveSourceIdString, domainSeparator, effectiveSelectionSize, eligibleSetSize, encodeEvmVerifyCalldata, encodeInt256, encodeInt256Decimal, encodeRequestAuth, encodeStarknetVerifyCalldata, ensureLength, estimateSubmitComputeUnits, evmAddressFromPrivateKey, feedPda, formatInt256Decimal, gatewayPda, getMolphaStarknetVerifierAddress, hashRequestAuth, hexToBytes, msUntilNextTick, nodePda, normalizeEndpoint, normalizeSecp256k1PublicKeyHex, parseEvmVerifyResult, parseGatewayInfo, parseStarknetVerifyResult, parseUpstreamQuote, planIdFromVariant, planPda, planVariant, probeSource, protocolConfigPda, registryPda, registryStatePda, requestMany, resolveRemainingAccounts, retryDelayMs, secp256k1PublicKeyFromCoordinates, selectedIndices, signSourcePayments, signersBitmapToDecimal, signersBitmapToStarknetUint256, signersBitmapToUint256, spreadOffsetMs, subscriptionPda, timestampAgeSeconds, timestampSeconds, toChecksumAddress, toFixedBytes, toFixedHex, transferWithAuthorizationDigest, transferWithAuthorizationHash, u256beFromBigInt, u32be, u32le, u64be, u64le, u8, utf8, validateSuppliedTerms, verifyCodeName };