1use std::array::TryFromSliceError;
4use std::fmt;
5
6use cashu::{CurrencyUnit, PaymentMethod};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use serde_json::Value;
9use thiserror::Error;
10
11use crate::nuts::Id;
12use crate::util::hex;
13#[cfg(feature = "wallet")]
14use crate::wallet::WalletKey;
15use crate::Amount;
16
17#[derive(Debug, Error)]
19pub enum Error {
20 #[error("No Key for Amount")]
22 AmountKey,
23 #[error("Keyset id not known: `{0}`")]
25 KeysetUnknown(Id),
26 #[error("Unit unsupported")]
28 UnsupportedUnit,
29 #[error("Payment failed")]
31 PaymentFailed,
32 #[error("Payment pending")]
34 PaymentPending,
35 #[error("Request already paid")]
37 RequestAlreadyPaid,
38 #[error("Invalid payment request")]
40 InvalidPaymentRequest,
41 #[error("Invoice Amount undefined")]
43 InvoiceAmountUndefined,
44 #[error("Split Values must be less then or equal to amount")]
46 SplitValuesGreater,
47 #[error("Amount Overflow")]
49 AmountOverflow,
50 #[error("Signature missing or invalid")]
52 SignatureMissingOrInvalid,
53 #[error("Amount Less Invoice is not allowed")]
55 AmountLessNotAllowed,
56 #[error("Multi-Part Internal Melt Quotes are not supported")]
58 InternalMultiPartMeltQuote,
59 #[error("Multi-Part payment is not supported for unit `{0}` and method `{1}`")]
61 MppUnitMethodNotSupported(CurrencyUnit, PaymentMethod),
62 #[error("Clear Auth Required")]
64 ClearAuthRequired,
65 #[error("Blind Auth Required")]
67 BlindAuthRequired,
68 #[error("Clear Auth Failed")]
70 ClearAuthFailed,
71 #[error("Blind Auth Failed")]
73 BlindAuthFailed,
74 #[error("Auth settings undefined")]
76 AuthSettingsUndefined,
77 #[error("Mint time outside of tolerance")]
79 MintTimeExceedsTolerance,
80 #[error("Insufficient blind auth tokens, must reauth")]
82 InsufficientBlindAuthTokens,
83 #[error("Auth localstore undefined")]
85 AuthLocalstoreUndefined,
86 #[error("Wallet cat not set")]
88 CatNotSet,
89 #[error("Could not get mint info")]
91 CouldNotGetMintInfo,
92 #[error("Amountless invoices are not supported for unit `{0}` and method `{1}`")]
94 AmountlessInvoiceNotSupported(CurrencyUnit, PaymentMethod),
95 #[error("Payment id seen for mint")]
97 DuplicatePaymentId,
98 #[error("Pubkey required")]
100 PubkeyRequired,
101 #[error("Invalid payment method")]
103 InvalidPaymentMethod,
104 #[error("Amount undefined")]
106 AmountUndefined,
107 #[error("Payment method unsupported")]
109 UnsupportedPaymentMethod,
110 #[error("Could not parse bolt12")]
112 Bolt12parse,
113
114 #[error("Failed to parse BIP353 address: {0}")]
116 Bip353Parse(String),
117
118 #[error("Operation timeout")]
120 Timeout,
121
122 #[error("Failed to resolve BIP353 address: {0}")]
124 Bip353Resolve(String),
125 #[error("No Lightning offer found in BIP353 payment instructions")]
127 Bip353NoLightningOffer,
128
129 #[error("Internal send error: {0}")]
131 SendError(String),
132
133 #[error("Internal receive error: {0}")]
135 RecvError(String),
136
137 #[error("Minting is disabled")]
140 MintingDisabled,
141 #[error("Unknown quote")]
143 UnknownQuote,
144 #[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
146 ExpiredQuote(u64, u64),
147 #[error("Amount must be between `{0}` and `{1}` is `{2}`")]
149 AmountOutofLimitRange(Amount, Amount, Amount),
150 #[error("Quote not paid")]
152 UnpaidQuote,
153 #[error("Quote pending")]
155 PendingQuote,
156 #[error("Quote already issued")]
158 IssuedQuote,
159 #[error("Quote is already paid")]
161 PaidQuote,
162 #[error("Payment state is unknown")]
164 UnknownPaymentState,
165 #[error("Minting is disabled")]
167 MeltingDisabled,
168 #[error("Unknown Keyset")]
170 UnknownKeySet,
171 #[error("Blinded Message is already signed")]
173 BlindedMessageAlreadySigned,
174 #[error("Inactive Keyset")]
176 InactiveKeyset,
177 #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
179 TransactionUnbalanced(u64, u64, u64),
180 #[error("Duplicate Inputs")]
182 DuplicateInputs,
183 #[error("Duplicate outputs")]
185 DuplicateOutputs,
186 #[error("Cannot have multiple units")]
188 MultipleUnits,
189 #[error("Input unit must match output")]
191 UnitMismatch,
192 #[error("Sig all cannot be used in melt")]
194 SigAllUsedInMelt,
195 #[error("Token Already Spent")]
197 TokenAlreadySpent,
198 #[error("Token Pending")]
200 TokenPending,
201 #[error("Internal Error")]
203 Internal,
204 #[error("Oidc client not set")]
206 OidcNotSet,
207
208 #[error("P2PK condition not met `{0}`")]
211 P2PKConditionsNotMet(String),
212 #[error("Duplicate signature from same pubkey in P2PK")]
214 DuplicateSignatureError,
215 #[error("Spending condition locktime not provided")]
217 LocktimeNotProvided,
218 #[error("Invalid spending conditions: `{0}`")]
220 InvalidSpendConditions(String),
221 #[error("Incorrect wallet: `{0}`")]
223 IncorrectWallet(String),
224 #[error("Unknown wallet: `{0}`")]
226 #[cfg(feature = "wallet")]
227 UnknownWallet(WalletKey),
228 #[error("Max fee exceeded")]
230 MaxFeeExceeded,
231 #[error("Url path segments could not be joined")]
233 UrlPathSegments,
234 #[error("Unknown error response: `{0}`")]
236 UnknownErrorResponse(String),
237 #[error("Could not verify DLEQ proof")]
239 CouldNotVerifyDleq,
240 #[error("Dleq proof not provided for signature")]
242 DleqProofNotProvided,
243 #[error("Token does not match wallet mint")]
246 IncorrectMint,
247 #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")]
249 MultiMintTokenNotSupported,
250 #[error("Preimage not provided")]
252 PreimageNotProvided,
253
254 #[error("Currency unit mismatch: wallet uses {expected}, but {found} provided")]
257 MultiMintCurrencyUnitMismatch {
258 expected: CurrencyUnit,
260 found: CurrencyUnit,
262 },
263 #[error("Unknown mint: {mint_url}")]
265 UnknownMint {
266 mint_url: String,
268 },
269 #[error("Transfer timeout: failed to transfer {amount} from {source_mint} to {target_mint}")]
271 TransferTimeout {
272 source_mint: String,
274 target_mint: String,
276 amount: Amount,
278 },
279 #[error("Insufficient funds")]
281 InsufficientFunds,
282 #[error("Unexpected proof state")]
284 UnexpectedProofState,
285 #[error("No active keyset")]
287 NoActiveKeyset,
288 #[error("Incorrect quote amount")]
290 IncorrectQuoteAmount,
291 #[error("Invoice Description not supported")]
293 InvoiceDescriptionUnsupported,
294 #[error("Invalid transaction direction")]
296 InvalidTransactionDirection,
297 #[error("Invalid transaction id")]
299 InvalidTransactionId,
300 #[error("Transaction not found")]
302 TransactionNotFound,
303 #[error("Invalid KV store key or namespace: {0}")]
305 KVStoreInvalidKey(String),
306 #[error("`{0}`")]
308 Custom(String),
309
310 #[error(transparent)]
313 Invoice(#[from] lightning_invoice::ParseOrSemanticError),
314 #[error(transparent)]
316 Bip32(#[from] bitcoin::bip32::Error),
317 #[error(transparent)]
319 ParseInt(#[from] std::num::ParseIntError),
320 #[error(transparent)]
322 UrlParseError(#[from] url::ParseError),
323 #[error(transparent)]
325 Utf8ParseError(#[from] std::string::FromUtf8Error),
326 #[error(transparent)]
328 SerdeJsonError(#[from] serde_json::Error),
329 #[error(transparent)]
331 Base64Error(#[from] bitcoin::base64::DecodeError),
332 #[error(transparent)]
334 HexError(#[from] hex::Error),
335 #[error("Http transport error {0:?}: {1}")]
337 HttpError(Option<u16>, String),
338 #[cfg(feature = "wallet")]
339 #[error(transparent)]
342 CashuUrl(#[from] crate::mint_url::Error),
343 #[error(transparent)]
345 Secret(#[from] crate::secret::Error),
346 #[error(transparent)]
348 AmountError(#[from] crate::amount::Error),
349 #[error(transparent)]
351 DHKE(#[from] crate::dhke::Error),
352 #[error(transparent)]
354 NUT00(#[from] crate::nuts::nut00::Error),
355 #[error(transparent)]
357 NUT01(#[from] crate::nuts::nut01::Error),
358 #[error(transparent)]
360 NUT02(#[from] crate::nuts::nut02::Error),
361 #[error(transparent)]
363 NUT03(#[from] crate::nuts::nut03::Error),
364 #[error(transparent)]
366 NUT04(#[from] crate::nuts::nut04::Error),
367 #[error(transparent)]
369 NUT05(#[from] crate::nuts::nut05::Error),
370 #[error(transparent)]
372 NUT11(#[from] crate::nuts::nut11::Error),
373 #[error(transparent)]
375 NUT12(#[from] crate::nuts::nut12::Error),
376 #[error(transparent)]
378 #[cfg(feature = "wallet")]
379 NUT13(#[from] crate::nuts::nut13::Error),
380 #[error(transparent)]
382 NUT14(#[from] crate::nuts::nut14::Error),
383 #[error(transparent)]
385 NUT18(#[from] crate::nuts::nut18::Error),
386 #[error(transparent)]
388 NUT20(#[from] crate::nuts::nut20::Error),
389 #[error(transparent)]
391 #[cfg(feature = "auth")]
392 NUT21(#[from] crate::nuts::nut21::Error),
393 #[error(transparent)]
395 #[cfg(feature = "auth")]
396 NUT22(#[from] crate::nuts::nut22::Error),
397 #[error(transparent)]
399 NUT23(#[from] crate::nuts::nut23::Error),
400 #[error(transparent)]
402 #[cfg(feature = "mint")]
403 QuoteId(#[from] crate::quote_id::QuoteIdError),
404 #[error(transparent)]
406 TryFromSliceError(#[from] TryFromSliceError),
407 #[error(transparent)]
409 Database(crate::database::Error),
410 #[error(transparent)]
412 #[cfg(feature = "mint")]
413 Payment(#[from] crate::payment::Error),
414}
415
416#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
420#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
421pub struct ErrorResponse {
422 pub code: ErrorCode,
424 #[serde(default)]
426 pub detail: String,
427}
428
429impl fmt::Display for ErrorResponse {
430 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431 write!(f, "code: {}, detail: {}", self.code, self.detail)
432 }
433}
434
435impl ErrorResponse {
436 pub fn new(code: ErrorCode, detail: String) -> Self {
438 Self { code, detail }
439 }
440
441 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
443 let value: Value = serde_json::from_str(json)?;
444
445 Self::from_value(value)
446 }
447
448 pub fn from_value(value: Value) -> Result<Self, serde_json::Error> {
450 match serde_json::from_value::<ErrorResponse>(value.clone()) {
451 Ok(res) => Ok(res),
452 Err(_) => Ok(Self {
453 code: ErrorCode::Unknown(999),
454 detail: value.to_string(),
455 }),
456 }
457 }
458}
459
460fn map_nut11_error(nut11_error: &crate::nuts::nut11::Error) -> ErrorCode {
462 match nut11_error {
463 crate::nuts::nut11::Error::SignaturesNotProvided => ErrorCode::WitnessMissingOrInvalid,
464 crate::nuts::nut11::Error::InvalidSignature => ErrorCode::WitnessMissingOrInvalid,
465 crate::nuts::nut11::Error::DuplicateSignature => ErrorCode::DuplicateSignature,
466 _ => ErrorCode::Unknown(9999), }
468}
469
470impl From<Error> for ErrorResponse {
471 fn from(err: Error) -> ErrorResponse {
472 match err {
473 Error::TokenAlreadySpent => ErrorResponse {
474 code: ErrorCode::TokenAlreadySpent,
475 detail: err.to_string(),
476 },
477 Error::UnsupportedUnit => ErrorResponse {
478 code: ErrorCode::UnsupportedUnit,
479 detail: err.to_string(),
480 },
481 Error::PaymentFailed => ErrorResponse {
482 code: ErrorCode::LightningError,
483 detail: err.to_string(),
484 },
485 Error::RequestAlreadyPaid => ErrorResponse {
486 code: ErrorCode::InvoiceAlreadyPaid,
487 detail: "Invoice already paid.".to_string(),
488 },
489 Error::TransactionUnbalanced(inputs_total, outputs_total, fee_expected) => {
490 ErrorResponse {
491 code: ErrorCode::TransactionUnbalanced,
492 detail: format!(
493 "Inputs: {inputs_total}, Outputs: {outputs_total}, expected_fee: {fee_expected}. Transaction inputs should equal outputs less fee"
494 ),
495 }
496 }
497 Error::MintingDisabled => ErrorResponse {
498 code: ErrorCode::MintingDisabled,
499 detail: err.to_string(),
500 },
501 Error::BlindedMessageAlreadySigned => ErrorResponse {
502 code: ErrorCode::BlindedMessageAlreadySigned,
503 detail: err.to_string(),
504 },
505 Error::InsufficientFunds => ErrorResponse {
506 code: ErrorCode::TransactionUnbalanced,
507 detail: err.to_string(),
508 },
509 Error::AmountOutofLimitRange(_min, _max, _amount) => ErrorResponse {
510 code: ErrorCode::AmountOutofLimitRange,
511 detail: err.to_string(),
512 },
513 Error::ExpiredQuote(_, _) => ErrorResponse {
514 code: ErrorCode::QuoteExpired,
515 detail: err.to_string(),
516 },
517 Error::PendingQuote => ErrorResponse {
518 code: ErrorCode::QuotePending,
519 detail: err.to_string(),
520 },
521 Error::TokenPending => ErrorResponse {
522 code: ErrorCode::TokenPending,
523 detail: err.to_string(),
524 },
525 Error::ClearAuthRequired => ErrorResponse {
526 code: ErrorCode::ClearAuthRequired,
527 detail: Error::ClearAuthRequired.to_string(),
528 },
529 Error::ClearAuthFailed => ErrorResponse {
530 code: ErrorCode::ClearAuthFailed,
531 detail: Error::ClearAuthFailed.to_string(),
532 },
533 Error::BlindAuthRequired => ErrorResponse {
534 code: ErrorCode::BlindAuthRequired,
535 detail: Error::BlindAuthRequired.to_string(),
536 },
537 Error::BlindAuthFailed => ErrorResponse {
538 code: ErrorCode::BlindAuthFailed,
539 detail: Error::BlindAuthFailed.to_string(),
540 },
541 Error::NUT20(err) => ErrorResponse {
542 code: ErrorCode::WitnessMissingOrInvalid,
543 detail: err.to_string(),
544 },
545 Error::DuplicateInputs => ErrorResponse {
546 code: ErrorCode::DuplicateInputs,
547 detail: err.to_string(),
548 },
549 Error::DuplicateOutputs => ErrorResponse {
550 code: ErrorCode::DuplicateOutputs,
551 detail: err.to_string(),
552 },
553 Error::MultipleUnits => ErrorResponse {
554 code: ErrorCode::MultipleUnits,
555 detail: err.to_string(),
556 },
557 Error::UnitMismatch => ErrorResponse {
558 code: ErrorCode::UnitMismatch,
559 detail: err.to_string(),
560 },
561 Error::UnpaidQuote => ErrorResponse {
562 code: ErrorCode::QuoteNotPaid,
563 detail: Error::UnpaidQuote.to_string(),
564 },
565 Error::NUT11(err) => {
566 let code = map_nut11_error(&err);
567 let extra = if matches!(err, crate::nuts::nut11::Error::SignaturesNotProvided) {
568 Some("P2PK signatures are required but not provided".to_string())
569 } else {
570 None
571 };
572 ErrorResponse {
573 code,
574 detail: match extra {
575 Some(extra) => format!("{err}. {extra}"),
576 None => err.to_string(),
577 },
578 }
579 },
580 Error::DuplicateSignatureError => ErrorResponse {
581 code: ErrorCode::DuplicateSignature,
582 detail: err.to_string(),
583 },
584 _ => ErrorResponse {
585 code: ErrorCode::Unknown(9999),
586 detail: err.to_string(),
587 },
588 }
589 }
590}
591
592#[cfg(feature = "mint")]
593impl From<crate::database::Error> for Error {
594 fn from(db_error: crate::database::Error) -> Self {
595 match db_error {
596 crate::database::Error::InvalidStateTransition(state) => match state {
597 crate::state::Error::Pending => Self::TokenPending,
598 crate::state::Error::AlreadySpent => Self::TokenAlreadySpent,
599 state => Self::Database(crate::database::Error::InvalidStateTransition(state)),
600 },
601 db_error => Self::Database(db_error),
602 }
603 }
604}
605
606#[cfg(not(feature = "mint"))]
607impl From<crate::database::Error> for Error {
608 fn from(db_error: crate::database::Error) -> Self {
609 Self::Database(db_error)
610 }
611}
612
613impl From<ErrorResponse> for Error {
614 fn from(err: ErrorResponse) -> Error {
615 match err.code {
616 ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
617 ErrorCode::QuoteNotPaid => Self::UnpaidQuote,
618 ErrorCode::QuotePending => Self::PendingQuote,
619 ErrorCode::QuoteExpired => Self::ExpiredQuote(0, 0),
620 ErrorCode::KeysetNotFound => Self::UnknownKeySet,
621 ErrorCode::KeysetInactive => Self::InactiveKeyset,
622 ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned,
623 ErrorCode::UnsupportedUnit => Self::UnsupportedUnit,
624 ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0),
625 ErrorCode::MintingDisabled => Self::MintingDisabled,
626 ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid,
627 ErrorCode::TokenNotVerified => Self::DHKE(crate::dhke::Error::TokenNotVerified),
628 ErrorCode::LightningError => Self::PaymentFailed,
629 ErrorCode::AmountOutofLimitRange => {
630 Self::AmountOutofLimitRange(Amount::default(), Amount::default(), Amount::default())
631 }
632 ErrorCode::TokenPending => Self::TokenPending,
633 ErrorCode::WitnessMissingOrInvalid => Self::SignatureMissingOrInvalid,
634 ErrorCode::DuplicateInputs => Self::DuplicateInputs,
635 ErrorCode::DuplicateOutputs => Self::DuplicateOutputs,
636 ErrorCode::MultipleUnits => Self::MultipleUnits,
637 ErrorCode::UnitMismatch => Self::UnitMismatch,
638 ErrorCode::ClearAuthRequired => Self::ClearAuthRequired,
639 ErrorCode::BlindAuthRequired => Self::BlindAuthRequired,
640 ErrorCode::DuplicateSignature => Self::DuplicateSignatureError,
641 _ => Self::UnknownErrorResponse(err.to_string()),
642 }
643 }
644}
645
646#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
648#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
649pub enum ErrorCode {
650 TokenAlreadySpent,
652 TokenPending,
654 QuoteNotPaid,
656 QuoteExpired,
658 QuotePending,
660 KeysetNotFound,
662 KeysetInactive,
664 BlindedMessageAlreadySigned,
666 UnsupportedUnit,
668 TokensAlreadyIssued,
670 MintingDisabled,
672 InvoiceAlreadyPaid,
674 TokenNotVerified,
676 LightningError,
678 TransactionUnbalanced,
680 AmountOutofLimitRange,
682 WitnessMissingOrInvalid,
684 DuplicateInputs,
686 DuplicateOutputs,
688 MultipleUnits,
690 UnitMismatch,
692 ClearAuthRequired,
694 ClearAuthFailed,
696 BlindAuthRequired,
698 BlindAuthFailed,
700 DuplicateSignature,
702 Unknown(u16),
704}
705
706impl ErrorCode {
707 pub fn from_code(code: u16) -> Self {
709 match code {
710 10002 => Self::BlindedMessageAlreadySigned,
711 10003 => Self::TokenNotVerified,
712 11001 => Self::TokenAlreadySpent,
713 11002 => Self::TransactionUnbalanced,
714 11005 => Self::UnsupportedUnit,
715 11006 => Self::AmountOutofLimitRange,
716 11007 => Self::DuplicateInputs,
717 11008 => Self::DuplicateOutputs,
718 11009 => Self::MultipleUnits,
719 11010 => Self::UnitMismatch,
720 11012 => Self::TokenPending,
721 12001 => Self::KeysetNotFound,
722 12002 => Self::KeysetInactive,
723 20000 => Self::LightningError,
724 20001 => Self::QuoteNotPaid,
725 20002 => Self::TokensAlreadyIssued,
726 20003 => Self::MintingDisabled,
727 20005 => Self::QuotePending,
728 20006 => Self::InvoiceAlreadyPaid,
729 20007 => Self::QuoteExpired,
730 20008 => Self::WitnessMissingOrInvalid,
731 20009 => Self::DuplicateSignature,
732 30001 => Self::ClearAuthRequired,
733 30002 => Self::ClearAuthFailed,
734 31001 => Self::BlindAuthRequired,
735 31002 => Self::BlindAuthFailed,
736 _ => Self::Unknown(code),
737 }
738 }
739
740 pub fn to_code(&self) -> u16 {
742 match self {
743 Self::BlindedMessageAlreadySigned => 10002,
744 Self::TokenNotVerified => 10003,
745 Self::TokenAlreadySpent => 11001,
746 Self::TransactionUnbalanced => 11002,
747 Self::UnsupportedUnit => 11005,
748 Self::AmountOutofLimitRange => 11006,
749 Self::DuplicateInputs => 11007,
750 Self::DuplicateOutputs => 11008,
751 Self::MultipleUnits => 11009,
752 Self::UnitMismatch => 11010,
753 Self::TokenPending => 11012,
754 Self::KeysetNotFound => 12001,
755 Self::KeysetInactive => 12002,
756 Self::LightningError => 20000,
757 Self::QuoteNotPaid => 20001,
758 Self::TokensAlreadyIssued => 20002,
759 Self::MintingDisabled => 20003,
760 Self::QuotePending => 20005,
761 Self::InvoiceAlreadyPaid => 20006,
762 Self::QuoteExpired => 20007,
763 Self::WitnessMissingOrInvalid => 20008,
764 Self::DuplicateSignature => 20009,
765 Self::ClearAuthRequired => 30001,
766 Self::ClearAuthFailed => 30002,
767 Self::BlindAuthRequired => 31001,
768 Self::BlindAuthFailed => 31002,
769 Self::Unknown(code) => *code,
770 }
771 }
772}
773
774impl Serialize for ErrorCode {
775 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
776 where
777 S: Serializer,
778 {
779 serializer.serialize_u16(self.to_code())
780 }
781}
782
783impl<'de> Deserialize<'de> for ErrorCode {
784 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
785 where
786 D: Deserializer<'de>,
787 {
788 let code = u16::deserialize(deserializer)?;
789
790 Ok(ErrorCode::from_code(code))
791 }
792}
793
794impl fmt::Display for ErrorCode {
795 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
796 write!(f, "{}", self.to_code())
797 }
798}