Skip to content

Caveats & Limitations

An honest list — read this before building on the bindings. Items are marked by design (a deliberate architectural decision, see the ADRs) or current limitation (expected to improve).

By design. The native library is offline, stateless, and side-effect-free: it makes no network calls and never submits a transaction. You supply chain data (UTXOs, protocol parameters) and submit the signed CBOR yourself with any HTTP client. Optional wrapper-side providers (Yaci DevKit, Blockfrost) and remote evaluators exist as conveniences — networking lives in the wrappers, never in libccl. Backend modules like Blockfrost/Koios/Ogmios clients are intentionally out of scope.

By design (for now). Node.js FFI libraries (ffi-napi, koffi) crash against GraalVM native-image libraries; Bun’s built-in bun:ffi works. Node support is wanted-but-blocked, not committed — revisit if Node FFI stabilizes.

Current limitation. A few native-library functions hit GraalVM reflection-configuration gaps and fail at runtime in all four wrappers:

BrokenUse instead
tx.from_json / tx.sign_with_secret_keyaccount.sign_tx (mnemonic-based signing)
plutus.data_to_json / plutus.data_from_jsonplutus.data_hash works; keep PlutusData as CBOR hex

Plutus transactions build fully offline: when you supply no execution units, the embedded Scalus UPLC evaluator computes them in-process. Caveats:

  • Scalus needs cost models in the protocol parameters; when absent, reference parameters are used as a fallback.
  • For node-authoritative costing, pass a remote evaluator (e.g. Blockfrost /utils/txs/evaluate) — the wrapper then does a two-pass build. Explicit units always take precedence.
  • Script/ledger evolution may outpace the embedded evaluator; the remote path is the escape hatch.
  • Python: one CclLib may be shared across threads — each OS thread attaches to the isolate lazily.
  • Go: all native calls are serialized onto one dedicated OS thread per Bridge (GraalVM isolates are thread-affine and goroutines migrate). Correctness over raw concurrency; use multiple Bridge instances for parallelism.
  • After close(), calls fail with a catchable error (e.g. Python’s CclClosedError) — this guards against passing a stale isolate handle to the native side, which would abort the whole process.
  • Pre-1.0, tracking a preview CCL. The bindings target CCL 0.8.0-pre4; the TxPlan schema is CCL’s and will be re-pinned when CCL 0.8.0 stabilizes. Expect breaking changes before 1.0.
  • Wrapper ↔ library version lock. The wrapper and native library must match on base semver; a mismatch fails fast at load (CCL_SKIP_VERSION_CHECK=1 overrides at your own risk).
  • Network enum ≠ on-chain network id. Network.MAINNET == 0 but a mainnet address’s on-chain network_id is 1. Never feed address.info()["network_id"] back into an API that takes a network.
  • Quantities are strings. Chain data carries amounts as strings ("quantity": "5000000") to avoid 2^53 float truncation — mind this in JavaScript especially.
  • Argument-order quirk: Python’s sign_tx/sign_tx_with_keys take the transaction before the network, unlike the other wrappers.

sign_tx witnesses with the payment key only. Stake, DRep, and committee certificates need their own witnesses via sign_tx_with_keys (roles: payment, stake, drep, committee_cold, committee_hot) — otherwise the node rejects with MissingVKeyWitnessesUTXOW. Each language’s Building Transactions page has the intent → roles table.

macOS Intel and Windows ARM64 have no prebuilt library; Alpine Python installs from source until musllinux wheels publish. Full matrix: Platforms & Packages.