@newton-xyz/policy-pack-shared 0.2.0 → 0.4.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 CHANGED
@@ -21,9 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  UnsupportedChainError: () => UnsupportedChainError,
24
+ UnsupportedEnvError: () => UnsupportedEnvError,
24
25
  decodePolicyParams: () => decodePolicyParams,
25
26
  encodePolicyParams: () => encodePolicyParams,
26
- getDeployment: () => getDeployment
27
+ getDeployment: () => getDeployment,
28
+ wrapOutput: () => wrapOutput
27
29
  });
28
30
  module.exports = __toCommonJS(index_exports);
29
31
 
@@ -51,17 +53,28 @@ function decodePolicyParams(pack, encoded) {
51
53
  }
52
54
 
53
55
  // src/pack.ts
54
- function getDeployment(pack, chainId) {
55
- const deployment = pack.deployments[chainId];
56
- if (!deployment) {
57
- const supported = Object.keys(pack.deployments).sort().join(", ") || "(none)";
56
+ function getDeployment(pack, chainId, env) {
57
+ const perEnv = pack.deployments[chainId];
58
+ if (!perEnv) {
59
+ const supportedChains = Object.keys(pack.deployments).sort().join(", ") || "(none)";
58
60
  throw new UnsupportedChainError(
59
- `Pack \`${pack.id}\` is not deployed on chain ${chainId}. Supported: ${supported}.`,
61
+ `Pack \`${pack.id}\` is not deployed on chain ${chainId}. Supported: ${supportedChains}.`,
60
62
  pack.id,
61
63
  chainId,
62
64
  Object.keys(pack.deployments)
63
65
  );
64
66
  }
67
+ const deployment = perEnv[env];
68
+ if (!deployment) {
69
+ const supportedEnvs = Object.keys(perEnv).sort().join(", ") || "(none)";
70
+ throw new UnsupportedEnvError(
71
+ `Pack \`${pack.id}\` is deployed on chain ${chainId} but not in env "${env}". Supported envs on this chain: ${supportedEnvs}.`,
72
+ pack.id,
73
+ chainId,
74
+ env,
75
+ Object.keys(perEnv)
76
+ );
77
+ }
65
78
  return deployment;
66
79
  }
67
80
  var UnsupportedChainError = class extends Error {
@@ -76,4 +89,23 @@ var UnsupportedChainError = class extends Error {
76
89
  supportedChainIds;
77
90
  name = "UnsupportedChainError";
78
91
  };
92
+ var UnsupportedEnvError = class extends Error {
93
+ constructor(message, packId, chainId, env, supportedEnvs) {
94
+ super(message);
95
+ this.packId = packId;
96
+ this.chainId = chainId;
97
+ this.env = env;
98
+ this.supportedEnvs = supportedEnvs;
99
+ }
100
+ packId;
101
+ chainId;
102
+ env;
103
+ supportedEnvs;
104
+ name = "UnsupportedEnvError";
105
+ };
106
+
107
+ // src/wrap.ts
108
+ function wrapOutput(packId, valueOrError) {
109
+ return JSON.stringify({ [packId]: valueOrError });
110
+ }
79
111
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/encoding.ts","../src/pack.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\";\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"],"mappings":";;;;;;;;;;;;;;;;;;;;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;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/encoding.ts","../src/pack.ts","../src/wrap.ts"],"sourcesContent":["export type { ChainId, Deployment, GatewayEnv } from \"./deployment\";\nexport { decodePolicyParams, encodePolicyParams } from \"./encoding\";\nexport type {\n\tPolicyPack,\n\tPrepareQueryArgs,\n\tPrepareQueryResult,\n} from \"./pack\";\nexport {\n\tgetDeployment,\n\tUnsupportedChainError,\n\tUnsupportedEnvError,\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, GatewayEnv } 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 → env → Deployment` map sliced from the\n * upstream `deployments.json` for this pack only.\n * Typed as `Partial<Record<ChainId, Partial<Record<GatewayEnv,\n * Deployment>>>>` so callers must handle `undefined`\n * both for unsupported chains and for chains where\n * only one env has been deployed. Use\n * `getDeployment(pack, chainId, env)` from this\n * package 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<\n\t\tPartial<Record<ChainId, Readonly<Partial<Record<GatewayEnv, Deployment>>>>>\n\t>;\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, env)` if the\n * pack is deployed there, or throws — `UnsupportedChainError` if the chain\n * itself has no entry, `UnsupportedEnvError` if the chain has at least one\n * env but not the one requested. Use this at every SDK callsite that reads\n * `pack.deployments[chainId][env]` so unsupported-cell 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\tenv: GatewayEnv,\n): Deployment {\n\tconst perEnv = pack.deployments[chainId];\n\tif (!perEnv) {\n\t\tconst supportedChains = 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: ${supportedChains}.`,\n\t\t\tpack.id,\n\t\t\tchainId,\n\t\t\tObject.keys(pack.deployments),\n\t\t);\n\t}\n\tconst deployment = perEnv[env];\n\tif (!deployment) {\n\t\tconst supportedEnvs = Object.keys(perEnv).sort().join(\", \") || \"(none)\";\n\t\tthrow new UnsupportedEnvError(\n\t\t\t`Pack \\`${pack.id}\\` is deployed on chain ${chainId} but not in env \"${env}\". Supported envs on this chain: ${supportedEnvs}.`,\n\t\t\tpack.id,\n\t\t\tchainId,\n\t\t\tenv,\n\t\t\tObject.keys(perEnv) as GatewayEnv[],\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/**\n * Thrown by `getDeployment` when a pack is deployed on the requested chain\n * but not in the requested env. Distinct from `UnsupportedChainError`\n * because the recovery is different: the curator either picks a different\n * env (typo / wrong gateway) or the AVS team needs to deploy the pack into\n * the requested env.\n */\nexport class UnsupportedEnvError extends Error {\n\toverride readonly name = \"UnsupportedEnvError\";\n\tconstructor(\n\t\tmessage: string,\n\t\treadonly packId: string,\n\t\treadonly chainId: ChainId,\n\t\treadonly env: GatewayEnv,\n\t\treadonly supportedEnvs: ReadonlyArray<GatewayEnv>,\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;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;;;ACmDO,SAAS,cACf,MACA,SACA,KACa;AACb,QAAM,SAAS,KAAK,YAAY,OAAO;AACvC,MAAI,CAAC,QAAQ;AACZ,UAAM,kBAAkB,OAAO,KAAK,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK;AAC3E,UAAM,IAAI;AAAA,MACT,UAAU,KAAK,EAAE,+BAA+B,OAAO,gBAAgB,eAAe;AAAA,MACtF,KAAK;AAAA,MACL;AAAA,MACA,OAAO,KAAK,KAAK,WAAW;AAAA,IAC7B;AAAA,EACD;AACA,QAAM,aAAa,OAAO,GAAG;AAC7B,MAAI,CAAC,YAAY;AAChB,UAAM,gBAAgB,OAAO,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK;AAC/D,UAAM,IAAI;AAAA,MACT,UAAU,KAAK,EAAE,2BAA2B,OAAO,oBAAoB,GAAG,oCAAoC,aAAa;AAAA,MAC3H,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,OAAO,KAAK,MAAM;AAAA,IACnB;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;AASO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAE9C,YACC,SACS,QACA,SACA,KACA,eACR;AACD,UAAM,OAAO;AALJ;AACA;AACA;AACA;AAAA,EAGV;AAAA,EANU;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EANQ,OAAO;AAU1B;;;AC/GO,SAAS,WAAW,QAAgB,cAA+B;AACzE,SAAO,KAAK,UAAU,EAAE,CAAC,MAAM,GAAG,aAAa,CAAC;AACjD;","names":[]}
package/dist/index.d.cts CHANGED
@@ -10,6 +10,14 @@ import { z } from 'zod';
10
10
  * same type they'd see at runtime.
11
11
  */
12
12
  type ChainId = string;
13
+ /**
14
+ * Newton AVS environment. Mirrors `@newton-xyz/newton-shield-sdk`'s
15
+ * `GatewayEnv`. The same `(chainId)` may have separate deployments under
16
+ * each env — distinct gateways, distinct TaskManager addresses, distinct
17
+ * registered operators. A pack policy deployed under `stagef` will not be
18
+ * evaluated by `prod` operators and vice versa.
19
+ */
20
+ type GatewayEnv = "stagef" | "prod";
13
21
  /**
14
22
  * On-chain address pair + content addressing for a deployed Newton policy
15
23
  * on a given chain. Mirrors the per-pack-per-chain entries in the upstream
@@ -127,13 +135,14 @@ interface PrepareQueryResult<TWasmArgs> {
127
135
  * via its own `prepareQuery` signature; the shared
128
136
  * interface keeps it `unknown` so the SDK can
129
137
  * forward it verbatim.
130
- * - `deployments` — `chainId → Deployment` map sliced from the upstream
131
- * `deployments.json` for this pack only. Typed as
132
- * `Partial<Record<ChainId, Deployment>>` so callers
133
- * must handle `undefined` for unsupported chains
134
- * rather than silently reading `.policy` off nothing.
135
- * Use `getDeployment(pack, chainId)` from this package
136
- * for the safe lookup.
138
+ * - `deployments` — `chainId → env → Deployment` map sliced from the
139
+ * upstream `deployments.json` for this pack only.
140
+ * Typed as `Partial<Record<ChainId, Partial<Record<GatewayEnv,
141
+ * Deployment>>>>` so callers must handle `undefined`
142
+ * both for unsupported chains and for chains where
143
+ * only one env has been deployed. Use
144
+ * `getDeployment(pack, chainId, env)` from this
145
+ * package for the safe lookup.
137
146
  * - `metadata` — static identity from the pack's `policy_metadata.json`.
138
147
  */
139
148
  interface PolicyPack<TParams, TWasmArgs, TSecrets> {
@@ -142,7 +151,7 @@ interface PolicyPack<TParams, TWasmArgs, TSecrets> {
142
151
  readonly wasmArgsSchema: z.ZodType<TWasmArgs>;
143
152
  readonly secretsSchema: z.ZodType<TSecrets>;
144
153
  prepareQuery?(args: PrepareQueryArgs, options?: unknown): Promise<PrepareQueryResult<TWasmArgs>>;
145
- readonly deployments: Readonly<Partial<Record<ChainId, Deployment>>>;
154
+ readonly deployments: Readonly<Partial<Record<ChainId, Readonly<Partial<Record<GatewayEnv, Deployment>>>>>>;
146
155
  readonly metadata: {
147
156
  readonly name: string;
148
157
  readonly version: string;
@@ -152,13 +161,14 @@ interface PolicyPack<TParams, TWasmArgs, TSecrets> {
152
161
  };
153
162
  }
154
163
  /**
155
- * Safe lookup helper. Returns the `Deployment` for `chainId` if the pack is
156
- * deployed on that chain, or throws `UnsupportedChainError` with the list of
157
- * chain ids the pack is known to support. Use this at every SDK callsite that
158
- * reads `pack.deployments[chainId]` so unsupported-chain failures surface
164
+ * Safe lookup helper. Returns the `Deployment` for `(chainId, env)` if the
165
+ * pack is deployed there, or throws `UnsupportedChainError` if the chain
166
+ * itself has no entry, `UnsupportedEnvError` if the chain has at least one
167
+ * env but not the one requested. Use this at every SDK callsite that reads
168
+ * `pack.deployments[chainId][env]` so unsupported-cell failures surface
159
169
  * immediately rather than as `undefined.policy` further down.
160
170
  */
161
- declare function getDeployment<TParams, TWasmArgs, TSecrets>(pack: PolicyPack<TParams, TWasmArgs, TSecrets>, chainId: ChainId): Deployment;
171
+ declare function getDeployment<TParams, TWasmArgs, TSecrets>(pack: PolicyPack<TParams, TWasmArgs, TSecrets>, chainId: ChainId, env: GatewayEnv): Deployment;
162
172
  /**
163
173
  * Thrown by `getDeployment` when a pack is asked for a chain it isn't
164
174
  * deployed on. SDK consumers can catch this specifically to surface a
@@ -172,5 +182,83 @@ declare class UnsupportedChainError extends Error {
172
182
  readonly name = "UnsupportedChainError";
173
183
  constructor(message: string, packId: string, chainId: ChainId, supportedChainIds: ReadonlyArray<ChainId>);
174
184
  }
185
+ /**
186
+ * Thrown by `getDeployment` when a pack is deployed on the requested chain
187
+ * but not in the requested env. Distinct from `UnsupportedChainError`
188
+ * because the recovery is different: the curator either picks a different
189
+ * env (typo / wrong gateway) or the AVS team needs to deploy the pack into
190
+ * the requested env.
191
+ */
192
+ declare class UnsupportedEnvError extends Error {
193
+ readonly packId: string;
194
+ readonly chainId: ChainId;
195
+ readonly env: GatewayEnv;
196
+ readonly supportedEnvs: ReadonlyArray<GatewayEnv>;
197
+ readonly name = "UnsupportedEnvError";
198
+ constructor(message: string, packId: string, chainId: ChainId, env: GatewayEnv, supportedEnvs: ReadonlyArray<GatewayEnv>);
199
+ }
200
+
201
+ /**
202
+ * Wrap a pack's WASM oracle output under its top-level `PACK_ID` key. Every
203
+ * `policy.js` MUST call this on every return path (success AND error) so the
204
+ * AVS-side `merge_jsons` (`newton-prover-avs/crates/operator/src/simulation.rs:296`)
205
+ * composes cleanly across packs without top-level key collisions.
206
+ *
207
+ * The merge is shallow + last-wins: pre-namespacing, vaultsfyi's
208
+ * `risk_score: number` would silently clobber chainalysis's `risk_score: string`
209
+ * under composition. After namespacing, every pack's output sits under its
210
+ * unique `PACK_ID` key and merges into a single `data.wasm` blob that
211
+ * composite Rego can reference unambiguously as `data.wasm.<pack-id>.<field>`.
212
+ *
213
+ * The contract this helper locks:
214
+ * - **Output is JSON-stringified** so `policy.js` can `return wrapOutput(...)`
215
+ * directly. The AVS host parses every PolicyData WASM's stdout as UTF-8
216
+ * JSON before merging.
217
+ * - **Top-level keys are exactly `[packId]`** — assertable in
218
+ * `<pack>/wrapping_test.rego` with a fixture `data.wasm.<pack-id>` shape.
219
+ * To be enforced at PR time by an AST-lint CI guard (Phase 0 § Stream C,
220
+ * shipped in a separate PR) that flags any raw `return JSON.stringify(...)`
221
+ * callsite in `<pack>/policy.js` not routed through this helper.
222
+ * - **`undefined` and other JSON-nonrepresentable payloads** (functions,
223
+ * symbols) cause `JSON.stringify` to omit the key, returning `'{}'` rather
224
+ * than `{ [packId]: undefined }`. The AVS contract doesn't admit such
225
+ * payloads — every `policy.js` returns either a structured success object
226
+ * or `{ error: "..." }` — so this is documented as out-of-contract input
227
+ * rather than guarded at runtime. The Stream C AST-lint catches the
228
+ * shape upstream.
229
+ * - **Both success and error paths route through here.** Returning
230
+ * `{"error": "..."}` directly from `policy.js` would collide across packs in
231
+ * `merge_jsons` (every pack's error key would land at the top level and
232
+ * the last one wins). Wrapping the error under `[packId]` keeps per-pack
233
+ * error semantics (composite Rego can selectively deny on
234
+ * `data.wasm.<pack-id>.error`).
235
+ *
236
+ * @param packId The pack's stable id, matching the `<name>OracleModule.id`
237
+ * that ships in Phase 1 and the `KNOWN_PACK_IDS` registry in
238
+ * Phase 2. Convention: lowercase single-word, matches the
239
+ * pack's folder name (e.g. `"vaultsfyi"`, `"chainalysis"`).
240
+ * @param valueOrError The pack's output payload — `{ score, risk_score, ... }`
241
+ * for success, `{ error: "..." }` for failure paths.
242
+ * Untyped at this layer because each pack's WASM emits
243
+ * its own shape and the `PolicyPack` / `OracleModule`
244
+ * interfaces today carry input schemas (`paramsSchema`,
245
+ * `wasmArgsSchema`, `secretsSchema`) but no on-chain
246
+ * output schema — composite Rego references fields by
247
+ * name (`data.wasm.<pack-id>.<field>`) and trusts the
248
+ * pack-specific WASM bindings paired to each
249
+ * `wasm_cid` in the manifest (Phase 1.5).
250
+ * @returns JSON-stringified `{ [packId]: valueOrError }` ready to drop into
251
+ * a `policy.js` `return` statement.
252
+ *
253
+ * @example
254
+ * // vaultsfyi/policy.js (success path)
255
+ * return wrapOutput("vaultsfyi", { score: 80, risk_score: 75, timestamp });
256
+ * // → '{"vaultsfyi":{"score":80,"risk_score":75,"timestamp":...}}'
257
+ *
258
+ * // vaultsfyi/policy.js (error path)
259
+ * return wrapOutput("vaultsfyi", { error: String(e) });
260
+ * // → '{"vaultsfyi":{"error":"..."}}' — namespaced, won't collide
261
+ */
262
+ declare function wrapOutput(packId: string, valueOrError: unknown): string;
175
263
 
176
- export { type ChainId, type Deployment, type PolicyPack, type PrepareQueryArgs, type PrepareQueryResult, UnsupportedChainError, decodePolicyParams, encodePolicyParams, getDeployment };
264
+ export { type ChainId, type Deployment, type GatewayEnv, type PolicyPack, type PrepareQueryArgs, type PrepareQueryResult, UnsupportedChainError, UnsupportedEnvError, decodePolicyParams, encodePolicyParams, getDeployment, wrapOutput };
package/dist/index.d.ts CHANGED
@@ -10,6 +10,14 @@ import { z } from 'zod';
10
10
  * same type they'd see at runtime.
11
11
  */
12
12
  type ChainId = string;
13
+ /**
14
+ * Newton AVS environment. Mirrors `@newton-xyz/newton-shield-sdk`'s
15
+ * `GatewayEnv`. The same `(chainId)` may have separate deployments under
16
+ * each env — distinct gateways, distinct TaskManager addresses, distinct
17
+ * registered operators. A pack policy deployed under `stagef` will not be
18
+ * evaluated by `prod` operators and vice versa.
19
+ */
20
+ type GatewayEnv = "stagef" | "prod";
13
21
  /**
14
22
  * On-chain address pair + content addressing for a deployed Newton policy
15
23
  * on a given chain. Mirrors the per-pack-per-chain entries in the upstream
@@ -127,13 +135,14 @@ interface PrepareQueryResult<TWasmArgs> {
127
135
  * via its own `prepareQuery` signature; the shared
128
136
  * interface keeps it `unknown` so the SDK can
129
137
  * forward it verbatim.
130
- * - `deployments` — `chainId → Deployment` map sliced from the upstream
131
- * `deployments.json` for this pack only. Typed as
132
- * `Partial<Record<ChainId, Deployment>>` so callers
133
- * must handle `undefined` for unsupported chains
134
- * rather than silently reading `.policy` off nothing.
135
- * Use `getDeployment(pack, chainId)` from this package
136
- * for the safe lookup.
138
+ * - `deployments` — `chainId → env → Deployment` map sliced from the
139
+ * upstream `deployments.json` for this pack only.
140
+ * Typed as `Partial<Record<ChainId, Partial<Record<GatewayEnv,
141
+ * Deployment>>>>` so callers must handle `undefined`
142
+ * both for unsupported chains and for chains where
143
+ * only one env has been deployed. Use
144
+ * `getDeployment(pack, chainId, env)` from this
145
+ * package for the safe lookup.
137
146
  * - `metadata` — static identity from the pack's `policy_metadata.json`.
138
147
  */
139
148
  interface PolicyPack<TParams, TWasmArgs, TSecrets> {
@@ -142,7 +151,7 @@ interface PolicyPack<TParams, TWasmArgs, TSecrets> {
142
151
  readonly wasmArgsSchema: z.ZodType<TWasmArgs>;
143
152
  readonly secretsSchema: z.ZodType<TSecrets>;
144
153
  prepareQuery?(args: PrepareQueryArgs, options?: unknown): Promise<PrepareQueryResult<TWasmArgs>>;
145
- readonly deployments: Readonly<Partial<Record<ChainId, Deployment>>>;
154
+ readonly deployments: Readonly<Partial<Record<ChainId, Readonly<Partial<Record<GatewayEnv, Deployment>>>>>>;
146
155
  readonly metadata: {
147
156
  readonly name: string;
148
157
  readonly version: string;
@@ -152,13 +161,14 @@ interface PolicyPack<TParams, TWasmArgs, TSecrets> {
152
161
  };
153
162
  }
154
163
  /**
155
- * Safe lookup helper. Returns the `Deployment` for `chainId` if the pack is
156
- * deployed on that chain, or throws `UnsupportedChainError` with the list of
157
- * chain ids the pack is known to support. Use this at every SDK callsite that
158
- * reads `pack.deployments[chainId]` so unsupported-chain failures surface
164
+ * Safe lookup helper. Returns the `Deployment` for `(chainId, env)` if the
165
+ * pack is deployed there, or throws `UnsupportedChainError` if the chain
166
+ * itself has no entry, `UnsupportedEnvError` if the chain has at least one
167
+ * env but not the one requested. Use this at every SDK callsite that reads
168
+ * `pack.deployments[chainId][env]` so unsupported-cell failures surface
159
169
  * immediately rather than as `undefined.policy` further down.
160
170
  */
161
- declare function getDeployment<TParams, TWasmArgs, TSecrets>(pack: PolicyPack<TParams, TWasmArgs, TSecrets>, chainId: ChainId): Deployment;
171
+ declare function getDeployment<TParams, TWasmArgs, TSecrets>(pack: PolicyPack<TParams, TWasmArgs, TSecrets>, chainId: ChainId, env: GatewayEnv): Deployment;
162
172
  /**
163
173
  * Thrown by `getDeployment` when a pack is asked for a chain it isn't
164
174
  * deployed on. SDK consumers can catch this specifically to surface a
@@ -172,5 +182,83 @@ declare class UnsupportedChainError extends Error {
172
182
  readonly name = "UnsupportedChainError";
173
183
  constructor(message: string, packId: string, chainId: ChainId, supportedChainIds: ReadonlyArray<ChainId>);
174
184
  }
185
+ /**
186
+ * Thrown by `getDeployment` when a pack is deployed on the requested chain
187
+ * but not in the requested env. Distinct from `UnsupportedChainError`
188
+ * because the recovery is different: the curator either picks a different
189
+ * env (typo / wrong gateway) or the AVS team needs to deploy the pack into
190
+ * the requested env.
191
+ */
192
+ declare class UnsupportedEnvError extends Error {
193
+ readonly packId: string;
194
+ readonly chainId: ChainId;
195
+ readonly env: GatewayEnv;
196
+ readonly supportedEnvs: ReadonlyArray<GatewayEnv>;
197
+ readonly name = "UnsupportedEnvError";
198
+ constructor(message: string, packId: string, chainId: ChainId, env: GatewayEnv, supportedEnvs: ReadonlyArray<GatewayEnv>);
199
+ }
200
+
201
+ /**
202
+ * Wrap a pack's WASM oracle output under its top-level `PACK_ID` key. Every
203
+ * `policy.js` MUST call this on every return path (success AND error) so the
204
+ * AVS-side `merge_jsons` (`newton-prover-avs/crates/operator/src/simulation.rs:296`)
205
+ * composes cleanly across packs without top-level key collisions.
206
+ *
207
+ * The merge is shallow + last-wins: pre-namespacing, vaultsfyi's
208
+ * `risk_score: number` would silently clobber chainalysis's `risk_score: string`
209
+ * under composition. After namespacing, every pack's output sits under its
210
+ * unique `PACK_ID` key and merges into a single `data.wasm` blob that
211
+ * composite Rego can reference unambiguously as `data.wasm.<pack-id>.<field>`.
212
+ *
213
+ * The contract this helper locks:
214
+ * - **Output is JSON-stringified** so `policy.js` can `return wrapOutput(...)`
215
+ * directly. The AVS host parses every PolicyData WASM's stdout as UTF-8
216
+ * JSON before merging.
217
+ * - **Top-level keys are exactly `[packId]`** — assertable in
218
+ * `<pack>/wrapping_test.rego` with a fixture `data.wasm.<pack-id>` shape.
219
+ * To be enforced at PR time by an AST-lint CI guard (Phase 0 § Stream C,
220
+ * shipped in a separate PR) that flags any raw `return JSON.stringify(...)`
221
+ * callsite in `<pack>/policy.js` not routed through this helper.
222
+ * - **`undefined` and other JSON-nonrepresentable payloads** (functions,
223
+ * symbols) cause `JSON.stringify` to omit the key, returning `'{}'` rather
224
+ * than `{ [packId]: undefined }`. The AVS contract doesn't admit such
225
+ * payloads — every `policy.js` returns either a structured success object
226
+ * or `{ error: "..." }` — so this is documented as out-of-contract input
227
+ * rather than guarded at runtime. The Stream C AST-lint catches the
228
+ * shape upstream.
229
+ * - **Both success and error paths route through here.** Returning
230
+ * `{"error": "..."}` directly from `policy.js` would collide across packs in
231
+ * `merge_jsons` (every pack's error key would land at the top level and
232
+ * the last one wins). Wrapping the error under `[packId]` keeps per-pack
233
+ * error semantics (composite Rego can selectively deny on
234
+ * `data.wasm.<pack-id>.error`).
235
+ *
236
+ * @param packId The pack's stable id, matching the `<name>OracleModule.id`
237
+ * that ships in Phase 1 and the `KNOWN_PACK_IDS` registry in
238
+ * Phase 2. Convention: lowercase single-word, matches the
239
+ * pack's folder name (e.g. `"vaultsfyi"`, `"chainalysis"`).
240
+ * @param valueOrError The pack's output payload — `{ score, risk_score, ... }`
241
+ * for success, `{ error: "..." }` for failure paths.
242
+ * Untyped at this layer because each pack's WASM emits
243
+ * its own shape and the `PolicyPack` / `OracleModule`
244
+ * interfaces today carry input schemas (`paramsSchema`,
245
+ * `wasmArgsSchema`, `secretsSchema`) but no on-chain
246
+ * output schema — composite Rego references fields by
247
+ * name (`data.wasm.<pack-id>.<field>`) and trusts the
248
+ * pack-specific WASM bindings paired to each
249
+ * `wasm_cid` in the manifest (Phase 1.5).
250
+ * @returns JSON-stringified `{ [packId]: valueOrError }` ready to drop into
251
+ * a `policy.js` `return` statement.
252
+ *
253
+ * @example
254
+ * // vaultsfyi/policy.js (success path)
255
+ * return wrapOutput("vaultsfyi", { score: 80, risk_score: 75, timestamp });
256
+ * // → '{"vaultsfyi":{"score":80,"risk_score":75,"timestamp":...}}'
257
+ *
258
+ * // vaultsfyi/policy.js (error path)
259
+ * return wrapOutput("vaultsfyi", { error: String(e) });
260
+ * // → '{"vaultsfyi":{"error":"..."}}' — namespaced, won't collide
261
+ */
262
+ declare function wrapOutput(packId: string, valueOrError: unknown): string;
175
263
 
176
- export { type ChainId, type Deployment, type PolicyPack, type PrepareQueryArgs, type PrepareQueryResult, UnsupportedChainError, decodePolicyParams, encodePolicyParams, getDeployment };
264
+ export { type ChainId, type Deployment, type GatewayEnv, type PolicyPack, type PrepareQueryArgs, type PrepareQueryResult, UnsupportedChainError, UnsupportedEnvError, decodePolicyParams, encodePolicyParams, getDeployment, wrapOutput };
package/dist/index.js CHANGED
@@ -22,17 +22,28 @@ function decodePolicyParams(pack, encoded) {
22
22
  }
23
23
 
24
24
  // src/pack.ts
25
- function getDeployment(pack, chainId) {
26
- const deployment = pack.deployments[chainId];
27
- if (!deployment) {
28
- const supported = Object.keys(pack.deployments).sort().join(", ") || "(none)";
25
+ function getDeployment(pack, chainId, env) {
26
+ const perEnv = pack.deployments[chainId];
27
+ if (!perEnv) {
28
+ const supportedChains = Object.keys(pack.deployments).sort().join(", ") || "(none)";
29
29
  throw new UnsupportedChainError(
30
- `Pack \`${pack.id}\` is not deployed on chain ${chainId}. Supported: ${supported}.`,
30
+ `Pack \`${pack.id}\` is not deployed on chain ${chainId}. Supported: ${supportedChains}.`,
31
31
  pack.id,
32
32
  chainId,
33
33
  Object.keys(pack.deployments)
34
34
  );
35
35
  }
36
+ const deployment = perEnv[env];
37
+ if (!deployment) {
38
+ const supportedEnvs = Object.keys(perEnv).sort().join(", ") || "(none)";
39
+ throw new UnsupportedEnvError(
40
+ `Pack \`${pack.id}\` is deployed on chain ${chainId} but not in env "${env}". Supported envs on this chain: ${supportedEnvs}.`,
41
+ pack.id,
42
+ chainId,
43
+ env,
44
+ Object.keys(perEnv)
45
+ );
46
+ }
36
47
  return deployment;
37
48
  }
38
49
  var UnsupportedChainError = class extends Error {
@@ -47,10 +58,31 @@ var UnsupportedChainError = class extends Error {
47
58
  supportedChainIds;
48
59
  name = "UnsupportedChainError";
49
60
  };
61
+ var UnsupportedEnvError = class extends Error {
62
+ constructor(message, packId, chainId, env, supportedEnvs) {
63
+ super(message);
64
+ this.packId = packId;
65
+ this.chainId = chainId;
66
+ this.env = env;
67
+ this.supportedEnvs = supportedEnvs;
68
+ }
69
+ packId;
70
+ chainId;
71
+ env;
72
+ supportedEnvs;
73
+ name = "UnsupportedEnvError";
74
+ };
75
+
76
+ // src/wrap.ts
77
+ function wrapOutput(packId, valueOrError) {
78
+ return JSON.stringify({ [packId]: valueOrError });
79
+ }
50
80
  export {
51
81
  UnsupportedChainError,
82
+ UnsupportedEnvError,
52
83
  decodePolicyParams,
53
84
  encodePolicyParams,
54
- getDeployment
85
+ getDeployment,
86
+ wrapOutput
55
87
  };
56
88
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/encoding.ts","../src/pack.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"],"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;","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, GatewayEnv } 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 → env → Deployment` map sliced from the\n * upstream `deployments.json` for this pack only.\n * Typed as `Partial<Record<ChainId, Partial<Record<GatewayEnv,\n * Deployment>>>>` so callers must handle `undefined`\n * both for unsupported chains and for chains where\n * only one env has been deployed. Use\n * `getDeployment(pack, chainId, env)` from this\n * package 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<\n\t\tPartial<Record<ChainId, Readonly<Partial<Record<GatewayEnv, Deployment>>>>>\n\t>;\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, env)` if the\n * pack is deployed there, or throws — `UnsupportedChainError` if the chain\n * itself has no entry, `UnsupportedEnvError` if the chain has at least one\n * env but not the one requested. Use this at every SDK callsite that reads\n * `pack.deployments[chainId][env]` so unsupported-cell 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\tenv: GatewayEnv,\n): Deployment {\n\tconst perEnv = pack.deployments[chainId];\n\tif (!perEnv) {\n\t\tconst supportedChains = 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: ${supportedChains}.`,\n\t\t\tpack.id,\n\t\t\tchainId,\n\t\t\tObject.keys(pack.deployments),\n\t\t);\n\t}\n\tconst deployment = perEnv[env];\n\tif (!deployment) {\n\t\tconst supportedEnvs = Object.keys(perEnv).sort().join(\", \") || \"(none)\";\n\t\tthrow new UnsupportedEnvError(\n\t\t\t`Pack \\`${pack.id}\\` is deployed on chain ${chainId} but not in env \"${env}\". Supported envs on this chain: ${supportedEnvs}.`,\n\t\t\tpack.id,\n\t\t\tchainId,\n\t\t\tenv,\n\t\t\tObject.keys(perEnv) as GatewayEnv[],\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/**\n * Thrown by `getDeployment` when a pack is deployed on the requested chain\n * but not in the requested env. Distinct from `UnsupportedChainError`\n * because the recovery is different: the curator either picks a different\n * env (typo / wrong gateway) or the AVS team needs to deploy the pack into\n * the requested env.\n */\nexport class UnsupportedEnvError extends Error {\n\toverride readonly name = \"UnsupportedEnvError\";\n\tconstructor(\n\t\tmessage: string,\n\t\treadonly packId: string,\n\t\treadonly chainId: ChainId,\n\t\treadonly env: GatewayEnv,\n\t\treadonly supportedEnvs: ReadonlyArray<GatewayEnv>,\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;;;ACmDO,SAAS,cACf,MACA,SACA,KACa;AACb,QAAM,SAAS,KAAK,YAAY,OAAO;AACvC,MAAI,CAAC,QAAQ;AACZ,UAAM,kBAAkB,OAAO,KAAK,KAAK,WAAW,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK;AAC3E,UAAM,IAAI;AAAA,MACT,UAAU,KAAK,EAAE,+BAA+B,OAAO,gBAAgB,eAAe;AAAA,MACtF,KAAK;AAAA,MACL;AAAA,MACA,OAAO,KAAK,KAAK,WAAW;AAAA,IAC7B;AAAA,EACD;AACA,QAAM,aAAa,OAAO,GAAG;AAC7B,MAAI,CAAC,YAAY;AAChB,UAAM,gBAAgB,OAAO,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,KAAK;AAC/D,UAAM,IAAI;AAAA,MACT,UAAU,KAAK,EAAE,2BAA2B,OAAO,oBAAoB,GAAG,oCAAoC,aAAa;AAAA,MAC3H,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,OAAO,KAAK,MAAM;AAAA,IACnB;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;AASO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EAE9C,YACC,SACS,QACA,SACA,KACA,eACR;AACD,UAAM,OAAO;AALJ;AACA;AACA;AACA;AAAA,EAGV;AAAA,EANU;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EANQ,OAAO;AAU1B;;;AC/GO,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.2.0",
3
+ "version": "0.4.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)",