use totalreclaw_memory::crypto;
use totalreclaw_memory::relay::{RelayClient, RelayConfig};
use totalreclaw_memory::search;
use totalreclaw_memory::setup;
use totalreclaw_memory::userop;
use totalreclaw_memory::wallet;
const RELAY_URL: &str = "https://api-staging.totalreclaw.xyz";
const INDEXING_WAIT_SECS: u64 = 45;
#[tokio::test]
#[ignore] async fn test_native_store_and_recall() {
println!("=== Native UserOp E2E Test ===\n");
let mnemonic = setup::generate_mnemonic();
println!("1. Generated test mnemonic: {}...", &mnemonic[..25]);
let eth_wallet = wallet::derive_eoa(&mnemonic).expect("EOA derivation failed");
println!("2. EOA: {}", eth_wallet.address);
let smart_account =
wallet::resolve_smart_account_address(ð_wallet.address, "https://sepolia.base.org")
.await
.expect("Smart Account resolution failed");
println!(" Smart Account: {}", smart_account);
let keys = crypto::derive_keys_from_mnemonic(&mnemonic).expect("Key derivation failed");
let auth_key_hex = hex::encode(keys.auth_key);
let auth_key_hash = crypto::compute_auth_key_hash(&keys.auth_key);
let salt_hex = hex::encode(keys.salt);
let relay = RelayClient::new(RelayConfig {
relay_url: RELAY_URL.to_string(),
auth_key_hex: auth_key_hex.clone(),
wallet_address: smart_account.clone(),
is_test: true,
chain_id: 84532,
});
println!("3. Registering with relay...");
match relay.register(&auth_key_hash, &salt_hex).await {
Ok(uid) => println!(" Registered: {}", uid),
Err(e) => println!(" Registration warning (may already exist): {}", e),
}
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let fact_text = format!("Native UserOp test fact at {}", ts);
println!("\n4. Storing: '{}'", fact_text);
let encrypted_b64 = crypto::encrypt(&fact_text, &keys.encryption_key).unwrap();
let encrypted_bytes = base64::Engine::decode(
&base64::engine::general_purpose::STANDARD,
&encrypted_b64,
)
.unwrap();
let encrypted_hex = hex::encode(&encrypted_bytes);
let blind_indices = totalreclaw_memory::blind::generate_blind_indices(&fact_text);
let content_fp =
totalreclaw_memory::fingerprint::generate_content_fingerprint(&fact_text, &keys.dedup_key);
let fact_id = uuid::Uuid::now_v7().to_string();
let timestamp = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
let payload = totalreclaw_memory::protobuf::FactPayload {
id: fact_id.clone(),
timestamp,
owner: smart_account.clone(),
encrypted_blob_hex: encrypted_hex,
blind_indices,
decay_score: 0.8,
source: "zeroclaw:native-userop-e2e".to_string(),
content_fp,
agent_id: "native-userop-e2e".to_string(),
encrypted_embedding: None,
version: totalreclaw_memory::protobuf::DEFAULT_PROTOBUF_VERSION,
};
let protobuf = totalreclaw_memory::protobuf::encode_fact_protobuf(&payload);
let calldata = userop::encode_single_call(&protobuf);
println!(" Calldata size: {} bytes", calldata.len());
let result = userop::submit_userop(
&calldata,
&smart_account,
ð_wallet.private_key,
RELAY_URL,
&auth_key_hex,
84532,
true,
)
.await
.expect("UserOp submission failed");
println!(
" Submitted: txHash={} userOpHash={} success={}",
result.tx_hash, result.user_op_hash, result.success
);
assert!(result.success, "UserOp should succeed");
assert!(
!result.tx_hash.is_empty(),
"Should have a transaction hash"
);
println!(
"\n5. Waiting {}s for subgraph indexing...",
INDEXING_WAIT_SECS
);
tokio::time::sleep(std::time::Duration::from_secs(INDEXING_WAIT_SECS)).await;
println!("\n6. Recalling via blind index search...");
let trapdoors =
totalreclaw_memory::blind::generate_blind_indices("native userop test fact");
let candidates =
search::search_candidates(&relay, &smart_account, &trapdoors, 100)
.await
.unwrap_or_default();
println!(" Found {} candidates", candidates.len());
let mut found = false;
for fact in &candidates {
if let Some(b64) = search::hex_blob_to_base64(&fact.encrypted_blob) {
if let Ok(text) = crypto::decrypt(&b64, &keys.encryption_key) {
println!(" Decrypted: {}", text);
if text.contains(&ts.to_string()) {
found = true;
}
}
}
}
assert!(found, "Should find the fact we stored via native UserOp");
println!("\nNative UserOp E2E PASSED!");
}
#[tokio::test]
#[ignore] async fn test_batch_store_and_recall() {
println!("=== Batch UserOp E2E Test ===\n");
let mnemonic = setup::generate_mnemonic();
println!("1. Generated test mnemonic: {}...", &mnemonic[..25]);
let eth_wallet = wallet::derive_eoa(&mnemonic).expect("EOA derivation failed");
let smart_account =
wallet::resolve_smart_account_address(ð_wallet.address, "https://sepolia.base.org")
.await
.expect("Smart Account resolution failed");
println!(" Smart Account: {}", smart_account);
let keys = crypto::derive_keys_from_mnemonic(&mnemonic).expect("Key derivation failed");
let auth_key_hex = hex::encode(keys.auth_key);
let auth_key_hash = crypto::compute_auth_key_hash(&keys.auth_key);
let salt_hex = hex::encode(keys.salt);
let relay = RelayClient::new(RelayConfig {
relay_url: RELAY_URL.to_string(),
auth_key_hex: auth_key_hex.clone(),
wallet_address: smart_account.clone(),
is_test: true,
chain_id: 84532,
});
relay
.register(&auth_key_hash, &salt_hex)
.await
.expect("Registration failed");
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let facts = vec![
format!("Batch test fact one at {}", ts),
format!("Batch test fact two at {}", ts + 1),
format!("Batch test fact three at {}", ts + 2),
];
let mut protobuf_payloads = Vec::new();
for (i, fact_text) in facts.iter().enumerate() {
let encrypted_b64 = crypto::encrypt(fact_text, &keys.encryption_key).unwrap();
let encrypted_bytes = base64::Engine::decode(
&base64::engine::general_purpose::STANDARD,
&encrypted_b64,
)
.unwrap();
let encrypted_hex = hex::encode(&encrypted_bytes);
let blind_indices = totalreclaw_memory::blind::generate_blind_indices(fact_text);
let content_fp = totalreclaw_memory::fingerprint::generate_content_fingerprint(
fact_text,
&keys.dedup_key,
);
let payload = totalreclaw_memory::protobuf::FactPayload {
id: uuid::Uuid::now_v7().to_string(),
timestamp: chrono::Utc::now()
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
owner: smart_account.clone(),
encrypted_blob_hex: encrypted_hex,
blind_indices,
decay_score: 0.8,
source: "zeroclaw:batch-e2e".to_string(),
content_fp,
agent_id: "batch-e2e".to_string(),
encrypted_embedding: None,
version: totalreclaw_memory::protobuf::DEFAULT_PROTOBUF_VERSION,
};
protobuf_payloads.push(totalreclaw_memory::protobuf::encode_fact_protobuf(&payload));
println!(" Fact {}: '{}'", i + 1, fact_text);
}
println!("\n2. Submitting {} facts as batch UserOp...", facts.len());
let result = relay
.submit_fact_batch_native(&protobuf_payloads, ð_wallet.private_key)
.await
.expect("Batch UserOp submission failed");
println!(
" Batch submitted: txHash={} success={}",
result.tx_hash, result.success
);
assert!(result.success, "Batch UserOp should succeed");
println!(
"\n3. Waiting {}s for subgraph indexing...",
INDEXING_WAIT_SECS
);
tokio::time::sleep(std::time::Duration::from_secs(INDEXING_WAIT_SECS)).await;
println!("\n4. Recalling via blind index search...");
let trapdoors =
totalreclaw_memory::blind::generate_blind_indices("batch test fact");
let candidates =
search::search_candidates(&relay, &smart_account, &trapdoors, 100)
.await
.unwrap_or_default();
println!(" Found {} candidates", candidates.len());
let mut found_count = 0;
for fact in &candidates {
if let Some(b64) = search::hex_blob_to_base64(&fact.encrypted_blob) {
if let Ok(text) = crypto::decrypt(&b64, &keys.encryption_key) {
println!(" Decrypted: {}", text);
if text.contains(&ts.to_string())
|| text.contains(&(ts + 1).to_string())
|| text.contains(&(ts + 2).to_string())
{
found_count += 1;
}
}
}
}
assert!(
found_count >= 3,
"Should find all 3 batch-stored facts, found {}",
found_count
);
println!("\nBatch UserOp E2E PASSED! Found {}/3 facts.", found_count);
}