From 354ed12c505e9b23c82affcfaf6130fc86a70ee8 Mon Sep 17 00:00:00 2001 From: takasaki404 Date: Wed, 25 Mar 2026 23:04:35 +0800 Subject: [PATCH 1/3] feat: use `Signer` trait for fee payer and session closer --- src/protocol/methods/tempo/method.rs | 22 +++++++++++++------- src/protocol/methods/tempo/session_method.rs | 17 +++++++++++---- src/server/tempo.rs | 12 ++++++++--- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/protocol/methods/tempo/method.rs b/src/protocol/methods/tempo/method.rs index fd586e8e..cb5ff488 100644 --- a/src/protocol/methods/tempo/method.rs +++ b/src/protocol/methods/tempo/method.rs @@ -103,7 +103,7 @@ fn parse_b256_hex(s: &str) -> Option { #[derive(Clone)] pub struct ChargeMethod

{ provider: Arc

, - fee_payer_signer: Option>, + fee_payer_signer: Option>, store: Option>, } @@ -136,7 +136,13 @@ where /// /// When set, requests with `feePayer: true` will be accepted and /// broadcast. Without a fee payer signer, such requests are rejected. - pub fn with_fee_payer(mut self, signer: alloy::signers::local::PrivateKeySigner) -> Self { + /// + /// Accepts any type implementing alloy's [`Signer`](alloy::signers::Signer) + /// trait — local private keys, KMS-backed signers, hardware wallets, etc. + pub fn with_fee_payer( + mut self, + signer: impl alloy::signers::Signer + Send + Sync + 'static, + ) -> Self { self.fee_payer_signer = Some(Arc::new(signer)); self } @@ -534,7 +540,8 @@ where ) })?; - self.cosign_fee_payer_transaction(&tx_bytes, fee_payer_signer, currency)? + self.cosign_fee_payer_transaction(&tx_bytes, fee_payer_signer, currency) + .await? } else { tx_bytes.to_vec() }; @@ -574,16 +581,16 @@ where /// Accepts a `0x78` fee payer envelope, recovers the sender via /// ecrecover, validates fee-payer invariants, then co-signs and /// returns a complete `0x76` transaction ready for broadcast. - fn cosign_fee_payer_transaction( + async fn cosign_fee_payer_transaction( &self, tx_bytes: &[u8], - fee_payer_signer: &alloy::signers::local::PrivateKeySigner, + fee_payer_signer: &(dyn alloy::signers::Signer + Send + Sync), fee_token: Address, ) -> Result, VerificationError> { use super::fee_payer_envelope::{FeePayerEnvelope78, TEMPO_FEE_PAYER_ENVELOPE_TYPE_ID}; use alloy::consensus::transaction::SignerRecoverable; use alloy::eips::Encodable2718; - use alloy::signers::SignerSync; + use alloy::signers::Signer as _; use tempo_primitives::transaction::TEMPO_EXPIRING_NONCE_KEY; if tx_bytes.is_empty() { @@ -660,7 +667,8 @@ where // Compute the fee payer signature hash and co-sign let fp_hash = tx.fee_payer_signature_hash(sender); let fp_sig = fee_payer_signer - .sign_hash_sync(&fp_hash) + .sign_hash(&fp_hash) + .await .map_err(|e| VerificationError::new(format!("Failed to co-sign transaction: {e}")))?; tx.fee_payer_signature = Some(fp_sig); diff --git a/src/protocol/methods/tempo/session_method.rs b/src/protocol/methods/tempo/session_method.rs index 004dfe96..bf4c2668 100644 --- a/src/protocol/methods/tempo/session_method.rs +++ b/src/protocol/methods/tempo/session_method.rs @@ -208,7 +208,10 @@ pub struct SessionMethod

{ store: Arc, config: SessionMethodConfig, /// Optional signer for submitting on-chain close transactions. - close_signer: Option>, + /// + /// Accepts any EVM signer (local key, KMS, hardware wallet, etc.). + // TODO: abstract signer for Solana sessions + close_signer: Option>, } impl

SessionMethod

{ @@ -249,7 +252,13 @@ where } /// Set the signer used for submitting on-chain close transactions. - pub fn with_close_signer(mut self, signer: alloy::signers::local::PrivateKeySigner) -> Self { + /// + /// Accepts any type implementing alloy's [`Signer`](alloy::signers::Signer) + /// trait — local private keys, KMS-backed signers, hardware wallets, etc. + pub fn with_close_signer( + mut self, + signer: impl alloy::signers::Signer + Send + Sync + 'static, + ) -> Self { self.close_signer = Some(Arc::new(signer)); self } @@ -680,7 +689,7 @@ where let close_tx_hash = if let Some(ref signer) = self.close_signer { use alloy::eips::Encodable2718; use alloy::primitives::Bytes; - use alloy::signers::SignerSync; + use alloy::signers::Signer as _; use alloy::sol_types::SolCall; use tempo_primitives::transaction::Call; use tempo_primitives::TempoTransaction; @@ -724,7 +733,7 @@ where }; let sig_hash = tempo_tx.signature_hash(); - let signature = signer.sign_hash_sync(&sig_hash).map_err(|e| { + let signature = signer.sign_hash(&sig_hash).await.map_err(|e| { VerificationError::network_error(format!("failed to sign close tx: {}", e)) })?; let signed_tx = tempo_tx.into_signed(signature.into()); diff --git a/src/server/tempo.rs b/src/server/tempo.rs index 951a1913..02b1050c 100644 --- a/src/server/tempo.rs +++ b/src/server/tempo.rs @@ -32,7 +32,7 @@ pub struct TempoBuilder { pub(crate) decimals: u32, pub(crate) fee_payer: bool, pub(crate) chain_id: Option, - pub(crate) fee_payer_signer: Option, + pub(crate) fee_payer_signer: Option>, } impl TempoBuilder { @@ -96,8 +96,14 @@ impl TempoBuilder { /// When clients send transactions with `feePayer: true`, the server /// uses this signer to co-sign and sponsor the transaction gas fees. /// The signer's account must have sufficient balance for gas. - pub fn fee_payer_signer(mut self, signer: alloy::signers::local::PrivateKeySigner) -> Self { - self.fee_payer_signer = Some(signer); + /// + /// Accepts any type implementing alloy's [`Signer`](alloy::signers::Signer) + /// trait — local private keys, KMS-backed signers, hardware wallets, etc. + pub fn fee_payer_signer( + mut self, + signer: impl alloy::signers::Signer + Send + Sync + 'static, + ) -> Self { + self.fee_payer_signer = Some(Box::new(signer)); self } } From ace87688e2c531ae8da4ca7e3d6e1f37020dbe1e Mon Sep 17 00:00:00 2001 From: takasaki404 Date: Wed, 25 Mar 2026 23:42:32 +0800 Subject: [PATCH 2/3] fix: compilation error --- src/protocol/methods/tempo/method.rs | 57 +++++++++++--------- src/protocol/methods/tempo/session_method.rs | 1 - 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/protocol/methods/tempo/method.rs b/src/protocol/methods/tempo/method.rs index cb5ff488..84159529 100644 --- a/src/protocol/methods/tempo/method.rs +++ b/src/protocol/methods/tempo/method.rs @@ -540,7 +540,7 @@ where ) })?; - self.cosign_fee_payer_transaction(&tx_bytes, fee_payer_signer, currency) + self.cosign_fee_payer_transaction(&tx_bytes, &**fee_payer_signer, currency) .await? } else { tx_bytes.to_vec() @@ -590,7 +590,6 @@ where use super::fee_payer_envelope::{FeePayerEnvelope78, TEMPO_FEE_PAYER_ENVELOPE_TYPE_ID}; use alloy::consensus::transaction::SignerRecoverable; use alloy::eips::Encodable2718; - use alloy::signers::Signer as _; use tempo_primitives::transaction::TEMPO_EXPIRING_NONCE_KEY; if tx_bytes.is_empty() { @@ -983,8 +982,8 @@ mod tests { /// Round-trip: sign 0x78 envelope → cosign_fee_payer_transaction /// succeeds and produces a valid co-signed 0x76 transaction. - #[test] - fn test_fee_payer_round_trip_0x78_envelope() { + #[tokio::test] + async fn test_fee_payer_round_trip_0x78_envelope() { use super::super::{FeePayerEnvelope78, TEMPO_FEE_PAYER_ENVELOPE_TYPE_ID}; use alloy::eips::Decodable2718; use alloy::signers::SignerSync; @@ -1014,11 +1013,13 @@ mod tests { let result = method.cosign_fee_payer_transaction( &encoded, - method.fee_payer_signer.as_ref().unwrap(), + &**method.fee_payer_signer.as_ref().unwrap(), fee_token, ); - let co_signed = result.expect("cosign should succeed for valid 0x78 envelope"); + let co_signed = result + .await + .expect("cosign should succeed for valid 0x78 envelope"); // Result should be a valid 0x76 transaction assert_eq!( @@ -1040,8 +1041,8 @@ mod tests { } /// cosign_fee_payer_transaction rejects txs with wrong nonce_key. - #[test] - fn test_cosign_rejects_wrong_nonce_key() { + #[tokio::test] + async fn test_cosign_rejects_wrong_nonce_key() { let client_signer = alloy::signers::local::PrivateKeySigner::random(); let fee_payer_signer = alloy::signers::local::PrivateKeySigner::random(); let fee_token: Address = "0x20c0000000000000000000000000000000000000" @@ -1061,11 +1062,11 @@ mod tests { let result = method.cosign_fee_payer_transaction( &encoded, - method.fee_payer_signer.as_ref().unwrap(), + &**method.fee_payer_signer.as_ref().unwrap(), fee_token, ); - let err = result.expect_err("should reject wrong nonce_key"); + let err = result.await.expect_err("should reject wrong nonce_key"); assert!( err.to_string().contains("expiring nonce key"), "error should mention expiring nonce key, got: {err}" @@ -1073,8 +1074,8 @@ mod tests { } /// cosign_fee_payer_transaction rejects txs without valid_before. - #[test] - fn test_cosign_rejects_missing_valid_before() { + #[tokio::test] + async fn test_cosign_rejects_missing_valid_before() { let client_signer = alloy::signers::local::PrivateKeySigner::random(); let fee_payer_signer = alloy::signers::local::PrivateKeySigner::random(); let fee_token: Address = "0x20c0000000000000000000000000000000000000" @@ -1094,11 +1095,13 @@ mod tests { let result = method.cosign_fee_payer_transaction( &encoded, - method.fee_payer_signer.as_ref().unwrap(), + &**method.fee_payer_signer.as_ref().unwrap(), fee_token, ); - let err = result.expect_err("should reject missing valid_before"); + let err = result + .await + .expect_err("should reject missing valid_before"); assert!( err.to_string().contains("must include valid_before"), "error should mention valid_before, got: {err}" @@ -1106,8 +1109,8 @@ mod tests { } /// cosign_fee_payer_transaction rejects txs with expired valid_before. - #[test] - fn test_cosign_rejects_expired_valid_before() { + #[tokio::test] + async fn test_cosign_rejects_expired_valid_before() { let client_signer = alloy::signers::local::PrivateKeySigner::random(); let fee_payer_signer = alloy::signers::local::PrivateKeySigner::random(); let fee_token: Address = "0x20c0000000000000000000000000000000000000" @@ -1134,11 +1137,13 @@ mod tests { let result = method.cosign_fee_payer_transaction( &encoded, - method.fee_payer_signer.as_ref().unwrap(), + &**method.fee_payer_signer.as_ref().unwrap(), fee_token, ); - let err = result.expect_err("should reject expired valid_before"); + let err = result + .await + .expect_err("should reject expired valid_before"); assert!( err.to_string().contains("expired"), "error should mention expiration, got: {err}" @@ -1146,8 +1151,8 @@ mod tests { } /// cosign_fee_payer_transaction rejects empty input. - #[test] - fn test_cosign_rejects_empty_input() { + #[tokio::test] + async fn test_cosign_rejects_empty_input() { let fee_payer_signer = alloy::signers::local::PrivateKeySigner::random(); let fee_token: Address = "0x20c0000000000000000000000000000000000000" .parse() @@ -1161,11 +1166,11 @@ mod tests { let result = method.cosign_fee_payer_transaction( &[], - method.fee_payer_signer.as_ref().unwrap(), + &**method.fee_payer_signer.as_ref().unwrap(), fee_token, ); - let err = result.expect_err("should reject empty input"); + let err = result.await.expect_err("should reject empty input"); assert!( err.to_string().contains("Empty transaction bytes"), "error should mention empty, got: {err}" @@ -1173,8 +1178,8 @@ mod tests { } /// cosign_fee_payer_transaction rejects non-0x78 type byte. - #[test] - fn test_cosign_rejects_wrong_type_byte() { + #[tokio::test] + async fn test_cosign_rejects_wrong_type_byte() { let fee_payer_signer = alloy::signers::local::PrivateKeySigner::random(); let fee_token: Address = "0x20c0000000000000000000000000000000000000" .parse() @@ -1188,11 +1193,11 @@ mod tests { let result = method.cosign_fee_payer_transaction( &[0x79, 0xc0], // wrong type byte - method.fee_payer_signer.as_ref().unwrap(), + &**method.fee_payer_signer.as_ref().unwrap(), fee_token, ); - let err = result.expect_err("should reject wrong type"); + let err = result.await.expect_err("should reject wrong type"); assert!( err.to_string() .contains("Expected fee payer envelope (0x78)"), diff --git a/src/protocol/methods/tempo/session_method.rs b/src/protocol/methods/tempo/session_method.rs index bf4c2668..f4b2d9ab 100644 --- a/src/protocol/methods/tempo/session_method.rs +++ b/src/protocol/methods/tempo/session_method.rs @@ -689,7 +689,6 @@ where let close_tx_hash = if let Some(ref signer) = self.close_signer { use alloy::eips::Encodable2718; use alloy::primitives::Bytes; - use alloy::signers::Signer as _; use alloy::sol_types::SolCall; use tempo_primitives::transaction::Call; use tempo_primitives::TempoTransaction; From e30ee98965908e1ec2e94ce22992fa7fadb97fdd Mon Sep 17 00:00:00 2001 From: takasaki404 Date: Wed, 25 Mar 2026 23:53:56 +0800 Subject: [PATCH 3/3] chore: remove Solana TODO comment --- src/protocol/methods/tempo/session_method.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/protocol/methods/tempo/session_method.rs b/src/protocol/methods/tempo/session_method.rs index f4b2d9ab..e0d79697 100644 --- a/src/protocol/methods/tempo/session_method.rs +++ b/src/protocol/methods/tempo/session_method.rs @@ -210,7 +210,6 @@ pub struct SessionMethod

{ /// Optional signer for submitting on-chain close transactions. /// /// Accepts any EVM signer (local key, KMS, hardware wallet, etc.). - // TODO: abstract signer for Solana sessions close_signer: Option>, }