Bro why aren’t you staking in the network dude why would you build this when you can stake tokens
Only after confirming the naughty list at least 5/13 times, for those launching …
Very cool release, the browser == native == replica gate in particular.
Practical question before I put time into this: BSL rules out commercial production use, so as I read it I can evaluate and prototype but can’t ship anything that pays the bills. Is that right, or is there more room than the summary suggests?
Asking because the parts I’d actually want are production parts. Running moxzid as the backend for a product and moving the same bytes to mainnet later is a great story, but not one I can start if the license blocks the endpoint.
Also wondering about the reasoning generally. BSL is usually a hedge against a big competitor in a big market, and Motoko isn’t a big market. Was there a specific threat model, or was it mainly about keeping a commercial licensing path open to fund the work?
No objection to the second answer if that’s what it is, it’d just be useful to know which one it is before planning around it. And if there’s a funding number for ICDevs that shortens the four years, I’d like to hear it.
I think they only accept the tears of unfathomable sadness.
Yummy, yummy, you guys.
I literally just haven’t had time to think through the options and figure out what to do yet. I spent three months, thousands of literal dollars, and forewent another tens of thousands of dollars of other opportunities to build it. I know it can help make the community run faster and farther and bring in more devs, but I also have to be a bit responsible. I’m open to suggestions.
I WANT to just hand this to ICDevs.org to maintain. In reality, ICDevs needs some combination of $1M in ICP donated by some blessed soul or $1M dedicated through the ICDV Miner(miner.icdevs.org) by enough folks who are willing to donate their maturity to advancing IC stuff.
Perhaps I can do something under the BSUL where every $500 in dedicated ICP authorizes up to $50k in revenue using the license (or whatever the rational ratio is…maybe it is exponential). (I should be specific that dedicated means that the person keeps the ICP principal, but the maturity is harvested as a donation to OSS).
Like I said…I’m open to ideas and want it to get used, so I’m highly suggestive to solutions.
Good work, really impressive!
On-chain the compile ran as 2,379 messages under the 40B/message cap you cite, so the compiler yields and resumes about 2,400 times; on top of whatever DTS is already doing underneath. Two things I was curious about:
- How are the yield points determined? Is it
performance_counter(and if so, counter 0, which resets on every await, or counter 1, the call-context counter), or a compiler-internal deterministic work counter that doesn’t consult the system at all?
If it’s the former, where the chunks fall is a function of the replica’s cost model rather than of the source alone. You clearly have evidence that boundaries don’t leak into codegen; the local build isn’t chunked at all and still matches byte-for-byte, which is a fairly wide gap to survive. What I can’t tell from the outside is whether that’s an invariant make alpha-check enforces or a property that has held so far. Does anything re-run the corpus or the self-compile under a deliberately small or randomized per-message budget, so the same sources compile with an order of magnitude more chunks and still hash the same? That’s the version of the gate I’d trust, mostly because the boundary is also where GC and heap layuot get perturbed, and “allocation order leaks into output” is a classic way reproducible builds quietly stop being reproducible.
The reason I’m poking at this rather than assuming it’s fine: Dfinity has re-weighted wasm instrumentation before, and reported roughly ±20% movement on typical workloads and an order of magnitude on interpreters. A future re-weighting wouldn’t have to break anything to be annoying; it would silently change the chunk count, the cost estimate, and the escrow sizing. Which leads to the practical question: since every artifact names the compiler by hash, does it (or shoudl it) also record the replica version and subnet it was built on? Otherwise “the same compiler, the same sources” may not fully pin down the build a year from now.
- What happens when a unit of work can’t fit in one chunk? A single enormous function, a very large literal, a type that blows up during inference; if any of those is atomic in your pipeline, then there’s a class of programs moc compiles locally that can’t be compiled on-chain at any total budget, which would put a bound on the “read the source, trust the on-chain build” story. Do you have a sense of where that ceiling sits in practice, and is it detected up front as a clean diagnostic or only discovered by trapping partway through? And in the trap case, what happens to the 3x escrow? refunded, partially consumed, stuck?
Thanks!!
Why can’t it be as simple as a taking a very small cut of the ICP someone pays to fund a compilation of their source code…?
At the end of the day, people don’t want to track and/or have to trust multiple certificate authorities. There are network effects at play. If you take a small cut on the capital that funds each compilation, and in return users get a compiled onchain canister with a trusted certificate that ties their canister to specific source code - then everyone’s happy.
If you’re able to upgrade the compiler canister though then the certificates would become invalidated (IMO), hence my suggestion above a blackholed instance per language version and/or hotfix
I could probably do with some more fuzz testing around this, but we get a little for free because the local compiler runs kind of wide open and the on chain compiler looks a bunch of different things: 1. The amount of memory growth(If you grow too fast the IC slaps you and puts you in a penalty box - I think on chain we look for 4MB of growth and then yield) 2. Instruction counts: looking out for the dreaded 40B Instruction limit…I think we give pleanty of overhead.
Claude and I have to had this discussion many times over the last three months that “******* you ************ how many times do I ******* have to tell you that every ******* leaf has to be ************* resumable”. And thus we finally go to the point where both the compiler and ethgent could compile on chain. I cannot promise that a user can’t write some motoko still hits some edge case where it doesn’t hit a yield point and hits 40B, but many, many, many places have been routed out (For example, the evm has think like massive precompiled byte arrays for sha3…like 3MB string…where we had to make even the parsing of individual lines resumable.
Currently we run the wide open limits and the on chain tests and check for byte parity. They pass. There may be some arbitrarily small point where they wouldn’t, but I think it is good so far. The more use it gets, the more we find out (The test suite pulls down the top 40 mops packages and checks them.)
This is a great point! Probably worth adding to the compiler canister as we formalize it.
See above. I cuss until it gets broken up.
We have resumability tests that pass…I think we keep the minimum and refund the rest. It will be fun to find one that actually breaks on chain and test it out.
Gotcha. So if the yield triggers are memory growth and instruction count, then byte divergence isn’t the thing to worry about. Unchunked local vs 2,400 chunks on-chain producing the same bytes is already a wide gap to survive.
The real risk is the one you named… source that compiles locally and traps on-chain because some leaf on that path was never made resumable. Latent until someone writes it. So maybe skip the randomized budget idea and just compile the corpus with a low ceiling instead. 1B instead of 40B. Any non-resumable leaf over that lights up immediately, and you already pull the top 40 mops packages, so no new test inputs (passes at 1B, huge margin at 40B, cheaper than cussing at it)
This is certainly an option for the on-chain part. In fact it is how it currently works. The number of cycles are estimated, an escrow is taken, the compile runs, the cycles used are measured and 3x the Cycles are charged. 1x is used to refresh the cycles and 2x goes as a donation to ICDevs. The issue is that it is a one and done. Once you have the wasm and the record, no repeat builds. Maybe people end up compiling hundreds of thousands of huge canisters. Maybe not? And the compiler and the evm are like $3.50 worth of cycles. A simple hello world actor is like fractions of a penny. Like I said, it just needs to be sustainable so that I’m not paying out of pocket for maintenance. Do you think people will pay $25 for a certified build? If so then maybe it is more viable than I think, but I’m not sure. If it is too expensive people won’t use it. So how to find the sweet spot?
Moxzi is more than this though, there are also Web and Server runtimes which don’t touch ICP/on chain at all and that likely is a separate model.
I probably need to split those things out and handle them seperately.
I get that you want this to be open source…I want this to be open sourced…it will be open sourced but in one situation I get to keep working on it and adding the extraordinary list of bad ass features that getting to this stage enables and in the other I’m building dashboards for enterprises with AI and c# until claude renders me redundant. I prefer world A.
Absolutely impressive! I wonder why Dfinity never thought about doing it. Now, why not start working on Rust too? ![]()
I think the price elasticity of demand would favour a small markup on the true cost rather than a 2x or more. The bigger the cut the bigger the incentive for someone to launch a cheaper service.
If you want to play the long game, I think you should be aiming for Moxzi to become the well known certificate authority for verifiably built and signed Motoko canisters. The one that most people use (particularly in this sort of situation) eventually becomes the only one anyone uses (because everyone wants to use the well known and trusted certificate - else they’ll just compile off chain anyway).
If you want to ensure DFINITY or someone else doesn’t launch a competitor (because they want one they and the community can verify) down the road when it becomes obvious that this maps very neatly onto what the IC Intelligence Gateway and Caffeine could make heavy use of - then I suggest worrying more about gaining wide spread adoption and trust than about squeezeing every user for the absolute maximum you think you could get away with (until that’s no longer true).
Again, great work!
THIS. Please don’t open-source it until you have proper long-term sustainability plan in place first.
Over the years, you’ve contributed a tremendous amount to the IC ecosystem, much of it for FREE. I think it’s time to also think about yourself and how ICDevs can be supported sustainably for the long term.
Wishing you and ICDevs all the very best ![]()
@skilesare congratulations! The compiler compiling itself on mainnet, byte-identical with the local build, and then evm_engine on top of that. The byte-identical part is what convinced me, that is a lot of work ![]()
For me this is one of the missing pieces on ICP. On Ethereum you open Etherscan and the verified source is sitting right next to the contract, and people take it for granted. Here we still say “trust me, I built it from this repo”, and checking it means reproducing the build yourself with the same moc, same mops, same platform. Your version is even better than Etherscan actually, because there the compile runs on a company server, and here it runs in a canister anyone can audit.
One thought about the AstroFlora / ICRC-118 direction you mentioned.
moxzi proves the bytes: these sources produced this wasm. But a registry also needs the other half, did this upgrade change the interface?, and a wasm hash cannot answer it.
public query func greet(n : Text) : async Text // before
public query func greet(n : Text) : async ?Text // after
One character, and every caller in production is decoding a shape it never saw before. Adding a comment to the same file moves the wasm hash exactly the same amount. Same signal, completely opposite meaning. And in Motoko you never see the .did change, because moc derives it for you.
How are you planning to handle this part? Maybe you already solve it with didc or something else, I would like to know first.
I am working on something that might be useful in the process of the awesome tools you are building. didc check is the right tool when you have both files in your hand and you want a yes/no, so I am not trying to replace it. But it gives you a relation between two files at check time, not something you can store. A registry row cannot store a relation, it stores fields.
That is what candid-core adds: an interface_id, a content address over the canonical semantic interface, which ignores formatting, comments, declaration names and declarations the actor cannot reach. You can put it in a registry entry, in a proposal, in an index, and compare it later without having the old file at all.
And it is not another Candid implementation to trust. Parsing and type checking are delegated to the official candid_parser, it only canonicalizes on top.
If it is useful, I am happy to write one gate for your alpha-check: compare moxzi’s emitted .did against moc’s by interface_id instead of by string, so spacing and ordering do not create false diffs. Tell me where it should go and I will open a PR.
Small honesty: I am not full time on candid-core these days. But it is published and Apache-2.0, and I would enjoy pointing it at moxzi.
Congratulations again. A Motoko compiler running in a browser tab is wild ![]()
The candid interface text is a metadata section on the wasm, so that could be compared between wasms and as a source of truth of it’s public interface.
As @sea-snake mentions, the candid does get put in the metadata section of the wasm. That may happen after linking, though. I agree this is important to get right. Let me get the code out and then you can take a look to see if it already has this gate to a sufficient degree(it is supposed to check metadata emitted vs moc for the test corpus…It actually helped me find a type bug today where fixing an edge-case type fall-through messed up all the metadata)
Root cause: moc’s mo_to_idl.ml:97 inlines a con whose opened body is literally Prim p. In moc, Nat is itself a con, so type Key = Nat opens to Con(Nat,), misses that arm, and gets a decl. Our typer resolves Nat straight to #Prim, so the structural test swallowed every alias. Comparing the con’s name to the prim’s reproduces moc’s behaviour without reshaping the prelude — the same rule stabConIsAbbrev already uses for stable-types. Fixed at both the decl and use sites, which must agree or you emit a decl nothing references.
…so hopefully we are watching for things to go wrong, but tracking is also super important and I’ll need to think through how to track what is becoming a super tuple of important factors (replica, compiler version, produced candid, previous candid, code hash, replica setting(maybe))
I did a demo video showing off the web demos and some basics around moxzi:
https://x.com/afat/status/2094756204903878746
What else do you all want to see?
hi Austin
maybe some short videos on “how to do X using Moxzi” “how to solve Y using Moxzi” etc using small motoko projects
because i gotta be honest, im not that low level of a dev (it’s why i use motoko
) to understand compiler things so if we could skip all that technicalities and go straight to problems & how Moxzi solve them ![]()
![]()
@sea-snake you are right, thank you. The candid is inside the wasm as the icp:public candid:service section and moc puts it there itself. I am not really a Motoko developer, I mostly write Rust canisters, where the .did is a file you manage yourself and attach with ic-wasm as a separate step. So it looked to me like a standalone artifact, and on the Motoko side it is not.
Same for my gate idea @skilesare, please ignore that part. If that section lives inside a wasm you are claiming is byte-identical, you need exact text there, not a semantic comparison. Keep it strict.
Where I still think an id helps is the other thing you mentioned:
That is a different comparison from your gate. The gate is one version and two compilers, and there you want exact bytes. Tracking is two versions over time, and there the cosmetic churn is only noise. Today’s bug is a good example pointed the other way: when moc changes how it spells an alias, every stored candid text moves and no interface actually changed. If you diff text across versions you get a false alarm on every canister in the registry.
interface_id is just a fingerprint for that column. Equal means the same wire interface, and it stays stable across formatting, aliases, declaration names, and declarations the actor cannot reach.
But I should be clear about what it is not. It is not a compatibility check. Adding a method is backward compatible and the id still changes. dfx already does the subtype check from the same metadata, and that stays the right tool for “is this upgrade safe”. The id only answers “did the meaning change at all”, cheaply, without keeping both texts around.
Thanks to everyone who has helped test so far and shared their ideas. I just shipped alpha-3 so you can reinstall, and some things should be fixed:
0.1.0-alpha.3
Correctness and the measurements that found it. Every fix below was reduced to a
minimal repro, checked against moc 1.14.1, and landed only after the corpus came
back byte-identical and the compiler still reproduced itself (C1 == C2).
The compiler no longer ships a module it cannot load
moxzi build never checked its own output. On two programs it emitted a module
with a value left on the operand stack and reported success — exit 0, a .wasm
and a .did on disk, the failure deferred to dfx install. Every write site
(local build, --remote, link) now validates through wasmtime::Module::validate
with the build’s own engine, so it sees the feature set the module targets; on
failure nothing is written and the exit code is 1. MOXZI_EMIT_INVALID=1 keeps
the artifact for inspection.
Fourteen programs moc compiles and we did not
Measured against moc’s test/run, these are now compiled, linked and validated —
on-chain as well as off:
- A let annotation on a non-
VarPpattern was elaborated against an
environment holding only the prelude’s types, solet (a, b) : T = …reported
“unbound type T” for any namedT. TheM9000 unimplemented: PathTit printed
was a fallback symptom, not the cause. - Implicit arguments with a tuple parameter produced an unloadable module:
the argument tuple was spread unconditionally, sot((3, 3))lowered to
t(3, 3, eq)— three arguments for a two-parameter function.f(a, b)and
g((a, b))are the same syntax; only the callee’s arity separates them. - Bottom is accepted where an array or iterable is required —
(loop {})[0],
for (x in (loop {}).vals()). - Contextual dot resolves to a local function, with a local definition taking
precedence over a module’s and inner definitions shadowing outer ones. - Type fields in an object type (
module { type A = Int }) are elaborated
rather than dropped, including through an and-pattern
(let { type T1 } and { type T2 } = …). - A deferred lambda’s result is the LUB of its fall-through type and every
returnin it, sochars().map(func(c) { … return #space … })types. - A type parameter is never solved to a mutable type, which had made
contextual dot ambiguous on a[var T]receiver.
Interface and metadata
candid:servicekeeps named type aliases. We inlined them, so a service
publishedinsert: (k: nat, v: text)where moc publishes
type Key = nat; … insert: (k: Key, v: Value)— the same Candid, a different
interface to every binding generator. Fixed at both the declaration and use
sites.- A shared actor class builds its caller context even without a binder, and
object patterns hash their own field names rather than the object type’s.
Metadata mismatches against moc: 71 → 60.
Gates
- On-chain compile is now gated (
scripts/corpus_picjs_gate.sh, in
alpha_check): every program in moc’stest/runcompiled inside the compiler
canister under PocketIC, linked through the linker canister, and validated.
269 of 273 — the four absent are programs moc 1.14.1 rejects too. Nothing had
measured this: the runner drove the retired classical backend, so every run
died on dead IR. - The behaviour gate was silently skipping. It looked for an interpreter with
wasmtimein two places, found neither, printedSKIPand exited 0 — a green
tick for 499 programs it never ran. Baseline 359 → 365, with four programs
recovered that had been burning the per-test timeout because the driver could
not answer#needClass, and one (non-motoko-install) that had never run at
all because a gzipped prebuilt artifact was fed to the Motoko compiler as
source. - Sixteen retired non-EOP milestone probes removed; the classical backend is
unsupported and nothing drives it now.
Known, measured, and not fixed
60 programs differ from moc in debug metadata only — nothing a replica executes
or a client reads. 51 are the “motoko” reverse-hash table, where we now
over-record because we compile more of the internals than moc does; 15 are
motoko:stable-types, 5 of those whitespace-only (our layout engine measures a
break against the current box, OCaml’s Format computes break sizes globally).
Both are reduced and written up under test/selfhost/probe-pending/.