├── .nvmrc ├── docs ├── .nvmrc ├── src │ ├── app │ │ ├── favicon.ico │ │ ├── apple-touch-icon.png │ │ ├── web-app-manifest-512x512.png │ │ ├── api │ │ │ └── search │ │ │ │ └── route.ts │ │ ├── global.css │ │ ├── (docs) │ │ │ ├── layout.tsx │ │ │ └── [[...slug]] │ │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── layout.config.tsx │ ├── lib │ │ └── source.ts │ └── mdx-components.tsx ├── postcss.config.mjs ├── .prettierignore ├── .prettierrc.mjs ├── vercel.json ├── .gitignore ├── content │ └── docs │ │ ├── meta.json │ │ ├── learn │ │ ├── hosting.mdx │ │ └── structure.mdx │ │ ├── resources │ │ ├── ecosystem.mdx │ │ └── commands.mdx │ │ ├── index.mdx │ │ ├── about.mdx │ │ └── guides │ │ ├── add-network.mdx │ │ ├── add-contract.mdx │ │ └── contract-development.mdx ├── source.config.ts ├── tsconfig.json ├── CHANGELOG.md ├── package.json └── next.config.ts ├── frontend ├── .nvmrc ├── src │ ├── app │ │ ├── favicon.ico │ │ ├── apple-touch-icon.png │ │ ├── web-app-manifest-512x512.png │ │ ├── page.tsx │ │ ├── layout.tsx │ │ └── app.tsx │ ├── lib │ │ ├── reactive-dot │ │ │ ├── types.d.ts │ │ │ ├── contracts.ts │ │ │ ├── is-tx-loading.ts │ │ │ ├── config.ts │ │ │ └── submit-tx-and-toast.ts │ │ ├── utils.ts │ │ ├── inkathon │ │ │ ├── constants.ts │ │ │ └── deployments.ts │ │ └── metadata.ts │ ├── components │ │ ├── no-ssr.tsx │ │ ├── layout │ │ │ ├── logo.tsx │ │ │ ├── skeletons.tsx │ │ │ ├── wrapper.tsx │ │ │ ├── footer.tsx │ │ │ └── header.tsx │ │ ├── web3 │ │ │ ├── account-provider.tsx │ │ │ ├── chain-info-card.tsx │ │ │ ├── map-account-button.tsx │ │ │ ├── chain-select.tsx │ │ │ ├── account-balance.tsx │ │ │ ├── account-select.tsx │ │ │ ├── connect-button.tsx │ │ │ └── contract-card.tsx │ │ └── ui │ │ │ ├── sonner.tsx │ │ │ ├── badge.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── card.tsx │ │ │ ├── table.tsx │ │ │ ├── button-extended.tsx │ │ │ ├── dialog.tsx │ │ │ └── select.tsx │ ├── styles │ │ ├── fonts.ts │ │ └── globals.css │ └── hooks │ │ └── use-is-mapped.ts ├── .prettierrc.mjs ├── public │ ├── inkathon-icon.png │ ├── inkathon-og-banner.jpg │ └── inkathon-readme-banner.png ├── postcss.config.mjs ├── .prettierignore ├── .gitignore ├── components.json ├── next.config.ts ├── tsconfig.json ├── package.json └── CHANGELOG.md ├── contracts ├── .nvmrc ├── deployments │ ├── .gitkeep │ └── flipper │ │ ├── flipper.polkavm │ │ ├── pop.ts │ │ ├── passethub.ts │ │ └── flipper.contract ├── .papi │ ├── descriptors │ │ ├── .gitignore │ │ └── package.json │ ├── metadata │ │ ├── dev.scale │ │ ├── pop.scale │ │ └── passethub.scale │ └── polkadot-api.json ├── Cargo.toml ├── .prettierrc.mjs ├── .gitignore ├── .prettierignore ├── codegen.sh ├── src │ └── flipper │ │ ├── Cargo.toml │ │ └── lib.rs ├── test.sh ├── tsconfig.json ├── postinstall.sh ├── build.sh ├── package.json ├── scripts │ ├── deploy.ts │ └── utils │ │ ├── write-addresses.ts │ │ ├── init-api.ts │ │ └── deploy-contract.ts └── CHANGELOG.md ├── create-inkathon-app ├── .nvmrc ├── bin │ └── create-inkathon-app ├── .prettierrc.mjs ├── .gitignore ├── .npmrc ├── src │ ├── index.ts │ ├── installer.ts │ ├── utils │ │ ├── package.ts │ │ ├── system.ts │ │ ├── validate.ts │ │ ├── messages.ts │ │ └── logger.ts │ ├── prompts.ts │ ├── git.ts │ ├── cleanup.ts │ ├── template.ts │ └── cli.ts ├── .prettierignore ├── tsconfig.json ├── README.md ├── package.json └── CHANGELOG.md ├── .dockerignore ├── .prettierignore ├── .prettierrc.mjs ├── vercel.json ├── .vscode ├── extensions.json └── settings.json ├── .changeset └── config.json ├── .gitignore ├── .github └── workflows │ └── release.yml ├── package.json ├── README.md ├── Dockerfile ├── biome.json ├── CLAUDE.md └── .cursor └── rules └── global.mdc /.nvmrc: -------------------------------------------------------------------------------- 1 | v24 -------------------------------------------------------------------------------- /docs/.nvmrc: -------------------------------------------------------------------------------- 1 | v24 -------------------------------------------------------------------------------- /frontend/.nvmrc: -------------------------------------------------------------------------------- 1 | v24 -------------------------------------------------------------------------------- /contracts/.nvmrc: -------------------------------------------------------------------------------- 1 | v24 -------------------------------------------------------------------------------- /contracts/deployments/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /create-inkathon-app/.nvmrc: -------------------------------------------------------------------------------- 1 | v24 -------------------------------------------------------------------------------- /contracts/.papi/descriptors/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !package.json 4 | -------------------------------------------------------------------------------- /contracts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | resolver = "3" 4 | members = ["src/flipper"] 5 | -------------------------------------------------------------------------------- /create-inkathon-app/bin/create-inkathon-app: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import '../dist/index.js' 3 | -------------------------------------------------------------------------------- /docs/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/docs/src/app/favicon.ico -------------------------------------------------------------------------------- /docs/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /contracts/.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | import prettierConfig from "../.prettierrc.mjs" 2 | 3 | export default prettierConfig 4 | -------------------------------------------------------------------------------- /frontend/.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | import prettierConfig from "../.prettierrc.mjs" 2 | 3 | export default prettierConfig 4 | -------------------------------------------------------------------------------- /contracts/.papi/metadata/dev.scale: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/contracts/.papi/metadata/dev.scale -------------------------------------------------------------------------------- /contracts/.papi/metadata/pop.scale: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/contracts/.papi/metadata/pop.scale -------------------------------------------------------------------------------- /docs/src/app/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/docs/src/app/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/public/inkathon-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/frontend/public/inkathon-icon.png -------------------------------------------------------------------------------- /create-inkathon-app/.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | import prettierConfig from "../.prettierrc.mjs" 2 | 3 | export default prettierConfig 4 | -------------------------------------------------------------------------------- /frontend/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | } 4 | 5 | export default config 6 | -------------------------------------------------------------------------------- /frontend/src/app/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/frontend/src/app/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/public/inkathon-og-banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/frontend/public/inkathon-og-banner.jpg -------------------------------------------------------------------------------- /contracts/.papi/metadata/passethub.scale: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/contracts/.papi/metadata/passethub.scale -------------------------------------------------------------------------------- /docs/src/app/web-app-manifest-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/docs/src/app/web-app-manifest-512x512.png -------------------------------------------------------------------------------- /frontend/public/inkathon-readme-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/frontend/public/inkathon-readme-banner.png -------------------------------------------------------------------------------- /contracts/deployments/flipper/flipper.polkavm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/contracts/deployments/flipper/flipper.polkavm -------------------------------------------------------------------------------- /frontend/src/app/web-app-manifest-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scio-labs/inkathon/HEAD/frontend/src/app/web-app-manifest-512x512.png -------------------------------------------------------------------------------- /create-inkathon-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Build 5 | dist 6 | tsconfig.tsbuildinfo 7 | 8 | # Logs 9 | *.log 10 | 11 | # OS 12 | .DS_Store -------------------------------------------------------------------------------- /create-inkathon-app/.npmrc: -------------------------------------------------------------------------------- 1 | access=public 2 | 3 | init-author-name=Scio Labs 4 | init-author-email=hello@scio.xyz 5 | init-author-url=https://scio.xyz/ 6 | init-license=GPL-3.0 -------------------------------------------------------------------------------- /create-inkathon-app/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { run } from "./cli.ts" 3 | 4 | run().catch((error) => { 5 | console.error(error) 6 | process.exit(1) 7 | }) 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .env.local.example 2 | .github 3 | .gitignore 4 | .prettierignore 5 | .vscode 6 | README.md 7 | biome.json 8 | components.json 9 | prettier.config.js 10 | traefik.ini 11 | vercel.json -------------------------------------------------------------------------------- /frontend/src/lib/reactive-dot/types.d.ts: -------------------------------------------------------------------------------- 1 | import type { config } from "./config.js" 2 | 3 | declare module "@reactive-dot/core" { 4 | export interface Register { 5 | config: typeof config 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | pnpm-lock.yaml 4 | bun.lockb 5 | bun.lock 6 | yarn.lock 7 | 8 | LICENSE 9 | 10 | *.ts 11 | *.js 12 | *.tsx 13 | *.jsx 14 | *.cjs 15 | *.mjs 16 | *.json 17 | *.jsonc 18 | *.html 19 | -------------------------------------------------------------------------------- /docs/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | pnpm-lock.yaml 4 | bun.lockb 5 | bun.lock 6 | yarn.lock 7 | 8 | LICENSE 9 | 10 | *.ts 11 | *.js 12 | *.tsx 13 | *.jsx 14 | *.cjs 15 | *.mjs 16 | *.json 17 | *.jsonc 18 | *.html -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | const prettierConfig = { 2 | semi: false, 3 | singleQuote: true, 4 | trailingComma: "all", 5 | printWidth: 100, 6 | tabWidth: 2, 7 | useTabs: false, 8 | plugins: [], 9 | } 10 | 11 | export default prettierConfig 12 | -------------------------------------------------------------------------------- /docs/.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | const prettierConfig = { 2 | semi: false, 3 | singleQuote: true, 4 | trailingComma: "all", 5 | printWidth: 100, 6 | tabWidth: 2, 7 | useTabs: false, 8 | plugins: [], 9 | } 10 | 11 | export default prettierConfig 12 | -------------------------------------------------------------------------------- /docs/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://openapi.vercel.sh/vercel.json", 3 | "framework": "nextjs", 4 | "buildCommand": "bun run build", 5 | "devCommand": "bun run dev", 6 | "outputDirectory": "./.next", 7 | "installCommand": "bun install" 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/lib/reactive-dot/contracts.ts: -------------------------------------------------------------------------------- 1 | import { contracts } from "@polkadot-api/descriptors" 2 | import { defineContract } from "@reactive-dot/core" 3 | 4 | export const flipperContract = defineContract({ 5 | type: "ink", 6 | descriptor: contracts.flipper, 7 | }) 8 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://openapi.vercel.sh/vercel.json", 3 | "framework": "nextjs", 4 | "buildCommand": "bun run codegen && bun run build", 5 | "devCommand": "bun run dev", 6 | "outputDirectory": "./frontend/.next", 7 | "installCommand": "bun install" 8 | } 9 | -------------------------------------------------------------------------------- /contracts/.gitignore: -------------------------------------------------------------------------------- 1 | # Build 2 | target/ 3 | Cargo.lock 4 | 5 | # Environment 6 | *.env* 7 | !.env*.example 8 | 9 | # OS 10 | .DS_Store 11 | 12 | # Security 13 | *.pem 14 | 15 | # Development 16 | **/*.rs.bk 17 | .node-data 18 | deployments/**/dev.json 19 | deployments/**/dev.ts 20 | -------------------------------------------------------------------------------- /create-inkathon-app/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | pnpm-lock.yaml 4 | bun.lockb 5 | bun.lock 6 | yarn.lock 7 | 8 | LICENSE 9 | 10 | *.ts 11 | *.js 12 | *.tsx 13 | *.jsx 14 | *.cjs 15 | *.mjs 16 | *.json 17 | *.jsonc 18 | *.html 19 | 20 | # CLI-specific 21 | dist -------------------------------------------------------------------------------- /docs/src/app/api/search/route.ts: -------------------------------------------------------------------------------- 1 | import { createFromSource } from "fumadocs-core/search/server" 2 | import { source } from "@/lib/source" 3 | 4 | export const { GET } = createFromSource(source, { 5 | // https://docs.orama.com/open-source/supported-languages 6 | language: "english", 7 | }) 8 | -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | pnpm-lock.yaml 4 | bun.lockb 5 | bun.lock 6 | yarn.lock 7 | 8 | LICENSE 9 | 10 | *.ts 11 | *.js 12 | *.tsx 13 | *.jsx 14 | *.cjs 15 | *.mjs 16 | *.json 17 | *.jsonc 18 | *.html 19 | 20 | # Frontend-specific 21 | out 22 | .next 23 | public -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "biomejs.biome", 4 | "esbenp.prettier-vscode", 5 | "tamasfe.even-better-toml", 6 | "rust-lang.rust-analyzer", 7 | "ink-analyzer.ink-analyzer", 8 | "dotenv.dotenv-vscode", 9 | "bradlc.vscode-tailwindcss" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /contracts/deployments/flipper/pop.ts: -------------------------------------------------------------------------------- 1 | export const ss58Address = "5EmVkhXpog9cASDqvJy5jbxa5Y1pHdNXMd8VyBtkmJoYRmZ1" 2 | export const evmAddress = "0x7794c95406c8263cd3bd27d4a276a6b31e7f6e9b" 3 | export const blockHash = "0x4c473cc930a6cfe3dc8c4029d072e7062de1243c5d5bdbe2438645dc5be7228e" 4 | export const blockNumber = 5336320 -------------------------------------------------------------------------------- /contracts/deployments/flipper/passethub.ts: -------------------------------------------------------------------------------- 1 | export const ss58Address = "5CcLzryQxo3bukUnWpjx9NNzDZS6wfLvBPps6KZdQkDZ6gGv" 2 | export const evmAddress = "0x1821a2ddb28eef980974addf7b933403144a1822" 3 | export const blockHash = "0x205c38caae38112d05ebcc9c26276d5afd121e33c288cc676829127d6d3ee514" 4 | export const blockNumber = 643619 -------------------------------------------------------------------------------- /docs/src/lib/source.ts: -------------------------------------------------------------------------------- 1 | import { loader } from "fumadocs-core/source" 2 | import { docs } from "@/.source" 3 | 4 | // See https://fumadocs.vercel.app/docs/headless/source-api for more info 5 | export const source = loader({ 6 | // it assigns a URL to your pages 7 | baseUrl: "/", 8 | source: docs.toFumadocsSource(), 9 | }) 10 | -------------------------------------------------------------------------------- /frontend/src/components/no-ssr.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from "next/dynamic" 2 | import type { PropsWithChildren } from "react" 3 | 4 | function NoSsrComponent({ children }: PropsWithChildren) { 5 | return <>{children} 6 | } 7 | 8 | export const NoSsr = dynamic(() => Promise.resolve(NoSsrComponent), { 9 | ssr: false, 10 | }) 11 | -------------------------------------------------------------------------------- /docs/src/app/global.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | @import 'fumadocs-ui/css/neutral.css'; 3 | @import 'fumadocs-ui/css/preset.css'; 4 | 5 | @layer base { 6 | /* Increase spacing before first sidebar item */ 7 | #nd-sidebar > div:nth-child(2) > div:first-of-type > div:first-of-type > p:first-of-type { 8 | margin-top: 0.5rem; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /contracts/.prettierignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | pnpm-lock.yaml 3 | bun.lockb 4 | bun.lock 5 | yarn.lock 6 | 7 | LICENSE 8 | 9 | node_modules 10 | out 11 | .next 12 | public 13 | 14 | *.ts 15 | *.js 16 | *.tsx 17 | *.jsx 18 | *.cjs 19 | *.mjs 20 | *.json 21 | *.jsonc 22 | *.html 23 | 24 | # Contracts-specific 25 | deployments 26 | target 27 | src 28 | .node-data 29 | -------------------------------------------------------------------------------- /docs/src/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import defaultMdxComponents from "fumadocs-ui/mdx" 2 | import type { MDXComponents } from "mdx/types" 3 | 4 | // use this function to get MDX components, you will need it for rendering MDX 5 | export function getMDXComponents(components?: MDXComponents): MDXComponents { 6 | return { 7 | ...defaultMdxComponents, 8 | ...components, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "scio-labs/inkathon" }], 4 | "commit": false, 5 | "fixed": [], 6 | "privatePackages": { "version": true, "tag": true }, 7 | "linked": [["*"]], 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch" 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/layout/logo.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image" 2 | import logoSrc from "@/public/inkathon-logo-with-version--white.svg" 3 | 4 | export function Logo() { 5 | return ( 6 | inkathon Logo 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/lib/inkathon/constants.ts: -------------------------------------------------------------------------------- 1 | export const ALICE = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" 2 | 3 | export const FAUCET_URLS: { [key: string]: string } = { 4 | pop: "https://learn.onpop.io/contracts/guides/bridge-tokens-to-pop-network", 5 | passethub: "https://faucet.polkadot.io/?parachain=1111", 6 | dev: "https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9944#/accounts", 7 | } 8 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # deps 2 | /node_modules 3 | 4 | # generated content 5 | .contentlayer 6 | .content-collections 7 | .source 8 | 9 | # test & build 10 | /coverage 11 | /.next/ 12 | /out/ 13 | /build 14 | *.tsbuildinfo 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | /.pnp 20 | .pnp.js 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # others 26 | .env*.local 27 | .vercel 28 | next-env.d.ts -------------------------------------------------------------------------------- /frontend/src/lib/reactive-dot/is-tx-loading.ts: -------------------------------------------------------------------------------- 1 | import { idle, MutationError, pending } from "@reactive-dot/core" 2 | import type { useMutation } from "@reactive-dot/react" 3 | 4 | export function isTxLoading(status: ReturnType[0]): boolean { 5 | return ( 6 | status === pending || 7 | (!(status instanceof MutationError) && status !== idle && status.type !== "finalized") 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /docs/src/app/(docs)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { DocsLayout } from "fumadocs-ui/layouts/docs" 2 | import type { ReactNode } from "react" 3 | import { baseOptions } from "@/app/layout.config" 4 | import { source } from "@/lib/source" 5 | 6 | export default function Layout({ children }: { children: ReactNode }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/styles/fonts.ts: -------------------------------------------------------------------------------- 1 | import { Geist_Mono as FontMono, Geist as FontSans } from "next/font/google" 2 | import { cn } from "../lib/utils" 3 | 4 | export const fontSans = FontSans({ 5 | variable: "--font-sans", 6 | subsets: ["latin"], 7 | style: ["normal"], 8 | }) 9 | 10 | export const fontMono = FontMono({ 11 | variable: "--font-mono", 12 | subsets: ["latin"], 13 | }) 14 | 15 | export const fontStyles = cn(fontSans.variable, fontMono.variable) 16 | -------------------------------------------------------------------------------- /contracts/codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | # ENVIRONMENT VARIABLES 5 | DIR="${DIR:=./deployments}" # Directory of deployment files 6 | 7 | # Determine all contracts under `$DIR` 8 | contracts=($(find $DIR -maxdepth 1 -mindepth 1 -type d -print | xargs -n 1 basename)) 9 | 10 | # Generate types for all contracts 11 | for i in "${contracts[@]}" 12 | do 13 | echo -e "\nGenerating types for '$DIR/$i'…" 14 | bunx polkadot-api ink add $DIR/$i/$i.contract 15 | done -------------------------------------------------------------------------------- /docs/content/docs/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "inkathon", 3 | "pages": [ 4 | "---Introduction---", 5 | "[Quickstart](/)", 6 | "about", 7 | "---Learn---", 8 | "learn/structure", 9 | "learn/contracts", 10 | "learn/frontend", 11 | "learn/hosting", 12 | "---Guides---", 13 | "guides/contract-development", 14 | "guides/add-network", 15 | "guides/add-contract", 16 | "---Resources---", 17 | "resources/commands", 18 | "resources/ecosystem" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.* 5 | .yarn/* 6 | !.yarn/patches 7 | !.yarn/plugins 8 | !.yarn/releases 9 | !.yarn/versions 10 | 11 | # Build 12 | /.next/ 13 | /out/ 14 | /build 15 | *.tsbuildinfo 16 | next-env.d.ts 17 | 18 | # Environment 19 | .env* 20 | 21 | # Logs 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | .pnpm-debug.log* 26 | 27 | # OS 28 | .DS_Store 29 | 30 | # Security 31 | *.pem 32 | 33 | # Testing 34 | /coverage 35 | 36 | # Platform 37 | .vercel 38 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/app/styles/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | **/node_modules 3 | **/.pnp 4 | .pnp.js 5 | .pnp.* 6 | .yarn/* 7 | !.yarn/patches 8 | !.yarn/plugins 9 | !.yarn/releases 10 | !.yarn/sdks 11 | !.yarn/versions 12 | 13 | # Build 14 | dist 15 | *.tsbuildinfo 16 | .turbo 17 | 18 | # Environment 19 | .env 20 | .env*.local 21 | 22 | # Logs 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # OS 29 | .DS_Store 30 | 31 | # Security 32 | *.pem 33 | 34 | # Testing 35 | coverage 36 | coverage.json 37 | 38 | # IDE 39 | .history/ 40 | 41 | # Platform 42 | .vercel 43 | .gitsigners 44 | -------------------------------------------------------------------------------- /contracts/src/flipper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flipper" 3 | version = "6.0.0-alpha" 4 | authors = ["Use Ink "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [dependencies] 9 | ink = { git = "https://github.com/use-ink/ink", tag = "v6.0.0-alpha", default-features = false } 10 | 11 | [dev-dependencies] 12 | ink_e2e = { git = "https://github.com/use-ink/ink", tag = "v6.0.0-alpha", features = [ 13 | "sandbox", 14 | ] } 15 | hex = { version = "0.4.3" } 16 | 17 | [lib] 18 | path = "lib.rs" 19 | 20 | [features] 21 | default = ["std"] 22 | std = ["ink/std"] 23 | ink-as-dependency = [] 24 | e2e-tests = [] 25 | 26 | [package.metadata.ink-lang] 27 | abi = "ink" 28 | -------------------------------------------------------------------------------- /frontend/src/components/web3/account-provider.tsx: -------------------------------------------------------------------------------- 1 | import type { WalletAccount } from "@reactive-dot/core/wallets.js" 2 | import { SignerProvider } from "@reactive-dot/react" 3 | import { createContext, type PropsWithChildren } from "react" 4 | 5 | export const accountContext = createContext(undefined) 6 | 7 | export function AccountProvider({ 8 | account, 9 | children, 10 | }: PropsWithChildren<{ 11 | account: WalletAccount | undefined 12 | }>) { 13 | return ( 14 | 15 | {children} 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/hooks/use-is-mapped.ts: -------------------------------------------------------------------------------- 1 | import { ss58ToEthereum } from "@polkadot-api/sdk-ink" 2 | import { idle } from "@reactive-dot/core" 3 | import { useLazyLoadQuery } from "@reactive-dot/react" 4 | import { use } from "react" 5 | import { accountContext } from "@/components/web3/account-provider" 6 | 7 | export function useIsMapped() { 8 | const account = use(accountContext) 9 | 10 | const originalAccount = useLazyLoadQuery((query) => 11 | account === undefined 12 | ? undefined 13 | : query.storage("Revive", "OriginalAccount", [ss58ToEthereum(account?.address)]), 14 | ) 15 | 16 | return originalAccount === idle ? idle : originalAccount !== undefined 17 | } 18 | -------------------------------------------------------------------------------- /docs/source.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, defineDocs, frontmatterSchema, metaSchema } from "fumadocs-mdx/config" 2 | 3 | // You can customise Zod schemas for frontmatter and `meta.json` here 4 | // see https://fumadocs.vercel.app/docs/mdx/collections#define-docs 5 | export const docs = defineDocs({ 6 | docs: { 7 | schema: frontmatterSchema, 8 | }, 9 | meta: { 10 | schema: metaSchema, 11 | }, 12 | }) 13 | 14 | export default defineConfig({ 15 | mdxOptions: { 16 | rehypeCodeOptions: { 17 | themes: { 18 | light: "catppuccin-latte", 19 | dark: "catppuccin-mocha", 20 | }, 21 | inline: "tailing-curly-colon", 22 | }, 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /contracts/.papi/descriptors/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0-autogenerated.17095327612331057434", 3 | "name": "@polkadot-api/descriptors", 4 | "files": [ 5 | "dist" 6 | ], 7 | "exports": { 8 | ".": { 9 | "types": "./dist/index.d.ts", 10 | "module": "./dist/index.mjs", 11 | "import": "./dist/index.mjs", 12 | "require": "./dist/index.js" 13 | }, 14 | "./package.json": "./package.json" 15 | }, 16 | "main": "./dist/index.js", 17 | "module": "./dist/index.mjs", 18 | "browser": "./dist/index.mjs", 19 | "types": "./dist/index.d.ts", 20 | "sideEffects": false, 21 | "peerDependencies": { 22 | "polkadot-api": ">=1.21.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /create-inkathon-app/src/installer.ts: -------------------------------------------------------------------------------- 1 | import { exec } from "node:child_process" 2 | import { promisify } from "node:util" 3 | 4 | const execAsync = promisify(exec) 5 | 6 | const getExecOptions = (projectPath: string) => ({ 7 | cwd: projectPath, 8 | env: { 9 | ...process.env, 10 | // Ensure Bun doesn't prompt for telemetry 11 | BUN_DISABLE_TELEMETRY: "1", 12 | }, 13 | }) 14 | 15 | export async function installDependencies(projectPath: string): Promise { 16 | await execAsync("bun install", getExecOptions(projectPath)) 17 | } 18 | 19 | export async function runCodegen(projectPath: string): Promise { 20 | await execAsync("bun run codegen", getExecOptions(projectPath)) 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useTheme } from "next-themes" 4 | import { Toaster as Sonner, type ToasterProps } from "sonner" 5 | 6 | const Toaster = ({ ...props }: ToasterProps) => { 7 | const { theme = "system" } = useTheme() 8 | 9 | return ( 10 | 22 | ) 23 | } 24 | 25 | export { Toaster } 26 | -------------------------------------------------------------------------------- /frontend/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ReactiveDotProvider } from "@reactive-dot/react" 4 | import { Footer } from "@/components/layout/footer" 5 | import { Header } from "@/components/layout/header" 6 | import { NoSsr } from "@/components/no-ssr" 7 | import { config } from "@/lib/reactive-dot/config" 8 | import { App } from "./app" 9 | 10 | export default function Home() { 11 | return ( 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /create-inkathon-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "lib": ["ES2022"], 6 | "moduleResolution": "bundler", 7 | "allowImportingTsExtensions": true, 8 | "noEmit": true, 9 | "composite": false, 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noUncheckedIndexedAccess": true, 15 | "skipLibCheck": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "esModuleInterop": true, 18 | "resolveJsonModule": true, 19 | "types": ["node"] 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_modules", "dist"] 23 | } 24 | -------------------------------------------------------------------------------- /contracts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | # ENVIRONMENT VARIABLES 5 | CONTRACTS_DIR="${CONTRACTS_DIR:=./src}" # Base contract directory 6 | DIR="${DIR:=./deployments}" # Output directory for build files 7 | 8 | # Copy command helper (cross-platform) 9 | CP_CMD=$(command -v cp &> /dev/null && echo "cp" || echo "copy") 10 | 11 | # Determine all contracts under `$CONTRACTS_DIR` 12 | contracts=($(find $CONTRACTS_DIR -maxdepth 1 -type d -exec test -f {}/Cargo.toml \; -print | xargs -n 1 basename)) 13 | 14 | # Test all contracts 15 | for i in "${contracts[@]}" 16 | do 17 | echo -e "\nTesting '$CONTRACTS_DIR/$i/Cargo.toml'…" 18 | (cd $CONTRACTS_DIR/$i && pop test) 19 | # cargo test --manifest-path $CONTRACTS_DIR/$i/Cargo.toml 20 | done -------------------------------------------------------------------------------- /frontend/src/components/layout/skeletons.tsx: -------------------------------------------------------------------------------- 1 | import { Button, type ButtonProps } from "../ui/button-extended" 2 | import { Wrapper } from "./wrapper" 3 | 4 | export function AppSkeleton() { 5 | return ( 6 | 7 | 8 | 9 |
10 | 11 | 12 |
13 |
14 | ) 15 | } 16 | 17 | export function CardSkeleton() { 18 | return
19 | } 20 | 21 | export function ButtonSkeleton(props: ButtonProps) { 22 | return 43 | 44 | 45 |

This account needs to be mapped first

46 |
47 | 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /docs/content/docs/resources/ecosystem.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ecosystem 3 | description: Documentations and resources available in the ink! ecosystem 4 | --- 5 | 6 | | Resource | Description | Link | 7 | | ----------------------- | ----------------------------------------------- | ---------------------------------------------------------- | 8 | | **ink! Documentation** | Official documentation for ink! smart contracts | [use.ink](https://use.ink/) | 9 | | **ink! Examples** | Repository of example contracts | [GitHub](https://github.com/use-ink/ink-examples) | 10 | | `cargo contract` | CLI tool for building and deploying contracts | [GitHub](https://github.com/use-ink/cargo-contract) | 11 | | `ink-node` | Local development node for ink! | [GitHub](https://github.com/use-ink/ink-node) | 12 | | **Pop CLI** | Enhanced development experience for ink! | [learn.onpop.io](https://learn.onpop.io/contracts) | 13 | | **PAPI (Polkadot API)** | Type-safe TypeScript API for Polkadot | [papi.how](https://papi.how) | 14 | | **ReactiveDOT** | Reactive state management library using Papi | [reactivedot.dev](https://reactivedot.dev) | 15 | | **Contracts UI** | Web interface for contract interaction | [ui.use.ink](https://ui.use.ink/) | 16 | | **Polkadot-JS UI** | Web interface for Polkadot network interaction | [polkadot.js.org](https://polkadot.js.org/apps/#/explorer) | 17 | -------------------------------------------------------------------------------- /frontend/src/components/web3/chain-select.tsx: -------------------------------------------------------------------------------- 1 | import type { ChainId } from "@reactive-dot/core" 2 | import { useChainSpecData } from "@reactive-dot/react" 3 | import { config } from "@/lib/reactive-dot/config" 4 | import { buttonVariants } from "../ui/button-extended" 5 | import { 6 | Select, 7 | SelectContent, 8 | SelectGroup, 9 | SelectItem, 10 | SelectLabel, 11 | SelectTrigger, 12 | SelectValue, 13 | } from "../ui/select" 14 | 15 | interface ChainSelectProps { 16 | chainId: ChainId 17 | setChainId: (chainId: ChainId) => void 18 | } 19 | export function ChainSelect({ chainId, setChainId }: ChainSelectProps) { 20 | const chainIds = Object.keys(config.chains) as (keyof typeof config.chains)[] 21 | 22 | return ( 23 | 42 | ) 43 | } 44 | 45 | function ChainSelectItem({ chainId }: { chainId: keyof typeof config.chains }) { 46 | const { name } = useChainSpecData({ chainId }) 47 | 48 | const formattedName = name === "passet-hub" ? "Passet Hub Testnet" : name 49 | 50 | return {formattedName} 51 | } 52 | -------------------------------------------------------------------------------- /docs/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Quickstart 3 | description: Kickstart your ink! hackathon projects and production dApps. 4 | --- 5 | 6 | import { Accordion, Accordions } from 'fumadocs-ui/components/accordion' 7 | import { Steps, Step } from 'fumadocs-ui/components/steps' 8 | import { Tab, Tabs } from 'fumadocs-ui/components/tabs' 9 | import Image from 'next/image' 10 | 11 | inkathon Banner 17 | 18 | Welcome to **inkathon v6** – the next generation full-stack boilerplate for ink! smart contracts running on PolkaVM. Powered by Papi, ReactiveDOT, Pop CLI, and more. 19 | 20 | Get up and running in seconds: 21 | 22 | 23 | 24 | 25 | ### Install Prerequisites 26 | 27 | - Node.js v20+ (recommended via [nvm](https://github.com/nvm-sh/nvm)) 28 | - [Bun](https://bun.sh/) package manager 29 | 30 | 31 | 32 | 33 | 34 | ### Create Your Project 35 | 36 | ```npm 37 | npx create-inkathon-app@latest 38 | ``` 39 | 40 | 41 | 42 | 43 | 44 | ### Start Frontend 45 | 46 | ```bash 47 | cd 48 | 49 | bun run dev 50 | ``` 51 | 52 | Congrats 🎉 Now open `localhost:3000` in your browser to play with your first local ink! dApp. 53 | 54 | 55 | 56 | 57 | ## Next Steps 58 | 59 | 60 | 65 | 70 | 71 | -------------------------------------------------------------------------------- /contracts/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # contracts 2 | 3 | ## 6.1.2 4 | 5 | ### Patch Changes 6 | 7 | - [#84](https://github.com/scio-labs/inkathon/pull/84) [`cb8ed40`](https://github.com/scio-labs/inkathon/commit/cb8ed40324892032d95e5952c23a0a21d6086044) Thanks [@voliva](https://github.com/voliva)! - update ink sdk dependencies 8 | 9 | fixes compatibility issues with newer runtimes 10 | 11 | ## 6.1.1 12 | 13 | ### Patch Changes 14 | 15 | - [`1498e99`](https://github.com/scio-labs/inkathon/commit/1498e99e082e0356975ef9df468e157b45cf883d) Thanks [@wottpal](https://github.com/wottpal)! - Added new contract test utility scripts using Pop CLI. Add sections about testing contracts in the docs accordingly. 16 | 17 | ## 6.1.0 18 | 19 | ### Minor Changes 20 | 21 | - [`f88dcec`](https://github.com/scio-labs/inkathon/commit/f88dcece84adb61a60a218a307f2de0697be5d88) Thanks [@wottpal](https://github.com/wottpal)! - Release new standalone documentation with learning resources & guides to get started. Check out https://docs.inkathon.xyz! 🚀 22 | 23 | ### Patch Changes 24 | 25 | - [`8024758`](https://github.com/scio-labs/inkathon/commit/8024758a0ed26a1d64dbe084121f8d3cff1766fc) Thanks [@wottpal](https://github.com/wottpal)! - Support Node.js v24 26 | 27 | ## 6.0.1 28 | 29 | ### Patch Changes 30 | 31 | - [`17cfc50`](https://github.com/scio-labs/inkathon/commit/17cfc50aaec65a9e52e664eb6ba40ac5f1fb04d4) Thanks [@wottpal](https://github.com/wottpal)! - Remove `@inkathon/` package name prefix for simplicity 32 | 33 | ## 6.0.0 34 | 35 | ### Major Changes 36 | 37 | - [`980eb4e`](https://github.com/scio-labs/inkathon/commit/980eb4e76b882a98aad95d3b2f561581afa361d1) Thanks [@wottpal](https://github.com/wottpal)! - Releasing ink v6 compatible inkathon boilerplate rewrite (Alpha). 38 | -------------------------------------------------------------------------------- /frontend/src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import { Slot } from "@radix-ui/react-slot" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | import type * as React from "react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const badgeVariants = cva( 8 | "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-md border px-2 py-0.5 font-medium text-xs transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3", 9 | { 10 | variants: { 11 | variant: { 12 | default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", 15 | destructive: 16 | "border-transparent bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90", 17 | outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | }, 24 | ) 25 | 26 | function Badge({ 27 | className, 28 | variant, 29 | asChild = false, 30 | ...props 31 | }: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }) { 32 | const Comp = asChild ? Slot : "span" 33 | 34 | return 35 | } 36 | 37 | export { Badge, badgeVariants } 38 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "contracts/.node-data/**": true, 4 | "contracts/.papi/*/**": true 5 | }, 6 | "search.useIgnoreFiles": true, 7 | "search.useParentIgnoreFiles": true, 8 | "biome.enabled": true, 9 | "eslint.enable": false, 10 | "prettier.enable": true, 11 | "editor.formatOnSave": true, 12 | "editor.formatOnType": false, 13 | "editor.codeActionsOnSave": { 14 | "source.fixAll.eslint": "never", 15 | "source.fixAll.biome": "explicit", 16 | "source.organizeImports": "never", 17 | "source.organizeImports.biome": "explicit" 18 | }, 19 | "[javascriptreact][typescriptreact][javascript][typescript][json][jsonc][tailwindcss][html]": { 20 | "editor.defaultFormatter": "biomejs.biome" 21 | }, 22 | "[markdown][mdx][yml][yaml][sass][scss][css][tailwindcss]": { 23 | "editor.defaultFormatter": "esbenp.prettier-vscode" 24 | }, 25 | "[markdown][mdx]": { 26 | "editor.wordWrap": "on" 27 | }, 28 | "[rust]": { 29 | "editor.defaultFormatter": "rust-lang.rust-analyzer" 30 | }, 31 | "typescript.updateImportsOnFileMove.enabled": "always", 32 | "javascript.updateImportsOnFileMove.enabled": "always", 33 | "typescript.tsdk": "node_modules/typescript/lib", 34 | "typescript.enablePromptUseWorkspaceTsdk": true, 35 | "typescript.preferences.autoImportSpecifierExcludeRegexes": [ 36 | "@radix-ui", 37 | "next/router", 38 | "next/dist", 39 | "react-aria-components" 40 | ], 41 | "files.associations": { 42 | "*.css": "tailwindcss" 43 | }, 44 | "tailwindCSS.experimental.classRegex": [ 45 | ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], 46 | ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] 47 | ], 48 | "github-actions.use-enterprise": true 49 | } 50 | -------------------------------------------------------------------------------- /docs/src/app/layout.config.tsx: -------------------------------------------------------------------------------- 1 | import { GithubInfo } from "fumadocs-ui/components/github-info" 2 | import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared" 3 | import { GlobeIcon, MessagesSquareIcon } from "lucide-react" 4 | import Image from "next/image" 5 | 6 | /** 7 | * Shared layout configurations 8 | */ 9 | export const baseOptions: BaseLayoutProps = { 10 | nav: { 11 | title: ( 12 | <> 13 | inkathon 21 | inkathon 29 | 30 | ), 31 | }, 32 | 33 | links: [ 34 | { 35 | type: "custom", 36 | children: ( 37 | 42 | ), 43 | }, 44 | { 45 | icon: , 46 | text: "Live Demo", 47 | url: "https://inkathon.xyz", 48 | secondary: false, 49 | external: true, 50 | }, 51 | { 52 | icon: , 53 | text: "Telegram", 54 | url: "https://t.me/inkathon", 55 | secondary: false, 56 | external: true, 57 | }, 58 | ], 59 | githubUrl: "https://github.com/scio-labs/inkathon", 60 | themeSwitch: { 61 | enabled: false, 62 | }, 63 | } 64 | -------------------------------------------------------------------------------- /docs/content/docs/about.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: About 3 | description: Kickstart your ink! hackathon projects and production dApps. 4 | --- 5 | 6 | ## The Project 7 | 8 | The inkathon boilerplate is almost as old as ink! itself. With 200+ stars, 25k+ `useInkathon` downloads, and 250+ public dependent repositories speaking for themselves. Its goal is to lower the entry barrier for all kinds of developers by reducing the initial setup cost by dozens of hours. 9 | 10 | We've also initiated the most active [ink! Telegram group](https://t.me/inkathon) and hosted a bunch of developer workshops at hackathons and conferences. 11 | 12 | ## The Boilerplate 13 | 14 | This project offers a complete setup for developing full-stack dApps on Polkadot with ink! smart contracts and Next.js and now shines in new glory with full ink! v6 support. Powered by Papi, ReactiveDOT, Pop CLI, and more. 15 | 16 | ### New Features in v6 17 | 18 | - **Full ink! v6 support** with PolkaVM compatibility 19 | - **Type-safe contract interactions** via PAPI 20 | - **Modern stack**: Bun, Next.js 15, React 19, Tailwind CSS v4 21 | - **Improved DX**: Better build scripts & deployment automation 22 | - **Production-ready**: Docker support, self-hosting optimized 23 | - **New `create-inkathon-app` CLI** for setting up the boilerplate in seconds 24 | 25 | ## Changelog 26 | 27 | The v6 changelog is available on the [GitHub releases page](https://github.com/scio-labs/inkathon/releases). 28 | 29 | The old ink! v5 compatible boilerplate and documentation is available on the 30 | [`v1`](https://github.com/scio-labs/inkathon/tree/v1) branch. 31 | 32 | 33 | Migrate to ink! v6 by following the [migration guide on 34 | use.ink](https://use.ink/docs/v6/faq/migrating-from-ink-5-to-6/). 35 | 36 | -------------------------------------------------------------------------------- /frontend/src/components/layout/header.tsx: -------------------------------------------------------------------------------- 1 | import { cva } from "class-variance-authority" 2 | import { BookIcon, GithubIcon, MessagesSquareIcon } from "lucide-react" 3 | import { Logo } from "./logo" 4 | import { Wrapper } from "./wrapper" 5 | 6 | const headerLinkVariants = cva([ 7 | "group flex cursor-pointer items-center gap-1.5", 8 | "*:last:underline-offset-2 *:last:group-hover:underline [&_svg]:size-4 [&_svg]:shrink-0", 9 | ]) 10 | 11 | export function Header() { 12 | return ( 13 | 14 | 15 | 16 |

17 | Next generation full-stack boilerplate for ink! smart contracts running on PolkaVM. Powered 18 | by Papi, ReactiveDOT, Pop CLI, and more. 19 |

20 | 21 | 52 |
53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /create-inkathon-app/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import picocolors from "picocolors" 2 | import type { Spinner } from "yocto-spinner" 3 | import createSpinner from "yocto-spinner" 4 | 5 | const pc = picocolors 6 | 7 | const isUnicodeSupported = 8 | process.platform !== "win32" || 9 | Boolean(process.env.WT_SESSION) || // Windows Terminal 10 | process.env.TERM_PROGRAM === "vscode" 11 | 12 | const symbols = { 13 | info: isUnicodeSupported ? "ℹ" : "i", 14 | success: isUnicodeSupported ? "✔" : "√", 15 | error: isUnicodeSupported ? "✖" : "×", 16 | warn: isUnicodeSupported ? "⚠" : "‼", 17 | } 18 | 19 | interface SpinnerInstance { 20 | success: (msg?: string) => void 21 | error: (msg?: string) => void 22 | stop: () => void 23 | } 24 | 25 | export const logger = { 26 | info: (msg: string) => console.log(`${pc.cyan(symbols.info)} ${msg}`), 27 | success: (msg: string) => console.log(`${pc.green(symbols.success)} ${msg}`), 28 | error: (msg: string) => console.log(`${pc.red(symbols.error)} ${msg}`), 29 | warn: (msg: string) => console.log(`${pc.yellow(symbols.warn)} ${msg}`), 30 | 31 | spinner: (text: string) => { 32 | let spinner: Spinner | null = null 33 | 34 | return { 35 | start: (): SpinnerInstance => { 36 | spinner = createSpinner({ text }) 37 | spinner.start() 38 | 39 | return { 40 | success: (msg?: string) => { 41 | if (spinner) { 42 | spinner.success(msg || text) 43 | } 44 | }, 45 | error: (msg?: string) => { 46 | if (spinner) { 47 | spinner.error(msg || text) 48 | } 49 | }, 50 | stop: () => { 51 | if (spinner) { 52 | spinner.stop() 53 | } 54 | }, 55 | } 56 | }, 57 | } 58 | }, 59 | } 60 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "6.1.2", 4 | "private": true, 5 | "type": "module", 6 | "engines": { 7 | "node": ">=20 <=24" 8 | }, 9 | "engineStrict": true, 10 | "scripts": { 11 | "dev": "next dev --turbo", 12 | "build": "next build --turbo", 13 | "build:standalone": "NEXT_OUTPUT=standalone next build --turbo", 14 | "start": "next start", 15 | "clean": "rm -rf .next tsconfig.tsbuildinfo", 16 | "lint": "biome check . && prettier . --check", 17 | "lint:fix": "biome check . --write && prettier . --write", 18 | "typecheck": "tsc --pretty --noEmit" 19 | }, 20 | "dependencies": { 21 | "@polkadot-api/descriptors": "file:../contracts/.papi/descriptors", 22 | "@polkadot-api/sdk-ink": "^0.6.0", 23 | "@radix-ui/react-dialog": "^1.1.15", 24 | "@radix-ui/react-dropdown-menu": "^2.1.16", 25 | "@radix-ui/react-select": "^2.2.6", 26 | "@radix-ui/react-separator": "^1.1.8", 27 | "@radix-ui/react-slot": "^1.2.4", 28 | "@radix-ui/react-tooltip": "^1.2.8", 29 | "@reactive-dot/core": "^0.67.0", 30 | "@reactive-dot/react": "^0.67.0", 31 | "@reactive-dot/wallet-walletconnect": "^0.18.4", 32 | "class-variance-authority": "^0.7.1", 33 | "clsx": "^2.1.1", 34 | "contracts": "workspace:*", 35 | "lucide-react": "^0.561.0", 36 | "next": "^16.0.10", 37 | "next-themes": "^0.4.6", 38 | "polkadot-api": "^1.23.1", 39 | "react": "^19.2.3", 40 | "react-dom": "^19.2.3", 41 | "rxjs": "^7.8.2", 42 | "sonner": "^2.0.7", 43 | "tailwind-merge": "^3.4.0" 44 | }, 45 | "devDependencies": { 46 | "@tailwindcss/postcss": "^4.1.18", 47 | "@tailwindcss/typography": "^0.5.19", 48 | "@types/node": "^24.10.3", 49 | "@types/react": "^19.2.7", 50 | "@types/react-dom": "^19.2.3", 51 | "tailwindcss": "^4.1.18", 52 | "tw-animate-css": "^1.4.0", 53 | "typescript": "^5.9.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /frontend/src/components/web3/account-balance.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import type { WalletAccount } from "@reactive-dot/core/wallets.js" 4 | import { useChainId, useSpendableBalance } from "@reactive-dot/react" 5 | import { FuelIcon } from "lucide-react" 6 | import { use } from "react" 7 | import { FAUCET_URLS } from "@/lib/inkathon/constants" 8 | import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip" 9 | import { accountContext } from "./account-provider" 10 | 11 | export function AccountBalance() { 12 | const account = use(accountContext) 13 | 14 | if (account === undefined) return null 15 | 16 | return 17 | } 18 | 19 | type AccountBalanceProps = { 20 | account: WalletAccount 21 | } 22 | 23 | function InnerAccountBalance({ account }: AccountBalanceProps) { 24 | const chainId = useChainId() 25 | const spendableBalance = useSpendableBalance(account.address) 26 | const faucetUrl = FAUCET_URLS[chainId] 27 | 28 | return ( 29 |
30 | {spendableBalance.toLocaleString()} 31 | 32 | {!!faucetUrl && ( 33 | 34 | 35 | 41 | 42 | 43 | 44 | 45 |

Get testnet tokens

46 |
47 |
48 | )} 49 |
50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inkathon", 3 | "description": "Full-Stack DApp Boilerplate for ink! Smart Contracts", 4 | "homepage": "https://inkathon.xyz", 5 | "author": "Scio Labs (https://scio.xyz)", 6 | "private": true, 7 | "type": "module", 8 | "workspaces": [ 9 | "frontend", 10 | "contracts", 11 | "create-inkathon-app", 12 | "docs" 13 | ], 14 | "engines": { 15 | "node": ">=20 <=24" 16 | }, 17 | "engineStrict": true, 18 | "packageManager": "bun@1.3.4", 19 | "scripts": { 20 | "dev": "bun run -F frontend dev", 21 | "node": "bun run -F contracts node", 22 | "dev-and-node": "concurrently \"bun run dev\" \"bun run node\" --names \"next,node\" --kill-others", 23 | "codegen": "bun run -F contracts codegen", 24 | "build": "bun run codegen && bun run -F frontend build", 25 | "build:standalone": "bun run codegen && bun run -F frontend build:standalone", 26 | "start": "bun run -F frontend start", 27 | "test": "bun run -F contracts test", 28 | "typecheck": "bun run -F '*' typecheck", 29 | "clean": "bun run -F '*' clean", 30 | "clean-install": "find . -type d -name 'node_modules' -prune -exec rm -rf '{}' + && find . -name 'bun.lock' -delete && bun install", 31 | "lint": "bun run -F '*' lint", 32 | "lint:fix": "bun run -F '*' lint:fix", 33 | "changeset:version": "changeset version", 34 | "changeset:publish": "changeset publish", 35 | "update": "bunx taze minor --interactive --recursive --write --install --includeLocked --exclude \"@types/node\"" 36 | }, 37 | "devDependencies": { 38 | "@biomejs/biome": "^2.3.8", 39 | "@changesets/changelog-github": "^0.5.2", 40 | "@changesets/cli": "^2.29.8", 41 | "concurrently": "^9.2.1", 42 | "nano-staged": "^0.9.0", 43 | "prettier": "^3.7.4", 44 | "taze": "^19.9.2", 45 | "typescript": "^5.9.3", 46 | "next": "^16.0.10" 47 | }, 48 | "trustedDependencies": [ 49 | "@tailwindcss/oxide", 50 | "contracts", 51 | "esbuild", 52 | "sharp" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /create-inkathon-app/src/cleanup.ts: -------------------------------------------------------------------------------- 1 | import { readdir, rm } from "node:fs/promises" 2 | import { join } from "node:path" 3 | 4 | const DIRECTORIES_TO_REMOVE: string[] = [".changeset", ".github", "create-inkathon-app", "docs"] 5 | const FILES_TO_REMOVE: string[] = [] 6 | 7 | async function cleanupDirectories(projectPath: string): Promise { 8 | for (const dir of DIRECTORIES_TO_REMOVE) { 9 | try { 10 | await rm(join(projectPath, dir), { recursive: true, force: true }) 11 | } catch (error) { 12 | // Silently continue if directory doesn't exist 13 | } 14 | } 15 | } 16 | 17 | async function cleanupFiles(projectPath: string): Promise { 18 | for (const file of FILES_TO_REMOVE) { 19 | try { 20 | await rm(join(projectPath, file), { force: true }) 21 | } catch (error) { 22 | // Silently continue if file doesn't exist 23 | } 24 | } 25 | } 26 | 27 | async function cleanupChangelogFiles(projectPath: string): Promise { 28 | async function removeChangelogsRecursively(dir: string): Promise { 29 | const entries = await readdir(dir, { withFileTypes: true }) 30 | 31 | for (const entry of entries) { 32 | const fullPath = join(dir, entry.name) 33 | 34 | if (entry.isDirectory()) { 35 | // Skip node_modules and hidden directories (except those we're already handling) 36 | if (entry.name === "node_modules" || (entry.name.startsWith(".") && entry.name !== ".")) { 37 | continue 38 | } 39 | await removeChangelogsRecursively(fullPath) 40 | } else if (entry.isFile() && entry.name === "CHANGELOG.md") { 41 | try { 42 | await rm(fullPath, { force: true }) 43 | } catch (error) { 44 | // Silently continue if file cannot be removed 45 | } 46 | } 47 | } 48 | } 49 | 50 | await removeChangelogsRecursively(projectPath) 51 | } 52 | 53 | export async function cleanupRepository(projectPath: string): Promise { 54 | await cleanupDirectories(projectPath) 55 | await cleanupFiles(projectPath) 56 | await cleanupChangelogFiles(projectPath) 57 | } 58 | -------------------------------------------------------------------------------- /frontend/src/components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as TooltipPrimitive from "@radix-ui/react-tooltip" 4 | import type * as React from "react" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function TooltipProvider({ 9 | delayDuration = 0, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 18 | ) 19 | } 20 | 21 | function Tooltip({ ...props }: React.ComponentProps) { 22 | return ( 23 | 24 | 25 | 26 | ) 27 | } 28 | 29 | function TooltipTrigger({ ...props }: React.ComponentProps) { 30 | return 31 | } 32 | 33 | function TooltipContent({ 34 | className, 35 | sideOffset = 0, 36 | children, 37 | ...props 38 | }: React.ComponentProps) { 39 | return ( 40 | 41 | 50 | {children} 51 | 52 | 53 | 54 | ) 55 | } 56 | 57 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } 58 | -------------------------------------------------------------------------------- /contracts/scripts/utils/write-addresses.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path" 2 | import dedent from "dedent" 3 | import yoctoSpinner from "yocto-spinner" 4 | import type { deployContract } from "./deploy-contract" 5 | 6 | type WritableDeployment = Omit>, "contract"> 7 | 8 | /** 9 | * Writes each given contract address & block to a `{baseDir}/{contract}/{network}.ts` file. 10 | * 11 | * @options 12 | * Environment variables: 13 | * DIR - Directory to write the contract addresses to. Default: `./deployments` 14 | */ 15 | export async function writeAddresses( 16 | deployments: { [name: string]: WritableDeployment }, 17 | metadata?: { [key: string]: string | number }, 18 | ) { 19 | console.log() 20 | const spinner = yoctoSpinner({ text: "Writing contract addresses…" }).start() 21 | 22 | try { 23 | const baseDir = process.env.DIR || "./deployments" 24 | const chainId = process.env.CHAIN || "dev" 25 | 26 | for (const [name, deployment] of Object.entries(deployments)) { 27 | const relativePath = path.join(baseDir, name, `${chainId}.ts`) 28 | const absolutePath = path.join(path.resolve(), relativePath) 29 | 30 | // Construct file content 31 | let fileContent = dedent` 32 | export const ss58Address = "${deployment.ss58Address}" 33 | export const evmAddress = "${deployment.evmAddress}" 34 | export const blockHash = "${deployment.block.hash}" 35 | export const blockNumber = ${deployment.block.number} 36 | ` 37 | 38 | // Iterate over metadata keys and write them to the file 39 | if (metadata) { 40 | for (const [key, value] of Object.entries(metadata)) { 41 | const valueFormatted = typeof value === "string" ? `'${value}'` : value 42 | fileContent += `export const ${key} = ${valueFormatted}\n` 43 | } 44 | } 45 | 46 | // Write file 47 | await Bun.write(absolutePath, fileContent) 48 | 49 | spinner.success(`Exported deployment info to file '${relativePath}'`) 50 | } 51 | } catch (error) { 52 | spinner.error("Failed to write contract addresses") 53 | throw error 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /create-inkathon-app/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # create-inkathon-app 2 | 3 | ## 6.1.0 4 | 5 | ### Patch Changes 6 | 7 | - [`8024758`](https://github.com/scio-labs/inkathon/commit/8024758a0ed26a1d64dbe084121f8d3cff1766fc) Thanks [@wottpal](https://github.com/wottpal)! - Support Node.js v24 8 | 9 | ## 6.0.7 10 | 11 | ### Patch Changes 12 | 13 | - [`7e7336b`](https://github.com/scio-labs/inkathon/commit/7e7336b2395798e5c4b183ba44c12027e545dbbb) Thanks [@wottpal](https://github.com/wottpal)! - Updated dependencies 14 | 15 | ## 6.0.6 16 | 17 | ### Patch Changes 18 | 19 | - [`2f9014c`](https://github.com/scio-labs/inkathon/commit/2f9014cc082796b2759d558b79da39fe7b0c88d6) Thanks [@wottpal](https://github.com/wottpal)! - Add link to new documentation 20 | 21 | ## 6.0.5 22 | 23 | ### Patch Changes 24 | 25 | - [`069d669`](https://github.com/scio-labs/inkathon/commit/069d6698e5d8d28c652790d68fcea3cd8e52b4ef) Thanks [@wottpal](https://github.com/wottpal)! - Remove new `docs` monorepo package 26 | 27 | ## 6.0.4 28 | 29 | ### Patch Changes 30 | 31 | - [`ed175d6`](https://github.com/scio-labs/inkathon/commit/ed175d6435084bea9e251f567f21e85fa7d2a91e) Thanks [@wottpal](https://github.com/wottpal)! - Ensure Node.js version compatibility 32 | 33 | ## 6.0.3 34 | 35 | ### Patch Changes 36 | 37 | - [`072dac9`](https://github.com/scio-labs/inkathon/commit/072dac94c5ae214098c7383568ff1f57a392d01f) Thanks [@wottpal](https://github.com/wottpal)! - Minor message output improvements 38 | 39 | ## 6.0.2 40 | 41 | ### Patch Changes 42 | 43 | - [`800bb00`](https://github.com/scio-labs/inkathon/commit/800bb003514cd6a1c05f5b48a48891211a0b8ed2) Thanks [@wottpal](https://github.com/wottpal)! - Various minor improvements & fixes 44 | 45 | ## 6.0.1 46 | 47 | ### Patch Changes 48 | 49 | - [`71851ee`](https://github.com/scio-labs/inkathon/commit/71851ee78475d3b887920cdf9d4f59023b7ac257) Thanks [@wottpal](https://github.com/wottpal)! - Various minor improvements & fixes 50 | 51 | ## 6.0.0 52 | 53 | ### Major Changes 54 | 55 | - [`ace5107`](https://github.com/scio-labs/inkathon/commit/ace5107ab3f059f93e4893410c3cd0999cd1e454) Thanks [@wottpal](https://github.com/wottpal)! - Initial release of create-inkathon-app - CLI tool to scaffold new inkathon projects 56 | -------------------------------------------------------------------------------- /frontend/src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import type * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Card({ className, ...props }: React.ComponentProps<"div">) { 6 | return ( 7 |
15 | ) 16 | } 17 | 18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 19 | return ( 20 |
28 | ) 29 | } 30 | 31 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 32 | return ( 33 |
38 | ) 39 | } 40 | 41 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 42 | return ( 43 |
48 | ) 49 | } 50 | 51 | function CardAction({ className, ...props }: React.ComponentProps<"div">) { 52 | return ( 53 |
58 | ) 59 | } 60 | 61 | function CardContent({ className, ...props }: React.ComponentProps<"div">) { 62 | return
63 | } 64 | 65 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 66 | return ( 67 |
72 | ) 73 | } 74 | 75 | export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent } 76 | -------------------------------------------------------------------------------- /create-inkathon-app/src/template.ts: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile } from "node:fs/promises" 2 | import { join } from "node:path" 3 | 4 | export async function updateTemplate( 5 | projectPath: string, 6 | displayName: string, 7 | packageName: string, 8 | ): Promise { 9 | // Update root package.json 10 | await updatePackageJson(join(projectPath, "package.json"), true, packageName) 11 | 12 | // Update frontend package.json 13 | await updatePackageJson(join(projectPath, "frontend", "package.json"), false) 14 | 15 | // Update contracts package.json 16 | await updatePackageJson(join(projectPath, "contracts", "package.json"), false) 17 | 18 | // Update README.md 19 | await updateReadme(join(projectPath, "README.md"), displayName) 20 | } 21 | 22 | async function updatePackageJson(filePath: string, isRoot: boolean, name?: string): Promise { 23 | const content = await readFile(filePath, "utf-8") 24 | const pkg = JSON.parse(content) 25 | 26 | if (isRoot) { 27 | // Update the name of the package 28 | if (!name) throw new Error("Project name is required for root package.json") 29 | pkg.name = name 30 | 31 | // Only keep `frontend` & `contracts` workspaces 32 | pkg.workspaces = ["frontend", "contracts"] 33 | 34 | // Clean up changeset-related items from root package.json 35 | if (pkg.scripts) { 36 | delete pkg.scripts["changeset:version"] 37 | delete pkg.scripts["changeset:publish"] 38 | } 39 | 40 | if (pkg.devDependencies) { 41 | delete pkg.devDependencies["@changesets/changelog-github"] 42 | delete pkg.devDependencies["@changesets/cli"] 43 | } 44 | } 45 | 46 | await writeFile(filePath, `${JSON.stringify(pkg, null, 2)}\n`) 47 | } 48 | 49 | async function updateReadme(filePath: string, displayName: string): Promise { 50 | let content = await readFile(filePath, "utf-8") 51 | 52 | // Replace the main title (first line starting with #) 53 | content = content.replace(/^# .+$/m, `# ${displayName}`) 54 | 55 | // Replace inkathon mentions in the description 56 | content = content.replace( 57 | /> Next generation full-stack boilerplate.+$/m, 58 | `> Full-stack dApp built with ink! smart contracts and Next.js`, 59 | ) 60 | 61 | await writeFile(filePath, content) 62 | } 63 | -------------------------------------------------------------------------------- /frontend/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # frontend 2 | 3 | ## 6.1.2 4 | 5 | ### Patch Changes 6 | 7 | - [#84](https://github.com/scio-labs/inkathon/pull/84) [`cb8ed40`](https://github.com/scio-labs/inkathon/commit/cb8ed40324892032d95e5952c23a0a21d6086044) Thanks [@voliva](https://github.com/voliva)! - update ink sdk dependencies 8 | 9 | fixes compatibility issues with newer runtimes 10 | 11 | - Updated dependencies [[`cb8ed40`](https://github.com/scio-labs/inkathon/commit/cb8ed40324892032d95e5952c23a0a21d6086044)]: 12 | - contracts@6.1.2 13 | 14 | ## 6.1.1 15 | 16 | ### Patch Changes 17 | 18 | - Updated dependencies [[`1498e99`](https://github.com/scio-labs/inkathon/commit/1498e99e082e0356975ef9df468e157b45cf883d)]: 19 | - contracts@6.1.1 20 | 21 | ## 6.1.0 22 | 23 | ### Minor Changes 24 | 25 | - [`f88dcec`](https://github.com/scio-labs/inkathon/commit/f88dcece84adb61a60a218a307f2de0697be5d88) Thanks [@wottpal](https://github.com/wottpal)! - Release new standalone documentation with learning resources & guides to get started. Check out https://docs.inkathon.xyz! 🚀 26 | 27 | ### Patch Changes 28 | 29 | - [`8024758`](https://github.com/scio-labs/inkathon/commit/8024758a0ed26a1d64dbe084121f8d3cff1766fc) Thanks [@wottpal](https://github.com/wottpal)! - Support Node.js v24 30 | 31 | - Updated dependencies [[`8024758`](https://github.com/scio-labs/inkathon/commit/8024758a0ed26a1d64dbe084121f8d3cff1766fc), [`f88dcec`](https://github.com/scio-labs/inkathon/commit/f88dcece84adb61a60a218a307f2de0697be5d88)]: 32 | - contracts@6.1.0 33 | 34 | ## 6.0.1 35 | 36 | ### Patch Changes 37 | 38 | - [`17cfc50`](https://github.com/scio-labs/inkathon/commit/17cfc50aaec65a9e52e664eb6ba40ac5f1fb04d4) Thanks [@wottpal](https://github.com/wottpal)! - Remove `@inkathon/` package name prefix for simplicity 39 | 40 | - Updated dependencies [[`17cfc50`](https://github.com/scio-labs/inkathon/commit/17cfc50aaec65a9e52e664eb6ba40ac5f1fb04d4)]: 41 | - contracts@6.0.1 42 | 43 | ## 6.0.0 44 | 45 | ### Major Changes 46 | 47 | - [`980eb4e`](https://github.com/scio-labs/inkathon/commit/980eb4e76b882a98aad95d3b2f561581afa361d1) Thanks [@wottpal](https://github.com/wottpal)! - Releasing ink v6 compatible inkathon boilerplate rewrite (Alpha). 48 | 49 | ### Patch Changes 50 | 51 | - Updated dependencies [[`980eb4e`](https://github.com/scio-labs/inkathon/commit/980eb4e76b882a98aad95d3b2f561581afa361d1)]: 52 | - contracts@6.0.0 53 | -------------------------------------------------------------------------------- /frontend/src/app/app.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import type { ChainId } from "@reactive-dot/core" 4 | import type { WalletAccount } from "@reactive-dot/core/wallets.js" 5 | import { ChainProvider } from "@reactive-dot/react" 6 | import { Suspense, useState } from "react" 7 | import { ButtonSkeleton, CardSkeleton } from "@/components/layout/skeletons" 8 | import { Wrapper } from "@/components/layout/wrapper" 9 | import { AccountBalance } from "@/components/web3/account-balance" 10 | import { AccountProvider } from "@/components/web3/account-provider" 11 | import { AccountSelect } from "@/components/web3/account-select" 12 | import { ChainInfoCard } from "@/components/web3/chain-info-card" 13 | import { ChainSelect } from "@/components/web3/chain-select" 14 | import { ContractCard } from "@/components/web3/contract-card" 15 | import { MapAccountButton } from "@/components/web3/map-account-button" 16 | 17 | export function App() { 18 | const [account, setAccount] = useState() 19 | const [chainId, setChainId] = useState("pop") 20 | 21 | return ( 22 | 23 | 24 | 25 |
26 | {/* Chain Selector */} 27 | }> 28 | 29 | 30 | 31 | {/* Connect Button */} 32 | }> 33 | 34 | 35 | 36 | {/* Account Mapping Button */} 37 | 38 | 39 | 40 | 41 | {/* Account Balance */} 42 | 43 | 44 | 45 |
46 | 47 |
48 | {/* Chain Metadata */} 49 | }> 50 | 51 | 52 | 53 | {/* Contract Read & Write */} 54 | }> 55 | 56 | 57 |
58 |
59 |
60 |
61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /contracts/scripts/utils/init-api.ts: -------------------------------------------------------------------------------- 1 | import type { dev } from "@polkadot-api/descriptors" 2 | import { getWsProvider } from "@polkadot-api/ws-provider/web" 3 | import { sr25519CreateDerive } from "@polkadot-labs/hdkd" 4 | import { DEV_PHRASE, entropyToMiniSecret, mnemonicToEntropy } from "@polkadot-labs/hdkd-helpers" 5 | import dotenv from "dotenv" 6 | import { AccountId, createClient } from "polkadot-api" 7 | import { getPolkadotSigner } from "polkadot-api/signer" 8 | import yoctoSpinner from "yocto-spinner" 9 | import papiJson from "../../.papi/polkadot-api.json" 10 | 11 | // Dynamically load environment from `.env.{chainId}` 12 | const chainId = process.env.CHAIN || "dev" 13 | dotenv.config({ path: `.env.${chainId}`, quiet: true }) 14 | 15 | /** 16 | * Initializes the Polkadot API client, signer, and related utilities for a given chain. 17 | * 18 | * @options 19 | * Environment variables: 20 | * CHAIN - Target chain to deploy the contract to (must be initialized with `bunx polkadot-api add `). Default: `dev` 21 | * ACCOUNT_URI - Account to deploy the contract from. If not set, uses `.env.{CHAIN}` or defaults to `//Alice` 22 | */ 23 | export async function initApi() { 24 | console.log() 25 | const spinner = yoctoSpinner({ text: "Initializing…" }).start() 26 | 27 | try { 28 | // Setup chain 29 | const chain = (await import("@polkadot-api/descriptors"))[chainId] as typeof dev 30 | const wsUrl = papiJson.entries[chainId as keyof typeof papiJson.entries]?.wsUrl 31 | if (!chain || !wsUrl) { 32 | throw new Error( 33 | `Chain '${chainId}' not found. Make sure to initialize with 'bunx polkadot-api add …' first!`, 34 | ) 35 | } 36 | 37 | // Setup client & API 38 | const client = createClient(getWsProvider(wsUrl)) 39 | const api = client.getTypedApi(chain) 40 | 41 | // Setup signer 42 | const accountUri = process.env.ACCOUNT_URI || "//Alice" 43 | const derive = sr25519CreateDerive(entropyToMiniSecret(mnemonicToEntropy(DEV_PHRASE))) 44 | const keyPair = derive(accountUri) 45 | const signer = getPolkadotSigner(keyPair.publicKey, "Sr25519", keyPair.sign) 46 | const ss58Address = AccountId().dec(signer.publicKey) 47 | 48 | spinner.success(`Initialized chain '${chainId}' with account '${ss58Address}'`) 49 | 50 | return { 51 | chain, 52 | client, 53 | api, 54 | keyPair, 55 | signer, 56 | ss58Address, 57 | } 58 | } catch (error) { 59 | spinner.error("Failed to initialize") 60 | throw error 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![inkathon Banner](frontend/public/inkathon-readme-banner.png) 2 | 3 | # inkathon v6 4 | 5 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 6 | [![Built with ink!](https://raw.githubusercontent.com/paritytech/ink/master/.images/badge.svg)](https://use.ink) 7 | ![Rust](https://img.shields.io/badge/Rust-000000?logo=rust&logoColor=white) 8 | ![TypeScript](https://img.shields.io/badge/TypeScript-000000?logo=typescript&logoColor=white) 9 | ![Next.js](https://img.shields.io/badge/Next.js-000000?logo=next.js&logoColor=white) 10 | 11 | > Next generation full-stack boilerplate for ink! smart contracts running on PolkaVM. Powered by Papi, ReactiveDOT, Pop CLI, and more. 12 | 13 | - 🤖 **Check out the [live demo](https://inkathon.xyz)** 14 | - 📖 **Explore our new [documentation](https://docs.inkathon.xyz)** 15 | - 💬 **Join the discussion on [Telegram](https://t.me/inkathon)** 16 | 17 | ## Quickstart ⚡ 18 | 19 | > [!IMPORTANT] 20 | > 21 | > - Setup Node.js v20+ (recommended via [nvm](https://github.com/nvm-sh/nvm)) 22 | > - Install [Bun](https://bun.sh/) 23 | 24 | Use the new `create-inkathon-app` CLI to set up the boilerplate: 25 | 26 | ```bash 27 | bunx create-inkathon-app@latest 28 | 29 | cd 30 | 31 | bun run dev 32 | ``` 33 | 34 | Learn more in our new [documentation](https://docs.inkathon.xyz). 35 | 36 | ## About the project 37 | 38 | The inkathon boilerplate is almost as old as ink! itself. With over 200 stars, more than 250 public projects depending on it, and the most active [ink! Telegram group](https://t.me/inkathon). 39 | 40 | It offers a complete setup for developing full-stack dApps on Polkadot with ink! smart contracts and Next.js and now shines in new glory with full ink! v6 support. Powered by Papi, ReactiveDOT, Pop CLI, and more. 41 | 42 | ## New Features in v6 43 | 44 | The boilerplate has been completely rebuilt with: 45 | 46 | - **Full ink! v6 support** with PolkaVM compatibility 47 | - **Type-safe contract interactions** via PAPI 48 | - **Modern stack**: Bun, Next.js 15, React 19, Tailwind CSS v4 49 | - **Improved DX**: Better build scripts & deployment automation 50 | - **Production-ready**: Docker support, self-hosting optimized 51 | - **New `create-inkathon-app` CLI** for setting up the boilerplate in seconds 52 | - **New [documentation](https://docs.inkathon.xyz)** with educational resources & guides 53 | 54 | ## Changelog 55 | 56 | > [!NOTE] 57 | > The old ink! v5 compatible boilerplate is available on the [`v1` branch](https://github.com/scio-labs/inkathon/tree/v1). 58 | 59 | The v6 changelog is available on the [GitHub releases page](https://github.com/scio-labs/inkathon/releases). 60 | -------------------------------------------------------------------------------- /contracts/scripts/utils/deploy-contract.ts: -------------------------------------------------------------------------------- 1 | import { createInkSdk, type GenericInkDescriptors } from "@polkadot-api/sdk-ink" 2 | import { Binary } from "polkadot-api" 3 | import yoctoSpinner from "yocto-spinner" 4 | import type { initApi } from "./init-api" 5 | 6 | /** 7 | * Deploys a contract to the specified chain. 8 | */ 9 | export async function deployContract< 10 | D extends GenericInkDescriptors, 11 | L extends string & keyof D["__types"]["constructors"], 12 | >( 13 | initResult: Awaited>, 14 | name: string, 15 | descriptors: D, 16 | constructorName: L, 17 | constructorArgs: D["__types"]["constructors"][L]["message"], 18 | ) { 19 | console.log() 20 | const spinner = yoctoSpinner({ text: "Deploying contract…" }).start() 21 | 22 | try { 23 | const { api, client, ss58Address: ss58SignerAddress, signer } = initResult 24 | 25 | // Load contract binary code 26 | const path = `./deployments/${name}/${name}.polkavm` 27 | const file = Bun.file(path) 28 | const blob = await file.bytes() 29 | const code = Binary.fromBytes(blob) 30 | const contractSdk = createInkSdk(client) 31 | 32 | // Check if account is mapped 33 | const isMapped = await contractSdk.addressIsMapped(ss58SignerAddress) 34 | if (!isMapped) { 35 | const txResult = await api.tx.Revive.map_account().signAndSubmit(signer) 36 | if (!txResult.ok) { 37 | console.error(txResult.dispatchError) 38 | throw new Error("Failed to map account") 39 | } 40 | } 41 | 42 | // Build deployment payload 43 | const constructorPayload = { 44 | origin: ss58SignerAddress, 45 | data: constructorArgs, 46 | } 47 | const deployer = contractSdk.getDeployer(descriptors, code) 48 | 49 | // Determine deployment address 50 | const evmAddress = await deployer.estimateAddress(constructorName, constructorPayload) 51 | if (!evmAddress) { 52 | throw new Error("Failed to estimate deployment address") 53 | } 54 | 55 | // Deploy contract 56 | const txResult = await deployer 57 | .deploy(constructorName, constructorPayload) 58 | .signAndSubmit(signer) 59 | 60 | if (!txResult.ok) { 61 | console.error(txResult.dispatchError) 62 | throw new Error("Failed to deploy contract") 63 | } 64 | 65 | const contract = contractSdk.getContract(descriptors, evmAddress) 66 | const ss58Address = contract.accountId.toString() 67 | 68 | spinner.success(`📜 Deployed contract '${name}' at address '${ss58Address}' (${evmAddress})`) 69 | 70 | return { contract, evmAddress, ss58Address, block: txResult.block } 71 | } catch (error) { 72 | spinner.error("Failed to deploy contract") 73 | throw error 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /frontend/src/components/ui/table.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import type * as React from "react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | function Table({ className, ...props }: React.ComponentProps<"table">) { 8 | return ( 9 |
10 | 15 | 16 | ) 17 | } 18 | 19 | function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { 20 | return 21 | } 22 | 23 | function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { 24 | return ( 25 | 30 | ) 31 | } 32 | 33 | function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { 34 | return ( 35 | 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 | --------------------------------------------------------------------------------