Learn Ink - 10 Code Examples & CST Typing Practice Test
Ink! is a Rust-based eDSL (embedded domain-specific language) for writing smart contracts on the Substrate blockchain framework. It emphasizes safety, efficiency, and tight integration with Polkadot and Substrate ecosystems.
Learn INK with Real Code Examples
Updated Nov 25, 2025
Code Sample Descriptions
Ink! Simple Counter Contract
#[ink::contract]
mod counter {
#[ink(storage)]
pub struct Counter {
value: u64,
}
impl Counter {
#[ink(constructor)]
pub fn new() -> Self {
Self { value: 0 }
}
#[ink(message)]
pub fn increment(&mut self) {
self.value += 1;
}
#[ink(message)]
pub fn reset(&mut self) {
self.value = 0;
}
}
}
A minimal Ink! contract defining a counter with increment and reset functions.
Ink! Owner-Only Message
#[ink::contract]
mod owner_message {
#[ink(storage)]
pub struct OwnerMessage {
owner: AccountId,
message: ink::prelude::string::String,
}
impl OwnerMessage {
#[ink(constructor)]
pub fn new() -> Self {
Self {
owner: Self::env().caller(),
message: String::from(""),
}
}
#[ink(message)]
pub fn set_message(&mut self, msg: String) {
assert!(self.env().caller() == self.owner);
self.message = msg;
}
}
}
A contract where only the owner can update a stored message.
Ink! Simple ERC20 Token
#[ink::contract]
mod token {
#[ink(storage)]
pub struct Token {
total: u64,
balances: ink::storage::Mapping<AccountId, u64>,
}
impl Token {
#[ink(constructor)]
pub fn new(amount: u64) -> Self {
let caller = Self::env().caller();
let mut balances = ink::storage::Mapping::new();
balances.insert(caller, &amount);
Self { total: amount, balances }
}
#[ink(message)]
pub fn transfer(&mut self, to: AccountId, amount: u64) {
let caller = self.env().caller();
let caller_balance = self.balances.get(caller).unwrap_or(0);
assert!(caller_balance >= amount);
self.balances.insert(caller, &(caller_balance - amount));
let to_balance = self.balances.get(to).unwrap_or(0);
self.balances.insert(to, &(to_balance + amount));
}
}
}
A basic ERC20-like token implementation in Ink! supporting mint, transfer, and balance queries.
Ink! Simple Vault (Deposit + Withdraw)
#[ink::contract]
mod vault {
#[ink(storage)]
pub struct Vault {}
impl Vault {
#[ink(constructor)]
pub fn new() -> Self { Self {} }
#[ink(message, payable)]
pub fn deposit(&mut self) {}
#[ink(message)]
pub fn withdraw(&mut self, amount: u128) {
self.env().transfer(self.env().caller(), amount).unwrap();
}
}
}
A vault contract for storing native chain tokens with deposit/withdraw logic.
Ink! Timelock Contract
#[ink::contract]
mod timelock {
#[ink(storage)]
pub struct Timelock {
unlock: u64,
}
impl Timelock {
#[ink(constructor)]
pub fn new(unlock: u64) -> Self { Self { unlock } }
#[ink(message)]
pub fn withdraw(&mut self) {
assert!(self.env().block_timestamp() >= self.unlock);
self.env().transfer(self.env().caller(), self.env().balance()).unwrap();
}
}
}
Funds can be withdrawn only after a specific block timestamp.
Ink! Simple Voting
#[ink::contract]
mod voting {
#[ink(storage)]
pub struct Voting {
yes: u64,
no: u64,
}
impl Voting {
#[ink(constructor)]
pub fn new() -> Self { Self { yes: 0, no: 0 } }
#[ink(message)]
pub fn vote(&mut self, choice: bool) {
if choice { self.yes += 1; } else { self.no += 1; }
}
}
}
A minimal yes/no voting system implemented in Ink!.
Ink! Whitelist Access
#[ink::contract]
mod whitelist {
#[ink(storage)]
pub struct Whitelist {
owner: AccountId,
allowed: ink::storage::Mapping<AccountId, bool>,
}
impl Whitelist {
#[ink(constructor)]
pub fn new() -> Self {
Self { owner: Self::env().caller(), allowed: ink::storage::Mapping::new() }
}
#[ink(message)]
pub fn add(&mut self, user: AccountId) {
assert!(self.env().caller() == self.owner);
self.allowed.insert(user, &true);
}
#[ink(message)]
pub fn restricted(&self) {
assert!(self.allowed.get(self.env().caller()).unwrap_or(false));
}
}
}
Only whitelisted users can perform a restricted action.
Ink! Event Logger
#[ink::contract]
mod logger {
#[ink(event)]
pub struct Log {
#[ink(topic)]
from: AccountId,
msg: String,
}
#[ink(storage)]
pub struct Logger {}
impl Logger {
#[ink(constructor)]
pub fn new() -> Self { Self {} }
#[ink(message)]
pub fn fire(&self, msg: String) {
self.env().emit_event(Log { from: self.env().caller(), msg });
}
}
}
Demonstrates Ink! events with a simple log message.
Ink! Immutable Config Example
#[ink::contract]
mod config {
#[ink(storage)]
pub struct Config {
config: u64,
}
impl Config {
#[ink(constructor)]
pub fn new(num: u64) -> Self {
Self { config: num }
}
#[ink(message)]
pub fn get(&self) -> u64 { self.config }
}
}
Shows how to use immutable data in Ink!.
Ink! Simple Multiplier
#[ink::contract]
mod multiplier {
#[ink(storage)]
pub struct Multiplier {
factor: u64,
}
impl Multiplier {
#[ink(constructor)]
pub fn new(f: u64) -> Self {
Self { factor: f }
}
#[ink(message)]
pub fn multiply(&self, x: u64) -> u64 {
x * self.factor
}
}
}
Multiplies any input by a stored factor.
Frequently Asked Questions about Ink
What is Ink?
Ink! is a Rust-based eDSL (embedded domain-specific language) for writing smart contracts on the Substrate blockchain framework. It emphasizes safety, efficiency, and tight integration with Polkadot and Substrate ecosystems.
What are the primary use cases for Ink?
High-security smart contracts. DeFi protocols on Substrate. NFT minting and marketplaces. On-chain governance modules. Wasm-based blockchain apps
What are the strengths of Ink?
Safe, type-checked contracts. High performance via Wasm. Strong ecosystem support in Polkadot. Auditable Rust code. Predictable gas and storage behavior
What are the limitations of Ink?
Limited to Substrate/Wasm chains. Smaller ecosystem than Solidity/EVM. Requires Rust proficiency. No EVM compatibility. Less tooling for testing vs EVM chains
How can I practice Ink typing speed?
CodeSpeedTest offers 10+ real Ink code examples for typing practice. You can measure your WPM, track accuracy, and improve your coding speed with guided exercises.