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}