@crediolabs/policy-synth 0.5.0 → 0.5.2

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.
@@ -18,7 +18,7 @@
18
18
  // rather than by tooling - which is why the tooling now exists.
19
19
  //
20
20
  // Adapted to grammar 3 from the version published in `@crediolabs/policy-synth`
21
- // 0.2.0, which came from the `octogate` repository and was lost when the npm
21
+ // 0.2.0, which came from a different repository and was lost when the npm
22
22
  // lineage moved here. `or` and `not` are gone from the grammar, so the cases
23
23
  // handling them are gone too; oracle bounds are gone from the stored document.
24
24
  //
@@ -121,12 +121,23 @@ function encodeSigner(s) {
121
121
  function encodePoliciesMap(args) {
122
122
  const entries = [];
123
123
  for (const ref of args.policies) {
124
- if (ref.kind === 'interpreter') {
125
- entries.push(new xdr.ScMapEntry({
126
- key: Address.fromString(ref.interpreterAddress).toScVal(),
127
- val: encodePolicyInstallParams(args),
128
- }));
124
+ // Refuse anything that is not an interpreter policy rather than skipping
125
+ // it. This loop used to `if (kind === 'interpreter')` and drop everything
126
+ // else in silence, so a caller attaching an OZ `spending_limit` beside the
127
+ // interpreter got a rule WITHOUT the cap and no indication of it - the
128
+ // policy the user asked for was simply absent from the install they signed.
129
+ // `PolicyRef` has one shape today, so TypeScript narrows this branch to
130
+ // `never`; the guard is for callers reaching the built JS untyped, and for
131
+ // the next kind added to `PolicyRef` without a case here. Failing loudly is
132
+ // the only safe direction: a dropped policy is a missing restriction.
133
+ if (ref.kind !== 'interpreter') {
134
+ const kind = JSON.stringify(ref.kind ?? null);
135
+ throw limitError('INSTALL_BUILD_FAILED', `policy kind ${kind} is not supported; this builder attaches interpreter policies only. Attach an OpenZeppelin built-in through the account layer instead - dropping it here would install a rule missing the restriction you asked for.`);
129
136
  }
137
+ entries.push(new xdr.ScMapEntry({
138
+ key: Address.fromString(ref.interpreterAddress).toScVal(),
139
+ val: encodePolicyInstallParams(args),
140
+ }));
130
141
  }
131
142
  entries.sort(sortByScValSymbolString);
132
143
  return xdr.ScVal.scvMap(entries);
@@ -48,6 +48,13 @@ function jsonToLeaf(value) {
48
48
  return { kind: 'call_arg', index: numberField(v, 'index') };
49
49
  case 'call_arg_len':
50
50
  return { kind: 'call_arg_len', index: numberField(v, 'index') };
51
+ case 'call_arg_field':
52
+ return {
53
+ kind: 'call_arg_field',
54
+ index: numberField(v, 'index'),
55
+ element: numberField(v, 'element'),
56
+ field: stringField(v, 'field'),
57
+ };
51
58
  case 'call_arg_scaled':
52
59
  // num/den stay decimal STRINGS: an i128 ratio does not survive a JS
53
60
  // number, and silently rounding one would change the floor.
@@ -3,8 +3,8 @@ import { type AuthorityOverlap } from '../install/authority-overlap.ts';
3
3
  import { type BuildInstallPolicyResult, type BuildRevokePolicyResult } from '../install/build-install-policy.ts';
4
4
  import { getInterpreterInfo } from '../install/get-interpreter-info.ts';
5
5
  import { type RecordTransactionInput, type SimulatePolicyInput, type SynthesizePolicyInput, type VerifyPolicyInput } from './schemas.ts';
6
- export type { DeclarePolicyInput, GetInterpreterInfoInput, InstallPolicyInput, RecordTransactionInput, RevokePolicyInput, SimulatePolicyInput, SynthesizePolicyInput, VerifyPolicyInput, } from './schemas.ts';
7
- export { ComposeUserResponsesSchema, DeclarePolicyInputSchema, GetInterpreterInfoInputSchema, InstallPolicyInputSchema, InterpreterOptionsSchema, MAINNET_RPC_URL, NetworkSchema, PINNED_INTERPRETER_ADDRESS_BY_NETWORK, PINNED_INTERPRETER_GRAMMAR_VERSION, PINNED_INTERPRETER_MAINNET_ADDRESS, PINNED_INTERPRETER_TESTNET_ADDRESS, PINNED_INTERPRETER_WASM_SHA256, PredicateLeafSchema, PredicateNodeSchema, RecordedTransactionSchema, RecordTransactionInputSchema, RevokePolicyInputSchema, RPC_URL_BY_NETWORK, SynthesizePolicyInputSchema, TESTNET_RPC_URL, ToolErrorSchema, } from './schemas.ts';
6
+ export type { DeclarePolicyInput, GetInterpreterInfoInput, InstallPolicyInput, OzBuiltinPolicy, RecordTransactionInput, RevokePolicyInput, SimulatePolicyInput, SynthesizePolicyInput, VerifyPolicyInput, } from './schemas.ts';
7
+ export { ComposeUserResponsesSchema, DeclarePolicyInputSchema, GetInterpreterInfoInputSchema, InstallPolicyInputSchema, InterpreterOptionsSchema, MAINNET_RPC_URL, NetworkSchema, PINNED_INTERPRETER_ADDRESS_BY_NETWORK, PINNED_INTERPRETER_GRAMMAR_VERSION, PINNED_INTERPRETER_MAINNET_ADDRESS, PINNED_INTERPRETER_TESTNET_ADDRESS, PINNED_INTERPRETER_WASM_SHA256, PINNED_OZ_POLICY_ADDRESS_BY_NETWORK, PINNED_OZ_POLICY_WASM_SHA256, PredicateLeafSchema, PredicateNodeSchema, RecordedTransactionSchema, RecordTransactionInputSchema, RevokePolicyInputSchema, RPC_URL_BY_NETWORK, SynthesizePolicyInputSchema, TESTNET_RPC_URL, ToolErrorSchema, } from './schemas.ts';
8
8
  export type RunRecordTransactionInput = RecordTransactionInput;
9
9
  export type RunSynthesizePolicyInput = SynthesizePolicyInput;
10
10
  export type RunSimulatePolicyInput = SimulatePolicyInput;
package/dist/run/index.js CHANGED
@@ -30,7 +30,7 @@ import { DeclarePolicyInputSchema, GetInterpreterInfoInputSchema, InstallPolicyI
30
30
  // downstream consumer) can import the canonical input shapes from the same
31
31
  // module that owns the tool-body glue. The strict schemas are the source of
32
32
  // truth - MCP tool shapes are derived from them.
33
- export { ComposeUserResponsesSchema, DeclarePolicyInputSchema, GetInterpreterInfoInputSchema, InstallPolicyInputSchema, InterpreterOptionsSchema, MAINNET_RPC_URL, NetworkSchema, PINNED_INTERPRETER_ADDRESS_BY_NETWORK, PINNED_INTERPRETER_GRAMMAR_VERSION, PINNED_INTERPRETER_MAINNET_ADDRESS, PINNED_INTERPRETER_TESTNET_ADDRESS, PINNED_INTERPRETER_WASM_SHA256, PredicateLeafSchema, PredicateNodeSchema, RecordedTransactionSchema, RecordTransactionInputSchema, RevokePolicyInputSchema, RPC_URL_BY_NETWORK, SynthesizePolicyInputSchema, TESTNET_RPC_URL, ToolErrorSchema, } from "./schemas.js";
33
+ export { ComposeUserResponsesSchema, DeclarePolicyInputSchema, GetInterpreterInfoInputSchema, InstallPolicyInputSchema, InterpreterOptionsSchema, MAINNET_RPC_URL, NetworkSchema, PINNED_INTERPRETER_ADDRESS_BY_NETWORK, PINNED_INTERPRETER_GRAMMAR_VERSION, PINNED_INTERPRETER_MAINNET_ADDRESS, PINNED_INTERPRETER_TESTNET_ADDRESS, PINNED_INTERPRETER_WASM_SHA256, PINNED_OZ_POLICY_ADDRESS_BY_NETWORK, PINNED_OZ_POLICY_WASM_SHA256, PredicateLeafSchema, PredicateNodeSchema, RecordedTransactionSchema, RecordTransactionInputSchema, RevokePolicyInputSchema, RPC_URL_BY_NETWORK, SynthesizePolicyInputSchema, TESTNET_RPC_URL, ToolErrorSchema, } from "./schemas.js";
34
34
  /** Map every tool name to its canonical domain error code. Replaces a 7-way
35
35
  * if/else so adding a tool adds one line here rather than a new branch in
36
36
  * each envelope call. */
@@ -1627,6 +1627,25 @@ export declare const MAINNET_RPC_URL = "https://mainnet.sorobanrpc.com";
1627
1627
  * a single constant - only the addresses and RPCs are network-scoped. */
1628
1628
  export declare const PINNED_INTERPRETER_ADDRESS_BY_NETWORK: Record<Network, string>;
1629
1629
  export declare const RPC_URL_BY_NETWORK: Record<Network, string>;
1630
+ /** The OpenZeppelin built-in policies we deployed instances of. These are OZ
1631
+ * EXAMPLE contracts, built by us from `OpenZeppelin/stellar-contracts` at tag
1632
+ * v0.7.2 and deployed by us. We have NOT audited them and upstream ships an
1633
+ * "experimental software ... as is" disclaimer; anything surfacing one of
1634
+ * these to a user must say so rather than implying we vouch for the code.
1635
+ * Provenance detail in `docs/audit/README.md` finding 7. */
1636
+ export type OzBuiltinPolicy = 'spending_limit' | 'simple_threshold' | 'weighted_threshold';
1637
+ /** Instance addresses per network. Exported so consumers import the pin
1638
+ * instead of copying a literal - a copied address is how a testnet id ends up
1639
+ * being queried against mainnet, which returns `Error(Storage, MissingValue)`
1640
+ * and reads exactly like "nothing is deployed there".
1641
+ *
1642
+ * Instance ids are network-scoped, so the addresses differ while the wasm
1643
+ * hash does not: each pair below was created from the same uploaded wasm (see
1644
+ * `PINNED_OZ_POLICY_WASM_SHA256`), verified by fetching the deployed bytes
1645
+ * back from both networks. */
1646
+ export declare const PINNED_OZ_POLICY_ADDRESS_BY_NETWORK: Record<Network, Record<OzBuiltinPolicy, string>>;
1647
+ /** sha256 of each deployed policy wasm, identical across both networks. */
1648
+ export declare const PINNED_OZ_POLICY_WASM_SHA256: Record<OzBuiltinPolicy, string>;
1630
1649
  /** Stellar network passphrases. Pinned here so the XDR envelope uses the
1631
1650
  * matching passphrase when the wallet signs (a mismatch yields invalid
1632
1651
  * hashes). */
@@ -364,6 +364,33 @@ export const RPC_URL_BY_NETWORK = {
364
364
  testnet: TESTNET_RPC_URL,
365
365
  mainnet: MAINNET_RPC_URL,
366
366
  };
367
+ /** Instance addresses per network. Exported so consumers import the pin
368
+ * instead of copying a literal - a copied address is how a testnet id ends up
369
+ * being queried against mainnet, which returns `Error(Storage, MissingValue)`
370
+ * and reads exactly like "nothing is deployed there".
371
+ *
372
+ * Instance ids are network-scoped, so the addresses differ while the wasm
373
+ * hash does not: each pair below was created from the same uploaded wasm (see
374
+ * `PINNED_OZ_POLICY_WASM_SHA256`), verified by fetching the deployed bytes
375
+ * back from both networks. */
376
+ export const PINNED_OZ_POLICY_ADDRESS_BY_NETWORK = {
377
+ testnet: {
378
+ spending_limit: 'CDH4KOBRUEZI6TTZ72YXR5YUIODB6RH3AF75KX56Z73DELRCA5TWFISP',
379
+ simple_threshold: 'CAYTIVQOEZDOQI4GC3XBXEEYHQUANQQJHPJVMXVRBREGSAP6TCN3DID6',
380
+ weighted_threshold: 'CCTNRFZCL45GTJICA3Z2KFQO3VEGBHGCVBLHQ3GLJKAGACQIJMYJS7T2',
381
+ },
382
+ mainnet: {
383
+ spending_limit: 'CA7IBD266HIHFDUIBZLPIAITJUA3DVY4JAG6K3QMGBKLZCXXLP5E2F7A',
384
+ simple_threshold: 'CDOGPGUFGGUDG25P3TG6XIXJKRRYOZ3PXUZIEPVH74KXRZIDKZ5HYEOS',
385
+ weighted_threshold: 'CDWPZ4YZ3YIJ64XSHRMERRF2L2H7XD6SPUZDP3KI56T7QXQCICC25V3J',
386
+ },
387
+ };
388
+ /** sha256 of each deployed policy wasm, identical across both networks. */
389
+ export const PINNED_OZ_POLICY_WASM_SHA256 = {
390
+ spending_limit: '9ce30ea1fe5c2dc5c9c49cf3462adb32e2c11d7dfadb15ef43a51ba56568de2b',
391
+ simple_threshold: '01c0be09eb6fb288cab2e878b4e890f7a38f75afab99aeb197861f44e2e2dfe6',
392
+ weighted_threshold: '78030272b06afb09d2949ab8877c9a8ae1ab9025b48f4edafd5816cc44f76eaa',
393
+ };
367
394
  /** Stellar network passphrases. Pinned here so the XDR envelope uses the
368
395
  * matching passphrase when the wallet signs (a mismatch yields invalid
369
396
  * hashes). */
@@ -19,7 +19,7 @@
19
19
  // rather than by tooling - which is why the tooling now exists.
20
20
  //
21
21
  // Adapted to grammar 3 from the version published in `@crediolabs/policy-synth`
22
- // 0.2.0, which came from the `octogate` repository and was lost when the npm
22
+ // 0.2.0, which came from a different repository and was lost when the npm
23
23
  // lineage moved here. `or` and `not` are gone from the grammar, so the cases
24
24
  // handling them are gone too; oracle bounds are gone from the stored document.
25
25
  //
@@ -125,12 +125,23 @@ function encodeSigner(s) {
125
125
  function encodePoliciesMap(args) {
126
126
  const entries = [];
127
127
  for (const ref of args.policies) {
128
- if (ref.kind === 'interpreter') {
129
- entries.push(new stellar_sdk_1.xdr.ScMapEntry({
130
- key: stellar_sdk_1.Address.fromString(ref.interpreterAddress).toScVal(),
131
- val: encodePolicyInstallParams(args),
132
- }));
128
+ // Refuse anything that is not an interpreter policy rather than skipping
129
+ // it. This loop used to `if (kind === 'interpreter')` and drop everything
130
+ // else in silence, so a caller attaching an OZ `spending_limit` beside the
131
+ // interpreter got a rule WITHOUT the cap and no indication of it - the
132
+ // policy the user asked for was simply absent from the install they signed.
133
+ // `PolicyRef` has one shape today, so TypeScript narrows this branch to
134
+ // `never`; the guard is for callers reaching the built JS untyped, and for
135
+ // the next kind added to `PolicyRef` without a case here. Failing loudly is
136
+ // the only safe direction: a dropped policy is a missing restriction.
137
+ if (ref.kind !== 'interpreter') {
138
+ const kind = JSON.stringify(ref.kind ?? null);
139
+ throw limitError('INSTALL_BUILD_FAILED', `policy kind ${kind} is not supported; this builder attaches interpreter policies only. Attach an OpenZeppelin built-in through the account layer instead - dropping it here would install a rule missing the restriction you asked for.`);
133
140
  }
141
+ entries.push(new stellar_sdk_1.xdr.ScMapEntry({
142
+ key: stellar_sdk_1.Address.fromString(ref.interpreterAddress).toScVal(),
143
+ val: encodePolicyInstallParams(args),
144
+ }));
134
145
  }
135
146
  entries.sort(sortByScValSymbolString);
136
147
  return stellar_sdk_1.xdr.ScVal.scvMap(entries);
@@ -51,6 +51,13 @@ function jsonToLeaf(value) {
51
51
  return { kind: 'call_arg', index: numberField(v, 'index') };
52
52
  case 'call_arg_len':
53
53
  return { kind: 'call_arg_len', index: numberField(v, 'index') };
54
+ case 'call_arg_field':
55
+ return {
56
+ kind: 'call_arg_field',
57
+ index: numberField(v, 'index'),
58
+ element: numberField(v, 'element'),
59
+ field: stringField(v, 'field'),
60
+ };
54
61
  case 'call_arg_scaled':
55
62
  // num/den stay decimal STRINGS: an i128 ratio does not survive a JS
56
63
  // number, and silently rounding one would change the floor.
@@ -3,8 +3,8 @@ import { type AuthorityOverlap } from '../install/authority-overlap.ts';
3
3
  import { type BuildInstallPolicyResult, type BuildRevokePolicyResult } from '../install/build-install-policy.ts';
4
4
  import { getInterpreterInfo } from '../install/get-interpreter-info.ts';
5
5
  import { type RecordTransactionInput, type SimulatePolicyInput, type SynthesizePolicyInput, type VerifyPolicyInput } from './schemas.ts';
6
- export type { DeclarePolicyInput, GetInterpreterInfoInput, InstallPolicyInput, RecordTransactionInput, RevokePolicyInput, SimulatePolicyInput, SynthesizePolicyInput, VerifyPolicyInput, } from './schemas.ts';
7
- export { ComposeUserResponsesSchema, DeclarePolicyInputSchema, GetInterpreterInfoInputSchema, InstallPolicyInputSchema, InterpreterOptionsSchema, MAINNET_RPC_URL, NetworkSchema, PINNED_INTERPRETER_ADDRESS_BY_NETWORK, PINNED_INTERPRETER_GRAMMAR_VERSION, PINNED_INTERPRETER_MAINNET_ADDRESS, PINNED_INTERPRETER_TESTNET_ADDRESS, PINNED_INTERPRETER_WASM_SHA256, PredicateLeafSchema, PredicateNodeSchema, RecordedTransactionSchema, RecordTransactionInputSchema, RevokePolicyInputSchema, RPC_URL_BY_NETWORK, SynthesizePolicyInputSchema, TESTNET_RPC_URL, ToolErrorSchema, } from './schemas.ts';
6
+ export type { DeclarePolicyInput, GetInterpreterInfoInput, InstallPolicyInput, OzBuiltinPolicy, RecordTransactionInput, RevokePolicyInput, SimulatePolicyInput, SynthesizePolicyInput, VerifyPolicyInput, } from './schemas.ts';
7
+ export { ComposeUserResponsesSchema, DeclarePolicyInputSchema, GetInterpreterInfoInputSchema, InstallPolicyInputSchema, InterpreterOptionsSchema, MAINNET_RPC_URL, NetworkSchema, PINNED_INTERPRETER_ADDRESS_BY_NETWORK, PINNED_INTERPRETER_GRAMMAR_VERSION, PINNED_INTERPRETER_MAINNET_ADDRESS, PINNED_INTERPRETER_TESTNET_ADDRESS, PINNED_INTERPRETER_WASM_SHA256, PINNED_OZ_POLICY_ADDRESS_BY_NETWORK, PINNED_OZ_POLICY_WASM_SHA256, PredicateLeafSchema, PredicateNodeSchema, RecordedTransactionSchema, RecordTransactionInputSchema, RevokePolicyInputSchema, RPC_URL_BY_NETWORK, SynthesizePolicyInputSchema, TESTNET_RPC_URL, ToolErrorSchema, } from './schemas.ts';
8
8
  export type RunRecordTransactionInput = RecordTransactionInput;
9
9
  export type RunSynthesizePolicyInput = SynthesizePolicyInput;
10
10
  export type RunSimulatePolicyInput = SimulatePolicyInput;
@@ -18,7 +18,7 @@
18
18
  // No business logic. No retries. No session state. The same call shape can
19
19
  // drive the CLI (which calls into the same core directly without MCP).
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.ToolErrorSchema = exports.TESTNET_RPC_URL = exports.SynthesizePolicyInputSchema = exports.RPC_URL_BY_NETWORK = exports.RevokePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.PredicateNodeSchema = exports.PredicateLeafSchema = exports.PINNED_INTERPRETER_WASM_SHA256 = exports.PINNED_INTERPRETER_TESTNET_ADDRESS = exports.PINNED_INTERPRETER_MAINNET_ADDRESS = exports.PINNED_INTERPRETER_GRAMMAR_VERSION = exports.PINNED_INTERPRETER_ADDRESS_BY_NETWORK = exports.NetworkSchema = exports.MAINNET_RPC_URL = exports.InterpreterOptionsSchema = exports.InstallPolicyInputSchema = exports.GetInterpreterInfoInputSchema = exports.DeclarePolicyInputSchema = exports.ComposeUserResponsesSchema = void 0;
21
+ exports.ToolErrorSchema = exports.TESTNET_RPC_URL = exports.SynthesizePolicyInputSchema = exports.RPC_URL_BY_NETWORK = exports.RevokePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.PredicateNodeSchema = exports.PredicateLeafSchema = exports.PINNED_OZ_POLICY_WASM_SHA256 = exports.PINNED_OZ_POLICY_ADDRESS_BY_NETWORK = exports.PINNED_INTERPRETER_WASM_SHA256 = exports.PINNED_INTERPRETER_TESTNET_ADDRESS = exports.PINNED_INTERPRETER_MAINNET_ADDRESS = exports.PINNED_INTERPRETER_GRAMMAR_VERSION = exports.PINNED_INTERPRETER_ADDRESS_BY_NETWORK = exports.NetworkSchema = exports.MAINNET_RPC_URL = exports.InterpreterOptionsSchema = exports.InstallPolicyInputSchema = exports.GetInterpreterInfoInputSchema = exports.DeclarePolicyInputSchema = exports.ComposeUserResponsesSchema = void 0;
22
22
  exports.runRecordTransaction = runRecordTransaction;
23
23
  exports.runSynthesizePolicy = runSynthesizePolicy;
24
24
  exports.runInstallPolicy = runInstallPolicy;
@@ -55,6 +55,8 @@ Object.defineProperty(exports, "PINNED_INTERPRETER_GRAMMAR_VERSION", { enumerabl
55
55
  Object.defineProperty(exports, "PINNED_INTERPRETER_MAINNET_ADDRESS", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_MAINNET_ADDRESS; } });
56
56
  Object.defineProperty(exports, "PINNED_INTERPRETER_TESTNET_ADDRESS", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_TESTNET_ADDRESS; } });
57
57
  Object.defineProperty(exports, "PINNED_INTERPRETER_WASM_SHA256", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_WASM_SHA256; } });
58
+ Object.defineProperty(exports, "PINNED_OZ_POLICY_ADDRESS_BY_NETWORK", { enumerable: true, get: function () { return schemas_ts_2.PINNED_OZ_POLICY_ADDRESS_BY_NETWORK; } });
59
+ Object.defineProperty(exports, "PINNED_OZ_POLICY_WASM_SHA256", { enumerable: true, get: function () { return schemas_ts_2.PINNED_OZ_POLICY_WASM_SHA256; } });
58
60
  Object.defineProperty(exports, "PredicateLeafSchema", { enumerable: true, get: function () { return schemas_ts_2.PredicateLeafSchema; } });
59
61
  Object.defineProperty(exports, "PredicateNodeSchema", { enumerable: true, get: function () { return schemas_ts_2.PredicateNodeSchema; } });
60
62
  Object.defineProperty(exports, "RecordedTransactionSchema", { enumerable: true, get: function () { return schemas_ts_2.RecordedTransactionSchema; } });
@@ -1627,6 +1627,25 @@ export declare const MAINNET_RPC_URL = "https://mainnet.sorobanrpc.com";
1627
1627
  * a single constant - only the addresses and RPCs are network-scoped. */
1628
1628
  export declare const PINNED_INTERPRETER_ADDRESS_BY_NETWORK: Record<Network, string>;
1629
1629
  export declare const RPC_URL_BY_NETWORK: Record<Network, string>;
1630
+ /** The OpenZeppelin built-in policies we deployed instances of. These are OZ
1631
+ * EXAMPLE contracts, built by us from `OpenZeppelin/stellar-contracts` at tag
1632
+ * v0.7.2 and deployed by us. We have NOT audited them and upstream ships an
1633
+ * "experimental software ... as is" disclaimer; anything surfacing one of
1634
+ * these to a user must say so rather than implying we vouch for the code.
1635
+ * Provenance detail in `docs/audit/README.md` finding 7. */
1636
+ export type OzBuiltinPolicy = 'spending_limit' | 'simple_threshold' | 'weighted_threshold';
1637
+ /** Instance addresses per network. Exported so consumers import the pin
1638
+ * instead of copying a literal - a copied address is how a testnet id ends up
1639
+ * being queried against mainnet, which returns `Error(Storage, MissingValue)`
1640
+ * and reads exactly like "nothing is deployed there".
1641
+ *
1642
+ * Instance ids are network-scoped, so the addresses differ while the wasm
1643
+ * hash does not: each pair below was created from the same uploaded wasm (see
1644
+ * `PINNED_OZ_POLICY_WASM_SHA256`), verified by fetching the deployed bytes
1645
+ * back from both networks. */
1646
+ export declare const PINNED_OZ_POLICY_ADDRESS_BY_NETWORK: Record<Network, Record<OzBuiltinPolicy, string>>;
1647
+ /** sha256 of each deployed policy wasm, identical across both networks. */
1648
+ export declare const PINNED_OZ_POLICY_WASM_SHA256: Record<OzBuiltinPolicy, string>;
1630
1649
  /** Stellar network passphrases. Pinned here so the XDR envelope uses the
1631
1650
  * matching passphrase when the wallet signs (a mismatch yields invalid
1632
1651
  * hashes). */
@@ -16,7 +16,7 @@
16
16
  // imports them here so its tool-shape bindings stay in step; the CLI imports
17
17
  // them here so it can build the same args envelope the MCP transport builds.
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.ToolErrorSchema = exports.GetInterpreterInfoInputSchema = exports.RevokePolicyInputSchema = exports.InstallPolicyInputSchema = exports.DeclarePolicyInputSchema = exports.NETWORK_PASSPHRASES = exports.RPC_URL_BY_NETWORK = exports.PINNED_INTERPRETER_ADDRESS_BY_NETWORK = exports.MAINNET_RPC_URL = exports.TESTNET_RPC_URL = exports.PINNED_INTERPRETER_GRAMMAR_VERSION = exports.PINNED_INTERPRETER_WASM_SHA256 = exports.PINNED_INTERPRETER_MAINNET_ADDRESS = exports.PINNED_INTERPRETER_TESTNET_ADDRESS = exports.ObservedRuleSchema = exports.VerifyPolicyInputSchema = exports.SimulatePolicyInputSchema = exports.PredicateNodeSchema = exports.PredicateLeafSchema = exports.SynthesizePolicyInputSchema = exports.InterpreterOptionsSchema = exports.RecordTransactionInputSchema = exports.ComposeUserResponsesSchema = exports.RecordedTransactionSchema = exports.ParseConfidenceSchema = exports.OnChainEventSchema = exports.TokenMovementSchema = exports.ContractInvocationSchema = exports.ScValSchema = exports.NetworkSchema = void 0;
19
+ exports.ToolErrorSchema = exports.GetInterpreterInfoInputSchema = exports.RevokePolicyInputSchema = exports.InstallPolicyInputSchema = exports.DeclarePolicyInputSchema = exports.NETWORK_PASSPHRASES = exports.PINNED_OZ_POLICY_WASM_SHA256 = exports.PINNED_OZ_POLICY_ADDRESS_BY_NETWORK = exports.RPC_URL_BY_NETWORK = exports.PINNED_INTERPRETER_ADDRESS_BY_NETWORK = exports.MAINNET_RPC_URL = exports.TESTNET_RPC_URL = exports.PINNED_INTERPRETER_GRAMMAR_VERSION = exports.PINNED_INTERPRETER_WASM_SHA256 = exports.PINNED_INTERPRETER_MAINNET_ADDRESS = exports.PINNED_INTERPRETER_TESTNET_ADDRESS = exports.ObservedRuleSchema = exports.VerifyPolicyInputSchema = exports.SimulatePolicyInputSchema = exports.PredicateNodeSchema = exports.PredicateLeafSchema = exports.SynthesizePolicyInputSchema = exports.InterpreterOptionsSchema = exports.RecordTransactionInputSchema = exports.ComposeUserResponsesSchema = exports.RecordedTransactionSchema = exports.ParseConfidenceSchema = exports.OnChainEventSchema = exports.TokenMovementSchema = exports.ContractInvocationSchema = exports.ScValSchema = exports.NetworkSchema = void 0;
20
20
  const zod_1 = require("zod");
21
21
  const address_ts_1 = require("../synth/address.js");
22
22
  /** Soroban `valid_until` is a u32 ledger sequence; a value above this cannot be
@@ -367,6 +367,33 @@ exports.RPC_URL_BY_NETWORK = {
367
367
  testnet: exports.TESTNET_RPC_URL,
368
368
  mainnet: exports.MAINNET_RPC_URL,
369
369
  };
370
+ /** Instance addresses per network. Exported so consumers import the pin
371
+ * instead of copying a literal - a copied address is how a testnet id ends up
372
+ * being queried against mainnet, which returns `Error(Storage, MissingValue)`
373
+ * and reads exactly like "nothing is deployed there".
374
+ *
375
+ * Instance ids are network-scoped, so the addresses differ while the wasm
376
+ * hash does not: each pair below was created from the same uploaded wasm (see
377
+ * `PINNED_OZ_POLICY_WASM_SHA256`), verified by fetching the deployed bytes
378
+ * back from both networks. */
379
+ exports.PINNED_OZ_POLICY_ADDRESS_BY_NETWORK = {
380
+ testnet: {
381
+ spending_limit: 'CDH4KOBRUEZI6TTZ72YXR5YUIODB6RH3AF75KX56Z73DELRCA5TWFISP',
382
+ simple_threshold: 'CAYTIVQOEZDOQI4GC3XBXEEYHQUANQQJHPJVMXVRBREGSAP6TCN3DID6',
383
+ weighted_threshold: 'CCTNRFZCL45GTJICA3Z2KFQO3VEGBHGCVBLHQ3GLJKAGACQIJMYJS7T2',
384
+ },
385
+ mainnet: {
386
+ spending_limit: 'CA7IBD266HIHFDUIBZLPIAITJUA3DVY4JAG6K3QMGBKLZCXXLP5E2F7A',
387
+ simple_threshold: 'CDOGPGUFGGUDG25P3TG6XIXJKRRYOZ3PXUZIEPVH74KXRZIDKZ5HYEOS',
388
+ weighted_threshold: 'CDWPZ4YZ3YIJ64XSHRMERRF2L2H7XD6SPUZDP3KI56T7QXQCICC25V3J',
389
+ },
390
+ };
391
+ /** sha256 of each deployed policy wasm, identical across both networks. */
392
+ exports.PINNED_OZ_POLICY_WASM_SHA256 = {
393
+ spending_limit: '9ce30ea1fe5c2dc5c9c49cf3462adb32e2c11d7dfadb15ef43a51ba56568de2b',
394
+ simple_threshold: '01c0be09eb6fb288cab2e878b4e890f7a38f75afab99aeb197861f44e2e2dfe6',
395
+ weighted_threshold: '78030272b06afb09d2949ab8877c9a8ae1ab9025b48f4edafd5816cc44f76eaa',
396
+ };
370
397
  /** Stellar network passphrases. Pinned here so the XDR envelope uses the
371
398
  * matching passphrase when the wallet signs (a mismatch yields invalid
372
399
  * hashes). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crediolabs/policy-synth",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "license": "MIT",
5
5
  "description": "Off-chain TypeScript synthesis core for the OZ Accounts Policy Builder. Records Soroban transactions, synthesises the minimal policy that permits exactly that flow, verifies it, and returns an unsigned install transaction.",
6
6
  "type": "module",
@@ -18,7 +18,7 @@
18
18
  // rather than by tooling - which is why the tooling now exists.
19
19
  //
20
20
  // Adapted to grammar 3 from the version published in `@crediolabs/policy-synth`
21
- // 0.2.0, which came from the `octogate` repository and was lost when the npm
21
+ // 0.2.0, which came from a different repository and was lost when the npm
22
22
  // lineage moved here. `or` and `not` are gone from the grammar, so the cases
23
23
  // handling them are gone too; oracle bounds are gone from the stored document.
24
24
  //
@@ -194,14 +194,28 @@ function encodeSigner(s: SignerDraft): xdr.ScVal {
194
194
  function encodePoliciesMap(args: BuildAddContextRuleArgs): xdr.ScVal {
195
195
  const entries: xdr.ScMapEntry[] = []
196
196
  for (const ref of args.policies) {
197
- if (ref.kind === 'interpreter') {
198
- entries.push(
199
- new xdr.ScMapEntry({
200
- key: Address.fromString(ref.interpreterAddress).toScVal(),
201
- val: encodePolicyInstallParams(args),
202
- })
197
+ // Refuse anything that is not an interpreter policy rather than skipping
198
+ // it. This loop used to `if (kind === 'interpreter')` and drop everything
199
+ // else in silence, so a caller attaching an OZ `spending_limit` beside the
200
+ // interpreter got a rule WITHOUT the cap and no indication of it - the
201
+ // policy the user asked for was simply absent from the install they signed.
202
+ // `PolicyRef` has one shape today, so TypeScript narrows this branch to
203
+ // `never`; the guard is for callers reaching the built JS untyped, and for
204
+ // the next kind added to `PolicyRef` without a case here. Failing loudly is
205
+ // the only safe direction: a dropped policy is a missing restriction.
206
+ if (ref.kind !== 'interpreter') {
207
+ const kind = JSON.stringify((ref as { kind?: unknown }).kind ?? null)
208
+ throw limitError(
209
+ 'INSTALL_BUILD_FAILED',
210
+ `policy kind ${kind} is not supported; this builder attaches interpreter policies only. Attach an OpenZeppelin built-in through the account layer instead - dropping it here would install a rule missing the restriction you asked for.`
203
211
  )
204
212
  }
213
+ entries.push(
214
+ new xdr.ScMapEntry({
215
+ key: Address.fromString(ref.interpreterAddress).toScVal(),
216
+ val: encodePolicyInstallParams(args),
217
+ })
218
+ )
205
219
  }
206
220
  entries.sort(sortByScValSymbolString)
207
221
  return xdr.ScVal.scvMap(entries)
@@ -57,6 +57,13 @@ function jsonToLeaf(value: unknown): PredicateLeaf {
57
57
  return { kind: 'call_arg', index: numberField(v, 'index') }
58
58
  case 'call_arg_len':
59
59
  return { kind: 'call_arg_len', index: numberField(v, 'index') }
60
+ case 'call_arg_field':
61
+ return {
62
+ kind: 'call_arg_field',
63
+ index: numberField(v, 'index'),
64
+ element: numberField(v, 'element'),
65
+ field: stringField(v, 'field'),
66
+ }
60
67
  case 'call_arg_scaled':
61
68
  // num/den stay decimal STRINGS: an i128 ratio does not survive a JS
62
69
  // number, and silently rounding one would change the floor.
package/src/run/index.ts CHANGED
@@ -77,6 +77,7 @@ export type {
77
77
  DeclarePolicyInput,
78
78
  GetInterpreterInfoInput,
79
79
  InstallPolicyInput,
80
+ OzBuiltinPolicy,
80
81
  RecordTransactionInput,
81
82
  RevokePolicyInput,
82
83
  SimulatePolicyInput,
@@ -100,6 +101,8 @@ export {
100
101
  PINNED_INTERPRETER_MAINNET_ADDRESS,
101
102
  PINNED_INTERPRETER_TESTNET_ADDRESS,
102
103
  PINNED_INTERPRETER_WASM_SHA256,
104
+ PINNED_OZ_POLICY_ADDRESS_BY_NETWORK,
105
+ PINNED_OZ_POLICY_WASM_SHA256,
103
106
  PredicateLeafSchema,
104
107
  PredicateNodeSchema,
105
108
  RecordedTransactionSchema,
@@ -421,6 +421,46 @@ export const RPC_URL_BY_NETWORK: Record<Network, string> = {
421
421
  mainnet: MAINNET_RPC_URL,
422
422
  }
423
423
 
424
+ /** The OpenZeppelin built-in policies we deployed instances of. These are OZ
425
+ * EXAMPLE contracts, built by us from `OpenZeppelin/stellar-contracts` at tag
426
+ * v0.7.2 and deployed by us. We have NOT audited them and upstream ships an
427
+ * "experimental software ... as is" disclaimer; anything surfacing one of
428
+ * these to a user must say so rather than implying we vouch for the code.
429
+ * Provenance detail in `docs/audit/README.md` finding 7. */
430
+ export type OzBuiltinPolicy = 'spending_limit' | 'simple_threshold' | 'weighted_threshold'
431
+
432
+ /** Instance addresses per network. Exported so consumers import the pin
433
+ * instead of copying a literal - a copied address is how a testnet id ends up
434
+ * being queried against mainnet, which returns `Error(Storage, MissingValue)`
435
+ * and reads exactly like "nothing is deployed there".
436
+ *
437
+ * Instance ids are network-scoped, so the addresses differ while the wasm
438
+ * hash does not: each pair below was created from the same uploaded wasm (see
439
+ * `PINNED_OZ_POLICY_WASM_SHA256`), verified by fetching the deployed bytes
440
+ * back from both networks. */
441
+ export const PINNED_OZ_POLICY_ADDRESS_BY_NETWORK: Record<
442
+ Network,
443
+ Record<OzBuiltinPolicy, string>
444
+ > = {
445
+ testnet: {
446
+ spending_limit: 'CDH4KOBRUEZI6TTZ72YXR5YUIODB6RH3AF75KX56Z73DELRCA5TWFISP',
447
+ simple_threshold: 'CAYTIVQOEZDOQI4GC3XBXEEYHQUANQQJHPJVMXVRBREGSAP6TCN3DID6',
448
+ weighted_threshold: 'CCTNRFZCL45GTJICA3Z2KFQO3VEGBHGCVBLHQ3GLJKAGACQIJMYJS7T2',
449
+ },
450
+ mainnet: {
451
+ spending_limit: 'CA7IBD266HIHFDUIBZLPIAITJUA3DVY4JAG6K3QMGBKLZCXXLP5E2F7A',
452
+ simple_threshold: 'CDOGPGUFGGUDG25P3TG6XIXJKRRYOZ3PXUZIEPVH74KXRZIDKZ5HYEOS',
453
+ weighted_threshold: 'CDWPZ4YZ3YIJ64XSHRMERRF2L2H7XD6SPUZDP3KI56T7QXQCICC25V3J',
454
+ },
455
+ }
456
+
457
+ /** sha256 of each deployed policy wasm, identical across both networks. */
458
+ export const PINNED_OZ_POLICY_WASM_SHA256: Record<OzBuiltinPolicy, string> = {
459
+ spending_limit: '9ce30ea1fe5c2dc5c9c49cf3462adb32e2c11d7dfadb15ef43a51ba56568de2b',
460
+ simple_threshold: '01c0be09eb6fb288cab2e878b4e890f7a38f75afab99aeb197861f44e2e2dfe6',
461
+ weighted_threshold: '78030272b06afb09d2949ab8877c9a8ae1ab9025b48f4edafd5816cc44f76eaa',
462
+ }
463
+
424
464
  /** Stellar network passphrases. Pinned here so the XDR envelope uses the
425
465
  * matching passphrase when the wallet signs (a mismatch yields invalid
426
466
  * hashes). */