﻿{"id":835564,"date":"2026-02-18T21:34:48","date_gmt":"2026-02-18T21:34:48","guid":{"rendered":"https:\/\/gridnet.org\/wpp\/?p=835564"},"modified":"2026-02-20T07:57:43","modified_gmt":"2026-02-20T07:57:43","slug":"from-sandbox-to-production-packaging-deployment-and-on-chain-publishing","status":"publish","type":"post","link":"https:\/\/mag.gridnet.org\/index.php\/2026\/02\/18\/from-sandbox-to-production-packaging-deployment-and-on-chain-publishing\/","title":{"rendered":"From Sandbox to Production \u2014 Packaging, Deployment, and On-Chain Publishing"},"content":{"rendered":"<p><!--  Article 6: From Sandbox to Production \u2014 Packaging, Deployment, and On-Chain Publishing  GRIDNET OS UI dApp Developer Series--><\/p>\n<h2>Introduction \u2014 The Deployment Journey<\/h2>\n<p>Every civilisation that ever mastered fire eventually learned to contain it. The raw, untamed energy of combustion became the controlled flame of a forge, then the precise heat of a kiln, and finally the regulated burn of an engine. Software follows an identical arc. The code you have been writing throughout this series \u2014 the HTML scaffolding, the GridScript logic, the event-driven dance between your dApp and the decentralised virtual machine \u2014 all of that has been fire in the wild. Powerful, yes. Potentially destructive if loosed onto a production blockchain without the ceramic vessel of a proper deployment pipeline.<\/p>\n<p>This article is that vessel. We shall walk, line by line through the actual source code, from the moment your dApp exists as editable files in a local sandbox to the moment it becomes an immutable, cryptographically signed, consensus-validated entity on the GRIDNET OS Decentralised File System (DFS). Along the way you will encounter the GridScript compilation pipeline, the V2 bytecode format with its keyword hash chain verification, the commit lifecycle with its retry logic and authentication challenges, and the on-chain metadata system that makes your application discoverable to every node in the network.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/sandbox-to-production-deployment-pipeline.svg\" alt=\"The GRIDNET OS Deployment Pipeline: From Development to Decentralized Production\" style=\"width:100%; max-width:900px; margin: 2em auto; display:block;\"><\/p>\n<p>If you have followed Articles 1 through 5, you already know how to extend <code>CWindow<\/code>, register event listeners on <code>CVMContext<\/code>, manipulate the DFS through <code>CFileSystem<\/code>, and handle consensus tasks. What you have not yet done is ship. This article bridges that gap.<\/p>\n<h2>The dApp Architecture \u2014 From Window to Blockchain<\/h2>\n<p>Before we package anything, let us crystallise what a GRIDNET OS dApp actually <em>is<\/em> at the architectural level. Every UI dApp in the system follows a consistent pattern, visible across <code>FileManager.js<\/code>, <code>Terminal.js<\/code>, and every application in the <code>dApps\/<\/code> directory.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/sandbox-to-production-app-architecture.svg\" alt=\"dApp Architecture: From Window to Blockchain\" style=\"width:100%; max-width:700px; margin: 2em auto; display:block;\"><\/p>\n<p>A dApp is a JavaScript ES module that exports a class extending <code>CWindow<\/code>. Every dApp class must implement two static methods that serve as its identity:<\/p>\n<pre>\nstatic getPackageID() {\n    return \"org.gridnetproject.UIdApps.fileManager\";\n}\n\nstatic getIcon() {\n    \/\/ Returns the path to the app's icon\n}\n<\/pre>\n<p>The <code>getPackageID()<\/code> method returns a reverse-domain-notation string that uniquely identifies the application throughout the entire GRIDNET OS ecosystem. This is not merely a label \u2014 it is the key by which the <code>CPackageManager<\/code> registers, discovers, and instantiates your application. The Terminal dApp uses <code>\"org.gridnetproject.UIdApps.terminal\"<\/code>; the File Manager uses <code>\"org.gridnetproject.UIdApps.fileManager\"<\/code>. Your third-party dApp should follow the same convention with your own domain.<\/p>\n<p>Inside the constructor, every dApp calls <code>super()<\/code> with its window geometry, HTML body string, title, icon, and configuration flags. It then registers for the VM events it needs:<\/p>\n<pre>\nconstructor(positionX, positionY, width, height) {\n    super(positionX, positionY, width, height, bodyHTML, \"My App\", MyApp.getIcon(), false);\n\n    \/\/ Register for decentralized VM events\n    CVMContext.getInstance().addVMMetaDataListener(\n        this.newVMMetaDataCallback.bind(this), this.mID);\n    CVMContext.getInstance().addNewDFSMsgListener(\n        this.newDFSMsgCallback.bind(this), this.mID);\n    CVMContext.getInstance().addVMStateChangedListener(\n        this.VMStateChangedCallback.bind(this), this.mID);\n}\n<\/pre>\n<p>The second argument to each <code>add*Listener()<\/code> call is the window&#8217;s <code>mID<\/code> \u2014 the application ID. This is critical: when your dApp closes, <code>CVMContext.unregisterEventListenersByAppID(appID)<\/code> iterates through every notification queue in <code>mNotificationListeners<\/code> and removes all callbacks associated with that ID, preventing memory leaks and phantom event handling.<\/p>\n<h2>The Development Workflow \u2014 Sandbox Testing and Local Iteration<\/h2>\n<p>The development workflow in GRIDNET OS is deceptively simple on the surface but architecturally profound beneath. Your dApp runs inside a browser connected to a full node via WebSocket. The <code>CVMContext<\/code> singleton manages that connection, tracking its state through a finite state machine: <code>disconnected \u2192 connecting \u2192 connected<\/code>.<\/p>\n<p>During development, your iteration loop looks like this:<\/p>\n<ol>\n<li><strong>Edit your dApp&#8217;s JavaScript module<\/strong> \u2014 the class extending <code>CWindow<\/code>, its HTML body template, its event handlers.<\/li>\n<li><strong>Load it through the Package Manager<\/strong> \u2014 the <code>CPackageManager<\/code> (instantiated as <code>this.mPackageManager<\/code> in the <code>CVMContext<\/code> constructor) discovers and registers available applications during <code>initialize()<\/code>.<\/li>\n<li><strong>Test against the live DFS<\/strong> \u2014 your dApp communicates with the decentralised file system through <code>CFileSystem<\/code> methods like <code>doCD()<\/code>, <code>doLS()<\/code>, <code>doSync()<\/code>. Each returns a request object whose <code>getReqID<\/code> you track via <code>window.addNetworkRequestID()<\/code>.<\/li>\n<li><strong>Execute GridScript commands<\/strong> \u2014 use <code>CVMContext.getInstance().processGridScript(cmd, threadID, processHandle, mode, reqID)<\/code> where <code>threadID<\/code> defaults to <code>new ArrayBuffer()<\/code>, <code>mode<\/code> defaults to <code>eVMMetaCodeExecutionMode.RAW<\/code>, and <code>reqID<\/code> defaults to <code>0<\/code>. For Promise-based dispatch with timeout support, use <code>processGridScriptA()<\/code>.<\/li>\n<li><strong>Observe results<\/strong> \u2014 your registered listeners fire when the full node responds. <code>newVMMetaDataCallback<\/code> handles VM metadata; <code>newDFSMsgCallback<\/code> handles file system responses; <code>newGridScriptResultCallback<\/code> handles GridScript execution results.<\/li>\n<\/ol>\n<p>The Terminal dApp (<code>Terminal.js<\/code>) is your primary debugging tool during development. It provides a direct xterm.js-based interface to the GridScript interpreter running on the full node. Through it, you can execute raw GridScript, inspect the DFS, check thread states, and verify that your dApp&#8217;s operations produce the expected on-chain effects.<\/p>\n<p>A critical detail for sandbox testing: the <code>CVMContext<\/code> tracks a <code>mCommitLockTime<\/code> of 60 seconds \u2014 the duration for which a pre-lock on the commit operandi is held before the actual lock is acquired. During development, if a commit gets stuck, call <code>syncVMKF(true, processHandle)<\/code> to break the lock, send an abort transaction (<code>\"at\"<\/code>) to the full node, and re-synchronise:<\/p>\n<pre>\n\/\/ Break a stuck commit and resynchronize\nCVMContext.getInstance().syncVMKF(true, myProcessHandle, true);\n<\/pre>\n<p>The third argument (<code>scopeSystemThreadP<\/code>) ensures the system thread&#8217;s scope is reset after the break \u2014 essential for preventing scope pollution between test runs.<\/p>\n<h2>The GridScript Compilation Pipeline<\/h2>\n<p>When your dApp is ready to deploy, its GridScript logic must be compiled into bytecode that the GRIDNET Core virtual machine can execute deterministically across every node in the network. This compilation is performed by <code>GridScriptCompiler.js<\/code> \u2014 a JavaScript implementation that produces bytecode binary-compatible with the C++ <code>GridScriptCompiler<\/code> in GRIDNET Core&#8217;s <code>scriptengine.cpp<\/code>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/sandbox-to-production-bytecode-format-1.svg\" alt=\"V2 Bytecode Format\" style=\"width:100%; max-width:800px; margin: 2em auto; display:block;\"><\/p>\n<h3>The Codeword Table<\/h3>\n<p>At the heart of the compiler lies the codeword table \u2014 an ordered array of every operation the GridScript VM understands. The table&#8217;s order is <em>sacred<\/em>: it must exactly match the C++ <code>codeWords[]<\/code> array in <code>scriptengine.cpp<\/code>, because opcode IDs are assigned sequentially by position. The base opcode ID is <strong>9<\/strong> (IDs 0\u20135 are reserved for special bytecode types: unsigned literals, signed literals, doubles, user opcodes, and string literals; IDs 6\u20138 are immediate words).<\/p>\n<p>Each codeword definition carries five properties:<\/p>\n<pre>\n\/\/ [name, allowedInKernelMode, inlineParams, hasBase58, hasBase64]\n['data64', true, 1, false, true],   \/\/ opcode 9\n['adata64', true, 1, false, true],  \/\/ opcode 10\n['data', true, 1, false, false],    \/\/ opcode 11\n['assert', true, 0, false, false],  \/\/ opcode 12\n\/\/ ... hundreds more in exact C++ order\n<\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/sandbox-to-production-codeword-table.svg\" alt=\"Selected Codeword Opcode Table\" style=\"width:100%; max-width:760px; margin: 2em auto; display:block;\"><\/p>\n<p>The distinction between kernel-mode and non-kernel codewords is paramount for deployment. Only codewords marked <code>allowedInKernelMode = true<\/code> can appear in on-chain transactions. Operations like <code>keygen<\/code> (opcode 110), <code>shutdown<\/code> (opcode 130), or <code>commit<\/code> (opcode 166) are explicitly non-kernel \u2014 they cannot be compiled into deployable bytecode. The compiler enforces this: only kernel-mode codewords are added to the compilation map, and lookups are case-insensitive (matching the C++ implementation).<\/p>\n<h3>Opcode Encoding<\/h3>\n<p>The encoding scheme is compact and deterministic. For opcode IDs \u2264 127, a single byte suffices. For IDs > 127 (which includes many important codewords like <code>echo<\/code> at 157, <code>send<\/code> at 195, or <code>setMeta<\/code> at 257), the compiler uses a two-byte extended encoding:<\/p>\n<pre>\nsetIDBits(id) {\n    if (id > 127) {\n        this.mCompilingExtendedID = true;\n        this.mCurrentOpCode = new Array(2).fill(0);\n        \/\/ High byte with bit 7 set, low byte plain\n        this.mCurrentOpCode[0] = (id >> 8) | 0x80;\n        this.mCurrentOpCode[1] = id & 0xFF;\n    } else {\n        this.mCompilingExtendedID = false;\n        this.mCurrentOpCode = [id & 0xFF];\n    }\n}\n<\/pre>\n<p>Content (inline parameters) follows a similar pattern. Lengths \u2264 127 encode in a single byte. Longer content uses an extended encoding where the first byte carries the number of length bytes with bit 7 set, followed by the actual length in little-endian order:<\/p>\n<pre>\nsetContentBits(content) {\n    const length = content.length;\n    if (length <= 127) {\n        this.mCurrentOpCode.push(length);\n    } else {\n        const numLengthBytes = this.getSignificantBytes(length);\n        this.mCurrentOpCode.push(numLengthBytes | 0x80);\n        for (let i = 0; i < numLengthBytes; i++) {\n            this.mCurrentOpCode.push((length >> (i * 8)) & 0xFF);\n        }\n    }\n    this.mCurrentOpCode.push(...content);\n}\n<\/pre>\n<h3>The V2 Keyword Hash Chain<\/h3>\n<p>Version 2 bytecode introduces a cryptographic integrity mechanism: the keyword image hash chain. Before compilation begins, the compiler initialises a hash seed:<\/p>\n<pre>\nH\u2080 = SHA-256(\"GRIDSCRIPT_V2_KEYWORD_IMAGE\")\n<\/pre>\n<p>As each codeword is compiled, the hash chain evolves:<\/p>\n<pre>\nH\u2099 = SHA-256(H\u2099\u208b\u2081 \u2225 keyword_name)\n<\/pre>\n<p>The <code>keyword_name<\/code> preserves the original case from the codeword table (e.g., <code>\"regPool\"<\/code> not <code>\"regpool\"<\/code>), matching the C++ implementation where <code>activeDefinition->name<\/code> retains its declared casing. The final hash is embedded in the V2 bytecode header:<\/p>\n<pre>\nV2 Bytecode: [VERSION_BYTE][32-BYTE HASH][OPCODE STREAM]\n<\/pre>\n<p>The version byte is computed as <code>192 + (version % 64)<\/code>, yielding 194 for V2. Upon decompilation or execution, the receiving node reconstructs the hash chain from the opcodes it encounters and compares it against the embedded hash. A mismatch means the bytecode was compiled with a different keyword set \u2014 a tamper detection mechanism that ensures bytecode integrity across the entire network.<\/p>\n<h3>V2 Enhancements: Empty Inline Arguments and Flag Support<\/h3>\n<p>V2 bytecode introduces two significant enhancements over V1. First, codewords with inline parameters can now be compiled with missing or empty arguments \u2014 the compiler stores a zero-length content field, and the decompiler outputs an empty string. V1 bytecode would fail on missing inline arguments; V2 handles them gracefully.<\/p>\n<p>Second, V2 adds flag support for inline parameters. Tokens matching the pattern <code>-flag<\/code> or <code>+flag<\/code> (e.g., <code>-t<\/code>) are recognised as flag prefixes. The compiler consumes both the flag and its following value token, prepending the flag text to the binary parameter data. Multiple flag-value pairs can be collected into a single inline parameter, matching the C++ runtime&#8217;s parsing behaviour.<\/p>\n<h3>Compiling Your dApp<\/h3>\n<p>To compile GridScript source code:<\/p>\n<pre>\nimport { GridScriptCompiler } from '\/lib\/GridScriptCompiler.js';\n\nconst compiler = new GridScriptCompiler();\nconst result = await compiler.compile('echo \"Hello, GRIDNET!\" cr');\n\nif (result.success) {\n    \/\/ result.bytecode is a Uint8Array of V2 bytecode\n    const base64 = await GridScriptCompiler.base64CheckEncode(result.bytecode);\n    console.log('Compiled bytecode (base64Check):', base64);\n} else {\n    console.error('Compilation errors:', result.errors);\n}\n<\/pre>\n<p>The <code>compile()<\/code> method is asynchronous because V2 hash chain computation requires SHA-256 operations, which use the Web Crypto API in browsers and the <code>crypto<\/code> module in Node.js \u2014 the compiler is fully environment-agnostic.<\/p>\n<h2>Committing to DFS \u2014 The Commit Lifecycle<\/h2>\n<p>Compilation produces bytecode. But bytecode alone is inert \u2014 a blueprint without a building site. To make your dApp real, you must commit it to the Decentralised File System. This is where GRIDNET OS&#8217;s architecture diverges most dramatically from conventional deployment: there is no server to <code>scp<\/code> files to, no container to push, no CDN to invalidate. Instead, you are writing directly into a consensus-validated, cryptographically authenticated, globally replicated state machine.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/gridnet.org\/wpp\/wp-content\/uploads\/2026\/02\/sandbox-to-production-commit-lifecycle.svg\" alt=\"The Commit Lifecycle\" style=\"width:100%; max-width:800px; margin: 2em auto; display:block;\"><\/p>\n<h3>The Commit Lock<\/h3>\n<p>The commit process begins with a lock. Only one application may commit at a time \u2014 the <code>CVMContext<\/code> enforces this through <code>tryLockCommit(appHandle)<\/code>. The lock is held for <code>mCommitLockTime<\/code> seconds (default: 60) and is identified by the requesting process&#8217;s ID:<\/p>\n<pre>\n\/\/ Inside CVMContext.commit()\nif (!this.tryLockCommit(appHandle, breakLock)) {\n    return false; \/\/ Lock held by another process\n}\nif (appHandle.getProcessID !== this.mCommitLockTakenBy) {\n    return false; \/\/ Lock taken by someone else\n}\n<\/pre>\n<h3>Thread Code Aggregation<\/h3>\n<p>When a commit is initiated, the full node aggregates code from all committable threads. The C++ method <code>getCodeFromAllThreads()<\/code> in <code>scriptengine.cpp<\/code> iterates through the system thread and all its child threads, collecting their pending code lines. Each thread carries VM flags that determine its behaviour:<\/p>\n<ul>\n<li><code>isThread<\/code> \u2014 distinguishes sub-threads from the system thread<\/li>\n<li><code>isPrivateThread<\/code> \u2014 marks threads whose code should not be publicly visible<\/li>\n<li><code>isNonCommittable<\/code> \u2014 excludes threads from the commit aggregation<\/li>\n<li><code>isDataThread<\/code> \u2014 marks data-only threads<\/li>\n<li><code>isUIAttached<\/code> \u2014 indicates whether a UI is attached to the thread<\/li>\n<\/ul>\n<p>The aggregated code includes an ERG (Energy Resource Gas) estimation for each thread, computed as <code>getERGUsed() + getFinalTransactionOverheadEstimation() + 1<\/code>. This estimate determines the computational cost of the transaction on the network.<\/p>\n<h3>The DFS Commit Dispatch<\/h3>\n<p>The actual commit is dispatched through <code>gFileSystem.doCommit(breakLock, threadID)<\/code>. This sends a DFS datagram to the full node, which then orchestrates the multi-step commit process:<\/p>\n<ol>\n<li><strong>Receive commit request<\/strong> \u2014 the full node acknowledges the DFS datagram<\/li>\n<li><strong>Aggregate thread code<\/strong> \u2014 all committable threads&#8217; code is collected<\/li>\n<li><strong>Request authentication<\/strong> \u2014 the node sends a QR intent or keychain challenge back to the web UI<\/li>\n<li><strong>Sign the transaction<\/strong> \u2014 the user authenticates via QR code scan (mobile app) or local keychain decryption<\/li>\n<li><strong>Build and broadcast<\/strong> \u2014 the signed transaction is broadcast to the network for consensus<\/li>\n<\/ol>\n<h3>Authentication: QR Code vs. Local Keychain<\/h3>\n<p>The authentication step deserves special attention. When the full node sends an authentication request (<code>eDataRequestType.QRIntentAuth<\/code>), the <code>CVMContext<\/code>&#8216;s <code>processVMMetaDataRequest()<\/code> method first attempts local keychain signing through the <code>CKeyChainManager<\/code>:<\/p>\n<pre>\nconst handledLocally = await this.mKeyChainManager\n    .signAuthenticationRequest(targetProcessHandle, request, qr);\n\nif (handledLocally === true) {\n    \/\/ Keychain manager successfully signed AND received confirmation\n    return true;\n} else {\n    \/\/ Fall back to QR code display\n    this.addGUITask({\n        id: request.id,\n        type: eUITTaskType.request,\n        dataType: request.dataType,\n        data: qr \/\/ QR code object for mobile app scanning\n    });\n}\n<\/pre>\n<p>Local keychain signing is the faster path: the <code>CKeyChainManager<\/code> unlocks the user&#8217;s stored keychain, signs the challenge data, and sends the response directly \u2014 no mobile app required. The QR code path is the fallback, displaying a scannable intent that the GRIDNET mobile token app processes.<\/p>\n<h3>Commit Monitoring and Retry Logic<\/h3>\n<p>After dispatching the commit, the <code>CVMContext<\/code> enters a monitoring phase through <code>scheduleCommitMonitoring(threadID)<\/code>. This is a sophisticated retry mechanism that handles the inherent unreliability of network communication:<\/p>\n<pre>\n\/\/ Retry configuration\nconst commitCheckInterval = 3000;  \/\/ Check after 3 seconds\nconst maxRetries = 3;\nconst retryDelays = [5000, 10000, 15000]; \/\/ Gradual backoff\n<\/pre>\n<p>If the <code>commitPending<\/code> signal is not received within 3 seconds, the monitor retries the commit with <code>breakCommit=true<\/code>, allowing the DFS datagram to be resent. After three failed retries (with 5s, 10s, 15s backoff), the system:<\/p>\n<ol>\n<li>Sends an abort notification to the full node (stopping any pending <code>askInt()<\/code>\/<code>askString()<\/code>\/<code>askBytes()<\/code> waits)<\/li>\n<li>Breaks the commit lock to allow recovery<\/li>\n<li>Sets the Magic Button to error state, notifying the user<\/li>\n<\/ol>\n<p>The commit state machine tracks progress through <code>eCommitState<\/code>: <code>none \u2192 prePending \u2192 pending \u2192 success<\/code> (or <code>aborted<\/code>). Notably, <code>success<\/code> and <code>aborted<\/code> are transient states \u2014 they trigger notifications but immediately transition back to <code>none<\/code>.<\/p>\n<h3>Connection Loss During Commit<\/h3>\n<p>If the WebSocket connection drops during a commit (the <code>onClose<\/code> handler fires), the system performs emergency cleanup:<\/p>\n<pre>\nonClose(evt) {\n    if (this.mCommitLockTaken || this.getIsCommiting) {\n        this.sendCommitAbortNotification();\n        this.breakCommitLock();\n    }\n    this.setConnectionState = eConnectionState.disconnected;\n}\n<\/pre>\n<p>This ensures that a dropped connection never leaves the system in a permanently locked state \u2014 a critical safety mechanism for production deployments.<\/p>\n<h2>On-Chain Publishing \u2014 Making Your App Discoverable<\/h2>\n<p>Once your commit succeeds, your dApp exists on-chain. But existence is not the same as discoverability. To make your application findable by other users and by the system&#8217;s Package Manager, you need to work with the DFS metadata system.<\/p>\n<p>The GridScript <code>setMeta<\/code> codeword (opcode 257) writes metadata to DFS entries. Its counterpart <code>getMeta<\/code> (opcode 259) reads them. Both accept Base58-encoded parameters when the <code>hasBase58<\/code> flag is set, allowing you to reference addresses and identifiers in human-readable form.<\/p>\n<p>The DFS supports access control through <code>setfacl<\/code> (opcode 223) and ownership through <code>chown<\/code> (opcode 225) \u2014 both kernel-mode codewords that can appear in on-chain transactions. You can set permissions on your application&#8217;s directory to control who can modify it while keeping it readable by all.<\/p>\n<p>For app registration, the <code>regPool<\/code> codeword (opcode 301) registers a token pool with Base64-encoded parameters. This mechanism extends to application registration: your dApp&#8217;s metadata includes its package ID, version, icon path, and entry point \u2014 everything the Package Manager needs to instantiate it.<\/p>\n<h2>Versioning and Updates<\/h2>\n<p>Updating a deployed dApp follows the same commit pipeline, but with an important consideration: the DFS is append-only at the consensus level. Each commit creates a new data block containing your updated code. The <code>update<\/code> codeword (opcode 256), <code>update1<\/code> (opcode 268), and <code>update2<\/code> (opcode 269) provide mechanisms for signalling state transitions within the VM.<\/p>\n<p>The <code>flag<\/code> codeword (opcode 260) and its dynamic variant <code>flagEx<\/code> (opcode 261) allow you to set flags on DFS entries, which can be used to mark versions, deprecate old releases, or signal feature availability.<\/p>\n<p>For practical versioning:<\/p>\n<ol>\n<li><strong>Maintain a version directory structure<\/strong> in DFS (e.g., <code>\/apps\/myapp\/v1\/<\/code>, <code>\/apps\/myapp\/v2\/<\/code>)<\/li>\n<li><strong>Update metadata<\/strong> to point the &#8220;current&#8221; reference to the latest version<\/li>\n<li><strong>Use <code>touch<\/code><\/strong> (opcode 262) to update timestamps on version directories<\/li>\n<li><strong>Set access controls<\/strong> with <code>setfacl<\/code> to prevent unauthorised modifications<\/li>\n<\/ol>\n<h2>Complete Deployment Walkthrough<\/h2>\n<p>Let us walk through the entire deployment process for a hypothetical dApp, <code>MyWidget<\/code>, from development to production.<\/p>\n<h3>Step 1: Create Your dApp Module<\/h3>\n<pre>\n\"use strict\"\nimport { CWindow } from \"\/lib\/window.js\"\n\nconst widgetBody = `\n  &lt;style&gt;\n    .widget-container { padding: 1em; color: #22fafc; }\n    .widget-btn { background: #1a2744; border: 1px solid #00f0ff;\n                  color: #00f0ff; padding: 0.5em 1em; cursor: pointer; }\n  &lt;\/style&gt;\n  &lt;div class=\"widget-container\"&gt;\n    &lt;h3&gt;My Widget&lt;\/h3&gt;\n    &lt;button class=\"widget-btn commitBtn\"&gt;Deploy&lt;\/button&gt;\n    &lt;div id=\"statusArea\"&gt;&lt;\/div&gt;\n  &lt;\/div&gt;\n`;\n\nclass CMyWidget extends CWindow {\n    static getPackageID() {\n        return \"com.example.UIdApps.myWidget\";\n    }\n\n    static getIcon() {\n        return \"\/images\/widget-icon.png\";\n    }\n\n    constructor(positionX, positionY, width, height) {\n        super(positionX, positionY, width, height,\n              widgetBody, \"My Widget\", CMyWidget.getIcon(), false);\n\n        \/\/ Register for VM events\n        CVMContext.getInstance().addVMStateChangedListener(\n            this.onVMStateChanged.bind(this), this.mID);\n        CVMContext.getInstance().addVMCommitStateChangedListener(\n            this.onCommitStateChanged.bind(this), this.mID);\n\n        \/\/ Bind UI events after DOM renders\n        this.whenReady(() => {\n            this.getElement('.commitBtn').onclick =\n                () => this.handleDeploy();\n        });\n    }\n\n    async handleDeploy() {\n        const ctx = CVMContext.getInstance();\n\n        \/\/ 1. Lock the commit operandi\n        if (!ctx.tryLockCommit(this)) {\n            this.showStatus('Commit lock held by another app');\n            return;\n        }\n\n        \/\/ 2. Execute GridScript to write files to DFS\n        \/\/ processGridScript() sends the command asynchronously \u2014\n        \/\/ results arrive via onGridScriptResult callback.\n        \/\/ For the commit to include these changes, the node\n        \/\/ aggregates all thread operations before finalizing.\n        const reqID = ctx.processGridScript(\n            'mkdir \/apps\/myWidget cd \/apps\/myWidget ' +\n            'data \"widget-config\" write config.json',\n            new ArrayBuffer(), this\n        );\n\n        \/\/ 3. Commit to chain (aggregates pending thread operations)\n        ctx.commit(this, false, 'system');\n    }\n\n    onCommitStateChanged(state) {\n        switch(state) {\n            case eCommitState.pending:\n                this.showStatus('Commit pending \u2014 awaiting consensus...');\n                break;\n            case eCommitState.success:\n                this.showStatus('\u2713 Successfully deployed on-chain!');\n                break;\n            case eCommitState.aborted:\n                this.showStatus('\u2717 Commit aborted');\n                break;\n        }\n    }\n\n    showStatus(msg) {\n        this.getElement('#statusArea').innerHTML = msg;\n    }\n}\n<\/pre>\n<h3>Step 2: Compile GridScript Components<\/h3>\n<pre>\nimport { GridScriptCompiler } from '\/lib\/GridScriptCompiler.js';\n\nconst compiler = new GridScriptCompiler();\n\n\/\/ Compile your on-chain logic\nconst result = await compiler.compile(\n    'cd \/apps\/myWidget ' +\n    'data \"v1.0.0\" write version.txt ' +\n    'setMeta -t myWidgetApp'\n);\n\nif (!result.success) {\n    console.error('Compilation failed:', result.errors);\n    \/\/ Common errors:\n    \/\/ - \"Unknown codeword: xyz\" \u2014 typo or non-kernel codeword\n    \/\/ - \"Missing inline parameter for xyz\" \u2014 V1 mode, missing arg\n    \/\/ - \"Invalid base58 parameter\" \u2014 malformed address\n}\n<\/pre>\n<h3>Step 3: Test in the Terminal<\/h3>\n<p>Open the Terminal dApp and verify your DFS operations work correctly:<\/p>\n<pre>\nmkdir \/apps\/myWidget\ncd \/apps\/myWidget\necho \"Hello from MyWidget\"\nls \/apps\/myWidget\n<\/pre>\n<h3>Step 4: Commit and Authenticate<\/h3>\n<p>Trigger the commit from your dApp. The system will present an authentication challenge \u2014 either handled automatically by the local keychain or via QR code. Upon successful authentication, the transaction is signed, broadcast to the network, and (after consensus) your dApp&#8217;s data is permanently written to the DFS.<\/p>\n<h3>Step 5: Verify Deployment<\/h3>\n<p>After the commit succeeds (your <code>onCommitStateChanged<\/code> listener fires with <code>eCommitState.success<\/code>), synchronise and verify:<\/p>\n<pre>\n\/\/ Synchronize to confirm on-chain state\nCVMContext.getInstance().syncVMKF(false, myProcessHandle);\n\n\/\/ List the deployed files\nCVMContext.getInstance().getFileSystem.doLS(threadID);\n<\/pre>\n<h2>Troubleshooting \u2014 Common Deployment Errors and Fixes<\/h2>\n<h3>&#8220;Unknown codeword&#8221; during compilation<\/h3>\n<p>The codeword you&#8217;re trying to compile either doesn&#8217;t exist in the codeword table, is misspelled, or is a non-kernel codeword that cannot appear in on-chain bytecode. Check the codeword definition in <code>GridScriptCompiler.js<\/code>&#8216;s <code>initializeCodewords()<\/code> method. Remember that lookups are case-insensitive \u2014 <code>\"Echo\"<\/code>, <code>\"echo\"<\/code>, and <code>\"ECHO\"<\/code> all resolve to opcode 157.<\/p>\n<h3>V2 keyword image verification failed<\/h3>\n<p>This error during decompilation means the bytecode was compiled with a different keyword set than the one the decompiler expects. This typically indicates a version mismatch between the compiler that produced the bytecode and the one trying to read it. Ensure your <code>GridScriptCompiler.js<\/code> matches the version deployed on the network. The error message includes the first 16 hex digits of both the computed and extracted hashes to aid debugging.<\/p>\n<h3>Commit lock timeout<\/h3>\n<p>If your commit appears stuck, the most common cause is a failed authentication step or a dropped connection during the commit flow. The automatic retry mechanism (3 attempts with gradual backoff) should recover most transient failures. If it doesn&#8217;t, the system breaks the lock after ~30 seconds and resets. You can also manually break the lock:<\/p>\n<pre>\nCVMContext.getInstance().syncVMKF(true, processHandle);\n<\/pre>\n<h3>&#8220;Network error&#8221; during processGridScript<\/h3>\n<p>This means <code>sendNetMsg()<\/code> failed, typically because the WebSocket is not in the <code>OPEN<\/code> state. Check <code>CVMContext.getInstance().getConnectionState<\/code> \u2014 it should be <code>eConnectionState.connected<\/code>. If not, the context&#8217;s controller thread will automatically attempt reconnection. The connection uses a closest-node heuristic on first attempt, then random selection for subsequent retries.<\/p>\n<h3>ERG exhaustion<\/h3>\n<p>Every GridScript instruction consumes ERG (Energy Resource Gas). The C++ engine checks ERG before each instruction via the <code>CHECK_ERG<\/code> macro. If your transaction&#8217;s ERG usage exceeds the limit, it fails with &#8220;out of ERG.&#8221; This is visible in the commit&#8217;s thread code aggregation, where each thread reports its ERG estimate. To fix: simplify your transaction, split it into multiple smaller commits, or increase the ERG bid through the transfer mechanism.<\/p>\n<h3>Anonymous process calls rejected<\/h3>\n<p>All user-mode <code>CVMContext<\/code> methods require a valid process handle. Calls to <code>processGridScript()<\/code>, <code>commit()<\/code>, or <code>syncVM()<\/code> without a proper handle (an object with <code>getProcessID<\/code> and <code>getPackageID<\/code>) are rejected with &#8220;Anonymous calls are not allowed.&#8221; Ensure your dApp always passes <code>this<\/code> (the <code>CWindow<\/code> instance) as the process handle.<\/p>\n<h2>Conclusion \u2014 The Permanence of Code<\/h2>\n<p>There is a philosophical weight to deploying code onto a blockchain that does not exist in traditional software engineering. When you push to a server, you can always push again. When you deploy a container, you can always replace it. But when you commit to the GRIDNET OS Decentralised File System, you are writing into a structure that is, by design, permanent \u2014 replicated across every full node, validated by consensus, authenticated by cryptographic signature.<\/p>\n<p>This permanence is both a discipline and a liberation. It demands that you test thoroughly in the sandbox, that you understand the compilation pipeline down to the byte level, that you respect the commit lifecycle&#8217;s authentication and retry mechanisms. But it also liberates you from the anxiety of server failures, the fragility of centralised infrastructure, the capriciousness of platform gatekeepers. Your dApp, once committed, exists as long as the network exists.<\/p>\n<p>You began this series by learning to extend a window. You end it by learning to write that window into the permanent record of a decentralised civilisation. The fire has been contained. The forge is ready. Build something worth keeping.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction \u2014 The Deployment Journey Every civilisation that ever mastered fire eventually learned to contain it. The raw, untamed energy of combustion&#8230;<\/p>\n","protected":false},"author":1,"featured_media":835563,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[162,17,163],"tags":[197,174,160,172,142,143,218,219,170,212],"class_list":["post-835564","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development","category-tutorial","category-tutorials","tag-dapps","tag-deployment","tag-developer-guide","tag-dfs","tag-gridnet-os","tag-gridscript","tag-on-chain","tag-publishing","tag-ui-dapps","tag-ui-development"],"_links":{"self":[{"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/posts\/835564","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/comments?post=835564"}],"version-history":[{"count":5,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/posts\/835564\/revisions"}],"predecessor-version":[{"id":835603,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/posts\/835564\/revisions\/835603"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/media\/835563"}],"wp:attachment":[{"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/media?parent=835564"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/categories?post=835564"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mag.gridnet.org\/index.php\/wp-json\/wp\/v2\/tags?post=835564"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}