Preface — The Language That Became an Operating System
In 1970, Charles H. Moore created Forth — a programming language so minimal, so close to the metal, that it could run on hardware with 8 kilobytes of memory. Forth was not elegant in the way that Lisp was elegant, nor structured in the way Pascal was structured. It was something else entirely: a language where the programmer thought in terms of a stack, where every operation consumed values and produced values, where the boundary between the language and the machine dissolved into nothing. NASA used Forth to control spacecraft. Telescope operators used it to point instruments at distant galaxies. It was the language of people who needed absolute control over every byte.Half a century later, a different kind of machine needed a language with those same properties — not a spacecraft, but a decentralized state machine. A machine that does not exist in any single location, that is maintained by independent operators across the globe, that must execute every instruction identically on every node, and that must do so with mathematical certainty. This machine is GRIDNET OS — the world’s first decentralized operating system. And its language is GridScript.GridScript is derived from Forth, but it is not Forth. Where Forth was designed to control hardware, GridScript was designed to control consensus. Where Forth communicated with serial ports and memory-mapped I/O, GridScript communicates with a Merkle Patricia Trie holding the global state of a decentralized network. Where Forth programs ran on a single processor, GridScript programs are replicated and executed across every node in the network — deterministically, verifiably, and irreversibly. And where Forth execution is essentially free — instructions run as fast as the processor allows with no accounting — every single GridScript instruction consumes ERG (Execution Resource Gas), making it a metered computation model where unbounded execution is impossible by design. This single difference transforms GridScript from a language into an economic mechanism: computation has a price, and that price prevents denial-of-service attacks against the network.GridScript serves simultaneously as:
- The transaction language — every blockchain transaction is a GridScript program compiled to bytecode
- The smart contract language — decentralized applications are written, deployed, and invoked in GridScript
- The system shell — operators manage GRIDNET Core nodes through an interactive GridScript terminal
- The consensus language — all nodes execute the same GridScript deterministically to reach agreement on state
- The query language — all data retrieval from the decentralized state machine is performed through GridScript execution
This article is a comprehensive guide to GridScript — its architecture, its execution model, its role as the backbone of a decentralized service-oriented architecture, and its practical use in building applications that run without servers, without central authorities, and without trust in any single entity.
I. Decentralized Processing Threads — The Cornerstone of Everything
If you understand only one concept in GridScript, let it be this: Decentralized Processing Threads (DPTs). They are the mechanism through which every interaction with GRIDNET OS occurs — every value transfer, every smart contract call, every data query, every file operation. DPTs are to GRIDNET OS what HTTP requests are to the web, except they execute Turing-complete code inside a metered virtual machine on a decentralized network.
BT and CT — Begin Thread, Commit Thread
The two most important commands in GridScript are BT (Begin Thread) and CT (Commit Thread). Together, they define the lifecycle of a transaction:
BT cd /YourDomainAddress send RecipientAddress 1000000000000000000 CT
When BT executes, it creates a new sub-thread — a sandbox where subsequent GridScript commands are accumulated but not executed. The commands are recorded into a code buffer. Inline commands like send are automatically converted to their explicit stack-based forms (sendEx) for serialization. The thread tracks its own ERG (Execution Resource Gas) consumption.When CT executes, it collects code from all ready threads, compiles the accumulated GridScript into bytecode, signs the transaction with the user’s private key, and submits it to the network. The transaction then propagates to all nodes, where it is re-executed in kernel mode — deterministically, identically, on every machine — and the resulting state changes are committed to the global Merkle Patricia Trie.
Ephemeral vs. Committed — The Two Modes of DPTs
Not all DPTs result in blockchain transactions. This distinction is fundamental:Committed DPTs follow the full BT → accumulate → CT cycle. They produce signed transactions that modify the global state. A value transfer, a smart contract deployment, an identity registration — these are committed DPTs.Ephemeral DPTs execute GridScript on a remote GRIDNET Core node without committing anything to the blockchain. They are read-only queries against the current state of the decentralized state machine. When the Blockchain Explorer displays a list of recent transactions, it is using ephemeral DPTs. When the Wallet dApp retrieves an account balance, it is using an ephemeral DPT. The GridScript executes, reads from the state trie, produces a BER-encoded result, and the thread is discarded. No transaction is signed. No bytecode is stored. No ERG is charged to the user’s account.This dual nature — the same mechanism serving both state-modifying transactions and read-only queries — is what makes DPTs the cornerstone of the entire GRIDNET OS architecture. Every UI dApp, every data retrieval operation, every interaction that a user has with the decentralized state machine flows through a DPT.
Multi-Thread Transactions
GridScript supports formulating up to five threads simultaneously (MAX_THREADS_COUNT = 5), enabling atomic multi-operation transactions:
BT cd /DomainA send Recipient1 1000000000000000000 BT cd /DomainB send Recipient2 2000000000000000000 CT \ Commits all ready threads as one atomic transaction
Thread management commands provide fine-grained control: RT (Resume Thread), ST (Suspend Thread), PT (Print Thread — show accumulated code), FT (Focus Thread — switch between threads), and AT (Abort Thread — discard accumulated code).
II. GridScript as a Decentralized Service-Oriented Architecture
Stop and consider what is actually happening when a user opens the Wallet UI dApp or the Blockchain Explorer in their browser.Every balance displayed. Every transaction listed. Every block detail rendered. Every identity resolved. None of it comes from a traditional server. There is no PHP backend. There is no .NET API. There is no REST endpoint hosted in a data center. Every single piece of data the user sees on screen is retrieved through GridScript code executing on remote GRIDNET Core nodes — decentralized machines operated independently across the network.This is the GridScript Service-Oriented Architecture (SOA), and it is what makes the entire GRIDNET OS dApp ecosystem work.
The Data Flow Pipeline
The SOA pipeline operates in six stages:
- JavaScript formulates the request — Browser-side code in VMContext.js (which exports the CVMContext singleton) constructs a GridScript data request. This might be a balance query, a transaction search, a block listing, or a domain lookup.
- BER encoding — The request is serialized using BER (Basic Encoding Rules) — an efficient binary encoding format derived from ASN.1. BER is not JSON. It is not Protocol Buffers. It is a compact, self-describing binary format that can represent complex nested data structures with minimal overhead. Web Workers perform the encoding off the main thread to keep the UI responsive.
- Double-encrypted transport — The BER-encoded request is wrapped in ECC (Elliptic Curve Cryptography) encryption at the application level, then transmitted through a TLS-secured WebSocket. Two independent encryption layers. An attacker who compromised TLS would still face a second cryptographic barrier.
- GridScript VM execution — The remote GRIDNET Core node receives the request, decodes it, and executes the corresponding GridScript handler. The VM traverses the Merkle Patricia Trie — the global state database — reading account data, transaction histories, block headers, identity tokens, or whatever the request demands.
- BER-encoded response — The results are packaged into structured BER metadata — organized into typed sections (directoryListing, searchResults, notifications, transactionInfo, blockInfo, domainInfo) containing typed entries. This structured format enables rich data exchange between the VM and the browser.
- CVMContext receives and dispatches — The BER-encoded response arrives back through the same double-encrypted, onion-routed channel. The in-browser GRIDNET OS subsystem — the CVMContext singleton — receives the raw data, decodes it using CVMMetaParser, and dispatches it through two distinct mechanisms:
- Active waiting (request-response) — A UI dApp that initiated a specific data request is actively awaiting the response. It holds a pending Promise or async callback keyed to the request ID it generated. When CVMContext encounters that request ID in the incoming BER data, it resolves the waiting call directly — the dApp receives exactly the data it asked for. This is the primary pattern for queries: the Wallet asks for a balance, the Explorer asks for a block list, and each
awaits the specific response. - Passive notification (event-driven) — UI dApps may also register general-purpose event listeners for broad categories of data — new block announcements, VM state changes, commit status transitions, DFS updates, incoming network messages. These listeners fire whenever CVMContext receives matching data, regardless of whether the dApp initiated the request. A new block notification may reach both the Explorer and the Wallet simultaneously; a commit confirmation may notify multiple dApps that share a transaction context. The dApp does not ask for this data — it arrives because the dApp has expressed interest in a category of events.
This dual dispatch model — active request-response correlation and passive event subscription — operating simultaneously through the same CVMContext singleton is what gives GRIDNET OS UI dApps both the responsiveness of traditional client-server applications and the real-time reactivity of event-driven systems.
- Active waiting (request-response) — A UI dApp that initiated a specific data request is actively awaiting the response. It holds a pending Promise or async callback keyed to the request ID it generated. When CVMContext encounters that request ID in the incoming BER data, it resolves the waiting call directly — the dApp receives exactly the data it asked for. This is the primary pattern for queries: the Wallet asks for a balance, the Explorer asks for a block list, and each
- UI dApp renders — Whether actively awaiting a response or passively notified of an event, the UI dApp instance receives the decoded data, processes it according to its own logic, and updates its Shadow DOM. The user sees data on screen and has no idea that what just happened bears no resemblance to a conventional HTTP request-response cycle. From the user’s perspective, it is indistinguishable from a centralized web application — but beneath the surface, every byte traveled through a decentralized, double-encrypted, censorship-resistant pipeline with no single point of failure.

Performance That Defies Expectations
The measured balance update latency — the round-trip time from browser request to rendered result through this entire double-encrypted, BER-encoded pipeline — is 20–45 milliseconds on a local network (WAN performance varies with node proximity) on a local network. This is on par with, and in many cases faster than, conventional centralized web frameworks querying a local database.This performance is possible because BER encoding is remarkably efficient for the structured data GRIDNET OS works with. Unlike JSON, which carries field names as human-readable strings in every message, BER uses numeric tags and length prefixes, producing payloads that are significantly smaller. Unlike Protocol Buffers, BER requires no schema compilation step — the encoding is self-describing. The VM Meta-Data Protocol builds on BER with a Sections → Entries structure, where each section has a typed purpose and each entry carries a request ID for correlation — enabling multiple concurrent asynchronous requests to be in flight simultaneously.
How dApps Use This Architecture
The Blockchain Explorer UI dApp provides a vivid example. When a user navigates to the Blocks view, the Explorer’s JavaScript constructs a GridScript request to fetch recent blocks with sorting and filtering parameters. The request is BER-encoded, dispatched through the encrypted WebSocket, and routed to a GRIDNET Core node. On that node, the GridScript VM executes a stateful iterator — a cursor that traverses the block chain with pagination support, filter matching, and sort optimization. The VM enhanced this capability with stateful iterators that remember position between calls, eliminating the need to re-traverse from the beginning for each page of results. The results — block headers with height, timestamp, transaction count, miner ID, difficulty, reward — are BER-encoded into a searchResults section and returned. The browser decodes and renders a Tabulator data grid with cyberpunk aesthetics.The Wallet dApp follows the same pattern for balance queries, transaction history, identity resolution, and token pool management. The old Wallet even managed off-chain state channels through this SOA layer — listing outgress channels, selecting token pools, and initiating cash-out operations, all through GridScript executing on remote nodes.
The BER Meta-Data Protocol
Communication between browser and GRIDNET Core follows a structured protocol built on BER encoding:
// JavaScript side — constructing a request const generator = new CVMMetaGenerator(); generator.addRAWGridScriptCmd(gridScriptCode, requestID, processID, vmID); const berData = generator.finalize(); // Send through encrypted WebSocket...
Architecture reference (GRIDNET Core internal):
// On the GRIDNET Core side — C++ processing CVMMetaGenerator gen; gen.beginSection(eVMMetaSectionType::searchResults); gen.addTransactionInfo(txDesc, reqID, appID, vmID); gen.endSection(); gen.finalize(); vector<uint8_t> data = gen.getData();
The protocol supports dozens of entry types: GridScriptCode, terminalData, transactionInfo, blockInfo, domainInfo, searchResults, fileContent, stateLessChannelElement, and many more. Each entry carries metadata for request-response correlation, enabling the browser to match responses to the specific UI component that initiated the request.The three-tier data hierarchy — Level 1 (raw transaction/receipt on-chain data), Level 2 (core info containers), Level 3 (BER-compatible description objects for transactions, blocks, and domains) — ensures that the same data structures are serialized identically in C++ and JavaScript, achieving full parity between the native Core implementation and the browser-side ECMA6 objects.
III. Where GridScript Runs — The Six Execution Contexts
GridScript is not confined to a single execution environment. It runs in six distinct contexts, each with different capabilities and security restrictions:
1. SSH — Direct Connection to GRIDNET Core
The most direct way to interact with GridScript. Connect via SSH to a running GRIDNET Core node and you have a full interactive terminal — the Forth heritage made tangible. Type commands, watch the stack, formulate transactions, manage the node. Every command in the GridScript vocabulary is available, subject only to authentication level.
\ SSH session example 10 20 + . \ prints 30 ." Hello, GRIDNET!" cr balance 'MyDomain' . \ prints balance in attoGNC
2. Local Instance — Ctrl+E in GRIDNET Core
When running GRIDNET Core directly, pressing Ctrl+E switches from the Events View to the Terminal View — an embedded GridScript console. This provides the same capabilities as SSH but with physical access to the machine. Certain administrative commands (like shutdown or firewall) may require this local context.
3. Terminal UI dApp — Browser-Based Terminal
The Terminal dApp brings GridScript to the browser. Built on xterm.js, it provides a full terminal experience within the GRIDNET OS desktop environment. The Terminal registers for VM metadata callbacks, DFS message events, and GridScript result notifications through CVMContext — the same SOA pipeline used by all dApps. Commands entered in the browser terminal are transmitted to a GRIDNET Core node for execution, with results streamed back in real-time.
4. Smart Contract Execution — On-Chain Bytecode
When a transaction is committed, its GridScript is compiled to bytecode and executed in kernel mode across all network nodes. Kernel mode is the consensus context — fully deterministic, no terminal I/O, no file system access, no user interaction. The VM checks REG_KERNEL_THREAD to enforce these restrictions. Every node must produce identical results for the same input, ensuring consensus.Smart contracts deployed via BC/EC (Begin Code / End Code) are stored as bytecode in the state trie:
BT cd /YourDomain/contracts BC cd /YourDomain getVar 'counter' 1 + 0 "counter" setVarEx EC CT
The bytecode format uses sequential opcode assignment starting from BASE_OPCODE_ID = 9. Single-byte encoding for opcodes ≤ 127, two-byte encoding for higher values. The order of codewords is immutable — new commands are always appended at the end. Reordering would break every compiled contract on the blockchain.
5. Implicit via JavaScript APIs — VMContext.js
This is the SOA context described in the previous section. JavaScript code in the browser generates GridScript automatically — the developer may not even realize GridScript is being executed. When a dApp calls a balance query or transaction search through CVMContext, the JavaScript constructs GridScript data requests, BER-encodes them, and dispatches them to remote nodes. The GridScript execution is implicit, invisible to the end user.
6. User Interactions with UI dApps
Drag a file in the File Manager. Send GNC in the Wallet. Deploy an identity token. Every user action in a UI dApp is ultimately formulated as GridScript — making it reproducible across the entire decentralized system once committed. The Wallet dApp pioneered this architecture: JavaScript ECMA6 formulates GridScript instruction sequences, Web Workers perform BER encoding and GridScript compilation and ECC signing off the main thread, and compiled bytecode is dispatched through onion-routed encrypted WebSocket channels.
The Security Model — Permission Layers in Depth
Every one of GridScript’s 290+ codewords carries 18 security properties that are checked at runtime before the instruction executes. This is not a coarse-grained permission system — it is per-instruction, per-context security enforcement. Understanding these layers is essential for any developer building on GRIDNET OS.
Permission Levels
From least restrictive to most restrictive:
Level 1: Public Commands — Arithmetic (+, -, *, /), stack operations (dup, drop, swap), comparison, and logic. These execute in any context — kernel mode, terminal, GUI, smart contract. No restrictions. A simple example:
10 20 + . \ Works everywhere — kernel, terminal, GUI, smart contract
\ Stack: 10 → 10 20 → 30 → (prints 30)
Level 2: Terminal-Only Commands (onlyFromTerminal = true) — BT, CT, keygen, BC/EC, setKey, node configuration. These cannot be called from kernel mode during consensus execution, nor remotely by untrusted code. They exist for interactive session management. If a smart contract attempted to call BT, the VM would reject it — you cannot formulate a transaction from within a transaction.
\ Terminal session — works: BT cd /MyDomain send RecipientAddr 1000000000000000000 CT \ Inside kernel mode (smart contract) — REJECTED: \ BT → RUNTIME_ERROR: "command only available from terminal"
Level 3: Local Admin Commands (requiresLocalAdminCredentials = true) — shutdown, firewall. These require physical or local access to the machine and administrator authentication. An SSH session from a remote location cannot execute these without first authenticating with local admin credentials via sudo.
Level 4: Overwatch Commands (allowedOnlyByAnOverwatch = true) — Critical system modifications that only the network’s highest-privilege entities (overwatches) can perform. The VM checks REG_EXECUTING_BY_OVERWATCH (register 16) before allowing execution. This is the ultimate security gate.
Level 5: Kernel-Restricted Commands (allowedInKernelMode = false) — File I/O, erg management, network operations, blockchain queries like getChain, stepBack. These exist for terminal use but are forbidden during consensus execution because they would break determinism. If time (which reads the local clock) were allowed in kernel mode, different nodes would get different results, causing consensus divergence — a network split.
The 18 Security Properties Per Codeword
Each codeword is declared with these security properties, checked at runtime:
allowedInKernelMode— Can this execute during consensus? (truefor state operations,falsefor I/O and config)requiresLocalAdminCredentials— Needs local admin login?onlyFromTerminal— Restricted to interactive terminal sessions?requiresSecurityToken— Must a cryptographic security token be on the stack?allowedOnlyByAnOverwatch— Reserved for super-admin entities?allowToBeExecutedFromRAWCodeMode— Allowed insideBC/ECblocks?baseERGCost— Minimum ERG units consumed per executioninlineParamsCount— Expected number of inline parametershasBase58BinaryInLineParams/hasBase64BinaryInLineParams— Parameter encodingreqStackWordsProceeding— Minimum required stack depthisDataReadFunction— Reads from state storage (for query optimization)doNotIncludeInSource— Exclude from compiled transaction source- Plus additional flags for hash codes, length tracking, and extended versions
This means the VM performs up to 18 checks before every single instruction executes. The overhead is minimal — boolean flag checks — but the security guarantee is absolute: no instruction can escape its designated context.
Security Registers — Identity and Privilege
GridScript maintains 48 special-purpose registers. Several are critical for security:
REG_CALLERS_ID (register 11) vs REG_AUTHENTICATED_SD (register 12) — This distinction is the critical security concept in GridScript. REG_CALLERS_ID identifies who signed and paid for the transaction — derived from the cryptographic signature. REG_AUTHENTICATED_SD identifies who has permission to execute the current operation. These can differ during delegation, proxy execution, or sponsored transactions:
\ Inside a smart contract — check both:
11 getReg \ Read REG_CALLERS_ID — who SIGNED the transaction
\ Stack: callers_identity_bytes
12 getReg \ Read REG_AUTHENTICATED_SD — who HAS PERMISSION
\ Stack: callers_identity_bytes authenticated_sd_bytes
fcomp \ Compare them
\ Stack: flag (are they the same entity?)
\ If different: delegation or proxy execution is occurring
⚠️ REG_AUTHENTICATED_SD must be cleared after a dApp exits — failure to do so causes privilege leakage where the next execution inherits the previous entity’s permissions.
REG_EXECUTING_BY_OVERWATCH (register 16) — When set, grants the highest privilege level. Only the system itself can set this during overwatch entity execution.
REG_KERNEL_THREAD (register 34) — The kernel mode flag. When set, execution is occurring as part of consensus. Only kernel threads can modify decentralized state. This register must never be user-modifiable.
REG_IS_THREAD_UI_AWARE (register 31) — Indicates whether the thread can perform UI operations. This register cannot be changed from user mode — preventing UI spoofing attacks where malicious code pretends to be a UI thread.
Security Tokens for Cross-Domain Access
Commands with requiresSecurityToken = true demand a cryptographic proof of authorization on the stack. The token layout (bottom to top):
\ Security token stack layout: Signature \ [0] Bottom — ECDSA/Schnorr signature over the token payload Nonce \ [1] Fresh nonce — prevents replay attacks PublicKey \ [2] Public key of the authorizing entity DomainID \ [3] Target state domain being accessed FunctionID \ [4] Top — specific function being invoked
The VM validates the security token by checking: (1) signature matches public key, (2) nonce is fresh, (3) permission is granted for the domain + function combination. If valid, REG_AUTHENTICATED_SD is updated to the authorized entity.
Determinism in Kernel Mode — What’s Allowed vs Forbidden
In kernel mode, every operation must be deterministic. The same inputs must produce the same outputs on every node in the network. Violations cause consensus divergence — a network split where nodes disagree on the state of the world.

| ✅ Allowed in Kernel Mode | ❌ Forbidden in Kernel Mode |
|---|---|
Pure arithmetic (+, -, *, /) |
Random number generation |
State trie reads/writes (getVar, setVarEx) |
Local timestamps (time — use block time instead) |
| Cryptographic operations with same keys | File system access |
| Stack manipulation | Network calls |
| BigInt operations | User input (accept) |
Value transfers (sendEx) |
ERG management (erg) |
Smart contract calls (callEx) |
Key generation (keygen) |
This is a key philosophical point: user activity is described in GridScript so it is reproducible. File drag operations, value transfers, identity registrations — everything. When stored on the blockchain, GridScript is compiled to bytecode. The language bridges the gap between human intent and deterministic machine execution.
IV. Stack-Based Fundamentals — The Forth Heritage
If you have never used a stack-based language, the mental model is simple:
- Everything is a word — commands, numbers, operators are all “words”
- Numbers push themselves onto the stack
- Words consume and produce stack values
- No parentheses needed — execution order is left-to-right, stack-driven
10 20 + \ Push 10, push 20, add → stack contains 30 5 * \ Push 5, multiply → stack contains 150 . \ Print top of stack → outputs "150"
Traditional programming: result = (10 + 20) * 5GridScript (postfix): 10 20 + 5 * .
The Three Stacks
GridScript maintains three stacks:Data Stack (dStack) — The primary operand stack. All values pass through here. Maximum depth: 256. This is where you push numbers, pointers, addresses, and where operations consume and produce results.Return Stack (rStack) — Used for temporary storage and loop control. The >r command moves a value from the data stack to the return stack; r> moves it back. Loop constructs (DO...LOOP) use the return stack to track loop indices.
10 >r \ Move 10 to return stack 20 30 + \ Data stack: 50 r> \ Move 10 back — data stack: 50 10
Meta Stack — Parallel to the data stack, tracking type information (integer, pointer, string, BigInt) for runtime type checking and debugging. Each value on the data stack has a corresponding type descriptor on the meta stack.
Stack Manipulation
The fundamental stack operations — inherited directly from Forth:
dup \ ( a -- a a ) Duplicate top swap \ ( a b -- b a ) Swap top two over \ ( a b -- a b a ) Copy second to top rot \ ( a b c -- b c a ) Rotate top three drop \ ( a -- ) Remove top pick \ ( ... u -- ... xu ) Copy item at depth u roll \ ( ... u -- ... ) Move item at depth u to top depth \ ( -- n ) Current stack depth
Key Differences from Standard Forth
GridScript extends Forth significantly for its blockchain context:
- Integer size: 64-bit integers plus arbitrary-precision BigInt for cryptocurrency values (1 GNC = 1018 attoGNC)
- Security: Multi-layer permission system — kernel mode, terminal mode, admin credentials, security tokens, overwatch
- Persistent state: Values stored in a Merkle Patricia Trie, not volatile memory
- ERG metering: Every operation costs ERG (Execution Resource Gas), preventing infinite loops and resource exhaustion
- Built-in cryptography: Key generation, signing, verification, encryption, hashing — all native commands
- Thread formulation: BT/CT transaction threading has no Forth equivalent
Control Flow — Conditionals, Loops, and Word Definitions
GridScript inherits Forth’s complete control flow vocabulary. All branching constructs compile to (zbranch) and (branch) opcodes at compile time. These are fully available in kernel mode (on-chain smart contracts).
Conditionals — IF…THEN…ELSE
\ IF...THEN (basic conditional)
: is-positive ( n -- )
0 > IF
." Positive!" cr
THEN
;
5 is-positive \ prints: Positive!
\ IF...ELSE...THEN
: abs ( n -- |n| )
dup 0 < IF
-1 *
ELSE
\ already positive, do nothing
THEN
;
-7 abs . \ prints: 7
\ Nested IF
: classify ( n -- )
dup 0 = IF
." Zero" drop
ELSE
0 > IF ." Positive" ELSE ." Negative" THEN
THEN
cr
;
How it works: IF consumes a flag from the stack. If the flag is true (nonzero), execution continues to the next word. If false (0), execution jumps to ELSE (if present) or THEN. THEN marks the end of the conditional block — it is not the “then do this” of English; it means “resume here.”
Counted Loops — DO…LOOP and DO…+LOOP
\ DO...LOOP — iterates from start to limit-1
: count-to ( n -- )
0 DO
i . \ 'i' pushes current loop index
LOOP cr
;
5 count-to \ prints: 0 1 2 3 4
\ DO...+LOOP — custom step size
: evens ( n -- )
0 DO
i .
2 +LOOP cr
;
10 evens \ prints: 0 2 4 6 8
\ Nested loops: 'j' gives the outer loop index
: grid ( rows cols -- )
0 DO
dup 0 DO
i . ." ," j . ." "
LOOP cr
LOOP drop
;
DO takes ( limit start -- ) from the stack. i pushes the current (inner) loop index; j pushes the outer loop index in nested loops. Both use the return stack internally.
Indefinite Loops — BEGIN…UNTIL and BEGIN…WHILE…REPEAT
\ BEGIN...UNTIL (post-test — always executes at least once)
: countdown ( n -- )
BEGIN
dup . 1 -
dup 0 =
UNTIL drop cr
;
\ BEGIN...WHILE...REPEAT (pre-test — may execute zero times)
: countdown2 ( n -- )
BEGIN
dup 0 >
WHILE
dup . 1 -
REPEAT drop cr
;
UNTIL consumes a flag — if true, the loop exits; if false, it branches back to BEGIN. WHILE consumes a flag — if true, execution continues to REPEAT which branches back to BEGIN; if false, execution jumps past REPEAT.
Defining New Words
\ Colon definitions — the primary abstraction mechanism
: square ( n -- n² ) dup * ;
: cube ( n -- n³ ) dup square * ;
5 cube . \ prints: 125
\ Constants
: SPEED 100 ;
\ Anonymous definitions (execution tokens)
:noname ( n -- n² ) dup * ; \ pushes XT onto stack
5 swap execute . \ prints: 25
\ CREATE...DOES> for defining words that create data
: constant ( n -- )
CREATE ,
DOES> @
;
100 constant MAX-SPEED
MAX-SPEED . \ prints: 100
Word definitions with : name ... ; are the fundamental way to build abstractions in GridScript. Every colon definition compiles to bytecode and can be called from other definitions, smart contracts, or interactive sessions.
GridScript++ — JavaScript-Like Branching
If Forth-style branching feels unfamiliar, GridScript++ provides JavaScript-compatible syntax including if/else, for, while, function, and more. GridScript++ is a separate language that can be invoked from GridScript via the evalGPP codeword. It compiles down to GridScript operations. For details, see:
- GridScript++: The Next-Generation Language
- GridScript++ System API Documentation
- Cryptography in GridScript++
- Enhancing GridScript++ with Console.log
Raw Memory Allocation — malloc/free in a Blockchain VM
GridScript provides something unusual for a blockchain virtual machine: raw, manual memory allocation akin to C’s malloc and free. While most blockchain VMs hide memory management behind abstractions, GridScript gives the developer direct control — and direct responsibility.
The Core Allocation Commands
alloc ( size -- addr ior ) — Allocates size bytes of memory, returning the address and an I/O result code (0 = success, nonzero = failure). This is GridScript’s malloc. Each byte allocated costs 1 ERG (ERG_COST_PER_RAM_BYTE = 1), so allocating 1024 bytes costs 1024 ERG in addition to the base instruction cost.
free ( addr -- ) — Frees a previously allocated memory block. The VM tracks every allocation internally — when you call free, the allocation is removed from the tracker. Memory leaks are detectable.
allot ( n -- ) — Allocates n bytes in the data space (dictionary area). Unlike alloc, this is a Forth-traditional word that advances the here pointer. The memory is not individually freeable — it persists until the VM session ends. Use allot for compile-time data structures; use alloc/free for runtime dynamic allocation.
here ( -- addr ) — Pushes the current data-space pointer. This is where the next allot or , (comma) will place data.
Memory Access Commands
!( value addr -- )— Store a cell (64-bit value) at address@( addr -- value )— Fetch a cell from addressc!( char addr -- )— Store a single byte at addressc@( addr -- char )— Fetch a single byte from addresscells( n -- n*cellsize )— Convert a count to cell-aligned byte size (multiply by 8 on 64-bit)cmove( src dest count -- )— Copycountbytes fromsrctodest(forward copy)cmove>( src dest count -- )— Copy bytes backward (safe for overlapping regions)fill( addr count char -- )— Fillcountbytes starting ataddrwith byte valuecharmemclr( addr -- )— Zero out a previously allocated memory regionmem( -- )— Display memory usage statistics (allocation tracker summary)
Worked Example: Allocate, Use, Free
\ Allocate 1024 bytes of memory
1024 alloc \ Stack: addr 0
\ addr = pointer to allocated block
\ 0 = ior (success)
\ ERG cost: 1024 (bytes) + 1 (base) = 1025 ERG
drop \ Drop ior, keep address
\ Stack: addr
\ Store the value 42 at the allocated address
dup 42 swap ! \ Stack: addr
\ dup → addr addr
\ 42 → addr addr 42
\ swap → addr 42 addr
\ ! → stores 42 at addr → Stack: addr
\ Fetch it back to verify
dup @ \ Stack: addr 42
\ dup → addr addr
\ @ → fetches cell at addr → addr 42
. cr \ Print: 42 (newline)
\ Stack: addr
\ Store bytes at offsets
dup 65 over c! \ Store 'A' (65) at addr+0
dup 1 + 66 over c! \ Store 'B' (66) at addr+1
dup 2 + 67 over c! \ Store 'C' (67) at addr+2
\ Read them back
dup c@ emit \ Print: A
dup 1 + c@ emit \ Print: B
dup 2 + c@ emit \ Print: C
cr \ Output so far: "42\nABC\n"
\ Zero out the first 256 bytes
dup 256 0 fill \ Fill 256 bytes with 0x00
\ Free the memory
free \ Stack: (empty)
\ Allocation removed from tracker
\ Show memory statistics
mem \ Displays allocation tracker summary
How the VM Tracks Allocations
Internally, the GridScript VM maintains a memory region registry. Every allocation (whether from alloc, string creation, or data commands) is registered as a memory_region with a start address and end address. The VM uses this registry for two purposes:
- Bounds checking — Before every memory access (
!,@,c!,c@,cmove,fill), the VM validates the target address against its registered memory allocations to verify the target address falls within a registered allocation. Out-of-bounds access throws a runtime error. This prevents buffer overflows and use-after-free vulnerabilities. - Leak detection — When
freeis called, the allocation is unregistered. When the VM session ends, any remaining allocations are visible viamem. The VM can detect memory that was allocated but never freed.
All memory access is bounds-checked by the VM. Out-of-bounds access or use-after-free throws a runtime error — the VM validates every pointer against its allocation registry before any read or write.
This means GridScript achieves something remarkable: it gives you C-level memory control inside a blockchain VM, while maintaining safety guarantees that C itself does not provide. Every pointer is validated. Every access is bounds-checked. The cost is ERG — 1 ERG per byte allocated — creating an economic disincentive against wasteful memory use.
Debugging GridScript
GridScript provides several introspection tools that make debugging interactive and immediate. Unlike traditional debuggers that require breakpoints and stepping, GridScript’s stack-based nature means you can inspect the entire execution state at any point by inserting diagnostic words.
Stack Inspection
\ .s — Non-destructive stack dump with rich type info (the most important debug tool) 10 20 30 .s \ Output: \ [ Data-Stack Contents ] Depth: 3 \ # | Value | Source | Description | DataType \ 0 | 30 | + | result | signedInteger \ 1 | 20 | (lit) | literal | signedInteger \ 2 | 10 | (lit) | literal | signedInteger \ Debug a calculation step by step: 10 20 .s \ Stack[2]: 10 20 + .s \ Stack[1]: 30 5 * .s \ Stack[1]: 150 \ depth — How many items are on the stack? 10 20 30 depth . \ prints: 3 \ .r — Return stack contents (useful when debugging loops) 10 >r 20 >r .r \ Shows return stack contents r> r> drop drop \ .m — Memory dump (shows raw buffer contents for pointers) 42 "hello" .m \ Shows memory regions for stack values \ .sr — All 48 special registers .sr \ Dumps register contents (identity, permissions, ERG state, etc.)
ERG and Resource Monitoring
\ ergusage — How much ERG consumed so far (terminal-only) ergusage \ Shows current ERG consumption \ mem — Memory allocation statistics mem \ Shows all tracked allocations, sizes, and leak candidates \ erg -getsandbox — Full sandbox execution stats erg -getsandbox \ Shows ERG consumed, ERG limit, ERG remaining, TX overhead
The compiler Codeword as a Debugging Tool
The compiler codeword (see Section V-A) is invaluable for debugging because it lets you compile GridScript snippets and inspect the resulting bytecode without committing anything:
\ Test-compile a snippet to verify syntax: compiler "dup dup + ." \ Shows: ✓ Compilation Successful, bytecode size, V2 hash \ Test-compile a transaction's code before committing: compiler "cd /MyDomain send Recipient 1000000000000000000" \ Verify bytecode is correct before using BT/CT
Common Errors and How to Diagnose
| Error | Cause | Diagnosis |
|---|---|---|
| Stack underflow | Operation consumed more values than available on stack | Insert .s before the failing operation to see what’s actually on the stack. Check stack effect comments ( before -- after ). |
| ERG exhausted / “out of ERG by N” | Transaction exceeded its ERG Limit | Use ergusage at intervals to find the expensive operation. Increase limit with erg -setlimit. Check loops — a 10,000-iteration loop with a 3-ERG body costs 30,000 ERG. |
| Invalid memory access | Pointer to freed memory or out-of-bounds access | Use mem to inspect allocations. Ensure all alloc/free pairs match. Check that pointers from string operations are still valid. |
| “command only available from terminal” | Used a terminal-only command ([T]) inside a smart contract or kernel mode |
Commands like BT, CT, keygen, erg, time cannot run in kernel mode. Use their kernel-safe equivalents or restructure your code. |
| Compilation failed | Invalid GridScript syntax | Use compiler "your code here" to test compilation with detailed error output and hints. |
| Type mismatch | Operation expected a different type on stack | Use .m to inspect the metadata stack and see the types of each stack item. |
Debugging Strategy: The .s Sandwich
When a word misbehaves, wrap its calls with .s:
\ Before: my-broken-word \ Debug version: ." Before: " .s cr my-broken-word ." After: " .s cr
This immediately reveals what the word consumed and produced, making stack imbalances visible.
V. Command Reference — The GridScript Vocabulary
GridScript’s vocabulary encompasses approximately 290 commands (codewords), each assigned a sequential opcode starting from BASE_OPCODE_ID = 9. Commands are categorized by function. Each entry below shows the command name, its stack effect notation ( before -- after ), and a brief description. Commands marked [T] are terminal-only (not available in kernel/consensus mode).
Data Input
data64( -- ptr )— Push base64Check-decoded binary data onto the stackadata64( -- ptr )— Advanced base64 data with metadatadata( -- ptr )— Push UTF-8 string data onto the stackdata58( -- ptr )— Push base58Check-decoded binary dataadata( -- ptr )— Advanced data with metadata
Assertions
assert( flag -- )— Abort execution if flag is false (0)assert0( flag -- )— Abort execution if flag is true (nonzero)anz( n -- flag )— Assert non-zero; abort if zeroaz( n -- flag )— Assert zero; abort if non-zero
Memory Access
!( value addr -- )— Store cell value at address@( addr -- value )— Fetch cell value from addressc!( char addr -- )— Store byte at addressc@( addr -- char )— Fetch byte from addresscells( n -- n*cellsize )— Convert count to cell-aligned byte sizecmove( src dest count -- )— Copy bytes forwardcmove>( src dest count -- )— Copy bytes backward (safe for overlap)fill( addr count char -- )— Fill memory region with byte valuealign( -- )— Align data pointer to cell boundaryaligned( addr -- aligned-addr )— Return nearest cell-aligned addressallot( n -- )— Allocate n bytes in data spacehere( -- addr )— Push current data-space pointeralloc( size -- addr ior )— Allocate memory block (malloc-style)free( addr -- )— Free previously allocated memorymemclr( addr -- )— Clear/zero a memory regionmem( -- )— Display memory usage statistics
Stack Manipulation
dup( a -- a a )— Duplicate top of stack (built-in primitive)swap( a b -- b a )— Swap top two stack items (built-in primitive)over( a b -- a b a )— Copy second item to top (built-in primitive)rot( a b c -- b c a )— Rotate top three items (built-in primitive)drop( a -- )— Remove top item from stackpick( ... u -- ... xu )— Copy item at depth u to toproll( ... u -- ... )— Move item at depth u to topdepth( -- n )— Push current stack depth>r( a -- ) R:( -- a )— Move top of data stack to return stackr>( -- a ) R:( a -- )— Move top of return stack to data stackr@( -- a ) R:( a -- a )— Copy top of return stack without removing
Arithmetic
+( a b -- a+b )— Addition-( a b -- a-b )— Subtraction*( a b -- a*b )— Multiplication/( a b -- a/b )— Integer division/mod( a b -- rem quot )— Division with remaindermod( a b -- rem )— Modulo (remainder only)
Comparison and Logic
<( a b -- flag )— Signed less-than comparison=( a b -- flag )— Equality comparisonu<( a b -- flag )— Unsigned less-than comparison>( a b -- flag )— Greater than (signed). Equivalent toswap <in standard Forth.comp( addr1 addr2 len -- flag )— Compare memory blocks byte-by-bytefcomp( ptr1 ptr2 -- flag )— Full-pointer compareand( a b -- a&b )— Bitwise ANDor( a b -- a|b )— Bitwise ORxor( a b -- a^b )— Bitwise XORlshift( a n -- result )— Left bit-shiftrshift( a n -- result )— Right bit-shift
Type Conversion
>num( u -- n )— Interpret unsigned value as signed>unum( n -- u )— Interpret signed value as unsignedconcat( ptr1 ptr2 -- ptr3 )— Concatenate two byte sequences
Output and Display
.( n -- )— Print signed numberu.( u -- )— Print unsigned numbercr( -- )— Output newlineemit( char -- )— Output single charactertype( addr len -- )— Print string from address and length.s( -- )— Display data stack contents (non-destructive).r( -- )— Display return stack.rs( -- )— Display return stack (alternate format).m( -- )— Dumps memory regions pointed to by stack values. Shows raw buffer contents for pointers, ‘atomic stack value’ for non-pointers. Use.sfor a comprehensive type-aware stack display..sr( -- )— Display special registers.m58( ptr -- )— Print pointer value as base58hx( -- )— Set hexadecimal display modeecho( -- )— Echo inline text to outputechoEx( ptr -- )— Echo text from stack pointercount( addr -- addr+1 len )— Convert counted string to address+lengthbl( -- 32 )— Push space character (ASCII 32)base( -- addr )— Push address of number-base variable
Control Flow and Looping
(branch)( -- )— Unconditional branch (runtime)(zbranch)( flag -- )— Branch if zero/false (runtime)(do)( limit start -- )— DO loop setup (runtime)(doParams)( -- )— DO parameter initialization(loop)( -- )— LOOP increment and test (runtime)(+loop)( n -- )— +LOOP increment by n and test (runtime)(i)( -- index )— Push inner loop counter(j)( -- index )— Push outer loop counterexit( -- )— Exit current word definition
Compiler and Interpreter Internals
(;)( -- )— End of colon definition (runtime semantics)(does)( -- )— DOES> runtime behavior(lit)( -- n )— Push literal from instruction stream(slit)( -- addr len )— Push string literal from instruction stream(dlit)( -- d )— Push double-precision literal(ulit)( -- u )— Push unsigned literal:( -- )— Begin colon definition of a new word:noname( -- xt )— Begin anonymous (unnamed) definitioncreate( -- )— Create a new dictionary entryevaluate( addr len -- )— Evaluate string as GridScript sourceexecute( xt -- )— Execute an execution tokenfind( addr -- xt flag )— Find word in dictionaryffindEx( -- )— Extended dictionary findhidden( -- )— Toggle hidden flag on latest definitioninterpret( -- )— Enter interpreter looplatest( -- xt )— Push execution token of most recent definitionparse( char -- addr len )— Parse input stream up to delimiter characterprompt( -- )— Display interpreter promptquit( -- )— Reset stacks, return to interpreterrefill( -- flag )— Refill input buffer from sourcesource( -- addr len )— Push current input source address and lengthstate( -- addr )— Push address of compilation state variableword( char -- addr )— Parse next word delimited by charwords( -- )— List all defined words in dictionaryxt>name( xt -- addr len )— Convert execution token to word namesee( -- )— Decompile the next word in input>body( xt -- addr )— Get data-field address from execution token>in( -- addr )— Push address of input-offset variableaccept( c-addr +n1 -- +n2 )— Accept user input into bufferabort( -- )— Abort execution, clear stacksabort-message( flag c-addr u -- )— Conditional abort with error message
Cryptographic Operations
keygen( -- privPtr pubPtr )— Generate ECDSA key pair [T]keygenS( -- privPtr pubPtr )— Generate Schnorr key pair [T]sign( dataPtr -- sigPtr )— Sign data with active private keyversig( sigPtr dataPtr pubPtr -- flag )— Verify ECDSA/Schnorr signaturecheckSig( sigPtr dataPtr pubPtr -- flag )— Check signature validitysig( -- sigPtr )— Get current transaction signatureenc( dataPtr keyPtr -- cipherPtr )— Encrypt datadec( cipherPtr keyPtr -- plainPtr )— Decrypt dataencAuth( dataPtr keyPtr -- cipherPtr )— Authenticated encryptionencS( dataPtr keyPtr -- cipherPtr )— Schnorr encryptiondecS( cipherPtr keyPtr -- plainPtr )— Schnorr decryptionencAuthS( dataPtr keyPtr -- cipherPtr )— Schnorr authenticated encryptionultimium( dataPtr -- hashPtr )— Compute Ultimium cryptographic hashaddr( -- addrPtr )— Get current domain addresscheckaddr( addrPtr -- flag )— Validate address formatgetNonce( -- nonce )— Get transaction noncenonce( -- nonce )— Push nonce value onto stack
State Domain and Navigation
cd( -- )— Change directory / navigate to state domainls( -- )— List directory contentsdir( -- )— List directory (alias for ls)mkdir( -- )— Create directory in state domainmkdirex( ptr -- )— Create directory (explicit pointer)cat( -- ptr )— Read file contents from state domainhead( -- )— Read first lines of filetail( -- )— Read last lines of filewrite( -- )— Write data to filetouch( -- )— Create file or update timestamprm( -- )— Remove filermEx( ptr -- )— Remove file (explicit pointer)csd( -- )— Change state domaincsds( -- )— List child state domainsgetsd( -- ptr )— Get current state domain pathffind( -- ptr )— Find file in directory treeless( -- )— Paged output display
Variables and State
getVar( -- ptr )— Read variable by inline namegetVarEx( namePtr domainPtr -- valuePtr )— Read variable (explicit name and domain)setvar( -- )— Write variable (inline name and value)setVarEx( value isHidden name/path -- )— Write variable to current domain. The domain is determined by the currentcdcontext, not passed as a parameter. TheisHiddenflag (0=visible, 1=hidden) controls whether the variable appears inlslistings.flag( -- )— Set or get flag variableflagEx( -- )— Flag variable (explicit)setMeta( -- )— Set metadata on file or variablesetMetaEx( -- )— Set metadata (explicit)getMeta( -- ptr )— Get metadata from file or variable
Value Transfer (GNC)
send( -- )— Send GNC to address (inline: address amount)sendEx( sendFlags targetAddr amount -- )— Send GNC (explicit). For simple transfers, sendFlags=0 is the default. Inside BT/CT, the inlinesendcommand auto-generates flags and security tokens.balance( -- ptr )— Query domain balancesacrifice( -- )— Burn/sacrifice GNC (inline amount)sacrificeEx( amountPtr -- )— Burn/sacrifice GNC (explicit)xvalue( -- value )— Get value attached to current transactiongotGNC( -- flag )— Check if GNC was received in this transaction
Identity and Registration
getID( -- idPtr )— Get domain identity tokengenIDEx( -- )— Generate identity tokenregid( -- )— Register domain identity or nicknamegetpub( -- pubPtr )— Get domain public key (inline domain)getpubX( domainPtr -- pubPtr )— Get domain public key (explicit)whoami( -- )— Display current identity informationwho( -- )— Display user information
Token Pools
genPool( -- )— Generate token pool interactively [T]genPoolEx( -- )— Generate token pool (explicit) [T]regPool( -- )— Register token pool on-chainregPoolEx( dataPtr -- )— Register pool (explicit data pointer)getPool( -- ptr )— Query pool informationgetPoolEx( idPtr -- ptr )— Query pool (explicit ID pointer)xTT( -- )— Process transmission token transferxTTex( -- )— Process transmission token (explicit)
Permissions and Access Control
setfacl( -- )— Set file access control listsetfaclEx( -- )— Set ACL (explicit)chown( -- )— Change file/directory ownershipchownEx( -- )— Change ownership (explicit)getfacl( -- )— Get access control list
Smart Contract Execution
call( -- )— Call smart contract at inline pathcallEx( pathPtr -- )— Call smart contract (explicit path)callTransfer( -- )— Call smart contract with value transfercallTransferEx( valuePtr pathPtr -- )— Call + transfer (explicit)scex( -- )— Smart contract executeevalGPP( -- )— Evaluate GridScript++ source file
Transaction Formulation
BT( -- )— Begin Transaction thread [T]CT( -- )— Commit Threads — compile, sign, and submit [T]AT( -- )— Abort current ThreadBC( -- )— Begin Code (raw code accumulation mode) [T]EC( -- )— End Code [T]GL( -- )— Get accumulated code Lines [T]insert( -- )— Insert code into thread [T]SC( -- )— Set Code at location [T]cc( -- )— Compile and store code [T]BTR( -- )— Begin Transaction Raw [T]
Thread Management
RT( -- )— Resume Thread [T]ST( -- )— Suspend Thread [T]PT( -- )— Print Thread (show accumulated code) [T]FT( -- )— Focus Thread — switch to thread by ID [T]
Registers
getReg( regNum -- value )— Read value from numbered registerrr( -- )— Clear all registers (destructive reset)rs( -- )— Clear all stacks (destructive reset)
⚠️ Note: Registers are read-only from user GridScript code. The VM sets registers internally during execution. Use getReg to read register values. Do not confuse rr/rs with register read/write — they are destructive reset commands.
ERG Management
erg( -- )— ERG management interface [T]ergusage( -- )— Display current ERG usageergs( -- )— Display ERG statistics
Key Management
setKey( -- )— Set active private key [T]getKey( -- keyPtr )— Get current private key pointer [T]setKeyID( -- )— Set active key by ID [T]keychain( -- )— List stored keys [T]genKey( -- )— Generate new key interactively [T]getCredentials( -- credPtr )— Get session credentials [T]genid( -- )— Generate identity interactively [T]
Blockchain and Consensus
getChain( -- )— Get blockchain information [T]getNextID( -- idPtr )— Get next block ID [T]commit( -- )— Commit pending state changes [T]stepBack( -- )— Step back one block [T]setPerspective( hashPtr -- )— Set state perspective to specific block [T]perspective( -- hashPtr )— Get current perspective hash [T]startFlow( -- )— Start execution flow [T]chain( -- )— Blockchain operations [T]resync( -- )— Resynchronize blockchain state [T]
State Updates
update( -- )— Trigger state updateupdate1( -- )— State update phase 1update2( -- )— State update phase 2
Network and Node Management
stat( -- )— Display node statistics [T]vt( -- )— View active threads [T]nstart( -- )— Start network service [T]nstop( -- )— Stop network service [T]npause( -- )— Pause network service [T]tstart( -- )— Start task [T]tstop( -- )— Stop task [T]sct( -- )— Set commit target [T]sync( -- )— Synchronize state [T]gct( -- )— Get commit target [T]cct( -- )— Clear commit target [T]net( -- )— Display network information [T]route( -- )— Display routing information [T]firewall( -- )— Firewall management [T]proxy( -- )— Proxy configuration [T]ifconfig( -- )— Network interface configuration [T]hotstart( -- )— Hot restart node [T]
System Information
time&date( -- sec min hr day mon yr )— Push local time and date componentsutctime&date( -- sec min hr day mon yr )— Push UTC time and date componentstime( -- )— Display current time [T]info( -- )— Display domain informationunused( -- n )— Push amount of unused data spacegetResult( -- result )— Get result of last executiongetTaskStatus( -- status )— Get status of current taskversion( -- )— Display GridScript version [T]
Admin and System
shutdown( -- )— Shutdown node (requires admin) [T]bye( -- )— Exit terminal session [T]sudo( -- )— Elevate to admin privileges [T]logmein( -- )— Login to node [T]backup( -- )— Backup node data [T]overwatch( -- )— Overwatch monitoring operationsVMMaintenance( -- )— Virtual machine maintenance operations
Mining and Performance
calcPerf( -- )— Calculate node performance [T]getworkersStat( -- )— Get worker statistics [T]genPow( -- )— Generate proof of work [T]gettasks( -- )— List active tasks [T]getTaskResult( -- )— Get task result [T]
Transaction History
receipts( -- )— View transaction receipts [T]rec( -- )— View receipt details [T]tx( -- )— View transaction history [T]transactions( -- )— View transaction history (alias) [T]diff( -- )— View state diff between blocks [T]warnings( -- )— View system warnings [T]market( -- )— Display market information [T]
Bytecode Compilation
compiler( -- )— Compile GridScript source to bytecode [T]compilerEx( sourcePtr -- bytecodePtr )— Compile to bytecode (explicit) [T]
Polls
poll( -- )— Create or manage on-chain pollspollEx( -- )— Poll operations (explicit parameters)
Miscellaneous
updateUI( -- )— Trigger UI refresh [T]debugUI( -- )— Debug UI state [T]export( -- )— Export data [T]import( -- )— Import data [T]nick( -- )— Set user nickname [T]wall( -- )— Broadcast wall message [T]snake( -- )— Snake game 🐍 [T]gridcraft( -- )— Gridcraft game [T]hexi( -- )— Hex inspector [T]context( -- )— Context operations (API gateway) — the GridScript-side handler that powers the Blockchain Explorer API. Supports subcommands like-c searchBlockchain,-c getBlocks,-c getDomainInfo, etc. Automatically consumesmkfilterresults from the stack. [T]cstorage( -- )— Cloud storage operations [T]txconfig( -- )— Transaction configuration [T]grid( -- )— Grid network operations [T]scm( -- )— Source control management [T]gts( -- )— Get thread status [T]mkfilter( -- ptr )— Create a search filter object for blockchain queries. Supports flags:-tx,-blocks,-domains,-addresses,-all, and arbitrary property filters via-flag "name" "value". Pushes base58Check-encoded filter to stack for automatic use withcontext. [T]pause( -- )— Alias forST(Suspend Thread) — pause the current thread [T]
ANS Forth File I/O
GridScript implements the ANS Forth file access wordset for local file operations on the GRIDNET Core node. These are terminal-only commands — they operate on the node’s local filesystem, not the decentralized state trie.
open-file( c-addr u fam -- fileid ior )— Open a file.c-addr uis the filename string,famis the file access modecreate-file( c-addr u fam -- fileid ior )— Create a new file (or truncate existing)close-file( fileid -- ior )— Close an open fileread-file( c-addr u fileid -- u2 ior )— Read up toubytes into buffer atc-addrread-line( c-addr u fileid -- u2 flag ior )— Read a line from fileread-char( fileid -- char ior )— Read a single characterwrite-file( c-addr u fileid -- ior )— Writeubytes from buffer to filewrite-line( c-addr u fileid -- ior )— Write a line to file (appends newline)write-char( char fileid -- ior )— Write a single character to fileflush-file( fileid -- ior )— Flush file buffers to diskdelete-file( c-addr u -- ior )— Delete a file by namerename-file( c-addr1 u1 c-addr2 u2 -- ior )— Rename a fileinclude-file( fileid -- )— Evaluate GridScript source from a filer/o( -- fam )— Read-only file access moder/w( -- fam )— Read-write file access modew/o( -- fam )— Write-only file access mode
Important distinction: These file I/O words operate on the local filesystem of the GRIDNET Core node, not on the decentralized state trie. For decentralized file operations (which are stored on-chain), use the DFS commands: cat, write, touch, rm, mkdir, ls. The ANS file words are useful for node administration tasks, log analysis, and scripting — not for dApp data storage.
Thread Aliases
GridScript provides alternative names for thread management commands that may feel more natural to developers coming from SQL or other paradigms:
begin— Alias forBT(Begin Thread)commit— Alias forCT(Commit Threads)abort— Alias forAT(Abort Thread)pause— Alias forST(Suspend Thread)
So the following two sequences are equivalent:
\ Traditional GridScript style: BT cd /MyDomain send Recipient 1000000000000000000 CT \ SQL-inspired alias style: begin cd /MyDomain send Recipient 1000000000000000000 commit
Opcode Architecture Note
Commands are assigned opcodes sequentially from BASE_OPCODE_ID = 9. Reserved IDs 1–5 handle literal types (unsigned, signed, double, user-defined, string). IDs 6–8 are immediate words (;, does>, immediate). The built-in primitives dup, swap, over, and rot are handled directly by the interpreter loop and do not consume opcodes from the main command array. The ordering of codewords is immutable — new commands are always appended — because reordering would break all existing compiled bytecode stored on the blockchain.
Inline Arguments — Command-Line-Style Parameters
Many GridScript codewords accept inline arguments parsed directly from the input stream at execution time, rather than consuming values from the stack. These look like command-line flags and parameters:
\ Inline arguments are parsed from the input stream, not the stack: ls -l -help \ -l and -help are inline parameters to ls erg -setbid 100 \ -setbid and 100 are inline parameters to erg chain -getblocks -page 1 -size 20 \ Multiple inline flags with values setfacl file1 -m u:lisa:w \ Unix-like ACL syntax as inline params cd /MyDomain \ The path is an inline parameter to cd send RecipientAddr 1000000000000000000 \ Address and amount are inline params
Each codeword declaration specifies an inlineParamsCount value indicating how many inline parameters it expects. The codeword reads these from the input stream via getInlineParamVal() at execution time.
Inline vs Explicit (Ex) Forms
During transaction formulation (BT...CT), inline commands are automatically converted to their explicit (Ex) stack-based forms for bytecode serialization. The Ex form is what gets compiled and stored on-chain:
| Inline (Terminal) | Explicit (Bytecode) | Stack Effect |
|---|---|---|
cd /path |
"/path" cdEx |
( pathPtr -- ) |
getVar 'name' |
"name" "domain" getVarEx |
( namePtr domainPtr -- valuePtr ) |
setvar 'k' 'v' |
"v" 0 "k" setVarEx |
( value isHidden name -- ) |
send addr amount |
0 "addr" amount sendEx |
( sendFlags targetAddr amount -- ) |
call /path |
"/path" callEx |
( pathPtr -- ) |
rm filename |
"filename" rmEx |
( ptr -- ) |
mkdir dirname |
"dirname" mkdirex |
( ptr -- ) |
This dual-form design means developers can use natural, readable inline syntax in the terminal while the system automatically produces the compact stack-based form for on-chain execution.
Built-in Man Pages — Discover Commands with -help
Many GridScript codewords have built-in help pages accessed via the -help inline flag. This is a key developer discovery tool — when unsure about a command’s syntax, always try command -help first:
ls -help \ File listing options and syntax erg -help \ ERG management subcommands setfacl -help \ Full ACL syntax with examples market -help \ Market information queries rm -help \ File removal options cat -help \ File content display options peers -help \ Peer/network information compiler -help \ Compilation options and flags
Help pages display formatted, colored output organized into [Description], [Syntax], and [Examples] sections. They are built into the codeword implementations and are always available in terminal mode.
Domain (Account) Creation
Domains (accounts) in GRIDNET OS follow the same creation model as Ethereum: a domain is created automatically when assets are first dispatched to it. There is no explicit “create account” transaction needed. When you send GNC to a new address, the receiving domain is created in the state trie as a side effect of the transfer:
\ This creates the domain if it doesn't exist yet: BT cd /YourDomain send NewDomainAddress 1000000000000000000 \ 1 GNC — domain is created automatically CT
A domain address is derived from the public key (via base58Check encoding). Once created, the domain persists in the Merkle Patricia Trie and can hold variables, files, smart contracts, identity tokens, and sub-domains.
Decentralized File System (DFS) API
GRIDNET OS includes an extremely sophisticated Decentralized File System with Unix-like file operations and fine-grained access control. Files are stored across the decentralized network with full ACL management.
File Operations
\ Familiar Unix-like commands: ls \ List directory contents ls -l \ Long listing format cd /domain/path \ Navigate to path mkdir mydir \ Create directory touch myfile \ Create file or update timestamp cat myfile \ Read file contents head myfile \ First lines of file tail myfile \ Last lines of file write myfile \ Write to file rm myfile \ Remove file ffind filename \ Search for file in directory tree less myfile \ Paged output display
Access Control Lists (ACLs)
The setfacl / getfacl codewords provide Unix-like ACL management for decentralized files:
\ Grant user lisa write access to file1: setfacl file1 -m u:lisa:w \ Display ACL entries for a file: getfacl file1 \ Clear all extended ACL entries: setfacl file1 -b \ Change file ownership: chown file1 newOwnerAddress \ Full syntax reference: setfacl -help \ Displays detailed help with all options
The ACL system supports user-level permissions (read, write, execute) on files and directories stored in the decentralized state trie. This enables multi-user access control in a fully decentralized context — no central authority manages permissions.
V-B. The Data Type System — BigInt, BigSInt, and BigFloat
GridScript’s data stack is not limited to fixed-size integers. The VM tracks the data type of every value on the stack via the parallel meta stack. Understanding these types is critical for cryptocurrency operations where values exceed 64-bit limits.
Supported Data Types
| Type | Description | Use Case |
|---|---|---|
signedInteger |
64-bit signed integer | General arithmetic, loop counters, flags |
unsignedInteger |
64-bit unsigned integer | Addresses, sizes, bitwise operations |
BigInt |
Arbitrary-precision unsigned integer | GNC values (1 GNC = 1018 attoGNC), balances, large computations |
BigSInt |
Arbitrary-precision signed integer | Signed large values, balance differences |
BigDouble / doublee |
Arbitrary-precision and IEEE 754 floating point | Decimal calculations, ratios, percentages |
pointer |
Memory address (points to allocated region) | String data, binary data, allocated memory blocks |
Automatic Type Promotion
When arithmetic operations encounter mixed types, the VM automatically promotes values to the wider type. For example, adding a signedInteger to a BigInt promotes the integer to BigInt before performing the operation. The result inherits the promoted type. This is particularly important for GNC value calculations — the send and sendEx commands work with BigInt values internally, so values like 1000000000000000000 (1 GNC) are automatically handled as arbitrary-precision integers.
Type Inspection
The .s command shows the data type of each stack item in its DataType column. The .m command shows memory regions for pointer types. Together, these debugging tools let you verify that your values have the expected types at every step:
\ Push a regular integer — type is signedInteger 42 .s \ # | Value | DataType \ 0 | 42 | signedInteger \ Push a GNC value — automatically BigInt 1000000000000000000 .s \ # | Value | DataType \ 0 | 1000000000000000000| BigInt \ String literal — type is pointer 42 "hello" .s \ # | Value | DataType \ 0 | 0x... | pointer \ 1 | 42 | signedInteger
V-C. Blockchain Search Filters — The mkfilter System
The mkfilter codeword creates structured search filter objects for querying the blockchain via the context command. This is the programmatic interface behind the Blockchain Explorer’s search functionality — and it is available to any developer building dApps that need to query blockchain data.
Basic Usage
\ Create a filter for transaction searches: mkfilter -tx -flag "tx_typeName" "sacrifice" \ Use it with context to search the blockchain: context -c searchBlockchain -search "" \ The filter is automatically detected and consumed from the stack
Standard Flags (Entity Types)
-tx— Include transactions (default if no flags specified)-blocks— Include blocks-domains— Include domains/accounts-addresses— Include addresses-all— Include all entity types
Arbitrary Property Filters
Use -flag "name" "value" to filter by specific properties. You can specify multiple flags — same-name flags use OR logic (match any).
Transaction Filters (prefix: tx_)
tx_type,tx_typeName— Transaction type (numeric or name)tx_sender,tx_receiver— Sender/receiver addresstx_value— Transfer valuetx_verifiableID— Verifiable transaction IDtx_sacrificedValue— Sacrificed (staked) amounttx_nonce,tx_result,tx_resultTxt— Nonce, result code, result texttx_auxContractPath— Smart contract pathtx_auxTokenPoolID— Token pool IDtx_auxFriendlyID— Human-readable identifier (nickname)tx_auxValue— Auxiliary value
Block Filters (prefix: block_)
block_blockID,block_type,block_height,block_keyHeightblock_solvedAt,block_minerID,block_parentIDblock_ergUsed,block_ergLimit,block_nonceblock_blockReward,block_receiptsCount,block_transactionsCount
Domain Filters (prefix: domain_)
domain_domain,domain_balance,domain_lockedBalancedomain_txCount,domain_txTotalReceived,domain_txTotalSentdomain_perspective,domain_perspectivesCountdomain_publicKey,domain_nickname,domain_stake
Transaction Types Reference
| ID | Name | ID | Name |
|---|---|---|---|
| 0 | transfer | 7 | tokenPoolRegistration |
| 1 | blockReward | 8 | identityRegistration |
| 2 | offChain | 9 | permissionChange |
| 3 | offChainCashOut | 10 | smartContractCall |
| 4 | contract | 11 | valueContractCall |
| 5 | genesisReward | 12 | gridScriptPP |
| 6 | sacrifice | 13 | arbitraryExecution |
Note: Transactions may have multiple types simultaneously — a transaction containing both sendEx and xTTex carries both transfer and offChainCashOut type flags. These appear as “Hybrid” transactions in the Explorer.
Worked Examples
\ 1. Find all sacrifice transactions mkfilter -tx -flag "tx_typeName" "sacrifice" context -c searchBlockchain -search "" \ 2. Find identity registrations by nickname "alice" mkfilter -tx -flag "tx_typeName" "identityRegistration" -flag "tx_auxFriendlyID" "alice" context -c searchBlockchain -search "" \ 3. Find blocks mined by a specific address mkfilter -blocks -flag "block_minerID" "1ABCxyz..." context -c searchBlockchain -search "" \ 4. Find domains by nickname mkfilter -domains -flag "domain_nickname" "bob" context -c searchBlockchain -search "" \ 5. Find smart contract calls to a specific contract mkfilter -tx -flag "tx_typeName" "smartContractCall" -flag "tx_auxContractPath" "/1ABCxyz.../app.gs" context -c searchBlockchain -search "" \ 6. Multi-type search (OR logic) — find both transfers and cashouts mkfilter -tx -flag "tx_typeName" "transfer" -flag "tx_typeName" "offChainCashOut" context -c searchBlockchain -search "" \ 7. Keep filter for reuse across multiple queries mkfilter -tx -flag "tx_typeName" "sacrifice" context -c searchBlockchain -search "" -keepfilter context -c searchBlockchain -search "" -keepfilter drop \ Remove filter when done
Filter Stack Integration
The filter is pushed onto the data stack as a base58Check-encoded pointer. The context command automatically detects and consumes filters from the stack. Use -keepfilter with context to retain the filter for reuse. Use -nostack to disable auto-detection entirely. To manually discard a filter, use drop.
VI. Practical Examples — GridScript in Action
Example 1: Value Transfer from the Terminal
\ Step 1: Set ERG parameters erg -setbid 100 erg -setlimit 50000 \ Step 2: Formulate and commit the transaction BT cd /YourDomainAddress send RecipientAddress 1000000000000000000 CT \ Step 3: Verify (after block confirmation) balance 'RecipientAddress' .
Example 2: Deploying a Smart Contract
BT cd /YourDomain/contracts BC \ Simple counter contract cd /YourDomain getVar 'counter' 1 + 0 "counter" setVarEx EC CT \ Later, call it: BT cd /YourDomain call /YourDomain/contracts CT
Example 3: Deploying an Identity Token
Identity tokens establish your presence in the GRIDNET OS ecosystem. The process involves a sacrificial transaction (staking GNC) followed by identity registration:
\ Step 1: Sacrifice GNC for the identity stake BT cd /YourDomain sacrifice '100000000000000000000' \ Stake 100 GNC CT \ Step 2: After confirmation, register the identity BT cd /YourDomain regid \ Register identity with nickname CT
The Wallet dApp’s Identity Token Registration Wizard automates this entire multi-stage process — including DPT pre-validation, BER-encoded identity token creation in JavaScript, and progressive commit monitoring — making it accessible without understanding the underlying GridScript.
Example 4: Querying Blockchain State
\ View domain information info 'DomainAddress' \ Check balance balance 'DomainAddress' . \ View transaction history tx DomainAddress \ Get blockchain status chain \ View receipts receipts DomainAddress
VII. GridScript in the JavaScript Ecosystem — The Browser Bridge
UI dApp Architecture — The Two-Part Model
Understanding how GRIDNET OS UI dApps work is essential before diving into the JavaScript APIs. A typical UI dApp has two distinct parts:
- JavaScript part — Executes in the web browser. Handles the user interface, DOM manipulation, event handling, and interacts with GRIDNET OS APIs through the CVMContext JavaScript singleton. This is what most dApp developers work with day-to-day. All GRIDNET OS API calls are JavaScript constructs.
- GridScript part — Executes on the GridScript VM on remote GRIDNET Core nodes. This is the low-level layer used when truly needed: value transfers, smart contract deployment and invocation, state modifications, identity operations. GridScript runs on the decentralized network — deterministically, verifiably, across every node.
The key insight: most GRIDNET OS API calls are JavaScript. Using GridScript directly is a low-level approach for when you need state changes committed to the blockchain. The JavaScript layer handles data retrieval, UI rendering, event subscriptions, and user interaction. The GridScript layer handles the things that must be deterministic and consensus-verified.
For hands-on tutorials on building UI dApps, see:
- Hello World UI dApp Tutorial — Step-by-step guide to creating your first UI dApp
- Decentralized OS With UI dApps — Architecture and design guidelines
The CVMContext Singleton
📖 Comprehensive Reference: CVMContext method signatures, event types, and the full JavaScript API are documented in the VMContext Documentation article. The Blockchain Explorer API Documentation covers the query API that CVMContext exposes for data retrieval. This section explains how CVMContext connects to the GridScript layer — for the full API reference, see those articles.
The context codeword in GridScript is what powers the Blockchain Explorer API under the hood — it is the GridScript-side handler that CVMContext calls into when JavaScript code makes data queries. When you use the Blockchain Explorer API from JavaScript, your request is ultimately executed as GridScript on a remote node via the context codeword.
CVMContext.getInstance() manages:
- WebSocket connection to GRIDNET Core nodes with automatic reconnection
- VM Meta-Data protocol parsing and generation via CVMMetaParser/CVMMetaGenerator
- Process/Thread management — each UI dApp gets a CProcess that can spawn CThreads for background GridScript execution on the remote node
- Request ID correlation — every outgoing request gets a unique ID; responses carry the same ID back, enabling concurrent asynchronous operations
- Event system — listeners for VM metadata, DFS messages, GridScript results, terminal data, VM state changes, and commit state transitions. This is the dispatch mechanism through which CVMContext notifies individual UI dApp instances — each dApp registers its callbacks with a window ID, and CVMContext routes incoming BER responses to the appropriate listeners. Multiple dApps can listen simultaneously; a single incoming response may trigger callbacks in multiple UI dApp instances
How a dApp Retrieves Data
When the Blockchain Explorer needs to display recent blocks:
// 1. JavaScript constructs the GridScript request
const generator = new CVMMetaGenerator();
const reqID = CVMMetaGenerator.genReqID();
generator.addRAWGridScriptCmd(
'chain -getblocks -page 1 -size 20 -sort height',
reqID, processID, vmID
);
// 2. BER-encode and send through WebSocket
const berData = generator.finalize();
vmContext.send(berData);
// 3. Register callback for this specific request
vmContext.addVMMetaDataListener((sections) => {
for (const section of sections) {
for (const entry of section.entries) {
if (entry.reqID === reqID) {
// 4. Decode BER response and render
const blockDesc = CBlockDesc.instantiate(entry.data);
renderBlockRow(blockDesc);
}
}
}
}, windowID);
This pattern — construct, encode, send, correlate, decode, render — is the heartbeat of every UI dApp in GRIDNET OS. The Terminal dApp uses it. The Wallet uses it. The Explorer uses it. The File Manager uses it. Even the Snake game uses it.
In-Browser GridScript Compilation
For committed transactions, the browser goes further. GridScriptCompiler.js compiles GridScript source code directly into bytecode in the browser:
- V1 bytecodes (legacy): raw opcode sequence
- V2 bytecodes (current): version byte + 32-byte hash + opcodes — enabling integrity verification
- Opcodes ≤ 127 use single-byte encoding; opcodes > 127 use two-byte encoding with a high-bit flag
The Wallet dApp uses this to compile, sign (via Ed25519 ECC), and dispatch transactions entirely from within the browser — no server involvement. CTransaction.js handles BER serialization of the transaction structure, CKeyChainManager provides HD wallet key derivation, and the compiled bytecode passes all pre-validation tests on GRIDNET Core including code integrity and signature verification.
The Compiler and Bytecode Format — From Source to On-Chain Execution
Understanding how GridScript source code becomes executable bytecode is essential for advanced development. The compilation pipeline operates identically in two implementations — the C++ reference compiler inside GRIDNET Core and the JavaScript compiler (GridScriptCompiler.js) running in the browser — producing bit-identical output.
The compiler Codeword — Terminal Developer Tool
The compiler codeword is a terminal-only developer utility for compiling GridScript source to bytecode, generating transactions, and testing code without committing anything to the blockchain:
\ Basic compilation — outputs base64 bytecode: compiler "cd /MyAddress" \ Output: ✓ Compilation Successful, bytecode size, V2 format \ Compile and generate an unsigned transaction: compiler "send 1BKGXMj441G5iWuQyxS7Bpr4bxr9vAvXWy 2000000000000000000" -tx \ Compile, generate, and sign a transaction: compiler "send Target 1000" -tx -sign <base58_private_key> \ Generate a QR code of the bytecode (for mobile scanning): compiler "cd /MyAddress" -qr \ Generate a QR code of the signed transaction: compiler "send Target 1000" -tx -sign <key> -qrtx \ Built-in help: compiler -help
Stack output after compilation: On success, the stack contains ( [tx_ptr if -tx] bytecode_ptr 1 ). On failure: ( error_msg_ptr 0 ). This enables programmatic reuse — you can compile bytecode and pass it to other operations.
ERG cost: 10 base + sourceLength/100. Terminal-only (onlyFromTerminal = true, allowedInKernelMode = false).
The V2 Bytecode Format
GridScript uses two bytecode formats. All new compilation produces V2; the decompiler supports both.
V1 (Legacy) — Raw opcodes with no integrity verification:
┌──────────┬──────────┬──────────┬──────────┐ │ OPCODE_1 │ PARAMS_1 │ OPCODE_2 │ PARAMS_2 │ ... └──────────┴──────────┴──────────┴──────────┘
V2 (Current) — Version header + SHA256 keyword hash chain for integrity verification:
┌──────────────┬───────────────────────────────┬──────────┬──────────┐ │ VERSION_BYTE │ 32-BYTE SHA256 KEYWORD HASH │ OPCODE_1 │ PARAMS_1 │ ... │ (≥ 192) │ (integrity verification) │ │ │ └──────────────┴───────────────────────────────┴──────────┴──────────┘ 1 byte 32 bytes opcodes...
The version byte is always ≥ 192 (if the raw version number is < 192, the formula 192 + (version % 64) is applied). The decompiler detects V1 vs V2 by checking: byte[0] < 192 → V1, byte[0] ≥ 192 → V2.
Opcode Encoding
Opcodes are assigned sequentially from BASE_OPCODE_ID = 9. The encoding uses a compact scheme:
Single-byte encoding (opcode ID ≤ 127):
┌──────────┐ │ ID_BYTE │ Example: 'cd' = opcode 148... wait, that's > 127 └──────────┘ Example: '+' = opcode 24 → [0x18]
Two-byte extended encoding (opcode ID > 127):
┌───────────────────┬──────────┐ │ HIGH_BYTE | 0x80 │ LOW_BYTE │ └───────────────────┴──────────┘ Bit 7 set on first byte; bytes in swapped order (little-endian with flag)
For example, cd at opcode 162: low byte = 0xA2, high byte = 0x00. After swap and flag: [0x80, 0xA2].
Length Encoding (for inline parameters)
When a codeword has inline parameters, the parameter data length is encoded after the opcode:
Simple (length ≤ 127): single byte [LENGTH]
Extended (length > 127): [NUM_LENGTH_BYTES | 0x80][BYTE_0][BYTE_1]... where length bytes are little-endian.
Parameters that are addresses (base58-encoded) are decoded to their binary form before storage. A base58 address like /144c2Aof5t3GDUTv8x9mPorpzMLhkFQLWp becomes 21 bytes of binary, not 35 bytes of ASCII.
The Keyword Hash Chain — Bytecode Integrity
V2 bytecode includes a 32-byte SHA256 hash that verifies the integrity of the compiled keyword sequence. The chain is computed during compilation:
1. Initialize: hash = SHA256(GRIDSCRIPT_IMAGE_INIT_STRING) 2. For each keyword compiled: hash = SHA256(hash + keyword_name_bytes) 3. Final hash is stored in the V2 header (bytes 1–32) \ During decompilation, the same chain is recomputed. \ If the final hash doesn't match the header → bytecode is corrupt or tampered.
This ensures that bytecode cannot be modified after compilation without detection — a critical property for smart contracts stored on the blockchain.
In-Browser Compilation — Full C++ Parity
The JavaScript compiler (GridScriptCompiler.js) achieves bit-identical output with the C++ reference compiler. This is what enables the Wallet dApp to compile, sign, and submit transactions entirely from the browser:
// Async compilation API (uses Web Crypto for SHA256 hash chain)
const result = await compiler.compile(sourceCode);
// Returns: { success: boolean, bytecode: Uint8Array|null, errors: string[] }
// Async decompilation (supports both V1 and V2)
const result = await compiler.decompile(bytecode);
// Returns: { success: boolean, source: string|null, errors: string[] }
// Full round-trip guarantee:
// compile(JS) → decompile(C++) → identical source
// compile(C++) → decompile(JS) → identical source
The JavaScript compiler uses the Web Crypto API (crypto.subtle.digest('SHA-256', data)) for the keyword hash chain, making compilation asynchronous. It handles all encoding details identically to C++: extended opcode encoding for IDs > 127, extended length encoding for parameters > 127 bytes, base58 address decoding, and V2 header generation.
The end-to-end pipeline in the Wallet dApp: GridScript source → GridScriptCompiler.js compiles to V2 bytecode → CTransaction.js wraps in BER-serialized transaction → CKeyChainManager signs with Ed25519 ECC → Web Workers handle encoding off the main thread → dispatched through onion-routed encrypted WebSocket. No server touches the code at any point.
VIII. Deploying Your UI dApp On-Chain
Everything described so far — the SOA pipeline, CVMContext, BER encoding, GridScript compilation in the browser — converges here: building and deploying a UI dApp that lives on the blockchain. No server. No app store. No gatekeeper. Your code is committed to the Decentralized File System (DFS), replicated across the network, and available to every user globally. It cannot be censored, taken down, or altered by anyone other than the deploying entity. This is what decentralized application deployment actually looks like.
The .app Package Format
A GRIDNET OS UI dApp is a single JavaScript file with the .app extension. It contains one ES6 class that extends CWindow — the base window class provided by the GRIDNET OS desktop environment — and exports it as the default export. That’s it. One file, one class, one export.
The package must declare a PackageID that follows the reverse-domain convention and begins with org.gridnetproject.UIdApps. — for example, org.gridnetproject.UIdApps.MyApp. The class implements three required identification methods:
getPackageID()— Returns the unique package identifier stringgetIcon()— Returns an emoji or icon character for the desktop and taskbargetDefaultCategory()— Returns the application category (e.g.,"Utilities","Finance","Games")
If your dApp depends on external libraries, bundle them into the single file using Rollup, Webpack, or any JS bundler. Do not bundle GRIDNET OS system libraries (CVMContext, CWindow, CTools, etc.) — these are provided by the runtime environment and available globally.
Minimal Code Skeleton
export default class MyApp extends CWindow {
getPackageID() { return "org.gridnetproject.UIdApps.MyApp"; }
getIcon() { return "🚀"; }
getDefaultCategory() { return "Utilities"; }
constructor(params) {
super(params);
this.setTitle("My First dApp");
}
onReady() {
// Shadow DOM — fully scoped, no style leakage
const body = this.getBodyElement();
body.innerHTML = `
<style>
.container { padding: 16px; color: #0f0; font-family: monospace; }
button { background: #222; color: #0f0; border: 1px solid #0f0;
padding: 8px 16px; cursor: pointer; }
button:hover { background: #0f0; color: #000; }
</style>
<div class="container">
<h2>Hello, GRIDNET OS!</h2>
<p id="output">Click the button to query the network.</p>
<button id="btn">Query Balance</button>
</div>
`;
// Event handling — standard DOM APIs inside Shadow DOM
body.querySelector("#btn").addEventListener("click", () => {
body.querySelector("#output").textContent = "Querying via CVMContext...";
// Use CVMContext APIs to interact with the network
});
}
}
Every UI dApp runs inside a Shadow DOM — styles and DOM are fully scoped to the window. Your CSS cannot leak into other dApps, and theirs cannot affect yours. The onReady() method is called once the window is initialized and the Shadow DOM is available.
Development Workflow
- Start from the template — Clone or download AppTemplate.js / UserDeployable.app from the GRIDNET OS GitHub repository. This is a working skeleton with all required methods already stubbed.
- Rename the class — Change the class name and update
getPackageID()to your unique identifier. - Set the window title — Call
this.setTitle("Your App Name")in the constructor. - Define your UI — Write your HTML and CSS inside
onReady()usingthis.getBodyElement().innerHTML. Everything lives in Shadow DOM. - Set an icon — Return an emoji from
getIcon()(displayed on the desktop, taskbar, and app launcher). - Add logic — Use CVMContext for network queries, CTools for utilities, and standard JavaScript for everything else.
Sandbox Testing — Zero Cost
Before committing anything to the blockchain, you can test your dApp for free:
- Drag your
.appfile onto the File Manager window in GRIDNET OS (the browser-based desktop). - The PackageManager automatically analyzes the file — it validates the class structure, checks the PackageID format, and verifies that required methods are present.
- Click to run — the dApp launches in a sandboxed window. You can interact with it, test UI behavior, and verify network queries. No GNC is consumed. Nothing is written to the blockchain.
This sandbox cycle — edit, drag, test — is your development loop. Iterate until your dApp works exactly as intended.
Committing to the Network — Permanent Deployment
When your dApp is ready for the world:
- Select the .app file in the File Manager.
- Click the ⋮⋮⋮ Magic Button — this opens the commit dialog.
- Commit to DFS — the file is compiled into a transaction, signed with your private key, and submitted to the network. This costs GNC (the transaction includes the file data, so larger dApps cost more).
- Once confirmed in a block, your dApp is permanently stored on the Decentralized File System — replicated across all nodes, globally accessible, censorship-resistant.
Your dApp now exists on-chain. It has no server to go down, no hosting bill to expire, no app store review to pass. It is code on a decentralized state machine, and it will remain there as long as the network exists.
User Installation
Any GRIDNET OS user can install your dApp:
- Navigate to your State-Domain (your account’s file hierarchy) using the File Manager or a direct link.
- Find the
.appfile in your published directory. - Click to install — the PackageManager registers it locally.
- The dApp’s icon appears on the user’s Desktop and in the App Launcher, ready to use.
Key APIs for dApp Development
GRIDNET OS provides a rich set of JavaScript APIs available to every UI dApp at runtime:
- CVMContext — The central singleton for all network communication. Data queries, GridScript execution, BER encoding/decoding, event subscriptions. See the VMContext Documentation and Blockchain Explorer API Documentation.
- CTools — Utility functions for formatting, conversion, and common operations.
- CSettingsManager — Persistent user preferences and dApp configuration storage.
- CContentHandler — MIME-type handling, file association, and content rendering.
- CNetMsg / WebRTC Swarms — Peer-to-peer messaging and real-time communication between dApp instances across the network.
- DFS (Decentralized File System) — File storage, retrieval, and management on the decentralized network.
- GridScriptCompiler.js — In-browser GridScript compilation for dApps that need to formulate and submit transactions programmatically.
Further Reading
- Hello World UI dApp Tutorial — Step-by-step guide from blank file to running dApp
- Decentralized OS With UI dApps — Architecture overview and design guidelines
- VMContext Documentation — Complete CVMContext API reference
- Blockchain Explorer API Documentation — Query API for blocks, transactions, domains
- GridScript++ Language — Higher-level syntax for complex dApp logic
IX. GridScript++ — The Extended Language
GridScript++ extends GridScript with higher-level constructs. It can be invoked from within GridScript using the evalGPP command:
BT cd /YourDomain evalGPP 'myScript.gpp' CT
GridScript++ provides facilities familiar to developers from languages like JavaScript — including a console.log function for debugging output, cryptographic APIs, and more complex control structures. It represents the evolution of GridScript toward developer accessibility while maintaining the deterministic execution guarantees required by the consensus layer.The relationship between GridScript and GridScript++ mirrors the relationship between assembly language and a high-level language — GridScript provides the raw stack-based instruction set that runs on the VM, while GridScript++ offers a more expressive syntax that compiles down to GridScript operations.
X. The ERG System — Metering Computation in a Trustless World
This is where GridScript most fundamentally departs from its Forth ancestry. In Forth, you type 10 20 + and the processor executes it for free — the only cost is electricity and time. In GridScript, that same 10 20 + consumes 3 ERG: 1 ERG for the literal 10, 1 ERG for the literal 20, 1 ERG for the addition. Every single instruction — every push, every pop, every comparison, every branch — has a price. This is not overhead. It is the mechanism that makes trustless computation possible.
ERG (Execution Resource Gas) is GridScript’s resource metering system, analogous to gas in Ethereum. It prevents infinite loops, denial-of-service attacks, and resource exhaustion in a network where anyone can submit arbitrary code for execution across all nodes.
Every Codeword Has a Price
Each of the 290+ codewords includes a base ERG cost in its declaration. This is the minimum cost — many operations accrue additional costs based on data size or computational complexity.
| Category | Operations | ERG Cost |
|---|---|---|
| Arithmetic | +, -, *, /, mod |
1 ERG base |
| Stack ops | dup, drop, swap, over, rot |
1 ERG base |
| Comparison/Logic | <, =, and, or, xor |
1 ERG base |
| State reads | getVar, getVarEx, cat, balance |
1 ERG base + dynamic data-size cost |
| State writes | setVarEx, write |
1 ERG base + dynamic data-size cost |
| Key generation | keygen, keygenS |
10 ERG |
| Signing | sign, versig |
80 ERG |
| Signature check | checkSig |
100 ERG |
| Encryption | enc, dec, encAuth, encAuthS, encS |
60–90 ERG + data size (enc, dec, encAuth: 60; encAuthS: 70; encS: 90) |
| Memory allocation | alloc |
1 ERG per byte (ERG_COST_PER_RAM_BYTE) |
| String operations | concat, type |
Proportional to string length |
| Hashing | ultimium |
30 ERG |
| Data space allocation | allot |
100 ERG base (high cost) |
Base costs shown. Some operations add dynamic costs proportional to data size.
The total ERG cost of any operation is: Total = baseERGCost + (memoryBytes × ERG_COST_PER_RAM_BYTE) + computationERG + overheadERG
Bid vs Limit — The Two ERG Parameters
Every transaction requires two ERG parameters, stored in dedicated registers:
- ERG Bid (
REG_KERNEL_ERG_BID, register 46) — The price per ERG unit you are willing to pay. This affects transaction priority: higher bids get processed first when the network is congested. Think of it as the gas price. - ERG Limit (
REG_KERNEL_ERG_LIMIT, register 47) — The maximum total ERG the transaction may consume. If execution exceeds this limit, the transaction fails and all state changes are rolled back. Think of it as the gas limit.
\ Set ERG parameters before formulating a transaction: erg -setbid 100 \ Willing to pay 100 per ERG unit (priority) erg -setlimit 50000 \ Maximum 50,000 ERG total for this transaction \ Query current settings: erg -getbid \ Displays current bid erg -getlimit \ Displays current limit \ Increment (useful for retry with higher values): erg -incbid 10 \ Increase bid by 10 erg -inclimit 5000 \ Increase limit by 5000 \ Query sandbox execution stats (read-only): erg -getsandbox \ Shows ERG consumed, ERG limit, ERG left, overhead estimate \ Full comprehensive report: erg \ Displays everything
The ERG Report
Calling erg with no parameters displays a comprehensive status report:
╔═══════════════════════════════════════╗ ║ ERG STATUS REPORT ║ ╠═══════════════════════════════════════╣ ║ Kernel ERG Bid: 100 ║ ║ Kernel ERG Limit: 50000 ║ ╠═══════════════════════════════════════╣ ║ Sandbox ERG Used: 1234 ║ ║ Sandbox ERG Limit: 100000 ║ ║ Sandbox ERG Left: 98766 ║ ║ TX Overhead Est: 500 ║ ╚═══════════════════════════════════════╝
The Kernel parameters (Bid/Limit) are what you set — they control the transaction when it executes on-chain during consensus. The Sandbox parameters are the current ephemeral execution context — they show how much ERG your interactive session has consumed so far.
ERG in the Transaction Lifecycle
Understanding when and how ERG is consumed requires tracing the full transaction lifecycle:
- Set Bid and Limit —
erg -setbid 100anderg -setlimit 50000. These values are stored in registers 46 and 47. If not set, the VM will prompt interactively (inconvenient for automation). - BT — Begin Thread — Creates a sandbox sub-thread. ERG accounting begins for the thread.
- Accumulate commands — Each command entered is recorded into the thread’s code buffer. The sandbox tracks estimated ERG consumption as commands execute locally for preview.
- CT — Commit Threads — The VM collects code from all ready threads, calculates total ERG:
Total = Σ(thread ERG consumed + thread transaction overhead). Verifies total ≤ ERG Limit. Compiles to bytecode, signs, submits. - Network propagation — Transaction propagates to all nodes. The ERG Bid and Limit are included in the transaction header.
- Kernel execution — On every node, the bytecode executes in kernel mode. Every instruction deducts ERG from the budget. The VM checks the remaining ERG budget between instructions in the execution loop.
- If ERG exhausted —
RUNTIME_ERROR("out of ERG by N"). Transaction fails. All state changes are rolled back. The ERG is still consumed (you pay for failed computation). - If successful — State changes committed to the Merkle Patricia Trie. Actual ERG consumed is recorded. Unused ERG is not charged.
How ERG Enforcement Works
Every instruction has an ERG cost. The VM checks the remaining ERG budget before each instruction executes. If your transaction runs out of ERG, it fails immediately and all state changes are rolled back. You pay for the computation consumed up to the failure point, but the blockchain state remains unchanged.
Use erg -getsandbox and ergusage during development to estimate costs before committing transactions. This lets you set an appropriate ERG Limit and avoid failed transactions.
The ERG Excuse Mechanism
REG_EXCUSE_ERG_USAGE (register 45) is a special flag that bypasses all ERG checks when set. It exists for one specific purpose: when a node is replaying blocks that have already been validated by the network (e.g., during synchronization or checkpoint recovery), the code is known to have succeeded previously. Re-checking ERG would be wasteful and could fail if limits have changed. When this register is set, all ERG checks pass through without verifying.
This register is only set by trusted internal code during checkpoint verification. It is never exposed to user-mode GridScript.
Thread ERG Synchronization
When erg is called from a sub-thread and sets values higher than the main thread’s values, it automatically updates the main thread. This ensures consistency across the thread hierarchy during transaction commitment — you don’t need to worry about setting ERG separately for each thread.
ERG Commands Reference
erg( -- )— Full ERG management interface. Terminal only. Supports subcommands:-setbid,-setlimit,-getbid,-getlimit,-getsandbox,-incbid,-inclimit,-helpergusage( -- )— Display current ERG consumption. Terminal-only. To monitor ERG in smart contracts, estimate costs against known limits before operations.ergs( -- )— Display ERG statistics.
Best Practices
- Estimate before committing — Use
erg -getsandboxto check consumption during development. The sandbox ERG reflects what your transaction will cost on-chain. - Set adequate limits — Too low and the transaction fails (wasting your effort). Too high wastes nothing (unused ERG is not charged), but set reasonable bounds.
- Minimize memory allocations — Each byte costs 1 ERG. A 1 MB allocation costs 1,048,576 ERG. Allocate only what you need.
- Cache state reads — Every
getVarcall costs 1 ERG base plus dynamic data-size cost. If you need the same variable twice, read it once anddupthe result. - Watch loops — A
DO...LOOPiterating 10,000 times with a 3-ERG body costs 30,000 ERG. ERG exhaustion inside a loop is a common cause of transaction failure.
Common Pitfalls
- ERG too low — Transaction fails at consensus. All work is lost. State is rolled back. Always test with
erg -getsandboxfirst. - Forgetting transaction overhead — transaction overhead adds to the total. Serialization and signature verification have real costs.
- Not setting bid/limit before CT — The VM will prompt interactively, which breaks automated workflows. Always set explicitly.
- Memory-intensive operations — Allocating a large buffer, performing cryptographic operations on it, then discarding it — the ERG cost is the sum of allocation + computation. Plan accordingly.
- Nested contract calls — Each
callExenters a new execution scope but shares the same ERG budget. Deep call chains can exhaust ERG unexpectedly.
XI. The Power of Reproducible, Verifiable Code
There is a profound idea at the heart of GridScript that transcends the technical details of stack operations and opcode encodings.Every action a user takes in GRIDNET OS — sending value, deploying an identity, creating a file, calling a smart contract — is formulated as GridScript. This GridScript is compiled to bytecode. That bytecode is signed, committed to the blockchain, and executed identically on every node in the network. The state changes are recorded in a Merkle Patricia Trie whose root hash is included in the block header.This means that every user action becomes reproducible, committable, and verifiable code. A value transfer is not a database entry updated by a trusted server — it is a program that executes and produces deterministic results. An identity registration is not a form submission to an authority — it is a cryptographically signed instruction sequence that any node can re-execute and verify.The GridScript VM has 48 registers, three stacks, 256 levels deep each, and a security model with six privilege levels. It compiles to a bytecode format where the opcode order is immutable — new commands are only ever appended, never inserted or reordered, preserving backward compatibility with every contract ever deployed. The codeword registry has grown to over 290 entries across the VM’s lifetime, and not one has ever been moved.This is what it means to build a language for a decentralized state machine. Not a language bolted onto a blockchain as an afterthought, but a language that is the blockchain’s native expression — from the stack operations inherited from Forth, through the transaction formulation threads that make state changes atomic, to the SOA architecture that turns every browser tab into a window onto a decentralized world.GridScript is the language of GRIDNET OS. And GRIDNET OS is, by construction, a machine that cannot lie about what it has done — because everything it has done is written in GridScript, compiled to bytecode, and verified by every node in the network.
GridScript and GRIDNET OS are created by Rafał Skowroński. Development has been conducted with full transparency since 2017 — nearly every line of code conceived, implemented, debugged, and validated on YouTube LIVE. For more information, visit gridnet.org or join the community at talk.gridnet.org.


