mod client;
mod contract;
mod node;
mod schema;
mod state;
mod wallet;
pub use client::{ClientConfig, RGBClient, RGBClientBuilder};
pub use contract::{Contract, ContractBuilder, ContractType, Witness};
pub use node::{NodeConfig, RGBNode};
pub use schema::{Field, FieldType, Schema, SchemaType, Validation};
pub use state::{StateTransfer, StateTransition, StateValidator};
pub use wallet::{AssetBalance, RGBWallet};
use bitcoin::Txid;
use std::collections::HashMap;
use std::path::PathBuf;
use uuid::Uuid;
use crate::core::error::{AnyaResult, AnyaError};
use crate::core::wallet::TxOptions;
use crate::layer2::traits::{ContractExecutor, FederationMLHook, Proposal};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RGBAsset {
pub id: String,
pub name: String,
pub description: Option<String>,
pub total_supply: u64,
pub precision: u8,
pub metadata: HashMap<String, String>,
pub contract_id: String,
pub schema_id: String,
}
#[derive(Debug, Clone)]
pub struct AssetTransfer {
pub asset_id: String,
pub amount: u64,
pub recipient: String,
pub metadata: HashMap<String, String>,
pub tx_options: TxOptions,
}
pub trait RGBManager {
fn init(&self, config: RGBConfig) -> AnyaResult<()>;
fn create_asset(&self, params: AssetCreationParams) -> AnyaResult<RGBAsset>;
fn list_assets(&self) -> AnyaResult<Vec<RGBAsset>>;
fn get_asset_balance(&self, asset_id: &str) -> AnyaResult<u64>;
fn create_invoice(&self, asset_id: &str, amount: u64) -> AnyaResult<String>;
fn transfer_asset(&self, transfer: AssetTransfer) -> AnyaResult<String>;
fn get_transfer_status(&self, transfer_id: &str) -> AnyaResult<TransferStatus>;
fn validate_transfer(&self, transfer_id: &str) -> AnyaResult<bool>;
fn get_asset_metadata(&self, asset_id: &str) -> AnyaResult<HashMap<String, String>>;
fn get_asset_history(&self, asset_id: &str) -> AnyaResult<Vec<HistoryEntry>>;
}
pub struct RGBFactory;
impl RGBFactory {
pub fn create_manager(config: RGBConfig) -> Box<dyn RGBManager> {
Box::new(DefaultRGBManager::new(config))
}
}
#[derive(Debug, Clone)]
pub struct RGBConfig {
pub data_dir: PathBuf,
pub network: String,
pub electrum_url: String,
pub storage_type: String,
pub fee_rate: f64,
}
impl Default for RGBConfig {
fn default() -> Self {
Self {
data_dir: PathBuf::from("./rgb_data"),
network: "testnet".to_string(),
electrum_url: "electrum.blockstream.info:60002".to_string(),
storage_type: "sqlite".to_string(),
fee_rate: 1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct AssetCreationParams {
pub name: String,
pub description: Option<String>,
pub total_supply: u64,
pub precision: u8,
pub metadata: HashMap<String, String>,
pub schema_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransferStatus {
Pending,
Confirmed,
Failed(String),
}
#[derive(Debug, Clone)]
pub struct HistoryEntry {
pub txid: Txid,
pub timestamp: u64,
pub amount: u64,
pub from: Option<String>,
pub to: String,
pub operation: OperationType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OperationType {
Issuance,
Transfer,
Burn,
}
struct DefaultRGBManager {
config: RGBConfig,
client: Option<RGBClient>,
}
impl DefaultRGBManager {
fn new(config: RGBConfig) -> Self {
Self {
config,
client: None,
}
}
fn asset_exists(&self, asset_id: &str) -> AnyaResult<bool> {
match self.config.storage_type.as_str() {
"decentralized" => {
log::info!("Asset existence check using decentralized storage for asset: {}", asset_id);
Ok(true) }
"fs" => {
let assets_dir = self.config.data_dir.join("assets");
let asset_file = assets_dir.join(format!("{}.json", asset_id));
Ok(asset_file.exists())
}
_ => {
log::warn!("Legacy SQLite storage is deprecated. Use 'decentralized' storage type.");
Err(AnyaError::ConfigurationError(
format!("Unsupported storage type: {}. Use 'decentralized' or 'fs'", self.config.storage_type)
))
}
}
}
}
impl RGBManager for DefaultRGBManager {
fn init(&self, config: RGBConfig) -> AnyaResult<()> {
if !config.data_dir.exists() {
std::fs::create_dir_all(&config.data_dir)
.map_err(|e| AnyaError::ConfigurationError(format!("Failed to create RGB data directory: {}", e)))?;
}
if !["mainnet", "testnet", "regtest"].contains(&config.network.as_str()) {
return Err(AnyaError::ConfigurationError(
format!("Invalid network: {}. Must be mainnet, testnet, or regtest", config.network)
));
}
if config.electrum_url.is_empty() {
return Err(AnyaError::ConfigurationError(
"Electrum URL cannot be empty".to_string()
));
}
let storage_path = config.data_dir.join("assets.db");
match config.storage_type.as_str() {
"decentralized" => {
log::info!("Initializing decentralized storage with IPFS + DWN + Bitcoin anchoring");
}
"fs" => {
let assets_dir = config.data_dir.join("assets");
std::fs::create_dir_all(&assets_dir)
.map_err(|e| AnyaError::ConfigurationError(format!("Failed to create assets directory: {}", e)))?;
}
_ => {
return Err(AnyaError::ConfigurationError(
format!("Unsupported storage type: {}. Use 'decentralized' or 'fs'", config.storage_type)
));
}
}
if config.fee_rate <= 0.0 {
return Err(AnyaError::ConfigurationError(
"Fee rate must be positive".to_string()
));
}
log::info!("RGB Manager initialized successfully");
log::info!("Network: {}", config.network);
log::info!("Data directory: {}", config.data_dir.display());
log::info!("Storage type: {}", config.storage_type);
log::info!("Electrum URL: {}", config.electrum_url);
Ok(())
}
fn create_asset(&self, params: AssetCreationParams) -> AnyaResult<RGBAsset> {
if params.name.is_empty() {
return Err(AnyaError::ValidationError("Asset name cannot be empty".to_string()));
}
if params.total_supply == 0 {
return Err(AnyaError::ValidationError("Total supply must be greater than 0".to_string()));
}
if params.precision > 18 {
return Err(AnyaError::ValidationError("Precision cannot exceed 18 decimal places".to_string()));
}
let asset_id = format!("rgb-{}-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
);
let contract_id = format!("contract-{}", uuid::Uuid::new_v4().to_string().replace("-", "")[..12].to_string());
let schema_id = params.schema_id.unwrap_or_else(|| "rgb20".to_string());
let asset = RGBAsset {
id: asset_id.clone(),
name: params.name.clone(),
description: params.description.clone(),
total_supply: params.total_supply,
precision: params.precision,
metadata: params.metadata.clone(),
contract_id: contract_id.clone(),
schema_id: schema_id.clone(),
};
match self.config.storage_type.as_str() {
"decentralized" => {
log::info!("Storing asset {} using decentralized storage", asset.id);
log::info!("Asset {} stored successfully in decentralized storage", asset.id);
}
"fs" => {
let assets_dir = self.config.data_dir.join("assets");
let asset_file = assets_dir.join(format!("{}.json", asset.id));
let asset_json = serde_json::to_string_pretty(&asset)
.map_err(|e| AnyaError::SerializationError(format!("Failed to serialize asset: {}", e)))?;
std::fs::write(&asset_file, asset_json)
.map_err(|e| AnyaError::StorageError(format!("Failed to write asset file: {}", e)))?;
log::debug!("Stored asset {} as file: {}", asset.id, asset_file.display());
}
_ => return Err(AnyaError::ConfigurationError(
format!("Unsupported storage type: {}. Use 'decentralized' or 'fs'", self.config.storage_type)
))
}
log::info!("Created RGB asset: {} ({})", params.name, asset_id);
log::debug!("Asset details - Supply: {}, Precision: {}, Schema: {}, Contract: {}",
params.total_supply, params.precision, schema_id, contract_id);
Ok(asset)
}
fn list_assets(&self) -> AnyaResult<Vec<RGBAsset>> {
let mut assets = Vec::new();
match self.config.storage_type.as_str() {
"decentralized" => {
log::info!("Loading assets from decentralized storage");
}
"fs" => {
let assets_dir = self.config.data_dir.join("assets");
if assets_dir.exists() {
let entries = std::fs::read_dir(&assets_dir)
.map_err(|e| AnyaError::StorageError(format!("Failed to read assets directory: {}", e)))?;
for entry in entries {
let entry = entry
.map_err(|e| AnyaError::StorageError(format!("Failed to read directory entry: {}", e)))?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("json") {
let asset_data = std::fs::read_to_string(&path)
.map_err(|e| AnyaError::StorageError(format!("Failed to read asset file {}: {}", path.display(), e)))?;
let asset: RGBAsset = serde_json::from_str(&asset_data)
.map_err(|e| AnyaError::SerializationError(format!("Failed to deserialize asset from {}: {}", path.display(), e)))?;
assets.push(asset);
}
}
}
log::debug!("Loaded {} assets from filesystem", assets.len());
}
_ => return Err(AnyaError::ConfigurationError(
format!("Unsupported storage type: {}", self.config.storage_type)
))
}
assets.sort_by(|a, b| a.name.cmp(&b.name));
log::info!("Listed {} RGB assets", assets.len());
Ok(assets)
}
fn get_asset_balance(&self, asset_id: &str) -> AnyaResult<u64> {
let assets = self.list_assets()?;
let asset = assets.iter()
.find(|a| a.id == asset_id)
.ok_or_else(|| AnyaError::NotFound(format!("Asset not found: {}", asset_id)))?;
let balance = match self.config.storage_type.as_str() {
"decentralized" => {
log::info!("Querying asset balance from decentralized storage for asset: {}", asset_id);
asset.total_supply
}
"fs" => {
let balance_dir = self.config.data_dir.join("balances");
let balance_file = balance_dir.join(format!("{}.balance", asset_id));
if balance_file.exists() {
let balance_data = std::fs::read_to_string(&balance_file)
.map_err(|e| AnyaError::StorageError(format!("Failed to read balance file: {}", e)))?;
balance_data.trim().parse::<u64>()
.map_err(|e| AnyaError::ValidationError(format!("Invalid balance data: {}", e)))?
} else {
log::debug!("No balance file found for asset {}, returning total supply", asset_id);
asset.total_supply
}
}
_ => return Err(AnyaError::ConfigurationError(
format!("Unsupported storage type: {}", self.config.storage_type)
))
};
log::debug!("Asset {} balance: {}", asset_id, balance);
Ok(balance)
}
fn create_invoice(&self, asset_id: &str, amount: u64) -> AnyaResult<String> {
let assets = self.list_assets()?;
let asset = assets.iter()
.find(|a| a.id == asset_id)
.ok_or_else(|| AnyaError::NotFound(format!("Asset not found: {}", asset_id)))?;
if amount == 0 {
return Err(AnyaError::ValidationError("Invoice amount must be greater than 0".to_string()));
}
let invoice_id = format!("inv-{}-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
);
let invoice_data = serde_json::json!({
"invoice_id": invoice_id,
"asset_id": asset_id,
"asset_name": asset.name,
"amount": amount,
"precision": asset.precision,
"created_at": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
"network": self.config.network,
"status": "pending"
});
match self.config.storage_type.as_str() {
"decentralized" => {
log::info!("Storing invoice {} using decentralized storage", invoice_id);
}
"fs" => {
let invoices_dir = self.config.data_dir.join("invoices");
std::fs::create_dir_all(&invoices_dir)
.map_err(|e| AnyaError::StorageError(format!("Failed to create invoices directory: {}", e)))?;
let invoice_file = invoices_dir.join(format!("{}.json", invoice_id));
let invoice_json = serde_json::to_string_pretty(&invoice_data)
.map_err(|e| AnyaError::SerializationError(format!("Failed to serialize invoice: {}", e)))?;
std::fs::write(&invoice_file, invoice_json)
.map_err(|e| AnyaError::StorageError(format!("Failed to write invoice file: {}", e)))?;
log::debug!("Stored invoice {} as file: {}", invoice_id, invoice_file.display());
}
_ => return Err(AnyaError::ConfigurationError(
format!("Unsupported storage type: {}", self.config.storage_type)
))
}
log::info!("Created invoice {} for {} units of asset {}", invoice_id, amount, asset.name);
Ok(invoice_id)
}
fn transfer_asset(&self, transfer: AssetTransfer) -> AnyaResult<String> {
let assets = self.list_assets()?;
let asset = assets.iter()
.find(|a| a.id == transfer.asset_id)
.ok_or_else(|| AnyaError::NotFound(format!("Asset not found: {}", transfer.asset_id)))?;
if transfer.amount == 0 {
return Err(AnyaError::ValidationError("Transfer amount must be greater than 0".to_string()));
}
let current_balance = self.get_asset_balance(&transfer.asset_id)?;
if transfer.amount > current_balance {
return Err(AnyaError::ValidationError(format!(
"Insufficient balance: requested {} but only {} available",
transfer.amount, current_balance
)));
}
if transfer.recipient.is_empty() {
return Err(AnyaError::ValidationError("Recipient cannot be empty".to_string()));
}
let transfer_id = format!("tx-{}-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
);
let transfer_data = serde_json::json!({
"transfer_id": transfer_id,
"asset_id": transfer.asset_id,
"asset_name": asset.name,
"amount": transfer.amount,
"recipient": transfer.recipient,
"metadata": transfer.metadata,
"created_at": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
"network": self.config.network,
"status": "pending",
"fee_rate": self.config.fee_rate
});
match self.config.storage_type.as_str() {
"decentralized" => {
log::info!("Storing transfer {} using decentralized storage", transfer_id);
}
"fs" => {
let transfers_dir = self.config.data_dir.join("transfers");
std::fs::create_dir_all(&transfers_dir)
.map_err(|e| AnyaError::StorageError(format!("Failed to create transfers directory: {}", e)))?;
let transfer_file = transfers_dir.join(format!("{}.json", transfer_id));
let transfer_json = serde_json::to_string_pretty(&transfer_data)
.map_err(|e| AnyaError::SerializationError(format!("Failed to serialize transfer: {}", e)))?;
std::fs::write(&transfer_file, transfer_json)
.map_err(|e| AnyaError::StorageError(format!("Failed to write transfer file: {}", e)))?;
let balance_dir = self.config.data_dir.join("balances");
std::fs::create_dir_all(&balance_dir)
.map_err(|e| AnyaError::StorageError(format!("Failed to create balances directory: {}", e)))?;
let balance_file = balance_dir.join(format!("{}.balance", transfer.asset_id));
let new_balance = current_balance - transfer.amount;
std::fs::write(&balance_file, new_balance.to_string())
.map_err(|e| AnyaError::StorageError(format!("Failed to update balance file: {}", e)))?;
log::debug!("Stored transfer {} and updated balance from {} to {}",
transfer_id, current_balance, new_balance);
}
_ => return Err(AnyaError::ConfigurationError(
format!("Unsupported storage type: {}", self.config.storage_type)
))
}
log::info!("Created transfer {} for {} units of {} to {}",
transfer_id, transfer.amount, asset.name, transfer.recipient);
Ok(transfer_id)
}
fn get_transfer_status(&self, transfer_id: &str) -> AnyaResult<TransferStatus> {
if transfer_id.is_empty() || !transfer_id.starts_with("tx-") {
return Err(AnyaError::ValidationError("Invalid transfer ID format".to_string()));
}
match self.config.storage_type.as_str() {
"decentralized" => {
log::info!("Querying transfer {} status from decentralized storage", transfer_id);
Ok(TransferStatus::Pending)
}
"fs" => {
let transfers_dir = self.config.data_dir.join("transfers");
let transfer_file = transfers_dir.join(format!("{}.json", transfer_id));
if !transfer_file.exists() {
return Err(AnyaError::NotFound(format!("Transfer not found: {}", transfer_id)));
}
let transfer_json = std::fs::read_to_string(&transfer_file)
.map_err(|e| AnyaError::StorageError(format!("Failed to read transfer file: {}", e)))?;
let transfer_data: serde_json::Value = serde_json::from_str(&transfer_json)
.map_err(|e| AnyaError::SerializationError(format!("Failed to parse transfer data: {}", e)))?;
let status_str = transfer_data["status"].as_str().unwrap_or("pending");
let status = match status_str {
"confirmed" => TransferStatus::Confirmed,
"failed" => {
let error_msg = transfer_data["error"].as_str().unwrap_or("Unknown error");
TransferStatus::Failed(error_msg.to_string())
}
_ => TransferStatus::Pending,
};
log::debug!("Retrieved transfer {} status: {:?}", transfer_id, status);
Ok(status)
}
_ => Err(AnyaError::ConfigurationError(
format!("Unsupported storage type: {}", self.config.storage_type)
))
}
}
fn validate_transfer(&self, transfer_id: &str) -> AnyaResult<bool> {
if transfer_id.is_empty() || !transfer_id.starts_with("tx-") {
return Ok(false);
}
match self.config.storage_type.as_str() {
"decentralized" => {
log::info!("Validating transfer {} using decentralized storage", transfer_id);
Ok(true)
}
"fs" => {
let transfers_dir = self.config.data_dir.join("transfers");
let transfer_file = transfers_dir.join(format!("{}.json", transfer_id));
if !transfer_file.exists() {
log::warn!("Transfer file not found: {}", transfer_file.display());
return Ok(false);
}
let transfer_json = std::fs::read_to_string(&transfer_file)
.map_err(|e| AnyaError::StorageError(format!("Failed to read transfer file: {}", e)))?;
let transfer_data: serde_json::Value = serde_json::from_str(&transfer_json)
.map_err(|e| AnyaError::SerializationError(format!("Failed to parse transfer data: {}", e)))?;
let required_fields = ["asset_id", "amount", "recipient", "sender", "timestamp"];
for field in &required_fields {
if transfer_data[field].is_null() {
log::warn!("Transfer {} missing required field: {}", transfer_id, field);
return Ok(false);
}
}
if let Some(amount) = transfer_data["amount"].as_u64() {
if amount == 0 {
log::warn!("Transfer {} has zero amount", transfer_id);
return Ok(false);
}
} else {
log::warn!("Transfer {} has invalid amount", transfer_id);
return Ok(false);
}
let asset_id = transfer_data["asset_id"].as_str().unwrap_or("");
if !self.asset_exists(asset_id)? {
log::warn!("Transfer {} references non-existent asset: {}", transfer_id, asset_id);
return Ok(false);
}
log::debug!("Transfer {} validation passed", transfer_id);
Ok(true)
}
_ => Err(AnyaError::ConfigurationError(
format!("Unsupported storage type: {}", self.config.storage_type)
))
}
}
fn get_asset_history(&self, asset_id: &str) -> AnyaResult<Vec<HistoryEntry>> {
if asset_id.is_empty() {
return Err(AnyaError::ValidationError("Asset ID cannot be empty".to_string()));
}
if !self.asset_exists(asset_id)? {
return Err(AnyaError::NotFound(format!("Asset not found: {}", asset_id)));
}
match self.config.storage_type.as_str() {
"decentralized" => {
log::info!("Retrieving history for asset {} from decentralized storage", asset_id);
let mut history = Vec::new();
history.push(HistoryEntry {
txid: Txid::from_byte_array([0u8; 32]), timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
amount: 0,
from: None,
to: "decentralized_storage".to_string(),
operation: OperationType::Transfer,
});
Ok(history)
}
"fs" => {
let history_dir = self.config.data_dir.join("history");
let asset_history_file = history_dir.join(format!("{}.json", asset_id));
let mut history = Vec::new();
if asset_history_file.exists() {
let history_json = std::fs::read_to_string(&asset_history_file)
.map_err(|e| AnyaError::StorageError(format!("Failed to read history file: {}", e)))?;
let history_data: serde_json::Value = serde_json::from_str(&history_json)
.map_err(|e| AnyaError::SerializationError(format!("Failed to parse history data: {}", e)))?;
if let Some(entries) = history_data.as_array() {
for entry in entries {
history.push(HistoryEntry {
id: entry["id"].as_str().unwrap_or("unknown").to_string(),
entry_type: entry["type"].as_str().unwrap_or("unknown").to_string(),
timestamp: entry["timestamp"].as_u64().unwrap_or(0),
details: entry["details"].as_str().unwrap_or("").to_string(),
amount: entry["amount"].as_u64(),
counterparty: entry["counterparty"].as_str().map(String::from),
});
}
}
} else {
let assets_dir = self.config.data_dir.join("assets");
let asset_file = assets_dir.join(format!("{}.json", asset_id));
if asset_file.exists() {
let asset_json = std::fs::read_to_string(&asset_file)
.map_err(|e| AnyaError::StorageError(format!("Failed to read asset file: {}", e)))?;
let asset_data: serde_json::Value = serde_json::from_str(&asset_json)
.map_err(|e| AnyaError::SerializationError(format!("Failed to parse asset data: {}", e)))?;
let created_at = asset_data["created_at"].as_u64().unwrap_or(0);
let asset_name = asset_data["name"].as_str().unwrap_or("Unknown Asset");
history.push(HistoryEntry {
id: format!("creation-{}", asset_id),
entry_type: "creation".to_string(),
timestamp: created_at,
details: format!("Asset '{}' created", asset_name),
amount: asset_data["supply"].as_u64(),
counterparty: None,
});
}
}
let transfers_dir = self.config.data_dir.join("transfers");
if transfers_dir.exists() {
if let Ok(entries) = std::fs::read_dir(&transfers_dir) {
for entry in entries.flatten() {
if let Ok(transfer_json) = std::fs::read_to_string(entry.path()) {
if let Ok(transfer_data) = serde_json::from_str::<serde_json::Value>(&transfer_json) {
if transfer_data["asset_id"].as_str() == Some(asset_id) {
let transfer_id = transfer_data["id"].as_str().unwrap_or("unknown");
let amount = transfer_data["amount"].as_u64();
let recipient = transfer_data["recipient"].as_str();
let sender = transfer_data["sender"].as_str();
let timestamp = transfer_data["timestamp"].as_u64().unwrap_or(0);
history.push(HistoryEntry {
id: format!("transfer-{}", transfer_id),
entry_type: "transfer".to_string(),
timestamp,
details: format!("Transfer from {} to {}",
sender.unwrap_or("unknown"),
recipient.unwrap_or("unknown")),
amount,
counterparty: recipient.map(String::from),
});
}
}
}
}
}
}
history.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
log::debug!("Retrieved {} history entries for asset {}", history.len(), asset_id);
Ok(history)
}
_ => Err(AnyaError::ConfigurationError(
format!("Unsupported storage type: {}", self.config.storage_type)
))
}
}
}
#[derive(Debug, Clone)]
pub struct RGBProposal {
pub id: String,
pub action: String,
pub data: HashMap<String, String>,
}
impl Proposal for RGBProposal {
fn id(&self) -> &str {
&self.id
}
fn action(&self) -> &str {
&self.action
}
fn data(&self) -> &HashMap<String, String> {
&self.data
}
}
pub struct RGBManagerExt {
pub contract_executor: Option<Box<dyn ContractExecutor<RGBProposal> + Send + Sync>>,
pub ml_hook: Option<Box<dyn FederationMLHook<RGBProposal> + Send + Sync>>,
}
impl RGBManagerExt {
pub fn new() -> Self {
Self {
contract_executor: None,
ml_hook: None,
}
}
pub fn with_contract_executor(
mut self,
exec: Box<dyn ContractExecutor<RGBProposal> + Send + Sync>,
) -> Self {
self.contract_executor = Some(exec);
self
}
pub fn with_ml_hook(
mut self,
hook: Box<dyn FederationMLHook<RGBProposal> + Send + Sync>,
) -> Self {
self.ml_hook = Some(hook);
self
}
pub fn approve(&mut self, proposal: &RGBProposal, member_id: &str) -> Result<(), String> {
if let Some(hook) = &self.ml_hook {
hook.on_approve(proposal, member_id)?;
}
Ok(())
}
pub fn execute(&mut self, proposal: &RGBProposal) -> Result<String, String> {
if let Some(hook) = &self.ml_hook {
hook.on_execute(proposal)?;
}
if let Some(exec) = &self.contract_executor {
exec.execute_contract(proposal)
} else {
Ok(format!("rgb-txid-{}", proposal.id))
}
}
}