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

210
contracts/sdk/dchain.go Normal file
View File

@@ -0,0 +1,210 @@
//go:build tinygo
// Package dchain is the DChain smart contract SDK for TinyGo.
//
// # Build a contract
//
// tinygo build -o mycontract.wasm -target wasip1 -no-debug ./mycontract
//
// # Deploy
//
// client deploy-contract --key key.json \
// --wasm mycontract.wasm --abi mycontract_abi.json \
// --node http://localhost:8081
//
// Each exported Go function becomes a callable contract method.
// All inputs come through Arg*/GetState; all outputs go through Log/SetState.
package dchain
import "unsafe"
// ── Argument accessors ────────────────────────────────────────────────────────
//go:wasmimport env get_arg_str
func hostGetArgStr(idx uint32, ptr uintptr, maxLen uint32) uint32
//go:wasmimport env get_arg_u64
func hostGetArgU64(idx uint32) uint64
// ArgStr returns the idx-th call argument as a string (max maxLen bytes).
// Returns "" if the index is out of range.
func ArgStr(idx int, maxLen int) string {
buf := make([]byte, maxLen)
n := hostGetArgStr(uint32(idx), uintptr(unsafe.Pointer(&buf[0])), uint32(maxLen))
if n == 0 {
return ""
}
return string(buf[:n])
}
// ArgU64 returns the idx-th call argument as a uint64. Returns 0 if out of range.
func ArgU64(idx int) uint64 {
return hostGetArgU64(uint32(idx))
}
// ── State ─────────────────────────────────────────────────────────────────────
//go:wasmimport env get_state
func hostGetState(kPtr uintptr, kLen uint32, dstPtr uintptr, dstLen uint32) uint32
//go:wasmimport env set_state
func hostSetState(kPtr uintptr, kLen uint32, vPtr uintptr, vLen uint32)
//go:wasmimport env get_u64
func hostGetU64(kPtr uintptr, kLen uint32) uint64
//go:wasmimport env put_u64
func hostPutU64(kPtr uintptr, kLen uint32, val uint64)
// GetState reads a value from contract state by key.
// Returns nil if the key does not exist.
func GetState(key string) []byte {
k := []byte(key)
buf := make([]byte, 1024)
n := hostGetState(
uintptr(unsafe.Pointer(&k[0])), uint32(len(k)),
uintptr(unsafe.Pointer(&buf[0])), uint32(len(buf)),
)
if n == 0 {
return nil
}
return buf[:n]
}
// GetStateStr reads a contract state value as a string.
func GetStateStr(key string) string {
v := GetState(key)
if v == nil {
return ""
}
return string(v)
}
// SetState writes a value to contract state.
// Passing an empty slice clears the key.
func SetState(key string, value []byte) {
k := []byte(key)
if len(value) == 0 {
// vPtr=0, vLen=0 → clear key
hostSetState(uintptr(unsafe.Pointer(&k[0])), uint32(len(k)), 0, 0)
return
}
hostSetState(
uintptr(unsafe.Pointer(&k[0])), uint32(len(k)),
uintptr(unsafe.Pointer(&value[0])), uint32(len(value)),
)
}
// SetStateStr writes a string value to contract state.
func SetStateStr(key, value string) {
SetState(key, []byte(value))
}
// GetU64 reads a uint64 stored by PutU64 from contract state.
func GetU64(key string) uint64 {
k := []byte(key)
return hostGetU64(uintptr(unsafe.Pointer(&k[0])), uint32(len(k)))
}
// PutU64 stores a uint64 in contract state as 8-byte big-endian.
func PutU64(key string, val uint64) {
k := []byte(key)
hostPutU64(uintptr(unsafe.Pointer(&k[0])), uint32(len(k)), val)
}
// ── Caller & chain ────────────────────────────────────────────────────────────
//go:wasmimport env get_caller
func hostGetCaller(bufPtr uintptr, bufLen uint32) uint32
//go:wasmimport env get_block_height
func hostGetBlockHeight() uint64
//go:wasmimport env get_contract_treasury
func hostGetContractTreasury(bufPtr uintptr, bufLen uint32) uint32
// Caller returns the hex pubkey of the transaction sender (or parent contract ID).
func Caller() string {
buf := make([]byte, 128)
n := hostGetCaller(uintptr(unsafe.Pointer(&buf[0])), uint32(len(buf)))
return string(buf[:n])
}
// BlockHeight returns the height of the block currently being processed.
func BlockHeight() uint64 {
return hostGetBlockHeight()
}
// Treasury returns the contract's ownerless escrow address.
// Derived as hex(sha256(contractID+":treasury")); no private key exists.
// Only this contract can spend from it via Transfer.
func Treasury() string {
buf := make([]byte, 64)
n := hostGetContractTreasury(uintptr(unsafe.Pointer(&buf[0])), uint32(len(buf)))
return string(buf[:n])
}
// ── Token operations ──────────────────────────────────────────────────────────
//go:wasmimport env get_balance
func hostGetBalance(pubPtr uintptr, pubLen uint32) int64
//go:wasmimport env transfer
func hostTransfer(fromPtr uintptr, fromLen uint32, toPtr uintptr, toLen uint32, amount uint64) uint32
// Balance returns the token balance of a hex pubkey address in µT.
func Balance(pubKey string) uint64 {
p := []byte(pubKey)
return uint64(hostGetBalance(uintptr(unsafe.Pointer(&p[0])), uint32(len(p))))
}
// Transfer sends amount µT from one address to another.
// Returns true on success, false if from has insufficient balance.
func Transfer(from, to string, amount uint64) bool {
f := []byte(from)
t := []byte(to)
return hostTransfer(
uintptr(unsafe.Pointer(&f[0])), uint32(len(f)),
uintptr(unsafe.Pointer(&t[0])), uint32(len(t)),
amount,
) == 0
}
// ── Inter-contract calls ──────────────────────────────────────────────────────
//go:wasmimport env call_contract
func hostCallContract(cidPtr uintptr, cidLen uint32, mthPtr uintptr, mthLen uint32, argPtr uintptr, argLen uint32) uint32
// CallContract executes a method on another deployed contract.
// argsJSON must be a JSON array, e.g. `["alice", "100"]`.
// Caller of the sub-contract is set to this contract's ID.
// Gas is shared — sub-call consumes from the parent's gas budget.
// Returns true on success.
func CallContract(contractID, method, argsJSON string) bool {
cid := []byte(contractID)
mth := []byte(method)
if argsJSON == "" {
argsJSON = "[]"
}
arg := []byte(argsJSON)
return hostCallContract(
uintptr(unsafe.Pointer(&cid[0])), uint32(len(cid)),
uintptr(unsafe.Pointer(&mth[0])), uint32(len(mth)),
uintptr(unsafe.Pointer(&arg[0])), uint32(len(arg)),
) == 0
}
// ── Logging ───────────────────────────────────────────────────────────────────
//go:wasmimport env log
func hostLog(msgPtr uintptr, msgLen uint32)
// Log writes a message to the contract log.
// Logs are visible in the block explorer at /contract?id=<id> → Logs tab.
func Log(msg string) {
b := []byte(msg)
if len(b) == 0 {
return
}
hostLog(uintptr(unsafe.Pointer(&b[0])), uint32(len(b)))
}