Multi-chain daily task dashboard with 20 networks • GenLayer AI contracts • NFT soulbound streaks
DGDreams is a unified dashboard where users connect their wallet once and execute daily on-chain tasks across 20 blockchain networks — Ethereum, Base, Arbitrum One, OP Mainnet, HyperEVM, Unichain, Tempo, Robinhood, Ink, Arc, BNB Chain and opBNB on mainnet, plus testnets including Arbitrum Sepolia, GenLayer Bradbury, LitVM, GIWA, ARC, and SimpleChain.
Users can run the same 3 daily tasks on any supported network:
- Daily Check-In — Start the day, build your streak
- GM — Morning greeting
- GN — Night sign-off
Each task writes to an on-chain contract that tracks streaks, action counts, and daily resets.
The day rolls over at 00:00 UTC — the same boundary for every user in every
timezone, because the contract buckets days as block.timestamp / 1 days. The app
mirrors that boundary exactly, so an open tab clears its "already done today"
state at the same instant the contract does.
One button runs the two missions plus three deploys back to back:
- GM and GN on
NikBase - DGDemo — a greeter
- DGLiteToken — a minimal ERC-20
- DGLiteNft — a minimal ERC-721
These are size-optimised editions from contracts/OneClick.sol.
Deployment gas is charged at 200 gas per byte of runtime code plus calldata, so
every function nobody calls is still paid for on every deployment. Stripping the
one-click contracts down to only what the flow exercises cuts the cost from
~2.78M to ~0.87M gas:
node scripts/measure-deploy-cost.mjs # prints the per-contract bill
The card picks its own network, independent of the mission page, and reuses addresses it has already deployed on that network, so a second run costs almost nothing.
GenLayer is a non-EVM chain using Python-based Intelligent Contracts with AI-validator consensus. This project integrates GenLayer as a first-class network:
- Contract:
NikBase— Python Intelligent Contract storing all user data as JSON in a singlestrstate field - Client:
genlayer-jsSDK with MetaMask provider (window.ethereum) - Read/Write: Separate code paths via
isGenLayer()check — wagmi for EVM chains, genlayer-js for GenLayer - Address:
0x1203ab4E8386220F792f129C605460fD0F52C412
Networks with a soulbound NFT contract support minting and upgrading:
- 7-day streak → Mint Bronze NFT
- Streak milestones → Upgrade through Silver, Gold, Diamond, Legend tiers
- NFTs are soulbound (non-transferable) and track your longest streak
src/
├── app/
│ ├── dashboard/ # Network grid overview
│ ├── tasks/ # Daily task execution page
│ ├── 2048/ # Binary milestone game
│ ├── deploy/ # Deploy the app's own contracts
│ ├── faq/ # Help & documentation
│ └── api/ # Backend API routes
├── components/
│ ├── DailyTaskPanel # Task execution modal (wagmi + genlayer-js)
│ ├── FiveInOne # 5-in-1 runner (missions + three deploys)
│ ├── DailyMissionDeck # Mission cards with UTC-day state
│ ├── Sidebar # Navigation sidebar
│ ├── DashboardLayout # Shared layout wrapper
│ └── ...
├── config/
│ └── chains.ts # All 20 chain definitions & NetworkConfig
├── lib/
│ ├── utcDay.ts # 00:00 UTC boundary shared by the whole app
│ ├── sequence.ts # Multi-step runner used by the 5-in-1 card
│ ├── genlayer/
│ │ ├── client.ts # genlayer-js client factory
│ │ └── tasks.ts # Write/read helpers for GenLayer
│ └── utils.ts # Shared utilities
└── db/
├── schema.ts # Drizzle ORM schema
└── index.ts # Database client
genlayer-contracts/
└── nikbase_genlayer.py # Python Intelligent Contract for GenLayer
User connects wallet → RainbowKit → wagmi (EVM chains)
└── genlayer-js (GenLayer)
↓
User selects network → switchChainAsync (wagmi)
└── manual wallet switch (GenLayer)
↓
User clicks "Start" → DailyTaskPanel opens
↓
┌──── isGenLayer(chainId)? ────┐
↓ ↓
genLayerWriteTask() writeContractAsync()
(genlayer-js SDK) (wagmi viem)
↓ ↓
waitForTxReceipt waitForTransactionReceipt
↓ ↓
Confirm task Handle revert / success
| Network | Chain ID | Currency |
|---|---|---|
| Ethereum | 1 | ETH |
| Base | 8453 | ETH |
| HyperEVM | 999 | HYPE |
| Unichain | 130 | ETH |
| Tempo | 4217 | USD |
| Robinhood | 4663 | ETH |
| Ink | 57073 | ETH |
| Arc | 5042 | USDC |
| BNB Chain | 56 | BNB |
| opBNB | 204 | BNB |
| Arbitrum One | 42161 | ETH |
| OP Mainnet | 10 | ETH |
💡 Arbitrum and OP are the cheapest networks here — a full 5-in-1 run costs well under a cent, so they are the best place to start.
forge build
# preview: prints the per-contract gas bill, sends nothing
npm run deploy:l2:dry
# deploy NikBase + Game2048 + SoulboundStreak to Arbitrum One, OP Mainnet
# and Arbitrum Sepolia, then wire the addresses into the app config
npm run deploy:l2Then verify on the explorers:
npm run verify:l2 # needs ARBITRUM_API_KEY / OPTIMISM_API_KEYBoth scripts read their target addresses back out of the app config, so a command
can never be pointed at the wrong address. Add --only <chainId> to restrict to
one network.
# Clone the repository
git clone https://github.com/Misagh95/dgdreams.git
cd dgdreams
# Install dependencies
npm install
# Set up environment variables
cp .env.example .env.local
# Add your WalletConnect Project ID to .env.local:
# NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_id
# SESSION_SECRET=<openssl rand -hex 32>
# DEPLOYER_PRIVATE_KEY=0x... (only needed to deploy contracts)
# Run development server
npm run dev- Node.js >= 18
- WalletConnect Project ID — get one at cloud.walletconnect.com
- MetaMask browser extension (for EVM chains + GenLayer via genlayer-js)
SESSION_SECRET— signs the wallet-login tokens used to record streaks. Generate one withnode -e "console.log(require('crypto').randomBytes(32).toString('hex'))"and set it in.env.localand in your deployment platform's environment. Without it the site still builds, but signing in and streak recording are disabled.
Intelligent Contracts written in Python and deployed on GenLayer Bradbury testnet. All contracts use real UTC time fetched via gl.nondet.web.render("https://worldtimeapi.org/api/timezone/Etc/UTC") with gl.eq_principle.strict_eq consensus.
genlayer-contracts/nikbase_genlayer.py
# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" }
from genlayer import *
import json
class NikBase(gl.Contract):
store: str
def _now(self) -> int:
def fetch_time() -> str:
raw = gl.nondet.web.render("https://worldtimeapi.org/api/timezone/Etc/UTC", mode="text")
...
return int(gl.eq_principle.strict_eq(fetch_time))
def _today(self) -> int:
return self._now() // 86400
@gl.public.write
def dailyCheckIn(self) -> str: ...
@gl.public.write
def gm(self) -> None: ...
@gl.public.write
def gn(self) -> None: ...Tracks daily on-chain actions (gm, gn, checkIn, dose, mood, sanitize, counter, spin) using real UTC time. Each action type can only be performed once per UTC day — enforced by comparing _today() against the stored lastActionDay per user.
genlayer-contracts/price_oracle.py
Fetches live cryptocurrency prices from the Binance API using GenLayer's AI-validator consensus — 5 independent validators fetch the data and agree on the result via gl.eq_principle.prompt_comparative with 1% tolerance before it's written on-chain.
class PriceOracle(gl.Contract):
store: str
@gl.public.view
def getPrice(self, symbol: str) -> str: ...
@gl.public.view
def isFresh(self, symbol: str, max_age_seconds: int) -> str:
# Returns {"fresh": bool, "age": int, "max_age": int}
@gl.public.write
def fetchPrice(self, symbol: str) -> typing.Any:
def fetch() -> str:
raw = gl.nondet.web.render(
f"https://api.binance.com/api/v3/ticker/24hr?symbol={symbol}USDT",
mode="text"
)
result = gl.eq_principle.prompt_comparative(
fetch,
"Two prices are equivalent if they are for the same symbol and the price values differ by less than 1%."
)
parsed = json.loads(str(result))
parsed["updated_at"] = self._now() # freshness timestamp
data[symbol] = parsed
self.store = json.dumps(data, sort_keys=True)Flow:
- User clicks "Fetch BTC/USDT" on the frontend
- Contract calls
gl.nondet.web.render()— each validator independently fetches Binance gl.eq_principle.prompt_comparative()ensures validators agree within 1% tolerance- Agreed price +
updated_attimestamp is stored on-chain isFresh(symbol, max_age_seconds)view method checks whether stored data is still fresh
Page: /genlayer-oracle — switch to GenLayer, select a symbol, and fetch
genlayer-contracts/prediction_market.py
A decentralized prediction market contract supporting conditional outcome resolution via direct web fetching or via the PriceOracle contract.
class PredictionMarket(gl.Contract):
store: str
@gl.public.write
def createMarket(self, question: str, source_url: str, target_value: str, condition: str, resolves_at: int) -> int: ...
@gl.public.write
def predict(self, market_id: int, outcome: int, amount: int) -> str: ...
@gl.public.write
def resolveMarket(self, market_id: int, oracle_addr: str = "") -> str:
# Direct resolution: validators fetch source_url and reach strict_eq consensus on outcome
# Optionally accepts oracle_addr for audit trail
@gl.public.write
def resolveWithOracle(self, market_id: int, oracle_addr: str, symbol: str, max_age: int) -> str:
# Oracle-mediated resolution:
# 1. Checks oracle.isFresh(symbol, max_age) — rejects if stale
# 2. Calls oracle.getPrice(symbol) for the current price
# 3. Resolves market by comparing price against target_value/condition
# 4. Stores oracle price + freshness info for audit trailTwo resolution modes:
resolveMarket— validators directly fetch the market'ssource_urland reach consensus viastrict_eq. The optionaloracle_addrparameter stores a reference for audit purposes.resolveWithOracle— resolves via a PriceOracle contract. First checks freshness viaisFresh(symbol, max_age), then fetches the price and resolves the market. Rejects stale data with an error message.
GenLayer uses Python (not Solidity) and a non-EVM runtime. Contract interactions go through
genlayer-js, not wagmi/viem.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack) |
| Language | TypeScript |
| Wallet | RainbowKit + Wagmi 2.x + Viem |
| genlayer-js | |
| Database | PostgreSQL + Drizzle ORM |
| Styling | CSS Variables + Tailwind-like utility classes |
| Deployment | Vercel |
| Contracts (EVM) | Solidity (Hardhat) |
| Python (GenLayer Studio) |
This project is licensed under the MIT License.
Built by @Misagh95 · dgdreams.space