From 37862083c42c97ba6523cd8169a25148563ff379695fd71d31da8095824c562f Mon Sep 17 00:00:00 2001 From: Mukan Erkin Date: Fri, 24 Apr 2026 00:00:26 +0300 Subject: [PATCH] feat(nu-proto): initial Faz 0 scaffold Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 2 + CLAUDE.md | 39 +++++++++++++++ rpc/api.proto | 89 ++++++++++++++++++++++++++++++++++ types/block.proto | 29 +++++++++++ types/nft.proto | 20 ++++++++ types/story_node.proto | 39 +++++++++++++++ types/transaction.proto | 103 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 321 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 rpc/api.proto create mode 100644 types/block.proto create mode 100644 types/nft.proto create mode 100644 types/story_node.proto create mode 100644 types/transaction.proto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa6ae35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +*.rs.bk diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bc2df08 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,39 @@ +# nu-proto — CLAUDE.md + +Tüm ekosistemin canonical tip ve RPC API tanımları. **Değişince diğer repolar kırılır.** + +## Kritik Kural + +`v0.1` tag'i atıldıktan sonra field silemezsin, field tipi değiştiremezsin. Yeni field eklemek non-breaking — sıra numarası bir kez atandıktan sonra değişmez. + +```protobuf +// BREAKING: field silindi/tipi değişti — bunu yapma v0.1 sonrası +// Yeni field ekle, eski field'ı deprecated bırak +``` + +## Dosya Yapısı + +``` +types/ + transaction.proto ← TxPayload enum; tüm sistemi kilitler + block.proto ← BlockHeader, Block, BlockReceipt + story_node.proto ← StoryNode, Story, NodeStatus + nft.proto ← Nft, Collection +rpc/ + api.proto ← Platform ↔ Node JSON-RPC sözleşmesi +``` + +## Derleme + +```bash +# Rust crate üret +protoc --rust_out=src/ types/*.proto rpc/*.proto + +# TypeScript (platform için) +protoc --ts_out=../nu-platform/src/proto/ types/*.proto rpc/*.proto +``` + +## Versiyon + +Şu an: **v0.1-dev** (henüz dondurulmadı) +v0.1 lock sonrası paralel geliştirme başlar. diff --git a/rpc/api.proto b/rpc/api.proto new file mode 100644 index 0000000..4beb2b9 --- /dev/null +++ b/rpc/api.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; + +package nu.rpc; + +import "types/block.proto"; +import "types/transaction.proto"; +import "types/story_node.proto"; +import "types/nft.proto"; + +// JSON-RPC v2 method catalog — v0.1 +// All methods over HTTP POST /rpc and WS /ws + +// ── Queries ────────────────────────────────────────────────────────────────── + +message GetBlockRequest { uint64 height = 1; } +message GetBlockResponse { nu.types.Block block = 1; } + +message GetTxRequest { string tx_id = 1; } +message GetTxResponse { + nu.types.Transaction tx = 1; + nu.types.BlockReceipt receipt = 2; +} + +message GetAccountRequest { string address = 1; } +message GetAccountResponse { + string address = 1; + uint64 balance = 2; // NUT micro-units + uint64 staked = 3; + uint64 locked = 4; + uint64 locked_until = 5; // Unix epoch ms + string pon_score = 6; // decimal string + repeated string nft_ids = 7; +} + +message GetStoryRequest { string story_id = 1; } +message GetStoryResponse { + nu.types.Story story = 1; + repeated nu.types.StoryNode nodes = 2; +} + +message GetNodeRequest { string node_id = 1; } // canonical or temp_id +message GetNodeResponse { nu.types.StoryNode node = 1; } + +message GetNftRequest { string nft_id = 1; } +message GetNftResponse { nu.types.Nft nft = 1; } + +message ListStoriesRequest { + uint32 page = 1; + uint32 per_page = 2; +} +message ListStoriesResponse { + repeated nu.types.Story stories = 1; + uint32 total = 2; +} + +message ListPendingVotesRequest { string voter = 1; } // Address; empty = all +message ListPendingVotesResponse { repeated nu.types.StoryNode nodes = 1; } + +// ── Mutations ──────────────────────────────────────────────────────────────── + +message SendRawTxRequest { bytes raw_tx = 1; } // protobuf-encoded signed Transaction +message SendRawTxResponse { + string tx_id = 1; + bool queued = 2; + string error = 3; +} + +// ── Chain Info ─────────────────────────────────────────────────────────────── + +message ChainInfoRequest {} +message ChainInfoResponse { + uint64 latest_height = 1; + string latest_hash = 2; + uint32 validator_count = 3; + string chain_id = 4; // e.g. "nu-devnet-1" +} + +// ── WebSocket Subscriptions (event stream) ─────────────────────────────────── + +message SubscribeRequest { + repeated string topics = 1; + // topics: "blocks", "node:", "vote:", "nft:
" +} + +message Event { + string topic = 1; + string kind = 2; // "new_block" | "node_approved" | "vote_cast" | "nft_minted" + bytes payload = 3; // protobuf-encoded type depending on kind +} diff --git a/types/block.proto b/types/block.proto new file mode 100644 index 0000000..a12fd47 --- /dev/null +++ b/types/block.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package nu.types; + +import "types/transaction.proto"; + +message BlockHeader { + uint64 height = 1; + string prev_block_hash = 2; // Hash256 + int64 timestamp = 3; // Unix epoch milliseconds + string validator_addr = 4; // Address + bytes validator_sig = 5; // secp256k1 + string tx_root = 6; // Merkle root of transactions + string state_root = 7; // Merkle Patricia Trie root + string receipts_root = 8; + uint32 slot = 9; +} + +message Block { + BlockHeader header = 1; + repeated Transaction transactions = 2; +} + +message BlockReceipt { + string tx_id = 1; + bool success = 2; + string error = 3; // empty if success + uint64 gas_used = 4; +} diff --git a/types/nft.proto b/types/nft.proto new file mode 100644 index 0000000..f22aba6 --- /dev/null +++ b/types/nft.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package nu.types; + +message Nft { + string nft_id = 1; // same as canonical node_id + string node_id = 2; + string owner = 3; // Address + string collection_id = 4; // empty until CollectionClaim + uint32 depth = 5; // path length from root + repeated string lineage = 6; // ancestor node_ids root → this + int64 minted_at = 7; +} + +message Collection { + string collection_id = 1; // leaf node_id of the path + string owner = 2; // Address + repeated string nft_ids = 3; // ordered root → leaf + int64 claimed_at = 4; +} diff --git a/types/story_node.proto b/types/story_node.proto new file mode 100644 index 0000000..35da841 --- /dev/null +++ b/types/story_node.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package nu.types; + +enum NodeStatus { + PENDING = 0; + VOTING_OPEN = 1; + APPROVED = 2; + REJECTED = 3; +} + +message WeightedVote { + string voter = 1; // Address + bool approve = 2; + string weight = 3; // decimal string; computed at finalization +} + +message StoryNode { + string node_id = 1; // canonical; empty until approved + string temp_id = 2; // client UUID during pending/voting + string story_id = 3; + string parent_id = 4; + string author = 5; // Address + string content_hash = 6; // IpfsHash + NodeStatus status = 7; + int64 submitted_at = 8; // Unix epoch ms + int64 vote_open_at = 9; // day 7 + int64 vote_end_at = 10; // day 10 + repeated WeightedVote votes = 11; + string nft_id = 12; // set on approval +} + +message Story { + string story_id = 1; + string root_node_id = 2; // canonical + string creator = 3; // Address + int64 created_at = 4; + bool is_genesis = 5; // genesis dev log — no voting, no entry fee +} diff --git a/types/transaction.proto b/types/transaction.proto new file mode 100644 index 0000000..74eac8d --- /dev/null +++ b/types/transaction.proto @@ -0,0 +1,103 @@ +syntax = "proto3"; + +package nu.types; + +option java_package = "tr.cw.narrativeunion.types"; +option java_outer_classname = "TransactionProto"; + +// Hash256: 32-byte SHA-256 digest, hex-encoded +// Address: 20-byte account address, hex-encoded +// IpfsHash: CIDv1 string + +message Transaction { + string tx_id = 1; // SHA-256(payload bytes) + string sender = 2; // Address + uint64 nonce = 3; + uint64 fee = 4; // NUT micro-units (1 NUT = 1_000_000 units) + bytes signature = 5; // secp256k1 compact (64 bytes) + TxPayload payload = 6; +} + +message TxPayload { + oneof kind { + TokenTransfer token_transfer = 1; + NodeSubmit node_submit = 2; + VoteRegister vote_register = 3; + VoteCast vote_cast = 4; + NftTransfer nft_transfer = 5; + CollectionClaim collection_claim = 6; + StakeOp stake_op = 7; + ValidatorRegister validator_register = 8; + // Auto-generated by validator — never submitted by users directly: + NodeApprove node_approve = 9; + NftMint nft_mint = 10; + NodeReject node_reject = 11; + VotingOpen voting_open = 12; + } +} + +message TokenTransfer { + string to = 1; // Address + uint64 amount = 2; +} + +message NodeSubmit { + string story_id = 1; + string parent_node_id = 2; // empty if root node + string content_hash = 3; // IpfsHash + uint64 entry_fee = 4; // must equal reward * 0.25 + string temp_id = 5; // client-generated UUID; replaced by canonical NodeId on approval +} + +message VoteRegister { + string node_id = 1; // temp_id at this stage + uint64 stake_lock = 2; // must equal node_reward * 0.10; locked 10 days +} + +message VoteCast { + string node_id = 1; + bool approve = 2; // true = approve, false = reject +} + +message NftTransfer { + string nft_id = 1; + string to = 2; // Address +} + +message CollectionClaim { + repeated string nft_ids = 1; // must form a valid lineage path from root +} + +message StakeOp { + enum Kind { + STAKE = 0; + UNSTAKE = 1; + } + Kind op = 1; + uint64 amount = 2; +} + +message ValidatorRegister { + uint64 stake = 1; // minimum 1_000_000_000 units (1000 NUT) +} + +// --- Auto-generated scheduler/validator transactions --- + +message NodeApprove { + string node_id = 1; // canonical NodeId assigned at approval + string temp_id = 2; // maps back to NodeSubmit.temp_id + string canonical_id = 3; // numeric path-encoded id e.g. "1159" +} + +message NftMint { + string node_id = 1; // canonical + string recipient = 2; // Address — node author +} + +message NodeReject { + string temp_id = 1; +} + +message VotingOpen { + string node_id = 1; // temp_id; triggered at day 7 +}