One Link to Launch Them All: Deep Linking for GRIDNET OS

No Comment Yet

NOW LIVE
GLinks are available in GRIDNET Core 1.9.4+
Deep linking for decentralized applications

One Click to Spawn a Decentralized Operating System

Imagine clicking a single link and watching a complete decentralized operating system materialize in your browser—pre-configured to show exactly the transaction, wallet address, or block you wanted to see. That’s what GLinks deliver: deep linking for decentralized applications, with the power to encapsulate not just navigation state, but the complete context of what you’re trying to accomplish.

This isn’t a simple URL redirect. GLinks bootstrap an entire GRIDNET OS instance, authenticate the user (anonymously or otherwise), launch the target dApp, navigate to the specified view, and execute an action—all from a single encoded URL parameter. It’s the kind of seamless experience we’ve come to expect from native apps, now available for the decentralized web.

Technical Deep Dive • GRIDNET OS Development Team • December 2025

The Challenge: True Decentralization of the User Interface

Most blockchain platforms today—even the most respected ones like AAVE, Uniswap, and countless others—rely on traditional centralized web servers to deliver their user interfaces. The blockchain itself may be decentralized, but the moment you visit their website, you’re trusting a centralized server to serve you the correct, unmodified frontend code. This creates a single point of failure, censorship vulnerability, and trust assumption that undermines the very principles of decentralization.

GRIDNET OS takes a fundamentally different approach. We deliver not just the backend, but the entire user interface—every UI dApp, every component, every asset—from a decentralized network. There is no central server to censor, compromise, or take down.

A Note on the Bootloader: Even in our case, when accessing GRIDNET OS via https://ui.gridnet.org, the initial bootloader itself must be delivered from one of our centralized servers. This stems from the inherently centralized nature of DNS entries—someone has to resolve that domain name. However, as soon as the bootloader executes, it strives to deliver all subsequent components—UI dApps, assets, scripts, and data—from peers discovered through Kademlia DHT. You can observe this in action: open Chrome DevTools and watch as nodes are discovered and reported during the boot sequence. The bootloader is the minimal trust anchor; everything else flows from the decentralized network.

What This Enables:

Multi-tasking & Windowed Applications — Run multiple dApps simultaneously in separate windows

Parallelism of Execution — True concurrent processing across dApps

Cross-Process Communication — dApps can securely communicate with each other

Decentralized File System — Store and retrieve files without central servers

Process Compartmentation — Isolated execution environments for security

Decentralized Processing Threads — Offload computation to the network

Onion-Routed Communication — Privacy-preserving network layer

Cross-Browser Data Exchange — Sync state across browser instances

The Trade-off: All of this power comes with a technical requirement—the entire operating system must load and initialize before any individual UI dApp can launch. The Window Manager, Package Manager, VM Context, WebSocket connections to decentralized nodes, cryptographic authentication—all of this must be ready before your Wallet or Explorer can open. This is fundamentally different from traditional web apps where a single HTTP request returns a ready-to-use page.

This is precisely why GLinks exist. They solve the challenge of deep linking into a system that must bootstrap itself first. GLinks carry the complete intent—which dApp, which view, which action, which data—and the GLink handler orchestrates the entire startup sequence, ensuring everything initializes in the correct order before finally delivering the user to their destination.

In traditional web applications, deep linking is straightforward—you craft a URL with path segments and query parameters, and the server routes you to the right page. But GRIDNET OS isn’t a traditional web application. It’s a complete operating system that runs in your browser, with multiple windowed applications, user sessions, blockchain connections, and cryptographic authentication.

When someone shares a link to view a specific transaction, what actually needs to happen?

  • Bootstrap the OS: Load GRIDNET OS core, initialize the window manager, establish WebSocket connections to decentralized nodes
  • Authenticate: Create an anonymous session or prompt for login—the blockchain doesn’t know “guest mode”
  • Launch the dApp: Find and instantiate the Blockchain Explorer application
  • Navigate to view: Switch to the Transactions tab
  • Execute action: Fetch and display the specific transaction details
  • Report status: Confirm to the user that everything worked (or explain what went wrong)

GLinks encapsulate all of this in a single URL. One click, and the entire orchestration happens automatically.

The Breakthrough

With GLinks, sharing blockchain data becomes as simple as sharing any other link. Post a transaction on social media, embed a payment request in an email, or create a QR code that opens someone’s wallet pre-filled with your address. The decentralized web finally gets the user experience it deserves.

2. Anatomy of a GLink

A GLink is a URL that carries encoded instructions for GRIDNET OS. Let’s dissect its structure:

URL Structure

GLINK URL ANATOMY

https://ui.gridnet.org/
?
glink
=
eyJhcHAiOiJvcmcuZ3JpZG5ldHByb2plY3QuVUlkQXBwcy53YWxsZXQiLCJ2aWV3Ijoic2VuZCIsImRhdGEiOnsiYWRkcmVzcyI6ImFsaWNlIn19

Base URL

https://ui.gridnet.org/

GRIDNET OS entry point
Parameter Name

glink

Query parameter identifier
Encoded Payload

Base64(JSON)

URL-safe Base64 encoded JSON

JSON Data Format

The encoded payload is a JSON object with the following structure:

JSON
GLink Data Structure
All fields except ‘app’ are optional

{
"app": "org.gridnetproject.UIdApps.wallet", // Required: Package ID
"view": "send", // Optional: View/tab to open
"action": 1, // Optional: Action ID to execute
"data": { // Optional: Action parameters
"address": "alice",
"amount": "100"
}
}

Field Type Required Description
app string Yes Package ID of the target UI dApp. Must be a valid installed application.
view string No Specific view, tab, or section within the dApp to navigate to.
action number No Numeric action ID. Each dApp defines its own action constants.
data object No Arbitrary data object passed to the dApp for processing the action.

Known Package IDs

Application Package ID
Wallet org.gridnetproject.UIdApps.wallet
Blockchain Explorer org.gridnetproject.UIdApps.BlockchainExplorer
File Manager org.gridnetproject.UIdApps.fileManager
Editor org.gridnetproject.UIdApps.editor
Image Viewer org.gridnetproject.UIdApps.imageViewer

3. The GLink Lifecycle

Understanding the complete lifecycle of a GLink is essential for both users and developers. From the moment a GLink URL is clicked to the final confirmation, here’s what happens:

GLINK LIFECYCLE FLOW

1
URL Detection
User clicks GLink URL. CGLinkHandler detects ?glink= parameter on page load.

CGLinkHandler.initialize() → CGLink.getFromCurrentURL()

2
Progress Indicator
Visual indicator shows “GLink detected” with target app name.

showIndicator() → updateIndicator('GLink detected', 10, appName)

3
Wait for System Ready
GRIDNET OS initializes: Window Manager, Package Manager, CVMContext.

systemReady(windowManager, packageManager, vmContext)

4
User Authentication
Wait for user logon (anonymous or authenticated session).

userLoggedIn() → processGLink(glinkData)

5
Launch Target dApp
Window Manager launches target app with GLink data as parameters.

windowManager.launchApp(app, true, launchData, 'glink')

6
dApp Processes GLink
dApp’s processGLink() accepts, navigates to view, executes action.

acceptGLink() → switchView() → executeAction()

7
Confirmation / Rejection
dApp reports success/failure. Indicator fades. URL parameter cleared.

confirmGLinkProcessed('Success!') or rejectGLink('Error')

System Operations

Handler Operations

Initialization

Success/Completion

Timeout Protection

The GLink handler implements a 15-second timeout for dApps to respond. If a dApp calls acceptGLink(), the timeout extends to 3 minutes for complex operations. This ensures users aren’t left waiting indefinitely while still giving dApps time for heavy processing.

4. Implementing GLink Support in Your UI dApp

Now that you understand how GLinks work conceptually, let’s build support into your own dApp. The process follows a clear pattern: detect → accept → process → confirm. Each step serves a specific purpose in ensuring a smooth user experience.

The most important thing to understand is why we structure things this way: the CGLinkHandler has strict timeouts (15 seconds initially, extending to 3 minutes after acceptance). If your dApp doesn’t respond in time, the GLink fails and the user sees an error. The pattern below ensures you always respond promptly while still having enough time to do complex operations.

Step 1: Import GLink Classes

You’ll need both classes: CGLink for creating GLinks programmatically (e.g., “Copy GLink to clipboard” buttons), and CGLinkHandler for responding to incoming GLinks:

import {
CGLink,
CGLinkHandler
} from '/lib/GLink.js';

Step 2: Detect and Accept GLink on Initialize

When your dApp starts, the first thing you need to check is: “Was I launched because of a GLink?” This check happens in your initialize() method, and the answer comes from inherited CWindow properties.

Critical: Call acceptGLink() as soon as you detect the GLink—this extends your timeout from 15 seconds to 3 minutes. Don’t wait until you’re ready to process; accept first, then take your time:

async initialize() {
// ... your normal initialization code ...
// Check if launched via GLink
if (this.wasLaunchedViaGLink) {
console.log('[MyApp] Detected launch via GLink');
// Get the GLink data passed to this dApp
const glinkData = this.getGLinkData;
// Accept immediately - this extends the timeout and shows progress
const glinkHandler = CGLinkHandler.getInstance();
glinkHandler.acceptGLink('MyApp initializing...');
// Process after a short delay to allow UI to stabilize
setTimeout(() => {
this.processGLink(glinkData);
}, 2000);
}
}

Key Properties (Inherited from CWindow)

  • this.wasLaunchedViaGLink – Boolean, true if app was launched via GLink
  • this.getGLinkData – Object containing {view, action, data} from the GLink

Step 3: Implement the processGLink Method

The processGLink() method is where the actual work happens. This is your dApp’s contract with the GLink system: you receive structured data (view, action, data) and you’re responsible for making something happen.

The pattern here is important: navigate first, then act. If the GLink specifies a view, switch to it before executing the action. This ensures your action has the right DOM context to work with. Always wrap everything in try/catch and always end with either confirmGLinkProcessed() or rejectGLink()—never leave the handler hanging.

/**
* Process GLink data - handle deep link navigation.
* This method is the heart of your GLink support.
* @param {Object} glinkData - The parsed GLink data {view, action, data}
*/

processGLink(glinkData) {
const glinkHandler = CGLinkHandler.getInstance();
if (!glinkData) {
glinkHandler.rejectGLink('No GLink data provided');
return;
}
// Accept the GLink (in case not already accepted)
glinkHandler.acceptGLink('Processing...');
const { view, action, data } = glinkData;
try {
// Step 1: Navigate to specified view (if any)
if (view) {
this.switchToView(view);
}
// Step 2: Execute action (if any)
if (action !== null && action !== undefined) {
this.executeGLinkAction(action, data);
}
// Step 3: Confirm success
glinkHandler.confirmGLinkProcessed('Action completed successfully');
} catch (error) {
console.error('[MyApp] GLink error:', error);
glinkHandler.rejectGLink(`Error: ${error.message}`);
}
}
/**
* Execute a specific GLink action
*/

executeGLinkAction(actionId, data) {
switch (actionId) {
case 1: // Example: View item details
this.viewItemDetails(data.itemId);
break;
case 2: // Example: Pre-fill form
this.prefillForm(data);
break;
default:
throw new Error(`Unknown action ID: ${actionId}`);
}
}

Step 4: Define Your Action IDs

Each dApp should define its own action constants as static properties:

// Define action constants for your dApp
static get GLinkActions() {
return {
VIEW_ITEM: 1,
PREFILL_FORM: 2,
SEARCH: 3,
NAVIGATE: 4
};
}

GLINK HANDLER METHODS
acceptGLink(message)
Acknowledges receipt of GLink. Extends timeout to 3 minutes.
When: As early as possible after detecting GLink
confirmGLinkProcessed(message)
Signals successful completion. Hides progress indicator with success animation.
When: After action is fully executed
rejectGLink(reason)
Signals failure or rejection. Shows error state before hiding indicator.
When: On error or invalid GLink data

5. Case Study: Wallet UI dApp

The Wallet UI dApp presents an interesting implementation challenge: what happens when a GLink arrives but the wallet is PIN-locked? You can’t just ignore the GLink—the user clicked a payment link for a reason. But you also can’t process financial operations without the user first proving they have access to their wallet.

The Wallet solves this elegantly by accepting the GLink immediately (to extend the timeout and provide feedback), storing it for later, and then processing it once the user unlocks with their PIN. Let’s examine how this works in practice.

Wallet GLink Actions

The Wallet defines five actions, covering the primary user workflows: sending funds, receiving funds, viewing history, and managing keychains and settings.

Action ID Name Data Fields Description
1 PREFILL_SEND address, amount
or recipients[]
Pre-fill send form with recipient(s). Supports both single recipient shorthand and multiple recipients array.
2 SHOW_RECEIVE (none) Open the Receive tab to display the user’s wallet address and QR code.
3 VIEW_HISTORY filter (optional) Open the transaction history view, optionally with a filter applied.
4 OPEN_KEYCHAIN (none) Open the keychain management view.
5 OPEN_SETTINGS (none) Open the wallet settings view.

Multiple Recipients Support

The Wallet’s PREFILL_SEND action supports two data formats: a simple {address, amount} object for single recipients, or a {recipients: [{address, amount}, ...]} array for batch payments. This flexibility lets you create both simple payment links and complex multi-recipient invoices.

Handling PIN-Locked State

Here’s where things get interesting. The CGLinkHandler has a 15-second timeout—if the dApp doesn’t respond, the GLink fails. But what if the user needs to enter their PIN first? That could easily take longer than 15 seconds.

The solution is a two-phase approach: accept immediately to extend the timeout to 3 minutes, then defer processing until the wallet is unlocked. The code stores the GLink data in a member variable and processes it after the PIN unlock callback fires:

// In initialize()
if (this.wasLaunchedViaGLink) {
const glinkHandler = CGLinkHandler.getInstance();
const glinkData = this.getGLinkData;
// Accept immediately to extend timeout
glinkHandler.acceptGLink('Wallet received GLink');
// Store for later processing
this.mPendingGLinkData = glinkData;
// Process immediately if not locked, otherwise wait for unlock
setTimeout(() => {
if (!this.mIsLocked && this.mPendingGLinkData) {
this.processGLink(this.mPendingGLinkData);
this.mPendingGLinkData = null;
}
}, 1000);
}
// In onPINUnlock() handler
if (this.mPendingGLinkData) {
console.log('[Wallet] Processing pending GLink after unlock...');
this.processGLink(this.mPendingGLinkData);
this.mPendingGLinkData = null;
}

Example: Payment Request GLink

// Create a GLink for a payment request
const paymentGLink = CGLink.create(
CGLink.PackageIDs.WALLET, // Target: Wallet dApp
'send', // View: Send tab
1, // Action: PREFILL_SEND
{
address: 'alice.grid', // Recipient address
amount: '100' // Amount in GNC
}
);
// Result: https://ui.gridnet.org/?glink=eyJhcHAiOiJvcmcuZ3JpZG5ldHByb2plY3QuVUlkQXBwcy53YWxsZXQiLCJ2aWV3Ijoic2VuZCIsImFjdGlvbiI6MSwiZGF0YSI6eyJhZGRyZXNzIjoiYWxpY2UuZ3JpZCIsImFtb3VudCI6IjEwMCJ9fQ

6. Case Study: Blockchain Explorer UI dApp

While the Wallet dealt with deferred processing (PIN unlock), the Blockchain Explorer faces a different challenge: asynchronous data loading. When you click a GLink to view a specific transaction, the Explorer needs to fetch that transaction from the blockchain before it can display it.

The Explorer’s approach is simpler than the Wallet’s because it doesn’t have a PIN gate. It accepts the GLink during initialization, waits a brief moment for the UI to stabilize, then processes the action. The key insight here is the use of a 2-second delay after accepting—this gives the Explorer’s data fetching mechanisms time to initialize without blocking the GLink timeout.

Explorer GLink Actions

The Explorer exposes four actions focused on blockchain data exploration:

Action ID Name Data Fields Description
1 VIEW_BLOCK blockId Fetch and display details of a specific block by its ID or height.
2 VIEW_TRANSACTION txId Fetch and display details of a specific transaction by its hash.
3 SEARCH_DOMAIN domain Look up a GRIDNET domain name and display its registration info.
4 SEARCH query Execute a general search query (blocks, transactions, domains, addresses).

Explorer processGLink Implementation

Notice how the Explorer’s implementation is more straightforward—it doesn’t need the deferred processing pattern because there’s no unlock step. It switches to the specified view (if any), then executes the action based on the numeric ID:

processGLink(glinkData) {
const glinkHandler = CGLinkHandler.getInstance();
if (!glinkData) {
glinkHandler.rejectGLink('No GLink data provided');
return;
}
glinkHandler.acceptGLink('Explorer processing...');
const { view, action, data } = glinkData;
try {
// Navigate to view if specified
if (view) {
this.ui.switchToSection(view);
}
// Execute action based on numeric ID
if (action !== null) {
switch (action) {
case 1: // VIEW_BLOCK
this.viewBlockDetails(data.blockId);
break;
case 2: // VIEW_TRANSACTION
this.viewTransactionDetails(data.txId);
break;
case 3: // VIEW_DOMAIN
this.searchDomain(data.domain);
break;
case 4: // SEARCH
this.performSearch(data.query);
break;
default:
glinkHandler.rejectGLink(`Unknown action: ${action}`);
return;
}
glinkHandler.confirmGLinkProcessed('Action completed');
} else if (view) {
glinkHandler.confirmGLinkProcessed('View opened');
} else {
glinkHandler.rejectGLink('No action or view specified');
}
} catch (error) {
glinkHandler.rejectGLink(`Error: ${error.message}`);
}
}

Creating Explorer GLinks

// View a specific transaction
const txGLink = CGLink.create(
CGLink.PackageIDs.EXPLORER,
'transactions',
2, // VIEW_TRANSACTION
{ txId: 'abc123...' }
);
// View a specific block
const blockGLink = CGLink.create(
CGLink.PackageIDs.EXPLORER,
'blocks',
1, // VIEW_BLOCK
{ blockId: '12345' }
);
// Search for a domain
const domainGLink = CGLink.create(
CGLink.PackageIDs.EXPLORER,
'search',
3, // VIEW_DOMAIN
{ domain: 'alice.grid' }
);

So far we’ve talked about receiving and processing GLinks. But what about creating them? When a user wants to share a transaction, copy a payment request link, or generate a QR code for their wallet address, your dApp needs to create GLinks on the fly.

The CGLink class makes this trivial. The create() method handles all the JSON construction and URL-safe Base64 encoding automatically—you just provide the semantic data.

Basic Creation

The method signature is CGLink.create(appPackageID, view?, action?, data?). Only the first parameter is required; everything else is optional:

import { CGLink } from '/lib/GLink.js';
// Simplest GLink - just launch the app (no specific action)
const simpleLink = CGLink.create('org.myapp.MyDApp');
// With view - open app to a specific tab
const viewLink = CGLink.create('org.myapp.MyDApp', 'settings');
// With view and action - open tab and do something
const actionLink = CGLink.create('org.myapp.MyDApp', 'items', 1);
// Complete GLink with data - the most common case
const fullLink = CGLink.create(
'org.myapp.MyDApp',
'items',
1,
{ itemId: 'abc123', highlight: true }
);

Adding a “Copy GLink” Button

A common UX pattern is to let users copy a GLink to their clipboard so they can share it. The CGLink.copyToClipboard() method handles both modern and legacy browser APIs:

// Add a "Share" button to your UI
copyGLinkBtn.addEventListener('click', async () => {
// Generate a GLink for the current state
const glink = CGLink.create(
MyDApp.getPackageID(),
this.currentView,
MyDApp.GLinkActions.VIEW_CURRENT,
{ itemId: this.currentItemId }
);
// Copy to clipboard with fallback for older browsers
const success = await CGLink.copyToClipboard(glink);
if (success) {
this.showNotification('GLink copied! Share it anywhere.');
}
});

8. API Reference

This section provides a complete reference for all GLink-related APIs. Use this as your go-to documentation when implementing GLink support.

CGLink Class

The CGLink class is a static utility for creating and parsing GLinks. You never instantiate it—just call its static methods directly.

Method Parameters Returns Description
create() app, view?, action?, data? string Create a GLink URL
parse() input Object|null Parse GLink from URL or string
hasGLink() none boolean Check if current URL has GLink
getFromCurrentURL() none Object|null Get parsed GLink from current URL
clearFromURL() none void Remove GLink from browser URL
copyToClipboard() glinkURL Promise<boolean> Copy GLink to clipboard

CGLinkHandler Class

The CGLinkHandler is a singleton that manages the GLink lifecycle at the system level. Your dApp interacts with it through its instance methods, primarily acceptGLink(), confirmGLinkProcessed(), and rejectGLink().

Method Parameters Description
getInstance() none Get singleton instance (always use this, never new)
acceptGLink() message? Acknowledge receipt, extend timeout to 3 minutes
confirmGLinkProcessed() message? Signal successful completion
rejectGLink() reason? Signal failure or rejection
hasPendingGLink() none Check if there’s a pending GLink
getPendingGLink() none Get pending GLink data

CWindow Properties (Inherited)

All UI dApps extend CWindow, which provides these GLink-related properties. You don’t need to do anything to get them—they’re automatically populated when your app is launched via GLink.

Property Type Description
wasLaunchedViaGLink boolean True if this window was launched via a GLink (check this first!)
getGLinkData Object Returns {view, action, data} from the GLink (only valid if wasLaunchedViaGLink is true)

9. Best Practices

Over time, we’ve learned what works and what doesn’t when implementing GLink support. Here are the patterns that consistently lead to a great user experience:

Accept Early

Call acceptGLink() as soon as possible after detecting a GLink. This extends the timeout and provides immediate feedback to the user.

Handle Delays Gracefully

If your dApp needs time to initialize (like Wallet’s PIN unlock), store the GLink data and process it later. Don’t block or timeout.

Always Confirm or Reject

Every GLink must end with either confirmGLinkProcessed() or rejectGLink(). Never leave a GLink hanging.

Validate Data

Always validate the GLink data before processing. Check for required fields and reasonable values. Reject with clear error messages.

Document Your Actions

Clearly document your dApp’s action IDs, supported views, and expected data fields. Other developers may want to create GLinks to your app.

Use Meaningful Messages

Provide descriptive messages in your acceptGLink(), confirmGLinkProcessed(), and rejectGLink() calls. Users see these in the progress indicator.

10. The Encoding/Decoding Flow

You’ll rarely need to manually encode or decode GLinks—the CGLink class handles it for you. But when debugging a misbehaving GLink or when you want to construct one by hand for testing, understanding the encoding process is invaluable.

GLinks use URL-safe Base64, which is slightly different from standard Base64. The reason is simple: standard Base64 includes +, /, and = characters, which have special meaning in URLs. URL-safe Base64 replaces these with URL-friendly alternatives.

GLINK ENCODING PIPELINE

1. JSON Object

{"app":"wallet","view":"send"}

2. UTF-8 Bytes

TextEncoder.encode()

3. Base64 Encode

btoa(binary)

4. URL-Safe

+ → - / → _ = removed

URL-Safe Base64 Transformations:
+

Plus becomes hyphen
/

_

Slash becomes underscore
=

(removed)

Padding stripped

Manual Testing with Browser Console

// Encode a GLink manually (for testing)
const data = { app: 'org.gridnetproject.UIdApps.wallet', view: 'send' };
const json = JSON.stringify(data);
const utf8 = new TextEncoder().encode(json);
let binary = '';
utf8.forEach(b => binary += String.fromCharCode(b));
const base64 = btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
console.log(`https://ui.gridnet.org/?glink=${base64}`);
// Decode a GLink manually (for debugging)
const encoded = 'eyJhcHAiOiJ3YWxsZXQifQ';
let restored = encoded.replace(/-/g, '+').replace(/_/g, '/');
while (restored.length % 4) restored += '=';
const decoded = atob(restored);
const bytes = new Uint8Array(decoded.length);
for (let i = 0; i < decoded.length; i++) bytes[i] = decoded.charCodeAt(i);
console.log(JSON.parse(new TextDecoder().decode(bytes)));

11. Communication Sequence Diagram

This diagram shows the exact sequence of calls between the CGLinkHandler and your UI dApp:

HANDLER ↔ DAPP COMMUNICATION

Browser
CGLinkHandler
UI dApp
1. URL with ?glink=
2. initialize() → parse
3. showIndicator()

4. launchApp(data, ‘glink’)

5. acceptGLink(‘Processing…’)
6. processGLink()

7. confirmGLinkProcessed(‘Success!’)
8. hideIndicator() ✓

12. Real-World Use Cases

Theory is great, but let’s see GLinks in action. Here are practical examples showing how GLinks solve real problems and create better user experiences across GRIDNET OS.

Payment Request Links

Perhaps the most compelling use case: imagine sending someone a link that opens their wallet with everything pre-filled—recipient, amount, even a memo. No manual typing, no copy-paste errors:

// Generate payment request for an invoice
function generateInvoiceLink(recipientDomain, amountGNC, invoiceId) {
return CGLink.create(
CGLink.PackageIDs.WALLET,
'send',
1, // PREFILL_SEND action
{
address: recipientDomain,
amount: amountGNC.toString(),
memo: `Invoice #${invoiceId}`
}
);
}
// Example: Generate link for $50 payment
const paymentLink = generateInvoiceLink('merchant.grid', 50, 'INV-2025-001');
// Can be embedded in email, QR code, or web page

Transaction Verification Links

Share links to verify transactions on the blockchain explorer:

// After sending a transaction, generate verification link
async function sendAndGetVerificationLink(recipient, amount) {
// Send the transaction
const txResult = await this.vmContext.sendTransaction(recipient, amount);
if (txResult.success) {
// Generate explorer link for recipient to verify
const verifyLink = CGLink.create(
CGLink.PackageIDs.EXPLORER,
'transactions',
2, // VIEW_TRANSACTION
{ txId: txResult.transactionId }
);
return {
txId: txResult.transactionId,
verificationLink: verifyLink
};
}
}

Domain Profile Links

// Create shareable profile link for a domain
const profileLink = CGLink.create(
CGLink.PackageIDs.EXPLORER,
'search',
3, // VIEW_DOMAIN
{ domain: 'alice.grid' }
);
// Use in social media bio, email signature, etc.
// "View my GRIDNET profile: [GLink URL]"

13. Troubleshooting Guide

GLink issues usually fall into a few common categories: timing problems, missing handler calls, or race conditions with UI rendering. Here are the most frequent problems and their solutions, gathered from real debugging sessions:

Problem: GLink times out with “App did not respond”

Cause: Your dApp is not calling acceptGLink() or confirmGLinkProcessed(). This is the most common GLink bug.

Solution: Ensure your initialize() method checks wasLaunchedViaGLink and calls acceptGLink() immediately. Then call confirmGLinkProcessed() or rejectGLink() when done.

Problem: GLink data is undefined in processGLink()

Cause: Accessing getGLinkData too early or the GLink was malformed.

Solution: Access this.getGLinkData only after checking this.wasLaunchedViaGLink === true. Use a short setTimeout delay if accessing during initialize.

Problem: Progress indicator stays visible after action completes

Cause: Missing confirmGLinkProcessed() or rejectGLink() call.

Solution: Ensure every code path ends with either confirmGLinkProcessed() or rejectGLink(). Use try/catch and call rejectGLink() in the catch block.

Problem: View switches but action doesn’t execute

Cause: Race condition – action executed before view transition completed.

Solution: Use requestAnimationFrame or a short delay after switching views before executing actions that depend on view-specific DOM elements.

Problem: GLink works first time but not on subsequent visits

Cause: GLink URL parameter remains in browser history.

Solution: CGLink.clearFromURL() is called automatically after successful processing. If implementing custom handling, call it manually after processing.

14. Security Considerations

GLinks are a powerful feature, but with power comes responsibility. Remember: the data in a GLink ultimately comes from an external source—a URL that could have been crafted by anyone. A malicious actor could create a GLink with unexpected values, attempting to trick users or exploit vulnerabilities in your dApp.

⚠️ The Cardinal Rule

GLinks should never automatically execute sensitive operations. They can pre-fill forms, navigate to views, and prepare data—but the user must always explicitly confirm any action that affects their assets or identity.

Validate All Input

Never trust GLink data blindly. Validate addresses, amounts, IDs, and all other fields before use. Check for reasonable lengths and formats.

Don’t Auto-Execute Transactions

GLinks should pre-fill forms but never automatically execute transactions. Always require explicit user confirmation for any blockchain operation.

Sanitize Display Content

If displaying GLink data (like memo fields), sanitize to prevent XSS. Never use innerHTML with unsanitized GLink data.

Handle Unknown Actions Gracefully

If a GLink contains an unknown action ID, reject it clearly. Don’t attempt to “guess” what was intended.

Example: Secure GLink Processing

processGLink(glinkData) {
const glinkHandler = CGLinkHandler.getInstance();
try {
// Validate required fields
if (!glinkData || typeof glinkData !== 'object') {
throw new Error('Invalid GLink data format');
}
const { action, data } = glinkData;
// Validate action is a known integer
if (action !== null && (!Number.isInteger(action) || action < 1 || action > 10)) {
throw new Error('Invalid action ID');
}
// Validate specific data fields based on action
if (action === 1) { // PREFILL_SEND
if (!data?.address || typeof data.address !== 'string') {
throw new Error('Invalid recipient address');
}
if (data.address.length > 64) { // Max domain length
throw new Error('Address too long');
}
if (data.amount) {
const amount = parseFloat(data.amount);
if (isNaN(amount) || amount <= 0) {
throw new Error('Invalid amount');
}
}
}
// Accept and process
glinkHandler.acceptGLink('Validated, processing...');
this.executeValidatedAction(action, data);
glinkHandler.confirmGLinkProcessed('Success');
} catch (error) {
console.error('[Security] GLink validation failed:', error);
glinkHandler.rejectGLink(error.message);
}
}

The Future of Decentralized UX

GLinks represent more than just a convenience feature—they’re a fundamental building block for making decentralized applications accessible to everyone. By enabling one-click spawning of a complete decentralized operating system with pre-configured state, we’ve eliminated one of the biggest friction points in blockchain adoption.

Share a transaction link on Twitter. Embed a payment request in an invoice. Create a QR code that opens someone’s wallet ready to send. The decentralized web is no longer just for developers—it’s for everyone.

— The GRIDNET OS Development Team

GRIDNET OS — The World’s First Windowed Decentralized Operating System
GRIDNET

Author

GRIDNET

Up Next

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *