use rust_x402::{
client::X402Client,
crypto::{
eip712::{create_transfer_with_authorization_hash, Domain},
signature::{generate_nonce, sign_message_hash, verify_payment_payload},
},
types::{ExactEvmPayload, ExactEvmPayloadAuthorization, PaymentPayload, PaymentRequirements},
Result,
};
use std::str::FromStr;
pub struct WalletIntegration {
private_key: String,
network: String,
}
impl WalletIntegration {
pub fn new(private_key: String, network: String) -> Self {
Self {
private_key,
network,
}
}
pub fn create_signed_payment_payload(
&self,
requirements: &PaymentRequirements,
from_address: &str,
) -> Result<PaymentPayload> {
let nonce = generate_nonce();
let now = chrono::Utc::now().timestamp();
let valid_after = (now - 60).to_string(); let valid_before = (now + 300).to_string();
let authorization = ExactEvmPayloadAuthorization::new(
from_address,
&requirements.pay_to,
&requirements.max_amount_required,
valid_after,
valid_before,
format!("{:?}", nonce),
);
let network_config = get_network_config(&self.network)?;
let domain = Domain {
name: "USD Coin".to_string(),
version: "2".to_string(),
chain_id: network_config.chain_id,
verifying_contract: network_config.usdc_contract,
};
let message_hash = create_transfer_with_authorization_hash(
&domain,
ethereum_types::Address::from_str(from_address)?,
ethereum_types::Address::from_str(&requirements.pay_to)?,
ethereum_types::U256::from_str_radix(&requirements.max_amount_required, 10)?,
ethereum_types::U256::from_str_radix(&authorization.valid_after, 10)?,
ethereum_types::U256::from_str_radix(&authorization.valid_before, 10)?,
nonce,
)?;
let signature = sign_message_hash(message_hash, &self.private_key)?;
let payload = ExactEvmPayload {
signature,
authorization,
};
let payment_payload =
PaymentPayload::new(&requirements.scheme, &requirements.network, payload);
let is_valid =
verify_payment_payload(&payment_payload.payload, from_address, &self.network)?;
if !is_valid {
return Err(rust_x402::X402Error::invalid_signature(
"Generated signature verification failed",
));
}
Ok(payment_payload)
}
pub async fn make_payment_request(
&self,
client: &X402Client,
url: &str,
from_address: &str,
) -> Result<serde_json::Value> {
let response = client.get(url).send().await?;
if response.status() != 402 {
return Ok(response.json().await?);
}
let payment_req: PaymentRequirements = response.json().await?;
let payment_payload = self.create_signed_payment_payload(&payment_req, from_address)?;
let final_response = client.get(url).payment(&payment_payload)?.send().await?;
if !final_response.status().is_success() {
return Err(rust_x402::X402Error::payment_verification_failed(format!(
"Payment request failed with status: {}",
final_response.status()
)));
}
if let Some(settlement_header) = final_response.headers().get("X-PAYMENT-RESPONSE") {
println!(
"Payment settled: {}",
settlement_header.to_str().unwrap_or("")
);
}
Ok(final_response.json().await?)
}
}
#[derive(Debug, Clone)]
struct NetworkConfig {
chain_id: u64,
usdc_contract: ethereum_types::Address,
}
fn get_network_config(network: &str) -> Result<NetworkConfig> {
match network {
"base-sepolia" => Ok(NetworkConfig {
chain_id: 84532,
usdc_contract: ethereum_types::Address::from_str(
"0x036CbD53842c5426634e7929541eC2318f3dCF7e",
)?,
}),
"base" => Ok(NetworkConfig {
chain_id: 8453,
usdc_contract: ethereum_types::Address::from_str(
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
)?,
}),
_ => Err(rust_x402::X402Error::invalid_network(format!(
"Unsupported network: {}",
network
))),
}
}
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
println!("🔐 Real Wallet Integration Example");
println!("===================================");
let private_key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
let network = "base-sepolia";
let from_address = "0x857b06519E91e3A54538791bDbb0E22373e36b66";
let wallet = WalletIntegration::new(private_key.to_string(), network.to_string());
let client = X402Client::new()?;
println!("💰 Making payment request...");
match wallet
.make_payment_request(&client, "http://localhost:4021/joke", from_address)
.await
{
Ok(response) => {
println!("✅ Payment successful!");
println!("Response: {}", serde_json::to_string_pretty(&response)?);
}
Err(e) => {
println!("❌ Payment failed: {}", e);
println!("This is expected if no server is running on localhost:4021");
}
}
println!("\n📝 Key Points for Production Implementation:");
println!("1. Use secure key storage (hardware wallets, encrypted files)");
println!("2. Implement proper error handling and retry logic");
println!("3. Add transaction monitoring and confirmation");
println!("4. Use proper network configuration management");
println!("5. Implement rate limiting and anti-replay protection");
println!("6. Add comprehensive logging and monitoring");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_network_config() {
let base_sepolia = get_network_config("base-sepolia").unwrap();
assert_eq!(base_sepolia.chain_id, 84532);
let base = get_network_config("base").unwrap();
assert_eq!(base.chain_id, 8453);
let unsupported = get_network_config("unsupported");
assert!(unsupported.is_err());
}
#[test]
fn test_wallet_creation() {
let wallet = WalletIntegration::new("0x1234".to_string(), "base-sepolia".to_string());
assert_eq!(wallet.network, "base-sepolia");
}
}