@@ -18,7 +18,7 @@ pub const MAX_SIGNERS: usize = 11;
1818const U64_BYTES : usize = 8 ;
1919
2020/// Instructions supported by the token program.
21- #[ repr( C ) ]
21+ #[ repr( C , u8 ) ]
2222#[ derive( Clone , Debug , PartialEq ) ]
2323pub enum TokenInstruction < ' a > {
2424 /// Initializes a new mint and optionally deposits all the newly minted
@@ -474,6 +474,78 @@ pub enum TokenInstruction<'a> {
474474 /// The `ui_amount` of tokens to reformat.
475475 ui_amount : & ' a str ,
476476 } ,
477+ /// This instruction is to be used to rescue SOL sent to any `TokenProgram`
478+ /// owned account by sending them to any other account, leaving behind only
479+ /// lamports for rent exemption.
480+ ///
481+ /// Accounts expected by this instruction:
482+ ///
483+ /// * Single owner/delegate
484+ /// 0. `[writable]` The source account.
485+ /// 1. `[writable]` The destination account.
486+ /// 2. `[signer]` The source account's owner/delegate.
487+ ///
488+ /// * Multisignature owner/delegate
489+ /// 0. `[writable]` The source account.
490+ /// 1. `[writable]` The destination account.
491+ /// 2. `[]` The source account's multisignature owner/delegate.
492+ /// 3. `..+M` `[signer]` M signer accounts.
493+ WithdrawExcessLamports = 38 ,
494+ /// Transfer lamports from a native SOL account to a destination account.
495+ ///
496+ /// This is useful to unwrap lamports from a wrapped SOL account.
497+ ///
498+ /// Accounts expected by this instruction:
499+ ///
500+ /// * Single owner/delegate
501+ /// 0. `[writable]` The source account.
502+ /// 1. `[writable]` The destination account.
503+ /// 2. `[signer]` The source account's owner/delegate.
504+ ///
505+ /// * Multisignature owner/delegate
506+ /// 0. `[writable]` The source account.
507+ /// 1. `[writable]` The destination account.
508+ /// 2. `[]` The source account's multisignature owner/delegate.
509+ /// 3. `..+M` `[signer]` M signer accounts.
510+ ///
511+ /// Data expected by this instruction:
512+ ///
513+ /// - `Option<u64>` The amount of lamports to transfer. When an amount is
514+ /// not specified, the entire balance of the source account will be
515+ /// transferred.
516+ UnwrapLamports {
517+ /// The amount of lamports to transfer.
518+ amount : COption < u64 > ,
519+ } = 45 ,
520+ /// Executes a batch of instructions. The instructions to be executed are
521+ /// specified in sequence on the instruction data. Each instruction
522+ /// provides:
523+ /// - `u8`: number of accounts
524+ /// - `u8`: instruction data length (includes the discriminator)
525+ /// - `u8`: instruction discriminator
526+ /// - `[u8]`: instruction data
527+ ///
528+ /// Accounts follow a similar pattern, where accounts for each instruction
529+ /// are specified in sequence. Therefore, the number of accounts
530+ /// expected by this instruction is variable, i.e., it depends on the
531+ /// instructions provided.
532+ ///
533+ /// Both the number of accounts and instruction data length are used to
534+ /// identify the slice of accounts and instruction data for each
535+ /// instruction. Since the instruction data length is specified as a `u8`,
536+ /// the maximum instruction data length is 255 bytes, which is sufficient
537+ /// for all current instructions in the program.
538+ ///
539+ /// When one or more batched instructions write return data, the batch
540+ /// instruction returns the data written by the last instruction that does
541+ /// so. That instruction does not have to be the final instruction in the
542+ /// batch: later instructions that do not write return data leave the last
543+ /// written return data unchanged.
544+ ///
545+ /// Note that it is not sound to have a `batch` instruction that contains
546+ /// other `batch` instruction; an error will be raised when this is
547+ /// detected.
548+ Batch = 255 ,
477549 // Any new variants also need to be added to program-2022 `TokenInstruction`, so that the
478550 // latter remains a superset of this instruction set. New variants also need to be added to
479551 // token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility
@@ -580,6 +652,12 @@ impl<'a> TokenInstruction<'a> {
580652 let ui_amount = std:: str:: from_utf8 ( rest) . map_err ( |_| InvalidInstruction ) ?;
581653 Self :: UiAmountToAmount { ui_amount }
582654 }
655+ 38 => Self :: WithdrawExcessLamports ,
656+ 45 => {
657+ let ( amount, _rest) = Self :: unpack_u64_option ( rest) ?;
658+ Self :: UnwrapLamports { amount }
659+ }
660+ 255 => Self :: Batch ,
583661 _ => return Err ( TokenError :: InvalidInstruction . into ( ) ) ,
584662 } )
585663 }
@@ -691,6 +769,16 @@ impl<'a> TokenInstruction<'a> {
691769 buf. push ( 24 ) ;
692770 buf. extend_from_slice ( ui_amount. as_bytes ( ) ) ;
693771 }
772+ & Self :: WithdrawExcessLamports => {
773+ buf. push ( 38 ) ;
774+ }
775+ Self :: UnwrapLamports { ref amount } => {
776+ buf. push ( 45 ) ;
777+ Self :: pack_u64_option ( amount, & mut buf) ;
778+ }
779+ & Self :: Batch => {
780+ buf. push ( 255 ) ;
781+ }
694782 } ;
695783 buf
696784 }
@@ -736,6 +824,30 @@ impl<'a> TokenInstruction<'a> {
736824 Ok ( ( value, & input[ U64_BYTES ..] ) )
737825 }
738826
827+ fn unpack_u64_option ( input : & [ u8 ] ) -> Result < ( COption < u64 > , & [ u8 ] ) , ProgramError > {
828+ match input. split_first ( ) {
829+ Option :: Some ( ( & 0 , rest) ) => Ok ( ( COption :: None , rest) ) ,
830+ Option :: Some ( ( & 1 , rest) ) if rest. len ( ) >= 8 => {
831+ let ( amount, rest) = rest
832+ . split_first_chunk :: < 8 > ( )
833+ . ok_or ( TokenError :: InvalidInstruction ) ?;
834+ let v = u64:: from_le_bytes ( * amount) ;
835+ Ok ( ( COption :: Some ( v) , rest) )
836+ }
837+ _ => Err ( TokenError :: InvalidInstruction . into ( ) ) ,
838+ }
839+ }
840+
841+ fn pack_u64_option ( value : & COption < u64 > , buf : & mut Vec < u8 > ) {
842+ match * value {
843+ COption :: Some ( ref amount) => {
844+ buf. push ( 1 ) ;
845+ buf. extend_from_slice ( & amount. to_le_bytes ( ) ) ;
846+ }
847+ COption :: None => buf. push ( 0 ) ,
848+ }
849+ }
850+
739851 fn unpack_amount_decimals ( input : & [ u8 ] ) -> Result < ( u64 , u8 , & [ u8 ] ) , ProgramError > {
740852 let ( amount, rest) = Self :: unpack_u64 ( input) ?;
741853 let ( & decimals, rest) = rest. split_first ( ) . ok_or ( TokenError :: InvalidInstruction ) ?;
@@ -1450,6 +1562,101 @@ pub fn ui_amount_to_amount(
14501562 } )
14511563}
14521564
1565+ /// Creates a `WithdrawExcessLamports` instruction
1566+ pub fn withdraw_excess_lamports (
1567+ token_program_id : & Pubkey ,
1568+ account_pubkey : & Pubkey ,
1569+ destination_pubkey : & Pubkey ,
1570+ authority_pubkey : & Pubkey ,
1571+ signer_pubkeys : & [ & Pubkey ] ,
1572+ ) -> Result < Instruction , ProgramError > {
1573+ check_program_account ( token_program_id) ?;
1574+ let data = TokenInstruction :: WithdrawExcessLamports . pack ( ) ;
1575+
1576+ let mut accounts = Vec :: with_capacity ( 3 + signer_pubkeys. len ( ) ) ;
1577+ accounts. push ( AccountMeta :: new ( * account_pubkey, false ) ) ;
1578+ accounts. push ( AccountMeta :: new ( * destination_pubkey, false ) ) ;
1579+ accounts. push ( AccountMeta :: new_readonly (
1580+ * authority_pubkey,
1581+ signer_pubkeys. is_empty ( ) ,
1582+ ) ) ;
1583+ for signer_pubkey in signer_pubkeys. iter ( ) {
1584+ accounts. push ( AccountMeta :: new_readonly ( * * signer_pubkey, true ) ) ;
1585+ }
1586+
1587+ Ok ( Instruction {
1588+ program_id : * token_program_id,
1589+ accounts,
1590+ data,
1591+ } )
1592+ }
1593+
1594+ /// Creates a `UnwrapLamports` instruction
1595+ pub fn unwrap_lamports (
1596+ token_program_id : & Pubkey ,
1597+ account_pubkey : & Pubkey ,
1598+ destination_pubkey : & Pubkey ,
1599+ authority_pubkey : & Pubkey ,
1600+ signer_pubkeys : & [ & Pubkey ] ,
1601+ amount : Option < u64 > ,
1602+ ) -> Result < Instruction , ProgramError > {
1603+ check_program_account ( token_program_id) ?;
1604+
1605+ let amount = amount. into ( ) ;
1606+ let data = TokenInstruction :: UnwrapLamports { amount } . pack ( ) ;
1607+
1608+ let mut accounts = Vec :: with_capacity ( 3 + signer_pubkeys. len ( ) ) ;
1609+ accounts. push ( AccountMeta :: new ( * account_pubkey, false ) ) ;
1610+ accounts. push ( AccountMeta :: new ( * destination_pubkey, false ) ) ;
1611+ accounts. push ( AccountMeta :: new_readonly (
1612+ * authority_pubkey,
1613+ signer_pubkeys. is_empty ( ) ,
1614+ ) ) ;
1615+ for signer_pubkey in signer_pubkeys. iter ( ) {
1616+ accounts. push ( AccountMeta :: new_readonly ( * * signer_pubkey, true ) ) ;
1617+ }
1618+
1619+ Ok ( Instruction {
1620+ program_id : * token_program_id,
1621+ accounts,
1622+ data,
1623+ } )
1624+ }
1625+
1626+ /// Creates a `Batch` instruction
1627+ pub fn batch (
1628+ token_program_id : & Pubkey ,
1629+ instructions : & [ Instruction ] ,
1630+ ) -> Result < Instruction , ProgramError > {
1631+ check_program_account ( token_program_id) ?;
1632+
1633+ let mut data: Vec < u8 > = TokenInstruction :: Batch . pack ( ) ;
1634+ let mut accounts: Vec < AccountMeta > = vec ! [ ] ;
1635+
1636+ for instruction in instructions {
1637+ if token_program_id != & instruction. program_id {
1638+ return Err ( ProgramError :: IncorrectProgramId ) ;
1639+ }
1640+
1641+ data. push ( instruction. accounts . len ( ) as u8 ) ;
1642+
1643+ if instruction. data . len ( ) > u8:: MAX as usize {
1644+ return Err ( ProgramError :: InvalidInstructionData ) ;
1645+ }
1646+
1647+ data. push ( instruction. data . len ( ) as u8 ) ;
1648+
1649+ data. extend_from_slice ( & instruction. data ) ;
1650+ accounts. extend_from_slice ( & instruction. accounts ) ;
1651+ }
1652+
1653+ Ok ( Instruction {
1654+ program_id : * token_program_id,
1655+ data,
1656+ accounts,
1657+ } )
1658+ }
1659+
14531660/// Utility function that checks index is between `MIN_SIGNERS` and
14541661/// `MAX_SIGNERS`
14551662pub fn is_valid_signer_index ( index : usize ) -> bool {
@@ -1699,6 +1906,38 @@ mod test {
16991906 assert_eq ! ( packed, expect) ;
17001907 let unpacked = TokenInstruction :: unpack ( & expect) . unwrap ( ) ;
17011908 assert_eq ! ( unpacked, check) ;
1909+
1910+ let check = TokenInstruction :: WithdrawExcessLamports ;
1911+ let packed = check. pack ( ) ;
1912+ let expect = vec ! [ 38u8 ] ;
1913+ assert_eq ! ( packed, expect) ;
1914+ let unpacked = TokenInstruction :: unpack ( & expect) . unwrap ( ) ;
1915+ assert_eq ! ( unpacked, check) ;
1916+
1917+ let check = TokenInstruction :: UnwrapLamports {
1918+ amount : COption :: Some ( 42 ) ,
1919+ } ;
1920+ let packed = check. pack ( ) ;
1921+ let expect = vec ! [ 45u8 , 1 , 42 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ;
1922+ assert_eq ! ( packed, expect) ;
1923+ let unpacked = TokenInstruction :: unpack ( & expect) . unwrap ( ) ;
1924+ assert_eq ! ( unpacked, check) ;
1925+
1926+ let check = TokenInstruction :: UnwrapLamports {
1927+ amount : COption :: None ,
1928+ } ;
1929+ let packed = check. pack ( ) ;
1930+ let expect = vec ! [ 45u8 , 0 ] ;
1931+ assert_eq ! ( packed, expect) ;
1932+ let unpacked = TokenInstruction :: unpack ( & expect) . unwrap ( ) ;
1933+ assert_eq ! ( unpacked, check) ;
1934+
1935+ let check = TokenInstruction :: Batch ;
1936+ let packed = check. pack ( ) ;
1937+ let expect = vec ! [ 255u8 ] ;
1938+ assert_eq ! ( packed, expect) ;
1939+ let unpacked = TokenInstruction :: unpack ( & expect) . unwrap ( ) ;
1940+ assert_eq ! ( unpacked, check) ;
17021941 }
17031942
17041943 #[ test]
0 commit comments