1use alloy::primitives::{keccak256, Address, B256, FixedBytes, U256};
6use alloy::providers::ProviderBuilder;
7use alloy::signers::local::PrivateKeySigner;
8use alloy::signers::Signer;
9use reqwest::{Client, Method, RequestBuilder, Response};
10use std::collections::HashMap;
11use std::time::{SystemTime, UNIX_EPOCH};
12
13use crate::agent_card::{
14 A2AAgentCard, AgentSkill, CardCredentials, SelfProtocolExtension, TrustModel,
15 get_provider_label,
16};
17use crate::constants::{
18 headers, network_config, IAgentRegistry, IHumanProofProvider, NetworkName, DEFAULT_NETWORK,
19};
20use crate::registration_flow::{
21 DeregistrationRequest, DeregistrationSession, RegistrationError, RegistrationRequest,
22 RegistrationSession, DEFAULT_API_BASE,
23};
24
25#[derive(Debug, Clone)]
27pub struct SelfAgentConfig {
28 pub private_key: String,
30 pub network: Option<NetworkName>,
32 pub registry_address: Option<Address>,
34 pub rpc_url: Option<String>,
36}
37
38#[derive(Debug, Clone)]
40pub struct AgentInfo {
41 pub address: Address,
42 pub agent_key: B256,
43 pub agent_id: U256,
44 pub is_verified: bool,
45 pub nullifier: U256,
46 pub agent_count: U256,
47}
48
49pub struct SelfAgent {
54 signer: PrivateKeySigner,
55 private_key: String,
56 network_name: NetworkName,
57 registry_address: Address,
58 rpc_url: String,
59 agent_key: B256,
60 http_client: Client,
61}
62
63impl SelfAgent {
64 pub fn new(config: SelfAgentConfig) -> Result<Self, crate::Error> {
66 let network_name = config.network.unwrap_or(DEFAULT_NETWORK);
67 let net = network_config(network_name);
68 let signer: PrivateKeySigner = config
69 .private_key
70 .parse()
71 .map_err(|_| crate::Error::InvalidPrivateKey)?;
72 let agent_key = address_to_agent_key(signer.address());
73
74 Ok(Self {
75 signer,
76 private_key: config.private_key,
77 network_name,
78 registry_address: config.registry_address.unwrap_or(net.registry_address),
79 rpc_url: config.rpc_url.unwrap_or_else(|| net.rpc_url.to_string()),
80 agent_key,
81 http_client: Client::new(),
82 })
83 }
84
85 pub fn address(&self) -> Address {
87 self.signer.address()
88 }
89
90 pub fn agent_key(&self) -> B256 {
92 self.agent_key
93 }
94
95 fn make_provider(
96 &self,
97 ) -> Result<impl alloy::providers::Provider + Clone, crate::Error> {
98 let url: reqwest::Url = self
99 .rpc_url
100 .parse()
101 .map_err(|_| crate::Error::InvalidRpcUrl)?;
102 Ok(ProviderBuilder::new().connect_http(url))
103 }
104
105 fn make_signer_provider(
107 &self,
108 ) -> Result<impl alloy::providers::Provider + Clone, crate::Error> {
109 let url: reqwest::Url = self
110 .rpc_url
111 .parse()
112 .map_err(|_| crate::Error::InvalidRpcUrl)?;
113 let wallet = alloy::network::EthereumWallet::from(self.signer.clone());
114 Ok(ProviderBuilder::new().wallet(wallet).connect_http(url))
115 }
116
117 pub async fn is_registered(&self) -> Result<bool, crate::Error> {
119 let provider = self.make_provider()?;
120 let registry = IAgentRegistry::new(self.registry_address, provider);
121 let result = registry
122 .isVerifiedAgent(self.agent_key)
123 .call()
124 .await
125 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
126 Ok(result)
127 }
128
129 pub async fn get_info(&self) -> Result<AgentInfo, crate::Error> {
131 let provider = self.make_provider()?;
132 let registry = IAgentRegistry::new(self.registry_address, provider);
133
134 let agent_id = registry
135 .getAgentId(self.agent_key)
136 .call()
137 .await
138 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
139
140 if agent_id == U256::ZERO {
141 return Ok(AgentInfo {
142 address: self.signer.address(),
143 agent_key: self.agent_key,
144 agent_id: U256::ZERO,
145 is_verified: false,
146 nullifier: U256::ZERO,
147 agent_count: U256::ZERO,
148 });
149 }
150
151 let is_verified = registry
152 .hasHumanProof(agent_id)
153 .call()
154 .await
155 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
156 let nullifier = registry
157 .getHumanNullifier(agent_id)
158 .call()
159 .await
160 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
161 let agent_count = registry
162 .getAgentCountForHuman(nullifier)
163 .call()
164 .await
165 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
166
167 Ok(AgentInfo {
168 address: self.signer.address(),
169 agent_key: self.agent_key,
170 agent_id,
171 is_verified,
172 nullifier,
173 agent_count,
174 })
175 }
176
177 pub async fn sign_request(
181 &self,
182 method: &str,
183 url: &str,
184 body: Option<&str>,
185 ) -> Result<HashMap<String, String>, crate::Error> {
186 let timestamp = now_millis().to_string();
187 self.sign_request_with_timestamp(method, url, body, ×tamp)
188 .await
189 }
190
191 pub async fn sign_request_with_timestamp(
193 &self,
194 method: &str,
195 url: &str,
196 body: Option<&str>,
197 timestamp: &str,
198 ) -> Result<HashMap<String, String>, crate::Error> {
199 let message = compute_signing_message(timestamp, method, url, body);
200
201 let signature = self
203 .signer
204 .sign_message(message.as_ref())
205 .await
206 .map_err(|e| crate::Error::SigningError(e.to_string()))?;
207
208 let sig_hex = format!("0x{}", hex::encode(signature.as_bytes()));
209
210 let mut headers_map = HashMap::new();
211 headers_map.insert(
212 headers::ADDRESS.to_string(),
213 format!("{:#x}", self.signer.address()),
214 );
215 headers_map.insert(headers::SIGNATURE.to_string(), sig_hex);
216 headers_map.insert(headers::TIMESTAMP.to_string(), timestamp.to_string());
217
218 Ok(headers_map)
219 }
220
221 pub async fn fetch(
223 &self,
224 url: &str,
225 method: Option<Method>,
226 body: Option<String>,
227 ) -> Result<Response, crate::Error> {
228 let method = method.unwrap_or(Method::GET);
229 let method_str = method.as_str();
230 let body_ref = body.as_deref();
231
232 let auth_headers = self.sign_request(method_str, url, body_ref).await?;
233
234 let mut request: RequestBuilder = self.http_client.request(method, url);
235 for (k, v) in &auth_headers {
236 request = request.header(k, v);
237 }
238 if let Some(b) = body {
239 request = request.header("content-type", "application/json");
240 request = request.body(b);
241 }
242
243 request
244 .send()
245 .await
246 .map_err(|e| crate::Error::HttpError(e.to_string()))
247 }
248
249 pub async fn get_agent_card(&self) -> Result<Option<A2AAgentCard>, crate::Error> {
253 let provider = self.make_provider()?;
254 let registry = IAgentRegistry::new(self.registry_address, provider);
255
256 let agent_id = registry
257 .getAgentId(self.agent_key)
258 .call()
259 .await
260 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
261 if agent_id == U256::ZERO {
262 return Ok(None);
263 }
264
265 let raw = registry
266 .getAgentMetadata(agent_id)
267 .call()
268 .await
269 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
270 if raw.is_empty() {
271 return Ok(None);
272 }
273
274 match serde_json::from_str::<A2AAgentCard>(&raw) {
275 Ok(card) if card.a2a_version == "0.1" => Ok(Some(card)),
276 _ => Ok(None),
277 }
278 }
279
280 pub async fn set_agent_card(
283 &self,
284 name: String,
285 description: Option<String>,
286 url: Option<String>,
287 skills: Option<Vec<AgentSkill>>,
288 ) -> Result<B256, crate::Error> {
289 let provider = self.make_signer_provider()?;
290 let registry = IAgentRegistry::new(self.registry_address, &provider);
291
292 let agent_id = registry
293 .getAgentId(self.agent_key)
294 .call()
295 .await
296 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
297 if agent_id == U256::ZERO {
298 return Err(crate::Error::RpcError("Agent not registered".into()));
299 }
300
301 let proof_provider_addr = registry
302 .getProofProvider(agent_id)
303 .call()
304 .await
305 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
306 if proof_provider_addr == Address::ZERO {
307 return Err(crate::Error::RpcError(
308 "Agent has no proof provider — cannot build card".into(),
309 ));
310 }
311
312 let proof_provider =
313 IHumanProofProvider::new(proof_provider_addr, &provider);
314
315 let provider_name = proof_provider
316 .providerName()
317 .call()
318 .await
319 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
320 let strength = proof_provider
321 .verificationStrength()
322 .call()
323 .await
324 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
325
326 let credentials = registry
327 .getAgentCredentials(agent_id)
328 .call()
329 .await
330 .ok();
331
332 let proof_type = get_provider_label(strength).to_string();
333
334 let mut trust_model = TrustModel {
335 proof_type,
336 sybil_resistant: true,
337 ofac_screened: false,
338 minimum_age_verified: 0,
339 };
340
341 let card_credentials = credentials.map(|creds| {
342 let older_than = creds.olderThan.try_into().unwrap_or(0u64);
343 let ofac_screened = creds.ofac.first().copied().unwrap_or(false);
344 trust_model.ofac_screened = ofac_screened;
345 trust_model.minimum_age_verified = older_than;
346
347 CardCredentials {
348 nationality: non_empty(&creds.nationality),
349 issuing_state: non_empty(&creds.issuingState),
350 older_than: if older_than > 0 { Some(older_than) } else { None },
351 ofac_clean: if ofac_screened { Some(true) } else { None },
352 has_name: if !creds.name.is_empty() { Some(true) } else { None },
353 has_date_of_birth: non_empty(&creds.dateOfBirth).map(|_| true),
354 has_gender: non_empty(&creds.gender).map(|_| true),
355 document_expiry: non_empty(&creds.expiryDate),
356 }
357 });
358
359 let chain_id: u64 = alloy::providers::Provider::get_chain_id(&provider)
360 .await
361 .map_err(|e: alloy::transports::RpcError<alloy::transports::TransportErrorKind>| crate::Error::RpcError(e.to_string()))?;
362
363 let card = A2AAgentCard {
364 a2a_version: "0.1".into(),
365 name,
366 description,
367 url,
368 capabilities: None,
369 skills,
370 self_protocol: SelfProtocolExtension {
371 agent_id: agent_id.try_into().unwrap_or(0),
372 registry: format!("{:#x}", self.registry_address),
373 chain_id,
374 proof_provider: format!("{:#x}", proof_provider_addr),
375 provider_name,
376 verification_strength: strength,
377 trust_model,
378 credentials: card_credentials,
379 },
380 };
381
382 let json =
383 serde_json::to_string(&card).map_err(|e| crate::Error::RpcError(e.to_string()))?;
384
385 let tx_hash = registry
386 .updateAgentMetadata(agent_id, json)
387 .send()
388 .await
389 .map_err(|e| crate::Error::RpcError(e.to_string()))?
390 .watch()
391 .await
392 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
393
394 Ok(tx_hash)
395 }
396
397 pub async fn to_agent_card_data_uri(&self) -> Result<String, crate::Error> {
399 let card = self
400 .get_agent_card()
401 .await?
402 .ok_or_else(|| crate::Error::RpcError("No A2A Agent Card set".into()))?;
403 let json =
404 serde_json::to_string(&card).map_err(|e| crate::Error::RpcError(e.to_string()))?;
405 use base64::Engine;
406 let encoded = base64::engine::general_purpose::STANDARD.encode(json.as_bytes());
407 Ok(format!("data:application/json;base64,{}", encoded))
408 }
409
410 pub async fn get_credentials(
412 &self,
413 ) -> Result<Option<IAgentRegistry::AgentCredentials>, crate::Error> {
414 let provider = self.make_provider()?;
415 let registry = IAgentRegistry::new(self.registry_address, provider);
416
417 let agent_id = registry
418 .getAgentId(self.agent_key)
419 .call()
420 .await
421 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
422 if agent_id == U256::ZERO {
423 return Ok(None);
424 }
425
426 let creds = registry
427 .getAgentCredentials(agent_id)
428 .call()
429 .await
430 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
431 Ok(Some(creds))
432 }
433
434 pub async fn get_verification_strength(&self) -> Result<u8, crate::Error> {
436 let provider = self.make_provider()?;
437 let registry = IAgentRegistry::new(self.registry_address, &provider);
438
439 let agent_id = registry
440 .getAgentId(self.agent_key)
441 .call()
442 .await
443 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
444 if agent_id == U256::ZERO {
445 return Ok(0);
446 }
447
448 let provider_addr = registry
449 .getProofProvider(agent_id)
450 .call()
451 .await
452 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
453 if provider_addr == Address::ZERO {
454 return Ok(0);
455 }
456
457 let proof_provider = IHumanProofProvider::new(provider_addr, &provider);
458 let strength = proof_provider
459 .verificationStrength()
460 .call()
461 .await
462 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
463 Ok(strength)
464 }
465
466 pub async fn request_registration(
473 req: RegistrationRequest,
474 api_base: Option<&str>,
475 ) -> Result<RegistrationSession, RegistrationError> {
476 RegistrationSession::request(req, api_base).await
477 }
478
479 pub async fn get_agent_info_rest(
481 agent_id: u64,
482 network: NetworkName,
483 api_base: Option<&str>,
484 ) -> Result<serde_json::Value, RegistrationError> {
485 let base = api_base.unwrap_or(DEFAULT_API_BASE);
486 let chain_id: u64 = match network {
487 NetworkName::Mainnet => 42220,
488 NetworkName::Testnet => 11142220,
489 };
490 let resp = reqwest::get(format!("{base}/api/agent/info/{chain_id}/{agent_id}"))
491 .await
492 .map_err(|e| RegistrationError::Http(e.to_string()))?;
493 resp.json()
494 .await
495 .map_err(|e| RegistrationError::Http(e.to_string()))
496 }
497
498 pub async fn get_agents_for_human(
500 address: &str,
501 network: NetworkName,
502 api_base: Option<&str>,
503 ) -> Result<serde_json::Value, RegistrationError> {
504 let base = api_base.unwrap_or(DEFAULT_API_BASE);
505 let chain_id: u64 = match network {
506 NetworkName::Mainnet => 42220,
507 NetworkName::Testnet => 11142220,
508 };
509 let resp = reqwest::get(format!("{base}/api/agent/agents/{chain_id}/{address}"))
510 .await
511 .map_err(|e| RegistrationError::Http(e.to_string()))?;
512 resp.json()
513 .await
514 .map_err(|e| RegistrationError::Http(e.to_string()))
515 }
516
517 pub async fn request_deregistration(
522 &self,
523 api_base: Option<&str>,
524 ) -> Result<DeregistrationSession, RegistrationError> {
525 let network_str = match self.network_name {
526 NetworkName::Mainnet => "mainnet",
527 NetworkName::Testnet => "testnet",
528 };
529 DeregistrationSession::request(
530 DeregistrationRequest {
531 network: network_str.to_string(),
532 agent_address: format!("{:#x}", self.signer.address()),
533 },
534 api_base,
535 )
536 .await
537 }
538}
539
540pub fn address_to_agent_key(address: Address) -> B256 {
543 let mut bytes = [0u8; 32];
544 bytes[12..32].copy_from_slice(address.as_ref());
545 FixedBytes(bytes)
546}
547
548fn now_millis() -> u64 {
550 SystemTime::now()
551 .duration_since(UNIX_EPOCH)
552 .unwrap()
553 .as_millis() as u64
554}
555
556fn non_empty(s: &str) -> Option<String> {
558 if s.is_empty() {
559 None
560 } else {
561 Some(s.to_string())
562 }
563}
564
565pub(crate) fn compute_signing_message(
568 timestamp: &str,
569 method: &str,
570 url: &str,
571 body: Option<&str>,
572) -> B256 {
573 let canonical_url = canonicalize_signing_url(url);
574 let body_text = body.unwrap_or("");
575 let body_hash = keccak256(body_text.as_bytes());
576 let body_hash_hex = format!("{:#x}", body_hash);
577 let concat = format!(
578 "{}{}{}{}",
579 timestamp,
580 method.to_uppercase(),
581 canonical_url,
582 body_hash_hex
583 );
584 keccak256(concat.as_bytes())
585}
586
587pub(crate) fn canonicalize_signing_url(url: &str) -> String {
589 if url.is_empty() {
590 return String::new();
591 }
592
593 if url.starts_with("http://") || url.starts_with("https://") {
594 if let Ok(parsed) = reqwest::Url::parse(url) {
595 let mut out = parsed.path().to_string();
596 if out.is_empty() {
597 out.push('/');
598 }
599 if let Some(query) = parsed.query() {
600 out.push('?');
601 out.push_str(query);
602 }
603 return out;
604 }
605 return url.to_string();
606 }
607
608 if url.starts_with('?') {
609 return format!("/{url}");
610 }
611 if url.starts_with('/') {
612 return url.to_string();
613 }
614
615 if let Ok(parsed) = reqwest::Url::parse(&format!("http://self.local/{url}")) {
617 let mut out = parsed.path().to_string();
618 if let Some(query) = parsed.query() {
619 out.push('?');
620 out.push_str(query);
621 }
622 return out;
623 }
624
625 url.to_string()
626}