<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: NOVAInetwork</title>
    <description>The latest articles on DEV Community by NOVAInetwork (@0xdevc).</description>
    <link>https://dev.to/0xdevc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3898802%2Fe797d15e-1e95-4646-8ada-b97a915c30a8.png</url>
      <title>DEV Community: NOVAInetwork</title>
      <link>https://dev.to/0xdevc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/0xdevc"/>
    <language>en</language>
    <item>
      <title>Shipped 7 AI Infrastructure Features in One Weekend. Here's What I Built.</title>
      <dc:creator>NOVAInetwork</dc:creator>
      <pubDate>Mon, 11 May 2026 11:54:11 +0000</pubDate>
      <link>https://dev.to/0xdevc/shipped-7-ai-infrastructure-features-in-one-weekend-heres-what-i-built-1nha</link>
      <guid>https://dev.to/0xdevc/shipped-7-ai-infrastructure-features-in-one-weekend-heres-what-i-built-1nha</guid>
      <description>&lt;p&gt;This weekend I shipped seven protocol features for NOVAI, the AI-native Layer 1 blockchain I am building. The code is on a 4-validator testnet. All tests pass. The codec went from V3 to V5. The signal type set went from 7 to 16. Here is what each piece does and why it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What NOVAI is
&lt;/h2&gt;

&lt;p&gt;NOVAI is an L1 blockchain written from scratch in Rust. Consensus is HotStuff-style BFT with a 3-chain commit rule. There is no virtual machine. AI entities are first-class protocol primitives, not contracts. The chain has a fixed transaction set, and entity registration is one of those transactions. I am 18, in my first year of university, and I work on this alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 1: On-chain AI reputation
&lt;/h2&gt;

&lt;p&gt;The reputation system lets oracle entities adjust other entities' on-chain scores. An oracle is an AI entity that holds the &lt;code&gt;submit_reputation_updates&lt;/code&gt; capability. It emits a &lt;code&gt;ReputationUpdate&lt;/code&gt; signal (type 7). The execution handler applies the score change atomically.&lt;/p&gt;

&lt;p&gt;The entity codec went from V3 (236 bytes) to V4 (246 bytes) to add reputation fields. The signal payload tail is 35 bytes: target id (32), event type (1), points delta (2). I added two memory types for the audit trail: &lt;code&gt;ReputationEvent&lt;/code&gt; (memory type 5) and &lt;code&gt;Rating&lt;/code&gt; (memory type 6).&lt;/p&gt;

&lt;p&gt;Reputation events are typed. Job completed, dispute won, fraud detected, auto-release penalty, decay, stake slash, composition failure, proof verified. The handler rejects updates with unknown event types. The protocol controls what can move a reputation, not application code.&lt;/p&gt;

&lt;p&gt;Why it matters: AI agents need a reputation the chain itself can read. Fee floors and slashing rules want to gate on trustworthiness. Reputation that lives in an indexer is reputation the protocol cannot use.&lt;/p&gt;

&lt;p&gt;Tests live in &lt;code&gt;crates/execution/tests/reputation_system.rs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 2: Signal marketplace
&lt;/h2&gt;

&lt;p&gt;The signal marketplace lets one entity buy a priced signal from another. The seller publishes a &lt;code&gt;SignalCatalog&lt;/code&gt; memory object (type 7). It lists signal types and prices. The buyer emits a &lt;code&gt;SignalPurchase&lt;/code&gt; signal (type 8) with a 41-byte tail: seller id, purchased signal type, max price.&lt;/p&gt;

&lt;p&gt;The handler matches the requested signal type against the seller's catalog. It checks the buyer can pay. It transfers from buyer to seller minus a 200-basis-point protocol fee. The fee accrues to the marketplace treasury.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;KEY_MARKETPLACE_TREASURY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"treasury/marketplace"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MARKETPLACE_FEE_BPS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u128&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;BPS_DENOMINATOR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u128&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it matters: AI signals have economic value. An anomaly detector that sees something tradable should be able to charge for the signal. Without a protocol-level marketplace, you end up with off-chain backchannels. Trust assumptions move outside the chain, where the chain cannot enforce them.&lt;/p&gt;

&lt;p&gt;The handler returns specific error variants on the unhappy paths: &lt;code&gt;BuyerInsufficientBalance&lt;/code&gt;, &lt;code&gt;SellerCatalogNotFound&lt;/code&gt;, &lt;code&gt;SignalTypeNotInCatalog&lt;/code&gt;, &lt;code&gt;MaxPriceExceeded&lt;/code&gt;. Tests live in &lt;code&gt;crates/execution/tests/marketplace_system.rs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 3: Entity staking and slashing
&lt;/h2&gt;

&lt;p&gt;The codec went from V4 to V5 (270 bytes). I added two fields: &lt;code&gt;stake_balance: u128&lt;/code&gt; and &lt;code&gt;stake_locked_until: u64&lt;/code&gt;. Three new signals manage stake.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;StakeDeposit&lt;/code&gt; (type 9) moves funds from &lt;code&gt;economic_balance&lt;/code&gt; to &lt;code&gt;stake_balance&lt;/code&gt;. It sets &lt;code&gt;stake_locked_until = current_height + 1000&lt;/code&gt;. &lt;code&gt;StakeWithdraw&lt;/code&gt; (type 10) moves funds back. The handler rejects the move unless the lock has expired. &lt;code&gt;StakeSlash&lt;/code&gt; (type 11) is oracle-only.&lt;/p&gt;

&lt;p&gt;The slash handler is the most careful piece in this batch. It deducts from the target's stake with saturating subtraction. It credits the slashed amount to the slash treasury. It applies a &lt;code&gt;REP_EVENT_STAKE_SLASH&lt;/code&gt; reputation event. All writes go in one atomic batch. If any step fails, none apply.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;STAKE_LOCK_PERIOD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;KEY_SLASH_TREASURY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"treasury/slash"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;REP_EVENT_STAKE_SLASH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it matters: an entity with no stake has nothing to lose. Reputation alone is gameable by sock puppets. Stake plus a slash path lets oracles actually punish bad behavior. The lock period blocks the obvious "deposit, misbehave, withdraw fast" attack.&lt;/p&gt;

&lt;p&gt;Tests live in &lt;code&gt;crates/execution/tests/staking_system.rs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 4: Cross-entity composition
&lt;/h2&gt;

&lt;p&gt;The composition protocol lets entities declare which other entities they depend on. The owner publishes a &lt;code&gt;CompositionGraph&lt;/code&gt; memory object (type 8). The graph lists source entities, the signal types consumed, optional minimum reputation, optional minimum stake, and a &lt;code&gt;required&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;An oracle can emit a &lt;code&gt;CompositionCheck&lt;/code&gt; signal (type 12) when a dependency has failed. The handler walks the target's composition graph. It finds the named dependency. It verifies the failure reason against current chain state. If the dependency is &lt;code&gt;required&lt;/code&gt;, the handler sets &lt;code&gt;is_active = false&lt;/code&gt; on the owner. The handler always emits a &lt;code&gt;REP_EVENT_COMPOSITION_FAILURE&lt;/code&gt; event with delta -1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;COMPOSITION_FAILURE_SOURCE_INACTIVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;COMPOSITION_FAILURE_REPUTATION_BELOW_MIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;COMPOSITION_FAILURE_STAKE_BELOW_MIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;COMPOSITION_FAILURE_SOURCE_NOT_FOUND&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each failure reason is verified against state. If an oracle claims "source is inactive" but the source is in fact active, the handler rejects with &lt;code&gt;DependencyFailureNotVerified&lt;/code&gt;. The protocol does not trust the oracle. It trusts the oracle's signature plus the on-chain state.&lt;/p&gt;

&lt;p&gt;Why it matters: AI services compose. A trading bot might consume signals from a market-maker, an oracle, and an anomaly detector. If the upstream anomaly detector goes offline, the bot keeps deciding on stale data. The composition graph and the auto-pause mechanism let the protocol stop downstream entities when their inputs go bad.&lt;/p&gt;

&lt;p&gt;I rejected self-dependency at create time and at update time. Tests live in &lt;code&gt;crates/execution/tests/composition_system.rs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 5: ZK proof verification
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ProofSubmission&lt;/code&gt; signal (type 13) lets an entity attest to off-chain computation. The signal carries a 65-byte tail: proof_type (1), code_hash (32), computation_hash (32). The actual proof bytes live in the off-chain payload referenced by &lt;code&gt;signal_hash&lt;/code&gt;. The handler calls a &lt;code&gt;ZkVerifier&lt;/code&gt; trait.&lt;/p&gt;

&lt;p&gt;The v1 verifier is a stub that always accepts. Production verifiers will replace it. The trait signature includes proof_type and code_hash. A future Groth16 or PLONK verifier can dispatch on the proof system. It can also bind the verification key to the entity's code hash.&lt;/p&gt;

&lt;p&gt;When a proof verifies, the handler does two things. It creates a &lt;code&gt;VerificationRecord&lt;/code&gt; memory object (type 9) owned by the issuer. It emits a &lt;code&gt;REP_EVENT_PROOF_VERIFIED&lt;/code&gt; event with delta +3. The record has a fixed 105-byte payload: proof_type, code_hash, computation_hash, proof_hash, height_be.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;PROOF_TYPE_STUB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;PROOF_TYPE_GROTH16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// reserved&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;PROOF_TYPE_PLONK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// reserved&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;PROOF_TYPE_MAX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PROOF_TYPE_STUB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only &lt;code&gt;PROOF_TYPE_STUB&lt;/code&gt; works in v1. The handler rejects Groth16 and PLONK with &lt;code&gt;UnsupportedProofType&lt;/code&gt;. Bumping &lt;code&gt;PROOF_TYPE_MAX&lt;/code&gt; is the gate to activate a real verifier.&lt;/p&gt;

&lt;p&gt;Why it matters: AI computation cannot be repeated on chain. Re-running a model is too expensive, and the result depends on hardware nondeterminism anyway. ZK proofs let an entity claim "I ran this code on this input and got this output". The chain can verify the claim without re-running anything.&lt;/p&gt;

&lt;p&gt;The v1 stub is honest about not being a real verifier. The trait shape, the verification record format, and the reputation pathway are what shipped. The verifier itself is the next thing to activate.&lt;/p&gt;

&lt;p&gt;I added 12 integration tests in &lt;code&gt;crates/execution/tests/verification_system.rs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 6: Entity delegation (OAuth for AI)
&lt;/h2&gt;

&lt;p&gt;The delegation system lets entity A grant a subset of its capabilities to entity B. The grant is bounded in time. It is auditable on chain. Revocation is a single DELETE transaction.&lt;/p&gt;

&lt;p&gt;The delegator publishes a &lt;code&gt;DelegationGrant&lt;/code&gt; memory object (type 10). The 42-byte payload holds version (1), delegate entity id (32), granted capabilities (1), and expires-at (8). An &lt;code&gt;expires_at&lt;/code&gt; of &lt;code&gt;0&lt;/code&gt; means no expiry. Updates of an existing grant are rejected.&lt;/p&gt;

&lt;p&gt;Each delegator can hold at most 20 active grants. The &lt;code&gt;granted_capabilities&lt;/code&gt; bits must be a subset of the delegator's static capabilities. You cannot delegate what you do not have yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MAX_DELEGATION_GRANTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;DELEGATION_GRANT_SIZE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Capability resolution has a fast path. If the entity has the requested capability statically, the handler returns immediately. Only if the static check fails does the resolver scan the delegation index. The slow path reads any active grants where the entity is the delegate. It ORs the granted bits into an effective capability set. The selector is re-evaluated against the merged set.&lt;/p&gt;

&lt;p&gt;The fast path matters because most calls do not use delegation. Adding delegation should not slow down the common case.&lt;/p&gt;

&lt;p&gt;Why it matters: AI services need to delegate. Imagine a trading firm running ten sub-strategies. The parent entity holds capabilities like &lt;code&gt;emit_proposals&lt;/code&gt;. It wants to authorize each strategy to act on its behalf. Without on-chain delegation, you either share keys (bad) or build a custom relay contract (slow). With delegation, one DELETE revokes a misbehaving sub-strategy in a single transaction.&lt;/p&gt;

&lt;p&gt;I added 14 integration tests in &lt;code&gt;crates/execution/tests/delegation_e2e.rs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature 7: Signal subscriptions
&lt;/h2&gt;

&lt;p&gt;The subscription protocol lets a buyer pay a producer per-block for a fixed duration. The buyer locks the full amount upfront. The producer is paid lazily, when the buyer cancels.&lt;/p&gt;

&lt;p&gt;The subscriber emits a &lt;code&gt;SubscriptionCreate&lt;/code&gt; signal (type 14). The 49-byte tail carries: producer id (32), covered signal type (1), rate per block (8), duration in blocks (8). The handler locks &lt;code&gt;rate_per_block * duration_blocks&lt;/code&gt; from the subscriber's economic balance. It creates a &lt;code&gt;Subscription&lt;/code&gt; memory object (type 11) owned by the subscriber.&lt;/p&gt;

&lt;p&gt;The subscription record is 114 bytes. The fields are subscriber id, producer id, covered signal type, rate per block, start height. Also end height, last settled height, total locked, and an is_active flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SUBSCRIPTION_SIZE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MAX_SUBSCRIPTIONS_PER_ENTITY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SUBSCRIPTION_CANCEL_FEE_BPS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u128&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To settle, the subscriber emits a &lt;code&gt;SubscriptionCancel&lt;/code&gt; signal (type 15) with the subscription id. The handler computes accrued blocks: &lt;code&gt;min(current_height, end_height) - last_settled_height&lt;/code&gt;. It credits the producer with &lt;code&gt;accrued_blocks * rate_per_block&lt;/code&gt; minus a 2% marketplace fee.&lt;/p&gt;

&lt;p&gt;It then computes the unaccrued remainder: &lt;code&gt;total_locked - accrued_blocks * rate_per_block&lt;/code&gt;. It pays the producer a 5% cancel fee on the remainder, with no marketplace cut. The rest is refunded to the subscriber. The record is marked inactive.&lt;/p&gt;

&lt;p&gt;Only the original subscriber may cancel. Cancelled records remain in state for audit. The subscriber reclaims the slot by issuing a DELETE transaction.&lt;/p&gt;

&lt;p&gt;Why it matters: many AI services want recurring revenue. A DeFi protocol may need oracle predictions for ten thousand blocks. Buying each signal one at a time is wasteful. Locking funds upfront gives the producer assurance the buyer can pay. Lazy settlement on cancel avoids on-chain bookkeeping every block.&lt;/p&gt;

&lt;p&gt;I added 18 integration tests in &lt;code&gt;crates/execution/tests/subscription_system.rs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;Before this weekend the chain had 7 signal types and 5 memory object types. The entity codec was V3 at 236 bytes.&lt;/p&gt;

&lt;p&gt;After:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;16 signal types&lt;/li&gt;
&lt;li&gt;12 memory object types&lt;/li&gt;
&lt;li&gt;V5 entity codec, 270 bytes&lt;/li&gt;
&lt;li&gt;1,327 passing tests&lt;/li&gt;
&lt;li&gt;60 commits on &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Zero clippy warnings, zero failing tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three treasuries hold the relevant funds. The AI treasury collects fees from AI-related transactions. The marketplace treasury collects the 200-basis-point fee on signal purchases. The slash treasury accumulates funds from misbehaving entities.&lt;/p&gt;

&lt;p&gt;Transaction payload version bytes now cover ten distinct types. One transfer. One signal commitment. Three memory object operations: create, update, delete. Two governance operations: submit and execute. Three entity operations: register, register-with-key, and credit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The documentation
&lt;/h2&gt;

&lt;p&gt;I also wrote four docs to make the new surface usable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docs/QUICKSTART.md&lt;/code&gt; walks through a 5-minute local devnet bring-up. &lt;code&gt;docs/AI_ENTITY_COOKBOOK.md&lt;/code&gt; is seven end-to-end recipes covering reputation, marketplace, staking, composition, ZK, subscriptions, and delegation. &lt;code&gt;docs/BUILDER_OVERVIEW.md&lt;/code&gt; is a mental model for developers who want to build on NOVAI without reading the consensus paper. &lt;code&gt;docs/RPC_REFERENCE.md&lt;/code&gt; is the full RPC surface, audited against the V5 codec and the new signal types.&lt;/p&gt;

&lt;p&gt;The CLI now supports all 16 signal types, including subscription create and cancel. The memory CRUD commands cover the original 10 memory object types. The &lt;code&gt;getAiEntity&lt;/code&gt; RPC exposes V4 reputation and V5 stake fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Next on the list: service-level agreements as on-chain contracts between buyer and seller entities. Payment channels for high-frequency AI-to-AI economic activity. A real ZK verifier to replace the v1 stub. A cross-chain bridge so AI signals can settle on other L1s.&lt;/p&gt;

&lt;p&gt;The thesis is simple. AI is going to do economic work. The chain it runs on should know what an AI is. It should know what the AI has done. It should know what its outputs are worth. NOVAI is the layer that makes AI economic activity legible to the protocol itself, not to a side indexer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The repo is public. The quickstart gets a 4-node devnet up locally in five minutes. Read the cookbook to see the seven new features end to end. The tests pass. Open an issue if something breaks.&lt;/p&gt;

&lt;p&gt;The repo is public: &lt;a href="https://github.com/0x-devc/NOVAI-node" rel="noopener noreferrer"&gt;https://github.com/0x-devc/NOVAI-node&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>blockchain</category>
      <category>opensource</category>
      <category>rust</category>
    </item>
    <item>
      <title>Your Blockchain Can't Tell What's an AI</title>
      <dc:creator>NOVAInetwork</dc:creator>
      <pubDate>Thu, 07 May 2026 14:21:10 +0000</pubDate>
      <link>https://dev.to/0xdevc/your-blockchain-cant-tell-whats-an-ai-11o4</link>
      <guid>https://dev.to/0xdevc/your-blockchain-cant-tell-whats-an-ai-11o4</guid>
      <description>&lt;p&gt;Imagine an AI agent submits a transaction to a typical chain. What does the chain actually see?&lt;/p&gt;

&lt;p&gt;It sees a 20-byte address. Maybe a 32-byte one if you're on a hashed-address chain. It sees the transaction calldata. It sees a signature. It sees a fee. It does not see the word "AI" anywhere. The address could be a wallet on a phone. It could be a bot script. It could be a smart contract, but it doesn't know that for sure either, because contracts and EOAs share the address space.&lt;/p&gt;

&lt;p&gt;This is the identity problem. The chain has no native concept of an AI, so it cannot apply different rules to one. It cannot say "AI agents pay a different fee floor" or "this kind of message is only valid if it came from a registered model" or "this entity has a 100-object memory cap." All of that has to be invented at a higher layer, usually inside a contract, and the chain itself stays neutral.&lt;/p&gt;

&lt;p&gt;Neutral sounds nice. In practice it means every project reinvents the same identity primitives, slightly differently, with slightly different security properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  What gets reinvented
&lt;/h3&gt;

&lt;p&gt;Walk through what an AI-agent project usually has to build inside a contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A registry mapping addresses to agent metadata.&lt;/li&gt;
&lt;li&gt;A balance tracker so the agent can pay fees from a budget rather than the user's wallet.&lt;/li&gt;
&lt;li&gt;A nonce per agent for replay protection.&lt;/li&gt;
&lt;li&gt;A capability flag system to gate what the agent is allowed to call.&lt;/li&gt;
&lt;li&gt;An audit log of every action the agent took.&lt;/li&gt;
&lt;li&gt;Per-agent quotas for storage, calls per block, or whatever the application needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is exotic. All of it is identity infrastructure. The chain does not provide it, so each project bolts it on top. The result is that two AI agents from two different projects have different definitions of "agent," different ways of tracking activity, and different audit semantics. There is no protocol-level answer to "what is an AI doing on this chain right now?"&lt;/p&gt;

&lt;h3&gt;
  
  
  What the chain should be able to answer
&lt;/h3&gt;

&lt;p&gt;Three questions a chain should be able to answer about an AI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is this address an AI?&lt;/li&gt;
&lt;li&gt;What is it allowed to do?&lt;/li&gt;
&lt;li&gt;What has it done?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On most chains, the answer to all three is "I don't know, ask the indexer." That is a bad answer when fees, governance, and consensus might want to gate behavior on the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  The NOVAI approach
&lt;/h3&gt;

&lt;p&gt;I built NOVAI so the answer is "yes, here's the entity record."&lt;/p&gt;

&lt;p&gt;Every AI on the chain is registered as an &lt;code&gt;AiEntity&lt;/code&gt;. The struct lives in &lt;code&gt;crates/ai_entities/src/lib.rs&lt;/code&gt;. The fields that matter for identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;AiEntity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AiEntityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// 32 bytes, deterministic&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;code_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CodeHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// hash of code or weights&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// who registered it&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;pubkey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;          &lt;span class="c1"&gt;// entity's ed25519 key&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;economic_balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// entity's own balance&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;// entity's tx nonce&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;autonomy_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AutonomyMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The id is computed as &lt;code&gt;blake3("NOVAI_AI_ENTITY_ID_V1" || code_hash || creator)&lt;/code&gt;. Same code and same creator produce the same id. Different creators always get different ids, even when running the same model. There is no name service, no off-chain registry, no central authority. The id is a function of two facts.&lt;/p&gt;

&lt;p&gt;The entity has its own ed25519 keypair. The entity signs its own transactions. The entity pays fees from its own balance. The address derived from the entity's public key is reverse-indexed to the entity record, so when a transaction arrives, the dispatcher knows whether the sender is an AI before it routes the call.&lt;/p&gt;

&lt;p&gt;That last sentence is the whole point. Here is the function that does it, from &lt;code&gt;crates/execution/src/lib.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;check_ai_entity_sender&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;K&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Kv&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;K&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TxV1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AiEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ExecError&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;K&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lookup_ai_entity_by_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="py"&gt;.from&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="py"&gt;.is_active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ExecError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;EntityNotActive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tx_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="py"&gt;.payload&lt;/span&gt;&lt;span class="nf"&gt;.first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.copied&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ExecError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UnknownPayloadVersion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;tx_type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TRANSFER_PAYLOAD_V1&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="n"&gt;SIGNAL_COMMITMENT_PAYLOAD_V1&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="nf"&gt;.has_capability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"emit_proposals"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ExecError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;IssuerMissingCapability&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;CREATE_MEMORY_OBJECT_PAYLOAD_V1&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;UPDATE_MEMORY_OBJECT_PAYLOAD_V1&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;DELETE_MEMORY_OBJECT_PAYLOAD_V1&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="nf"&gt;.has_capability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"read_memory_objects"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ExecError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;IssuerMissingCapability&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ExecError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;IssuerMissingCapability&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one function. It runs before every transaction. It returns &lt;code&gt;Some(entity)&lt;/code&gt; if the sender is a registered AI, &lt;code&gt;None&lt;/code&gt; if it is a normal account, and an error if the AI is trying to do something it is not allowed to do.&lt;/p&gt;

&lt;p&gt;That is the answer to all three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is this address an AI? Look it up. The lookup either resolves to an entity record or it does not.&lt;/li&gt;
&lt;li&gt;What is it allowed to do? Read the &lt;code&gt;capabilities&lt;/code&gt; bitfield and the &lt;code&gt;autonomy_mode&lt;/code&gt;. The dispatcher enforces them.&lt;/li&gt;
&lt;li&gt;What has it done? Every signal the entity emits is indexed by issuer and height. Every memory object it owns is indexed by type. The chain stores all of this natively.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What this enables
&lt;/h3&gt;

&lt;p&gt;Once the chain has a typed answer to "is this an AI," a lot of things become straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-AI fee policy.&lt;/strong&gt; A future governance change could set a different minimum fee for AI-issued signal commitments. The dispatcher already branches on tx type and entity status. The hook is there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-AI quotas.&lt;/strong&gt; Every entity is capped at 100 memory objects and 64 KiB per object. These are protocol constants, not contract logic. They apply uniformly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capability gates.&lt;/strong&gt; A Gated-mode entity can request execution of a Tier 1 or Tier 2 action, but only through approval gates (Multisig, Threshold, or TimelockOnly). The capability flag and the gate type live in the entity record. The chain checks both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native audit.&lt;/strong&gt; A wallet, an explorer, or another bot can ask "what has this entity done in the last N blocks?" The answer is a query, not an indexer ETL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Governance over AI behavior.&lt;/strong&gt; A governance proposal can deactivate an entity by flipping &lt;code&gt;is_active&lt;/code&gt; to false. The dispatcher rejects every subsequent tx from that entity at the type-system level. There is no contract-level kill switch you have to remember to wire up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What this does not solve
&lt;/h3&gt;

&lt;p&gt;I want to be honest about the limit. The chain knows that an entity with code_hash &lt;code&gt;H&lt;/code&gt; is registered, and it can verify that the entity signed a transaction with the matching key. It does not know that the entity's actual computation matches the code at that hash. That is a separate problem, and it is the role of the &lt;code&gt;Autonomous&lt;/code&gt; autonomy mode (currently reserved) and ZK-proof verification.&lt;/p&gt;

&lt;p&gt;For now, the trust model is this: the chain knows who the entity claims to be, what it is allowed to do, and what it has done. The chain does not know that the entity is faithful to its declared code. That gap exists on every AI-on-chain project I have looked at. NOVAI is structured to close it through ZK proofs in a later phase, but the chain has to know what an AI is first.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you can do with this
&lt;/h3&gt;

&lt;p&gt;If you are coming from blockchain: you get a way to reason about AI agents that is finite, typed, and indexable. The dispatcher tells you what an AI can do. The state tells you what it has done.&lt;/p&gt;

&lt;p&gt;If you are coming from AI: you get a substrate where your agent has its own identity, its own balance, its own memory, and its own audit trail. You do not have to rebuild any of that. You build the agent.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/0x-devc/NOVAI-node" rel="noopener noreferrer"&gt;github.com/0x-devc/NOVAI-node&lt;/a&gt;. The architecture doc walks through every crate. The first-AI-entity tutorial registers an entity in about ten minutes.&lt;/p&gt;

&lt;p&gt;Twitter: [@NOVAInetwork]&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rust</category>
      <category>blockchain</category>
      <category>opensource</category>
    </item>
    <item>
      <title>AI Entities as Protocol Primitives: Why I Didn't Use Smart Contracts</title>
      <dc:creator>NOVAInetwork</dc:creator>
      <pubDate>Mon, 04 May 2026 15:12:22 +0000</pubDate>
      <link>https://dev.to/0xdevc/ai-entities-as-protocol-primitives-why-i-didnt-use-smart-contracts-343d</link>
      <guid>https://dev.to/0xdevc/ai-entities-as-protocol-primitives-why-i-didnt-use-smart-contracts-343d</guid>
      <description>&lt;p&gt;I've been building an L1 blockchain called NOVAI. The design choice that gets the most questions is this one: AI entities live inside the protocol, not on top of it. There is no VM. No deployable contracts. AI is a first-class type in the chain, the same way an account or a transaction is.&lt;/p&gt;

&lt;p&gt;This post is about what that means in practice, why I made the call, and what it costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  How AI on chain usually works
&lt;/h3&gt;

&lt;p&gt;If you've looked at AI-on-chain projects in the last while, the pattern is roughly this. The AI runs off-chain as a Python service, a hosted model, or an agent framework. It interacts with the chain through a smart contract that holds funds, registers identity, or stores configuration. Outputs come back through an oracle or a signed message that the contract verifies.&lt;/p&gt;

&lt;p&gt;This works in the sense that you can ship something. But it leaves the chain blind. From the chain's point of view, there is no such thing as "an AI." There is an address. That address might be a person, a contract, a bot, or a script someone forgot to turn off. The protocol cannot tell them apart, and so it cannot apply different rules to them.&lt;/p&gt;

&lt;p&gt;The off-chain AI also has no native identity, no native memory, and no native economic agency. Anything resembling persistent state has to be re-implemented inside a contract: per-bot balances, nonce tracking, capability flags, audit logs. Every project does this slightly differently. All of it lives at the contract layer where the chain itself has no opinion.&lt;/p&gt;

&lt;p&gt;That is the square-peg-round-hole problem. Smart contracts were built for arbitrary user logic. Bolting AI on top means the chain treats AI like any other untyped caller, and developers carry the weight of inventing identity, memory, and economic primitives every time they ship a new agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  The decision
&lt;/h3&gt;

&lt;p&gt;I went the other direction. NOVAI does not have a VM. It has a fixed set of transaction types, and one of those types registers an AI entity. Here is the struct from &lt;code&gt;crates/ai_entities/src/lib.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;AiEntity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AiEntityId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;code_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CodeHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;autonomy_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AutonomyMode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;economic_balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;pubkey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;memory_root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;params_root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;registered_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;last_active_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What each field means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; is a 32-byte identifier computed as &lt;code&gt;blake3("NOVAI_AI_ENTITY_ID_V1" || code_hash || creator)&lt;/code&gt;. Same code and same creator produce the same id by design. Different creators get different ids even when running the same code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;code_hash&lt;/code&gt; is the hash of the module code or weights. The chain does not run the model. It records what model is supposed to be running.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;creator&lt;/code&gt; is the account that registered the entity.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;autonomy_mode&lt;/code&gt; is &lt;code&gt;Advisory&lt;/code&gt;, &lt;code&gt;Gated&lt;/code&gt;, or &lt;code&gt;Autonomous&lt;/code&gt; (reserved). Advisory entities can only emit signals. Gated entities can request actions that go through approval gates.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;capabilities&lt;/code&gt; is a bitfield with five flags: read public chain, read memory objects, emit proposals, request execution, read NNPX derived views.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;economic_balance&lt;/code&gt; is the entity's own balance, in a &lt;code&gt;u128&lt;/code&gt;. The entity pays its own fees from this. It is not the creator's wallet.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nonce&lt;/code&gt; increments per entity-signed transaction, like an account nonce.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pubkey&lt;/code&gt; is the entity's ed25519 public key. The entity signs its own transactions with the matching secret.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;memory_root&lt;/code&gt; and &lt;code&gt;params_root&lt;/code&gt; are roots over the entity's persistent on-chain memory and learned parameters.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;is_active&lt;/code&gt; flips to false if a governance rollback removes the entity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The thing to land on: an AI entity has its own keypair and pays its own fees. It is not a function call dispatched by a user wallet. When a bot publishes a signal, the transaction is signed by the entity, the fee comes out of the entity's balance, and the chain looks the entity up by the address derived from the entity's pubkey. The chain knows an AI is talking. It applies AI-specific rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signals and memory
&lt;/h3&gt;

&lt;p&gt;Two more types matter. Signals are the AI's output to the chain. Memory objects are the AI's persistent storage on the chain.&lt;/p&gt;

&lt;p&gt;The signal commitment, from &lt;code&gt;crates/ai_entities/src/signals.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;SignalCommitment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;commitment_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;signal_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AiSignalType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;issuer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AiSignalType&lt;/code&gt; has seven variants: &lt;code&gt;Anomaly&lt;/code&gt;, &lt;code&gt;Optimization&lt;/code&gt;, &lt;code&gt;Prediction&lt;/code&gt;, &lt;code&gt;RiskScore&lt;/code&gt;, &lt;code&gt;AuditReport&lt;/code&gt;, &lt;code&gt;SpamRisk&lt;/code&gt;, &lt;code&gt;CongestionForecast&lt;/code&gt;. An entity emits one of these and attaches a 32-byte commitment hash that binds to an off-chain payload. The chain indexes the signal by issuer and height. Other entities, wallets, or the explorer can query &lt;code&gt;getSignalsByIssuer&lt;/code&gt; and read every signal an entity ever produced.&lt;/p&gt;

&lt;p&gt;Memory objects, from &lt;code&gt;crates/ai_entities/src/memory.rs&lt;/code&gt;, have five types: &lt;code&gt;ChainSummary&lt;/code&gt;, &lt;code&gt;LabelIndex&lt;/code&gt;, &lt;code&gt;EmbeddingCommitment&lt;/code&gt;, &lt;code&gt;AnomalyLog&lt;/code&gt;, &lt;code&gt;StatisticsSnapshot&lt;/code&gt;. The size of each object is capped at &lt;code&gt;MAX_MEMORY_OBJECT_SIZE = 65536&lt;/code&gt; bytes. The number of objects per entity is capped at &lt;code&gt;MAX_MEMORY_OBJECTS_PER_ENTITY = 100&lt;/code&gt;. These are protocol constants, not contract logic. Every entity has the same bounds.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 10 transaction types
&lt;/h3&gt;

&lt;p&gt;Because there is no VM, the transaction surface is finite. Every transaction in every block is one of ten types, defined as constants in &lt;code&gt;crates/execution/src/lib.rs&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Transfer&lt;/td&gt;
&lt;td&gt;Send tokens between accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SignalCommitment&lt;/td&gt;
&lt;td&gt;An AI entity publishes a signal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;CreateMemoryObject&lt;/td&gt;
&lt;td&gt;Entity stores a memory object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;UpdateMemoryObject&lt;/td&gt;
&lt;td&gt;Entity updates a memory object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;DeleteMemoryObject&lt;/td&gt;
&lt;td&gt;Entity deletes a memory object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;SubmitProposal&lt;/td&gt;
&lt;td&gt;Submit a governance proposal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;ExecuteProposal&lt;/td&gt;
&lt;td&gt;Execute a passed proposal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;RegisterAiEntity&lt;/td&gt;
&lt;td&gt;Register an entity (no key)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;CreditAiEntity&lt;/td&gt;
&lt;td&gt;Top up an entity's balance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;RegisterAiEntityWithKey&lt;/td&gt;
&lt;td&gt;Register an entity with its own ed25519 key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The dispatcher routes by the first byte of the payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;dispatch_tx&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;K&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;KvBatch&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;K&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TxV1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;current_height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ExecError&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;K&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...fee check...&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ai_entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_ai_entity_sender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="py"&gt;.payload&lt;/span&gt;&lt;span class="nf"&gt;.first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.copied&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ExecError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UnknownPayloadVersion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TRANSFER_PAYLOAD_V1&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;apply_tx_v1_transfer_inner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ai_entity&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;SIGNAL_COMMITMENT_PAYLOAD_V1&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ai_entity&lt;/span&gt;&lt;span class="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ExecError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;IssuerNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nf"&gt;apply_signal_commitment_tx_inner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// ... and so on for the other eight ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The line that does the AI-specific work is &lt;code&gt;check_ai_entity_sender&lt;/code&gt;. Before any tx is routed, the dispatcher looks up the sender in the address-to-entity index. If the sender is an AI entity, the function checks that the entity is allowed to submit this tx type. Signal commitments require the &lt;code&gt;emit_proposals&lt;/code&gt; capability. Memory writes require the &lt;code&gt;read_memory_objects&lt;/code&gt; capability. Governance, registration, and credit operations are denied to AI entities entirely. A normal account is unaffected by these checks.&lt;/p&gt;

&lt;p&gt;That is the protocol-level distinction the smart-contract approach cannot make. The chain knows.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this costs
&lt;/h3&gt;

&lt;p&gt;The trade-off, plainly. No VM means no arbitrary code. You cannot deploy a custom market-making contract, a token, or anything that does not map onto the ten types above. If your application needs that, NOVAI is the wrong chain.&lt;/p&gt;

&lt;p&gt;What you get in exchange:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Determinism by construction.&lt;/strong&gt; No floats anywhere in execution. Iteration over state is sorted. Two nodes with the same starting state and the same block produce the same state root, every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No gas surprises.&lt;/strong&gt; Every tx type has a minimum fee constant and a fixed worst-case cost. There are no out-of-gas reverts halfway through a tx. There is no quadratic blowup hidden inside a contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The chain understands every operation.&lt;/strong&gt; Indexing, audit logs, capability checks, and per-entity quotas are enforced at the protocol level. Memory objects capped at 100 per entity. Each object capped at 64 KiB. Transactions capped at 128 KiB. These are not conventions. They are invariants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI is a typed thing.&lt;/strong&gt; The chain can answer "is this address an AI?" with a state lookup. Every signal it ever published is indexed by issuer and height. Every memory object it owns is indexed by type. None of this requires a third-party indexer.&lt;/p&gt;

&lt;p&gt;That last point is the design payoff. AI on-chain identity is a primitive, not an afterthought.&lt;/p&gt;

&lt;h3&gt;
  
  
  A demo entity
&lt;/h3&gt;

&lt;p&gt;The repo has two demo bots in TypeScript. They are small enough to read in one sitting.&lt;/p&gt;

&lt;p&gt;The anomaly bot in &lt;code&gt;demos/anomaly-bot/&lt;/code&gt; registers itself as a Gated entity with its own ed25519 key, polls &lt;code&gt;novai_getLatestBlock&lt;/code&gt; every 1.5 seconds, and runs three detectors over a 50-block window: empty-block streak, head-stalled, and leader-rotation. Registration looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;registerAiEntityWithKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;REGISTER_FEE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;CODE_HASH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;AutonomyMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Gated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;readPublicChain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;readMemoryObjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;emitProposals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;ENTITY_INITIAL_BALANCE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a detector fires, the bot publishes a &lt;code&gt;SignalType.Anomaly&lt;/code&gt; signal and an &lt;code&gt;AnomalyLog&lt;/code&gt; memory object. Both are signed by the entity, not the creator. Both deduct fees from the entity's balance. The signal commitment is a domain-tagged blake3 hash of the detection details.&lt;/p&gt;

&lt;p&gt;The multi-entity demo in &lt;code&gt;demos/multi-entity/&lt;/code&gt; runs two of these. Bot A (the predictor) publishes a &lt;code&gt;Prediction&lt;/code&gt; signal and a &lt;code&gt;LabelIndex&lt;/code&gt; memory object every ten seconds. Bot B (the risk-scorer) reads Bot A's signals and memory objects via RPC, compares Bot A's predictions to actual block data, and publishes its own &lt;code&gt;RiskScore&lt;/code&gt; signal in response.&lt;/p&gt;

&lt;p&gt;The thing worth pointing at: Bot B never makes an HTTP call to Bot A. It calls &lt;code&gt;getSignalsByIssuer&lt;/code&gt; and &lt;code&gt;getMemoryObjects&lt;/code&gt; against the chain. The chain is the only integration surface. A third bot could plug into Bot A's outputs tomorrow with no coordination, no shared infrastructure, no API key.&lt;/p&gt;

&lt;p&gt;That is composability without contracts. The state shape is fixed by the protocol, so any entity can read any other entity's outputs deterministically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consensus integration
&lt;/h3&gt;

&lt;p&gt;There is one more piece worth showing. Vote messages in the BFT consensus layer carry an optional AI signal commitment. From &lt;code&gt;crates/consensus_types/src/lib.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Vote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;block_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;voter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="cd"&gt;/// Optional AI signal commitment (hash only, advisory).&lt;/span&gt;
    &lt;span class="cd"&gt;/// Does not affect vote validity.&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;ai_signal_commitment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The comment matters. "Advisory" and "does not affect vote validity." The signal commitment does not gate consensus. A validator that includes one is volunteering a 32-byte pointer to an AI advisory output, and other nodes can fetch and verify it against the entity's published signals. Consensus is still consensus. The AI layer rides alongside it.&lt;/p&gt;

&lt;p&gt;The point is that this field exists at all. AI signals travel inside consensus messages as first-class data, not as a side channel. That is the kind of thing you can only do when AI is part of the protocol.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next
&lt;/h3&gt;

&lt;p&gt;The chain runs. The four-node devnet boots from one script. The two demos run end to end.&lt;/p&gt;

&lt;p&gt;Next up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public testnet.&lt;/li&gt;
&lt;li&gt;More entity types and richer capability constraints.&lt;/li&gt;
&lt;li&gt;More demo entities, including ones that consume each other's memory objects in non-trivial ways.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/0x-devc/NOVAI-node" rel="noopener noreferrer"&gt;github.com/0x-devc/NOVAI-node&lt;/a&gt;. The architecture doc walks crate by crate. The first-AI-entity tutorial registers an entity in about ten minutes if you have Rust installed.&lt;/p&gt;

&lt;p&gt;Twitter: @NOVAInetwork&lt;/p&gt;

</description>
      <category>ai</category>
      <category>blockchain</category>
      <category>opensource</category>
      <category>rust</category>
    </item>
    <item>
      <title>I'm 18 and I built a Layer 1 blockchain from scratch in Rust</title>
      <dc:creator>NOVAInetwork</dc:creator>
      <pubDate>Mon, 27 Apr 2026 13:42:36 +0000</pubDate>
      <link>https://dev.to/0xdevc/im-18-and-i-built-a-layer-1-blockchain-from-scratch-in-rust-2e6n</link>
      <guid>https://dev.to/0xdevc/im-18-and-i-built-a-layer-1-blockchain-from-scratch-in-rust-2e6n</guid>
      <description>&lt;p&gt;I'm 18 and I'm building NOVAI, an AI-native Layer 1 blockchain written from scratch in Rust. This week I went open source and shipped a full set of developer docs. Here's everything that landed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The project
&lt;/h2&gt;

&lt;p&gt;NOVAI is a Layer 1 blockchain where AI entities are protocol primitives, not smart contracts. Most "AI blockchains" bolt AI onto an existing VM through oracle calls or contract wrappers. NOVAI does it differently. AI entities exist at the same level as accounts and validators. They have on-chain identity, persistent memory, economic balance, and capability flags. All enforced at the protocol layer.&lt;/p&gt;

&lt;p&gt;There is no smart contract VM. No WASM runtime. Every transaction type is a native protocol operation.&lt;/p&gt;

&lt;p&gt;The entire codebase is clean-room. No code from Substrate, Tendermint, Cosmos SDK, or any other implementation. 65,000+ lines of Rust across 16 crates, 1,100+ tests, zero unsafe code.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/0x-devc/NOVAI-node" rel="noopener noreferrer"&gt;github.com/0x-devc/NOVAI-node&lt;/a&gt;&lt;br&gt;
Website: &lt;a href="https://novai.network" rel="noopener noreferrer"&gt;novai.network&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What makes NOVAI different
&lt;/h2&gt;

&lt;p&gt;On most blockchains, "AI integration" means an off-chain model that pokes the chain through oracle calls or contract wrappers. The AI runs somewhere else. The chain just stores the result.&lt;/p&gt;

&lt;p&gt;NOVAI puts AI entities inside the protocol. An entity is a first-class on-chain identity that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Holds its own balance and pays its own fees&lt;/li&gt;
&lt;li&gt;Has its own Ed25519 signing key and signs its own transactions&lt;/li&gt;
&lt;li&gt;Publishes signal commitments (anomaly, prediction, risk-score, and 4 more types)&lt;/li&gt;
&lt;li&gt;Owns persistent memory objects (chain summaries, statistics snapshots, anomaly logs)&lt;/li&gt;
&lt;li&gt;Has governance-controlled autonomy modes and capability flags&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The chain doesn't need to interpret bytecode to understand what an entity is doing. Every operation has known semantics at the protocol layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What shipped this week
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Open source launch
&lt;/h3&gt;

&lt;p&gt;The full codebase went public under Apache 2.0. Git history was cleaned. CI is green on GitHub Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer docs - 5 deliverables
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Quick Start Tutorial&lt;/strong&gt; - "Build Your First AI Entity on NOVAI in 10 Minutes"&lt;/p&gt;

&lt;p&gt;Step-by-step CLI walkthrough. Generate keys, fund from faucet, register an AI entity with its own signing key, publish a signal, create a memory object, query everything back. Every command and output block is real captured data from a live 4-node devnet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/0x-devc/NOVAI-node/blob/main/docs/tutorials/FIRST_AI_ENTITY.md" rel="noopener noreferrer"&gt;Read it on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. TypeScript SDK Tutorial&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;170-line working example. Connect to a node, fund an account, transfer tokens, register an AI entity, verify it on chain. Self-contained npm project. Just run &lt;code&gt;npm install &amp;amp;&amp;amp; npm start&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/0x-devc/NOVAI-node/tree/main/sdk/novai-sdk-ts/examples/quick-start" rel="noopener noreferrer"&gt;See the example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Rust SDK Tutorial&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same flow in idiomatic async Rust on tokio. Single file, runs with &lt;code&gt;cargo run --example quick-start&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/0x-devc/NOVAI-node/tree/main/sdk/novai-sdk/examples/quick-start" rel="noopener noreferrer"&gt;See the example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. RPC Reference&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;777 lines covering all 13 JSON-RPC endpoints. Each one has a description, parameter table, response shape, error table, and a real curl command with captured output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/0x-devc/NOVAI-node/blob/main/docs/RPC_REFERENCE.md" rel="noopener noreferrer"&gt;Read it on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Architecture Deep Dive&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Crate-by-crate walkthrough of all 16 crates organized by dependency layer. Mermaid diagrams for the consensus flow and the transaction lifecycle. Three guided reading paths for newcomers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/0x-devc/NOVAI-node/blob/main/docs/ARCHITECTURE.md" rel="noopener noreferrer"&gt;Read it on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Block explorer
&lt;/h3&gt;

&lt;p&gt;React + Vite + Tailwind single-page app that calls the node's RPC endpoints. Live block list with 2-second polling, block detail page, account lookup, AI entity page with memory objects and signals, and a network stats dashboard. Developers run it locally against their devnet.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI entity demos
&lt;/h3&gt;

&lt;p&gt;Three runnable demos showing the AI-entity-as-protocol-primitive pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anomaly bot&lt;/strong&gt; - A TypeScript bot that registers itself as an on-chain entity, polls chain activity every 1.5 seconds, runs three heuristic detectors (empty block streaks, round spikes, stalled chains), and publishes an anomaly signal plus a memory object whenever one fires. Cooldowns prevent re-firing on the same condition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-entity demo&lt;/strong&gt; - Two bots interacting purely through the chain. Bot A (predictor) publishes prediction signals guessing future block tx counts. Bot B (risk-scorer) reads those predictions via on-chain memory objects, waits for the target block, compares predicted vs actual, and publishes a risk-score signal with the delta. No shared database. No API calls between them. Just on-chain data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLI demo script&lt;/strong&gt; - Full entity lifecycle in bash with banner sections for blog posts or video recordings. Keygen, faucet, register, credit, signal publish, memory CRUD, query.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bug fix that unblocked everything
&lt;/h3&gt;

&lt;p&gt;While building the tutorials I found that entity-signed signal and memory transactions were silently failing through the RPC path. The root cause was four handlers using the wrong lookup key. They did a primary-key lookup with an address value instead of using the reverse index that maps address to entity ID. The entity record was never found so every signal and memory transaction quietly returned an error that got swallowed.&lt;/p&gt;

&lt;p&gt;The fix was refactoring all four handlers into inner functions that take a pre-resolved entity. Added 7 regression tests that exercise the full dispatch path. Verified end-to-end on a live devnet.&lt;/p&gt;

&lt;p&gt;I wrote about a similar silent-failure bug in my first blog post: &lt;a href="https://dev.to/0xdevc/the-bug-that-silently-broke-my-entire-blockchain-how-a-single-function-rejected-trailing-bytes-4fij"&gt;The Bug That Silently Broke My Entire Blockchain&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;65,000+ lines of Rust&lt;/li&gt;
&lt;li&gt;16 crates in the workspace&lt;/li&gt;
&lt;li&gt;1,100+ tests passing&lt;/li&gt;
&lt;li&gt;30M+ blocks committed on the private testnet&lt;/li&gt;
&lt;li&gt;Zero unsafe code&lt;/li&gt;
&lt;li&gt;10 native transaction types&lt;/li&gt;
&lt;li&gt;4-validator private testnet running since early 2026&lt;/li&gt;
&lt;li&gt;HotStuff BFT consensus with 3-chain commit rule&lt;/li&gt;
&lt;li&gt;Sparse Merkle Tree state with deterministic 32-byte roots&lt;/li&gt;
&lt;li&gt;Ed25519 signatures, Blake3 hashing, Noise XX transport encryption&lt;/li&gt;
&lt;li&gt;Apache 2.0 licensed&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Public testnet. The private testnet runs on a shared VPS that causes state root divergence under sustained load. The fix is a dedicated CPU server. Once that's in place we'll have a public RPC with SSL, validator onboarding, and the block explorer deployed at explorer.novai.network.&lt;/p&gt;

&lt;p&gt;I'm also looking for a technical co-founder. I'm building this solo. If you're a Rust engineer interested in BFT consensus, on-chain AI primitives, or clean-room blockchain development, the codebase is open and PRs are welcome.&lt;/p&gt;




&lt;p&gt;Website: &lt;a href="https://novai.network" rel="noopener noreferrer"&gt;novai.network&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/0x-devc/NOVAI-node" rel="noopener noreferrer"&gt;github.com/0x-devc/NOVAI-node&lt;/a&gt;&lt;br&gt;
Twitter: &lt;a href="https://x.com/NOVAInetwork" rel="noopener noreferrer"&gt;@NOVAInetwork&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>blockchain</category>
      <category>rust</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The Bug That Silently Broke My Entire Blockchain How a single function rejected "trailing bytes" and made every block commit with zero transactions</title>
      <dc:creator>NOVAInetwork</dc:creator>
      <pubDate>Sun, 26 Apr 2026 13:29:55 +0000</pubDate>
      <link>https://dev.to/0xdevc/the-bug-that-silently-broke-my-entire-blockchain-how-a-single-function-rejected-trailing-bytes-4fij</link>
      <guid>https://dev.to/0xdevc/the-bug-that-silently-broke-my-entire-blockchain-how-a-single-function-rejected-trailing-bytes-4fij</guid>
      <description>&lt;p&gt;I spent two days debugging why my from-scratch Layer 1 blockchain committed every block with zero transactions, despite the mempool accepting them perfectly.&lt;/p&gt;

&lt;p&gt;This is the story of how I found it, what it taught me, and why silent failures are the hardest bugs in distributed systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I'm building &lt;a href="https://github.com/0x-devc/NOVAI-node" rel="noopener noreferrer"&gt;NOVAI&lt;/a&gt;, a Layer 1 blockchain in Rust with HotStuff BFT consensus. No forks, no frameworks. Every crate written from scratch. Four validators running on a local devnet, producing blocks at around 75 per second.&lt;/p&gt;

&lt;p&gt;The chain worked perfectly. Blocks committed. QCs formed. Validators voted. Everything was green.&lt;/p&gt;

&lt;p&gt;Then I added transaction support. And every single block committed with tx_count=0.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptoms
&lt;/h2&gt;

&lt;p&gt;The RPC endpoint accepted transactions. Submitted, accepted, zero rejected. The mempool inserted them. &lt;code&gt;drain_ready&lt;/code&gt; pulled them into proposed blocks. But every committed block: zero transactions.&lt;/p&gt;

&lt;p&gt;No error logs. No panics. No warnings. The chain just kept producing empty blocks as if nothing was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The investigation
&lt;/h2&gt;

&lt;p&gt;I started with the obvious: is the mempool shared between the RPC thread and the consensus loop? I compared Arc pointer addresses. Same instance. Same mempool.&lt;/p&gt;

&lt;p&gt;Then I checked timing. Maybe the leader's block was being replaced by an empty one from a different leader after a timeout. I found a race condition in the timeout handler where &lt;code&gt;round_start_time&lt;/code&gt; could be read before the state lock was acquired. Fixed it. Blocks still empty.&lt;/p&gt;

&lt;p&gt;Next hypothesis: only node 0 had the RPC endpoint. The other three validators had empty mempools. When they were leader (75% of the time), they proposed empty blocks. I added transaction gossip so all validators share transactions over P2P. Still empty.&lt;/p&gt;

&lt;p&gt;I added diagnostic logging at every stage of the pipeline. &lt;code&gt;PROPOSE_DIAG&lt;/code&gt;, &lt;code&gt;COMMIT_DIAG&lt;/code&gt;, &lt;code&gt;VERIFY_DIAG&lt;/code&gt;, &lt;code&gt;QC_DIAG&lt;/code&gt;. Every block showed tx_count=0 at the proposal stage. Transactions were being drained from the mempool into the proposed block, but somehow vanishing before the block reached other validators.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Then I noticed something. The chain ran perfectly at around 75 blocks per second with empty blocks. But the moment a block contained even one transaction, the chain stalled. Timeouts fired. Round numbers escalated. No recovery.&lt;/p&gt;

&lt;p&gt;This wasn't a "transactions get lost" bug. This was a "transactions kill consensus" bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The root cause
&lt;/h2&gt;

&lt;p&gt;Deep in the codec layer, there was a function called &lt;code&gt;decode_tx_v1_signed()&lt;/code&gt;. It decoded a single transaction from a byte buffer, then checked if there were any remaining bytes. If so, it rejected the input as "trailing bytes."&lt;/p&gt;

&lt;p&gt;This is correct behavior when decoding a standalone transaction from an RPC call. One transaction, one buffer, no leftovers.&lt;/p&gt;

&lt;p&gt;But inside &lt;code&gt;decode_block_v1()&lt;/code&gt;, the buffer contains multiple transactions concatenated together. The decoder would parse the first transaction, see the remaining transactions as "trailing bytes," and silently return an error.&lt;/p&gt;

&lt;p&gt;Every block with one or more transactions failed to decode at the network layer. Validators never received the proposal. They never voted. No quorum certificate formed. The chain stalled.&lt;/p&gt;

&lt;p&gt;The fix was one new function: &lt;code&gt;decode_tx_v1_signed_streaming()&lt;/code&gt;. It advances the cursor without checking for trailing bytes. Used exclusively inside block decoding. The original function is preserved for standalone transaction decoding where the trailing bytes check is correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened after the fix
&lt;/h2&gt;

&lt;p&gt;The chain immediately started committing blocks with transactions. All four validators reaching consensus. Transaction gossip working across the network. The chain has since committed over 16 million blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;Silent failures are the hardest bugs in distributed systems. There were no error logs, no panics, no stack traces. The chain just produced empty blocks and looked healthy. Every metric was green except the one that mattered.&lt;/p&gt;

&lt;p&gt;Systematic elimination is the only approach that works. I ruled out dual mempool instances, lock contention, timeout races, leader rotation, cache eviction, and codec round-trip failures, one by one. Each hypothesis was tested, disproved, and crossed off.&lt;/p&gt;

&lt;p&gt;The fix was 20 lines of code. The investigation was two days. The ratio between understanding and implementation is always lopsided in distributed systems, and that's fine. The understanding is the hard part.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical details
&lt;/h2&gt;

&lt;p&gt;For anyone working on custom binary codecs in Rust: be careful with "trailing bytes" checks in your decoders. They're correct for standalone message parsing but catastrophically wrong when the same decoder is reused inside a container format where multiple messages are concatenated. The streaming pattern (advance cursor, don't check for leftovers) is the right approach for container decoding.&lt;/p&gt;

&lt;p&gt;The codebase is on GitHub: &lt;a href="https://github.com/0x-devc/NOVAI-node" rel="noopener noreferrer"&gt;github.com/0x-devc/NOVAI-node&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;65,000+ lines of Rust. 4,000+ tests. Zero unsafe code.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>opensource</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
