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 ourenum
definition, we instruct the Rust compiler to automatically implement theErrorCode
trait for ourActionError
enum. This trait is essential for our program to handleActionError
as an error within the Solana and Znap environment.enum ActionError { ... }
: Here, we define a new data type called anenum
(enumeration) with the nameActionError
. Anenum
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 theNotClaimedWallet
variant within ourActionError
enum. Its purpose is to associate a user-friendly error message with this specific variant. When theNotClaimedWallet
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:
- We obtain the user's public key:
receiver_pubkey
is obtained from the request data (ctx.payload.account
) and converted to aPubkey
type. - We define the whitelist:
white_list
is a vector containing the public keys authorized to perform the action. - We validate the user's public key: We check if
receiver_pubkey
is present inwhite_list
. - 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 stringctx.payload.account
into a public key (Pubkey
). If the conversion is successful, it returnsOk(pubkey)
. If it fails, it returnsErr(ParsePubkeyError)
..or_else(...)
: This method allows us to handle the case wherePubkey::from_str
returns an error (Err
). The closure|_| ...
is executed only if an error occurs.Err(Error::from(ActionError::InvalidReceiverAccountPublicKey))
: Inside theor_else
closure, we create and return a new error of typeError
using our custom errorActionError::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 eitherPubkey::from_str
or theor_else
closure returns an error, the current function will immediately return with that error. If both operations are successful, the value ofreceiver_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:
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.