@molpha/sdk 0.2.0-dev-20261001125531 → 0.2.0-dev-20261005112114

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
@@ -23,10 +23,10 @@ At a high level:
23
23
  Consumer
24
24
  └─ subscribes (USDC) on Solana
25
25
  └─ derives sourceId = keccak256(canonical apiConfig)
26
- └─ signs a RequestAuth bound to (programId, gateway, sourceId, signaturesRequired, timestamp)
26
+ └─ signs a RequestAuth bound to (programId, gateway, sourceId, signaturesRequired, authTimestamp)
27
27
 
28
28
  Gateway
29
- └─ coordinates a signing round for (sourceId, signaturesRequired)
29
+ └─ assigns the round's timestamp (unix ms) from its own clock and coordinates a signing round
30
30
 
31
31
  Verifier nodes
32
32
  └─ fetch/recompute the API result independently
@@ -46,7 +46,7 @@ Every signed attestation commits to the same message across chains:
46
46
  ```text
47
47
  message = keccak256(
48
48
  keccak256("MOLPHA_MESSAGE_V1") || value || sourceId || u32be(registryVersion) ||
49
- u8(signaturesRequired) || u64be(canonicalTimestamp) || signersBitmap
49
+ u8(signaturesRequired) || u64be(timestamp) || signersBitmap
50
50
  )
51
51
  ```
52
52
 
@@ -64,7 +64,7 @@ Runtime dependencies include `@solana/kit`, `@anchor-lang/core`, and `@noble/*`.
64
64
 
65
65
  | Import | Use |
66
66
  |---|---|
67
- | `@molpha/sdk` | Facade (`MolphaSDK`), `MolphaGateway`, `MolphaSolanaClient`, core hashing (`deriveSourceId`, `attestationMessageHash`, `hashRequestAuth`), EVM/Starknet helpers. Browser-safe; no `fs` in the main entry. |
67
+ | `@molpha/sdk` | Facade (`MolphaSDK`), `MolphaGateway`, `MolphaSolanaClient`, core hashing (`deriveSourceId`, `attestationMessageHash`, `hashRequestAuth`, `timestampSeconds`), EVM/Starknet helpers. Browser-safe; no `fs` in the main entry. |
68
68
  | `@molpha/sdk/utils` | `walletFromKeypairFile`, `loadKeypair` — load a Solana CLI keypair as an Anchor `Wallet`. Node.js only. |
69
69
 
70
70
  The package is ESM with `"sideEffects": false`, so gateway-only or read-only apps can tree-shake unused paths.
@@ -149,11 +149,13 @@ Gateway request authorization binds the gateway's on-chain account, so the clien
149
149
 
150
150
  ```text
151
151
  requestAuthHash = keccak256(
152
- "MOLPHA_REQAUTH_V1" || programId || gatewayPda || sourceId || u8(signaturesRequired) || u64le(timestamp)
152
+ "MOLPHA_REQAUTH_V1" || programId || gatewayPda || sourceId || u8(signaturesRequired) || u64le(authTimestamp)
153
153
  )
154
154
  gatewayPda = PDA(["molpha_gateway", gatewayAuthority], programId)
155
155
  ```
156
156
 
157
+ `authTimestamp` is the caller's unix **seconds** at signing: a freshness stamp for the authorization only (the gateway rejects stamps outside its `request_auth.window_seconds`, 60 s by default). It is read from the SDK clock for every attempt, so a retry after a 409 signs a fresh one. It has nothing to do with the round's `timestamp`.
158
+
157
159
  Pass `gatewayAuthority` (the gateway's base58 signing pubkey) per endpoint to pin it. When omitted, the SDK calls `GET {url}/v1/info` once per endpoint and reads:
158
160
 
159
161
  ```json
@@ -162,6 +164,20 @@ Pass `gatewayAuthority` (the gateway's base58 signing pubkey) per endpoint to pi
162
164
 
163
165
  A `programId` that differs from the client's is rejected. Because the hash differs per gateway, the auth signature is recomputed for every endpoint actually tried during failover — with a browser wallet that means one signing prompt per endpoint tried. `/v1/info` is never contacted when no signer is configured (dev zero-signature path).
164
166
 
167
+ ### Round timestamp
168
+
169
+ The request body carries `authSig` and `authTimestamp` but no round timestamp. The gateway assigns the
170
+ round's `timestamp` from its own clock, in unix **milliseconds**, on a tick grid
171
+ (`floor(now / tickMs) * tickMs`, one second by default), and the result carries it in
172
+ `payload.timestamp`. Committee selection reads only the timestamp's one-second window, so
173
+ nobody can pick a committee by picking a time.
174
+
175
+ - The message the nodes sign is `keccak256(MOLPHA_MESSAGE_V1 || value || sourceId || u32be(registryVersion) || u8(quorum) || u64be(timestamp) || signersBitmap)`.
176
+ - Chain clocks, epochs and `maxAge`/staleness are in **seconds**: compare with
177
+ `timestampSeconds(payload.timestamp)` (floored `ts / 1000`). `timestampAgeSeconds(ts, nowSeconds)` saturates at 0.
178
+ - 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`, default 1000: set it to the gateway's `round.tick_ms`).
179
+ - Private API secrets are encrypted for every node of the registry (the committee is unknown until the gateway stamps the round); the gateway forwards only the selected nodes' envelopes. `verifyNodeKeys` therefore authenticates all of them.
180
+
165
181
  ## Wallet
166
182
 
167
183
  `wallet` is a single `MolphaWallet` used across both protocol surfaces:
@@ -169,7 +185,7 @@ A `programId` that differs from the client's is rejected. Because the hash diffe
169
185
  | Layer | What it signs |
170
186
  |---|---|
171
187
  | Solana client | Transactions such as `subscribe`, `extendSubscription`, `submitAttestation` |
172
- | Gateway client | `hashRequestAuth({ programId, gateway, sourceId, signaturesRequired, timestamp })` for authenticated gateway requests |
188
+ | Gateway client | `hashRequestAuth({ programId, gateway, sourceId, signaturesRequired, authTimestamp })` for authenticated gateway requests |
173
189
 
174
190
  Gateway auth is resolved automatically when you use `MolphaSDK`:
175
191
 
@@ -182,7 +198,8 @@ Gateway auth is resolved automatically when you use `MolphaSDK`:
182
198
  explicit `signer`. Standalone `new MolphaGateway(...)` omits auth unless you pass
183
199
  a `defaultSigner` (third constructor arg) or per-call `signer`.
184
200
 
185
- The all-zero `authSig` path is for development only. Production jobs should authenticate gateway requests.
201
+ The all-zero `authSig` path is for development only: a gateway rejects it with 401. Production jobs should authenticate gateway requests.
202
+
186
203
 
187
204
  ### Node.js utility
188
205
 
@@ -276,7 +293,7 @@ const result = await sdk.gateway.requestSignedData({
276
293
 
277
294
  The gateway round uses the current on-chain registry version. Selected verifier nodes independently fetch/recompute the result and sign only if the observed value matches the canonical result.
278
295
 
279
- The returned `Attestation` matches the cross-VM struct (`payload` + `signature`), plus gateway-only `value` (human-readable) and `fresh`. `payload` carries `sourceId`, the signed 32-byte `value`, `canonicalTimestamp`, `registryVersion`, and `signaturesRequired`; `signature` carries the aggregate Schnorr material and `signersBitmap`. `signaturesRequired` must be at least the protocol's `min_signers` (currently 3) or the chain rejects the submit.
296
+ The returned `Attestation` matches the cross-VM struct (`payload` + `signature`), plus gateway-only `value` (human-readable) and `fresh`. `payload` carries `sourceId`, the signed 32-byte `value`, `timestamp`, `registryVersion`, and `signaturesRequired`; `signature` carries the aggregate Schnorr material and `signersBitmap`. `signaturesRequired` must be at least the protocol's `min_signers` (currently 3) or the chain rejects the submit.
280
297
 
281
298
  A gateway response is a coordination result, not proof of settlement or of on-chain verification. Consumers still decide freshness, source, quorum and replay policy.
282
299
 
@@ -302,7 +319,7 @@ const feedState = await sdk.solana.readFeed(result.payload.sourceId, signaturesR
302
319
 
303
320
  `FeedAccount.value` is the 32 signed bytes (`valueKind.value`) or their keccak preimage hash (`valueKind.hash`); `submitter` is the wallet that created the feed.
304
321
 
305
- The subscription read no longer exposes usage. The program dropped `used_rounds` / `prepaid_usdc` / `price` from `Subscription`; round quota is counted by the gateway's off-chain outbox, so `SubscriptionInfo` carries only `owner`, `planType`, `validUntil`, `maxRounds`, `delegateCount`, `maxDelegates`, `maxSigners`.
322
+ The subscription read does not expose usage: the program stores no `used_rounds` / `prepaid_usdc` / `price`, and round quota is counted by the gateway's off-chain outbox. `SubscriptionInfo` carries only `owner`, `planType`, `validUntil`, `maxRounds`, `delegateCount`, `maxDelegates`, `maxSigners`.
306
323
 
307
324
  ### One-call request + submit
308
325
 
@@ -540,7 +557,7 @@ attestation:
540
557
  sourceId: `0x${string}`, // bytes32
541
558
  registryVersion: number, // uint32
542
559
  signaturesRequired: number, // uint8
543
- canonicalTimestamp: bigint, // uint64
560
+ timestamp: bigint, // uint64
544
561
  },
545
562
  signature: {
546
563
  signature: `0x${string}`, // bytes32 (Attestation.signature.s)
@@ -692,7 +709,7 @@ attestation:
692
709
  source_id: u256,
693
710
  registry_version: u32,
694
711
  signatures_required: u8,
695
- canonical_timestamp: u64,
712
+ timestamp: u64,
696
713
  },
697
714
  signature: {
698
715
  signature: u256,
@@ -742,7 +759,7 @@ shared with the EVM verifier contract and exported as `VERIFY_CODES`:
742
759
  | 0 | `OK` | Verified |
743
760
  | 2 | `BAD_REGISTRY_VERSION` | `registryVersion` does not exist on this verifier |
744
761
  | 3 | `MALFORMED` | Structurally invalid input, or dated in the future when `maxAge != 0` |
745
- | 4 | `NOT_YET_ACTIVE` | `canonicalTimestamp` predates the registry version's activation |
762
+ | 4 | `NOT_YET_ACTIVE` | `timestamp` predates the registry version's activation |
746
763
  | 5 | `VERSION_EXPIRED` | Registry version superseded more than the grace window earlier |
747
764
  | 7 | `BAD_QUORUM` | Signers are not within the round's derived selection group |
748
765
  | 8 | `BAD_AGGREGATE` | The signers' aggregate key is the point at infinity |
@@ -770,7 +787,7 @@ A Molpha attestation is valid only if the verifier can confirm:
770
787
  - the update targets the expected `sourceId`;
771
788
  - the result was signed against a specific `registryVersion` (an immutable node-set snapshot);
772
789
  - the quorum satisfies `signaturesRequired`;
773
- - the signer bitmap is a subset of the deterministic selection for `(sourceId, registryVersion, canonicalTimestamp)`;
790
+ - the signer bitmap is a subset of the deterministic selection for `(sourceId, registryVersion, timestamp)`;
774
791
  - the aggregate Schnorr signature over `attestationMessageHash(...)` is valid;
775
792
  - the timestamp is within the accepted freshness bounds;
776
793
  - on Solana, the signer `Node` accounts passed as remaining accounts are exactly `registry.nodes[bit]` for every set bit, and the supplied coalition key matches the sum of their keys.
@@ -834,7 +851,7 @@ Current scope:
834
851
 
835
852
  - Solana subscription and extend flow;
836
853
  - deterministic source id and attestation message hashing;
837
- - gateway signed-data requests (failover, retries, per-gateway request auth, context cache);
854
+ - gateway signed-data requests (failover, tick-aware retries, per-gateway request auth, context cache);
838
855
  - Solana attestation submission and feed/registry reads;
839
856
  - private API encryption helpers (pre-production);
840
857
  - caller-funded x402 payments for paywalled API sources (Base USDC, pre-production);
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, 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-LqCFWwV4.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-LqCFWwV4.js';
5
5
  import BN from 'bn.js';
6
6
 
7
7
  /**
@@ -48,8 +48,8 @@ interface RequestAuthFields {
48
48
  sourceId: Uint8Array | string;
49
49
  /** u8 — the per-request quorum. */
50
50
  signaturesRequired: number;
51
- /** u64 unix seconds. */
52
- timestamp: number | bigint;
51
+ /** u64 unix SECONDS at signing (the auth freshness stamp, not the round's timestamp). */
52
+ authTimestamp: number | bigint;
53
53
  }
54
54
  /** Borsh encoding of `RequestAuth` — the hash body, without the domain. */
55
55
  declare function encodeRequestAuth(fields: RequestAuthFields): Uint8Array;
@@ -117,15 +117,16 @@ interface RequestSignedDataOptions {
117
117
  */
118
118
  subscriptionOwner?: string;
119
119
  /**
120
- * Solana pubkey (base58) of the consumer authority that signs gateway auth.
120
+ * Solana pubkey (base58) of the consumer: the subscription owner, or a delegate of it.
121
121
  * Overrides the gateway's `defaultConsumerAuthority` when set.
122
122
  */
123
123
  consumerAuthority?: string;
124
124
  /**
125
- * Signs `hashRequestAuth({ programId, gateway, sourceId, signaturesRequired, timestamp })`.
125
+ * Signs `hashRequestAuth({ programId, gateway, sourceId, signaturesRequired, authTimestamp })`.
126
126
  * 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).
127
+ * invoked — once per endpoint actually tried, with a fresh `authTimestamp` each time.
128
+ * Overrides the gateway's `defaultSigner` when set. When both are omitted, sends an all-zero
129
+ * authSig (a gateway rejects it with 401; for tests and local tooling only).
129
130
  */
130
131
  signer?: Signer;
131
132
  encrypt?: {
@@ -144,8 +145,13 @@ interface RequestSignedDataOptions {
144
145
  sourcePayment?: SourcePaymentOptions;
145
146
  /** Max accepted value age in seconds. Default 60. */
146
147
  maxAge?: number;
147
- /** Each retry re-rolls the timestamp. Default 15. */
148
+ /** Max attempts; each retry waits for a later gateway tick. Default 15. */
148
149
  maxRetries?: number;
150
+ /**
151
+ * The gateway's tick grid in milliseconds (its `round.tick_ms`). A retry waits for the start of
152
+ * the next tick so it is a new round, not a duplicate of the last. Default 1000.
153
+ */
154
+ tickMs?: number;
149
155
  /** Per-request timeout in ms. Default 5000. */
150
156
  timeoutMs?: number;
151
157
  /**
@@ -177,8 +183,9 @@ interface MolphaGatewayOptions {
177
183
  /** Solana pubkey (base58) of the consumer authority used when a request omits it. */
178
184
  defaultConsumerAuthority?: string;
179
185
  /**
180
- * Molpha program id the request authorization is bound to. Defaults to the vendored
181
- * `MOLPHA_PROGRAM_ID`; must match the gateway's deployment.
186
+ * Molpha program id the request authorization is bound to (and that `fetchGatewayInfo`
187
+ * checks the gateway settles against). Defaults to the vendored `MOLPHA_PROGRAM_ID`;
188
+ * must match the gateway's deployment.
182
189
  */
183
190
  programId?: string;
184
191
  /**
@@ -197,9 +204,11 @@ interface MolphaGatewayOptions {
197
204
  * {@link MolphaGateway.prepareContext} and reuse across many rounds.
198
205
  */
199
206
  interface RoundContext extends RegistrySelectionConfig {
200
- /** Full node set used to encrypt private API secrets for the selected nodes. */
207
+ /** Full registry node set: private API secrets are encrypted for every node in it. */
201
208
  nodes: Node[];
202
209
  }
210
+ /** Milliseconds from `nowMs` to the start of the next tick, plus a millisecond of margin. */
211
+ declare function msUntilNextTick(nowMs: number, tickMs: number): number;
203
212
  /** Default gateway base URL when `endpoints` is omitted. */
204
213
  declare const DEFAULT_GATEWAY_ENDPOINT = "https://dev-gateway.molpha.io/";
205
214
  /** Thrown for terminal gateway errors (400/401) — never retried. */
@@ -222,8 +231,8 @@ declare class MolphaGateway {
222
231
  private readonly gatewayPdas;
223
232
  constructor(endpoints?: GatewayEndpointInput | GatewayEndpointInput[], getRegistrySelectionConfig?: () => Promise<RegistrySelectionConfig>, defaultSigner?: Signer,
224
233
  /**
225
- * Either a default subscription owner (base58) or gateway options. A string
226
- * keeps the previous positional form used by standalone callers/tests.
234
+ * Either a default subscription owner (base58) or gateway options. A string is the
235
+ * shorthand for `{ defaultSubscriptionOwner }` used by standalone callers/tests.
227
236
  */
228
237
  defaultSubscriptionOwnerOrOptions?: string | MolphaGatewayOptions, defaultConsumerAuthority?: string);
229
238
  /** Tries endpoints in order; returns the first node list it can fetch. */
@@ -233,7 +242,7 @@ declare class MolphaGateway {
233
242
  * selection policy, which sizes the eligible set a paid source must fund.
234
243
  *
235
244
  * 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
245
+ * chain, and is absent when that read failed. Prefer the
237
246
  * on-chain read (`MolphaSolanaClient.getRegistrySelectionConfig`) whenever a
238
247
  * Solana connection is available — `requestSignedData` already does.
239
248
  */
@@ -257,12 +266,23 @@ declare class MolphaGateway {
257
266
  prepareContext(): Promise<RoundContext>;
258
267
  /**
259
268
  * 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.
269
+ * failover. The body is POSTed to each endpoint in order until one `completed`s.
270
+ *
271
+ * The round's `sourceId` is derived from `apiConfig`. The request carries no round timestamp:
272
+ * the gateway assigns the round's `timestamp` (unix milliseconds) from its own clock
273
+ * on a tick grid, and the committee follows from it. The result carries the assigned
274
+ * timestamp and the signers' bitmap. A retry waits for a later tick, because a round that was
275
+ * already reserved or dispatched cannot run again in the same one (HTTP 409).
262
276
  *
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.
277
+ * The request is authorized by a `RequestAuth` signature (`authSig`) over the program id, the
278
+ * endpoint's Gateway PDA, the source id, the quorum and `authTimestamp` (unix seconds, read
279
+ * from the local clock for every attempt). It only proves the caller may spend the
280
+ * subscription's rounds; it is not part of the round, so the signature is recomputed for every
281
+ * endpoint tried.
282
+ *
283
+ * Because the committee is unknown until the gateway stamps the round, private API
284
+ * secrets are encrypted for **every** node of the registry; the gateway forwards only the
285
+ * selected nodes' envelopes.
266
286
  *
267
287
  * By default this fetches the registry selection config up front and the node
268
288
  * set only when the round needs it (private API encryption, or when the registry
@@ -392,8 +412,7 @@ interface PlanInfo {
392
412
  isActive: boolean;
393
413
  }
394
414
  /**
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
415
+ * On-chain `Subscription`. The program does not track usage (round quota is counted by the
397
416
  * gateway's off-chain outbox), so there is no on-chain "rounds used" figure to read.
398
417
  */
399
418
  interface SubscriptionInfo {
@@ -428,8 +447,8 @@ interface FeedAccount {
428
447
  hash: Record<string, never>;
429
448
  };
430
449
  submitter: Address;
431
- /** u64 unix seconds. */
432
- canonicalTimestamp: BN;
450
+ /** u64 unix MILLISECONDS (the round's gateway-assigned timestamp). */
451
+ timestamp: BN;
433
452
  signaturesRequired: number;
434
453
  signersBitmap: number[];
435
454
  registryVersion: number;
@@ -443,7 +462,7 @@ interface SubmitAttestationArgs {
443
462
  sourceId: number[];
444
463
  registryVersion: number;
445
464
  signaturesRequired: number;
446
- canonicalTimestamp: BN;
465
+ timestamp: BN;
447
466
  };
448
467
  signature: {
449
468
  aggSigS: number[];
@@ -709,8 +728,8 @@ interface AttestationMessageFields {
709
728
  signersBitmap: string | Uint8Array;
710
729
  /** 32-byte packed value (hex or bytes). */
711
730
  value: string | Uint8Array;
712
- /** Unix seconds (u64). */
713
- canonicalTimestamp: number | bigint;
731
+ /** Unix milliseconds (u64), assigned by the gateway. */
732
+ timestamp: number | bigint;
714
733
  }
715
734
  /** Compute the attestation message hash (32 bytes). */
716
735
  declare function attestationMessageHash(fields: AttestationMessageFields): Uint8Array;
@@ -726,9 +745,17 @@ declare function normalizeSecp256k1PublicKeyHex(publicKeyHex: string, label?: st
726
745
  declare function secp256k1PublicKeyFromCoordinates(x: Uint8Array, y: Uint8Array, label?: string): string;
727
746
 
728
747
  /**
729
- * `seed = keccak256(keccak256("MOLPHA_SELECTION_V1") || sourceId || be32(rv) || be64(ts))`.
748
+ * Width in milliseconds of the window selection reads from the timestamp. The timestamp
749
+ * is unix milliseconds; the seed uses `floor(timestamp / SELECTION_WINDOW_MS)`, so
750
+ * sub-second precision never changes the committee. Changing it is a consensus break and must
751
+ * bump the selection prefix.
730
752
  */
731
- declare function deriveSelectionSeed(sourceId: Uint8Array, registryVersion: number, canonicalTimestamp: number | bigint): Uint8Array;
753
+ declare const SELECTION_WINDOW_MS = 1000n;
754
+ /**
755
+ * `seed = keccak256(keccak256("MOLPHA_SELECTION_V1") || sourceId || be32(rv) || be64(ts / 1000))`,
756
+ * where `ts` is the timestamp in unix MILLISECONDS (its 1 s window index is hashed).
757
+ */
758
+ declare function deriveSelectionSeed(sourceId: Uint8Array, registryVersion: number, timestamp: number | bigint): Uint8Array;
732
759
  /** `min(signaturesRequired + redundancyBuffer, nodeCount)`. */
733
760
  declare function effectiveSelectionSize(signaturesRequired: number, redundancyBuffer: number, nodeCount: number): number;
734
761
  /** Is bit `bit` set in the 32-byte big-endian `bitmap`? */
@@ -743,10 +770,24 @@ declare function selectedIndices(bitmap: Uint8Array, nodeCount: number): number[
743
770
  declare function deriveGroupBitmap(seed: Uint8Array, nodeCount: number, groupSize: number): Uint8Array;
744
771
  /**
745
772
  * Convenience orchestrator: derive the selection bitmap end-to-end.
746
- * `redundancy` defaults to 0; `ts` defaults to the current unix second.
773
+ * `redundancy` defaults to 0; `ts` is the timestamp in unix MILLISECONDS and defaults
774
+ * to now. The gateway assigns the real timestamp of a round, so this is for tests and tools.
747
775
  */
748
776
  declare function deriveSelectionBitmap(sourceId: Uint8Array, registryVersion: number, nodeCount: number, signaturesRequired: number, redundancy?: number, ts?: number | bigint): Uint8Array;
749
777
 
778
+ /**
779
+ * `timestamp` is unix MILLISECONDS, assigned by the gateway. Chain clocks (Solana `Clock`,
780
+ * EVM `block.timestamp`), epoch windows and `maxAge`/staleness are measured in SECONDS, so
781
+ * anything that compares the two must go through {@link timestampSeconds}.
782
+ */
783
+ /** `timestamp` (unix ms) as whole unix seconds, floored. */
784
+ declare function timestampSeconds(timestamp: number | bigint): number;
785
+ /**
786
+ * Age in whole seconds of a timestamp against `nowSeconds`, saturating at 0 so a
787
+ * timestamp slightly ahead of the local clock never goes negative.
788
+ */
789
+ declare function timestampAgeSeconds(timestamp: number | bigint, nowSeconds: number): number;
790
+
750
791
  /**
751
792
  * Result codes returned by Molpha's stateless verifier contracts (`verify` on EVM and
752
793
  * Starknet). Shared across VMs: the same attestation yields the same code on every chain.
@@ -761,11 +802,11 @@ declare const VERIFY_CODES: {
761
802
  readonly FEED_WITNESS: 1;
762
803
  /** The payload's `registryVersion` does not exist on this verifier. */
763
804
  readonly BAD_REGISTRY_VERSION: 2;
764
- /** Structurally invalid input, or a `canonicalTimestamp` in the future when `maxAge != 0`. */
805
+ /** Structurally invalid input, or a `timestamp` in the future when `maxAge != 0`. */
765
806
  readonly MALFORMED: 3;
766
- /** `canonicalTimestamp` predates the registry version's activation. */
807
+ /** `timestamp` predates the registry version's activation. */
767
808
  readonly NOT_YET_ACTIVE: 4;
768
- /** The registry version was superseded more than the grace window before `canonicalTimestamp`. */
809
+ /** The registry version was superseded more than the grace window before `timestamp`. */
769
810
  readonly VERSION_EXPIRED: 5;
770
811
  /** Reserved. Never returned. */
771
812
  readonly COMPROMISED_QUORUM: 6;
@@ -823,7 +864,7 @@ declare const MOLPHA_VERIFIER_ABI: readonly [{
823
864
  readonly type: "uint8";
824
865
  readonly internalType: "uint8";
825
866
  }, {
826
- readonly name: "canonicalTimestamp";
867
+ readonly name: "timestamp";
827
868
  readonly type: "uint64";
828
869
  readonly internalType: "uint64";
829
870
  }];
@@ -1071,8 +1112,8 @@ interface EvmAttestationPayload {
1071
1112
  registryVersion: number;
1072
1113
  /** `uint8`. */
1073
1114
  signaturesRequired: number;
1074
- /** `uint64`, unix seconds. */
1075
- canonicalTimestamp: bigint;
1115
+ /** `uint64`, unix MILLISECONDS (`timestamp / 1000` is the seconds the verifier's `maxAge` uses). */
1116
+ timestamp: bigint;
1076
1117
  }
1077
1118
  /** `IVerifier.SchnorrSignature`. */
1078
1119
  interface EvmSchnorrSignature {
@@ -1136,7 +1177,7 @@ declare function buildEvmVerifierArgs(attestation: Attestation, options: BuildEv
1136
1177
  * ABI calldata for `verify(attestation, maxAge)`: the 4-byte selector followed by nine
1137
1178
  * 32-byte words. Both structs are static, so they encode inline with no offsets:
1138
1179
  *
1139
- * `value, sourceId, registryVersion, signaturesRequired, canonicalTimestamp, signature,
1180
+ * `value, sourceId, registryVersion, signaturesRequired, timestamp, signature,
1140
1181
  * commitment, signersBitmap, maxAge`
1141
1182
  *
1142
1183
  * For a raw `eth_call` (`{ to: verifier, data }`).
@@ -1182,8 +1223,8 @@ interface StarknetAttestationPayload {
1182
1223
  registry_version: number;
1183
1224
  /** `u8`. */
1184
1225
  signatures_required: number;
1185
- /** `u64`, unix seconds. */
1186
- canonical_timestamp: number;
1226
+ /** `u64`, unix MILLISECONDS (`/ 1000` is the seconds the verifier's `max_age` uses). */
1227
+ timestamp: number;
1187
1228
  }
1188
1229
  /** Starknet calldata shape for `SchnorrSignature`. */
1189
1230
  interface StarknetSchnorrSignature {
@@ -1243,7 +1284,7 @@ declare function buildStarknetVerifierArgs(attestation: Attestation, options: Bu
1243
1284
  * Flat felt calldata for `verify(attestation, max_age)`, in Cairo `Serde` order — 13 felts:
1244
1285
  *
1245
1286
  * `value.low, value.high, source_id.low, source_id.high, registry_version,
1246
- * signatures_required, canonical_timestamp, signature.low, signature.high, commitment,
1287
+ * signatures_required, timestamp, signature.low, signature.high, commitment,
1247
1288
  * signers_bitmap.low, signers_bitmap.high, max_age`
1248
1289
  *
1249
1290
  * For a raw `starknet_call` with `entry_point_selector = selector("verify")`.
@@ -1342,4 +1383,4 @@ declare class MolphaSDK {
1342
1383
  }>;
1343
1384
  }
1344
1385
 
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 };
1386
+ 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, SELECTION_WINDOW_MS, 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, msUntilNextTick, nodePda, normalizeEndpoint, normalizeSecp256k1PublicKeyHex, parseEvmVerifyResult, parseGatewayInfo, parseStarknetVerifyResult, parseUpstreamQuote, planIdFromVariant, planPda, planVariant, probeSource, protocolConfigPda, registryPda, registryStatePda, resolveRemainingAccounts, secp256k1PublicKeyFromCoordinates, selectedIndices, signSourcePayments, signersBitmapToDecimal, signersBitmapToStarknetUint256, signersBitmapToUint256, subscriptionPda, timestampAgeSeconds, timestampSeconds, toChecksumAddress, toFixedBytes, toFixedHex, transferWithAuthorizationDigest, transferWithAuthorizationHash, u256beFromBigInt, u32be, u32le, u64be, u64le, u8, utf8, validateSuppliedTerms, verifyCodeName };