r402_aptos/chain/
provider.rs1use std::fmt::{Debug, Formatter};
4use std::sync::Arc;
5
6use aptos_sdk::account::Ed25519Account;
7use aptos_sdk::crypto::Ed25519PrivateKey;
8use aptos_sdk::transaction::PartiallySigned;
9use aptos_sdk::transaction::types::FeePayerRawTransaction;
10use aptos_sdk::types::AccountAddress;
11use aptos_sdk::{Aptos, AptosConfig};
12use r402_core::chain::{ChainId, ChainProvider};
13
14use super::codec::{AptosCodecError, DecodedAptosPayment, aptos_config_for, signed_from_decoded};
15use super::types::AptosChainReference;
16
17#[derive(Clone)]
19pub struct AptosFeePayer {
20 account: Ed25519Account,
21}
22
23impl Debug for AptosFeePayer {
24 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
25 f.debug_struct("AptosFeePayer")
26 .field("address", &self.account.address().to_long_string())
27 .finish_non_exhaustive()
28 }
29}
30
31impl AptosFeePayer {
32 pub fn from_private_key_hex(private_key: impl AsRef<str>) -> Result<Self, AptosProviderError> {
38 let account = Ed25519Account::from_private_key_hex(private_key.as_ref())
39 .map_err(|e| AptosProviderError::Parse(e.to_string()))?;
40 Ok(Self { account })
41 }
42
43 #[must_use]
45 pub const fn new(account: Ed25519Account) -> Self {
46 Self { account }
47 }
48
49 pub fn from_private_key_bytes(bytes: &[u8]) -> Result<Self, AptosProviderError> {
55 let key = Ed25519PrivateKey::from_bytes(bytes)
56 .map_err(|e| AptosProviderError::Parse(e.to_string()))?;
57 Ok(Self {
58 account: Ed25519Account::from_private_key(key),
59 })
60 }
61
62 #[must_use]
64 pub fn address_long(&self) -> String {
65 self.account.address().to_long_string()
66 }
67
68 #[must_use]
70 pub const fn account(&self) -> &Ed25519Account {
71 &self.account
72 }
73}
74
75#[derive(Debug, thiserror::Error)]
77pub enum AptosProviderError {
78 #[error("aptos provider parse error: {0}")]
80 Parse(String),
81 #[error("aptos rpc error: {0}")]
83 Rpc(String),
84 #[error("aptos submit error: {0}")]
86 Submit(String),
87 #[error(transparent)]
89 Codec(#[from] AptosCodecError),
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct AptosSimulationResult {
95 pub success: bool,
97 pub vm_status: String,
99}
100
101#[derive(Clone)]
103pub struct AptosChainProvider {
104 chain: AptosChainReference,
105 fee_payers: Vec<AptosFeePayer>,
106 aptos: Arc<Aptos>,
107 sponsor_transactions: bool,
108}
109
110impl Debug for AptosChainProvider {
111 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112 f.debug_struct("AptosChainProvider")
113 .field("chain", &self.chain)
114 .field(
115 "fee_payers",
116 &self
117 .fee_payers
118 .iter()
119 .map(AptosFeePayer::address_long)
120 .collect::<Vec<_>>(),
121 )
122 .field("sponsor_transactions", &self.sponsor_transactions)
123 .finish_non_exhaustive()
124 }
125}
126
127impl AptosChainProvider {
128 pub fn new(
134 chain: AptosChainReference,
135 fee_payers: Vec<AptosFeePayer>,
136 rpc_url: Option<&str>,
137 ) -> Result<Self, AptosProviderError> {
138 let config = aptos_config_for(chain.chain_id(), rpc_url)
139 .map_err(|e| AptosProviderError::Parse(e.to_string()))?;
140 Self::from_config(chain, fee_payers, config)
141 }
142
143 pub fn from_config(
149 chain: AptosChainReference,
150 fee_payers: Vec<AptosFeePayer>,
151 config: AptosConfig,
152 ) -> Result<Self, AptosProviderError> {
153 let aptos = Aptos::new(config).map_err(|e| AptosProviderError::Rpc(e.to_string()))?;
154 Ok(Self {
155 chain,
156 fee_payers,
157 aptos: Arc::new(aptos),
158 sponsor_transactions: true,
159 })
160 }
161
162 #[must_use]
164 pub const fn with_sponsor_transactions(mut self, sponsor: bool) -> Self {
165 self.sponsor_transactions = sponsor;
166 self
167 }
168
169 #[must_use]
171 pub const fn sponsor_transactions(&self) -> bool {
172 self.sponsor_transactions
173 }
174
175 #[must_use]
177 pub fn fee_payer_addresses(&self) -> Vec<String> {
178 self.fee_payers
179 .iter()
180 .map(AptosFeePayer::address_long)
181 .collect()
182 }
183
184 #[must_use]
186 pub const fn chain_reference(&self) -> AptosChainReference {
187 self.chain
188 }
189
190 #[must_use]
192 pub fn aptos(&self) -> &Aptos {
193 self.aptos.as_ref()
194 }
195
196 #[must_use]
198 pub fn fee_payer_for(&self, address: AccountAddress) -> Option<&AptosFeePayer> {
199 self.fee_payers
200 .iter()
201 .find(|p| p.account.address() == address)
202 }
203
204 pub async fn fungible_asset_balance(
210 &self,
211 owner: &str,
212 asset: &str,
213 ) -> Result<u64, AptosProviderError> {
214 let owner = AccountAddress::from_hex(owner)
215 .map_err(|e| AptosProviderError::Parse(e.to_string()))?;
216 let asset = AccountAddress::from_hex(asset)
217 .map_err(|e| AptosProviderError::Parse(e.to_string()))?;
218 let values = self
219 .aptos
220 .view(
221 "0x1::primary_fungible_store::balance",
222 vec!["0x1::fungible_asset::Metadata".to_owned()],
223 vec![
224 serde_json::Value::String(owner.to_long_string()),
225 serde_json::Value::String(asset.to_long_string()),
226 ],
227 )
228 .await
229 .map_err(|e| AptosProviderError::Rpc(e.to_string()))?;
230 parse_u64_view(&values)
231 }
232
233 pub async fn simulate_payment(
239 &self,
240 transaction_base64: &str,
241 ) -> Result<AptosSimulationResult, AptosProviderError> {
242 let decoded = DecodedAptosPayment::from_base64(transaction_base64)?;
243 let signed = signed_from_decoded(&decoded);
244 let result = self
245 .aptos
246 .simulate_signed(&signed)
247 .await
248 .map_err(|e| AptosProviderError::Rpc(e.to_string()))?;
249 Ok(AptosSimulationResult {
250 success: result.success(),
251 vm_status: result.vm_status().to_owned(),
252 })
253 }
254
255 pub async fn sign_and_submit(
261 &self,
262 transaction_base64: &str,
263 fee_payer: Option<&str>,
264 ) -> Result<String, AptosProviderError> {
265 let decoded = DecodedAptosPayment::from_base64(transaction_base64)?;
266 let signed = if let Some(fee_payer) = fee_payer {
267 let fee_payer_addr = AccountAddress::from_hex(fee_payer)
268 .map_err(|e| AptosProviderError::Parse(e.to_string()))?;
269 let payer = self.fee_payer_for(fee_payer_addr).ok_or_else(|| {
270 AptosProviderError::Submit(format!(
271 "fee payer {} is not managed by this facilitator",
272 fee_payer_addr.to_long_string()
273 ))
274 })?;
275 let mut partial = PartiallySigned::new(FeePayerRawTransaction::new_simple(
276 decoded.transaction.raw_transaction.clone(),
277 fee_payer_addr,
278 ));
279 partial.sender_auth = Some(decoded.sender_authenticator.clone());
280 partial
281 .sign_as_fee_payer(payer.account())
282 .map_err(|e| AptosProviderError::Submit(e.to_string()))?;
283 partial
284 .finalize()
285 .map_err(|e| AptosProviderError::Submit(e.to_string()))?
286 } else {
287 signed_from_decoded(&decoded)
288 };
289
290 let result = self
291 .aptos
292 .submit_and_wait(&signed, None)
293 .await
294 .map_err(|e| AptosProviderError::Submit(e.to_string()))?;
295 if let Some(hash) = result.data.get("hash").and_then(serde_json::Value::as_str) {
296 return Ok(hash.to_owned());
297 }
298 signed
299 .hash()
300 .map(|h| h.to_string())
301 .map_err(|e| AptosProviderError::Submit(e.to_string()))
302 }
303}
304
305impl ChainProvider for AptosChainProvider {
306 fn signer_addresses(&self) -> Vec<String> {
307 self.fee_payer_addresses()
308 }
309
310 fn chain_id(&self) -> ChainId {
311 self.chain.into()
312 }
313}
314
315fn parse_u64_view(values: &[serde_json::Value]) -> Result<u64, AptosProviderError> {
316 let first = values.first().ok_or_else(|| {
317 AptosProviderError::Rpc("empty view response for fungible asset balance".to_owned())
318 })?;
319 if let Some(n) = first.as_u64() {
320 return Ok(n);
321 }
322 if let Some(s) = first.as_str() {
323 return s
324 .parse()
325 .map_err(|e| AptosProviderError::Rpc(format!("invalid balance view: {e}")));
326 }
327 Err(AptosProviderError::Rpc(format!(
328 "unexpected balance view: {first}"
329 )))
330}