Clarity is the smart contract language of the Stacks blockchain, and it is deliberately unlike every language a TypeScript developer has used: it is a LISP-shaped, decidable, non-Turing-complete language that ships its source to the chain rather than a compiled bytecode, so anyone can read exactly what they are calling. Cryptuon’s StxScript closes the ergonomics gap without giving any of that up — you write contracts with familiar TypeScript-style syntax, static types, decorators, generics, and match expressions, and the transpiler emits plain, readable, audit-ready Clarity that a reviewer can diff against your source line by line. There is no new VM, no runtime, and no token: StxScript is a build-time tool whose only output is Clarity text.
TL;DR
- Clarity’s weirdness is the feature. No reentrancy, decidable execution, no unbounded loops, and source published on-chain. Those properties exist because the language refuses things other languages allow.
- The cost is ergonomics. LISP prefix notation, no generics, verbose
responseplumbing, and an editor experience far behind what a TypeScript team expects. - StxScript is a transpiler, not a VM. It has a four-stage pipeline and emits Clarity source. The chain still runs Clarity, so every Clarity security property survives intact.
- The output is meant to be read. Emitted Clarity keeps your structure and naming, so an auditor reviews Clarity — the thing that actually executes — not a compiler’s internal representation.
- The toolchain is the real deliverable. A pip package ships
stxscript(CLI),stxscript-lsp(language server), a formatter, a linter, a scaffolder, and a watch mode. - The honest tradeoff: you now have a build step between you and the chain, and when the transpiler is wrong you debug two languages instead of one.
- See how we run Solana and Stacks migrations, or describe your project if you are migrating a Stacks codebase.
What makes Clarity unusual
If you arrive from Solidity, Clarity looks like a step backwards. That reaction lasts until the first time you read a post-mortem for a reentrancy exploit and realise the class of bug simply cannot be expressed in Clarity.
Decidability
Clarity is not Turing-complete, and this is intentional. There are no unbounded loops and no recursion. Every iteration construct — map, fold, filter — operates over a list whose maximum length is part of its type. That means the cost of any call can be determined statically, before execution, from the code alone.
For a TypeScript developer, the closest intuition is a type system that refuses to compile anything whose runtime is not provably bounded. It forbids programs you might want to write. It also forbids the entire category of “this function is fine until the array gets big enough” failures.
Interpreted, with source on chain
Solidity compiles to EVM bytecode, and verifying that a published contract matches its claimed source requires trusting a block explorer’s verification service, a reproducible build, and a matching compiler version. Clarity skips the problem: the source is the deployed artefact. What you read on the explorer is what executes.
This changes the economics of code review in a way that matters for a transpiler. Because the Clarity text is what ships, an auditor is not reviewing your StxScript — they are reviewing the emitted Clarity. A transpiler whose output is unreadable is therefore not merely inelegant; it actively degrades the security property that made the chain attractive.
No reentrancy
Clarity has no dynamic dispatch to an arbitrary address mid-execution in the way Solidity’s call does. Contract calls are statically known, and the language’s design prevents a callee from re-entering a caller before the caller’s state changes commit. The result is that the single most expensive bug class in EVM history does not exist here.
Response types everywhere
Every public function returns a response — either (ok value) or (err code). There are no exceptions, no revert with a string, and no silent failure. An (err ...) from a public function rolls back the whole transaction. This is Rust’s Result applied uniformly, and it is excellent for correctness and extremely verbose to write by hand.
Why a transpiler beats a new VM
The tempting alternative is to build a language that compiles to something else entirely, or to run a different VM alongside Clarity. Both throw away what makes Stacks worth building on.
A new VM means a new consensus surface, a new audit surface, and a new security history starting from zero. It also splits the ecosystem: contracts written for the new VM cannot be called by existing Clarity contracts, and the tooling, indexers, and wallets all need to learn a second execution model.
A transpiler does none of that. StxScript’s output is ordinary Clarity that any existing Clarity contract can call, that Clarinet can test, that the Stacks explorer renders, and that a Clarity auditor can review without learning StxScript at all. The transpiler is optional infrastructure sitting entirely on the authoring side of the boundary. If the project disappeared tomorrow, the contracts already deployed would keep running, and the emitted Clarity in your repository would remain perfectly maintainable by hand.
That last property is the acid test for any source-to-source tool. A transpiler you cannot walk away from is a lock-in mechanism wearing a compiler’s clothes.
The four-stage pipeline
StxScript is a conventional compiler front end with an unusual back end.
- Lex and parse. TypeScript-flavoured source becomes an AST. The grammar deliberately borrows TypeScript’s shape —
interface,type, generics, decorators, arrow functions — so an editor’s muscle memory transfers. - Type check. Static types are resolved and checked against Clarity’s type lattice. This is the stage that catches the majority of errors, and it catches them in terms of your source, with your line numbers.
- Lower to IR. The typed AST is lowered to an intermediate representation whose nodes map one-to-one onto Clarity forms. Constructs with no direct Clarity equivalent —
match, early returns, chained optionals — are desugared here. - Emit Clarity. The IR is printed as Clarity source with preserved naming and structure, then formatted.
The design constraint running through all four stages is that emission must be predictable. A developer should be able to look at a StxScript construct and know what Clarity it produces, because sooner or later they will be sitting next to an auditor doing exactly that.
Writing a SIP-010 token
SIP-010 is the fungible token standard on Stacks, the rough equivalent of ERC-20. Here is a minimal implementation in StxScript.
import { ft, Response, ok, err, Principal, UInt, StringAscii } from "stxscript";
const ERR_UNAUTHORIZED = 100;
const ERR_INSUFFICIENT = 101;
@fungibleToken("harbour")
declare const harbour: ft;
const CONTRACT_OWNER: Principal = txSender;
@map
const balancesMemo: Map<Principal, StringAscii<64>> = new Map();
@readonly
function getName(): Response<StringAscii<32>, never> {
return ok("Harbour");
}
@readonly
function getBalance(who: Principal): Response<UInt, never> {
return ok(ftGetBalance(harbour, who));
}
@public
function transfer(
amount: UInt,
from: Principal,
to: Principal,
memo: StringAscii<64> | null,
): Response<boolean, UInt> {
if (txSender !== from) {
return err(ERR_UNAUTHORIZED);
}
const moved = ftTransfer(harbour, amount, from, to);
if (moved.isErr) {
return err(ERR_INSUFFICIENT);
}
if (memo !== null) {
balancesMemo.set(to, memo);
}
return ok(true);
}
@public
function mint(amount: UInt, recipient: Principal): Response<boolean, UInt> {
if (txSender !== CONTRACT_OWNER) {
return err(ERR_UNAUTHORIZED);
}
return ftMint(harbour, amount, recipient);
}
And the Clarity it emits:
(define-fungible-token harbour)
(define-constant ERR_UNAUTHORIZED u100)
(define-constant ERR_INSUFFICIENT u101)
(define-constant CONTRACT_OWNER tx-sender)
(define-map balancesMemo principal (string-ascii 64))
(define-read-only (get-name)
(ok "Harbour"))
(define-read-only (get-balance (who principal))
(ok (ft-get-balance harbour who)))
(define-public (transfer
(amount uint)
(from principal)
(to principal)
(memo (optional (string-ascii 64))))
(begin
(asserts! (is-eq tx-sender from) (err ERR_UNAUTHORIZED))
(unwrap! (ft-transfer? harbour amount from to) (err ERR_INSUFFICIENT))
(match memo m (begin (map-set balancesMemo to m) true) true)
(ok true)))
(define-public (mint (amount uint) (recipient principal))
(begin
(asserts! (is-eq tx-sender CONTRACT_OWNER) (err ERR_UNAUTHORIZED))
(ft-mint? harbour amount recipient)))
Three things are worth noticing in that output.
First, the early return err(...) guards became asserts!. This is the idiomatic Clarity pattern and it is what a reviewer expects to see. A naive transpiler would emit nested if expressions, which are correct and much harder to read.
Second, memo: StringAscii<64> | null became (optional (string-ascii 64)), and the null check became a match. Clarity’s optional is genuinely an option type, not a nullable pointer, so the union type in the source is the honest spelling of it.
Third, the constant names, the map name, and the function bodies are recognisably yours. Function names are kebab-cased because that is Clarity convention, and nothing else is renamed, mangled, or inlined.
The type mapping
The whole surface of the language reduces to a mapping table. Knowing it is most of knowing StxScript.
| StxScript | Clarity | Notes |
|---|---|---|
UInt | uint | Unsigned 128-bit. Literals emit with the u prefix. |
Int | int | Signed 128-bit. |
boolean | bool | |
Principal | principal | Covers both standard and contract principals. |
StringAscii<N> / StringUtf8<N> | (string-ascii N) / (string-utf8 N) | The length parameter is mandatory; Clarity has no unbounded strings. |
Buffer<N> | (buff N) | |
T | null | (optional T) | Narrowing via !== null becomes match. |
Response<T, E> | (response T E) | ok(v) and err(e) map directly. |
T[] with @maxLen(N) | (list N T) | Length is part of the type, as in Clarity. |
interface / type object | (tuple ...) | Field order is normalised so tuples compare predictably. |
Map<K, V> with @map | define-map | .get / .set / .delete become map-get? / map-set / map-delete. |
@public / @readonly / @private | define-public / define-read-only / define-private | |
@trait | define-trait / impl-trait | Used to declare SIP-010 and SIP-009 conformance. |
The gaps are as informative as the mappings. There is no StxScript type that produces an unbounded list, because Clarity has none. There is no while, because Clarity has none. The transpiler will not invent a construct the target cannot express — it will refuse to compile.
Errors, match, and the plumbing Clarity makes you write
Hand-written Clarity spends a great deal of its line count on unwrap!, try!, asserts!, and nested match. It is correct, and it buries intent.
@public
function claim(id: UInt): Response<UInt, UInt> {
const listing = listings.get(id);
return match (listing) {
null => err(ERR_NOT_FOUND),
some(l) => {
if (l.claimed) return err(ERR_ALREADY_CLAIMED);
if (blockHeight < l.unlockHeight) return err(ERR_TOO_EARLY);
listings.set(id, { ...l, claimed: true });
const paid = stxTransfer(l.amount, contractPrincipal, txSender);
if (paid.isErr) return err(ERR_TRANSFER_FAILED);
return ok(l.amount);
},
};
}
The emitted Clarity threads the same logic through match, asserts!, and unwrap! in the order a Clarity developer would have written by hand. What the source buys you is that the business logic — not found, already claimed, too early, pay out — reads as four lines instead of being spread across four levels of nesting.
The { ...l, claimed: true } spread deserves a note. Clarity has merge for tuples, so the spread lowers to (merge l { claimed: true }). Object spread is one of the few places where TypeScript syntax happens to be a perfect fit for an existing Clarity primitive.
The toolchain
The compiler is the least interesting part of the package. What a TypeScript team actually misses when they move to Clarity is everything around the compiler.
pip install stxscript
stxscript new harbour-token --template sip010 # scaffold a project
stxscript build # emit ./contracts/*.clar
stxscript fmt --check # formatter, CI-friendly
stxscript lint # lints both source and emitted Clarity
stxscript check # type-check without emitting
stxscript watch # rebuild on save
stxscript doc # extract docs from annotations
stxscript-lsp provides the editor half: completion over the Clarity built-ins, hover types, go-to-definition across contracts, and inline diagnostics from the type checker. For a team used to an editor that knows what tx-sender is and whether the tuple field they just typed exists, this is the difference between a language they will adopt and one they will avoid.
The linter is the piece worth calling out. It lints in both directions — it flags StxScript-level mistakes, and it inspects the emitted Clarity for patterns a Clarity reviewer would object to. That second check is what keeps the transpiler honest: it is an explicit commitment that the output is meant to be read by humans who do not use the tool.
StxScript resources: stxscript.cryptuon.com · Documentation · Source on GitHub
Where it fits with Clarinet
StxScript is not a testing framework and does not try to be. Clarinet, Hiro’s toolchain, remains the way to run unit tests, simulate a chain, and manage deployments. The intended arrangement is that stxscript build emits .clar files into the directory Clarinet already watches, and Clarinet takes over from there. The two tools compose because StxScript’s output boundary is exactly the file format Clarinet consumes.
How the options compare
| Approach | Ergonomics | Auditability | Clarity guarantees preserved | Maturity |
|---|---|---|---|---|
| StxScript | TypeScript syntax, generics, match, LSP, formatter, linter | Emits readable Clarity that reviewers read directly; source-to-output diffing is the workflow | Fully — the chain runs plain Clarity | Production-leaning; small ecosystem, one implementation |
| Hand-written Clarity | Prefix notation, manual unwrap! plumbing, thin editor support | Best possible: one artefact, no build step, nothing between author and reviewer | Fully | Mature; the reference path, and what every auditor knows |
| Clarinet (alongside either) | Excellent testing, simnet, deployment plans | Not a language tool; does not change what ships | Fully | Mature, widely used, maintained by Hiro |
| Solidity plus a bridge to Stacks | Familiar to EVM teams | Adds a bridge’s trust assumptions to every contract interaction | No — you inherit EVM semantics, including reentrancy | Varies by bridge; a materially larger attack surface |
Hand-written Clarity genuinely wins on auditability, and it is dishonest to pretend otherwise. One artefact, no build step, no transpiler bug class, and every Stacks auditor already fluent in it. If your contract is eighty lines and will be reviewed by three people, write it in Clarity and skip the toolchain entirely.
StxScript’s case is about scale and team composition. It pays off when you have a TypeScript team, a contract surface large enough that the plumbing dominates the logic, and a codebase you expect to maintain for years. Below that threshold the build step is overhead.
Business outcome: what this actually saves
The measurable costs on a Stacks project are onboarding time, review time, and the defect rate in the plumbing between business rules.
Onboarding. Hiring a Clarity developer is hiring from a small pool. Retraining a TypeScript developer is not, but the first month is spent fighting prefix notation rather than learning the security model. StxScript moves that month: the syntax is free, and the developer’s attention goes to Clarity’s actual semantics — optional, response, post-conditions, decidability — which is where it should have been.
Review. A contract where the asserts! chain is generated consistently is faster to review than one where each function plumbs errors slightly differently. Consistency is not glamorous, but audit hours are billed by the hour, and mechanical uniformity in the emitted output means a reviewer can pattern-match instead of re-reading.
Defect surface. The class of bug that StxScript removes is not exotic: it is a missed unwrap!, a match arm that returns the wrong type, a tuple field misspelled. The type checker catches those at build time, in your source, with your line numbers. Clarity would catch most of them too, at deploy time, in a message referring to a form you did not write.
What StxScript emphatically does not save you from is design error. A transpiler cannot tell you your access control model is wrong. Cryptuon makes the same point about the other compiler in the portfolio in Deploy Solidity on Solana Without Learning Rust — syntax familiarity is a productivity tool, never a substitute for understanding the target chain.
Limitations
Current limitations
- A build step now exists between you and the chain. When a transpiler bug bites, you are debugging two languages and a mapping. The linter and the readable output are mitigations, not eliminations.
- Not every Clarity form has ergonomic sugar. Post-conditions, some trait interactions, and newer Clarity built-ins may need an escape hatch or a hand-written contract until the mapping catches up.
- The ecosystem is one implementation deep. There is no second StxScript compiler to cross-check against, which is a real difference from the TypeScript world.
- Auditors will still ask for the Clarity. That is correct and by design, but it means the emitted file is the review artefact and your source is context. Teams that treat the
.claroutput as a build product to be gitignored will have an uncomfortable audit. - No runtime means no runtime features. There is no StxScript standard library on chain, no helper contract, and no upgrade shim. Everything is compile-time.
Roadmap items
- Broader coverage of Clarity built-ins as the language evolves, with the mapping table generated from the compiler rather than maintained by hand.
- Source maps from emitted Clarity back to StxScript line numbers, so a Clarinet test failure points at the source you wrote.
- Deeper Clarinet integration, including generating test scaffolding from
@publicsignatures. - A formal statement of which emitted patterns are guaranteed stable across compiler versions, so audited output does not silently change shape on upgrade.
Frequently Asked Questions
What does StxScript compile to?
Plain Clarity source. The transpiler emits .clar files that are deployed exactly like hand-written Clarity contracts. There is no bytecode step, no runtime library, and no on-chain component belonging to StxScript. The Stacks chain never knows the tool exists.
Do I still need to learn Clarity?
Yes, and the tool is designed on that assumption. StxScript removes the syntax barrier, not the semantic one. You still need to understand response and optional types, post-conditions, decidability limits, and how tx-sender differs from contract-caller. What changes is that you learn those concepts while writing code that reads like code you already know.
Is Clarity really immune to reentrancy?
Clarity’s design eliminates the reentrancy pattern that dominates EVM exploits: there is no mid-execution dynamic call into arbitrary attacker-controlled code that can re-enter the caller before its state changes commit. That removes one very expensive bug class. It does not make contracts safe in general — authorisation errors, arithmetic mistakes, and flawed economic design are all still entirely possible.
Does StxScript have a token?
No. StxScript is a build-time developer tool released under the MIT licence and distributed as a pip package. There is no token, no protocol, no fee, and no on-chain component. Several Cryptuon projects do have tokens and several deliberately do not, as covered in Tokenomics Across Cryptuon: Earned vs Allocated.
Can I use StxScript with Clarinet?
Yes, and that is the intended workflow. Point stxscript build at the contracts/ directory Clarinet already reads, and use Clarinet for unit tests, simnet simulation, and deployment plans. The tools compose cleanly because StxScript’s output boundary is the file format Clarinet consumes.
How do I migrate an existing Clarity codebase?
Incrementally, and usually not all of it. Clarity contracts and StxScript-emitted contracts are the same artefact once built, so they call each other with no adapter. The practical pattern is to leave audited, deployed contracts untouched and author new ones in StxScript, converting older files only when they are being substantially rewritten anyway.
The bottom line
Clarity made a set of unusual choices — decidable, interpreted, source-on-chain, no reentrancy — and every one of them makes contracts easier to reason about. The price is an authoring experience that a TypeScript team finds alien, which shows up as slower onboarding and more time spent on plumbing than on logic.
StxScript pays that price down without touching the chain. Familiar syntax, a real type checker, an editor that understands your code, and an output file that an auditor reads as ordinary Clarity. The tool sits entirely on your side of the deployment boundary, which means the decision to use it is reversible in a way that adopting a new VM never is.
If you are weighing a Stacks project, migrating a Clarity codebase, or sizing the cost of moving a TypeScript team onto Bitcoin-settled contracts, see how we scope Solana and Stacks migrations, read the underlying design notes on research, or describe your project.