# hive-rs Technical Specification
**A Rust client library for the Hive blockchain with 1:1 dhive API parity**
Crate name: `hive-rs`
License: BSD-3-Clause (matching dhive)
MSRV: 1.75+ (for async trait stabilization)
Repository: TBD
---
## 1. Goals
Build a native Rust crate that mirrors the full public API surface of `@hiveio/dhive` (v1.3.2). Any developer familiar with dhive should be able to pick up hive-rs and know exactly where everything is. The module structure, method names, and type names should map 1:1 wherever Rust idioms allow.
Where dhive returns Promises, hive-rs returns `impl Future`. Where dhive uses classes, hive-rs uses structs with impl blocks. Where dhive uses TypeScript interfaces, hive-rs uses structs with serde derives. Where dhive uses Node.js streams, hive-rs uses `tokio::sync::mpsc` channels or `Stream` traits.
No WASM target on v1.0. Pure Rust. WASM can come later via `wasm-bindgen` feature flag.
---
## 2. Crate Architecture
```
hive-rs/
Cargo.toml
src/
lib.rs # Re-exports everything. Entry point.
client.rs # Client struct (equivalent to dhive Client class)
# Core API modules (mirror dhive's class structure)
api/
mod.rs
database.rs # DatabaseAPI - condenser_api + database_api queries
broadcast.rs # BroadcastAPI - transaction signing and broadcasting
blockchain.rs # Blockchain - block streaming, iterators
hivemind.rs # HivemindAPI - bridge API (communities, notifications, ranked posts)
rc.rs # RCAPI - resource credit queries
account_by_key.rs # AccountByKeyAPI - key-to-account lookups
transaction_status.rs # TransactionStatusAPI - tx status polling
# Cryptography
crypto/
mod.rs
keys.rs # PrivateKey, PublicKey structs
signature.rs # Signature struct, canonical signature enforcement
memo.rs # Memo encryption/decryption (ECIES with AES-256-CBC)
utils.rs # sha256, ripemd160, doubleSha256, WIF encode/decode
# Serialization (binary protocol for transaction signing)
serialization/
mod.rs
serializer.rs # Binary serializer matching Hive's C++ fc::raw::pack
deserializer.rs # Binary deserializer
types.rs # Primitive type serializers (varint, fixed int, string, etc.)
# Type definitions (1:1 with dhive interfaces)
types/
mod.rs
account.rs # Account, ExtendedAccount
asset.rs # Asset struct (HIVE, HBD, VESTS parsing)
authority.rs # Authority struct (weight_threshold, account_auths, key_auths)
block.rs # SignedBlock, SignedBlockHeader, BlockHeader
chain.rs # DynamicGlobalProperties, ChainProperties
comment.rs # Comment, Discussion
operation.rs # All 50+ operation types as enum variants
price.rs # Price struct (base/quote)
rc.rs # RCAccount, RCParams, RCPool, Manabar
transaction.rs # Transaction, SignedTransaction, TransactionConfirmation
misc.rs # VestingDelegation, WitnessProps, etc.
# Transport
transport/
mod.rs
http.rs # HTTP/2 JSON-RPC transport (reqwest)
failover.rs # Multi-node failover with backoff
# Utilities
utils/
mod.rs
asset_helpers.rs # getVestingSharePrice, getVests
iterators.rs # Block/operation async iterators
nonce.rs # uniqueNonce generation
```
---
## 3. Client
The `Client` is the root entry point, identical to dhive's `Client` class.
### dhive API
```typescript
const client = new Client(["https://api.hive.blog", "https://api.openhive.network"], {
timeout: 10000,
failoverThreshold: 3,
consoleOnFailover: false,
agent: customAgent,
addressPrefix: "STM",
chainId: "beeab0de00000000000000000000000000000000000000000000000000000000",
rebrandedApi: true
});
client.database // DatabaseAPI
client.broadcast // BroadcastAPI
client.blockchain // Blockchain
client.hivemind // HivemindAPI
client.rc // RCAPI
client.keys // AccountByKeyAPI
client.transaction // TransactionStatusAPI
client.call(api, method, params) // raw JSON-RPC
```
### Rust equivalent
```rust
use hive_rs::{Client, ClientOptions};
let client = Client::new(
vec!["https://api.hive.blog", "https://api.openhive.network"],
ClientOptions {
timeout: Duration::from_secs(10),
failover_threshold: 3,
address_prefix: "STM".to_string(),
chain_id: ChainId::mainnet(),
..Default::default()
},
);
// Sub-APIs accessed as fields
client.database // &DatabaseApi
client.broadcast // &BroadcastApi
client.blockchain // &Blockchain
client.hivemind // &HivemindApi
client.rc // &RcApi
client.keys // &AccountByKeyApi
client.transaction // &TransactionStatusApi
// Raw JSON-RPC escape hatch
client.call::<T>("condenser_api", "get_accounts", json!(["beggars"])).await?;
```
### ClientOptions struct
```rust
pub struct ClientOptions {
pub timeout: Duration, // default 10s
pub failover_threshold: u32, // default 3
pub address_prefix: String, // default "STM"
pub chain_id: ChainId, // default mainnet
pub backoff: BackoffStrategy, // default exponential
}
```
### Transport and Failover
The transport layer wraps `reqwest::Client` with automatic node failover.
Failover rules (matching dhive behavior):
- Track consecutive failures per node
- When failures >= `failover_threshold`, move to next node in the list
- Cycle through all nodes before retrying from the beginning
- Configurable backoff strategy (default: exponential with jitter)
- Timeout errors, connection refused, and HTTP 5xx all count as failures
All JSON-RPC calls go through the same transport and get automatic failover for free.
---
## 4. DatabaseAPI
Maps to dhive's `DatabaseAPI` class. Wraps `condenser_api.*` and `database_api.*` RPC methods.
### Full method list (1:1 with dhive)
```rust
impl DatabaseApi {
// Account methods
pub async fn get_accounts(&self, accounts: &[&str]) -> Result<Vec<ExtendedAccount>>;
pub async fn get_account_count(&self) -> Result<u64>;
pub async fn get_account_history(&self, account: &str, from: i64, limit: u32) -> Result<Vec<AccountHistoryEntry>>;
pub async fn get_account_reputations(&self, lower_bound: &str, limit: u32) -> Result<Vec<AccountReputation>>;
pub async fn get_owner_history(&self, account: &str) -> Result<Vec<OwnerHistory>>;
pub async fn get_recovery_request(&self, account: &str) -> Result<Option<RecoveryRequest>>;
// Content methods
pub async fn get_content(&self, author: &str, permlink: &str) -> Result<Discussion>;
pub async fn get_content_replies(&self, author: &str, permlink: &str) -> Result<Vec<Discussion>>;
pub async fn get_discussions(&self, by: DiscussionQueryCategory, query: &DisqussionQuery) -> Result<Vec<Discussion>>;
pub async fn get_discussions_by_author_before_date(
&self, author: &str, start_permlink: &str, before_date: &str, limit: u32
) -> Result<Vec<Discussion>>;
pub async fn get_active_votes(&self, author: &str, permlink: &str) -> Result<Vec<ActiveVote>>;
// Chain state methods
pub async fn get_dynamic_global_properties(&self) -> Result<DynamicGlobalProperties>;
pub async fn get_chain_properties(&self) -> Result<ChainProperties>;
pub async fn get_feed_history(&self) -> Result<FeedHistory>;
pub async fn get_current_median_history_price(&self) -> Result<Price>;
pub async fn get_hardfork_version(&self) -> Result<String>;
pub async fn get_next_scheduled_hardfork(&self) -> Result<ScheduledHardfork>;
pub async fn get_reward_fund(&self, name: &str) -> Result<RewardFund>;
pub async fn get_config(&self) -> Result<serde_json::Value>;
pub async fn get_version(&self) -> Result<Version>;
// Witness methods
pub async fn get_active_witnesses(&self) -> Result<Vec<String>>;
pub async fn get_witness_by_account(&self, account: &str) -> Result<Option<Witness>>;
// Vesting / delegation methods
pub async fn get_vesting_delegations(
&self, account: &str, from: &str, limit: u32
) -> Result<Vec<VestingDelegation>>;
pub async fn get_expiring_vesting_delegations(
&self, account: &str, from: &str, limit: u32
) -> Result<Vec<ExpiringVestingDelegation>>;
// Market methods
pub async fn get_order_book(&self, limit: u32) -> Result<OrderBook>;
pub async fn get_open_orders(&self, account: &str) -> Result<Vec<OpenOrder>>;
pub async fn get_recent_trades(&self, limit: u32) -> Result<Vec<MarketTrade>>;
pub async fn get_market_history(
&self, bucket_seconds: u32, start: &str, end: &str
) -> Result<Vec<MarketBucket>>;
pub async fn get_market_history_buckets(&self) -> Result<Vec<u32>>;
// Savings / conversion methods
pub async fn get_savings_withdraw_from(&self, account: &str) -> Result<Vec<SavingsWithdraw>>;
pub async fn get_savings_withdraw_to(&self, account: &str) -> Result<Vec<SavingsWithdraw>>;
pub async fn get_conversion_requests(&self, account: &str) -> Result<Vec<ConversionRequest>>;
pub async fn get_collateralized_conversion_requests(&self, account: &str) -> Result<Vec<CollateralizedConversionRequest>>;
// Follow methods
pub async fn get_followers(
&self, account: &str, start: &str, follow_type: &str, limit: u32
) -> Result<Vec<FollowEntry>>;
pub async fn get_following(
&self, account: &str, start: &str, follow_type: &str, limit: u32
) -> Result<Vec<FollowEntry>>;
pub async fn get_follow_count(&self, account: &str) -> Result<FollowCount>;
pub async fn get_reblogged_by(&self, author: &str, permlink: &str) -> Result<Vec<String>>;
// Blog methods
pub async fn get_blog(&self, account: &str, start_entry_id: i32, limit: u32) -> Result<Vec<BlogEntry>>;
pub async fn get_blog_entries(&self, account: &str, start_entry_id: i32, limit: u32) -> Result<Vec<BlogEntryLight>>;
// Transaction verification
pub async fn get_potential_signatures(&self, tx: &SignedTransaction) -> Result<Vec<String>>;
pub async fn get_required_signatures(&self, tx: &SignedTransaction, available: &[String]) -> Result<Vec<String>>;
pub async fn verify_authority(&self, tx: &SignedTransaction) -> Result<bool>;
// Key lookup
pub async fn get_key_references(&self, keys: &[String]) -> Result<Vec<Vec<String>>>;
// Escrow
pub async fn get_escrow(&self, from: &str, escrow_id: u32) -> Result<Option<Escrow>>;
// Proposals (DHF)
pub async fn find_proposals(&self, proposal_ids: &[i64]) -> Result<Vec<Proposal>>;
pub async fn list_proposals(
&self, start: serde_json::Value, limit: u32, order: &str, order_direction: &str, status: &str
) -> Result<Vec<Proposal>>;
// Recurrent transfers
pub async fn find_recurrent_transfers(&self, from: &str) -> Result<Vec<RecurrentTransfer>>;
// Operations in block
pub async fn get_ops_in_block(&self, block_num: u32, virtual_only: bool) -> Result<Vec<AppliedOperation>>;
// Block methods
pub async fn get_block(&self, block_num: u32) -> Result<Option<SignedBlock>>;
pub async fn get_block_header(&self, block_num: u32) -> Result<Option<BlockHeader>>;
}
```
### DiscussionQueryCategory enum (matching dhive)
```rust
pub enum DiscussionQueryCategory {
Trending,
Created,
Active,
Cashout,
Payout,
Votes,
Children,
Hot,
Feed,
Blog,
Comments,
Promoted,
}
```
---
## 5. BroadcastAPI
Maps to dhive's `BroadcastAPI` class. Handles transaction construction, signing, and broadcasting.
### Core methods
```rust
impl BroadcastApi {
/// Sign and broadcast a transaction containing the given operations.
/// Equivalent to dhive's broadcast.sendOperations()
pub async fn send_operations(
&self,
operations: Vec<Operation>,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
/// Broadcast an already-signed transaction.
/// Equivalent to dhive's broadcast.send()
pub async fn send(
&self,
transaction: SignedTransaction,
) -> Result<TransactionConfirmation>;
/// Build a transaction with proper ref_block and expiration but don't sign it.
/// Equivalent to dhive's broadcast.createTransaction()
pub async fn create_transaction(
&self,
operations: Vec<Operation>,
expiration: Option<Duration>, // default 60s
) -> Result<Transaction>;
/// Sign a transaction without broadcasting.
/// Equivalent to top-level dhive signTransaction()
pub fn sign_transaction(
&self,
transaction: &Transaction,
keys: &[&PrivateKey],
) -> Result<SignedTransaction>;
// -- Convenience methods (1:1 with dhive BroadcastAPI) --
pub async fn vote(
&self,
params: VoteOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn comment(
&self,
params: CommentOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn comment_with_options(
&self,
comment: CommentOperation,
options: CommentOptionsOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn delete_comment(
&self,
params: DeleteCommentOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn custom_json(
&self,
params: CustomJsonOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn transfer(
&self,
params: TransferOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn transfer_to_vesting(
&self,
params: TransferToVestingOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn withdraw_vesting(
&self,
params: WithdrawVestingOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn delegate_vesting_shares(
&self,
params: DelegateVestingSharesOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn transfer_to_savings(
&self,
params: TransferToSavingsOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn transfer_from_savings(
&self,
params: TransferFromSavingsOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn claim_reward_balance(
&self,
params: ClaimRewardBalanceOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn account_update(
&self,
params: AccountUpdateOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn account_update2(
&self,
params: AccountUpdate2Operation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn create_claimed_account(
&self,
params: CreateClaimedAccountOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn claim_account(
&self,
params: ClaimAccountOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn update_proposal(
&self,
params: UpdateProposalOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn update_proposal_votes(
&self,
params: UpdateProposalVotesOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn create_proposal(
&self,
params: CreateProposalOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn remove_proposal(
&self,
params: RemoveProposalOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn recurrent_transfer(
&self,
params: RecurrentTransferOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
// Witness operations
pub async fn witness_update(
&self,
params: WitnessUpdateOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn witness_set_properties(
&self,
owner: &str,
props: WitnessProps,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn account_witness_vote(
&self,
params: AccountWitnessVoteOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn account_witness_proxy(
&self,
params: AccountWitnessProxyOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
// Escrow
pub async fn escrow_transfer(
&self,
params: EscrowTransferOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn escrow_approve(
&self,
params: EscrowApproveOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn escrow_dispute(
&self,
params: EscrowDisputeOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn escrow_release(
&self,
params: EscrowReleaseOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
// Limit orders
pub async fn limit_order_create(
&self,
params: LimitOrderCreateOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn limit_order_create2(
&self,
params: LimitOrderCreate2Operation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn limit_order_cancel(
&self,
params: LimitOrderCancelOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
// Convert
pub async fn convert(
&self,
params: ConvertOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn collateralized_convert(
&self,
params: CollateralizedConvertOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
// Other
pub async fn feed_publish(
&self,
params: FeedPublishOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn set_withdraw_vesting_route(
&self,
params: SetWithdrawVestingRouteOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn decline_voting_rights(
&self,
params: DeclineVotingRightsOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn change_recovery_account(
&self,
params: ChangeRecoveryAccountOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn request_account_recovery(
&self,
params: RequestAccountRecoveryOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
pub async fn recover_account(
&self,
params: RecoverAccountOperation,
key: &PrivateKey,
) -> Result<TransactionConfirmation>;
}
```
---
## 6. Blockchain
Maps to dhive's `Blockchain` class. Provides block and operation streaming.
### dhive API
```typescript
// Async iterators
client.blockchain.getBlocks(options?) // yields SignedBlock
client.blockchain.getBlockNumbers(options?) // yields block numbers
client.blockchain.getOperations(options?) // yields AppliedOperation
// Node.js streams
client.blockchain.getBlockStream(options?)
client.blockchain.getOperationsStream(options?)
// Utility
client.blockchain.getCurrentBlockNum(mode?)
client.blockchain.getCurrentBlockHeader(mode?)
client.blockchain.getCurrentBlock(mode?)
```
### Rust equivalent
```rust
pub enum BlockchainMode {
/// Only return irreversible blocks
Irreversible,
/// Return latest blocks (may be reversed)
Latest,
}
pub struct BlockchainStreamOptions {
pub from: Option<u32>, // starting block number
pub to: Option<u32>, // ending block number (None = infinite)
pub mode: BlockchainMode, // default: Irreversible
}
impl Blockchain {
/// Returns an async Stream of SignedBlock
pub fn get_blocks(
&self,
options: Option<BlockchainStreamOptions>,
) -> impl Stream<Item = Result<SignedBlock>>;
/// Returns an async Stream of block numbers
pub fn get_block_numbers(
&self,
options: Option<BlockchainStreamOptions>,
) -> impl Stream<Item = Result<u32>>;
/// Returns an async Stream of AppliedOperation
pub fn get_operations(
&self,
options: Option<BlockchainStreamOptions>,
) -> impl Stream<Item = Result<AppliedOperation>>;
/// Get current block number
pub async fn get_current_block_num(
&self,
mode: Option<BlockchainMode>,
) -> Result<u32>;
/// Get current block header
pub async fn get_current_block_header(
&self,
mode: Option<BlockchainMode>,
) -> Result<BlockHeader>;
/// Get current full block
pub async fn get_current_block(
&self,
mode: Option<BlockchainMode>,
) -> Result<SignedBlock>;
}
```
The stream implementation polls `get_dynamic_global_properties` to discover new blocks, then fetches them sequentially. It uses `tokio::time::interval` with a 3-second tick (matching Hive's block time) and handles gaps gracefully.
---
## 7. HivemindAPI
Maps to dhive's `HivemindAPI` class. Wraps the `bridge` API namespace.
```rust
impl HivemindApi {
pub async fn get_ranked_posts(&self, query: &PostsQuery) -> Result<Vec<Discussion>>;
pub async fn get_account_posts(&self, query: &AccountPostsQuery) -> Result<Vec<Discussion>>;
pub async fn get_community(&self, query: &CommunityQuery) -> Result<Option<CommunityDetail>>;
pub async fn list_communities(&self, query: &ListCommunitiesQuery) -> Result<Vec<CommunityDetail>>;
pub async fn get_community_roles(&self, query: &CommunityRolesQuery) -> Result<Vec<CommunityRole>>;
pub async fn get_account_notifications(&self, query: &AccountNotifsQuery) -> Result<Vec<Notification>>;
pub async fn get_discussion(&self, author: &str, permlink: &str) -> Result<Discussion>;
pub async fn get_post(&self, author: &str, permlink: &str, observer: &str) -> Result<Discussion>;
}
```
---
## 8. RCAPI
Maps to dhive's `RCAPI` class. Resource credit queries.
```rust
impl RcApi {
pub async fn find_rc_accounts(&self, accounts: &[&str]) -> Result<Vec<RCAccount>>;
pub async fn get_resource_params(&self) -> Result<RCParams>;
pub async fn get_resource_pool(&self) -> Result<RCPool>;
pub async fn calculate_cost(&self, operations: &[Operation]) -> Result<i64>;
}
```
---
## 9. AccountByKeyAPI and TransactionStatusAPI
```rust
impl AccountByKeyApi {
pub async fn get_key_references(&self, keys: &[&str]) -> Result<Vec<Vec<String>>>;
}
impl TransactionStatusApi {
pub async fn find_transaction(
&self,
transaction_id: &str,
expiration: Option<&str>,
) -> Result<TransactionStatus>;
}
```
---
## 10. Cryptography Module
This is the hardest part. Hive uses secp256k1 with some Graphene/EOS-specific conventions.
### PrivateKey
```rust
pub struct PrivateKey {
secret: secp256k1::SecretKey,
}
impl PrivateKey {
/// Create from WIF (Wallet Import Format) string
/// e.g., "5J..."
pub fn from_wif(wif: &str) -> Result<Self>;
/// Derive from username/password/role (matching dhive's PrivateKey.fromLogin)
/// key = sha256(username + role + password)
pub fn from_login(username: &str, password: &str, role: KeyRole) -> Self;
/// Create from raw 32 bytes
pub fn from_bytes(bytes: [u8; 32]) -> Result<Self>;
/// Generate a random private key
pub fn generate() -> Self;
/// Export to WIF string
pub fn to_wif(&self) -> String;
/// Get the corresponding public key
pub fn public_key(&self) -> PublicKey;
/// Sign a message digest (returns canonical signature per Graphene rules)
pub fn sign(&self, digest: &[u8; 32]) -> Result<Signature>;
/// Create a shared secret for memo encryption (ECDH)
pub fn get_shared_secret(&self, public_key: &PublicKey) -> [u8; 64];
}
```
### PublicKey
```rust
pub struct PublicKey {
key: secp256k1::PublicKey,
prefix: String, // "STM" for mainnet
}
impl PublicKey {
/// Parse from Hive-format string: "STM7abc..."
pub fn from_string(s: &str) -> Result<Self>;
/// Export to Hive-format string
pub fn to_string(&self) -> String;
/// Verify a signature
pub fn verify(&self, digest: &[u8; 32], signature: &Signature) -> bool;
}
impl Display for PublicKey { /* "STM..." format */ }
impl FromStr for PublicKey { /* parse "STM..." format */ }
```
### Signature
```rust
pub struct Signature {
data: [u8; 65], // recovery_id (1 byte) + r (32 bytes) + s (32 bytes)
}
impl Signature {
/// Recover the public key from a signature and message digest
pub fn recover(&self, digest: &[u8; 32]) -> Result<PublicKey>;
/// Check if signature is canonical (required by Hive/Graphene)
pub fn is_canonical(&self) -> bool;
}
```
### KeyRole enum
```rust
pub enum KeyRole {
Owner,
Active,
Posting,
Memo,
}
```
### Memo encryption/decryption
dhive supports encrypted memos using ECIES (Elliptic Curve Integrated Encryption Scheme) with AES-256-CBC. hive-rs must match this exactly.
```rust
pub mod memo {
/// Encrypt a memo message.
/// Returns "#" prefixed encrypted string.
pub fn encode(
message: &str,
sender_private: &PrivateKey,
receiver_public: &PublicKey,
) -> Result<String>;
/// Decrypt a memo message.
/// Input must be "#" prefixed encrypted string.
pub fn decode(
encoded: &str,
receiver_private: &PrivateKey,
) -> Result<String>;
}
```
Encryption flow (matching dhive exactly):
1. Generate a one-time nonce (uniqueNonce)
2. Compute shared secret via ECDH: `sha512(sender_private * receiver_public)`
3. Derive AES key from shared secret bytes [0..32]
4. Derive IV from shared secret bytes [32..48]
5. AES-256-CBC encrypt the message
6. Compute checksum: `sha256(shared_secret)[0..4]`
7. Serialize: `from_public + to_public + nonce + checksum + encrypted_message`
8. Prefix with "#"
### Crypto utilities
```rust
pub mod crypto_utils {
pub fn sha256(data: &[u8]) -> [u8; 32];
pub fn double_sha256(data: &[u8]) -> [u8; 32];
pub fn ripemd160(data: &[u8]) -> [u8; 20];
pub fn sha512(data: &[u8]) -> [u8; 64];
}
```
### Transaction signing
```rust
/// Generate the digest that must be signed for a transaction.
/// This is the core of Hive transaction signing.
pub fn transaction_digest(
transaction: &Transaction,
chain_id: &ChainId,
) -> [u8; 32] {
// 1. Binary-serialize the transaction (fc::raw::pack format)
// 2. Prepend the chain_id bytes
// 3. sha256 the whole thing
}
/// Generate the transaction ID (used for lookups)
pub fn generate_trx_id(transaction: &Transaction) -> String {
// sha256 of serialized transaction, hex-encoded, first 40 chars
}
/// Sign a transaction with one or more keys.
pub fn sign_transaction(
transaction: &Transaction,
keys: &[&PrivateKey],
chain_id: &ChainId,
) -> SignedTransaction {
let digest = transaction_digest(transaction, chain_id);
let signatures: Vec<String> = keys.iter()
.map(|k| k.sign(&digest).to_hex())
.collect();
SignedTransaction {
transaction: transaction.clone(),
signatures,
}
}
```
### Canonical signature enforcement
Hive requires canonical signatures (same as EOS/Graphene). If a signature is not canonical, the signing code must retry with a different nonce (increment a counter appended to the hash) until a canonical signature is produced. This is a critical detail. dhive's `isCanonicalSignature()` checks specific byte ranges. hive-rs must replicate this exactly.
---
## 11. Binary Serialization
Hive transactions must be serialized to a specific binary format for signing. This matches the C++ `fc::raw::pack` format used by `hived`.
### Primitive types
| uint8 | 1 byte LE |
| uint16 | 2 bytes LE |
| uint32 | 4 bytes LE |
| int16 | 2 bytes LE signed |
| int64 | 8 bytes LE signed |
| uint64 | 8 bytes LE |
| varint32 | LEB128 variable length |
| string | varint32 length + UTF-8 bytes |
| bool | 1 byte (0x00 or 0x01) |
| date | uint32 (seconds since epoch) |
| public_key | 33 bytes compressed secp256k1 |
| asset | int64 amount + uint8 precision + 7-byte symbol (null padded) |
| authority | uint32 weight_threshold + map<string, uint16> account_auths + map<public_key, uint16> key_auths |
| optional<T> | uint8 (0=absent, 1=present) + T if present |
| array<T> | varint32 count + count * T |
| static_variant | varint32 type_id + serialized variant data |
| operation | static_variant (type_id = operation order index) |
### Operation serialization order
Operations must be serialized with their numeric type ID matching Hive's `operation_order`. This is defined in the C++ code and must be replicated exactly.
```rust
pub enum Operation {
Vote(VoteOperation), // 0
Comment(CommentOperation), // 1
Transfer(TransferOperation), // 2
TransferToVesting(TransferToVestingOperation), // 3
WithdrawVesting(WithdrawVestingOperation), // 4
LimitOrderCreate(LimitOrderCreateOperation), // 5
LimitOrderCancel(LimitOrderCancelOperation), // 6
FeedPublish(FeedPublishOperation), // 7
Convert(ConvertOperation), // 8
AccountCreate(AccountCreateOperation), // 9
AccountUpdate(AccountUpdateOperation), // 10
WitnessUpdate(WitnessUpdateOperation), // 11
AccountWitnessVote(AccountWitnessVoteOperation), // 12
AccountWitnessProxy(AccountWitnessProxyOperation), // 13
Pow(PowOperation), // 14
Custom(CustomOperation), // 15
ReportOverProduction(ReportOverProductionOperation), // 16
DeleteComment(DeleteCommentOperation), // 17
CustomJson(CustomJsonOperation), // 18
CommentOptions(CommentOptionsOperation), // 19
SetWithdrawVestingRoute(SetWithdrawVestingRouteOperation), // 20
LimitOrderCreate2(LimitOrderCreate2Operation), // 21
ClaimAccount(ClaimAccountOperation), // 22
CreateClaimedAccount(CreateClaimedAccountOperation), // 23
RequestAccountRecovery(RequestAccountRecoveryOperation), // 24
RecoverAccount(RecoverAccountOperation), // 25
ChangeRecoveryAccount(ChangeRecoveryAccountOperation), // 26
EscrowTransfer(EscrowTransferOperation), // 27
EscrowDispute(EscrowDisputeOperation), // 28
EscrowRelease(EscrowReleaseOperation), // 29
Pow2(Pow2Operation), // 30
EscrowApprove(EscrowApproveOperation), // 31
TransferToSavings(TransferToSavingsOperation), // 32
TransferFromSavings(TransferFromSavingsOperation), // 33
CancelTransferFromSavings(CancelTransferFromSavingsOperation), // 34
CustomBinary(CustomBinaryOperation), // 35
DeclineVotingRights(DeclineVotingRightsOperation), // 36
ResetAccount(ResetAccountOperation), // 37
SetResetAccount(SetResetAccountOperation), // 38
ClaimRewardBalance(ClaimRewardBalanceOperation), // 39
DelegateVestingShares(DelegateVestingSharesOperation), // 40
AccountCreateWithDelegation(AccountCreateWithDelegationOperation), // 41
WitnessSetProperties(WitnessSetPropertiesOperation), // 42
AccountUpdate2(AccountUpdate2Operation), // 43
CreateProposal(CreateProposalOperation), // 44
UpdateProposalVotes(UpdateProposalVotesOperation), // 45
RemoveProposal(RemoveProposalOperation), // 46
UpdateProposal(UpdateProposalOperation), // 47
CollateralizedConvert(CollateralizedConvertOperation), // 48
RecurrentTransfer(RecurrentTransferOperation), // 49
}
```
All 50 operation types must be serializable. The `Serialize` and `Deserialize` traits from serde handle JSON. A separate `HiveSerialize` trait handles the binary wire format.
```rust
pub trait HiveSerialize {
fn hive_serialize(&self, buf: &mut Vec<u8>);
}
pub trait HiveDeserialize: Sized {
fn hive_deserialize(buf: &mut &[u8]) -> Result<Self>;
}
```
---
## 12. Type Definitions (Core Types)
### Asset
```rust
#[derive(Debug, Clone, PartialEq)]
pub struct Asset {
pub amount: i64, // satoshi-level amount
pub precision: u8, // decimal places (3 for HIVE/HBD, 6 for VESTS)
pub symbol: AssetSymbol,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AssetSymbol {
Hive, // "HIVE"
Hbd, // "HBD"
Vests, // "VESTS"
Custom(String),
}
impl Asset {
pub fn hive(amount: f64) -> Self; // 1.000 HIVE
pub fn hbd(amount: f64) -> Self; // 1.000 HBD
pub fn vests(amount: f64) -> Self; // 1.000000 VESTS
pub fn from_string(s: &str) -> Result<Self>; // parse "1.000 HIVE"
}
impl Display for Asset {
// Outputs "1.000 HIVE" format matching dhive
}
impl FromStr for Asset {
// Parses "1.000 HIVE" format
}
// Serde: serialize/deserialize as string "1.000 HIVE"
impl Serialize for Asset { ... }
impl<'de> Deserialize<'de> for Asset { ... }
```
### Authority
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Authority {
pub weight_threshold: u32,
pub account_auths: Vec<(String, u16)>,
pub key_auths: Vec<(String, u16)>, // PublicKey string + weight
}
```
### Price
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Price {
pub base: Asset,
pub quote: Asset,
}
```
### DynamicGlobalProperties
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DynamicGlobalProperties {
pub id: u32,
pub head_block_number: u32,
pub head_block_id: String,
pub time: String,
pub current_witness: String,
pub total_pow: u64,
pub num_pow_witnesses: u32,
pub virtual_supply: Asset,
pub current_supply: Asset,
pub init_hbd_supply: Asset,
pub current_hbd_supply: Asset,
pub total_vesting_fund_hive: Asset,
pub total_vesting_shares: Asset,
pub total_reward_fund_hive: Asset,
pub total_reward_shares2: String,
pub pending_rewarded_vesting_shares: Asset,
pub pending_rewarded_vesting_hive: Asset,
pub hbd_interest_rate: u16,
pub hbd_print_rate: u16,
pub maximum_block_size: u32,
pub required_actions_partition_percent: u16,
pub current_aslot: u64,
pub recent_slots_filled: String,
pub participation_count: u8,
pub last_irreversible_block_num: u32,
pub vote_power_reserve_rate: u32,
pub delegation_return_period: u32,
pub reverse_auction_seconds: u32,
pub available_account_subsidies: i64,
pub hbd_stop_percent: u16,
pub hbd_start_percent: u16,
pub next_maintenance_time: String,
pub last_budget_time: String,
pub next_daily_maintenance_time: String,
pub content_reward_percent: u16,
pub vesting_reward_percent: u16,
pub proposal_fund_percent: u16,
pub dhf_interval_ledger: Asset,
pub downvote_pool_percent: u16,
pub current_remove_threshold: i32,
#[serde(default)]
pub early_voting_seconds: u64,
#[serde(default)]
pub mid_voting_seconds: u64,
#[serde(default)]
pub max_consecutive_recurrent_transfer_failures: u16,
#[serde(default)]
pub max_recurrent_transfer_end_date: u16,
#[serde(default)]
pub min_recurrent_transfers_recurrence: u16,
#[serde(default)]
pub max_open_recurrent_transfers: u16,
}
```
### SignedBlock, Transaction, etc.
All remaining dhive interfaces get 1:1 Rust struct equivalents with `#[derive(Debug, Clone, Serialize, Deserialize)]`. Every field name maps directly. Where dhive uses `camelCase`, Rust uses `snake_case` with `#[serde(rename_all = "camelCase")]` or field-level `#[serde(rename = "...")]` as needed to match the JSON-RPC responses.
---
## 13. Error Handling
```rust
#[derive(Debug, thiserror::Error)]
pub enum HiveError {
#[error("RPC error {code}: {message}")]
Rpc { code: i64, message: String, data: Option<serde_json::Value> },
#[error("Transport error: {0}")]
Transport(#[from] reqwest::Error),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Invalid key: {0}")]
InvalidKey(String),
#[error("Signing error: {0}")]
Signing(String),
#[error("All nodes failed after {attempts} attempts")]
AllNodesFailed { attempts: u32 },
#[error("Timeout after {0:?}")]
Timeout(Duration),
#[error("Invalid asset format: {0}")]
InvalidAsset(String),
#[error("{0}")]
Other(String),
}
pub type Result<T> = std::result::Result<T, HiveError>;
```
---
## 14. Utility Functions (top-level exports matching dhive)
```rust
// These are exported at the crate root, same as dhive
/// Calculate the VESTS-to-HIVE price from global properties
pub fn get_vesting_share_price(props: &DynamicGlobalProperties) -> Price;
/// Convert HP to VESTS
pub fn get_vests(props: &DynamicGlobalProperties, hive_power: &Asset) -> Asset;
/// Create an operation bitmask filter for get_account_history
pub fn make_bit_mask_filter(operations: &[OperationName]) -> (u64, u64);
/// Build a witness_set_properties operation from human-readable props
pub fn build_witness_update_op(
owner: &str,
props: WitnessProps,
) -> WitnessSetPropertiesOperation;
```
---
## 15. Dependencies
| `reqwest` (with `rustls-tls`) | HTTP/2 transport |
| `serde` + `serde_json` | JSON serialization for RPC |
| `tokio` | Async runtime |
| `futures` | Stream trait for blockchain streaming |
| `secp256k1` (rust-bitcoin) | Elliptic curve operations (use the C backend `secp256k1-sys` for performance) |
| `sha2` | SHA-256, SHA-512 |
| `ripemd` | RIPEMD-160 (for public key encoding) |
| `aes` + `cbc` | AES-256-CBC for memo encryption |
| `bs58` | Base58 encoding/decoding for WIF keys |
| `rand` | Random key generation, nonce generation |
| `thiserror` | Error derive macro |
| `tracing` | Structured logging (failover events, etc.) |
| `chrono` | Timestamp handling |
Dev dependencies: `tokio-test`, `wiremock` (for mocking RPC responses), `hex`
---
## 16. Feature Flags
```toml
[features]
default = ["rustls"]
rustls = ["reqwest/rustls-tls"]
native-tls = ["reqwest/native-tls"]
testnet = [] # Sets default chain_id to testnet
```
---
## 17. Implementation Priority
### Phase 1: Core foundation (week 1-2)
- Transport layer with failover
- Client struct
- JSON-RPC call plumbing
- All type definitions with serde
- Asset parsing
### Phase 2: Read APIs (week 3-4)
- DatabaseAPI (all read methods)
- HivemindAPI
- RCAPI
- AccountByKeyAPI
- TransactionStatusAPI
### Phase 3: Crypto (week 5-6)
- PrivateKey / PublicKey (WIF, from_login)
- secp256k1 signing with canonical signature enforcement
- Binary serialization for all 50 operation types
- Transaction digest computation
- Transaction signing
### Phase 4: Write APIs (week 7-8)
- BroadcastAPI (send, sendOperations, all convenience methods)
- Memo encryption/decryption
### Phase 5: Streaming and polish (week 9-10)
- Blockchain streaming (blocks, operations)
- Utility functions (getVestingSharePrice, etc.)
- Integration tests against testnet
- Documentation
- Publish to crates.io
---
## 18. Testing Strategy
### Unit tests
- Asset parsing/formatting round-trips
- Key generation, WIF encode/decode round-trips
- Binary serialization matches known-good hex from dhive
- Transaction digest matches dhive output for identical transactions
- Canonical signature detection
### Integration tests (against Hive testnet)
- Fetch accounts, blocks, global properties
- Broadcast a vote, comment, transfer
- Stream blocks for 30 seconds and verify continuity
- Failover by pointing first node at a dead endpoint
- Memo encrypt with hive-rs, decrypt with dhive (and vice versa)
### Conformance tests
- Generate test vectors with dhive (serialize operations, sign transactions, compute digests)
- Verify hive-rs produces byte-identical output for the same inputs
- This is the most important category. If the binary serialization or signing diverges by even one byte, transactions get rejected.
---
## 19. Usage Example (what the developer experience looks like)
```rust
use hive_rs::{Client, PrivateKey, Asset};
use hive_rs::types::*;
#[tokio::main]
async fn main() -> hive_rs::Result<()> {
let client = Client::new_default(); // uses default mainnet nodes
// Read chain state
let props = client.database.get_dynamic_global_properties().await?;
println!("Head block: {}", props.head_block_number);
// Fetch an account
let accounts = client.database.get_accounts(&["beggars"]).await?;
println!("Account: {:?}", accounts[0].name);
// Get trending posts
let posts = client.database.get_discussions(
DiscussionQueryCategory::Trending,
&DisqussionQuery { tag: Some("hive".into()), limit: Some(5), ..Default::default() },
).await?;
for post in &posts {
println!("{} by {}", post.title, post.author);
}
// Sign and broadcast a vote
let key = PrivateKey::from_wif("5J...")?;
let result = client.broadcast.vote(
VoteOperation {
voter: "myaccount".into(),
author: "beggars".into(),
permlink: "some-post".into(),
weight: 10000,
},
&key,
).await?;
println!("Included in block: {}", result.block_num);
// Stream new blocks
use futures::StreamExt;
let mut blocks = client.blockchain.get_blocks(None);
while let Some(block) = blocks.next().await {
let block = block?;
println!("Block {} - {} txs", block.block_id, block.transactions.len());
}
Ok(())
}
```