Files
dchain/blockchain/types.go
vsecoder 88848efa63 feat(chain): remove channels, add social feed (Phase A of v2.0.0)
Replaces the channel/membership model with a VK/Twitter-style feed:
public posts, follow graph, likes. Views are deliberately off-chain
(counted by the hosting relay, Phase B).

Removed
  - EventCreateChannel, EventAddMember
  - CreateChannelPayload, AddMemberPayload, ChannelMember
  - prefixChannel, prefixChanMember
  - chain.Channel(), chain.ChannelMembers()
  - node/api_channels.go
  - GetChannel, GetChannelMembers on ExplorerQuery

Added
  - Events: CREATE_POST, DELETE_POST, FOLLOW, UNFOLLOW, LIKE_POST, UNLIKE_POST
  - Payloads: CreatePostPayload, DeletePostPayload, FollowPayload,
    UnfollowPayload, LikePostPayload, UnlikePostPayload
  - Stored shape: PostRecord (author, size, hash, hosting relay, timestamp,
    reply/quote refs, soft-delete flag, fee paid)
  - State prefixes: post:, postbyauthor:, follow:, followin:, like:, likecount:
  - Queries: Post(), PostsByAuthor(), Following(), Followers(),
    LikeCount(), HasLiked()
  - Cached like counter via bumpLikeCount helper

Pricing
  - BasePostFee = 1000 µT (aligned with MinFee block-validation floor)
  - PostByteFee = 1 µT/byte of compressed content
  - Total fee credited in full to HostingRelay pub (storage compensation)
  - MaxPostSize = 256 KiB

Integrity
  - CREATE_POST validates content_hash length (32 B) and size range
  - DELETE_POST restricted to post.Author
  - Duplicate FOLLOW / LIKE rejected
  - reply_to and quote_of mutually exclusive

Tests
  - TestFeedCreatePost: post stored, indexed, host credited
  - TestFeedInsufficientFee: underpaid post is skipped
  - TestFeedFollowUnfollow: follow graph round-trips via forward + inbound indices
  - TestFeedLikeUnlike: like toggles with dedup, counter stays accurate
  - TestFeedDeletePostByOther: non-author deletion rejected

This is Phase A (chain-layer). Phase B adds the relay feed-mailbox
(post bodies + gossipsub) and HTTP endpoints. Phase C adds the client
Feed tab.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 18:36:00 +03:00

581 lines
25 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package blockchain
import (
"crypto/sha256"
"encoding/binary"
"time"
)
// EventType defines what kind of event a transaction represents.
type EventType string
const (
EventRegisterKey EventType = "REGISTER_KEY"
EventOpenPayChan EventType = "OPEN_PAY_CHAN"
EventClosePayChan EventType = "CLOSE_PAY_CHAN"
EventTransfer EventType = "TRANSFER"
EventRelayProof EventType = "RELAY_PROOF"
EventRegisterRelay EventType = "REGISTER_RELAY" // node advertises relay service
EventBindWallet EventType = "BIND_WALLET" // node binds a payout wallet address
EventSlash EventType = "SLASH" // penalise a misbehaving validator
EventHeartbeat EventType = "HEARTBEAT" // liveness ping from a node
EventBlockReward EventType = "BLOCK_REWARD" // synthetic tx indexed on block commit
EventContactRequest EventType = "CONTACT_REQUEST" // paid first-contact request (ICQ-style)
EventAcceptContact EventType = "ACCEPT_CONTACT" // recipient accepts a pending request
EventBlockContact EventType = "BLOCK_CONTACT" // recipient blocks a sender
EventAddValidator EventType = "ADD_VALIDATOR" // existing validator adds a new one
EventRemoveValidator EventType = "REMOVE_VALIDATOR" // existing validator removes one (or self-removal)
EventDeployContract EventType = "DEPLOY_CONTRACT" // deploy a WASM smart contract
EventCallContract EventType = "CALL_CONTRACT" // call a method on a deployed contract
EventStake EventType = "STAKE" // lock tokens as validator stake
EventUnstake EventType = "UNSTAKE" // release staked tokens back to balance
EventIssueToken EventType = "ISSUE_TOKEN" // create a new fungible token
EventTransferToken EventType = "TRANSFER_TOKEN" // transfer fungible tokens between addresses
EventBurnToken EventType = "BURN_TOKEN" // destroy fungible tokens
EventMintNFT EventType = "MINT_NFT" // mint a new non-fungible token
EventTransferNFT EventType = "TRANSFER_NFT" // transfer NFT ownership
EventBurnNFT EventType = "BURN_NFT" // burn (destroy) an NFT
// ── Social feed (v2.0.0) ──────────────────────────────────────────────
// Replaces the old channel model with a VK/Twitter-style timeline.
// Posts are plaintext, publicly readable, size-priced. Bodies live in
// the relay feed-mailbox; on-chain we only keep metadata + author.
EventCreatePost EventType = "CREATE_POST" // author publishes a post
EventDeletePost EventType = "DELETE_POST" // author soft-deletes their post
EventFollow EventType = "FOLLOW" // follow another author's feed
EventUnfollow EventType = "UNFOLLOW" // unfollow an author
EventLikePost EventType = "LIKE_POST" // like a post
EventUnlikePost EventType = "UNLIKE_POST" // remove a previous like
)
// Token amounts are stored in micro-tokens (µT).
// 1 token = 1_000_000 µT
const (
MicroToken uint64 = 1
Token uint64 = 1_000_000
// MinFee is the minimum transaction fee paid to the block validator.
// Validators earn fees as their only income — no block reward minting.
MinFee uint64 = 1_000 // 0.001 T per transaction
// GenesisAllocation is a one-time mint at block 0 for the bootstrap validator.
// All subsequent token supply comes only from re-distribution of existing balances.
GenesisAllocation uint64 = 21_000_000 * Token // 21 million T, fixed supply
// SlashAmount is the penalty deducted from a misbehaving validator's balance.
SlashAmount uint64 = 50 * Token
// RegistrationFee is the one-time fee to register an identity on-chain
// (EventRegisterKey). Paid to the block validator. High enough to deter
// Sybil attacks while remaining affordable.
RegistrationFee uint64 = 1_000_000 // 1 T
// MinContactFee is the minimum amount a sender must pay the recipient when
// submitting an EventContactRequest (anti-spam; goes directly to recipient).
MinContactFee uint64 = 5_000 // 0.005 T
// ── Feed pricing (v2.0.0) ─────────────────────────────────────────────
// A post's on-chain fee is BasePostFee + bytes_on_disk × PostByteFee.
// The fee is paid by the author and credited in full to the hosting
// relay (the node that received POST /feed/publish and stored the body).
// Size-based pricing is what aligns incentives: a 200-byte tweet is
// cheap, a 256 KB video costs ~0.26 T — node operators' storage cost
// is covered.
//
// Note: BasePostFee is set to MinFee (1000 µT) because chain-level block
// validation requires every tx's Fee ≥ MinFee. So the true minimum a
// post can cost is MinFee + size × PostByteFee. A 0-byte post is
// rejected (Size must be > 0), so in practice a ~50-byte text post
// costs ~1050 µT (~$0.001 depending on token price).
BasePostFee uint64 = 1_000 // 0.001 T flat per post — aligned with MinFee floor
PostByteFee uint64 = 1 // 1 µT per byte of stored content
// MaxPostSize caps a single post's on-wire size (text + attachment, post
// compression). Hard limit — node refuses larger envelopes to protect
// storage and bandwidth.
MaxPostSize uint64 = 256 * 1024 // 256 KiB
// LikeFee / FollowFee / UnlikeFee / UnfollowFee / DeletePostFee all use
// MinFee (1000 µT) — standard tx fee paid to the validator. No extra
// cost; these events carry no body.
)
// Transaction is the atomic unit recorded in a block.
// Bodies of messages are NEVER stored here — only identity/channel events.
type Transaction struct {
ID string `json:"id"`
Type EventType `json:"type"`
From string `json:"from"` // hex-encoded Ed25519 public key
To string `json:"to"` // hex-encoded Ed25519 public key (if applicable)
Amount uint64 `json:"amount"` // µT to transfer (for TRANSFER type)
Fee uint64 `json:"fee"` // µT paid to the block validator
Memo string `json:"memo,omitempty"`
Payload []byte `json:"payload"` // JSON-encoded event-specific data
Signature []byte `json:"signature"` // Ed25519 sig over canonical bytes
Timestamp time.Time `json:"timestamp"`
}
// RegisterKeyPayload is embedded in EventRegisterKey transactions.
type RegisterKeyPayload struct {
PubKey string `json:"pub_key"` // hex-encoded Ed25519 public key
Nickname string `json:"nickname"` // human-readable, non-unique
PowNonce uint64 `json:"pow_nonce"` // proof-of-work nonce (Sybil barrier)
PowTarget string `json:"pow_target"`
X25519PubKey string `json:"x25519_pub_key,omitempty"` // hex Curve25519 key for E2E messaging
}
// ── Feed payloads (v2.0.0) ─────────────────────────────────────────────────
// CreatePostPayload is embedded in EventCreatePost transactions. The body
// itself is NOT stored on-chain — it lives in the relay feed-mailbox keyed
// by PostID. On-chain we only keep author, size, hash, timestamp and any
// reply/quote reference for ordering and proof of authorship.
//
// PostID is computed client-side as hex(sha256(author || content_hash || ts)[:16])
// — same scheme as envelope IDs. Clients include it so the relay can store
// the body under a stable key before the chain commit lands.
//
// HostingRelay is the node pubkey (Ed25519 hex) that accepted the POST
// /feed/publish call and holds the body. Readers resolve it via the chain
// and fetch the body directly from that relay (or via gossipsub replicas).
// The fee is credited to this pub.
//
// QuoteOf / ReplyTo are mutually exclusive; set at most one. ReplyTo makes
// the post a reply in a thread; QuoteOf creates a link/reference block.
type CreatePostPayload struct {
PostID string `json:"post_id"`
ContentHash []byte `json:"content_hash"` // sha256 of body-bytes, 32 B
Size uint64 `json:"size"` // bytes on disk (compressed)
HostingRelay string `json:"hosting_relay"` // hex Ed25519 of storing node
ReplyTo string `json:"reply_to,omitempty"` // parent post ID
QuoteOf string `json:"quote_of,omitempty"` // referenced post ID
}
// DeletePostPayload — author soft-deletes their own post. Stored marker
// lets clients hide the post; relay can GC the body on the next sweep.
type DeletePostPayload struct {
PostID string `json:"post_id"`
}
// FollowPayload / UnfollowPayload — follow graph. tx.From = follower,
// tx.To = target. No body.
type FollowPayload struct{}
type UnfollowPayload struct{}
// LikePostPayload / UnlikePostPayload — per-post like indicator. tx.From
// = liker. The counter is derived on read.
type LikePostPayload struct {
PostID string `json:"post_id"`
}
type UnlikePostPayload struct {
PostID string `json:"post_id"`
}
// PostRecord is what we store on-chain under post:<postID>. Consumers of
// PostsByAuthor / query endpoints decode this.
type PostRecord struct {
PostID string `json:"post_id"`
Author string `json:"author"` // hex Ed25519
ContentHash []byte `json:"content_hash"`
Size uint64 `json:"size"`
HostingRelay string `json:"hosting_relay"`
ReplyTo string `json:"reply_to,omitempty"`
QuoteOf string `json:"quote_of,omitempty"`
CreatedAt int64 `json:"created_at"` // unix seconds (tx timestamp)
Deleted bool `json:"deleted,omitempty"`
FeeUT uint64 `json:"fee_ut"` // total fee paid
}
// RegisterRelayPayload is embedded in EventRegisterRelay transactions.
// A node publishes this to advertise itself as a relay service provider.
// Clients look up relay nodes via GET /api/relays.
type RegisterRelayPayload struct {
// X25519PubKey is the hex-encoded Curve25519 public key for NaCl envelope encryption.
// Senders use this key to seal messages addressed to this relay node.
X25519PubKey string `json:"x25519_pub_key"`
// FeePerMsgUT is the relay fee the node charges per delivered envelope (in µT).
FeePerMsgUT uint64 `json:"fee_per_msg_ut"`
// Multiaddr is the optional libp2p multiaddr string for direct connections.
Multiaddr string `json:"multiaddr,omitempty"`
}
// RelayProofPayload proves that a relay/recipient node received an envelope.
// The sender pre-authorises the fee by signing FeeAuthBytes(EnvelopeID, FeeUT).
// On-chain the fee is pulled from the sender's balance and credited to the relay.
type RelayProofPayload struct {
// EnvelopeID is the stable identifier of the delivered envelope (hex).
EnvelopeID string `json:"envelope_id"`
// EnvelopeHash is SHA-256(nonce || ciphertext) — prevents double-claiming.
EnvelopeHash []byte `json:"envelope_hash"`
// SenderPubKey is the Ed25519 public key of the envelope sender (hex).
SenderPubKey string `json:"sender_pub_key"`
// FeeUT is the delivery fee the relay claims from the sender's balance.
FeeUT uint64 `json:"fee_ut"`
// FeeSig is the sender's Ed25519 signature over FeeAuthBytes(EnvelopeID, FeeUT).
// This authorises the relay to pull FeeUT from the sender's on-chain balance.
FeeSig []byte `json:"fee_sig"`
// RelayPubKey is the Ed25519 public key of the relay claiming the fee (hex).
RelayPubKey string `json:"relay_pub_key"`
// DeliveredAt is the unix timestamp of delivery.
DeliveredAt int64 `json:"delivered_at"`
// RecipientSig is the recipient's optional Ed25519 sig over EnvelopeHash,
// proving the message was successfully decrypted (not required for fee claim).
RecipientSig []byte `json:"recipient_sig,omitempty"`
}
// FeeAuthBytes returns the canonical byte string that the sender must sign
// to pre-authorise a relay fee pull. The relay includes this signature in
// RelayProofPayload.FeeSig when submitting the proof on-chain.
//
// Format: SHA-256("relay-fee:" || envelopeID || uint64BE(feeUT))
func FeeAuthBytes(envelopeID string, feeUT uint64) []byte {
h := sha256.New()
h.Write([]byte("relay-fee:"))
h.Write([]byte(envelopeID))
var b [8]byte
binary.BigEndian.PutUint64(b[:], feeUT)
h.Write(b[:])
return h.Sum(nil)
}
// TransferPayload carries an optional memo for token transfers.
type TransferPayload struct {
Memo string `json:"memo,omitempty"`
}
// BindWalletPayload links a node's signing key to a separate payout wallet.
// After this tx is committed, block fees and relay fees are credited to
// WalletPubKey instead of the node's own pub key.
type BindWalletPayload struct {
WalletPubKey string `json:"wallet_pub_key"`
WalletAddr string `json:"wallet_addr"`
}
// SlashPayload is submitted by a validator to penalise a misbehaving peer.
type SlashPayload struct {
OffenderPubKey string `json:"offender_pub_key"`
Reason string `json:"reason"` // "double_vote" | "downtime" | "equivocation"
Evidence []byte `json:"evidence,omitempty"`
}
// HeartbeatPayload is a periodic liveness signal published by active nodes.
// It carries the node's current chain height so peers can detect lagging nodes.
// Heartbeats cost MinFee (paid to the block validator) and earn no reward —
// they exist to build reputation and prove liveness.
type HeartbeatPayload struct {
PubKey string `json:"pub_key"`
ChainHeight uint64 `json:"chain_height"`
PeerCount int `json:"peer_count"`
Version string `json:"version"`
}
// OpenPayChanPayload locks deposits from two parties into a payment channel.
type OpenPayChanPayload struct {
ChannelID string `json:"channel_id"`
PartyA string `json:"party_a"`
PartyB string `json:"party_b"`
DepositA uint64 `json:"deposit_a_ut"`
DepositB uint64 `json:"deposit_b_ut"`
ExpiryBlock uint64 `json:"expiry_block"`
SigB []byte `json:"sig_b"` // PartyB's Ed25519 sig over channel params
}
// ClosePayChanPayload settles a payment channel and distributes balances.
type ClosePayChanPayload struct {
ChannelID string `json:"channel_id"`
BalanceA uint64 `json:"balance_a_ut"`
BalanceB uint64 `json:"balance_b_ut"`
Nonce uint64 `json:"nonce"`
SigA []byte `json:"sig_a"`
SigB []byte `json:"sig_b"`
}
// PayChanState is stored on-chain for each open payment channel.
type PayChanState struct {
ChannelID string `json:"channel_id"`
PartyA string `json:"party_a"`
PartyB string `json:"party_b"`
DepositA uint64 `json:"deposit_a_ut"`
DepositB uint64 `json:"deposit_b_ut"`
ExpiryBlock uint64 `json:"expiry_block"`
OpenedBlock uint64 `json:"opened_block"`
Nonce uint64 `json:"nonce"`
Closed bool `json:"closed"`
}
// BlockRewardPayload is attached to synthetic BLOCK_REWARD transactions.
// These are index-only records so the explorer can show validator fee income.
// There is no minting — the FeeReward comes from existing transaction fees.
type BlockRewardPayload struct {
ValidatorPubKey string `json:"validator_pub_key"`
TargetPubKey string `json:"target_pub_key"`
FeeReward uint64 `json:"fee_reward_ut"`
TotalReward uint64 `json:"total_reward_ut"`
}
// ContactRequestPayload is embedded in EventContactRequest transactions.
// The sender pays tx.Amount directly to the recipient (anti-spam fee).
// A pending contact record is stored on-chain for the recipient to accept or block.
type ContactRequestPayload struct {
Intro string `json:"intro,omitempty"` // optional plaintext intro (≤ 280 chars)
}
// AcceptContactPayload is embedded in EventAcceptContact transactions.
// tx.From accepts a pending request from tx.To.
type AcceptContactPayload struct{}
// BlockContactPayload is embedded in EventBlockContact transactions.
// tx.From blocks tx.To; future contact requests from tx.To are rejected.
type BlockContactPayload struct {
Reason string `json:"reason,omitempty"`
}
// AddValidatorPayload is embedded in EventAddValidator transactions.
// tx.From must already be a validator; tx.To is the new validator's pub key.
//
// Admission is gated by two things:
// 1. Stake: the candidate (tx.To) must have STAKE'd at least
// MinValidatorStake beforehand. Prevents anyone spinning up a free
// validator without economic buy-in.
// 2. Multi-sig: at least ⌈2/3⌉ of the CURRENT validator set must approve.
// The tx sender counts as one; remaining approvals go in CoSignatures.
// For a 1-validator chain (fresh genesis / tests) sender alone is 2/3,
// so CoSignatures can be empty — backward-compat is preserved.
type AddValidatorPayload struct {
Reason string `json:"reason,omitempty"`
CoSignatures []ValidatorCoSig `json:"cosigs,omitempty"`
}
// ValidatorCoSig is an off-chain-assembled approval from one existing
// validator for a specific candidate admission. The signature is over the
// canonical digest returned by AdmitDigest(candidatePubKeyHex).
type ValidatorCoSig struct {
PubKey string `json:"pubkey"` // Ed25519 hex of a current validator
Signature []byte `json:"signature"` // Ed25519 signature over AdmitDigest(candidate)
}
// AdmitDigest returns the canonical bytes a validator signs to approve
// admitting `candidatePubHex` as a new validator. Stable across implementations
// so co-sigs collected off-chain verify identically on-chain.
func AdmitDigest(candidatePubHex string) []byte {
h := sha256.New()
h.Write([]byte("DCHAIN-ADD-VALIDATOR\x00"))
h.Write([]byte(candidatePubHex))
return h.Sum(nil)
}
// MinValidatorStake is the minimum µT a candidate must have locked in
// `stake:<pubkey>` before an ADD_VALIDATOR naming them is accepted.
// 1 T = 1_000_000 µT — small enough that testnets can afford it easily,
// large enough to deter "register 100 fake validators to 51%-attack".
const MinValidatorStake uint64 = 1_000_000
// RemoveValidatorPayload is embedded in EventRemoveValidator transactions.
// tx.From must be a validator; tx.To is the validator to remove.
//
// Two legitimate use cases:
// 1. Self-removal (tx.From == tx.To): always allowed, no cosigs needed.
// Lets a validator gracefully leave the set without requiring others.
// 2. Forced removal (tx.From != tx.To): requires ⌈2/3⌉ cosigs of the
// current validator set — same pattern as ADD_VALIDATOR. Stops a
// single validator from unilaterally kicking peers.
//
// The signed payload is AdmitDigest(tx.To) but with the domain byte flipped
// — see RemoveDigest below. This prevents a cosig collected for "admit X"
// from being replayed as "remove X".
type RemoveValidatorPayload struct {
Reason string `json:"reason,omitempty"`
CoSignatures []ValidatorCoSig `json:"cosigs,omitempty"`
}
// RemoveDigest is the canonical bytes a validator signs to approve removing
// `targetPubHex` from the set. Distinct from AdmitDigest so signatures
// can't be cross-replayed between add and remove operations.
func RemoveDigest(targetPubHex string) []byte {
h := sha256.New()
h.Write([]byte("DCHAIN-REMOVE-VALIDATOR\x00"))
h.Write([]byte(targetPubHex))
return h.Sum(nil)
}
// DeployContractPayload is embedded in EventDeployContract transactions.
// WASMBase64 is the base64-encoded WASM binary. It is stored in the tx so that
// nodes can replay the chain from genesis and re-derive contract state.
type DeployContractPayload struct {
WASMBase64 string `json:"wasm_b64"`
ABIJson string `json:"abi_json"`
InitArgs string `json:"init_args_json,omitempty"`
}
// CallContractPayload is embedded in EventCallContract transactions.
type CallContractPayload struct {
ContractID string `json:"contract_id"`
Method string `json:"method"`
ArgsJSON string `json:"args_json,omitempty"`
GasLimit uint64 `json:"gas_limit"`
}
// ContractRecord is stored in BadgerDB at contract:<contractID>.
// WASMBytes is NOT in the block; it is derived from the deploy tx payload on replay.
type ContractRecord struct {
ContractID string `json:"contract_id"`
WASMBytes []byte `json:"wasm_bytes"`
ABIJson string `json:"abi_json"`
DeployerPub string `json:"deployer_pub"`
DeployedAt uint64 `json:"deployed_at"` // block height
}
// MinDeployFee is the minimum fee for a DEPLOY_CONTRACT transaction.
// Covers storage costs for the WASM binary.
const MinDeployFee uint64 = 10_000 // 0.01 T
// MinCallFee is the minimum base fee for a CALL_CONTRACT transaction.
// Gas costs are billed on top of this.
const MinCallFee uint64 = MinFee
// ContractLogEntry is one log message emitted by a contract via env.log().
// Stored in BadgerDB at clog:<contractID>:<blockHeight_20d>:<seq_05d>.
type ContractLogEntry struct {
ContractID string `json:"contract_id"`
BlockHeight uint64 `json:"block_height"`
TxID string `json:"tx_id"`
Seq int `json:"seq"`
Message string `json:"message"`
}
// GasPrice is the cost in µT per 1 gas unit consumed during contract execution.
const GasPrice uint64 = 1 // 1 µT per gas unit
// MinStake is the minimum amount a validator must stake.
const MinStake uint64 = 1_000 * Token // 1000 T
// MinIssueTokenFee is the fee required to issue a new token.
const MinIssueTokenFee uint64 = 100_000 // 0.1 T
// StakePayload is embedded in EventStake transactions.
// tx.Amount holds the amount to stake; tx.Fee is the transaction fee.
type StakePayload struct{}
// UnstakePayload is embedded in EventUnstake transactions.
// The entire current stake is returned to the staker's balance.
type UnstakePayload struct{}
// IssueTokenPayload is embedded in EventIssueToken transactions.
// The new token is credited to tx.From with TotalSupply units.
type IssueTokenPayload struct {
Name string `json:"name"` // human-readable token name, e.g. "My Token"
Symbol string `json:"symbol"` // ticker symbol, e.g. "MTK"
Decimals uint8 `json:"decimals"` // decimal places, e.g. 6 → 1 token = 1_000_000 base units
TotalSupply uint64 `json:"total_supply"` // initial supply in base units
}
// TransferTokenPayload is embedded in EventTransferToken transactions.
// tx.To is the recipient; tx.Amount is ignored (use payload Amount).
type TransferTokenPayload struct {
TokenID string `json:"token_id"`
Amount uint64 `json:"amount"` // in base units
}
// BurnTokenPayload is embedded in EventBurnToken transactions.
type BurnTokenPayload struct {
TokenID string `json:"token_id"`
Amount uint64 `json:"amount"` // in base units
}
// TokenRecord is stored in BadgerDB at token:<tokenID>.
type TokenRecord struct {
TokenID string `json:"token_id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals uint8 `json:"decimals"`
TotalSupply uint64 `json:"total_supply"` // current (may decrease via burns)
Issuer string `json:"issuer"` // creator pubkey
IssuedAt uint64 `json:"issued_at"` // block height
}
// MinMintNFTFee is the fee required to mint a new NFT.
const MinMintNFTFee uint64 = 10_000 // 0.01 T
// MintNFTPayload is embedded in EventMintNFT transactions.
type MintNFTPayload struct {
Name string `json:"name"` // human-readable name
Description string `json:"description,omitempty"`
URI string `json:"uri,omitempty"` // off-chain metadata URI (IPFS, https, etc.)
Attributes string `json:"attributes,omitempty"` // JSON string of trait attributes
}
// TransferNFTPayload is embedded in EventTransferNFT transactions.
// tx.To is the new owner; tx.From must be current owner.
type TransferNFTPayload struct {
NFTID string `json:"nft_id"`
}
// BurnNFTPayload is embedded in EventBurnNFT transactions.
type BurnNFTPayload struct {
NFTID string `json:"nft_id"`
}
// NFTRecord is stored in BadgerDB at nft:<nftID>.
type NFTRecord struct {
NFTID string `json:"nft_id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
URI string `json:"uri,omitempty"`
Attributes string `json:"attributes,omitempty"`
Owner string `json:"owner"` // current owner pubkey
Issuer string `json:"issuer"` // original minter pubkey
MintedAt uint64 `json:"minted_at"` // block height
Burned bool `json:"burned,omitempty"`
}
// ContactStatus is the state of a contact relationship.
type ContactStatus string
const (
ContactPending ContactStatus = "pending"
ContactAccepted ContactStatus = "accepted"
ContactBlocked ContactStatus = "blocked"
)
// ContactInfo is returned by the contacts API.
type ContactInfo struct {
RequesterPub string `json:"requester_pub"`
RequesterAddr string `json:"requester_addr"`
Status ContactStatus `json:"status"`
Intro string `json:"intro,omitempty"`
FeeUT uint64 `json:"fee_ut"`
TxID string `json:"tx_id"`
CreatedAt int64 `json:"created_at"`
}
// IdentityInfo is returned by GET /api/identity/{pubkey}.
type IdentityInfo struct {
PubKey string `json:"pub_key"`
Address string `json:"address"`
X25519Pub string `json:"x25519_pub"` // hex Curve25519 key; empty if not published
Nickname string `json:"nickname"`
Registered bool `json:"registered"` // true if REGISTER_KEY tx was committed
}
// ConsensusMessage types used by the PBFT engine over the P2P layer.
type MsgType string
const (
MsgPrePrepare MsgType = "PRE_PREPARE"
MsgPrepare MsgType = "PREPARE"
MsgCommit MsgType = "COMMIT"
MsgViewChange MsgType = "VIEW_CHANGE"
MsgNewView MsgType = "NEW_VIEW"
)
// ConsensusMsg is the envelope sent between validators.
type ConsensusMsg struct {
Type MsgType `json:"type"`
View uint64 `json:"view"`
SeqNum uint64 `json:"seq_num"`
BlockHash []byte `json:"block_hash"`
Block *Block `json:"block,omitempty"`
From string `json:"from"`
Signature []byte `json:"signature"`
}