@molpha/sdk 0.1.0
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/LICENSE +21 -0
- package/README.md +698 -0
- package/dist/chunk-MFELBWSL.js +51 -0
- package/dist/chunk-MFELBWSL.js.map +1 -0
- package/dist/index.d.ts +584 -0
- package/dist/index.js +6779 -0
- package/dist/index.js.map +1 -0
- package/dist/utils.d.ts +14 -0
- package/dist/utils.js +20 -0
- package/dist/utils.js.map +1 -0
- package/dist/wallet-CbpYQO4l.d.ts +122 -0
- package/idl/index.ts +14 -0
- package/idl/molpha.json +5563 -0
- package/package.json +88 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Molpha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
# @molpha/sdk
|
|
2
|
+
|
|
3
|
+
Browser-first TypeScript SDK for **Molpha data consumers and feed owners**.
|
|
4
|
+
|
|
5
|
+
Use it to:
|
|
6
|
+
|
|
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;
|
|
11
|
+
- verify/read the latest feed value;
|
|
12
|
+
- build EVM and Starknet verifier arguments from the same signed result.
|
|
13
|
+
|
|
14
|
+
**Runtime:** Node.js `>=20.19.0` (ESM).
|
|
15
|
+
|
|
16
|
+
## Protocol model
|
|
17
|
+
|
|
18
|
+
Molpha turns off-chain API responses into verified on-chain data.
|
|
19
|
+
|
|
20
|
+
At a high level:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
Consumer / feed owner
|
|
24
|
+
└─ subscribes (USDC) on Solana
|
|
25
|
+
└─ derives feedId from owner + apiConfigHash + signaturesRequired
|
|
26
|
+
|
|
27
|
+
Gateway
|
|
28
|
+
└─ coordinates a signing round for a feedId
|
|
29
|
+
|
|
30
|
+
Verifier nodes
|
|
31
|
+
└─ fetch/recompute the API result independently
|
|
32
|
+
└─ sign the canonical result if valid
|
|
33
|
+
|
|
34
|
+
Solana / EVM / Starknet verifiers
|
|
35
|
+
└─ verify quorum, registry version, signer bitmap, timestamp, and aggregate signature
|
|
36
|
+
└─ finalize or expose the verified value
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
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
|
+
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
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm add @molpha/sdk
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Runtime dependencies include `@solana/kit`, `@anchor-lang/core`, and `@noble/*`. `bn.js` is an optional peer dependency (used by the Solana / Anchor path).
|
|
50
|
+
|
|
51
|
+
> **Migration from `@molpha-oracle/sdk`:** The package was renamed to `@molpha/sdk` starting at `0.1.0`. `@molpha-oracle/sdk` is deprecated — update install commands and imports:
|
|
52
|
+
>
|
|
53
|
+
> ```bash
|
|
54
|
+
> pnpm remove @molpha-oracle/sdk
|
|
55
|
+
> pnpm add @molpha/sdk
|
|
56
|
+
> ```
|
|
57
|
+
>
|
|
58
|
+
> Replace `@molpha-oracle/sdk` with `@molpha/sdk` in all import paths (including `@molpha/sdk/utils`).
|
|
59
|
+
|
|
60
|
+
| Import | Use |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `@molpha/sdk` | Facade (`MolphaSDK`), `MolphaGateway`, `MolphaSolanaClient`, core types, EVM/Starknet helpers. Browser-safe; no `fs` in the main entry. |
|
|
63
|
+
| `@molpha/sdk/utils` | `walletFromKeypairFile`, `loadKeypair` — load a Solana CLI keypair as an Anchor `Wallet`. Node.js only. |
|
|
64
|
+
|
|
65
|
+
The package is ESM with `"sideEffects": false`, so gateway-only or read-only apps can tree-shake unused paths.
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { web3 } from "@anchor-lang/core";
|
|
71
|
+
import {
|
|
72
|
+
MolphaSDK,
|
|
73
|
+
PlanType,
|
|
74
|
+
deriveApiConfigHash,
|
|
75
|
+
deriveFeedIdString,
|
|
76
|
+
} from "@molpha/sdk";
|
|
77
|
+
import { walletFromKeypairFile } from "@molpha/sdk/utils";
|
|
78
|
+
|
|
79
|
+
const wallet = walletFromKeypairFile("~/.config/solana/id.json");
|
|
80
|
+
const sdk = new MolphaSDK({
|
|
81
|
+
connection: new web3.Connection("https://api.devnet.solana.com", "confirmed"),
|
|
82
|
+
wallet,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Subscribe if needed (USDC on Solana)
|
|
86
|
+
const plan = await sdk.solana.getPlan(PlanType.Basic);
|
|
87
|
+
await sdk.solana.subscribe(PlanType.Basic, {
|
|
88
|
+
maxPriceUsdc: plan.subscriptionPrice,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const apiConfig = {
|
|
92
|
+
url: "https://api.example.com/price",
|
|
93
|
+
responseParser: "$.price",
|
|
94
|
+
};
|
|
95
|
+
const signaturesRequired = 3;
|
|
96
|
+
const feedId = deriveFeedIdString(
|
|
97
|
+
wallet.publicKey.toBytes(),
|
|
98
|
+
deriveApiConfigHash(apiConfig),
|
|
99
|
+
signaturesRequired,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const { result, signature } = await sdk.requestAndSubmit(feedId, {
|
|
103
|
+
apiConfig,
|
|
104
|
+
signaturesRequired,
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
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.
|
|
109
|
+
|
|
110
|
+
## Configuration
|
|
111
|
+
|
|
112
|
+
### Required
|
|
113
|
+
|
|
114
|
+
| Option | Description |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `connection` | Anchor-compatible Solana RPC connection. |
|
|
117
|
+
| `wallet` | [`MolphaWallet`](#wallet). Used for Solana transactions and gateway authentication when available. |
|
|
118
|
+
|
|
119
|
+
### Optional
|
|
120
|
+
|
|
121
|
+
| Option | Default |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `endpoints` | `DEFAULT_GATEWAY_ENDPOINT` — string or array for failover |
|
|
124
|
+
| `programId` | `MOLPHA_PROGRAM_ADDRESS` from the vendored IDL |
|
|
125
|
+
| `idl` | `MOLPHA_IDL` from `idl/molpha.json` |
|
|
126
|
+
| `commitment` | `"confirmed"` |
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import {
|
|
130
|
+
DEFAULT_GATEWAY_ENDPOINT,
|
|
131
|
+
MOLPHA_IDL,
|
|
132
|
+
MOLPHA_PROGRAM_ADDRESS,
|
|
133
|
+
} from "@molpha/sdk";
|
|
134
|
+
|
|
135
|
+
const sdk = new MolphaSDK({
|
|
136
|
+
connection,
|
|
137
|
+
wallet,
|
|
138
|
+
endpoints: [DEFAULT_GATEWAY_ENDPOINT, "https://backup.example.com"],
|
|
139
|
+
// programId: "YourProgramAddress...",
|
|
140
|
+
// idl: MOLPHA_IDL,
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Wallet
|
|
145
|
+
|
|
146
|
+
`wallet` is a single `MolphaWallet` used across both protocol surfaces:
|
|
147
|
+
|
|
148
|
+
| Layer | What it signs |
|
|
149
|
+
|---|---|
|
|
150
|
+
| Solana client | Transactions such as `subscribe`, `extendSubscription`, `submitDataUpdate` |
|
|
151
|
+
| Gateway client | `authMessage(feedId, timestamp)` for authenticated gateway requests |
|
|
152
|
+
|
|
153
|
+
Gateway auth is resolved automatically when you use `MolphaSDK`:
|
|
154
|
+
|
|
155
|
+
1. Use `wallet.signAuthMessage` if provided.
|
|
156
|
+
2. Else derive signing from Anchor `Wallet.payer` when the secret key is available, such as with `walletFromKeypairFile`.
|
|
157
|
+
3. Else omit auth and use an all-zero `authSig`.
|
|
158
|
+
|
|
159
|
+
`MolphaSDK` passes the resolved signer to `sdk.gateway` as its default, so
|
|
160
|
+
`sdk.gateway.requestSignedData({ feedId, apiConfig, signaturesRequired })` authenticates without an
|
|
161
|
+
explicit `signer`. Standalone `new MolphaGateway(...)` omits auth unless you pass
|
|
162
|
+
a `defaultSigner` (third constructor arg) or per-call `signer`.
|
|
163
|
+
|
|
164
|
+
The all-zero `authSig` path is for development only. Production jobs should authenticate gateway requests.
|
|
165
|
+
|
|
166
|
+
### Node.js utility
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { walletFromKeypairFile } from "@molpha/sdk/utils";
|
|
170
|
+
|
|
171
|
+
const wallet = walletFromKeypairFile("~/.config/solana/id.json");
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Browser wallet adapter
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import type { MolphaWallet } from "@molpha/sdk";
|
|
178
|
+
|
|
179
|
+
const wallet: MolphaWallet = {
|
|
180
|
+
publicKey: adapter.publicKey,
|
|
181
|
+
signTransaction: (tx) => adapter.signTransaction(tx),
|
|
182
|
+
signAllTransactions: (txs) => adapter.signAllTransactions(txs),
|
|
183
|
+
signAuthMessage: async (msg) => new Uint8Array(await adapter.signMessage(msg)),
|
|
184
|
+
};
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
You can also override gateway auth per call with `gateway.requestSignedData({ ..., signer })` or with the same field in `requestAndSubmit`.
|
|
188
|
+
|
|
189
|
+
## Core flow
|
|
190
|
+
|
|
191
|
+
Use `MolphaSDK` for the end-to-end path, or use `MolphaSolanaClient` / `MolphaGateway` separately when you only need one side.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { web3 } from "@anchor-lang/core";
|
|
195
|
+
import {
|
|
196
|
+
MolphaSDK,
|
|
197
|
+
PlanType,
|
|
198
|
+
deriveApiConfigHash,
|
|
199
|
+
deriveFeedIdString,
|
|
200
|
+
} from "@molpha/sdk";
|
|
201
|
+
import { walletFromKeypairFile } from "@molpha/sdk/utils";
|
|
202
|
+
|
|
203
|
+
const sdk = new MolphaSDK({
|
|
204
|
+
connection: new web3.Connection("https://api.devnet.solana.com", "confirmed"),
|
|
205
|
+
wallet: walletFromKeypairFile("~/.config/solpha/id.json"),
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 1. Subscribe
|
|
210
|
+
|
|
211
|
+
Subscriptions are paid in USDC on Solana.
|
|
212
|
+
|
|
213
|
+
For local/dev testing on Solana Devnet, you can request test USDC from Circle's faucet: [https://faucet.circle.com/](https://faucet.circle.com/) (select `USDC` on `Solana Devnet`).
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
const plan = await sdk.solana.getPlan(PlanType.Basic);
|
|
217
|
+
|
|
218
|
+
// Show plan.subscriptionPrice to the user before charging.
|
|
219
|
+
const { pricePaid } = await sdk.solana.subscribe(PlanType.Basic, {
|
|
220
|
+
maxPriceUsdc: plan.subscriptionPrice,
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`maxPriceUsdc` is a safety bound. The transaction aborts if the live plan price is higher than the amount the user approved.
|
|
225
|
+
|
|
226
|
+
### 2. Derive a feed id
|
|
227
|
+
|
|
228
|
+
There is no separate `createJob` instruction. Feed identity is deterministic:
|
|
229
|
+
|
|
230
|
+
```text
|
|
231
|
+
feedId = keccak256("MOLPHA_JOB_V1" || owner || apiConfigHash || [signaturesRequired])
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
const apiConfig = {
|
|
236
|
+
url: "https://api.example.com/price",
|
|
237
|
+
responseParser: "$.price",
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const signaturesRequired = 3;
|
|
241
|
+
const feedId = deriveFeedIdString(
|
|
242
|
+
wallet.publicKey.toBytes(),
|
|
243
|
+
deriveApiConfigHash(apiConfig),
|
|
244
|
+
signaturesRequired,
|
|
245
|
+
);
|
|
246
|
+
```
|
|
247
|
+
|
|
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.
|
|
249
|
+
|
|
250
|
+
### 3. Request signed data from the gateway
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
const result = await sdk.gateway.requestSignedData({
|
|
254
|
+
feedId,
|
|
255
|
+
apiConfig,
|
|
256
|
+
signaturesRequired,
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
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
|
+
|
|
262
|
+
The returned `DataUpdateResult` includes the signed value, canonical timestamp, registry version, required quorum, signer bitmap, and aggregate signature.
|
|
263
|
+
|
|
264
|
+
### 4. Submit on Solana
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
const { signature } = await sdk.solana.submitDataUpdate(result);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Then read the finalized feed:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
const feed = await sdk.solana.readFeed(feedId);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### One-call request + submit
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
const { result, signature } = await sdk.requestAndSubmit(feedId, {
|
|
280
|
+
apiConfig,
|
|
281
|
+
signaturesRequired,
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
This is equivalent to:
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
const result = await sdk.gateway.requestSignedData({ feedId, apiConfig, signaturesRequired });
|
|
289
|
+
const { signature } = await sdk.solana.submitDataUpdate(result);
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Fast requests with a cached context
|
|
293
|
+
|
|
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.
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
// Fetch registryVersion + redundancyBuffer + nodes once.
|
|
301
|
+
const context = await sdk.gateway.prepareContext(feedId);
|
|
302
|
+
|
|
303
|
+
// Reuse it across rounds — no prelude fetches.
|
|
304
|
+
const result = await sdk.gateway.requestSignedData({ feedId, apiConfig, signaturesRequired, context });
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`context` is a `Partial<RoundContext>`, so you can cache only what you have
|
|
308
|
+
and let `requestSignedData` fetch the rest:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
const result = await sdk.gateway.requestSignedData({
|
|
312
|
+
feedId,
|
|
313
|
+
apiConfig,
|
|
314
|
+
signaturesRequired,
|
|
315
|
+
context: { nodes }, // registryVersion + redundancyBuffer still fetched fresh
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
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`.
|
|
323
|
+
|
|
324
|
+
## Private APIs and encrypted secrets
|
|
325
|
+
|
|
326
|
+
Jobs can use private APIs without sending plaintext secrets to the gateway.
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
const result = await sdk.gateway.requestSignedData({
|
|
330
|
+
feedId,
|
|
331
|
+
apiConfig: {
|
|
332
|
+
url: "https://api.example.com/private-price?key={{secret.apiKey}}",
|
|
333
|
+
responseParser: "$.price",
|
|
334
|
+
},
|
|
335
|
+
signaturesRequired,
|
|
336
|
+
encrypt: {
|
|
337
|
+
secrets: {
|
|
338
|
+
apiKey: process.env.API_KEY!,
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`MolphaSDK` wires `verifyNodeKeys` to `solana.verifyNodeKeysForPrivateApi`, which authenticates gateway node encryption keys against on-chain Node accounts before secrets are encrypted.
|
|
345
|
+
|
|
346
|
+
Secrets are encrypted into per-node envelopes. The gateway coordinates the round but should not receive plaintext API credentials.
|
|
347
|
+
|
|
348
|
+
Private API access is still an active security-sensitive surface. Do not treat encrypted secret delivery as production-ready until gateway/node-side test vectors and validation are complete.
|
|
349
|
+
|
|
350
|
+
## EVM verification
|
|
351
|
+
|
|
352
|
+
After a gateway round, the same signed result can be verified on EVM chains.
|
|
353
|
+
|
|
354
|
+
The SDK ships deployed testnet verifier addresses and framework-agnostic tuple builders. It does not depend on ethers or viem at runtime.
|
|
355
|
+
|
|
356
|
+
### Deployed verifier address
|
|
357
|
+
|
|
358
|
+
The verifier is deployed with CREATE2 so the contract address is the same on every
|
|
359
|
+
supported EVM chain.
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
import { MOLPHA_VERIFIER_ADDRESS } from "@molpha/sdk";
|
|
363
|
+
|
|
364
|
+
const address = MOLPHA_VERIFIER_ADDRESS;
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Supported network ids (selection helpers only): `evm-sepolia`, `arbitrum-sepolia`, `avalanche-fuji`, `bsc-testnet`.
|
|
368
|
+
|
|
369
|
+
### Build verifier arguments
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
import { buildEvmVerifierArgs } from "@molpha/sdk";
|
|
373
|
+
|
|
374
|
+
const result = await sdk.gateway.requestSignedData({ feedId, apiConfig, signaturesRequired });
|
|
375
|
+
|
|
376
|
+
const { dataUpdate, signature } = buildEvmVerifierArgs(result);
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
The generated tuples match the Molpha EVM verifier ABI:
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
// dataUpdate:
|
|
383
|
+
// [bytes32 feedId,
|
|
384
|
+
// uint32 registryVersion,
|
|
385
|
+
// uint32 signaturesRequired,
|
|
386
|
+
// bytes32 valuePacked,
|
|
387
|
+
// uint64 timestamp]
|
|
388
|
+
|
|
389
|
+
// signature:
|
|
390
|
+
// [bytes32 s,
|
|
391
|
+
// address commitment,
|
|
392
|
+
// uint256 signersBitmap]
|
|
393
|
+
```
|
|
394
|
+
|
|
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.
|
|
396
|
+
|
|
397
|
+
### ethers
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
import { Contract } from "ethers";
|
|
401
|
+
import {
|
|
402
|
+
buildEvmVerifierArgs,
|
|
403
|
+
MOLPHA_VERIFIER_ADDRESS,
|
|
404
|
+
} from "@molpha/sdk";
|
|
405
|
+
|
|
406
|
+
const verifier = new Contract(
|
|
407
|
+
MOLPHA_VERIFIER_ADDRESS,
|
|
408
|
+
abi,
|
|
409
|
+
signer,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
const { dataUpdate, signature } = buildEvmVerifierArgs(result);
|
|
413
|
+
|
|
414
|
+
await verifier.verify(dataUpdate, signature);
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### viem
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
import { createPublicClient, http } from "viem";
|
|
421
|
+
import {
|
|
422
|
+
buildEvmVerifierArgs,
|
|
423
|
+
MOLPHA_VERIFIER_ABI,
|
|
424
|
+
MOLPHA_VERIFIER_ADDRESS,
|
|
425
|
+
} from "@molpha/sdk";
|
|
426
|
+
|
|
427
|
+
const client = createPublicClient({
|
|
428
|
+
chain,
|
|
429
|
+
transport: http(),
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const { dataUpdate, signature } = buildEvmVerifierArgs(result);
|
|
433
|
+
|
|
434
|
+
await client.readContract({
|
|
435
|
+
address: MOLPHA_VERIFIER_ADDRESS,
|
|
436
|
+
abi: MOLPHA_VERIFIER_ABI,
|
|
437
|
+
functionName: "verify",
|
|
438
|
+
args: [
|
|
439
|
+
{
|
|
440
|
+
jobId: dataUpdate[0],
|
|
441
|
+
registryVersion: dataUpdate[1],
|
|
442
|
+
signaturesRequired: dataUpdate[2],
|
|
443
|
+
value: dataUpdate[3],
|
|
444
|
+
canonicalTimestamp: BigInt(dataUpdate[4]),
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
signature: signature[0],
|
|
448
|
+
commitment: signature[1],
|
|
449
|
+
signersBitmap: signature[2],
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Lower-level helpers are also exported for manual integrations:
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
import {
|
|
459
|
+
toFixedHex,
|
|
460
|
+
signersBitmapToUint256,
|
|
461
|
+
signersBitmapToDecimal,
|
|
462
|
+
} from "@molpha/sdk";
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Starknet verification
|
|
466
|
+
|
|
467
|
+
After a gateway round, the same signed result can be verified on Starknet.
|
|
468
|
+
|
|
469
|
+
The SDK ships deployed testnet verifier addresses and framework-agnostic struct
|
|
470
|
+
builders. It does not depend on `starknet.js` at runtime.
|
|
471
|
+
|
|
472
|
+
### Deployed verifier addresses
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
import {
|
|
476
|
+
MOLPHA_VERIFIER_STARKNET_ADDRESSES,
|
|
477
|
+
MOLPHA_VERIFIER_STARKNET_SEPOLIA,
|
|
478
|
+
getMolphaStarknetVerifierAddress,
|
|
479
|
+
} from "@molpha/sdk";
|
|
480
|
+
|
|
481
|
+
const address = getMolphaStarknetVerifierAddress("starknet-sepolia");
|
|
482
|
+
|
|
483
|
+
// or:
|
|
484
|
+
const sepolia = MOLPHA_VERIFIER_STARKNET_ADDRESSES["starknet-sepolia"];
|
|
485
|
+
const sepoliaDirect = MOLPHA_VERIFIER_STARKNET_SEPOLIA;
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
| Network | Constant |
|
|
489
|
+
|---|---|
|
|
490
|
+
| Starknet Sepolia | `MOLPHA_VERIFIER_STARKNET_SEPOLIA` |
|
|
491
|
+
|
|
492
|
+
### Build verifier arguments
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
import { buildStarknetVerifierArgs } from "@molpha/sdk";
|
|
496
|
+
|
|
497
|
+
const result = await sdk.gateway.requestSignedData({ feedId, apiConfig, signaturesRequired });
|
|
498
|
+
|
|
499
|
+
const { dataUpdate, signature } = buildStarknetVerifierArgs(result);
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
The generated objects match the Molpha Starknet verifier interface:
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
// dataUpdate:
|
|
506
|
+
// {
|
|
507
|
+
// feed_id: u256,
|
|
508
|
+
// registry_version: u32,
|
|
509
|
+
// signatures_required: u32,
|
|
510
|
+
// value: u256,
|
|
511
|
+
// canonical_timestamp: u64,
|
|
512
|
+
// }
|
|
513
|
+
|
|
514
|
+
// signature:
|
|
515
|
+
// {
|
|
516
|
+
// signature: u256,
|
|
517
|
+
// commitment: felt252, // EVM-style 20-byte address as felt
|
|
518
|
+
// signers_bitmap: u256,
|
|
519
|
+
// }
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
Lower-level helpers are also exported:
|
|
523
|
+
|
|
524
|
+
```ts
|
|
525
|
+
import {
|
|
526
|
+
commitmentAddressToStarknetFelt,
|
|
527
|
+
signersBitmapToStarknetUint256,
|
|
528
|
+
} from "@molpha/sdk";
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## What verification checks
|
|
532
|
+
|
|
533
|
+
A Molpha data update is valid only if the verifier can confirm:
|
|
534
|
+
|
|
535
|
+
- the update targets the expected `feedId`;
|
|
536
|
+
- the result was signed against a specific `registryVersion`;
|
|
537
|
+
- 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;
|
|
541
|
+
- 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).
|
|
543
|
+
|
|
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.
|
|
545
|
+
|
|
546
|
+
## IDL vendoring
|
|
547
|
+
|
|
548
|
+
The Solana client needs the Anchor IDL for the Molpha program.
|
|
549
|
+
|
|
550
|
+
A vendored copy ships under `idl/` and is used by default:
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
import { MOLPHA_IDL, MOLPHA_PROGRAM_ADDRESS } from "@molpha/sdk";
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
Override `idl` and `programId` when targeting another deployment.
|
|
557
|
+
|
|
558
|
+
Keep the vendored IDL aligned with the deployed program. Mismatched IDL/program versions can produce invalid account derivations, decoding errors, or failed instruction simulation.
|
|
559
|
+
|
|
560
|
+
## Standalone clients
|
|
561
|
+
|
|
562
|
+
`MolphaSDK` is a convenience facade.
|
|
563
|
+
|
|
564
|
+
You can also use the lower-level clients directly:
|
|
565
|
+
|
|
566
|
+
```ts
|
|
567
|
+
import {
|
|
568
|
+
MolphaGateway,
|
|
569
|
+
MolphaSolanaClient,
|
|
570
|
+
gatewaySignerFromWallet,
|
|
571
|
+
} from "@molpha/sdk";
|
|
572
|
+
|
|
573
|
+
const solana = MolphaSolanaClient.create({
|
|
574
|
+
connection,
|
|
575
|
+
wallet,
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
const gateway = new MolphaGateway(
|
|
579
|
+
endpoints,
|
|
580
|
+
() => solana.getRegistrySelectionConfig(),
|
|
581
|
+
gatewaySignerFromWallet(wallet),
|
|
582
|
+
{
|
|
583
|
+
defaultSubscriptionOwner: wallet.publicKey.toBase58(),
|
|
584
|
+
verifyNodeKeys: (args) => solana.verifyNodeKeysForPrivateApi(args),
|
|
585
|
+
},
|
|
586
|
+
);
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
The facade wires the registry selection config resolver, gateway signer, subscription owner, and node-key verifier automatically.
|
|
590
|
+
|
|
591
|
+
## Status
|
|
592
|
+
|
|
593
|
+
`0.0.0` (unreleased) — first stable release `@molpha/sdk@0.1.0` is pending via changesets.
|
|
594
|
+
|
|
595
|
+
Current scope:
|
|
596
|
+
|
|
597
|
+
- 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;
|
|
601
|
+
- private API encryption helpers (pre-production);
|
|
602
|
+
- EVM and Starknet verifier argument building;
|
|
603
|
+
- deployed testnet verifier address helpers.
|
|
604
|
+
|
|
605
|
+
Known limitations:
|
|
606
|
+
|
|
607
|
+
- private API envelope encryption still needs gateway/node-side test-vector validation;
|
|
608
|
+
- verifier-node registration and admin tooling are intentionally outside this package;
|
|
609
|
+
- production deployments should use authenticated gateway requests;
|
|
610
|
+
- testnet verifier addresses may change between protocol releases.
|
|
611
|
+
|
|
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.
|
|
613
|
+
|
|
614
|
+
## Develop
|
|
615
|
+
|
|
616
|
+
```bash
|
|
617
|
+
pnpm install
|
|
618
|
+
pnpm typecheck
|
|
619
|
+
pnpm test
|
|
620
|
+
pnpm build
|
|
621
|
+
pnpm e2e-demo
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
## Releasing
|
|
625
|
+
|
|
626
|
+
Versioning and publishing are automated with Changesets.
|
|
627
|
+
|
|
628
|
+
Versions follow semver and are driven by the nature of each change, not by the branch it merges from.
|
|
629
|
+
|
|
630
|
+
### Workflow
|
|
631
|
+
|
|
632
|
+
1. Add a changeset with your change:
|
|
633
|
+
|
|
634
|
+
```bash
|
|
635
|
+
pnpm changeset
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
Choose:
|
|
639
|
+
|
|
640
|
+
- `patch` for fixes;
|
|
641
|
+
- `minor` for features;
|
|
642
|
+
- `major` for breaking changes.
|
|
643
|
+
|
|
644
|
+
While the package is pre-`1.0.0`, use `minor` for breaking changes and `patch` for features/fixes. Only select `major` when intentionally cutting `1.0.0`.
|
|
645
|
+
|
|
646
|
+
2. Stable releases from `main`
|
|
647
|
+
|
|
648
|
+
When changes land on `main`, the release workflow opens a release PR that bumps `package.json` and updates `CHANGELOG.md`.
|
|
649
|
+
|
|
650
|
+
Merging that PR publishes to npm on the `latest` tag:
|
|
651
|
+
|
|
652
|
+
```bash
|
|
653
|
+
npm install @molpha/sdk
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
3. Prereleases from `dev`
|
|
657
|
+
|
|
658
|
+
Pushes to `dev` publish a snapshot version on the `dev` dist-tag, for example:
|
|
659
|
+
|
|
660
|
+
```text
|
|
661
|
+
0.2.0-dev-<timestamp>
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
Install with:
|
|
665
|
+
|
|
666
|
+
```bash
|
|
667
|
+
npm install @molpha/sdk@dev
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
Requires at least one pending changeset.
|
|
671
|
+
|
|
672
|
+
### One-time setup
|
|
673
|
+
|
|
674
|
+
1. Add `NPM_TOKEN` under GitHub repo settings:
|
|
675
|
+
|
|
676
|
+
```text
|
|
677
|
+
Settings → Secrets and variables → Actions
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
Use a granular npm automation token with publish access to the `@molpha` scope.
|
|
681
|
+
|
|
682
|
+
2. Publish a stable release from `main` first.
|
|
683
|
+
|
|
684
|
+
npm assigns the first published version to the `latest` tag regardless of `--tag`. If a `dev` snapshot is published before any stable release, that prerelease can become `latest`.
|
|
685
|
+
|
|
686
|
+
The `dev` workflow should guard against this and fail until a stable `latest` exists.
|
|
687
|
+
|
|
688
|
+
3. Deprecate the legacy package name on npm (one-time, after `@molpha/sdk@0.1.0` is published):
|
|
689
|
+
|
|
690
|
+
```bash
|
|
691
|
+
npm deprecate "@molpha-oracle/sdk" "Package renamed to @molpha/sdk. Please migrate."
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
If `latest` ever points to a prerelease, repoint it after publishing a stable version:
|
|
695
|
+
|
|
696
|
+
```bash
|
|
697
|
+
npm dist-tag add @molpha/sdk@<stable-version> latest
|
|
698
|
+
```
|