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.
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.
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.
Table of Contents
- 1. Why GLinks Matter: The Deep Linking Revolution
- 2. Anatomy of a GLink
- 3. The GLink Lifecycle
- 4. Implementing GLink Support in Your UI dApp
- 5. Case Study: Wallet UI dApp
- 6. Case Study: Blockchain Explorer UI dApp
- 7. Creating GLinks Programmatically
- 8. API Reference
- 9. Best Practices
- 10. The Encoding/Decoding Flow
- 11. Communication Sequence Diagram
- 12. Real-World Use Cases
- 13. Troubleshooting Guide
- 14. Security Considerations
1. Why GLinks Matter: The Deep Linking Revolution
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
=
eyJhcHAiOiJvcmcuZ3JpZG5ldHByb2plY3QuVUlkQXBwcy53YWxsZXQiLCJ2aWV3Ijoic2VuZCIsImRhdGEiOnsiYWRkcmVzcyI6ImFsaWNlIn19
https://ui.gridnet.org/
glink
Base64(JSON)
JSON Data Format
The encoded payload is a JSON object with the following structure:
{
"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= parameter on page load. CGLinkHandler.initialize() → CGLink.getFromCurrentURL()
showIndicator() → updateIndicator('GLink detected', 10, appName)
systemReady(windowManager, packageManager, vmContext)
userLoggedIn() → processGLink(glinkData)
windowManager.launchApp(app, true, launchData, 'glink')
processGLink() accepts, navigates to view, executes action. acceptGLink() → switchView() → executeAction()
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 GLinkthis.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
};
}
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, amountor 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' }
);
7. Creating GLinks Programmatically
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.
{"app":"wallet","view":"send"}
TextEncoder.encode()
btoa(binary)
+ → - / → _ = removed
→
–
→
_
→
(removed)
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:
|
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.


