Address
oct2YehV…pjkUvJ
oct2YehVLezCi2RCcSkURc3nyyYtzxmspwGHHALm6pjkUvJ
State
OCT balance
3,809.007OCT
wallet balance
Type
Contract
smart contract on chain
Chain-level
View on Octrascan · devnet ↗
raw txs · nonce · pubkey
Pipoke (no profile)
this wallet has not registered a Pipoke profile
Contract
History
live · 20s · last 0Source · ABI · Bytecode✓ verifiedexpand →
Source · ABI · Bytecode
✓ verified✓ verified
// OctraVPN — decentralized private mesh ("Tailscale-on-chain") for Octra.
//
// v1 production AML, validated against the live mainnet
// `octra_compileAml` RPC (see `scripts/compile-check.sh`). Built
// against confirmed Octra primitives only:
// - runtime: require, transfer, emit, caller, origin, value, epoch,
// self_addr, checkpoint/commit/rollback, concat, to_string,
// parse_ints, mget, sha256, len, is_address, ed25519_ok
// - FHE: fhe_load_pk, fhe_deser, fhe_ser, fhe_add, fhe_sub,
// fhe_add_const, fhe_scale, fhe_verify_zero
//
// Design highlights:
// - Operators stake OU in-program (bond_endpoint); both governance
// slashing (gov_slash_operator) and cryptographic equivocation
// slashing (slash_double_sign, settle_claim) are available — the
// latter became possible once Octra confirmed `ed25519_ok` (see
// `docs/aml-gap-analysis.md`, mainnet reference
// octBDvZSiTqdEBAyFSp79CHeoLMR9MzHugX9YkHtuQ57MRB).
// - Tailnets are member groups with shared treasuries; sessions
// are single-hop with validator-only settle. Receipt integrity
// is economic (the client's max-pay deposit), not cryptographic.
// - Earnings accumulate as HFHE ciphertext per operator's pubkey,
// claimed via two-step (fhe_verify_zero + plain transfer).
// - IDs are self-incrementing int counters (tailnet_count,
// session_count) — no SHA256-derived bytes IDs.
//
// See `docs/aml-grammar.md` for the AML grammar reference,
// `docs/aml-gap-analysis.md` for the audit + migration rationale,
// and `docs/whitepaper.md` for the formal claims.
contract OctraVPN {
// ============================================================
// Structs
// ============================================================
struct EndpointRecord {
active: int // 1 once registered; 0 after retire
endpoint: string // public address (ip:port or .onion)
wg_pubkey: string // X25519 noise pubkey (hex)
receipt_pubkey: string // ed25519 pubkey used to sign off-chain
// dual-signed receipts (canonical payload:
// H("octravpn-receipt-v1" || sid || seq ||
// bytes_used || blind)); separate from
// the Octra account/tx-signing key.
// Stored as hex per docs/keys.md.
region: string
price_per_mb: int
registered_at: int
reputation: int // monotonic count of settled sessions
}
struct Tailnet {
owner: address
treasury: int // OU available for paid traffic
member_count: int
acl_policy: string // hex of off-chain ACL doc hash
created_at: int
exit_count: int
}
struct Session {
tailnet_id: int
exit: address // single configured exit (v1: single-hop)
opener: address // wallet that called open_session; only it can confirm settlement
deposit: int // locked from tailnet treasury at open
opened_at: int
status: int // 0=open, 1=settled, 2=refunded
}
/// Operator's or client's settlement claim for a session. Two
/// of these (one from each side) gate `settle` — see §settle.
struct ClaimRecord {
set: int // 1 if a claim has been recorded
bytes_used: int
claimed_at: int
}
// ============================================================
// Events
// ============================================================
event EndpointRegistered(addr: address, endpoint: string, region: string)
event EndpointUpdated(addr: address)
event EndpointRetired(addr: address)
event KeysRotated(addr: address)
event StakeBonded(addr: address, amount: int, new_stake: int)
event StakeUnbondingStarted(addr: address, stake: int, unlock_epoch: int)
event StakeUnbondingFinalized(addr: address, amount: int)
event OperatorSlashed(addr: address, stake: int, burn_amt: int, bounty_amt: int)
event TailnetCreated(tailnet_id: int, owner: address)
event TailnetMemberAdded(tailnet_id: int, member: address)
event TailnetMemberRemoved(tailnet_id: int, member: address)
event TailnetDeposit(tailnet_id: int, amount: int, new_treasury: int)
event TailnetExitConfigured(tailnet_id: int, exit_addr: address)
event TailnetAclUpdated(tailnet_id: int, acl_policy: string)
event SessionOpened(session_id: int, tailnet_id: int, exit: address, deposit: int, opened_at: int)
event SettleClaimed(session_id: int, exit: address, bytes_used: int)
event SettleConfirmed(session_id: int, opener: address, bytes_used: int)
event SettleDispute(session_id: int, operator_bytes: int, client_bytes: int)
event SessionSettled(session_id: int, exit: address, bytes_used: int, total_paid: int, refund: int)
event SessionRefunded(session_id: int, reason: string)
event SessionSwept(session_id: int)
event JoinTokenPrecommitted(tailnet_id: int, token_hash: bytes)
event JoinTokenRedeemed(tailnet_id: int, member: address, token_hash: bytes)
event EarningsClaimed(operator: address, amount: int)
event DeviceRegistered(wallet: address, device: address)
event DeviceRevoked(wallet: address, device: address)
event ProgramTreasuryWithdrawn(to: address, amount: int)
// ============================================================
// Constants
// ============================================================
const MIN_ENDPOINT_STAKE_DEFAULT: int = 1000000000 // 1000 OCT
const UNBOND_GRACE_DEFAULT: int = 10000 // ~30 days at 10s epochs
const SLASH_BURN_DEFAULT_BPS: int = 9000 // 90%
const SLASH_BOUNTY_DEFAULT_BPS: int = 1000 // 10%
const PROTOCOL_FEE_DEFAULT_BPS: int = 50 // 0.5%
const SWEEP_GRACE_MULT_DEFAULT: int = 10
const SWEEP_BOUNTY_DEFAULT_BPS: int = 100 // 1%
const BPS_DENOM: int = 10000
// ============================================================
// State
// ============================================================
state {
owner: address
paused: int
// Endpoints
endpoint_count: int
endpoint_index: map[int]address // counter -> addr
endpoints: map[address]EndpointRecord
// Operator stake
endpoint_stake: map[address]int
endpoint_unbonding_stake: map[address]int
endpoint_unbonding_unlock: map[address]int
endpoint_slashed: map[address]int // 1 = permanently slashed
// HFHE pubkey + zero-ciphertext per operator, plus the running
// encrypted earnings ledger.
op_pk_set: map[address]int // 1 if operator has registered an HFHE pk
op_pk: map[address]string
op_zero_ct: map[address]string
enc_earnings: map[address]string
// Tailnets
tailnet_count: int
tailnets: map[int]Tailnet
members: map[int]map[address]int // tid -> addr -> 1 if member
tailnet_exits: map[int]map[address]int // tid -> exit_addr -> 1
// Sessions
session_count: int
sessions: map[int]Session
// Two-tx settlement: operator's claim + client's confirmation.
// Settlement only applies when both agree on `bytes_used`. A
// mismatch records a public dispute (both claims stay on chain).
operator_claims: map[int]ClaimRecord
client_confirms: map[int]ClaimRecord
// Pre-auth join tokens (hash-precommit pattern: owner pre-
// publishes sha256(preimage); anyone with the preimage redeems).
// join_token_commits[tid][hash] = 1 means owner published it.
// join_token_redeemed[hash] = 1 means it's been used once.
join_token_commits: map[int]map[bytes]int
join_token_redeemed: map[bytes]int
// Devices
device_owner: map[address]address
// Program treasury (Tier 2 fee + burn share of slashed stakes).
treasury: int
burned: int
// Params (flat — direct struct-field assign isn't supported).
min_session_deposit: int
min_tailnet_deposit: int
session_grace_epochs: int
sweep_grace_multiplier: int
sweep_bounty_bps: int
min_endpoint_stake: int
unbond_grace_epochs: int
slash_burn_bps: int
slash_bounty_bps: int
protocol_fee_bps: int
}
// ============================================================
// Constructor
// ============================================================
constructor(
initial_min_session_deposit: int,
initial_min_tailnet_deposit: int,
initial_session_grace_epochs: int
) {
require(initial_min_session_deposit > 0, "session deposit > 0")
require(initial_min_tailnet_deposit > 0, "tailnet deposit > 0")
require(initial_session_grace_epochs > 0, "session grace > 0")
self.owner = origin
self.paused = 0
self.endpoint_count = 0
self.tailnet_count = 0
self.session_count = 0
self.treasury = 0
self.burned = 0
self.min_session_deposit = initial_min_session_deposit
self.min_tailnet_deposit = initial_min_tailnet_deposit
self.session_grace_epochs = initial_session_grace_epochs
self.sweep_grace_multiplier = SWEEP_GRACE_MULT_DEFAULT
self.sweep_bounty_bps = SWEEP_BOUNTY_DEFAULT_BPS
self.min_endpoint_stake = MIN_ENDPOINT_STAKE_DEFAULT
self.unbond_grace_epochs = UNBOND_GRACE_DEFAULT
self.slash_burn_bps = SLASH_BURN_DEFAULT_BPS
self.slash_bounty_bps = SLASH_BOUNTY_DEFAULT_BPS
self.protocol_fee_bps = PROTOCOL_FEE_DEFAULT_BPS
}
// ============================================================
// Private helpers
// ============================================================
private fn require_not_paused() {
require(self.paused == 0, "paused")
}
private fn require_owner() {
require(caller == self.owner, "not owner")
}
private fn endpoint_is_active(addr: address): bool {
if self.endpoints[addr].active == 0 {
return false
}
if self.endpoint_slashed[addr] != 0 {
return false
}
if self.endpoint_stake[addr] < self.min_endpoint_stake {
return false
}
return true
}
// ============================================================
// Operator stake (bond / unbond / slash)
// ============================================================
fn bond_endpoint(): bool {
require_not_paused()
require(value > 0, "no value")
require(self.endpoint_slashed[caller] == 0, "previously slashed")
require(self.endpoint_unbonding_stake[caller] == 0, "unbonding in progress")
self.endpoint_stake[caller] = self.endpoint_stake[caller] + value
emit StakeBonded(caller, value, self.endpoint_stake[caller])
return true
}
fn unbond_endpoint(): bool {
require_not_paused()
let amt = self.endpoint_stake[caller]
require(amt > 0, "no stake")
require(self.endpoint_unbonding_stake[caller] == 0, "already unbonding")
let unlock = epoch + self.unbond_grace_epochs
self.endpoint_unbonding_stake[caller] = amt
self.endpoint_unbonding_unlock[caller] = unlock
self.endpoint_stake[caller] = 0
if self.endpoints[caller].active != 0 {
self.endpoints[caller].active = 0
emit EndpointRetired(caller)
}
emit StakeUnbondingStarted(caller, amt, unlock)
return true
}
fn finalize_unbond(): bool {
require_not_paused()
let amt = self.endpoint_unbonding_stake[caller]
require(amt > 0, "no unbonding")
require(epoch >= self.endpoint_unbonding_unlock[caller], "grace not elapsed")
self.endpoint_unbonding_stake[caller] = 0
self.endpoint_unbonding_unlock[caller] = 0
require(transfer(caller, amt), "transfer failed")
emit StakeUnbondingFinalized(caller, amt)
return true
}
// Cryptographic equivocation slash.
//
// The off-chain dual-signed-receipt protocol (see
// `crates/octravpn-core/src/receipt.rs`, canonical payload:
// H("octravpn-receipt-v1" || session_id || seq || bytes_used ||
// blind)) makes the operator's `receipt_pubkey` a non-repudiation
// anchor. An honest operator never signs two different payloads
// under that key; if anyone presents two such payloads + sigs the
// operator is provably equivocating, regardless of what those
// payloads encode. AML's `ed25519_ok(pk, msg, sig)` host call lets
// us verify both signatures in-program (confirmed by the Octra dev
// team 2026-05-14; reference deployment
// octBDvZSiTqdEBAyFSp79CHeoLMR9MzHugX9YkHtuQ57MRB).
//
// The `session_id` parameter is the *claimed* session id; we do
// NOT parse the payload to enforce it (AML lacks a byte-slicer).
// The slasher built both payloads from real off-chain receipts, so
// the sig-verify gate already ensures they're real receipts under
// the operator's receipt key. Any two distinct signed payloads
// under one receipt key are evidence enough — the operator loses
// its bond and the caller gets the bounty (90 % burn / 10 %
// bounty, mirroring `gov_slash_operator`).
fn slash_double_sign(
operator_addr: address,
session_id: int,
payload_a: string,
sig_a: string,
payload_b: string,
sig_b: string
): bool {
require_not_paused()
require(self.endpoint_slashed[operator_addr] == 0, "already slashed")
require(payload_a != payload_b, "payloads identical")
let receipt_pk = self.endpoints[operator_addr].receipt_pubkey
require(len(receipt_pk) > 0, "operator has no receipt pubkey")
require(ed25519_ok(receipt_pk, payload_a, sig_a), "sig_a invalid")
require(ed25519_ok(receipt_pk, payload_b, sig_b), "sig_b invalid")
let live = self.endpoint_stake[operator_addr]
let unb = self.endpoint_unbonding_stake[operator_addr]
let total = live + unb
require(total > 0, "no stake to slash")
let burn_amt = total * self.slash_burn_bps / BPS_DENOM
let bounty_amt = total - burn_amt
self.endpoint_stake[operator_addr] = 0
self.endpoint_unbonding_stake[operator_addr] = 0
self.endpoint_unbonding_unlock[operator_addr] = 0
self.endpoint_slashed[operator_addr] = 1
if self.endpoints[operator_addr].active != 0 {
self.endpoints[operator_addr].active = 0
}
self.treasury = self.treasury + burn_amt
if bounty_amt > 0 {
require(transfer(caller, bounty_amt), "bounty transfer failed")
}
emit OperatorSlashed(operator_addr, total, burn_amt, bounty_amt)
return true
}
// Governance slash. Complements the cryptographic equivocation
// slashes (`slash_double_sign`, in-AML equivocation in
// `settle_claim`). The owner submits a slash backed by off-chain
// evidence verification (`octravpn slash-evidence verify`).
fn gov_slash_operator(operator_addr: address): bool {
require_not_paused()
require_owner()
require(self.endpoint_slashed[operator_addr] == 0, "already slashed")
let live = self.endpoint_stake[operator_addr]
let unb = self.endpoint_unbonding_stake[operator_addr]
let total = live + unb
require(total > 0, "no stake to slash")
let burn_amt = total * self.slash_burn_bps / BPS_DENOM
let bounty_amt = total - burn_amt
self.endpoint_stake[operator_addr] = 0
self.endpoint_unbonding_stake[operator_addr] = 0
self.endpoint_unbonding_unlock[operator_addr] = 0
self.endpoint_slashed[operator_addr] = 1
if self.endpoints[operator_addr].active != 0 {
self.endpoints[operator_addr].active = 0
}
self.treasury = self.treasury + burn_amt
if bounty_amt > 0 {
require(transfer(caller, bounty_amt), "bounty transfer failed")
}
emit OperatorSlashed(operator_addr, total, burn_amt, bounty_amt)
return true
}
// ============================================================
// Endpoint lifecycle
// ============================================================
fn register_endpoint(
endpoint: string,
wg_pubkey: string,
hfhe_pubkey: string,
initial_enc_zero: string,
region: string,
price_per_mb: int,
receipt_pubkey: string
): bool {
require_not_paused()
require(self.endpoint_slashed[caller] == 0, "previously slashed")
require(
self.endpoint_stake[caller] >= self.min_endpoint_stake,
"must bond_endpoint first"
)
require(price_per_mb > 0, "price > 0")
require(len(wg_pubkey) > 0, "wg pubkey required")
require(len(hfhe_pubkey) > 0, "hfhe pubkey required")
require(len(initial_enc_zero) > 0, "initial enc(0) required")
require(len(receipt_pubkey) > 0, "receipt pubkey required")
require(caller == origin, "direct caller only")
require(self.endpoints[caller].active == 0, "already registered")
self.op_pk_set[caller] = 1
self.op_pk[caller] = hfhe_pubkey
self.op_zero_ct[caller] = initial_enc_zero
self.enc_earnings[caller] = initial_enc_zero
self.endpoints[caller].active = 1
self.endpoints[caller].endpoint = endpoint
self.endpoints[caller].wg_pubkey = wg_pubkey
self.endpoints[caller].receipt_pubkey = receipt_pubkey
self.endpoints[caller].region = region
self.endpoints[caller].price_per_mb = price_per_mb
self.endpoints[caller].registered_at = epoch
self.endpoints[caller].reputation = 0
self.endpoint_index[self.endpoint_count] = caller
self.endpoint_count = self.endpoint_count + 1
emit EndpointRegistered(caller, endpoint, region)
return true
}
fn update_endpoint(endpoint: string, region: string, price_per_mb: int): bool {
require_not_paused()
require(self.endpoints[caller].active != 0, "not registered")
require(price_per_mb > 0, "price > 0")
self.endpoints[caller].endpoint = endpoint
self.endpoints[caller].region = region
self.endpoints[caller].price_per_mb = price_per_mb
emit EndpointUpdated(caller)
return true
}
// Rotating HFHE keys requires the operator to have claimed all
// outstanding earnings first — the existing ciphertext is
// encrypted under the OLD key and won't decrypt under the new.
fn rotate_keys(
new_wg_pubkey: string,
new_hfhe_pubkey: string,
new_initial_enc_zero: string
): bool {
require_not_paused()
require(self.endpoints[caller].active != 0, "not registered")
require(caller == origin, "direct caller only")
require(len(new_wg_pubkey) > 0, "wg pubkey required")
require(len(new_hfhe_pubkey) > 0, "hfhe pubkey required")
require(len(new_initial_enc_zero) > 0, "initial enc(0) required")
// Refuse rotation while earnings are non-zero.
require(self.enc_earnings[caller] == self.op_zero_ct[caller], "claim earnings first")
self.endpoints[caller].wg_pubkey = new_wg_pubkey
self.op_pk[caller] = new_hfhe_pubkey
self.op_zero_ct[caller] = new_initial_enc_zero
self.enc_earnings[caller] = new_initial_enc_zero
emit KeysRotated(caller)
return true
}
fn retire_endpoint(): bool {
require_not_paused()
require(self.endpoints[caller].active != 0, "not registered")
self.endpoints[caller].active = 0
emit EndpointRetired(caller)
return true
}
// ============================================================
// Tailnet lifecycle
// ============================================================
fn create_tailnet(acl_policy: string): int {
require_not_paused()
require(value >= self.min_tailnet_deposit, "tailnet deposit below min")
require(len(acl_policy) > 0, "acl policy required")
let tid = self.tailnet_count
self.tailnet_count = self.tailnet_count + 1
self.tailnets[tid].owner = caller
self.tailnets[tid].treasury = value
self.tailnets[tid].member_count = 1
self.tailnets[tid].acl_policy = acl_policy
self.tailnets[tid].created_at = epoch
self.tailnets[tid].exit_count = 0
// Owner is implicitly the first member.
self.members[tid][caller] = 1
emit TailnetCreated(tid, caller)
emit TailnetMemberAdded(tid, caller)
return tid
}
fn add_member(tailnet_id: int, member: address): bool {
require_not_paused()
require(tailnet_id < self.tailnet_count, "tailnet not found")
require(self.tailnets[tailnet_id].owner == caller, "not tailnet owner")
require(self.members[tailnet_id][member] == 0, "already member")
self.members[tailnet_id][member] = 1
self.tailnets[tailnet_id].member_count = self.tailnets[tailnet_id].member_count + 1
emit TailnetMemberAdded(tailnet_id, member)
return true
}
fn remove_member(tailnet_id: int, member: address): bool {
require_not_paused()
require(tailnet_id < self.tailnet_count, "tailnet not found")
require(self.tailnets[tailnet_id].owner == caller, "not tailnet owner")
require(member != self.tailnets[tailnet_id].owner, "cannot remove owner")
require(self.members[tailnet_id][member] == 1, "not member")
self.members[tailnet_id][member] = 0
self.tailnets[tailnet_id].member_count = self.tailnets[tailnet_id].member_count - 1
emit TailnetMemberRemoved(tailnet_id, member)
return true
}
fn deposit_to_tailnet(tailnet_id: int): bool {
require_not_paused()
require(value > 0, "no value")
require(tailnet_id < self.tailnet_count, "tailnet not found")
self.tailnets[tailnet_id].treasury = self.tailnets[tailnet_id].treasury + value
let new_t = self.tailnets[tailnet_id].treasury
emit TailnetDeposit(tailnet_id, value, new_t)
return true
}
fn configure_tailnet_exit(tailnet_id: int, exit_addr: address): bool {
require_not_paused()
require(tailnet_id < self.tailnet_count, "tailnet not found")
require(self.tailnets[tailnet_id].owner == caller, "not tailnet owner")
require(self.endpoints[exit_addr].active != 0, "exit not registered")
require(endpoint_is_active(exit_addr), "exit inactive")
require(self.tailnet_exits[tailnet_id][exit_addr] == 0, "already configured")
self.tailnet_exits[tailnet_id][exit_addr] = 1
self.tailnets[tailnet_id].exit_count = self.tailnets[tailnet_id].exit_count + 1
emit TailnetExitConfigured(tailnet_id, exit_addr)
return true
}
fn update_acl(tailnet_id: int, new_acl_policy: string): bool {
require_not_paused()
require(tailnet_id < self.tailnet_count, "tailnet not found")
require(self.tailnets[tailnet_id].owner == caller, "not tailnet owner")
require(len(new_acl_policy) > 0, "acl required")
self.tailnets[tailnet_id].acl_policy = new_acl_policy
emit TailnetAclUpdated(tailnet_id, new_acl_policy)
return true
}
// ============================================================
// Pre-auth join tokens (hash-precommit pattern).
// ============================================================
//
// We can't verify an off-chain Ed25519 signature inside AML, so
// we substitute a hash-precommit: the tailnet owner publishes
// `sha256(token_preimage)` on chain via `precommit_join_token`;
// anyone holding the preimage redeems via `redeem_join_token`.
//
// Trade-off vs the v0 design: the token hash is publicly visible
// on chain at issue time (chain observers see "this tailnet has
// a pending invite"), but no signature verification is needed.
// The preimage is shared off-chain (out-of-band) with the
// intended joiner.
//
// Tokens are one-shot: once redeemed, the hash is marked spent
// and can't be redeemed again.
fn precommit_join_token(tailnet_id: int, token_hash: bytes): bool {
require_not_paused()
require(tailnet_id < self.tailnet_count, "tailnet not found")
require(self.tailnets[tailnet_id].owner == caller, "not tailnet owner")
require(len(token_hash) == 32, "token hash must be 32B (sha256)")
require(self.join_token_commits[tailnet_id][token_hash] == 0, "already committed")
require(self.join_token_redeemed[token_hash] == 0, "hash already used")
self.join_token_commits[tailnet_id][token_hash] = 1
emit JoinTokenPrecommitted(tailnet_id, token_hash)
return true
}
fn redeem_join_token(tailnet_id: int, token_preimage: bytes): bool {
require_not_paused()
require(tailnet_id < self.tailnet_count, "tailnet not found")
require(len(token_preimage) > 0, "preimage required")
let h = sha256(token_preimage)
require(self.join_token_commits[tailnet_id][h] == 1, "unknown token")
require(self.join_token_redeemed[h] == 0, "already redeemed")
require(self.members[tailnet_id][caller] == 0, "already member")
self.members[tailnet_id][caller] = 1
self.tailnets[tailnet_id].member_count = self.tailnets[tailnet_id].member_count + 1
self.join_token_redeemed[h] = 1
emit TailnetMemberAdded(tailnet_id, caller)
emit JoinTokenRedeemed(tailnet_id, caller, h)
return true
}
// ============================================================
// Device registry
// ============================================================
fn register_device(device: address): bool {
require_not_paused()
require(is_address(device), "bad device addr")
let existing = self.device_owner[device]
if existing == caller {
return true
}
require(existing == 0, "device attached to another wallet")
self.device_owner[device] = caller
emit DeviceRegistered(caller, device)
return true
}
fn revoke_device(device: address): bool {
require_not_paused()
require(self.device_owner[device] == caller, "not device owner")
self.device_owner[device] = 0
emit DeviceRevoked(caller, device)
return true
}
view fn get_device_owner(device: address): address {
return self.device_owner[device]
}
view fn is_device_of(device: address, wallet: address): bool {
return self.device_owner[device] == wallet
}
// ============================================================
// Session lifecycle (single-hop)
// ============================================================
// Open a single-hop session against a configured tailnet exit.
// The client deposits `max_pay` from the tailnet treasury — this
// is the ceiling the validator can ever extract.
fn open_session(
tailnet_id: int,
exit_addr: address,
max_pay: int
): int {
require_not_paused()
require(tailnet_id < self.tailnet_count, "tailnet not found")
// Membership: caller is a member directly, OR caller is a
// device whose owner is a member.
let direct = self.members[tailnet_id][caller]
if direct == 0 {
let owner_wallet = self.device_owner[caller]
require(owner_wallet != 0, "not a member")
require(self.members[tailnet_id][owner_wallet] == 1, "device's owner is not a member")
}
require(self.tailnet_exits[tailnet_id][exit_addr] == 1, "exit not configured")
require(endpoint_is_active(exit_addr), "exit inactive")
require(max_pay >= self.min_session_deposit, "deposit below min")
require(self.tailnets[tailnet_id].treasury >= max_pay, "treasury insufficient")
// Lock deposit from treasury.
self.tailnets[tailnet_id].treasury = self.tailnets[tailnet_id].treasury - max_pay
let sid = self.session_count
self.session_count = self.session_count + 1
self.sessions[sid].tailnet_id = tailnet_id
self.sessions[sid].exit = exit_addr
self.sessions[sid].opener = caller
self.sessions[sid].deposit = max_pay
self.sessions[sid].opened_at = epoch
self.sessions[sid].status = 0
emit SessionOpened(sid, tailnet_id, exit_addr, max_pay, epoch)
return sid
}
// ============================================================
// Two-tx settle: operator claims, client confirms.
// ============================================================
//
// Octra's runtime ed25519-verifies every tx before AML sees it,
// so when `settle_claim` fires we know the operator's signature
// is good, and when `settle_confirm` fires we know the client's
// signature is good. Two on-chain txs give us cryptographic
// dual-sig without an in-AML `verify_ed25519` host call.
//
// The state machine:
// 1. Operator calls `settle_claim(sid, bytes_used)`. AML records
// the claim. If the operator already claimed a DIFFERENT
// value for this session, that's equivocation and the
// operator is slashed atomically right here.
// 2. Client (the session opener) calls
// `settle_confirm(sid, bytes_used)` matching the operator's
// claim → settlement applies (FHE earnings credit, refund,
// fee). Mismatch → a public `SettleDispute` event with both
// values; settlement does NOT apply.
// 3. If the client never confirms within grace, `claim_no_show`
// refunds the deposit and the operator gets nothing.
//
// The dispute records (both signed txs) stay on chain as evidence
// for off-chain reputation / dispute-resolution systems.
fn settle_claim(session_id: int, bytes_used: int): bool {
require_not_paused()
require(session_id < self.session_count, "session not found")
require(self.sessions[session_id].status == 0, "session not open")
require(bytes_used >= 0, "bytes >= 0")
require(caller == self.sessions[session_id].exit, "not session exit")
require(endpoint_is_active(caller), "operator inactive")
let prev_set = self.operator_claims[session_id].set
if prev_set == 1 {
let prev_bytes = self.operator_claims[session_id].bytes_used
if prev_bytes == bytes_used {
// Idempotent re-claim (network retry etc.): no-op.
return true
}
// Equivocation: same operator, same session, different bytes.
// Slash atomically using the same logic as gov_slash_operator
// (90 % burn, 10 % bounty to submitter, permanent flag).
let live = self.endpoint_stake[caller]
let unb = self.endpoint_unbonding_stake[caller]
let total = live + unb
let burn_amt = total * self.slash_burn_bps / BPS_DENOM
let bounty_amt = total - burn_amt
self.endpoint_stake[caller] = 0
self.endpoint_unbonding_stake[caller] = 0
self.endpoint_unbonding_unlock[caller] = 0
self.endpoint_slashed[caller] = 1
if self.endpoints[caller].active != 0 {
self.endpoints[caller].active = 0
}
self.treasury = self.treasury + burn_amt
// No bounty transfer here — caller IS the operator. The
// bounty is forfeited (it stays in self.treasury via burn).
self.treasury = self.treasury + bounty_amt
emit OperatorSlashed(caller, total, burn_amt + bounty_amt, 0)
// Force-refund the session deposit to the tailnet treasury;
// the session can't be settled by a slashed operator.
self.sessions[session_id].status = 2
let tid_eq = self.sessions[session_id].tailnet_id
let dep_eq = self.sessions[session_id].deposit
self.tailnets[tid_eq].treasury = self.tailnets[tid_eq].treasury + dep_eq
emit SessionRefunded(session_id, "operator-equivocation")
return false
}
self.operator_claims[session_id].set = 1
self.operator_claims[session_id].bytes_used = bytes_used
self.operator_claims[session_id].claimed_at = epoch
emit SettleClaimed(session_id, caller, bytes_used)
return true
}
// Client-side confirmation. The session opener (whoever called
// open_session) is the only address that can confirm.
fn settle_confirm(session_id: int, bytes_used: int): bool {
require_not_paused()
require(session_id < self.session_count, "session not found")
require(self.sessions[session_id].status == 0, "session not open")
require(bytes_used >= 0, "bytes >= 0")
require(caller == self.sessions[session_id].opener, "not session opener")
let op_set = self.operator_claims[session_id].set
require(op_set == 1, "operator has not claimed yet")
let op_bytes = self.operator_claims[session_id].bytes_used
if op_bytes != bytes_used {
// Disagreement. Record the dispute publicly. Settlement does
// NOT apply — either party can later call claim_no_show after
// grace to refund the deposit (operator gets nothing).
self.client_confirms[session_id].set = 1
self.client_confirms[session_id].bytes_used = bytes_used
self.client_confirms[session_id].claimed_at = epoch
emit SettleDispute(session_id, op_bytes, bytes_used)
return false
}
// Match — apply the settlement.
let exit_addr = self.sessions[session_id].exit
require(endpoint_is_active(exit_addr), "operator inactive")
let price = self.endpoints[exit_addr].price_per_mb
let total_paid = bytes_used * price
let deposit = self.sessions[session_id].deposit
require(total_paid <= deposit, "claim exceeds escrow")
let protocol_fee = total_paid * self.protocol_fee_bps / BPS_DENOM
let net_pay = total_paid - protocol_fee
let refund = deposit - total_paid
self.sessions[session_id].status = 1
self.client_confirms[session_id].set = 1
self.client_confirms[session_id].bytes_used = bytes_used
self.client_confirms[session_id].claimed_at = epoch
if net_pay > 0 {
let pk = fhe_load_pk(exit_addr)
let zero_ct = fhe_deser(self.op_zero_ct[exit_addr])
let pay_ct = fhe_add_const(pk, zero_ct, net_pay)
let cur = fhe_deser(self.enc_earnings[exit_addr])
let new_enc = fhe_add(pk, cur, pay_ct)
self.enc_earnings[exit_addr] = fhe_ser(new_enc)
}
self.endpoints[exit_addr].reputation = self.endpoints[exit_addr].reputation + 1
if protocol_fee > 0 {
self.treasury = self.treasury + protocol_fee
}
if refund > 0 {
let tid = self.sessions[session_id].tailnet_id
self.tailnets[tid].treasury = self.tailnets[tid].treasury + refund
}
emit SettleConfirmed(session_id, caller, bytes_used)
emit SessionSettled(session_id, exit_addr, bytes_used, total_paid, refund)
return true
}
fn claim_no_show(session_id: int): bool {
require_not_paused()
require(session_id < self.session_count, "session not found")
require(self.sessions[session_id].status == 0, "session not open")
let opened = self.sessions[session_id].opened_at
require(epoch >= opened + self.session_grace_epochs, "grace not elapsed")
self.sessions[session_id].status = 2
let tid = self.sessions[session_id].tailnet_id
let dep = self.sessions[session_id].deposit
self.tailnets[tid].treasury = self.tailnets[tid].treasury + dep
emit SessionRefunded(session_id, "no-show")
return true
}
fn sweep_expired_session(session_id: int): bool {
require_not_paused()
require(session_id < self.session_count, "session not found")
require(self.sessions[session_id].status == 0, "session not open")
let opened = self.sessions[session_id].opened_at
let sweep_grace = self.session_grace_epochs * self.sweep_grace_multiplier
require(epoch >= opened + sweep_grace, "sweep grace not elapsed")
self.sessions[session_id].status = 2
let dep = self.sessions[session_id].deposit
let bounty = dep * self.sweep_bounty_bps / BPS_DENOM
let refund = dep - bounty
if bounty > 0 {
require(transfer(caller, bounty), "bounty transfer failed")
}
if refund > 0 {
let tid = self.sessions[session_id].tailnet_id
self.tailnets[tid].treasury = self.tailnets[tid].treasury + refund
}
emit SessionSwept(session_id)
return true
}
// ============================================================
// Earnings claim (two-step: FHE verify + plain transfer)
// ============================================================
view fn get_encrypted_earnings(addr: address): string {
return self.enc_earnings[addr]
}
// Two-step claim. AML verifies the operator's FHE zero-proof
// that `enc_earnings - enc(claimed_amount) = enc(0)`, zeroes the
// ledger, and transfers plaintext OU. The operator's wallet then
// wraps the funds in a native op_type="stealth" tx (off-AML) for
// unlinkable payout.
//
// Partial claims are not supported in v1; the operator must
// claim the exact accumulated total.
fn claim_earnings(claimed_amount: int, proof: string): bool {
require_not_paused()
require(self.endpoint_slashed[caller] == 0, "operator slashed")
require(self.op_pk_set[caller] == 1, "no hfhe pubkey")
require(claimed_amount > 0, "amount > 0")
require(len(proof) > 0, "proof required")
let pk = fhe_load_pk(caller)
let zero_ct = fhe_deser(self.op_zero_ct[caller])
let claim_ct = fhe_add_const(pk, zero_ct, claimed_amount)
let cur = fhe_deser(self.enc_earnings[caller])
let delta = fhe_sub(pk, cur, claim_ct)
require(fhe_verify_zero(pk, delta, proof), "bad opening")
// Reset and pay out.
self.enc_earnings[caller] = self.op_zero_ct[caller]
require(transfer(caller, claimed_amount), "transfer failed")
emit EarningsClaimed(caller, claimed_amount)
return true
}
// ============================================================
// Views
// ============================================================
view fn get_endpoint(addr: address): EndpointRecord {
return self.endpoints[addr]
}
view fn get_endpoint_stake(addr: address): int {
return self.endpoint_stake[addr]
}
view fn get_endpoint_unbonding(addr: address): int {
return self.endpoint_unbonding_stake[addr]
}
view fn get_endpoint_unlock(addr: address): int {
return self.endpoint_unbonding_unlock[addr]
}
view fn is_endpoint_slashed(addr: address): bool {
return self.endpoint_slashed[addr] != 0
}
view fn get_tailnet(tailnet_id: int): Tailnet {
return self.tailnets[tailnet_id]
}
view fn is_tailnet_member(tailnet_id: int, addr: address): bool {
return self.members[tailnet_id][addr] == 1
}
view fn is_tailnet_exit(tailnet_id: int, addr: address): bool {
return self.tailnet_exits[tailnet_id][addr] == 1
}
view fn get_session(session_id: int): Session {
return self.sessions[session_id]
}
view fn get_endpoint_at(index: int): address {
return self.endpoint_index[index]
}
view fn endpoint_count_view(): int {
return self.endpoint_count
}
view fn tailnet_count_view(): int {
return self.tailnet_count
}
view fn session_count_view(): int {
return self.session_count
}
view fn get_program_treasury(): int {
return self.treasury
}
view fn get_min_endpoint_stake(): int {
return self.min_endpoint_stake
}
view fn get_protocol_fee_bps(): int {
return self.protocol_fee_bps
}
// ============================================================
// Governance
// ============================================================
fn set_params(
new_min_session_deposit: int,
new_min_tailnet_deposit: int,
new_session_grace_epochs: int,
new_sweep_grace_multiplier: int,
new_sweep_bounty_bps: int,
new_min_endpoint_stake: int,
new_unbond_grace_epochs: int,
new_slash_burn_bps: int,
new_slash_bounty_bps: int,
new_protocol_fee_bps: int
): bool {
require_owner()
require(new_min_session_deposit > 0, "session deposit > 0")
require(new_min_tailnet_deposit > 0, "tailnet deposit > 0")
require(new_session_grace_epochs > 0, "session grace > 0")
require(new_sweep_grace_multiplier > 0, "sweep > 0")
require(new_sweep_bounty_bps <= 1000, "sweep bounty <= 10%")
require(new_min_endpoint_stake >= 100000000, "stake floor too low")
require(new_unbond_grace_epochs >= 1000, "unbond grace too short")
require(new_slash_burn_bps >= 5000, "burn share too low")
require(new_slash_burn_bps + new_slash_bounty_bps == BPS_DENOM, "slash bps sum")
require(new_protocol_fee_bps <= 200, "protocol fee > 2%")
self.min_session_deposit = new_min_session_deposit
self.min_tailnet_deposit = new_min_tailnet_deposit
self.session_grace_epochs = new_session_grace_epochs
self.sweep_grace_multiplier = new_sweep_grace_multiplier
self.sweep_bounty_bps = new_sweep_bounty_bps
self.min_endpoint_stake = new_min_endpoint_stake
self.unbond_grace_epochs = new_unbond_grace_epochs
self.slash_burn_bps = new_slash_burn_bps
self.slash_bounty_bps = new_slash_bounty_bps
self.protocol_fee_bps = new_protocol_fee_bps
return true
}
fn set_paused(v: int): bool {
require_owner()
self.paused = v
return true
}
fn transfer_ownership(new_owner: address): bool {
require_owner()
require(is_address(new_owner), "bad addr")
self.owner = new_owner
return true
}
fn withdraw_program_treasury(to: address, amount: int): bool {
require_owner()
require(amount > 0, "amount > 0")
require(self.treasury >= amount, "treasury insufficient")
self.treasury = self.treasury - amount
require(transfer(to, amount), "transfer failed")
emit ProgramTreasuryWithdrawn(to, amount)
return true
}
}