1#![allow(unexpected_cfgs)]
2use anchor_lang::prelude::*;
3
4declare_id!("4uvZW8K4g4jBg7dzPNbb9XDxJLFBK7V6iC76uofmYvEU");
5
6#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, PartialEq)]
7#[repr(u8)]
8pub enum SerializationFormat {
9 Borsh = 0,
10 AbiJson = 1,
11}
12
13#[program]
14pub mod chain_signatures_project {
15 use super::*;
16
17 pub fn initialize(ctx: Context<Initialize>, signature_deposit: u64) -> Result<()> {
18 let program_state = &mut ctx.accounts.program_state;
19 program_state.admin = ctx.accounts.admin.key();
20 program_state.signature_deposit = signature_deposit;
21
22 Ok(())
23 }
24
25 pub fn update_deposit(ctx: Context<AdminOnly>, new_deposit: u64) -> Result<()> {
26 let program_state = &mut ctx.accounts.program_state;
27 program_state.signature_deposit = new_deposit;
28
29 emit!(DepositUpdatedEvent {
30 old_deposit: program_state.signature_deposit,
31 new_deposit,
32 });
33
34 Ok(())
35 }
36
37 pub fn withdraw_funds(ctx: Context<WithdrawFunds>, amount: u64) -> Result<()> {
38 let program_state = &ctx.accounts.program_state;
39 let recipient = &ctx.accounts.recipient;
40
41 let program_state_info = program_state.to_account_info();
42 require!(
43 program_state_info.lamports() >= amount,
44 ChainSignaturesError::InsufficientFunds
45 );
46
47 require!(
48 recipient.key() != Pubkey::default(),
49 ChainSignaturesError::InvalidRecipient
50 );
51
52 **program_state_info.try_borrow_mut_lamports()? -= amount;
54 **recipient.try_borrow_mut_lamports()? += amount;
55
56 emit!(FundsWithdrawnEvent {
57 amount,
58 recipient: recipient.key(),
59 });
60
61 Ok(())
62 }
63
64 pub fn sign(
65 ctx: Context<Sign>,
66 payload: [u8; 32],
67 key_version: u32,
68 path: String,
69 algo: String,
70 dest: String,
71 params: String,
72 ) -> Result<()> {
73 let program_state = &ctx.accounts.program_state;
74 let requester = &ctx.accounts.requester;
75 let system_program = &ctx.accounts.system_program;
76
77 let payer = match &ctx.accounts.fee_payer {
78 Some(fee_payer) => fee_payer.to_account_info(),
79 None => requester.to_account_info(),
80 };
81
82 require!(
83 payer.lamports() >= program_state.signature_deposit,
84 ChainSignaturesError::InsufficientDeposit
85 );
86
87 let transfer_instruction = anchor_lang::system_program::Transfer {
88 from: payer,
89 to: program_state.to_account_info(),
90 };
91
92 anchor_lang::system_program::transfer(
93 CpiContext::new(system_program.to_account_info(), transfer_instruction),
94 program_state.signature_deposit,
95 )?;
96
97 emit_cpi!(SignatureRequestedEvent {
99 sender: *requester.key,
100 payload,
101 key_version,
102 deposit: program_state.signature_deposit,
103 chain_id: 0,
104 path,
105 algo,
106 dest,
107 params,
108 fee_payer: match &ctx.accounts.fee_payer {
109 Some(payer) => Some(*payer.key),
110 None => None,
111 },
112 });
113
114 Ok(())
115 }
116
117 pub fn sign_respond(
118 ctx: Context<SignRespond>,
119 serialized_transaction: Vec<u8>,
120 slip44_chain_id: u32,
121 key_version: u32,
122 path: String,
123 algo: String,
124 dest: String,
125 params: String,
126 explorer_deserialization_format: SerializationFormat,
127 explorer_deserialization_schema: Vec<u8>,
128 callback_serialization_format: SerializationFormat,
129 callback_serialization_schema: Vec<u8>,
130 ) -> Result<()> {
131 let program_state = &ctx.accounts.program_state;
132 let requester = &ctx.accounts.requester;
133 let system_program = &ctx.accounts.system_program;
134
135 let payer = match &ctx.accounts.fee_payer {
136 Some(fee_payer) => fee_payer.to_account_info(),
137 None => requester.to_account_info(),
138 };
139
140 require!(
141 payer.lamports() >= program_state.signature_deposit,
142 ChainSignaturesError::InsufficientDeposit
143 );
144
145 require!(
146 !serialized_transaction.is_empty(),
147 ChainSignaturesError::InvalidTransaction
148 );
149
150 let transfer_instruction = anchor_lang::system_program::Transfer {
151 from: payer,
152 to: program_state.to_account_info(),
153 };
154
155 anchor_lang::system_program::transfer(
156 CpiContext::new(system_program.to_account_info(), transfer_instruction),
157 program_state.signature_deposit,
158 )?;
159
160 emit_cpi!(SignRespondRequestedEvent {
161 sender: *requester.key,
162 transaction_data: serialized_transaction,
163 slip44_chain_id,
164 key_version,
165 deposit: program_state.signature_deposit,
166 path,
167 algo,
168 dest,
169 params,
170 explorer_deserialization_format: explorer_deserialization_format as u8,
171 explorer_deserialization_schema,
172 callback_serialization_format: callback_serialization_format as u8,
173 callback_serialization_schema
174 });
175
176 Ok(())
177 }
178
179 pub fn respond(
180 ctx: Context<Respond>,
181 request_ids: Vec<[u8; 32]>,
182 signatures: Vec<Signature>,
183 ) -> Result<()> {
184 require!(
185 request_ids.len() == signatures.len(),
186 ChainSignaturesError::InvalidInputLength
187 );
188
189 for i in 0..request_ids.len() {
190 emit!(SignatureRespondedEvent {
191 request_id: request_ids[i],
192 responder: *ctx.accounts.responder.key,
193 signature: signatures[i].clone(),
194 });
195 }
196
197 Ok(())
198 }
199
200 pub fn read_respond(
201 ctx: Context<ReadRespond>,
202 request_id: [u8; 32],
203 serialized_output: Vec<u8>,
204 signature: Signature,
205 ) -> Result<()> {
206 emit!(ReadRespondedEvent {
214 request_id,
215 responder: *ctx.accounts.responder.key,
216 serialized_output,
217 signature,
218 });
219
220 Ok(())
221 }
222}
223
224#[account]
225pub struct ProgramState {
226 pub admin: Pubkey,
227 pub signature_deposit: u64,
228}
229
230#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
231pub struct AffinePoint {
232 pub x: [u8; 32],
233 pub y: [u8; 32],
234}
235
236#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
237pub struct Signature {
238 pub big_r: AffinePoint,
239 pub s: [u8; 32],
240 pub recovery_id: u8,
241}
242
243#[derive(Accounts)]
244pub struct Initialize<'info> {
245 #[account(
246 init,
247 payer = admin,
248 space = 8 + 32 + 8,
249 seeds = [b"program-state"],
250 bump
251 )]
252 pub program_state: Account<'info, ProgramState>,
253 #[account(mut)]
254 pub admin: Signer<'info>,
255 pub system_program: Program<'info, System>,
256}
257
258#[derive(Accounts)]
259pub struct AdminOnly<'info> {
260 #[account(
261 mut,
262 seeds = [b"program-state"],
263 bump,
264 has_one = admin @ ChainSignaturesError::Unauthorized
265 )]
266 pub program_state: Account<'info, ProgramState>,
267 #[account(mut)]
268 pub admin: Signer<'info>,
269 pub system_program: Program<'info, System>,
270}
271
272#[derive(Accounts)]
273pub struct WithdrawFunds<'info> {
274 #[account(
275 mut,
276 seeds = [b"program-state"],
277 bump,
278 has_one = admin @ ChainSignaturesError::Unauthorized
279 )]
280 pub program_state: Account<'info, ProgramState>,
281
282 #[account(mut)]
283 pub admin: Signer<'info>,
284
285 #[account(mut)]
288 pub recipient: AccountInfo<'info>,
289
290 pub system_program: Program<'info, System>,
291}
292
293#[event_cpi]
294#[derive(Accounts)]
295pub struct Sign<'info> {
296 #[account(mut, seeds = [b"program-state"], bump)]
297 pub program_state: Account<'info, ProgramState>,
298 #[account(mut)]
299 pub requester: Signer<'info>,
300 #[account(mut)]
301 pub fee_payer: Option<Signer<'info>>,
302 pub system_program: Program<'info, System>,
303}
304
305#[event_cpi]
306#[derive(Accounts)]
307pub struct SignRespond<'info> {
308 #[account(mut, seeds = [b"program-state"], bump)]
309 pub program_state: Account<'info, ProgramState>,
310 #[account(mut)]
311 pub requester: Signer<'info>,
312 #[account(mut)]
313 pub fee_payer: Option<Signer<'info>>,
314 pub system_program: Program<'info, System>,
315 pub instructions: Option<AccountInfo<'info>>,
316}
317
318#[derive(Accounts)]
319pub struct Respond<'info> {
320 pub responder: Signer<'info>,
321}
322
323#[derive(Accounts)]
324pub struct ReadRespond<'info> {
325 pub responder: Signer<'info>,
326}
327
328#[event]
329pub struct SignatureRequestedEvent {
330 pub sender: Pubkey,
331 pub payload: [u8; 32],
332 pub key_version: u32,
333 pub deposit: u64,
334 pub chain_id: u64,
335 pub path: String,
336 pub algo: String,
337 pub dest: String,
338 pub params: String,
339 pub fee_payer: Option<Pubkey>,
340}
341
342#[event]
343pub struct SignRespondRequestedEvent {
344 pub sender: Pubkey,
345 pub transaction_data: Vec<u8>,
346 pub slip44_chain_id: u32,
347 pub key_version: u32,
348 pub deposit: u64,
349 pub path: String,
350 pub algo: String,
351 pub dest: String,
352 pub params: String,
353 pub explorer_deserialization_format: u8,
354 pub explorer_deserialization_schema: Vec<u8>,
355 pub callback_serialization_format: u8,
356 pub callback_serialization_schema: Vec<u8>,
357}
358
359#[event]
360pub struct SignatureErrorEvent {
361 pub request_id: [u8; 32],
362 pub responder: Pubkey,
363 pub error: String,
364}
365
366#[event]
367pub struct SignatureRespondedEvent {
368 pub request_id: [u8; 32],
369 pub responder: Pubkey,
370 pub signature: Signature,
371}
372
373#[event]
374pub struct ReadRespondedEvent {
375 pub request_id: [u8; 32],
376 pub responder: Pubkey,
377 pub serialized_output: Vec<u8>,
378 pub signature: Signature,
379}
380
381#[event]
382pub struct DepositUpdatedEvent {
383 pub old_deposit: u64,
384 pub new_deposit: u64,
385}
386
387#[event]
388pub struct FundsWithdrawnEvent {
389 pub amount: u64,
390 pub recipient: Pubkey,
391}
392
393#[error_code]
394pub enum ChainSignaturesError {
395 #[msg("Insufficient deposit amount")]
396 InsufficientDeposit,
397 #[msg("Arrays must have the same length")]
398 InvalidInputLength,
399 #[msg("Unauthorized access")]
400 Unauthorized,
401 #[msg("Insufficient funds for withdrawal")]
402 InsufficientFunds,
403 #[msg("Invalid recipient address")]
404 InvalidRecipient,
405 #[msg("Invalid transaction data")]
406 InvalidTransaction,
407 #[msg("Missing instruction sysvar")]
408 MissingInstructionSysvar,
409}