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 fn message(&self) -> &str {
157 &self.message
158 }
159
160 fn with_hint(mut self, hint: &str) -> Self {
162 self.message = format!("{} (hint: {})", self.message, hint);
163 self
164 }
165
166 fn broadcast_rejected(txid: &str) -> Self {
171 Self {
172 status: StatusCode::BAD_GATEWAY,
173 code: "BROADCAST_REJECTED",
174 message: format!(
175 "broadcast rejected: transaction {txid} is not present on the network. \
176 The broadcaster (ARC) dropped it — most likely error 465 \"fee too low\" \
177 on a deep unconfirmed BEEF. The funds were NOT sent."
178 ),
179 }
180 }
181}
182
183impl From<anyhow::Error> for AppError {
184 fn from(err: anyhow::Error) -> Self {
185 Self::from_wallet_error(err)
186 }
187}
188
189impl From<(StatusCode, Json<serde_json::Value>)> for AppError {
190 fn from((status, json): (StatusCode, Json<serde_json::Value>)) -> Self {
191 let message = json
192 .0
193 .get("message")
194 .and_then(|m| m.as_str())
195 .unwrap_or_default()
196 .to_string();
197 let (_, code) = Self::classify(&message);
198 Self {
199 status,
200 code,
201 message,
202 }
203 }
204}
205
206impl IntoResponse for AppError {
207 fn into_response(self) -> Response {
208 tracing::warn!(
209 status = self.status.as_u16(),
210 code = self.code,
211 message = %self.message,
212 "request error"
213 );
214 (
215 self.status,
216 Json(json!({"code": self.code, "message": self.message})),
217 )
218 .into_response()
219 }
220}
221
222pub async fn is_authenticated() -> Json<serde_json::Value> {
224 Json(json!({"authenticated": true}))
225}
226
227pub async fn get_public_key(
229 State(wallet): State<WalletState>,
230 headers: HeaderMap,
231 Json(req): Json<McGetPublicKeyReq>,
232) -> Result<Json<McGetPublicKeyRes>, AppError> {
233 let originator = extract_originator(&headers)?;
234
235 let args = GetPublicKeyArgs {
236 identity_key: req.identity_key,
237 protocol_id: req.protocol_id.map(|v| parse_protocol_id(&v)).transpose()?,
238 key_id: req.key_id,
239 counterparty: req
240 .counterparty
241 .map(|s| parse_counterparty(&s))
242 .transpose()?,
243 for_self: req.for_self,
244 };
245
246 let result = wallet
247 .get_public_key(args, &originator)
248 .await
249 .map_err(AppError::from_wallet_error)?;
250
251 Ok(Json(McGetPublicKeyRes {
252 public_key: result.public_key,
253 }))
254}
255
256pub async fn create_signature(
258 State(wallet): State<WalletState>,
259 headers: HeaderMap,
260 Json(req): Json<McCreateSignatureReq>,
261) -> Result<Json<McCreateSignatureRes>, AppError> {
262 let originator = extract_originator(&headers)?;
263
264 let hash_to_directly_sign = req
267 .hash_to_directly_sign
268 .map(|h| {
269 <[u8; 32]>::try_from(h.as_slice()).map_err(|_| {
270 AppError::from_wallet_error("Validation error: hashToDirectlySign must be 32 bytes")
271 })
272 })
273 .transpose()?;
274 if req.data.is_none() && hash_to_directly_sign.is_none() {
275 return Err(AppError::from_wallet_error(
276 "Validation error: one of data or hashToDirectlySign is required",
277 ));
278 }
279
280 let args = CreateSignatureArgs {
281 data: req.data,
282 hash_to_directly_sign,
283 protocol_id: parse_protocol_id(&req.protocol_id)?,
284 key_id: req.key_id,
285 counterparty: Some(parse_counterparty(&req.counterparty)?),
286 };
287
288 let result = wallet
289 .create_signature(args, &originator)
290 .await
291 .map_err(AppError::from_wallet_error)?;
292
293 Ok(Json(McCreateSignatureRes {
294 signature: result.signature,
295 }))
296}
297
298pub async fn create_action(
304 State(wallet): State<WalletState>,
305 axum::Extension(spending_lock): axum::Extension<super::SpendingLock>,
306 axum::Extension(verifier): axum::Extension<crate::broadcast_verify::BroadcastVerifier>,
307 headers: HeaderMap,
308 Json(req): Json<McCreateActionReq>,
309) -> Result<Json<McCreateActionRes>, AppError> {
310 let originator = extract_originator(&headers)?;
311
312 let (opt_no_send, opt_accept_delayed) = req
315 .options
316 .as_ref()
317 .map(|o| {
318 (
319 o.no_send.unwrap_or(false),
320 o.accept_delayed_broadcast.unwrap_or(false),
321 )
322 })
323 .unwrap_or((false, false));
324
325 let outputs = req
326 .outputs
327 .map(|outs| {
328 outs.into_iter()
329 .map(|o| {
330 Ok(CreateActionOutput {
331 locking_script: hex::decode(&o.locking_script)?,
332 satoshis: o.satoshis,
333 output_description: o.output_description,
334 basket: o.basket,
335 custom_instructions: o.custom_instructions,
336 tags: o.tags,
337 })
338 })
339 .collect::<Result<Vec<_>>>()
340 })
341 .transpose()?;
342
343 let inputs = match req.inputs {
349 Some(reqs) if !reqs.is_empty() => {
350 let mut mapped = Vec::with_capacity(reqs.len());
351 for i in reqs {
352 let outpoint = Outpoint::from_string(&i.outpoint)
353 .map_err(|e| anyhow::anyhow!("invalid input outpoint '{}': {e}", i.outpoint))?;
354 let unlocking_script = match i.unlocking_script {
355 Some(h) => Some(hex::decode(&h).context("invalid unlockingScript hex")?),
356 None => None,
357 };
358 mapped.push(CreateActionInput {
359 outpoint,
360 input_description: i.input_description.unwrap_or_else(|| "input".to_string()),
361 unlocking_script,
362 unlocking_script_length: i.unlocking_script_length,
363 sequence_number: i.sequence_number,
364 });
365 }
366 Some(mapped)
367 }
368 _ => Some(vec![]),
369 };
370
371 let args = CreateActionArgs {
372 description: req.description,
373 input_beef: req.input_beef,
374 inputs,
375 outputs,
376 lock_time: req.lock_time,
377 version: None,
378 labels: req.labels,
379 options: req.options.map(|o| CreateActionOptions {
380 accept_delayed_broadcast: o.accept_delayed_broadcast,
381 randomize_outputs: o.randomize_outputs,
382 sign_and_process: o.sign_and_process,
383 no_send: o.no_send,
384 trust_self: match o.trust_self.as_deref() {
385 Some("known") => Some(TrustSelf::Known),
386 _ => None,
387 },
388 ..Default::default()
389 }),
390 };
391
392 let output_count = args.outputs.as_ref().map(|o| o.len()).unwrap_or(0);
393 let total_sats: u64 = args
394 .outputs
395 .as_ref()
396 .map(|o| o.iter().map(|x| x.satoshis).sum())
397 .unwrap_or(0);
398
399 let _guard = spending_lock.lock().await;
402 let result = wallet
403 .create_action(args, &originator)
404 .await
405 .map_err(AppError::from_wallet_error)?;
406 drop(_guard);
407
408 tracing::info!(
409 originator = %originator,
410 outputs = output_count,
411 total_satoshis = total_sats,
412 txid = ?result.txid.as_ref().map(|t| to_hex(t)),
413 "createAction"
414 );
415
416 let atomic_beef = match (result.beef.as_ref(), result.txid.as_ref()) {
419 (Some(beef_bytes), Some(txid_bytes)) => {
420 let txid_hex = to_hex(txid_bytes);
421 match bsv_sdk::transaction::Beef::from_binary(beef_bytes) {
422 Ok(mut beef) => match beef.to_binary_atomic(&txid_hex) {
423 Ok(ab) => Some(ab),
424 Err(e) => {
425 tracing::warn!(error = %e, "Failed to build AtomicBEEF, falling back to raw tx");
426 result.tx.clone()
427 }
428 },
429 Err(e) => {
430 tracing::warn!(error = %e, "Failed to parse BEEF, falling back to raw tx");
431 result.tx.clone()
432 }
433 }
434 }
435 _ => result.tx.clone(),
436 };
437
438 if !opt_no_send && !opt_accept_delayed && result.signable_transaction.is_none() {
445 if let Some(txid_bytes) = result.txid.as_ref() {
446 let txid_hex = to_hex(txid_bytes);
447 if verifier.verify(&txid_hex).await
448 == crate::broadcast_verify::BroadcastVerification::Rejected
449 {
450 return Err(AppError::broadcast_rejected(&txid_hex));
451 }
452 }
453 }
454
455 Ok(Json(McCreateActionRes {
456 txid: result.txid.map(|t| to_hex(&t)),
457 tx: atomic_beef,
458 send_with_results: result
459 .send_with_results
460 .and_then(|r| serde_json::to_value(r).ok()),
461 signable_transaction: result.signable_transaction.map(|st| {
462 let reference =
466 String::from_utf8(st.reference).unwrap_or_else(|e| hex::encode(e.into_bytes()));
467 McSignableTransaction {
468 tx: st.tx,
469 reference,
470 }
471 }),
472 no_send_change: result
473 .no_send_change
474 .and_then(|v| serde_json::to_value(v).ok()),
475 }))
476}
477
478pub async fn internalize_action(
480 State(wallet): State<WalletState>,
481 headers: HeaderMap,
482 Json(req): Json<McInternalizeActionReq>,
483) -> Result<Json<McInternalizeActionRes>, AppError> {
484 let originator = extract_originator(&headers)?;
485
486 let args = InternalizeActionArgs {
487 tx: req.tx,
488 outputs: req
489 .outputs
490 .into_iter()
491 .map(|o| InternalizeOutput {
492 output_index: o.output_index,
493 protocol: o.protocol,
494 payment_remittance: o.payment_remittance.map(|p| WalletPayment {
495 derivation_prefix: p.derivation_prefix,
496 derivation_suffix: p.derivation_suffix,
497 sender_identity_key: p.sender_identity_key,
498 }),
499 insertion_remittance: o.insertion_remittance.map(|i| BasketInsertion {
500 basket: i.basket,
501 custom_instructions: i.custom_instructions,
502 tags: i.tags,
503 }),
504 })
505 .collect(),
506 description: req.description,
507 labels: None,
508 seek_permission: None,
509 };
510
511 let output_count = args.outputs.len();
512 let result = wallet
513 .internalize_action(args, &originator)
514 .await
515 .map_err(AppError::from_wallet_error)?;
516
517 tracing::info!(
518 originator = %originator,
519 outputs = output_count,
520 accepted = result.accepted,
521 "internalizeAction"
522 );
523
524 Ok(Json(McInternalizeActionRes {
525 accepted: result.accepted,
526 }))
527}
528
529fn parse_protocol_id(v: &serde_json::Value) -> Result<Protocol> {
533 let arr = v.as_array().context("protocolID must be an array")?;
534 let level = arr
535 .first()
536 .and_then(|v| v.as_u64())
537 .context("protocolID[0] must be an integer")?;
538 let name = arr
539 .get(1)
540 .and_then(|v| v.as_str())
541 .context("protocolID[1] must be a string")?;
542 Ok(Protocol::new(
543 match level {
544 0 => SecurityLevel::Silent,
545 1 => SecurityLevel::App,
546 2 => SecurityLevel::Counterparty,
547 _ => anyhow::bail!("invalid security level: {level}"),
548 },
549 name,
550 ))
551}
552
553fn parse_counterparty(s: &str) -> Result<Counterparty> {
555 match s {
556 "self" => Ok(Counterparty::Self_),
557 "anyone" => Ok(Counterparty::Anyone),
558 hex => Ok(Counterparty::Other(PublicKey::from_hex(hex)?)),
559 }
560}
561
562pub async fn get_height(
568 State(wallet): State<WalletState>,
569) -> Result<Json<GetHeightResult>, AppError> {
570 let result = wallet
571 .get_height("localhost")
572 .await
573 .map_err(AppError::from_wallet_error)?;
574 Ok(Json(result))
575}
576
577pub async fn get_network(
579 State(wallet): State<WalletState>,
580) -> Result<Json<GetNetworkResult>, AppError> {
581 let result = wallet
582 .get_network("localhost")
583 .await
584 .map_err(AppError::from_wallet_error)?;
585 Ok(Json(result))
586}
587
588pub async fn get_version(
590 State(wallet): State<WalletState>,
591) -> Result<Json<GetVersionResult>, AppError> {
592 let result = wallet
593 .get_version("localhost")
594 .await
595 .map_err(AppError::from_wallet_error)?;
596 Ok(Json(result))
597}
598
599pub async fn wait_for_authentication(
601 State(wallet): State<WalletState>,
602) -> Result<Json<AuthenticatedResult>, AppError> {
603 let result = wallet
604 .wait_for_authentication("localhost")
605 .await
606 .map_err(AppError::from_wallet_error)?;
607 Ok(Json(result))
608}
609
610pub async fn get_header_for_height(
616 State(wallet): State<WalletState>,
617 headers: HeaderMap,
618 Json(args): Json<GetHeaderArgs>,
619) -> Result<Json<GetHeaderResult>, AppError> {
620 let originator = extract_originator(&headers)?;
621 let result = wallet
622 .get_header_for_height(args, &originator)
623 .await
624 .map_err(AppError::from_wallet_error)?;
625 Ok(Json(result))
626}
627
628pub async fn verify_signature(
634 State(wallet): State<WalletState>,
635 headers: HeaderMap,
636 Json(req): Json<McVerifySignatureReq>,
637) -> Result<Json<serde_json::Value>, AppError> {
638 let originator = extract_originator(&headers)?;
639 let args = VerifySignatureArgs {
640 data: req.data,
641 hash_to_directly_verify: None,
642 signature: req.signature,
643 protocol_id: parse_protocol_id(&req.protocol_id)?,
644 key_id: req.key_id,
645 counterparty: req
646 .counterparty
647 .map(|s| parse_counterparty(&s))
648 .transpose()?,
649 for_self: req.for_self,
650 };
651 let result = wallet
652 .verify_signature(args, &originator)
653 .await
654 .map_err(AppError::from_wallet_error)?;
655 Ok(Json(json!({ "valid": result.valid })))
656}
657
658pub async fn encrypt(
660 State(wallet): State<WalletState>,
661 headers: HeaderMap,
662 Json(req): Json<McEncryptReq>,
663) -> Result<Json<serde_json::Value>, AppError> {
664 let originator = extract_originator(&headers)?;
665 let args = EncryptArgs {
666 plaintext: req.plaintext,
667 protocol_id: parse_protocol_id(&req.protocol_id)?,
668 key_id: req.key_id,
669 counterparty: req
670 .counterparty
671 .map(|s| parse_counterparty(&s))
672 .transpose()?,
673 };
674 let result = wallet
675 .encrypt(args, &originator)
676 .await
677 .map_err(AppError::from_wallet_error)?;
678 Ok(Json(json!({ "ciphertext": result.ciphertext })))
679}
680
681pub async fn decrypt(
683 State(wallet): State<WalletState>,
684 headers: HeaderMap,
685 Json(req): Json<McDecryptReq>,
686) -> Result<Json<serde_json::Value>, AppError> {
687 let originator = extract_originator(&headers)?;
688 let args = DecryptArgs {
689 ciphertext: req.ciphertext,
690 protocol_id: parse_protocol_id(&req.protocol_id)?,
691 key_id: req.key_id,
692 counterparty: req
693 .counterparty
694 .map(|s| parse_counterparty(&s))
695 .transpose()?,
696 };
697 let result = wallet
698 .decrypt(args, &originator)
699 .await
700 .map_err(AppError::from_wallet_error)?;
701 Ok(Json(json!({ "plaintext": result.plaintext })))
702}
703
704pub async fn create_hmac(
706 State(wallet): State<WalletState>,
707 headers: HeaderMap,
708 Json(req): Json<McCreateHmacReq>,
709) -> Result<Json<serde_json::Value>, AppError> {
710 let originator = extract_originator(&headers)?;
711 let args = CreateHmacArgs {
712 data: req.data,
713 protocol_id: parse_protocol_id(&req.protocol_id)?,
714 key_id: req.key_id,
715 counterparty: req
716 .counterparty
717 .map(|s| parse_counterparty(&s))
718 .transpose()?,
719 };
720 let result = wallet
721 .create_hmac(args, &originator)
722 .await
723 .map_err(AppError::from_wallet_error)?;
724 Ok(Json(json!({ "hmac": result.hmac.to_vec() })))
725}
726
727pub async fn verify_hmac(
729 State(wallet): State<WalletState>,
730 headers: HeaderMap,
731 Json(req): Json<McVerifyHmacReq>,
732) -> Result<Json<serde_json::Value>, AppError> {
733 let originator = extract_originator(&headers)?;
734 let hmac: [u8; 32] = req
735 .hmac
736 .try_into()
737 .map_err(|_| anyhow::anyhow!("hmac must be exactly 32 bytes"))?;
738 let args = VerifyHmacArgs {
739 data: req.data,
740 hmac,
741 protocol_id: parse_protocol_id(&req.protocol_id)?,
742 key_id: req.key_id,
743 counterparty: req
744 .counterparty
745 .map(|s| parse_counterparty(&s))
746 .transpose()?,
747 };
748 let result = wallet
749 .verify_hmac(args, &originator)
750 .await
751 .map_err(AppError::from_wallet_error)?;
752 Ok(Json(json!({ "valid": result.valid })))
753}
754
755pub async fn sign_action(
761 State(wallet): State<WalletState>,
762 headers: HeaderMap,
763 Json(args): Json<SignActionArgs>,
764) -> Result<Json<SignActionResult>, AppError> {
765 let originator = extract_originator(&headers)?;
766 let reference = args.reference.clone();
767 let result = wallet
768 .sign_action(args, &originator)
769 .await
770 .map_err(AppError::from_wallet_error)?;
771 tracing::info!(originator = %originator, reference = %reference, "signAction");
772 Ok(Json(result))
773}
774
775pub async fn abort_action(
777 State(wallet): State<WalletState>,
778 headers: HeaderMap,
779 Json(args): Json<AbortActionArgs>,
780) -> Result<Json<AbortActionResult>, AppError> {
781 let originator = extract_originator(&headers)?;
782 let result = wallet.abort_action(args, &originator).await.map_err(|e| {
783 let mut err = AppError::from_wallet_error(e);
788 if err.message().contains("unproven") {
789 err = err.with_hint(
790 "an 'unproven' tx may already be on-chain; if its broadcast \
791 never landed, run `bsv-wallet cleanup-abandoned --execute` \
792 (verifies chain absence before restoring inputs) or wait for \
793 the daemon's auto-reconcile tick",
794 );
795 }
796 err
797 })?;
798 Ok(Json(result))
799}
800
801pub async fn list_actions(
803 State(wallet): State<WalletState>,
804 headers: HeaderMap,
805 Json(args): Json<ListActionsArgs>,
806) -> Result<Json<ListActionsResult>, AppError> {
807 let originator = extract_originator(&headers)?;
808 let result = wallet
809 .list_actions(args, &originator)
810 .await
811 .map_err(AppError::from_wallet_error)?;
812 Ok(Json(result))
813}
814
815pub async fn list_outputs(
817 State(wallet): State<WalletState>,
818 headers: HeaderMap,
819 Json(args): Json<ListOutputsArgs>,
820) -> Result<Json<ListOutputsResult>, AppError> {
821 let originator = extract_originator(&headers)?;
822 let result = wallet
823 .list_outputs(args, &originator)
824 .await
825 .map_err(AppError::from_wallet_error)?;
826 Ok(Json(result))
827}
828
829pub async fn relinquish_output(
834 State(wallet): State<WalletState>,
835 axum::Extension(spending_lock): axum::Extension<super::SpendingLock>,
836 headers: HeaderMap,
837 Json(args): Json<RelinquishOutputArgs>,
838) -> Result<Json<RelinquishOutputResult>, AppError> {
839 let originator = extract_originator(&headers)?;
840 let _guard = spending_lock.lock().await;
841 let result = wallet
842 .relinquish_output(args, &originator)
843 .await
844 .map_err(AppError::from_wallet_error)?;
845 Ok(Json(result))
846}
847
848pub async fn acquire_certificate(
854 State(wallet): State<WalletState>,
855 headers: HeaderMap,
856 Json(args): Json<AcquireCertificateArgs>,
857) -> Result<Json<WalletCertificate>, AppError> {
858 let originator = extract_originator(&headers)?;
859 let result = wallet
860 .acquire_certificate(args, &originator)
861 .await
862 .map_err(AppError::from_wallet_error)?;
863 Ok(Json(result))
864}
865
866pub async fn list_certificates(
868 State(wallet): State<WalletState>,
869 headers: HeaderMap,
870 Json(args): Json<ListCertificatesArgs>,
871) -> Result<Json<ListCertificatesResult>, AppError> {
872 let originator = extract_originator(&headers)?;
873 let result = wallet
874 .list_certificates(args, &originator)
875 .await
876 .map_err(AppError::from_wallet_error)?;
877 Ok(Json(result))
878}
879
880pub async fn prove_certificate(
882 State(wallet): State<WalletState>,
883 headers: HeaderMap,
884 Json(args): Json<ProveCertificateArgs>,
885) -> Result<Json<ProveCertificateResult>, AppError> {
886 let originator = extract_originator(&headers)?;
887 let result = wallet
888 .prove_certificate(args, &originator)
889 .await
890 .map_err(AppError::from_wallet_error)?;
891 Ok(Json(result))
892}
893
894pub async fn relinquish_certificate(
896 State(wallet): State<WalletState>,
897 headers: HeaderMap,
898 Json(args): Json<RelinquishCertificateArgs>,
899) -> Result<Json<RelinquishCertificateResult>, AppError> {
900 let originator = extract_originator(&headers)?;
901 let result = wallet
902 .relinquish_certificate(args, &originator)
903 .await
904 .map_err(AppError::from_wallet_error)?;
905 Ok(Json(result))
906}
907
908pub async fn discover_by_identity_key(
914 State(wallet): State<WalletState>,
915 headers: HeaderMap,
916 Json(args): Json<DiscoverByIdentityKeyArgs>,
917) -> Result<Json<DiscoverCertificatesResult>, AppError> {
918 let originator = extract_originator(&headers)?;
919 let result = wallet
920 .discover_by_identity_key(args, &originator)
921 .await
922 .map_err(AppError::from_wallet_error)?;
923 Ok(Json(result))
924}
925
926pub async fn discover_by_attributes(
928 State(wallet): State<WalletState>,
929 headers: HeaderMap,
930 Json(args): Json<DiscoverByAttributesArgs>,
931) -> Result<Json<DiscoverCertificatesResult>, AppError> {
932 let originator = extract_originator(&headers)?;
933 let result = wallet
934 .discover_by_attributes(args, &originator)
935 .await
936 .map_err(AppError::from_wallet_error)?;
937 Ok(Json(result))
938}
939
940pub async fn reveal_counterparty_key_linkage(
942 State(wallet): State<WalletState>,
943 headers: HeaderMap,
944 Json(req): Json<McRevealCounterpartyKeyLinkageReq>,
945) -> Result<Json<RevealCounterpartyKeyLinkageResult>, AppError> {
946 let originator = extract_originator(&headers)?;
947 let args = WalletRevealCounterpartyArgs {
948 counterparty: PublicKey::from_hex(&req.counterparty)
949 .map_err(|e| anyhow::anyhow!("{}", e))?,
950 verifier: PublicKey::from_hex(&req.verifier).map_err(|e| anyhow::anyhow!("{}", e))?,
951 privileged: req.privileged,
952 privileged_reason: req.privileged_reason,
953 };
954 let result = wallet
955 .reveal_counterparty_key_linkage(args, &originator)
956 .await
957 .map_err(AppError::from_wallet_error)?;
958 Ok(Json(result))
959}
960
961pub async fn reveal_specific_key_linkage(
963 State(wallet): State<WalletState>,
964 headers: HeaderMap,
965 Json(req): Json<McRevealSpecificKeyLinkageReq>,
966) -> Result<Json<RevealSpecificKeyLinkageResult>, AppError> {
967 let originator = extract_originator(&headers)?;
968 let args = WalletRevealSpecificArgs {
969 counterparty: parse_counterparty(&req.counterparty)?,
970 verifier: PublicKey::from_hex(&req.verifier).map_err(|e| anyhow::anyhow!("{}", e))?,
971 protocol_id: parse_protocol_id(&req.protocol_id)?,
972 key_id: req.key_id,
973 privileged: req.privileged,
974 privileged_reason: req.privileged_reason,
975 };
976 let result = wallet
977 .reveal_specific_key_linkage(args, &originator)
978 .await
979 .map_err(AppError::from_wallet_error)?;
980 Ok(Json(result))
981}