1
0
forked from maik/IPFS-portal
Files
maikrolf 99c113b6f5 fix: bw/stats 404, gateway links, history crash
- swr.ts: remove useBandwidth(), drop bw from useDashboard()
- events/route.ts: skip bandwidth SSE (Kubo 0.28.0 unsupported)
- usage/page.tsx: remove getBWStats call + bw references
- helpers.ts: gatewayLink -> ipfs.maos.dedyn.io (subdomain only)
- upload/page.tsx: fix local gatewayLink (subdomain only)
- storage.ts: update default gatewayUrl, add defensive getHistory()
2026-07-05 16:57:29 +02:00

13 KiB
Raw Permalink Blame History

IPFS Portal — Architecture

Overview

IPFS Portal is a Next.js SPA (Single Page Application) that provides a browser-based interface for managing an IPFS node, uploading files, and handling crypto payments via a smart contract. All pages are 'use client' — no server-side rendering.

Stack

Layer Technology
Frontend Next.js 14+ (React, 'use client' SPA)
Styling Tailwind CSS + custom glass/surface design tokens
Icons Lucide React
Wallet EIP-6963 + viem
Blockchain zkSync Local (chain 270)
IPFS Kubo RPC API (port 5001)
User Mgmt Python backend (port 8444)
IPFS Gateway https://maos.dedyn.io/ipfs/<CID>

System Architecture

                              BROWSER (React SPA)
                                    │
              ┌─────────────────────┼──────────────────────┐
              │                     │                      │
              ▼                     ▼                      ▼
      11 Pages (SPA)          WalletProvider          PaymentService
      ┌──────────────┐       (EIP-6963)              (viem, chain 270)
      │ apiFetch()   │       │                       │
      │ wrapper      │       │                       │
      └──────┬───────┘       │                       │
             │               │                       │
             ▼               ▼                       ▼
      ┌─────────────────────────────────────────────────────┐
      │           Next.js API Route: [...path]/route.ts      │
      │                                                     │
      │  matchRoute(path) →                                  │
      │    'health'   → inline JSON                         │
      │    'users'    → proxyUserAPI() → :8444 (Python)     │
      │    default    → proxyIPFS() → :5001 (Kubo)          │
      │                                                     │
      │  mapToKuboAPI(path) → /api/v0/* translation         │
      └─────────────────────┬───────────────────────────────┘
                            │
              ┌─────────────┴─────────────┐
              │                           │
              ▼                           ▼
      ┌─────────────────┐       ┌─────────────────┐
      │   Kubo IPFS     │       │  Python User API │
      │   Daemon        │       │  (port 8444)     │
      │   (port 5001)   │       │                  │
      │                  │       │  htpasswd mgmt  │
      │  /api/v0/id     │       │  Auth: X-Admin-  │
      │  /api/v0/swarm  │       │  Key header      │
      │  /api/v0/pin/*  │       │                  │
      │  /api/v0/add    │       └──────────────────┘
      │  /api/v0/ls     │
      │  /api/v0/name/  │
      │  /api/v0/repo   │            CHAIN 270
      │  /api/v0/bw     │       ┌──────────────────┐
      └─────────────────┘       │  Smart Contract   │
                                │  0xCBc6...       │
                                │  zkSync Local     │
                                │  RPC :3050        │
                                │                  │
                                │  payWithETH()    │
                                │  payWithToken()  │
                                │  getPrice()      │
                                │  configureToken()│
                                └──────────────────┘

Pages

Page Route Lines Backend Dependencies Description
Landing / ~130 Kubo (health) Hero + connect wallet + quick stats
Dashboard /dashboard 253 Kubo (peers, pins, health, repo, bw) Node overview, stats, bandwidth
Upload /upload 590 Kubo + Smart Contract Quick + crypto file upload
Explorer /explorer 244 Kubo (ls, cat) IPFS content browser
IPNS /ipns 285 Kubo (keys, publish, resolve) IPNS name management
Users /users 275 Python API + Smart Contract htpasswd users + wallet lookup
Peers /peers 63 Kubo (swarm/peers) Connected peers list
Pins /pins 167 Kubo (pin/ls, add, rm) Pin management
Admin/Payment /admin/payment 536 Smart Contract Owner payment dashboard
Settings /settings 319 localStorage only App configuration
History /history 393 localStorage only Upload history viewer

Note: IPNS page does NOT wrap in PortalLayout — appears standalone. All other pages use the shared layout.


Data Flow

1. API Flow (all pages except Settings, History)

Page Component
  → apiFetch('/api/endpoint')
    → fetch('/api/endpoint', { credentials: 'include' })
      → Next.js [...path]/route.ts
        → matchRoute(path)
          → proxyIPFS()  → Kubo RPC :5001
          → proxyUserAPI() → Python :8444

2. Upload Flow (Quick)

User drags file
  → UploadPage state: FileEntry[]
  → startUpload() — sequential per file
    → uploadFile(file) → apiFetch POST /api/files/upload (FormData)
      → proxyIPFS → Kubo /api/v0/add
    → addToHistory({ cid, name, size }) → localStorage
  → Show results (CID, gateway link, copy)

3. Upload Flow (Crypto)

User selects file
  → PaymentPanel shown
    → WalletProvider.connectWallet() → get address
    → PaymentService.getPrice(bytes, addr) → { finalWei, isFree }
    → PaymentService.getSupportedTokens() → [ETH, USDC, MAOS]
    → PaymentService.getMAOSDiscountTiers() → discount %
  → User pays
    → Step 1: uploadFile(file) → Kubo → get CID
    → Step 2a (free): onPaid({ method: 'free' })
    → Step 2b (ETH): payWithETH(provider, cid, size, wei)
      → sendTransaction → contract.payWithETH(cid, bytesSize)
    → Step 2c (token): approveAndPayWithToken(...)
      → check allowance → approve if needed → contract.payWithToken(...)
    → addToHistory({ method: 'eth' }) → localStorage

4. Payment Admin Flow

Admin connects wallet
  → Check isOwner (compare connected address to contract.owner)
  → Load: getOwner, getBasePricePerMB, getFreeTierBytes,
           getSupportedTokens, getMAOSDiscountTiers, getTotalUploads
  → Per token: getTotalRevenue, getBalance (ETH) / balanceOf (ERC20)
  → Owner can: setPrice, setFreeTier, configureToken,
               add/remove discount tiers, withdraw funds

Key Modules

src/lib/api.ts (217 lines)

Central API layer. Exports:

Function HTTP Endpoint Category
getNodeInfo() GET /api/node/info Node
getPeers() GET /api/peers Peers
listPins() / addPin() / removePin() GET/POST/DELETE /api/pins Pins
uploadFile(file) POST (FormData) /api/files/upload Files
listUsers() / createUser() / deleteUser() GET/POST/DELETE /api/users Users
explorerLs() / explorerCat() / explorerStat() POST /api/explorer/* Explorer
listIPNSKeys() / ipnsPublish() / ipnsResolve() / ipnsGenKey() GET/POST /api/ipns/* IPNS
getRepoStats() / getBWStats() GET /api/repo / /api/bw Stats
checkHealth() GET /api/health Health

src/lib/wallet.ts (196 lines)

Wallet connection layer.

  • EIP-6963 event-based provider discovery (discoverWallets)
  • Legacy window.ethereum fallback
  • MAOS Wallet preferred (window.maosv6)
  • Chain 270 (zkSync Local) switch + add
  • Helpers: formatWeiToETH, formatBytes, signMessage, getBalance

src/lib/payment.ts (539 lines)

Smart contract interaction via viem.

  • 28 methods wrapping ABI functions
  • Supports ETH and ERC20 token payments
  • Read: getPrice, getUserStats, getSupportedTokens, getMAOSDiscountTiers
  • Write: payWithETH, approveAndPayWithToken
  • Admin: adminSetPricePerMB, adminConfigureToken, adminWithdrawETH, etc.
  • Contract: 0xCBc6b8aeea129c206F4836799621C833Bf8B9BDe on chain 270

src/lib/storage.ts (98 lines)

localStorage persistence.

  • PortalSettings: gateway URL, API endpoint, storage max, refresh, theme
  • UploadRecord: history with dedup by CID+name, capped at 500 entries

src/app/api/[...path]/route.ts (~320 lines)

Catch-all API proxy. Routes:

Path Handler Backend
/api/health handleHealth() Inline JSON
/api/users* proxyUserAPI() Python :8444
/api/explorer/* / /api/ipns/* / default proxyIPFS() Kubo :5001

mapToKuboAPI(path) translates Portal API paths to Kubo RPC endpoints:

  • /node/info/api/v0/id
  • /peers/api/v0/swarm/peers
  • /pins/api/v0/pin/ls|add|rm
  • /files/upload/api/v0/add
  • /explorer/ls/api/v0/ls
  • /ipns/publish/api/v0/name/publish
  • /repo/stats/api/v0/repo/stat
  • /bw/stats/api/v0/bw

Smart Contract (Chain 270 — zkSync Local)

Address: 0xCBc6b8aeea129c206F4836799621C833Bf8B9BDe RPC: http://tom1687.no-ip.org:3050

Functions

Category Functions
Payment payWithETH(cid, bytesSize) (payable), payWithToken(cid, bytes, symbol)
Pricing getPrice(bytes, user)(totalWei, discountBps, finalWei, isFree)
Free Tier freeTierBytes(), userTotalBytes(user)
Token Mgmt configureToken(symbol, addr, decimals, weiPerToken, enabled)
Discounts getMAOSDiscountTiers(), setMAOSDiscountTier(minBal, bps), removeMAOSDiscountTier(index)
Admin setPricePerMB(wei), setFreeTierBytes(bytes), withdrawETH(to), withdrawToken(symbol, to), transferOwnership(to)
Stats totalRevenue(symbol), totalUploads(), getUserStats(user)
Events UploadPaidETH, UploadPaidToken, FreeUploadUsed

Known Bug

Contract returns duplicate token entries — admin page deduplicates on symbol.


Graph Analysis

Static (AST) View

  • 251 nodes, 401 edges, 17 communities
  • Graphify knowledge graph at graphify-out/graph.json
  • 79 isolated nodes (type definitions, interfaces, scripts)

Bridge Nodes

Node Edges Role
PaymentService 31 Bridges Smart Contract ↔ UI (Payments)
WalletProvider 15 Bridges Wallet ↔ PaymentService

Graph Gap

The AST parser cannot trace URL-routed connections. The runtime flow:

apiFetch('/api/node/info') → fetch('/api/node/info')
  → [...path]/route.ts → proxyIPFS() → Kubo :5001

Appears as 3 disconnected communities in the graph but is one continuous flow at runtime.


Code Quality Summary

Issues Found (24 total)

Severity Count Key Items
🔴 Critical 4 XSS, unhandled promises, silent catch blocks, oversized files
🟡 Medium 5 15× @ts-expect-error, 8× as any, duplicate helpers, precision loss
🟢 Low 3 Unused imports, console.* logging, IPNS layout inconsistency

Priority Fixes

  1. Window type augmentation — Declare Window interface in wallet.ts to eliminate 15 @ts-expect-error
  2. Extract duplicate patterns — Consolidate formatBytes, gatewayLink, truncateCid into lib/helpers.ts
  3. Add error logging — Every empty catch block gets console.error or structured logging
  4. Split oversized filesupload/page.tsx (590→2 components), admin/payment/page.tsx (536→4 views), payment.ts (539→abi/types/service)
  5. Fix XSS — Add DOMPurify or replace dangerouslySetInnerHTML in FilePreview.tsx
  6. Fix unhandled promises — Add .catch() to isDeployed().then()
  7. Replace as any casts — Use proper viem parseAbi types instead of as unknown as X

Configuration

  • API_BASE: '' (empty — same origin; nginx routes /api/* to backend)
  • Kubo RPC: http://127.0.0.1:5001
  • Python API: http://192.168.1.176:8444 (htpasswd + admin key)
  • Smart Contract: 0xCBc6... on chain 270 (zkSync Local)
  • IPFS Gateway: https://maos.dedyn.io/ipfs/
  • Refresh Interval: 15s (Dashboard)
  • Max Uploads: 20 files, 100MB each
  • Storage Max: 30 GB (localStorage setting)
  • Admin Key: maos-admin-2024 (X-Admin-Key header)