npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@crediolabs/policy-synth

v0.1.16

Published

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.

Readme

@crediolabs/policy-synth

Off-chain TypeScript synthesis core for the OpenZeppelin Accounts Policy Builder.

It records a Soroban transaction (from an on-chain hash or a raw envelope XDR), synthesises the minimal policy that permits exactly that flow, and compiles it through the OZ Accounts adapter into a proposed policy. When the policy needs constraint shapes OZ built-ins cannot express (exact ordered swap paths, oracle price bounds, per-method scoping, recipient allowlists), the interpreter adapter is opted in to emit a parallel predicate-shaped PolicyDocument that installs alongside the OZ primitives. The synthesis is self-verified end-to-end via simulatePolicy / verifyPolicy and the runner of the deny-case harness before any bytes are emitted.

The package is pure ESM, node-compatible, and has a single runtime dependency (@stellar/stellar-sdk). MIT-licensed.

Install

npm install @crediolabs/policy-synth

Usage

There are two front-ends, both co-equal:

  • Recording — decode a real transaction, then infer the minimal policy.
  • Mandate — a declarative spec that lowers deterministically (no inference).
import {
  recordTransaction,
  synthesizeFromRecording,
  synthesizeFromMandate,
  placeholderOzConfig,
  type MandateSpec,
} from '@crediolabs/policy-synth'

const oz = placeholderOzConfig('mainnet')

// --- Recording front-end -------------------------------------------------
const recorded = await recordTransaction({ network: 'mainnet', hash: '<tx-hash>' })
if (!recorded.ok) throw new Error(recorded.error.message)

const inferred = synthesizeFromRecording(recorded.data, { network: 'mainnet' }, oz)
if (inferred.ok) console.log(inferred.data)

// --- Mandate front-end (deterministic) -----------------------------------
const spec: MandateSpec = {
  chain: 'stellar',
  contract: 'CTOKEN',
  method: 'transfer',
  spendingLimit: { token: 'CTOKEN', limit: '5000000', windowSeconds: 2592000 },
}
const deterministic = synthesizeFromMandate(spec, oz)
if (deterministic.ok) console.log(deterministic.data)

Every entry point returns a discriminated ToolResponse<T>:

type ToolResponse<T> = { ok: true; data: T } | { ok: false; error: ToolError }

ToolError carries a machine-readable code, a message, a severity, and a retryable flag, so callers (and agents) can branch without parsing prose.

Recording modes and the confidence gate

  • On-chain (hash set): fetched via the injected RPC fetcher (default: the public Soroban RPC for the requested network). Pass your own fetcher to use a custom endpoint or to test offline.
  • Simulation / XDR (xdr set): the envelope XDR is decoded directly.

XDR/simulation mode has no raw on-chain events, so the recorder cannot run its events cross-check and lowers parseConfidence. It therefore fails closed at the default threshold; to accept a simulation-only recording, pass an explicit confidenceOverride that clears the gate.

Interpreter predicate emission (the interpreter opt-in)

OZ built-ins express spending limits, simple thresholds, and weighted thresholds. They cannot express an exact ordered swap path, an oracle price bound, per-method scoping, a recipient allowlist, or an invocation-count window. The recording path surfaces those gaps as warnings by default ("Not covered by OZ built-in primitives: ..."). When the caller opts in to the interpreter adapter, the same constraint set is routed to the parallel interpreter IR; the adapter compiles it into a canonical predicate PolicyDocument and merges it with the OZ refs. The byte blob on the wire is the canonical XDR the on-chain interpreter will consume.

The opt-in is purely additive — every ToolResponse shape is unchanged; the two new fields are policyDocuments (the predicate-shaped interpreter doc) and one policyRef of kind: 'interpreter'.

import { Address } from '@stellar/stellar-sdk'
import {
  synthesizeFromRecording,
  placeholderOzConfig,
} from '@crediolabs/policy-synth'

const oz = placeholderOzConfig('mainnet')
const smartAccount = Address.contract(Buffer.alloc(32, 0xee)).toString()

const result = synthesizeFromRecording(
  recordedTx,
  {
    network: 'mainnet',
    userResponses: {
      windowSeconds: 2592000,                    // 30 days
      limitAmount: '1000000000',                 // supplied cap
      validUntilLedger: 200000000,               // future ledger
      oraclePriceBound: [                        // optional oracle bound
        { asset: 'CEURC', operator: 'lt', value: '1000000000' },
      ],
      swapRecipientAllowlist: ['GOWNER'],        // optional allowlist
    },
    interpreter: {
      smartAccountAddress: smartAccount,         // MUST be a C... contract address
      installNonce: 1,                           // first install -> 1
      // oracleParams: { maxStalenessSeconds: 60, maxDeviationBps: 100 }
      // (tighten-only vs the wasm defaults; widening is rejected)
    },
  },
  oz
)

if (result.ok) {
  console.log(result.data.policyDocuments.length)  // >= 1 when constraints are routable
  const interpreterRef = result.data.policyRefs.find((r) => r.kind === 'interpreter')
  console.log(interpreterRef?.predicateBlobBase64) // canonical XDR, base64
  console.log(result.data.contextRule.validUntilLedger)
}

The interpreter compile path is fail-closed:

  • SCOPE_SELF_CALL — the call's recipient equals the smart account.
  • ORACLE_LEAF_INVALID_POSITION — an oracle leaf is wrongly nested.
  • ORACLE_PARAMS_OUT_OF_RANGEoracleParams widening vs the wasm defaults.
  • SYNTHESIS_ERROR — the interpreter IR is not fully covered.
  • DENY_CASE_FAILURE — the emitted predicate fails the deny-case battery; details.failures lists the flipped dimension(s).

A recorded swap that compiles to a permissive policy under OZ alone therefore stays permissive unless the interpreter opt-in is supplied AND the predicate self-verifies end-to-end.

Self-verify + minimise (always-on with the opt-in)

Opting in to the interpreter also turns on the self-verify pipeline:

  1. The adapter emits the candidate predicate.
  2. The synth builds a permit EvalContext from the recorded transaction (the only call the user actually performed).
  3. The candidate is minimised — load-bearing-free top-level conjuncts are dropped (and predicates only; other shapes are returned unchanged).
  4. The minimised predicate is run through the deny-case battery — a structural fingerprint across contract, function, args, amount, window, oracle, recipient, frequency. Each case must deny.
  5. The intended recorded call is evaluated against the predicate; it must permit.
  6. The (possibly minimised) predicate is re-encoded; the canonical bytes + the SHA-256 hash are stamped back onto the PolicyDocument and the interpreter policyRef.

A successful ok: true is the proof that the emitted document is minimal AND self-verified. A failure surfaces the matching gate code (see above).

Simulate and verify (the verify/ surface)

The same self-verify pipeline is exposed as a public API for callers that want to re-run a check on a proposed predicate without re-synthesising:

import { simulatePolicy, verifyPolicy } from '@crediolabs/policy-synth'

// Runtime check: re-evaluate the predicate against the recorded call.
const runtime = simulatePolicy(predicate, recordedTx, {
  validUntilLedger: 200000000,
  oraclePricesByAsset: { CEURC: { price: '999999999', timestampSeconds: now } },
})

// Static minimality check: prove no top-level conjunct is load-bearing-free.
const staticCheck = verifyPolicy(predicate, recordedTx)

The boundary is pinned:

  • SIMULATION_ERROR — runtime evaluation failed (malformed fixture, missing oracle price, uncontrolled throw). The policy may still be minimal.
  • VERIFICATION_FAILED — the static minimality check failed. The policy is structurally over-broad regardless of how any concrete call evaluates.

Both are deterministic: same (predicate, recordedTx, opts) → byte-identical envelope.

Review-card (the human-audit surface)

The package emits a deterministic review-card summary so a human auditor can sanity-check the inferred policy without re-running the synthesis:

import {
  buildReviewCardSummary,
  classifyConflict,
  summaryCrossCheck,
} from '@crediolabs/policy-synth'

const summary = buildReviewCardSummary(proposedPolicy, recordedTx)
const conflict = classifyConflict(proposedPolicy, recordedTx)
const crossCheck = summaryCrossCheck(proposedPolicy, recordedTx)

The summary is the canonical human-readable digest of the proposed policy; classifyConflict flags refs that contradict the recording; summaryCrossCheck re-derives the summary from the raw refs and the recording, and reports discrepancies. All three are pure and deterministic.

Codegen escape hatch (the Rust interpreter)

When the canonical verifier is unavailable, the recorder can emit a Rust source file that performs the same predicate evaluation off-chain via a cargo build. The escape hatch is OUT of the audited happy path: the synthesiser never calls generateRust itself; the CLI subcommand is the only entry point.

import { generateRust, compileCheck, hasRustToolchain } from '@crediolabs/policy-synth'

if (await hasRustToolchain()) {
  const { source, path } = generateRust(predicate, { out: 'policy.rs' })
  const gate = await compileCheck({ crateDir: '.', predicate })
  if (!gate.ok) console.error('compile gate failed:', gate.error)
}

The escape hatch is toolchain-gated: hasRustToolchain() returns false when cargo is not installed, and compileCheck() refuses to run on a machine without one. The generated source is not the on-chain interpreter — it is a deterministic off-chain reference that re-evaluates the same predicate for parity testing.

Composition rules

The merged policyRefs on a ProposedPolicy are ordered [interpreterRef?, ...oz_builtinRefs] and bounded by OZ_LIMITS.maxPoliciesPerRule (5). The orchestrator refuses to install a policy that exceeds this cap (POLICY_CAP_EXCEEDED). The OZ-side uncovered warnings that the interpreter actually lowered (per-method scoping, recipient allowlists, exact ordered sequences, oracle price bounds, invocation-count windows, token-mismatch spending limits) are dropped from the user-facing warnings when the interpreter succeeds — the warning list reflects what is still UN-enforced, not what OZ alone could not do.

Status

Implemented and unit-test covered: the recorder, both synthesizer front-ends, the OZ Accounts adapter, the interpreter adapter, the predicate encoder, the evaluator, the deny-case battery, the minimiser, the self-verify pipeline, the simulate / verify surface, the review-card builder, the cross-check, and the Rust codegen escape hatch.

The on-chain Rust interpreter, install-transaction assembly, and live RPC integration are wired in the @crediolabs/policy-builder-cli layer (separate package).

License

MIT.