//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= → Logs tab. func Log(msg string) { b := []byte(msg) if len(b) == 0 { return } hostLog(uintptr(unsafe.Pointer(&b[0])), uint32(len(b))) }