verifyhash 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 +201 -0
- package/README.md +883 -0
- package/cli/abi/ContributionRegistry.json +881 -0
- package/cli/agent.js +2173 -0
- package/cli/anchor-artifact.js +853 -0
- package/cli/anchor.js +400 -0
- package/cli/claim.js +881 -0
- package/cli/core/agent-commit.js +448 -0
- package/cli/core/agent-session.js +598 -0
- package/cli/core/anchor-binding.js +663 -0
- package/cli/core/attestation.js +580 -0
- package/cli/core/evidence-plans.js +495 -0
- package/cli/core/fixtures/evidence-plans/baseline.json +19 -0
- package/cli/core/fulfill-intake.js +1082 -0
- package/cli/core/go-live-preflight.js +481 -0
- package/cli/core/license.js +534 -0
- package/cli/core/manifest.js +243 -0
- package/cli/core/packetseal.js +591 -0
- package/cli/core/registryArtifact.js +49 -0
- package/cli/core/revocation.js +539 -0
- package/cli/core/rfc3161.js +389 -0
- package/cli/core/timestamp.js +482 -0
- package/cli/core/trust-asof.js +479 -0
- package/cli/dataset.js +2950 -0
- package/cli/evidence.js +2227 -0
- package/cli/fulfill-webhook-http.js +438 -0
- package/cli/git.js +220 -0
- package/cli/hash.js +550 -0
- package/cli/identity.js +1072 -0
- package/cli/journal-cli.js +1110 -0
- package/cli/journal-log.js +454 -0
- package/cli/journal.js +334 -0
- package/cli/lineage.js +447 -0
- package/cli/list.js +287 -0
- package/cli/parcel.js +1509 -0
- package/cli/proof.js +578 -0
- package/cli/prove.js +300 -0
- package/cli/receipt.js +631 -0
- package/cli/registry.js +331 -0
- package/cli/reputation.js +344 -0
- package/cli/revocation.js +495 -0
- package/cli/serve-verify-http.js +298 -0
- package/cli/serve-verify.js +333 -0
- package/cli/show.js +339 -0
- package/cli/verify.js +383 -0
- package/cli/vh.js +3927 -0
- package/docs/ADOPT.md +183 -0
- package/docs/ADOPTION.json +11 -0
- package/docs/AGENTTRACE.md +247 -0
- package/docs/ANCHORING.md +167 -0
- package/docs/AUDIT.md +55 -0
- package/docs/CONFORMANCE.md +107 -0
- package/docs/DATALEDGER.md +638 -0
- package/docs/DECIDE.md +47 -0
- package/docs/DECISIONS-PENDING.md +27 -0
- package/docs/DEPLOY-PUBLIC-SITE.md +301 -0
- package/docs/ENGINE-LEDGER.json +12 -0
- package/docs/EVIDENCE.md +519 -0
- package/docs/GO-LIVE.md +66 -0
- package/docs/IDENTITY.md +123 -0
- package/docs/INDEPENDENT-VERIFICATION.md +377 -0
- package/docs/INTEGRITY-JOURNAL.md +337 -0
- package/docs/KEY-LIFECYCLE.md +179 -0
- package/docs/LICENSING.md +46 -0
- package/docs/LINEAGE.md +307 -0
- package/docs/LOOP-AUDIT-2026-07-03.json +580 -0
- package/docs/LOOP-HARDENING-PLAN.md +44 -0
- package/docs/MERKLE-LEAVES.md +113 -0
- package/docs/METRICS.jsonl +31 -0
- package/docs/MORNING.md +204 -0
- package/docs/PILOT.md +444 -0
- package/docs/PROOFPARCEL.md +227 -0
- package/docs/PROOFS.md +262 -0
- package/docs/RECEIPTS.md +341 -0
- package/docs/REPUTATION.md +158 -0
- package/docs/SDK.md +301 -0
- package/docs/STRATEGY-ARCHIVE.md +5055 -0
- package/docs/SUPERVISOR-RUNBOOK.md +52 -0
- package/docs/TRUST-BOUNDARIES.md +335 -0
- package/docs/TRUSTLEDGER.md +1976 -0
- package/docs/USAGE-BUDGET.json +121 -0
- package/docs/VERIFY-SERVICE.md +168 -0
- package/index.js +160 -0
- package/package.json +41 -0
- package/trustledger/build-standalone.js +796 -0
- package/trustledger/cli.js +3179 -0
- package/trustledger/close.js +391 -0
- package/trustledger/corpus.js +159 -0
- package/trustledger/dist/BUILD-PROVENANCE.json +99 -0
- package/trustledger/dist/trustledger-standalone.html +6197 -0
- package/trustledger/dist/trustledger-standalone.html.sha256 +1 -0
- package/trustledger/door-core.js +442 -0
- package/trustledger/fixtures/bank.csv +7 -0
- package/trustledger/fixtures/bank.malformed.csv +3 -0
- package/trustledger/fixtures/bank.noalias.csv +5 -0
- package/trustledger/fixtures/bank.ofx +34 -0
- package/trustledger/fixtures/bank.real.csv +5 -0
- package/trustledger/fixtures/corpus/_shared/prior-close.json +22 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/inputs.json +14 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/inputs.json +14 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/continuity-break--benign-twin/inputs.json +15 -0
- package/trustledger/fixtures/corpus/continuity-break--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/continuity-break--out-of-trust/inputs.json +15 -0
- package/trustledger/fixtures/corpus/continuity-break--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/inputs.json +13 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/inputs.json +15 -0
- package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/inputs.json +15 -0
- package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/inputs.json +16 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/inputs.json +13 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/e2e/bank.aliased.csv +4 -0
- package/trustledger/fixtures/e2e/bank.csv +4 -0
- package/trustledger/fixtures/e2e/bank.nsf.csv +4 -0
- package/trustledger/fixtures/e2e/quickbooks.csv +6 -0
- package/trustledger/fixtures/e2e/quickbooks.nsf.csv +8 -0
- package/trustledger/fixtures/e2e/rentroll.csv +6 -0
- package/trustledger/fixtures/e2e/rentroll.nsf.csv +8 -0
- package/trustledger/fixtures/e2e/rentroll.short.csv +5 -0
- package/trustledger/fixtures/plans/baseline.json +25 -0
- package/trustledger/fixtures/plans/price-binding.example.json +27 -0
- package/trustledger/fixtures/policy/ambiguous-deposit-example.json +12 -0
- package/trustledger/fixtures/policy/baseline.json +19 -0
- package/trustledger/fixtures/policy/ca-example.json +12 -0
- package/trustledger/fixtures/policy/negative-tenant-ledger-example.json +12 -0
- package/trustledger/fixtures/policy/owner-overdraw-example.json +12 -0
- package/trustledger/fixtures/quickbooks.csv +7 -0
- package/trustledger/fixtures/quickbooks.real.csv +5 -0
- package/trustledger/fixtures/rentroll.csv +6 -0
- package/trustledger/fixtures/rentroll.real.csv +4 -0
- package/trustledger/ingest.js +1163 -0
- package/trustledger/lib/policy-bundled-loader.js +44 -0
- package/trustledger/lib/sha256-vendored.js +227 -0
- package/trustledger/license.js +563 -0
- package/trustledger/match.js +551 -0
- package/trustledger/plans.js +551 -0
- package/trustledger/policy.js +398 -0
- package/trustledger/public/index.html +512 -0
- package/trustledger/reconcile.js +1486 -0
- package/trustledger/report.js +887 -0
- package/trustledger/seal.js +854 -0
- package/trustledger/server.js +391 -0
- package/trustledger/valueproof.js +350 -0
package/cli/show.js
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// `vh show <0xhash> [--contract a] [--rpc u] [--json]` — look up ONE record by its content hash,
|
|
4
|
+
// with NO local content needed.
|
|
5
|
+
//
|
|
6
|
+
// Where `vh verify <path>` re-derives a hash from real bytes on disk and binds it to the chain, `show`
|
|
7
|
+
// starts from a hash you already have (e.g. one printed by `vh list`, copied from a receipt, or quoted
|
|
8
|
+
// in a PR) and just reads back the registry record for THAT exact hash:
|
|
9
|
+
// * If a record exists -> print contributor, attribution strength, timestamp (+ISO), blockNumber, uri.
|
|
10
|
+
// * If getRecord reverts with NotAnchored -> print a clear "NOT ANCHORED" line and exit non-zero,
|
|
11
|
+
// distinguishing that expected "no such record" from a genuine RPC/network error (reusing the same
|
|
12
|
+
// `isNotAnchoredError` classifier verify.js uses, so the two commands never drift).
|
|
13
|
+
//
|
|
14
|
+
// CRITICAL TRUST CAVEAT (and why this is a deliberately weaker tool than `verify`): `show` proves only
|
|
15
|
+
// that this EXACT hash is on-chain. It does NOT, and cannot, re-derive content — it never touches your
|
|
16
|
+
// files. So a record here does NOT bind the hash to any real bytes you hold; to make that binding you
|
|
17
|
+
// must still run `vh verify <path>` (which recomputes the hash from the path and compares). The output
|
|
18
|
+
// and usage cross-link the two commands so a reader is never lulled into treating a `show` hit as proof
|
|
19
|
+
// that some file is authentic.
|
|
20
|
+
//
|
|
21
|
+
// Read-only by construction: it takes a PROVIDER only, never a signer and never a key.
|
|
22
|
+
|
|
23
|
+
const { isNotAnchoredError } = require("./verify");
|
|
24
|
+
const {
|
|
25
|
+
assertRegistry,
|
|
26
|
+
formatRegistryLine,
|
|
27
|
+
formatSkippedLine,
|
|
28
|
+
jsonRegistryBlock,
|
|
29
|
+
jsonSkippedBlock,
|
|
30
|
+
} = require("./registry");
|
|
31
|
+
|
|
32
|
+
const ARTIFACT = require("./core/registryArtifact");
|
|
33
|
+
const ABI = ARTIFACT.abi;
|
|
34
|
+
|
|
35
|
+
// Outcomes of a show run. ANCHORED == a record exists for the queried hash; NOT_ANCHORED == the
|
|
36
|
+
// contract reverted NotAnchored (no record). A genuine RPC error is neither — it throws.
|
|
37
|
+
const STATUS = Object.freeze({
|
|
38
|
+
ANCHORED: "ANCHORED",
|
|
39
|
+
NOT_ANCHORED: "NOT_ANCHORED",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// The lineage sentinel: a record whose `parent` is the 32-byte zero hash has NO predecessor and is a
|
|
43
|
+
// "lineage root" (the contract's documented convention; bytes32(0) == "no predecessor / root of a
|
|
44
|
+
// lineage"). Both the human block and the --json shape flag this explicitly so a consumer can tell a
|
|
45
|
+
// deliberate root from a missing/omitted field.
|
|
46
|
+
const ZERO_HASH = "0x" + "0".repeat(64);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* True iff `parent` is the zero-hash sentinel (== lineage root / no predecessor). Tolerant of a
|
|
50
|
+
* null/undefined/missing value (an older record shape or a NOT_ANCHORED result) — those are treated
|
|
51
|
+
* as "root" too, so callers never crash on a missing edge.
|
|
52
|
+
*/
|
|
53
|
+
function isRoot(parent) {
|
|
54
|
+
return parent == null || BigInt(parent) === 0n;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The two attribution phrases, kept consistent with cli/verify.js / cli/list.js so show, verify and
|
|
58
|
+
// list never disagree about what `contributor` is allowed to mean for a given record.
|
|
59
|
+
const ATTRIBUTION_PROVEN =
|
|
60
|
+
"proven first claimant (commit-reveal, front-running-resistant)";
|
|
61
|
+
const ATTRIBUTION_ANCHOR_ONLY =
|
|
62
|
+
"first anchorer only — NOT proven authorship (anyone could have anchored this hash)";
|
|
63
|
+
|
|
64
|
+
// The trust caveat that LEADS every human-readable run. It spells out the core limitation: a `show`
|
|
65
|
+
// hit only proves the hash is on-chain, never that any file you hold actually hashes to it. The cross
|
|
66
|
+
// link to `vh verify` is load-bearing, not decorative — it is the only command that binds bytes.
|
|
67
|
+
const TRUST_CAVEAT = [
|
|
68
|
+
"NOTE: `show` proves only that THIS exact hash is recorded on-chain. It does NOT re-derive any",
|
|
69
|
+
"content — it never reads your files — so a hit here does NOT bind this hash to real bytes you hold.",
|
|
70
|
+
"To prove a file/dir actually hashes to this record, run `vh verify <path>` (it recomputes the hash",
|
|
71
|
+
"from the path and compares). Also: `uri` is an UNTRUSTED hint (never fetched/validated), and",
|
|
72
|
+
"`contributor` only means proven authorship when authorBound is true (commit-reveal).",
|
|
73
|
+
].join("\n");
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validate that `value` is a 32-byte (0x + 64 hex chars) content hash, the exact shape getRecord
|
|
77
|
+
* keys on. Returns the lowercased, normalized hash. Throws a usage-grade error otherwise so a
|
|
78
|
+
* malformed/short hash hard-errors BEFORE any network call (the caller surfaces usage on throw).
|
|
79
|
+
*
|
|
80
|
+
* @param {string} value
|
|
81
|
+
* @param {object} ethersLib ethers v6 module (for isHexString)
|
|
82
|
+
* @returns {string} the normalized 0x-prefixed 32-byte hash (lowercase)
|
|
83
|
+
*/
|
|
84
|
+
function normalizeContentHash(value, ethersLib) {
|
|
85
|
+
if (value === undefined || value === null || value === "") {
|
|
86
|
+
throw new Error("show requires a <0xhash> (a 32-byte content hash)");
|
|
87
|
+
}
|
|
88
|
+
if (typeof value !== "string") {
|
|
89
|
+
throw new Error(`invalid content hash: expected a 0x string, got ${typeof value}`);
|
|
90
|
+
}
|
|
91
|
+
if (!value.startsWith("0x") && !value.startsWith("0X")) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`invalid content hash: must be 0x-prefixed 32-byte hex, got: ${value}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
// isHexString(v, 32) is true ONLY for exactly 0x + 64 hex chars — rejecting short, long, and
|
|
97
|
+
// non-hex inputs in one check, before we ever build a provider or send a request.
|
|
98
|
+
if (!ethersLib.isHexString(value, 32)) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`invalid content hash: must be a 32-byte (64 hex char) 0x value, got: ${value} ` +
|
|
101
|
+
`(length ${value.length}). Did you mean to run \`vh verify <path>\` on a file instead?`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return value.toLowerCase();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Format a unix-seconds bigint as an ISO-8601 UTC string for human display. */
|
|
108
|
+
function isoFromUnix(unixSeconds) {
|
|
109
|
+
try {
|
|
110
|
+
return new Date(Number(unixSeconds) * 1000).toISOString();
|
|
111
|
+
} catch (_) {
|
|
112
|
+
return "(unparseable)";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The human attribution phrase for a record, reusing verify.js/list.js wording exactly. */
|
|
117
|
+
function attributionFor(authorBound) {
|
|
118
|
+
return authorBound ? ATTRIBUTION_PROVEN : ATTRIBUTION_ANCHOR_ONLY;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Render a show result as the human-readable block the CLI prints. Always leads with the trust
|
|
123
|
+
* caveat (which cross-links `vh verify`), then either the record fields or a NOT ANCHORED block.
|
|
124
|
+
*/
|
|
125
|
+
function formatShow(r) {
|
|
126
|
+
const lines = [TRUST_CAVEAT, ""];
|
|
127
|
+
// T-11.2: the registry-authentication confirmation (or the loud skip warning), printed BEFORE the
|
|
128
|
+
// record so a reader sees the contract was authenticated before believing any field below.
|
|
129
|
+
if (r.identitySkipped) {
|
|
130
|
+
lines.push(formatSkippedLine());
|
|
131
|
+
} else if (r.registry) {
|
|
132
|
+
lines.push(formatRegistryLine(r.registry));
|
|
133
|
+
}
|
|
134
|
+
lines.push(` contentHash: ${r.contentHash}`);
|
|
135
|
+
if (r.status === STATUS.ANCHORED) {
|
|
136
|
+
const ts = r.timestamp == null ? "(unknown)" : isoFromUnix(r.timestamp);
|
|
137
|
+
// `parent` is the optional immutable lineage edge. A root (0x0) renders as "(none) — lineage
|
|
138
|
+
// root" so a reader can tell a deliberate root from a missing field; a parented record shows the
|
|
139
|
+
// predecessor hash and `vh show <parent>` to walk one step back. Per TRUST BOUNDARIES the edge is
|
|
140
|
+
// only a CLAIM by this record's author — it proves neither content ancestry nor authorship.
|
|
141
|
+
const parentLine = isRoot(r.parent)
|
|
142
|
+
? " parent: (none) — lineage root (no predecessor)"
|
|
143
|
+
: ` parent: ${r.parent} (claimed predecessor — walk it with \`vh show ${r.parent}\`)`;
|
|
144
|
+
lines.push(
|
|
145
|
+
" result: ANCHORED",
|
|
146
|
+
` contributor: ${r.contributor}`,
|
|
147
|
+
` attribution: ${attributionFor(r.authorBound)}`,
|
|
148
|
+
` authorBound: ${r.authorBound}`,
|
|
149
|
+
` timestamp: ${r.timestamp} (${ts})`,
|
|
150
|
+
` blockNumber: ${r.blockNumber}`,
|
|
151
|
+
` uri: ${r.uri ? r.uri : "(none)"}`,
|
|
152
|
+
parentLine,
|
|
153
|
+
"",
|
|
154
|
+
" This record attests only that the EXACT hash above is on-chain. To bind it to real bytes,",
|
|
155
|
+
" run `vh verify <path>` — `show` does not re-derive content. A `parent` is only this author's",
|
|
156
|
+
" CLAIMED predecessor: it proves neither content ancestry nor a transfer of the parent's authorship."
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
lines.push(
|
|
160
|
+
" result: NOT ANCHORED",
|
|
161
|
+
" No record exists for this content hash. It was never anchored (or you mistyped the hash).",
|
|
162
|
+
" If you have the content, `vh anchor <path>` / `vh claim <path>` can anchor it; `vh verify",
|
|
163
|
+
" <path>` recomputes a path's hash and tells you whether THAT resolves to a record."
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return lines.join("\n");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Shape a show result for `--json`: BigInts become Numbers (unix seconds / block heights fit safely)
|
|
171
|
+
* and the attribution phrase is included so a machine consumer gets the same semantics as the human
|
|
172
|
+
* block. NOT_ANCHORED is a first-class JSON value (anchored:false), not an error object, so a script
|
|
173
|
+
* can branch on it without parsing stderr — while still seeing a non-zero exit from the CLI.
|
|
174
|
+
*/
|
|
175
|
+
function jsonShow(r) {
|
|
176
|
+
// T-11.2: the machine-readable registry block — the same identity a UI/indexer can depend on to
|
|
177
|
+
// know the record was read from an authenticated registry (or that the check was skipped).
|
|
178
|
+
const registry = r.identitySkipped
|
|
179
|
+
? jsonSkippedBlock()
|
|
180
|
+
: r.registry
|
|
181
|
+
? jsonRegistryBlock(r.registry)
|
|
182
|
+
: null;
|
|
183
|
+
if (r.status === STATUS.ANCHORED) {
|
|
184
|
+
const root = isRoot(r.parent);
|
|
185
|
+
return {
|
|
186
|
+
contentHash: r.contentHash,
|
|
187
|
+
registry,
|
|
188
|
+
anchored: true,
|
|
189
|
+
contributor: r.contributor,
|
|
190
|
+
authorBound: r.authorBound,
|
|
191
|
+
attribution: attributionFor(r.authorBound),
|
|
192
|
+
timestamp: Number(r.timestamp),
|
|
193
|
+
timestampISO: isoFromUnix(r.timestamp),
|
|
194
|
+
blockNumber: Number(r.blockNumber),
|
|
195
|
+
uri: r.uri ? r.uri : null,
|
|
196
|
+
// Lineage edge (T-10.1): `parent` is always present in the contract; surface it explicitly so an
|
|
197
|
+
// indexer/UI consuming the documented --json contract can see the edge. A root serializes
|
|
198
|
+
// parent:null + isRoot:true (so a deliberate root is distinguishable from a missing key), a
|
|
199
|
+
// parented record carries the predecessor hash + isRoot:false. The edge is only this author's
|
|
200
|
+
// CLAIMED predecessor — it proves neither content ancestry nor a transfer of authorship.
|
|
201
|
+
parent: root ? null : r.parent,
|
|
202
|
+
isRoot: root,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
contentHash: r.contentHash,
|
|
207
|
+
registry,
|
|
208
|
+
anchored: false,
|
|
209
|
+
note:
|
|
210
|
+
"NOT ANCHORED: no on-chain record for this hash. `show` only proves a hash is on-chain; " +
|
|
211
|
+
"run `vh verify <path>` to bind a record to real content.",
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Look up ONE record by content hash. Read-only: requires a provider, never a signer.
|
|
217
|
+
*
|
|
218
|
+
* Validates the hash shape FIRST (a malformed/short hash throws before any network call), then reads
|
|
219
|
+
* getRecord(hash). A NotAnchored revert is the expected "no record" path (STATUS.NOT_ANCHORED); any
|
|
220
|
+
* other failure (bad RPC, wrong address, network down) is re-thrown rather than masqueraded as
|
|
221
|
+
* "not anchored" — exactly as verify.js handles it, via the shared `isNotAnchoredError`.
|
|
222
|
+
*
|
|
223
|
+
* @param {object} opts
|
|
224
|
+
* @param {string} opts.contentHash the 0x 32-byte hash to look up
|
|
225
|
+
* @param {string} opts.contractAddress deployed ContributionRegistry address to read from
|
|
226
|
+
* @param {object} opts.provider ethers v6 Provider (read-only RPC connection)
|
|
227
|
+
* @param {boolean} [opts.json] emit a JSON object instead of the human block
|
|
228
|
+
* @param {object} [opts.ethers] ethers v6 module (defaults to the bundled one)
|
|
229
|
+
* @param {(s:string)=>void} [opts.log] sink for output (defaults to process.stdout)
|
|
230
|
+
* @returns {Promise<{
|
|
231
|
+
* status: "ANCHORED"|"NOT_ANCHORED",
|
|
232
|
+
* contentHash: string,
|
|
233
|
+
* contributor: string|null,
|
|
234
|
+
* authorBound: boolean|null,
|
|
235
|
+
* timestamp: bigint|null,
|
|
236
|
+
* blockNumber: bigint|null,
|
|
237
|
+
* uri: string|null,
|
|
238
|
+
* parent: string|null,
|
|
239
|
+
* }>}
|
|
240
|
+
*/
|
|
241
|
+
async function runShow(opts) {
|
|
242
|
+
const ethersLib = opts.ethers || require("ethers");
|
|
243
|
+
const log = opts.log || ((s) => process.stdout.write(s));
|
|
244
|
+
|
|
245
|
+
// Validate the hash BEFORE touching the contract address / provider, so a bad hash hard-errors with
|
|
246
|
+
// a usage-grade message and never reaches the network.
|
|
247
|
+
const contentHash = normalizeContentHash(opts.contentHash, ethersLib);
|
|
248
|
+
|
|
249
|
+
const { contractAddress, provider } = opts;
|
|
250
|
+
if (!contractAddress) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
"no contract address: pass --contract <address> or set VH_CONTRACT in the environment"
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (!ethersLib.isAddress(contractAddress)) {
|
|
256
|
+
throw new Error(`invalid contract address: ${contractAddress}`);
|
|
257
|
+
}
|
|
258
|
+
if (!provider) {
|
|
259
|
+
throw new Error("no provider: pass --rpc <url> or set VH_RPC_URL / AMOY_RPC_URL");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// T-11.2: authenticate the registry BEFORE reading the record — no record is reported until we have
|
|
263
|
+
// confirmed there is a real verifyhash ContributionRegistry at this address (unless the caller
|
|
264
|
+
// explicitly, loudly opts out with skipIdentityCheck for a known not-yet-deployed/local-dev target).
|
|
265
|
+
let registryAuth = null;
|
|
266
|
+
if (!opts.skipIdentityCheck) {
|
|
267
|
+
registryAuth = await assertRegistry({ provider, contractAddress, ethers: ethersLib });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const iface = new ethersLib.Interface(ABI);
|
|
271
|
+
const notAnchoredSelector = iface.getError("NotAnchored").selector;
|
|
272
|
+
|
|
273
|
+
const contract = new ethersLib.Contract(
|
|
274
|
+
ethersLib.getAddress(contractAddress),
|
|
275
|
+
ABI,
|
|
276
|
+
provider
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
let record = null;
|
|
280
|
+
try {
|
|
281
|
+
record = await contract.getRecord(contentHash);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (isNotAnchoredError(err, ethersLib, notAnchoredSelector)) {
|
|
284
|
+
record = null; // expected "no record" -> NOT_ANCHORED below
|
|
285
|
+
} else {
|
|
286
|
+
throw err; // genuine failure (network/address/etc.) — don't masquerade as NOT ANCHORED.
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const result = {
|
|
291
|
+
contentHash,
|
|
292
|
+
status: record === null ? STATUS.NOT_ANCHORED : STATUS.ANCHORED,
|
|
293
|
+
// T-11.2: the resolved registry identity (or null when skipped). Surfaced in both the human block
|
|
294
|
+
// and --json so a consumer can SEE the registry was authenticated before believing this record.
|
|
295
|
+
registry: registryAuth,
|
|
296
|
+
identitySkipped: Boolean(opts.skipIdentityCheck),
|
|
297
|
+
contributor: null,
|
|
298
|
+
authorBound: null,
|
|
299
|
+
timestamp: null,
|
|
300
|
+
blockNumber: null,
|
|
301
|
+
uri: null,
|
|
302
|
+
parent: null,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
if (record !== null) {
|
|
306
|
+
result.contributor = record.contributor;
|
|
307
|
+
result.authorBound = Boolean(record.authorBound);
|
|
308
|
+
result.timestamp = BigInt(record.timestamp);
|
|
309
|
+
result.blockNumber = BigInt(record.blockNumber);
|
|
310
|
+
result.uri = record.uri;
|
|
311
|
+
// The immutable lineage edge (T-10.1). Normalize to a lowercase 0x string so isRoot() / equality
|
|
312
|
+
// checks are stable; a root reads back as the 32-byte zero hash.
|
|
313
|
+
result.parent = String(record.parent).toLowerCase();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (opts.json) {
|
|
317
|
+
log(JSON.stringify(jsonShow(result), null, 2) + "\n");
|
|
318
|
+
} else {
|
|
319
|
+
log(formatShow(result) + "\n");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
module.exports = {
|
|
326
|
+
runShow,
|
|
327
|
+
normalizeContentHash,
|
|
328
|
+
formatShow,
|
|
329
|
+
jsonShow,
|
|
330
|
+
attributionFor,
|
|
331
|
+
isoFromUnix,
|
|
332
|
+
isRoot,
|
|
333
|
+
STATUS,
|
|
334
|
+
TRUST_CAVEAT,
|
|
335
|
+
ZERO_HASH,
|
|
336
|
+
ATTRIBUTION_PROVEN,
|
|
337
|
+
ATTRIBUTION_ANCHOR_ONLY,
|
|
338
|
+
ABI,
|
|
339
|
+
};
|