Private Transaction Builder

Private Transaction Builder - Complete API Reference

This document provides a comprehensive guide to building private transactions using the Roru SDK's TransactionBuilder API.

Overview

The TransactionBuilder provides a fluent, type-safe API for constructing private transactions. It handles note selection, change calculation, fee estimation, and transaction structure automatically.

Basic Usage

Simple Transaction

Rust Example:

use roru_sdk::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize client
    let client = RoruClient::new(ClientConfig::default()).await?;
    let wallet = client.create_wallet().await?;
    
    // Generate recipient address
    let recipient = wallet.generate_address()?;
    
    // Build transaction
    let tx = TransactionBuilder::new()
        .to(recipient)
        .amount(1_000_000) // 1.0 tokens (6 decimals)
        .build()?;
    
    // Send transaction
    let result = wallet.send_transaction(tx).await?;
    println!("Transaction sent: {:?}", result.tx_hash);
    
    Ok(())
}

TypeScript Example:

import { RoruClient, TransactionBuilder } from '@roru/sdk';

async function main() {
    const client = new RoruClient({
        infraEndpoint: 'https://infra.roru.labs'
    });
    
    const wallet = await client.createWallet();
    const recipient = wallet.generateAddress();
    
    const tx = new TransactionBuilder()
        .to(recipient)
        .amount(1_000_000) // 1.0 tokens
        .build();
    
    const result = await wallet.sendTransaction(tx);
    console.log('Transaction sent:', result.txHash);
}

main().catch(console.error);

Python Example:

from roru_sdk import RoruClient, TransactionBuilder

async def main():
    client = RoruClient()
    wallet = await client.create_wallet()
    recipient = wallet.generate_address()
    
    tx = TransactionBuilder() \
        .to(recipient) \
        .amount(1_000_000) \
        .build()
    
    result = await wallet.send_transaction(tx)
    print(f"Transaction sent: {result.tx_hash}")

asyncio.run(main())

Advanced Usage

Custom Input Selection

Rust:

// Manually select input notes
let input_notes = vec![
    wallet.get_note_by_index(0)?,
    wallet.get_note_by_index(1)?,
];

let tx = TransactionBuilder::new()
    .inputs(input_notes)
    .to(recipient)
    .amount(500_000)
    .build()?;

Multiple Outputs

Rust:

let recipient1 = wallet.generate_address()?;
let recipient2 = wallet.generate_address()?;

let tx = TransactionBuilder::new()
    .to(recipient1)
    .amount(500_000)
    .add_output(recipient2, 300_000)
    .build()?;

With Memo

Rust:

let memo = b"Payment for services";

let tx = TransactionBuilder::new()
    .to(recipient)
    .amount(1_000_000)
    .memo(memo)
    .build()?;

Custom Fee

Rust:

let tx = TransactionBuilder::new()
    .to(recipient)
    .amount(1_000_000)
    .fee(10_000) // Custom fee amount
    .build()?;

Priority Level

Rust:

let tx = TransactionBuilder::new()
    .to(recipient)
    .amount(1_000_000)
    .priority(Priority::High) // Fast settlement
    .build()?;

API Reference

TransactionBuilder Methods

new() -> TransactionBuilder

Creates a new transaction builder instance.

Returns: TransactionBuilder

Example:

let builder = TransactionBuilder::new();

to(address: ShieldedAddress) -> Self

Sets the primary recipient address.

Parameters:

  • address: The shielded address of the recipient

Returns: Self for method chaining

Example:

let tx = TransactionBuilder::new()
    .to(recipient_address)
    .amount(1_000_000)
    .build()?;

amount(value: u64) -> Self

Sets the transaction amount in smallest units.

Parameters:

  • value: Amount in smallest token unit (e.g., 1_000_000 = 1.0 tokens with 6 decimals)

Returns: Self for method chaining

Example:

let tx = TransactionBuilder::new()
    .to(recipient)
    .amount(1_000_000) // 1.0 tokens
    .build()?;

inputs(notes: Vec<Note>) -> Self

Manually specify input notes to use.

Parameters:

  • notes: Vector of notes to spend

Returns: Self for method chaining

Example:

let input_notes = wallet.get_notes()?;
let tx = TransactionBuilder::new()
    .inputs(input_notes)
    .to(recipient)
    .amount(500_000)
    .build()?;

add_output(address: ShieldedAddress, amount: u64) -> Self

Add an additional output to the transaction.

Parameters:

  • address: Recipient address

  • amount: Amount for this output

Returns: Self for method chaining

Example:

let tx = TransactionBuilder::new()
    .to(recipient1)
    .amount(500_000)
    .add_output(recipient2, 300_000)
    .build()?;

memo(data: &[u8]) -> Self

Add a memo field to the transaction.

Parameters:

  • data: Memo data (max 512 bytes)

Returns: Self for method chaining

Example:

let tx = TransactionBuilder::new()
    .to(recipient)
    .amount(1_000_000)
    .memo(b"Payment ID: 12345")
    .build()?;

fee(amount: u64) -> Self

Set a custom fee amount.

Parameters:

  • amount: Fee amount in smallest units

Returns: Self for method chaining

Example:

let tx = TransactionBuilder::new()
    .to(recipient)
    .amount(1_000_000)
    .fee(10_000)
    .build()?;

priority(level: Priority) -> Self

Set transaction priority level.

Parameters:

  • level: Priority::Low, Priority::Normal, or Priority::High

Returns: Self for method chaining

Example:

let tx = TransactionBuilder::new()
    .to(recipient)
    .amount(1_000_000)
    .priority(Priority::High)
    .build()?;

asset(asset_id: AssetId) -> Self

Specify the asset for this transaction.

Parameters:

  • asset_id: Asset identifier

Returns: Self for method chaining

Example:

let usdc = AssetId::new(ChainId::Ethereum, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let tx = TransactionBuilder::new()
    .to(recipient)
    .amount(1_000_000)
    .asset(usdc)
    .build()?;

expiry(timestamp: u64) -> Self

Set transaction expiry timestamp.

Parameters:

  • timestamp: Unix timestamp when transaction expires

Returns: Self for method chaining

Example:

let expiry = current_timestamp() + 3600; // 1 hour from now
let tx = TransactionBuilder::new()
    .to(recipient)
    .amount(1_000_000)
    .expiry(expiry)
    .build()?;

build() -> Result<Transaction>

Builds the final transaction.

Returns: Result<Transaction>

Errors:

  • InsufficientBalance: Not enough funds

  • InvalidAddress: Invalid recipient address

  • InvalidAmount: Invalid amount value

  • NoteSelectionFailed: Could not select appropriate notes

Example:

let tx = TransactionBuilder::new()
    .to(recipient)
    .amount(1_000_000)
    .build()?;

Transaction Structure

Transaction Type

pub struct Transaction {
    pub inputs: Vec<Input>,
    pub outputs: Vec<Output>,
    pub proof: Option<Proof>, // Generated during send
    pub nullifiers: Vec<Nullifier>,
    pub public_data: PublicData,
    pub signature: Option<Signature>, // Added during signing
    pub metadata: TransactionMetadata,
}

Input Structure

pub struct Input {
    pub note: Note,
    pub merkle_path: MerklePath,
    pub nullifier: Nullifier,
}

Output Structure

pub struct Output {
    pub commitment: Commitment,
    pub encrypted_note: EncryptedNote,
    pub recipient: ShieldedAddress,
}

Error Handling

Common Errors

InsufficientBalance:

match tx_builder.build() {
    Ok(tx) => { /* success */ }
    Err(RoruError::InsufficientBalance) => {
        println!("Not enough funds. Current balance: {}", wallet.get_balance()?);
    }
    Err(e) => { /* other error */ }
}

InvalidAddress:

match tx_builder.to(invalid_address).build() {
    Ok(tx) => { /* success */ }
    Err(RoruError::InvalidAddress(msg)) => {
        println!("Invalid address: {}", msg);
    }
    Err(e) => { /* other error */ }
}

Best Practices

1. Always Check Balance First

let balance = wallet.get_balance().await?;
if balance.shielded < amount {
    return Err(Error::InsufficientBalance);
}

2. Handle Change Properly

The builder automatically handles change, but you can verify:

let tx = builder.build()?;
if let Some(change) = tx.get_change() {
    println!("Change amount: {}", change);
}

3. Use Appropriate Priority

// For time-sensitive transactions
let tx = builder
    .priority(Priority::High)
    .build()?;

// For regular transactions
let tx = builder
    .priority(Priority::Normal)
    .build()?;

4. Validate Before Building

// Validate recipient address
if !wallet.is_valid_address(&recipient) {
    return Err(Error::InvalidAddress);
}

// Validate amount
if amount == 0 || amount > MAX_AMOUNT {
    return Err(Error::InvalidAmount);
}

Complete Example

Rust - Full Transaction Flow:

use roru_sdk::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize
    let config = ClientConfig {
        infra_endpoint: "https://infra.roru.labs".to_string(),
        api_key: Some(env::var("RORU_API_KEY")?),
        ..Default::default()
    };
    
    let client = RoruClient::new(config).await?;
    let wallet = client.load_wallet(&master_key).await?;
    
    // Sync state
    wallet.sync_state().await?;
    
    // Check balance
    let balance = wallet.get_balance().await?;
    println!("Current balance: {}", balance.shielded);
    
    // Generate recipient address
    let recipient = ShieldedAddress::from_string(
        "roru1abc123..."
    )?;
    
    // Build transaction
    let tx = TransactionBuilder::new()
        .to(recipient)
        .amount(1_000_000)
        .memo(b"Payment for services")
        .priority(Priority::Normal)
        .build()?;
    
    // Estimate fees
    let fee_estimate = wallet.estimate_fees(&tx).await?;
    println!("Estimated fee: {}", fee_estimate.total);
    
    // Send transaction
    let result = wallet.send_transaction(tx).await?;
    
    // Wait for confirmation
    let confirmation = wallet.wait_for_confirmation(
        result.tx_hash,
        Duration::from_secs(60)
    ).await?;
    
    println!("Transaction confirmed in block: {}", confirmation.block_number);
    
    Ok(())
}

Conclusion

The TransactionBuilder provides:

  • Type Safety: Compile-time validation

  • Flexibility: Multiple configuration options

  • Automatic Handling: Note selection, change, fees

  • Error Handling: Clear error messages

  • Developer Experience: Fluent, intuitive API

Use the builder for all private transaction creation in your applications.

Last updated