chain_signatures/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(unexpected_cfgs)]
3
4pub mod evm;
5use anchor_lang::prelude::*;
6
7declare_id!("SigMcRMjKfnC7RDG5q4yUMZM1s5KJ9oYTPP4NmJRDRw");
8
9#[program]
10pub mod chain_signatures {
11    use super::*;
12
13    /// Initialize the program state.
14    ///
15    /// # Admin Only
16    ///
17    /// This instruction is restricted to program deployment and is **not intended
18    /// for application developers**. It can only be called once to set up the program.
19    ///
20    /// # Arguments
21    ///
22    /// * `signature_deposit` - Required deposit in lamports for signature requests
23    /// * `chain_id` - CAIP-2 chain identifier (e.g., "solana:mainnet")
24    ///
25    /// # Accounts
26    ///
27    /// * `program_state` - PDA to store program configuration
28    /// * `admin` - Admin account (becomes program admin)
29    pub fn initialize(
30        ctx: Context<Initialize>,
31        signature_deposit: u64,
32        chain_id: String,
33    ) -> Result<()> {
34        let program_state = &mut ctx.accounts.program_state;
35        program_state.admin = ctx.accounts.admin.key();
36        program_state.signature_deposit = signature_deposit;
37        program_state.chain_id = chain_id;
38
39        Ok(())
40    }
41
42    /// Update the required signature deposit amount.
43    ///
44    /// # Admin Only
45    ///
46    /// This instruction is restricted to the program administrator and is **not intended
47    /// for application developers**. It is used for program maintenance.
48    ///
49    /// # Arguments
50    ///
51    /// * `new_deposit` - New deposit amount in lamports
52    ///
53    /// # Emits
54    ///
55    /// * [`DepositUpdatedEvent`]
56    pub fn update_deposit(ctx: Context<AdminOnly>, new_deposit: u64) -> Result<()> {
57        let program_state = &mut ctx.accounts.program_state;
58        let old_deposit = program_state.signature_deposit;
59        program_state.signature_deposit = new_deposit;
60
61        emit!(DepositUpdatedEvent {
62            old_deposit,
63            new_deposit,
64        });
65
66        Ok(())
67    }
68
69    /// Withdraw accumulated funds from the program.
70    ///
71    /// # Admin Only
72    ///
73    /// This instruction is restricted to the program administrator and is **not intended
74    /// for application developers**. It is used for program maintenance.
75    ///
76    /// # Arguments
77    ///
78    /// * `amount` - Amount to withdraw in lamports
79    ///
80    /// # Errors
81    ///
82    /// * [`ChainSignaturesError::InsufficientFunds`] - Program has insufficient balance
83    /// * [`ChainSignaturesError::InvalidRecipient`] - Recipient is zero address
84    ///
85    /// # Emits
86    ///
87    /// * [`FundsWithdrawnEvent`]
88    pub fn withdraw_funds(ctx: Context<WithdrawFunds>, amount: u64) -> Result<()> {
89        let program_state = &ctx.accounts.program_state;
90        let recipient = &ctx.accounts.recipient;
91
92        let program_state_info = program_state.to_account_info();
93        require!(
94            program_state_info.lamports() >= amount,
95            ChainSignaturesError::InsufficientFunds
96        );
97
98        require!(
99            recipient.key() != Pubkey::default(),
100            ChainSignaturesError::InvalidRecipient
101        );
102
103        // Transfer funds from program_state to recipient
104        **program_state_info.try_borrow_mut_lamports()? -= amount;
105        **recipient.try_borrow_mut_lamports()? += amount;
106
107        emit!(FundsWithdrawnEvent {
108            amount,
109            recipient: recipient.key(),
110        });
111
112        Ok(())
113    }
114
115    /// Request a signature from the MPC network on a 32-byte payload.
116    ///
117    /// The payload is typically a transaction hash that needs to be signed.
118    /// The MPC network will respond with a signature via [`respond`].
119    ///
120    /// # Arguments
121    ///
122    /// * `payload` - 32-byte data to sign (typically a transaction hash)
123    /// * `key_version` - MPC key version to use
124    /// * `path` - Derivation path for the user's key (e.g., `"my_wallet"`)
125    /// * `algo` - Reserved for future use (pass empty string `""`)
126    /// * `dest` - Reserved for future use (pass empty string `""`)
127    /// * `params` - Reserved for future use (pass empty string `""`)
128    ///
129    /// # Emits
130    ///
131    /// * [`SignatureRequestedEvent`]
132    ///
133    /// # Example
134    ///
135    /// ```typescript,ignore
136    /// await program.methods
137    ///   .sign(
138    ///     Array.from(txHash),  // [u8; 32] payload to sign
139    ///     0,                    // key_version
140    ///     "my_wallet",          // path (derivation path)
141    ///     "",                   // algo (reserved, pass empty string)
142    ///     "",                   // dest (reserved, pass empty string)
143    ///     ""                    // params (reserved, pass empty string)
144    ///   )
145    ///   .accounts({ ... })
146    ///   .rpc();
147    /// ```
148    pub fn sign(
149        ctx: Context<Sign>,
150        payload: [u8; 32],
151        key_version: u32,
152        path: String,
153        algo: String,
154        dest: String,
155        params: String,
156    ) -> Result<()> {
157        let program_state = &ctx.accounts.program_state;
158        let requester = &ctx.accounts.requester;
159        let system_program = &ctx.accounts.system_program;
160
161        let payer = match &ctx.accounts.fee_payer {
162            Some(fee_payer) => fee_payer.to_account_info(),
163            None => requester.to_account_info(),
164        };
165
166        require!(
167            payer.lamports() >= program_state.signature_deposit,
168            ChainSignaturesError::InsufficientDeposit
169        );
170
171        let transfer_instruction = anchor_lang::system_program::Transfer {
172            from: payer,
173            to: program_state.to_account_info(),
174        };
175
176        anchor_lang::system_program::transfer(
177            CpiContext::new(system_program.to_account_info(), transfer_instruction),
178            program_state.signature_deposit,
179        )?;
180
181        emit_cpi!(SignatureRequestedEvent {
182            sender: *requester.key,
183            payload,
184            key_version,
185            deposit: program_state.signature_deposit,
186            chain_id: program_state.chain_id.clone(),
187            path,
188            algo,
189            dest,
190            params,
191            fee_payer: match &ctx.accounts.fee_payer {
192                Some(payer) => Some(*payer.key),
193                None => None,
194            },
195        });
196
197        Ok(())
198    }
199
200    /// Initiate a bidirectional cross-chain transaction with execution result callback.
201    ///
202    /// This is the primary entry point for cross-chain transactions. The flow:
203    /// 1. User submits unsigned transaction → MPC signs and responds
204    /// 2. User broadcasts to destination chain
205    /// 3. MPC observes execution via light client
206    /// 4. MPC returns execution result via [`respond_bidirectional`]
207    ///
208    /// # Arguments
209    ///
210    /// * `serialized_transaction` - serialized unsigned transaction for destination chain
211    /// * `caip2_id` - CAIP-2 chain identifier (e.g., `"eip155:1"` for Ethereum mainnet)
212    /// * `key_version` - MPC key version to use
213    /// * `path` - Derivation path for signing key
214    /// * `algo` - Reserved for future use (pass empty string `""`)
215    /// * `dest` - Reserved for future use (pass empty string `""`)
216    /// * `params` - Reserved for future use (pass empty string `""`)
217    /// * `program_id` - Callback program ID (reserved for future use)
218    /// * `output_deserialization_schema` - serialization schema for parsing destination chain output
219    /// * `respond_serialization_schema` - serialization schema for serializing response to source chain
220    ///
221    /// # Emits
222    ///
223    /// * [`SignBidirectionalEvent`]
224    ///
225    /// # Errors
226    ///
227    /// * [`ChainSignaturesError::InvalidTransaction`] - Empty transaction data
228    /// * [`ChainSignaturesError::InsufficientDeposit`] - Insufficient deposit
229    pub fn sign_bidirectional(
230        ctx: Context<SignBidirectional>,
231        serialized_transaction: Vec<u8>,
232        caip2_id: String,
233        key_version: u32,
234        path: String,
235        algo: String,
236        dest: String,
237        params: String,
238        program_id: Pubkey,
239        output_deserialization_schema: Vec<u8>,
240        respond_serialization_schema: Vec<u8>,
241    ) -> Result<()> {
242        let program_state = &ctx.accounts.program_state;
243        let requester = &ctx.accounts.requester;
244        let system_program = &ctx.accounts.system_program;
245
246        let payer = match &ctx.accounts.fee_payer {
247            Some(fee_payer) => fee_payer.to_account_info(),
248            None => requester.to_account_info(),
249        };
250
251        require!(
252            payer.lamports() >= program_state.signature_deposit,
253            ChainSignaturesError::InsufficientDeposit
254        );
255
256        require!(
257            !serialized_transaction.is_empty(),
258            ChainSignaturesError::InvalidTransaction
259        );
260
261        let transfer_instruction = anchor_lang::system_program::Transfer {
262            from: payer,
263            to: program_state.to_account_info(),
264        };
265
266        anchor_lang::system_program::transfer(
267            CpiContext::new(system_program.to_account_info(), transfer_instruction),
268            program_state.signature_deposit,
269        )?;
270
271        emit_cpi!(SignBidirectionalEvent {
272            sender: *requester.key,
273            serialized_transaction,
274            caip2_id,
275            key_version,
276            deposit: program_state.signature_deposit,
277            path,
278            algo,
279            dest,
280            params,
281            program_id,
282            output_deserialization_schema,
283            respond_serialization_schema
284        });
285
286        Ok(())
287    }
288
289    /// Respond to signature requests with generated signatures.
290    ///
291    /// Called by MPC responders after signature generation. Supports batched
292    /// requests where each signature is linked to its request via `request_id`.
293    ///
294    /// # Security Note
295    ///
296    /// **Any address can call this function.** Clients must verify signature
297    /// validity off-chain before trusting the response.
298    ///
299    /// # Arguments
300    ///
301    /// * `request_ids` - Array of 32-byte request identifiers
302    /// * `signatures` - Corresponding ECDSA signatures
303    ///
304    /// # Emits
305    ///
306    /// * [`SignatureRespondedEvent`] for each signature
307    pub fn respond(
308        ctx: Context<Respond>,
309        request_ids: Vec<[u8; 32]>,
310        signatures: Vec<Signature>,
311    ) -> Result<()> {
312        require!(
313            request_ids.len() == signatures.len(),
314            ChainSignaturesError::InvalidInputLength
315        );
316
317        for i in 0..request_ids.len() {
318            emit_cpi!(SignatureRespondedEvent {
319                request_id: request_ids[i],
320                responder: *ctx.accounts.responder.key,
321                signature: signatures[i].clone(),
322            });
323        }
324
325        Ok(())
326    }
327
328    /// Report signature generation errors from the MPC network.
329    ///
330    /// # Warning: Debugging Only
331    ///
332    /// This function is **solely for debugging purposes** and should not be used
333    /// in production or relied upon for any business logic. Error events are
334    /// informational only and are not cryptographically verified.
335    ///
336    /// # Security Note
337    ///
338    /// **Any address can call this function.** Do not rely on error events
339    /// for business logic decisions.
340    ///
341    /// # Arguments
342    ///
343    /// * `errors` - Array of error responses with request IDs and messages
344    ///
345    /// # Emits
346    ///
347    /// * [`SignatureErrorEvent`] for each error
348    pub fn respond_error(ctx: Context<RespondError>, errors: Vec<ErrorResponse>) -> Result<()> {
349        for error in errors {
350            emit!(SignatureErrorEvent {
351                request_id: error.request_id,
352                responder: *ctx.accounts.responder.key,
353                error: error.error_message,
354            });
355        }
356
357        Ok(())
358    }
359
360    /// Get the current signature deposit amount. View function.
361    ///
362    /// # Returns
363    ///
364    /// Current signature deposit in lamports.
365    pub fn get_signature_deposit(ctx: Context<GetSignatureDeposit>) -> Result<u64> {
366        let program_state = &ctx.accounts.program_state;
367        Ok(program_state.signature_deposit)
368    }
369
370    /// Finalize a bidirectional flow with execution results from the destination chain.
371    ///
372    /// Called by MPC responders after observing transaction confirmation on the
373    /// destination chain. The signature proves the authenticity of the output.
374    ///
375    /// # Arguments
376    ///
377    /// * `request_id` - Original 32-byte request identifier
378    /// * `serialized_output` - Serialized execution output per `respond_serialization_schema`
379    /// * `signature` - ECDSA signature over `keccak256(request_id || serialized_output)`
380    ///
381    /// # Output Format
382    ///
383    /// For **successful transactions**:
384    /// - Contract call: Serialized return value per schema
385    /// - Simple transfer: Empty success indicator
386    ///
387    /// For **failed transactions**:
388    /// - Magic prefix `0xdeadbeef` followed by failure indicator
389    ///
390    /// # Emits
391    ///
392    /// * [`RespondBidirectionalEvent`]
393    pub fn respond_bidirectional(
394        ctx: Context<ReadRespond>,
395        request_id: [u8; 32],
396        serialized_output: Vec<u8>,
397        signature: Signature,
398    ) -> Result<()> {
399        emit!(RespondBidirectionalEvent {
400            request_id,
401            responder: *ctx.accounts.responder.key,
402            serialized_output,
403            signature,
404        });
405
406        Ok(())
407    }
408}
409
410/// Program configuration state stored in a PDA.
411///
412/// Seeds: `[b"program-state"]`
413#[account]
414pub struct ProgramState {
415    /// Admin account with permission to update settings and withdraw funds.
416    pub admin: Pubkey,
417    /// Required deposit in lamports for signature requests.
418    pub signature_deposit: u64,
419    /// CAIP-2 chain identifier (e.g., "solana:mainnet").
420    pub chain_id: String,
421}
422
423/// A point on the secp256k1 elliptic curve in affine coordinates.
424///
425/// Used to represent the R point in ECDSA signatures.
426///
427/// # Size
428///
429/// 64 bytes total (32 bytes x + 32 bytes y)
430#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
431pub struct AffinePoint {
432    /// X coordinate (big-endian, 32 bytes)
433    pub x: [u8; 32],
434    /// Y coordinate (big-endian, 32 bytes)
435    pub y: [u8; 32],
436}
437
438/// ECDSA signature in affine point representation.
439///
440/// Compatible with secp256k1 curve operations. Can be converted to
441/// standard RSV format for use with Ethereum and other chains.
442///
443/// # Size
444///
445/// 97 bytes total (64 bytes big_r + 32 bytes s + 1 byte recovery_id)
446///
447/// # Conversion to RSV
448///
449/// ```typescript,ignore
450/// const r = Buffer.from(signature.bigR.x).toString('hex');
451/// const s = Buffer.from(signature.s).toString('hex');
452/// const v = signature.recoveryId + 27;
453/// ```
454#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
455pub struct Signature {
456    /// R point of the ECDSA signature (affine coordinates)
457    pub big_r: AffinePoint,
458    /// s scalar of the signature (32 bytes, big-endian)
459    pub s: [u8; 32],
460    /// Recovery ID (0 or 1) for public key recovery
461    pub recovery_id: u8,
462}
463
464/// Error information for failed signature requests.
465#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
466pub struct ErrorResponse {
467    /// Identifier of the failed request
468    pub request_id: [u8; 32],
469    /// Human-readable error description
470    pub error_message: String,
471}
472
473#[derive(Accounts)]
474pub struct Initialize<'info> {
475    #[account(
476        init,
477        payer = admin,
478        space = 8 + 32 + 8 + 4 + 128, // discriminator + admin + deposit + string length + max chain_id length
479        seeds = [b"program-state"],
480        bump
481    )]
482    pub program_state: Account<'info, ProgramState>,
483    #[account(mut)]
484    pub admin: Signer<'info>,
485    pub system_program: Program<'info, System>,
486}
487
488#[derive(Accounts)]
489pub struct AdminOnly<'info> {
490    #[account(
491        mut,
492        seeds = [b"program-state"],
493        bump,
494        has_one = admin @ ChainSignaturesError::Unauthorized
495    )]
496    pub program_state: Account<'info, ProgramState>,
497    #[account(mut)]
498    pub admin: Signer<'info>,
499    pub system_program: Program<'info, System>,
500}
501
502#[derive(Accounts)]
503pub struct WithdrawFunds<'info> {
504    #[account(
505        mut,
506        seeds = [b"program-state"],
507        bump,
508        has_one = admin @ ChainSignaturesError::Unauthorized
509    )]
510    pub program_state: Account<'info, ProgramState>,
511
512    #[account(mut)]
513    pub admin: Signer<'info>,
514
515    /// CHECK: The safety check is performed in the withdraw_funds
516    /// function by checking it is not the zero address.
517    #[account(mut)]
518    pub recipient: AccountInfo<'info>,
519
520    pub system_program: Program<'info, System>,
521}
522
523#[event_cpi]
524#[derive(Accounts)]
525pub struct Sign<'info> {
526    #[account(mut, seeds = [b"program-state"], bump)]
527    pub program_state: Account<'info, ProgramState>,
528    #[account(mut)]
529    pub requester: Signer<'info>,
530    #[account(mut)]
531    pub fee_payer: Option<Signer<'info>>,
532    pub system_program: Program<'info, System>,
533}
534
535#[event_cpi]
536#[derive(Accounts)]
537pub struct SignBidirectional<'info> {
538    #[account(mut, seeds = [b"program-state"], bump)]
539    pub program_state: Account<'info, ProgramState>,
540    #[account(mut)]
541    pub requester: Signer<'info>,
542    #[account(mut)]
543    pub fee_payer: Option<Signer<'info>>,
544    pub system_program: Program<'info, System>,
545    pub instructions: Option<AccountInfo<'info>>,
546}
547
548#[event_cpi]
549#[derive(Accounts)]
550pub struct Respond<'info> {
551    pub responder: Signer<'info>,
552}
553
554#[derive(Accounts)]
555pub struct RespondError<'info> {
556    pub responder: Signer<'info>,
557}
558
559#[derive(Accounts)]
560pub struct GetSignatureDeposit<'info> {
561    #[account(seeds = [b"program-state"], bump)]
562    pub program_state: Account<'info, ProgramState>,
563}
564
565#[derive(Accounts)]
566pub struct ReadRespond<'info> {
567    pub responder: Signer<'info>,
568}
569
570/// Emitted when a signature is requested via the [`chain_signatures::sign`] instruction.
571///
572/// # Event Type
573///
574/// CPI event (emitted via `emit_cpi!`)
575#[event]
576pub struct SignatureRequestedEvent {
577    /// Solana address of the requester.
578    pub sender: Pubkey,
579    /// 32-byte payload to be signed (typically a transaction hash).
580    pub payload: [u8; 32],
581    /// MPC key version used for signing.
582    pub key_version: u32,
583    /// Deposit amount paid in lamports.
584    pub deposit: u64,
585    /// CAIP-2 chain identifier of this program (e.g., "solana:mainnet").
586    pub chain_id: String,
587    /// Derivation path for the user's signing key.
588    pub path: String,
589    /// Signing algorithm (e.g., "secp256k1").
590    pub algo: String,
591    /// Response destination chain.
592    pub dest: String,
593    /// Additional JSON parameters.
594    pub params: String,
595    /// Optional separate fee payer account.
596    pub fee_payer: Option<Pubkey>,
597}
598
599/// Emitted when a bidirectional cross-chain request is made via
600/// [`chain_signatures::sign_bidirectional`].
601///
602/// # Event Type
603///
604/// CPI event (emitted via `emit_cpi!`)
605///
606/// # Usage
607///
608/// The MPC network listens for this event to:
609/// 1. Sign the transaction and call [`chain_signatures::respond`]
610/// 2. Store the pending tx in backlog for observation
611/// 3. Monitor destination chain for confirmation
612/// 4. Call [`chain_signatures::respond_bidirectional`] with results
613#[event]
614pub struct SignBidirectionalEvent {
615    /// Solana address of the requester.
616    pub sender: Pubkey,
617    /// RLP-encoded unsigned transaction for the destination chain.
618    pub serialized_transaction: Vec<u8>,
619    /// CAIP-2 chain identifier of the destination (e.g., "eip155:1" for Ethereum).
620    pub caip2_id: String,
621    /// MPC key version used for signing.
622    pub key_version: u32,
623    /// Deposit amount paid in lamports.
624    pub deposit: u64,
625    /// Derivation path for the user's signing key.
626    pub path: String,
627    /// Signing algorithm (e.g., "secp256k1").
628    pub algo: String,
629    /// Response destination identifier.
630    pub dest: String,
631    /// Additional JSON parameters.
632    pub params: String,
633    /// Callback program ID (reserved for future use).
634    pub program_id: Pubkey,
635    /// Schema for parsing destination chain call output (JSON-encoded).
636    pub output_deserialization_schema: Vec<u8>,
637    /// Schema for serializing response to source chain (JSON-encoded).
638    pub respond_serialization_schema: Vec<u8>,
639}
640
641/// Emitted when the MPC network returns a signature via [`chain_signatures::respond`].
642///
643/// # Event Type
644///
645/// CPI event (emitted via `emit_cpi!`)
646///
647/// # Security Warning
648///
649/// **Any address can emit this event.** Clients **must** verify signature validity
650/// by recovering the public key and comparing with the expected derived key.
651#[event]
652pub struct SignatureRespondedEvent {
653    /// Request identifier linking this response to the original request.
654    /// Computed as `keccak256(sender || payload || ...)` - see module docs.
655    pub request_id: [u8; 32],
656    /// Address of the responder. Clients must verify the signature was produced by the MPC.
657    pub responder: Pubkey,
658    /// ECDSA signature in affine point format.
659    pub signature: Signature,
660}
661
662/// Emitted when signature generation fails via [`chain_signatures::respond_error`].
663///
664/// # Warning: Debugging Only
665///
666/// This event is **solely for debugging purposes**. Do not use in production
667/// or rely upon for any business logic.
668///
669/// # Event Type
670///
671/// Regular event (emitted via `emit!`)
672///
673/// # Security Warning
674///
675/// **Any address can emit this event.** Error events are not cryptographically
676/// verified and should never be trusted for business logic decisions.
677#[event]
678pub struct SignatureErrorEvent {
679    /// Request identifier of the failed request.
680    pub request_id: [u8; 32],
681    /// Address of the MPC responder. Error events are not cryptographically verified.
682    pub responder: Pubkey,
683    /// Human-readable error description.
684    pub error: String,
685}
686
687/// Emitted when the MPC network returns execution results for a bidirectional
688/// request via [`chain_signatures::respond_bidirectional`].
689///
690/// # Event Type
691///
692/// Regular event (emitted via `emit!`)
693///
694/// # Output Format
695///
696/// **Successful transactions:**
697/// - Contract call: Return value serialized per `respond_serialization_schema`
698/// - Simple transfer: Empty success indicator
699///
700/// **Failed transactions:**
701/// - Magic prefix `0xdeadbeef` followed by failure indicator
702///
703/// # Signature Verification
704///
705/// The signature is computed over `keccak256(request_id || serialized_output)` using
706/// the special derivation path `"solana response key"`. See module-level docs for
707/// verification procedure.
708///
709/// # Security Warning
710///
711/// **Any address can emit this event.** Clients **must** verify the signature
712/// before trusting the output.
713#[event]
714pub struct RespondBidirectionalEvent {
715    /// Original request identifier.
716    pub request_id: [u8; 32],
717    /// Address of the MPC responder. Clients must verify the signature was produced by the MPC.
718    pub responder: Pubkey,
719    /// Serialized execution output per `respond_serialization_schema`.
720    /// Check for `0xdeadbeef` prefix to detect failures.
721    pub serialized_output: Vec<u8>,
722    /// ECDSA signature over `keccak256(request_id || serialized_output)`.
723    pub signature: Signature,
724}
725
726/// Emitted when the admin updates the signature deposit via
727/// [`chain_signatures::update_deposit`].
728#[event]
729pub struct DepositUpdatedEvent {
730    /// Previous deposit amount in lamports.
731    pub old_deposit: u64,
732    /// New deposit amount in lamports.
733    pub new_deposit: u64,
734}
735
736/// Emitted when the admin withdraws funds via [`chain_signatures::withdraw_funds`].
737#[event]
738pub struct FundsWithdrawnEvent {
739    /// Amount withdrawn in lamports.
740    pub amount: u64,
741    /// Recipient address.
742    pub recipient: Pubkey,
743}
744
745#[error_code]
746pub enum ChainSignaturesError {
747    #[msg("Insufficient deposit amount")]
748    InsufficientDeposit,
749    #[msg("Arrays must have the same length")]
750    InvalidInputLength,
751    #[msg("Unauthorized access")]
752    Unauthorized,
753    #[msg("Insufficient funds for withdrawal")]
754    InsufficientFunds,
755    #[msg("Invalid recipient address")]
756    InvalidRecipient,
757    #[msg("Invalid transaction data")]
758    InvalidTransaction,
759    #[msg("Missing instruction sysvar")]
760    MissingInstructionSysvar,
761}