Files
dchain/cmd/wallet/main.go
vsecoder 7e7393e4f8 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
2026-04-17 14:16:44 +03:00

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)
}