Skip to Content
Getting StartedSmart Contract Development

Smart Contract Development on TOS Network

Welcome to smart contract development on TOS Network! This guide will teach you how to build, deploy, and manage smart contracts using Rust powered by the TAKO runtime. Experience modern Rust development with ultra-low gas costs and native blockchain features.

Why TOS Network for Smart Contracts?

Rust Smart Contracts

  • Modern Language: Use Rust’s safety guarantees and performance
  • No Runtime Overhead: Compiles to efficient TBPFv3 bytecode
  • Native Features: Direct access to VRF, referrals, batch transfers
  • Ultra-Low Gas: $0.001 per transaction average

TAKO Runtime

  • eBPF-Inspired: Efficient, sandboxed execution
  • Fast Compilation: Quick development iteration
  • Predictable Costs: Gas costs are transparent and low
  • Security First: Memory-safe execution environment

Native Features Integration

  • VRF Randomness: Verifiable random numbers for gaming and lotteries
  • Referral System: Built-in referral rewards at protocol level
  • Batch Transfers: Send to multiple recipients in one transaction
  • Privacy Options: Optional encrypted transfers with UNO

Development Environment Setup

Prerequisites

Before starting, ensure you have:

  • Rust 1.70+ with wasm32-unknown-unknown target
  • TOS Platform Tools for compilation
  • Git for version control
  • TOS CLI for deployment

Install TOS Development Tools

# Install Rust (if not already installed) curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source ~/.cargo/env # Add WebAssembly target rustup target add wasm32-unknown-unknown # Install TOS Platform Tools curl -fsSL https://install.tos.network/tools | bash # Verify installation tos-tools --version

Create Development Project

# Create new smart contract project cargo new --lib my_first_contract cd my_first_contract # Project structure my_first_contract/ ├── src/ │ └── lib.rs ├── Cargo.toml └── README.md

Configure Cargo.toml

[package] name = "my_first_contract" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] tako_sdk = "0.1" [profile.release] opt-level = "z" lto = true codegen-units = 1 panic = "abort"

Your First Smart Contract

Hello World Contract

Create src/lib.rs:

#![no_std] #![no_main] use tako_sdk::*; #[no_mangle] pub extern "C" fn entrypoint() -> u64 { // Log a message (visible in transaction logs) log("Hello from TOS Network!"); // Return success SUCCESS }

Build and Deploy

# Build the contract cargo build --release --target wasm32-unknown-unknown # Deploy to testnet tos deploy \ --contract target/wasm32-unknown-unknown/release/my_first_contract.wasm \ --network testnet

Understanding TAKO Contracts

Contract Structure

#![no_std] #![no_main] use tako_sdk::*; // Contract entrypoint - called for every transaction #[no_mangle] pub extern "C" fn entrypoint() -> u64 { // Get the method being called let method = get_method(); // Route to appropriate handler match method.as_str() { "initialize" => initialize(), "transfer" => transfer(), "balance_of" => balance_of(), _ => { log("Unknown method"); ERROR_INVALID_METHOD } } }

Storage Operations

use tako_sdk::*; // Store a value fn store_balance(address: &[u8], amount: u64) -> u64 { let key = ["balance:", address].concat(); storage_write(&key, &amount.to_le_bytes()) } // Read a value fn get_balance(address: &[u8]) -> u64 { let key = ["balance:", address].concat(); let mut buffer = [0u8; 8]; if storage_read(&key, &mut buffer) == SUCCESS { u64::from_le_bytes(buffer) } else { 0 } } // Delete a value fn clear_balance(address: &[u8]) -> u64 { let key = ["balance:", address].concat(); storage_delete(&key) }

Getting Transaction Context

use tako_sdk::*; fn get_context() { // Get transaction sender let sender = get_caller(); // Get contract address let contract = get_address(); // Get TOS value sent with transaction let value = get_value(); // Get current block height let height = get_block_height(); // Get block timestamp let timestamp = get_timestamp(); }

Token Contract Example

ERC20-Compatible Token

#![no_std] #![no_main] use tako_sdk::*; const DECIMALS: u8 = 8; const TOTAL_SUPPLY: u64 = 1_000_000_00000000; // 1M tokens #[no_mangle] pub extern "C" fn entrypoint() -> u64 { let method = get_method(); match method.as_str() { "initialize" => initialize(), "name" => name(), "symbol" => symbol(), "decimals" => decimals(), "total_supply" => total_supply(), "balance_of" => balance_of(), "transfer" => transfer(), "approve" => approve(), "allowance" => allowance(), "transfer_from" => transfer_from(), _ => ERROR_INVALID_METHOD } } fn initialize() -> u64 { // Only allow initialization once let mut initialized = [0u8; 1]; if storage_read(b"initialized", &mut initialized) == SUCCESS { return ERROR_ALREADY_INITIALIZED; } // Set owner and initial supply let owner = get_caller(); storage_write(b"initialized", &[1]); storage_write(b"owner", &owner); // Give all tokens to owner let key = [b"balance:", &owner[..]].concat(); storage_write(&key, &TOTAL_SUPPLY.to_le_bytes()); // Emit event emit_event("Transfer", &[&[0u8; 32], &owner, &TOTAL_SUPPLY.to_le_bytes()]); SUCCESS } fn transfer() -> u64 { let args = get_args(); let to = &args[0..32]; let amount = u64::from_le_bytes(args[32..40].try_into().unwrap()); let from = get_caller(); // Get sender balance let from_key = [b"balance:", &from[..]].concat(); let mut from_balance_bytes = [0u8; 8]; storage_read(&from_key, &mut from_balance_bytes); let from_balance = u64::from_le_bytes(from_balance_bytes); // Check sufficient balance if from_balance < amount { return ERROR_INSUFFICIENT_BALANCE; } // Get recipient balance let to_key = [b"balance:", to].concat(); let mut to_balance_bytes = [0u8; 8]; storage_read(&to_key, &mut to_balance_bytes); let to_balance = u64::from_le_bytes(to_balance_bytes); // Update balances storage_write(&from_key, &(from_balance - amount).to_le_bytes()); storage_write(&to_key, &(to_balance + amount).to_le_bytes()); // Emit transfer event emit_event("Transfer", &[&from, to, &amount.to_le_bytes()]); SUCCESS } fn balance_of() -> u64 { let args = get_args(); let account = &args[0..32]; let key = [b"balance:", account].concat(); let mut balance_bytes = [0u8; 8]; storage_read(&key, &mut balance_bytes); // Return balance in result set_return_data(&balance_bytes); SUCCESS } fn name() -> u64 { set_return_data(b"My Token"); SUCCESS } fn symbol() -> u64 { set_return_data(b"MTK"); SUCCESS } fn decimals() -> u64 { set_return_data(&[DECIMALS]); SUCCESS } fn total_supply() -> u64 { set_return_data(&TOTAL_SUPPLY.to_le_bytes()); SUCCESS } fn approve() -> u64 { let args = get_args(); let spender = &args[0..32]; let amount = u64::from_le_bytes(args[32..40].try_into().unwrap()); let owner = get_caller(); // Store allowance let key = [b"allowance:", &owner[..], b":", spender].concat(); storage_write(&key, &amount.to_le_bytes()); emit_event("Approval", &[&owner, spender, &amount.to_le_bytes()]); SUCCESS } fn allowance() -> u64 { let args = get_args(); let owner = &args[0..32]; let spender = &args[32..64]; let key = [b"allowance:", owner, b":", spender].concat(); let mut amount_bytes = [0u8; 8]; storage_read(&key, &mut amount_bytes); set_return_data(&amount_bytes); SUCCESS } fn transfer_from() -> u64 { let args = get_args(); let from = &args[0..32]; let to = &args[32..64]; let amount = u64::from_le_bytes(args[64..72].try_into().unwrap()); let spender = get_caller(); // Check allowance let allowance_key = [b"allowance:", from, b":", &spender[..]].concat(); let mut allowance_bytes = [0u8; 8]; storage_read(&allowance_key, &mut allowance_bytes); let current_allowance = u64::from_le_bytes(allowance_bytes); if current_allowance < amount { return ERROR_INSUFFICIENT_ALLOWANCE; } // Update allowance storage_write(&allowance_key, &(current_allowance - amount).to_le_bytes()); // Perform transfer (similar to transfer()) let from_key = [b"balance:", from].concat(); let mut from_balance_bytes = [0u8; 8]; storage_read(&from_key, &mut from_balance_bytes); let from_balance = u64::from_le_bytes(from_balance_bytes); if from_balance < amount { return ERROR_INSUFFICIENT_BALANCE; } let to_key = [b"balance:", to].concat(); let mut to_balance_bytes = [0u8; 8]; storage_read(&to_key, &mut to_balance_bytes); let to_balance = u64::from_le_bytes(to_balance_bytes); storage_write(&from_key, &(from_balance - amount).to_le_bytes()); storage_write(&to_key, &(to_balance + amount).to_le_bytes()); emit_event("Transfer", &[from, to, &amount.to_le_bytes()]); SUCCESS }

Using Native Features

VRF Random Numbers

use tako_sdk::*; fn lottery_draw() -> u64 { // Request VRF random number let seed = get_args(); let random = vrf_random(&seed); // Use random number for lottery let winner_index = random % get_participant_count(); // ... distribute prize SUCCESS }

Referral Rewards

use tako_sdk::*; fn register_with_referral() -> u64 { let args = get_args(); let referrer = &args[0..32]; let new_user = get_caller(); // Register referral relationship referral_set(&new_user, referrer); SUCCESS } fn distribute_referral_rewards() -> u64 { let user = get_caller(); let amount: u64 = 100_00000000; // 100 tokens // Automatically distribute to referral chain referral_distribute(&user, amount); SUCCESS }

Batch Transfers

use tako_sdk::*; fn airdrop() -> u64 { let args = get_args(); // Parse recipients and amounts let count = args[0] as usize; let mut recipients = Vec::new(); let mut amounts = Vec::new(); let mut offset = 1; for _ in 0..count { recipients.push(&args[offset..offset+32]); offset += 32; amounts.push(u64::from_le_bytes(args[offset..offset+8].try_into().unwrap())); offset += 8; } // Execute batch transfer batch_transfer(&recipients, &amounts); SUCCESS }

Testing Smart Contracts

Unit Testing

#[cfg(test)] mod tests { use super::*; use tako_sdk::testing::*; #[test] fn test_transfer() { // Setup test environment let mut env = TestEnv::new(); let owner = env.create_account(1000_00000000); let recipient = env.create_account(0); // Deploy contract env.set_caller(owner); let result = initialize(); assert_eq!(result, SUCCESS); // Check initial balance env.set_args(&owner); balance_of(); let balance = env.get_return_data_u64(); assert_eq!(balance, 1000_00000000); // Transfer tokens let transfer_args = [&recipient[..], &100_00000000u64.to_le_bytes()].concat(); env.set_args(&transfer_args); let result = transfer(); assert_eq!(result, SUCCESS); // Verify balances env.set_args(&owner); balance_of(); assert_eq!(env.get_return_data_u64(), 900_00000000); env.set_args(&recipient); balance_of(); assert_eq!(env.get_return_data_u64(), 100_00000000); } }

Running Tests

# Run all tests cargo test # Run specific test cargo test test_transfer # Run with output cargo test -- --nocapture

Deployment

Testnet Deployment

# Build optimized contract cargo build --release --target wasm32-unknown-unknown # Deploy to testnet tos deploy \ --contract target/wasm32-unknown-unknown/release/my_token.wasm \ --network testnet \ --gas-limit 1000000 # Output: # Contract deployed! # Address: tos1contract_abc123... # Transaction: 0xtxhash... # Gas used: 234,567 # Cost: $0.002

Mainnet Deployment

# Pre-deployment checklist # - All tests passing # - Security review completed # - Gas optimization verified # Deploy to mainnet tos deploy \ --contract target/wasm32-unknown-unknown/release/my_token.wasm \ --network mainnet \ --gas-limit 2000000 \ --confirm

Interacting with Contracts

# Call view function tos call \ --contract tos1contract_abc123... \ --method balance_of \ --args "tos1user_address..." # Send transaction tos send \ --contract tos1contract_abc123... \ --method transfer \ --args "tos1recipient...,1000000000"

Gas Costs

OperationApproximate Cost
Deploy contract$0.01 - $0.05
Token transfer$0.001
Storage write$0.0001 per byte
Storage readFree
VRF random$0.002
Batch transfer (10 recipients)$0.005

Best Practices

Security

// Always validate inputs fn transfer() -> u64 { let args = get_args(); if args.len() < 40 { return ERROR_INVALID_ARGS; } let amount = u64::from_le_bytes(args[32..40].try_into().unwrap()); if amount == 0 { return ERROR_ZERO_AMOUNT; } // ... continue } // Use checked arithmetic fn safe_add(a: u64, b: u64) -> Option<u64> { a.checked_add(b) } // Implement access control fn only_owner() -> bool { let caller = get_caller(); let mut owner = [0u8; 32]; storage_read(b"owner", &mut owner); caller == owner }

Gas Optimization

// Cache storage reads fn optimized_batch() -> u64 { let from = get_caller(); let from_key = [b"balance:", &from[..]].concat(); // Read balance once let mut balance_bytes = [0u8; 8]; storage_read(&from_key, &mut balance_bytes); let mut balance = u64::from_le_bytes(balance_bytes); // Process all transfers for (recipient, amount) in transfers { balance -= amount; // Update recipient... } // Write sender balance once storage_write(&from_key, &balance.to_le_bytes()); SUCCESS }

Next Steps

Ready to build more advanced contracts? Check out the complete examples and SDK reference.

Last updated on