@@ -32,15 +32,12 @@ use solana_sdk::{
32
32
message:: Message ,
33
33
native_token:: * ,
34
34
program_option:: COption ,
35
- program_pack:: Pack ,
36
35
pubkey:: Pubkey ,
37
36
signature:: { Keypair , Signer } ,
38
37
system_program,
39
38
transaction:: Transaction ,
40
39
} ;
41
- use spl_associated_token_account:: {
42
- get_associated_token_address_with_program_id, instruction:: create_associated_token_account,
43
- } ;
40
+ use spl_associated_token_account:: get_associated_token_address_with_program_id;
44
41
use spl_token_2022:: {
45
42
extension:: {
46
43
interest_bearing_mint:: InterestBearingConfig , memo_transfer:: MemoTransfer ,
@@ -789,7 +786,7 @@ async fn validate_mint(config: &Config<'_>, token: Pubkey) -> Result<Pubkey, Err
789
786
#[ allow( clippy:: too_many_arguments) ]
790
787
async fn command_transfer (
791
788
config : & Config < ' _ > ,
792
- token : Pubkey ,
789
+ token_pubkey : Pubkey ,
793
790
ui_amount : Option < f64 > ,
794
791
recipient : Pubkey ,
795
792
sender : Option < Pubkey > ,
@@ -804,33 +801,45 @@ async fn command_transfer(
804
801
no_wait : bool ,
805
802
allow_non_system_account_recipient : bool ,
806
803
) -> CommandResult {
807
- let mint_info = config. get_mint_info ( & token, mint_decimals) . await ?;
804
+ let token = token_client_from_config ( config, & token_pubkey) ;
805
+ let mint_info = config. get_mint_info ( & token_pubkey, mint_decimals) . await ?;
806
+
807
+ // decimals arg, which determines whether transfer or transfer_checked instruction is used
808
+ // the logic here is tricky because "do what we are told" and "do the right thing" are at odds
809
+ let decimals = if use_unchecked_instruction {
810
+ None
811
+ } else if !config. sign_only {
812
+ Some ( mint_info. decimals )
813
+ } else if mint_decimals. is_some ( ) {
814
+ mint_decimals
815
+ } else {
816
+ return Err ( "Decimals must be specified with --mint-decimals in offline mode" . into ( ) ) ;
817
+ } ;
818
+
819
+ // pubkey of the actual account we are sending from
808
820
let sender = if let Some ( sender) = sender {
809
821
sender
810
822
} else {
811
- get_associated_token_address_with_program_id ( & sender_owner, & token , & mint_info . program_id )
823
+ token . get_associated_token_address ( & sender_owner)
812
824
} ;
813
- config. check_account ( & sender, Some ( token) ) . await ?;
825
+
826
+ // in any sign_only block, we can safely unwrap this
827
+ let maybe_sender_state = match token. get_account_info ( & sender) . await {
828
+ Ok ( a) => Some ( a) ,
829
+ Err ( _) if config. sign_only => None ,
830
+ Err ( e) => {
831
+ return Err ( format ! ( "Error: failed to fetch sender account {}: {}" , sender, e) . into ( ) )
832
+ }
833
+ } ;
834
+
814
835
let maybe_transfer_balance =
815
836
ui_amount. map ( |ui_amount| spl_token:: ui_amount_to_amount ( ui_amount, mint_info. decimals ) ) ;
837
+
838
+ // the amount we will transfer, as a u64
816
839
let transfer_balance = if !config. sign_only {
817
- let sender_token_amount = config
818
- . rpc_client
819
- . get_token_account_balance ( & sender)
820
- . await
821
- . map_err ( |err| {
822
- format ! (
823
- "Error: Failed to get token balance of sender address {}: {}" ,
824
- sender, err
825
- )
826
- } ) ?;
827
- let sender_balance = sender_token_amount. amount . parse :: < u64 > ( ) . map_err ( |err| {
828
- format ! (
829
- "Token account {} balance could not be parsed: {}" ,
830
- sender, err
831
- )
832
- } ) ?;
840
+ let sender_balance = maybe_sender_state. unwrap ( ) . base . amount ;
833
841
let transfer_balance = maybe_transfer_balance. unwrap_or ( sender_balance) ;
842
+
834
843
println_display (
835
844
config,
836
845
format ! (
@@ -851,59 +860,58 @@ async fn command_transfer(
851
860
)
852
861
. into ( ) ) ;
853
862
}
863
+
854
864
transfer_balance
855
865
} else {
856
866
maybe_transfer_balance. unwrap ( )
857
867
} ;
858
868
859
- let mut instructions = vec ! [ ] ;
869
+ // determine whether recipient is a token account or an expected owner of one
870
+ let recipient_is_token_account = if !config. sign_only {
871
+ // in online mode we can fetch it and see
872
+ let maybe_recipient_account_data = config. program_client . get_account ( recipient) . await ?;
860
873
861
- let mut recipient_token_account = recipient;
862
- let mut minimum_balance_for_rent_exemption = 0 ;
874
+ // if the account exists...
875
+ if let Some ( recipient_account_data) = maybe_recipient_account_data {
876
+ // ...and its a token or system account, we are happy
877
+ // but if its something else, we gate transfer with a flag
878
+ let recipient_is_token_account = recipient_account_data. owner == config. program_id
879
+ && Account :: valid_account_data ( & recipient_account_data. data ) ;
880
+ let recipient_is_system_account = recipient_account_data. owner == system_program:: id ( ) ;
863
881
864
- let recipient_is_token_account = if !config. sign_only {
865
- let recipient_account_info = config
866
- . rpc_client
867
- . get_account_with_commitment ( & recipient, config. rpc_client . commitment ( ) )
868
- . await ?
869
- . value
870
- . map ( |account| {
871
- (
872
- account. owner == mint_info. program_id
873
- && Account :: valid_account_data ( account. data . as_slice ( ) ) ,
874
- account. owner == system_program:: id ( ) ,
875
- )
876
- } ) ;
877
- if let Some ( ( recipient_is_token_account, recipient_is_system_account) ) =
878
- recipient_account_info
879
- {
880
882
if !recipient_is_token_account
881
883
&& !recipient_is_system_account
882
884
&& !allow_non_system_account_recipient
883
885
{
884
886
return Err ( "Error: The recipient address is not owned by the System Program. \
885
- Add `--allow-non-system-account-recipient` to complete the transfer. \
886
- ". into ( ) ) ;
887
+ Add `--allow-non-system-account-recipient` to complete the transfer.". into ( ) ) ;
887
888
}
888
- } else if recipient_account_info. is_none ( ) && !allow_unfunded_recipient {
889
+
890
+ recipient_is_token_account
891
+ }
892
+ // if it doesnt exist, it definitely isnt a token account!
893
+ // we gate transfer with a different flag
894
+ else if maybe_recipient_account_data. is_none ( ) && allow_unfunded_recipient {
895
+ false
896
+ } else {
889
897
return Err ( "Error: The recipient address is not funded. \
890
- Add `--allow-unfunded-recipient` to complete the transfer. \
891
- "
892
- . into ( ) ) ;
898
+ Add `--allow-unfunded-recipient` to complete the transfer."
899
+ . into ( ) ) ;
893
900
}
894
- recipient_account_info
895
- . map ( |( recipient_is_token_account, _) | recipient_is_token_account)
896
- . unwrap_or ( false )
897
901
} else {
902
+ // in offline mode we gotta trust them
898
903
!recipient_is_ata_owner
899
904
} ;
900
905
901
- if !recipient_is_token_account {
902
- recipient_token_account = get_associated_token_address_with_program_id (
903
- & recipient,
904
- & mint_info. address ,
905
- & mint_info. program_id ,
906
- ) ;
906
+ // now if its a token account, life is ez
907
+ let ( recipient_token_account, fundable_owner) = if recipient_is_token_account {
908
+ ( recipient, None )
909
+ }
910
+ // but if not, we need to determine if we can or should create an ata for recipient
911
+ else {
912
+ // first, get the ata address
913
+ let recipient_token_account = token. get_associated_token_address ( & recipient) ;
914
+
907
915
println_display (
908
916
config,
909
917
format ! (
@@ -912,19 +920,16 @@ async fn command_transfer(
912
920
) ,
913
921
) ;
914
922
923
+ // if we can fetch it to determine if it exists, do so
915
924
let needs_funding = if !config. sign_only {
916
925
if let Some ( recipient_token_account_data) = config
917
- . rpc_client
918
- . get_account_with_commitment (
919
- & recipient_token_account,
920
- config. rpc_client . commitment ( ) ,
921
- )
926
+ . program_client
927
+ . get_account ( recipient_token_account)
922
928
. await ?
923
- . value
924
929
{
925
930
if recipient_token_account_data. owner == system_program:: id ( ) {
926
931
true
927
- } else if recipient_token_account_data. owner == mint_info . program_id {
932
+ } else if recipient_token_account_data. owner == config . program_id {
928
933
false
929
934
} else {
930
935
return Err (
@@ -934,80 +939,54 @@ async fn command_transfer(
934
939
} else {
935
940
true
936
941
}
937
- } else {
942
+ }
943
+ // otherwise trust the cli flag
944
+ else {
938
945
fund_recipient
939
946
} ;
940
947
941
- if needs_funding {
948
+ // and now we determine if we will actually fund it, based on its need and our willingness
949
+ let fundable_owner = if needs_funding {
942
950
if fund_recipient {
943
- if !config. sign_only {
944
- minimum_balance_for_rent_exemption += config
945
- . program_client
946
- . get_minimum_balance_for_rent_exemption ( Account :: LEN )
947
- . await ?;
948
- println_display (
949
- config,
950
- format ! (
951
- " Funding recipient: {} ({} SOL)" ,
952
- recipient_token_account,
953
- lamports_to_sol( minimum_balance_for_rent_exemption)
954
- ) ,
955
- ) ;
956
- }
957
- instructions. push ( create_associated_token_account (
958
- & config. fee_payer . pubkey ( ) ,
959
- & recipient,
960
- & mint_info. address ,
961
- & mint_info. program_id ,
962
- ) ) ;
951
+ println_display (
952
+ config,
953
+ format ! ( " Funding recipient: {}" , recipient_token_account, ) ,
954
+ ) ;
955
+
956
+ Some ( recipient)
963
957
} else {
964
958
return Err (
965
959
"Error: Recipient's associated token account does not exist. \
966
960
Add `--fund-recipient` to fund their account"
967
961
. into ( ) ,
968
962
) ;
969
963
}
970
- }
964
+ } else {
965
+ None
966
+ } ;
967
+
968
+ ( recipient_token_account, fundable_owner)
969
+ } ;
970
+
971
+ // set up memo if provided...
972
+ if let Some ( text) = memo {
973
+ token. with_memo ( text, vec ! [ config. default_signer. pubkey( ) ] ) ;
971
974
}
972
975
973
- if use_unchecked_instruction {
974
- #[ allow( deprecated) ]
975
- instructions. push ( transfer (
976
- & mint_info. program_id ,
977
- & sender,
978
- & recipient_token_account,
979
- & sender_owner,
980
- & config. multisigner_pubkeys ,
981
- transfer_balance,
982
- ) ?) ;
983
- } else {
984
- instructions. push ( transfer_checked (
985
- & mint_info. program_id ,
976
+ // ...and, finally, the transfer
977
+ let res = token
978
+ . transfer (
986
979
& sender,
987
- & mint_info. address ,
988
980
& recipient_token_account,
989
981
& sender_owner,
990
- & config. multisigner_pubkeys ,
991
982
transfer_balance,
992
- mint_info. decimals ,
993
- ) ?) ;
994
- }
995
- if let Some ( text) = memo {
996
- instructions. push ( spl_memo:: build_memo (
997
- text. as_bytes ( ) ,
998
- & [ & config. fee_payer . pubkey ( ) ] ,
999
- ) ) ;
1000
- }
1001
- let tx_return = handle_tx (
1002
- & CliSignerInfo {
1003
- signers : bulk_signers,
1004
- } ,
1005
- config,
1006
- no_wait,
1007
- minimum_balance_for_rent_exemption,
1008
- instructions,
1009
- )
1010
- . await ?;
983
+ decimals,
984
+ fundable_owner,
985
+ & bulk_signers,
986
+ )
987
+ . await ?;
988
+
989
+ let tx_return = finish_tx ( config, & res, no_wait) . await ?;
1011
990
Ok ( match tx_return {
1012
991
TransactionReturnData :: CliSignature ( signature) => {
1013
992
config. output_format . formatted_string ( & signature)
@@ -3541,6 +3520,7 @@ mod tests {
3541
3520
solana_sdk:: {
3542
3521
bpf_loader,
3543
3522
signature:: { write_keypair_file, Keypair , Signer } ,
3523
+ transaction:: Transaction ,
3544
3524
} ,
3545
3525
solana_test_validator:: { ProgramInfo , TestValidator , TestValidatorGenesis } ,
3546
3526
spl_token_client:: client:: {
@@ -3830,8 +3810,7 @@ mod tests {
3830
3810
let value: serde_json:: Value = serde_json:: from_str ( & result. unwrap ( ) ) . unwrap ( ) ;
3831
3811
let mint = Pubkey :: from_str ( value[ "commandOutput" ] [ "address" ] . as_str ( ) . unwrap ( ) ) . unwrap ( ) ;
3832
3812
let account = config. rpc_client . get_account ( & mint) . await . unwrap ( ) ;
3833
- let mint_account =
3834
- StateWithExtensionsOwned :: < spl_token_2022:: state:: Mint > :: unpack ( account. data ) . unwrap ( ) ;
3813
+ let mint_account = StateWithExtensionsOwned :: < Mint > :: unpack ( account. data ) . unwrap ( ) ;
3835
3814
let extension = mint_account
3836
3815
. get_extension :: < InterestBearingConfig > ( )
3837
3816
. unwrap ( ) ;
@@ -3852,8 +3831,7 @@ mod tests {
3852
3831
let new_rate: i16 = 300 ;
3853
3832
let token = create_interest_bearing_token ( & config, & payer, initial_rate) . await ;
3854
3833
let account = config. rpc_client . get_account ( & token) . await . unwrap ( ) ;
3855
- let mint_account =
3856
- StateWithExtensionsOwned :: < spl_token_2022:: state:: Mint > :: unpack ( account. data ) . unwrap ( ) ;
3834
+ let mint_account = StateWithExtensionsOwned :: < Mint > :: unpack ( account. data ) . unwrap ( ) ;
3857
3835
let extension = mint_account
3858
3836
. get_extension :: < InterestBearingConfig > ( )
3859
3837
. unwrap ( ) ;
@@ -3873,8 +3851,7 @@ mod tests {
3873
3851
. await ;
3874
3852
let _value: serde_json:: Value = serde_json:: from_str ( & result. unwrap ( ) ) . unwrap ( ) ;
3875
3853
let account = config. rpc_client . get_account ( & token) . await . unwrap ( ) ;
3876
- let mint_account =
3877
- StateWithExtensionsOwned :: < spl_token_2022:: state:: Mint > :: unpack ( account. data ) . unwrap ( ) ;
3854
+ let mint_account = StateWithExtensionsOwned :: < Mint > :: unpack ( account. data ) . unwrap ( ) ;
3878
3855
let extension = mint_account
3879
3856
. get_extension :: < InterestBearingConfig > ( )
3880
3857
. unwrap ( ) ;
@@ -4327,8 +4304,8 @@ mod tests {
4327
4304
result. unwrap ( ) ;
4328
4305
4329
4306
let account = config. rpc_client . get_account ( & token) . await . unwrap ( ) ;
4330
- let mint = Mint :: unpack ( & account. data ) . unwrap ( ) ;
4331
- assert_eq ! ( mint. mint_authority, COption :: None ) ;
4307
+ let mint = StateWithExtensionsOwned :: < Mint > :: unpack ( account. data ) . unwrap ( ) ;
4308
+ assert_eq ! ( mint. base . mint_authority, COption :: None ) ;
4332
4309
}
4333
4310
}
4334
4311
0 commit comments