Od zera do działającego programu.
Najkrótsza ścieżka: portfel → faucet → kontrakt AML → deploy → weryfikacja.
-
Utwórz portfel
Wejdź na wallet.octra.org lub zainstaluj klienta CLI. Format adresu:
oct+ base58(sha256(pubkey[32:])). Zapisz seed phrase offline — nie ma odzyskiwania konta. -
Pobierz testnet OCT z faucetu
Discord → kanał
#faucet→ wyślij swój adresoct.... Potrzebujesz minimum0.2 OCT(200 000 OU) na deploy + margines na opłatę transakcji (~1.5× recommendedFee). -
Otwórz Dev Tools w kliencie
W kliencie webowym przejdź do
Dev Tools. UI używa starszych etykiet: compile contract, deploy contract, call contract — to prawidłowe nazwy w obecnym UI (mimo że dokumentacja mówi o "programach"). -
Napisz i skompiluj kontrakt AML
Wklej kod AML w zakładce compile contract. Pamiętaj o słowie kluczowym
contract(nieprogram), ibooljakoint 0/1. Kliknij compile — dostaniesz WASM bytecode. -
Deploy
W zakładce deploy contract: wklej skompilowany kod, ustaw
ou=200000. Dostaniesz adres kontraktu w formacieoct.... Lub przez RPC:POST /rpcz metodąoctra_deployContract. -
Wywołaj i zweryfikuj
Zakładka call contract: wpisz adres kontraktu + nazwę funkcji. Dla metod view (read-only) użyj trybu view, dla zmieniających stan — send call tx. Sprawdź transakcję na octrascan.io.
contract.
Konfiguracja środowiska.
Octra nie wymaga lokalnej instalacji kompilatora — wszystko działa przez klienta webowego lub CLI.
Klient webowy (zalecane do nauki)
wallet.octra.org
Portfel + Dev Tools w przeglądarce. Nie wymaga instalacji. Sekcja
Dev Toolsma kompilator AML, deploy i call interface.IDE — VS Code z rozszerzeniem
Brak oficjalnego rozszerzenia językowego. Użyj podświetlania składni Rust lub OCaml jako przybliżenia. Oficjalne rozszerzenie jest w planach.
Struktura projektu wieloplikowego
// projekt wieloplikowy AML main.aml interfaces/ IOCS01.aml // interfejsy tokenów IVault.aml // main.aml importuje z interfaces/ import IOCS01 from "interfaces/IOCS01.aml" contract Token implements IOCS01 { // implementacja }
Konfiguracja RPC
curl -X POST https://rpc.octra.org/rpc \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"node_status","params":[]}'
AppliedML — podstawy składni.
AppliedML (AML) to własny język kontraktów Octra kompilowany do WASM. Składnia inspirowana Rust/OCaml z natywnymi typami zaszyfrowanymi.
Minimalny program
contract Counter { state { owner: address count: int } event Incremented(by: address, value: int) constructor() { self.owner = origin self.count = 0 } view fn get_count(): int { return self.count } fn inc(): bool { self.count = self.count + 1 emit Incremented(caller, self.count) return true } }
Deklaracje najwyższego poziomu
| deklaracja | opis |
|---|---|
| import X from "path" | Import z pliku lub interfejsu |
| interface Name { ... } | Definicja interfejsu (sygnatury) |
| contract Name { ... } | Główna jednostka deployowana (program) |
| contract Name implements I | Program implementujący interfejs |
| const NAME = value | Stała kompilowana |
| state { ... } | Trwałe pola stanu (storage) |
| event Name(...) | Schemat eventu |
| constructor(...) { ... } | Inicjalizacja przy deployu |
| fn name(...) | Standardowy entrypoint (zmienia stan) |
| view fn name(...) | Read-only — nie zmienia stanu |
| private fn name(...) | Wewnętrzny helper (nie wywoływany z zewnątrz) |
| nonreentrant fn name(...) | Chroniony przed reentrancy |
Typy danych.
| typ | opis | status |
|---|---|---|
| int | Liczba całkowita. Podstawowy typ numeryczny. | ✓ stable |
| bool | true / false. Uwaga: w state przechowuj jako int (0/1), nie jako bool. | ✓ stable |
| string | UTF-8 string (np. nazwa tokenu). | ✓ stable |
| address | Adres Octra lub kontekstowy (caller, origin). | ✓ stable |
| map[K]V | Keyed persistent storage. Np. map[address]int dla sald. |
✓ stable |
| map[K1]map[K2]V | Zagnieżdżona mapa. Używana np. dla grants: map[address]map[address]int. |
✓ stable |
| ct_int | Zaszyfrowana liczba całkowita. Operacje: ct_add, ct_sub, ct_gte. Odszyfrowanie tylko uprawnionymi kluczem. | ✓ HFHE |
| ct_bool | Zaszyfrowany bit logiczny. Operacje: ct_and, ct_or, ct_not. | ✓ HFHE |
| cipher | Opakowanie kryptograficzne FHE (niższy poziom niż ct_int). | weryfikuj |
| bytes / bytes32 | Surowe bajty. | weryfikuj |
| u64, u128, u256 | Unsigned integers. Weryfikuj obsługę w aktualnym kompilatorze przed użyciem produkcyjnym. | weryfikuj |
| list[T] | Lista wartości. | weryfikuj |
| option[T] | Opcjonalna wartość (może być null). | weryfikuj |
| struct Name { ... } | Nazwany rekord (typ złożony). | weryfikuj |
| enum Name { ... } | Nazwany wariant. | weryfikuj |
State, storage i wzorce dostępu.
state { owner: address total_supply: int is_paused: int // bool jako 0/1 ! balances: map[address]int grants: map[address]map[address]int secret_val: ct_int // zaszyfrowane pole } // Wzorce dostępu do state: self.owner // proste pole self.balances[addr] // wpis mapy self.grants[owner][spender] // zagnieżdżona mapa self.total_supply = supply // przypisanie self.balances[to] = self.balances[to] + amount // aktualizacja self.balances[to] += amount // compound (jeśli wspierane)
Zmienne kontekstowe
| zmienna | typ | opis |
|---|---|---|
| caller | address | Adres wywołującego transakcję (msg.sender w Solidity). |
| origin | address | Oryginalny nadawca transakcji (tx.origin w Solidity). Używaj w constructor do ustawienia owner. |
| value | int | Kwota natywnego OCT przesłana z transakcją (w OU). Używaj w funkcjach deposit. |
| admin | address | Adres administratora kontraktu (deployer). |
Storage layout — konceptualnie
field → proste pole field:key → wpis mapy field:key1:key2 → zagnieżdżona mapa field:key:pole → pole structa w mapie field:0, field:1 → elementy listy
Zasady krytyczne — zapamiętaj.
| reguła | dobrze | źle |
|---|---|---|
| Słowo kluczowe kontraktu UI mówi "contract", dokumentacja mówi "program" — w kodzie zawsze: |
contract Foo { } |
program Foo { } |
| Bool w state Nie używaj bool jako pola state — przechowuj jako int: |
is_active: int // 0 lub 1 |
is_active: bool |
| Opłata za deploy Minimalny ou przy deploymencie kontraktu: |
ou=200000 (0.2 OCT) |
ou=1000 |
| Margines na opłatę transakcji Zawsze używaj recommendedFee z marginesem: |
fee = recommendedFee × 1.5 |
fee = recommendedFee |
| Brak ct_mul Mnożenie ciphertext × ciphertext nie istnieje. Możliwe: plaintext × ciphertext: |
ct_add, ct_sub, ct_gte |
ct_mul(a, b) // crash |
| view fn Metody read-only muszą mieć modyfikator view — bez niego zmienią stan: |
view fn get_balance(): int |
fn get_balance(): int |
| Attach OCT do wywołania tokenu Przy wywołaniu metod tokenowych (transfer, grant) NIE dołączaj natywnego OCT: |
value = 0 (przy token calls) |
value = 1000 (niszczy tx) |
Przykłady — Hello World i interfejsy.
contract HelloWorld { state { counter: int secret: ct_int // zaszyfrowana liczba is_open: int // bool jako 0/1 } constructor() { self.counter = 0 self.is_open = 1 self.secret = encrypt(42) } fn increment(): bool { require(self.is_open == 1) self.counter = self.counter + 1 return true } fn add_to_secret(amount: ct_int) { // secret nigdy nie jest odszyfrowany przez serwer self.secret = ct_add(self.secret, amount) } fn close() { require(caller == admin) self.is_open = 0 } view fn get_counter(): int { return self.counter } }
// Definicja interfejsu — tylko sygnatury interface IVault { fn deposit(): bool fn withdraw(amount: ct_int): bool fn lock(): bool view fn is_locked(): int }
import IVault from "interfaces/IVault.aml" contract Vault implements IVault { state { balance: ct_int owner: address locked: int } constructor() { self.owner = origin self.balance = encrypt(0) self.locked = 0 } fn deposit(): bool { require(caller == self.owner) require(self.locked == 0) self.balance = ct_add(self.balance, encrypt(value)) return true } fn withdraw(amount: ct_int): bool { require(caller == self.owner) require(ct_gte(self.balance, amount)) self.balance = ct_sub(self.balance, amount) send(caller, decrypt(amount)) return true } fn lock(): bool { require(caller == self.owner) self.locked = 1 return true } view fn is_locked(): int { return self.locked } }
contract EventDemo { event Transfer(from: address, to: address, amount: int) event Paused(by: address) event PrivateDeposit(account: address) // kwota NIE w evencie! state { owner: address } constructor() { self.owner = origin } fn transfer(to: address, amount: int): bool { emit Transfer(caller, to, amount) return true } fn private_deposit(): bool { // NIE emituj kwoty — to by ujawniło wartość! emit PrivateDeposit(caller) return true } }
Operacje na zaszyfrowanych danych.
Octra implementuje własny schemat HFHE (Hypergraph FHE). Dostępne operacje na szyfrach (ciphertext) są ściśle określone — sprawdź status przed użyciem.
Wzorce projektowe z HFHE.
Akumulator sald (bez ujawniania)
// Bezpieczny pattern: check → compute → update fn withdraw(amount: ct_int) { // 1. Sprawdź saldo BEZ odszyfrowywania require(ct_gte(self.balance, amount)) // 2. Zaktualizuj zaszyfrowane saldo self.balance = ct_sub(self.balance, amount) // 3. Wyślij kwotę — decrypt TYLKO dla send() send(caller, decrypt(amount)) }
Blind comparison (aukcja)
// Porównaj oferty bez ich ujawniania fn bid(amount: ct_int) { require(self.ended == 0) // ct_gte nie ujawnia wartości — tylko wynik bool if ct_gte(amount, self.best_bid) { self.best_bid = amount self.best_addr = caller } }
Zliczanie głosów (bez ujawniania wyborów)
fn vote(choice: ct_bool) { // choice=1 → TAK, choice=0 → NIE // Dodaj 1 do yes jeśli choice=true self.yes = ct_add(self.yes, ct_and(choice, encrypt(1))) // Dodaj 1 do no jeśli choice=false (ct_not) self.no = ct_add(self.no, ct_and(ct_not(choice), encrypt(1))) }
Transport — JSON-RPC 2.0.
Octra eksponuje interfejs JSON-RPC 2.0 przez jeden endpoint. Klient webowy domyślnie używa POST /rpc.
POST /rpc
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "METHOD_NAME",
"params": [] // tablica pozycyjna — nie obiekt!
}// Sukces: { "jsonrpc": "2.0", "id": 1, "result": { /* dane */ } } // Błąd (standard JSON-RPC): { "jsonrpc": "2.0", "id": 1, "error": { "code": -32000, "message": "error" } } // Typowe przyczyny błędów submitu: // - nieprawidłowy podpis // - nieprawidłowy nonce // - niewystarczające saldo // - zduplikowana transakcja // - za niska opłata (fee too low) // - staging full // - nieprawidłowy adres
Metody — Node & Accounts.
| metoda | params | opis | typ |
|---|---|---|---|
| node_version | [] | Wersja oprogramowania i protokołu węzła. | read |
| node_status | [] | Bieżąca epoka, walidator, rooty, timestamp, wersja sieci. | read |
| node_stats | [] | Zagregowane statystyki: liczba kont, supply, transakcje. | read |
| node_metrics | [] | Wewnętrzne metryki wydajności i liczniki. | read |
| metoda | params | opis | typ |
|---|---|---|---|
| octra_balance | [address] | Saldo, raw balance, nonce, pending nonce, status klucza publicznego. | read |
| octra_account | [address, limit?] | Przegląd konta + ostatnie transakcje. | read |
| octra_nonce | [address] | Potwierdzony nonce dla adresu. | read |
| octra_publicKey | [address] | Zarejestrowany klucz publiczny ed25519. | read |
| octra_validateAddress | [address] | Walidacja formatu adresu. | read |
| octra_supply | [] | Łączny, maksymalny i spalony supply. | read |
curl -X POST https://rpc.octra.org/rpc \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "octra_balance", "params": ["octTwójAdres..."] }' // Response: { "result": { "balance": "1.500000", // OCT (6 miejsc) "balance_raw": 1500000, // OU "nonce": 5, "pending_nonce": 5 } }
Metody — Transactions.
| metoda | params | opis |
|---|---|---|
| octra_submit | [tx_json] | Wyślij pojedynczą podpisaną transakcję do stagingu. Acceptance ≠ confirmation. |
| octra_submitBatch | [transactions] | Wyślij wiele podpisanych transakcji jednym wywołaniem. |
| metoda | params | opis |
|---|---|---|
| octra_transaction | [hash] | Stan transakcji: pending / confirmed / rejected / dropped. |
| octra_recentTransactions | [limit?, offset?] | Ostatnie potwierdzone transakcje. |
| octra_transactions | [epoch_id?, limit?] | Lista transakcji po epoce lub ostatnie. |
| octra_transactionsByAddress | [address, limit?, offset?] | Historia transakcji dla adresu. |
{
"from": "octNadawca...",
"to": "octOdbiorca...",
"amount": 1000000, // w OU (1 OCT = 1 000 000 OU)
"fee": 1500, // recommendedFee × 1.5
"nonce": 6, // octra_nonce(address) + 1
"timestamp": 1748466000, // unix ms
"public_key":"ed25519_hex...",
"signature": "ed25519_sig_hex..."
}Metody — Programs (kontrakty).
| metoda | params | opis | typ |
|---|---|---|---|
| octra_deployContract | [contract_code, ou, from, fee, signature] | Deploy kontraktu AML. Wymagane ou=200000. Zwraca adres kontraktu. | write |
| octra_callContract | [address, fn_name, params, from, fee, sig] | Wywołanie funkcji zmieniającej stan (send call tx). | write |
| octra_viewContract | [address, fn_name, params] | Read-only wywołanie view fn — nie wymaga podpisu ani opłaty. | read |
| octra_getContract | [address] | Stan kontraktu, ABI, storage (zaszyfrowane pola jako hex). | read |
| octra_compileContract | [source_code] | Kompilacja AML → WASM bytecode przez RPC. | read |
// Read-only — get_count() nie zmienia stanu curl -X POST https://rpc.octra.org/rpc \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "octra_viewContract", "params": [ "octAdresKontraktu...", "get_count", [] ] }' // Dla read-only: użyj trybu "view" w kliencie // Dla write: użyj trybu "send call tx"
Metody — FHE & Stealth.
| metoda | opis |
|---|---|
| octra_encryptBalance | Zaszyfruj saldo konta (publiczny → prywatny). |
| octra_decryptBalance | Odszyfruj zaszyfrowane saldo (wymaga klucza prywatnego). |
| octra_encryptValue | Zaszyfruj wartość do ct_int (do użycia jako param kontraktu). |
| metoda | opis |
|---|---|
| octra_stealthTransfer | Prywatny transfer OCT — ukrywa nadawcę i kwotę. Wymaga zaszyfrowanego salda. |
| octra_claimTransfers | Odbiór oczekujących prywatnych transferów. |
| octra_pendingTransfers | Lista oczekujących prywatnych transferów dla adresu. |
Circles — izolowane środowiska obliczeń.
Circles to izolowane środowiska zasobów i wykonania w Octra. Pozwalają deployować zaszyfrowane aplikacje webowe adresowane przez oct:// URI.
Payload deploy (domyślny)
{
"runtime": "octb",
"privacy_class": "sealed",
"browser_mode": "native_sealed",
"resource_mode": "sealed_read",
"code_b64": null,
"policy_hash": null,
"members_root": null,
"export_policy": null,
"limits": {
"max_stable_bytes": "33554432", // 32MB state
"max_assets_bytes": "33554432", // 32MB assets
"max_inline_value": "65536", // 64KB inline
"max_wasm_bytes": "33554432" // 32MB WASM
}
}Circle — deploy i przewidywanie ID.
Klient może przewidzieć Circle ID przed wysłaniem transakcji deploy. ID zależy od adresu deployera, nonce i hash payloadu.
// Deterministyczne Circle ID payload_hash = h256("octra:circle_deploy_payload:v1", json(payload)) seed = h256("octra:circle_deploy_id:v1", deployer, nonce, payload_hash) circle_id = "oct" + base58(seed)[0:44] // Uwaga: zmiana payloadu lub deployer adresu = inny circle_id
# Przez lokalny klient webowy (port 8420) curl -X POST http://127.0.0.1:8420/api/circle/deploy \ -H 'Content-Type: application/json' \ -d '{ "circle_id": "oct...", "runtime": "octb", "privacy_class": "sealed", "browser_mode": "native_sealed", "resource_mode": "sealed_read" }'
Circle — upload zaszyfrowanych assetów.
# Upload zaszyfrowanego assetu przez local API curl -X POST http://127.0.0.1:8420/api/circle/upload \ -H 'Content-Type: application/json' \ -d '{ "circle_id": "oct...", "path": "/index.html", "content": "base64_zaszyfrowanej_zawartosci..." }' # Odczyt przez klienta (rozszyfrowanie lokalne): # GET http://127.0.0.1:8420/oct/<circle_id>/index.html # Klient pobiera z RPC, rozszyfrowuje lokalnie, serwuje
Standard OCS-01 — tokeny fungibilne.
OCS-01 to aktualny standardowy szablon tokenu w kliencie Octra. Analogia: ERC-20 na Ethereum. Tokeny są osobne od natywnego OCT — transfer tokenu wciąż wymaga OCT na opłatę.
interface IOCS01 { fn transfer(to: address, amount: int): bool fn grant(spender: address, amount: int): bool fn pull(from: address, to: address, amount: int): bool fn balance_of(addr: address): int fn allowance(owner: address, spender: address): int fn get_name(): string fn get_symbol(): string fn get_total_supply(): int }
Metody OCS-01 — parametry
| metoda | params (raw) | opis |
|---|---|---|
| transfer | ["adres_odbiorcy", 1000] | Przenieś tokeny od callera do odbiorcy. Amount w raw units. |
| grant | ["adres_spender", 1000] | Przyznaj allowance dla spender. Spender może później pull. |
| pull | ["adres_from", "adres_to", 1000] | Przenieś tokeny używając grantu. Caller musi mieć wystarczające allowance. |
| balance_of | ["adres"] | Saldo tokenu dla adresu (view fn — darmowe). |
| allowance | ["owner", "spender"] | Ile owner przyznał spenderowi (view fn). |
| get_name / get_symbol / get_total_supply | [] | Metadane tokenu (view fn — darmowe). |
decimals=6: saldo raw 1000000 = wyświetlane 1.000000. Przy ręcznym wywoływaniu przez Dev Tools — zawsze używaj raw units.
Private Vault — zaszyfrowany skarbiec.
contract PrivateVault { state { balance: ct_int owner: address locked: int // bool jako 0/1 } constructor() { self.owner = origin self.balance = encrypt(0) self.locked = 0 } fn deposit(): bool { require(caller == self.owner) require(self.locked == 0) // value = natywny OCT przesłany z tx (w OU) self.balance = ct_add(self.balance, encrypt(value)) return true } fn withdraw(amount: ct_int): bool { require(caller == self.owner) // check ≥ bez odszyfrowywania require(ct_gte(self.balance, amount)) self.balance = ct_sub(self.balance, amount) send(caller, decrypt(amount)) return true } fn lock(): bool { require(caller == self.owner) self.locked = 1 return true } view fn is_locked(): int { return self.locked } }
Sealed Auction — zapieczętowana aukcja.
Żaden uczestnik (ani operator sieci) nie widzi wartości ofert. Wygrywający wyłaniany przez ct_gte bez dekryptacji. Wdrożone na mainnecie: oct56ix53jDR...
contract SealedAuction { state { best_bid: ct_int best_addr: address ended: int admin: address } constructor() { self.admin = caller self.best_bid = encrypt(0) self.ended = 0 } fn bid(amount: ct_int) { require(self.ended == 0) // Blind comparison — nikt nie widzi wartości if ct_gte(amount, self.best_bid) { self.best_bid = amount self.best_addr = caller } } fn finalize(): address { require(caller == self.admin) self.ended = 1 return self.best_addr // zwycięzca — adres jawny } // Admin może odszyfrować najwyższą ofertę po zakończeniu view fn get_winner(): address { require(self.ended == 1) return self.best_addr } }
Private Vote — zaszyfrowane głosowanie.
contract PrivateVote { state { yes_count: ct_int no_count: ct_int ended: int admin: address } constructor() { self.admin = caller self.yes_count = encrypt(0) self.no_count = encrypt(0) self.ended = 0 } fn vote(choice: ct_bool) { // choice=1 → TAK, choice=0 → NIE require(self.ended == 0) // Nikt nie widzie indywidualnego głosu self.yes_count = ct_add( self.yes_count, ct_and(choice, encrypt(1)) ) self.no_count = ct_add( self.no_count, ct_and(ct_not(choice), encrypt(1)) ) } fn close() { require(caller == self.admin) self.ended = 1 } // Wynik: TAK wygrało (ct_bool — jeszcze zaszyfrowany) view fn result(): ct_bool { require(self.ended == 1) return ct_gte(self.yes_count, self.no_count) } }
Token OCS-01 — minimalny.
import IOCS01 from "interfaces/IOCS01.aml" contract MyToken implements IOCS01 { state { owner: address total_supply: int name: string symbol: string decimals: int balances: map[address]int grants: map[address]map[address]int } constructor(supply: int) { self.owner = origin self.name = "My Token" self.symbol = "MTK" self.decimals = 6 self.total_supply = supply self.balances[origin] = supply // cały supply do deployer } fn transfer(to: address, amount: int): bool { require(self.balances[caller] >= amount) self.balances[caller] = self.balances[caller] - amount self.balances[to] = self.balances[to] + amount return true } fn grant(spender: address, amount: int): bool { self.grants[caller][spender] = amount return true } fn pull(from: address, to: address, amount: int): bool { require(self.grants[from][caller] >= amount) require(self.balances[from] >= amount) self.grants[from][caller] = self.grants[from][caller] - amount self.balances[from] = self.balances[from] - amount self.balances[to] = self.balances[to] + amount return true } view fn balance_of(addr: address): int { return self.balances[addr] } view fn allowance(owner: address, spender: address): int { return self.grants[owner][spender] } view fn get_name(): string { return self.name } view fn get_symbol(): string { return self.symbol } view fn get_total_supply(): int { return self.total_supply } }
Gotowy żeby budować?
Oficjalna dokumentacja, sandbox do testowania i kanał Discord dla deweloperów.