1use borsh::BorshSerialize;
9use borsh::BorshDeserialize;
10
11pub const CLAIM_EPOCH_REWARD_DISCRIMINATOR: [u8; 8] = [226, 196, 170, 104, 125, 21, 190, 232];
12
13#[derive(Debug)]
15pub struct ClaimEpochReward {
16
17
18 pub authority: solana_address::Address,
19
20
21 pub epoch_vault: solana_address::Address,
22
23
24 pub epoch_vault_iteration: solana_address::Address,
25
26
27 pub usd_mint: solana_address::Address,
28
29
30 pub btc_mint: solana_address::Address,
31
32
33 pub epoch_vault_usd_ata: solana_address::Address,
34
35
36 pub epoch_vault_btc_ata: solana_address::Address,
37
38
39 pub authority_usd_ata: solana_address::Address,
40
41
42 pub authority_btc_ata: solana_address::Address,
43
44
45 pub token_program: solana_address::Address,
46
47
48 pub associated_token_program: solana_address::Address,
49
50
51 pub system_program: solana_address::Address,
52 }
53
54impl ClaimEpochReward {
55 pub fn instruction(&self) -> solana_instruction::Instruction {
56 self.instruction_with_remaining_accounts(&[])
57 }
58 #[allow(clippy::arithmetic_side_effects)]
59 #[allow(clippy::vec_init_then_push)]
60 pub fn instruction_with_remaining_accounts(&self, remaining_accounts: &[solana_instruction::AccountMeta]) -> solana_instruction::Instruction {
61 let mut accounts = Vec::with_capacity(12+ remaining_accounts.len());
62 accounts.push(solana_instruction::AccountMeta::new(
63 self.authority,
64 true
65 ));
66 accounts.push(solana_instruction::AccountMeta::new(
67 self.epoch_vault,
68 false
69 ));
70 accounts.push(solana_instruction::AccountMeta::new(
71 self.epoch_vault_iteration,
72 false
73 ));
74 accounts.push(solana_instruction::AccountMeta::new_readonly(
75 self.usd_mint,
76 false
77 ));
78 accounts.push(solana_instruction::AccountMeta::new_readonly(
79 self.btc_mint,
80 false
81 ));
82 accounts.push(solana_instruction::AccountMeta::new(
83 self.epoch_vault_usd_ata,
84 false
85 ));
86 accounts.push(solana_instruction::AccountMeta::new(
87 self.epoch_vault_btc_ata,
88 false
89 ));
90 accounts.push(solana_instruction::AccountMeta::new(
91 self.authority_usd_ata,
92 false
93 ));
94 accounts.push(solana_instruction::AccountMeta::new(
95 self.authority_btc_ata,
96 false
97 ));
98 accounts.push(solana_instruction::AccountMeta::new_readonly(
99 self.token_program,
100 false
101 ));
102 accounts.push(solana_instruction::AccountMeta::new_readonly(
103 self.associated_token_program,
104 false
105 ));
106 accounts.push(solana_instruction::AccountMeta::new_readonly(
107 self.system_program,
108 false
109 ));
110 accounts.extend_from_slice(remaining_accounts);
111 let data = ClaimEpochRewardInstructionData::new().try_to_vec().unwrap();
112
113 solana_instruction::Instruction {
114 program_id: crate::SATRUSH_ID,
115 accounts,
116 data,
117 }
118 }
119}
120
121#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)]
122 pub struct ClaimEpochRewardInstructionData {
123 discriminator: [u8; 8],
124 }
125
126impl ClaimEpochRewardInstructionData {
127 pub fn new() -> Self {
128 Self {
129 discriminator: [226, 196, 170, 104, 125, 21, 190, 232],
130 }
131 }
132
133 pub(crate) fn try_to_vec(&self) -> Result<Vec<u8>, std::io::Error> {
134 borsh::to_vec(self)
135 }
136 }
137
138impl Default for ClaimEpochRewardInstructionData {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144
145
146#[derive(Clone, Debug, Default)]
163pub struct ClaimEpochRewardBuilder {
164 authority: Option<solana_address::Address>,
165 epoch_vault: Option<solana_address::Address>,
166 epoch_vault_iteration: Option<solana_address::Address>,
167 usd_mint: Option<solana_address::Address>,
168 btc_mint: Option<solana_address::Address>,
169 epoch_vault_usd_ata: Option<solana_address::Address>,
170 epoch_vault_btc_ata: Option<solana_address::Address>,
171 authority_usd_ata: Option<solana_address::Address>,
172 authority_btc_ata: Option<solana_address::Address>,
173 token_program: Option<solana_address::Address>,
174 associated_token_program: Option<solana_address::Address>,
175 system_program: Option<solana_address::Address>,
176 __remaining_accounts: Vec<solana_instruction::AccountMeta>,
177}
178
179impl ClaimEpochRewardBuilder {
180 pub fn new() -> Self {
181 Self::default()
182 }
183 #[inline(always)]
184 pub fn authority(&mut self, authority: solana_address::Address) -> &mut Self {
185 self.authority = Some(authority);
186 self
187 }
188 #[inline(always)]
189 pub fn epoch_vault(&mut self, epoch_vault: solana_address::Address) -> &mut Self {
190 self.epoch_vault = Some(epoch_vault);
191 self
192 }
193 #[inline(always)]
194 pub fn epoch_vault_iteration(&mut self, epoch_vault_iteration: solana_address::Address) -> &mut Self {
195 self.epoch_vault_iteration = Some(epoch_vault_iteration);
196 self
197 }
198 #[inline(always)]
199 pub fn usd_mint(&mut self, usd_mint: solana_address::Address) -> &mut Self {
200 self.usd_mint = Some(usd_mint);
201 self
202 }
203 #[inline(always)]
204 pub fn btc_mint(&mut self, btc_mint: solana_address::Address) -> &mut Self {
205 self.btc_mint = Some(btc_mint);
206 self
207 }
208 #[inline(always)]
209 pub fn epoch_vault_usd_ata(&mut self, epoch_vault_usd_ata: solana_address::Address) -> &mut Self {
210 self.epoch_vault_usd_ata = Some(epoch_vault_usd_ata);
211 self
212 }
213 #[inline(always)]
214 pub fn epoch_vault_btc_ata(&mut self, epoch_vault_btc_ata: solana_address::Address) -> &mut Self {
215 self.epoch_vault_btc_ata = Some(epoch_vault_btc_ata);
216 self
217 }
218 #[inline(always)]
219 pub fn authority_usd_ata(&mut self, authority_usd_ata: solana_address::Address) -> &mut Self {
220 self.authority_usd_ata = Some(authority_usd_ata);
221 self
222 }
223 #[inline(always)]
224 pub fn authority_btc_ata(&mut self, authority_btc_ata: solana_address::Address) -> &mut Self {
225 self.authority_btc_ata = Some(authority_btc_ata);
226 self
227 }
228 #[inline(always)]
230 pub fn token_program(&mut self, token_program: solana_address::Address) -> &mut Self {
231 self.token_program = Some(token_program);
232 self
233 }
234 #[inline(always)]
236 pub fn associated_token_program(&mut self, associated_token_program: solana_address::Address) -> &mut Self {
237 self.associated_token_program = Some(associated_token_program);
238 self
239 }
240 #[inline(always)]
242 pub fn system_program(&mut self, system_program: solana_address::Address) -> &mut Self {
243 self.system_program = Some(system_program);
244 self
245 }
246 #[inline(always)]
248 pub fn add_remaining_account(&mut self, account: solana_instruction::AccountMeta) -> &mut Self {
249 self.__remaining_accounts.push(account);
250 self
251 }
252 #[inline(always)]
254 pub fn add_remaining_accounts(&mut self, accounts: &[solana_instruction::AccountMeta]) -> &mut Self {
255 self.__remaining_accounts.extend_from_slice(accounts);
256 self
257 }
258 #[allow(clippy::clone_on_copy)]
259 pub fn instruction(&self) -> solana_instruction::Instruction {
260 let accounts = ClaimEpochReward {
261 authority: self.authority.expect("authority is not set"),
262 epoch_vault: self.epoch_vault.expect("epoch_vault is not set"),
263 epoch_vault_iteration: self.epoch_vault_iteration.expect("epoch_vault_iteration is not set"),
264 usd_mint: self.usd_mint.expect("usd_mint is not set"),
265 btc_mint: self.btc_mint.expect("btc_mint is not set"),
266 epoch_vault_usd_ata: self.epoch_vault_usd_ata.expect("epoch_vault_usd_ata is not set"),
267 epoch_vault_btc_ata: self.epoch_vault_btc_ata.expect("epoch_vault_btc_ata is not set"),
268 authority_usd_ata: self.authority_usd_ata.expect("authority_usd_ata is not set"),
269 authority_btc_ata: self.authority_btc_ata.expect("authority_btc_ata is not set"),
270 token_program: self.token_program.unwrap_or(solana_address::address!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")),
271 associated_token_program: self.associated_token_program.unwrap_or(solana_address::address!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")),
272 system_program: self.system_program.unwrap_or(solana_address::address!("11111111111111111111111111111111")),
273 };
274
275 accounts.instruction_with_remaining_accounts(&self.__remaining_accounts)
276 }
277}
278
279 pub struct ClaimEpochRewardCpiAccounts<'a, 'b> {
281
282
283 pub authority: &'b solana_account_info::AccountInfo<'a>,
284
285
286 pub epoch_vault: &'b solana_account_info::AccountInfo<'a>,
287
288
289 pub epoch_vault_iteration: &'b solana_account_info::AccountInfo<'a>,
290
291
292 pub usd_mint: &'b solana_account_info::AccountInfo<'a>,
293
294
295 pub btc_mint: &'b solana_account_info::AccountInfo<'a>,
296
297
298 pub epoch_vault_usd_ata: &'b solana_account_info::AccountInfo<'a>,
299
300
301 pub epoch_vault_btc_ata: &'b solana_account_info::AccountInfo<'a>,
302
303
304 pub authority_usd_ata: &'b solana_account_info::AccountInfo<'a>,
305
306
307 pub authority_btc_ata: &'b solana_account_info::AccountInfo<'a>,
308
309
310 pub token_program: &'b solana_account_info::AccountInfo<'a>,
311
312
313 pub associated_token_program: &'b solana_account_info::AccountInfo<'a>,
314
315
316 pub system_program: &'b solana_account_info::AccountInfo<'a>,
317 }
318
319pub struct ClaimEpochRewardCpi<'a, 'b> {
321 pub __program: &'b solana_account_info::AccountInfo<'a>,
323
324
325 pub authority: &'b solana_account_info::AccountInfo<'a>,
326
327
328 pub epoch_vault: &'b solana_account_info::AccountInfo<'a>,
329
330
331 pub epoch_vault_iteration: &'b solana_account_info::AccountInfo<'a>,
332
333
334 pub usd_mint: &'b solana_account_info::AccountInfo<'a>,
335
336
337 pub btc_mint: &'b solana_account_info::AccountInfo<'a>,
338
339
340 pub epoch_vault_usd_ata: &'b solana_account_info::AccountInfo<'a>,
341
342
343 pub epoch_vault_btc_ata: &'b solana_account_info::AccountInfo<'a>,
344
345
346 pub authority_usd_ata: &'b solana_account_info::AccountInfo<'a>,
347
348
349 pub authority_btc_ata: &'b solana_account_info::AccountInfo<'a>,
350
351
352 pub token_program: &'b solana_account_info::AccountInfo<'a>,
353
354
355 pub associated_token_program: &'b solana_account_info::AccountInfo<'a>,
356
357
358 pub system_program: &'b solana_account_info::AccountInfo<'a>,
359 }
360
361impl<'a, 'b> ClaimEpochRewardCpi<'a, 'b> {
362 pub fn new(
363 program: &'b solana_account_info::AccountInfo<'a>,
364 accounts: ClaimEpochRewardCpiAccounts<'a, 'b>,
365 ) -> Self {
366 Self {
367 __program: program,
368 authority: accounts.authority,
369 epoch_vault: accounts.epoch_vault,
370 epoch_vault_iteration: accounts.epoch_vault_iteration,
371 usd_mint: accounts.usd_mint,
372 btc_mint: accounts.btc_mint,
373 epoch_vault_usd_ata: accounts.epoch_vault_usd_ata,
374 epoch_vault_btc_ata: accounts.epoch_vault_btc_ata,
375 authority_usd_ata: accounts.authority_usd_ata,
376 authority_btc_ata: accounts.authority_btc_ata,
377 token_program: accounts.token_program,
378 associated_token_program: accounts.associated_token_program,
379 system_program: accounts.system_program,
380 }
381 }
382 #[inline(always)]
383 pub fn invoke(&self) -> solana_program_error::ProgramResult {
384 self.invoke_signed_with_remaining_accounts(&[], &[])
385 }
386 #[inline(always)]
387 pub fn invoke_with_remaining_accounts(&self, remaining_accounts: &[(&'b solana_account_info::AccountInfo<'a>, bool, bool)]) -> solana_program_error::ProgramResult {
388 self.invoke_signed_with_remaining_accounts(&[], remaining_accounts)
389 }
390 #[inline(always)]
391 pub fn invoke_signed(&self, signers_seeds: &[&[&[u8]]]) -> solana_program_error::ProgramResult {
392 self.invoke_signed_with_remaining_accounts(signers_seeds, &[])
393 }
394 #[allow(clippy::arithmetic_side_effects)]
395 #[allow(clippy::clone_on_copy)]
396 #[allow(clippy::vec_init_then_push)]
397 pub fn invoke_signed_with_remaining_accounts(
398 &self,
399 signers_seeds: &[&[&[u8]]],
400 remaining_accounts: &[(&'b solana_account_info::AccountInfo<'a>, bool, bool)]
401 ) -> solana_program_error::ProgramResult {
402 let mut accounts = Vec::with_capacity(12+ remaining_accounts.len());
403 accounts.push(solana_instruction::AccountMeta::new(
404 *self.authority.key,
405 true
406 ));
407 accounts.push(solana_instruction::AccountMeta::new(
408 *self.epoch_vault.key,
409 false
410 ));
411 accounts.push(solana_instruction::AccountMeta::new(
412 *self.epoch_vault_iteration.key,
413 false
414 ));
415 accounts.push(solana_instruction::AccountMeta::new_readonly(
416 *self.usd_mint.key,
417 false
418 ));
419 accounts.push(solana_instruction::AccountMeta::new_readonly(
420 *self.btc_mint.key,
421 false
422 ));
423 accounts.push(solana_instruction::AccountMeta::new(
424 *self.epoch_vault_usd_ata.key,
425 false
426 ));
427 accounts.push(solana_instruction::AccountMeta::new(
428 *self.epoch_vault_btc_ata.key,
429 false
430 ));
431 accounts.push(solana_instruction::AccountMeta::new(
432 *self.authority_usd_ata.key,
433 false
434 ));
435 accounts.push(solana_instruction::AccountMeta::new(
436 *self.authority_btc_ata.key,
437 false
438 ));
439 accounts.push(solana_instruction::AccountMeta::new_readonly(
440 *self.token_program.key,
441 false
442 ));
443 accounts.push(solana_instruction::AccountMeta::new_readonly(
444 *self.associated_token_program.key,
445 false
446 ));
447 accounts.push(solana_instruction::AccountMeta::new_readonly(
448 *self.system_program.key,
449 false
450 ));
451 remaining_accounts.iter().for_each(|remaining_account| {
452 accounts.push(solana_instruction::AccountMeta {
453 pubkey: *remaining_account.0.key,
454 is_signer: remaining_account.1,
455 is_writable: remaining_account.2,
456 })
457 });
458 let data = ClaimEpochRewardInstructionData::new().try_to_vec().unwrap();
459
460 let instruction = solana_instruction::Instruction {
461 program_id: crate::SATRUSH_ID,
462 accounts,
463 data,
464 };
465 let mut account_infos = Vec::with_capacity(13 + remaining_accounts.len());
466 account_infos.push(self.__program.clone());
467 account_infos.push(self.authority.clone());
468 account_infos.push(self.epoch_vault.clone());
469 account_infos.push(self.epoch_vault_iteration.clone());
470 account_infos.push(self.usd_mint.clone());
471 account_infos.push(self.btc_mint.clone());
472 account_infos.push(self.epoch_vault_usd_ata.clone());
473 account_infos.push(self.epoch_vault_btc_ata.clone());
474 account_infos.push(self.authority_usd_ata.clone());
475 account_infos.push(self.authority_btc_ata.clone());
476 account_infos.push(self.token_program.clone());
477 account_infos.push(self.associated_token_program.clone());
478 account_infos.push(self.system_program.clone());
479 remaining_accounts.iter().for_each(|remaining_account| account_infos.push(remaining_account.0.clone()));
480
481 if signers_seeds.is_empty() {
482 solana_cpi::invoke(&instruction, &account_infos)
483 } else {
484 solana_cpi::invoke_signed(&instruction, &account_infos, signers_seeds)
485 }
486 }
487}
488
489#[derive(Clone, Debug)]
506pub struct ClaimEpochRewardCpiBuilder<'a, 'b> {
507 instruction: Box<ClaimEpochRewardCpiBuilderInstruction<'a, 'b>>,
508}
509
510impl<'a, 'b> ClaimEpochRewardCpiBuilder<'a, 'b> {
511 pub fn new(program: &'b solana_account_info::AccountInfo<'a>) -> Self {
512 let instruction = Box::new(ClaimEpochRewardCpiBuilderInstruction {
513 __program: program,
514 authority: None,
515 epoch_vault: None,
516 epoch_vault_iteration: None,
517 usd_mint: None,
518 btc_mint: None,
519 epoch_vault_usd_ata: None,
520 epoch_vault_btc_ata: None,
521 authority_usd_ata: None,
522 authority_btc_ata: None,
523 token_program: None,
524 associated_token_program: None,
525 system_program: None,
526 __remaining_accounts: Vec::new(),
527 });
528 Self { instruction }
529 }
530 #[inline(always)]
531 pub fn authority(&mut self, authority: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
532 self.instruction.authority = Some(authority);
533 self
534 }
535 #[inline(always)]
536 pub fn epoch_vault(&mut self, epoch_vault: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
537 self.instruction.epoch_vault = Some(epoch_vault);
538 self
539 }
540 #[inline(always)]
541 pub fn epoch_vault_iteration(&mut self, epoch_vault_iteration: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
542 self.instruction.epoch_vault_iteration = Some(epoch_vault_iteration);
543 self
544 }
545 #[inline(always)]
546 pub fn usd_mint(&mut self, usd_mint: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
547 self.instruction.usd_mint = Some(usd_mint);
548 self
549 }
550 #[inline(always)]
551 pub fn btc_mint(&mut self, btc_mint: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
552 self.instruction.btc_mint = Some(btc_mint);
553 self
554 }
555 #[inline(always)]
556 pub fn epoch_vault_usd_ata(&mut self, epoch_vault_usd_ata: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
557 self.instruction.epoch_vault_usd_ata = Some(epoch_vault_usd_ata);
558 self
559 }
560 #[inline(always)]
561 pub fn epoch_vault_btc_ata(&mut self, epoch_vault_btc_ata: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
562 self.instruction.epoch_vault_btc_ata = Some(epoch_vault_btc_ata);
563 self
564 }
565 #[inline(always)]
566 pub fn authority_usd_ata(&mut self, authority_usd_ata: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
567 self.instruction.authority_usd_ata = Some(authority_usd_ata);
568 self
569 }
570 #[inline(always)]
571 pub fn authority_btc_ata(&mut self, authority_btc_ata: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
572 self.instruction.authority_btc_ata = Some(authority_btc_ata);
573 self
574 }
575 #[inline(always)]
576 pub fn token_program(&mut self, token_program: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
577 self.instruction.token_program = Some(token_program);
578 self
579 }
580 #[inline(always)]
581 pub fn associated_token_program(&mut self, associated_token_program: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
582 self.instruction.associated_token_program = Some(associated_token_program);
583 self
584 }
585 #[inline(always)]
586 pub fn system_program(&mut self, system_program: &'b solana_account_info::AccountInfo<'a>) -> &mut Self {
587 self.instruction.system_program = Some(system_program);
588 self
589 }
590 #[inline(always)]
592 pub fn add_remaining_account(&mut self, account: &'b solana_account_info::AccountInfo<'a>, is_writable: bool, is_signer: bool) -> &mut Self {
593 self.instruction.__remaining_accounts.push((account, is_writable, is_signer));
594 self
595 }
596 #[inline(always)]
601 pub fn add_remaining_accounts(&mut self, accounts: &[(&'b solana_account_info::AccountInfo<'a>, bool, bool)]) -> &mut Self {
602 self.instruction.__remaining_accounts.extend_from_slice(accounts);
603 self
604 }
605 #[inline(always)]
606 pub fn invoke(&self) -> solana_program_error::ProgramResult {
607 self.invoke_signed(&[])
608 }
609 #[allow(clippy::clone_on_copy)]
610 #[allow(clippy::vec_init_then_push)]
611 pub fn invoke_signed(&self, signers_seeds: &[&[&[u8]]]) -> solana_program_error::ProgramResult {
612 let instruction = ClaimEpochRewardCpi {
613 __program: self.instruction.__program,
614
615 authority: self.instruction.authority.expect("authority is not set"),
616
617 epoch_vault: self.instruction.epoch_vault.expect("epoch_vault is not set"),
618
619 epoch_vault_iteration: self.instruction.epoch_vault_iteration.expect("epoch_vault_iteration is not set"),
620
621 usd_mint: self.instruction.usd_mint.expect("usd_mint is not set"),
622
623 btc_mint: self.instruction.btc_mint.expect("btc_mint is not set"),
624
625 epoch_vault_usd_ata: self.instruction.epoch_vault_usd_ata.expect("epoch_vault_usd_ata is not set"),
626
627 epoch_vault_btc_ata: self.instruction.epoch_vault_btc_ata.expect("epoch_vault_btc_ata is not set"),
628
629 authority_usd_ata: self.instruction.authority_usd_ata.expect("authority_usd_ata is not set"),
630
631 authority_btc_ata: self.instruction.authority_btc_ata.expect("authority_btc_ata is not set"),
632
633 token_program: self.instruction.token_program.expect("token_program is not set"),
634
635 associated_token_program: self.instruction.associated_token_program.expect("associated_token_program is not set"),
636
637 system_program: self.instruction.system_program.expect("system_program is not set"),
638 };
639 instruction.invoke_signed_with_remaining_accounts(signers_seeds, &self.instruction.__remaining_accounts)
640 }
641}
642
643#[derive(Clone, Debug)]
644struct ClaimEpochRewardCpiBuilderInstruction<'a, 'b> {
645 __program: &'b solana_account_info::AccountInfo<'a>,
646 authority: Option<&'b solana_account_info::AccountInfo<'a>>,
647 epoch_vault: Option<&'b solana_account_info::AccountInfo<'a>>,
648 epoch_vault_iteration: Option<&'b solana_account_info::AccountInfo<'a>>,
649 usd_mint: Option<&'b solana_account_info::AccountInfo<'a>>,
650 btc_mint: Option<&'b solana_account_info::AccountInfo<'a>>,
651 epoch_vault_usd_ata: Option<&'b solana_account_info::AccountInfo<'a>>,
652 epoch_vault_btc_ata: Option<&'b solana_account_info::AccountInfo<'a>>,
653 authority_usd_ata: Option<&'b solana_account_info::AccountInfo<'a>>,
654 authority_btc_ata: Option<&'b solana_account_info::AccountInfo<'a>>,
655 token_program: Option<&'b solana_account_info::AccountInfo<'a>>,
656 associated_token_program: Option<&'b solana_account_info::AccountInfo<'a>>,
657 system_program: Option<&'b solana_account_info::AccountInfo<'a>>,
658 __remaining_accounts: Vec<(&'b solana_account_info::AccountInfo<'a>, bool, bool)>,
660}
661