Skip to main content

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    /// Chain-agnostic lifecycle reference: <https://docs.sig.network/architecture/sign-bidirectional>
209    ///
210    /// # Arguments
211    ///
212    /// * `serialized_transaction` - serialized unsigned transaction for destination chain
213    /// * `caip2_id` - CAIP-2 chain identifier (e.g., `"eip155:1"` for Ethereum mainnet)
214    /// * `key_version` - MPC key version to use
215    /// * `path` - Derivation path for signing key
216    /// * `algo` - Reserved for future use (pass empty string `""`)
217    /// * `dest` - Reserved for future use (pass empty string `""`)
218    /// * `params` - Reserved for future use (pass empty string `""`)
219    /// * `program_id` - Callback program ID (reserved for future use)
220    /// * `output_deserialization_schema` - serialization schema for parsing destination chain output
221    /// * `respond_serialization_schema` - serialization schema for serializing response to source chain
222    ///
223    /// # Emits
224    ///
225    /// * [`SignBidirectionalEvent`]
226    ///
227    /// # Errors
228    ///
229    /// * [`ChainSignaturesError::InvalidTransaction`] - Empty transaction data
230    /// * [`ChainSignaturesError::InsufficientDeposit`] - Insufficient deposit
231    pub fn sign_bidirectional(
232        ctx: Context<SignBidirectional>,
233        serialized_transaction: Vec<u8>,
234        caip2_id: String,
235        key_version: u32,
236        path: String,
237        algo: String,
238        dest: String,
239        params: String,
240        program_id: Pubkey,
241        output_deserialization_schema: Vec<u8>,
242        respond_serialization_schema: Vec<u8>,
243    ) -> Result<()> {
244        let program_state = &ctx.accounts.program_state;
245        let requester = &ctx.accounts.requester;
246        let system_program = &ctx.accounts.system_program;
247
248        let payer = match &ctx.accounts.fee_payer {
249            Some(fee_payer) => fee_payer.to_account_info(),
250            None => requester.to_account_info(),
251        };
252
253        require!(
254            payer.lamports() >= program_state.signature_deposit,
255            ChainSignaturesError::InsufficientDeposit
256        );
257
258        require!(
259            !serialized_transaction.is_empty(),
260            ChainSignaturesError::InvalidTransaction
261        );
262
263        let transfer_instruction = anchor_lang::system_program::Transfer {
264            from: payer,
265            to: program_state.to_account_info(),
266        };
267
268        anchor_lang::system_program::transfer(
269            CpiContext::new(system_program.to_account_info(), transfer_instruction),
270            program_state.signature_deposit,
271        )?;
272
273        emit_cpi!(SignBidirectionalEvent {
274            sender: *requester.key,
275            serialized_transaction,
276            caip2_id,
277            key_version,
278            deposit: program_state.signature_deposit,
279            path,
280            algo,
281            dest,
282            params,
283            program_id,
284            output_deserialization_schema,
285            respond_serialization_schema
286        });
287
288        Ok(())
289    }
290
291    /// Respond to signature requests with generated signatures.
292    ///
293    /// Called by MPC responders after signature generation. Supports batched
294    /// requests where each signature is linked to its request via `request_id`.
295    ///
296    /// # Security Note
297    ///
298    /// **Any address can call this function.** Clients must verify signature
299    /// validity off-chain before trusting the response.
300    ///
301    /// # Arguments
302    ///
303    /// * `request_ids` - Array of 32-byte request identifiers
304    /// * `signatures` - Corresponding ECDSA signatures
305    ///
306    /// # Emits
307    ///
308    /// * [`SignatureRespondedEvent`] for each signature
309    pub fn respond(
310        ctx: Context<Respond>,
311        request_ids: Vec<[u8; 32]>,
312        signatures: Vec<Signature>,
313    ) -> Result<()> {
314        require!(
315            request_ids.len() == signatures.len(),
316            ChainSignaturesError::InvalidInputLength
317        );
318
319        for i in 0..request_ids.len() {
320            emit_cpi!(SignatureRespondedEvent {
321                request_id: request_ids[i],
322                responder: *ctx.accounts.responder.key,
323                signature: signatures[i].clone(),
324            });
325        }
326
327        Ok(())
328    }
329
330    /// Report signature generation errors from the MPC network.
331    ///
332    /// # Warning: Debugging Only
333    ///
334    /// This function is **solely for debugging purposes** and should not be used
335    /// in production or relied upon for any business logic. Error events are
336    /// informational only and are not cryptographically verified.
337    ///
338    /// # Security Note
339    ///
340    /// **Any address can call this function.** Do not rely on error events
341    /// for business logic decisions.
342    ///
343    /// # Arguments
344    ///
345    /// * `errors` - Array of error responses with request IDs and messages
346    ///
347    /// # Emits
348    ///
349    /// * [`SignatureErrorEvent`] for each error
350    pub fn respond_error(ctx: Context<RespondError>, errors: Vec<ErrorResponse>) -> Result<()> {
351        for error in errors {
352            emit!(SignatureErrorEvent {
353                request_id: error.request_id,
354                responder: *ctx.accounts.responder.key,
355                error: error.error_message,
356            });
357        }
358
359        Ok(())
360    }
361
362    /// Get the current signature deposit amount. View function.
363    ///
364    /// # Returns
365    ///
366    /// Current signature deposit in lamports.
367    pub fn get_signature_deposit(ctx: Context<GetSignatureDeposit>) -> Result<u64> {
368        let program_state = &ctx.accounts.program_state;
369        Ok(program_state.signature_deposit)
370    }
371
372    /// Finalize a bidirectional flow with execution results from the destination chain.
373    ///
374    /// Called by MPC responders after observing transaction confirmation on the
375    /// destination chain. The signature proves the authenticity of the output.
376    ///
377    /// # Arguments
378    ///
379    /// * `request_id` - Original 32-byte request identifier
380    /// * `serialized_output` - Serialized execution output per `respond_serialization_schema`
381    /// * `signature` - ECDSA signature over `keccak256(request_id || serialized_output)`
382    ///
383    /// # Output Format
384    ///
385    /// For **successful transactions**:
386    /// - Contract call: Serialized return value per schema
387    /// - Simple transfer: Empty success indicator
388    ///
389    /// For **failed transactions**:
390    /// - Magic prefix `0xdeadbeef` followed by failure indicator
391    ///
392    /// # Emits
393    ///
394    /// * [`RespondBidirectionalEvent`]
395    pub fn respond_bidirectional(
396        ctx: Context<ReadRespond>,
397        request_id: [u8; 32],
398        serialized_output: Vec<u8>,
399        signature: Signature,
400    ) -> Result<()> {
401        emit!(RespondBidirectionalEvent {
402            request_id,
403            responder: *ctx.accounts.responder.key,
404            serialized_output,
405            signature,
406        });
407
408        Ok(())
409    }
410}
411
412/// Program configuration state stored in a PDA.
413///
414/// Seeds: `[b"program-state"]`
415#[account]
416pub struct ProgramState {
417    /// Admin account with permission to update settings and withdraw funds.
418    pub admin: Pubkey,
419    /// Required deposit in lamports for signature requests.
420    pub signature_deposit: u64,
421    /// CAIP-2 chain identifier (e.g., "solana:mainnet").
422    pub chain_id: String,
423}
424
425/// A point on the secp256k1 elliptic curve in affine coordinates.
426///
427/// Used to represent the R point in ECDSA signatures.
428///
429/// # Size
430///
431/// 64 bytes total (32 bytes x + 32 bytes y)
432#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
433pub struct AffinePoint {
434    /// X coordinate (big-endian, 32 bytes)
435    pub x: [u8; 32],
436    /// Y coordinate (big-endian, 32 bytes)
437    pub y: [u8; 32],
438}
439
440/// ECDSA signature in affine point representation.
441///
442/// Compatible with secp256k1 curve operations. Can be converted to
443/// standard RSV format for use with Ethereum and other chains.
444///
445/// # Size
446///
447/// 97 bytes total (64 bytes big_r + 32 bytes s + 1 byte recovery_id)
448///
449/// # Conversion to RSV
450///
451/// ```typescript,ignore
452/// const r = Buffer.from(signature.bigR.x).toString('hex');
453/// const s = Buffer.from(signature.s).toString('hex');
454/// const v = signature.recoveryId + 27;
455/// ```
456#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
457pub struct Signature {
458    /// R point of the ECDSA signature (affine coordinates)
459    pub big_r: AffinePoint,
460    /// s scalar of the signature (32 bytes, big-endian)
461    pub s: [u8; 32],
462    /// Recovery ID (0 or 1) for public key recovery
463    pub recovery_id: u8,
464}
465
466/// Error information for failed signature requests.
467#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
468pub struct ErrorResponse {
469    /// Identifier of the failed request
470    pub request_id: [u8; 32],
471    /// Human-readable error description
472    pub error_message: String,
473}
474
475#[derive(Accounts)]
476pub struct Initialize<'info> {
477    #[account(
478        init,
479        payer = admin,
480        space = 8 + 32 + 8 + 4 + 128, // discriminator + admin + deposit + string length + max chain_id length
481        seeds = [b"program-state"],
482        bump
483    )]
484    pub program_state: Account<'info, ProgramState>,
485    #[account(mut)]
486    pub admin: Signer<'info>,
487    pub system_program: Program<'info, System>,
488}
489
490#[derive(Accounts)]
491pub struct AdminOnly<'info> {
492    #[account(
493        mut,
494        seeds = [b"program-state"],
495        bump,
496        has_one = admin @ ChainSignaturesError::Unauthorized
497    )]
498    pub program_state: Account<'info, ProgramState>,
499    #[account(mut)]
500    pub admin: Signer<'info>,
501    pub system_program: Program<'info, System>,
502}
503
504#[derive(Accounts)]
505pub struct WithdrawFunds<'info> {
506    #[account(
507        mut,
508        seeds = [b"program-state"],
509        bump,
510        has_one = admin @ ChainSignaturesError::Unauthorized
511    )]
512    pub program_state: Account<'info, ProgramState>,
513
514    #[account(mut)]
515    pub admin: Signer<'info>,
516
517    /// CHECK: The safety check is performed in the withdraw_funds
518    /// function by checking it is not the zero address.
519    #[account(mut)]
520    pub recipient: AccountInfo<'info>,
521
522    pub system_program: Program<'info, System>,
523}
524
525#[event_cpi]
526#[derive(Accounts)]
527pub struct Sign<'info> {
528    #[account(mut, seeds = [b"program-state"], bump)]
529    pub program_state: Account<'info, ProgramState>,
530    #[account(mut)]
531    pub requester: Signer<'info>,
532    #[account(mut)]
533    pub fee_payer: Option<Signer<'info>>,
534    pub system_program: Program<'info, System>,
535}
536
537#[event_cpi]
538#[derive(Accounts)]
539pub struct SignBidirectional<'info> {
540    #[account(mut, seeds = [b"program-state"], bump)]
541    pub program_state: Account<'info, ProgramState>,
542    #[account(mut)]
543    pub requester: Signer<'info>,
544    #[account(mut)]
545    pub fee_payer: Option<Signer<'info>>,
546    pub system_program: Program<'info, System>,
547    pub instructions: Option<AccountInfo<'info>>,
548}
549
550#[event_cpi]
551#[derive(Accounts)]
552pub struct Respond<'info> {
553    pub responder: Signer<'info>,
554}
555
556#[derive(Accounts)]
557pub struct RespondError<'info> {
558    pub responder: Signer<'info>,
559}
560
561#[derive(Accounts)]
562pub struct GetSignatureDeposit<'info> {
563    #[account(seeds = [b"program-state"], bump)]
564    pub program_state: Account<'info, ProgramState>,
565}
566
567#[derive(Accounts)]
568pub struct ReadRespond<'info> {
569    pub responder: Signer<'info>,
570}
571
572/// Emitted when a signature is requested via the [`chain_signatures::sign`] instruction.
573///
574/// # Event Type
575///
576/// CPI event (emitted via `emit_cpi!`)
577#[event]
578pub struct SignatureRequestedEvent {
579    /// Solana address of the requester.
580    pub sender: Pubkey,
581    /// 32-byte payload to be signed (typically a transaction hash).
582    pub payload: [u8; 32],
583    /// MPC key version used for signing.
584    pub key_version: u32,
585    /// Deposit amount paid in lamports.
586    pub deposit: u64,
587    /// CAIP-2 chain identifier of this program (e.g., "solana:mainnet").
588    pub chain_id: String,
589    /// Derivation path for the user's signing key.
590    pub path: String,
591    /// Signing algorithm (e.g., "secp256k1").
592    pub algo: String,
593    /// Response destination chain.
594    pub dest: String,
595    /// Additional JSON parameters.
596    pub params: String,
597    /// Optional separate fee payer account.
598    pub fee_payer: Option<Pubkey>,
599}
600
601/// Emitted when a bidirectional cross-chain request is made via
602/// [`chain_signatures::sign_bidirectional`].
603///
604/// # Event Type
605///
606/// CPI event (emitted via `emit_cpi!`)
607///
608/// # Usage
609///
610/// The MPC network listens for this event to:
611/// 1. Sign the transaction and call [`chain_signatures::respond`]
612/// 2. Store the pending tx in backlog for observation
613/// 3. Monitor destination chain for confirmation
614/// 4. Call [`chain_signatures::respond_bidirectional`] with results
615#[event]
616pub struct SignBidirectionalEvent {
617    /// Solana address of the requester.
618    pub sender: Pubkey,
619    /// RLP-encoded unsigned transaction for the destination chain.
620    pub serialized_transaction: Vec<u8>,
621    /// CAIP-2 chain identifier of the destination (e.g., "eip155:1" for Ethereum).
622    pub caip2_id: String,
623    /// MPC key version used for signing.
624    pub key_version: u32,
625    /// Deposit amount paid in lamports.
626    pub deposit: u64,
627    /// Derivation path for the user's signing key.
628    pub path: String,
629    /// Signing algorithm (e.g., "secp256k1").
630    pub algo: String,
631    /// Response destination identifier.
632    pub dest: String,
633    /// Additional JSON parameters.
634    pub params: String,
635    /// Callback program ID (reserved for future use).
636    pub program_id: Pubkey,
637    /// Schema for parsing destination chain call output (JSON-encoded).
638    pub output_deserialization_schema: Vec<u8>,
639    /// Schema for serializing response to source chain (JSON-encoded).
640    pub respond_serialization_schema: Vec<u8>,
641}
642
643/// Emitted when the MPC network returns a signature via [`chain_signatures::respond`].
644///
645/// # Event Type
646///
647/// CPI event (emitted via `emit_cpi!`)
648///
649/// # Security Warning
650///
651/// **Any address can emit this event.** Clients **must** verify signature validity
652/// by recovering the public key and comparing with the expected derived key.
653#[event]
654pub struct SignatureRespondedEvent {
655    /// Request identifier linking this response to the original request.
656    /// Computed as `keccak256(sender || payload || ...)` - see module docs.
657    pub request_id: [u8; 32],
658    /// Address of the responder. Clients must verify the signature was produced by the MPC.
659    pub responder: Pubkey,
660    /// ECDSA signature in affine point format.
661    pub signature: Signature,
662}
663
664/// Emitted when signature generation fails via [`chain_signatures::respond_error`].
665///
666/// # Warning: Debugging Only
667///
668/// This event is **solely for debugging purposes**. Do not use in production
669/// or rely upon for any business logic.
670///
671/// # Event Type
672///
673/// Regular event (emitted via `emit!`)
674///
675/// # Security Warning
676///
677/// **Any address can emit this event.** Error events are not cryptographically
678/// verified and should never be trusted for business logic decisions.
679#[event]
680pub struct SignatureErrorEvent {
681    /// Request identifier of the failed request.
682    pub request_id: [u8; 32],
683    /// Address of the MPC responder. Error events are not cryptographically verified.
684    pub responder: Pubkey,
685    /// Human-readable error description.
686    pub error: String,
687}
688
689/// Emitted when the MPC network returns execution results for a bidirectional
690/// request via [`chain_signatures::respond_bidirectional`].
691///
692/// # Event Type
693///
694/// Regular event (emitted via `emit!`)
695///
696/// # Output Format
697///
698/// **Successful transactions:**
699/// - Contract call: Return value serialized per `respond_serialization_schema`
700/// - Simple transfer: Empty success indicator
701///
702/// **Failed transactions:**
703/// - Magic prefix `0xdeadbeef` followed by failure indicator
704///
705/// # Signature Verification
706///
707/// The signature is computed over `keccak256(request_id || serialized_output)` using
708/// the special derivation path `"solana response key"`. See module-level docs for
709/// verification procedure.
710///
711/// # Security Warning
712///
713/// **Any address can emit this event.** Clients **must** verify the signature
714/// before trusting the output.
715#[event]
716pub struct RespondBidirectionalEvent {
717    /// Original request identifier.
718    pub request_id: [u8; 32],
719    /// Address of the MPC responder. Clients must verify the signature was produced by the MPC.
720    pub responder: Pubkey,
721    /// Serialized execution output per `respond_serialization_schema`.
722    /// Check for `0xdeadbeef` prefix to detect failures.
723    pub serialized_output: Vec<u8>,
724    /// ECDSA signature over `keccak256(request_id || serialized_output)`.
725    pub signature: Signature,
726}
727
728/// Emitted when the admin updates the signature deposit via
729/// [`chain_signatures::update_deposit`].
730#[event]
731pub struct DepositUpdatedEvent {
732    /// Previous deposit amount in lamports.
733    pub old_deposit: u64,
734    /// New deposit amount in lamports.
735    pub new_deposit: u64,
736}
737
738/// Emitted when the admin withdraws funds via [`chain_signatures::withdraw_funds`].
739#[event]
740pub struct FundsWithdrawnEvent {
741    /// Amount withdrawn in lamports.
742    pub amount: u64,
743    /// Recipient address.
744    pub recipient: Pubkey,
745}
746
747#[error_code]
748pub enum ChainSignaturesError {
749    #[msg("Insufficient deposit amount")]
750    InsufficientDeposit,
751    #[msg("Arrays must have the same length")]
752    InvalidInputLength,
753    #[msg("Unauthorized access")]
754    Unauthorized,
755    #[msg("Insufficient funds for withdrawal")]
756    InsufficientFunds,
757    #[msg("Invalid recipient address")]
758    InvalidRecipient,
759    #[msg("Invalid transaction data")]
760    InvalidTransaction,
761    #[msg("Missing instruction sysvar")]
762    MissingInstructionSysvar,
763}