Skip to main content

Custom Errors

Custom errors allow you to control the error messages displayed to the user when a failure occurs or a condition is not met during transaction creation.

This approach enhances the user experience by avoiding generic or undescriptive error messages, which are often difficult to understand. In some cases, the user's wallet application (such as Phantom) might even fail to send the transaction due to reasons beyond your control, potentially resulting in no error message being displayed to the user at all.

By creating custom errors, you can take control of errors that occur during the transaction generation process, before it is sent to the network.

Defining a Custom Error

To create a custom error in Znap, first define a new enumerated type (enum) outside your main module. This enum will represent the different custom errors that your Solana Action can generate.

#[derive(ErrorCode)]
enum ActionError {
#[error(msg = "Your wallet cannot claim this blink!")]
NotClaimedWallet
}

We've now defined the structure of our custom error, but let's break down each part of the code in more detail:

  • #[derive(ErrorCode)]: This line doesn't define a variable; instead, it uses a Rust feature called "derivation." By placing #[derive(ErrorCode)] before our enum definition, we instruct the Rust compiler to automatically implement the ErrorCode trait for our ActionError enum. This trait is essential for our program to handle ActionError as an error within the Solana and Znap environment.
  • enum ActionError { ... }: Here, we define a new data type called an enum (enumeration) with the name ActionError. An enum allows us to define a data type that can hold one value from a finite set of possibilities. In this case, each possibility will represent a specific type of error that our application might encounter.
  • #[error(msg = "Your wallet cannot claim this blink!")]: This attribute is applied to the NotClaimedWallet variant within our ActionError enum. Its purpose is to associate a user-friendly error message with this specific variant. When the NotClaimedWallet error occurs during program execution, the message "Your wallet cannot claim this blink!" will be displayed to the user.

With these three elements, we have created a new custom error type called NotClaimedWallet that can be used in our code to handle a specific error case in a more informative and controlled manner.

Implementing a Custom Error

Now that we have defined our custom error NotClaimedWallet, let's see how we can use it within a function:

let receiver_pubkey = Pubkey::from_str(&ctx.payload.account);
let white_list = vec![
pubkey!("9vPm6wd795YQRd2Ae1ZHuBESanEmiqbfgtJ5bbfyvy5J"),
pubkey!("D5TiA9gpwdXgAc1KcMr6uWLUKBwfAR5xbhAMofda4NcB"),
pubkey!("8YBmaVKAMoqT3BVFmSiU166XJrXNXKL6gWE5iNck2sj8"),
pubkey!("8byTaGAbftXnv7FWt6zhR8zTpy9t7fitFRgbtW7EWcAt"),
pubkey!("FRhk5aC4ZgRKtzJzfKQLPaKdQYpyw8RQdYeVYMQg2gky"),
// Add more public keys as needed
];

if !white_list.contains(&receiver_pubkey) {
return Err(Error::from(ActionError::NotClaimedWallet))
}

In this example:

  1. We obtain the user's public key: receiver_pubkey is obtained from the request data (ctx.payload.account) and converted to a Pubkey type.
  2. We define the whitelist: white_list is a vector containing the public keys authorized to perform the action.
  3. We validate the user's public key: We check if receiver_pubkey is present in white_list.
  4. We return the custom error: If the user's public key is not in the whitelist, an error is returned using Err(Error::from(ActionError::NotClaimedWallet)). This indicates that the transaction cannot be generated and provides the user with a clear error message: "Your wallet cannot claim this blink!".

Handling Errors When Obtaining an Unknown Public Key

When converting the text string to a public key using Pubkey::from_str, there's a possibility that the conversion might fail if the string is not properly formatted. To handle this scenario and provide a more informative error message, we'll create a new custom error called InvalidReceiverAccountPublicKey.

The updated code looks like this:

let receiver_pubkey = Pubkey::from_str(&ctx.payload.account)
.or_else(|_| Err(Error::from(ActionError::InvalidReceiverAccountPublicKey)))?;

Let's break down the key parts:

  • Pubkey::from_str(&ctx.payload.account): This attempts to convert the text string ctx.payload.account into a public key (Pubkey). If the conversion is successful, it returns Ok(pubkey). If it fails, it returns Err(ParsePubkeyError).
  • .or_else(...): This method allows us to handle the case where Pubkey::from_str returns an error (Err). The closure |_| ... is executed only if an error occurs.
  • Err(Error::from(ActionError::InvalidReceiverAccountPublicKey)): Inside the or_else closure, we create and return a new error of type Error using our custom error ActionError::InvalidReceiverAccountPublicKey. This allows us to provide a specific error message for this case.
  • ?: The ? operator at the end of the expression is used for error propagation. If either Pubkey::from_str or the or_else closure returns an error, the current function will immediately return with that error. If both operations are successful, the value of receiver_pubkey will be the converted public key.

To finalize the implementation of our new error handling, we need to update the definition of our ActionError enum to include the new InvalidReceiverAccountPublicKey variant:

#[derive(ErrorCode)]
enum ActionError {
#[error(msg = "Invalid receiver account public key")]
InvalidReceiverAccountPublicKey,
#[error(msg = "Your wallet cannot claim this blink!")]
NotClaimedWallet,
}

With this modification, our enum can now represent both errors: an invalid receiver public key and an unauthorized wallet attempting to claim the "blink."

Visualizing a Custom Error

And with that, the implementation is complete! Now, when a user attempts to claim the "blink" with an unauthorized wallet, they will encounter a clear and concise error message like this:

image.png

This message, generated by our custom error, clearly indicates the problem to the user, guiding them towards a resolution.

Beyond Whitelists:

While this example focused on whitelist validation, custom errors in Rust are a versatile tool. You can use them to:

  • Validate user input: Ensure that data entered by the user meets the requirements of your Solana Action.
  • Handle specific error conditions: Create informative messages for different error scenarios within your code.
  • Improve user experience: Provide clear and concise information about issues that may arise.

By mastering the use of custom errors, you can provide users of your "blinks" with a smoother and more intuitive experience, guiding them with clear and helpful messages whenever they encounter any obstacles.