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
241 lines
6.8 KiB
Go
241 lines
6.8 KiB
Go
// cmd/wallet — wallet management CLI.
|
|
//
|
|
// Commands:
|
|
//
|
|
// wallet create --type node|user --label <name> --out <file> [--pass <phrase>]
|
|
// wallet info --wallet <file> [--pass <phrase>]
|
|
// wallet balance --wallet <file> [--pass <phrase>] --db <chaindata>
|
|
// wallet bind --wallet <file> [--pass <phrase>] --node-key <node-key.json>
|
|
// Build a BIND_WALLET transaction to link a node to this wallet.
|
|
// Print the tx JSON (broadcast separately).
|
|
// wallet address --pub-key <hex> Derive DC address from any pub key
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"time"
|
|
|
|
"go-blockchain/blockchain"
|
|
"go-blockchain/economy"
|
|
"go-blockchain/identity"
|
|
"go-blockchain/wallet"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
usage()
|
|
os.Exit(1)
|
|
}
|
|
switch os.Args[1] {
|
|
case "create":
|
|
cmdCreate(os.Args[2:])
|
|
case "info":
|
|
cmdInfo(os.Args[2:])
|
|
case "balance":
|
|
cmdBalance(os.Args[2:])
|
|
case "bind":
|
|
cmdBind(os.Args[2:])
|
|
case "address":
|
|
cmdAddress(os.Args[2:])
|
|
default:
|
|
usage()
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Print(`wallet — manage DC wallets
|
|
|
|
Commands:
|
|
create --type node|user --label <name> --out <file.json> [--pass <phrase>]
|
|
info --wallet <file> [--pass <phrase>]
|
|
balance --wallet <file> [--pass <phrase>] --db <chaindata>
|
|
bind --wallet <file> [--pass <phrase>] --node-key <node.json>
|
|
address --pub-key <hex>
|
|
`)
|
|
}
|
|
|
|
func cmdCreate(args []string) {
|
|
fs := flag.NewFlagSet("create", flag.ExitOnError)
|
|
wtype := fs.String("type", "user", "wallet type: node or user")
|
|
label := fs.String("label", "My Wallet", "wallet label")
|
|
out := fs.String("out", "wallet.json", "output file")
|
|
pass := fs.String("pass", "", "encryption passphrase (empty = no encryption)")
|
|
if err := fs.Parse(args); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
wt := wallet.UserWallet
|
|
if *wtype == "node" {
|
|
wt = wallet.NodeWallet
|
|
}
|
|
|
|
w, err := wallet.New(wt, *label)
|
|
if err != nil {
|
|
log.Fatalf("create wallet: %v", err)
|
|
}
|
|
|
|
if err := w.Save(*out, *pass); err != nil {
|
|
log.Fatalf("save wallet: %v", err)
|
|
}
|
|
|
|
fmt.Printf("Wallet created:\n")
|
|
fmt.Printf(" type: %s\n", w.Type)
|
|
fmt.Printf(" label: %s\n", w.Label)
|
|
fmt.Printf(" address: %s\n", w.Address)
|
|
fmt.Printf(" pub_key: %s\n", w.ID.PubKeyHex())
|
|
fmt.Printf(" saved: %s\n", *out)
|
|
if *pass == "" {
|
|
fmt.Println(" warning: no passphrase set — private key is unencrypted!")
|
|
}
|
|
}
|
|
|
|
func cmdInfo(args []string) {
|
|
fs := flag.NewFlagSet("info", flag.ExitOnError)
|
|
file := fs.String("wallet", "wallet.json", "wallet file")
|
|
pass := fs.String("pass", "", "passphrase")
|
|
if err := fs.Parse(args); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
w, err := wallet.Load(*file, *pass)
|
|
if err != nil {
|
|
log.Fatalf("load wallet: %v", err)
|
|
}
|
|
|
|
data, _ := json.MarshalIndent(w.Info(), "", " ")
|
|
fmt.Println(string(data))
|
|
}
|
|
|
|
func cmdBalance(args []string) {
|
|
fs := flag.NewFlagSet("balance", flag.ExitOnError)
|
|
file := fs.String("wallet", "wallet.json", "wallet file")
|
|
pass := fs.String("pass", "", "passphrase")
|
|
dbPath := fs.String("db", "./chaindata", "chain DB path")
|
|
if err := fs.Parse(args); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
w, err := wallet.Load(*file, *pass)
|
|
if err != nil {
|
|
log.Fatalf("load wallet: %v", err)
|
|
}
|
|
|
|
chain, err := blockchain.NewChain(*dbPath)
|
|
if err != nil {
|
|
log.Fatalf("open chain: %v", err)
|
|
}
|
|
defer chain.Close()
|
|
|
|
bal, err := chain.Balance(w.ID.PubKeyHex())
|
|
if err != nil {
|
|
log.Fatalf("query balance: %v", err)
|
|
}
|
|
|
|
rep, err := chain.Reputation(w.ID.PubKeyHex())
|
|
if err != nil {
|
|
log.Printf("reputation unavailable: %v", err)
|
|
}
|
|
|
|
binding, _ := chain.WalletBinding(w.ID.PubKeyHex())
|
|
|
|
fmt.Printf("Wallet: %s\n", w.Short())
|
|
fmt.Printf(" Address: %s\n", w.Address)
|
|
fmt.Printf(" Pub key: %s\n", w.ID.PubKeyHex())
|
|
fmt.Printf(" Balance: %s (%d µT)\n", economy.FormatTokens(bal), bal)
|
|
fmt.Printf(" Reputation: score=%d rank=%s (blocks=%d relay=%d slashes=%d)\n",
|
|
rep.Score, rep.Rank(), rep.BlocksProduced, rep.RelayProofs, rep.SlashCount)
|
|
if binding != "" {
|
|
fmt.Printf(" Wallet binding: → %s\n", wallet.PubKeyToAddress(binding))
|
|
} else if w.Type == wallet.NodeWallet {
|
|
fmt.Printf(" Wallet binding: (none — rewards go to node key itself)\n")
|
|
}
|
|
}
|
|
|
|
func cmdBind(args []string) {
|
|
fs := flag.NewFlagSet("bind", flag.ExitOnError)
|
|
file := fs.String("wallet", "wallet.json", "payout wallet file")
|
|
pass := fs.String("pass", "", "wallet passphrase")
|
|
nodeKeyFile := fs.String("node-key", "node.json", "node identity JSON file")
|
|
if err := fs.Parse(args); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Load payout wallet (where rewards should go)
|
|
w, err := wallet.Load(*file, *pass)
|
|
if err != nil {
|
|
log.Fatalf("load wallet: %v", err)
|
|
}
|
|
|
|
// Load node identity (the one that signs blocks)
|
|
type rawKey struct {
|
|
PubKey string `json:"pub_key"`
|
|
PrivKey string `json:"priv_key"`
|
|
}
|
|
raw, err := os.ReadFile(*nodeKeyFile)
|
|
if err != nil {
|
|
log.Fatalf("read node key: %v", err)
|
|
}
|
|
var rk rawKey
|
|
if err := json.Unmarshal(raw, &rk); err != nil {
|
|
log.Fatalf("parse node key: %v", err)
|
|
}
|
|
nodeID, err := identity.FromHex(rk.PubKey, rk.PrivKey)
|
|
if err != nil {
|
|
log.Fatalf("load node identity: %v", err)
|
|
}
|
|
|
|
// Build BIND_WALLET transaction signed by the node key
|
|
payload := blockchain.BindWalletPayload{
|
|
WalletPubKey: w.ID.PubKeyHex(),
|
|
WalletAddr: w.Address,
|
|
}
|
|
payloadBytes, _ := json.Marshal(payload)
|
|
|
|
tx := &blockchain.Transaction{
|
|
ID: fmt.Sprintf("bind-%d", time.Now().UnixNano()),
|
|
Type: blockchain.EventBindWallet,
|
|
From: nodeID.PubKeyHex(),
|
|
To: w.ID.PubKeyHex(),
|
|
Payload: payloadBytes,
|
|
Fee: blockchain.MinFee,
|
|
Timestamp: time.Now().UTC(),
|
|
}
|
|
// Sign with the node key (node authorises the binding)
|
|
signBytes, _ := json.Marshal(struct {
|
|
ID string `json:"id"`
|
|
Type blockchain.EventType `json:"type"`
|
|
From string `json:"from"`
|
|
To string `json:"to"`
|
|
Amount uint64 `json:"amount"`
|
|
Fee uint64 `json:"fee"`
|
|
Payload []byte `json:"payload"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}{tx.ID, tx.Type, tx.From, tx.To, tx.Amount, tx.Fee, tx.Payload, tx.Timestamp})
|
|
tx.Signature = nodeID.Sign(signBytes)
|
|
|
|
data, _ := json.MarshalIndent(tx, "", " ")
|
|
fmt.Printf("BIND_WALLET transaction (broadcast to a node to commit):\n\n%s\n\n", string(data))
|
|
fmt.Printf("Effect: node %s...%s will pay rewards to wallet %s\n",
|
|
nodeID.PubKeyHex()[:8], nodeID.PubKeyHex()[len(nodeID.PubKeyHex())-4:],
|
|
w.Address)
|
|
}
|
|
|
|
func cmdAddress(args []string) {
|
|
fs := flag.NewFlagSet("address", flag.ExitOnError)
|
|
pubKey := fs.String("pub-key", "", "hex-encoded Ed25519 public key")
|
|
if err := fs.Parse(args); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if *pubKey == "" {
|
|
log.Fatal("--pub-key is required")
|
|
}
|
|
addr := wallet.PubKeyToAddress(*pubKey)
|
|
fmt.Printf("pub_key: %s\naddress: %s\n", *pubKey, addr)
|
|
}
|