1use anyhow::{Context, Result};
2use axum::extract::State;
3use axum::http::{HeaderMap, StatusCode};
4use axum::response::{IntoResponse, Response};
5use axum::Json;
6use bsv_sdk::primitives::{to_hex, PublicKey};
7use bsv_sdk::wallet::{
8 AbortActionArgs,
10 AbortActionResult,
11 AcquireCertificateArgs,
13 AuthenticatedResult,
15 BasketInsertion,
17 Counterparty,
19 CreateActionArgs,
20 CreateActionInput,
21 CreateActionOptions,
22 CreateActionOutput,
23 CreateHmacArgs,
24 CreateSignatureArgs,
25 DecryptArgs,
27 DiscoverByAttributesArgs,
29 DiscoverByIdentityKeyArgs,
30 DiscoverCertificatesResult,
31 EncryptArgs,
32 GetHeaderArgs,
34 GetHeaderResult,
35 GetHeightResult,
36 GetNetworkResult,
37 GetPublicKeyArgs,
38 GetVersionResult,
39 InternalizeActionArgs,
40 InternalizeOutput,
41 ListActionsArgs,
42 ListActionsResult,
43 ListCertificatesArgs,
44 ListCertificatesResult,
45 ListOutputsArgs,
47 ListOutputsResult,
48 Outpoint,
49 Protocol,
50 ProveCertificateArgs,
51 ProveCertificateResult,
52 RelinquishCertificateArgs,
53 RelinquishCertificateResult,
54 RelinquishOutputArgs,
55 RelinquishOutputResult,
56 RevealCounterpartyKeyLinkageResult,
58 RevealSpecificKeyLinkageResult,
59 SecurityLevel,
60 SignActionArgs,
61 SignActionResult,
62 TrustSelf,
63 VerifyHmacArgs,
64 VerifySignatureArgs,
65 WalletCertificate,
66 WalletInterface,
67 WalletPayment,
68 WalletRevealCounterpartyArgs,
69 WalletRevealSpecificArgs,
70};
71use bsv_wallet_toolbox::{Services, StorageSqlx, Wallet};
72use serde_json::json;
73use std::sync::Arc;
74
75use super::types::*;
76
77pub type WalletState = Arc<Wallet<StorageSqlx, Services>>;
78
79fn extract_originator(
81 headers: &HeaderMap,
82) -> Result<String, (StatusCode, Json<serde_json::Value>)> {
83 if let Some(origin) = headers.get("origin").and_then(|v| v.to_str().ok()) {
84 if let Ok(url) = url::Url::parse(origin) {
85 if let Some(host) = url.host_str() {
86 return Ok(host.to_string());
87 }
88 }
89 }
90 if let Some(originator) = headers.get("originator").and_then(|v| v.to_str().ok()) {
91 return Ok(originator.to_string());
92 }
93 Err((
94 StatusCode::BAD_REQUEST,
95 Json(json!({"message": "Origin header required"})),
96 ))
97}
98
99pub struct AppError {
101 status: StatusCode,
102 code: &'static str,
103 message: String,
104}
105
106impl AppError {
107 fn classify(msg: &str) -> (StatusCode, &'static str) {
109 let clean = msg.strip_prefix("wallet error: ").unwrap_or(msg);
111
112 if clean.starts_with("Insufficient funds:") || clean.starts_with("insufficient funds:") {
113 (StatusCode::PAYMENT_REQUIRED, "INSUFFICIENT_FUNDS")
114 } else if clean.starts_with("Authentication required")
115 || clean.starts_with("Invalid identity key:")
116 {
117 (StatusCode::UNAUTHORIZED, "AUTHENTICATION_REQUIRED")
118 } else if clean.starts_with("Access denied:") {
119 (StatusCode::FORBIDDEN, "ACCESS_DENIED")
120 } else if clean.starts_with("Entity not found:") || clean.starts_with("not found:") {
121 (StatusCode::NOT_FOUND, "NOT_FOUND")
122 } else if clean.starts_with("Duplicate entity:") || clean.starts_with("Invalid operation:")
123 {
124 (StatusCode::CONFLICT, "CONFLICT")
125 } else if clean.starts_with("Validation error:")
126 || clean.starts_with("Invalid argument:")
127 || clean.starts_with("Origin header required")
128 {
129 (StatusCode::BAD_REQUEST, "VALIDATION_ERROR")
130 } else if clean.starts_with("Storage error:")
131 || clean.starts_with("Database error:")
132 || clean.starts_with("Internal error:")
133 {
134 (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR")
135 } else if clean.starts_with("Service error:")
136 || clean.starts_with("Network error:")
137 || clean.starts_with("Broadcast failed:")
138 {
139 (StatusCode::BAD_GATEWAY, "SERVICE_ERROR")
140 } else {
141 (StatusCode::BAD_REQUEST, "ERROR")
142 }
143 }
144
145 fn from_wallet_error(e: impl std::fmt::Display) -> Self {
147 let message = e.to_string();
148 let (status, code) = Self::classify(&message);
149 Self {
150 status,
151 code,
152 message,
153 }
154 }
155}
156
157impl From<anyhow::Error> for AppError {
158 fn from(err: anyhow::Error) -> Self {
159 Self::from_wallet_error(err)
160 }
161}
162
163impl From<(StatusCode, Json<serde_json::Value>)> for AppError {
164 fn from((status, json): (StatusCode, Json<serde_json::Value>)) -> Self {
165 let message = json
166 .0
167 .get("message")
168 .and_then(|m| m.as_str())
169 .unwrap_or_default()
170 .to_string();
171 let (_, code) = Self::classify(&message);
172 Self {
173 status,
174 code,
175 message,
176 }
177 }
178}
179
180impl IntoResponse for AppError {
181 fn into_response(self) -> Response {
182 tracing::warn!(
183 status = self.status.as_u16(),
184 code = self.code,
185 message = %self.message,
186 "request error"
187 );
188 (
189 self.status,
190 Json(json!({"code": self.code, "message": self.message})),
191 )
192 .into_response()
193 }
194}
195
196pub async fn is_authenticated() -> Json<serde_json::Value> {
198 Json(json!({"authenticated": true}))
199}
200
201pub async fn get_public_key(
203 State(wallet): State<WalletState>,
204 headers: HeaderMap,
205 Json(req): Json<McGetPublicKeyReq>,
206) -> Result<Json<McGetPublicKeyRes>, AppError> {
207 let originator = extract_originator(&headers)?;
208
209 let args = GetPublicKeyArgs {
210 identity_key: req.identity_key,
211 protocol_id: req.protocol_id.map(|v| parse_protocol_id(&v)).transpose()?,
212 key_id: req.key_id,
213 counterparty: req
214 .counterparty
215 .map(|s| parse_counterparty(&s))
216 .transpose()?,
217 for_self: req.for_self,
218 };
219
220 let result = wallet
221 .get_public_key(args, &originator)
222 .await
223 .map_err(AppError::from_wallet_error)?;
224
225 Ok(Json(McGetPublicKeyRes {
226 public_key: result.public_key,
227 }))
228}
229
230pub async fn create_signature(
232 State(wallet): State<WalletState>,
233 headers: HeaderMap,
234 Json(req): Json<McCreateSignatureReq>,
235) -> Result<Json<McCreateSignatureRes>, AppError> {
236 let originator = extract_originator(&headers)?;
237
238 let hash_to_directly_sign = req
241 .hash_to_directly_sign
242 .map(|h| {
243 <[u8; 32]>::try_from(h.as_slice()).map_err(|_| {
244 AppError::from_wallet_error("Validation error: hashToDirectlySign must be 32 bytes")
245 })
246 })
247 .transpose()?;
248 if req.data.is_none() && hash_to_directly_sign.is_none() {
249 return Err(AppError::from_wallet_error(
250 "Validation error: one of data or hashToDirectlySign is required",
251 ));
252 }
253
254 let args = CreateSignatureArgs {
255 data: req.data,
256 hash_to_directly_sign,
257 protocol_id: parse_protocol_id(&req.protocol_id)?,
258 key_id: req.key_id,
259 counterparty: Some(parse_counterparty(&req.counterparty)?),
260 };
261
262 let result = wallet
263 .create_signature(args, &originator)
264 .await
265 .map_err(AppError::from_wallet_error)?;
266
267 Ok(Json(McCreateSignatureRes {
268 signature: result.signature,
269 }))
270}
271
272pub async fn create_action(
278 State(wallet): State<WalletState>,
279 axum::Extension(spending_lock): axum::Extension<super::SpendingLock>,
280 headers: HeaderMap,
281 Json(req): Json<McCreateActionReq>,
282) -> Result<Json<McCreateActionRes>, AppError> {
283 let originator = extract_originator(&headers)?;
284
285 let outputs = req
286 .outputs
287 .map(|outs| {
288 outs.into_iter()
289 .map(|o| {
290 Ok(CreateActionOutput {
291 locking_script: hex::decode(&o.locking_script)?,
292 satoshis: o.satoshis,
293 output_description: o.output_description,
294 basket: o.basket,
295 custom_instructions: o.custom_instructions,
296 tags: o.tags,
297 })
298 })
299 .collect::<Result<Vec<_>>>()
300 })
301 .transpose()?;
302
303 let inputs = match req.inputs {
309 Some(reqs) if !reqs.is_empty() => {
310 let mut mapped = Vec::with_capacity(reqs.len());
311 for i in reqs {
312 let outpoint = Outpoint::from_string(&i.outpoint)
313 .map_err(|e| anyhow::anyhow!("invalid input outpoint '{}': {e}", i.outpoint))?;
314 let unlocking_script = match i.unlocking_script {
315 Some(h) => Some(hex::decode(&h).context("invalid unlockingScript hex")?),
316 None => None,
317 };
318 mapped.push(CreateActionInput {
319 outpoint,
320 input_description: i.input_description.unwrap_or_else(|| "input".to_string()),
321 unlocking_script,
322 unlocking_script_length: i.unlocking_script_length,
323 sequence_number: i.sequence_number,
324 });
325 }
326 Some(mapped)
327 }
328 _ => Some(vec![]),
329 };
330
331 let args = CreateActionArgs {
332 description: req.description,
333 input_beef: req.input_beef,
334 inputs,
335 outputs,
336 lock_time: req.lock_time,
337 version: None,
338 labels: req.labels,
339 options: req.options.map(|o| CreateActionOptions {
340 accept_delayed_broadcast: o.accept_delayed_broadcast,
341 randomize_outputs: o.randomize_outputs,
342 sign_and_process: o.sign_and_process,
343 no_send: o.no_send,
344 trust_self: match o.trust_self.as_deref() {
345 Some("known") => Some(TrustSelf::Known),
346 _ => None,
347 },
348 ..Default::default()
349 }),
350 };
351
352 let output_count = args.outputs.as_ref().map(|o| o.len()).unwrap_or(0);
353 let total_sats: u64 = args
354 .outputs
355 .as_ref()
356 .map(|o| o.iter().map(|x| x.satoshis).sum())
357 .unwrap_or(0);
358
359 let _guard = spending_lock.lock().await;
362 let result = wallet
363 .create_action(args, &originator)
364 .await
365 .map_err(AppError::from_wallet_error)?;
366 drop(_guard);
367
368 tracing::info!(
369 originator = %originator,
370 outputs = output_count,
371 total_satoshis = total_sats,
372 txid = ?result.txid.as_ref().map(|t| to_hex(t)),
373 "createAction"
374 );
375
376 let atomic_beef = match (result.beef.as_ref(), result.txid.as_ref()) {
379 (Some(beef_bytes), Some(txid_bytes)) => {
380 let txid_hex = to_hex(txid_bytes);
381 match bsv_sdk::transaction::Beef::from_binary(beef_bytes) {
382 Ok(mut beef) => match beef.to_binary_atomic(&txid_hex) {
383 Ok(ab) => Some(ab),
384 Err(e) => {
385 tracing::warn!(error = %e, "Failed to build AtomicBEEF, falling back to raw tx");
386 result.tx.clone()
387 }
388 },
389 Err(e) => {
390 tracing::warn!(error = %e, "Failed to parse BEEF, falling back to raw tx");
391 result.tx.clone()
392 }
393 }
394 }
395 _ => result.tx.clone(),
396 };
397
398 Ok(Json(McCreateActionRes {
399 txid: result.txid.map(|t| to_hex(&t)),
400 tx: atomic_beef,
401 send_with_results: result
402 .send_with_results
403 .and_then(|r| serde_json::to_value(r).ok()),
404 signable_transaction: result.signable_transaction.map(|st| {
405 let reference =
409 String::from_utf8(st.reference).unwrap_or_else(|e| hex::encode(e.into_bytes()));
410 McSignableTransaction {
411 tx: st.tx,
412 reference,
413 }
414 }),
415 no_send_change: result
416 .no_send_change
417 .and_then(|v| serde_json::to_value(v).ok()),
418 }))
419}
420
421pub async fn internalize_action(
423 State(wallet): State<WalletState>,
424 headers: HeaderMap,
425 Json(req): Json<McInternalizeActionReq>,
426) -> Result<Json<McInternalizeActionRes>, AppError> {
427 let originator = extract_originator(&headers)?;
428
429 let args = InternalizeActionArgs {
430 tx: req.tx,
431 outputs: req
432 .outputs
433 .into_iter()
434 .map(|o| InternalizeOutput {
435 output_index: o.output_index,
436 protocol: o.protocol,
437 payment_remittance: o.payment_remittance.map(|p| WalletPayment {
438 derivation_prefix: p.derivation_prefix,
439 derivation_suffix: p.derivation_suffix,
440 sender_identity_key: p.sender_identity_key,
441 }),
442 insertion_remittance: o.insertion_remittance.map(|i| BasketInsertion {
443 basket: i.basket,
444 custom_instructions: i.custom_instructions,
445 tags: i.tags,
446 }),
447 })
448 .collect(),
449 description: req.description,
450 labels: None,
451 seek_permission: None,
452 };
453
454 let output_count = args.outputs.len();
455 let result = wallet
456 .internalize_action(args, &originator)
457 .await
458 .map_err(AppError::from_wallet_error)?;
459
460 tracing::info!(
461 originator = %originator,
462 outputs = output_count,
463 accepted = result.accepted,
464 "internalizeAction"
465 );
466
467 Ok(Json(McInternalizeActionRes {
468 accepted: result.accepted,
469 }))
470}
471
472fn parse_protocol_id(v: &serde_json::Value) -> Result<Protocol> {
476 let arr = v.as_array().context("protocolID must be an array")?;
477 let level = arr
478 .first()
479 .and_then(|v| v.as_u64())
480 .context("protocolID[0] must be an integer")?;
481 let name = arr
482 .get(1)
483 .and_then(|v| v.as_str())
484 .context("protocolID[1] must be a string")?;
485 Ok(Protocol::new(
486 match level {
487 0 => SecurityLevel::Silent,
488 1 => SecurityLevel::App,
489 2 => SecurityLevel::Counterparty,
490 _ => anyhow::bail!("invalid security level: {level}"),
491 },
492 name,
493 ))
494}
495
496fn parse_counterparty(s: &str) -> Result<Counterparty> {
498 match s {
499 "self" => Ok(Counterparty::Self_),
500 "anyone" => Ok(Counterparty::Anyone),
501 hex => Ok(Counterparty::Other(PublicKey::from_hex(hex)?)),
502 }
503}
504
505pub async fn get_height(
511 State(wallet): State<WalletState>,
512) -> Result<Json<GetHeightResult>, AppError> {
513 let result = wallet
514 .get_height("localhost")
515 .await
516 .map_err(AppError::from_wallet_error)?;
517 Ok(Json(result))
518}
519
520pub async fn get_network(
522 State(wallet): State<WalletState>,
523) -> Result<Json<GetNetworkResult>, AppError> {
524 let result = wallet
525 .get_network("localhost")
526 .await
527 .map_err(AppError::from_wallet_error)?;
528 Ok(Json(result))
529}
530
531pub async fn get_version(
533 State(wallet): State<WalletState>,
534) -> Result<Json<GetVersionResult>, AppError> {
535 let result = wallet
536 .get_version("localhost")
537 .await
538 .map_err(AppError::from_wallet_error)?;
539 Ok(Json(result))
540}
541
542pub async fn wait_for_authentication(
544 State(wallet): State<WalletState>,
545) -> Result<Json<AuthenticatedResult>, AppError> {
546 let result = wallet
547 .wait_for_authentication("localhost")
548 .await
549 .map_err(AppError::from_wallet_error)?;
550 Ok(Json(result))
551}
552
553pub async fn get_header_for_height(
559 State(wallet): State<WalletState>,
560 headers: HeaderMap,
561 Json(args): Json<GetHeaderArgs>,
562) -> Result<Json<GetHeaderResult>, AppError> {
563 let originator = extract_originator(&headers)?;
564 let result = wallet
565 .get_header_for_height(args, &originator)
566 .await
567 .map_err(AppError::from_wallet_error)?;
568 Ok(Json(result))
569}
570
571pub async fn verify_signature(
577 State(wallet): State<WalletState>,
578 headers: HeaderMap,
579 Json(req): Json<McVerifySignatureReq>,
580) -> Result<Json<serde_json::Value>, AppError> {
581 let originator = extract_originator(&headers)?;
582 let args = VerifySignatureArgs {
583 data: req.data,
584 hash_to_directly_verify: None,
585 signature: req.signature,
586 protocol_id: parse_protocol_id(&req.protocol_id)?,
587 key_id: req.key_id,
588 counterparty: req
589 .counterparty
590 .map(|s| parse_counterparty(&s))
591 .transpose()?,
592 for_self: req.for_self,
593 };
594 let result = wallet
595 .verify_signature(args, &originator)
596 .await
597 .map_err(AppError::from_wallet_error)?;
598 Ok(Json(json!({ "valid": result.valid })))
599}
600
601pub async fn encrypt(
603 State(wallet): State<WalletState>,
604 headers: HeaderMap,
605 Json(req): Json<McEncryptReq>,
606) -> Result<Json<serde_json::Value>, AppError> {
607 let originator = extract_originator(&headers)?;
608 let args = EncryptArgs {
609 plaintext: req.plaintext,
610 protocol_id: parse_protocol_id(&req.protocol_id)?,
611 key_id: req.key_id,
612 counterparty: req
613 .counterparty
614 .map(|s| parse_counterparty(&s))
615 .transpose()?,
616 };
617 let result = wallet
618 .encrypt(args, &originator)
619 .await
620 .map_err(AppError::from_wallet_error)?;
621 Ok(Json(json!({ "ciphertext": result.ciphertext })))
622}
623
624pub async fn decrypt(
626 State(wallet): State<WalletState>,
627 headers: HeaderMap,
628 Json(req): Json<McDecryptReq>,
629) -> Result<Json<serde_json::Value>, AppError> {
630 let originator = extract_originator(&headers)?;
631 let args = DecryptArgs {
632 ciphertext: req.ciphertext,
633 protocol_id: parse_protocol_id(&req.protocol_id)?,
634 key_id: req.key_id,
635 counterparty: req
636 .counterparty
637 .map(|s| parse_counterparty(&s))
638 .transpose()?,
639 };
640 let result = wallet
641 .decrypt(args, &originator)
642 .await
643 .map_err(AppError::from_wallet_error)?;
644 Ok(Json(json!({ "plaintext": result.plaintext })))
645}
646
647pub async fn create_hmac(
649 State(wallet): State<WalletState>,
650 headers: HeaderMap,
651 Json(req): Json<McCreateHmacReq>,
652) -> Result<Json<serde_json::Value>, AppError> {
653 let originator = extract_originator(&headers)?;
654 let args = CreateHmacArgs {
655 data: req.data,
656 protocol_id: parse_protocol_id(&req.protocol_id)?,
657 key_id: req.key_id,
658 counterparty: req
659 .counterparty
660 .map(|s| parse_counterparty(&s))
661 .transpose()?,
662 };
663 let result = wallet
664 .create_hmac(args, &originator)
665 .await
666 .map_err(AppError::from_wallet_error)?;
667 Ok(Json(json!({ "hmac": result.hmac.to_vec() })))
668}
669
670pub async fn verify_hmac(
672 State(wallet): State<WalletState>,
673 headers: HeaderMap,
674 Json(req): Json<McVerifyHmacReq>,
675) -> Result<Json<serde_json::Value>, AppError> {
676 let originator = extract_originator(&headers)?;
677 let hmac: [u8; 32] = req
678 .hmac
679 .try_into()
680 .map_err(|_| anyhow::anyhow!("hmac must be exactly 32 bytes"))?;
681 let args = VerifyHmacArgs {
682 data: req.data,
683 hmac,
684 protocol_id: parse_protocol_id(&req.protocol_id)?,
685 key_id: req.key_id,
686 counterparty: req
687 .counterparty
688 .map(|s| parse_counterparty(&s))
689 .transpose()?,
690 };
691 let result = wallet
692 .verify_hmac(args, &originator)
693 .await
694 .map_err(AppError::from_wallet_error)?;
695 Ok(Json(json!({ "valid": result.valid })))
696}
697
698pub async fn sign_action(
704 State(wallet): State<WalletState>,
705 headers: HeaderMap,
706 Json(args): Json<SignActionArgs>,
707) -> Result<Json<SignActionResult>, AppError> {
708 let originator = extract_originator(&headers)?;
709 let reference = args.reference.clone();
710 let result = wallet
711 .sign_action(args, &originator)
712 .await
713 .map_err(AppError::from_wallet_error)?;
714 tracing::info!(originator = %originator, reference = %reference, "signAction");
715 Ok(Json(result))
716}
717
718pub async fn abort_action(
720 State(wallet): State<WalletState>,
721 headers: HeaderMap,
722 Json(args): Json<AbortActionArgs>,
723) -> Result<Json<AbortActionResult>, AppError> {
724 let originator = extract_originator(&headers)?;
725 let result = wallet
726 .abort_action(args, &originator)
727 .await
728 .map_err(AppError::from_wallet_error)?;
729 Ok(Json(result))
730}
731
732pub async fn list_actions(
734 State(wallet): State<WalletState>,
735 headers: HeaderMap,
736 Json(args): Json<ListActionsArgs>,
737) -> Result<Json<ListActionsResult>, AppError> {
738 let originator = extract_originator(&headers)?;
739 let result = wallet
740 .list_actions(args, &originator)
741 .await
742 .map_err(AppError::from_wallet_error)?;
743 Ok(Json(result))
744}
745
746pub async fn list_outputs(
748 State(wallet): State<WalletState>,
749 headers: HeaderMap,
750 Json(args): Json<ListOutputsArgs>,
751) -> Result<Json<ListOutputsResult>, AppError> {
752 let originator = extract_originator(&headers)?;
753 let result = wallet
754 .list_outputs(args, &originator)
755 .await
756 .map_err(AppError::from_wallet_error)?;
757 Ok(Json(result))
758}
759
760pub async fn relinquish_output(
765 State(wallet): State<WalletState>,
766 axum::Extension(spending_lock): axum::Extension<super::SpendingLock>,
767 headers: HeaderMap,
768 Json(args): Json<RelinquishOutputArgs>,
769) -> Result<Json<RelinquishOutputResult>, AppError> {
770 let originator = extract_originator(&headers)?;
771 let _guard = spending_lock.lock().await;
772 let result = wallet
773 .relinquish_output(args, &originator)
774 .await
775 .map_err(AppError::from_wallet_error)?;
776 Ok(Json(result))
777}
778
779pub async fn acquire_certificate(
785 State(wallet): State<WalletState>,
786 headers: HeaderMap,
787 Json(args): Json<AcquireCertificateArgs>,
788) -> Result<Json<WalletCertificate>, AppError> {
789 let originator = extract_originator(&headers)?;
790 let result = wallet
791 .acquire_certificate(args, &originator)
792 .await
793 .map_err(AppError::from_wallet_error)?;
794 Ok(Json(result))
795}
796
797pub async fn list_certificates(
799 State(wallet): State<WalletState>,
800 headers: HeaderMap,
801 Json(args): Json<ListCertificatesArgs>,
802) -> Result<Json<ListCertificatesResult>, AppError> {
803 let originator = extract_originator(&headers)?;
804 let result = wallet
805 .list_certificates(args, &originator)
806 .await
807 .map_err(AppError::from_wallet_error)?;
808 Ok(Json(result))
809}
810
811pub async fn prove_certificate(
813 State(wallet): State<WalletState>,
814 headers: HeaderMap,
815 Json(args): Json<ProveCertificateArgs>,
816) -> Result<Json<ProveCertificateResult>, AppError> {
817 let originator = extract_originator(&headers)?;
818 let result = wallet
819 .prove_certificate(args, &originator)
820 .await
821 .map_err(AppError::from_wallet_error)?;
822 Ok(Json(result))
823}
824
825pub async fn relinquish_certificate(
827 State(wallet): State<WalletState>,
828 headers: HeaderMap,
829 Json(args): Json<RelinquishCertificateArgs>,
830) -> Result<Json<RelinquishCertificateResult>, AppError> {
831 let originator = extract_originator(&headers)?;
832 let result = wallet
833 .relinquish_certificate(args, &originator)
834 .await
835 .map_err(AppError::from_wallet_error)?;
836 Ok(Json(result))
837}
838
839pub async fn discover_by_identity_key(
845 State(wallet): State<WalletState>,
846 headers: HeaderMap,
847 Json(args): Json<DiscoverByIdentityKeyArgs>,
848) -> Result<Json<DiscoverCertificatesResult>, AppError> {
849 let originator = extract_originator(&headers)?;
850 let result = wallet
851 .discover_by_identity_key(args, &originator)
852 .await
853 .map_err(AppError::from_wallet_error)?;
854 Ok(Json(result))
855}
856
857pub async fn discover_by_attributes(
859 State(wallet): State<WalletState>,
860 headers: HeaderMap,
861 Json(args): Json<DiscoverByAttributesArgs>,
862) -> Result<Json<DiscoverCertificatesResult>, AppError> {
863 let originator = extract_originator(&headers)?;
864 let result = wallet
865 .discover_by_attributes(args, &originator)
866 .await
867 .map_err(AppError::from_wallet_error)?;
868 Ok(Json(result))
869}
870
871pub async fn reveal_counterparty_key_linkage(
873 State(wallet): State<WalletState>,
874 headers: HeaderMap,
875 Json(req): Json<McRevealCounterpartyKeyLinkageReq>,
876) -> Result<Json<RevealCounterpartyKeyLinkageResult>, AppError> {
877 let originator = extract_originator(&headers)?;
878 let args = WalletRevealCounterpartyArgs {
879 counterparty: PublicKey::from_hex(&req.counterparty)
880 .map_err(|e| anyhow::anyhow!("{}", e))?,
881 verifier: PublicKey::from_hex(&req.verifier).map_err(|e| anyhow::anyhow!("{}", e))?,
882 privileged: req.privileged,
883 privileged_reason: req.privileged_reason,
884 };
885 let result = wallet
886 .reveal_counterparty_key_linkage(args, &originator)
887 .await
888 .map_err(AppError::from_wallet_error)?;
889 Ok(Json(result))
890}
891
892pub async fn reveal_specific_key_linkage(
894 State(wallet): State<WalletState>,
895 headers: HeaderMap,
896 Json(req): Json<McRevealSpecificKeyLinkageReq>,
897) -> Result<Json<RevealSpecificKeyLinkageResult>, AppError> {
898 let originator = extract_originator(&headers)?;
899 let args = WalletRevealSpecificArgs {
900 counterparty: parse_counterparty(&req.counterparty)?,
901 verifier: PublicKey::from_hex(&req.verifier).map_err(|e| anyhow::anyhow!("{}", e))?,
902 protocol_id: parse_protocol_id(&req.protocol_id)?,
903 key_id: req.key_id,
904 privileged: req.privileged,
905 privileged_reason: req.privileged_reason,
906 };
907 let result = wallet
908 .reveal_specific_key_linkage(args, &originator)
909 .await
910 .map_err(AppError::from_wallet_error)?;
911 Ok(Json(result))
912}