use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use aptos_sdk::account::Ed25519Account;
use aptos_sdk::crypto::Ed25519PrivateKey;
use aptos_sdk::transaction::PartiallySigned;
use aptos_sdk::transaction::types::FeePayerRawTransaction;
use aptos_sdk::types::AccountAddress;
use aptos_sdk::{Aptos, AptosConfig};
use r402_core::chain::{ChainId, ChainProvider};
use super::codec::{AptosCodecError, DecodedAptosPayment, aptos_config_for, signed_from_decoded};
use super::types::AptosChainReference;
#[derive(Clone)]
pub struct AptosFeePayer {
account: Ed25519Account,
}
impl Debug for AptosFeePayer {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AptosFeePayer")
.field("address", &self.account.address().to_long_string())
.finish_non_exhaustive()
}
}
impl AptosFeePayer {
pub fn from_private_key_hex(private_key: impl AsRef<str>) -> Result<Self, AptosProviderError> {
let account = Ed25519Account::from_private_key_hex(private_key.as_ref())
.map_err(|e| AptosProviderError::Parse(e.to_string()))?;
Ok(Self { account })
}
#[must_use]
pub const fn new(account: Ed25519Account) -> Self {
Self { account }
}
pub fn from_private_key_bytes(bytes: &[u8]) -> Result<Self, AptosProviderError> {
let key = Ed25519PrivateKey::from_bytes(bytes)
.map_err(|e| AptosProviderError::Parse(e.to_string()))?;
Ok(Self {
account: Ed25519Account::from_private_key(key),
})
}
#[must_use]
pub fn address_long(&self) -> String {
self.account.address().to_long_string()
}
#[must_use]
pub const fn account(&self) -> &Ed25519Account {
&self.account
}
}
#[derive(Debug, thiserror::Error)]
pub enum AptosProviderError {
#[error("aptos provider parse error: {0}")]
Parse(String),
#[error("aptos rpc error: {0}")]
Rpc(String),
#[error("aptos submit error: {0}")]
Submit(String),
#[error(transparent)]
Codec(#[from] AptosCodecError),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AptosSimulationResult {
pub success: bool,
pub vm_status: String,
}
#[derive(Clone)]
pub struct AptosChainProvider {
chain: AptosChainReference,
fee_payers: Vec<AptosFeePayer>,
aptos: Arc<Aptos>,
sponsor_transactions: bool,
}
impl Debug for AptosChainProvider {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AptosChainProvider")
.field("chain", &self.chain)
.field(
"fee_payers",
&self
.fee_payers
.iter()
.map(AptosFeePayer::address_long)
.collect::<Vec<_>>(),
)
.field("sponsor_transactions", &self.sponsor_transactions)
.finish_non_exhaustive()
}
}
impl AptosChainProvider {
pub fn new(
chain: AptosChainReference,
fee_payers: Vec<AptosFeePayer>,
rpc_url: Option<&str>,
) -> Result<Self, AptosProviderError> {
let config = aptos_config_for(chain.chain_id(), rpc_url)
.map_err(|e| AptosProviderError::Parse(e.to_string()))?;
Self::from_config(chain, fee_payers, config)
}
pub fn from_config(
chain: AptosChainReference,
fee_payers: Vec<AptosFeePayer>,
config: AptosConfig,
) -> Result<Self, AptosProviderError> {
let aptos = Aptos::new(config).map_err(|e| AptosProviderError::Rpc(e.to_string()))?;
Ok(Self {
chain,
fee_payers,
aptos: Arc::new(aptos),
sponsor_transactions: true,
})
}
#[must_use]
pub const fn with_sponsor_transactions(mut self, sponsor: bool) -> Self {
self.sponsor_transactions = sponsor;
self
}
#[must_use]
pub const fn sponsor_transactions(&self) -> bool {
self.sponsor_transactions
}
#[must_use]
pub fn fee_payer_addresses(&self) -> Vec<String> {
self.fee_payers
.iter()
.map(AptosFeePayer::address_long)
.collect()
}
#[must_use]
pub const fn chain_reference(&self) -> AptosChainReference {
self.chain
}
#[must_use]
pub fn aptos(&self) -> &Aptos {
self.aptos.as_ref()
}
#[must_use]
pub fn fee_payer_for(&self, address: AccountAddress) -> Option<&AptosFeePayer> {
self.fee_payers
.iter()
.find(|p| p.account.address() == address)
}
pub async fn fungible_asset_balance(
&self,
owner: &str,
asset: &str,
) -> Result<u64, AptosProviderError> {
let owner = AccountAddress::from_hex(owner)
.map_err(|e| AptosProviderError::Parse(e.to_string()))?;
let asset = AccountAddress::from_hex(asset)
.map_err(|e| AptosProviderError::Parse(e.to_string()))?;
let values = self
.aptos
.view(
"0x1::primary_fungible_store::balance",
vec!["0x1::fungible_asset::Metadata".to_owned()],
vec![
serde_json::Value::String(owner.to_long_string()),
serde_json::Value::String(asset.to_long_string()),
],
)
.await
.map_err(|e| AptosProviderError::Rpc(e.to_string()))?;
parse_u64_view(&values)
}
pub async fn simulate_payment(
&self,
transaction_base64: &str,
) -> Result<AptosSimulationResult, AptosProviderError> {
let decoded = DecodedAptosPayment::from_base64(transaction_base64)?;
let signed = signed_from_decoded(&decoded);
let result = self
.aptos
.simulate_signed(&signed)
.await
.map_err(|e| AptosProviderError::Rpc(e.to_string()))?;
Ok(AptosSimulationResult {
success: result.success(),
vm_status: result.vm_status().to_owned(),
})
}
pub async fn sign_and_submit(
&self,
transaction_base64: &str,
fee_payer: Option<&str>,
) -> Result<String, AptosProviderError> {
let decoded = DecodedAptosPayment::from_base64(transaction_base64)?;
let signed = if let Some(fee_payer) = fee_payer {
let fee_payer_addr = AccountAddress::from_hex(fee_payer)
.map_err(|e| AptosProviderError::Parse(e.to_string()))?;
let payer = self.fee_payer_for(fee_payer_addr).ok_or_else(|| {
AptosProviderError::Submit(format!(
"fee payer {} is not managed by this facilitator",
fee_payer_addr.to_long_string()
))
})?;
let mut partial = PartiallySigned::new(FeePayerRawTransaction::new_simple(
decoded.transaction.raw_transaction.clone(),
fee_payer_addr,
));
partial.sender_auth = Some(decoded.sender_authenticator.clone());
partial
.sign_as_fee_payer(payer.account())
.map_err(|e| AptosProviderError::Submit(e.to_string()))?;
partial
.finalize()
.map_err(|e| AptosProviderError::Submit(e.to_string()))?
} else {
signed_from_decoded(&decoded)
};
let result = self
.aptos
.submit_and_wait(&signed, None)
.await
.map_err(|e| AptosProviderError::Submit(e.to_string()))?;
if let Some(hash) = result.data.get("hash").and_then(serde_json::Value::as_str) {
return Ok(hash.to_owned());
}
signed
.hash()
.map(|h| h.to_string())
.map_err(|e| AptosProviderError::Submit(e.to_string()))
}
}
impl ChainProvider for AptosChainProvider {
fn signer_addresses(&self) -> Vec<String> {
self.fee_payer_addresses()
}
fn chain_id(&self) -> ChainId {
self.chain.into()
}
}
fn parse_u64_view(values: &[serde_json::Value]) -> Result<u64, AptosProviderError> {
let first = values.first().ok_or_else(|| {
AptosProviderError::Rpc("empty view response for fungible asset balance".to_owned())
})?;
if let Some(n) = first.as_u64() {
return Ok(n);
}
if let Some(s) = first.as_str() {
return s
.parse()
.map_err(|e| AptosProviderError::Rpc(format!("invalid balance view: {e}")));
}
Err(AptosProviderError::Rpc(format!(
"unexpected balance view: {first}"
)))
}