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
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:
- Detects all contracts in
/src
- Builds each with
cargo contract build --release
- Copies artifacts to
/deployments
- 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
- Build contracts to generate metadata
- Run codegen to create TypeScript descriptors
- 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
- Follow ink! conventions: Use standard patterns from examples
- Keep contracts simple: Minimize on-chain logic
- Test thoroughly: Unit and integration tests
- Document messages: Clear names and comments
- Version carefully: Contracts are immutable once deployed
- Optimize for size: Use
--release
builds - 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(())
}