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