Skip to main content
This guide demonstrates common patterns for interacting with a TAPLE node, including creating governance, handling notifications, and performing API operations.

Complete Example

Here’s a complete example from the TAPLE Core repository that demonstrates basic node usage:
use std::str::FromStr;

use taple_core::crypto::*;
use taple_core::request::*;
use taple_core::signature::*;
use taple_core::*;

#[tokio::main]
async fn main() {
    // Generate random node ID key pair
    let node_key_pair = crypto::KeyPair::Ed25519(Ed25519KeyPair::from_seed(&[]));

    // Configure minimal settings
    let settings = {
        let mut settings = Settings::default();
        settings.node.secret_key = hex::encode(node_key_pair.secret_key_bytes());
        settings
    };

    // Build node
    let (mut node, api) = Node::build(settings, MemoryManager::new())
        .expect("TAPLE node built");

    // Create a minimal governance
    let governance_key = api
        .add_keys(KeyDerivator::Ed25519)
        .await
        .expect("Error getting server response");
    
    let create_subject_request = EventRequest::Create(StartRequest {
        governance_id: DigestIdentifier::default(),
        name: "".to_string(),
        namespace: "".to_string(),
        schema_id: "governance".to_string(),
        public_key: governance_key,
    });
    
    let signed_request = Signed::<EventRequest> {
        content: create_subject_request.clone(),
        signature: Signature::new(&create_subject_request, &node_key_pair).unwrap(),
    };

    // Send the signed request to the node
    let _request_id = api.external_request(signed_request).await.unwrap();

    // Wait until event notification
    let subject_id = if let Notification::NewEvent { sn: _, subject_id } = node
        .recv_notification()
        .await
        .expect("NewEvent notification received")
    {
        subject_id
    } else {
        panic!("Unexpected notification");
    };

    // Get the new subject data
    let subject = api
        .get_subject(DigestIdentifier::from_str(&subject_id).unwrap())
        .await
        .unwrap_or_else(|_| panic!("Error getting subject"));

    println!("Subject ID: {}", subject.subject_id.to_str());

    node.shutdown_gracefully().await;
}
This example is available at /home/daytona/workspace/source/examples/basic_usage/src/main.rs:1.

Key Concepts

Node Initialization

1

Generate a key pair

Every node requires a cryptographic key pair for identity:
let node_key_pair = crypto::KeyPair::Ed25519(
    Ed25519KeyPair::from_seed(&[])
);
For production, use a secure seed or load existing keys.
2

Configure settings

let settings = {
    let mut settings = Settings::default();
    settings.node.secret_key = hex::encode(node_key_pair.secret_key_bytes());
    settings
};
3

Build the node

let (mut node, api) = Node::build(settings, MemoryManager::new())
    .expect("TAPLE node built");
Returns a tuple of:
  • node - For handling notifications
  • api - For performing operations

Working with Keys

Generate additional keys for signing requests:
let governance_key = api
    .add_keys(KeyDerivator::Ed25519)
    .await
    .expect("Error getting server response");
Available key derivators:
  • KeyDerivator::Ed25519
  • KeyDerivator::Secp256k1 (requires secp256k1 feature)

Creating Requests

All write operations require creating and signing an EventRequest:
// Create the request
let create_subject_request = EventRequest::Create(StartRequest {
    governance_id: DigestIdentifier::default(),
    name: "".to_string(),
    namespace: "".to_string(),
    schema_id: "governance".to_string(),
    public_key: governance_key,
});

// Sign the request
let signed_request = Signed::<EventRequest> {
    content: create_subject_request.clone(),
    signature: Signature::new(&create_subject_request, &node_key_pair).unwrap(),
};

// Submit to the node
let request_id = api.external_request(signed_request).await.unwrap();
See /home/daytona/workspace/source/core/src/node.rs:71 for the node build implementation.

Handling Notifications

The node communicates state changes through notifications. You must consume notifications to prevent blocking:

Single Notification

let notification = node.recv_notification().await;

if let Some(Notification::NewEvent { sn, subject_id }) = notification {
    println!("New event #{} for subject {}", sn, subject_id);
}

Notification Handler

node.handle_notifications(|notification| {
    match notification {
        Notification::NewEvent { sn, subject_id } => {
            println!("Event #{}: {}", sn, subject_id);
        }
        _ => {}
    }
}).await;

Drop Notifications

If you don’t need to process notifications:
tokio::spawn(async move {
    node.drop_notifications().await;
});
See /home/daytona/workspace/source/core/src/node.rs:356 for notification handling methods.

API Operations

The Api object provides methods for interacting with the node.

Reading Data

Get a Subject

let subject = api
    .get_subject(subject_id)
    .await?;

println!("Subject ID: {}", subject.subject_id.to_str());
println!("Governance: {}", subject.governance_id.to_str());
println!("Sequence Number: {}", subject.sn);

Get Events

// Get all events for a subject
let events = api
    .get_events(subject_id, None, None)
    .await?;

// Get events with pagination
let events = api
    .get_events(subject_id, Some(0), Some(10))
    .await?;

for event in events {
    println!("Event: {:?}", event);
}

Get Subjects

// Get all subjects in a namespace
let subjects = api
    .get_subjects("namespace".to_string(), None, None)
    .await?;

// Get governances
let governances = api
    .get_governances(String::new(), None, None)
    .await?;

// Get subjects by governance
let subjects = api
    .get_subjects_by_governance(governance_id, None, None)
    .await?;
See /home/daytona/workspace/source/core/src/api/api.rs:32 for the complete API reference.

Approval Operations

With the approval feature enabled, you can handle voting requests:
#[cfg(feature = "approval")]
{
    // Get pending approval requests
    let pending = api.get_pending_requests().await?;
    
    for request in pending {
        // Vote on a request
        let approval = api
            .approval_request(request.id, true)  // true = accept
            .await?;
            
        println!("Voted on request: {:?}", approval);
    }
}

Preauthorized Subjects

Allow the node to accept events for specific subjects:
use std::collections::HashSet;
use taple_core::KeyIdentifier;

let providers = HashSet::new();
// Add provider identifiers to the set

api.add_preauthorize_subject(&subject_id, &providers).await?;

Error Handling

All API operations return Result<T, ApiError>. Common error types:
  • ApiError::NotFound - Requested resource doesn’t exist
  • ApiError::InvalidParameters - Invalid identifier or parameters
  • ApiError::InternalError - Internal node error
Example error handling:
match api.get_subject(subject_id).await {
    Ok(subject) => println!("Found: {}", subject.subject_id.to_str()),
    Err(ApiError::NotFound) => println!("Subject not found"),
    Err(e) => eprintln!("Error: {:?}", e),
}

Best Practices

  1. Always handle notifications - Unprocessed notifications will block the node
  2. Use proper database - MemoryManager is only for testing; use persistent storage in production
  3. Secure key management - Never hardcode secret keys; use secure key storage
  4. Graceful shutdown - Always call shutdown_gracefully() to properly close the node
  5. Enable required features - Only enable features you need to minimize dependencies

Running the Example

To run the basic usage example from the repository:
cd examples/basic_usage
cargo run
You should see output similar to:
Controller ID: EtbFWPL6eU_YE2JqRb6xVdmVH14e6C28aBZ_VuOFYxuQ
Subject ID: JjyqcA-_hn5JfwkJXWGGGGVlQYe5H88ziHPJ_gGKLU-Q

Next Steps

Build docs developers (and LLMs) love