@molpha/sdk 0.2.0-dev-20260913100330 → 0.2.0-dev-20261001125531
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 +162 -97
- package/dist/{chunk-DT6IKCWB.js → chunk-2X22JY7Q.js} +3 -15
- package/dist/chunk-2X22JY7Q.js.map +1 -0
- package/dist/index.d.ts +474 -96
- package/dist/index.js +3651 -5271
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +2 -6
- package/dist/utils.js +2 -6
- package/dist/utils.js.map +1 -1
- package/dist/{wallet-DwdBZgl1.d.ts → wallet-HfGXPpOp.d.ts} +49 -15
- package/idl/molpha.json +2542 -4657
- package/package.json +1 -1
- package/dist/chunk-DT6IKCWB.js.map +0 -1
package/README.md
CHANGED
|
@@ -45,12 +45,14 @@ Every signed attestation commits to the same message across chains:
|
|
|
45
45
|
|
|
46
46
|
```text
|
|
47
47
|
message = keccak256(
|
|
48
|
-
keccak256("MOLPHA_MESSAGE_V1") || sourceId || u32be(registryVersion) ||
|
|
49
|
-
|
|
48
|
+
keccak256("MOLPHA_MESSAGE_V1") || value || sourceId || u32be(registryVersion) ||
|
|
49
|
+
u8(signaturesRequired) || u64be(canonicalTimestamp) || signersBitmap
|
|
50
50
|
)
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
The preimage is 141 bytes (32 + 32 + 32 + 4 + 1 + 8 + 32).
|
|
54
|
+
|
|
55
|
+
`attestationMessageHash` / `attestationMessageHashFromAttestation` recompute it client-side.
|
|
54
56
|
|
|
55
57
|
## Install
|
|
56
58
|
|
|
@@ -60,15 +62,6 @@ pnpm add @molpha/sdk
|
|
|
60
62
|
|
|
61
63
|
Runtime dependencies include `@solana/kit`, `@anchor-lang/core`, and `@noble/*`. `bn.js` is an optional peer dependency (used by the Solana / Anchor path).
|
|
62
64
|
|
|
63
|
-
> **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:
|
|
64
|
-
>
|
|
65
|
-
> ```bash
|
|
66
|
-
> pnpm remove @molpha-oracle/sdk
|
|
67
|
-
> pnpm add @molpha/sdk
|
|
68
|
-
> ```
|
|
69
|
-
>
|
|
70
|
-
> Replace `@molpha-oracle/sdk` with `@molpha/sdk` in all import paths (including `@molpha/sdk/utils`).
|
|
71
|
-
|
|
72
65
|
| Import | Use |
|
|
73
66
|
|---|---|
|
|
74
67
|
| `@molpha/sdk` | Facade (`MolphaSDK`), `MolphaGateway`, `MolphaSolanaClient`, core hashing (`deriveSourceId`, `attestationMessageHash`, `hashRequestAuth`), EVM/Starknet helpers. Browser-safe; no `fs` in the main entry. |
|
|
@@ -108,7 +101,7 @@ const { result, signature, feed } = await sdk.requestAndSubmit({
|
|
|
108
101
|
});
|
|
109
102
|
|
|
110
103
|
// The round's identity, for reads and cross-chain verification.
|
|
111
|
-
const sourceId = deriveSourceIdString(apiConfig); // === result.sourceId
|
|
104
|
+
const sourceId = deriveSourceIdString(apiConfig); // === result.payload.sourceId
|
|
112
105
|
```
|
|
113
106
|
|
|
114
107
|
`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.
|
|
@@ -223,8 +216,7 @@ import { web3 } from "@anchor-lang/core";
|
|
|
223
216
|
import {
|
|
224
217
|
MolphaSDK,
|
|
225
218
|
PlanType,
|
|
226
|
-
|
|
227
|
-
deriveFeedIdString,
|
|
219
|
+
deriveSourceIdString,
|
|
228
220
|
} from "@molpha/sdk";
|
|
229
221
|
import { walletFromKeypairFile } from "@molpha/sdk/utils";
|
|
230
222
|
|
|
@@ -268,6 +260,8 @@ const apiConfig = {
|
|
|
268
260
|
const sourceId = deriveSourceIdString(apiConfig); // 64 hex chars, no 0x
|
|
269
261
|
```
|
|
270
262
|
|
|
263
|
+
The canonical JSON has the fixed key order `url`, `method`, `headers` (sorted by UTF-16 code units, `{}` if empty), `responseParser`, `valueTransform` and, for [tolerance mode](#median-tolerance-mode), a trailing `aggregation`. The key is omitted for exact mode, so existing five-field configs keep their `sourceId`.
|
|
264
|
+
|
|
271
265
|
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.
|
|
272
266
|
|
|
273
267
|
### 3. Request signed data from the gateway
|
|
@@ -282,7 +276,9 @@ const result = await sdk.gateway.requestSignedData({
|
|
|
282
276
|
|
|
283
277
|
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.
|
|
284
278
|
|
|
285
|
-
The returned `
|
|
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.
|
|
280
|
+
|
|
281
|
+
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.
|
|
286
282
|
|
|
287
283
|
### 4. Submit on Solana
|
|
288
284
|
|
|
@@ -290,12 +286,24 @@ The returned `DataUpdateResult` includes `sourceId`, the signed value, canonical
|
|
|
290
286
|
const { signature, feed } = await sdk.solana.submitAttestation(result);
|
|
291
287
|
```
|
|
292
288
|
|
|
289
|
+
`submit_attestation` takes `{ attestation: { payload, signature }, rawValue, coalitionKey }` plus one read-only `Node` account per signer (ascending signer-bit order, resolved from the registry snapshot the round was signed against). The SDK builds all of it: it fetches the signer `Node` accounts in one batched read, sums their secp256k1 keys into the affine **coalition key** (`computeCoalitionKey`), and checks the program's signer-count bounds before sending. The coalition key is unsigned instruction data that the program checks projectively against its own sum, so a wrong key only fails the transaction. Pass `{ coalitionKey }` to skip the `Node` fetch when you already hold it.
|
|
290
|
+
|
|
291
|
+
If the signed 32-byte value is the keccak digest of a longer preimage, pass `{ rawValue }` (up to 256 bytes). The SDK verifies the digest before sending:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
const { signature, feed } = await sdk.solana.submitAttestation(result, { rawValue });
|
|
295
|
+
```
|
|
296
|
+
|
|
293
297
|
Then read the feed this wallet wrote for that source and quorum:
|
|
294
298
|
|
|
295
299
|
```ts
|
|
296
|
-
const feedState = await sdk.solana.readFeed(result.sourceId, signaturesRequired);
|
|
300
|
+
const feedState = await sdk.solana.readFeed(result.payload.sourceId, signaturesRequired);
|
|
297
301
|
```
|
|
298
302
|
|
|
303
|
+
`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
|
+
|
|
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`.
|
|
306
|
+
|
|
299
307
|
### One-call request + submit
|
|
300
308
|
|
|
301
309
|
```ts
|
|
@@ -345,6 +353,54 @@ refresh the context when the on-chain registry changes. The SDK derives the
|
|
|
345
353
|
selection from the on-chain `nodeCount` and refuses a cached node list whose length
|
|
346
354
|
disagrees with it. The same `context` field is accepted by `requestAndSubmit`.
|
|
347
355
|
|
|
356
|
+
## Median tolerance mode
|
|
357
|
+
|
|
358
|
+
By default every signing node must observe the identical value. For noisy numeric sources
|
|
359
|
+
(prices), add an `aggregation` object and the selected nodes instead exchange signed
|
|
360
|
+
observations, drop stale ones and outliers, and sign the **lower median** of
|
|
361
|
+
`signaturesRequired` surviving observations:
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
const apiConfig = {
|
|
365
|
+
url: "https://api.example.com/price",
|
|
366
|
+
responseParser: "$.price",
|
|
367
|
+
aggregation: {
|
|
368
|
+
mode: "tolerance",
|
|
369
|
+
rule: "median",
|
|
370
|
+
maxDeviationBps: 50, // u32: allowed distance from the lower median, in bps
|
|
371
|
+
maxAgeMs: 2000, // > 0: max observation age in milliseconds
|
|
372
|
+
numeric: { type: "int256", decimals: 8 }, // value = round-half-even(price * 10^8)
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const { result } = await sdk.requestAndSubmit({ apiConfig, signaturesRequired: 5 });
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Rules (the node is the reference; the SDK rejects violations before any request is made, with `AggregationConfigError`):
|
|
380
|
+
|
|
381
|
+
- `aggregation` is part of the `sourceId` and is **omitted** for exact mode. `mode: "exact"` is rejected because it would change the identity.
|
|
382
|
+
- Only `rule: "median"` and `numeric.type: "int256"` are supported. `decimals` is `0..255`, `maxDeviationBps` a u32, `maxAgeMs` a positive integer.
|
|
383
|
+
- Tolerance needs `signaturesRequired >= 3`.
|
|
384
|
+
- The nested object is rebuilt in canonical order (`mode`, `rule`, `maxDeviationBps`, `maxAgeMs`, `numeric{type, decimals}`); extra or reordered keys on your input never reach the hash. For the config above, `sourceId` hashes exactly `{"url":...,"method":"GET","headers":{},"responseParser":"$.price","valueTransform":"","aggregation":{"mode":"tolerance","rule":"median","maxDeviationBps":50,"maxAgeMs":2000,"numeric":{"type":"int256","decimals":8}}}` (pinned against the node's Go test `TestAggregationSourceIdentity`).
|
|
385
|
+
- The signed value is a signed `int256` (two's-complement `bytes32`). For tolerance results `Attestation.value` is rendered from the signed `payload.value` at the source's `decimals`, and `signature.signersBitmap` / signature fields must come from the response, since the nodes choose the final signing set after the observation exchange.
|
|
386
|
+
- The gateway must forward `aggregation` to the nodes. If it derives a different identity, the response's `sourceId` / `configHash` will not match and the SDK throws; a gateway that echoes `aggregation` in its response must echo the requested policy.
|
|
387
|
+
|
|
388
|
+
Helpers for the signed value (mirror the node's `new/tolerance` package):
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
import {
|
|
392
|
+
encodeInt256Decimal, // ("42150.12345678", 8) -> 32-byte Uint8Array, round half to even, bounds-checked
|
|
393
|
+
decodeInt256, // bytes32 (Uint8Array | hex) -> bigint
|
|
394
|
+
formatInt256Decimal, // bytes32 or bigint, decimals -> "42150.12345678" (trailing zeros trimmed)
|
|
395
|
+
encodeInt256, // bigint -> bytes32
|
|
396
|
+
INT256_MIN,
|
|
397
|
+
INT256_MAX,
|
|
398
|
+
} from "@molpha/sdk";
|
|
399
|
+
|
|
400
|
+
const feedState = await sdk.solana.readFeed(result.payload.sourceId, 5);
|
|
401
|
+
const price = formatInt256Decimal(Uint8Array.from(feedState!.value), 8);
|
|
402
|
+
```
|
|
403
|
+
|
|
348
404
|
## Private APIs and encrypted secrets
|
|
349
405
|
|
|
350
406
|
Sources can use private APIs without sending plaintext secrets to the gateway.
|
|
@@ -366,7 +422,7 @@ const result = await sdk.gateway.requestSignedData({
|
|
|
366
422
|
|
|
367
423
|
`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.
|
|
368
424
|
|
|
369
|
-
Secrets are encrypted into per-node envelopes. The gateway coordinates the round but should not receive plaintext API credentials.
|
|
425
|
+
Secrets are encrypted into per-node envelopes. The gateway coordinates the round but should not receive plaintext API credentials. The encrypted plaintext is the canonical config (including `aggregation`) with secrets substituted.
|
|
370
426
|
|
|
371
427
|
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.
|
|
372
428
|
|
|
@@ -455,90 +511,121 @@ Supported network ids (selection helpers only): `evm-sepolia`, `arbitrum-sepolia
|
|
|
455
511
|
|
|
456
512
|
### Build verifier arguments
|
|
457
513
|
|
|
514
|
+
The verifier's entrypoint is
|
|
515
|
+
`verify(Attestation attestation, uint64 maxAge) returns (bool success, uint8 code)`.
|
|
516
|
+
|
|
458
517
|
```ts
|
|
459
518
|
import { buildEvmVerifierArgs } from "@molpha/sdk";
|
|
460
519
|
|
|
461
520
|
const result = await sdk.gateway.requestSignedData({ apiConfig, signaturesRequired });
|
|
462
521
|
|
|
463
|
-
const {
|
|
522
|
+
const { attestation, maxAge } = buildEvmVerifierArgs(result, { maxAge: 300 });
|
|
464
523
|
```
|
|
465
524
|
|
|
466
|
-
|
|
525
|
+
`maxAge` is required. It is the freshness window in seconds: the verifier reports an older
|
|
526
|
+
attestation as `STALE`, and one dated after `block.timestamp` as `MALFORMED`. `0` disables the
|
|
527
|
+
check entirely — pass it only when your contract enforces freshness or ordering itself, because
|
|
528
|
+
a stateless verifier otherwise accepts a correctly signed attestation forever.
|
|
467
529
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
// uint32 signaturesRequired,
|
|
473
|
-
// bytes32 valuePacked,
|
|
474
|
-
// uint64 timestamp]
|
|
530
|
+
The generated object matches the Solidity `IVerifier.Attestation` struct, using viem's
|
|
531
|
+
primitive types so it passes straight into `readContract` or an ethers `Contract`. Member
|
|
532
|
+
order is ABI order (and the signed message's order). The gateway `Attestation` uses the same
|
|
533
|
+
nested shape with lowercase hex strings instead of viem primitives:
|
|
475
534
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
535
|
+
```ts
|
|
536
|
+
attestation:
|
|
537
|
+
{
|
|
538
|
+
payload: {
|
|
539
|
+
value: `0x${string}`, // bytes32 (Attestation.payload.value)
|
|
540
|
+
sourceId: `0x${string}`, // bytes32
|
|
541
|
+
registryVersion: number, // uint32
|
|
542
|
+
signaturesRequired: number, // uint8
|
|
543
|
+
canonicalTimestamp: bigint, // uint64
|
|
544
|
+
},
|
|
545
|
+
signature: {
|
|
546
|
+
signature: `0x${string}`, // bytes32 (Attestation.signature.s)
|
|
547
|
+
commitment: `0x${string}`, // address (Attestation.signature.commitmentAddr)
|
|
548
|
+
signersBitmap: bigint, // uint256
|
|
549
|
+
},
|
|
550
|
+
}
|
|
551
|
+
maxAge: bigint // uint64
|
|
480
552
|
```
|
|
481
553
|
|
|
482
|
-
|
|
554
|
+
The builder range-checks every integer against its Solidity type. Out-of-range calldata never
|
|
555
|
+
reaches `verify`: the ABI decoder reverts on it instead of returning a result code.
|
|
483
556
|
|
|
484
|
-
###
|
|
557
|
+
### viem
|
|
485
558
|
|
|
486
559
|
```ts
|
|
487
|
-
import {
|
|
560
|
+
import { createPublicClient, http } from "viem";
|
|
488
561
|
import {
|
|
489
562
|
buildEvmVerifierArgs,
|
|
563
|
+
MOLPHA_VERIFIER_ABI,
|
|
490
564
|
MOLPHA_VERIFIER_ADDRESS,
|
|
565
|
+
parseEvmVerifyResult,
|
|
491
566
|
} from "@molpha/sdk";
|
|
492
567
|
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
abi,
|
|
496
|
-
signer,
|
|
497
|
-
);
|
|
568
|
+
const client = createPublicClient({ chain, transport: http() });
|
|
569
|
+
const { attestation, maxAge } = buildEvmVerifierArgs(result, { maxAge: 300 });
|
|
498
570
|
|
|
499
|
-
const
|
|
571
|
+
const returned = await client.readContract({
|
|
572
|
+
address: MOLPHA_VERIFIER_ADDRESS,
|
|
573
|
+
abi: MOLPHA_VERIFIER_ABI,
|
|
574
|
+
functionName: "verify",
|
|
575
|
+
args: [attestation, maxAge],
|
|
576
|
+
});
|
|
500
577
|
|
|
501
|
-
|
|
578
|
+
const { success, code, reason } = parseEvmVerifyResult(returned);
|
|
579
|
+
// { success: true, code: 0, reason: "OK" }
|
|
580
|
+
// { success: false, code: 10, reason: "STALE" }
|
|
502
581
|
```
|
|
503
582
|
|
|
504
|
-
###
|
|
583
|
+
### ethers
|
|
505
584
|
|
|
506
585
|
```ts
|
|
507
|
-
import {
|
|
586
|
+
import { Contract } from "ethers";
|
|
508
587
|
import {
|
|
509
588
|
buildEvmVerifierArgs,
|
|
510
589
|
MOLPHA_VERIFIER_ABI,
|
|
511
590
|
MOLPHA_VERIFIER_ADDRESS,
|
|
591
|
+
parseEvmVerifyResult,
|
|
512
592
|
} from "@molpha/sdk";
|
|
513
593
|
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
transport: http(),
|
|
517
|
-
});
|
|
594
|
+
const verifier = new Contract(MOLPHA_VERIFIER_ADDRESS, MOLPHA_VERIFIER_ABI, provider);
|
|
595
|
+
const { attestation, maxAge } = buildEvmVerifierArgs(result, { maxAge: 300 });
|
|
518
596
|
|
|
519
|
-
const {
|
|
597
|
+
const { success, code, reason } = parseEvmVerifyResult(await verifier.verify(attestation, maxAge));
|
|
598
|
+
```
|
|
520
599
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
{
|
|
534
|
-
signature: signature[0],
|
|
535
|
-
commitment: signature[1],
|
|
536
|
-
signersBitmap: signature[2],
|
|
537
|
-
},
|
|
538
|
-
],
|
|
539
|
-
});
|
|
600
|
+
### Raw `eth_call`
|
|
601
|
+
|
|
602
|
+
`encodeEvmVerifyCalldata` produces the full calldata (selector `0x67e2907b` plus nine static
|
|
603
|
+
words), and `parseEvmVerifyResult` also accepts the raw 64-byte return data:
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
import { buildEvmVerifierArgs, encodeEvmVerifyCalldata, parseEvmVerifyResult } from "@molpha/sdk";
|
|
607
|
+
|
|
608
|
+
const args = buildEvmVerifierArgs(result, { maxAge: 300 });
|
|
609
|
+
const returnData = await provider.call({ to: verifierAddress, data: encodeEvmVerifyCalldata(args) });
|
|
610
|
+
|
|
611
|
+
const { success, code, reason } = parseEvmVerifyResult(returnData);
|
|
540
612
|
```
|
|
541
613
|
|
|
614
|
+
`verify` never reverts; a rejection is a result code from the shared `VERIFY_CODES` table (see
|
|
615
|
+
[the Starknet section](#call-verify-and-read-the-result) for the full list). The EVM verifier
|
|
616
|
+
returns `MALFORMED` for a zero `signaturesRequired`, a zero or out-of-range signature scalar, a
|
|
617
|
+
zero commitment, or fewer set bitmap bits than `signaturesRequired`. `parseEvmVerifyResult`
|
|
618
|
+
throws when `success` and `code` disagree, which means the call did not reach a Molpha verifier
|
|
619
|
+
of this interface.
|
|
620
|
+
|
|
621
|
+
### Registry reads
|
|
622
|
+
|
|
623
|
+
`MOLPHA_VERIFIER_ABI` also covers every read-only registry view — `getRegistryVersion`,
|
|
624
|
+
`getTotalNodes`, `redundancyBuffer`, `getRegistryRoot` / `getRegistryPointer` (current or per
|
|
625
|
+
version), `activatesAt`, `retiredAt`, `isLatestVersion`, `nodeStatus`, `isNode` — and the
|
|
626
|
+
`InvalidRegistryVersion` error the per-version views revert with. Owner-only mutators are not
|
|
627
|
+
included.
|
|
628
|
+
|
|
542
629
|
Lower-level helpers are also exported for manual integrations:
|
|
543
630
|
|
|
544
631
|
```ts
|
|
@@ -594,7 +681,8 @@ enforces freshness or ordering itself, because a stateless verifier otherwise ac
|
|
|
594
681
|
correctly signed attestation forever.
|
|
595
682
|
|
|
596
683
|
The generated object matches the Cairo `Attestation` struct. Member order is Cairo `Serde`
|
|
597
|
-
order (and the signed message's order)
|
|
684
|
+
order (and the signed message's order). The gateway `Attestation` uses the same nested shape
|
|
685
|
+
with lowercase hex strings instead of Cairo felts:
|
|
598
686
|
|
|
599
687
|
```ts
|
|
600
688
|
attestation:
|
|
@@ -685,7 +773,9 @@ A Molpha attestation is valid only if the verifier can confirm:
|
|
|
685
773
|
- the signer bitmap is a subset of the deterministic selection for `(sourceId, registryVersion, canonicalTimestamp)`;
|
|
686
774
|
- the aggregate Schnorr signature over `attestationMessageHash(...)` is valid;
|
|
687
775
|
- the timestamp is within the accepted freshness bounds;
|
|
688
|
-
- on Solana, the signer `Node` accounts passed as remaining accounts are exactly `registry.nodes[bit]` for every set bit.
|
|
776
|
+
- 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.
|
|
777
|
+
|
|
778
|
+
A valid signature does not replace consumer policy: freshness, source, quorum and replay checks remain the consumer's responsibility.
|
|
689
779
|
|
|
690
780
|
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.
|
|
691
781
|
|
|
@@ -701,6 +791,8 @@ import { MOLPHA_IDL, MOLPHA_PROGRAM_ADDRESS } from "@molpha/sdk";
|
|
|
701
791
|
|
|
702
792
|
Override `idl` and `programId` when targeting another deployment.
|
|
703
793
|
|
|
794
|
+
The vendored IDL is `anchor idl build -p molpha` output for `molpha-solana-program` `3d01170` ("Epoch settlements (#47)").
|
|
795
|
+
|
|
704
796
|
Keep the vendored IDL aligned with the deployed program. Mismatched IDL/program versions can produce invalid account derivations, decoding errors, or failed instruction simulation.
|
|
705
797
|
|
|
706
798
|
## Standalone clients
|
|
@@ -746,7 +838,7 @@ Current scope:
|
|
|
746
838
|
- Solana attestation submission and feed/registry reads;
|
|
747
839
|
- private API encryption helpers (pre-production);
|
|
748
840
|
- caller-funded x402 payments for paywalled API sources (Base USDC, pre-production);
|
|
749
|
-
- EVM and Starknet verifier argument building,
|
|
841
|
+
- EVM and Starknet verifier argument building, `verify` calldata encoding and result decoding;
|
|
750
842
|
- deployed testnet verifier address helpers.
|
|
751
843
|
|
|
752
844
|
Known limitations:
|
|
@@ -760,27 +852,6 @@ Known limitations:
|
|
|
760
852
|
|
|
761
853
|
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).
|
|
762
854
|
|
|
763
|
-
## Migrating from 0.1.x
|
|
764
|
-
|
|
765
|
-
| Before | After |
|
|
766
|
-
|---|---|
|
|
767
|
-
| `deriveFeedId(owner, apiConfigHash, sigReq)` / `deriveFeedIdString` | removed — use `deriveSourceId(apiConfig)` / `deriveSourceIdString` |
|
|
768
|
-
| `deriveApiConfigHash(apiConfig)` | `deriveSourceId(apiConfig)` (old name kept as a deprecated alias, same bytes) |
|
|
769
|
-
| `requestSignedData({ feedId, ... })` | `requestSignedData({ apiConfig, signaturesRequired, ... })` — `sourceId` is derived from `apiConfig` |
|
|
770
|
-
| `prepareContext(feedId)` | `prepareContext()` |
|
|
771
|
-
| `requestAndSubmit(feedId, opts)` | `requestAndSubmit(opts)` |
|
|
772
|
-
| `authMessage(feedId, timestamp)` (sha256) | `hashRequestAuth({ programId, gateway, sourceId, signaturesRequired, timestamp })` (keccak) |
|
|
773
|
-
| `endpoints: string[]` | `endpoints: (string \| { url, gatewayAuthority })[]` |
|
|
774
|
-
| `submitDataUpdate(result)` | `submitAttestation(result)` (deprecated alias kept); returns `{ signature, feed }` |
|
|
775
|
-
| `readFeed(feedId)` | `readFeed(sourceId, signaturesRequired, submitter?)` |
|
|
776
|
-
| `result.feedId` / `NodeKeyVerifierArgs.feedId` | `.sourceId` |
|
|
777
|
-
| EVM tuple `feedId`, ABI `jobId` | `sourceId` |
|
|
778
|
-
| Starknet `feed_id` | `source_id` |
|
|
779
|
-
| `buildStarknetVerifierArgs(result)` → `{ dataUpdate, signature }` | `buildStarknetVerifierArgs(result, { maxAge })` → `{ attestation, maxAge }` for `verify(attestation, max_age)` |
|
|
780
|
-
| `StarknetDataUpdate` (`signatures_required: u32`) | `StarknetAttestationPayload` (`signatures_required: u8`, `value` first), nested in `StarknetAttestation` |
|
|
781
|
-
| Starknet `verify` returns `bool` | returns `(bool, u8)` — decode with `parseStarknetVerifyResult` |
|
|
782
|
-
| `resolveRegistryIndexForVersion`, `VIRTUAL_INDEX`, `nodePda(index)` | removed — signer accounts are `registry.nodes[bit]`; `nodePda(owner)` |
|
|
783
|
-
|
|
784
855
|
## Develop
|
|
785
856
|
|
|
786
857
|
```bash
|
|
@@ -861,12 +932,6 @@ Versions follow semver and are driven by the nature of each change, not by the b
|
|
|
861
932
|
|
|
862
933
|
The `dev` workflow should guard against this and fail until a stable `latest` exists.
|
|
863
934
|
|
|
864
|
-
3. Deprecate the legacy package name on npm (one-time, after `@molpha/sdk@0.1.0` is published):
|
|
865
|
-
|
|
866
|
-
```bash
|
|
867
|
-
npm deprecate "@molpha-oracle/sdk" "Package renamed to @molpha/sdk. Please migrate."
|
|
868
|
-
```
|
|
869
|
-
|
|
870
935
|
If `latest` ever points to a prerelease, repoint it after publishing a stable version:
|
|
871
936
|
|
|
872
937
|
```bash
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { web3 } from '@anchor-lang/core';
|
|
2
2
|
import { address, getAddressEncoder, getAddressDecoder } from '@solana/kit';
|
|
3
|
-
import { ed25519 } from '@noble/curves/ed25519.js';
|
|
4
3
|
|
|
5
4
|
// src/solana/kit.ts
|
|
6
5
|
var TOKEN_PROGRAM_ADDRESS = address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
@@ -38,18 +37,7 @@ function setComputeUnitLimit(units) {
|
|
|
38
37
|
function keypairFromSecretKey(secretKey) {
|
|
39
38
|
return web3.Keypair.fromSecretKey(secretKey);
|
|
40
39
|
}
|
|
41
|
-
var isKeypair = (value) => typeof value === "object" && value !== null && "secretKey" in value && value.secretKey instanceof Uint8Array;
|
|
42
|
-
function signerFromKeypair(keypair) {
|
|
43
|
-
const seed = keypair.secretKey.slice(0, 32);
|
|
44
|
-
return async (message) => ed25519.sign(message, seed);
|
|
45
|
-
}
|
|
46
|
-
function gatewaySignerFromWallet(wallet) {
|
|
47
|
-
const molpha = wallet;
|
|
48
|
-
if (molpha.signAuthMessage) return molpha.signAuthMessage;
|
|
49
|
-
if ("payer" in wallet && isKeypair(wallet.payer)) return signerFromKeypair(wallet.payer);
|
|
50
|
-
return void 0;
|
|
51
|
-
}
|
|
52
40
|
|
|
53
|
-
export { SYSTEM_PROGRAM_ADDRESS, TOKEN_PROGRAM_ADDRESS, addressBytes, addressFromBytes, findProgramAddressSync,
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
55
|
-
//# sourceMappingURL=chunk-
|
|
41
|
+
export { SYSTEM_PROGRAM_ADDRESS, TOKEN_PROGRAM_ADDRESS, addressBytes, addressFromBytes, findProgramAddressSync, getAssociatedTokenAddressSync, keypairFromSecretKey, setComputeUnitLimit, toPublicKey, toSolanaAddress };
|
|
42
|
+
//# sourceMappingURL=chunk-2X22JY7Q.js.map
|
|
43
|
+
//# sourceMappingURL=chunk-2X22JY7Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/solana/kit.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","file":"chunk-2X22JY7Q.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"]}
|