@molpha/sdk 0.1.0 → 0.2.0-dev-20260911175245

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
@@ -5,9 +5,9 @@ Browser-first TypeScript SDK for **Molpha data consumers and feed owners**.
5
5
  Use it to:
6
6
 
7
7
  - subscribe to a Molpha plan on Solana;
8
- - derive a feed id from owner + API config hash + quorum;
9
- - request a threshold-signed data update from the gateway;
10
- - submit the signed result on-chain;
8
+ - derive a source id from an API config;
9
+ - request a threshold-signed attestation from the gateway;
10
+ - submit the signed result on-chain (`submit_attestation`);
11
11
  - verify/read the latest feed value;
12
12
  - build EVM and Starknet verifier arguments from the same signed result.
13
13
 
@@ -20,12 +20,13 @@ Molpha turns off-chain API responses into verified on-chain data.
20
20
  At a high level:
21
21
 
22
22
  ```text
23
- Consumer / feed owner
23
+ Consumer
24
24
  └─ subscribes (USDC) on Solana
25
- └─ derives feedId from owner + apiConfigHash + signaturesRequired
25
+ └─ derives sourceId = keccak256(canonical apiConfig)
26
+ └─ signs a RequestAuth bound to (programId, gateway, sourceId, signaturesRequired, timestamp)
26
27
 
27
28
  Gateway
28
- └─ coordinates a signing round for a feedId
29
+ └─ coordinates a signing round for (sourceId, signaturesRequired)
29
30
 
30
31
  Verifier nodes
31
32
  └─ fetch/recompute the API result independently
@@ -38,7 +39,18 @@ Solana / EVM / Starknet verifiers
38
39
 
39
40
  The gateway is a coordination layer, not a trusted oracle. A result is trusted only if it carries a valid threshold signature from the selected verifier nodes for the current registry version.
40
41
 
41
- Molpha uses Solana as the canonical protocol chain for subscriptions, registry state, node accounts, and feed state. EVM and Starknet verifier contracts are stateless verification surfaces: they verify signed Molpha data updates without managing subscriptions or feed configuration locally.
42
+ Molpha uses Solana as the canonical protocol chain for subscriptions, registry snapshots, node accounts, and feed state. EVM and Starknet verifier contracts are stateless verification surfaces: they verify signed Molpha attestations without managing subscriptions or source configuration locally.
43
+
44
+ Every signed attestation commits to the same message across chains:
45
+
46
+ ```text
47
+ message = keccak256(
48
+ keccak256("MOLPHA_MESSAGE_V1") || sourceId || u32be(registryVersion) ||
49
+ u32be(signaturesRequired) || signersBitmap || value || u64be(canonicalTimestamp)
50
+ )
51
+ ```
52
+
53
+ `attestationMessageHash` / `attestationMessageHashFromResult` recompute it client-side.
42
54
 
43
55
  ## Install
44
56
 
@@ -59,7 +71,7 @@ Runtime dependencies include `@solana/kit`, `@anchor-lang/core`, and `@noble/*`.
59
71
 
60
72
  | Import | Use |
61
73
  |---|---|
62
- | `@molpha/sdk` | Facade (`MolphaSDK`), `MolphaGateway`, `MolphaSolanaClient`, core types, EVM/Starknet helpers. Browser-safe; no `fs` in the main entry. |
74
+ | `@molpha/sdk` | Facade (`MolphaSDK`), `MolphaGateway`, `MolphaSolanaClient`, core hashing (`deriveSourceId`, `attestationMessageHash`, `hashRequestAuth`), EVM/Starknet helpers. Browser-safe; no `fs` in the main entry. |
63
75
  | `@molpha/sdk/utils` | `walletFromKeypairFile`, `loadKeypair` — load a Solana CLI keypair as an Anchor `Wallet`. Node.js only. |
64
76
 
65
77
  The package is ESM with `"sideEffects": false`, so gateway-only or read-only apps can tree-shake unused paths.
@@ -68,18 +80,14 @@ The package is ESM with `"sideEffects": false`, so gateway-only or read-only app
68
80
 
69
81
  ```ts
70
82
  import { web3 } from "@anchor-lang/core";
71
- import {
72
- MolphaSDK,
73
- PlanType,
74
- deriveApiConfigHash,
75
- deriveFeedIdString,
76
- } from "@molpha/sdk";
83
+ import { MolphaSDK, PlanType, deriveSourceIdString } from "@molpha/sdk";
77
84
  import { walletFromKeypairFile } from "@molpha/sdk/utils";
78
85
 
79
86
  const wallet = walletFromKeypairFile("~/.config/solana/id.json");
80
87
  const sdk = new MolphaSDK({
81
88
  connection: new web3.Connection("https://api.devnet.solana.com", "confirmed"),
82
89
  wallet,
90
+ // endpoints: [{ url: "https://gateway.example.com", gatewayAuthority: "<base58>" }],
83
91
  });
84
92
 
85
93
  // Subscribe if needed (USDC on Solana)
@@ -93,19 +101,17 @@ const apiConfig = {
93
101
  responseParser: "$.price",
94
102
  };
95
103
  const signaturesRequired = 3;
96
- const feedId = deriveFeedIdString(
97
- wallet.publicKey.toBytes(),
98
- deriveApiConfigHash(apiConfig),
99
- signaturesRequired,
100
- );
101
104
 
102
- const { result, signature } = await sdk.requestAndSubmit(feedId, {
105
+ const { result, signature, feed } = await sdk.requestAndSubmit({
103
106
  apiConfig,
104
107
  signaturesRequired,
105
108
  });
109
+
110
+ // The round's identity, for reads and cross-chain verification.
111
+ const sourceId = deriveSourceIdString(apiConfig); // === result.sourceId
106
112
  ```
107
113
 
108
- `requestAndSubmit` requests a threshold-signed data update from the gateway (against the current on-chain registry version) and submits it to Solana in one call. The first successful submit creates the feed account if it does not already exist.
114
+ `requestAndSubmit` requests a threshold-signed attestation from the gateway (against the current on-chain registry version) and submits it to Solana via `submit_attestation` in one call. The first successful submit creates the feed account for `(sourceId, signaturesRequired, submitter)` if it does not already exist.
109
115
 
110
116
  ## Configuration
111
117
 
@@ -120,8 +126,8 @@ const { result, signature } = await sdk.requestAndSubmit(feedId, {
120
126
 
121
127
  | Option | Default |
122
128
  |---|---|
123
- | `endpoints` | `DEFAULT_GATEWAY_ENDPOINT` — string or array for failover |
124
- | `programId` | `MOLPHA_PROGRAM_ADDRESS` from the vendored IDL |
129
+ | `endpoints` | `DEFAULT_GATEWAY_ENDPOINT` — URL, `{ url, gatewayAuthority }`, or an array of either for failover (see [Gateway identity](#gateway-identity)) |
130
+ | `programId` | `MOLPHA_PROGRAM_ADDRESS` from the vendored IDL; also bound into gateway request auth |
125
131
  | `idl` | `MOLPHA_IDL` from `idl/molpha.json` |
126
132
  | `commitment` | `"confirmed"` |
127
133
 
@@ -135,20 +141,42 @@ import {
135
141
  const sdk = new MolphaSDK({
136
142
  connection,
137
143
  wallet,
138
- endpoints: [DEFAULT_GATEWAY_ENDPOINT, "https://backup.example.com"],
144
+ endpoints: [
145
+ DEFAULT_GATEWAY_ENDPOINT,
146
+ { url: "https://backup.example.com", gatewayAuthority: "<gateway base58 pubkey>" },
147
+ ],
139
148
  // programId: "YourProgramAddress...",
140
149
  // idl: MOLPHA_IDL,
141
150
  });
142
151
  ```
143
152
 
153
+ ### Gateway identity
154
+
155
+ Gateway request authorization binds the gateway's on-chain account, so the client must know which gateway it is talking to:
156
+
157
+ ```text
158
+ requestAuthHash = keccak256(
159
+ "MOLPHA_REQAUTH_V1" || programId || gatewayPda || sourceId || u8(signaturesRequired) || u64le(timestamp)
160
+ )
161
+ gatewayPda = PDA(["molpha_gateway", gatewayAuthority], programId)
162
+ ```
163
+
164
+ 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:
165
+
166
+ ```json
167
+ { "status": "ok", "data": { "gatewayAuthority": "<base58>", "programId": "<base58>" } }
168
+ ```
169
+
170
+ 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).
171
+
144
172
  ## Wallet
145
173
 
146
174
  `wallet` is a single `MolphaWallet` used across both protocol surfaces:
147
175
 
148
176
  | Layer | What it signs |
149
177
  |---|---|
150
- | Solana client | Transactions such as `subscribe`, `extendSubscription`, `submitDataUpdate` |
151
- | Gateway client | `authMessage(feedId, timestamp)` for authenticated gateway requests |
178
+ | Solana client | Transactions such as `subscribe`, `extendSubscription`, `submitAttestation` |
179
+ | Gateway client | `hashRequestAuth({ programId, gateway, sourceId, signaturesRequired, timestamp })` for authenticated gateway requests |
152
180
 
153
181
  Gateway auth is resolved automatically when you use `MolphaSDK`:
154
182
 
@@ -157,7 +185,7 @@ Gateway auth is resolved automatically when you use `MolphaSDK`:
157
185
  3. Else omit auth and use an all-zero `authSig`.
158
186
 
159
187
  `MolphaSDK` passes the resolved signer to `sdk.gateway` as its default, so
160
- `sdk.gateway.requestSignedData({ feedId, apiConfig, signaturesRequired })` authenticates without an
188
+ `sdk.gateway.requestSignedData({ apiConfig, signaturesRequired })` authenticates without an
161
189
  explicit `signer`. Standalone `new MolphaGateway(...)` omits auth unless you pass
162
190
  a `defaultSigner` (third constructor arg) or per-call `signer`.
163
191
 
@@ -223,12 +251,12 @@ const { pricePaid } = await sdk.solana.subscribe(PlanType.Basic, {
223
251
 
224
252
  `maxPriceUsdc` is a safety bound. The transaction aborts if the live plan price is higher than the amount the user approved.
225
253
 
226
- ### 2. Derive a feed id
254
+ ### 2. Source id
227
255
 
228
- There is no separate `createJob` instruction. Feed identity is deterministic:
256
+ There is no create-feed instruction. A data source is identified by its canonical API config:
229
257
 
230
258
  ```text
231
- feedId = keccak256("MOLPHA_JOB_V1" || owner || apiConfigHash || [signaturesRequired])
259
+ sourceId = keccak256(JSON.stringify(canonicalizeAPIConfig(apiConfig)))
232
260
  ```
233
261
 
234
262
  ```ts
@@ -237,21 +265,16 @@ const apiConfig = {
237
265
  responseParser: "$.price",
238
266
  };
239
267
 
240
- const signaturesRequired = 3;
241
- const feedId = deriveFeedIdString(
242
- wallet.publicKey.toBytes(),
243
- deriveApiConfigHash(apiConfig),
244
- signaturesRequired,
245
- );
268
+ const sourceId = deriveSourceIdString(apiConfig); // 64 hex chars, no 0x
246
269
  ```
247
270
 
248
- The on-chain feed commits to the `apiConfigHash`, not the full API config. This binds the feed to a specific off-chain data source and parsing logic while keeping large config payloads and secrets off-chain. Pass the same `apiConfig` (including `{{secret.*}}` placeholders) to gateway requests that you hashed when deriving the feed id.
271
+ The gateway, the Solana program and the EVM/Starknet verifiers all recompute `sourceId` from the same canonical config, so pass the same `apiConfig` (including `{{secret.*}}` placeholders) every time. `signaturesRequired` is **not** part of `sourceId`: on Solana a feed account is keyed by `(sourceId, signaturesRequired, submitter)`, so the same source can be tracked at different quorums.
249
272
 
250
273
  ### 3. Request signed data from the gateway
251
274
 
252
275
  ```ts
276
+ const signaturesRequired = 3;
253
277
  const result = await sdk.gateway.requestSignedData({
254
- feedId,
255
278
  apiConfig,
256
279
  signaturesRequired,
257
280
  });
@@ -259,24 +282,24 @@ const result = await sdk.gateway.requestSignedData({
259
282
 
260
283
  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.
261
284
 
262
- The returned `DataUpdateResult` includes the signed value, canonical timestamp, registry version, required quorum, signer bitmap, and aggregate signature.
285
+ The returned `DataUpdateResult` includes `sourceId`, the signed value, canonical timestamp, registry version, required quorum, signer bitmap, and aggregate signature. `signaturesRequired` must be at least the protocol's `min_signers` (currently 3) or the chain rejects the submit.
263
286
 
264
287
  ### 4. Submit on Solana
265
288
 
266
289
  ```ts
267
- const { signature } = await sdk.solana.submitDataUpdate(result);
290
+ const { signature, feed } = await sdk.solana.submitAttestation(result);
268
291
  ```
269
292
 
270
- Then read the finalized feed:
293
+ Then read the feed this wallet wrote for that source and quorum:
271
294
 
272
295
  ```ts
273
- const feed = await sdk.solana.readFeed(feedId);
296
+ const feedState = await sdk.solana.readFeed(result.sourceId, signaturesRequired);
274
297
  ```
275
298
 
276
299
  ### One-call request + submit
277
300
 
278
301
  ```ts
279
- const { result, signature } = await sdk.requestAndSubmit(feedId, {
302
+ const { result, signature, feed } = await sdk.requestAndSubmit({
280
303
  apiConfig,
281
304
  signaturesRequired,
282
305
  });
@@ -285,23 +308,24 @@ const { result, signature } = await sdk.requestAndSubmit(feedId, {
285
308
  This is equivalent to:
286
309
 
287
310
  ```ts
288
- const result = await sdk.gateway.requestSignedData({ feedId, apiConfig, signaturesRequired });
289
- const { signature } = await sdk.solana.submitDataUpdate(result);
311
+ const result = await sdk.gateway.requestSignedData({ apiConfig, signaturesRequired });
312
+ const { signature, feed } = await sdk.solana.submitAttestation(result);
290
313
  ```
291
314
 
292
315
  ### Fast requests with a cached context
293
316
 
294
- By default every `requestSignedData` call fetches slow-changing inputs up
295
- front (in parallel): the on-chain registry version and redundancy buffer (one
296
- account read), and the node set. When you run many rounds for the same feed,
297
- fetch these once and reuse them so each round is a single gateway POST.
317
+ By default every `requestSignedData` call reads the on-chain registry up front
318
+ (current version, redundancy buffer and node count of that snapshot) and fetches
319
+ the gateway node set only when the round needs it (private API encryption). When
320
+ you run many rounds for the same source, fetch these once and reuse them so each
321
+ round is a single gateway POST.
298
322
 
299
323
  ```ts
300
- // Fetch registryVersion + redundancyBuffer + nodes once.
301
- const context = await sdk.gateway.prepareContext(feedId);
324
+ // Fetch registryVersion + redundancyBuffer + nodeCount + nodes once.
325
+ const context = await sdk.gateway.prepareContext();
302
326
 
303
327
  // Reuse it across rounds — no prelude fetches.
304
- const result = await sdk.gateway.requestSignedData({ feedId, apiConfig, signaturesRequired, context });
328
+ const result = await sdk.gateway.requestSignedData({ apiConfig, signaturesRequired, context });
305
329
  ```
306
330
 
307
331
  `context` is a `Partial<RoundContext>`, so you can cache only what you have
@@ -309,25 +333,24 @@ and let `requestSignedData` fetch the rest:
309
333
 
310
334
  ```ts
311
335
  const result = await sdk.gateway.requestSignedData({
312
- feedId,
313
336
  apiConfig,
314
337
  signaturesRequired,
315
- context: { nodes }, // registryVersion + redundancyBuffer still fetched fresh
338
+ context: { nodes }, // registryVersion + redundancyBuffer + nodeCount still fetched fresh
316
339
  });
317
340
  ```
318
341
 
319
342
  Caching is opt-in because these inputs can drift. A stale `registryVersion`,
320
- `redundancyBuffer`, or node set yields a result the chain will reject — refresh
321
- the context when the on-chain registry changes. The same `context` field is
322
- accepted by `requestAndSubmit`.
343
+ `redundancyBuffer`, `nodeCount`, or node set yields a result the chain will reject —
344
+ refresh the context when the on-chain registry changes. The SDK derives the
345
+ selection from the on-chain `nodeCount` and refuses a cached node list whose length
346
+ disagrees with it. The same `context` field is accepted by `requestAndSubmit`.
323
347
 
324
348
  ## Private APIs and encrypted secrets
325
349
 
326
- Jobs can use private APIs without sending plaintext secrets to the gateway.
350
+ Sources can use private APIs without sending plaintext secrets to the gateway.
327
351
 
328
352
  ```ts
329
353
  const result = await sdk.gateway.requestSignedData({
330
- feedId,
331
354
  apiConfig: {
332
355
  url: "https://api.example.com/private-price?key={{secret.apiKey}}",
333
356
  responseParser: "$.price",
@@ -341,7 +364,7 @@ const result = await sdk.gateway.requestSignedData({
341
364
  });
342
365
  ```
343
366
 
344
- `MolphaSDK` wires `verifyNodeKeys` to `solana.verifyNodeKeysForPrivateApi`, which authenticates gateway node encryption keys against on-chain Node accounts before secrets are encrypted.
367
+ `MolphaSDK` wires `verifyNodeKeys` to `solana.verifyNodeKeysForPrivateApi`, which authenticates gateway node encryption keys against the on-chain `Node` accounts of the round's registry snapshot (`registry.nodes[index]`) before secrets are encrypted.
345
368
 
346
369
  Secrets are encrypted into per-node envelopes. The gateway coordinates the round but should not receive plaintext API credentials.
347
370
 
@@ -371,7 +394,7 @@ Supported network ids (selection helpers only): `evm-sepolia`, `arbitrum-sepolia
371
394
  ```ts
372
395
  import { buildEvmVerifierArgs } from "@molpha/sdk";
373
396
 
374
- const result = await sdk.gateway.requestSignedData({ feedId, apiConfig, signaturesRequired });
397
+ const result = await sdk.gateway.requestSignedData({ apiConfig, signaturesRequired });
375
398
 
376
399
  const { dataUpdate, signature } = buildEvmVerifierArgs(result);
377
400
  ```
@@ -380,7 +403,7 @@ The generated tuples match the Molpha EVM verifier ABI:
380
403
 
381
404
  ```ts
382
405
  // dataUpdate:
383
- // [bytes32 feedId,
406
+ // [bytes32 sourceId,
384
407
  // uint32 registryVersion,
385
408
  // uint32 signaturesRequired,
386
409
  // bytes32 valuePacked,
@@ -392,7 +415,7 @@ The generated tuples match the Molpha EVM verifier ABI:
392
415
  // uint256 signersBitmap]
393
416
  ```
394
417
 
395
- Note: the shipped `MOLPHA_VERIFIER_ABI` still names the first struct field `jobId` for contract compatibility; the SDK value is the feed id bytes32.
418
+ Note: the deployed contract's source names the first struct field `jobId`; the SDK ABI names it `sourceId` (component names do not affect encoding) and the value is the 32-byte source id.
396
419
 
397
420
  ### ethers
398
421
 
@@ -437,7 +460,7 @@ await client.readContract({
437
460
  functionName: "verify",
438
461
  args: [
439
462
  {
440
- jobId: dataUpdate[0],
463
+ sourceId: dataUpdate[0],
441
464
  registryVersion: dataUpdate[1],
442
465
  signaturesRequired: dataUpdate[2],
443
466
  value: dataUpdate[3],
@@ -494,17 +517,17 @@ const sepoliaDirect = MOLPHA_VERIFIER_STARKNET_SEPOLIA;
494
517
  ```ts
495
518
  import { buildStarknetVerifierArgs } from "@molpha/sdk";
496
519
 
497
- const result = await sdk.gateway.requestSignedData({ feedId, apiConfig, signaturesRequired });
520
+ const result = await sdk.gateway.requestSignedData({ apiConfig, signaturesRequired });
498
521
 
499
522
  const { dataUpdate, signature } = buildStarknetVerifierArgs(result);
500
523
  ```
501
524
 
502
- The generated objects match the Molpha Starknet verifier interface:
525
+ The generated objects match the Molpha Starknet verifier interface (the Cairo struct still names the first field `feed_id`; positional calldata is unchanged):
503
526
 
504
527
  ```ts
505
528
  // dataUpdate:
506
529
  // {
507
- // feed_id: u256,
530
+ // source_id: u256,
508
531
  // registry_version: u32,
509
532
  // signatures_required: u32,
510
533
  // value: u256,
@@ -530,18 +553,17 @@ import {
530
553
 
531
554
  ## What verification checks
532
555
 
533
- A Molpha data update is valid only if the verifier can confirm:
556
+ A Molpha attestation is valid only if the verifier can confirm:
534
557
 
535
- - the update targets the expected `feedId`;
536
- - the result was signed against a specific `registryVersion`;
558
+ - the update targets the expected `sourceId`;
559
+ - the result was signed against a specific `registryVersion` (an immutable node-set snapshot);
537
560
  - the quorum satisfies `signaturesRequired`;
538
- - the signer bitmap maps to valid selected nodes;
539
- - the aggregate Schnorr signature is valid;
540
- - the signed value and canonical timestamp match the message;
561
+ - the signer bitmap is a subset of the deterministic selection for `(sourceId, registryVersion, canonicalTimestamp)`;
562
+ - the aggregate Schnorr signature over `attestationMessageHash(...)` is valid;
541
563
  - the timestamp is within the accepted freshness bounds;
542
- - on Solana, remaining accounts resolve correctly for the registry version (including previous-version remap during transitions).
564
+ - on Solana, the signer `Node` accounts passed as remaining accounts are exactly `registry.nodes[bit]` for every set bit.
543
565
 
544
- Solana verification finalizes feed state via `submit_data_update`. EVM and Starknet verification are stateless and return whether the signed Molpha update is valid for the deployed verifier registry.
566
+ Solana verification finalizes feed state via `submit_attestation`. EVM and Starknet verification are stateless and return whether the signed Molpha attestation is valid for the deployed verifier registry.
545
567
 
546
568
  ## IDL vendoring
547
569
 
@@ -595,9 +617,9 @@ The facade wires the registry selection config resolver, gateway signer, subscri
595
617
  Current scope:
596
618
 
597
619
  - Solana subscription and extend flow;
598
- - deterministic feed ID derivation;
599
- - gateway signed-data requests (failover, retries, context cache);
600
- - Solana data update submission and feed reads;
620
+ - deterministic source id and attestation message hashing;
621
+ - gateway signed-data requests (failover, retries, per-gateway request auth, context cache);
622
+ - Solana attestation submission and feed/registry reads;
601
623
  - private API encryption helpers (pre-production);
602
624
  - EVM and Starknet verifier argument building;
603
625
  - deployed testnet verifier address helpers.
@@ -609,7 +631,25 @@ Known limitations:
609
631
  - production deployments should use authenticated gateway requests;
610
632
  - testnet verifier addresses may change between protocol releases.
611
633
 
612
- Solana paths such as selection bitmap, previous-version remap, and `submit_data_update` remaining-accounts resolution are aligned with the Molpha program version vendored in this repo.
634
+ Solana paths such as selection bitmap and `submit_attestation` remaining-accounts resolution are aligned with the Molpha program version vendored in this repo (`MoLFnEbuMS5gWnXNfUMLAYSqRM3eQZKWRzjeMQfqbT3`, not yet deployed).
635
+
636
+ ## Migrating from 0.1.x
637
+
638
+ | Before | After |
639
+ |---|---|
640
+ | `deriveFeedId(owner, apiConfigHash, sigReq)` / `deriveFeedIdString` | removed — use `deriveSourceId(apiConfig)` / `deriveSourceIdString` |
641
+ | `deriveApiConfigHash(apiConfig)` | `deriveSourceId(apiConfig)` (old name kept as a deprecated alias, same bytes) |
642
+ | `requestSignedData({ feedId, ... })` | `requestSignedData({ apiConfig, signaturesRequired, ... })` — `sourceId` is derived from `apiConfig` |
643
+ | `prepareContext(feedId)` | `prepareContext()` |
644
+ | `requestAndSubmit(feedId, opts)` | `requestAndSubmit(opts)` |
645
+ | `authMessage(feedId, timestamp)` (sha256) | `hashRequestAuth({ programId, gateway, sourceId, signaturesRequired, timestamp })` (keccak) |
646
+ | `endpoints: string[]` | `endpoints: (string \| { url, gatewayAuthority })[]` |
647
+ | `submitDataUpdate(result)` | `submitAttestation(result)` (deprecated alias kept); returns `{ signature, feed }` |
648
+ | `readFeed(feedId)` | `readFeed(sourceId, signaturesRequired, submitter?)` |
649
+ | `result.feedId` / `NodeKeyVerifierArgs.feedId` | `.sourceId` |
650
+ | EVM tuple `feedId`, ABI `jobId` | `sourceId` |
651
+ | Starknet `feed_id` | `source_id` |
652
+ | `resolveRegistryIndexForVersion`, `VIRTUAL_INDEX`, `nodePda(index)` | removed — signer accounts are `registry.nodes[bit]`; `nodePda(owner)` |
613
653
 
614
654
  ## Develop
615
655
 
@@ -671,13 +711,19 @@ Versions follow semver and are driven by the nature of each change, not by the b
671
711
 
672
712
  ### One-time setup
673
713
 
674
- 1. Add `NPM_TOKEN` under GitHub repo settings:
714
+ 1. Configure npm [trusted publishing](https://docs.npmjs.com/trusted-publishers/) for `@molpha/sdk`:
675
715
 
676
716
  ```text
677
- Settings → Secrets and variables → Actions
717
+ npmjs.com → @molpha/sdk → Settings → Trusted publishing
678
718
  ```
679
719
 
680
- Use a granular npm automation token with publish access to the `@molpha` scope.
720
+ Add a trusted publisher for the release workflow:
721
+
722
+ | Workflow file | Branches | Purpose |
723
+ | ------------- | -------------- | ------------------------------- |
724
+ | `release.yml` | `main`, `dev` | Stable releases + dev snapshots |
725
+
726
+ Use organization `Molpha`, repository `sdk`, and the exact workflow filename (including `.yml`). No `NPM_TOKEN` secret is required.
681
727
 
682
728
  2. Publish a stable release from `main` first.
683
729
 
@@ -1,5 +1,5 @@
1
1
  import { web3 } from '@anchor-lang/core';
2
- import { address, getAddressEncoder } from '@solana/kit';
2
+ import { address, getAddressEncoder, getAddressDecoder } from '@solana/kit';
3
3
  import { ed25519 } from '@noble/curves/ed25519.js';
4
4
 
5
5
  // src/solana/kit.ts
@@ -9,6 +9,7 @@ var ASSOCIATED_TOKEN_PROGRAM_ADDRESS = address(
9
9
  );
10
10
  var SYSTEM_PROGRAM_ADDRESS = address("11111111111111111111111111111111");
11
11
  var addressEncoder = getAddressEncoder();
12
+ var addressDecoder = getAddressDecoder();
12
13
  function toSolanaAddress(value) {
13
14
  return address(typeof value === "string" ? value : value.toBase58());
14
15
  }
@@ -18,6 +19,9 @@ function toPublicKey(value) {
18
19
  function addressBytes(value) {
19
20
  return Uint8Array.from(addressEncoder.encode(toSolanaAddress(value)));
20
21
  }
22
+ function addressFromBytes(bytes) {
23
+ return addressDecoder.decode(bytes);
24
+ }
21
25
  function findProgramAddressSync(seeds, programAddress) {
22
26
  const [pda] = web3.PublicKey.findProgramAddressSync(seeds, toPublicKey(programAddress));
23
27
  return toSolanaAddress(pda);
@@ -46,6 +50,6 @@ function gatewaySignerFromWallet(wallet) {
46
50
  return void 0;
47
51
  }
48
52
 
49
- export { SYSTEM_PROGRAM_ADDRESS, TOKEN_PROGRAM_ADDRESS, addressBytes, findProgramAddressSync, gatewaySignerFromWallet, getAssociatedTokenAddressSync, keypairFromSecretKey, setComputeUnitLimit, signerFromKeypair, toPublicKey, toSolanaAddress };
50
- //# sourceMappingURL=chunk-MFELBWSL.js.map
51
- //# sourceMappingURL=chunk-MFELBWSL.js.map
53
+ export { SYSTEM_PROGRAM_ADDRESS, TOKEN_PROGRAM_ADDRESS, addressBytes, addressFromBytes, findProgramAddressSync, gatewaySignerFromWallet, getAssociatedTokenAddressSync, keypairFromSecretKey, setComputeUnitLimit, signerFromKeypair, toPublicKey, toSolanaAddress };
54
+ //# sourceMappingURL=chunk-DT6IKCWB.js.map
55
+ //# sourceMappingURL=chunk-DT6IKCWB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/solana/kit.ts","../src/wallet.ts"],"names":[],"mappings":";;;;;AAaO,IAAM,qBAAA,GAAwB,QAAQ,6CAA6C;AACnF,IAAM,gCAAA,GAAmC,OAAA;AAAA,EAC9C;AACF,CAAA;AACO,IAAM,sBAAA,GAAyB,QAAQ,kCAAkC;AAEhF,IAAM,iBAAiB,iBAAA,EAAkB;AACzC,IAAM,iBAAiB,iBAAA,EAAkB;AAElC,SAAS,gBAAgB,KAAA,EAA+B;AAC7D,EAAA,OAAO,QAAQ,OAAO,KAAA,KAAU,WAAW,KAAA,GAAQ,KAAA,CAAM,UAAU,CAAA;AACrE;AAEO,SAAS,YAAY,KAAA,EAA2D;AACrF,EAAA,OAAO,iBAAiB,IAAA,CAAK,SAAA,GAAY,QAAQ,IAAI,IAAA,CAAK,UAAU,KAAK,CAAA;AAC3E;AAEO,SAAS,aAAa,KAAA,EAAkC;AAC7D,EAAA,OAAO,WAAW,IAAA,CAAK,cAAA,CAAe,OAAO,eAAA,CAAgB,KAAK,CAAC,CAAC,CAAA;AACtE;AAGO,SAAS,iBAAiB,KAAA,EAA4B;AAC3D,EAAA,OAAO,cAAA,CAAe,OAAO,KAAK,CAAA;AACpC;AAEO,SAAS,sBAAA,CACd,OACA,cAAA,EACS;AACT,EAAA,MAAM,CAAC,GAAG,CAAA,GAAI,IAAA,CAAK,UAAU,sBAAA,CAAuB,KAAA,EAAO,WAAA,CAAY,cAAc,CAAC,CAAA;AACtF,EAAA,OAAO,gBAAgB,GAAG,CAAA;AAC5B;AAUO,SAAS,6BAAA,CACd,IAAA,EACA,KAAA,EACA,YAAA,GAA8B,qBAAA,EACrB;AACT,EAAA,OAAO,sBAAA;AAAA,IACL,CAAC,aAAa,KAAK,CAAA,EAAG,aAAa,YAAY,CAAA,EAAG,YAAA,CAAa,IAAI,CAAC,CAAA;AAAA,IACpE;AAAA,GACF;AACF;AAEO,SAAS,oBAAoB,KAAA,EAAkC;AACpE,EAAA,OAAO,IAAA,CAAK,oBAAA,CAAqB,mBAAA,CAAoB,EAAE,OAAO,CAAA;AAChE;AAEO,SAAS,qBAAqB,SAAA,EAAsC;AACzE,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,aAAA,CAAc,SAAS,CAAA;AAC7C;ACvDA,IAAM,SAAA,GAAY,CAAC,KAAA,KACjB,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,IAAA,IACV,WAAA,IAAe,KAAA,IACf,KAAA,CAAM,SAAA,YAAqB,UAAA;AAGtB,SAAS,kBAAkB,OAAA,EAAgC;AAChE,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,SAAA,CAAU,KAAA,CAAM,GAAG,EAAE,CAAA;AAC1C,EAAA,OAAO,OAAO,OAAA,KAA6C,OAAA,CAAQ,IAAA,CAAK,SAAS,IAAI,CAAA;AACvF;AAGO,SAAS,wBAAwB,MAAA,EAAoC;AAC1E,EAAA,MAAM,MAAA,GAAS,MAAA;AACf,EAAA,IAAI,MAAA,CAAO,eAAA,EAAiB,OAAO,MAAA,CAAO,eAAA;AAC1C,EAAA,IAAI,OAAA,IAAW,UAAU,SAAA,CAAU,MAAA,CAAO,KAAK,CAAA,EAAG,OAAO,iBAAA,CAAkB,MAAA,CAAO,KAAK,CAAA;AACvF,EAAA,OAAO,MAAA;AACT","file":"chunk-DT6IKCWB.js","sourcesContent":["import { web3 } from \"@anchor-lang/core\";\nimport { address, getAddressDecoder, getAddressEncoder, type Address } from \"@solana/kit\";\n\nexport type SolanaAddress = Address | string | InstanceType<typeof web3.PublicKey>;\nexport type SolanaConnection = InstanceType<typeof web3.Connection>;\nexport type SolanaKeypair = InstanceType<typeof web3.Keypair>;\nexport type SolanaInstruction = InstanceType<typeof web3.TransactionInstruction>;\nexport type SolanaAccountMeta = {\n pubkey: InstanceType<typeof web3.PublicKey>;\n isSigner: boolean;\n isWritable: boolean;\n};\n\nexport const TOKEN_PROGRAM_ADDRESS = address(\"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA\");\nexport const ASSOCIATED_TOKEN_PROGRAM_ADDRESS = address(\n \"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL\",\n);\nexport const SYSTEM_PROGRAM_ADDRESS = address(\"11111111111111111111111111111111\");\n\nconst addressEncoder = getAddressEncoder();\nconst addressDecoder = getAddressDecoder();\n\nexport function toSolanaAddress(value: SolanaAddress): Address {\n return address(typeof value === \"string\" ? value : value.toBase58());\n}\n\nexport function toPublicKey(value: SolanaAddress): InstanceType<typeof web3.PublicKey> {\n return value instanceof web3.PublicKey ? value : new web3.PublicKey(value);\n}\n\nexport function addressBytes(value: SolanaAddress): Uint8Array {\n return Uint8Array.from(addressEncoder.encode(toSolanaAddress(value)));\n}\n\n/** Base58 `Address` from 32 raw bytes (e.g. a `Registry.nodes[i]` entry). */\nexport function addressFromBytes(bytes: Uint8Array): Address {\n return addressDecoder.decode(bytes);\n}\n\nexport function findProgramAddressSync(\n seeds: Uint8Array[],\n programAddress: SolanaAddress,\n): Address {\n const [pda] = web3.PublicKey.findProgramAddressSync(seeds, toPublicKey(programAddress));\n return toSolanaAddress(pda);\n}\n\nexport function findProgramPublicKeySync(\n seeds: Uint8Array[],\n programAddress: SolanaAddress,\n): InstanceType<typeof web3.PublicKey> {\n const [pda] = web3.PublicKey.findProgramAddressSync(seeds, toPublicKey(programAddress));\n return pda;\n}\n\nexport function getAssociatedTokenAddressSync(\n mint: SolanaAddress,\n owner: SolanaAddress,\n tokenProgram: SolanaAddress = TOKEN_PROGRAM_ADDRESS,\n): Address {\n return findProgramAddressSync(\n [addressBytes(owner), addressBytes(tokenProgram), addressBytes(mint)],\n ASSOCIATED_TOKEN_PROGRAM_ADDRESS,\n );\n}\n\nexport function setComputeUnitLimit(units: number): SolanaInstruction {\n return web3.ComputeBudgetProgram.setComputeUnitLimit({ units });\n}\n\nexport function keypairFromSecretKey(secretKey: Uint8Array): SolanaKeypair {\n return web3.Keypair.fromSecretKey(secretKey);\n}\n\nexport function generateKeypair(): SolanaKeypair {\n return web3.Keypair.generate();\n}\n","/**\n * Unified wallet surface: Anchor txs + optional gateway authSig signing.\n */\nimport type { Wallet } from \"@anchor-lang/core\";\nimport { ed25519 } from \"@noble/curves/ed25519.js\";\nimport type { Signer } from \"./core/types.js\";\nimport type { SolanaKeypair } from \"./solana/kit.js\";\n\n/** Anchor `Wallet` plus optional gateway auth override. */\nexport type MolphaWallet = Wallet & {\n /**\n * Signs gateway request-auth hashes (`hashRequestAuth`). When omitted, derived from Anchor\n * `Wallet.payer` when the keypair secret is available (Node `Wallet`, etc.).\n */\n signAuthMessage?: Signer;\n};\n\nconst isKeypair = (value: unknown): value is SolanaKeypair =>\n typeof value === \"object\" &&\n value !== null &&\n \"secretKey\" in value &&\n value.secretKey instanceof Uint8Array;\n\n/** ed25519 gateway signer from a Solana keypair's 32-byte seed. */\nexport function signerFromKeypair(keypair: SolanaKeypair): Signer {\n const seed = keypair.secretKey.slice(0, 32);\n return async (message: Uint8Array): Promise<Uint8Array> => ed25519.sign(message, seed);\n}\n\n/** Resolve the gateway auth signer for a Molpha wallet, if available. */\nexport function gatewaySignerFromWallet(wallet: Wallet): Signer | undefined {\n const molpha = wallet as MolphaWallet;\n if (molpha.signAuthMessage) return molpha.signAuthMessage;\n if (\"payer\" in wallet && isKeypair(wallet.payer)) return signerFromKeypair(wallet.payer);\n return undefined;\n}\n"]}