Skip to content

ngh1105/genlayer-zero

Repository files navigation

From Zero to GenLayer — LLM ERC20 dApp (GenLayerJS + studionet)

Node.js Vite GenLayerJS

A production-ready React dApp demonstrating LLM ERC20 token interactions on GenLayer StudioNet using GenLayerJS SDK with Material UI.

TL;DR / Quickstart

# Install dependencies
pnpm i

# Setup environment
cp .env.example .env
# Edit .env with your contract address and private key

# Start development server
pnpm dev

Required Environment Variables:

  • VITE_CONTRACT_LLM_ERC20=<0x...> - Your deployed contract address
  • VITE_PK=<private key test> - Test private key for direct signing
  • VITE_ADDR=<address> - Alternative: address for external signing (MetaMask)

What You'll Build

A mini dApp that reads/writes to llm_erc20 contract with the following features:

  • Read Operations: get_balance_of, get_balances
  • Write Operations: mint, transfer
  • Modern UI: Material UI with dark/light theme toggle
  • Real-time Feedback: Snackbar notifications and loading states

GenLayer Foundation Knowledge

GenLayer Studio

GenLayer Studio is the IDE/Console for deploying Intelligent Contracts - smart contracts enhanced with AI capabilities for complex decision-making.

GenLayerJS SDK

The official JavaScript SDK for interacting with GenLayer network:

  • Create clients for different chains (studionet, testnetAsimov)
  • Read/write contract data
  • Wait for transaction confirmations
  • Retrieve contract schemas

Core Principles

  • Optimistic Democracy Consensus: Transactions are optimistically accepted and can be appealed if consensus is challenged
  • Equivalence Principle: All participants have equal voting power in the consensus mechanism

Contract Deployment

Method A – GenLayer Studio GUI (Recommended for Beginners)

  1. Open studio.genlayer.com
  2. Click "New Contract" → Paste llm_erc20 contract code
  3. Deploy on StudioNet
  4. Copy the Contract Address

Method B – GenLayerJS SDK (Code Example)

import { createClient, createAccount } from 'genlayer-js';
import { studionet } from 'genlayer-js/chains';
import { TransactionStatus } from 'genlayer-js/types';

// Setup client with studionet
const account = createAccount(process.env.PK!);
const client = createClient({ chain: studionet, account });

// Initialize consensus (required before any operations)
await client.initializeConsensusSmartContract();

// Deploy contract
const hash = await client.deployContract({ 
  code: CONTRACT_CODE, 
  args: [], 
  leaderOnly: false 
});

// Wait for deployment confirmation
const receipt = await client.waitForTransactionReceipt({
  hash, 
  status: TransactionStatus.ACCEPTED, 
  retries: 50, 
  interval: 5000,
});

console.log('Deployed at:', receipt.data?.contract_address);

⚠️ Security Warning: Only use test private keys for development. Never use production keys.

Project Workflow (End-to-End)

Complete Checklist

  • Deploy contract on GenLayer Studio (StudioNet) → copy contract address
  • Mint test tokens in Studio (or via dApp after configuring keys)
  • Configure .env: VITE_CONTRACT_LLM_ERC20, choose one signing method: VITE_PK (test private key) or VITE_ADDR (MetaMask)
  • Run dApp: pnpm i && pnpm dev
  • On-chain interaction from UI: get_balance_ofget_balancestransfer
  • Log & screenshot: read results, transfer accepted
  • Submit mission: repo link + images/short screencast

Known Issues on StudioNet

  • Pending transactions: Use waitForTransactionReceipt with ACCEPTED status, increase retries/interval
  • Network delays: StudioNet may have higher latency than localnet
  • Consensus time: Transactions may take longer to reach finality

Environment Configuration

Create .env file:

VITE_CONTRACT_LLM_ERC20=0xYourContractOnStudionet
# Choose ONE of the following
VITE_PK=0xYourTestPrivateKey
# VITE_ADDR=0xYourAddressForExternalSigning
# (optional) VITE_RPC_ENDPOINT=https://studio.genlayer.com/api

⚠️ Security Warning: Only use test private keys for development. Never use production keys.

Explanation:

  • VITE_PK: SDK handles signing directly with private key
  • VITE_ADDR: External signing via wallet (MetaMask)

MUI Setup

Required Packages

pnpm add @mui/material @mui/icons-material @emotion/react @emotion/styled

Theme Configuration

The project uses Material UI with dark theme by default and theme toggle functionality:

import { CustomThemeProvider } from './contexts/ThemeContext'
import ThemeWrapper from './components/ThemeWrapper'

const AppWithTheme = () => {
  return (
    <CustomThemeProvider>
      <ThemeWrapper>
        <App />
      </ThemeWrapper>
    </CustomThemeProvider>
  );
};

UI Components Used

The Erc20Demo.tsx component utilizes these MUI components:

  • AppBar: Fixed header with title and theme toggle
  • Card: Main content containers
  • Grid: Responsive layout system
  • Table: Display all balances data
  • Snackbar: Real-time notifications
  • TextField: Input fields with validation
  • Button: Action buttons with loading states

GenLayerJS Integration in Project

Client Setup

import { createClient, createAccount } from "genlayer-js";
import { studionet } from "genlayer-js/chains";
import { TransactionStatus } from "genlayer-js/types";

const CONTRACT = import.meta.env.VITE_CONTRACT_LLM_ERC20 as `0x${string}`;
const PK = import.meta.env.VITE_PK as string | undefined;
const ADDR = import.meta.env.VITE_ADDR as `0x${string}` | undefined;

// Create client with proper configuration
let clientConfig: any = { chain: studionet };
if (PK) {
  clientConfig.account = createAccount(PK as `0x${string}`);
} else if (ADDR) {
  clientConfig.account = ADDR;
}

export const client = createClient(clientConfig);

// Initialize consensus smart contract (required before any contract interaction)
let initPromise: Promise<void> | null = null;
export async function ensureInitialized() {
  if (!initPromise) {
    initPromise = client.initializeConsensusSmartContract();
  }
  return initPromise;
}

Contract Interaction Wrappers

// Low-level contract interaction wrappers
export async function readContract(functionName: string, args: any[] = []) {
  await ensureInitialized();
  return client.readContract({
    address: CONTRACT,
    functionName,
    args,
  });
}

export async function writeContract(functionName: string, args: any[] = []) {
  await ensureInitialized();
  const hash = await client.writeContract({
    address: CONTRACT,
    functionName,
    args,
    value: 0n,
  });
  
  // Wait for transaction to be accepted
  return client.waitForTransactionReceipt({
    hash,
    status: TransactionStatus.ACCEPTED,
    retries: 60,
    interval: 1000,
  });
}

ERC20 API Layer

import { erc20, getSchema } from "../../lib/genlayer";

export async function getBalanceOf(address: string) {
  if (!/^0x[a-fA-F0-9]{40}$/.test(address)) throw new Error("Invalid address");
  const v = await erc20.getBalanceOf(address);
  return Number(v);
}

export async function getBalances() {
  try {
    console.log("Fetching balances...");
    const rawData = await erc20.getBalances();
    console.log("Raw balances data:", rawData, "Type:", typeof rawData);
    
    // Handle Map object response
    if (rawData instanceof Map) {
      const result = Array.from(rawData.entries()).map(([address, balance]) => ({
        address,
        balance: Number(balance)
      }));
      console.log("Map balances:", result);
      return result;
    }
    
    // Handle other formats...
    return [];
  } catch (error) {
    console.error("Error fetching balances:", error);
    return [];
  }
}

UI Integration

export default function Erc20Demo() {
  const { mode, toggleMode } = useCustomTheme();
  const [connectedWallet, setConnectedWallet] = useState<string>('');
  const [walletBalance, setWalletBalance] = useState<string | number>('-');
  
  // Auto-load wallet balance when wallet is connected
  useEffect(() => {
    if (connectedWallet) {
      loadWalletBalance();
    }
  }, [connectedWallet]);

  const handleTransfer = async () => {
    const result = await safe(() => transfer(Number(transferAmount), transferTo));
    if (result !== undefined) {
      showSnackbar('Transfer completed successfully!', 'success');
      await loadWalletBalance(); // Refresh balance
    }
  };
}

On-Chain Interaction Examples

Read Operations (View Functions)

// Initialize client and consensus
const client = createClient({ chain: studionet, account });
await client.initializeConsensusSmartContract();

// Get balance of specific address
const balance = await client.readContract({
  address: CONTRACT,
  functionName: 'get_balance_of',
  args: ['0x584713626396fA15CA12870d924f743CD1c09961'],
});

// Get all balances
const allBalances = await client.readContract({
  address: CONTRACT,
  functionName: 'get_balances',
  args: [],
});

Write Operations (Transactions)

// Initialize client and consensus
const client = createClient({ chain: studionet, account });
await client.initializeConsensusSmartContract();

// Transfer tokens
const hash = await client.writeContract({
  address: CONTRACT,
  functionName: 'transfer',
  args: [100, '0xReceiver'],
  value: 0n,
});

// Wait for confirmation
const receipt = await client.waitForTransactionReceipt({
  hash, 
  status: TransactionStatus.ACCEPTED,
  retries: 60,
  interval: 1000,
});

Contract Schema

// Initialize client and consensus
const client = createClient({ chain: studionet, account });
await client.initializeConsensusSmartContract();

// Get contract schema
const schema = await client.getContractSchema({ address: CONTRACT });
console.log(schema.methods);

Running the dApp

Development Commands

# Install dependencies
pnpm i

# Start development server
pnpm dev

UI Operations

  1. Check Balance: Enter address → Click "Get Balance"
  2. View All Balances: Click "Get All Balances" → See table with all addresses
  3. Transfer Tokens: Enter receiver address and amount → Click "Transfer Tokens"
  4. Theme Toggle: Switch between dark/light mode
  5. Copy Addresses: Click copy icons to copy contract/addresses

UI Features

  • Real-time Feedback: Snackbar notifications for success/error
  • Loading States: Circular progress indicators during operations
  • Address Validation: Automatic validation for Ethereum addresses
  • Responsive Design: Mobile-friendly layout
  • Accessibility: ARIA labels and keyboard navigation

Troubleshooting

Common Issues

InternalRpcError: Invalid parameters

  • Check address format (0x + 40 hex characters)
  • Ensure amount > 0
  • Verify contract address is correct

Wallet not connected: Missing authentication

  • Add VITE_PK for direct signing
  • Or add VITE_ADDR for external signing
  • Ensure private key is valid

Transaction pending: Long wait times

  • Use waitForTransactionReceipt with proper retry settings
  • Check network status
  • Try again with higher retry count

CORS errors: Custom endpoint issues

  • Use default studionet endpoint
  • Or configure dev proxy in vite.config.ts

Debug Tips

// Enable debug logging
console.log("Raw data:", rawData);
console.log("Transaction hash:", hash);
console.log("Receipt:", receipt);

Mission Checklist: "From Zero to GenLayer"

  • Deploy llm_erc20 on StudioNet
  • Get contract address and configure .env
  • Demo UI: read / write operations working
  • Screenshots: get_balance_of, get_balances, transfer accepted
  • README with complete guide + GenLayerJS code examples
  • (Optional) 1-2 minute demo video

Next Steps & Extensions

Immediate Next Steps

  • Add log_indexer as second module
  • Implement appealTransaction for consensus cases
  • Create React hook useGenLayerClient() for shared client and tx state
  • Add batch transaction processing
  • Implement real-time event listening

Advanced Features

  • Multi-signature wallet integration
  • Contract upgrade mechanisms
  • Advanced error handling and retry logic
  • Performance optimization for large datasets
  • Integration with external APIs and services

Appendix

Environment Template

# Contract Configuration
VITE_CONTRACT_LLM_ERC20=0xYourContractOnStudionet

# Choose ONE of the following
VITE_PK=0xYourTestPrivateKey
# VITE_ADDR=0xYourAddressForExternalSigning

# (optional) VITE_RPC_ENDPOINT=https://studio.genlayer.com/api

⚠️ Security Warning: Only use test private keys for development. Never use production keys.

Package Scripts

{
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  }
}

License

MIT License - see LICENSE file for details.


Built with ❤️ using GenLayerJS and Material UI

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors