Skip to content
SORA Codex Community-curated documentation

Smart Contracts on Iroha

Hyperledger Iroha supports two complementary approaches to programmable logic: Iroha Special Instructions (ISI) for common operations and WASM smart contracts for custom business logic. This dual approach provides both simplicity and flexibility for SORA v3 development.

Iroha Special Instructions

Pre-built, optimized commands for common blockchain operations. Fast, secure, and easy to use.

WASM Contracts

Custom WebAssembly code for complex business logic. Flexible, powerful, and Turing-complete.


ISI are domain-oriented commands that handle common blockchain operations without custom code.

BenefitDescription
PerformanceNative execution, no VM overhead
SecurityAudited, battle-tested implementations
SimplicityNo coding required for common tasks
DeterminismGuaranteed same result on all nodes

Create new entities in the blockchain:

// Register a new domain
Register::domain(Domain::new("finance".parse()?))
// Register an account
Register::account(Account::new(
"alice@finance".parse()?
))
// Register an asset definition
Register::asset_definition(
AssetDefinition::quantity("usd#finance".parse()?)
)

Instructions can be combined for complex operations:

// Atomic batch of instructions
let batch = vec![
Register::account(new_account).into(),
Mint::asset_quantity(initial_balance, account_asset).into(),
SetKeyValue::account(account_id, key, value).into(),
];
// All succeed or all fail
client.submit_all_blocking(batch)?;

For custom business logic, Iroha supports WebAssembly (WASM) smart contracts written in Rust.

#![no_std]
extern crate alloc;
use iroha_wasm::prelude::*;
#[iroha_wasm::main]
fn main(host: Iroha) {
// Access execution context
let authority = host.context().authority();
// Query state
let account = host.query_single(
FindAccountById::new(authority.clone())
).unwrap();
// Execute instructions
let instruction = Transfer::asset_quantity(
source_asset,
amount,
destination,
);
host.submit(instruction).unwrap();
}

WASM contracts can:

CapabilityExample
Query stateRead account balances, asset metadata
Execute ISITransfer, mint, burn assets
Emit eventsTrigger external notifications
Access contextGet caller, block height, timestamp
ValidateImplement custom permission logic
#![no_std]
extern crate alloc;
use iroha_wasm::prelude::*;
use alloc::vec::Vec;
/// Vesting contract that releases tokens over time
#[iroha_wasm::main]
fn claim_vested(host: Iroha) {
let authority = host.context().authority();
let current_time = host.context().block_timestamp();
// Read vesting schedule from account metadata
let vesting_start: u128 = host.query_single(
FindAccountMetadata::new(
authority.clone(),
"vesting_start".parse().unwrap()
)
).unwrap().try_into().unwrap();
let vesting_duration: u128 = host.query_single(
FindAccountMetadata::new(
authority.clone(),
"vesting_duration".parse().unwrap()
)
).unwrap().try_into().unwrap();
let total_amount: u32 = host.query_single(
FindAccountMetadata::new(
authority.clone(),
"vesting_amount".parse().unwrap()
)
).unwrap().try_into().unwrap();
// Calculate vested amount
let elapsed = current_time.saturating_sub(vesting_start);
let vested_fraction = elapsed.min(vesting_duration) as f64
/ vesting_duration as f64;
let vested_amount = (total_amount as f64 * vested_fraction) as u32;
// Transfer vested tokens
let transfer = Transfer::asset_quantity(
vesting_pool_asset,
vested_amount,
authority.clone(),
);
host.submit(transfer).dbg_unwrap();
}

Triggers enable event-driven smart contract execution:

TypeFires When
By EventSpecific event occurs (transfer, mint, etc.)
By TimeScheduled time reached
By BlockNew block committed
Pre-commitBefore block finalization
// Register a trigger that collects fees on every transfer
let trigger = Trigger::new(
"fee_collector".parse()?,
Action::new(
collect_fee_wasm, // WASM contract bytecode
Repeats::Indefinitely,
authority,
FilterBox::Data(DataEventFilter::ByAssetEventFilter(
AssetEventFilter::ByTransferred
)),
),
);
Register::trigger(trigger);

// Grant permission to mint specific asset
Grant::permission(
PermissionToken::new(
"CanMintAssetsWithDefinition".parse()?,
json!({ "asset_definition_id": "xor#sora" }),
),
IdBox::AccountId("minter@sora".parse()?),
)

Implement custom validation logic:

#[iroha_wasm::validator]
fn validate_transfer(
host: Iroha,
instruction: InstructionBox,
) -> Result {
// Only allow transfers during business hours
let hour = host.context().block_timestamp() % 86400 / 3600;
if hour < 9 || hour > 17 {
return Err(ValidationFail::NotPermitted(
"Transfers only allowed 9AM-5PM".into()
));
}
Ok(())
}

Terminal window
# Install Rust toolchain
rustup target add wasm32-unknown-unknown
# Install Iroha WASM tools
cargo install iroha_wasm_builder
Terminal window
# Build optimized WASM
cargo build --release --target wasm32-unknown-unknown
# Optimize size
wasm-opt -Oz target/wasm32-unknown-unknown/release/contract.wasm \
-o contract.opt.wasm
// Load WASM bytecode
let wasm = fs::read("contract.opt.wasm")?;
// Register as executable
let executable = WasmSmartContract::new(wasm);
Register::executable(executable);
// Or register as trigger
let trigger = Trigger::new(
trigger_id,
Action::new(executable, repeats, authority, filter),
);
Register::trigger(trigger);
#[test]
fn test_contract() {
let network = TestNetwork::new();
let client = network.client();
// Deploy contract
client.submit(Register::trigger(trigger))?;
// Trigger event
client.submit(Transfer::asset_quantity(...))?;
// Verify result
let balance = client.query(FindAssetQuantityById::new(...))?;
assert_eq!(balance, expected);
}

  1. Validate all inputs — Never trust external data
  2. Check permissions — Verify caller authorization
  3. Handle errors — Use proper error handling, not panics
  4. Limit loops — Avoid unbounded iterations
  5. Minimize state — Reduce attack surface
PracticeReason
Use ISI when possibleNative execution is faster
Batch instructionsReduce transaction overhead
Minimize queriesEach query has latency
Optimize WASM sizeSmaller = faster deployment
// Version your contracts
const CONTRACT_VERSION: u32 = 1;
// Store version in metadata
SetKeyValue::asset_definition(
contract_asset,
"version".parse()?,
CONTRACT_VERSION.into(),
)