@@ -20,7 +20,7 @@ use super::{not_implemented_err, RunloopContext, SurfnetRpcContext};
2020use crate :: {
2121 error:: { SurfpoolError , SurfpoolResult } ,
2222 rpc:: { utils:: verify_pubkey, State } ,
23- surfnet:: locker:: SvmAccessContext ,
23+ surfnet:: { locker:: SvmAccessContext , GetAccountResult } ,
2424} ;
2525
2626#[ rpc]
@@ -377,12 +377,20 @@ impl AccountsData for SurfpoolAccountsDataRpc {
377377 inner : account_update,
378378 ..
379379 } = svm_locker. get_account ( & remote_ctx, & pubkey, None ) . await ?;
380-
381380 svm_locker. write_account_update ( account_update. clone ( ) ) ;
382381
382+ let SvmAccessContext {
383+ inner : associated_data,
384+ ..
385+ } = svm_locker. get_local_account_associated_data ( & account_update) ;
386+
383387 Ok ( RpcResponse {
384388 context : RpcResponseContext :: new ( slot) ,
385- value : account_update. try_into_ui_account ( config. encoding , config. data_slice ) ,
389+ value : account_update. try_into_ui_account (
390+ config. encoding ,
391+ config. data_slice ,
392+ associated_data,
393+ ) ,
386394 } )
387395 } )
388396 }
@@ -425,9 +433,11 @@ impl AccountsData for SurfpoolAccountsDataRpc {
425433 let mut ui_accounts = vec ! [ ] ;
426434 {
427435 for account_update in account_updates. into_iter ( ) {
428- ui_accounts. push (
429- account_update. try_into_ui_account ( config. encoding , config. data_slice ) ,
430- ) ;
436+ ui_accounts. push ( account_update. try_into_ui_account (
437+ config. encoding ,
438+ config. data_slice ,
439+ None ,
440+ ) ) ;
431441 }
432442 }
433443
@@ -648,13 +658,30 @@ impl AccountsData for SurfpoolAccountsDataRpc {
648658#[ cfg( test) ]
649659mod tests {
650660 use solana_account:: Account ;
661+ use solana_client:: rpc_client:: RpcClient ;
662+ use solana_keypair:: Keypair ;
651663 use solana_pubkey:: Pubkey ;
652- use solana_sdk:: { program_option:: COption , program_pack:: Pack } ;
664+ use solana_sdk:: {
665+ program_option:: COption , program_pack:: Pack , system_instruction:: create_account,
666+ } ;
667+ use solana_signer:: Signer ;
668+ use solana_transaction:: Transaction ;
669+ use spl_associated_token_account:: {
670+ get_associated_token_address_with_program_id, instruction:: create_associated_token_account,
671+ } ;
653672 use spl_token:: state:: { Account as TokenAccount , AccountState , Mint } ;
673+ use spl_token_2022:: {
674+ extension:: StateWithExtensions ,
675+ instruction:: { initialize_mint2, mint_to, transfer_checked} ,
676+ state:: Account as Token2022Account ,
677+ } ;
654678
655679 use super :: * ;
656680 use crate :: {
657- surfnet:: { remote:: SurfnetRemoteClient , GetAccountResult } ,
681+ surfnet:: {
682+ remote:: { SomeRemoteCtx , SurfnetRemoteClient } ,
683+ GetAccountResult ,
684+ } ,
658685 tests:: helpers:: TestSetup ,
659686 } ;
660687
@@ -1019,4 +1046,210 @@ mod tests {
10191046 let error_msg = res. unwrap_err ( ) . to_string ( ) ;
10201047 println ! ( "✅ Remote RPC failure handled: {}" , error_msg) ;
10211048 }
1049+
1050+ #[ tokio:: test( flavor = "multi_thread" ) ]
1051+ async fn test_transfer_token ( ) {
1052+ // Create connection to local validator
1053+ let client = TestSetup :: new ( SurfpoolAccountsDataRpc ) ;
1054+ let recent_blockhash = client
1055+ . context
1056+ . svm_locker
1057+ . with_svm_reader ( |svm_reader| svm_reader. latest_blockhash ( ) ) ;
1058+
1059+ // Generate a new keypair for the fee payer
1060+ let fee_payer = Keypair :: new ( ) ;
1061+
1062+ // Generate a second keypair for the token recipient
1063+ let recipient = Keypair :: new ( ) ;
1064+
1065+ // Airdrop 1 SOL to fee payer
1066+ client
1067+ . context
1068+ . svm_locker
1069+ . airdrop ( & fee_payer. pubkey ( ) , 1_000_000_000 )
1070+ . unwrap ( ) ;
1071+
1072+ // Airdrop 1 SOL to recipient for rent exemption
1073+ client
1074+ . context
1075+ . svm_locker
1076+ . airdrop ( & recipient. pubkey ( ) , 1_000_000_000 )
1077+ . unwrap ( ) ;
1078+
1079+ // Generate keypair to use as address of mint
1080+ let mint = Keypair :: new ( ) ;
1081+
1082+ // Get default mint account size (in bytes), no extensions enabled
1083+ let mint_space = Mint :: LEN ;
1084+ let mint_rent = client. context . svm_locker . with_svm_reader ( |svm_reader| {
1085+ svm_reader
1086+ . inner
1087+ . minimum_balance_for_rent_exemption ( mint_space)
1088+ } ) ;
1089+
1090+ // Instruction to create new account for mint (token 2022 program)
1091+ let create_account_instruction = create_account (
1092+ & fee_payer. pubkey ( ) , // payer
1093+ & mint. pubkey ( ) , // new account (mint)
1094+ mint_rent, // lamports
1095+ mint_space as u64 , // space
1096+ & spl_token_2022:: id ( ) , // program id
1097+ ) ;
1098+
1099+ // Instruction to initialize mint account data
1100+ let initialize_mint_instruction = initialize_mint2 (
1101+ & spl_token_2022:: id ( ) ,
1102+ & mint. pubkey ( ) , // mint
1103+ & fee_payer. pubkey ( ) , // mint authority
1104+ Some ( & fee_payer. pubkey ( ) ) , // freeze authority
1105+ 2 , // decimals
1106+ )
1107+ . unwrap ( ) ;
1108+
1109+ // Calculate the associated token account address for fee_payer
1110+ let source_token_address = get_associated_token_address_with_program_id (
1111+ & fee_payer. pubkey ( ) , // owner
1112+ & mint. pubkey ( ) , // mint
1113+ & spl_token_2022:: id ( ) , // program_id
1114+ ) ;
1115+
1116+ // Instruction to create associated token account for fee_payer
1117+ let create_source_ata_instruction = create_associated_token_account (
1118+ & fee_payer. pubkey ( ) , // funding address
1119+ & fee_payer. pubkey ( ) , // wallet address
1120+ & mint. pubkey ( ) , // mint address
1121+ & spl_token_2022:: id ( ) , // program id
1122+ ) ;
1123+
1124+ // Calculate the associated token account address for recipient
1125+ let destination_token_address = get_associated_token_address_with_program_id (
1126+ & recipient. pubkey ( ) , // owner
1127+ & mint. pubkey ( ) , // mint
1128+ & spl_token_2022:: id ( ) , // program_id
1129+ ) ;
1130+
1131+ // Instruction to create associated token account for recipient
1132+ let create_destination_ata_instruction = create_associated_token_account (
1133+ & fee_payer. pubkey ( ) , // funding address
1134+ & recipient. pubkey ( ) , // wallet address
1135+ & mint. pubkey ( ) , // mint address
1136+ & spl_token_2022:: id ( ) , // program id
1137+ ) ;
1138+
1139+ // Amount of tokens to mint (100 tokens with 2 decimal places)
1140+ let amount = 100_00 ;
1141+
1142+ // Create mint_to instruction to mint tokens to the source token account
1143+ let mint_to_instruction = mint_to (
1144+ & spl_token_2022:: id ( ) ,
1145+ & mint. pubkey ( ) , // mint
1146+ & source_token_address, // destination
1147+ & fee_payer. pubkey ( ) , // authority
1148+ & [ & fee_payer. pubkey ( ) ] , // signer
1149+ amount, // amount
1150+ )
1151+ . unwrap ( ) ;
1152+
1153+ // Create transaction and add instructions
1154+ let transaction = Transaction :: new_signed_with_payer (
1155+ & [
1156+ create_account_instruction,
1157+ initialize_mint_instruction,
1158+ create_source_ata_instruction,
1159+ create_destination_ata_instruction,
1160+ mint_to_instruction,
1161+ ] ,
1162+ Some ( & fee_payer. pubkey ( ) ) ,
1163+ & [ & fee_payer, & mint] ,
1164+ recent_blockhash,
1165+ ) ;
1166+
1167+ // Send and confirm transaction
1168+ client. context . svm_locker . with_svm_writer ( |svm_writer| {
1169+ svm_writer
1170+ . send_transaction ( transaction. clone ( ) . into ( ) , false )
1171+ . unwrap ( ) ;
1172+ } ) ;
1173+
1174+ println ! ( "Mint Address: {}" , mint. pubkey( ) ) ;
1175+ println ! ( "Recipient Address: {}" , recipient. pubkey( ) ) ;
1176+ println ! ( "Source Token Account Address: {}" , source_token_address) ;
1177+ println ! (
1178+ "Destination Token Account Address: {}" ,
1179+ destination_token_address
1180+ ) ;
1181+ println ! ( "Minted {} tokens to the source token account" , amount) ;
1182+
1183+ // Get the latest blockhash for the transfer transaction
1184+ let recent_blockhash = client
1185+ . context
1186+ . svm_locker
1187+ . with_svm_reader ( |svm_reader| svm_reader. latest_blockhash ( ) ) ;
1188+
1189+ // Amount of tokens to transfer (0.50 tokens with 2 decimals)
1190+ let transfer_amount = 50 ;
1191+
1192+ // Create transfer_checked instruction to send tokens from source to destination
1193+ let transfer_instruction = transfer_checked (
1194+ & spl_token_2022:: id ( ) , // program id
1195+ & source_token_address, // source
1196+ & mint. pubkey ( ) , // mint
1197+ & destination_token_address, // destination
1198+ & fee_payer. pubkey ( ) , // owner of source
1199+ & [ & fee_payer. pubkey ( ) ] , // signers
1200+ transfer_amount, // amount
1201+ 2 , // decimals
1202+ )
1203+ . unwrap ( ) ;
1204+
1205+ // Create transaction for transferring tokens
1206+ let transaction = Transaction :: new_signed_with_payer (
1207+ & [ transfer_instruction] ,
1208+ Some ( & fee_payer. pubkey ( ) ) ,
1209+ & [ & fee_payer] ,
1210+ recent_blockhash,
1211+ ) ;
1212+
1213+ // Send and confirm transaction
1214+ client. context . svm_locker . with_svm_writer ( |svm_writer| {
1215+ svm_writer
1216+ . send_transaction ( transaction. clone ( ) . into ( ) , false )
1217+ . unwrap ( ) ;
1218+ } ) ;
1219+
1220+ println ! ( "Successfully transferred 0.50 tokens from sender to recipient" ) ;
1221+
1222+ // Get token account balances to verify the transfer
1223+ let source_token_account = client
1224+ . context
1225+ . svm_locker
1226+ . get_account_local ( & source_token_address)
1227+ . inner ;
1228+ let destination_token_account = client
1229+ . context
1230+ . svm_locker
1231+ . get_account_local ( & destination_token_address)
1232+ . inner ;
1233+
1234+ if let GetAccountResult :: FoundAccount ( _, source_account, _) = source_token_account {
1235+ let unpacked =
1236+ StateWithExtensions :: < Token2022Account > :: unpack ( & source_account. data ) . unwrap ( ) ;
1237+ println ! (
1238+ "Source Token Account Balance: {} tokens" ,
1239+ unpacked. base. amount
1240+ ) ;
1241+ assert_eq ! ( unpacked. base. amount, 9950 ) ;
1242+ }
1243+
1244+ if let GetAccountResult :: FoundAccount ( _, destination_account, _) = destination_token_account
1245+ {
1246+ let unpacked =
1247+ StateWithExtensions :: < Token2022Account > :: unpack ( & destination_account. data ) . unwrap ( ) ;
1248+ println ! (
1249+ "Destination Token Account Balance: {} tokens" ,
1250+ unpacked. base. amount
1251+ ) ;
1252+ assert_eq ! ( unpacked. base. amount, 50 ) ;
1253+ }
1254+ }
10221255}
0 commit comments