constellation-metagraph-sdk 0.2.0

Rust SDK for signing data and currency transactions on Constellation Network metagraphs built with metakit
Documentation

Constellation Metagraph SDK - Rust

Rust SDK for signing data and currency transactions on Constellation Network metagraphs built with the metakit framework.

Scope: This SDK supports both data transactions (state updates) and metagraph token transactions (value transfers). It implements the standardized serialization, hashing, and signing routines defined by metakit and may not be compatible with metagraphs using custom serialization.

Installation

Add to your Cargo.toml:

[dependencies]
constellation-metagraph-sdk = "0.2"

Or use cargo:

cargo add constellation-metagraph-sdk

Quick Start

Data Transactions

use constellation_sdk::{
    wallet::generate_key_pair,
    signed_object::create_signed_object,
    verify::verify,
};
use serde_json::json;

fn main() {
    // Generate a key pair
    let key_pair = generate_key_pair();
    println!("Address: {}", key_pair.address);

    // Sign data
    let data = json!({ "action": "UPDATE", "payload": { "key": "value" } });
    let signed = create_signed_object(&data, &key_pair.private_key, false).unwrap();

    // Verify
    let result = verify(&signed, false);
    println!("Valid: {}", result.is_valid);
}

Currency Transactions

use constellation_sdk::{
    generate_key_pair,
    create_currency_transaction,
    verify_currency_transaction,
    TransferParams,
    TransactionReference,
};

fn main() {
    // Generate keys
    let sender = generate_key_pair();
    let recipient = generate_key_pair();

    // Create token transaction
    let tx = create_currency_transaction(
        TransferParams {
            destination: recipient.address,
            amount: 100.5,
            fee: 0.0,
        },
        &sender.private_key,
        TransactionReference {
            hash: "abc123...".to_string(),
            ordinal: 0,
        },
    ).unwrap();

    // Verify
    let result = verify_currency_transaction(&tx);
    println!("Valid: {}", result.is_valid);
}

API Reference

Data Transactions

High-Level API

create_signed_object(value, private_key, is_data_update) -> Result<Signed<T>>

Create a signed object with a single signature.

let signed = create_signed_object(&data, &private_key, false)?;

// For L1 submission (DataUpdate)
let signed = create_signed_object(&data, &private_key, true)?;

add_signature(signed, private_key, is_data_update) -> Result<Signed<T>>

Add an additional signature to an existing signed object.

let mut signed = create_signed_object(&data, &party1_key, false)?;
signed = add_signature(&signed, &party2_key, false)?;
// signed.proofs.len() == 2

batch_sign(value, private_keys, is_data_update) -> Result<Signed<T>>

Create a signed object with multiple signatures at once.

let signed = batch_sign(&data, &[key1, key2, key3], false)?;
// signed.proofs.len() == 3

verify(signed, is_data_update) -> VerificationResult

Verify all signatures on a signed object.

let result = verify(&signed, false);
if result.is_valid {
    println!("All signatures valid");
} else {
    println!("Invalid proofs: {:?}", result.invalid_proofs);
}

Low-Level Primitives

canonicalize(data) -> Result<String>

Canonicalize JSON data according to RFC 8785.

let canonical = canonicalize(&json!({"b": 2, "a": 1}))?;
// "{\"a\":1,\"b\":2}"

to_bytes(data, is_data_update) -> Result<Vec<u8>>

Convert data to binary bytes for signing.

// Regular encoding
let bytes = to_bytes(&data, false)?;

// DataUpdate encoding (with Constellation prefix)
let bytes = to_bytes(&data, true)?;

hash_data(data) -> Result<Hash> / hash_bytes(bytes) -> Hash

Compute SHA-256 hash.

let hash = hash_data(&data)?;
println!("{}", hash.value);  // 64-char hex
println!("{:?}", hash.bytes); // [u8; 32]

sign(data, private_key) / sign_data_update(data, private_key)

Sign data and return a proof.

let proof = sign(&data, &private_key)?;
// SignatureProof { id: "...", signature: "..." }

sign_hash(hash_hex, private_key) -> Result<String>

Sign a pre-computed hash.

let hash = hash_data(&data)?;
let signature = sign_hash(&hash.value, &private_key)?;

Wallet Utilities

generate_key_pair() -> KeyPair

Generate a new random key pair.

let key_pair = generate_key_pair();
// KeyPair { private_key, public_key, address }

key_pair_from_private_key(private_key) -> Result<KeyPair>

Derive a key pair from an existing private key.

let key_pair = key_pair_from_private_key(&existing_private_key)?;

get_public_key_id(private_key) -> Result<String>

Get the public key ID (128 chars, no 04 prefix) for use in proofs.

let id = get_public_key_id(&private_key)?;

Currency Transactions

create_currency_transaction(params, private_key, last_ref) -> Result<CurrencyTransaction>

Create a metagraph token transaction.

use constellation_sdk::{create_currency_transaction, TransferParams, TransactionReference};

let tx = create_currency_transaction(
    TransferParams {
        destination: "DAG...recipient".to_string(),
        amount: 100.5,  // 100.5 tokens
        fee: 0.0,
    },
    &private_key,
    TransactionReference {
        hash: "abc123...".to_string(),
        ordinal: 5,
    },
)?;

create_currency_transaction_batch(transfers, private_key, last_ref) -> Result<Vec<CurrencyTransaction>>

Create multiple token transactions in a batch.

let transfers = vec![
    TransferParams { destination: "DAG...1".to_string(), amount: 10.0, fee: 0.0 },
    TransferParams { destination: "DAG...2".to_string(), amount: 20.0, fee: 0.0 },
    TransferParams { destination: "DAG...3".to_string(), amount: 30.0, fee: 0.0 },
];

let txns = create_currency_transaction_batch(
    transfers,
    &private_key,
    TransactionReference { hash: "abc123...".to_string(), ordinal: 5 },
)?;

sign_currency_transaction(transaction, private_key) -> Result<CurrencyTransaction>

Add an additional signature to a currency transaction (for multi-sig).

let mut tx = create_currency_transaction(params, &key1, last_ref)?;
tx = sign_currency_transaction(&tx, &key2)?;
// tx.proofs.len() == 2

verify_currency_transaction(transaction) -> VerificationResult

Verify all signatures on a currency transaction.

let result = verify_currency_transaction(&tx);
println!("Valid: {}", result.is_valid);

hash_currency_transaction(transaction) -> Hash

Hash a currency transaction.

let hash = hash_currency_transaction(&tx);
println!("Hash: {}", hash.value);

get_transaction_reference(transaction, ordinal) -> TransactionReference

Get a transaction reference for chaining transactions.

let tx_ref = get_transaction_reference(&tx, 6);
// Use tx_ref as last_ref for next transaction

Utility Functions

// Validate DAG address
is_valid_dag_address("DAG...");  // true/false

// Convert between token units and smallest units
token_to_units(100.5);    // 10050000000
units_to_token(10050000000);  // 100.5

// Token decimals constant
TOKEN_DECIMALS;  // 1e-8

Network Operations

Enable the network feature in your Cargo.toml:

[dependencies]
constellation-metagraph-sdk = { version = "0.2", features = ["network"] }

CurrencyL1Client

Client for interacting with Currency L1 nodes.

use constellation_sdk::network::{CurrencyL1Client, NetworkConfig};

let config = NetworkConfig {
    l1_url: Some("http://localhost:9010".to_string()),
    timeout: Some(30),  // optional, defaults to 30s
    ..Default::default()
};

let client = CurrencyL1Client::new(config)?;

// Get last transaction reference for an address
let last_ref = client.get_last_reference("DAG...").await?;

// Submit a signed transaction
let result = client.post_transaction(&signed_tx).await?;
println!("Transaction hash: {}", result.hash);

// Check pending transaction status
if let Some(pending) = client.get_pending_transaction(&result.hash).await? {
    println!("Status: {:?}", pending.status);  // Waiting, InProgress, or Accepted
}

// Check node health
let is_healthy = client.check_health().await;

DataL1Client

Client for interacting with Data L1 nodes (metagraphs).

use constellation_sdk::network::{DataL1Client, NetworkConfig};

let config = NetworkConfig {
    data_l1_url: Some("http://localhost:8080".to_string()),
    ..Default::default()
};

let client = DataL1Client::new(config)?;

// Estimate fee for data submission
let fee_info = client.estimate_fee(&signed_data).await?;
println!("Fee: {}, Address: {}", fee_info.fee, fee_info.address);

// Submit signed data
let result = client.post_data(&signed_data).await?;
println!("Data hash: {}", result.hash);

// Check node health
let is_healthy = client.check_health().await;

Combined Configuration

let config = NetworkConfig {
    l1_url: Some("http://localhost:9010".to_string()),      // Currency L1
    data_l1_url: Some("http://localhost:8080".to_string()), // Data L1
    timeout: Some(30),
};

let l1_client = CurrencyL1Client::new(config.clone())?;
let data_client = DataL1Client::new(config)?;

Network Types

pub struct NetworkConfig {
    pub l1_url: Option<String>,       // Currency L1 endpoint
    pub data_l1_url: Option<String>,  // Data L1 endpoint
    pub timeout: Option<u64>,         // Request timeout in seconds
}

pub struct PostTransactionResponse {
    pub hash: String,
}

pub struct PendingTransaction {
    pub hash: String,
    pub status: TransactionStatus,  // Waiting, InProgress, Accepted
    pub transaction: CurrencyTransaction,
}

pub struct EstimateFeeResponse {
    pub fee: i64,
    pub address: String,
}

pub struct PostDataResponse {
    pub hash: String,
}

pub enum NetworkError {
    HttpError { message: String, status_code: Option<u16>, response: Option<String> },
    Timeout,
    ConfigError(String),
    SerializationError(String),
}

Types

pub struct SignatureProof {
    pub id: String,        // Public key (128 chars)
    pub signature: String, // DER signature hex
}

pub struct Signed<T> {
    pub value: T,
    pub proofs: Vec<SignatureProof>,
}

pub struct KeyPair {
    pub private_key: String,
    pub public_key: String,
    pub address: String,
}

pub struct Hash {
    pub value: String,     // 64-char hex
    pub bytes: [u8; 32],   // 32 bytes
}

pub struct VerificationResult {
    pub is_valid: bool,
    pub valid_proofs: Vec<SignatureProof>,
    pub invalid_proofs: Vec<SignatureProof>,
}

// Currency transaction types
pub struct TransactionReference {
    pub hash: String,      // 64-char hex transaction hash
    pub ordinal: i64,      // Transaction ordinal number
}

pub struct CurrencyTransactionValue {
    pub source: String,         // Source DAG address
    pub destination: String,    // Destination DAG address
    pub amount: i64,           // Amount in smallest units (1e-8)
    pub fee: i64,              // Fee in smallest units (1e-8)
    pub parent: TransactionReference,
    pub salt: String,          // Random salt for uniqueness
}

pub type CurrencyTransaction = Signed<CurrencyTransactionValue>;

pub struct TransferParams {
    pub destination: String,   // Destination DAG address
    pub amount: f64,          // Amount in token units (e.g., 100.5 tokens)
    pub fee: f64,             // Fee in token units (defaults to 0)
}

Usage Examples

Submit DataUpdate to L1

use constellation_sdk::{
    wallet::generate_key_pair,
    signed_object::create_signed_object,
};
use serde_json::json;

let data_update = json!({
    "action": "TRANSFER",
    "from": "address1",
    "to": "address2",
    "amount": 100
});

// Sign as DataUpdate
let signed = create_signed_object(&data_update, &private_key, true)?;

// Submit to data-l1 (using your HTTP client)
// POST http://l1-node:9300/data with signed as JSON body

Multi-Signature Workflow

use constellation_sdk::{
    signed_object::{create_signed_object, add_signature},
    verify::verify,
};

// Party 1 creates and signs
let mut signed = create_signed_object(&data, &party1_key, false)?;

// Party 2 adds signature
signed = add_signature(&signed, &party2_key, false)?;

// Party 3 adds signature
signed = add_signature(&signed, &party3_key, false)?;

// Verify all signatures
let result = verify(&signed, false);
println!("{} valid signatures", result.valid_proofs.len());

Currency Transactions

Create and Verify Token Transaction

use constellation_sdk::{
    generate_key_pair,
    create_currency_transaction,
    verify_currency_transaction,
    TransferParams,
    TransactionReference,
};

// Generate keys
let sender_key = generate_key_pair();
let recipient_key = generate_key_pair();

// Get last transaction reference (from network or previous transaction)
let last_ref = TransactionReference {
    hash: "abc123...previous-tx-hash".to_string(),
    ordinal: 5,
};

// Create transaction
let tx = create_currency_transaction(
    TransferParams {
        destination: recipient_key.address.clone(),
        amount: 100.5,  // 100.5 tokens
        fee: 0.0,
    },
    &sender_key.private_key,
    last_ref,
)?;

// Verify
let result = verify_currency_transaction(&tx);
println!("Transaction valid: {}", result.is_valid);

// Note: Network submission not yet implemented in this SDK
// You can submit the transaction using dag4.js or custom network code

Batch Token Transactions

use constellation_sdk::{
    create_currency_transaction_batch,
    TransferParams,
    TransactionReference,
};

let last_ref = TransactionReference {
    hash: "abc123...".to_string(),
    ordinal: 10,
};

let transfers = vec![
    TransferParams { destination: "DAG...1".to_string(), amount: 10.0, fee: 0.0 },
    TransferParams { destination: "DAG...2".to_string(), amount: 20.0, fee: 0.0 },
    TransferParams { destination: "DAG...3".to_string(), amount: 30.0, fee: 0.0 },
];

// Create batch (transactions are automatically chained)
let txns = create_currency_transaction_batch(
    transfers,
    &private_key,
    last_ref,
)?;

// txns[0].value.parent.ordinal == 10
// txns[1].value.parent.ordinal == 11
// txns[2].value.parent.ordinal == 12

Multi-Signature Token Transaction

use constellation_sdk::{
    create_currency_transaction,
    sign_currency_transaction,
    verify_currency_transaction,
    TransferParams,
    TransactionReference,
};

let key1 = generate_key_pair();
let key2 = generate_key_pair();
let recipient = generate_key_pair();

let last_ref = TransactionReference {
    hash: "abc123...".to_string(),
    ordinal: 0,
};

// Create transaction with first signature
let mut tx = create_currency_transaction(
    TransferParams {
        destination: recipient.address.clone(),
        amount: 100.0,
        fee: 0.0,
    },
    &key1.private_key,
    last_ref,
)?;

// Add second signature
tx = sign_currency_transaction(&tx, &key2.private_key)?;

// Verify both signatures
let result = verify_currency_transaction(&tx);
println!("{} valid signatures", result.valid_proofs.len());

Development

# Run tests
cargo test

# Run tests with output
cargo test -- --nocapture

# Check for issues
cargo clippy

# Format code
cargo fmt

# Build release
cargo build --release

License

Apache-2.0