Smart Contract Basics - Setup & Token Contract
Sekarang kita akan mulai membuat smart contract pertama! 🚀
📦 Setup Development Environment
1. Install Rust
Rust adalah bahasa pemrograman yang akan kita gunakan untuk menulis smart contract.
macOS/Linux:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Windows:
Download dan install dari rustup.rs
Verifikasi instalasi:
rustc --version
cargo --version
Seharusnya muncul versi Rust yang terinstall (contoh: rustc 1.84.0).
2. Install Target WASM
Smart contract Stellar dikompilasi ke WebAssembly (WASM). Install target WASM:
rustup target add wasm32v1-none
Catatan: Untuk Rust versi < 1.85.0, gunakan:
rustup target add wasm32-unknown-unknown
3. Install Visual Studio Code
Download dan install VS Code dari code.visualstudio.com
Install Extension:
- Rust Analyzer - Autocomplete dan syntax highlighting Rust
- CodeLLDB - Debugging Rust code
Cara install:
- Buka VS Code
- Tekan
Ctrl+Shift+X(Windows/Linux) atauCmd+Shift+X(Mac) - Search “Rust Analyzer” dan “CodeLLDB”
- Klik Install
4. Install Stellar CLI
Stellar CLI adalah tool untuk build, test, dan deploy smart contract.
macOS/Linux (menggunakan Homebrew):
brew install stellar-cli
Install dari source (semua platform):
cargo install --locked stellar-cli@23.1.4
Autocompletion:
echo "source <(stellar completion --shell bash)" >> ~/.bashrc
Verifikasi instalasi:
stellar --version
Seharusnya muncul versi stellar-cli (contoh: stellar 23.1.4).
5. Generate Wallet untuk Testnet
Kita perlu wallet untuk deploy contract ke Stellar Testnet.
Generate identity bernama alice:
stellar keys generate alice --network testnet --fund
Flag --fund akan otomatis request XLM dari Friendbot (faucet testnet).
Import address (create dari extension/apps):
stellar keys add <name> --seed-phrase
Lihat public key:
stellar keys address alice
Output: GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Lihat secret key:
stellar keys show alice
⚠️ PENTING: Jangan pernah share secret key! Ini seperti password wallet Anda.
🪙 Membuat Token Contract
Kita akan membuat contract sederhana untuk create token custom. Token ini bisa digunakan untuk:
- 💰 Cryptocurrency (coin/token)
- 🎟️ Voucher/Kupon digital
- 🏆 Points/Reward system
- 🎨 NFT (Non-Fungible Token)
Struktur Project
Initialize project baru:
stellar contract init my-token-project --name token
cd my-token-project
Struktur folder:
my-token-project/
├── Cargo.toml
├── contracts/
│ └── token/
│ ├── Cargo.toml
│ ├── src/
│ │ ├── lib.rs
│ │ └── test.rs
📝 Token Contract Code
Sekarang kita akan menulis code! Buka contracts/token/src/lib.rs dan replace dengan code berikut:
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, symbol_short};
// Storage keys
const BALANCE: soroban_sdk::Symbol = symbol_short!("BALANCE");
const NAME: soroban_sdk::Symbol = symbol_short!("NAME");
const SYMBOL: soroban_sdk::Symbol = symbol_short!("SYMBOL");
const TOTAL: soroban_sdk::Symbol = symbol_short!("TOTAL");
// Contract struct
#[contract]
pub struct TokenContract;
// Token data structure
#[contracttype]
#[derive(Clone)]
pub struct TokenInfo {
pub name: String,
pub symbol: String,
pub total_supply: i128,
}
#[contractimpl]
impl TokenContract {
// Initialize token dengan nama, symbol, dan supply
pub fn initialize(
env: Env,
admin: Address,
name: String,
symbol: String,
total_supply: i128,
) {
// Verify admin authorization
admin.require_auth();
// Validasi input
if total_supply <= 0 {
panic!("Total supply harus lebih dari 0");
}
// Simpan token info
env.storage().instance().set(&NAME, &name);
env.storage().instance().set(&SYMBOL, &symbol);
env.storage().instance().set(&TOTAL, &total_supply);
// Set balance admin = total supply
env.storage().instance().set(&BALANCE, &total_supply);
}
// Get nama token
pub fn get_name(env: Env) -> String {
env.storage().instance().get(&NAME).unwrap()
}
// Get symbol token
pub fn get_symbol(env: Env) -> String {
env.storage().instance().get(&SYMBOL).unwrap()
}
// Get total supply
pub fn get_total_supply(env: Env) -> i128 {
env.storage().instance().get(&TOTAL).unwrap()
}
// Get balance
pub fn get_balance(env: Env) -> i128 {
env.storage().instance().get(&BALANCE).unwrap_or(0)
}
// Transfer token (simplified - real token contract lebih kompleks)
pub fn transfer(env: Env, from: Address, to: Address, amount: i128) {
// Verify authorization
from.require_auth();
// Validasi amount
if amount <= 0 {
panic!("Amount harus lebih dari 0");
}
// Get current balances (simplified)
let balance: i128 = env.storage().instance().get(&BALANCE).unwrap_or(0);
// Check sufficient balance
if balance < amount {
panic!("Balance tidak cukup");
}
// Update balance (simplified version)
let new_balance = balance - amount;
env.storage().instance().set(&BALANCE, &new_balance);
}
}
mod test;
🧪 Test Code
Sekarang kita akan menulis test! Buka contracts/token/src/test.rs:
#![cfg(test)]
use super::*;
use soroban_sdk::{testutils::Address as _, Address, Env, String};
#[test]
fn test_initialize_token() {
// Setup environment
let env = Env::default();
let contract_id = env.register(TokenContract, ());
let client = TokenContractClient::new(&env, &contract_id);
// Create admin address
let admin = Address::generate(&env);
// Mock authorization
env.mock_all_auths();
// Initialize token
let name = String::from_str(&env, "Indonesian Rupiah");
let symbol = String::from_str(&env, "IDR");
let supply = 1_000_000_000i128; // 1 miliar
client.initialize(&admin, &name, &symbol, &supply);
// Verify token info
assert_eq!(client.get_name(), name);
assert_eq!(client.get_symbol(), symbol);
assert_eq!(client.get_total_supply(), supply);
assert_eq!(client.get_balance(), supply);
}
#[test]
fn test_get_token_info() {
let env = Env::default();
let contract_id = env.register(TokenContract, ());
let client = TokenContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
env.mock_all_auths();
let name = String::from_str(&env, "Workshop Token");
let symbol = String::from_str(&env, "WST");
let supply = 5_000_000i128;
client.initialize(&admin, &name, &symbol, &supply);
// Test getter functions
assert_eq!(client.get_name(), name);
assert_eq!(client.get_symbol(), symbol);
assert_eq!(client.get_total_supply(), supply);
}
#[test]
#[should_panic(expected = "Total supply harus lebih dari 0")]
fn test_initialize_invalid_supply() {
let env = Env::default();
let contract_id = env.register(TokenContract, ());
let client = TokenContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
env.mock_all_auths();
let name = String::from_str(&env, "Bad Token");
let symbol = String::from_str(&env, "BAD");
let supply = 0i128; // Invalid!
// Should panic
client.initialize(&admin, &name, &symbol, &supply);
}
#[test]
fn test_transfer() {
let env = Env::default();
let contract_id = env.register(TokenContract, ());
let client = TokenContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
let user = Address::generate(&env);
env.mock_all_auths();
let name = String::from_str(&env, "Test Token");
let symbol = String::from_str(&env, "TST");
let supply = 1_000_000i128;
client.initialize(&admin, &name, &symbol, &supply);
// Transfer tokens
let transfer_amount = 100_000i128;
client.transfer(&admin, &user, &transfer_amount);
// Balance should be reduced
assert_eq!(client.get_balance(), supply - transfer_amount);
}
🧪 Running Tests
Jalankan test untuk memastikan contract bekerja:
cargo test
Output yang diharapkan:
running 4 tests
test test::test_initialize_token ... ok
test test::test_get_token_info ... ok
test test::test_initialize_invalid_supply ... ok
test test::test_transfer ... ok
test result: ok. 4 passed; 0 failed
Tips:
- Jika ada error, baca pesan error dengan teliti
- Error messages Rust sangat helpful!
- Jangan panik jika test gagal, ini normal saat belajar
🏗️ Build Contract
Setelah test pass, build contract menjadi WASM:
stellar contract build
Check file WASM yang dihasilkan:
ls target/wasm32v1-none/release/*.wasm
Seharusnya muncul file token.wasm.
🚀 Deploy ke Testnet
Sekarang waktunya deploy contract ke Stellar Testnet!
Step 1: Deploy Contract
stellar contract deploy \
--wasm target/wasm32v1-none/release/token.wasm \
--source-account alice \
--network testnet \
--alias my_token
Output: Contract ID (contoh: CACDYF3CYMJEJTIVFESQYZTN67GO2R5D5IUABTCUG3HXQSRXCSOROBAN)
Simpan contract ID ini!
Step 2: Initialize Token
Sekarang kita initialize token dengan nama, symbol, dan supply:
stellar contract invoke \
--id <CONTRACT_ID> \
--source-account alice \
--network testnet \
-- \
initialize \
--admin <ALICE_PUBLIC_KEY> \
--name "Indonesian Rupiah Token" \
--symbol "IDRT" \
--total_supply "1000000000"
Ganti <CONTRACT_ID> dengan contract ID dari step sebelumnya.
Ganti <ALICE_PUBLIC_KEY> dengan public key alice (dari stellar keys address alice).
Step 3: Check Token Info
Get nama token:
stellar contract invoke \
--id <CONTRACT_ID> \
--source-account alice \
--network testnet \
-- \
get_name
Output: "Indonesian Rupiah Token"
Get symbol:
stellar contract invoke \
--id <CONTRACT_ID> \
--source-account alice \
--network testnet \
-- \
get_symbol
Output: "IDRT"
Get total supply:
stellar contract invoke \
--id <CONTRACT_ID> \
--source-account alice \
--network testnet \
-- \
get_total_supply
Output: 1000000000
🎉 Congratulations!
Anda sudah berhasil:
- ✅ Setup development environment
- ✅ Membuat token contract dari scratch
- ✅ Menulis dan menjalankan test
- ✅ Build contract ke WASM
- ✅ Deploy ke Stellar Testnet
- ✅ Interact dengan deployed contract
💡 Penjelasan Code
Kenapa #![no_std]?
Soroban contracts tidak menggunakan Rust standard library karena:
- 🎯 Size - Standard library terlalu besar (contract max 64KB)
- ⚡ Performance - Optimized untuk blockchain
- 🔒 Security - Controlled environment
Kenapa i128 untuk amount?
- ✅ Stellar ecosystem standard
- ✅ Compatible dengan native token (XLM)
- ✅ Allows safe error checking
- ✅ Prevent overflow issues
Storage Keys
const BALANCE: Symbol = symbol_short!("BALANCE");
- Symbol = short string (max 32 chars)
symbol_short!()= compile-time optimization (max 9 chars)- Digunakan sebagai key di blockchain storage
🎓 Exercise
Coba modifikasi contract:
- Add mint function - Create token baru
- Add burn function - Destroy token
- Add multiple balances - Track balance per address (hint: use
Map<Address, i128>) - Add allowance - Izinkan address lain transfer token kamu
📚 Resources
Next: Mari kita buat contract yang lebih kompleks - Crowdfunding Contract! 🚀