tr]:last:border-b-0", className)}
38 | {...props}
39 | />
40 | )
41 | }
42 |
43 | function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
44 | return (
45 |
53 | )
54 | }
55 |
56 | function TableHead({ className, ...props }: React.ComponentProps<"th">) {
57 | return (
58 | [role=checkbox]]:translate-y-[2px]",
62 | className,
63 | )}
64 | {...props}
65 | />
66 | )
67 | }
68 |
69 | function TableCell({ className, ...props }: React.ComponentProps<"td">) {
70 | return (
71 | | [role=checkbox]]:translate-y-[2px]",
75 | className,
76 | )}
77 | {...props}
78 | />
79 | )
80 | }
81 |
82 | function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
83 | return (
84 |
89 | )
90 | }
91 |
92 | export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
93 |
--------------------------------------------------------------------------------
/frontend/src/components/web3/account-select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import type { WalletAccount } from "@reactive-dot/core/wallets.js"
4 | import { useAccounts, useConnectedWallets, useWalletDisconnector } from "@reactive-dot/react"
5 | import { useCallback, useEffect } from "react"
6 | import { toast } from "sonner"
7 | import { buttonVariants } from "../ui/button-extended"
8 | import {
9 | Select,
10 | SelectContent,
11 | SelectGroup,
12 | SelectItem,
13 | SelectLabel,
14 | SelectSeparator,
15 | SelectTrigger,
16 | SelectValue,
17 | } from "../ui/select"
18 | import { ConnectButton } from "./connect-button"
19 |
20 | interface AccountSelectProps {
21 | account?: WalletAccount
22 | setAccount: (account?: WalletAccount) => void
23 | }
24 | export function AccountSelect({ account, setAccount }: AccountSelectProps) {
25 | const accounts = useAccounts()
26 | const connectedWallets = useConnectedWallets()
27 | const disconnectWallet = useWalletDisconnector()[1]
28 |
29 | useEffect(() => {
30 | if (account || !accounts?.length) return
31 | setAccount(accounts[0])
32 | }, [accounts])
33 |
34 | const handleDisconnect = useCallback(async () => {
35 | if (!connectedWallets?.length) return
36 |
37 | const disconnectAllWallets = Promise.all(
38 | connectedWallets.map((wallet) => disconnectWallet(wallet)),
39 | )
40 | toast.promise(disconnectAllWallets, {
41 | loading: "Disconnecting from wallet...",
42 | success: "Wallet disconnected",
43 | error: "Failed to disconnect from wallet",
44 | })
45 | }, [disconnectWallet, connectedWallets])
46 |
47 | const handleValueChange = useCallback(
48 | async (value: "disconnect" | string) => {
49 | if (value === "disconnect") {
50 | await handleDisconnect()
51 | setAccount(undefined)
52 | return
53 | }
54 |
55 | const account = accounts?.find((account) => account.address === value)
56 | if (account) setAccount(account)
57 | },
58 | [accounts, setAccount],
59 | )
60 |
61 | if (!account) {
62 | return
63 | }
64 |
65 | return (
66 |
89 | )
90 | }
91 |
--------------------------------------------------------------------------------
/frontend/src/components/web3/connect-button.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import type { Wallet } from "@reactive-dot/core/wallets.js"
4 | import {
5 | useAccounts,
6 | useConnectedWallets,
7 | useWalletConnector,
8 | useWalletDisconnector,
9 | useWallets,
10 | } from "@reactive-dot/react"
11 | import { LinkIcon, UnlinkIcon, WalletIcon } from "lucide-react"
12 | import { useCallback } from "react"
13 | import { toast } from "sonner"
14 | import { Button } from "../ui/button-extended"
15 | import {
16 | DropdownMenu,
17 | DropdownMenuContent,
18 | DropdownMenuItem,
19 | DropdownMenuTrigger,
20 | } from "../ui/dropdown-menu"
21 |
22 | export function ConnectButton() {
23 | const accounts = useAccounts()
24 | const wallets = useWallets()
25 | const connectedWallets = useConnectedWallets()
26 |
27 | const connectWallet = useWalletConnector()[1]
28 | const disconnectWallet = useWalletDisconnector()[1]
29 |
30 | const handleConnect = useCallback(
31 | async (wallet?: Wallet) => {
32 | if (!wallets?.length) return
33 |
34 | toast.promise(connectWallet(wallet ?? wallets[0]), {
35 | loading: "Connecting to wallet...",
36 | success: "Wallet connected",
37 | error: "Failed to connect to wallet",
38 | })
39 | },
40 | [connectWallet, wallets],
41 | )
42 |
43 | const handleDisconnect = useCallback(async () => {
44 | if (!connectedWallets?.length) return
45 |
46 | const disconnectAllWallets = Promise.all(
47 | connectedWallets.map((wallet) => disconnectWallet(wallet)),
48 | )
49 | toast.promise(disconnectAllWallets, {
50 | loading: "Disconnecting from wallet...",
51 | success: "Wallet disconnected",
52 | error: "Failed to disconnect from wallet",
53 | })
54 | }, [disconnectWallet, connectedWallets])
55 |
56 | if (accounts?.length > 0) {
57 | return (
58 |
66 | )
67 | }
68 |
69 | return (
70 |
71 |
72 |
75 |
76 |
77 |
81 | {!wallets?.length && No wallets found}
82 |
83 | {wallets.map((wallet) => (
84 | handleConnect(wallet)}
87 | className="justify-center rounded-lg"
88 | >
89 | {wallet.name}
90 |
91 | ))}
92 |
93 |
94 | )
95 | }
96 |
--------------------------------------------------------------------------------
/frontend/src/components/web3/contract-card.tsx:
--------------------------------------------------------------------------------
1 | import { useChainId, useContractMutation, useLazyLoadQuery, useStore } from "@reactive-dot/react"
2 | import { startTransition, use } from "react"
3 | import { finalize } from "rxjs"
4 | import { useIsMapped } from "@/hooks/use-is-mapped"
5 | import { flipper } from "@/lib/inkathon/deployments"
6 | import { flipperContract } from "@/lib/reactive-dot/contracts"
7 | import { submitTxAndToast } from "@/lib/reactive-dot/submit-tx-and-toast"
8 | import { Button } from "../ui/button-extended"
9 | import { Card, CardHeader, CardTitle } from "../ui/card"
10 | import { Table, TableBody, TableCell, TableRow } from "../ui/table"
11 | import { accountContext } from "./account-provider"
12 |
13 | export function ContractCard() {
14 | const chain = useChainId()
15 |
16 | return (
17 |
18 |
19 | Flipper Contract
20 | {use(accountContext) !== undefined && }
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Address
29 | {flipper.evmAddresses[chain]}
30 |
31 |
32 |
33 | Language
34 | {flipper.contract.metadata?.source.language}
35 |
36 |
37 |
38 | Compiler
39 | {flipper.contract.metadata?.source.compiler}
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | function FlipStatus() {
48 | const chain = useChainId()
49 | const state = useLazyLoadQuery((query) =>
50 | query.contract(flipperContract, flipper.evmAddresses[chain], (query) => query.message("get")),
51 | )
52 |
53 | return (
54 |
55 | Flip State
56 | {state ? "True" : "False"}
57 |
58 | )
59 | }
60 |
61 | function FlipTx() {
62 | const chain = useChainId()
63 | const isMapped = useIsMapped()
64 |
65 | const store = useStore()
66 | const [_, flip] = useContractMutation((mutate) =>
67 | mutate(flipperContract, flipper.evmAddresses[chain], "flip"),
68 | )
69 |
70 | return (
71 |
99 | )
100 | }
101 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/button-extended.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * IMPORTANT: This file is modified from the original shadcn/ui file.
3 | * DO NOT OVERWRITE IT WITH THE CLI.
4 | */
5 |
6 | import { Slot } from "@radix-ui/react-slot"
7 | import { cva, type VariantProps } from "class-variance-authority"
8 | import { LoaderIcon } from "lucide-react"
9 | import * as React from "react"
10 | import { cn } from "@/lib/utils"
11 |
12 | const buttonOuterVariants = cva(
13 | "relative inline-flex cursor-pointer select-none items-center justify-center whitespace-nowrap font-medium text-sm ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
14 | {
15 | variants: {
16 | variant: {
17 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
18 | destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
19 | outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
20 | secondary:
21 | "bg-foreground/5 text-foreground hover:bg-foreground/15 dark:bg-foreground/10 dark:hover:bg-foreground/15",
22 | ghost: "hover:bg-accent hover:text-accent-foreground",
23 | link: "text-primary decoration-inherit underline-offset-2 hover:underline",
24 | glass:
25 | "!bg-foreground/25 hover:!bg-foreground/30 border border-foreground/10 shadow-lg ring-1 ring-foreground/10 ring-offset-2 ring-offset-background hover:ring-2 hover:ring-foreground/20",
26 | },
27 | size: {
28 | sm: "h-8 rounded-lg px-3",
29 | default: "h-10 rounded-lg px-4 py-2",
30 | lg: "h-11 rounded-xl px-5",
31 | xl: "h-12 rounded-2xl px-9 text-base",
32 | icon: "h-10 w-10 rounded-md",
33 | },
34 | },
35 | defaultVariants: {
36 | variant: "default",
37 | size: "default",
38 | },
39 | },
40 | )
41 |
42 | const buttonInnerVariants = cva("inline-flex items-center justify-center gap-2", {
43 | variants: {
44 | isLoading: {
45 | true: "pointer-events-none opacity-0",
46 | },
47 | },
48 | })
49 |
50 | const buttonVariants = (props?: Parameters[0]) =>
51 | cn(buttonOuterVariants(props), buttonInnerVariants())
52 |
53 | export interface ButtonProps
54 | extends React.ButtonHTMLAttributes,
55 | VariantProps {
56 | asChild?: boolean
57 | isLoading?: boolean
58 | loadingText?: string
59 | }
60 |
61 | const Button = React.forwardRef(
62 | (
63 | {
64 | className,
65 | variant,
66 | size,
67 | asChild = false,
68 | isLoading = false,
69 | loadingText,
70 | children,
71 | disabled = false,
72 | ...props
73 | },
74 | ref,
75 | ) => {
76 | const Comp = asChild ? Slot : "button"
77 | return (
78 |
84 |
85 | {children}
86 |
87 | {/* Loading Spinner */}
88 | {isLoading && (
89 |
90 |
91 | {!!loadingText && loadingText}
92 |
93 | )}
94 |
95 |
96 | )
97 | },
98 | )
99 | Button.displayName = "Button"
100 |
101 | export { Button, buttonVariants }
102 |
--------------------------------------------------------------------------------
/docs/content/docs/guides/add-network.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Add a Custom Network
3 | description: Integrate new Polkadot chains into your inkathon project
4 | ---
5 |
6 | import { Steps, Step } from 'fumadocs-ui/components/steps'
7 | import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
8 | import { File, Folder, Files } from 'fumadocs-ui/components/files'
9 |
10 | ## Prerequisites
11 |
12 | - Working inkathon project
13 | - [Set up](/guides/contract-development) environment for contract development
14 | - Access to the network's RPC endpoint
15 | - Account with funds on the target network (for deployments)
16 |
17 |
18 | Make sure the respective network supports pallet-revive which is mandatory to support ink! v6
19 | contracts. Find currently supported networks in the [use.ink
20 | documentation](https://use.ink/docs/v6/where-to-deploy).
21 |
22 |
23 | ## Step-by-Step Guide
24 |
25 |
26 |
27 |
28 |
29 | ### Generate Type Definitions
30 |
31 | Generate type definitions for the new network using Papi:
32 |
33 | ```bash
34 | # From the contracts directory
35 | cd contracts
36 |
37 | # Add the network (replace with your network details)
38 | bunx polkadot-api add -w
39 |
40 | # Example for Pop Network
41 | bunx polkadot-api add -w wss://rpc1.paseo.popnetwork.xyz pop
42 | ```
43 |
44 | This creates type definitions in `contracts/.papi/descriptors/` that enable type-safe interactions.
45 |
46 |
47 |
48 |
49 |
50 | ### Deploy Contracts
51 |
52 | Now you can deploy the contract to your new network:
53 |
54 | ```bash
55 | CHAIN= bun run deploy
56 | ```
57 |
58 | By default, the deployer account is `//Alice` who is pre-funded on local nodes. You can overwrite this by either passing an `ACCOUNT_URI` variable or defining an `.env.` file (see [environment](/learn/contracts#environment)).
59 |
60 |
61 |
62 |
63 |
64 | ### Configure Frontend
65 |
66 | Update the frontend to connect to your new network and import the freshly deployed contract:
67 |
68 | ```tsx title="frontend/src/lib/reactive-dot/config.ts"
69 | import { newChain } from '@polkadot-api/descriptors' // [!code ++]
70 | // …
71 |
72 | export const config = defineConfig({
73 | chains: {
74 | pop: {
75 | descriptor: pop,
76 | provider: getWsProvider('wss://rpc1.paseo.popnetwork.xyz'),
77 | },
78 | passethub: {
79 | descriptor: passethub,
80 | provider: getWsProvider('wss://testnet-passet-hub.polkadot.io'),
81 | },
82 | newChain: {
83 | descriptor: newChain, // [!code ++]
84 | provider: getWsProvider('TODO'), // [!code ++]
85 | },
86 | },
87 | // …
88 | })
89 | ```
90 |
91 | ```tsx title="frontend/src/lib/inkathon/deployments.ts"
92 | import * as flipperNewChain from 'contracts/deployments/flipper/newChain' // [!code ++]
93 | // …
94 |
95 | export const flipper = {
96 | contract: contracts.flipper,
97 | evmAddresses: {
98 | pop: flipperPop.evmAddress,
99 | passethub: flipperPassethub.evmAddress,
100 | newChain: flipperNewChain.evmAddress, // [!code ++]
101 | },
102 | ss58Addresses: {
103 | pop: flipperPop.ss58Address,
104 | passethub: flipperPassethub.ss58Address,
105 | newChain: flipperNewChain.ss58Address, // [!code ++]
106 | },
107 | }
108 | ```
109 |
110 |
111 |
112 |
113 |
114 | ## Learn More
115 |
116 |
117 |
122 |
127 |
132 |
137 |
138 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Multi-stage Dockerfile for inkathon
2 | # syntax=docker/dockerfile:1
3 |
4 | # Base image: Alpine Linux with Bun runtime and Node.js 22
5 | # Alpine chosen for smaller image size and security benefits
6 | FROM imbios/bun-node:latest-22-alpine AS base
7 |
8 | # Install bash (required for build scripts)
9 | RUN apk add --no-cache bash
10 |
11 | # ===============================================
12 | # STAGE 1: Dependencies Installation
13 | # Only rebuild this layer when package files change
14 | # ===============================================
15 | FROM base AS deps
16 | WORKDIR /app
17 |
18 | # Note: libc6-compat may be needed for some Node.js packages on Alpine
19 | # Uncomment the line below if you encounter compatibility issues
20 | # See: https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine
21 | # RUN apk add --no-cache libc6-compat
22 |
23 | # Copy all files
24 | COPY . .
25 |
26 | # Install dependencies with frozen lockfiles to ensure reproducible builds
27 | # Automatically detect and use the appropriate package manager
28 | RUN \
29 | if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
30 | elif [ -f package-lock.json ]; then npm ci; \
31 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
32 | elif [ -f bun.lockb ]; then bun install; \
33 | elif [ -f bun.lock ]; then bun install; \
34 | else echo "Lockfile not found." && exit 1; \
35 | fi
36 |
37 | # ===============================================
38 | # STAGE 2: Application Builder
39 | # Build the Next.js application with optimizations
40 | # ===============================================
41 | FROM base AS builder
42 | WORKDIR /app
43 |
44 | # Copy everything from the deps stage
45 | COPY --from=deps /app .
46 |
47 | # Disable Next.js telemetry for privacy and faster builds
48 | ENV NEXT_TELEMETRY_DISABLED=1
49 |
50 | # Build the application using the standalone output feature
51 | # This creates a minimal production bundle for optimal performance
52 | RUN \
53 | if [ -f yarn.lock ]; then yarn run build:standalone; \
54 | elif [ -f package-lock.json ]; then npm run build:standalone; \
55 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build:standalone; \
56 | elif [ -f bun.lockb ]; then bun run build:standalone; \
57 | elif [ -f bun.lock ]; then bun run build:standalone; \
58 | else echo "Lockfile not found." && exit 1; \
59 | fi
60 |
61 |
62 | # ===============================================
63 | # STAGE 3: Production Runtime
64 | # Minimal production image with only necessary files
65 | # ===============================================
66 | FROM base AS runner
67 | WORKDIR /app
68 |
69 | # Install curl for health checks (required for deployment platforms like Coolify)
70 | # Update package index and install curl without caching to minimize image size
71 | RUN apk update && apk add --no-cache curl
72 |
73 | # Copy static assets from the builder stage
74 | COPY --from=builder /app/frontend/public ./frontend/public
75 |
76 | # Create system user and group for security best practices
77 | # Use specific UIDs to avoid conflicts and ensure consistency across environments
78 | RUN addgroup --system --gid 7294 nodejs \
79 | && adduser --system --uid 7294 nextjs \
80 | && mkdir -p frontend/.next \
81 | && chown nextjs:nodejs frontend/.next
82 |
83 | # Copy the standalone Next.js build output with proper ownership
84 | # Standalone output includes only necessary files, reducing image size significantly
85 | # See: https://nextjs.org/docs/advanced-features/output-file-tracing
86 | COPY --from=builder --chown=nextjs:nodejs /app/frontend/.next/standalone ./
87 | COPY --from=builder --chown=nextjs:nodejs /app/frontend/.next/static ./frontend/.next/static
88 |
89 | # Switch to non-root user for security
90 | USER nextjs
91 |
92 | # Environment variables for production configuration
93 | ENV NEXT_TELEMETRY_DISABLED=1
94 | ENV NODE_ENV=production
95 | ENV HOSTNAME="0.0.0.0"
96 | ENV PORT=3000
97 |
98 | # Expose the port that the application will run on
99 | EXPOSE 3000
100 |
101 | # Start the Next.js server using the standalone build executable
102 | CMD ["node", "frontend/server.js"]
103 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
3 | "files": {
4 | "ignoreUnknown": true,
5 | "includes": [
6 | "**",
7 | "!**/contracts/deployments",
8 | "!**/contracts/.papi",
9 | "!**/node_modules",
10 | "!**/dist"
11 | ]
12 | },
13 | "vcs": {
14 | "enabled": true,
15 | "clientKind": "git",
16 | "useIgnoreFile": true
17 | },
18 | "formatter": {
19 | "enabled": true,
20 | "indentStyle": "space",
21 | "formatWithErrors": true,
22 | "indentWidth": 2,
23 | "lineWidth": 100
24 | },
25 | "javascript": {
26 | "formatter": {
27 | "quoteStyle": "double",
28 | "trailingCommas": "all",
29 | "semicolons": "asNeeded"
30 | }
31 | },
32 | "html": {
33 | "formatter": {
34 | "enabled": true
35 | }
36 | },
37 | "css": {
38 | "formatter": {
39 | "enabled": false
40 | },
41 | "parser": {
42 | "tailwindDirectives": true
43 | }
44 | },
45 | "assist": {
46 | "actions": {
47 | "source": {
48 | "organizeImports": "on"
49 | }
50 | }
51 | },
52 | "linter": {
53 | "enabled": true,
54 | "rules": {
55 | "style": {
56 | "noUnusedTemplateLiteral": "off",
57 | "useImportType": "warn",
58 | "useLiteralEnumMembers": "error",
59 | "useNodejsImportProtocol": "error",
60 | "useAsConstAssertion": "error",
61 | "useEnumInitializers": "error",
62 | "useSelfClosingElements": "error",
63 | "useConst": "error",
64 | "useSingleVarDeclarator": "error",
65 | "useNumberNamespace": "error",
66 | "noInferrableTypes": "error",
67 | "useExponentiationOperator": "error",
68 | "noParameterAssign": "off",
69 | "noNonNullAssertion": "error",
70 | "useDefaultParameterLast": "error",
71 | "useExportType": "error",
72 | "noUselessElse": "error",
73 | "useShorthandFunctionType": "error",
74 | "useTemplate": {
75 | "level": "warn",
76 | "fix": "safe"
77 | },
78 | "noRestrictedImports": {
79 | "level": "error",
80 | "options": {
81 | "paths": {
82 | "process": "Use '@/env/(client|server)' instead",
83 | "next/router": "Use 'next/navigation' instead",
84 | "zod": "Use 'zod/v4' instead",
85 | "dayjs": "Use 'date-fns' instead",
86 | "@/components/originui/dialog": "If no good reason, use '@/components/ui/dialog' instead",
87 | "@/components/originui/button": "If no good reason, use '@/components/ui/button' instead",
88 | "@/components/originui/badge": "If no good reason, use '@/components/ui/badge' instead"
89 | }
90 | }
91 | }
92 | },
93 | "a11y": {
94 | "useAriaPropsForRole": "off",
95 | "useFocusableInteractive": "off",
96 | "useSemanticElements": "off",
97 | "noRedundantRoles": "off",
98 | "noSvgWithoutTitle": "off",
99 | "useKeyWithClickEvents": "off",
100 | "noLabelWithoutControl": "off"
101 | },
102 | "correctness": {
103 | "useExhaustiveDependencies": "off",
104 | "noChildrenProp": "off",
105 | "noUnusedImports": {
106 | "level": "warn",
107 | "fix": "safe"
108 | },
109 | "noUnusedFunctionParameters": "off",
110 | "noUnusedVariables": "off"
111 | },
112 | "nursery": {
113 | "useSortedClasses": {
114 | "level": "info",
115 | "fix": "safe",
116 | "options": {
117 | "functions": ["clsx", "cva", "tw"]
118 | }
119 | }
120 | },
121 | "complexity": {
122 | "noForEach": "off",
123 | "noBannedTypes": "off",
124 | "noImportantStyles": "off",
125 | "noUselessFragments": {
126 | "fix": "safe"
127 | }
128 | },
129 | "suspicious": {
130 | "noArrayIndexKey": "off",
131 | "noDocumentCookie": "off",
132 | "noExplicitAny": "error",
133 | "noUnknownAtRules": "off"
134 | },
135 | "security": {
136 | "noDangerouslySetInnerHtml": "off"
137 | },
138 | "performance": {
139 | "noAccumulatingSpread": "off"
140 | }
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/docs/content/docs/resources/commands.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Command Cheat Sheet
3 | description: Comprehensive list of commands for the perfect development workflow for inkathon projects
4 | ---
5 |
6 | ## Project Setup
7 |
8 | | Command | Description |
9 | | --------------------------------------- | --------------------------- |
10 | | `npx create-inkathon-app@latest{:bash}` | Create new inkathon project |
11 |
12 | ## Global Commands
13 |
14 | Execute from the project root:
15 |
16 | | Command | Description | Category |
17 | | --------------------------------- | ------------------------------------------- | ------------ |
18 | | `bun run dev{:bash}` | Start frontend development server | Development |
19 | | `bun run node{:bash}` | Run local ink-node | Development |
20 | | `bun run dev-and-node{:bash}` | Run frontend and node concurrently | Development |
21 | | `bun run codegen{:bash}` | Generate TypeScript types from contracts | Production |
22 | | `bun run build{:bash}` | Build production frontend with codegen | Production |
23 | | `bun run build:standalone{:bash}` | Build self-hosted production frontend | Production |
24 | | `bun run start{:bash}` | Start production server | Production |
25 | | `bun run lint{:bash}` | Run linter checks across all workspaces | Code Quality |
26 | | `bun run lint:fix{:bash}` | Auto-fix linting issues in all workspaces | Code Quality |
27 | | `bun run typecheck{:bash}` | TypeScript type checking for all workspaces | Code Quality |
28 | | `bun run clean{:bash}` | Remove build artifacts from all workspaces | Maintenance |
29 | | `bun run clean-install{:bash}` | Remove all node_modules and reinstall | Maintenance |
30 | | `bun run update{:bash}` | Interactive dependency updates | Maintenance |
31 |
32 | ## Frontend Commands
33 |
34 | Execute from `/frontend` directory or from root with `bun run -F frontend `:
35 |
36 | | Command | Description | Category |
37 | | --------------------------------- | ----------------------------------- | ------------ |
38 | | `bun run dev{:bash}` | Start Next.js dev server with Turbo | Development |
39 | | `bun run build{:bash}` | Build for production with Turbo | Production |
40 | | `bun run build:standalone{:bash}` | Build self-hosted production build | Production |
41 | | `bun run start{:bash}` | Start production server | Production |
42 | | `bun run lint{:bash}` | Run Biome & Prettier checks | Code Quality |
43 | | `bun run lint:fix{:bash}` | Auto-fix Biome & Prettier issues | Code Quality |
44 | | `bun run typecheck{:bash}` | TypeScript type checking | Code Quality |
45 | | `bun run clean{:bash}` | Remove .next and build artifacts | Maintenance |
46 |
47 | ## Contract Commands
48 |
49 | Execute from `/contracts` directory or from root with `bun run -F contracts `:
50 |
51 | | Command | Description | Category |
52 | | ------------------------------------------ | ----------------------------------- | ------------ |
53 | | `bun run node{:bash}` | Start local ink-node with dev chain | Development |
54 | | `bun run build{:bash}` | Build all contracts in `/src` | Build |
55 | | `bun run test{:bash}` | Run unit tests for all contracts | Testing |
56 | | `bun run codegen{:bash}` | Generate TypeScript types with Papi | Build |
57 | | `bun run deploy{:bash}` | Deploy on local dev chain (default) | Deployment |
58 | | `CHAIN= bun run deploy{:bash}` | Deploy contract on `` | Deployment |
59 | | `ACCOUNT_URI= bun run deploy{:bash}` | Deploy contract with `` | Deployment |
60 | | `bun run lint{:bash}` | Run Biome & Prettier checks | Code Quality |
61 | | `bun run lint:fix{:bash}` | Auto-fix Biome & Prettier issues | Code Quality |
62 | | `bun run typecheck{:bash}` | TypeScript type checking | Code Quality |
63 | | `bun run clean{:bash}` | Remove target and build artifacts | Maintenance |
64 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as DialogPrimitive from "@radix-ui/react-dialog"
4 | import { XIcon } from "lucide-react"
5 | import type * as React from "react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | function Dialog({ ...props }: React.ComponentProps) {
10 | return
11 | }
12 |
13 | function DialogTrigger({ ...props }: React.ComponentProps) {
14 | return
15 | }
16 |
17 | function DialogPortal({ ...props }: React.ComponentProps) {
18 | return
19 | }
20 |
21 | function DialogClose({ ...props }: React.ComponentProps) {
22 | return
23 | }
24 |
25 | function DialogOverlay({
26 | className,
27 | ...props
28 | }: React.ComponentProps) {
29 | return (
30 |
38 | )
39 | }
40 |
41 | function DialogContent({
42 | className,
43 | children,
44 | showCloseButton = true,
45 | ...props
46 | }: React.ComponentProps & {
47 | showCloseButton?: boolean
48 | }) {
49 | return (
50 |
51 |
52 |
60 | {children}
61 | {showCloseButton && (
62 |
66 |
67 | Close
68 |
69 | )}
70 |
71 |
72 | )
73 | }
74 |
75 | function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
76 | return (
77 |
82 | )
83 | }
84 |
85 | function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
86 | return (
87 |
92 | )
93 | }
94 |
95 | function DialogTitle({ className, ...props }: React.ComponentProps) {
96 | return (
97 |
102 | )
103 | }
104 |
105 | function DialogDescription({
106 | className,
107 | ...props
108 | }: React.ComponentProps) {
109 | return (
110 |
115 | )
116 | }
117 |
118 | export {
119 | Dialog,
120 | DialogClose,
121 | DialogContent,
122 | DialogDescription,
123 | DialogFooter,
124 | DialogHeader,
125 | DialogOverlay,
126 | DialogPortal,
127 | DialogTitle,
128 | DialogTrigger,
129 | }
130 |
--------------------------------------------------------------------------------
/docs/content/docs/guides/add-contract.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Add a New Contract
3 | description: Create and integrate new ink! smart contracts in your project
4 | ---
5 |
6 | import { Steps, Step } from 'fumadocs-ui/components/steps'
7 | import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
8 |
9 | ## Prerequisites
10 |
11 | - Working inkathon project
12 | - [Set up](/guides/contract-development) environment for contract development
13 | - Local node running (optional)
14 |
15 | ## Step-by-Step Guide
16 |
17 |
18 |
19 |
20 |
21 | ### Create Contract
22 |
23 | Use Pop CLI to scaffold a new contract in the `contracts/src` directory:
24 |
25 | ```bash
26 | cd contracts/src
27 |
28 | # Create a new contract with a template
29 | pop new contract --template
30 |
31 | # Example: Create an ERC20 token
32 | pop new contract my_token --template erc20
33 | ```
34 |
35 |
36 | View all available contract templates:
37 |
38 | ```bash
39 | pop new contract --template
40 | ```
41 |
42 | Common templates include `erc20`, `erc721`, `erc1155`, `dns`, `multisig`, and more.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | ### Update Cargo Configuration
51 |
52 | Add your new contract to the workspace members in `contracts/Cargo.toml`:
53 |
54 | ```toml title="contracts/Cargo.toml"
55 | [workspace]
56 | members = [
57 | "src/flipper",
58 | "src/", # [!code ++]
59 | ]
60 | ```
61 |
62 |
63 |
64 |
65 |
66 | ### Build & Generate Types
67 |
68 | Build the contract and generate TypeScript types:
69 |
70 | ```bash
71 | cd contracts
72 |
73 | # Build all contracts
74 | bun run build
75 |
76 | # Generate TypeScript types with Papi
77 | bun run codegen
78 | ```
79 |
80 | This creates:
81 |
82 | - Contract artifacts in `/contracts/deployments//`
83 | - TypeScript types accessible via `@polkadot-api/descriptors`
84 |
85 |
86 |
87 |
88 |
89 | ### Deploy Contract
90 |
91 | Deploy your contract to the desired chain:
92 |
93 |
94 |
95 | ```bash
96 | # Deploy to local dev chain (default)
97 | CHAIN=dev bun run deploy
98 | ```
99 |
100 |
101 | ```bash
102 | # Deploy to Pop Network
103 | CHAIN=pop bun run deploy
104 | ````
105 |
106 |
107 | ```bash
108 | # Deploy to any configured chain
109 | CHAIN= bun run deploy
110 | ````
111 |
112 |
113 |
114 |
115 | Deployment addresses are exported to `/contracts/deployments//.ts`
116 |
117 |
118 |
119 |
120 |
121 | ### Configure Frontend
122 |
123 | Import and configure your new contract in the frontend:
124 |
125 | ```tsx title="frontend/src/lib/inkathon/deployments.ts"
126 | import { contracts } from '@polkadot-api/descriptors'
127 | import * as newContractDev from 'contracts/deployments/newContract/dev'
128 | import * as newContractPop from 'contracts/deployments/newContract/pop'
129 |
130 | // Add your contract configuration
131 | export const newContract = {
132 | contract: contracts.newContract,
133 | evmAddresses: {
134 | dev: newContractDev.evmAddress,
135 | pop: newContractPop.evmAddress,
136 | },
137 | ss58Addresses: {
138 | dev: newContractDev.ss58Address,
139 | pop: newContractPop.ss58Address,
140 | },
141 | }
142 |
143 | export const deployments = {
144 | newContract,
145 | }
146 | ```
147 |
148 |
149 |
150 |
151 |
152 | ### Use Contract in Frontend
153 |
154 | Create components to interact with your contract:
155 |
156 | ```tsx title="frontend/src/components/web3/contract-card.tsx"
157 | 'use client'
158 |
159 | import { createInkSdk } from '@polkadot-api/sdk-ink'
160 | import { deployments } from '@/lib/inkathon/deployments'
161 | import { useChainId, useClient } from '@reactive-dot/react'
162 |
163 | export function ContractCard() {
164 | const chainId = useChainId()
165 | const client = useClient()
166 |
167 | const queryContract = useCallback(async () => {
168 | if (!chainId) return
169 |
170 | const { contract, evmAddresses } = deployments.newContract
171 |
172 | // Create SDK & contract instance
173 | const sdk = createInkSdk(client)
174 | const contract = sdk.getContract(contract, evmAddresses[chainId])
175 |
176 | // Interact with your contract
177 | // ...
178 | }, [client, chainId])
179 | }
180 | ```
181 |
182 |
183 |
184 |
185 |
186 | ## Learn More
187 |
188 |
189 |
194 |
199 |
204 |
209 |
210 |
--------------------------------------------------------------------------------
/frontend/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @import 'tailwindcss';
2 |
3 | @import 'tw-animate-css';
4 | @plugin "@tailwindcss/typography";
5 |
6 | @custom-variant dark (&:is(.dark *));
7 |
8 | :root {
9 | --radius: 0.8rem;
10 |
11 | --background: oklch(1 0 0);
12 | --foreground: oklch(0.145 0 0);
13 | --card: oklch(1 0 0);
14 | --card-foreground: oklch(0.145 0 0);
15 | --popover: oklch(1 0 0);
16 | --popover-foreground: oklch(0.145 0 0);
17 | --primary: oklch(0.205 0 0);
18 | --primary-foreground: oklch(0.985 0 0);
19 | --secondary: oklch(0.97 0 0);
20 | --secondary-foreground: oklch(0.205 0 0);
21 | --muted: oklch(0.97 0 0);
22 | --muted-foreground: oklch(0.556 0 0);
23 | --accent: oklch(0.97 0 0);
24 | --accent-foreground: oklch(0.205 0 0);
25 | --destructive: oklch(0.577 0.245 27.325);
26 | --border: oklch(0.922 0 0);
27 | --input: oklch(0.922 0 0);
28 |
29 | --ring: oklch(0.708 0 0);
30 | --chart-1: oklch(0.646 0.222 41.116);
31 | --chart-2: oklch(0.6 0.118 184.704);
32 | --chart-3: oklch(0.398 0.07 227.392);
33 | --chart-4: oklch(0.828 0.189 84.429);
34 | --chart-5: oklch(0.769 0.188 70.08);
35 |
36 | --sidebar: oklch(0.985 0 0);
37 | --sidebar-foreground: oklch(0.145 0 0);
38 | --sidebar-primary: oklch(0.205 0 0);
39 | --sidebar-primary-foreground: oklch(0.985 0 0);
40 | --sidebar-accent: oklch(0.97 0 0);
41 | --sidebar-accent-foreground: oklch(0.205 0 0);
42 | --sidebar-border: oklch(0.922 0 0);
43 | --sidebar-ring: oklch(0.708 0 0);
44 | }
45 |
46 | .dark {
47 | --background: oklch(0 0 0);
48 | --foreground: oklch(0.985 0 0);
49 | --card: oklch(0.205 0 0);
50 | --card-foreground: oklch(0.985 0 0);
51 | --popover: oklch(0.205 0 0);
52 | --popover-foreground: oklch(0.985 0 0);
53 | --primary: oklch(0.922 0 0);
54 | --primary-foreground: oklch(0.205 0 0);
55 | --secondary: oklch(0.269 0 0);
56 | --secondary-foreground: oklch(0.985 0 0);
57 | --muted: oklch(0.269 0 0);
58 | --muted-foreground: oklch(0.708 0 0);
59 | --accent: oklch(0.269 0 0);
60 | --accent-foreground: oklch(0.985 0 0);
61 | --destructive: oklch(0.704 0.191 22.216);
62 | --border: oklch(1 0 0 / 10%);
63 | --input: oklch(1 0 0 / 15%);
64 | --ring: oklch(0.556 0 0);
65 |
66 | --chart-1: oklch(0.488 0.243 264.376);
67 | --chart-2: oklch(0.696 0.17 162.48);
68 | --chart-3: oklch(0.769 0.188 70.08);
69 | --chart-4: oklch(0.627 0.265 303.9);
70 | --chart-5: oklch(0.645 0.246 16.439);
71 |
72 | --sidebar: oklch(0.205 0 0);
73 | --sidebar-foreground: oklch(0.985 0 0);
74 | --sidebar-primary: oklch(0.488 0.243 264.376);
75 | --sidebar-primary-foreground: oklch(0.985 0 0);
76 | --sidebar-accent: oklch(0.269 0 0);
77 | --sidebar-accent-foreground: oklch(0.985 0 0);
78 | --sidebar-border: oklch(1 0 0 / 10%);
79 | --sidebar-ring: oklch(0.556 0 0);
80 | }
81 |
82 | @theme inline {
83 | --color-background: var(--background);
84 | --color-foreground: var(--foreground);
85 | --color-ring: var(--ring);
86 | --color-input: var(--input);
87 | --color-border: var(--border);
88 | --color-destructive: var(--destructive);
89 | --color-accent-foreground: var(--accent-foreground);
90 | --color-accent: var(--accent);
91 | --color-muted-foreground: var(--muted-foreground);
92 | --color-muted: var(--muted);
93 | --color-secondary-foreground: var(--secondary-foreground);
94 | --color-secondary: var(--secondary);
95 | --color-primary-foreground: var(--primary-foreground);
96 | --color-primary: var(--primary);
97 | --color-popover-foreground: var(--popover-foreground);
98 | --color-popover: var(--popover);
99 | --color-card-foreground: var(--card-foreground);
100 | --color-card: var(--card);
101 |
102 | --color-chart-5: var(--chart-5);
103 | --color-chart-4: var(--chart-4);
104 | --color-chart-3: var(--chart-3);
105 | --color-chart-2: var(--chart-2);
106 | --color-chart-1: var(--chart-1);
107 |
108 | --color-sidebar: var(--sidebar);
109 | --color-sidebar-ring: var(--sidebar-ring);
110 | --color-sidebar-border: var(--sidebar-border);
111 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
112 | --color-sidebar-accent: var(--sidebar-accent);
113 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
114 | --color-sidebar-primary: var(--sidebar-primary);
115 | --color-sidebar-foreground: var(--sidebar-foreground);
116 |
117 | --radius-sm: calc(var(--radius) - 4px);
118 | --radius-md: calc(var(--radius) - 2px);
119 | --radius-lg: var(--radius);
120 | --radius-xl: calc(var(--radius) + 4px);
121 | --radius-2xl: calc(var(--radius) + 8px);
122 | }
123 |
124 | @layer base {
125 | * {
126 | @apply border-border outline-ring/50;
127 | }
128 | html {
129 | @apply scroll-smooth;
130 | }
131 |
132 | body {
133 | @apply bg-background text-foreground font-sans antialiased;
134 |
135 | @apply [&_*_::selection]:bg-foreground [&_*_::selection]:text-background;
136 |
137 | font-synthesis: none !important;
138 | text-rendering: optimizeLegibility;
139 | }
140 |
141 | /**
142 | * Custom ink!athon Styles
143 | */
144 | body {
145 | @apply bg-gradient-to-b from-background/100 via-background/100 to-[hsl(255deg,50%,20%)] bg-no-repeat bg-center bg-fixed;
146 | }
147 | .inkathon-card {
148 | @apply !min-h-[250px] !bg-gradient-to-b !border !from-foreground/5 !via-foreground/0 !to-foreground/0 !rounded-3xl !border-foreground/15 !bg-foreground/8 !shadow-xl !ring-1 !ring-foreground/15 !ring-offset-2 !ring-offset-background;
149 | }
150 | .inkathon-card-table {
151 | @apply table-fixed border-foreground/10 border-t;
152 | @apply [&_tr]:hover:!bg-foreground/5;
153 | @apply [&_td]:truncate [&_td]:border-foreground/10 [&_td]:border-b [&_td]:first:w-1/3 [&_td]:first:!pl-6 [&_td]:first:font-medium [&_td]:first:text-muted-foreground [&_td]:last:!pr-6 [&_td]:last:text-right [&_td]:last:font-mono;
154 | }
155 | .inkathon-select {
156 | @apply !h-11 !min-w-[200px] *:data-[slot=select-value]:!inline *:data-[slot=select-value]:!truncate;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/create-inkathon-app/src/cli.ts:
--------------------------------------------------------------------------------
1 | import { existsSync } from "node:fs"
2 | import { resolve } from "node:path"
3 | import { Command } from "commander"
4 | import picocolors from "picocolors"
5 | import { cleanupRepository } from "./cleanup.ts"
6 | import { cloneRepository, initializeGit } from "./git.ts"
7 | import { installDependencies, runCodegen } from "./installer.ts"
8 | import { getProjectNames } from "./prompts.ts"
9 | import { updateTemplate } from "./template.ts"
10 | import { logger } from "./utils/logger.ts"
11 | import { displayIntro, displaySuccess } from "./utils/messages.ts"
12 | import { getEngineRequirements, getPackageVersion } from "./utils/package.ts"
13 | import {
14 | checkBunInstalled,
15 | checkGitInstalled,
16 | checkNodeVersion,
17 | checkUnixShell,
18 | } from "./utils/system.ts"
19 |
20 | const pc = picocolors
21 |
22 | export async function run(): Promise {
23 | const program = new Command()
24 |
25 | program
26 | .name("create-inkathon-app")
27 | .description("Create ink! smart contract dApps with one command")
28 | .version(getPackageVersion())
29 | .argument("[project-name]", "Name of your project")
30 | .option("-y, --yes", "Skip prompts and use defaults")
31 | .parse()
32 |
33 | const options = program.opts()
34 | const args = program.args
35 |
36 | displayIntro()
37 |
38 | // Check system requirements
39 | if (!checkUnixShell()) {
40 | logger.error("This tool requires a Unix shell (Linux or macOS). Windows is not supported yet.")
41 | process.exit(1)
42 | }
43 |
44 | // Check Node.js version
45 | const engines = getEngineRequirements()
46 | if (engines?.node) {
47 | const nodeCheck = checkNodeVersion(engines.node)
48 | if (!nodeCheck.isValid) {
49 | logger.error(
50 | `Node.js version ${pc.bold(nodeCheck.currentVersion)} does not satisfy the required version ${pc.bold(
51 | nodeCheck.requiredVersion,
52 | )}.`,
53 | )
54 | logger.info("Please update Node.js to continue.")
55 | logger.info(
56 | "Visit https://github.com/nvm-sh/nvm (recommended) or https://nodejs.org for installation instructions.",
57 | )
58 | process.exit(1)
59 | }
60 | }
61 |
62 | // Check Git installation
63 | if (!checkGitInstalled()) {
64 | logger.error("Git is not installed. Please install Git to continue.")
65 | logger.info("Visit https://git-scm.com/downloads for installation instructions.")
66 | process.exit(1)
67 | }
68 |
69 | // Check Bun installation
70 | if (!checkBunInstalled()) {
71 | logger.error("Bun is not installed. Please install Bun to continue.")
72 | logger.info("Visit https://bun.sh for installation instructions.")
73 | process.exit(1)
74 | }
75 |
76 | // Get project names
77 | const projectNames = options.yes
78 | ? {
79 | displayName: args[0] || "My Inkathon App",
80 | packageName: args[0]?.toLowerCase().replace(/[^a-z0-9-]/g, "-") || "my-inkathon-app",
81 | directory: args[0]?.toLowerCase().replace(/[^a-z0-9-]/g, "-") || "my-inkathon-app",
82 | }
83 | : await getProjectNames(args[0])
84 |
85 | const projectPath = resolve(process.cwd(), projectNames.directory)
86 |
87 | // Check if directory already exists
88 | if (existsSync(projectPath)) {
89 | logger.error(
90 | `Directory ${pc.bold(projectNames.directory)} already exists. Please choose a different name.`,
91 | )
92 | process.exit(1)
93 | }
94 |
95 | console.log(`\nCreating a new inkathon app in ${pc.cyan(projectPath)}...\n`)
96 |
97 | // Clone repository
98 | const cloneSpinner = logger.spinner("Cloning inkathon repository...").start()
99 | try {
100 | await cloneRepository(projectPath)
101 | cloneSpinner.success("Repository cloned successfully")
102 | } catch (error) {
103 | cloneSpinner.error("Failed to clone repository")
104 | logger.error((error as Error).message)
105 | process.exit(1)
106 | }
107 |
108 | // Cleanup repository
109 | const cleanupSpinner = logger.spinner("Cleaning up repository...").start()
110 | try {
111 | await cleanupRepository(projectPath)
112 | cleanupSpinner.success("Repository cleaned up successfully")
113 | } catch (error) {
114 | cleanupSpinner.error("Failed to clean up repository")
115 | logger.warn((error as Error).message)
116 | // Don't exit here, cleanup is not critical
117 | }
118 |
119 | // Update template
120 | const templateSpinner = logger.spinner("Updating project files...").start()
121 | try {
122 | await updateTemplate(projectPath, projectNames.displayName, projectNames.packageName)
123 | templateSpinner.success("Project files updated successfully")
124 | } catch (error) {
125 | templateSpinner.error("Failed to update project files")
126 | logger.error((error as Error).message)
127 | process.exit(1)
128 | }
129 |
130 | // Install dependencies
131 | const installSpinner = logger.spinner("Installing dependencies...").start()
132 | try {
133 | await installDependencies(projectPath)
134 | installSpinner.success("Dependencies installed successfully")
135 | } catch (error) {
136 | installSpinner.error("Failed to install dependencies")
137 | logger.error((error as Error).message)
138 | process.exit(1)
139 | }
140 |
141 | // Run codegen
142 | const codegenSpinner = logger.spinner("Generating types with PAPI...").start()
143 | try {
144 | await runCodegen(projectPath)
145 | codegenSpinner.success("PAPI types generated successfully")
146 | } catch (error) {
147 | codegenSpinner.error("Failed to generate PAPI types")
148 | logger.warn((error as Error).message)
149 | // Don't exit here, codegen might fail if no contracts are deployed yet
150 | }
151 |
152 | // Initialize git
153 | const gitSpinner = logger.spinner("Initializing git repository...").start()
154 | try {
155 | await initializeGit(projectPath)
156 | gitSpinner.success("Git repository initialized")
157 | } catch (error) {
158 | gitSpinner.error("Failed to initialize git repository")
159 | logger.warn((error as Error).message)
160 | // Don't exit here, git init is not critical
161 | }
162 |
163 | // Success message
164 | displaySuccess(projectNames.displayName, projectPath, projectNames.directory)
165 | }
166 |
--------------------------------------------------------------------------------
/contracts/src/flipper/lib.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(not(feature = "std"), no_std, no_main)]
2 |
3 | #[ink::contract]
4 | pub mod flipper {
5 | #[ink(storage)]
6 | pub struct Flipper {
7 | value: bool,
8 | }
9 |
10 | impl Flipper {
11 | /// Creates a new flipper smart contract initialized with the given value.
12 | #[ink(constructor)]
13 | pub fn new(init_value: bool) -> Self {
14 | Self { value: init_value }
15 | }
16 |
17 | /// Creates a new flipper smart contract initialized to `false`.
18 | #[ink(constructor)]
19 | pub fn new_default() -> Self {
20 | Self::new(Default::default())
21 | }
22 |
23 | /// Flips the current value of the Flipper's boolean.
24 | #[ink(message)]
25 | pub fn flip(&mut self) {
26 | self.value = !self.value;
27 | }
28 |
29 | /// Returns the current value of the Flipper's boolean.
30 | #[ink(message)]
31 | pub fn get(&self) -> bool {
32 | self.value
33 | }
34 | }
35 |
36 | #[cfg(test)]
37 | mod tests {
38 | use super::*;
39 |
40 | #[ink::test]
41 | fn default_works() {
42 | let flipper = Flipper::new_default();
43 | assert!(!flipper.get());
44 | }
45 |
46 | #[ink::test]
47 | fn it_works() {
48 | let mut flipper = Flipper::new(false);
49 | assert!(!flipper.get());
50 | flipper.flip();
51 | assert!(flipper.get());
52 | }
53 | }
54 |
55 | #[cfg(all(test, feature = "e2e-tests"))]
56 | mod e2e_tests {
57 | use super::*;
58 | use ink_e2e::ContractsBackend;
59 |
60 | type E2EResult = std::result::Result>;
61 |
62 | #[ink_e2e::test]
63 | async fn it_works(mut client: Client) -> E2EResult<()> {
64 | // given
65 | let mut constructor = FlipperRef::new(false);
66 | let contract = client
67 | .instantiate("flipper", &ink_e2e::bob(), &mut constructor)
68 | .submit()
69 | .await
70 | .expect("instantiate failed");
71 | let mut call_builder = contract.call_builder::();
72 |
73 | let get = call_builder.get();
74 | let get_res = client.call(&ink_e2e::bob(), &get).submit().await?;
75 | assert!(!get_res.return_value());
76 |
77 | // when
78 | let flip = call_builder.flip();
79 | let _flip_res = client
80 | .call(&ink_e2e::bob(), &flip)
81 | .submit()
82 | .await
83 | .expect("flip failed");
84 |
85 | // then
86 | let get = call_builder.get();
87 | let get_res = client.call(&ink_e2e::bob(), &get).dry_run().await?;
88 | assert!(get_res.return_value());
89 |
90 | Ok(())
91 | }
92 |
93 | #[ink_e2e::test]
94 | async fn default_works(mut client: Client) -> E2EResult<()> {
95 | // given
96 | let mut constructor = FlipperRef::new_default();
97 |
98 | // when
99 | let contract = client
100 | .instantiate("flipper", &ink_e2e::bob(), &mut constructor)
101 | .submit()
102 | .await
103 | .expect("instantiate failed");
104 | let call_builder = contract.call_builder::();
105 |
106 | // then
107 | let get = call_builder.get();
108 | let get_res = client.call(&ink_e2e::bob(), &get).dry_run().await?;
109 | assert!(!get_res.return_value());
110 |
111 | Ok(())
112 | }
113 |
114 | /// This test illustrates how to test an existing on-chain contract.
115 | ///
116 | /// You can utilize this to e.g. create a snapshot of a production chain
117 | /// and run the E2E tests against a deployed contract there.
118 | /// This process is explained [here](https://use.ink/5.x/basics/contract-testing/chain-snapshot).
119 | ///
120 | /// Before executing the test:
121 | /// * Make sure you have a node running in the background,
122 | /// * Supply the environment variable `CONTRACT_HEX` that points to a deployed
123 | /// flipper contract. You can take the SS58 address which `cargo contract
124 | /// instantiate` gives you and convert it to hex using `subkey inspect
125 | /// `.
126 | ///
127 | /// The test is then run like this:
128 | ///
129 | /// ```
130 | /// # The env variable needs to be set, otherwise `ink_e2e` will spawn a new
131 | /// # node process for each test.
132 | /// $ export CONTRACTS_NODE_URL=ws://127.0.0.1:9944
133 | ///
134 | /// $ export CONTRACT_ADDR_HEX=0x2c75f0aa09dbfbfd49e6286a0f2edd3b4913f04a58b13391c79e96782f5713e3
135 | /// $ cargo test --features e2e-tests e2e_test_deployed_contract -- --ignored
136 | /// ```
137 | ///
138 | /// # Developer Note
139 | ///
140 | /// The test is marked as ignored, as it has the above pre-conditions to succeed.
141 | #[ink_e2e::test]
142 | #[ignore]
143 | async fn e2e_test_deployed_contract(
144 | mut client: Client,
145 | ) -> E2EResult<()> {
146 | // given
147 | use ink::Address;
148 | let addr = std::env::var("CONTRACT_ADDR_HEX")
149 | .unwrap()
150 | .replace("0x", "");
151 | let addr_bytes: Vec = hex::decode(addr).unwrap();
152 | let addr = Address::from_slice(&addr_bytes[..]);
153 |
154 | use std::str::FromStr;
155 | let suri = ink_e2e::subxt_signer::SecretUri::from_str("//Alice").unwrap();
156 | let caller = ink_e2e::Keypair::from_uri(&suri).unwrap();
157 |
158 | // when
159 | // Invoke `Flipper::get()` from `caller`'s account
160 | let call_builder = ink_e2e::create_call_builder::(addr);
161 | let get = call_builder.get();
162 | let get_res = client.call(&caller, &get).dry_run().await?;
163 |
164 | // then
165 | assert!(get_res.return_value());
166 |
167 | Ok(())
168 | }
169 | }
170 | }
--------------------------------------------------------------------------------
/docs/content/docs/guides/contract-development.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Setup Contract Development
3 | description: Complete guide to set up your ink! development environment
4 | ---
5 |
6 | import { Steps, Step } from 'fumadocs-ui/components/steps'
7 | import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
8 | import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'
9 |
10 | ## Prerequisites
11 |
12 | - Follow our [quickstart guide](/) to scaffold and set up your inkathon project.
13 |
14 | ---
15 |
16 | ## Environment Setup
17 |
18 |
19 |
20 |
21 |
22 | ### Install Rust and Cargo
23 |
24 | ```bash
25 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
26 | ```
27 |
28 |
29 |
30 |
31 |
32 | ### Install Pop CLI
33 |
34 | Pop CLI provides an enhanced developer experience for working with ink! contracts:
35 |
36 | ```bash
37 | cargo install --git https://github.com/r0gue-io/pop-cli.git --branch v6.0.0-alpha.1 --no-default-features --locked -F polkavm-contracts,chain,telemetry
38 | ```
39 |
40 |
41 | Make sure to install a v6-compatible version of Pop CLI from GitHub. If the command above is not
42 | working as expected, follow the official [Pop CLI installation
43 | guide](https://learn.onpop.io/contracts/welcome/install-pop-cli).
44 |
45 |
46 |
47 |
48 |
49 |
50 | ### Run Pop CLI Setup Wizard
51 |
52 | Pop CLI will take care of setting up your Rust environment for Polkadot development.
53 |
54 | ```bash
55 | pop install
56 | ```
57 |
58 |
59 |
60 |
61 | ---
62 |
63 | ## Local Node Setup
64 |
65 | If you plan to deploy on a live testnet, you can skip this step.
66 |
67 |
68 |
69 |
70 | ### Install ink-node
71 |
72 | Download and install the latest release from the [`ink-node`](https://github.com/use-ink/ink-node) repository.
73 |
74 | ```bash
75 | curl -L https://github.com/use-ink/ink-node/releases/latest/download/ink-node-mac-universal.tar.gz | tar xz
76 | ```
77 |
78 |
79 |
80 |
81 |
82 | ### Make ink-node globally accessible
83 |
84 | ```bash
85 | sudo mv ink-node /usr/local/bin/
86 | ```
87 |
88 |
89 |
90 |
91 | ### Verify installation
92 |
93 | ```bash
94 | ink-node --version
95 | ```
96 |
97 |
98 |
99 |
100 | ---
101 |
102 | ## Build & Deploy Contracts
103 |
104 |
105 |
106 |
107 | ### Start the Local Node
108 |
109 | If you plan to deploy on a live testnet, you can skip this step.
110 |
111 | Open a separate terminal and run the local node in the background:
112 |
113 | ```bash
114 | cd contracts
115 | bun run node
116 | ```
117 |
118 | The node will start at `ws://127.0.0.1:9944`
119 |
120 |
121 |
122 |
123 |
124 | ### Build Contracts
125 |
126 | In a new terminal, build the example contract:
127 |
128 | ```bash
129 | cd contracts
130 |
131 | # Build contracts
132 | bun run build
133 |
134 | # (Re-)generate Papi types
135 | bun run codegen
136 | ```
137 |
138 |
139 |
140 |
141 |
142 | ### Test Contracts
143 |
144 | Run unit tests for your contracts to ensure they work correctly:
145 |
146 | ```bash
147 | bun run test
148 | ```
149 |
150 |
151 | The test script uses `pop test` under the hood to run unit tests for all contracts in the `/src`
152 | directory.
153 |
154 |
155 |
156 |
157 |
158 |
159 | ### Deploy Contracts
160 |
161 | Now you can deploy the contract to your local node:
162 |
163 | ```bash
164 | CHAIN=dev bun run deploy
165 | ```
166 |
167 | By default, the deployer account is `//Alice` who is pre-funded on local nodes. You can overwrite this by either passing an `ACCOUNT_URI` variable or defining an `.env.` file (see [environment](/learn/contracts#environment)).
168 |
169 |
170 |
171 |
172 |
173 | ### Configure Frontend
174 |
175 | Update the frontend to connect to your local node (optional) and import the freshly deployed contract:
176 |
177 | ```tsx title="frontend/src/lib/reactive-dot/config.ts"
178 | import { dev } from '@polkadot-api/descriptors' // [!code ++]
179 | // …
180 |
181 | export const config = defineConfig({
182 | chains: {
183 | pop: {
184 | descriptor: pop,
185 | provider: getWsProvider('wss://rpc1.paseo.popnetwork.xyz'),
186 | },
187 | passethub: {
188 | descriptor: passethub,
189 | provider: getWsProvider('wss://testnet-passet-hub.polkadot.io'),
190 | },
191 | dev: {
192 | descriptor: dev, // [!code ++]
193 | provider: getWsProvider('ws://127.0.0.1:9944'), // [!code ++]
194 | },
195 | },
196 | // …
197 | })
198 | ```
199 |
200 | ```tsx title="frontend/src/lib/inkathon/deployments.ts"
201 | import * as flipperDev from 'contracts/deployments/flipper/dev' // [!code ++]
202 | // …
203 |
204 | export const flipper = {
205 | contract: contracts.flipper,
206 | evmAddresses: {
207 | pop: flipperPop.evmAddress,
208 | passethub: flipperPassethub.evmAddress,
209 | dev: flipperDev.evmAddress, // [!code ++]
210 | },
211 | ss58Addresses: {
212 | pop: flipperPop.ss58Address,
213 | passethub: flipperPassethub.ss58Address,
214 | dev: flipperDev.ss58Address, // [!code ++]
215 | },
216 | }
217 | ```
218 |
219 |
220 |
221 |
222 |
223 | ### Start Frontend Development
224 |
225 | Now you can already interact with your contract in the frontend.
226 |
227 | ```bash
228 | # From project root
229 | bun run dev
230 |
231 | # Or run both frontend and node together
232 | bun run dev-and-node
233 | ```
234 |
235 | Visit [http://localhost:3000](http://localhost:3000) to see your dApp!
236 |
237 |
238 |
239 |
240 | ## Learn More
241 |
242 |
243 |
248 |
253 |
258 |
263 |
264 |
--------------------------------------------------------------------------------
/docs/content/docs/learn/structure.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Project Structure
3 | description: Understanding the inkathon approach
4 | ---
5 |
6 | import { File, Folder, Files } from 'fumadocs-ui/components/files'
7 | import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'
8 |
9 | ## Monorepo Architecture
10 |
11 | Projects set up with inkathon use a monorepo structure splitting your [frontend](/learn/frontend) and [smart contracts](/learn/contracts) into separate workspaces.
12 |
13 | This provides clear separation between different parts of the application while maintaining easy cross-package imports, shared configurations, and a single source of truth for contract metadata.
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | Don't be afraid, it's really just a fancy term for having nested folders with multiple
66 | `package.json` files. The package manager (Bun) takes care of installing the correct dependencies
67 | for each workspace and executing scripts where you need them.
68 |
69 |
70 | ### Why Monorepo?
71 |
72 |
73 |
74 | All packages share common dependencies like TypeScript, Biome, and Prettier, ensuring
75 | consistency across the codebase and reducing duplication or multiple sources of truth.
76 |
77 |
78 | The frontend can directly import contract deployments and types from the contracts package,
79 | enabling seamless type-safe integration. No more manually copying and pasting contract addresses
80 | and metadata files.
81 |
82 |
83 | Run the entire stack with a single command, making development and testing more efficient.
84 |
85 |
86 | Changes that affect multiple packages can be committed together, maintaining consistency and
87 | preventing version mismatches.
88 |
89 |
90 |
91 | ## Workspaces
92 |
93 | ### `/` – Root Directory
94 |
95 | - **Configuration Files**: Configs for VSCode, TypeScript, Biome, Prettier
96 | - **Scripts**: Global commands that orchestrate across workspaces
97 | - **Hosting**: Deployment configs for self-hosting and Vercel
98 | - **AI Rules**: Tailored development rules for Cursor and Claude
99 |
100 | ### `/contracts` – ink! Smart Contracts
101 |
102 | - **Source Code**: Rust contract implementations
103 | - **Build Artifacts**: Compiled contracts and metadata
104 | - **Deployments**: Multichain deployment addresses
105 | - **Tooling**: Advanced build & deployment scripts using Pop CLI, `cargo contract`, and `ink-node`
106 | - **Type Generation**: Generated Papi type descriptors for contracts & chains
107 |
108 | ### `/frontend` – Next.js Frontend
109 |
110 | - **Styling**: Tailwind CSS v4 & shadcn/ui components
111 | - **React Components**: Reusable Web3 components for wallet connection, chain selection, and contract interaction, and more
112 | - **Web3 Integration**: Type-safe multichain wallet connection and contract interaction with Papi and ReactiveDOT
113 |
114 | ## Available Commands
115 |
116 | Execute from the root directory only:
117 |
118 | ```bash
119 | # Development
120 | bun run dev # Start frontend development server
121 | bun run node # Start local ink-node
122 | bun run dev-and-node # Run both frontend and node concurrently
123 | bun run codegen # Generate TypeScript types from contracts
124 | bun run build # Build frontend (includes codegen)
125 | bun run start # Start production frontend server
126 |
127 | # Code Quality
128 | bun run lint # Run linting checks across all workspaces
129 | bun run lint:fix # Auto-fix linting issues
130 | bun run typecheck # TypeScript checking across all workspaces
131 |
132 | # Maintenance
133 | bun run clean # Remove build artifacts from all workspaces
134 | bun run clean-install # Remove node_modules and reinstall dependencies
135 | bun run update # Update dependencies interactively
136 | ```
137 |
138 | Find all available commands in the [command cheat sheet](/resources/commands).
139 |
140 | ## Learn More
141 |
142 |
143 |
148 |
153 |
158 |
163 |
164 |
--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------
1 | ## Project Overview
2 |
3 | Full-stack project for ink! smart contracts on Polkadot using PolkaVM and a Next.js frontend.
4 |
5 | ## Tech Stack
6 |
7 | - **Smart Contracts**: ink! (Rust)
8 | - **Frontend**: Next.js 15, React 19, TypeScript
9 | - **Blockchain**: Polkadot API (PAPI), ReactiveDOT
10 | - **Package Manager**: Bun
11 | - **Styling**: Tailwind CSS v4
12 | - **UI Components**: shadcn/ui, Radix UI
13 |
14 | ## Project Structure
15 |
16 | - `/frontend` - Next.js application
17 | - `/frontend/src/components/web3/` - Web3 components (account-select, chain-select, contract-card, etc.)
18 | - `/frontend/src/lib/reactive-dot/` - ReactiveDOT configuration
19 | - `/frontend/src/lib/inkathon/` - Constants and deployment configurations
20 | - `/contracts` - ink! smart contracts
21 | - `/contracts/src` - Contract source code
22 | - `/contracts/deployments` - Built contracts and deployment files
23 |
24 | ## Commands
25 |
26 | ### Development
27 |
28 | - `bun run dev` - Start frontend development server
29 | - `bun run node` - Run local ink-node
30 | - `bun run dev-and-node` - Run both frontend and node concurrently
31 |
32 | ### Smart Contracts
33 |
34 | - `bun run -F contracts build` - Build all contracts
35 | - `bun run codegen` - Generate TypeScript types from contracts
36 | - `bun run -F contracts deploy` - Deploy contracts
37 |
38 | ### Code Quality
39 |
40 | - `bun run lint` - Run linter (Biome + Prettier)
41 | - `bun run lint:fix` - Auto-fix linting issues
42 | - `bun run typecheck` - Run TypeScript type checking
43 |
44 | ### Build & Clean
45 |
46 | - `bun run build` - Build production frontend
47 | - `bun run clean` - Clean build artifacts
48 | - `bun run clean-install` - Remove all node_modules and reinstall
49 |
50 | ## Development Workflow
51 |
52 | ### Quick Start
53 |
54 | 1. Run `bun run node` to start local chain
55 | 2. Run `bun run dev` for frontend development
56 | 3. After contract changes: `bun run -F contracts build` then `bun codegen`
57 | 4. **IMPORTANT**: Always run `bun run lint` and `bun run typecheck` before committing
58 |
59 | ### Writing Smart Contracts
60 |
61 | 1. Create a new contract directory in `/contracts/src//`
62 | 2. Add `Cargo.toml` and `lib.rs` following ink! v6 conventions
63 | 3. Use the flipper contract as a reference template
64 | 4. Contract must be annotated with `#[ink::contract]`
65 |
66 | ### Building Contracts
67 |
68 | - `bun run -F contracts build` - Builds all contracts in `/contracts/src/`
69 | - Build outputs: `.contract`, `.json`, and `.polkavm` files in `/contracts/deployments//`
70 | - Uses `cargo contract build --release` under the hood
71 |
72 | ### Type Generation (PAPI)
73 |
74 | 1. After building contracts, run `bun run codegen` to generate TypeScript types
75 | 2. PAPI reads contract metadata from `/contracts/deployments/`
76 | 3. Generated types are available via `@polkadot-api/descriptors`
77 | 4. Contract descriptors accessible as `contracts.`
78 |
79 | ### Deploying Contracts
80 |
81 | ```bash
82 | # Deploy to local dev chain (default)
83 | bun run -F contracts deploy
84 |
85 | # Deploy to specific chain
86 | CHAIN=pop bun run -F contracts deploy
87 |
88 | # Custom account (default: //Alice)
89 | ACCOUNT_URI="//Bob" CHAIN=pop bun run -F contracts deploy
90 | ```
91 |
92 | Deployment addresses are automatically exported to `/contracts/deployments//.ts`
93 |
94 | ### Adding New Networks
95 |
96 | 1. Generate PAPI types for the chain:
97 | ```bash
98 | bunx polkadot-api add -w
99 | ```
100 | 2. Add chain configuration in `/frontend/src/lib/reactive-dot/config.ts`:
101 | ```typescript
102 | chains: {
103 | yourchain: {
104 | descriptor: yourchain,
105 | provider: getWsProvider("wss://your-rpc-url"),
106 | }
107 | }
108 | ```
109 | 3. Deploy contracts to the new chain:
110 | ```bash
111 | CHAIN= bun run -F contracts deploy
112 | ```
113 |
114 | ### Frontend Integration
115 |
116 | 1. Import contract deployments in `/frontend/src/lib/inkathon/deployments.ts`
117 | 2. Add contract addresses for each chain:
118 | ```typescript
119 | import { evmAddress, ss58Address } from 'contracts/deployments//'
120 | ```
121 | 3. Use contracts in components with generated types:
122 | ```typescript
123 | import { contracts } from "@polkadot-api/descriptors"
124 | const contract = contracts.
125 | ```
126 |
127 | ### Complete Development Flow
128 |
129 | 1. Write/modify contract in `/contracts/src/`
130 | 2. Build: `bun run -F contracts build`
131 | 3. Generate types: `bun run codegen`
132 | 4. Deploy: `CHAIN= bun run -F contracts deploy`
133 | 5. Update frontend imports in `deployments.ts`
134 | 6. Use contract in frontend components with full type safety
135 |
136 | ## Code Conventions
137 |
138 | ### TypeScript
139 |
140 | - Functional components with `function` keyword
141 | - Named exports preferred over default exports
142 |
143 | ### File Naming
144 |
145 | - All files: lowercase kebab-case (`connect-button.tsx`)
146 |
147 | ### React/Next.js
148 |
149 | - Minimize 'use client' usage - prefer Server Components
150 | - Wrap client components in Suspense with fallbacks
151 | - Web3 components using ReactiveDOT are client components
152 |
153 | ### Styling (Tailwind CSS v4)
154 |
155 | - **IMPORTANT**: This project uses Tailwind CSS v4
156 | - Mobile-first responsive design
157 |
158 | ## Available Chains
159 |
160 | - `dev` - Local ink-node (wss://127.0.0.1:9944)
161 | - `pop` - Pop Network (wss://rpc1.paseo.popnetwork.xyz)
162 | - `passethub` - Paseo Asset Hub (wss://testnet-passet-hub.polkadot.io)
163 |
164 | ## Key Files
165 |
166 | - **Chain Configuration**: `/frontend/src/lib/reactive-dot/config.ts` - ReactiveDOT chain setup
167 | - **Constants**: `/frontend/src/lib/inkathon/constants.ts` - Alice account, faucet URLs
168 | - **Deployments**: `/frontend/src/lib/inkathon/deployments.ts` - Contract deployment info
169 | - **Contract deployments**: `/contracts/deployments/` - Built contract files
170 | - **Example contract**: `/contracts/src/flipper/lib.rs` - Flipper smart contract
171 |
172 | ## Important Notes
173 |
174 | - This is a monorepo with Bun workspaces
175 | - Frontend and contracts are separate workspaces
176 | - Always check existing patterns before implementing new features
177 |
178 | ## Related Documentation
179 |
180 | - **ink!**: https://use.ink/ - Smart contract language documentation
181 | - **Polkadot API (PAPI)**: https://papi.how - TypeScript API for Polkadot
182 | - **ReactiveDOT**: https://reactivedot.dev - Reactive library for Polkadot
183 | - **Pop CLI**: https://learn.onpop.io/contracts - Development tools for ink!
184 | - **ink-node**: https://github.com/use-ink/ink-node - Local development node
185 | - **cargo-contract**: https://github.com/use-ink/cargo-contract - Contract build tool
186 |
--------------------------------------------------------------------------------
/contracts/deployments/flipper/flipper.contract:
--------------------------------------------------------------------------------
1 | {"source":{"hash":"0xe0e253381de78ef54364a13c2ef4d1a2b055b5500b317737a4966629a33e2259","language":"ink! 6.0.0-alpha","compiler":"rustc 1.88.0","contract_binary":"0x50564d00001807000000000000010600c00040a000045505000000000e00000019000000240000002f00000063616c6c5f646174615f636f70796765745f73746f726167657365616c5f72657475726e7365745f73746f7261676576616c75655f7472616e7366657272656405110283760463616c6c84bb066465706c6f79068695100285b9a4019902b402b502dc0201032e03870314045804b104cc04350558058d05b1059511c87b10307b15287b16206472531910bb008d2c84c407c842056487642aae52107c7b95770178ab95aa01ac5af6c8480cc949068469f884cb07c8950a510b19017b197b16087b1c1033054911189ab608951918846701d4b90351070e8217107c77783733050101846702510714821710c857077f77c8530979979555020184670497b603510711821710c857078177c853037a37018219188d67847038c9b808958308642701c8470c95cb08c84308d06905aeab198289953308cf0908d458087bc895770828e1642a28693306821b1084b70449111851070c81877a17183306040184b702510717c84607c837077f77951818d4680879879566020184b701510714c86404c843037c37951818d46808788701821718cf0707d457077bc78216088219c89b088469072809c89b0884690701c8a909ae9a107c8795880178a795aa01ac9af66427821030821528821620951138320064c764cbaea5d282789577087b58955508aea5c528f39511f07b1008648a78170795180733090164a733002028e4039511a87b10507b15487b16406416330500000233070000023300020a040149112049112849113049113880571480521880591c805a04805308805c0c805810380b00000297992097772097cc2097aa20d42909951220d48707d43c08d4ba0a7b1a7b18087b17107b1918330704017c68017c69027c6a037c6b97880897991097aa18d4b8087c6b047c6c05d4a9097c6a067c650797cc08d4cb0b97aa10975518d45a0a956608d49808d4ba0a97aa20d4a8087b289522089577ff5207b4951920330705330805019588ff51080f829a959908510af5330704018210508215488216409511583200827808510814827a7ca9958bff95aa017b7a7b7b0828053309019597ff8e77957a01939a330702db8a073200003300069511f87b101f03000002330800000233090233070a020000009511c87b10307b15287b16207b1733070000027b1708491110004049111895170850100ac702821710821518ac574b821608c95707c856087b18087b1710491118951808821750100c71fe821810821718ac7826821a08975920989920977720987b20330764680a03821030821528821620951138320000821910835501ac590400330800000233070a0200838852080f330a330901330b0000022816330a013b0a000002330b000002c8ab0b330902019511f87b10330800000278ba0a02009511c87b10307b15287b162050101004fe8477ff005217050e01330933050000023307000002330800400a013807000002977930977828847aff0098993898883895abd1987718510b2495aa9d520ad7009599c65209d00095885bff5208c8009577af5207c1003306281d95997aff5209b5009588a55208ae00957727ff5207a6003306010133070000027b170849111000404911189517085010128f01821710821518ac577f7b16821808c957069759207a1608c88505989920951b083307645a0a0101801808ac865b8377511703565207527b15087b18109517085010141afe8477ff005117023d821810520836821851083e8477013300049511d87b10207b1518330500401e0300000233080000027b187b150849111001641833000e28d7fc00330801330701330008289ffe330801e078075010160afe33083307330008288afe9511d87b10207b15187b1610501018bffc8477ff005217056f3309330600000233050000023308004033070000020a01380b0000029555047b15491108fc3f97b93097b82884baff0098993898883895ac9f98b718510c3095aa65ff520a44959952ff52093d958863ff5208369577a2520730641750101a3dfd8477ff005217021a281e00959911ff5209179588825208119577c252070b50101c63fd2842fd33080133070133000828defd9511e87b10107b15087b166475827710c89706ac7623825a08ac6a1d825ac8a70750101e78fa7b561082101082150882169511183200009511f07b1008481104951804330904501020ba8210089511103200495228a994922449949424492a93a4944992d294a449929294aa24299924256592943449292999949292a44a29292949499214429a24499224244992a42499a42449922449922425493229c92429a9a4d494922685907a921412929024499214922449aa246992a196124a08c9146a928408851087249224894444a222121119129290a42449524d8a2409114949924408254a2649122a4992105108110e292491248944224921a29124094992249524292149699224920000","build_info":{"build_mode":"Release","cargo_contract_version":"6.0.0-alpha","rust_toolchain":"stable-aarch64-apple-darwin"}},"contract":{"name":"flipper","version":"6.0.0-alpha","authors":["Use Ink "]},"image":null,"spec":{"constructors":[{"args":[{"label":"init_value","type":{"displayName":["bool"],"type":0}}],"default":false,"docs":["Creates a new flipper smart contract initialized with the given value."],"label":"new","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":2},"selector":"0x9bae9d5e"},{"args":[],"default":false,"docs":["Creates a new flipper smart contract initialized to `false`."],"label":"new_default","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":2},"selector":"0x61ef7e3e"}],"docs":[],"environment":{"accountId":{"displayName":["AccountId"],"type":6},"balance":{"displayName":["Balance"],"type":9},"blockNumber":{"displayName":["BlockNumber"],"type":12},"chainExtension":{"displayName":["ChainExtension"],"type":13},"hash":{"displayName":["Hash"],"type":10},"maxEventTopics":4,"staticBufferSize":16384,"timestamp":{"displayName":["Timestamp"],"type":11}},"events":[],"lang_error":{"displayName":["ink","LangError"],"type":4},"messages":[{"args":[],"default":false,"docs":[" Flips the current value of the Flipper's boolean."],"label":"flip","mutates":true,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":2},"selector":"0x633aa551"},{"args":[],"default":false,"docs":[" Returns the current value of the Flipper's boolean."],"label":"get","mutates":false,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":5},"selector":"0x2f865bd9"}]},"storage":{"root":{"layout":{"struct":{"fields":[{"layout":{"leaf":{"key":"0x00000000","ty":0}},"name":"value"}],"name":"Flipper"}},"root_key":"0x00000000","ty":1}},"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"}]}},"path":["flipper","flipper","Flipper"]}},{"id":2,"type":{"def":{"variant":{"variants":[{"fields":[{"type":3}],"index":0,"name":"Ok"},{"fields":[{"type":4}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":3},{"name":"E","type":4}],"path":["Result"]}},{"id":3,"type":{"def":{"tuple":[]}}},{"id":4,"type":{"def":{"variant":{"variants":[{"index":1,"name":"CouldNotReadInput"}]}},"path":["ink_primitives","LangError"]}},{"id":5,"type":{"def":{"variant":{"variants":[{"fields":[{"type":0}],"index":0,"name":"Ok"},{"fields":[{"type":4}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":0},{"name":"E","type":4}],"path":["Result"]}},{"id":6,"type":{"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","AccountId"]}},{"id":7,"type":{"def":{"array":{"len":32,"type":8}}}},{"id":8,"type":{"def":{"primitive":"u8"}}},{"id":9,"type":{"def":{"primitive":"u128"}}},{"id":10,"type":{"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","Hash"]}},{"id":11,"type":{"def":{"primitive":"u64"}}},{"id":12,"type":{"def":{"primitive":"u32"}}},{"id":13,"type":{"def":{"variant":{}},"path":["ink_primitives","types","NoChainExtension"]}}],"version":5}
--------------------------------------------------------------------------------
/frontend/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as SelectPrimitive from "@radix-ui/react-select"
4 | import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
5 | import type * as React from "react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | function Select({ ...props }: React.ComponentProps) {
10 | return
11 | }
12 |
13 | function SelectGroup({ ...props }: React.ComponentProps) {
14 | return
15 | }
16 |
17 | function SelectValue({ ...props }: React.ComponentProps) {
18 | return
19 | }
20 |
21 | function SelectTrigger({
22 | className,
23 | size = "default",
24 | children,
25 | ...props
26 | }: React.ComponentProps & {
27 | size?: "sm" | "default"
28 | }) {
29 | return (
30 |
39 | {children}
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | function SelectContent({
48 | className,
49 | children,
50 | position = "popper",
51 | ...props
52 | }: React.ComponentProps) {
53 | return (
54 |
55 |
66 |
67 |
74 | {children}
75 |
76 |
77 |
78 |
79 | )
80 | }
81 |
82 | function SelectLabel({ className, ...props }: React.ComponentProps) {
83 | return (
84 |
89 | )
90 | }
91 |
92 | function SelectItem({
93 | className,
94 | children,
95 | ...props
96 | }: React.ComponentProps) {
97 | return (
98 |
106 |
107 |
108 |
109 |
110 |
111 | {children}
112 |
113 | )
114 | }
115 |
116 | function SelectSeparator({
117 | className,
118 | ...props
119 | }: React.ComponentProps) {
120 | return (
121 |
126 | )
127 | }
128 |
129 | function SelectScrollUpButton({
130 | className,
131 | ...props
132 | }: React.ComponentProps) {
133 | return (
134 |
139 |
140 |
141 | )
142 | }
143 |
144 | function SelectScrollDownButton({
145 | className,
146 | ...props
147 | }: React.ComponentProps) {
148 | return (
149 |
154 |
155 |
156 | )
157 | }
158 |
159 | export {
160 | Select,
161 | SelectContent,
162 | SelectGroup,
163 | SelectItem,
164 | SelectLabel,
165 | SelectScrollDownButton,
166 | SelectScrollUpButton,
167 | SelectSeparator,
168 | SelectTrigger,
169 | SelectValue,
170 | }
171 |
--------------------------------------------------------------------------------
/.cursor/rules/global.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description:
3 | globs:
4 | alwaysApply: true
5 | ---
6 |
7 | ## Project Overview
8 |
9 | Full-stack project for ink! smart contracts on Polkadot using PolkaVM and a Next.js frontend.
10 |
11 | ## Tech Stack
12 |
13 | - **Smart Contracts**: ink! (Rust)
14 | - **Frontend**: Next.js 15, React 19, TypeScript
15 | - **Blockchain**: Polkadot API (PAPI), ReactiveDOT
16 | - **Package Manager**: Bun
17 | - **Styling**: Tailwind CSS v4
18 | - **UI Components**: shadcn/ui, Radix UI
19 |
20 | ## Project Structure
21 |
22 | - `/frontend` - Next.js application
23 | - `/frontend/src/components/web3/` - Web3 components (account-select, chain-select, contract-card, etc.)
24 | - `/frontend/src/lib/reactive-dot/` - ReactiveDOT configuration
25 | - `/frontend/src/lib/inkathon/` - Constants and deployment configurations
26 | - `/contracts` - ink! smart contracts
27 | - `/contracts/src` - Contract source code
28 | - `/contracts/deployments` - Built contracts and deployment files
29 |
30 | ## Commands
31 |
32 | ### Development
33 |
34 | - `bun run dev` - Start frontend development server
35 | - `bun run node` - Run local ink-node
36 | - `bun run dev-and-node` - Run both frontend and node concurrently
37 |
38 | ### Smart Contracts
39 |
40 | - `bun run -F contracts build` - Build all contracts
41 | - `bun run codegen` - Generate TypeScript types from contracts
42 | - `bun run -F contracts deploy` - Deploy contracts
43 |
44 | ### Code Quality
45 |
46 | - `bun run lint` - Run linter (Biome + Prettier)
47 | - `bun run lint:fix` - Auto-fix linting issues
48 | - `bun run typecheck` - Run TypeScript type checking
49 |
50 | ### Build & Clean
51 |
52 | - `bun run build` - Build production frontend
53 | - `bun run clean` - Clean build artifacts
54 | - `bun run clean-install` - Remove all node_modules and reinstall
55 |
56 | ## Development Workflow
57 |
58 | ### Quick Start
59 |
60 | 1. Run `bun run node` to start local chain
61 | 2. Run `bun run dev` for frontend development
62 | 3. After contract changes: `bun run -F contracts build` then `bun codegen`
63 | 4. **IMPORTANT**: Always run `bun run lint` and `bun run typecheck` before committing
64 |
65 | ### Writing Smart Contracts
66 |
67 | 1. Create a new contract directory in `/contracts/src//`
68 | 2. Add `Cargo.toml` and `lib.rs` following ink! v6 conventions
69 | 3. Use the flipper contract as a reference template
70 | 4. Contract must be annotated with `#[ink::contract]`
71 |
72 | ### Building Contracts
73 |
74 | - `bun run -F contracts build` - Builds all contracts in `/contracts/src/`
75 | - Build outputs: `.contract`, `.json`, and `.polkavm` files in `/contracts/deployments//`
76 | - Uses `cargo contract build --release` under the hood
77 |
78 | ### Type Generation (PAPI)
79 |
80 | 1. After building contracts, run `bun run codegen` to generate TypeScript types
81 | 2. PAPI reads contract metadata from `/contracts/deployments/`
82 | 3. Generated types are available via `@polkadot-api/descriptors`
83 | 4. Contract descriptors accessible as `contracts.`
84 |
85 | ### Deploying Contracts
86 |
87 | ```bash
88 | # Deploy to local dev chain (default)
89 | bun run -F contracts deploy
90 |
91 | # Deploy to specific chain
92 | CHAIN=pop bun run -F contracts deploy
93 |
94 | # Custom account (default: //Alice)
95 | ACCOUNT_URI="//Bob" CHAIN=pop bun run -F contracts deploy
96 | ```
97 |
98 | Deployment addresses are automatically exported to `/contracts/deployments//.ts`
99 |
100 | ### Adding New Networks
101 |
102 | 1. Generate PAPI types for the chain:
103 | ```bash
104 | bunx polkadot-api add -w
105 | ```
106 | 2. Add chain configuration in `/frontend/src/lib/reactive-dot/config.ts`:
107 | ```typescript
108 | chains: {
109 | yourchain: {
110 | descriptor: yourchain,
111 | provider: getWsProvider("wss://your-rpc-url"),
112 | }
113 | }
114 | ```
115 | 3. Deploy contracts to the new chain:
116 | ```bash
117 | CHAIN= bun run -F contracts deploy
118 | ```
119 |
120 | ### Frontend Integration
121 |
122 | 1. Import contract deployments in `/frontend/src/lib/inkathon/deployments.ts`
123 | 2. Add contract addresses for each chain:
124 | ```typescript
125 | import { evmAddress, ss58Address } from 'contracts/deployments//'
126 | ```
127 | 3. Use contracts in components with generated types:
128 | ```typescript
129 | import { contracts } from "@polkadot-api/descriptors"
130 | const contract = contracts.
131 | ```
132 |
133 | ### Complete Development Flow
134 |
135 | 1. Write/modify contract in `/contracts/src/`
136 | 2. Build: `bun run -F contracts build`
137 | 3. Generate types: `bun run codegen`
138 | 4. Deploy: `CHAIN= bun run -F contracts deploy`
139 | 5. Update frontend imports in `deployments.ts`
140 | 6. Use contract in frontend components with full type safety
141 |
142 | ## Code Conventions
143 |
144 | ### TypeScript
145 |
146 | - Functional components with `function` keyword
147 | - Named exports preferred over default exports
148 |
149 | ### File Naming
150 |
151 | - All files: lowercase kebab-case (`connect-button.tsx`)
152 |
153 | ### React/Next.js
154 |
155 | - Minimize 'use client' usage - prefer Server Components
156 | - Wrap client components in Suspense with fallbacks
157 | - Web3 components using ReactiveDOT are client components
158 |
159 | ### Styling (Tailwind CSS v4)
160 |
161 | - **IMPORTANT**: This project uses Tailwind CSS v4
162 | - Mobile-first responsive design
163 |
164 | ## Available Chains
165 |
166 | - `dev` - Local ink-node (wss://127.0.0.1:9944)
167 | - `pop` - Pop Network (wss://rpc1.paseo.popnetwork.xyz)
168 | - `passethub` - Paseo Asset Hub (wss://testnet-passet-hub.polkadot.io)
169 |
170 | ## Key Files
171 |
172 | - **Chain Configuration**: `/frontend/src/lib/reactive-dot/config.ts` - ReactiveDOT chain setup
173 | - **Constants**: `/frontend/src/lib/inkathon/constants.ts` - Alice account, faucet URLs
174 | - **Deployments**: `/frontend/src/lib/inkathon/deployments.ts` - Contract deployment info
175 | - **Contract deployments**: `/contracts/deployments/` - Built contract files
176 | - **Example contract**: `/contracts/src/flipper/lib.rs` - Flipper smart contract
177 |
178 | ## Important Notes
179 |
180 | - This is a monorepo with Bun workspaces
181 | - Frontend and contracts are separate workspaces
182 | - Always check existing patterns before implementing new features
183 |
184 | ## Related Documentation
185 |
186 | - **ink!**: https://use.ink/ - Smart contract language documentation
187 | - **Polkadot API (PAPI)**: https://papi.how - TypeScript API for Polkadot
188 | - **ReactiveDOT**: https://reactivedot.dev - Reactive library for Polkadot
189 | - **Pop CLI**: https://learn.onpop.io/contracts - Development tools for ink!
190 | - **ink-node**: https://github.com/use-ink/ink-node - Local development node
191 | - **cargo-contract**: https://github.com/use-ink/cargo-contract - Contract build tool
192 |
--------------------------------------------------------------------------------
|