|
4 | 4 | //! All transactions use expiring nonces (nonceKey=MAX, nonce=0) so no |
5 | 5 | //! on-chain nonce fetch is needed. |
6 | 6 |
|
7 | | -use std::num::NonZeroU64; |
| 7 | +use std::{num::NonZeroU64, time::Duration}; |
8 | 8 |
|
9 | 9 | use alloy::{ |
| 10 | + network::Network, |
10 | 11 | primitives::{Address, Bytes, TxKind, B256, U256}, |
11 | | - providers::Provider, |
| 12 | + providers::{PendingTransactionError, Provider}, |
12 | 13 | sol, |
13 | 14 | sol_types::SolCall, |
14 | 15 | }; |
@@ -349,6 +350,122 @@ pub async fn submit_tempo_tx( |
349 | 350 | } |
350 | 351 | } |
351 | 352 |
|
| 353 | +/// On-chain receipt type for the Tempo network. |
| 354 | +pub type TempoTxReceipt = <mpp::client::TempoNetwork as Network>::ReceiptResponse; |
| 355 | + |
| 356 | +/// A broadcast Tempo transaction together with its mined receipt. |
| 357 | +pub struct ConfirmedTempoTx { |
| 358 | + pub tx_hash: String, |
| 359 | + pub receipt: TempoTxReceipt, |
| 360 | +} |
| 361 | + |
| 362 | +/// Maximum time to wait for a transaction receipt before reporting unknown finality. |
| 363 | +const RECEIPT_TIMEOUT: Duration = Duration::from_secs(30); |
| 364 | + |
| 365 | +/// Submit a Tempo type-0x76 transaction and wait for its mined receipt. |
| 366 | +/// |
| 367 | +/// Unlike [`submit_tempo_tx`], which returns as soon as the transaction is |
| 368 | +/// broadcast, this waits for inclusion so callers can inspect the receipt |
| 369 | +/// (status and logs). Uses the same stored-key-authorization retry path. |
| 370 | +/// |
| 371 | +/// # Errors |
| 372 | +/// |
| 373 | +/// Returns an error when signing, broadcast, or receipt polling fails (including |
| 374 | +/// a timeout, in which case the error carries the broadcast tx hash). |
| 375 | +pub async fn submit_tempo_tx_and_wait( |
| 376 | + provider: &alloy::providers::RootProvider<mpp::client::TempoNetwork>, |
| 377 | + wallet: &Signer, |
| 378 | + chain_id: u64, |
| 379 | + fee_token: Address, |
| 380 | + from: Address, |
| 381 | + calls: Vec<tempo_primitives::transaction::Call>, |
| 382 | +) -> ChannelResult<ConfirmedTempoTx> { |
| 383 | + let tx_bytes = |
| 384 | + resolve_and_sign_tx(provider, wallet, chain_id, fee_token, from, calls.clone()).await?; |
| 385 | + |
| 386 | + let pending = match provider.send_raw_transaction(&tx_bytes).await { |
| 387 | + Ok(pending) => pending, |
| 388 | + Err(original) if wallet.has_stored_key_authorization() => { |
| 389 | + let provisioning_signer = |
| 390 | + wallet |
| 391 | + .with_key_authorization() |
| 392 | + .ok_or_else(|| KeyError::SigningOperation { |
| 393 | + operation: "key provisioning", |
| 394 | + reason: "stored key authorization could not be applied to signing mode" |
| 395 | + .to_string(), |
| 396 | + })?; |
| 397 | + let retry_bytes = resolve_and_sign_tx( |
| 398 | + provider, |
| 399 | + &provisioning_signer, |
| 400 | + chain_id, |
| 401 | + fee_token, |
| 402 | + from, |
| 403 | + calls, |
| 404 | + ) |
| 405 | + .await?; |
| 406 | + provider |
| 407 | + .send_raw_transaction(&retry_bytes) |
| 408 | + .await |
| 409 | + .map_err(|source| { |
| 410 | + classify_tx_error(&source) |
| 411 | + .or_else(|| classify_tx_error(&original)) |
| 412 | + .unwrap_or_else(|| { |
| 413 | + NetworkError::RpcSource { |
| 414 | + operation: "broadcast transaction", |
| 415 | + source: Box::new(original), |
| 416 | + } |
| 417 | + .into() |
| 418 | + }) |
| 419 | + })? |
| 420 | + } |
| 421 | + Err(source) => { |
| 422 | + return Err(classify_tx_error(&source).unwrap_or_else(|| { |
| 423 | + NetworkError::RpcSource { |
| 424 | + operation: "broadcast transaction", |
| 425 | + source: Box::new(source), |
| 426 | + } |
| 427 | + .into() |
| 428 | + })) |
| 429 | + } |
| 430 | + }; |
| 431 | + |
| 432 | + let tx_hash = format!("{:#x}", pending.tx_hash()); |
| 433 | + |
| 434 | + let receipt = pending |
| 435 | + .with_timeout(Some(RECEIPT_TIMEOUT)) |
| 436 | + .get_receipt() |
| 437 | + .await |
| 438 | + .map_err(|source| NetworkError::RpcSource { |
| 439 | + operation: "await transaction receipt", |
| 440 | + source: Box::new(TxReceiptError { |
| 441 | + tx_hash: tx_hash.clone(), |
| 442 | + source, |
| 443 | + }), |
| 444 | + })?; |
| 445 | + |
| 446 | + Ok(ConfirmedTempoTx { tx_hash, receipt }) |
| 447 | +} |
| 448 | + |
| 449 | +/// Wraps a receipt-polling failure with the broadcast tx hash so users can |
| 450 | +/// follow up on a transaction whose finality is unknown. |
| 451 | +#[derive(Debug)] |
| 452 | +struct TxReceiptError { |
| 453 | + tx_hash: String, |
| 454 | + source: PendingTransactionError, |
| 455 | +} |
| 456 | + |
| 457 | +impl std::fmt::Display for TxReceiptError { |
| 458 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 459 | + write!(f, "{} (tx {})", self.source, self.tx_hash) |
| 460 | + } |
| 461 | +} |
| 462 | + |
| 463 | +impl std::error::Error for TxReceiptError { |
| 464 | + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| 465 | + Some(&self.source) |
| 466 | + } |
| 467 | +} |
| 468 | + |
352 | 469 | // ==================== Transaction Construction ==================== |
353 | 470 |
|
354 | 471 | /// Build the escrow open calls: approve + open. |
|
0 commit comments