@molpha/sdk 0.2.0-dev-20261008064918 → 0.2.0-dev-20261010184043

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/README.md CHANGED
@@ -193,30 +193,46 @@ A `programId` that differs from the client's is rejected. Because the hash diffe
193
193
 
194
194
  ### Round timestamp
195
195
 
196
- The request body carries `authSig` and `authTimestamp` but no round timestamp. The gateway assigns the
197
- round's `timestamp` from its own clock, in unix **milliseconds**, on a tick grid
198
- (`floor(now / tickMs) * tickMs`, one second by default), and the result carries it in
199
- `payload.timestamp`. Committee selection reads only the timestamp's one-second window, so
196
+ The request body carries `authSig` and `authTimestamp` but no round timestamp. The gateway stamps the
197
+ round with its own clock, in unix **milliseconds**, floored to a fixed 100 ms tick
198
+ (`floor(nowMs / 100) * 100`), and the result carries the stamp in `payload.timestamp`. The tick is a
199
+ protocol constant, exported as `ROUND_TICK_MS`: it is the same on every gateway and node, it is not
200
+ configurable, and `GET /v1/info` does not report it. Stamping does not wait for a tick boundary, so
201
+ it adds no latency. Committee selection reads only the timestamp's one-second window (ten ticks), so
200
202
  nobody can pick a committee by picking a time.
201
203
 
204
+ - One feed (a source, a quorum and a registry version) runs at most 10 rounds per second, whatever
205
+ the request rate.
206
+ - Requests for the same feed inside one tick share one round: the nodes run it once and every caller
207
+ gets the result. Each caller still spends its own unit of round quota, or its own payment.
208
+ - One consumer gets one round per tick per feed, so at most 10 per second. A second request by the
209
+ same consumer for the same source and quorum inside the tick is refused with HTTP 409; the SDK
210
+ retries it after one tick.
202
211
  - The message the nodes sign is `keccak256(MOLPHA_MESSAGE_V1 || value || sourceId || u32be(registryVersion) || u8(quorum) || u64be(timestamp) || signersBitmap)`.
203
212
  - Chain clocks, epochs and `maxAge`/staleness are in **seconds**: compare with
204
213
  `timestampSeconds(payload.timestamp)` (floored `ts / 1000`). `timestampAgeSeconds(ts, nowSeconds)` saturates at 0.
205
- - A round that was already reserved for this consumer, or dispatched to the nodes, cannot run again in the same tick (HTTP 409), so a retry waits for the next one. `tickMs` defaults to the gateway's advertised `tickMs` (read once, on the first retry, from `GET /v1/info`), else 1000.
214
+ - The SDK returns `payload.timestamp` exactly as the gateway gave it. Nodes enforce the grid when
215
+ they sign; the on-chain verifiers do not check it.
206
216
 
207
217
  ### Retries and timeouts
208
218
 
209
- `requestSignedData` makes up to `maxRetries` attempts (default 6) and chooses the wait before each by why the last one failed (`retryDelayMs`). Every wait reaches at least the next tick, then adds jitter so clients that failed together do not retry together:
219
+ `requestSignedData` makes up to `maxRetries` attempts (default 6) and chooses the wait before each by why the last one failed (`retryDelayMs`). Every wait carries jitter so clients that failed together do not retry together. The backoff step is a capped exponential: 250 ms, 500 ms, up to 5 s.
210
220
 
211
221
  | Last attempt | Wait before the next |
212
222
  | --- | --- |
213
- | 409 (this consumer already has a round for the source in this tick) | next tick, plus up to half a tick |
214
- | 503 or 429 (gateway at capacity, or nodes unavailable) | the later of the next tick and the gateway's `Retry-After`, plus up to a tick |
215
- | timeout, network error, other 5xx | capped exponential backoff (250 ms up to 5 s), half of it randomized |
223
+ | 409 (this consumer already has a round for the source and quorum in the current tick) | one tick (`ROUND_TICK_MS`, 100 ms), plus up to 20 ms |
224
+ | 503 or 429 (gateway at capacity, or too few nodes accepted the round) | the later of the gateway's `Retry-After` and the backoff step, plus up to one more step |
225
+ | timeout, network error, other 5xx | the backoff step, half of it randomized |
216
226
  | 400, 401, 402, 403 | none: terminal. 403 means the subscription is inactive or its round quota for the term is spent |
217
227
 
218
228
  A 400 saying the `registryVersion` is not the current one (a cached context, or a registry roll between your read and the request) is the exception: the SDK reads the registry afresh and retries once, without spending an attempt.
219
229
 
230
+ The wait after a 409 is a full tick rather than the time left to the next boundary: a full tick puts the retry in a later tick whatever the offset between the client's clock and the gateway's, so the SDK never computes a boundary from the local clock. A caller polling one feed gains nothing by asking more often than once per tick: space the requests at least `ROUND_TICK_MS` apart.
231
+
232
+ A retry is a new request, and the gateway stamps it anew, so it is a **new round**: it spends another unit of round quota or needs another payment, and with a paywalled source another set of payment authorizations. Nothing ties a retry to the attempt before it; there is no idempotency key. Set `maxRetries: 1` to make a single attempt and decide about a retry yourself.
233
+
234
+ The gateway limits the total load: at capacity it answers HTTP 503 with a `Retry-After`, and a consumer over its request rate gets HTTP 429. It also answers 503 when too few nodes accept a round. The SDK treats them alike: it backs off and retries, and throws a `GatewayError` with the status when the attempts run out.
235
+
220
236
  `timeoutMs` defaults to 35 s, above the gateway's own wait for a round (`roundTimeoutSeconds` in `/v1/info`, 30 s by default). A shorter timeout abandons a round the gateway is still running, and the retry then starts another. A source that cannot be fetched is reported as soon as enough nodes have failed (usually well under a second), not after the wait.
221
237
 
222
238
  ## Wallet
@@ -403,6 +419,34 @@ for (const r of results) {
403
419
 
404
420
  It reads the registry inputs once for the whole batch; bounds concurrency, starting from half the gateway's advertised `maxInflightRounds` (at most 64, else 32) and adapting (halving on a busy answer, growing back while requests succeed); runs Solana submits on their own smaller limiter (`submitConcurrency`, default 8); never discards the signed attestation of a round whose submit failed; and returns one result per feed in input order, so a failing feed does not stop the others. Pass `submit: false` to collect attestations only. Throughput is limited by the submit path (one transaction per attestation) long before it is limited by the gateway.
405
421
 
422
+ ### Batching attestations of one feed
423
+
424
+ At a high update rate, submit several attestations of the same feed in one transaction:
425
+
426
+ ```ts
427
+ const { signature, count } = await sdk.solana.submitAttestations([older, newer, newest]);
428
+ ```
429
+
430
+ Each becomes its own `submit_attestation` instruction, sorted by timestamp, and instructions run in
431
+ the order they are listed, so every attestation is applied, oldest first. Separate transactions give
432
+ no such order. Sent a few hundred milliseconds apart they reach a leader in a different order often
433
+ enough (about a third at 250 ms spacing in a devnet run) that the older one is refused with
434
+ `FeedNotNewer`; a batch makes the spacing between transactions several times longer, and one
435
+ transaction costs one fee.
436
+
437
+ - All attestations must be for one feed (the same `sourceId` and `signaturesRequired`), with distinct
438
+ timestamps. They may differ in registry version and signer set.
439
+ - The transaction is atomic. If a newer attestation of the feed has already landed, every
440
+ instruction is stale and none applies, so batch what is due together and send batches of one feed
441
+ in order.
442
+ - A transaction holds 1232 bytes, which is about three attestations with three signers, two with
443
+ four or five. `maxAttestationsPerTransaction(signerCount, { distinctSigners })` estimates it, and a
444
+ batch that does not fit throws `BatchTooLargeError` before sending, with `fits` saying how many
445
+ go: submit those and the rest apart. A priority-fee instruction costs bytes too.
446
+ - `rawValue` is not supported in a batch; use `submitAttestation` for values longer than 32 bytes.
447
+ - An attestation waits for the others in its batch, so a batch of `n` made at an interval of `t`
448
+ delays the oldest by `(n - 1) * t`.
449
+
406
450
  ### Fast requests with a cached context
407
451
 
408
452
  By default every `requestSignedData` call reads the on-chain registry up front
@@ -917,7 +961,7 @@ Current scope:
917
961
 
918
962
  - Solana subscription and extend flow;
919
963
  - deterministic source id and attestation message hashing;
920
- - gateway signed-data requests (failover, tick-aware retries, per-gateway request auth, context cache);
964
+ - gateway signed-data requests (failover, retries with backoff, per-gateway request auth, context cache);
921
965
  - Solana attestation submission and feed/registry reads;
922
966
  - private API encryption helpers (pre-production);
923
967
  - caller-funded x402 payments for paywalled API sources (Base USDC, pre-production);
package/dist/index.d.ts CHANGED
@@ -40,8 +40,6 @@ interface GatewayInfo {
40
40
  gatewayAuthority: string;
41
41
  /** Program id the gateway settles against; must match the client's when present. */
42
42
  programId?: string;
43
- /** The gateway's round timestamp grid in milliseconds (`round.tick_ms`), when advertised. */
44
- tickMs?: number;
45
43
  /** Longest the gateway waits for a round, in seconds (`node.agg_wait_seconds`), when advertised. */
46
44
  roundTimeoutSeconds?: number;
47
45
  /** Rounds the gateway runs at once (`limits.max_inflight_rounds`); 0 or absent: not advertised. */
@@ -165,17 +163,12 @@ interface RequestSignedDataOptions {
165
163
  /** Max accepted value age in seconds. Default 60. */
166
164
  maxAge?: number;
167
165
  /**
168
- * Max attempts. Each retry waits for a later gateway tick, plus jitter, and for longer after a
169
- * busy answer (`Retry-After`) or a failure that is not a conflict (capped exponential backoff);
170
- * see {@link retryDelayMs}. Default {@link DEFAULT_MAX_RETRIES}.
166
+ * Max attempts. The wait before each retry depends on why the last attempt failed: one round
167
+ * tick after a duplicate (HTTP 409), the gateway's `Retry-After` or a capped exponential backoff
168
+ * after a busy answer, the backoff after anything else; see {@link retryDelayMs}. Every attempt
169
+ * the gateway accepts spends another unit of round quota. Default {@link DEFAULT_MAX_RETRIES}.
171
170
  */
172
171
  maxRetries?: number;
173
- /**
174
- * The gateway's tick grid in milliseconds (its `round.tick_ms`). A retry waits for the start of
175
- * the next tick so it is a new round, not a duplicate of the last. Default: the gateway's
176
- * advertised `tickMs` (read once, on the first retry), else 1000.
177
- */
178
- tickMs?: number;
179
172
  /**
180
173
  * Per-request timeout in ms. Default {@link DEFAULT_ROUND_TIMEOUT_MS}, above the gateway's own
181
174
  * wait for a round: a shorter timeout abandons rounds the gateway is still running and the retry
@@ -228,6 +221,8 @@ interface MolphaGatewayOptions {
228
221
  allowUnverifiedNodeKeysForPrivateApi?: boolean;
229
222
  /** Source of randomness in [0, 1) for retry jitter; defaults to `Math.random` (tests only). */
230
223
  random?: () => number;
224
+ /** Waits `ms` before a retry; defaults to a `setTimeout` timer (tests only). */
225
+ sleep?: (ms: number) => Promise<void>;
231
226
  }
232
227
  /**
233
228
  * The slow-changing inputs a `requestSignedData` round binds to. Fetch once with
@@ -237,8 +232,6 @@ interface RoundContext extends RegistrySelectionConfig {
237
232
  /** Full registry node set: private API secrets are encrypted for every node in it. */
238
233
  nodes: Node[];
239
234
  }
240
- /** Milliseconds from `nowMs` to the start of the next tick, plus a millisecond of margin. */
241
- declare function msUntilNextTick(nowMs: number, tickMs: number): number;
242
235
  /**
243
236
  * Default per-request timeout: above the gateway's own wait for a round (`node.agg_wait_seconds`,
244
237
  * 30 s by default), so the client does not abandon a round the gateway is still running and then
@@ -249,9 +242,10 @@ declare const DEFAULT_ROUND_TIMEOUT_MS = 35000;
249
242
  declare const DEFAULT_MAX_RETRIES = 6;
250
243
  /**
251
244
  * Why an attempt failed, as far as it decides how long to wait before the next one:
252
- * - `conflict`: HTTP 409, this consumer already has a round for the source in this tick;
253
- * - `busy`: HTTP 503 or 429, the gateway is at capacity or a node set is unavailable, optionally
254
- * with the gateway's `Retry-After`;
245
+ * - `conflict`: HTTP 409, this consumer already has a round for the source and quorum in the
246
+ * current round tick;
247
+ * - `busy`: HTTP 503 or 429, the gateway is at capacity, or too few nodes accepted the round
248
+ * (unavailable, or busy), optionally with the gateway's `Retry-After`;
255
249
  * - `error`: anything else that may be transient (timeout, network, other 5xx).
256
250
  */
257
251
  type RetryCause = {
@@ -263,16 +257,17 @@ type RetryCause = {
263
257
  kind: "error";
264
258
  };
265
259
  /**
266
- * How long to wait before the next attempt, in ms. Every delay reaches at least the start of the
267
- * next tick (a retry inside the tick that just failed would be a duplicate round), then adds jitter
268
- * so clients that failed together do not retry together:
269
- * - conflict: the next tick plus up to half a tick;
270
- * - busy: the later of the next tick and the gateway's `Retry-After` (a tick if absent), plus up to
271
- * a tick;
272
- * - error: capped exponential backoff (250 ms, 500 ms, ... 5 s) with half of it randomized.
273
- * `failures` is the number of attempts that have failed so far (1 after the first).
260
+ * How long to wait before the next attempt, in ms. Each delay carries jitter, so clients that
261
+ * failed together do not retry together:
262
+ * - conflict: one full round tick ({@link ROUND_TICK_MS}) plus up to 20 ms. The gateway stamps a
263
+ * round with the tick it is in, so a retry a full tick later is in a later tick whatever the
264
+ * offset between the two clocks: no boundary is computed from the local clock;
265
+ * - busy: the later of the gateway's `Retry-After` and the backoff step, plus up to one more step;
266
+ * - error: the backoff step, half of it randomized.
267
+ * The backoff step is a capped exponential (250 ms, 500 ms, ... 5 s). `failures` is the number of
268
+ * attempts that have failed so far (1 after the first).
274
269
  */
275
- declare function retryDelayMs(cause: RetryCause, failures: number, nowMs: number, tickMs: number, random?: () => number): number;
270
+ declare function retryDelayMs(cause: RetryCause, failures: number, random?: () => number): number;
276
271
 
277
272
  /** Thrown for terminal gateway errors (400/401) — never retried. */
278
273
  declare class GatewayError extends Error {
@@ -292,9 +287,8 @@ declare class MolphaGateway {
292
287
  private readonly programIdBytes;
293
288
  /** Gateway PDA per endpoint URL; resolved once per client lifetime. */
294
289
  private readonly gatewayPdas;
295
- /** Advertised timing per endpoint URL, read lazily on the first retry. */
296
- private readonly advertisedInfo;
297
290
  private readonly random;
291
+ private readonly sleep;
298
292
  constructor(endpoints?: GatewayEndpointInput | GatewayEndpointInput[], getRegistrySelectionConfig?: () => Promise<RegistrySelectionConfig>, defaultSigner?: Signer,
299
293
  /**
300
294
  * Either a default subscription owner (base58) or gateway options. A string is the
@@ -337,10 +331,13 @@ declare class MolphaGateway {
337
331
  * failover. The body is POSTed to each endpoint in order until one `completed`s.
338
332
  *
339
333
  * The round's `sourceId` is derived from `apiConfig`. The request carries no round timestamp:
340
- * the gateway assigns the round's `timestamp` (unix milliseconds) from its own clock
341
- * on a tick grid, and the committee follows from it. The result carries the assigned
342
- * timestamp and the signers' bitmap. A retry waits for a later tick, because a round that was
343
- * already reserved or dispatched cannot run again in the same one (HTTP 409).
334
+ * the gateway stamps the round with its own clock floored to the round tick (`timestamp`, unix
335
+ * milliseconds, a multiple of {@link ROUND_TICK_MS}), and the committee follows from it. The
336
+ * result carries the assigned timestamp and the signers' bitmap; the timestamp is taken as the
337
+ * gateway gave it. Requests for the same source, quorum and registry version inside one tick
338
+ * share one round. One consumer gets one round per tick per source and quorum: a second request
339
+ * in the tick is refused (HTTP 409) and retried one tick later. A retry is stamped anew, so it
340
+ * is a new round that spends another unit of round quota.
344
341
  *
345
342
  * The request is authorized by a `RequestAuth` signature (`authSig`) over the program id, the
346
343
  * endpoint's Gateway PDA, the source id, the quorum and `authTimestamp` (unix seconds, read
@@ -370,12 +367,6 @@ declare class MolphaGateway {
370
367
  * encryption) or when nothing else can tell us the node count.
371
368
  */
372
369
  private resolveContext;
373
- /**
374
- * The gateway's advertised tick grid, read once per endpoint and only when a retry needs it. Any
375
- * failure (an old gateway without `/v1/info`, a timeout) means "not advertised", so the caller
376
- * falls back to the default; a failure is not cached.
377
- */
378
- private advertisedTickMs;
379
370
  /** Gateway PDA bytes for an endpoint, cached per URL. A failed lookup is not cached. */
380
371
  private resolveGatewayPda;
381
372
  private lookupGatewayPda;
@@ -479,6 +470,33 @@ declare function planIdFromVariant(variant: Record<string, unknown>): PlanId;
479
470
  * benchmark's, and a limit that is hit fails the transaction for good.
480
471
  */
481
472
  declare function estimateSubmitComputeUnits(signerCount: number): number;
473
+ /**
474
+ * Compute-unit limit for one transaction that carries a `submit_attestation` per entry of
475
+ * `signerCounts`: the same margin as {@link estimateSubmitComputeUnits} on the sum, with the fixed
476
+ * 10k once for the transaction.
477
+ */
478
+ declare function estimateSubmitBatchComputeUnits(signerCounts: number[]): number;
479
+ /**
480
+ * Bytes of `submit_attestation` instruction data with no raw value: the 8 byte discriminator, the
481
+ * payload (32 + 32 + 4 + 1 + 8), the signature (32 + 20 + 32), the empty `Option` and the coalition
482
+ * key (32 + 32).
483
+ */
484
+ declare const SUBMIT_INSTRUCTION_DATA_BYTES = 234;
485
+ /**
486
+ * Serialized size of a transaction with `count` attestations of `signerCount` signers each, to plan
487
+ * a batch before there is one. The signer `Node` accounts are shared between attestations, so the
488
+ * size depends on how many distinct ones there are: `distinctSigners` (default `signerCount`, the
489
+ * best case; the registry's node count is the worst).
490
+ */
491
+ declare function estimateSubmitTransactionSize(count: number, signerCount: number, opts?: {
492
+ distinctSigners?: number;
493
+ priorityFee?: boolean;
494
+ }): number;
495
+ /** The most attestations of `signerCount` signers that fit in one transaction; see {@link estimateSubmitTransactionSize}. */
496
+ declare function maxAttestationsPerTransaction(signerCount: number, opts?: {
497
+ distinctSigners?: number;
498
+ priorityFee?: boolean;
499
+ }): number;
482
500
  type Commitment$1 = NonNullable<ConstructorParameters<typeof AnchorProvider>[2]>["commitment"];
483
501
  interface SubscribeResult {
484
502
  signature: string;
@@ -516,6 +534,31 @@ interface SubmitResult {
516
534
  /** Feed PDA written by this submit (`["molpha_feed", sourceId, [signaturesRequired], submitter]`). */
517
535
  feed: Address;
518
536
  }
537
+ /** What {@link MolphaSolanaClient.submitAttestations} returns. */
538
+ interface SubmitAttestationsResult extends SubmitResult {
539
+ /** Attestations in the transaction. They were applied in timestamp order. */
540
+ count: number;
541
+ }
542
+ /**
543
+ * Thrown by {@link MolphaSolanaClient.submitAttestations} when the attestations do not fit in one
544
+ * transaction. Nothing was sent: submit the first `fits` of them (oldest first) and the rest apart.
545
+ */
546
+ declare class BatchTooLargeError extends RangeError {
547
+ /** How many of the attestations, oldest first, do fit. */
548
+ readonly fits: number;
549
+ /** The serialized size of the whole batch, in bytes. */
550
+ readonly size: number;
551
+ /** The largest transaction the network accepts, in bytes. */
552
+ readonly limit: number;
553
+ readonly count: number;
554
+ constructor(
555
+ /** How many of the attestations, oldest first, do fit. */
556
+ fits: number,
557
+ /** The serialized size of the whole batch, in bytes. */
558
+ size: number,
559
+ /** The largest transaction the network accepts, in bytes. */
560
+ limit: number, count: number);
561
+ }
519
562
  /**
520
563
  * On-chain `Feed` (Anchor-decoded). `value` is always the signed
521
564
  * `AttestationPayload.value` (32 bytes): the oracle value itself when `valueKind` is
@@ -586,6 +629,8 @@ interface SubmitAttestationOptions {
586
629
  */
587
630
  rawValue?: Uint8Array;
588
631
  }
632
+ /** Options for {@link MolphaSolanaClient.submitAttestations}. */
633
+ type SubmitAttestationsOptions = Pick<SubmitAttestationOptions, "computeUnitLimit" | "priorityFeeMicroLamports">;
589
634
  interface CreateClientOpts {
590
635
  connection: SolanaConnection;
591
636
  wallet: Wallet;
@@ -668,6 +713,33 @@ declare class MolphaSolanaClient {
668
713
  * `keccak256(rawValue)`; {@link buildSubmitAttestationArgs} validates that before send.
669
714
  */
670
715
  submitAttestation(attestation: Attestation, opts?: SubmitAttestationOptions): Promise<SubmitResult>;
716
+ /**
717
+ * Submit several attestations of one feed in one transaction.
718
+ *
719
+ * Each becomes its own `submit_attestation` instruction, in timestamp order, and instructions run
720
+ * in the order they are listed: all of them are applied, oldest first, whatever order other
721
+ * transactions reach the leader in. That is what separate submits cannot promise. Transactions sent
722
+ * milliseconds apart for one feed are overtaken by a newer one often enough (about a third at 250 ms
723
+ * spacing in a devnet run) that the older one fails with `FeedNotNewer`; a batch makes the spacing
724
+ * between transactions several times longer and spends one fee and one signature for them all.
725
+ *
726
+ * The transaction is atomic: if one instruction fails (typically because a newer attestation
727
+ * of the feed landed first, so every instruction is stale) none of them applies. Batch only what
728
+ * is due together, and keep batches of one feed in the order they were made.
729
+ *
730
+ * All attestations must be for one feed (the same `sourceId` and `signaturesRequired`) with
731
+ * distinct timestamps. They need not share a registry version or a signer set. Values longer than
732
+ * 32 bytes (`rawValue`) are not supported here: submit those with {@link submitAttestation}.
733
+ *
734
+ * @throws {BatchTooLargeError} when they do not fit in one transaction (about three do with
735
+ * three signers; see {@link maxAttestationsPerTransaction}). Nothing is sent.
736
+ */
737
+ submitAttestations(attestations: Attestation[], opts?: SubmitAttestationsOptions): Promise<SubmitAttestationsResult>;
738
+ /**
739
+ * Everything one `submit_attestation` instruction needs: the arguments, the named accounts, the
740
+ * signer `Node` accounts (ascending bit order) and the feed address.
741
+ */
742
+ private prepareSubmit;
671
743
  /** The registry for `version`, reused for {@link REGISTRY_CACHE_MS}. */
672
744
  private fetchRegistryCached;
673
745
  private resolvePriorityFee;
@@ -877,6 +949,15 @@ declare function deriveSelectionBitmap(sourceId: Uint8Array, registryVersion: nu
877
949
  * EVM `block.timestamp`), epoch windows and `maxAge`/staleness are measured in SECONDS, so
878
950
  * anything that compares the two must go through {@link timestampSeconds}.
879
951
  */
952
+ /**
953
+ * The round tick in milliseconds: the gateway stamps a round with its clock floored to this grid
954
+ * (`floor(nowMs / ROUND_TICK_MS) * ROUND_TICK_MS`) and nodes reject a round whose timestamp is off
955
+ * it. A feed (source, quorum, registry version) therefore runs at most ten rounds per second, and
956
+ * every selection window (`SELECTION_WINDOW_MS`, one second) holds exactly ten ticks. A
957
+ * protocol constant, the same in every gateway and node: not configuration, and not reported by
958
+ * `GET /v1/info`.
959
+ */
960
+ declare const ROUND_TICK_MS = 100;
880
961
  /** `timestamp` (unix ms) as whole unix seconds, floored. */
881
962
  declare function timestampSeconds(timestamp: number | bigint): number;
882
963
  /**
@@ -1467,8 +1548,8 @@ interface BulkOptions {
1467
1548
  */
1468
1549
  concurrency?: number;
1469
1550
  /**
1470
- * Spread request starts over this many ms by a hash of the feed's source id. Feeds that are due on
1471
- * the same tick otherwise arrive together and queue at the gateway. Default 0 (no spreading).
1551
+ * Spread request starts over this many ms by a hash of the feed's source id. Feeds that are due at
1552
+ * the same moment otherwise arrive together and queue at the gateway. Default 0 (no spreading).
1472
1553
  */
1473
1554
  spreadMs?: number;
1474
1555
  /** Submit each attestation to Solana after its round. Default true. */
@@ -1592,4 +1673,4 @@ declare class MolphaSDK {
1592
1673
  }>;
1593
1674
  }
1594
1675
 
1595
- 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_DEV_VERIFIERS_PROVISIONAL, MOLPHA_IDL, MOLPHA_PROGRAM_ADDRESS, MOLPHA_PROGRAM_ID, MOLPHA_SDK_PROFILE, 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 };
1676
+ export { APIConfig, AdaptiveLimiter, AggregationConfig, AggregationConfigError, AssetDomain, Attestation, type AttestationMessageFields, BatchTooLargeError, 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_DEV_VERIFIERS_PROVISIONAL, MOLPHA_IDL, MOLPHA_PROGRAM_ADDRESS, MOLPHA_PROGRAM_ID, MOLPHA_SDK_PROFILE, 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, ROUND_TICK_MS, RegistrySelectionConfig, type RegistryStateView, type RegistryView, type RequestAuthFields, type RequestSignedDataOptions, type RetryCause, type RoundContext, SELECTION_WINDOW_MS, SUBMIT_INSTRUCTION_DATA_BYTES, type Secp256k1KeyInput, Signer, SourcePaymentOptions, type StarknetAttestation, type StarknetAttestationPayload, type StarknetFeltLike, type StarknetSchnorrSignature, type StarknetVerifierArgs, type StarknetVerifyResult, type SubmitAttestationArgs, type SubmitAttestationOptions, type SubmitAttestationsOptions, type SubmitAttestationsResult, 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, estimateSubmitBatchComputeUnits, estimateSubmitComputeUnits, estimateSubmitTransactionSize, evmAddressFromPrivateKey, feedPda, formatInt256Decimal, gatewayPda, getMolphaStarknetVerifierAddress, hashRequestAuth, hexToBytes, maxAttestationsPerTransaction, 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 };