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,818 @@
// gen generates contracts/escrow/escrow.wasm
// Run from repo root: go run ./contracts/escrow/gen/
//
// Methods: init, create, release, refund, dispute, resolve, info
//
// State keys (per escrow id):
// "admin" → admin address
// "e:<id>:b" → buyer address
// "e:<id>:s" → seller address
// "e:<id>:v" → amount (8-byte big-endian u64)
// "e:<id>:x" → status byte:
// 'a' = active (funds locked)
// 'd' = disputed
// 'r' = released to seller
// 'f' = refunded to buyer
//
// The contract treasury holds the locked funds during active/disputed state.
package main
import (
"fmt"
"os"
)
// ── LEB128 & builders ────────────────────────────────────────────────────────
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)
}
}
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)
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...))
}
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 ic64(v int64) []byte { return cat([]byte{0x42}, s(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} }
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 drop() []byte { return []byte{0x1A} }
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 i64Eqz() []byte { return []byte{0x50} }
func i32Load8U() []byte { return []byte{0x2D, 0x00, 0x00} }
func i32Store8() []byte { return []byte{0x3A, 0x00, 0x00} }
// ── Memory layout ─────────────────────────────────────────────────────────────
//
// 0x000 64 arg[0]: escrow id
// 0x040 128 arg[1]: seller address or winner string
// 0x140 128 caller buffer
// 0x1C0 64 treasury buffer
// 0x200 128 state-read buffer 1 (buyer or seller)
// 0x280 128 state-read buffer 2 (seller or admin)
// 0x300 2 status byte buffer
// 0x310 256 scratch (buildField key workspace)
//
// Constants at 0x500+:
// 0x500 5 "admin"
// 0x506 13 "initialized: "
// 0x514 10 "created: "
// 0x51E 10 "released: "
// 0x529 10 "refunded: "
// 0x534 10 "disputed: "
// 0x53F 10 "resolved: "
// 0x54A 14 "unauthorized: "
// 0x559 11 "not found: "
// 0x565 14 "already init: "
// 0x574 12 "not active: "
// 0x581 14 "not disputed: "
// 0x590 7 "buyer: "
// 0x598 8 "seller: "
// 0x5A1 8 "status: "
// 0x5AA 7 "active"
// 0x5B1 9 "disputed"
// 0x5BB 9 "released"
// 0x5C5 8 "refunded"
// 0x5CE 6 "buyer" (for resolve winner comparison)
// 0x5D4 6 "seller"
const (
offArg0 int32 = 0x000
offArg1 int32 = 0x040
offCaller int32 = 0x140
offTreasury int32 = 0x1C0
offRead1 int32 = 0x200
offRead2 int32 = 0x280
offStatBuf int32 = 0x300
offScratch int32 = 0x310
offKeyAdmin int32 = 0x500
offInitedPfx int32 = 0x506
offCreatedPfx int32 = 0x514
offReleasedPfx int32 = 0x51E
offRefundedPfx int32 = 0x529
offDisputedPfx int32 = 0x534
offResolvedPfx int32 = 0x53F
offUnauthPfx int32 = 0x54A
offNotFoundPfx int32 = 0x559
offAlreadyPfx int32 = 0x565
offNotActivePfx int32 = 0x574
offNotDispPfx int32 = 0x581
offBuyerPfx int32 = 0x590
offSellerPfx int32 = 0x598
offStatusPfx int32 = 0x5A1
offStrActive int32 = 0x5AA
offStrDisputed int32 = 0x5B1
offStrReleased int32 = 0x5BB
offStrRefunded int32 = 0x5C5
offStrBuyer int32 = 0x5CE
offStrSeller int32 = 0x5D4
)
// ── Import / function indices ─────────────────────────────────────────────────
const (
fnGetArgStr = 0
fnGetArgU64 = 1
fnGetCaller = 2
fnGetState = 3
fnSetState = 4
fnLog = 5
fnTransfer = 6
fnGetContractTreasury = 7
fnPutU64 = 8
fnGetU64 = 9
fnBytesEqual = 10
fnMemcpy = 11
fnLogPrefix = 12
fnBuildField = 13 // (idOff, idLen, fieldChar i32) → keyLen i32
fnInit = 14
fnCreate = 15
fnRelease = 16
fnRefund = 17
fnDispute = 18
fnResolve = 19
fnInfo = 20
)
// ── Helper bodies ─────────────────────────────────────────────────────────────
func bytesEqualBody() []byte {
return funcBody(
withLocals(localDecl(2, tI32)),
ic32(1), lset(4), ic32(0), lset(3),
block_(), loop_(),
lget(3), lget(2), i32GeU(), brIf_(1),
lget(0), lget(3), i32Add(), i32Load8U(),
lget(1), lget(3), i32Add(), i32Load8U(),
i32Ne(), if_(), ic32(0), lset(4), br_(2), end_(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0), end_(), end_(),
lget(4),
)
}
func memcpyBody() []byte {
return funcBody(
withLocals(localDecl(1, tI32)),
ic32(0), lset(3),
block_(), loop_(),
lget(3), lget(2), i32GeU(), brIf_(1),
lget(0), lget(3), i32Add(),
lget(1), lget(3), i32Add(), i32Load8U(),
i32Store8(),
lget(3), ic32(1), i32Add(), lset(3),
br_(0), end_(), end_(),
)
}
func logPrefixBody() []byte {
// (prefixPtr, prefixLen, suffixPtr, suffixLen i32)
return funcBody(
noLocals,
ic32(offScratch), lget(0), lget(1), call(fnMemcpy),
ic32(offScratch), lget(1), i32Add(), lget(2), lget(3), call(fnMemcpy),
ic32(offScratch), lget(1), lget(3), i32Add(), call(fnLog),
)
}
// buildField(idOff, idLen, fieldChar i32) → keyLen i32
// Writes "e:<id>:<fieldChar>" into offScratch.
func buildFieldBody() []byte {
return funcBody(
noLocals,
ic32(offScratch), ic32('e'), i32Store8(),
ic32(offScratch+1), ic32(':'), i32Store8(),
ic32(offScratch+2), lget(0), lget(1), call(fnMemcpy),
ic32(offScratch+2), lget(1), i32Add(), ic32(':'), i32Store8(),
ic32(offScratch+3), lget(1), i32Add(), lget(2), i32Store8(),
lget(1), ic32(4), i32Add(),
)
}
// isCallerAdminCode: reads admin into offRead2, checks caller == admin.
// Params: callerLen local index, adminLen local index.
// Returns inline code that leaves i32 (1=is admin) on stack.
func isCallerAdminCode(callerLenLocal, adminLenLocal uint32) []byte {
return cat(
ic32(offKeyAdmin), ic32(5), ic32(offRead2), ic32(128), call(fnGetState), lset(adminLenLocal),
lget(adminLenLocal), i32Eqz(),
ifI32(), ic32(0), else_(),
lget(callerLenLocal), lget(adminLenLocal), i32Ne(),
ifI32(), ic32(0), else_(),
ic32(offCaller), ic32(offRead2), lget(callerLenLocal), call(fnBytesEqual),
end_(), end_(),
)
}
// ── Contract method bodies ────────────────────────────────────────────────────
// init() — set caller as admin
// Locals: callerLen(0), existingLen(1)
func initBody() []byte {
return funcBody(
withLocals(localDecl(2, tI32)),
ic32(offCaller), ic32(128), call(fnGetCaller), lset(0),
ic32(offKeyAdmin), ic32(5), ic32(offRead2), ic32(128), call(fnGetState), lset(1),
lget(1), ic32(0), i32GtU(), if_(),
ic32(offAlreadyPfx), ic32(14), ic32(offCaller), lget(0), call(fnLogPrefix),
return_(),
end_(),
ic32(offKeyAdmin), ic32(5), ic32(offCaller), lget(0), call(fnSetState),
ic32(offInitedPfx), ic32(13), ic32(offCaller), lget(0), call(fnLogPrefix),
)
}
// create(id, seller, amount u64)
// Locals: idLen(0), sellerLen(1), callerLen(2), treasuryLen(3), keyLen(4) [i32]
// amount(5) [i64]
func createBody() []byte {
return funcBody(
withLocals(localDecl(5, tI32), localDecl(1, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(1), ic32(offArg1), ic32(128), call(fnGetArgStr), lset(1),
lget(1), i32Eqz(), if_(), return_(), end_(),
ic32(2), call(fnGetArgU64), lset(5),
// Check id not already used
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32GtU(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Get caller (buyer) and treasury
ic32(offCaller), ic32(128), call(fnGetCaller), lset(2),
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(3),
// Transfer amount: buyer → treasury
ic32(offCaller), lget(2), ic32(offTreasury), lget(3), lget(5), call(fnTransfer), drop(),
// Write buyer: e:<id>:b → caller
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offCaller), lget(2), call(fnSetState),
// Write seller: e:<id>:s → arg1
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offArg1), lget(1), call(fnSetState),
// Write amount: e:<id>:v
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), lget(5), call(fnPutU64),
// Write status = 'a': e:<id>:x
ic32(offStatBuf), ic32('a'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offStatBuf), ic32(1), call(fnSetState),
ic32(offCreatedPfx), ic32(9), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// checkActiveStatus: reads status byte into offStatBuf; returns inline code
// that returns with an error log if status != expectedChar.
func checkStatus(idLenLocal uint32, expectedChar int32, errPfxOff int32, errPfxLen int32) []byte {
return cat(
ic32(offArg0), lget(idLenLocal), ic32('x'), call(fnBuildField),
// keyLen on stack — but we need it in a local. Use the call result directly:
// Actually we need to save it. This is getting complex. Let me inline differently.
// Instead, we'll read status without saving keyLen — just check immediately.
// Actually the fn returns keyLen on stack. We need (keyPtr, keyLen, dstPtr, dstLen) for get_state.
// So: ic32(offScratch), [keyLen], ic32(offStatBuf), ic32(2), call(fnGetState)
// But keyLen is already on the stack after buildField!
// We can do: ic32(offScratch), <stack: keyLen>, ...
// No wait, stack ordering: we push ic32(offScratch) BEFORE the keyLen.
// For get_state(keyPtr, keyLen, dstPtr, dstLen):
// We need: keyPtr first, then keyLen.
// buildField leaves keyLen on stack, but we need keyPtr first.
// This is the fundamental issue: the keyPtr (offScratch) needs to be pushed before keyLen.
//
// Fix: save keyLen to a local first, then push offScratch, keyLen.
// But we don't have a spare local in this helper...
//
// For now, let me just not use this helper function and inline the check in each method.
ic32(0), // placeholder - don't use this helper
)
}
// release(id) — buyer releases funds to seller
// Locals: idLen(0), buyerLen(1), sellerLen(2), callerLen(3), treasuryLen(4), keyLen(5) [i32]
// amount(6) [i64]
func releaseBody() []byte {
return funcBody(
withLocals(localDecl(6, tI32), localDecl(1, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check status == 'a'
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('a'), i32Ne(), if_(),
ic32(offNotActivePfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Check caller == buyer
ic32(offCaller), ic32(128), call(fnGetCaller), lset(3),
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offRead1), ic32(128), call(fnGetState), lset(1),
lget(3), lget(1), i32Ne(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
ic32(offCaller), ic32(offRead1), lget(3), call(fnBytesEqual),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Get seller, treasury, amount
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offRead2), ic32(128), call(fnGetState), lset(2),
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(4),
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), call(fnGetU64), lset(6),
// Transfer: treasury → seller
ic32(offTreasury), lget(4), ic32(offRead2), lget(2), lget(6), call(fnTransfer), drop(),
// Mark status = 'r'
ic32(offStatBuf), ic32('r'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offStatBuf), ic32(1), call(fnSetState),
ic32(offReleasedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// refund(id) — seller voluntarily refunds buyer
// Locals: idLen(0), sellerLen(1), buyerLen(2), callerLen(3), treasuryLen(4), keyLen(5) [i32]
// amount(6) [i64]
func refundBody() []byte {
return funcBody(
withLocals(localDecl(6, tI32), localDecl(1, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check status == 'a'
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('a'), i32Ne(), if_(),
ic32(offNotActivePfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Check caller == seller
ic32(offCaller), ic32(128), call(fnGetCaller), lset(3),
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offRead1), ic32(128), call(fnGetState), lset(1),
lget(3), lget(1), i32Ne(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
ic32(offCaller), ic32(offRead1), lget(3), call(fnBytesEqual),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Get buyer, treasury, amount
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offRead2), ic32(128), call(fnGetState), lset(2),
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(4),
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), call(fnGetU64), lset(6),
// Transfer: treasury → buyer
ic32(offTreasury), lget(4), ic32(offRead2), lget(2), lget(6), call(fnTransfer), drop(),
// Mark status = 'f'
ic32(offStatBuf), ic32('f'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(5),
ic32(offScratch), lget(5), ic32(offStatBuf), ic32(1), call(fnSetState),
ic32(offRefundedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// dispute(id) — buyer or seller raises a dispute
// Locals: idLen(0), callerLen(1), buyerLen(2), sellerLen(3), keyLen(4) [i32]
func disputeBody() []byte {
return funcBody(
withLocals(localDecl(5, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check status == 'a'
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('a'), i32Ne(), if_(),
ic32(offNotActivePfx), ic32(12), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Caller must be buyer or seller
ic32(offCaller), ic32(128), call(fnGetCaller), lset(1),
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offRead1), ic32(128), call(fnGetState), lset(2),
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offRead2), ic32(128), call(fnGetState), lset(3),
// isBuyer = callerLen==buyerLen && bytes_equal(caller, buyer, callerLen)
// isSeller = callerLen==sellerLen && bytes_equal(caller, seller, callerLen)
// if !isBuyer && !isSeller: unauthorized
lget(1), lget(2), i32Ne(),
if_(),
// callerLen != buyerLen → check seller
lget(1), lget(3), i32Ne(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
ic32(offCaller), ic32(offRead2), lget(1), call(fnBytesEqual),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
// callerLen == buyerLen → check bytes
ic32(offCaller), ic32(offRead1), lget(1), call(fnBytesEqual),
i32Eqz(), if_(),
// not buyer — check seller
lget(1), lget(3), i32Ne(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
ic32(offCaller), ic32(offRead2), lget(1), call(fnBytesEqual),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
end_(),
end_(),
// Mark status = 'd'
ic32(offStatBuf), ic32('d'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(4),
ic32(offScratch), lget(4), ic32(offStatBuf), ic32(1), call(fnSetState),
ic32(offDisputedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// resolve(id, winner) — admin resolves disputed escrow
// winner arg must be "buyer" or "seller"
// Locals: idLen(0), winnerLen(1), callerLen(2), adminLen(3),
// recipientLen(4), treasuryLen(5), keyLen(6) [i32]
// amount(7) [i64]
func resolveBody() []byte {
return funcBody(
withLocals(localDecl(7, tI32), localDecl(1, tI64)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
ic32(1), ic32(offArg1), ic32(128), call(fnGetArgStr), lset(1),
lget(1), i32Eqz(), if_(), return_(), end_(),
// Check status == 'd'
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('d'), i32Ne(), if_(),
ic32(offNotDispPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
else_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Check caller is admin
ic32(offCaller), ic32(128), call(fnGetCaller), lset(2),
isCallerAdminCode(2, 3),
i32Eqz(), if_(),
ic32(offUnauthPfx), ic32(14), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Determine recipient based on winner arg
// winner == "buyer" (5 bytes) → recipient = buyer address
// winner == "seller" (6 bytes) → recipient = seller address
ic32(offTreasury), ic32(64), call(fnGetContractTreasury), lset(5),
ic32(offArg0), lget(0), ic32('v'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), call(fnGetU64), lset(7),
// Compare winner to "buyer"
lget(1), ic32(5), i32Ne(), if_(),
// not "buyer" — assume "seller"
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), ic32(offRead1), ic32(128), call(fnGetState), lset(4),
else_(),
// might be "buyer" — verify bytes
ic32(offArg1), ic32(offStrBuyer), ic32(5), call(fnBytesEqual),
i32Eqz(), if_(),
// not "buyer" bytes — default to seller
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), ic32(offRead1), ic32(128), call(fnGetState), lset(4),
else_(),
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), ic32(offRead1), ic32(128), call(fnGetState), lset(4),
end_(),
end_(),
// Transfer: treasury → recipient
ic32(offTreasury), lget(5), ic32(offRead1), lget(4), lget(7), call(fnTransfer), drop(),
// Mark status = 'r' (released, settled)
ic32(offStatBuf), ic32('r'), i32Store8(),
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(6),
ic32(offScratch), lget(6), ic32(offStatBuf), ic32(1), call(fnSetState),
ic32(offResolvedPfx), ic32(10), ic32(offArg0), lget(0), call(fnLogPrefix),
)
}
// info(id) — log escrow details
// Locals: idLen(0), buyerLen(1), sellerLen(2), keyLen(3) [i32]
func infoBody() []byte {
return funcBody(
withLocals(localDecl(4, tI32)),
ic32(0), ic32(offArg0), ic32(64), call(fnGetArgStr), lset(0),
lget(0), i32Eqz(), if_(), return_(), end_(),
// Check exists
ic32(offArg0), lget(0), ic32('x'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offStatBuf), ic32(2), call(fnGetState),
ic32(0), i32Ne(), i32Eqz(), if_(),
ic32(offNotFoundPfx), ic32(11), ic32(offArg0), lget(0), call(fnLogPrefix),
return_(),
end_(),
// Log buyer
ic32(offArg0), lget(0), ic32('b'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offRead1), ic32(128), call(fnGetState), lset(1),
ic32(offBuyerPfx), ic32(7), ic32(offRead1), lget(1), call(fnLogPrefix),
// Log seller
ic32(offArg0), lget(0), ic32('s'), call(fnBuildField), lset(3),
ic32(offScratch), lget(3), ic32(offRead2), ic32(128), call(fnGetState), lset(2),
ic32(offSellerPfx), ic32(8), ic32(offRead2), lget(2), call(fnLogPrefix),
// Log status
ic32(offStatBuf), i32Load8U(), ic32('a'), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('d'), i32Ne(), if_(),
ic32(offStatBuf), i32Load8U(), ic32('r'), i32Ne(), if_(),
ic32(offStatusPfx), ic32(8), ic32(offStrRefunded), ic32(8), call(fnLogPrefix),
else_(),
ic32(offStatusPfx), ic32(8), ic32(offStrReleased), ic32(8), call(fnLogPrefix),
end_(),
else_(),
ic32(offStatusPfx), ic32(8), ic32(offStrDisputed), ic32(8), call(fnLogPrefix),
end_(),
else_(),
ic32(offStatusPfx), ic32(8), ic32(offStrActive), ic32(6), call(fnLogPrefix),
end_(),
)
}
// ── main ──────────────────────────────────────────────────────────────────────
func main() {
// Types:
// 0: (i32,i32,i32)→(i32) get_arg_str, bytes_equal, buildField
// 1: (i32)→(i64) get_arg_u64
// 2: (i32,i32,i32,i32)→(i32) get_state
// 3: (i32,i32,i32,i32)→() set_state, log_prefix
// 4: (i32,i32)→() log
// 5: (i32,i32,i32,i32,i64)→(i32) transfer
// 6: (i32,i32)→(i32) get_caller, get_contract_treasury
// 7: (i32,i32,i64)→() put_u64
// 8: (i32,i32)→(i64) get_u64
// 9: ()→() exported methods
// 10: (i32,i32,i32)→() memcpy
typeSection := section(0x01, vec(
functype([]byte{tI32, tI32, tI32}, []byte{tI32}), // 0
functype([]byte{tI32}, []byte{tI64}), // 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{tI32, tI32, tI32, tI32, tI64}, []byte{tI32}), // 5
functype([]byte{tI32, tI32}, []byte{tI32}), // 6
functype([]byte{tI32, tI32, tI64}, []byte{}), // 7
functype([]byte{tI32, tI32}, []byte{tI64}), // 8
functype([]byte{}, []byte{}), // 9
functype([]byte{tI32, tI32, tI32}, []byte{}), // 10
))
importSection := section(0x02, vec(
importFunc("env", "get_arg_str", 0), // 0
importFunc("env", "get_arg_u64", 1), // 1
importFunc("env", "get_caller", 6), // 2
importFunc("env", "get_state", 2), // 3
importFunc("env", "set_state", 3), // 4
importFunc("env", "log", 4), // 5
importFunc("env", "transfer", 5), // 6
importFunc("env", "get_contract_treasury", 6), // 7
importFunc("env", "put_u64", 7), // 8
importFunc("env", "get_u64", 8), // 9
))
// 11 local functions
functionSection := section(0x03, vec(
u(0), // bytes_equal type 0
u(10), // memcpy type 10
u(3), // log_prefix type 3
u(0), // build_field type 0
u(9), // init type 9
u(9), // create type 9
u(9), // release type 9
u(9), // refund type 9
u(9), // dispute type 9
u(9), // resolve type 9
u(9), // info type 9
))
memorySection := section(0x05, vec(cat([]byte{0x00}, u(2)))) // 2 pages
exportSection := section(0x07, vec(
exportEntry("memory", 0x02, 0),
exportEntry("init", 0x00, fnInit),
exportEntry("create", 0x00, fnCreate),
exportEntry("release", 0x00, fnRelease),
exportEntry("refund", 0x00, fnRefund),
exportEntry("dispute", 0x00, fnDispute),
exportEntry("resolve", 0x00, fnResolve),
exportEntry("info", 0x00, fnInfo),
))
dataSection := section(0x0B, cat(
u(21),
dataSegment(offKeyAdmin, []byte("admin")),
dataSegment(offInitedPfx, []byte("initialized: ")),
dataSegment(offCreatedPfx, []byte("created: ")),
dataSegment(offReleasedPfx, []byte("released: ")),
dataSegment(offRefundedPfx, []byte("refunded: ")),
dataSegment(offDisputedPfx, []byte("disputed: ")),
dataSegment(offResolvedPfx, []byte("resolved: ")),
dataSegment(offUnauthPfx, []byte("unauthorized: ")),
dataSegment(offNotFoundPfx, []byte("not found: ")),
dataSegment(offAlreadyPfx, []byte("already init: ")),
dataSegment(offNotActivePfx,[]byte("not active: ")),
dataSegment(offNotDispPfx, []byte("not disputed: ")),
dataSegment(offBuyerPfx, []byte("buyer: ")),
dataSegment(offSellerPfx, []byte("seller: ")),
dataSegment(offStatusPfx, []byte("status: ")),
dataSegment(offStrActive, []byte("active")),
dataSegment(offStrDisputed, []byte("disputed")),
dataSegment(offStrReleased, []byte("released")),
dataSegment(offStrRefunded, []byte("refunded")),
dataSegment(offStrBuyer, []byte("buyer")),
dataSegment(offStrSeller, []byte("seller")),
))
codeSection := section(0x0A, cat(
u(11),
bytesEqualBody(),
memcpyBody(),
logPrefixBody(),
buildFieldBody(),
initBody(),
createBody(),
releaseBody(),
refundBody(),
disputeBody(),
resolveBody(),
infoBody(),
))
module := cat(
[]byte{0x00, 0x61, 0x73, 0x6d},
[]byte{0x01, 0x00, 0x00, 0x00},
typeSection,
importSection,
functionSection,
memorySection,
exportSection,
dataSection,
codeSection,
)
out := "contracts/escrow/escrow.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))
}