@provex/manifest 1.9.2-rc.20260719220119.92e7405
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 +15 -0
- package/dist/canonicalize.d.ts +16 -0
- package/dist/canonicalize.js +32 -0
- package/dist/fetch.d.ts +17 -0
- package/dist/fetch.js +31 -0
- package/dist/hash.d.ts +15 -0
- package/dist/hash.js +15 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/schema.d.ts +72 -0
- package/dist/schema.js +41 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @provex/manifest
|
|
2
|
+
|
|
3
|
+
Pure helpers for Provex release manifests — the standardized `version.json`
|
|
4
|
+
a decentralized application serves to declare a release.
|
|
5
|
+
|
|
6
|
+
- `parseManifest` — validate an unknown value into a `ReleaseManifest`.
|
|
7
|
+
- `canonicalize` — deterministic canonical-JSON bytes (sorted keys, no
|
|
8
|
+
insignificant whitespace) so reformatting never changes the hash.
|
|
9
|
+
- `manifestHash` — the keccak256 of the canonical bytes; the unit a release
|
|
10
|
+
is proven and endorsed against.
|
|
11
|
+
- `dappKey` — keccak256 of the lowercased domain; the per-application key.
|
|
12
|
+
- `fetchManifest` — retrieve and validate a live manifest from a domain,
|
|
13
|
+
trying `/version.json` then `/.well-known/version.json`.
|
|
14
|
+
|
|
15
|
+
Browser-safe and dependency-light (Zod plus viem as a peer).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constrained canonical-JSON for release manifests.
|
|
3
|
+
*
|
|
4
|
+
* Equivalent to RFC 8785 (JSON Canonicalization Scheme) over the manifest's
|
|
5
|
+
* restricted value space: the schema forbids floats and non-finite numbers,
|
|
6
|
+
* so recursive key sorting plus `JSON.stringify` of each primitive yields a
|
|
7
|
+
* single, stable byte sequence for a given manifest. If a future schema
|
|
8
|
+
* version admits arbitrary values, replace `canonicalizeValue` with a full
|
|
9
|
+
* RFC 8785 implementation behind this same signature.
|
|
10
|
+
*/
|
|
11
|
+
import type { ReleaseManifest } from './schema.js';
|
|
12
|
+
/**
|
|
13
|
+
* Deterministic canonical-JSON bytes for a manifest. Key order and whitespace
|
|
14
|
+
* in the source do not affect the output.
|
|
15
|
+
*/
|
|
16
|
+
export declare const canonicalize: (manifest: ReleaseManifest) => Uint8Array;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constrained canonical-JSON for release manifests.
|
|
3
|
+
*
|
|
4
|
+
* Equivalent to RFC 8785 (JSON Canonicalization Scheme) over the manifest's
|
|
5
|
+
* restricted value space: the schema forbids floats and non-finite numbers,
|
|
6
|
+
* so recursive key sorting plus `JSON.stringify` of each primitive yields a
|
|
7
|
+
* single, stable byte sequence for a given manifest. If a future schema
|
|
8
|
+
* version admits arbitrary values, replace `canonicalizeValue` with a full
|
|
9
|
+
* RFC 8785 implementation behind this same signature.
|
|
10
|
+
*/
|
|
11
|
+
const canonicalizeValue = (value) => {
|
|
12
|
+
if (value === null)
|
|
13
|
+
return 'null';
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return `[${value.map(canonicalizeValue).join(',')}]`;
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === 'object') {
|
|
18
|
+
const record = value;
|
|
19
|
+
const members = Object.keys(record)
|
|
20
|
+
.filter((key) => record[key] !== undefined)
|
|
21
|
+
.sort()
|
|
22
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalizeValue(record[key])}`);
|
|
23
|
+
return `{${members.join(',')}}`;
|
|
24
|
+
}
|
|
25
|
+
// string | number (integer) | boolean — JSON.stringify is canonical here.
|
|
26
|
+
return JSON.stringify(value);
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Deterministic canonical-JSON bytes for a manifest. Key order and whitespace
|
|
30
|
+
* in the source do not affect the output.
|
|
31
|
+
*/
|
|
32
|
+
export const canonicalize = (manifest) => new TextEncoder().encode(canonicalizeValue(manifest));
|
package/dist/fetch.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieve and validate a live release manifest from a domain.
|
|
3
|
+
*
|
|
4
|
+
* Tries the root `/version.json` first, then the `/.well-known/version.json`
|
|
5
|
+
* fallback. The returned `dappKey` derives from the manifest's self-declared
|
|
6
|
+
* `domain` (the same value the gate will bind a proof to), not from the
|
|
7
|
+
* request origin, so a client preview matches what the gate signs.
|
|
8
|
+
*/
|
|
9
|
+
import type { Hex } from 'viem';
|
|
10
|
+
import { type ReleaseManifest } from './schema.js';
|
|
11
|
+
export interface FetchedManifest {
|
|
12
|
+
manifest: ReleaseManifest;
|
|
13
|
+
manifestHash: Hex;
|
|
14
|
+
dappKey: Hex;
|
|
15
|
+
origin: string;
|
|
16
|
+
}
|
|
17
|
+
export declare const fetchManifest: (origin: string) => Promise<FetchedManifest>;
|
package/dist/fetch.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieve and validate a live release manifest from a domain.
|
|
3
|
+
*
|
|
4
|
+
* Tries the root `/version.json` first, then the `/.well-known/version.json`
|
|
5
|
+
* fallback. The returned `dappKey` derives from the manifest's self-declared
|
|
6
|
+
* `domain` (the same value the gate will bind a proof to), not from the
|
|
7
|
+
* request origin, so a client preview matches what the gate signs.
|
|
8
|
+
*/
|
|
9
|
+
import { parseManifest } from './schema.js';
|
|
10
|
+
import { manifestHash, dappKey } from './hash.js';
|
|
11
|
+
const MANIFEST_PATHS = ['/version.json', '/.well-known/version.json'];
|
|
12
|
+
const normalizeOrigin = (origin) => {
|
|
13
|
+
const withScheme = /^https?:\/\//i.test(origin) ? origin : `https://${origin}`;
|
|
14
|
+
return withScheme.replace(/\/+$/, '');
|
|
15
|
+
};
|
|
16
|
+
export const fetchManifest = async (origin) => {
|
|
17
|
+
const base = normalizeOrigin(origin);
|
|
18
|
+
for (const path of MANIFEST_PATHS) {
|
|
19
|
+
const response = await fetch(`${base}${path}`);
|
|
20
|
+
if (!response.ok)
|
|
21
|
+
continue;
|
|
22
|
+
const manifest = parseManifest(await response.json());
|
|
23
|
+
return {
|
|
24
|
+
manifest,
|
|
25
|
+
manifestHash: manifestHash(manifest),
|
|
26
|
+
dappKey: dappKey(manifest.domain),
|
|
27
|
+
origin: base,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`No valid release manifest found at ${base}`);
|
|
31
|
+
};
|
package/dist/hash.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hash helpers binding a release to a deterministic identifier.
|
|
3
|
+
*
|
|
4
|
+
* - `manifestHash` is the unit a release is proven and endorsed against.
|
|
5
|
+
* - `dappKey` is the stable per-application key (the timeline subject),
|
|
6
|
+
* derived from the domain. When an on-chain registry later owns dapp
|
|
7
|
+
* identity, `dappKey` switches to the registry identifier; consumers treat
|
|
8
|
+
* it as an opaque 32-byte value, so the signature does not change.
|
|
9
|
+
*/
|
|
10
|
+
import { type Hex } from 'viem';
|
|
11
|
+
import type { ReleaseManifest } from './schema.js';
|
|
12
|
+
/** keccak256 of the canonical-JSON bytes of the manifest. */
|
|
13
|
+
export declare const manifestHash: (manifest: ReleaseManifest) => Hex;
|
|
14
|
+
/** keccak256 of the lowercased domain, encoded as hex. */
|
|
15
|
+
export declare const dappKey: (domain: string) => Hex;
|
package/dist/hash.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hash helpers binding a release to a deterministic identifier.
|
|
3
|
+
*
|
|
4
|
+
* - `manifestHash` is the unit a release is proven and endorsed against.
|
|
5
|
+
* - `dappKey` is the stable per-application key (the timeline subject),
|
|
6
|
+
* derived from the domain. When an on-chain registry later owns dapp
|
|
7
|
+
* identity, `dappKey` switches to the registry identifier; consumers treat
|
|
8
|
+
* it as an opaque 32-byte value, so the signature does not change.
|
|
9
|
+
*/
|
|
10
|
+
import { keccak256, stringToHex } from 'viem';
|
|
11
|
+
import { canonicalize } from './canonicalize.js';
|
|
12
|
+
/** keccak256 of the canonical-JSON bytes of the manifest. */
|
|
13
|
+
export const manifestHash = (manifest) => keccak256(canonicalize(manifest));
|
|
14
|
+
/** keccak256 of the lowercased domain, encoded as hex. */
|
|
15
|
+
export const dappKey = (domain) => keccak256(stringToHex(domain.toLowerCase()));
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @provex/manifest — release-manifest standard helpers.
|
|
3
|
+
* The shared contract guaranteeing the browser, gate, indexer, and UI derive
|
|
4
|
+
* identical hashes for the same release.
|
|
5
|
+
*/
|
|
6
|
+
export { parseManifest, ReleaseManifestSchema, type ReleaseManifest } from './schema.js';
|
|
7
|
+
export { canonicalize } from './canonicalize.js';
|
|
8
|
+
export { manifestHash, dappKey } from './hash.js';
|
|
9
|
+
export { fetchManifest, type FetchedManifest } from './fetch.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @provex/manifest — release-manifest standard helpers.
|
|
3
|
+
* The shared contract guaranteeing the browser, gate, indexer, and UI derive
|
|
4
|
+
* identical hashes for the same release.
|
|
5
|
+
*/
|
|
6
|
+
export { parseManifest, ReleaseManifestSchema } from './schema.js';
|
|
7
|
+
export { canonicalize } from './canonicalize.js';
|
|
8
|
+
export { manifestHash, dappKey } from './hash.js';
|
|
9
|
+
export { fetchManifest } from './fetch.js';
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release-manifest schema and parser.
|
|
3
|
+
*
|
|
4
|
+
* A release manifest is the standardized `version.json` a decentralized
|
|
5
|
+
* application serves to declare a release. The schema is strict: unknown
|
|
6
|
+
* fields are rejected so the hashed value space stays tight and predictable.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
export declare const ReleaseManifestSchema: z.ZodObject<{
|
|
10
|
+
schema: z.ZodLiteral<"provex-release/1">;
|
|
11
|
+
name: z.ZodString;
|
|
12
|
+
domain: z.ZodString;
|
|
13
|
+
version: z.ZodString;
|
|
14
|
+
released: z.ZodString;
|
|
15
|
+
frontend: z.ZodOptional<z.ZodObject<{
|
|
16
|
+
cid: z.ZodString;
|
|
17
|
+
}, "strict", z.ZodTypeAny, {
|
|
18
|
+
cid: string;
|
|
19
|
+
}, {
|
|
20
|
+
cid: string;
|
|
21
|
+
}>>;
|
|
22
|
+
contracts: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
23
|
+
chain: z.ZodNumber;
|
|
24
|
+
address: z.ZodString;
|
|
25
|
+
codeHash: z.ZodOptional<z.ZodString>;
|
|
26
|
+
}, "strict", z.ZodTypeAny, {
|
|
27
|
+
chain: number;
|
|
28
|
+
address: string;
|
|
29
|
+
codeHash?: string | undefined;
|
|
30
|
+
}, {
|
|
31
|
+
chain: number;
|
|
32
|
+
address: string;
|
|
33
|
+
codeHash?: string | undefined;
|
|
34
|
+
}>, "many">>;
|
|
35
|
+
links: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
36
|
+
}, "strict", z.ZodTypeAny, {
|
|
37
|
+
schema: "provex-release/1";
|
|
38
|
+
name: string;
|
|
39
|
+
domain: string;
|
|
40
|
+
version: string;
|
|
41
|
+
released: string;
|
|
42
|
+
frontend?: {
|
|
43
|
+
cid: string;
|
|
44
|
+
} | undefined;
|
|
45
|
+
contracts?: {
|
|
46
|
+
chain: number;
|
|
47
|
+
address: string;
|
|
48
|
+
codeHash?: string | undefined;
|
|
49
|
+
}[] | undefined;
|
|
50
|
+
links?: Record<string, string> | undefined;
|
|
51
|
+
}, {
|
|
52
|
+
schema: "provex-release/1";
|
|
53
|
+
name: string;
|
|
54
|
+
domain: string;
|
|
55
|
+
version: string;
|
|
56
|
+
released: string;
|
|
57
|
+
frontend?: {
|
|
58
|
+
cid: string;
|
|
59
|
+
} | undefined;
|
|
60
|
+
contracts?: {
|
|
61
|
+
chain: number;
|
|
62
|
+
address: string;
|
|
63
|
+
codeHash?: string | undefined;
|
|
64
|
+
}[] | undefined;
|
|
65
|
+
links?: Record<string, string> | undefined;
|
|
66
|
+
}>;
|
|
67
|
+
export type ReleaseManifest = z.infer<typeof ReleaseManifestSchema>;
|
|
68
|
+
/**
|
|
69
|
+
* Validate an unknown value into a {@link ReleaseManifest}.
|
|
70
|
+
* @throws ZodError if the value is not a well-formed manifest.
|
|
71
|
+
*/
|
|
72
|
+
export declare const parseManifest: (input: unknown) => ReleaseManifest;
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release-manifest schema and parser.
|
|
3
|
+
*
|
|
4
|
+
* A release manifest is the standardized `version.json` a decentralized
|
|
5
|
+
* application serves to declare a release. The schema is strict: unknown
|
|
6
|
+
* fields are rejected so the hashed value space stays tight and predictable.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
const hostnameSchema = z
|
|
10
|
+
.string()
|
|
11
|
+
.regex(/^[a-z0-9.-]+$/i, 'Expected a bare hostname, e.g. "pulsex.com"');
|
|
12
|
+
const addressSchema = z
|
|
13
|
+
.string()
|
|
14
|
+
.regex(/^0x[0-9a-fA-F]{40}$/, 'Expected a 0x-prefixed 20-byte address');
|
|
15
|
+
const codeHashSchema = z
|
|
16
|
+
.string()
|
|
17
|
+
.regex(/^0x[0-9a-fA-F]{64}$/, 'Expected a 0x-prefixed 32-byte hash');
|
|
18
|
+
const contractSchema = z
|
|
19
|
+
.object({
|
|
20
|
+
chain: z.number().int().positive(),
|
|
21
|
+
address: addressSchema,
|
|
22
|
+
codeHash: codeHashSchema.optional(),
|
|
23
|
+
})
|
|
24
|
+
.strict();
|
|
25
|
+
export const ReleaseManifestSchema = z
|
|
26
|
+
.object({
|
|
27
|
+
schema: z.literal('provex-release/1'),
|
|
28
|
+
name: z.string().min(1),
|
|
29
|
+
domain: hostnameSchema,
|
|
30
|
+
version: z.string().min(1),
|
|
31
|
+
released: z.string().datetime(), // ISO-8601, UTC ("Z"); offsets rejected
|
|
32
|
+
frontend: z.object({ cid: z.string().min(1) }).strict().optional(),
|
|
33
|
+
contracts: z.array(contractSchema).optional(),
|
|
34
|
+
links: z.record(z.string()).optional(),
|
|
35
|
+
})
|
|
36
|
+
.strict();
|
|
37
|
+
/**
|
|
38
|
+
* Validate an unknown value into a {@link ReleaseManifest}.
|
|
39
|
+
* @throws ZodError if the value is not a well-formed manifest.
|
|
40
|
+
*/
|
|
41
|
+
export const parseManifest = (input) => ReleaseManifestSchema.parse(input);
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@provex/manifest",
|
|
3
|
+
"version": "1.9.2-rc.20260719220119.92e7405",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/provex-tech/monorepo.git",
|
|
10
|
+
"directory": "packages/manifest"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"test": "node --import tsx --test 'src/**/*.test.ts'",
|
|
24
|
+
"test:watch": "node --import tsx --test --watch 'src/**/*.test.ts'"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"zod": "^3.24.1"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"viem": "^2.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"tsx": "^4.20.3",
|
|
39
|
+
"typescript": "^5.9.2",
|
|
40
|
+
"viem": "^2.38.0"
|
|
41
|
+
}
|
|
42
|
+
}
|