522 lines
17 KiB
Plaintext
522 lines
17 KiB
Plaintext
//! ACC-721: 唯一资产证书协议接口
|
||
//!
|
||
//! 提供与NAC区块链上ACC-721证书交互的客户端接口
|
||
|
||
use crate::client::NRPC3Client;
|
||
use crate::error::{NACError, Result};
|
||
use crate::types::*;
|
||
use nac_udm::primitives::{Address, Hash, Timestamp};
|
||
use nac_udm::l1_protocol::gnacs::GNACSCode;
|
||
use nac_udm::l1_protocol::acc::acc721::{
|
||
AssetId, SovereigntyType, AssetDNA, AssetValuation,
|
||
CustodyInfo, InsuranceInfo, CollateralInfo, FragmentationPool,
|
||
};
|
||
use serde_json::json;
|
||
|
||
/// ACC-721唯一资产证书接口
|
||
pub struct ACC721 {
|
||
client: NRPC3Client,
|
||
}
|
||
|
||
impl ACC721 {
|
||
/// 创建新的ACC-721接口实例
|
||
pub fn new(client: NRPC3Client) -> Self {
|
||
Self { client }
|
||
}
|
||
|
||
/// 获取资产持有者
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `asset_id` - 资产ID
|
||
///
|
||
/// # 返回
|
||
/// 资产持有者地址
|
||
pub async fn get_asset_holder(
|
||
&self,
|
||
certificate_address: &Address,
|
||
asset_id: AssetId,
|
||
) -> Result<Address> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"asset_id": asset_id,
|
||
});
|
||
|
||
let response = self.client.call("acc721_getAssetHolder", params).await?;
|
||
|
||
let holder_hex = response["result"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing holder address".to_string()))?;
|
||
|
||
Address::from_hex(holder_hex)
|
||
.map_err(|e| NACError::InvalidAddress(format!("Invalid holder address: {}", e)))
|
||
}
|
||
|
||
/// 铸造唯一资产
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `to` - 接收者地址
|
||
/// * `asset_id` - 资产ID
|
||
/// * `metadata_uri` - 资产元数据URI
|
||
/// * `physical_fingerprint` - 物理指纹哈希
|
||
/// * `legal_document_hash` - 法律文件哈希
|
||
/// * `custodian` - 托管方地址
|
||
/// * `insurer` - 保险方地址
|
||
/// * `insurance_coverage` - 保险金额(XTZH)
|
||
/// * `insurance_expiry` - 保险到期时间
|
||
///
|
||
/// # 返回
|
||
/// 资产DNA
|
||
pub async fn mint_asset(
|
||
&self,
|
||
certificate_address: &Address,
|
||
to: &Address,
|
||
asset_id: AssetId,
|
||
metadata_uri: String,
|
||
physical_fingerprint: Hash,
|
||
legal_document_hash: Hash,
|
||
custodian: Address,
|
||
insurer: Address,
|
||
insurance_coverage: u128,
|
||
insurance_expiry: Timestamp,
|
||
) -> Result<AssetDNA> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"to": to,
|
||
"asset_id": asset_id,
|
||
"metadata_uri": metadata_uri,
|
||
"physical_fingerprint": physical_fingerprint,
|
||
"legal_document_hash": legal_document_hash,
|
||
"custodian": custodian,
|
||
"insurer": insurer,
|
||
"insurance_coverage": insurance_coverage,
|
||
"insurance_expiry": insurance_expiry,
|
||
});
|
||
|
||
let response = self.client.call("acc721_mintAsset", params).await?;
|
||
|
||
// 解析AssetDNA
|
||
let dna_data = &response["result"];
|
||
let asset_dna = AssetDNA {
|
||
dna_hash: Hash::from_hex(
|
||
dna_data["dna_hash"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing dna_hash".to_string()))?
|
||
)?,
|
||
physical_fingerprint,
|
||
legal_document_hash,
|
||
generated_at: Timestamp::from_secs(
|
||
dna_data["generated_at"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing generated_at".to_string()))?
|
||
),
|
||
};
|
||
|
||
Ok(asset_dna)
|
||
}
|
||
|
||
/// 转移唯一资产
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `from` - 发送者地址
|
||
/// * `to` - 接收者地址
|
||
/// * `asset_id` - 资产ID
|
||
///
|
||
/// # 返回
|
||
/// 宪法收据哈希
|
||
pub async fn transfer_asset(
|
||
&self,
|
||
certificate_address: &Address,
|
||
from: &Address,
|
||
to: &Address,
|
||
asset_id: AssetId,
|
||
) -> Result<Hash> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"from": from,
|
||
"to": to,
|
||
"asset_id": asset_id,
|
||
});
|
||
|
||
let response = self.client.call("acc721_transferAsset", params).await?;
|
||
|
||
let receipt_hash = response["result"]["constitutional_receipt"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing constitutional_receipt".to_string()))?;
|
||
|
||
Hash::from_hex(receipt_hash)
|
||
.map_err(|e| NACError::InvalidHash(format!("Invalid receipt hash: {}", e)))
|
||
}
|
||
|
||
/// 授权资产
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `owner` - 资产持有者地址
|
||
/// * `approved` - 被授权者地址
|
||
/// * `asset_id` - 资产ID
|
||
pub async fn approve_asset(
|
||
&self,
|
||
certificate_address: &Address,
|
||
owner: &Address,
|
||
approved: &Address,
|
||
asset_id: AssetId,
|
||
) -> Result<()> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"owner": owner,
|
||
"approved": approved,
|
||
"asset_id": asset_id,
|
||
});
|
||
|
||
self.client.call("acc721_approveAsset", params).await?;
|
||
Ok(())
|
||
}
|
||
|
||
/// 销毁资产
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `owner` - 资产持有者地址
|
||
/// * `asset_id` - 资产ID
|
||
pub async fn burn_asset(
|
||
&self,
|
||
certificate_address: &Address,
|
||
owner: &Address,
|
||
asset_id: AssetId,
|
||
) -> Result<()> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"owner": owner,
|
||
"asset_id": asset_id,
|
||
});
|
||
|
||
self.client.call("acc721_burnAsset", params).await?;
|
||
Ok(())
|
||
}
|
||
|
||
/// 碎片化资产
|
||
///
|
||
/// 将唯一资产碎片化为多个ACC-20代币
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `owner` - 资产持有者地址
|
||
/// * `asset_id` - 资产ID
|
||
/// * `fragment_count` - 碎片总数
|
||
/// * `fragment_price` - 碎片价格(XTZH)
|
||
///
|
||
/// # 返回
|
||
/// 碎片化池信息
|
||
pub async fn fragmentize_asset(
|
||
&self,
|
||
certificate_address: &Address,
|
||
owner: &Address,
|
||
asset_id: AssetId,
|
||
fragment_count: u64,
|
||
fragment_price: u128,
|
||
) -> Result<FragmentationPool> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"owner": owner,
|
||
"asset_id": asset_id,
|
||
"fragment_count": fragment_count,
|
||
"fragment_price": fragment_price,
|
||
});
|
||
|
||
let response = self.client.call("acc721_fragmentizeAsset", params).await?;
|
||
|
||
// 解析FragmentationPool
|
||
let pool_data = &response["result"];
|
||
let pool = FragmentationPool {
|
||
fragment_token_address: Address::from_hex(
|
||
pool_data["fragment_token_address"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing fragment_token_address".to_string()))?
|
||
)?,
|
||
total_fragments: pool_data["total_fragments"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing total_fragments".to_string()))?,
|
||
fragment_price_xtzh: pool_data["fragment_price_xtzh"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing fragment_price_xtzh".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid fragment_price: {}", e)))?,
|
||
fragmentized_at: Timestamp::from_secs(
|
||
pool_data["fragmentized_at"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing fragmentized_at".to_string()))?
|
||
),
|
||
is_recomposable: pool_data["is_recomposable"]
|
||
.as_bool()
|
||
.ok_or(NACError::InvalidResponse("Missing is_recomposable".to_string()))?,
|
||
};
|
||
|
||
Ok(pool)
|
||
}
|
||
|
||
/// 更新资产估值
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `asset_id` - 资产ID
|
||
/// * `valuation` - 新的资产估值
|
||
pub async fn update_valuation(
|
||
&self,
|
||
certificate_address: &Address,
|
||
asset_id: AssetId,
|
||
valuation: AssetValuation,
|
||
) -> Result<()> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"asset_id": asset_id,
|
||
"valuation": {
|
||
"value_xtzh": valuation.value_xtzh.to_string(),
|
||
"valuation_provider": valuation.valuation_provider,
|
||
"valued_at": valuation.valued_at.as_secs(),
|
||
"validity_period": valuation.validity_period,
|
||
},
|
||
});
|
||
|
||
self.client.call("acc721_updateValuation", params).await?;
|
||
Ok(())
|
||
}
|
||
|
||
/// 获取资产估值
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `asset_id` - 资产ID
|
||
///
|
||
/// # 返回
|
||
/// 资产估值
|
||
pub async fn get_asset_valuation(
|
||
&self,
|
||
certificate_address: &Address,
|
||
asset_id: AssetId,
|
||
) -> Result<AssetValuation> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"asset_id": asset_id,
|
||
});
|
||
|
||
let response = self.client.call("acc721_getAssetValuation", params).await?;
|
||
|
||
// 解析AssetValuation
|
||
let val_data = &response["result"];
|
||
let valuation = AssetValuation {
|
||
value_xtzh: val_data["value_xtzh"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing value_xtzh".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid value_xtzh: {}", e)))?,
|
||
valuation_provider: Address::from_hex(
|
||
val_data["valuation_provider"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing valuation_provider".to_string()))?
|
||
)?,
|
||
valued_at: Timestamp::from_secs(
|
||
val_data["valued_at"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing valued_at".to_string()))?
|
||
),
|
||
validity_period: val_data["validity_period"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing validity_period".to_string()))?,
|
||
};
|
||
|
||
Ok(valuation)
|
||
}
|
||
|
||
/// 获取资产DNA
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `asset_id` - 资产ID
|
||
///
|
||
/// # 返回
|
||
/// 资产DNA
|
||
pub async fn get_asset_dna(
|
||
&self,
|
||
certificate_address: &Address,
|
||
asset_id: AssetId,
|
||
) -> Result<AssetDNA> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"asset_id": asset_id,
|
||
});
|
||
|
||
let response = self.client.call("acc721_getAssetDNA", params).await?;
|
||
|
||
// 解析AssetDNA
|
||
let dna_data = &response["result"];
|
||
let asset_dna = AssetDNA {
|
||
dna_hash: Hash::from_hex(
|
||
dna_data["dna_hash"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing dna_hash".to_string()))?
|
||
)?,
|
||
physical_fingerprint: Hash::from_hex(
|
||
dna_data["physical_fingerprint"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing physical_fingerprint".to_string()))?
|
||
)?,
|
||
legal_document_hash: Hash::from_hex(
|
||
dna_data["legal_document_hash"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing legal_document_hash".to_string()))?
|
||
)?,
|
||
generated_at: Timestamp::from_secs(
|
||
dna_data["generated_at"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing generated_at".to_string()))?
|
||
),
|
||
};
|
||
|
||
Ok(asset_dna)
|
||
}
|
||
|
||
/// 获取资产元数据
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `asset_id` - 资产ID
|
||
///
|
||
/// # 返回
|
||
/// 资产元数据URI
|
||
pub async fn get_asset_metadata(
|
||
&self,
|
||
certificate_address: &Address,
|
||
asset_id: AssetId,
|
||
) -> Result<String> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"asset_id": asset_id,
|
||
});
|
||
|
||
let response = self.client.call("acc721_getAssetMetadata", params).await?;
|
||
|
||
let metadata_uri = response["result"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing metadata URI".to_string()))?
|
||
.to_string();
|
||
|
||
Ok(metadata_uri)
|
||
}
|
||
|
||
/// 获取托管信息
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `asset_id` - 资产ID
|
||
///
|
||
/// # 返回
|
||
/// 托管信息
|
||
pub async fn get_custody_info(
|
||
&self,
|
||
certificate_address: &Address,
|
||
asset_id: AssetId,
|
||
) -> Result<CustodyInfo> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"asset_id": asset_id,
|
||
});
|
||
|
||
let response = self.client.call("acc721_getCustodyInfo", params).await?;
|
||
|
||
// 解析CustodyInfo
|
||
let custody_data = &response["result"];
|
||
let custody_info = CustodyInfo {
|
||
custodian: Address::from_hex(
|
||
custody_data["custodian"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing custodian".to_string()))?
|
||
)?,
|
||
custody_start: Timestamp::from_secs(
|
||
custody_data["custody_start"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing custody_start".to_string()))?
|
||
),
|
||
is_active: custody_data["is_active"]
|
||
.as_bool()
|
||
.ok_or(NACError::InvalidResponse("Missing is_active".to_string()))?,
|
||
custody_proof: Hash::from_hex(
|
||
custody_data["custody_proof"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing custody_proof".to_string()))?
|
||
)?,
|
||
};
|
||
|
||
Ok(custody_info)
|
||
}
|
||
|
||
/// 获取保险信息
|
||
///
|
||
/// # 参数
|
||
/// * `certificate_address` - 证书地址
|
||
/// * `asset_id` - 资产ID
|
||
///
|
||
/// # 返回
|
||
/// 保险信息
|
||
pub async fn get_insurance_info(
|
||
&self,
|
||
certificate_address: &Address,
|
||
asset_id: AssetId,
|
||
) -> Result<InsuranceInfo> {
|
||
let params = json!({
|
||
"certificate_address": certificate_address,
|
||
"asset_id": asset_id,
|
||
});
|
||
|
||
let response = self.client.call("acc721_getInsuranceInfo", params).await?;
|
||
|
||
// 解析InsuranceInfo
|
||
let insurance_data = &response["result"];
|
||
let insurance_info = InsuranceInfo {
|
||
insurer: Address::from_hex(
|
||
insurance_data["insurer"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing insurer".to_string()))?
|
||
)?,
|
||
coverage_xtzh: insurance_data["coverage_xtzh"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing coverage_xtzh".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid coverage_xtzh: {}", e)))?,
|
||
insurance_start: Timestamp::from_secs(
|
||
insurance_data["insurance_start"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing insurance_start".to_string()))?
|
||
),
|
||
insurance_expiry: Timestamp::from_secs(
|
||
insurance_data["insurance_expiry"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing insurance_expiry".to_string()))?
|
||
),
|
||
policy_number: insurance_data["policy_number"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing policy_number".to_string()))?
|
||
.to_string(),
|
||
};
|
||
|
||
Ok(insurance_info)
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[tokio::test]
|
||
async fn test_acc721_interface() {
|
||
// 这里只是接口测试,实际需要连接到NAC节点
|
||
let client = NRPC3Client::new("https://rpc.newassetchain.io");
|
||
let acc721 = ACC721::new(client);
|
||
|
||
// 测试将在实际连接到NAC节点后进行
|
||
assert!(true);
|
||
}
|
||
}
|