@newton-xyz/policy-pack-shared 0.1.1 → 0.3.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/dist/index.cjs +32 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -7
- package/dist/index.d.ts +97 -7
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -21,10 +21,36 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
UnsupportedChainError: () => UnsupportedChainError,
|
|
24
|
-
|
|
24
|
+
decodePolicyParams: () => decodePolicyParams,
|
|
25
|
+
encodePolicyParams: () => encodePolicyParams,
|
|
26
|
+
getDeployment: () => getDeployment,
|
|
27
|
+
wrapOutput: () => wrapOutput
|
|
25
28
|
});
|
|
26
29
|
module.exports = __toCommonJS(index_exports);
|
|
27
30
|
|
|
31
|
+
// src/encoding.ts
|
|
32
|
+
var import_viem = require("viem");
|
|
33
|
+
function sortKeysDeep(value) {
|
|
34
|
+
if (Array.isArray(value)) return value.map(sortKeysDeep);
|
|
35
|
+
if (value && typeof value === "object") {
|
|
36
|
+
const obj = value;
|
|
37
|
+
const sorted = {};
|
|
38
|
+
for (const key of Object.keys(obj).sort()) {
|
|
39
|
+
sorted[key] = sortKeysDeep(obj[key]);
|
|
40
|
+
}
|
|
41
|
+
return sorted;
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
function encodePolicyParams(pack, params) {
|
|
46
|
+
const validated = pack.paramsSchema.parse(params);
|
|
47
|
+
return (0, import_viem.toHex)(JSON.stringify(sortKeysDeep(validated)));
|
|
48
|
+
}
|
|
49
|
+
function decodePolicyParams(pack, encoded) {
|
|
50
|
+
const json = new TextDecoder("utf-8", { fatal: true }).decode((0, import_viem.hexToBytes)(encoded));
|
|
51
|
+
return pack.paramsSchema.parse(JSON.parse(json));
|
|
52
|
+
}
|
|
53
|
+
|
|
28
54
|
// src/pack.ts
|
|
29
55
|
function getDeployment(pack, chainId) {
|
|
30
56
|
const deployment = pack.deployments[chainId];
|
|
@@ -51,4 +77,9 @@ var UnsupportedChainError = class extends Error {
|
|
|
51
77
|
supportedChainIds;
|
|
52
78
|
name = "UnsupportedChainError";
|
|
53
79
|
};
|
|
80
|
+
|
|
81
|
+
// src/wrap.ts
|
|
82
|
+
function wrapOutput(packId, valueOrError) {
|
|
83
|
+
return JSON.stringify({ [packId]: valueOrError });
|
|
84
|
+
}
|
|
54
85
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/pack.ts"],"sourcesContent":["export type { ChainId, Deployment } from \"./deployment\";\nexport type {\n\tPolicyPack,\n\tPrepareQueryArgs,\n\tPrepareQueryResult,\n} from \"./pack\";\nexport {\n\tgetDeployment,\n\tUnsupportedChainError,\n} from \"./pack\";\n","import type { Address, Hex, PublicClient } from \"viem\";\nimport type { z } from \"zod\";\nimport type { ChainId, Deployment } from \"./deployment\";\n\n/**\n * Inputs that a pack's `prepareQuery` reads at intent-build time.\n *\n * The Shield SDK passes a viem `PublicClient` (so the pack can read on-chain\n * state) and the vault address the curator is acting on. Packs that don't\n * need on-chain state can ignore both — `prepareQuery` is optional.\n */\nexport interface PrepareQueryArgs {\n\treadonly publicClient: PublicClient;\n\treadonly vault: Address;\n}\n\n/**\n * `wasmArgs` payload (untyped at this layer; each pack narrows it via its own\n * `WasmArgsSchema`) plus an optional pre-image hash for binding the AVS-side\n * evaluation to a specific on-chain state. VaultsFYI uses this to bind the\n * evaluation to a `keccak(supplyQueue)` snapshot so the policy can reject\n * attestations whose underlying allocation has shifted between intent build\n * and on-chain submission.\n */\nexport interface PrepareQueryResult<TWasmArgs> {\n\treadonly wasmArgs: TWasmArgs;\n\treadonly freshnessHash?: Hex;\n}\n\n/**\n * Canonical typed contract every published `@newton-xyz/policy-pack-<name>`\n * package implements. `@newton-xyz/newton-shield-sdk`'s `createShield(...)`\n * accepts `PolicyPack<P, W, S>` as the curator's pack argument.\n *\n * Type parameters:\n * - `TParams` — the shape stored on-chain in `NewtonPolicyData.policyParams`\n * (e.g. risk envelope thresholds for VaultsFYI).\n * - `TWasmArgs` — the shape passed to the policy's WASM oracle at evaluation\n * time (e.g. `{ vault, network, lastKnownAllocationHash }`).\n * - `TSecrets` — required API credentials uploaded before any run/sim.\n *\n * The fields:\n * - `id` — stable identifier of the form `<pack>/<purpose>/<version>`,\n * e.g. `vaultsfyi/risk-envelope/v1`. Used for telemetry\n * and for cross-referencing the `policy_metadata.json`.\n * - `paramsSchema` — zod schema enforced at curator setup time when the\n * pack is bound to a `NewtonPolicyData`.\n * - `wasmArgsSchema` — zod schema enforced per call when the SDK builds the\n * intent and forwards `wasmArgs` to the gateway.\n * - `secretsSchema` — zod schema enforced at upload-time. Validates the\n * shape of the secrets the operator stores in the AVS.\n * - `encodeParams` / `decodeParams` — ABI round-trip for the on-chain\n * `policyParams` bytes. Must round-trip cleanly so the\n * SDK can read the on-chain value back and confirm it\n * matches the curator's intended config.\n * - `prepareQuery` — optional. When present, the SDK invokes it on every\n * call to gather chain-state freshness inputs. Packs\n * that don't need this (e.g. KYC-only packs) omit it.\n * The optional second `options` argument is a\n * pack-typed escape hatch for per-call overrides —\n * e.g. VaultsFYI's `previousAllocationHash` for\n * freshness binding. Each concrete pack narrows it\n * via its own `prepareQuery` signature; the shared\n * interface keeps it `unknown` so the SDK can\n * forward it verbatim.\n * - `deployments` — `chainId → Deployment` map sliced from the upstream\n * `deployments.json` for this pack only. Typed as\n * `Partial<Record<ChainId, Deployment>>` so callers\n * must handle `undefined` for unsupported chains\n * rather than silently reading `.policy` off nothing.\n * Use `getDeployment(pack, chainId)` from this package\n * for the safe lookup.\n * - `metadata` — static identity from the pack's `policy_metadata.json`.\n */\nexport interface PolicyPack<TParams, TWasmArgs, TSecrets> {\n\treadonly id: string;\n\treadonly paramsSchema: z.ZodType<TParams>;\n\treadonly wasmArgsSchema: z.ZodType<TWasmArgs>;\n\treadonly secretsSchema: z.ZodType<TSecrets>;\n\tencodeParams(params: TParams): Hex;\n\tdecodeParams(encoded: Hex): TParams;\n\tprepareQuery?(args: PrepareQueryArgs, options?: unknown): Promise<PrepareQueryResult<TWasmArgs>>;\n\treadonly deployments: Readonly<Partial<Record<ChainId, Deployment>>>;\n\treadonly metadata: {\n\t\treadonly name: string;\n\t\treadonly version: string;\n\t\treadonly description: string;\n\t\treadonly author?: string;\n\t\treadonly link?: string;\n\t};\n}\n\n/**\n * Safe lookup helper. Returns the `Deployment` for `chainId` if the pack is\n * deployed on that chain, or throws `UnsupportedChainError` with the list of\n * chain ids the pack is known to support. Use this at every SDK callsite that\n * reads `pack.deployments[chainId]` so unsupported-chain failures surface\n * immediately rather than as `undefined.policy` further down.\n */\nexport function getDeployment<TParams, TWasmArgs, TSecrets>(\n\tpack: PolicyPack<TParams, TWasmArgs, TSecrets>,\n\tchainId: ChainId,\n): Deployment {\n\tconst deployment = pack.deployments[chainId];\n\tif (!deployment) {\n\t\tconst supported = Object.keys(pack.deployments).sort().join(\", \") || \"(none)\";\n\t\tthrow new UnsupportedChainError(\n\t\t\t`Pack \\`${pack.id}\\` is not deployed on chain ${chainId}. Supported: ${supported}.`,\n\t\t\tpack.id,\n\t\t\tchainId,\n\t\t\tObject.keys(pack.deployments),\n\t\t);\n\t}\n\treturn deployment;\n}\n\n/**\n * Thrown by `getDeployment` when a pack is asked for a chain it isn't\n * deployed on. SDK consumers can catch this specifically to surface a\n * curator-friendly error rather than a `TypeError: Cannot read property\n * 'policy' of undefined`.\n */\nexport class UnsupportedChainError extends Error {\n\toverride readonly name = \"UnsupportedChainError\";\n\tconstructor(\n\t\tmessage: string,\n\t\treadonly packId: string,\n\t\treadonly chainId: ChainId,\n\t\treadonly supportedChainIds: ReadonlyArray<ChainId>,\n\t) {\n\t\tsuper(message);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmGO,SAAS,cACf,MACA,SACa;AACb,QAAM,aAAa,KAAK,YAAY,OAAO;AAC3C,MAAI,CAAC,YAAY;AAChB,UAAM,YAAY,OAAO,KAAK,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK;AACrE,UAAM,IAAI;AAAA,MACT,UAAU,KAAK,EAAE,+BAA+B,OAAO,gBAAgB,SAAS;AAAA,MAChF,KAAK;AAAA,MACL;AAAA,MACA,OAAO,KAAK,KAAK,WAAW;AAAA,IAC7B;AAAA,EACD;AACA,SAAO;AACR;AAQO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAEhD,YACC,SACS,QACA,SACA,mBACR;AACD,UAAM,OAAO;AAJJ;AACA;AACA;AAAA,EAGV;AAAA,EALU;AAAA,EACA;AAAA,EACA;AAAA,EALQ,OAAO;AAS1B;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/encoding.ts","../src/pack.ts","../src/wrap.ts"],"sourcesContent":["export type { ChainId, Deployment } from \"./deployment\";\nexport { decodePolicyParams, encodePolicyParams } from \"./encoding\";\nexport type {\n\tPolicyPack,\n\tPrepareQueryArgs,\n\tPrepareQueryResult,\n} from \"./pack\";\nexport {\n\tgetDeployment,\n\tUnsupportedChainError,\n} from \"./pack\";\nexport { wrapOutput } from \"./wrap\";\n","import { type Hex, hexToBytes, toHex } from \"viem\";\nimport type { z } from \"zod\";\n\n/**\n * The on-chain `policyParams` byte format is a Newton-protocol invariant. The\n * AVS host reads it as `String::from_utf8 → serde_json::from_str` (see\n * `newton-prover-avs/crates/core/src/common/task.rs:402-408`); the SDK must\n * therefore write **UTF-8 JSON**. Keys are sorted recursively so two\n * semantically-equal params objects always produce byte-identical output —\n * the SDK's `verifyPolicyBinding` does a byte-equality check against\n * `getPolicyConfig().policyParams`, which would otherwise depend on JS\n * `JSON.stringify` insertion order.\n */\nfunction sortKeysDeep(value: unknown): unknown {\n\tif (Array.isArray(value)) return value.map(sortKeysDeep);\n\tif (value && typeof value === \"object\") {\n\t\tconst obj = value as Record<string, unknown>;\n\t\tconst sorted: Record<string, unknown> = {};\n\t\tfor (const key of Object.keys(obj).sort()) {\n\t\t\tsorted[key] = sortKeysDeep(obj[key]);\n\t\t}\n\t\treturn sorted;\n\t}\n\treturn value;\n}\n\n/**\n * Encode a pack's params for on-chain storage. Validates against the pack's\n * `paramsSchema` so a curator typo throws here rather than silently writing\n * AVS-rejecting bytes. Returns `Hex` so it drops directly into a viem\n * `setPolicy(bytes)` call.\n */\nexport function encodePolicyParams<T>(\n\tpack: { readonly paramsSchema: z.ZodType<T> },\n\tparams: T,\n): Hex {\n\tconst validated = pack.paramsSchema.parse(params);\n\treturn toHex(JSON.stringify(sortKeysDeep(validated)));\n}\n\n/**\n * Decode `policyParams` bytes read from `getPolicyConfig().policyParams` back\n * into the pack's typed params shape. Revalidates against `paramsSchema` so a\n * stale or corrupted on-chain blob throws at the SDK boundary rather than\n * yielding a partially-valid object that crashes deeper in the call.\n *\n * `fatal: true` matches AVS-side `String::from_utf8` behavior — invalid UTF-8\n * throws here rather than silently becoming U+FFFD and diverging from a path\n * the AVS would reject.\n */\nexport function decodePolicyParams<T>(\n\tpack: { readonly paramsSchema: z.ZodType<T> },\n\tencoded: Hex,\n): T {\n\tconst json = new TextDecoder(\"utf-8\", { fatal: true }).decode(hexToBytes(encoded));\n\treturn pack.paramsSchema.parse(JSON.parse(json));\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\nimport type { z } from \"zod\";\nimport type { ChainId, Deployment } from \"./deployment\";\n\n/**\n * Inputs that a pack's `prepareQuery` reads at intent-build time.\n *\n * The Shield SDK passes a viem `PublicClient` (so the pack can read on-chain\n * state) and the vault address the curator is acting on. Packs that don't\n * need on-chain state can ignore both — `prepareQuery` is optional.\n */\nexport interface PrepareQueryArgs {\n\treadonly publicClient: PublicClient;\n\treadonly vault: Address;\n}\n\n/**\n * `wasmArgs` payload (untyped at this layer; each pack narrows it via its own\n * `WasmArgsSchema`) plus an optional pre-image hash for binding the AVS-side\n * evaluation to a specific on-chain state. VaultsFYI uses this to bind the\n * evaluation to a `keccak(supplyQueue)` snapshot so the policy can reject\n * attestations whose underlying allocation has shifted between intent build\n * and on-chain submission.\n */\nexport interface PrepareQueryResult<TWasmArgs> {\n\treadonly wasmArgs: TWasmArgs;\n\treadonly freshnessHash?: Hex;\n}\n\n/**\n * Canonical typed contract every published `@newton-xyz/policy-pack-<name>`\n * package implements. `@newton-xyz/newton-shield-sdk`'s `createShield(...)`\n * accepts `PolicyPack<P, W, S>` as the curator's pack argument.\n *\n * Type parameters:\n * - `TParams` — the shape stored on-chain in `NewtonPolicyData.policyParams`\n * (e.g. risk envelope thresholds for VaultsFYI).\n * - `TWasmArgs` — the shape passed to the policy's WASM oracle at evaluation\n * time (e.g. `{ vault, network, lastKnownAllocationHash }`).\n * - `TSecrets` — required API credentials uploaded before any run/sim.\n *\n * The fields:\n * - `id` — stable identifier of the form `<pack>/<purpose>/<version>`,\n * e.g. `vaultsfyi/risk-envelope/v1`. Used for telemetry\n * and for cross-referencing the `policy_metadata.json`.\n * - `paramsSchema` — zod schema enforced at curator setup time when the\n * pack is bound to a `NewtonPolicyData`.\n * - `wasmArgsSchema` — zod schema enforced per call when the SDK builds the\n * intent and forwards `wasmArgs` to the gateway.\n * - `secretsSchema` — zod schema enforced at upload-time. Validates the\n * shape of the secrets the operator stores in the AVS.\n *\n * Encoding is *not* a per-pack concern. The on-chain `policyParams` byte\n * format is a Newton-protocol invariant — UTF-8 JSON, sorted keys —\n * implemented once in this package as `encodePolicyParams` /\n * `decodePolicyParams`. See `./encoding.ts`. Earlier shapes of this\n * interface required each pack to ship its own `encodeParams` /\n * `decodeParams`; that structurally invited drift (vaultsfyi@0.2.0 shipped\n * ABI bytes against an AVS that reads `serde_json::from_str`, breaking\n * every call). The interface now leaves byte-format to the protocol.\n *\n * - `prepareQuery` — optional. When present, the SDK invokes it on every\n * call to gather chain-state freshness inputs. Packs\n * that don't need this (e.g. KYC-only packs) omit it.\n * The optional second `options` argument is a\n * pack-typed escape hatch for per-call overrides —\n * e.g. VaultsFYI's `previousAllocationHash` for\n * freshness binding. Each concrete pack narrows it\n * via its own `prepareQuery` signature; the shared\n * interface keeps it `unknown` so the SDK can\n * forward it verbatim.\n * - `deployments` — `chainId → Deployment` map sliced from the upstream\n * `deployments.json` for this pack only. Typed as\n * `Partial<Record<ChainId, Deployment>>` so callers\n * must handle `undefined` for unsupported chains\n * rather than silently reading `.policy` off nothing.\n * Use `getDeployment(pack, chainId)` from this package\n * for the safe lookup.\n * - `metadata` — static identity from the pack's `policy_metadata.json`.\n */\nexport interface PolicyPack<TParams, TWasmArgs, TSecrets> {\n\treadonly id: string;\n\treadonly paramsSchema: z.ZodType<TParams>;\n\treadonly wasmArgsSchema: z.ZodType<TWasmArgs>;\n\treadonly secretsSchema: z.ZodType<TSecrets>;\n\tprepareQuery?(args: PrepareQueryArgs, options?: unknown): Promise<PrepareQueryResult<TWasmArgs>>;\n\treadonly deployments: Readonly<Partial<Record<ChainId, Deployment>>>;\n\treadonly metadata: {\n\t\treadonly name: string;\n\t\treadonly version: string;\n\t\treadonly description: string;\n\t\treadonly author?: string;\n\t\treadonly link?: string;\n\t};\n}\n\n/**\n * Safe lookup helper. Returns the `Deployment` for `chainId` if the pack is\n * deployed on that chain, or throws `UnsupportedChainError` with the list of\n * chain ids the pack is known to support. Use this at every SDK callsite that\n * reads `pack.deployments[chainId]` so unsupported-chain failures surface\n * immediately rather than as `undefined.policy` further down.\n */\nexport function getDeployment<TParams, TWasmArgs, TSecrets>(\n\tpack: PolicyPack<TParams, TWasmArgs, TSecrets>,\n\tchainId: ChainId,\n): Deployment {\n\tconst deployment = pack.deployments[chainId];\n\tif (!deployment) {\n\t\tconst supported = Object.keys(pack.deployments).sort().join(\", \") || \"(none)\";\n\t\tthrow new UnsupportedChainError(\n\t\t\t`Pack \\`${pack.id}\\` is not deployed on chain ${chainId}. Supported: ${supported}.`,\n\t\t\tpack.id,\n\t\t\tchainId,\n\t\t\tObject.keys(pack.deployments),\n\t\t);\n\t}\n\treturn deployment;\n}\n\n/**\n * Thrown by `getDeployment` when a pack is asked for a chain it isn't\n * deployed on. SDK consumers can catch this specifically to surface a\n * curator-friendly error rather than a `TypeError: Cannot read property\n * 'policy' of undefined`.\n */\nexport class UnsupportedChainError extends Error {\n\toverride readonly name = \"UnsupportedChainError\";\n\tconstructor(\n\t\tmessage: string,\n\t\treadonly packId: string,\n\t\treadonly chainId: ChainId,\n\t\treadonly supportedChainIds: ReadonlyArray<ChainId>,\n\t) {\n\t\tsuper(message);\n\t}\n}\n","/**\n * Wrap a pack's WASM oracle output under its top-level `PACK_ID` key. Every\n * `policy.js` MUST call this on every return path (success AND error) so the\n * AVS-side `merge_jsons` (`newton-prover-avs/crates/operator/src/simulation.rs:296`)\n * composes cleanly across packs without top-level key collisions.\n *\n * The merge is shallow + last-wins: pre-namespacing, vaultsfyi's\n * `risk_score: number` would silently clobber chainalysis's `risk_score: string`\n * under composition. After namespacing, every pack's output sits under its\n * unique `PACK_ID` key and merges into a single `data.wasm` blob that\n * composite Rego can reference unambiguously as `data.wasm.<pack-id>.<field>`.\n *\n * The contract this helper locks:\n * - **Output is JSON-stringified** so `policy.js` can `return wrapOutput(...)`\n * directly. The AVS host parses every PolicyData WASM's stdout as UTF-8\n * JSON before merging.\n * - **Top-level keys are exactly `[packId]`** — assertable in\n * `<pack>/wrapping_test.rego` with a fixture `data.wasm.<pack-id>` shape.\n * To be enforced at PR time by an AST-lint CI guard (Phase 0 § Stream C,\n * shipped in a separate PR) that flags any raw `return JSON.stringify(...)`\n * callsite in `<pack>/policy.js` not routed through this helper.\n * - **`undefined` and other JSON-nonrepresentable payloads** (functions,\n * symbols) cause `JSON.stringify` to omit the key, returning `'{}'` rather\n * than `{ [packId]: undefined }`. The AVS contract doesn't admit such\n * payloads — every `policy.js` returns either a structured success object\n * or `{ error: \"...\" }` — so this is documented as out-of-contract input\n * rather than guarded at runtime. The Stream C AST-lint catches the\n * shape upstream.\n * - **Both success and error paths route through here.** Returning\n * `{\"error\": \"...\"}` directly from `policy.js` would collide across packs in\n * `merge_jsons` (every pack's error key would land at the top level and\n * the last one wins). Wrapping the error under `[packId]` keeps per-pack\n * error semantics (composite Rego can selectively deny on\n * `data.wasm.<pack-id>.error`).\n *\n * @param packId The pack's stable id, matching the `<name>OracleModule.id`\n * that ships in Phase 1 and the `KNOWN_PACK_IDS` registry in\n * Phase 2. Convention: lowercase single-word, matches the\n * pack's folder name (e.g. `\"vaultsfyi\"`, `\"chainalysis\"`).\n * @param valueOrError The pack's output payload — `{ score, risk_score, ... }`\n * for success, `{ error: \"...\" }` for failure paths.\n * Untyped at this layer because each pack's WASM emits\n * its own shape and the `PolicyPack` / `OracleModule`\n * interfaces today carry input schemas (`paramsSchema`,\n * `wasmArgsSchema`, `secretsSchema`) but no on-chain\n * output schema — composite Rego references fields by\n * name (`data.wasm.<pack-id>.<field>`) and trusts the\n * pack-specific WASM bindings paired to each\n * `wasm_cid` in the manifest (Phase 1.5).\n * @returns JSON-stringified `{ [packId]: valueOrError }` ready to drop into\n * a `policy.js` `return` statement.\n *\n * @example\n * // vaultsfyi/policy.js (success path)\n * return wrapOutput(\"vaultsfyi\", { score: 80, risk_score: 75, timestamp });\n * // → '{\"vaultsfyi\":{\"score\":80,\"risk_score\":75,\"timestamp\":...}}'\n *\n * // vaultsfyi/policy.js (error path)\n * return wrapOutput(\"vaultsfyi\", { error: String(e) });\n * // → '{\"vaultsfyi\":{\"error\":\"...\"}}' — namespaced, won't collide\n */\nexport function wrapOutput(packId: string, valueOrError: unknown): string {\n\treturn JSON.stringify({ [packId]: valueOrError });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA4C;AAa5C,SAAS,aAAa,OAAyB;AAC9C,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,YAAY;AACvD,MAAI,SAAS,OAAO,UAAU,UAAU;AACvC,UAAM,MAAM;AACZ,UAAM,SAAkC,CAAC;AACzC,eAAW,OAAO,OAAO,KAAK,GAAG,EAAE,KAAK,GAAG;AAC1C,aAAO,GAAG,IAAI,aAAa,IAAI,GAAG,CAAC;AAAA,IACpC;AACA,WAAO;AAAA,EACR;AACA,SAAO;AACR;AAQO,SAAS,mBACf,MACA,QACM;AACN,QAAM,YAAY,KAAK,aAAa,MAAM,MAAM;AAChD,aAAO,mBAAM,KAAK,UAAU,aAAa,SAAS,CAAC,CAAC;AACrD;AAYO,SAAS,mBACf,MACA,SACI;AACJ,QAAM,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,WAAO,wBAAW,OAAO,CAAC;AACjF,SAAO,KAAK,aAAa,MAAM,KAAK,MAAM,IAAI,CAAC;AAChD;;;AC+CO,SAAS,cACf,MACA,SACa;AACb,QAAM,aAAa,KAAK,YAAY,OAAO;AAC3C,MAAI,CAAC,YAAY;AAChB,UAAM,YAAY,OAAO,KAAK,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK;AACrE,UAAM,IAAI;AAAA,MACT,UAAU,KAAK,EAAE,+BAA+B,OAAO,gBAAgB,SAAS;AAAA,MAChF,KAAK;AAAA,MACL;AAAA,MACA,OAAO,KAAK,KAAK,WAAW;AAAA,IAC7B;AAAA,EACD;AACA,SAAO;AACR;AAQO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAEhD,YACC,SACS,QACA,SACA,mBACR;AACD,UAAM,OAAO;AAJJ;AACA;AACA;AAAA,EAGV;AAAA,EALU;AAAA,EACA;AAAA,EACA;AAAA,EALQ,OAAO;AAS1B;;;AC3EO,SAAS,WAAW,QAAgB,cAA+B;AACzE,SAAO,KAAK,UAAU,EAAE,CAAC,MAAM,GAAG,aAAa,CAAC;AACjD;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -39,6 +39,29 @@ interface Deployment {
|
|
|
39
39
|
readonly notes?: string;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Encode a pack's params for on-chain storage. Validates against the pack's
|
|
44
|
+
* `paramsSchema` so a curator typo throws here rather than silently writing
|
|
45
|
+
* AVS-rejecting bytes. Returns `Hex` so it drops directly into a viem
|
|
46
|
+
* `setPolicy(bytes)` call.
|
|
47
|
+
*/
|
|
48
|
+
declare function encodePolicyParams<T>(pack: {
|
|
49
|
+
readonly paramsSchema: z.ZodType<T>;
|
|
50
|
+
}, params: T): Hex;
|
|
51
|
+
/**
|
|
52
|
+
* Decode `policyParams` bytes read from `getPolicyConfig().policyParams` back
|
|
53
|
+
* into the pack's typed params shape. Revalidates against `paramsSchema` so a
|
|
54
|
+
* stale or corrupted on-chain blob throws at the SDK boundary rather than
|
|
55
|
+
* yielding a partially-valid object that crashes deeper in the call.
|
|
56
|
+
*
|
|
57
|
+
* `fatal: true` matches AVS-side `String::from_utf8` behavior — invalid UTF-8
|
|
58
|
+
* throws here rather than silently becoming U+FFFD and diverging from a path
|
|
59
|
+
* the AVS would reject.
|
|
60
|
+
*/
|
|
61
|
+
declare function decodePolicyParams<T>(pack: {
|
|
62
|
+
readonly paramsSchema: z.ZodType<T>;
|
|
63
|
+
}, encoded: Hex): T;
|
|
64
|
+
|
|
42
65
|
/**
|
|
43
66
|
* Inputs that a pack's `prepareQuery` reads at intent-build time.
|
|
44
67
|
*
|
|
@@ -84,10 +107,16 @@ interface PrepareQueryResult<TWasmArgs> {
|
|
|
84
107
|
* intent and forwards `wasmArgs` to the gateway.
|
|
85
108
|
* - `secretsSchema` — zod schema enforced at upload-time. Validates the
|
|
86
109
|
* shape of the secrets the operator stores in the AVS.
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
110
|
+
*
|
|
111
|
+
* Encoding is *not* a per-pack concern. The on-chain `policyParams` byte
|
|
112
|
+
* format is a Newton-protocol invariant — UTF-8 JSON, sorted keys —
|
|
113
|
+
* implemented once in this package as `encodePolicyParams` /
|
|
114
|
+
* `decodePolicyParams`. See `./encoding.ts`. Earlier shapes of this
|
|
115
|
+
* interface required each pack to ship its own `encodeParams` /
|
|
116
|
+
* `decodeParams`; that structurally invited drift (vaultsfyi@0.2.0 shipped
|
|
117
|
+
* ABI bytes against an AVS that reads `serde_json::from_str`, breaking
|
|
118
|
+
* every call). The interface now leaves byte-format to the protocol.
|
|
119
|
+
*
|
|
91
120
|
* - `prepareQuery` — optional. When present, the SDK invokes it on every
|
|
92
121
|
* call to gather chain-state freshness inputs. Packs
|
|
93
122
|
* that don't need this (e.g. KYC-only packs) omit it.
|
|
@@ -112,8 +141,6 @@ interface PolicyPack<TParams, TWasmArgs, TSecrets> {
|
|
|
112
141
|
readonly paramsSchema: z.ZodType<TParams>;
|
|
113
142
|
readonly wasmArgsSchema: z.ZodType<TWasmArgs>;
|
|
114
143
|
readonly secretsSchema: z.ZodType<TSecrets>;
|
|
115
|
-
encodeParams(params: TParams): Hex;
|
|
116
|
-
decodeParams(encoded: Hex): TParams;
|
|
117
144
|
prepareQuery?(args: PrepareQueryArgs, options?: unknown): Promise<PrepareQueryResult<TWasmArgs>>;
|
|
118
145
|
readonly deployments: Readonly<Partial<Record<ChainId, Deployment>>>;
|
|
119
146
|
readonly metadata: {
|
|
@@ -146,4 +173,67 @@ declare class UnsupportedChainError extends Error {
|
|
|
146
173
|
constructor(message: string, packId: string, chainId: ChainId, supportedChainIds: ReadonlyArray<ChainId>);
|
|
147
174
|
}
|
|
148
175
|
|
|
149
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Wrap a pack's WASM oracle output under its top-level `PACK_ID` key. Every
|
|
178
|
+
* `policy.js` MUST call this on every return path (success AND error) so the
|
|
179
|
+
* AVS-side `merge_jsons` (`newton-prover-avs/crates/operator/src/simulation.rs:296`)
|
|
180
|
+
* composes cleanly across packs without top-level key collisions.
|
|
181
|
+
*
|
|
182
|
+
* The merge is shallow + last-wins: pre-namespacing, vaultsfyi's
|
|
183
|
+
* `risk_score: number` would silently clobber chainalysis's `risk_score: string`
|
|
184
|
+
* under composition. After namespacing, every pack's output sits under its
|
|
185
|
+
* unique `PACK_ID` key and merges into a single `data.wasm` blob that
|
|
186
|
+
* composite Rego can reference unambiguously as `data.wasm.<pack-id>.<field>`.
|
|
187
|
+
*
|
|
188
|
+
* The contract this helper locks:
|
|
189
|
+
* - **Output is JSON-stringified** so `policy.js` can `return wrapOutput(...)`
|
|
190
|
+
* directly. The AVS host parses every PolicyData WASM's stdout as UTF-8
|
|
191
|
+
* JSON before merging.
|
|
192
|
+
* - **Top-level keys are exactly `[packId]`** — assertable in
|
|
193
|
+
* `<pack>/wrapping_test.rego` with a fixture `data.wasm.<pack-id>` shape.
|
|
194
|
+
* To be enforced at PR time by an AST-lint CI guard (Phase 0 § Stream C,
|
|
195
|
+
* shipped in a separate PR) that flags any raw `return JSON.stringify(...)`
|
|
196
|
+
* callsite in `<pack>/policy.js` not routed through this helper.
|
|
197
|
+
* - **`undefined` and other JSON-nonrepresentable payloads** (functions,
|
|
198
|
+
* symbols) cause `JSON.stringify` to omit the key, returning `'{}'` rather
|
|
199
|
+
* than `{ [packId]: undefined }`. The AVS contract doesn't admit such
|
|
200
|
+
* payloads — every `policy.js` returns either a structured success object
|
|
201
|
+
* or `{ error: "..." }` — so this is documented as out-of-contract input
|
|
202
|
+
* rather than guarded at runtime. The Stream C AST-lint catches the
|
|
203
|
+
* shape upstream.
|
|
204
|
+
* - **Both success and error paths route through here.** Returning
|
|
205
|
+
* `{"error": "..."}` directly from `policy.js` would collide across packs in
|
|
206
|
+
* `merge_jsons` (every pack's error key would land at the top level and
|
|
207
|
+
* the last one wins). Wrapping the error under `[packId]` keeps per-pack
|
|
208
|
+
* error semantics (composite Rego can selectively deny on
|
|
209
|
+
* `data.wasm.<pack-id>.error`).
|
|
210
|
+
*
|
|
211
|
+
* @param packId The pack's stable id, matching the `<name>OracleModule.id`
|
|
212
|
+
* that ships in Phase 1 and the `KNOWN_PACK_IDS` registry in
|
|
213
|
+
* Phase 2. Convention: lowercase single-word, matches the
|
|
214
|
+
* pack's folder name (e.g. `"vaultsfyi"`, `"chainalysis"`).
|
|
215
|
+
* @param valueOrError The pack's output payload — `{ score, risk_score, ... }`
|
|
216
|
+
* for success, `{ error: "..." }` for failure paths.
|
|
217
|
+
* Untyped at this layer because each pack's WASM emits
|
|
218
|
+
* its own shape and the `PolicyPack` / `OracleModule`
|
|
219
|
+
* interfaces today carry input schemas (`paramsSchema`,
|
|
220
|
+
* `wasmArgsSchema`, `secretsSchema`) but no on-chain
|
|
221
|
+
* output schema — composite Rego references fields by
|
|
222
|
+
* name (`data.wasm.<pack-id>.<field>`) and trusts the
|
|
223
|
+
* pack-specific WASM bindings paired to each
|
|
224
|
+
* `wasm_cid` in the manifest (Phase 1.5).
|
|
225
|
+
* @returns JSON-stringified `{ [packId]: valueOrError }` ready to drop into
|
|
226
|
+
* a `policy.js` `return` statement.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* // vaultsfyi/policy.js (success path)
|
|
230
|
+
* return wrapOutput("vaultsfyi", { score: 80, risk_score: 75, timestamp });
|
|
231
|
+
* // → '{"vaultsfyi":{"score":80,"risk_score":75,"timestamp":...}}'
|
|
232
|
+
*
|
|
233
|
+
* // vaultsfyi/policy.js (error path)
|
|
234
|
+
* return wrapOutput("vaultsfyi", { error: String(e) });
|
|
235
|
+
* // → '{"vaultsfyi":{"error":"..."}}' — namespaced, won't collide
|
|
236
|
+
*/
|
|
237
|
+
declare function wrapOutput(packId: string, valueOrError: unknown): string;
|
|
238
|
+
|
|
239
|
+
export { type ChainId, type Deployment, type PolicyPack, type PrepareQueryArgs, type PrepareQueryResult, UnsupportedChainError, decodePolicyParams, encodePolicyParams, getDeployment, wrapOutput };
|
package/dist/index.d.ts
CHANGED
|
@@ -39,6 +39,29 @@ interface Deployment {
|
|
|
39
39
|
readonly notes?: string;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Encode a pack's params for on-chain storage. Validates against the pack's
|
|
44
|
+
* `paramsSchema` so a curator typo throws here rather than silently writing
|
|
45
|
+
* AVS-rejecting bytes. Returns `Hex` so it drops directly into a viem
|
|
46
|
+
* `setPolicy(bytes)` call.
|
|
47
|
+
*/
|
|
48
|
+
declare function encodePolicyParams<T>(pack: {
|
|
49
|
+
readonly paramsSchema: z.ZodType<T>;
|
|
50
|
+
}, params: T): Hex;
|
|
51
|
+
/**
|
|
52
|
+
* Decode `policyParams` bytes read from `getPolicyConfig().policyParams` back
|
|
53
|
+
* into the pack's typed params shape. Revalidates against `paramsSchema` so a
|
|
54
|
+
* stale or corrupted on-chain blob throws at the SDK boundary rather than
|
|
55
|
+
* yielding a partially-valid object that crashes deeper in the call.
|
|
56
|
+
*
|
|
57
|
+
* `fatal: true` matches AVS-side `String::from_utf8` behavior — invalid UTF-8
|
|
58
|
+
* throws here rather than silently becoming U+FFFD and diverging from a path
|
|
59
|
+
* the AVS would reject.
|
|
60
|
+
*/
|
|
61
|
+
declare function decodePolicyParams<T>(pack: {
|
|
62
|
+
readonly paramsSchema: z.ZodType<T>;
|
|
63
|
+
}, encoded: Hex): T;
|
|
64
|
+
|
|
42
65
|
/**
|
|
43
66
|
* Inputs that a pack's `prepareQuery` reads at intent-build time.
|
|
44
67
|
*
|
|
@@ -84,10 +107,16 @@ interface PrepareQueryResult<TWasmArgs> {
|
|
|
84
107
|
* intent and forwards `wasmArgs` to the gateway.
|
|
85
108
|
* - `secretsSchema` — zod schema enforced at upload-time. Validates the
|
|
86
109
|
* shape of the secrets the operator stores in the AVS.
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
110
|
+
*
|
|
111
|
+
* Encoding is *not* a per-pack concern. The on-chain `policyParams` byte
|
|
112
|
+
* format is a Newton-protocol invariant — UTF-8 JSON, sorted keys —
|
|
113
|
+
* implemented once in this package as `encodePolicyParams` /
|
|
114
|
+
* `decodePolicyParams`. See `./encoding.ts`. Earlier shapes of this
|
|
115
|
+
* interface required each pack to ship its own `encodeParams` /
|
|
116
|
+
* `decodeParams`; that structurally invited drift (vaultsfyi@0.2.0 shipped
|
|
117
|
+
* ABI bytes against an AVS that reads `serde_json::from_str`, breaking
|
|
118
|
+
* every call). The interface now leaves byte-format to the protocol.
|
|
119
|
+
*
|
|
91
120
|
* - `prepareQuery` — optional. When present, the SDK invokes it on every
|
|
92
121
|
* call to gather chain-state freshness inputs. Packs
|
|
93
122
|
* that don't need this (e.g. KYC-only packs) omit it.
|
|
@@ -112,8 +141,6 @@ interface PolicyPack<TParams, TWasmArgs, TSecrets> {
|
|
|
112
141
|
readonly paramsSchema: z.ZodType<TParams>;
|
|
113
142
|
readonly wasmArgsSchema: z.ZodType<TWasmArgs>;
|
|
114
143
|
readonly secretsSchema: z.ZodType<TSecrets>;
|
|
115
|
-
encodeParams(params: TParams): Hex;
|
|
116
|
-
decodeParams(encoded: Hex): TParams;
|
|
117
144
|
prepareQuery?(args: PrepareQueryArgs, options?: unknown): Promise<PrepareQueryResult<TWasmArgs>>;
|
|
118
145
|
readonly deployments: Readonly<Partial<Record<ChainId, Deployment>>>;
|
|
119
146
|
readonly metadata: {
|
|
@@ -146,4 +173,67 @@ declare class UnsupportedChainError extends Error {
|
|
|
146
173
|
constructor(message: string, packId: string, chainId: ChainId, supportedChainIds: ReadonlyArray<ChainId>);
|
|
147
174
|
}
|
|
148
175
|
|
|
149
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Wrap a pack's WASM oracle output under its top-level `PACK_ID` key. Every
|
|
178
|
+
* `policy.js` MUST call this on every return path (success AND error) so the
|
|
179
|
+
* AVS-side `merge_jsons` (`newton-prover-avs/crates/operator/src/simulation.rs:296`)
|
|
180
|
+
* composes cleanly across packs without top-level key collisions.
|
|
181
|
+
*
|
|
182
|
+
* The merge is shallow + last-wins: pre-namespacing, vaultsfyi's
|
|
183
|
+
* `risk_score: number` would silently clobber chainalysis's `risk_score: string`
|
|
184
|
+
* under composition. After namespacing, every pack's output sits under its
|
|
185
|
+
* unique `PACK_ID` key and merges into a single `data.wasm` blob that
|
|
186
|
+
* composite Rego can reference unambiguously as `data.wasm.<pack-id>.<field>`.
|
|
187
|
+
*
|
|
188
|
+
* The contract this helper locks:
|
|
189
|
+
* - **Output is JSON-stringified** so `policy.js` can `return wrapOutput(...)`
|
|
190
|
+
* directly. The AVS host parses every PolicyData WASM's stdout as UTF-8
|
|
191
|
+
* JSON before merging.
|
|
192
|
+
* - **Top-level keys are exactly `[packId]`** — assertable in
|
|
193
|
+
* `<pack>/wrapping_test.rego` with a fixture `data.wasm.<pack-id>` shape.
|
|
194
|
+
* To be enforced at PR time by an AST-lint CI guard (Phase 0 § Stream C,
|
|
195
|
+
* shipped in a separate PR) that flags any raw `return JSON.stringify(...)`
|
|
196
|
+
* callsite in `<pack>/policy.js` not routed through this helper.
|
|
197
|
+
* - **`undefined` and other JSON-nonrepresentable payloads** (functions,
|
|
198
|
+
* symbols) cause `JSON.stringify` to omit the key, returning `'{}'` rather
|
|
199
|
+
* than `{ [packId]: undefined }`. The AVS contract doesn't admit such
|
|
200
|
+
* payloads — every `policy.js` returns either a structured success object
|
|
201
|
+
* or `{ error: "..." }` — so this is documented as out-of-contract input
|
|
202
|
+
* rather than guarded at runtime. The Stream C AST-lint catches the
|
|
203
|
+
* shape upstream.
|
|
204
|
+
* - **Both success and error paths route through here.** Returning
|
|
205
|
+
* `{"error": "..."}` directly from `policy.js` would collide across packs in
|
|
206
|
+
* `merge_jsons` (every pack's error key would land at the top level and
|
|
207
|
+
* the last one wins). Wrapping the error under `[packId]` keeps per-pack
|
|
208
|
+
* error semantics (composite Rego can selectively deny on
|
|
209
|
+
* `data.wasm.<pack-id>.error`).
|
|
210
|
+
*
|
|
211
|
+
* @param packId The pack's stable id, matching the `<name>OracleModule.id`
|
|
212
|
+
* that ships in Phase 1 and the `KNOWN_PACK_IDS` registry in
|
|
213
|
+
* Phase 2. Convention: lowercase single-word, matches the
|
|
214
|
+
* pack's folder name (e.g. `"vaultsfyi"`, `"chainalysis"`).
|
|
215
|
+
* @param valueOrError The pack's output payload — `{ score, risk_score, ... }`
|
|
216
|
+
* for success, `{ error: "..." }` for failure paths.
|
|
217
|
+
* Untyped at this layer because each pack's WASM emits
|
|
218
|
+
* its own shape and the `PolicyPack` / `OracleModule`
|
|
219
|
+
* interfaces today carry input schemas (`paramsSchema`,
|
|
220
|
+
* `wasmArgsSchema`, `secretsSchema`) but no on-chain
|
|
221
|
+
* output schema — composite Rego references fields by
|
|
222
|
+
* name (`data.wasm.<pack-id>.<field>`) and trusts the
|
|
223
|
+
* pack-specific WASM bindings paired to each
|
|
224
|
+
* `wasm_cid` in the manifest (Phase 1.5).
|
|
225
|
+
* @returns JSON-stringified `{ [packId]: valueOrError }` ready to drop into
|
|
226
|
+
* a `policy.js` `return` statement.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* // vaultsfyi/policy.js (success path)
|
|
230
|
+
* return wrapOutput("vaultsfyi", { score: 80, risk_score: 75, timestamp });
|
|
231
|
+
* // → '{"vaultsfyi":{"score":80,"risk_score":75,"timestamp":...}}'
|
|
232
|
+
*
|
|
233
|
+
* // vaultsfyi/policy.js (error path)
|
|
234
|
+
* return wrapOutput("vaultsfyi", { error: String(e) });
|
|
235
|
+
* // → '{"vaultsfyi":{"error":"..."}}' — namespaced, won't collide
|
|
236
|
+
*/
|
|
237
|
+
declare function wrapOutput(packId: string, valueOrError: unknown): string;
|
|
238
|
+
|
|
239
|
+
export { type ChainId, type Deployment, type PolicyPack, type PrepareQueryArgs, type PrepareQueryResult, UnsupportedChainError, decodePolicyParams, encodePolicyParams, getDeployment, wrapOutput };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
// src/encoding.ts
|
|
2
|
+
import { hexToBytes, toHex } from "viem";
|
|
3
|
+
function sortKeysDeep(value) {
|
|
4
|
+
if (Array.isArray(value)) return value.map(sortKeysDeep);
|
|
5
|
+
if (value && typeof value === "object") {
|
|
6
|
+
const obj = value;
|
|
7
|
+
const sorted = {};
|
|
8
|
+
for (const key of Object.keys(obj).sort()) {
|
|
9
|
+
sorted[key] = sortKeysDeep(obj[key]);
|
|
10
|
+
}
|
|
11
|
+
return sorted;
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function encodePolicyParams(pack, params) {
|
|
16
|
+
const validated = pack.paramsSchema.parse(params);
|
|
17
|
+
return toHex(JSON.stringify(sortKeysDeep(validated)));
|
|
18
|
+
}
|
|
19
|
+
function decodePolicyParams(pack, encoded) {
|
|
20
|
+
const json = new TextDecoder("utf-8", { fatal: true }).decode(hexToBytes(encoded));
|
|
21
|
+
return pack.paramsSchema.parse(JSON.parse(json));
|
|
22
|
+
}
|
|
23
|
+
|
|
1
24
|
// src/pack.ts
|
|
2
25
|
function getDeployment(pack, chainId) {
|
|
3
26
|
const deployment = pack.deployments[chainId];
|
|
@@ -24,8 +47,16 @@ var UnsupportedChainError = class extends Error {
|
|
|
24
47
|
supportedChainIds;
|
|
25
48
|
name = "UnsupportedChainError";
|
|
26
49
|
};
|
|
50
|
+
|
|
51
|
+
// src/wrap.ts
|
|
52
|
+
function wrapOutput(packId, valueOrError) {
|
|
53
|
+
return JSON.stringify({ [packId]: valueOrError });
|
|
54
|
+
}
|
|
27
55
|
export {
|
|
28
56
|
UnsupportedChainError,
|
|
29
|
-
|
|
57
|
+
decodePolicyParams,
|
|
58
|
+
encodePolicyParams,
|
|
59
|
+
getDeployment,
|
|
60
|
+
wrapOutput
|
|
30
61
|
};
|
|
31
62
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pack.ts"],"sourcesContent":["import type { Address, Hex, PublicClient } from \"viem\";\nimport type { z } from \"zod\";\nimport type { ChainId, Deployment } from \"./deployment\";\n\n/**\n * Inputs that a pack's `prepareQuery` reads at intent-build time.\n *\n * The Shield SDK passes a viem `PublicClient` (so the pack can read on-chain\n * state) and the vault address the curator is acting on. Packs that don't\n * need on-chain state can ignore both — `prepareQuery` is optional.\n */\nexport interface PrepareQueryArgs {\n\treadonly publicClient: PublicClient;\n\treadonly vault: Address;\n}\n\n/**\n * `wasmArgs` payload (untyped at this layer; each pack narrows it via its own\n * `WasmArgsSchema`) plus an optional pre-image hash for binding the AVS-side\n * evaluation to a specific on-chain state. VaultsFYI uses this to bind the\n * evaluation to a `keccak(supplyQueue)` snapshot so the policy can reject\n * attestations whose underlying allocation has shifted between intent build\n * and on-chain submission.\n */\nexport interface PrepareQueryResult<TWasmArgs> {\n\treadonly wasmArgs: TWasmArgs;\n\treadonly freshnessHash?: Hex;\n}\n\n/**\n * Canonical typed contract every published `@newton-xyz/policy-pack-<name>`\n * package implements. `@newton-xyz/newton-shield-sdk`'s `createShield(...)`\n * accepts `PolicyPack<P, W, S>` as the curator's pack argument.\n *\n * Type parameters:\n * - `TParams` — the shape stored on-chain in `NewtonPolicyData.policyParams`\n * (e.g. risk envelope thresholds for VaultsFYI).\n * - `TWasmArgs` — the shape passed to the policy's WASM oracle at evaluation\n * time (e.g. `{ vault, network, lastKnownAllocationHash }`).\n * - `TSecrets` — required API credentials uploaded before any run/sim.\n *\n * The fields:\n * - `id` — stable identifier of the form `<pack>/<purpose>/<version>`,\n * e.g. `vaultsfyi/risk-envelope/v1`. Used for telemetry\n * and for cross-referencing the `policy_metadata.json`.\n * - `paramsSchema` — zod schema enforced at curator setup time when the\n * pack is bound to a `NewtonPolicyData`.\n * - `wasmArgsSchema` — zod schema enforced per call when the SDK builds the\n * intent and forwards `wasmArgs` to the gateway.\n * - `secretsSchema` — zod schema enforced at upload-time. Validates the\n * shape of the secrets the operator stores in the AVS.\n * - `encodeParams` / `decodeParams` — ABI round-trip for the on-chain\n * `policyParams` bytes. Must round-trip cleanly so the\n * SDK can read the on-chain value back and confirm it\n * matches the curator's intended config.\n * - `prepareQuery` — optional. When present, the SDK invokes it on every\n * call to gather chain-state freshness inputs. Packs\n * that don't need this (e.g. KYC-only packs) omit it.\n * The optional second `options` argument is a\n * pack-typed escape hatch for per-call overrides —\n * e.g. VaultsFYI's `previousAllocationHash` for\n * freshness binding. Each concrete pack narrows it\n * via its own `prepareQuery` signature; the shared\n * interface keeps it `unknown` so the SDK can\n * forward it verbatim.\n * - `deployments` — `chainId → Deployment` map sliced from the upstream\n * `deployments.json` for this pack only. Typed as\n * `Partial<Record<ChainId, Deployment>>` so callers\n * must handle `undefined` for unsupported chains\n * rather than silently reading `.policy` off nothing.\n * Use `getDeployment(pack, chainId)` from this package\n * for the safe lookup.\n * - `metadata` — static identity from the pack's `policy_metadata.json`.\n */\nexport interface PolicyPack<TParams, TWasmArgs, TSecrets> {\n\treadonly id: string;\n\treadonly paramsSchema: z.ZodType<TParams>;\n\treadonly wasmArgsSchema: z.ZodType<TWasmArgs>;\n\treadonly secretsSchema: z.ZodType<TSecrets>;\n\tencodeParams(params: TParams): Hex;\n\tdecodeParams(encoded: Hex): TParams;\n\tprepareQuery?(args: PrepareQueryArgs, options?: unknown): Promise<PrepareQueryResult<TWasmArgs>>;\n\treadonly deployments: Readonly<Partial<Record<ChainId, Deployment>>>;\n\treadonly metadata: {\n\t\treadonly name: string;\n\t\treadonly version: string;\n\t\treadonly description: string;\n\t\treadonly author?: string;\n\t\treadonly link?: string;\n\t};\n}\n\n/**\n * Safe lookup helper. Returns the `Deployment` for `chainId` if the pack is\n * deployed on that chain, or throws `UnsupportedChainError` with the list of\n * chain ids the pack is known to support. Use this at every SDK callsite that\n * reads `pack.deployments[chainId]` so unsupported-chain failures surface\n * immediately rather than as `undefined.policy` further down.\n */\nexport function getDeployment<TParams, TWasmArgs, TSecrets>(\n\tpack: PolicyPack<TParams, TWasmArgs, TSecrets>,\n\tchainId: ChainId,\n): Deployment {\n\tconst deployment = pack.deployments[chainId];\n\tif (!deployment) {\n\t\tconst supported = Object.keys(pack.deployments).sort().join(\", \") || \"(none)\";\n\t\tthrow new UnsupportedChainError(\n\t\t\t`Pack \\`${pack.id}\\` is not deployed on chain ${chainId}. Supported: ${supported}.`,\n\t\t\tpack.id,\n\t\t\tchainId,\n\t\t\tObject.keys(pack.deployments),\n\t\t);\n\t}\n\treturn deployment;\n}\n\n/**\n * Thrown by `getDeployment` when a pack is asked for a chain it isn't\n * deployed on. SDK consumers can catch this specifically to surface a\n * curator-friendly error rather than a `TypeError: Cannot read property\n * 'policy' of undefined`.\n */\nexport class UnsupportedChainError extends Error {\n\toverride readonly name = \"UnsupportedChainError\";\n\tconstructor(\n\t\tmessage: string,\n\t\treadonly packId: string,\n\t\treadonly chainId: ChainId,\n\t\treadonly supportedChainIds: ReadonlyArray<ChainId>,\n\t) {\n\t\tsuper(message);\n\t}\n}\n"],"mappings":";AAmGO,SAAS,cACf,MACA,SACa;AACb,QAAM,aAAa,KAAK,YAAY,OAAO;AAC3C,MAAI,CAAC,YAAY;AAChB,UAAM,YAAY,OAAO,KAAK,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK;AACrE,UAAM,IAAI;AAAA,MACT,UAAU,KAAK,EAAE,+BAA+B,OAAO,gBAAgB,SAAS;AAAA,MAChF,KAAK;AAAA,MACL;AAAA,MACA,OAAO,KAAK,KAAK,WAAW;AAAA,IAC7B;AAAA,EACD;AACA,SAAO;AACR;AAQO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAEhD,YACC,SACS,QACA,SACA,mBACR;AACD,UAAM,OAAO;AAJJ;AACA;AACA;AAAA,EAGV;AAAA,EALU;AAAA,EACA;AAAA,EACA;AAAA,EALQ,OAAO;AAS1B;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/encoding.ts","../src/pack.ts","../src/wrap.ts"],"sourcesContent":["import { type Hex, hexToBytes, toHex } from \"viem\";\nimport type { z } from \"zod\";\n\n/**\n * The on-chain `policyParams` byte format is a Newton-protocol invariant. The\n * AVS host reads it as `String::from_utf8 → serde_json::from_str` (see\n * `newton-prover-avs/crates/core/src/common/task.rs:402-408`); the SDK must\n * therefore write **UTF-8 JSON**. Keys are sorted recursively so two\n * semantically-equal params objects always produce byte-identical output —\n * the SDK's `verifyPolicyBinding` does a byte-equality check against\n * `getPolicyConfig().policyParams`, which would otherwise depend on JS\n * `JSON.stringify` insertion order.\n */\nfunction sortKeysDeep(value: unknown): unknown {\n\tif (Array.isArray(value)) return value.map(sortKeysDeep);\n\tif (value && typeof value === \"object\") {\n\t\tconst obj = value as Record<string, unknown>;\n\t\tconst sorted: Record<string, unknown> = {};\n\t\tfor (const key of Object.keys(obj).sort()) {\n\t\t\tsorted[key] = sortKeysDeep(obj[key]);\n\t\t}\n\t\treturn sorted;\n\t}\n\treturn value;\n}\n\n/**\n * Encode a pack's params for on-chain storage. Validates against the pack's\n * `paramsSchema` so a curator typo throws here rather than silently writing\n * AVS-rejecting bytes. Returns `Hex` so it drops directly into a viem\n * `setPolicy(bytes)` call.\n */\nexport function encodePolicyParams<T>(\n\tpack: { readonly paramsSchema: z.ZodType<T> },\n\tparams: T,\n): Hex {\n\tconst validated = pack.paramsSchema.parse(params);\n\treturn toHex(JSON.stringify(sortKeysDeep(validated)));\n}\n\n/**\n * Decode `policyParams` bytes read from `getPolicyConfig().policyParams` back\n * into the pack's typed params shape. Revalidates against `paramsSchema` so a\n * stale or corrupted on-chain blob throws at the SDK boundary rather than\n * yielding a partially-valid object that crashes deeper in the call.\n *\n * `fatal: true` matches AVS-side `String::from_utf8` behavior — invalid UTF-8\n * throws here rather than silently becoming U+FFFD and diverging from a path\n * the AVS would reject.\n */\nexport function decodePolicyParams<T>(\n\tpack: { readonly paramsSchema: z.ZodType<T> },\n\tencoded: Hex,\n): T {\n\tconst json = new TextDecoder(\"utf-8\", { fatal: true }).decode(hexToBytes(encoded));\n\treturn pack.paramsSchema.parse(JSON.parse(json));\n}\n","import type { Address, Hex, PublicClient } from \"viem\";\nimport type { z } from \"zod\";\nimport type { ChainId, Deployment } from \"./deployment\";\n\n/**\n * Inputs that a pack's `prepareQuery` reads at intent-build time.\n *\n * The Shield SDK passes a viem `PublicClient` (so the pack can read on-chain\n * state) and the vault address the curator is acting on. Packs that don't\n * need on-chain state can ignore both — `prepareQuery` is optional.\n */\nexport interface PrepareQueryArgs {\n\treadonly publicClient: PublicClient;\n\treadonly vault: Address;\n}\n\n/**\n * `wasmArgs` payload (untyped at this layer; each pack narrows it via its own\n * `WasmArgsSchema`) plus an optional pre-image hash for binding the AVS-side\n * evaluation to a specific on-chain state. VaultsFYI uses this to bind the\n * evaluation to a `keccak(supplyQueue)` snapshot so the policy can reject\n * attestations whose underlying allocation has shifted between intent build\n * and on-chain submission.\n */\nexport interface PrepareQueryResult<TWasmArgs> {\n\treadonly wasmArgs: TWasmArgs;\n\treadonly freshnessHash?: Hex;\n}\n\n/**\n * Canonical typed contract every published `@newton-xyz/policy-pack-<name>`\n * package implements. `@newton-xyz/newton-shield-sdk`'s `createShield(...)`\n * accepts `PolicyPack<P, W, S>` as the curator's pack argument.\n *\n * Type parameters:\n * - `TParams` — the shape stored on-chain in `NewtonPolicyData.policyParams`\n * (e.g. risk envelope thresholds for VaultsFYI).\n * - `TWasmArgs` — the shape passed to the policy's WASM oracle at evaluation\n * time (e.g. `{ vault, network, lastKnownAllocationHash }`).\n * - `TSecrets` — required API credentials uploaded before any run/sim.\n *\n * The fields:\n * - `id` — stable identifier of the form `<pack>/<purpose>/<version>`,\n * e.g. `vaultsfyi/risk-envelope/v1`. Used for telemetry\n * and for cross-referencing the `policy_metadata.json`.\n * - `paramsSchema` — zod schema enforced at curator setup time when the\n * pack is bound to a `NewtonPolicyData`.\n * - `wasmArgsSchema` — zod schema enforced per call when the SDK builds the\n * intent and forwards `wasmArgs` to the gateway.\n * - `secretsSchema` — zod schema enforced at upload-time. Validates the\n * shape of the secrets the operator stores in the AVS.\n *\n * Encoding is *not* a per-pack concern. The on-chain `policyParams` byte\n * format is a Newton-protocol invariant — UTF-8 JSON, sorted keys —\n * implemented once in this package as `encodePolicyParams` /\n * `decodePolicyParams`. See `./encoding.ts`. Earlier shapes of this\n * interface required each pack to ship its own `encodeParams` /\n * `decodeParams`; that structurally invited drift (vaultsfyi@0.2.0 shipped\n * ABI bytes against an AVS that reads `serde_json::from_str`, breaking\n * every call). The interface now leaves byte-format to the protocol.\n *\n * - `prepareQuery` — optional. When present, the SDK invokes it on every\n * call to gather chain-state freshness inputs. Packs\n * that don't need this (e.g. KYC-only packs) omit it.\n * The optional second `options` argument is a\n * pack-typed escape hatch for per-call overrides —\n * e.g. VaultsFYI's `previousAllocationHash` for\n * freshness binding. Each concrete pack narrows it\n * via its own `prepareQuery` signature; the shared\n * interface keeps it `unknown` so the SDK can\n * forward it verbatim.\n * - `deployments` — `chainId → Deployment` map sliced from the upstream\n * `deployments.json` for this pack only. Typed as\n * `Partial<Record<ChainId, Deployment>>` so callers\n * must handle `undefined` for unsupported chains\n * rather than silently reading `.policy` off nothing.\n * Use `getDeployment(pack, chainId)` from this package\n * for the safe lookup.\n * - `metadata` — static identity from the pack's `policy_metadata.json`.\n */\nexport interface PolicyPack<TParams, TWasmArgs, TSecrets> {\n\treadonly id: string;\n\treadonly paramsSchema: z.ZodType<TParams>;\n\treadonly wasmArgsSchema: z.ZodType<TWasmArgs>;\n\treadonly secretsSchema: z.ZodType<TSecrets>;\n\tprepareQuery?(args: PrepareQueryArgs, options?: unknown): Promise<PrepareQueryResult<TWasmArgs>>;\n\treadonly deployments: Readonly<Partial<Record<ChainId, Deployment>>>;\n\treadonly metadata: {\n\t\treadonly name: string;\n\t\treadonly version: string;\n\t\treadonly description: string;\n\t\treadonly author?: string;\n\t\treadonly link?: string;\n\t};\n}\n\n/**\n * Safe lookup helper. Returns the `Deployment` for `chainId` if the pack is\n * deployed on that chain, or throws `UnsupportedChainError` with the list of\n * chain ids the pack is known to support. Use this at every SDK callsite that\n * reads `pack.deployments[chainId]` so unsupported-chain failures surface\n * immediately rather than as `undefined.policy` further down.\n */\nexport function getDeployment<TParams, TWasmArgs, TSecrets>(\n\tpack: PolicyPack<TParams, TWasmArgs, TSecrets>,\n\tchainId: ChainId,\n): Deployment {\n\tconst deployment = pack.deployments[chainId];\n\tif (!deployment) {\n\t\tconst supported = Object.keys(pack.deployments).sort().join(\", \") || \"(none)\";\n\t\tthrow new UnsupportedChainError(\n\t\t\t`Pack \\`${pack.id}\\` is not deployed on chain ${chainId}. Supported: ${supported}.`,\n\t\t\tpack.id,\n\t\t\tchainId,\n\t\t\tObject.keys(pack.deployments),\n\t\t);\n\t}\n\treturn deployment;\n}\n\n/**\n * Thrown by `getDeployment` when a pack is asked for a chain it isn't\n * deployed on. SDK consumers can catch this specifically to surface a\n * curator-friendly error rather than a `TypeError: Cannot read property\n * 'policy' of undefined`.\n */\nexport class UnsupportedChainError extends Error {\n\toverride readonly name = \"UnsupportedChainError\";\n\tconstructor(\n\t\tmessage: string,\n\t\treadonly packId: string,\n\t\treadonly chainId: ChainId,\n\t\treadonly supportedChainIds: ReadonlyArray<ChainId>,\n\t) {\n\t\tsuper(message);\n\t}\n}\n","/**\n * Wrap a pack's WASM oracle output under its top-level `PACK_ID` key. Every\n * `policy.js` MUST call this on every return path (success AND error) so the\n * AVS-side `merge_jsons` (`newton-prover-avs/crates/operator/src/simulation.rs:296`)\n * composes cleanly across packs without top-level key collisions.\n *\n * The merge is shallow + last-wins: pre-namespacing, vaultsfyi's\n * `risk_score: number` would silently clobber chainalysis's `risk_score: string`\n * under composition. After namespacing, every pack's output sits under its\n * unique `PACK_ID` key and merges into a single `data.wasm` blob that\n * composite Rego can reference unambiguously as `data.wasm.<pack-id>.<field>`.\n *\n * The contract this helper locks:\n * - **Output is JSON-stringified** so `policy.js` can `return wrapOutput(...)`\n * directly. The AVS host parses every PolicyData WASM's stdout as UTF-8\n * JSON before merging.\n * - **Top-level keys are exactly `[packId]`** — assertable in\n * `<pack>/wrapping_test.rego` with a fixture `data.wasm.<pack-id>` shape.\n * To be enforced at PR time by an AST-lint CI guard (Phase 0 § Stream C,\n * shipped in a separate PR) that flags any raw `return JSON.stringify(...)`\n * callsite in `<pack>/policy.js` not routed through this helper.\n * - **`undefined` and other JSON-nonrepresentable payloads** (functions,\n * symbols) cause `JSON.stringify` to omit the key, returning `'{}'` rather\n * than `{ [packId]: undefined }`. The AVS contract doesn't admit such\n * payloads — every `policy.js` returns either a structured success object\n * or `{ error: \"...\" }` — so this is documented as out-of-contract input\n * rather than guarded at runtime. The Stream C AST-lint catches the\n * shape upstream.\n * - **Both success and error paths route through here.** Returning\n * `{\"error\": \"...\"}` directly from `policy.js` would collide across packs in\n * `merge_jsons` (every pack's error key would land at the top level and\n * the last one wins). Wrapping the error under `[packId]` keeps per-pack\n * error semantics (composite Rego can selectively deny on\n * `data.wasm.<pack-id>.error`).\n *\n * @param packId The pack's stable id, matching the `<name>OracleModule.id`\n * that ships in Phase 1 and the `KNOWN_PACK_IDS` registry in\n * Phase 2. Convention: lowercase single-word, matches the\n * pack's folder name (e.g. `\"vaultsfyi\"`, `\"chainalysis\"`).\n * @param valueOrError The pack's output payload — `{ score, risk_score, ... }`\n * for success, `{ error: \"...\" }` for failure paths.\n * Untyped at this layer because each pack's WASM emits\n * its own shape and the `PolicyPack` / `OracleModule`\n * interfaces today carry input schemas (`paramsSchema`,\n * `wasmArgsSchema`, `secretsSchema`) but no on-chain\n * output schema — composite Rego references fields by\n * name (`data.wasm.<pack-id>.<field>`) and trusts the\n * pack-specific WASM bindings paired to each\n * `wasm_cid` in the manifest (Phase 1.5).\n * @returns JSON-stringified `{ [packId]: valueOrError }` ready to drop into\n * a `policy.js` `return` statement.\n *\n * @example\n * // vaultsfyi/policy.js (success path)\n * return wrapOutput(\"vaultsfyi\", { score: 80, risk_score: 75, timestamp });\n * // → '{\"vaultsfyi\":{\"score\":80,\"risk_score\":75,\"timestamp\":...}}'\n *\n * // vaultsfyi/policy.js (error path)\n * return wrapOutput(\"vaultsfyi\", { error: String(e) });\n * // → '{\"vaultsfyi\":{\"error\":\"...\"}}' — namespaced, won't collide\n */\nexport function wrapOutput(packId: string, valueOrError: unknown): string {\n\treturn JSON.stringify({ [packId]: valueOrError });\n}\n"],"mappings":";AAAA,SAAmB,YAAY,aAAa;AAa5C,SAAS,aAAa,OAAyB;AAC9C,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,YAAY;AACvD,MAAI,SAAS,OAAO,UAAU,UAAU;AACvC,UAAM,MAAM;AACZ,UAAM,SAAkC,CAAC;AACzC,eAAW,OAAO,OAAO,KAAK,GAAG,EAAE,KAAK,GAAG;AAC1C,aAAO,GAAG,IAAI,aAAa,IAAI,GAAG,CAAC;AAAA,IACpC;AACA,WAAO;AAAA,EACR;AACA,SAAO;AACR;AAQO,SAAS,mBACf,MACA,QACM;AACN,QAAM,YAAY,KAAK,aAAa,MAAM,MAAM;AAChD,SAAO,MAAM,KAAK,UAAU,aAAa,SAAS,CAAC,CAAC;AACrD;AAYO,SAAS,mBACf,MACA,SACI;AACJ,QAAM,OAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,WAAW,OAAO,CAAC;AACjF,SAAO,KAAK,aAAa,MAAM,KAAK,MAAM,IAAI,CAAC;AAChD;;;AC+CO,SAAS,cACf,MACA,SACa;AACb,QAAM,aAAa,KAAK,YAAY,OAAO;AAC3C,MAAI,CAAC,YAAY;AAChB,UAAM,YAAY,OAAO,KAAK,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK;AACrE,UAAM,IAAI;AAAA,MACT,UAAU,KAAK,EAAE,+BAA+B,OAAO,gBAAgB,SAAS;AAAA,MAChF,KAAK;AAAA,MACL;AAAA,MACA,OAAO,KAAK,KAAK,WAAW;AAAA,IAC7B;AAAA,EACD;AACA,SAAO;AACR;AAQO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAEhD,YACC,SACS,QACA,SACA,mBACR;AACD,UAAM,OAAO;AAJJ;AACA;AACA;AAAA,EAGV;AAAA,EALU;AAAA,EACA;AAAA,EACA;AAAA,EALQ,OAAO;AAS1B;;;AC3EO,SAAS,WAAW,QAAgB,cAA+B;AACzE,SAAO,KAAK,UAAU,EAAE,CAAC,MAAM,GAAG,aAAa,CAAC;AACjD;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newton-xyz/policy-pack-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shared TypeScript contract for Newton policy packs. Defines the PolicyPack<Params, WasmArgs, Secrets> interface that @newton-xyz/newton-shield-sdk consumes.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Newton Protocol <https://x.com/newton_xyz> (https://newton.xyz)",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"tsup": "^8.5.1",
|
|
50
|
+
"tsx": "^4.19.0",
|
|
50
51
|
"typescript": "^5.5.0",
|
|
51
52
|
"viem": "^2.0.0",
|
|
52
53
|
"zod": "^3.23.0"
|
|
@@ -55,6 +56,6 @@
|
|
|
55
56
|
"build": "tsup",
|
|
56
57
|
"dev": "tsup --watch",
|
|
57
58
|
"typecheck": "tsc --noEmit",
|
|
58
|
-
"test": "
|
|
59
|
+
"test": "node --import tsx --test src/*.test.ts"
|
|
59
60
|
}
|
|
60
61
|
}
|