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:
183
node/api_tokens.go
Normal file
183
node/api_tokens.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go-blockchain/blockchain"
|
||||
"go-blockchain/wallet"
|
||||
)
|
||||
|
||||
// GET /api/nfts — list all NFTs.
|
||||
func apiNFTs(q ExplorerQuery) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if q.GetNFTs == nil {
|
||||
jsonErr(w, fmt.Errorf("NFT query not available"), 503)
|
||||
return
|
||||
}
|
||||
nfts, err := q.GetNFTs()
|
||||
if err != nil {
|
||||
jsonErr(w, err, 500)
|
||||
return
|
||||
}
|
||||
if nfts == nil {
|
||||
nfts = []blockchain.NFTRecord{}
|
||||
}
|
||||
jsonOK(w, map[string]any{
|
||||
"count": len(nfts),
|
||||
"nfts": nfts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/nfts/{id} — single NFT metadata
|
||||
// GET /api/nfts/owner/{pubkey} — NFTs owned by address
|
||||
func apiNFTByID(q ExplorerQuery) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
path := strings.TrimPrefix(r.URL.Path, "/api/nfts/")
|
||||
parts := strings.SplitN(path, "/", 2)
|
||||
|
||||
// /api/nfts/owner/{pubkey}
|
||||
if parts[0] == "owner" {
|
||||
if len(parts) < 2 || parts[1] == "" {
|
||||
jsonErr(w, fmt.Errorf("pubkey required"), 400)
|
||||
return
|
||||
}
|
||||
pubKey := parts[1]
|
||||
if strings.HasPrefix(pubKey, "DC") && q.AddressToPubKey != nil {
|
||||
if pk, err := q.AddressToPubKey(pubKey); err == nil && pk != "" {
|
||||
pubKey = pk
|
||||
}
|
||||
}
|
||||
if q.NFTsByOwner == nil {
|
||||
jsonErr(w, fmt.Errorf("NFT query not available"), 503)
|
||||
return
|
||||
}
|
||||
nfts, err := q.NFTsByOwner(pubKey)
|
||||
if err != nil {
|
||||
jsonErr(w, err, 500)
|
||||
return
|
||||
}
|
||||
if nfts == nil {
|
||||
nfts = []blockchain.NFTRecord{}
|
||||
}
|
||||
jsonOK(w, map[string]any{"count": len(nfts), "nfts": nfts})
|
||||
return
|
||||
}
|
||||
|
||||
// /api/nfts/{id}
|
||||
nftID := parts[0]
|
||||
if nftID == "" {
|
||||
jsonErr(w, fmt.Errorf("NFT ID required"), 400)
|
||||
return
|
||||
}
|
||||
if q.GetNFT == nil {
|
||||
jsonErr(w, fmt.Errorf("NFT query not available"), 503)
|
||||
return
|
||||
}
|
||||
rec, err := q.GetNFT(nftID)
|
||||
if err != nil {
|
||||
jsonErr(w, err, 500)
|
||||
return
|
||||
}
|
||||
if rec == nil {
|
||||
jsonErr(w, fmt.Errorf("NFT %s not found", nftID), 404)
|
||||
return
|
||||
}
|
||||
// Attach owner address for convenience.
|
||||
ownerAddr := ""
|
||||
if rec.Owner != "" {
|
||||
ownerAddr = wallet.PubKeyToAddress(rec.Owner)
|
||||
}
|
||||
jsonOK(w, map[string]any{
|
||||
"nft": rec,
|
||||
"owner_address": ownerAddr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/tokens — list all issued tokens.
|
||||
func apiTokens(q ExplorerQuery) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if q.GetTokens == nil {
|
||||
jsonErr(w, fmt.Errorf("token query not available"), 503)
|
||||
return
|
||||
}
|
||||
tokens, err := q.GetTokens()
|
||||
if err != nil {
|
||||
jsonErr(w, err, 500)
|
||||
return
|
||||
}
|
||||
if tokens == nil {
|
||||
tokens = []blockchain.TokenRecord{}
|
||||
}
|
||||
jsonOK(w, map[string]any{
|
||||
"count": len(tokens),
|
||||
"tokens": tokens,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/tokens/{id} — token metadata
|
||||
// GET /api/tokens/{id}/balance/{pub} — token balance for a public key or DC address
|
||||
func apiTokenByID(q ExplorerQuery) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
path := strings.TrimPrefix(r.URL.Path, "/api/tokens/")
|
||||
parts := strings.SplitN(path, "/", 3)
|
||||
|
||||
tokenID := parts[0]
|
||||
if tokenID == "" {
|
||||
jsonErr(w, fmt.Errorf("token ID required"), 400)
|
||||
return
|
||||
}
|
||||
|
||||
// /api/tokens/{id}/balance/{pub}
|
||||
if len(parts) == 3 && parts[1] == "balance" {
|
||||
pubOrAddr := parts[2]
|
||||
if pubOrAddr == "" {
|
||||
jsonErr(w, fmt.Errorf("pubkey or address required"), 400)
|
||||
return
|
||||
}
|
||||
// Resolve DC address to pubkey if needed.
|
||||
pubKey := pubOrAddr
|
||||
if strings.HasPrefix(pubOrAddr, "DC") && q.AddressToPubKey != nil {
|
||||
if pk, err := q.AddressToPubKey(pubOrAddr); err == nil && pk != "" {
|
||||
pubKey = pk
|
||||
}
|
||||
}
|
||||
if q.TokenBalance == nil {
|
||||
jsonErr(w, fmt.Errorf("token balance query not available"), 503)
|
||||
return
|
||||
}
|
||||
bal, err := q.TokenBalance(tokenID, pubKey)
|
||||
if err != nil {
|
||||
jsonErr(w, err, 500)
|
||||
return
|
||||
}
|
||||
jsonOK(w, map[string]any{
|
||||
"token_id": tokenID,
|
||||
"pub_key": pubKey,
|
||||
"address": wallet.PubKeyToAddress(pubKey),
|
||||
"balance": bal,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// /api/tokens/{id}
|
||||
if q.GetToken == nil {
|
||||
jsonErr(w, fmt.Errorf("token query not available"), 503)
|
||||
return
|
||||
}
|
||||
rec, err := q.GetToken(tokenID)
|
||||
if err != nil {
|
||||
jsonErr(w, err, 500)
|
||||
return
|
||||
}
|
||||
if rec == nil {
|
||||
jsonErr(w, fmt.Errorf("token %s not found", tokenID), 404)
|
||||
return
|
||||
}
|
||||
jsonOK(w, rec)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user