From 408cec059f45a8c40b79f6fe02ce20f39debd264 Mon Sep 17 00:00:00 2001 From: jerrymusaga Date: Wed, 4 Jun 2025 17:23:32 +0100 Subject: [PATCH 1/7] Jerry Musaga | Register for OpenGuild Da Nang Hackcamp 2025 --- README.md | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 1005506..6b18c89 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -
![Da Nang Hackcamp](./assets/danang_hackcamp.png) @@ -33,7 +32,7 @@ git clone https://github.com/openguild-labs/open-danang-hackcamp-2025.git Go to **Participant Registration** section and register to be the workshop participants. Add the below to the list, replace any placeholder with your personal information. ``` -| πŸ¦„ | Name | Github username | Your current occupation | +| πŸ¦„ | Jerry Musaga | jerrymusaga | Intern @ Blockfuse Labs | ``` - Step 5: `Commit` your code and push to the forked Github repository @@ -53,16 +52,16 @@ git commit -m " | Register for OpenGuild Da Nang Hackcamp 2025" ## Discover the List of Challenges πŸ† -Total OpenGuild prize pool: **2000$ / 5 winners** +Total OpenGuild prize pool: **2000$ / 5 winners**
Total Bifrost Parner prize pool: **300$-1000$** (dedicated to Bifrost's Challenge) -| Challenge | Description | Action | Bounty | +| Challenge | Description | Action | Bounty | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------ | -| 1 | Building Uniswap V2 Application | [Take Challenge](./challenge-1-uniswapv2/README.md) | $ | -| 2 | Building Lending and Borrowing Application | [Take Challenge](./challenge-2-lending-borrowing/README.md) | $ | -| 3 | Build a cross-chain application using XCM | [Take Challenge](./challenge-3-xcm/README.md) | $ | -| 4 | Polkadot SDK | [Take Challenge](./challenge-4-polkadot-sdk/README.md) | $ | -| Parner Track - Bifrost | Create a unique and optimal UX for interacting with Bifrost Liquid Staking Tokens available on many chains | [Take Challenge](./challenge-bifrost/README.md) | $300-$1000 | +| 1 | Building Uniswap V2 Application | [Take Challenge](./challenge-1-uniswapv2/README.md) | $ | +| 2 | Building Lending and Borrowing Application | [Take Challenge](./challenge-2-lending-borrowing/README.md) | $ | +| 3 | Build a cross-chain application using XCM | [Take Challenge](./challenge-3-xcm/README.md) | $ | +| 4 | Polkadot SDK | [Take Challenge](./challenge-4-polkadot-sdk/README.md) | $ | +| Parner Track - Bifrost | Create a unique and optimal UX for interacting with Bifrost Liquid Staking Tokens available on many chains | [Take Challenge](./challenge-bifrost/README.md) | $300-$1000 |

@@ -70,21 +69,19 @@ Total Bifrost Parner prize pool: **300$-1000$** (dedicated to Bifrost's Challeng ## πŸ‘‰ Resource for Development and Support Channels -### Paseo Contract Faucet +### Paseo Contract Faucet Link : https://faucet.polkadot.io/?parachain=1111 -### Explorer +### Explorer Link : https://blockscout-passet-hub.parity-testnet.parity.io/ ### RPC -+ ETH - EVM-compatible RPC: `https://testnet-passet-hub-eth-rpc.polkadot.io/` - -+ ChainID: `0x190f1b45` - +- ETH - EVM-compatible RPC: `https://testnet-passet-hub-eth-rpc.polkadot.io/` +- ChainID: `0x190f1b45` ### Resources @@ -97,17 +94,13 @@ Link : https://blockscout-passet-hub.parity-testnet.parity.io/ - [OpenGuild Learn](https://learn.openguild.wtf/) ### Support Channels + - [Discord OpenGuild](https://github.com/openguild-labs) - [Discord Polkadot](https://discord.gg/polkadot) - [Stack Exchange](https://substrate.meta.stackexchange.com/) - [Telegram](https://t.me/substratedevs) - [Reddit](https://www.reddit.com/r/Polkadot/) - - - - - ## πŸ‘‰ Contribute to OpenGuild Community OpenGuild is a builder-driven community centered around Polkadot. OpenGuild is built by Web3 builders for Web3 builders. Our primary aim is to cater to developers seeking a comprehensive understanding of the Polkadot blockchain, providing curated, in-depth materials with a low-level approach. @@ -116,8 +109,3 @@ OpenGuild is a builder-driven community centered around Polkadot. OpenGuild is b - **Website:** [OpenGuild Website](https://openguild.wtf/) - **Github:** [OpenGuild Labs](https://github.com/openguild-labs) - **Discord**: [Openguild Discord Channel](https://discord.gg/bcjMzxqtD7) - - - - - From 76e20e4e60cdc4b4990bb02e86d1b4ee1a83cf48 Mon Sep 17 00:00:00 2001 From: jerrymusaga Date: Fri, 6 Jun 2025 22:15:48 +0100 Subject: [PATCH 2/7] building the UIUX for the app --- .../frontend/app/crosschainNFT/page.tsx | 19 + challenge-3-xcm/frontend/app/layout.tsx | 22 +- challenge-3-xcm/frontend/app/page.tsx | 19 +- .../components/create-wallet-dialog.tsx | 154 ++++ .../frontend/components/navbar.tsx | 244 +++++- .../frontend/components/xcm/NFT.tsx | 622 ++++++++++++++++ challenge-3-xcm/frontend/lib/atoms.ts | 7 + challenge-3-xcm/frontend/package-lock.json | 703 ++++++++++++++++++ challenge-3-xcm/frontend/package.json | 1 + 9 files changed, 1753 insertions(+), 38 deletions(-) create mode 100644 challenge-3-xcm/frontend/app/crosschainNFT/page.tsx create mode 100644 challenge-3-xcm/frontend/components/create-wallet-dialog.tsx create mode 100644 challenge-3-xcm/frontend/components/xcm/NFT.tsx create mode 100644 challenge-3-xcm/frontend/lib/atoms.ts diff --git a/challenge-3-xcm/frontend/app/crosschainNFT/page.tsx b/challenge-3-xcm/frontend/app/crosschainNFT/page.tsx new file mode 100644 index 0000000..a24520c --- /dev/null +++ b/challenge-3-xcm/frontend/app/crosschainNFT/page.tsx @@ -0,0 +1,19 @@ +"use client"; + +import dynamic from "next/dynamic"; + +// Import Vesting with dynamic import (since it uses client hooks) +const XCM = dynamic(() => import("@/components/xcm/NFT"), { + ssr: false, +}); + +export default function VestingPage() { + return ( +
+

+ Crosschain NFT Marketplace +

+ +
+ ); +} diff --git a/challenge-3-xcm/frontend/app/layout.tsx b/challenge-3-xcm/frontend/app/layout.tsx index db98252..e2cf563 100644 --- a/challenge-3-xcm/frontend/app/layout.tsx +++ b/challenge-3-xcm/frontend/app/layout.tsx @@ -1,14 +1,15 @@ import type { Metadata } from "next"; import { Unbounded } from "next/font/google"; import "./globals.css"; -import '@rainbow-me/rainbowkit/styles.css'; -import { Providers } from '@/app/providers'; +import "@rainbow-me/rainbowkit/styles.css"; +import { Providers } from "@/app/providers"; +import Navbar from "@/components/navbar"; const unbounded = Unbounded({ - subsets: ['latin'], - weight: ['400', '700'], - display: 'swap', -}) + subsets: ["latin"], + weight: ["400", "700"], + display: "swap", +}); export const metadata: Metadata = { title: "DOT UI kit", @@ -22,13 +23,10 @@ export default function RootLayout({ }>) { return ( - + -
- {children} -
+ +
{children}
diff --git a/challenge-3-xcm/frontend/app/page.tsx b/challenge-3-xcm/frontend/app/page.tsx index fb01185..c64dd29 100644 --- a/challenge-3-xcm/frontend/app/page.tsx +++ b/challenge-3-xcm/frontend/app/page.tsx @@ -102,7 +102,24 @@ export default function Home() {
- Maintained by buildstation.org with support from OpenGuild + Maintained by{" "} + + buildstation.org + {" "} + with support from{" "} + + OpenGuild +
diff --git a/challenge-3-xcm/frontend/components/create-wallet-dialog.tsx b/challenge-3-xcm/frontend/components/create-wallet-dialog.tsx new file mode 100644 index 0000000..d154a97 --- /dev/null +++ b/challenge-3-xcm/frontend/components/create-wallet-dialog.tsx @@ -0,0 +1,154 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useAtom } from "jotai"; +import { createWalletDialogOpenAtom } from "@/lib/atoms"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogFooter, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { KeyRound, Ban, ExternalLink, LockKeyhole, Shield } from "lucide-react"; +import Image from "next/image"; +import { + createSigpassWallet, + checkBrowserWebAuthnSupport, +} from "@/lib/sigpass"; + +/** + * Globally mounted dialog that lets users generate a Sigpass wallet. + * Because it's outside the drawer hierarchy, it survives when the drawer unmounts. + */ +export default function CreateWalletDialog() { + const [open, setOpen] = useAtom(createWalletDialogOpenAtom); + const [webAuthn, setWebAuthn] = useState(false); + + useEffect(() => { + setWebAuthn(checkBrowserWebAuthnSupport()); + }, []); + + const handleCreate = async () => { + await createSigpassWallet("dapp"); + // Dialog closes only after successful creation + setOpen(false); + }; + + return ( + + + {/* Header with gradient background */} +
+ + + + Create Wallet + + + Instantly get a wallet secured by  + + Passkey + + + +
+ +
+
+

+ What is a Wallet? +

+ +
+
+ Digital assets icon +
+
+

+ A Home for your Digital Assets +

+

+ Wallets are used to send, receive, store, and display digital + assets like Polkadot and NFTs. +

+
+
+ +
+
+ Login icon +
+
+

A new way to Log In

+

+ Instead of creating new accounts and passwords on every + website, just connect your wallet. +

+
+
+
+ + + + Learn more + + + {webAuthn ? ( + + ) : ( + + )} + +
+ +
+

+ Powered by  + + Sigpass + +

+
+
+
+ ); +} diff --git a/challenge-3-xcm/frontend/components/navbar.tsx b/challenge-3-xcm/frontend/components/navbar.tsx index d212169..125a86a 100644 --- a/challenge-3-xcm/frontend/components/navbar.tsx +++ b/challenge-3-xcm/frontend/components/navbar.tsx @@ -1,32 +1,226 @@ +"use client"; + +import { useState } from "react"; import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { + Home, + Wallet as WalletIcon, + Send, + PenLine, + Coins, + Boxes, + ChevronDown, + Menu, + Sparkles, + Shield, + LockKeyhole, + ArrowRight, +} from "lucide-react"; +import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; +import { cn } from "@/lib/utils"; +import SigpassKit from "@/components/sigpasskit"; +import { + Drawer, + DrawerTrigger, + DrawerContent, + DrawerClose, +} from "@/components/ui/drawer"; +import { Button } from "@/components/ui/button"; +import CreateWalletDialog from "@/components/create-wallet-dialog"; + +const primaryNav = [ + { href: "/", label: "Home", icon: Home }, + { href: "/crosschainNFT", label: "XCM", icon: Boxes }, +]; export default function Navbar() { + const pathname = usePathname(); + const [walletDrawerOpen, setWalletDrawerOpen] = useState(false); + return ( -
- - Home - - - Wallet - - - Send transaction - - - Write contract - - +
+
+
+
+ πŸ”— +
+ +
+

+ XCM NFT Bridge +

+

+ Cross-chain NFT minting & transfers +

+
+ +
+ {/* Desktop nav */} + + +
+ {/* Wallet drawer */} + + + + + + { + // Close the wallet drawer whenever any button inside is clicked + const target = e.target as HTMLElement; + if (target.closest("button")) setWalletDrawerOpen(false); + }} + className="border-0 bg-white dark:bg-gray-900 shadow-2xl overflow-hidden p-0" + > + {/* Header with gradient */} +
+
+
+
+
+ +
+
+

+ Secure Wallet +

+

+ Create or connect a passkey wallet +

+
+
+ + {/* Main content with negative margin to overlap with header */} +
+
+ +
+ + + + +
+ + {/* Footer attribution */} +
+

+ Powered by the Polkadot ecosystem +

+
+
+
+ + {/* Mobile menu */} + +
+
+
+ + {/* Mount the global create-wallet dialog once, outside the header */} + + + ); +} + +/* -------------------------------------------------------------------------- */ +/* Helper components */ +/* -------------------------------------------------------------------------- */ + +function NavItem({ + href, + active, + children, +}: { + href: string; + active: boolean; + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} + +function DropdownLink({ + href, + external = false, + children, +}: { + href: string; + external?: boolean; + children: React.ReactNode; +}) { + const common = + "flex items-center rounded-sm px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground"; + return external ? ( + + + {children} + + + ) : ( + + {children} + + ); +} + +function MobileMenu({ pathname }: { pathname: string | null }) { + return ( + + + + + - Mint/Redeem LST Bifrost - -
+ {primaryNav.map(({ href, label }) => ( + + {label} + + ))} + + + Mint / Redeem Bifrost + + + ); } diff --git a/challenge-3-xcm/frontend/components/xcm/NFT.tsx b/challenge-3-xcm/frontend/components/xcm/NFT.tsx new file mode 100644 index 0000000..4df3880 --- /dev/null +++ b/challenge-3-xcm/frontend/components/xcm/NFT.tsx @@ -0,0 +1,622 @@ +import React, { useState, useEffect } from "react"; +import { + Upload, + Wallet, + ArrowRight, + CheckCircle, + AlertCircle, + ExternalLink, + Loader2, + Image, + Send, + Eye, + History, +} from "lucide-react"; + +const XCMNFTApp = () => { + const [activeTab, setActiveTab] = useState("mint"); + const [walletConnected, setWalletConnected] = useState(false); + const [account, setAccount] = useState(null); + const [balances, setBalances] = useState({ assetHub: "0", parachain: "0" }); + const [nftData, setNftData] = useState({ + name: "", + description: "", + image: null, + }); + const [transferData, setTransferData] = useState({ + nftId: "", + targetChain: "moonbeam", + }); + const [transactions, setTransactions] = useState([]); + const [loading, setLoading] = useState(false); + const [mintedNFTs, setMintedNFTs] = useState([]); + + // Mock data for demonstration + const chains = [ + { id: "asset-hub", name: "Asset Hub", color: "bg-blue-500" }, + { id: "moonbeam", name: "Moonbeam", color: "bg-purple-500" }, + { id: "astar", name: "Astar", color: "bg-green-500" }, + { id: "acala", name: "Acala", color: "bg-red-500" }, + ]; + + const mockNFTs = [ + { + id: "001", + name: "Polkadot Gem #1", + chain: "asset-hub", + status: "minted", + image: "🎨", + }, + { + id: "002", + name: "Cross Chain Art", + chain: "moonbeam", + status: "transferred", + image: "πŸ–ΌοΈ", + }, + { + id: "003", + name: "XCM Wonder", + chain: "asset-hub", + status: "minted", + image: "✨", + }, + ]; + + const mockTransactions = [ + { + id: "1", + type: "mint", + nft: "Polkadot Gem #1", + from: "Asset Hub", + to: "Asset Hub", + status: "success", + hash: "0x123...abc", + }, + { + id: "2", + type: "transfer", + nft: "Cross Chain Art", + from: "Asset Hub", + to: "Moonbeam", + status: "pending", + hash: "0x456...def", + }, + { + id: "3", + type: "transfer", + nft: "XCM Wonder", + from: "Asset Hub", + to: "Astar", + status: "success", + hash: "0x789...ghi", + }, + ]; + + useEffect(() => { + setMintedNFTs(mockNFTs); + setTransactions(mockTransactions); + }, []); + + const connectWallet = async () => { + setLoading(true); + setTimeout(() => { + setWalletConnected(true); + setAccount("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"); + setBalances({ assetHub: "125.4", parachain: "89.2" }); + setLoading(false); + }, 2000); + }; + + const mintNFT = async () => { + if (!nftData.name || !nftData.description) return; + + setLoading(true); + const newTransaction = { + id: Date.now().toString(), + type: "mint", + nft: nftData.name, + from: "Asset Hub", + to: "Asset Hub", + status: "pending", + hash: `0x${Math.random().toString(16).substr(2, 8)}...${Math.random() + .toString(16) + .substr(2, 3)}`, + }; + + setTransactions((prev) => [newTransaction, ...prev]); + + setTimeout(() => { + const newNFT = { + id: (mintedNFTs.length + 1).toString().padStart(3, "0"), + name: nftData.name, + chain: "asset-hub", + status: "minted", + image: nftData.image ? "πŸ–ΌοΈ" : "🎨", + }; + + setMintedNFTs((prev) => [newNFT, ...prev]); + setTransactions((prev) => + prev.map((tx) => + tx.id === newTransaction.id ? { ...tx, status: "success" } : tx + ) + ); + setNftData({ name: "", description: "", image: null }); + setLoading(false); + }, 3000); + }; + + const transferNFT = async () => { + if (!transferData.nftId || !transferData.targetChain) return; + + setLoading(true); + const nft = mintedNFTs.find((n) => n.id === transferData.nftId); + const targetChainName = chains.find( + (c) => c.id === transferData.targetChain + )?.name; + + const newTransaction = { + id: Date.now().toString(), + type: "transfer", + nft: nft?.name || "Unknown NFT", + from: "Asset Hub", + to: targetChainName, + status: "pending", + hash: `0x${Math.random().toString(16).substr(2, 8)}...${Math.random() + .toString(16) + .substr(2, 3)}`, + }; + + setTransactions((prev) => [newTransaction, ...prev]); + + setTimeout(() => { + setMintedNFTs((prev) => + prev.map((nft) => + nft.id === transferData.nftId + ? { ...nft, chain: transferData.targetChain, status: "transferred" } + : nft + ) + ); + setTransactions((prev) => + prev.map((tx) => + tx.id === newTransaction.id ? { ...tx, status: "success" } : tx + ) + ); + setTransferData({ nftId: "", targetChain: "moonbeam" }); + setLoading(false); + }, 4000); + }; + + const handleImageUpload = (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + setNftData((prev) => ({ ...prev, image: e.target.result })); + }; + reader.readAsDataURL(file); + } + }; + + return ( +
+
+ {/* Navigation Tabs */} +
+ {[ + { id: "mint", label: "Mint NFT", icon: Image }, + { id: "transfer", label: "Transfer NFT", icon: Send }, + { id: "gallery", label: "My NFTs", icon: Eye }, + { id: "history", label: "History", icon: History }, + ].map((tab) => { + const Icon = tab.icon; + return ( + + ); + })} +
+ + {/* Main Content */} +
+ {/* Main Panel */} +
+ {activeTab === "mint" && ( +
+

+ Mint NFT on Asset Hub +

+ +
+
+ + + setNftData((prev) => ({ + ...prev, + name: e.target.value, + })) + } + className="w-full bg-white/5 border border-white/20 rounded-lg px-4 py-3 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500" + placeholder="Enter NFT name" + /> +
+ +
+ +