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 Erc8004AgentDocument, AgentSkill, CardCredentials, SelfProtocolExtension, TrustModel,
15 Erc8004Service, AgentInterface,
16 get_provider_label,
17};
18use crate::constants::{
19 headers, network_config, IAgentRegistry, IHumanProofProvider, NetworkName, DEFAULT_NETWORK,
20};
21use crate::registration_flow::{
22 DeregistrationRequest, DeregistrationSession, RegistrationError, RegistrationRequest,
23 RegistrationSession, DEFAULT_API_BASE,
24};
25
26#[derive(Debug, Clone)]
28pub struct SelfAgentConfig {
29 pub private_key: String,
31 pub network: Option<NetworkName>,
33 pub registry_address: Option<Address>,
35 pub rpc_url: Option<String>,
37}
38
39#[derive(Debug, Clone)]
41pub struct AgentInfo {
42 pub address: Address,
43 pub agent_key: B256,
44 pub agent_id: U256,
45 pub is_verified: bool,
46 pub nullifier: U256,
47 pub agent_count: U256,
48}
49
50pub struct SelfAgent {
55 signer: PrivateKeySigner,
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 network_name,
77 registry_address: config.registry_address.unwrap_or(net.registry_address),
78 rpc_url: config.rpc_url.unwrap_or_else(|| net.rpc_url.to_string()),
79 agent_key,
80 http_client: Client::new(),
81 })
82 }
83
84 pub fn address(&self) -> Address {
86 self.signer.address()
87 }
88
89 pub fn agent_key(&self) -> B256 {
91 self.agent_key
92 }
93
94 fn make_provider(
95 &self,
96 ) -> Result<impl alloy::providers::Provider + Clone, crate::Error> {
97 let url: reqwest::Url = self
98 .rpc_url
99 .parse()
100 .map_err(|_| crate::Error::InvalidRpcUrl)?;
101 Ok(ProviderBuilder::new().connect_http(url))
102 }
103
104 fn make_signer_provider(
106 &self,
107 ) -> Result<impl alloy::providers::Provider + Clone, crate::Error> {
108 let url: reqwest::Url = self
109 .rpc_url
110 .parse()
111 .map_err(|_| crate::Error::InvalidRpcUrl)?;
112 let wallet = alloy::network::EthereumWallet::from(self.signer.clone());
113 Ok(ProviderBuilder::new().wallet(wallet).connect_http(url))
114 }
115
116 pub async fn is_registered(&self) -> Result<bool, crate::Error> {
118 let provider = self.make_provider()?;
119 let registry = IAgentRegistry::new(self.registry_address, provider);
120 let result = registry
121 .isVerifiedAgent(self.agent_key)
122 .call()
123 .await
124 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
125 Ok(result)
126 }
127
128 pub async fn get_info(&self) -> Result<AgentInfo, crate::Error> {
130 let provider = self.make_provider()?;
131 let registry = IAgentRegistry::new(self.registry_address, provider);
132
133 let agent_id = registry
134 .getAgentId(self.agent_key)
135 .call()
136 .await
137 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
138
139 if agent_id == U256::ZERO {
140 return Ok(AgentInfo {
141 address: self.signer.address(),
142 agent_key: self.agent_key,
143 agent_id: U256::ZERO,
144 is_verified: false,
145 nullifier: U256::ZERO,
146 agent_count: U256::ZERO,
147 });
148 }
149
150 let is_verified = registry
151 .hasHumanProof(agent_id)
152 .call()
153 .await
154 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
155 let nullifier = registry
156 .getHumanNullifier(agent_id)
157 .call()
158 .await
159 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
160 let agent_count = registry
161 .getAgentCountForHuman(nullifier)
162 .call()
163 .await
164 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
165
166 Ok(AgentInfo {
167 address: self.signer.address(),
168 agent_key: self.agent_key,
169 agent_id,
170 is_verified,
171 nullifier,
172 agent_count,
173 })
174 }
175
176 pub async fn sign_request(
180 &self,
181 method: &str,
182 url: &str,
183 body: Option<&str>,
184 ) -> Result<HashMap<String, String>, crate::Error> {
185 let timestamp = now_millis().to_string();
186 self.sign_request_with_timestamp(method, url, body, ×tamp)
187 .await
188 }
189
190 pub async fn sign_request_with_timestamp(
192 &self,
193 method: &str,
194 url: &str,
195 body: Option<&str>,
196 timestamp: &str,
197 ) -> Result<HashMap<String, String>, crate::Error> {
198 let message = compute_signing_message(timestamp, method, url, body);
199
200 let signature = self
202 .signer
203 .sign_message(message.as_ref())
204 .await
205 .map_err(|e| crate::Error::SigningError(e.to_string()))?;
206
207 let sig_hex = format!("0x{}", hex::encode(signature.as_bytes()));
208
209 let mut headers_map = HashMap::new();
210 headers_map.insert(
211 headers::ADDRESS.to_string(),
212 format!("{:#x}", self.signer.address()),
213 );
214 headers_map.insert(headers::SIGNATURE.to_string(), sig_hex);
215 headers_map.insert(headers::TIMESTAMP.to_string(), timestamp.to_string());
216
217 Ok(headers_map)
218 }
219
220 pub async fn fetch(
222 &self,
223 url: &str,
224 method: Option<Method>,
225 body: Option<String>,
226 ) -> Result<Response, crate::Error> {
227 let method = method.unwrap_or(Method::GET);
228 let method_str = method.as_str();
229 let body_ref = body.as_deref();
230
231 let auth_headers = self.sign_request(method_str, url, body_ref).await?;
232
233 let mut request: RequestBuilder = self.http_client.request(method, url);
234 for (k, v) in &auth_headers {
235 request = request.header(k, v);
236 }
237 if let Some(b) = body {
238 request = request.header("content-type", "application/json");
239 request = request.body(b);
240 }
241
242 request
243 .send()
244 .await
245 .map_err(|e| crate::Error::HttpError(e.to_string()))
246 }
247
248 pub async fn get_agent_card(&self) -> Result<Option<Erc8004AgentDocument>, 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::<Erc8004AgentDocument>(&raw) {
275 Ok(card) => 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 mut services = Vec::new();
365 let mut supported_interfaces = None;
366 if let Some(ref agent_url) = url {
367 services.push(Erc8004Service {
368 name: "A2A".to_string(),
369 endpoint: agent_url.clone(),
370 version: Some("0.3.0".to_string()),
371 });
372 supported_interfaces = Some(vec![AgentInterface {
373 url: agent_url.clone(),
374 protocol_binding: "JSONRPC".to_string(),
375 protocol_version: "0.3.0".to_string(),
376 }]);
377 }
378
379 let card = Erc8004AgentDocument {
380 doc_type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1".to_string(),
381 name,
382 description: description.unwrap_or_default(),
383 image: String::new(),
384 services,
385 active: None,
386 registrations: None,
387 supported_trust: None,
388 version: None,
389 url,
390 provider: None,
391 capabilities: None,
392 security_schemes: None,
393 security: None,
394 default_input_modes: None,
395 default_output_modes: None,
396 supported_interfaces,
397 icon_url: None,
398 documentation_url: None,
399 signatures: None,
400 extensions: None,
401 self_protocol: Some(SelfProtocolExtension {
402 agent_id: agent_id.try_into().unwrap_or(0),
403 registry: format!("{:#x}", self.registry_address),
404 chain_id,
405 proof_provider: format!("{:#x}", proof_provider_addr),
406 provider_name,
407 verification_strength: strength,
408 trust_model,
409 credentials: card_credentials,
410 }),
411 skills,
412 };
413
414 let json =
415 serde_json::to_string(&card).map_err(|e| crate::Error::RpcError(e.to_string()))?;
416
417 let tx_hash = registry
418 .updateAgentMetadata(agent_id, json)
419 .send()
420 .await
421 .map_err(|e| crate::Error::RpcError(e.to_string()))?
422 .watch()
423 .await
424 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
425
426 Ok(tx_hash)
427 }
428
429 pub async fn to_agent_card_data_uri(&self) -> Result<String, crate::Error> {
431 let card = self
432 .get_agent_card()
433 .await?
434 .ok_or_else(|| crate::Error::RpcError("No A2A Agent Card set".into()))?;
435 let json =
436 serde_json::to_string(&card).map_err(|e| crate::Error::RpcError(e.to_string()))?;
437 use base64::Engine;
438 let encoded = base64::engine::general_purpose::STANDARD.encode(json.as_bytes());
439 Ok(format!("data:application/json;base64,{}", encoded))
440 }
441
442 pub async fn get_credentials(
444 &self,
445 ) -> Result<Option<IAgentRegistry::AgentCredentials>, crate::Error> {
446 let provider = self.make_provider()?;
447 let registry = IAgentRegistry::new(self.registry_address, provider);
448
449 let agent_id = registry
450 .getAgentId(self.agent_key)
451 .call()
452 .await
453 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
454 if agent_id == U256::ZERO {
455 return Ok(None);
456 }
457
458 let creds = registry
459 .getAgentCredentials(agent_id)
460 .call()
461 .await
462 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
463 Ok(Some(creds))
464 }
465
466 pub async fn get_verification_strength(&self) -> Result<u8, crate::Error> {
468 let provider = self.make_provider()?;
469 let registry = IAgentRegistry::new(self.registry_address, &provider);
470
471 let agent_id = registry
472 .getAgentId(self.agent_key)
473 .call()
474 .await
475 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
476 if agent_id == U256::ZERO {
477 return Ok(0);
478 }
479
480 let provider_addr = registry
481 .getProofProvider(agent_id)
482 .call()
483 .await
484 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
485 if provider_addr == Address::ZERO {
486 return Ok(0);
487 }
488
489 let proof_provider = IHumanProofProvider::new(provider_addr, &provider);
490 let strength = proof_provider
491 .verificationStrength()
492 .call()
493 .await
494 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
495 Ok(strength)
496 }
497
498 pub async fn request_registration(
505 req: RegistrationRequest,
506 api_base: Option<&str>,
507 ) -> Result<RegistrationSession, RegistrationError> {
508 RegistrationSession::request(req, api_base).await
509 }
510
511 pub async fn get_agent_info_rest(
513 agent_id: u64,
514 network: NetworkName,
515 api_base: Option<&str>,
516 ) -> Result<serde_json::Value, RegistrationError> {
517 let base = api_base.unwrap_or(DEFAULT_API_BASE);
518 let chain_id: u64 = match network {
519 NetworkName::Mainnet => 42220,
520 NetworkName::Testnet => 11142220,
521 };
522 let resp = reqwest::get(format!("{base}/api/agent/info/{chain_id}/{agent_id}"))
523 .await
524 .map_err(|e| RegistrationError::Http(e.to_string()))?;
525 resp.json()
526 .await
527 .map_err(|e| RegistrationError::Http(e.to_string()))
528 }
529
530 pub async fn get_agents_for_human(
532 address: &str,
533 network: NetworkName,
534 api_base: Option<&str>,
535 ) -> Result<serde_json::Value, RegistrationError> {
536 let base = api_base.unwrap_or(DEFAULT_API_BASE);
537 let chain_id: u64 = match network {
538 NetworkName::Mainnet => 42220,
539 NetworkName::Testnet => 11142220,
540 };
541 let resp = reqwest::get(format!("{base}/api/agent/agents/{chain_id}/{address}"))
542 .await
543 .map_err(|e| RegistrationError::Http(e.to_string()))?;
544 resp.json()
545 .await
546 .map_err(|e| RegistrationError::Http(e.to_string()))
547 }
548
549 pub async fn request_deregistration(
554 &self,
555 api_base: Option<&str>,
556 ) -> Result<DeregistrationSession, RegistrationError> {
557 let network_str = match self.network_name {
558 NetworkName::Mainnet => "mainnet",
559 NetworkName::Testnet => "testnet",
560 };
561 DeregistrationSession::request(
562 DeregistrationRequest {
563 network: network_str.to_string(),
564 agent_address: format!("{:#x}", self.signer.address()),
565 },
566 api_base,
567 )
568 .await
569 }
570}
571
572pub fn address_to_agent_key(address: Address) -> B256 {
575 let mut bytes = [0u8; 32];
576 bytes[12..32].copy_from_slice(address.as_ref());
577 FixedBytes(bytes)
578}
579
580fn now_millis() -> u64 {
582 SystemTime::now()
583 .duration_since(UNIX_EPOCH)
584 .unwrap()
585 .as_millis() as u64
586}
587
588fn non_empty(s: &str) -> Option<String> {
590 if s.is_empty() {
591 None
592 } else {
593 Some(s.to_string())
594 }
595}
596
597pub(crate) fn compute_signing_message(
600 timestamp: &str,
601 method: &str,
602 url: &str,
603 body: Option<&str>,
604) -> B256 {
605 let canonical_url = canonicalize_signing_url(url);
606 let body_text = body.unwrap_or("");
607 let body_hash = keccak256(body_text.as_bytes());
608 let body_hash_hex = format!("{:#x}", body_hash);
609 let concat = format!(
610 "{}{}{}{}",
611 timestamp,
612 method.to_uppercase(),
613 canonical_url,
614 body_hash_hex
615 );
616 keccak256(concat.as_bytes())
617}
618
619pub(crate) fn canonicalize_signing_url(url: &str) -> String {
621 if url.is_empty() {
622 return String::new();
623 }
624
625 if url.starts_with("http://") || url.starts_with("https://") {
626 if let Ok(parsed) = reqwest::Url::parse(url) {
627 let mut out = parsed.path().to_string();
628 if out.is_empty() {
629 out.push('/');
630 }
631 if let Some(query) = parsed.query() {
632 out.push('?');
633 out.push_str(query);
634 }
635 return out;
636 }
637 return url.to_string();
638 }
639
640 if url.starts_with('?') {
641 return format!("/{url}");
642 }
643 if url.starts_with('/') {
644 return url.to_string();
645 }
646
647 if let Ok(parsed) = reqwest::Url::parse(&format!("http://self.local/{url}")) {
649 let mut out = parsed.path().to_string();
650 if let Some(query) = parsed.query() {
651 out.push('?');
652 out.push_str(query);
653 }
654 return out;
655 }
656
657 url.to_string()
658}