inkathoninkathon

Contracts Architecture

Understanding ink! smart contract development in inkathon

ink! v6 Overview

inkathon uses ink! v6, the latest version of Parity's smart contract language for Polkadot. Key features include:

  • PolkaVM Support: Next-generation virtual machine compatibility
  • Improved Performance: Optimized contract execution
  • Enhanced Developer Experience: Better error messages and tooling
  • Type Safety: Rust's type system for secure contracts

Project Structure

package.json
build.sh
codegen.sh
deploy.sh
Cargo.toml

Available Commands

Execute from the contracts directory or root with -F contracts:

# Development
bun run node         # Start local ink-node
bun run build        # Build all contracts
bun run codegen      # Generate TypeScript types
bun run deploy       # Deploy contracts

# Code Quality
bun run lint         # Run linting checks
bun run lint:fix     # Auto-fix issues
bun run typecheck    # TypeScript checking

# Maintenance
bun run clean        # Remove build artifacts

Contract Development Workflow

Create Contract Structure

Create a new directory in /contracts/src/:

mkdir contracts/src/my-contract
cd contracts/src/my-contract

Write Contract Code

Create lib.rs with your contract logic:

#![cfg_attr(not(feature = "std"), no_std, no_main)]

#[ink::contract]
mod my_contract {
    #[ink(storage)]
    pub struct MyContract {
        value: u32,
    }

    impl MyContract {
        #[ink(constructor)]
        pub fn new(init_value: u32) -> Self {
            Self { value: init_value }
        }

        #[ink(message)]
        pub fn get(&self) -> u32 {
            self.value
        }

        #[ink(message)]
        pub fn set(&mut self, new_value: u32) {
            self.value = new_value;
        }
    }
}

Configure Cargo.toml

[package]
name = "my-contract"
version = "0.1.0"
edition = "2021"

[dependencies]
ink = { version = "6.0.0-alpha", default-features = false }

[lib]
crate-type = ["cdylib"]

[features]
default = ["std"]
std = ["ink/std"]

Build Contract

bun run build

This generates:

  • .contract - Complete contract bundle
  • .json - Metadata for interactions
  • .polkavm - PolkaVM bytecode

Generate Types

bun run codegen

Creates TypeScript types from contract metadata.

Deploy Contract

# Uses //Alice by default
CHAIN=dev bun run deploy
# Set your account in .env.pop
CHAIN=pop bun run deploy

Build System

Build Script (build.sh)

The build script automatically:

  1. Detects all contracts in /src
  2. Builds each with cargo contract build --release
  3. Copies artifacts to /deployments
  4. Organizes by contract name

Output Structure

deployments/
└── my-contract/
    ├── my-contract.contract  # Full bundle
    ├── my-contract.json      # Metadata
    ├── my-contract.polkavm   # PolkaVM code
    ├── dev.ts               # Dev chain addresses
    └── pop.ts               # Pop chain addresses

Type Generation (PAPI)

How It Works

  1. Build contracts to generate metadata
  2. Run codegen to create TypeScript descriptors
  3. Import types in frontend code

Generated Types

// In frontend code
import { contracts } from "@polkadot-api/descriptors"

// Fully typed contract interface
const flipper = contracts.flipper

Deployment Management

Deployment Script

The deploy.ts script handles:

  • Account management (defaults to //Alice)
  • Gas estimation
  • Contract instantiation
  • Address export to TypeScript files

Environment Configuration

# Local development
ACCOUNT_URI="//Alice"
# Pop Network
ACCOUNT_URI="your seed phrase here"

Exported Addresses

Deployments create TypeScript files:

// deployments/my-contract/dev.ts
export const evmAddress = "0x..."
export const ss58Address = "5..."

Testing Contracts

Unit Tests

Add tests in your contract:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn default_works() {
        let contract = MyContract::new(42);
        assert_eq!(contract.get(), 42);
    }
}

Run with:

cargo test

Integration Tests

Use the deployed contract in frontend tests:

import { contracts } from "@polkadot-api/descriptors"
import { contractDeployments } from "@/lib/inkathon/deployments"

test("contract interaction", async () => {
  const address = contractDeployments.myContract.dev.ss58Address
  // Test contract calls
})

Best Practices

  1. Follow ink! conventions: Use standard patterns from examples
  2. Keep contracts simple: Minimize on-chain logic
  3. Test thoroughly: Unit and integration tests
  4. Document messages: Clear names and comments
  5. Version carefully: Contracts are immutable once deployed
  6. Optimize for size: Use --release builds
  7. Handle errors gracefully: Use Result types

Common Patterns

Storage Patterns

#[ink(storage)]
pub struct Contract {
    // Simple value
    owner: AccountId,
    // Mapping
    balances: Mapping<AccountId, Balance>,
    // Vector
    items: Vec<Item>,
}

Event Emission

#[ink(event)]
pub struct Transfer {
    from: AccountId,
    to: AccountId,
    value: Balance,
}

// In message
self.env().emit_event(Transfer { from, to, value });

Access Control

#[ink(message)]
pub fn admin_only(&mut self) -> Result<(), Error> {
    if self.env().caller() != self.owner {
        return Err(Error::Unauthorized);
    }
    // Admin logic
    Ok(())
}