Templates
Every template ships as a complete, compiling contract rather than a snippet. The Quorlin ones are written against the language specification and are checked against the grammar in CI, so none of them teaches syntax the compiler would reject.
Counter
QUORLIN · KVMThe smallest contract that shows reads against writes.
The smallest contract that shows the difference between reads and writes: only increment produces a transaction hash.
// Counter — the smallest contract that shows `reads` against `writes`.
//
// Only `increment` is a `writes`, so only `increment` produces a transaction
// hash. `value` compiles to a static call, which the VM refuses to let write.
contract Counter {
number count;
address owner;
event Incremented(address indexed by, number newValue);
constructor {
owner = caller;
count = 0;
}
reads number value() {
return count;
}
writes number increment() {
// Checked arithmetic: this stops the call on overflow rather than wrapping.
count = count + 1;
emit Incremented(caller, count);
return count;
}
writes reset() {
require caller == owner, "only the owner";
count = 0;
}
}
Fungible Token
QUORLIN · KVMA complete EIP-20 token. The allowance is spent, not merely checked.
The allowance is spent rather than merely checked, and transfers to the zero address are refused — a burn the contract never accounted for is the most common way tokens are lost.
// A complete EIP-20 fungible token.
//
// The allowance is spent, not merely checked — an allowance that is verified
// and left in place can be replayed for the whole approved amount.
contract Token {
text tokenName;
text tokenSymbol;
number totalTokens;
address owner;
map<address, number> balances;
map<address, map<address, number>> allowances;
event Transfer(address indexed from, address indexed to, number amount);
event Approval(address indexed owner, address indexed spender, number amount);
constructor {
owner = caller;
tokenName = "Kortana Example";
tokenSymbol = "KEX";
totalTokens = 1000000;
balances[caller] = 1000000;
emit Transfer(nobody, caller, 1000000);
}
reads text name() {
return tokenName;
}
reads text symbol() {
return tokenSymbol;
}
reads number decimals() {
return 18;
}
reads number totalSupply() {
return totalTokens;
}
reads number balanceOf(address account) {
return balances[account];
}
reads number allowance(address holder, address spender) {
return allowances[holder][spender];
}
writes truth transfer(address recipient, number amount) {
// A transfer to the zero address is indistinguishable from a burn the
// contract never accounted for. Refuse it.
require recipient != nobody, "transfer to the zero address";
number held = balances[caller];
require held >= amount, "transfer amount exceeds balance";
balances[caller] = held - amount;
balances[recipient] = balances[recipient] + amount;
emit Transfer(caller, recipient, amount);
return yes;
}
writes truth approve(address spender, number amount) {
require spender != nobody, "approve to the zero address";
allowances[caller][spender] = amount;
emit Approval(caller, spender, amount);
return yes;
}
writes truth transferFrom(address holder, address recipient, number amount) {
require recipient != nobody, "transfer to the zero address";
number allowed = allowances[holder][caller];
require allowed >= amount, "insufficient allowance";
number held = balances[holder];
require held >= amount, "transfer amount exceeds balance";
// Spend the allowance before moving the balance.
allowances[holder][caller] = allowed - amount;
balances[holder] = held - amount;
balances[recipient] = balances[recipient] + amount;
emit Transfer(holder, recipient, amount);
return yes;
}
}
Escrow
QUORLIN · KVMA record-based agreement between a buyer and a seller, with an arbiter.
State transitions are guarded on the current state, so a released agreement cannot be refunded and vice versa.
// Escrow — a record-based agreement between a buyer and a seller.
//
// Records are one level and hold scalars only, which keeps the storage layout
// readable straight off the source.
record Agreement {
address buyer;
address seller;
number amount;
number state;
}
contract Escrow {
// 0 none · 1 funded · 2 released · 3 refunded
map<number, Agreement> agreements;
number nextId;
address arbiter;
event Opened(number indexed id, address indexed buyer, address indexed seller, number amount);
event Released(number indexed id, address indexed seller, number amount);
event Refunded(number indexed id, address indexed buyer, number amount);
constructor {
arbiter = caller;
nextId = 1;
}
writes number open(address seller, number amount) {
require seller != nobody, "seller is the zero address";
require seller != caller, "buyer and seller are the same account";
require amount > 0, "amount must be positive";
number id = nextId;
agreements[id].buyer = caller;
agreements[id].seller = seller;
agreements[id].amount = amount;
agreements[id].state = 1;
nextId = id + 1;
emit Opened(id, caller, seller, amount);
return id;
}
writes release(number id) {
require agreements[id].state == 1, "agreement is not funded";
// Either the buyer or the arbiter may release. Quorlin spells logic
// as words -- and, or, not -- and they short-circuit.
require caller == agreements[id].buyer or caller == arbiter, "not authorised to release";
agreements[id].state = 2;
emit Released(id, agreements[id].seller, agreements[id].amount);
}
writes refund(number id) {
require agreements[id].state == 1, "agreement is not funded";
require caller == agreements[id].seller or caller == arbiter, "not authorised to refund";
agreements[id].state = 3;
emit Refunded(id, agreements[id].buyer, agreements[id].amount);
}
reads number stateOf(number id) {
return agreements[id].state;
}
reads Agreement agreementOf(number id) {
// A record may be returned, but not held in a local.
return agreements[id];
}
}
Access Control
QUORLIN · KVMTwo-step ownership transfer and a pause switch.
Ownership moves in two steps. A single-step transfer to a mistyped address strands the contract permanently; requiring the nominee to accept proves the key exists.
// Two-step ownership transfer.
//
// A single-step transfer to a mistyped address strands the contract for good.
// The nominee must accept, which proves the key exists and is controlled.
contract Ownable {
address owner;
address pendingOwner;
truth paused;
event OwnershipOffered(address indexed from, address indexed to);
event OwnershipAccepted(address indexed newOwner);
event PausedChanged(truth isPaused);
constructor {
owner = caller;
paused = no;
}
reads address currentOwner() {
return owner;
}
reads truth isPaused() {
return paused;
}
writes offerOwnership(address nominee) {
// Use caller, not sender -- a check against sender can be passed by
// tricking a user into calling through a contract you control.
require caller == owner, "only the owner";
require nominee != nobody, "nominee is the zero address";
pendingOwner = nominee;
emit OwnershipOffered(owner, nominee);
}
writes acceptOwnership() {
require caller == pendingOwner, "only the nominee";
owner = pendingOwner;
pendingOwner = nobody;
emit OwnershipAccepted(owner);
}
writes setPaused(truth value) {
require caller == owner, "only the owner";
paused = value;
emit PausedChanged(value);
}
}
Protocol Reader
QUORLIN · KVMReads Kortana's staking, chain and governance system contracts.
Reads the staking, chain and governance system contracts. A contract cannot stake, delegate or vote — that bound is deliberate, because staking state lives outside the world-state trie and a reverted call could not undo a stake move.
// Reading Kortana's own protocols from a contract.
//
// Staking, governance and dPOH are protocol-level rather than contracts. Three
// system contracts at reserved addresses expose them as reads. A contract
// cannot stake, delegate or vote — staking state lives outside the world-state
// trie, so a write reachable from a contract could not be rolled back by a
// revert, and two nodes would disagree about who is a validator.
contract ValidatorGated {
map<address, number> deposits;
number totalDeposited;
event Deposited(address indexed who, number amount);
constructor {
totalDeposited = 0;
}
writes deposit(number amount) {
require amount > 0, "amount must be positive";
require IKortanaStaking(kortanaStaking).isActiveValidator(caller), "validators only";
deposits[caller] = deposits[caller] + amount;
totalDeposited = totalDeposited + amount;
emit Deposited(caller, amount);
}
reads number weightOf(address validator) {
return IKortanaStaking(kortanaStaking).totalStakeOf(validator);
}
reads number networkStake() {
return IKortanaStaking(kortanaStaking).totalActiveStake();
}
reads number currentEpoch() {
return IKortanaChain(kortanaChain).epoch();
}
reads number minimumStake() {
return IKortanaChain(kortanaChain).minValidatorStake();
}
reads truth proposalOpen(number proposalId) {
return IKortanaGovernance(kortanaGovernance).isVotingOpen(proposalId);
}
}
ERC-20 Token
SOLIDITY · KEVMA self-contained ERC-20 with custom errors. Compiles with no imports.
Self-contained with no imports, so it compiles in the browser without a package manager. Uses custom errors and spends the allowance before moving the balance.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @title A minimal, self-contained ERC-20 for Kortana's KEVM.
/// @notice No external imports, so it compiles in the browser without a
/// package manager. For production, prefer a reviewed library.
contract Token {
string public name = "Kortana Example";
string public symbol = "KEX";
uint8 public constant decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
error InsufficientBalance(uint256 available, uint256 required);
error InsufficientAllowance(uint256 available, uint256 required);
error ZeroAddress();
constructor(uint256 initialSupply) {
totalSupply = initialSupply;
balanceOf[msg.sender] = initialSupply;
emit Transfer(address(0), msg.sender, initialSupply);
}
function transfer(address to, uint256 value) external returns (bool) {
if (to == address(0)) revert ZeroAddress();
uint256 held = balanceOf[msg.sender];
if (held < value) revert InsufficientBalance(held, value);
unchecked {
balanceOf[msg.sender] = held - value;
}
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
return true;
}
function approve(address spender, uint256 value) external returns (bool) {
if (spender == address(0)) revert ZeroAddress();
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}
function transferFrom(address from, address to, uint256 value) external returns (bool) {
if (to == address(0)) revert ZeroAddress();
uint256 allowed = allowance[from][msg.sender];
if (allowed < value) revert InsufficientAllowance(allowed, value);
uint256 held = balanceOf[from];
if (held < value) revert InsufficientBalance(held, value);
// Spend the allowance before moving the balance.
unchecked {
allowance[from][msg.sender] = allowed - value;
balanceOf[from] = held - value;
}
balanceOf[to] += value;
emit Transfer(from, to, value);
return true;
}
}
Simple Storage
SOLIDITY · KEVMA value, a setter and an event — the shortest path to a receipt.
A value, a setter and an event — the shortest path from source to a receipt.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @title The smallest useful contract — a value, a setter, and an event.
contract SimpleStorage {
uint256 private _value;
address public immutable owner;
event ValueChanged(address indexed by, uint256 previous, uint256 current);
constructor() {
owner = msg.sender;
}
function get() external view returns (uint256) {
return _value;
}
function set(uint256 newValue) external {
uint256 previous = _value;
_value = newValue;
emit ValueChanged(msg.sender, previous, newValue);
}
}
Every template is available when you create a project. Open Studio →