503 lines
17 KiB
Rust
503 lines
17 KiB
Rust
//! ACC-20C: 兼容层协议接口
|
||
//!
|
||
//! 提供与NAC区块链上ACC-20C兼容层交互的客户端接口
|
||
|
||
// NacLensClient 已更名为 NAC Lens 客户端
|
||
use crate::error::{NACError, Result};
|
||
use crate::adapters::NacLensClient;
|
||
use nac_udm::primitives::{Address, Hash, Timestamp};
|
||
use nac_udm::l1_protocol::gnacs::GNACSCode;
|
||
use nac_udm::l1_protocol::acc20c::{
|
||
WrappedAsset, WrapperConfig, WrapperStatus, WrappedAssetStatus,
|
||
ComplianceSnapshot, EthAddress, u256,
|
||
};
|
||
use serde_json::json;
|
||
|
||
/// ACC-20C兼容层接口
|
||
///
|
||
/// ACC-20C是NAC与以太坊生态之间的战略性桥梁,提供:
|
||
/// - 双向包装:NAC ACC-20 ↔ 以太坊 ERC-721
|
||
/// - 合规保留:包装后仍保留KYC/AML检查
|
||
/// - 状态同步:跨链事件监听和状态同步
|
||
/// - 元数据生成:符合OpenSea标准的ERC-721元数据
|
||
pub struct ACC20C {
|
||
client: NacLensClient,
|
||
}
|
||
|
||
impl ACC20C {
|
||
/// 创建新的ACC-20C接口实例
|
||
pub fn new(client: NacLensClient) -> Self {
|
||
Self { client }
|
||
}
|
||
|
||
/// 包装ACC-20为ERC-721
|
||
///
|
||
/// 将NAC链上的ACC-20资产包装成以太坊链上的ERC-721 NFT
|
||
///
|
||
/// # 参数
|
||
/// * `wrapper_address` - 包装器合约地址
|
||
/// * `owner` - NAC链上的所有者地址
|
||
/// * `eth_recipient` - 以太坊链上的接收者地址
|
||
/// * `amount` - 包装数量
|
||
///
|
||
/// # 返回
|
||
/// ERC-721 Token ID
|
||
///
|
||
/// # 流程
|
||
/// 1. 验证包装器状态
|
||
/// 2. 验证合规性
|
||
/// 3. 锁定ACC-20资产
|
||
/// 4. 生成ERC-721 NFT
|
||
/// 5. 记录包装信息
|
||
pub async fn wrap(
|
||
&self,
|
||
wrapper_address: &Address,
|
||
owner: &Address,
|
||
eth_recipient: &EthAddress,
|
||
amount: u128,
|
||
) -> Result<u256> {
|
||
let params = json!({
|
||
"wrapper_address": wrapper_address,
|
||
"owner": owner,
|
||
"eth_recipient": format!("0x{}", hex::encode(eth_recipient.0)),
|
||
"amount": amount.to_string(),
|
||
});
|
||
|
||
let response = self.client.call("acc20c_wrap", params).await?;
|
||
|
||
let token_id_data = &response["result"]["token_id"];
|
||
let token_id = u256 {
|
||
low: token_id_data["low"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing token_id.low".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid token_id.low: {}", e)))?,
|
||
high: token_id_data["high"]
|
||
.as_str()
|
||
.unwrap_or("0")
|
||
.parse()
|
||
.unwrap_or(0),
|
||
};
|
||
|
||
Ok(token_id)
|
||
}
|
||
|
||
/// 解包装ERC-721为ACC-20
|
||
///
|
||
/// 将以太坊链上的ERC-721 NFT解包装回NAC链上的ACC-20资产
|
||
///
|
||
/// # 参数
|
||
/// * `wrapper_address` - 包装器合约地址
|
||
/// * `token_id` - ERC-721 Token ID
|
||
/// * `eth_owner` - 以太坊链上的所有者地址
|
||
/// * `nac_recipient` - NAC链上的接收者地址
|
||
///
|
||
/// # 返回
|
||
/// 解包装的ACC-20数量
|
||
///
|
||
/// # 流程
|
||
/// 1. 验证TokenId存在
|
||
/// 2. 验证所有权
|
||
/// 3. 销毁ERC-721 NFT
|
||
/// 4. 解锁ACC-20资产
|
||
/// 5. 转回接收者
|
||
pub async fn unwrap(
|
||
&self,
|
||
wrapper_address: &Address,
|
||
token_id: u256,
|
||
eth_owner: &EthAddress,
|
||
nac_recipient: &Address,
|
||
) -> Result<u128> {
|
||
let params = json!({
|
||
"wrapper_address": wrapper_address,
|
||
"token_id": {
|
||
"low": token_id.low.to_string(),
|
||
"high": token_id.high.to_string(),
|
||
},
|
||
"eth_owner": format!("0x{}", hex::encode(eth_owner.0)),
|
||
"nac_recipient": nac_recipient,
|
||
});
|
||
|
||
let response = self.client.call("acc20c_unwrap", params).await?;
|
||
|
||
let amount = response["result"]["amount"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing amount".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid amount: {}", e)))?;
|
||
|
||
Ok(amount)
|
||
}
|
||
|
||
/// 查询包装资产信息
|
||
///
|
||
/// # 参数
|
||
/// * `wrapper_address` - 包装器合约地址
|
||
/// * `token_id` - ERC-721 Token ID
|
||
///
|
||
/// # 返回
|
||
/// 包装资产详细信息
|
||
pub async fn get_wrapped_asset(
|
||
&self,
|
||
wrapper_address: &Address,
|
||
token_id: u256,
|
||
) -> Result<WrappedAsset> {
|
||
let params = json!({
|
||
"wrapper_address": wrapper_address,
|
||
"token_id": {
|
||
"low": token_id.low.to_string(),
|
||
"high": token_id.high.to_string(),
|
||
},
|
||
});
|
||
|
||
let response = self.client.call("acc20c_getWrappedAsset", params).await?;
|
||
|
||
let asset_data = &response["result"];
|
||
|
||
// 解析以太坊地址
|
||
let eth_owner_hex = asset_data["current_owner"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing current_owner".to_string()))?
|
||
.trim_start_matches("0x");
|
||
let eth_owner_bytes = hex::decode(eth_owner_hex)
|
||
.map_err(|e| NACError::InvalidAddress(format!("Invalid eth address: {}", e)))?;
|
||
let mut eth_owner_array = [0u8; 20];
|
||
eth_owner_array.copy_from_slice(ð_owner_bytes[..20]);
|
||
|
||
let wrapped_asset = WrappedAsset {
|
||
token_id,
|
||
original_holdings: asset_data["original_holdings"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing original_holdings".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid holdings: {}", e)))?,
|
||
original_owner: Address::from_hex(
|
||
asset_data["original_owner"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing original_owner".to_string()))?
|
||
).map_err(|e| NACError::InvalidAddress(format!("Invalid original_owner: {}", e)))?,
|
||
current_owner: EthAddress(eth_owner_array),
|
||
gnacs_code: serde_json::from_value(asset_data["gnacs_code"].clone())
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid gnacs_code: {}", e)))?,
|
||
wrapped_at: Timestamp::from_secs(
|
||
asset_data["wrapped_at"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing wrapped_at".to_string()))?
|
||
),
|
||
metadata_uri: asset_data["metadata_uri"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing metadata_uri".to_string()))?
|
||
.to_string(),
|
||
compliance_snapshot: self.parse_compliance_snapshot(&asset_data["compliance_snapshot"])?,
|
||
status: serde_json::from_value(asset_data["status"].clone())
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid status: {}", e)))?,
|
||
};
|
||
|
||
Ok(wrapped_asset)
|
||
}
|
||
|
||
/// 查询锁定的持有量
|
||
///
|
||
/// # 参数
|
||
/// * `wrapper_address` - 包装器合约地址
|
||
/// * `owner` - 所有者地址
|
||
///
|
||
/// # 返回
|
||
/// 锁定的ACC-20数量
|
||
pub async fn get_locked_holdings(
|
||
&self,
|
||
wrapper_address: &Address,
|
||
owner: &Address,
|
||
) -> Result<u128> {
|
||
let params = json!({
|
||
"wrapper_address": wrapper_address,
|
||
"owner": owner,
|
||
});
|
||
|
||
let response = self.client.call("acc20c_getLockedHoldings", params).await?;
|
||
|
||
let holdings = response["result"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing holdings".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid holdings: {}", e)))?;
|
||
|
||
Ok(holdings)
|
||
}
|
||
|
||
/// 冻结包装资产
|
||
///
|
||
/// # 参数
|
||
/// * `wrapper_address` - 包装器合约地址
|
||
/// * `token_id` - ERC-721 Token ID
|
||
pub async fn freeze_wrapped_asset(
|
||
&self,
|
||
wrapper_address: &Address,
|
||
token_id: u256,
|
||
) -> Result<()> {
|
||
let params = json!({
|
||
"wrapper_address": wrapper_address,
|
||
"token_id": {
|
||
"low": token_id.low.to_string(),
|
||
"high": token_id.high.to_string(),
|
||
},
|
||
});
|
||
|
||
self.client.call("acc20c_freezeWrappedAsset", params).await?;
|
||
Ok(())
|
||
}
|
||
|
||
/// 解冻包装资产
|
||
///
|
||
/// # 参数
|
||
/// * `wrapper_address` - 包装器合约地址
|
||
/// * `token_id` - ERC-721 Token ID
|
||
pub async fn unfreeze_wrapped_asset(
|
||
&self,
|
||
wrapper_address: &Address,
|
||
token_id: u256,
|
||
) -> Result<()> {
|
||
let params = json!({
|
||
"wrapper_address": wrapper_address,
|
||
"token_id": {
|
||
"low": token_id.low.to_string(),
|
||
"high": token_id.high.to_string(),
|
||
},
|
||
});
|
||
|
||
self.client.call("acc20c_unfreezeWrappedAsset", params).await?;
|
||
Ok(())
|
||
}
|
||
|
||
/// 查询包装器配置
|
||
///
|
||
/// # 参数
|
||
/// * `wrapper_address` - 包装器合约地址
|
||
///
|
||
/// # 返回
|
||
/// 包装器配置信息
|
||
pub async fn get_wrapper_config(
|
||
&self,
|
||
wrapper_address: &Address,
|
||
) -> Result<WrapperConfig> {
|
||
let params = json!({
|
||
"wrapper_address": wrapper_address,
|
||
});
|
||
|
||
let response = self.client.call("acc20c_getWrapperConfig", params).await?;
|
||
|
||
let config_data = &response["result"];
|
||
let config = WrapperConfig {
|
||
acc20_per_nft: config_data["acc20_per_nft"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing acc20_per_nft".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid acc20_per_nft: {}", e)))?,
|
||
allow_partial_wrap: config_data["allow_partial_wrap"]
|
||
.as_bool()
|
||
.ok_or(NACError::InvalidResponse("Missing allow_partial_wrap".to_string()))?,
|
||
wrap_fee_bps: config_data["wrap_fee_bps"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing wrap_fee_bps".to_string()))? as u16,
|
||
unwrap_fee_bps: config_data["unwrap_fee_bps"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing unwrap_fee_bps".to_string()))? as u16,
|
||
fee_recipient: Address::from_hex(
|
||
config_data["fee_recipient"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing fee_recipient".to_string()))?
|
||
).map_err(|e| NACError::InvalidAddress(format!("Invalid fee_recipient: {}", e)))?,
|
||
preserve_compliance: config_data["preserve_compliance"]
|
||
.as_bool()
|
||
.ok_or(NACError::InvalidResponse("Missing preserve_compliance".to_string()))?,
|
||
min_wrap_amount: config_data["min_wrap_amount"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing min_wrap_amount".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid min_wrap_amount: {}", e)))?,
|
||
max_wrap_amount: config_data["max_wrap_amount"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing max_wrap_amount".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid max_wrap_amount: {}", e)))?,
|
||
};
|
||
|
||
Ok(config)
|
||
}
|
||
|
||
/// 查询包装器状态
|
||
///
|
||
/// # 参数
|
||
/// * `wrapper_address` - 包装器合约地址
|
||
///
|
||
/// # 返回
|
||
/// 包装器状态
|
||
pub async fn get_wrapper_status(
|
||
&self,
|
||
wrapper_address: &Address,
|
||
) -> Result<WrapperStatus> {
|
||
let params = json!({
|
||
"wrapper_address": wrapper_address,
|
||
});
|
||
|
||
let response = self.client.call("acc20c_getWrapperStatus", params).await?;
|
||
|
||
let status = serde_json::from_value(response["result"].clone())
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid status: {}", e)))?;
|
||
|
||
Ok(status)
|
||
}
|
||
|
||
/// 获取元数据URI
|
||
///
|
||
/// # 参数
|
||
/// * `wrapper_address` - 包装器合约地址
|
||
/// * `token_id` - ERC-721 Token ID
|
||
///
|
||
/// # 返回
|
||
/// 元数据URI(符合ERC-721标准)
|
||
pub async fn get_metadata_uri(
|
||
&self,
|
||
wrapper_address: &Address,
|
||
token_id: u256,
|
||
) -> Result<String> {
|
||
let params = json!({
|
||
"wrapper_address": wrapper_address,
|
||
"token_id": {
|
||
"low": token_id.low.to_string(),
|
||
"high": token_id.high.to_string(),
|
||
},
|
||
});
|
||
|
||
let response = self.client.call("acc20c_getMetadataURI", params).await?;
|
||
|
||
let uri = response["result"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing metadata URI".to_string()))?
|
||
.to_string();
|
||
|
||
Ok(uri)
|
||
}
|
||
|
||
/// 计算包装手续费
|
||
///
|
||
/// # 参数
|
||
/// * `wrapper_address` - 包装器合约地址
|
||
/// * `amount` - 包装数量
|
||
///
|
||
/// # 返回
|
||
/// 手续费金额
|
||
pub async fn calculate_wrap_fee(
|
||
&self,
|
||
wrapper_address: &Address,
|
||
amount: u128,
|
||
) -> Result<u128> {
|
||
let params = json!({
|
||
"wrapper_address": wrapper_address,
|
||
"amount": amount.to_string(),
|
||
});
|
||
|
||
let response = self.client.call("acc20c_calculateWrapFee", params).await?;
|
||
|
||
let fee = response["result"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing fee".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid fee: {}", e)))?;
|
||
|
||
Ok(fee)
|
||
}
|
||
|
||
/// 计算解包装手续费
|
||
///
|
||
/// # 参数
|
||
/// * `wrapper_address` - 包装器合约地址
|
||
/// * `amount` - 解包装数量
|
||
///
|
||
/// # 返回
|
||
/// 手续费金额
|
||
pub async fn calculate_unwrap_fee(
|
||
&self,
|
||
wrapper_address: &Address,
|
||
amount: u128,
|
||
) -> Result<u128> {
|
||
let params = json!({
|
||
"wrapper_address": wrapper_address,
|
||
"amount": amount.to_string(),
|
||
});
|
||
|
||
let response = self.client.call("acc20c_calculateUnwrapFee", params).await?;
|
||
|
||
let fee = response["result"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing fee".to_string()))?
|
||
.parse()
|
||
.map_err(|e| NACError::InvalidResponse(format!("Invalid fee: {}", e)))?;
|
||
|
||
Ok(fee)
|
||
}
|
||
|
||
// === 私有辅助函数 ===
|
||
|
||
/// 解析合规快照
|
||
fn parse_compliance_snapshot(
|
||
&self,
|
||
snapshot_data: &serde_json::Value,
|
||
) -> Result<ComplianceSnapshot> {
|
||
let snapshot = ComplianceSnapshot {
|
||
kyc_level: snapshot_data["kyc_level"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing kyc_level".to_string()))? as u8,
|
||
aml_status: snapshot_data["aml_status"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing aml_status".to_string()))? as u8,
|
||
compliance_level: snapshot_data["compliance_level"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing compliance_level".to_string()))? as u8,
|
||
jurisdiction: snapshot_data["jurisdiction"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing jurisdiction".to_string()))? as u8,
|
||
snapshot_at: Timestamp::from_secs(
|
||
snapshot_data["snapshot_at"]
|
||
.as_u64()
|
||
.ok_or(NACError::InvalidResponse("Missing snapshot_at".to_string()))?
|
||
),
|
||
snapshot_hash: Hash::from_hex(
|
||
snapshot_data["snapshot_hash"]
|
||
.as_str()
|
||
.ok_or(NACError::InvalidResponse("Missing snapshot_hash".to_string()))?
|
||
).map_err(|e| NACError::InvalidHash(format!("Invalid snapshot_hash: {}", e)))?,
|
||
};
|
||
|
||
Ok(snapshot)
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[tokio::test]
|
||
async fn test_acc20c_interface() {
|
||
// 这里只是接口测试,实际需要连接到NAC节点
|
||
let client = NacLensClient::new("https://rpc.newassetchain.io");
|
||
let acc20c = ACC20C::new(client);
|
||
|
||
// 测试将在实际连接到NAC节点后进行
|
||
assert!(true);
|
||
}
|
||
|
||
#[test]
|
||
fn test_u256() {
|
||
let token_id = u256::from_u128(12345);
|
||
assert_eq!(token_id.low, 12345);
|
||
assert_eq!(token_id.high, 0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_eth_address() {
|
||
let eth_addr = EthAddress([0u8; 20]);
|
||
assert_eq!(eth_addr.0.len(), 20);
|
||
}
|
||
}
|