@perpscope/percolator-adapter 0.6.0 → 0.8.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/README.md +17 -0
- package/bin/perpscope.mjs +61 -0
- package/index.js +1 -0
- package/package.json +4 -1
- package/src/lib/percolator-adapter.js +79 -1
- package/src/lib/read-only-rpc-fetcher.js +4 -0
package/README.md
CHANGED
|
@@ -63,6 +63,23 @@ The full field-level contract is documented in `../../docs/field-compatibility-m
|
|
|
63
63
|
|
|
64
64
|
`compareCompatibilityReports(previous, current)` returns `perpscope.compatibility-diff` with score delta, status change, new/resolved fields, section drift, and merged alias suggestions.
|
|
65
65
|
|
|
66
|
+
`buildCompatibilityRealityCheck(report, { input })` returns `perpscope.reality-check` with provenance, required/useful mapped counts, unknown fields, and alias counts. Use it when a terminal needs to show whether a capture is synthetic, real-backed candidate, or externally submitted.
|
|
67
|
+
|
|
68
|
+
## CLI
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
perpscope compat report capture.json
|
|
72
|
+
perpscope compat diff previous.json current.json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Try it locally with:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
perpscope compat diff ../../examples/fixture-pack-minimal-terminal.json ../../examples/fixture-pack-drifted-aliases.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For the real-backed candidate path, try `../../examples/fixture-pack-real-sanitized-rpc-shape.json`.
|
|
82
|
+
|
|
66
83
|
## DTO Example
|
|
67
84
|
|
|
68
85
|
```js
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
buildPercolatorCompatibilityReport,
|
|
5
|
+
compareCompatibilityReports,
|
|
6
|
+
exportCompatibilityReport,
|
|
7
|
+
normalizePercolatorSnapshot,
|
|
8
|
+
parsePercolatorJson
|
|
9
|
+
} from "../index.js";
|
|
10
|
+
|
|
11
|
+
const [, , ...args] = process.argv;
|
|
12
|
+
|
|
13
|
+
function usage() {
|
|
14
|
+
return [
|
|
15
|
+
"Usage:",
|
|
16
|
+
" perpscope compat report <capture.json>",
|
|
17
|
+
" perpscope compat diff <previous.json> <current.json>",
|
|
18
|
+
"",
|
|
19
|
+
"Read-only only: the adapter rejects wallet, signer, transaction, instruction, order, private key, seed, mnemonic, and API key fields."
|
|
20
|
+
].join("\n");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readCapture(path) {
|
|
24
|
+
if (!path) throw new Error("Missing capture path.");
|
|
25
|
+
return parsePercolatorJson(readFileSync(path, "utf8"));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildReport(input) {
|
|
29
|
+
const snapshot = normalizePercolatorSnapshot(input);
|
|
30
|
+
return buildPercolatorCompatibilityReport(input, snapshot);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function main() {
|
|
34
|
+
const [scope, command, ...rest] = args;
|
|
35
|
+
if (!scope || scope === "--help" || scope === "-h") {
|
|
36
|
+
console.log(usage());
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (scope !== "compat") throw new Error(`Unknown scope: ${scope}`);
|
|
40
|
+
if (command === "report") {
|
|
41
|
+
const input = readCapture(rest[0]);
|
|
42
|
+
console.log(JSON.stringify(exportCompatibilityReport(input), null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (command === "diff") {
|
|
46
|
+
const previous = buildReport(readCapture(rest[0]));
|
|
47
|
+
const current = buildReport(readCapture(rest[1]));
|
|
48
|
+
console.log(JSON.stringify(compareCompatibilityReports(previous, current), null, 2));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`Unknown compat command: ${command || ""}`.trim());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
main();
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(error.message);
|
|
58
|
+
console.error("");
|
|
59
|
+
console.error(usage());
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
}
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perpscope/percolator-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Read-only Percolator adapter helpers for Solana perps terminals.",
|
|
6
6
|
"main": "./index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"perpscope": "bin/perpscope.mjs"
|
|
9
|
+
},
|
|
7
10
|
"exports": {
|
|
8
11
|
".": "./index.js"
|
|
9
12
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { normalizeFundingSkewHistory } from "./funding-history.js";
|
|
2
2
|
|
|
3
|
-
export const PERPSCOPE_ADAPTER_VERSION = "0.
|
|
3
|
+
export const PERPSCOPE_ADAPTER_VERSION = "0.8.0";
|
|
4
4
|
|
|
5
5
|
const KEYPAIR_FIELD_PATTERN = /(^|_)(secret|private|keypair|mnemonic|seed|walletPath|wallet)(_|$)/i;
|
|
6
6
|
const HISTORY_COMMAND_KEYS = new Set([
|
|
@@ -231,6 +231,60 @@ export function exportCompatibilityReportFromReport(report, options = {}) {
|
|
|
231
231
|
};
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
export function buildCompatibilityRealityCheck(inputOrReport, options = {}) {
|
|
235
|
+
const hasReportShape = inputOrReport && typeof inputOrReport === "object" && Array.isArray(inputOrReport.recognizedSections);
|
|
236
|
+
const input = hasReportShape ? options.input : inputOrReport;
|
|
237
|
+
const report = hasReportShape ? normalizeCompatibilityReport(inputOrReport) : buildPercolatorCompatibilityReport(inputOrReport);
|
|
238
|
+
const requiredFields = COMPATIBILITY_FIELD_SPECS.filter((field) => field.severity === "danger");
|
|
239
|
+
const optionalFields = COMPATIBILITY_FIELD_SPECS.filter((field) => field.severity !== "danger");
|
|
240
|
+
const missingFieldSet = new Set((report.missingFields || []).map((field) => field.field));
|
|
241
|
+
const mappedRequired = requiredFields.filter((field) => !missingFieldSet.has(field.field));
|
|
242
|
+
const mappedOptional = optionalFields.filter((field) => !missingFieldSet.has(field.field));
|
|
243
|
+
const provenance = realityProvenance(input, report);
|
|
244
|
+
const unknownCount = report.summary?.ignoredCount ?? report.ignoredFields.length;
|
|
245
|
+
const aliasCount = report.summary?.suggestionCount ?? report.aliasSuggestions.length;
|
|
246
|
+
const dangerMissing = (report.missingFields || []).filter((field) => field.severity === "danger").length;
|
|
247
|
+
const warningMissing = (report.missingFields || []).length - dangerMissing;
|
|
248
|
+
const tone = dangerMissing
|
|
249
|
+
? "danger"
|
|
250
|
+
: warningMissing || unknownCount || provenance.status === "candidate"
|
|
251
|
+
? "warning"
|
|
252
|
+
: "good";
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
schema: "perpscope.reality-check",
|
|
256
|
+
version: 1,
|
|
257
|
+
package: {
|
|
258
|
+
name: "@perpscope/percolator-adapter",
|
|
259
|
+
version: options.packageVersion || PERPSCOPE_ADAPTER_VERSION
|
|
260
|
+
},
|
|
261
|
+
generatedAt: options.generatedAt || new Date().toISOString(),
|
|
262
|
+
tone,
|
|
263
|
+
status: provenance.status,
|
|
264
|
+
sourceKind: provenance.kind,
|
|
265
|
+
provenance,
|
|
266
|
+
mapped: {
|
|
267
|
+
requiredCount: mappedRequired.length,
|
|
268
|
+
requiredTotal: requiredFields.length,
|
|
269
|
+
optionalCount: mappedOptional.length,
|
|
270
|
+
optionalTotal: optionalFields.length,
|
|
271
|
+
recognizedCount: report.summary?.recognizedCount ?? report.recognizedSections.length
|
|
272
|
+
},
|
|
273
|
+
gaps: {
|
|
274
|
+
dangerMissing,
|
|
275
|
+
warningMissing,
|
|
276
|
+
unknownCount,
|
|
277
|
+
aliasCount
|
|
278
|
+
},
|
|
279
|
+
lanes: [
|
|
280
|
+
realityLane("required", `${mappedRequired.length}/${requiredFields.length}`, dangerMissing ? "danger" : "good"),
|
|
281
|
+
realityLane("useful", `${mappedOptional.length}/${optionalFields.length}`, warningMissing ? "warning" : "good"),
|
|
282
|
+
realityLane("unknown", String(unknownCount), unknownCount ? "warning" : "good"),
|
|
283
|
+
realityLane("aliases", String(aliasCount), aliasCount ? "good" : "neutral")
|
|
284
|
+
]
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
234
288
|
const COMPATIBILITY_SECTION_SPECS = [
|
|
235
289
|
{
|
|
236
290
|
id: "safety",
|
|
@@ -659,6 +713,30 @@ function compatibilityDiffSummary(report) {
|
|
|
659
713
|
};
|
|
660
714
|
}
|
|
661
715
|
|
|
716
|
+
function realityLane(label, value, tone = "neutral") {
|
|
717
|
+
return { label, value, tone };
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function realityProvenance(input, report) {
|
|
721
|
+
const source = report.source || {};
|
|
722
|
+
const inputSource = input && typeof input === "object" && !Array.isArray(input) ? input.source || {} : {};
|
|
723
|
+
const kind = stringOf(input, ["fixtureKind", "sourceKind"], inputSource.kind || source.shape || report.shape || "decoded-capture");
|
|
724
|
+
const candidate = /candidate|read-only-rpc|real/i.test(kind) || Boolean(inputSource.realBacked);
|
|
725
|
+
const submitted = Boolean(inputSource.submittedBy) || Boolean(inputSource.externalSubmission);
|
|
726
|
+
const status = submitted ? "submitted" : candidate ? "candidate" : "synthetic";
|
|
727
|
+
return {
|
|
728
|
+
status,
|
|
729
|
+
kind,
|
|
730
|
+
label: source.label || stringOf(input, ["label"], "decoded capture"),
|
|
731
|
+
cluster: source.cluster || stringOf(input, ["cluster"], "unknown"),
|
|
732
|
+
basis: inputSource.basis || inputSource.capture || "",
|
|
733
|
+
fixture: stringOf(input, ["fixture"], ""),
|
|
734
|
+
sanitized: inputSource.sanitized !== false,
|
|
735
|
+
submittedBy: inputSource.submittedBy || "",
|
|
736
|
+
note: inputSource.note || (status === "candidate" ? "real-backed candidate; still waiting on third-party decoded shape" : "")
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
662
740
|
function differenceByField(left = [], right = []) {
|
|
663
741
|
const rightFields = new Set((right || []).map((entry) => entry.field));
|
|
664
742
|
return (left || []).filter((entry) => !rightFields.has(entry.field));
|
|
@@ -97,6 +97,10 @@ export function buildReadOnlyRpcSnapshot(request) {
|
|
|
97
97
|
if (decoded.params) commands.push({ command: "slab:params", output: decoded.params });
|
|
98
98
|
if (decoded.engine) commands.push({ command: "slab:engine", output: decoded.engine });
|
|
99
99
|
if (decoded.bestPrice) commands.push({ command: "best-price", output: decoded.bestPrice });
|
|
100
|
+
if (decoded.receipts || decoded.executionReceipts) commands.push({ command: "execution:receipts", output: decoded.receipts || decoded.executionReceipts });
|
|
101
|
+
if (decoded.fundingSkew || decoded.fundingHistory || decoded.fundingSkewHistory) {
|
|
102
|
+
commands.push({ command: "funding-skew-history", output: decoded.fundingSkew || decoded.fundingHistory || decoded.fundingSkewHistory });
|
|
103
|
+
}
|
|
100
104
|
if (decoded.accounts) commands.push({ command: "slab:accounts", output: decoded.accounts });
|
|
101
105
|
if (decoded.bitmap) commands.push({ command: "slab:bitmap", output: decoded.bitmap });
|
|
102
106
|
|