From d1238a39297ce2be69aabcf98473cdaf29adbd0c Mon Sep 17 00:00:00 2001 From: Jack Pooley <169029079+jackpooleywc@users.noreply.github.com> Date: Sat, 14 Sep 2024 10:50:41 +0200 Subject: [PATCH] feat(op receipt): implement wait_for_user_operation_receipt --- .../Models/FFIUserOperationReceipt.swift | 79 +++++++++++++++++++ crates/ffi/src/account_client.rs | 13 +++ crates/ffi/src/lib.rs | 5 ++ crates/yttrium/src/account_client.rs | 28 +++++++ .../bundler/models/user_operation_receipt.rs | 4 +- crates/yttrium/src/transaction/send.rs | 18 ----- .../transaction/send/simple_account_test.rs | 36 --------- .../swift/Sources/Yttrium/AccountClient.swift | 16 ++++ .../YttriumTests/AccountClientTests.swift | 6 +- 9 files changed, 147 insertions(+), 58 deletions(-) create mode 100644 crates/ffi/YttriumCore/Sources/YttriumCore/SwiftFFI/Models/FFIUserOperationReceipt.swift diff --git a/crates/ffi/YttriumCore/Sources/YttriumCore/SwiftFFI/Models/FFIUserOperationReceipt.swift b/crates/ffi/YttriumCore/Sources/YttriumCore/SwiftFFI/Models/FFIUserOperationReceipt.swift new file mode 100644 index 0000000..71c6190 --- /dev/null +++ b/crates/ffi/YttriumCore/Sources/YttriumCore/SwiftFFI/Models/FFIUserOperationReceipt.swift @@ -0,0 +1,79 @@ +import Foundation + +public struct UserOperationReceipt: Codable { + + public struct Receipt: Codable { + public let transactionHash: String + public let transactionIndex: String + public let block_hash: String + public let block_number: String + public let from: String + public let to: String + public let cumulativeGasUsed: String + public let gas_used: String + public let contractAddress: String? + public let status: String + public let logsBloom: String + public let effectiveGasPrice: String + + public init( + transactionHash: String, + transactionIndex: String, + block_hash: String, + block_number: String, + from: String, + to: String, + cumulativeGasUsed: String, + gas_used: String, + contractAddress: String?, + status: String, + logsBloom: String, + effectiveGasPrice: String + ) { + self.transactionHash = transactionHash + self.transactionIndex = transactionIndex + self.block_hash = block_hash + self.block_number = block_number + self.from = from + self.to = to + self.cumulativeGasUsed = cumulativeGasUsed + self.gas_used = gas_used + self.contractAddress = contractAddress + self.status = status + self.logsBloom = logsBloom + self.effectiveGasPrice = effectiveGasPrice + } + } + + public let userOpHash: String + public let entryPoint: String + public let sender: String + public let nonce: String + public let paymaster: String + public let actualGasCost: String + public let actualGasUsed: String + public let success: Bool + public let receipt: Receipt + + public init( + userOpHash: String, + entryPoint: String, + sender: String, + nonce: String, + paymaster: String, + actualGasCost: String, + actualGasUsed: String, + success: Bool, + receipt: Receipt + ) { + self.userOpHash = userOpHash + self.entryPoint = entryPoint + self.sender = sender + self.nonce = nonce + self.paymaster = paymaster + self.actualGasCost = actualGasCost + self.actualGasUsed = actualGasUsed + self.success = success + self.receipt = receipt + } +} diff --git a/crates/ffi/src/account_client.rs b/crates/ffi/src/account_client.rs index 3cdccdb..02e18f0 100644 --- a/crates/ffi/src/account_client.rs +++ b/crates/ffi/src/account_client.rs @@ -127,6 +127,19 @@ impl FFIAccountClient { .sign_message_with_mnemonic(message, mnemonic) .map_err(|e| FFIError::Unknown(e.to_string())) } + + pub async fn wait_for_user_operation_receipt( + &self, + user_operation_hash: String, + ) -> Result { + self.account_client + .wait_for_user_operation_receipt(user_operation_hash) + .await + .iter() + .map(serde_json::to_string) + .collect::>() + .map_err(|e| FFIError::Unknown(e.to_string())) + } } impl From for Transaction { diff --git a/crates/ffi/src/lib.rs b/crates/ffi/src/lib.rs index 6c0b35f..2921b76 100644 --- a/crates/ffi/src/lib.rs +++ b/crates/ffi/src/lib.rs @@ -79,6 +79,11 @@ mod ffi { message: String, mnemonic: String, ) -> Result; + + pub async fn wait_for_user_operation_receipt( + &self, + user_operation_hash: String + ) -> Result; } extern "Rust" { diff --git a/crates/yttrium/src/account_client.rs b/crates/yttrium/src/account_client.rs index c1e8e14..0b865ee 100644 --- a/crates/yttrium/src/account_client.rs +++ b/crates/yttrium/src/account_client.rs @@ -1,3 +1,7 @@ +use crate::bundler::{ + client::BundlerClient, config::BundlerConfig, + models::user_operation_receipt::UserOperationReceipt, +}; use crate::config::Config; use crate::private_key_service::PrivateKeyService; use crate::sign_service::SignService; @@ -161,6 +165,30 @@ impl AccountClient { Ok(signature) } + + pub async fn wait_for_user_operation_receipt( + &self, + user_operation_hash: String, + ) -> eyre::Result { + println!("Querying for receipts..."); + + let bundler_base_url = self.config.clone().endpoints.bundler.base_url; + + let bundler_client = + BundlerClient::new(BundlerConfig::new(bundler_base_url.clone())); + let receipt = bundler_client + .wait_for_user_operation_receipt(user_operation_hash.clone()) + .await?; + + println!("Received User Operation receipt: {:?}", receipt); + + let tx_hash = receipt.clone().receipt.transaction_hash; + println!( + "UserOperation included: https://sepolia.etherscan.io/tx/{}", + tx_hash + ); + Ok(receipt) + } } impl AccountClient { diff --git a/crates/yttrium/src/bundler/models/user_operation_receipt.rs b/crates/yttrium/src/bundler/models/user_operation_receipt.rs index e652b14..ba17fa0 100644 --- a/crates/yttrium/src/bundler/models/user_operation_receipt.rs +++ b/crates/yttrium/src/bundler/models/user_operation_receipt.rs @@ -1,7 +1,7 @@ use alloy::primitives::Address; use serde::{Deserialize, Serialize}; -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserOperationReceiptReceipt { pub transaction_hash: String, @@ -19,7 +19,7 @@ pub struct UserOperationReceiptReceipt { pub effective_gas_price: String, } -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize,)] #[serde(rename_all = "camelCase")] pub struct UserOperationReceipt { pub user_op_hash: String, diff --git a/crates/yttrium/src/transaction/send.rs b/crates/yttrium/src/transaction/send.rs index a41e09d..aa16a2b 100644 --- a/crates/yttrium/src/transaction/send.rs +++ b/crates/yttrium/src/transaction/send.rs @@ -349,24 +349,6 @@ mod tests { println!("Received User Operation hash: {:?}", user_operation_hash); - // let receipt = bundler_client - // .get_user_operation_receipt(user_operation_hash.clone()) - // .await?; - - // println!("Received User Operation receipt: {:?}", receipt); - - // println!("Querying for receipts..."); - - // let receipt = bundler_client - // .wait_for_user_operation_receipt(user_operation_hash.clone()) - // .await?; - - // let tx_hash = receipt.receipt.transaction_hash; - // println!( - // "UserOperation included: https://sepolia.etherscan.io/tx/{}", - // tx_hash - // ); - Ok(user_operation_hash) } diff --git a/crates/yttrium/src/transaction/send/simple_account_test.rs b/crates/yttrium/src/transaction/send/simple_account_test.rs index dee693a..f76495a 100644 --- a/crates/yttrium/src/transaction/send/simple_account_test.rs +++ b/crates/yttrium/src/transaction/send/simple_account_test.rs @@ -252,28 +252,6 @@ pub async fn send_transaction_with_signer( println!("Received User Operation hash: {:?}", user_operation_hash); - // TODO convert to polling - use std::time::Duration; - tokio::time::sleep(Duration::from_secs(2)).await; - - let receipt = bundler_client - .get_user_operation_receipt(user_operation_hash.clone()) - .await?; - - println!("Received User Operation receipt: {:?}", receipt); - - println!("Querying for receipts..."); - - let receipt = bundler_client - .wait_for_user_operation_receipt(user_operation_hash.clone()) - .await?; - - let tx_hash = receipt.receipt.transaction_hash; - println!( - "UserOperation included: https://sepolia.etherscan.io/tx/{}", - tx_hash - ); - Ok(user_operation_hash) } @@ -479,20 +457,6 @@ mod tests { ) .await?; - println!("Querying for receipts..."); - - let receipt = bundler_client - .wait_for_user_operation_receipt(user_operation_hash.clone()) - .await?; - - println!("Received User Operation receipt: {:?}", receipt); - - let tx_hash = receipt.receipt.transaction_hash; - println!( - "UserOperation included: https://sepolia.etherscan.io/tx/{}", - tx_hash - ); - Ok(user_operation_hash) } diff --git a/platforms/swift/Sources/Yttrium/AccountClient.swift b/platforms/swift/Sources/Yttrium/AccountClient.swift index 8eccefc..3c7f968 100644 --- a/platforms/swift/Sources/Yttrium/AccountClient.swift +++ b/platforms/swift/Sources/Yttrium/AccountClient.swift @@ -131,6 +131,22 @@ public final class AccountClient: AccountClientProtocol { ) .toString() } + + public func waitForUserOperationReceipt( + userOperationHash: String + ) async throws -> UserOperationReceipt { + let jsonString = try await coreAccountClient + .wait_for_user_operation_receipt( + userOperationHash.intoRustString() + ) + .toString() + let jsonData = Data(jsonString.utf8) + let receipt = try JSONDecoder().decode( + UserOperationReceipt.self, + from: jsonData + ) + return receipt + } } extension Transaction { diff --git a/platforms/swift/Tests/YttriumTests/AccountClientTests.swift b/platforms/swift/Tests/YttriumTests/AccountClientTests.swift index 012179e..d855997 100644 --- a/platforms/swift/Tests/YttriumTests/AccountClientTests.swift +++ b/platforms/swift/Tests/YttriumTests/AccountClientTests.swift @@ -27,7 +27,8 @@ final class AccountClientTests: XCTestCase { entryPoint: Self.entryPoint, chainId: Self.chainId, config: config, - signerType: .privateKey + signerType: .privateKey, + safe: false ) accountClient.register(privateKey: Self.privateKeyHex) @@ -44,7 +45,8 @@ final class AccountClientTests: XCTestCase { entryPoint: Self.entryPoint, chainId: Self.chainId, config: config, - signerType: .privateKey + signerType: .privateKey, + safe: false ) accountClient.register(privateKey: Self.privateKeyHex)