chore: initial commit for v0.0.1

DChain single-node blockchain + React Native messenger client.

Core:
- PBFT consensus with multi-sig validator admission + equivocation slashing
- BadgerDB + schema migration scaffold (CurrentSchemaVersion=0)
- libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1)
- Native Go contracts (username_registry) alongside WASM (wazero)
- WebSocket gateway with topic-based fanout + Ed25519-nonce auth
- Relay mailbox with NaCl envelope encryption (X25519 + Ed25519)
- Prometheus /metrics, per-IP rate limit, body-size cap

Deployment:
- Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus
- 3-node dev compose (docker-compose.yml) with mocked internet topology
- 3-validator prod compose (deploy/prod/) for federation
- Auto-update from Gitea via /api/update-check + systemd timer
- Build-time version injection (ldflags → node --version)
- UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER)

Client (client-app/):
- Expo / React Native / NativeWind
- E2E NaCl encryption, typing indicator, contact requests
- Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch

Documentation:
- README.md, CHANGELOG.md, CONTEXT.md
- deploy/single/README.md with 6 operator scenarios
- deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design
- docs/contracts/*.md per contract
This commit is contained in:
vsecoder
2026-04-17 14:16:44 +03:00
commit 7e7393e4f8
196 changed files with 55947 additions and 0 deletions

View File

@@ -0,0 +1,458 @@
// gen generates contracts/name_registry/name_registry.wasm
// Run from the repo root: go run ./contracts/name_registry/gen/
//
// Contract methods: register, resolve, transfer, release
// Host imports from "env": get_arg_str, get_caller, get_state, set_state, log
//
// Every contract action emits a human-readable log entry visible in the
// block explorer, e.g.:
// "registered: alice"
// "name taken: alice"
// "not found: alice"
// "owner: <pubkey>"
// "transferred: alice"
// "unauthorized: alice"
// "released: alice"
package main
import (
"fmt"
"os"
)
// ── LEB128 ───────────────────────────────────────────────────────────────────
func u(v uint64) []byte {
var b []byte
for {
bt := byte(v & 0x7f)
v >>= 7
if v != 0 {
bt |= 0x80
}
b = append(b, bt)
if v == 0 {
return b
}
}
}
func s(v int64) []byte {
var b []byte
for {
bt := byte(v & 0x7f)
v >>= 7
sign := (bt & 0x40) != 0
if (v == 0 && !sign) || (v == -1 && sign) {
return append(b, bt)
}
b = append(b, bt|0x80)
}
}
// ── Builders ──────────────────────────────────────────────────────────────────
func cat(slices ...[]byte) []byte {
var out []byte
for _, sl := range slices {
out = append(out, sl...)
}
return out
}
func wstr(str string) []byte { return cat(u(uint64(len(str))), []byte(str)) }
func section(id byte, content []byte) []byte {
return cat([]byte{id}, u(uint64(len(content))), content)
}
func vec(items ...[]byte) []byte {
out := u(uint64(len(items)))
for _, it := range items {
out = append(out, it...)
}
return out
}
func functype(params, results []byte) []byte {
return cat([]byte{0x60}, u(uint64(len(params))), params, u(uint64(len(results))), results)
}
func importFunc(mod, name string, typeIdx uint32) []byte {
return cat(wstr(mod), wstr(name), []byte{0x00}, u(uint64(typeIdx)))
}
func exportEntry(name string, kind byte, idx uint32) []byte {
return cat(wstr(name), []byte{kind}, u(uint64(idx)))
}
func dataSegment(offset int32, data []byte) []byte {
return cat(
[]byte{0x00},
[]byte{0x41}, s(int64(offset)), []byte{0x0B},
u(uint64(len(data))), data,
)
}
func funcBody(localDecls []byte, instrs ...[]byte) []byte {
inner := cat(localDecls)
for _, ins := range instrs {
inner = append(inner, ins...)
}
inner = append(inner, 0x0B) // end
return cat(u(uint64(len(inner))), inner)
}
var noLocals = u(0)
func localDecl(n uint32, typ byte) []byte { return cat(u(uint64(n)), []byte{typ}) }
func withLocals(decls ...[]byte) []byte {
return cat(u(uint64(len(decls))), cat(decls...))
}
// ── Instructions ──────────────────────────────────────────────────────────────
const (
tI32 byte = 0x7F
tI64 byte = 0x7E
)
func call(fn uint32) []byte { return cat([]byte{0x10}, u(uint64(fn))) }
func lget(i uint32) []byte { return cat([]byte{0x20}, u(uint64(i))) }
func lset(i uint32) []byte { return cat([]byte{0x21}, u(uint64(i))) }
func ic32(v int32) []byte { return cat([]byte{0x41}, s(int64(v))) }
func block_() []byte { return []byte{0x02, 0x40} }
func loop_() []byte { return []byte{0x03, 0x40} }
func if_() []byte { return []byte{0x04, 0x40} }
func ifI32() []byte { return []byte{0x04, tI32} } // if that returns i32
func else_() []byte { return []byte{0x05} }
func end_() []byte { return []byte{0x0B} }
func br_(lbl uint32) []byte { return cat([]byte{0x0C}, u(uint64(lbl))) }
func brIf_(lbl uint32) []byte { return cat([]byte{0x0D}, u(uint64(lbl))) }
func return_() []byte { return []byte{0x0F} }
func i32Eqz() []byte { return []byte{0x45} }
func i32Ne() []byte { return []byte{0x47} }
func i32GtU() []byte { return []byte{0x4B} }
func i32GeU() []byte { return []byte{0x4F} }
func i32Add() []byte { return []byte{0x6A} }
func i32Load8U() []byte { return []byte{0x2D, 0x00, 0x00} }
func i32Store8() []byte { return []byte{0x3A, 0x00, 0x00} }
// ── Memory layout ─────────────────────────────────────────────────────────────
const (
offArg0 = 0x000 // arg[0] name buffer (64 bytes)
offArg1 = 0x040 // arg[1] new_owner buffer (128 bytes)
offCaller = 0x0C0 // caller pubkey buffer (128 bytes)
offStateRead = 0x140 // existing owner buffer (128 bytes)
// Verbose prefix strings — each ends with ": " for readable log messages.
offRegisteredPfx = 0x200 // "registered: " 12 bytes
offNameTakenPfx = 0x20C // "name taken: " 12 bytes
offNotFoundPfx = 0x218 // "not found: " 11 bytes
offOwnerPfx = 0x224 // "owner: " 7 bytes
offTransferredPfx = 0x22C // "transferred: " 13 bytes
offUnauthPfx = 0x23A // "unauthorized: " 14 bytes
offReleasedPfx = 0x249 // "released: " 10 bytes
// Scratch buffer for building concatenated log messages.
offScratch = 0x300 // 256 bytes
)
// Import function indices (order must match importSection below)
const (
fnGetArgStr = 0 // get_arg_str(idx, dstPtr, dstLen i32) → i32
fnGetCaller = 1 // get_caller(bufPtr, bufLen i32) → i32
fnGetState = 2 // get_state(kP,kL,dP,dL i32) → i32
fnSetState = 3 // set_state(kP,kL,vP,vL i32)
fnLog = 4 // log(msgPtr, msgLen i32)
)
// Local function indices (imports first, then locals in declaration order)
const (
fnBytesEqual = 5 // $bytes_equal(aPtr,bPtr,len i32) → i32
fnMemcpy = 6 // $memcpy(dst,src,len i32)
fnLogPrefixName = 7 // $log_prefix_name(pfxPtr,pfxLen,sfxPtr,sfxLen i32)
fnRegister = 8
fnResolve = 9
fnTransfer = 10
fnRelease = 11
)
// ── $bytes_equal helper ───────────────────────────────────────────────────────
// (aPtr i32, bPtr i32, len i32) → i32
// locals: i(3), same(4)
func bytesEqualBody() []byte {
return funcBody(
withLocals(localDecl(2, tI32)), // locals 3=i, 4=same
// same = 1; i = 0
ic32(1), lset(4),
ic32(0), lset(3),
block_(),
loop_(),
lget(3), lget(2), i32GeU(), brIf_(1), // i >= len → exit block
// load mem[aPtr+i]
lget(0), lget(3), i32Add(), i32Load8U(),
// load mem[bPtr+i]
lget(1), lget(3), i32Add(), i32Load8U(),
i32Ne(), if_(),
ic32(0), lset(4),
br_(2), // exit block
end_(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0), // next iteration
end_(),
end_(),
lget(4), // return same
)
}
// ── $memcpy helper ────────────────────────────────────────────────────────────
// (dst i32, src i32, len i32) — copies len bytes from src to dst.
// locals: i(3)
func memcpyBody() []byte {
return funcBody(
withLocals(localDecl(1, tI32)), // local 3 = i
ic32(0), lset(3), // i = 0
block_(),
loop_(),
lget(3), lget(2), i32GeU(), brIf_(1), // if i >= len: exit
// mem[dst+i] = mem[src+i]
lget(0), lget(3), i32Add(), // dst+i (store address)
lget(1), lget(3), i32Add(), i32Load8U(), // load mem[src+i]
i32Store8(),
lget(3), ic32(1), i32Add(), lset(3), // i++
br_(0),
end_(),
end_(),
)
}
// ── $log_prefix_name helper ───────────────────────────────────────────────────
// (prefixPtr i32, prefixLen i32, suffixPtr i32, suffixLen i32)
// Builds "prefix<suffix>" in scratch buffer and logs it.
func logPrefixNameBody() []byte {
return funcBody(
noLocals,
// memcpy(offScratch, prefixPtr, prefixLen)
ic32(offScratch), lget(0), lget(1), call(fnMemcpy),
// memcpy(offScratch + prefixLen, suffixPtr, suffixLen)
ic32(offScratch), lget(1), i32Add(), lget(2), lget(3), call(fnMemcpy),
// log(offScratch, prefixLen + suffixLen)
ic32(offScratch), lget(1), lget(3), i32Add(), call(fnLog),
)
}
// ── isOwner: shared caller-vs-existing check ──────────────────────────────────
// Assumes: caller is at offCaller with len callerLenLocal,
// existing is at offStateRead with len existingLenLocal.
// Returns instructions that leave i32 (1=same, 0=not) on stack.
func isOwnerCheck(callerLenLocal, existingLenLocal uint32) []byte {
return cat(
// if callerLen != existingLen → push 0
ifI32(),
ic32(0),
else_(),
// else call bytes_equal(offCaller, offStateRead, callerLen)
ic32(offCaller), ic32(offStateRead), lget(callerLenLocal),
call(fnBytesEqual),
end_(),
)
}
func main() {
// ── Type section ──────────────────────────────────────────────────────────
// Type 0: (i32,i32,i32)→(i32) get_arg_str, bytes_equal
// Type 1: (i32,i32)→(i32) get_caller
// Type 2: (i32,i32,i32,i32)→(i32) get_state
// Type 3: (i32,i32,i32,i32)→() set_state, log_prefix_name
// Type 4: (i32,i32)→() log
// Type 5: ()→() register, resolve, transfer, release
// Type 6: (i32,i32,i32)→() memcpy
typeSection := section(0x01, vec(
functype([]byte{tI32, tI32, tI32}, []byte{tI32}), // 0
functype([]byte{tI32, tI32}, []byte{tI32}), // 1
functype([]byte{tI32, tI32, tI32, tI32}, []byte{tI32}), // 2
functype([]byte{tI32, tI32, tI32, tI32}, []byte{}), // 3
functype([]byte{tI32, tI32}, []byte{}), // 4
functype([]byte{}, []byte{}), // 5
functype([]byte{tI32, tI32, tI32}, []byte{}), // 6
))
// ── Import section ────────────────────────────────────────────────────────
importSection := section(0x02, vec(
importFunc("env", "get_arg_str", 0), // fnGetArgStr=0 type 0
importFunc("env", "get_caller", 1), // fnGetCaller=1 type 1
importFunc("env", "get_state", 2), // fnGetState=2 type 2
importFunc("env", "set_state", 3), // fnSetState=3 type 3
importFunc("env", "log", 4), // fnLog=4 type 4
))
// ── Function section: 7 local functions ──────────────────────────────────
functionSection := section(0x03, vec(
u(0), // bytes_equal → type 0
u(6), // memcpy → type 6
u(3), // log_prefix_name → type 3
u(5), // register → type 5
u(5), // resolve → type 5
u(5), // transfer → type 5
u(5), // release → type 5
))
// ── Memory section ────────────────────────────────────────────────────────
memorySection := section(0x05, vec(cat([]byte{0x00}, u(1))))
// ── Export section ────────────────────────────────────────────────────────
exportSection := section(0x07, vec(
exportEntry("memory", 0x02, 0),
exportEntry("register", 0x00, fnRegister),
exportEntry("resolve", 0x00, fnResolve),
exportEntry("transfer", 0x00, fnTransfer),
exportEntry("release", 0x00, fnRelease),
))
// ── Data section ──────────────────────────────────────────────────────────
dataSection := section(0x0B, cat(
u(7),
dataSegment(offRegisteredPfx, []byte("registered: ")),
dataSegment(offNameTakenPfx, []byte("name taken: ")),
dataSegment(offNotFoundPfx, []byte("not found: ")),
dataSegment(offOwnerPfx, []byte("owner: ")),
dataSegment(offTransferredPfx, []byte("transferred: ")),
dataSegment(offUnauthPfx, []byte("unauthorized: ")),
dataSegment(offReleasedPfx, []byte("released: ")),
))
// ── Code section ─────────────────────────────────────────────────────────
// register(): locals nameLen(0), callerLen(1), existingLen(2)
registerBody := funcBody(
withLocals(localDecl(3, tI32)),
// nameLen = get_arg_str(0, offArg0, 64)
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
// if nameLen == 0: return
lget(0), i32Eqz(), if_(), return_(), end_(),
// existingLen = get_state(offArg0, nameLen, offStateRead, 128)
ic32(offArg0), lget(0), ic32(offStateRead), ic32(128), call(fnGetState), lset(2),
// if existingLen > 0: log("name taken: <name>"); return
lget(2), ic32(0), i32GtU(), if_(),
ic32(offNameTakenPfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// callerLen = get_caller(offCaller, 128)
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
// set_state(offArg0, nameLen, offCaller, callerLen)
ic32(offArg0), lget(0), ic32(offCaller), lget(1), call(fnSetState),
// log("registered: <name>")
ic32(offRegisteredPfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
// resolve(): locals nameLen(0), ownerLen(1)
resolveBody := funcBody(
withLocals(localDecl(2, tI32)),
// nameLen = get_arg_str(0, offArg0, 64)
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// ownerLen = get_state(offArg0, nameLen, offStateRead, 128)
ic32(offArg0), lget(0), ic32(offStateRead), ic32(128), call(fnGetState), lset(1),
// if ownerLen == 0: log("not found: <name>"); return
lget(1), i32Eqz(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// log("owner: <pubkey>")
// The pubkey stored in state is the raw caller string (hex-encoded),
// so the log will display the human-readable address.
ic32(offOwnerPfx), ic32(7), ic32(offStateRead), lget(1), call(fnLogPrefixName),
)
// transfer(): locals nameLen(0), newOwnerLen(1), callerLen(2), existingLen(3)
transferBody := funcBody(
withLocals(localDecl(4, tI32)),
// nameLen = get_arg_str(0, offArg0, 64)
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// newOwnerLen = get_arg_str(1, offArg1, 128)
ic32(1), ic32(offArg1), ic32(128), call(fnGetArgStr), lset(1),
lget(1), i32Eqz(), if_(), return_(), end_(),
// existingLen = get_state(offArg0, nameLen, offStateRead, 128)
ic32(offArg0), lget(0), ic32(offStateRead), ic32(128), call(fnGetState), lset(3),
// if existingLen == 0: not registered → anyone can claim
lget(3), i32Eqz(), if_(),
ic32(offArg0), lget(0), ic32(offArg1), lget(1), call(fnSetState),
ic32(offTransferredPfx), ic32(13), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// callerLen = get_caller(offCaller, 128)
ic32(offCaller), ic32(128), call(fnGetCaller), lset(2),
// isOwner = (callerLen == existingLen) ? bytes_equal(...) : 0
lget(2), lget(3), i32Ne(),
isOwnerCheck(2, 3),
// if !isOwner: log("unauthorized: <name>"); return
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// Authorized
ic32(offArg0), lget(0), ic32(offArg1), lget(1), call(fnSetState),
ic32(offTransferredPfx), ic32(13), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
// release(): locals nameLen(0), callerLen(1), existingLen(2)
releaseBody := funcBody(
withLocals(localDecl(3, tI32)),
// nameLen = get_arg_str(0, offArg0, 64)
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// existingLen = get_state(offArg0, nameLen, offStateRead, 128)
ic32(offArg0), lget(0), ic32(offStateRead), ic32(128), call(fnGetState), lset(2),
// if existingLen == 0: log("not found: <name>"); return
lget(2), i32Eqz(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// callerLen = get_caller(offCaller, 128)
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
// isOwner check
lget(1), lget(2), i32Ne(),
isOwnerCheck(1, 2),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefixName),
return_(),
end_(),
// Store empty value → effectively releases the name
ic32(offArg0), lget(0), ic32(offArg0), ic32(0), call(fnSetState),
ic32(offReleasedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefixName),
)
codeSection := section(0x0A, cat(
u(7),
bytesEqualBody(),
memcpyBody(),
logPrefixNameBody(),
registerBody,
resolveBody,
transferBody,
releaseBody,
))
// ── Assemble module ───────────────────────────────────────────────────────
module := cat(
[]byte{0x00, 0x61, 0x73, 0x6d}, // \0asm
[]byte{0x01, 0x00, 0x00, 0x00}, // version 1
typeSection,
importSection,
functionSection,
memorySection,
exportSection,
dataSection,
codeSection,
)
out := "contracts/name_registry/name_registry.wasm"
if err := os.WriteFile(out, module, 0644); err != nil {
fmt.Fprintln(os.Stderr, "write:", err)
os.Exit(1)
}
fmt.Printf("Written %s (%d bytes)\n", out, len(module))
}

Binary file not shown.

View File

@@ -0,0 +1,301 @@
(module
;; Name Registry smart contract
;;
;; Maps human-readable names → owner public keys on-chain.
;; Each name can only be registered once; only the current owner can
;; transfer or release it.
;;
;; Methods (all void, no WASM return values):
;; register(name string) — claim a name for the caller
;; resolve(name string) — log the current owner pubkey
;; transfer(name string, new_owner string) — give name to another pubkey
;; release(name string) — delete name registration
;;
;; State keys: the raw name bytes → owner pubkey bytes.
;; All args come in via env.get_arg_str / env.get_arg_u64.
;; ── imports ──────────────────────────────────────────────────────────────
(import "env" "get_arg_str"
(func $get_arg_str (param i32 i32 i32) (result i32)))
(import "env" "get_caller"
(func $get_caller (param i32 i32) (result i32)))
(import "env" "get_state"
(func $get_state (param i32 i32 i32 i32) (result i32)))
(import "env" "set_state"
(func $set_state (param i32 i32 i32 i32)))
(import "env" "log"
(func $log (param i32 i32)))
;; ── memory ───────────────────────────────────────────────────────────────
;; Offset Size Purpose
;; 0x000 64 arg[0] buffer — name (max 64 bytes)
;; 0x040 128 arg[1] buffer — new_owner pubkey (max 128 bytes)
;; 0x0C0 128 caller pubkey buffer
;; 0x140 128 state-read buffer (existing owner)
;; 0x200 ~128 verbose log prefix strings
;; 0x300 256 scratch buffer — build "prefix: name" log messages
(memory (export "memory") 1)
;; ── verbose log prefix strings ───────────────────────────────────────────
;; Each entry is a human-readable prefix ending with ": " so that the
;; log message becomes "prefix: <argument>" — readable in the explorer.
;;
;; "registered: " 12 bytes @ 0x200
;; "name taken: " 12 bytes @ 0x20C
;; "not found: " 11 bytes @ 0x218
;; "owner: " 7 bytes @ 0x224
;; "transferred: " 13 bytes @ 0x22C
;; "unauthorized: " 14 bytes @ 0x23A
;; "released: " 10 bytes @ 0x249
(data (i32.const 0x200) "registered: ")
(data (i32.const 0x20C) "name taken: ")
(data (i32.const 0x218) "not found: ")
(data (i32.const 0x224) "owner: ")
(data (i32.const 0x22C) "transferred: ")
(data (i32.const 0x23A) "unauthorized: ")
(data (i32.const 0x249) "released: ")
;; ── helpers ───────────────────────────────────────────────────────────────
;; $memcpy: copy len bytes from src to dst
(func $memcpy (param $dst i32) (param $src i32) (param $len i32)
(local $i i32)
(local.set $i (i32.const 0))
(block $break
(loop $loop
(br_if $break (i32.ge_u (local.get $i) (local.get $len)))
(i32.store8
(i32.add (local.get $dst) (local.get $i))
(i32.load8_u (i32.add (local.get $src) (local.get $i))))
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $loop)
)
)
)
;; $log_prefix_name: build "<prefix><suffix>" in scratch buf 0x300 and log it.
;; prefixPtr / prefixLen — the prefix string (e.g. "registered: ", 12 bytes)
;; suffixPtr / suffixLen — the name / pubkey to append
(func $log_prefix_name
(param $prefixPtr i32) (param $prefixLen i32)
(param $suffixPtr i32) (param $suffixLen i32)
;; copy prefix → scratch[0]
(call $memcpy (i32.const 0x300) (local.get $prefixPtr) (local.get $prefixLen))
;; copy suffix → scratch[prefixLen]
(call $memcpy
(i32.add (i32.const 0x300) (local.get $prefixLen))
(local.get $suffixPtr)
(local.get $suffixLen))
;; log scratch[0 .. prefixLen+suffixLen)
(call $log
(i32.const 0x300)
(i32.add (local.get $prefixLen) (local.get $suffixLen)))
)
;; $bytes_equal: compare mem[aPtr..aPtr+len) with mem[bPtr..bPtr+len)
;; Params: aPtr(0) bPtr(1) len(2)
;; Result: i32 1 = equal, 0 = not equal
(func $bytes_equal (param i32 i32 i32) (result i32)
(local $i i32)
(local $same i32)
(local.set $same (i32.const 1))
(local.set $i (i32.const 0))
(block $break
(loop $loop
(br_if $break (i32.ge_u (local.get $i) (local.get 2)))
(if (i32.ne
(i32.load8_u (i32.add (local.get 0) (local.get $i)))
(i32.load8_u (i32.add (local.get 1) (local.get $i))))
(then (local.set $same (i32.const 0)) (br $break))
)
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(br $loop)
)
)
(local.get $same)
)
;; ── register(name) ────────────────────────────────────────────────────────
;; Claims `name` for the caller.
;; Logs "registered: <name>" on success, "name taken: <name>" on conflict.
(func (export "register")
(local $nameLen i32)
(local $callerLen i32)
(local $existingLen i32)
;; Read name into 0x000, max 64 bytes
(local.set $nameLen
(call $get_arg_str (i32.const 0) (i32.const 0x000) (i32.const 64)))
(if (i32.eqz (local.get $nameLen)) (then return))
;; Check if name is already taken
(local.set $existingLen
(call $get_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x140) (i32.const 128)))
(if (i32.gt_u (local.get $existingLen) (i32.const 0))
(then
(call $log_prefix_name
(i32.const 0x20C) (i32.const 12) ;; "name taken: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Store: state[name] = caller_pubkey
(local.set $callerLen
(call $get_caller (i32.const 0x0C0) (i32.const 128)))
(call $set_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x0C0) (local.get $callerLen))
(call $log_prefix_name
(i32.const 0x200) (i32.const 12) ;; "registered: "
(i32.const 0x000) (local.get $nameLen))
)
;; ── resolve(name) ─────────────────────────────────────────────────────────
;; Logs "owner: <pubkey>" for the registered name, or "not found: <name>".
(func (export "resolve")
(local $nameLen i32)
(local $ownerLen i32)
(local.set $nameLen
(call $get_arg_str (i32.const 0) (i32.const 0x000) (i32.const 64)))
(if (i32.eqz (local.get $nameLen)) (then return))
(local.set $ownerLen
(call $get_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x140) (i32.const 128)))
(if (i32.eqz (local.get $ownerLen))
(then
(call $log_prefix_name
(i32.const 0x218) (i32.const 11) ;; "not found: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Log "owner: <pubkey bytes>"
;; The pubkey stored in state is the raw caller pubkey bytes that the
;; host wrote via get_caller — these are the hex-encoded public key
;; string bytes, so the log will show the readable hex address.
(call $log_prefix_name
(i32.const 0x224) (i32.const 7) ;; "owner: "
(i32.const 0x140) (local.get $ownerLen))
)
;; ── transfer(name, new_owner) ─────────────────────────────────────────────
;; Transfers ownership of `name` to `new_owner`.
;; Only the current owner may call this (or anyone if name is unregistered).
;; Logs "transferred: <name>" on success, "unauthorized: <name>" otherwise.
(func (export "transfer")
(local $nameLen i32)
(local $newOwnerLen i32)
(local $callerLen i32)
(local $existingLen i32)
(local.set $nameLen
(call $get_arg_str (i32.const 0) (i32.const 0x000) (i32.const 64)))
(if (i32.eqz (local.get $nameLen)) (then return))
(local.set $newOwnerLen
(call $get_arg_str (i32.const 1) (i32.const 0x040) (i32.const 128)))
(if (i32.eqz (local.get $newOwnerLen)) (then return))
;; Read existing owner into 0x140
(local.set $existingLen
(call $get_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x140) (i32.const 128)))
;; If not registered, anyone can claim → register directly for new_owner
(if (i32.eqz (local.get $existingLen))
(then
(call $set_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x040) (local.get $newOwnerLen))
(call $log_prefix_name
(i32.const 0x22C) (i32.const 13) ;; "transferred: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Verify caller == existing owner
(local.set $callerLen
(call $get_caller (i32.const 0x0C0) (i32.const 128)))
(if (i32.eqz
(call $bytes_equal
(i32.const 0x0C0) (i32.const 0x140)
(if (result i32) (i32.ne (local.get $callerLen) (local.get $existingLen))
(then (i32.const 0)) ;; length mismatch → not equal
(else (local.get $callerLen)))))
(then
(call $log_prefix_name
(i32.const 0x23A) (i32.const 14) ;; "unauthorized: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Authorized — update owner
(call $set_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x040) (local.get $newOwnerLen))
(call $log_prefix_name
(i32.const 0x22C) (i32.const 13) ;; "transferred: "
(i32.const 0x000) (local.get $nameLen))
)
;; ── release(name) ─────────────────────────────────────────────────────────
;; Removes a name registration. Only the current owner may call this.
;; Logs "released: <name>" on success.
(func (export "release")
(local $nameLen i32)
(local $callerLen i32)
(local $existingLen i32)
(local.set $nameLen
(call $get_arg_str (i32.const 0) (i32.const 0x000) (i32.const 64)))
(if (i32.eqz (local.get $nameLen)) (then return))
(local.set $existingLen
(call $get_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x140) (i32.const 128)))
(if (i32.eqz (local.get $existingLen))
(then
(call $log_prefix_name
(i32.const 0x218) (i32.const 11) ;; "not found: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Verify caller == owner
(local.set $callerLen
(call $get_caller (i32.const 0x0C0) (i32.const 128)))
(if (i32.eqz
(call $bytes_equal
(i32.const 0x0C0) (i32.const 0x140)
(if (result i32) (i32.ne (local.get $callerLen) (local.get $existingLen))
(then (i32.const 0))
(else (local.get $callerLen)))))
(then
(call $log_prefix_name
(i32.const 0x23A) (i32.const 14) ;; "unauthorized: "
(i32.const 0x000) (local.get $nameLen))
return
)
)
;; Store empty bytes → effectively deletes the record
(call $set_state
(i32.const 0x000) (local.get $nameLen)
(i32.const 0x000) (i32.const 0))
(call $log_prefix_name
(i32.const 0x249) (i32.const 10) ;; "released: "
(i32.const 0x000) (local.get $nameLen))
)
)

View File

@@ -0,0 +1,6 @@
{"methods":[
{"name":"register", "args":[{"name":"name","type":"string"}]},
{"name":"resolve", "args":[{"name":"name","type":"string"}]},
{"name":"transfer", "args":[{"name":"name","type":"string"},{"name":"new_owner","type":"string"}]},
{"name":"release", "args":[{"name":"name","type":"string"}]}
]}