This document provides a comprehensive overview of all FFI implementations in the Tari project, their purposes, available functions, integration patterns, and usage examples for developers integrating Tari functionality into applications.
Tari provides Foreign Function Interface (FFI) libraries that enable integration with applications written in languages other than Rust. These C-compatible libraries expose Tari's core functionality for wallet operations, mining utilities, and blockchain interactions.
- minotari_wallet_ffi - Comprehensive wallet functionality for mobile and desktop applications
- minotari_mining_helper_ffi - Mining utilities for mining pools and stratum servers
- iOS - Static library (.a) integration
- Android - Dynamic library (.so) integration
- Linux - Dynamic library (.so)
- macOS - Dynamic library (.dylib)
- Windows - Dynamic library (.dll)
- C/C++ - Direct FFI usage
- JavaScript/Node.js - Via ffi-napi bindings
- Swift - iOS integration
- Java/Kotlin - Android integration
- Python - Via ctypes or cffi
- C#/.NET - Via P/Invoke
Library Name: libminotari_wallet_ffi
Header File: wallet.h
Source Location: base_layer/wallet_ffi/
Primary Use Cases: Mobile wallets, desktop applications, embedded wallet functionality
All FFI functions follow C-style memory management:
- Functions that create objects return pointers that must be destroyed
- Each create function has a corresponding destroy function
- Memory leaks will occur if destroy functions are not called
- All string returns must be freed with
string_destroy()
All functions use consistent error handling:
- Error output parameter (
error_out: *mut c_int) - Error codes defined in the library
- Zero (0) indicates success
- Non-zero values indicate specific error types
The wallet uses callback functions for asynchronous notifications:
- Transaction events (received, confirmed, mined)
- Balance updates
- Connectivity status changes
- Validation completion events
// Basic wallet instance
struct TariWallet;
// Transaction types
struct TariCompletedTransactions;
struct TariPendingInboundTransactions;
struct TariPendingOutboundTransactions;
// Address and key types
struct TariAddress;
struct TariPublicKey;
struct TariPrivateKey;
// Balance information
struct Balance;
// Collection types
struct TariPublicKeys;
struct TariSeedWords;
// Utility types
struct ByteVector;
struct TariVector;
struct TariUtxo;// Transport configuration
struct TransportConfig;
struct P2pConfig;
// Base node state
struct TariBaseNodeState;wallet_create
TariWallet* wallet_create(
CommsConfig* config, // Communications configuration
const char* log_path, // Log file path
unsigned int num_rolling_log_files, // Number of rolling log files
unsigned int size_per_log_file_bytes, // Size per log file
const char* passphrase, // Wallet passphrase (optional)
const char* seed_words, // Seed words for recovery (optional)
// Callback functions (16 callbacks)
void (*callback_received_transaction)(TariPendingInboundTransaction*),
void (*callback_received_transaction_reply)(TariCompletedTransaction*),
void (*callback_received_finalized_transaction)(TariCompletedTransaction*),
void (*callback_transaction_broadcast)(TariCompletedTransaction*),
void (*callback_transaction_mined)(TariCompletedTransaction*),
void (*callback_transaction_mined_unconfirmed)(TariCompletedTransaction*, uint64_t),
void (*callback_faux_transaction_confirmed)(TariCompletedTransaction*),
void (*callback_faux_transaction_unconfirmed)(TariCompletedTransaction*, uint64_t),
void (*callback_transaction_send_result)(uint64_t, TariTransactionSendStatus*),
void (*callback_transaction_cancellation)(TariCompletedTransaction*),
void (*callback_txo_validation_complete)(uint64_t, uint64_t),
void (*callback_balance_updated)(Balance*),
void (*callback_transaction_validation_complete)(uint64_t, uint8_t),
void (*callback_connectivity_status)(uint64_t, uint64_t),
bool* recovery_in_progress, // Output: recovery status
int* error_out // Error output
);wallet_destroy
void wallet_destroy(TariWallet* wallet);wallet_set_base_node_peer
bool wallet_set_base_node_peer(
TariWallet* wallet,
unsigned char* public_key, // Base node public key (32 bytes)
char* address, // Base node address
int* error_out
);wallet_set_num_confirmations_required
void wallet_set_num_confirmations_required(
TariWallet* wallet,
uint64_t num_confirmations,
int* error_out
);wallet_get_num_confirmations_required
uint64_t wallet_get_num_confirmations_required(
TariWallet* wallet,
int* error_out
);wallet_get_available_balance
uint64_t wallet_get_available_balance(
TariWallet* wallet,
int* error_out
);wallet_get_pending_incoming_balance
uint64_t wallet_get_pending_incoming_balance(
TariWallet* wallet,
int* error_out
);wallet_get_pending_outgoing_balance
uint64_t wallet_get_pending_outgoing_balance(
TariWallet* wallet,
int* error_out
);wallet_get_public_key
TariPublicKey* wallet_get_public_key(
TariWallet* wallet,
int* error_out
);wallet_get_address
TariAddress* wallet_get_address(
TariWallet* wallet,
int* error_out
);wallet_get_seed_words
TariSeedWords* wallet_get_seed_words(
TariWallet* wallet,
int* error_out
);wallet_send_transaction
uint64_t wallet_send_transaction(
TariWallet* wallet,
TariAddress* destination, // Recipient address
uint64_t amount, // Amount in MicroMinotari
uint64_t fee_per_gram, // Fee rate
char* message, // Transaction message
bool one_sided, // One-sided transaction flag
int* error_out
);wallet_burn_tari
uint64_t wallet_burn_tari(
TariWallet* wallet,
uint64_t amount, // Amount to burn
uint64_t fee_per_gram, // Fee rate
char* message, // Burn message
int* error_out
);wallet_get_completed_transactions
TariCompletedTransactions* wallet_get_completed_transactions(
TariWallet* wallet,
int* error_out
);wallet_get_pending_inbound_transactions
TariPendingInboundTransactions* wallet_get_pending_inbound_transactions(
TariWallet* wallet,
int* error_out
);wallet_get_pending_outbound_transactions
TariPendingOutboundTransactions* wallet_get_pending_outbound_transactions(
TariWallet* wallet,
int* error_out
);wallet_get_cancelled_transactions
TariCompletedTransactions* wallet_get_cancelled_transactions(
TariWallet* wallet,
int* error_out
);wallet_cancel_pending_transaction
bool wallet_cancel_pending_transaction(
TariWallet* wallet,
uint64_t transaction_id,
int* error_out
);wallet_coin_split
uint64_t wallet_coin_split(
TariWallet* wallet,
uint64_t amount_per_split, // Amount for each output
uint64_t split_count, // Number of outputs to create
uint64_t fee_per_gram, // Fee rate
char* msg, // Split message
uint64_t lock_height, // Lock height (0 for immediate)
int* error_out
);wallet_start_transaction_validation
uint64_t wallet_start_transaction_validation(
TariWallet* wallet,
int* error_out
);wallet_start_txo_validation
uint64_t wallet_start_txo_validation(
TariWallet* wallet,
int* error_out
);wallet_start_recovery
bool wallet_start_recovery(
TariWallet* wallet,
void (*callback_recovery_progress)(uint8_t, uint64_t, uint64_t),
int* error_out
);wallet_preview_coin_join
TariCoinPreview* wallet_preview_coin_join(
TariWallet* wallet,
TariVector* commitments, // UTXOs to join
uint64_t fee_per_gram, // Fee rate
int* error_out
);wallet_get_unspent_amounts
TariVector* wallet_get_unspent_amounts(
TariWallet* wallet,
int* error_out
);wallet_address_from_private_key
TariAddress* wallet_address_from_private_key(
TariPrivateKey* secret_key,
int* network,
int* error_out
);wallet_address_from_hex
TariAddress* wallet_address_from_hex(
const char* hex_string,
int* error_out
);basenode_state_get_node_id
ByteVector* basenode_state_get_node_id(
TariBaseNodeState* base_node_state,
int* error_out
);basenode_state_get_height_of_the_longest_chain
uint64_t basenode_state_get_height_of_the_longest_chain(
TariBaseNodeState* base_node_state,
int* error_out
);basenode_state_get_is_node_synced
bool basenode_state_get_is_node_synced(
TariBaseNodeState* base_node_state,
int* error_out
);string_destroy
void string_destroy(char* s);byte_vector_destroy
void byte_vector_destroy(ByteVector* bytes);tari_vector_destroy
void destroy_tari_vector(TariVector* vector);Library Name: libminotari_mining_helper_ffi
Header File: tari_mining_helper.h
Source Location: base_layer/tari_mining_helper_ffi/
Primary Use Cases: Mining pools, stratum servers, mining software integration
public_key_hex_validate
bool public_key_hex_validate(
const char* hex, // Hex-encoded public key
int* error_out
);inject_nonce
void inject_nonce(
ByteVector* header, // Block header bytes
unsigned long long nonce, // Nonce value to inject
int* error_out
);inject_coinbase
void inject_coinbase(
ByteVector* block_template_bytes, // Block template
unsigned long long coinbase_value, // Coinbase value
bool stealth_payment, // Stealth payment flag
bool revealed_value_proof, // Revealed value proof flag
const char* wallet_payment_address, // Payment address
const char* coinbase_extra, // Extra coinbase data
unsigned int network, // Network type (0=mainnet, 1=esmeralda, etc.)
int* error_out
);share_difficulty
unsigned long long share_difficulty(
ByteVector* header, // Block header
unsigned int network, // Network type
int* error_out
);share_validate
int share_validate(
ByteVector* header, // Block header
const char* hash, // Share hash
unsigned int network, // Network type
unsigned long long share_difficulty, // Required share difficulty
unsigned long long template_difficulty, // Required block difficulty
int* error_out
);Return Values for share_validate:
0- Valid Block (meets both share and block difficulty)1- Valid Share (meets share difficulty only)2- Invalid Share3- Invalid Difficulty
byte_vector_create
ByteVector* byte_vector_create(
const unsigned char* byte_array,
unsigned int element_count,
int* error_out
);byte_vector_get_length
unsigned int byte_vector_get_length(
const ByteVector* vec,
int* error_out
);byte_vector_get_at
unsigned char byte_vector_get_at(
ByteVector* ptr,
unsigned int position,
int* error_out
);byte_vector_destroy
void byte_vector_destroy(ByteVector* bytes);const ffi = require("ffi-napi");
const ref = require("ref-napi");
// Load the wallet library
const libWallet = ffi.Library("./libminotari_wallet_ffi.dylib", {
// Function definitions
wallet_create: ["pointer", [/* parameters */]],
wallet_destroy: ["void", ["pointer"]],
wallet_get_available_balance: ["uint64", ["pointer", "pointer"]],
// ... more functions
});const i32 = ref.types.int32;
let err = ref.alloc(i32);
let balance = libWallet.wallet_get_available_balance(wallet, err);
if (err.deref() !== 0) {
console.error("Error getting balance:", err.deref());
}const receivedTx = ffi.Callback("void", ["pointer"], function(ptr) {
console.log("Transaction received:", ptr);
});
const balanceUpdated = ffi.Callback("void", ["pointer"], function(ptr) {
console.log("Balance updated:", ptr);
});// In your bridging header
#import "wallet.h"
// Swift usage
class TariWallet {
private var walletPtr: OpaquePointer?
func create() throws {
var error: Int32 = 0
walletPtr = wallet_create(
config,
logPath,
numLogFiles,
logFileSize,
passphrase,
seedWords,
// ... callbacks
&error
)
if error != 0 {
throw WalletError.creationFailed(error)
}
}
deinit {
if let ptr = walletPtr {
wallet_destroy(ptr)
}
}
}let receivedTransactionCallback: @convention(c) (UnsafeMutablePointer<TariPendingInboundTransaction>?) -> Void = { transaction in
// Handle received transaction
NotificationCenter.default.post(name: .transactionReceived, object: transaction)
}public class TariWallet {
static {
System.loadLibrary("minotari_wallet_ffi");
}
private long walletPtr;
// Native method declarations
private native long walletCreate(
long commsConfig,
String logPath,
int numLogFiles,
int logFileSize,
String passphrase,
String seedWords,
// ... parameters
);
private native void walletDestroy(long walletPtr);
private native long walletGetAvailableBalance(long walletPtr);
// Callback interface
public interface TransactionCallback {
void onTransactionReceived(long transactionPtr);
void onBalanceUpdated(long balancePtr);
}
}class TariWalletManager {
private var wallet: TariWallet? = null
fun createWallet(config: WalletConfig) {
wallet = TariWallet().apply {
create(
config.commsConfig,
config.logPath,
// ... parameters
)
}
}
fun getBalance(): Long {
return wallet?.getAvailableBalance() ?: 0L
}
}import ctypes
from ctypes import POINTER, c_char_p, c_void_p, c_uint64, c_int
# Load library
lib = ctypes.CDLL('./libminotari_wallet_ffi.so')
# Define function signatures
lib.wallet_create.argtypes = [
c_void_p, # config
c_char_p, # log_path
# ... more parameters
]
lib.wallet_create.restype = c_void_p
lib.wallet_get_available_balance.argtypes = [c_void_p, POINTER(c_int)]
lib.wallet_get_available_balance.restype = c_uint64
class TariWallet:
def __init__(self):
self.wallet_ptr = None
def create(self, config):
error = c_int(0)
self.wallet_ptr = lib.wallet_create(
config,
b"/tmp/wallet.log",
# ... parameters
ctypes.byref(error)
)
if error.value != 0:
raise Exception(f"Wallet creation failed: {error.value}")
def get_balance(self):
error = c_int(0)
balance = lib.wallet_get_available_balance(
self.wallet_ptr,
ctypes.byref(error)
)
if error.value != 0:
raise Exception(f"Balance query failed: {error.value}")
return balanceusing System;
using System.Runtime.InteropServices;
public class TariWallet
{
const string DLL_NAME = "minotari_wallet_ffi.dll";
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr wallet_create(
IntPtr config,
[MarshalAs(UnmanagedType.LPStr)] string logPath,
uint numLogFiles,
uint logFileSize,
[MarshalAs(UnmanagedType.LPStr)] string passphrase,
[MarshalAs(UnmanagedType.LPStr)] string seedWords,
// ... callback parameters
out int error
);
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
public static extern void wallet_destroy(IntPtr wallet);
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
public static extern ulong wallet_get_available_balance(
IntPtr wallet,
out int error
);
// Callback delegates
public delegate void TransactionReceivedCallback(IntPtr transaction);
public delegate void BalanceUpdatedCallback(IntPtr balance);
}public class WalletManager
{
private IntPtr walletPtr;
public void CreateWallet(WalletConfig config)
{
int error;
walletPtr = TariWallet.wallet_create(
config.CommsConfigPtr,
config.LogPath,
config.NumLogFiles,
config.LogFileSize,
config.Passphrase,
config.SeedWords,
// ... callbacks
out error
);
if (error != 0)
{
throw new WalletException($"Failed to create wallet: {error}");
}
}
public ulong GetBalance()
{
int error;
var balance = TariWallet.wallet_get_available_balance(walletPtr, out error);
if (error != 0)
{
throw new WalletException($"Failed to get balance: {error}");
}
return balance;
}
}#include "tari_mining_helper.h"
int validate_share_submission(
const unsigned char* header_bytes,
size_t header_length,
const char* share_hash,
unsigned int network,
unsigned long long pool_difficulty,
unsigned long long network_difficulty
) {
int error = 0;
// Create byte vector from header
ByteVector* header = byte_vector_create(header_bytes, header_length, &error);
if (error != 0) {
return -1; // Creation failed
}
// Validate the share
int result = share_validate(
header,
share_hash,
network,
pool_difficulty,
network_difficulty,
&error
);
// Cleanup
byte_vector_destroy(header);
if (error != 0) {
return -1; // Validation failed
}
return result; // 0=valid block, 1=valid share, 2=invalid
}void process_block_template(
unsigned char* template_bytes,
size_t template_length,
const char* miner_address,
unsigned long long coinbase_value
) {
int error = 0;
// Create byte vector
ByteVector* template = byte_vector_create(template_bytes, template_length, &error);
if (error != 0) return;
// Inject coinbase
inject_coinbase(
template,
coinbase_value,
false, // not stealth
false, // use bulletproof+
miner_address,
"Pool mining", // coinbase extra
1, // esmeralda network
&error
);
if (error == 0) {
// Template ready for mining
distribute_to_miners(template);
}
byte_vector_destroy(template);
}# Build for current platform
cargo build --release --package minotari_wallet_ffi
# Cross-compile for iOS
cargo build --release --target aarch64-apple-ios --package minotari_wallet_ffi
# Cross-compile for Android
cargo build --release --target aarch64-linux-android --package minotari_wallet_ffi# Build mining helper
cargo build --release --package minotari_mining_helper_ffiThe C headers are automatically generated using cbindgen:
# In Cargo.toml
[build-dependencies]
cbindgen = "0.29"// In build.rs
use cbindgen::Config;
fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
cbindgen::Builder::new()
.with_crate(crate_dir)
.with_language(cbindgen::Language::C)
.generate()
.expect("Unable to generate bindings")
.write_to_file("wallet.h");
}#!/bin/bash
# mobile_build.sh for iOS
# Set targets
TARGETS="aarch64-apple-ios x86_64-apple-ios-sim"
# Build for each target
for TARGET in $TARGETS; do
cargo build --release --target $TARGET --package minotari_wallet_ffi
done
# Create universal library
lipo -create \
target/aarch64-apple-ios/release/libminotari_wallet_ffi.a \
target/x86_64-apple-ios-sim/release/libminotari_wallet_ffi.a \
-output libminotari_wallet_ffi_universal.a#!/bin/bash
# Android build script
# Set up NDK
export ANDROID_NDK_HOME="/path/to/ndk"
export PATH="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH"
# Build for Android architectures
TARGETS="aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android"
for TARGET in $TARGETS; do
cargo build --release --target $TARGET --package minotari_wallet_ffi
done// Common error codes
#define WALLET_SUCCESS 0
#define WALLET_NULL_ERROR 1
#define WALLET_ALLOCATION_ERROR 2
#define WALLET_INVALID_ARGUMENT 3
#define WALLET_INSUFFICIENT_FUNDS 4
#define WALLET_NETWORK_ERROR 5
#define WALLET_STORAGE_ERROR 6
#define WALLET_CRYPTO_ERROR 7
#define WALLET_INVALID_ADDRESS 8
#define WALLET_TRANSACTION_ERROR 9
#define WALLET_NOT_INITIALIZED 10// Mining helper error codes
#define MINING_SUCCESS 0
#define MINING_NULL_ERROR 1
#define MINING_INVALID_HEADER 2
#define MINING_INVALID_DIFFICULTY 3
#define MINING_NETWORK_ERROR 4
#define MINING_VALIDATION_ERROR 5- Always call destroy functions for created objects
- Check for null pointers before use
- Handle error codes consistently
- Free strings returned from FFI functions
- Always check error output parameters
- Implement proper error recovery
- Log errors for debugging
- Provide meaningful error messages to users
- FFI functions are not thread-safe unless documented
- Use proper synchronization when accessing from multiple threads
- Callback functions may be called from different threads
- Minimize FFI crossing overhead
- Batch operations when possible
- Cache frequently accessed data
- Use appropriate data structures for collections
- Validate all input parameters
- Protect sensitive data in memory
- Use secure random number generation
- Implement proper key management
This comprehensive overview provides all the essential information for integrating Tari's FFI libraries into applications across different platforms and programming languages, with detailed function references, integration patterns, and best practices.