diff --git a/crates/lib/src/rpc_server/method/transfer_transaction.rs b/crates/lib/src/rpc_server/method/transfer_transaction.rs index 5409fa9c..cb72e9b2 100644 --- a/crates/lib/src/rpc_server/method/transfer_transaction.rs +++ b/crates/lib/src/rpc_server/method/transfer_transaction.rs @@ -11,42 +11,52 @@ use utoipa::ToSchema; use crate::{ constant::NATIVE_SOL, state::{get_config, get_request_signer_with_signer_key}, - transaction::{ - TransactionUtil, VersionedMessageExt, VersionedTransactionOps, VersionedTransactionResolved, - }, + transaction::{TransactionUtil, VersionedMessageExt}, validator::transaction_validator::TransactionValidator, CacheUtil, KoraError, }; +/// **DEPRECATED**: Use `getPaymentInstruction` instead for fee payment flows. +/// This endpoint will be removed in a future version. #[derive(Debug, Deserialize, ToSchema)] pub struct TransferTransactionRequest { pub amount: u64, pub token: String, + /// The source wallet address pub source: String, + /// The destination wallet address pub destination: String, - /// Optional signer signer_key to ensure consistency across related RPC calls + /// Optional signer key to ensure consistency across related RPC calls #[serde(default, skip_serializing_if = "Option::is_none")] pub signer_key: Option, } +/// **DEPRECATED**: Use `getPaymentInstruction` instead for fee payment flows. #[derive(Debug, Serialize, ToSchema)] pub struct TransferTransactionResponse { + /// Unsigned base64-encoded transaction pub transaction: String, + /// Unsigned base64-encoded message pub message: String, pub blockhash: String, /// Public key of the signer used (for client consistency) pub signer_pubkey: String, } +/// **DEPRECATED**: Use `getPaymentInstruction` instead for fee payment flows. +/// +/// Creates an unsigned transfer transaction from source to destination. +/// Kora is the fee payer but does NOT sign - user must sign before submitting. +#[deprecated(since = "2.2.0", note = "Use getPaymentInstruction instead for fee payment flows")] pub async fn transfer_transaction( rpc_client: &Arc, request: TransferTransactionRequest, ) -> Result { let signer = get_request_signer_with_signer_key(request.signer_key.as_deref())?; let config = get_config()?; - let fee_payer = signer.pubkey(); + let signer_pubkey = signer.pubkey(); - let validator = TransactionValidator::new(config, fee_payer)?; + let validator = TransactionValidator::new(config, signer_pubkey)?; let source = Pubkey::from_str(&request.source) .map_err(|e| KoraError::ValidationError(format!("Invalid source address: {e}")))?; @@ -55,13 +65,12 @@ pub async fn transfer_transaction( let token_mint = Pubkey::from_str(&request.token) .map_err(|e| KoraError::ValidationError(format!("Invalid token address: {e}")))?; - // manually check disallowed account because we're creating the message + // Check source and destination are not disallowed if validator.is_disallowed_account(&source) { return Err(KoraError::InvalidTransaction(format!( "Source account {source} is disallowed" ))); } - if validator.is_disallowed_account(&destination) { return Err(KoraError::InvalidTransaction(format!( "Destination account {destination} is disallowed" @@ -88,9 +97,10 @@ pub async fn transfer_transaction( .await .map_err(|_| KoraError::AccountNotFound(source_ata.to_string()))?; + // Create ATA for destination if it doesn't exist (Kora pays for ATA creation) if CacheUtil::get_account(config, rpc_client, &dest_ata, false).await.is_err() { instructions.push(token_program.create_associated_token_account_instruction( - &fee_payer, + &signer_pubkey, // Kora pays for ATA creation &destination, &token_mint.address(), )); @@ -119,36 +129,19 @@ pub async fn transfer_transaction( let message = VersionedMessage::Legacy(Message::new_with_blockhash( &instructions, - Some(&fee_payer), + Some(&signer_pubkey), // Kora as fee payer &blockhash.0, )); let transaction = TransactionUtil::new_unsigned_versioned_transaction(message); - let mut resolved_transaction = - VersionedTransactionResolved::from_kora_built_transaction(&transaction)?; - - // validate transaction before signing - validator.validate_transaction(config, &mut resolved_transaction, rpc_client).await?; - - // Find the fee payer position in the account keys - let fee_payer_position = resolved_transaction.find_signer_position(&fee_payer)?; - - let message_bytes = resolved_transaction.transaction.message.serialize(); - let signature = signer - .sign_message(&message_bytes) - .await - .map_err(|e| KoraError::SigningError(e.to_string()))?; - - resolved_transaction.transaction.signatures[fee_payer_position] = signature; - - let encoded = resolved_transaction.encode_b64_transaction()?; + let encoded = TransactionUtil::encode_versioned_transaction(&transaction)?; let message_encoded = transaction.message.encode_b64_message()?; Ok(TransferTransactionResponse { transaction: encoded, message: message_encoded, blockhash: blockhash.0.to_string(), - signer_pubkey: fee_payer.to_string(), + signer_pubkey: signer_pubkey.to_string(), }) } @@ -164,6 +157,7 @@ mod tests { }; #[tokio::test] + #[allow(deprecated)] async fn test_transfer_transaction_invalid_source() { let config = ConfigMockBuilder::new().build(); update_config(config).unwrap(); @@ -193,6 +187,7 @@ mod tests { } #[tokio::test] + #[allow(deprecated)] async fn test_transfer_transaction_invalid_destination() { let config = ConfigMockBuilder::new().build(); update_config(config).unwrap(); @@ -204,7 +199,7 @@ mod tests { amount: 1000, token: Pubkey::new_unique().to_string(), source: Pubkey::new_unique().to_string(), - destination: "invalid_pubkey".to_string(), + destination: "invalid".to_string(), signer_key: None, }; @@ -221,6 +216,7 @@ mod tests { } #[tokio::test] + #[allow(deprecated)] async fn test_transfer_transaction_invalid_token() { let config = ConfigMockBuilder::new().build(); update_config(config).unwrap(); diff --git a/crates/lib/src/rpc_server/rpc.rs b/crates/lib/src/rpc_server/rpc.rs index e10cdbca..da371910 100644 --- a/crates/lib/src/rpc_server/rpc.rs +++ b/crates/lib/src/rpc_server/rpc.rs @@ -9,6 +9,7 @@ use utoipa::{ ToSchema, }; +#[allow(deprecated)] use crate::rpc_server::method::{ estimate_transaction_fee::{ estimate_transaction_fee, EstimateTransactionFeeRequest, EstimateTransactionFeeResponse, @@ -98,11 +99,13 @@ impl KoraRpc { result } + #[deprecated(since = "2.2.0", note = "Use getPaymentInstruction instead for fee payment flows")] pub async fn transfer_transaction( &self, request: TransferTransactionRequest, ) -> Result { info!("Transfer transaction request: {request:?}"); + #[allow(deprecated)] let result = transfer_transaction(&self.rpc_client, request).await; info!("Transfer transaction response: {result:?}"); result diff --git a/crates/lib/src/rpc_server/server.rs b/crates/lib/src/rpc_server/server.rs index 4d6020b3..0678205b 100644 --- a/crates/lib/src/rpc_server/server.rs +++ b/crates/lib/src/rpc_server/server.rs @@ -127,10 +127,12 @@ macro_rules! register_method_if_enabled { // For methods with parameters ($module:expr, $enabled_methods:expr, $field:ident, $method_name:expr, $rpc_method:ident, with_params) => { if $enabled_methods.$field { + #[allow(deprecated)] let _ = $module.register_async_method($method_name, |rpc_params, rpc_context| async move { let rpc = rpc_context.as_ref(); let params = rpc_params.parse()?; + #[allow(deprecated)] rpc.$rpc_method(params).await.map_err(Into::into) }); } diff --git a/sdks/ts/src/client.ts b/sdks/ts/src/client.ts index 814040eb..686537dd 100644 --- a/sdks/ts/src/client.ts +++ b/sdks/ts/src/client.ts @@ -20,8 +20,8 @@ import { GetPaymentInstructionResponse, } from './types/index.js'; import crypto from 'crypto'; -import { getInstructionsFromBase64Message } from './utils/transaction.js'; import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS, getTransferInstruction } from '@solana-program/token'; +import { getInstructionsFromBase64Message } from './utils/transaction.js'; /** * Kora RPC client for interacting with the Kora paymaster service. @@ -248,13 +248,17 @@ export class KoraClient { } /** - * Creates a token transfer transaction with Kora as the fee payer. + * Creates an unsigned transfer transaction. + * + * @deprecated Use `getPaymentInstruction` instead for fee payment flows. + * * @param request - Transfer request parameters * @param request.amount - Amount to transfer (in token's smallest unit) * @param request.token - Mint address of the token to transfer * @param request.source - Source wallet public key * @param request.destination - Destination wallet public key - * @returns Base64-encoded signed transaction, base64-encoded message, blockhash, and parsed instructions + * @param request.signer_key - Optional signer key to select specific Kora signer + * @returns Unsigned transaction, message, blockhash, and signer info * @throws {Error} When the RPC call fails or token is not supported * * @example @@ -263,11 +267,9 @@ export class KoraClient { * amount: 1000000, // 1 USDC (6 decimals) * token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', * source: 'sourceWalletPublicKey', - * destination: 'destinationWalletPublicKey' + * destination: 'destinationWalletPublicKey', * }); - * console.log('Transaction:', transfer.transaction); - * console.log('Message:', transfer.message); - * console.log('Instructions:', transfer.instructions); + * console.log('Signer:', transfer.signer_pubkey); * ``` */ async transferTransaction(request: TransferTransactionRequest): Promise { diff --git a/sdks/ts/src/types/index.ts b/sdks/ts/src/types/index.ts index 50bdecbf..de3aa785 100644 --- a/sdks/ts/src/types/index.ts +++ b/sdks/ts/src/types/index.ts @@ -5,7 +5,8 @@ import { Instruction } from '@solana/kit'; */ /** - * Parameters for creating a token transfer transaction. + * Parameters for creating a transfer transaction. + * @deprecated Use `getPaymentInstruction` instead for fee payment flows. */ export interface TransferTransactionRequest { /** Amount to transfer in the token's smallest unit (e.g., lamports for SOL) */ @@ -16,7 +17,7 @@ export interface TransferTransactionRequest { source: string; /** Public key of the destination wallet (not token account) */ destination: string; - /** Optional signer address for the transaction */ + /** Optional signer key to select a specific Kora signer */ signer_key?: string; } @@ -82,15 +83,17 @@ export interface GetPaymentInstructionRequest { /** * Response from creating a transfer transaction. + * The transaction is unsigned. + * @deprecated Use `getPaymentInstruction` instead for fee payment flows. */ export interface TransferTransactionResponse { - /** Base64-encoded signed transaction */ + /** Base64-encoded unsigned transaction */ transaction: string; - /** Base64-encoded message */ + /** Base64-encoded unsigned message */ message: string; /** Recent blockhash used in the transaction */ blockhash: string; - /** Public key of the signer used to send the transaction */ + /** Public key of the Kora signer (fee payer) */ signer_pubkey: string; /** Parsed instructions from the transaction message */ instructions: Instruction[]; diff --git a/sdks/ts/test/integration.test.ts b/sdks/ts/test/integration.test.ts index 113fdc8a..37437f57 100644 --- a/sdks/ts/test/integration.test.ts +++ b/sdks/ts/test/integration.test.ts @@ -3,15 +3,15 @@ import setupTestSuite from './setup.js'; import { runAuthenticationTests } from './auth-setup.js'; import { Address, - generateKeyPairSigner, - getBase64EncodedWireTransaction, + getBase64Decoder, getBase64Encoder, getTransactionDecoder, - signTransaction, + getTransactionEncoder, + partiallySignTransaction, type KeyPairSigner, type Transaction, } from '@solana/kit'; -import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token'; +import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token'; function transactionFromBase64(base64: string): Transaction { const encoder = getBase64Encoder(); @@ -20,26 +20,29 @@ function transactionFromBase64(base64: string): Transaction { return decoder.decode(messageBytes); } +function transactionToBase64(transaction: Transaction): string { + const txEncoder = getTransactionEncoder(); + const txBytes = txEncoder.encode(transaction); + const base64Decoder = getBase64Decoder(); + return base64Decoder.decode(txBytes); +} + const AUTH_ENABLED = process.env.ENABLE_AUTH === 'true'; const KORA_SIGNER_TYPE = process.env.KORA_SIGNER_TYPE || 'memory'; describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without auth'} | signer type: ${KORA_SIGNER_TYPE})`, () => { let client: KoraClient; let testWallet: KeyPairSigner; let testWalletAddress: Address; - let destinationAddress: Address; let usdcMint: Address; let koraAddress: Address; - let koraRpcUrl: string; beforeAll(async () => { const testSuite = await setupTestSuite(); client = testSuite.koraClient; testWallet = testSuite.testWallet; testWalletAddress = testWallet.address; - destinationAddress = testSuite.destinationAddress; usdcMint = testSuite.usdcMint; koraAddress = testSuite.koraAddress; - koraRpcUrl = testSuite.koraRpcUrl; }, 90000); // allow adequate time for airdrops and token initialization // Run authentication tests only when auth is enabled @@ -145,12 +148,12 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without }); describe('Transaction Operations', () => { - it('should create transfer transaction', async () => { + it('should create transfer transaction (DEPRECATED endpoint)', async () => { const request = { amount: 1000000, // 1 USDC token: usdcMint, source: testWalletAddress, - destination: destinationAddress, + destination: koraAddress, // user specifies destination }; const response = await client.transferTransaction(request); @@ -158,30 +161,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without expect(response.transaction).toBeDefined(); expect(response.blockhash).toBeDefined(); expect(response.message).toBeDefined(); - expect(response.instructions).toBeDefined(); - // since setup created ATA for destination, we should not expect ata instruction, only transfer instruction - expect(response.instructions?.length).toBe(1); - expect(response.instructions?.[0].programAddress).toBe(TOKEN_PROGRAM_ADDRESS); - }); - it('should create transfer transaction to address with no ATA', async () => { - const randomDestination = await generateKeyPairSigner(); - const request = { - amount: 1000000, // 1 USDC - token: usdcMint, - source: testWalletAddress, - destination: randomDestination.address, - }; - - const response = await client.transferTransaction(request); - expect(response).toBeDefined(); - expect(response.transaction).toBeDefined(); - expect(response.blockhash).toBeDefined(); - expect(response.message).toBeDefined(); - expect(response.instructions).toBeDefined(); - // since setup created ATA for destination, we should not expect ata instruction, only transfer instruction - expect(response.instructions?.length).toBe(2); - expect(response.instructions?.[0].programAddress).toBe(ASSOCIATED_TOKEN_PROGRAM_ADDRESS); - expect(response.instructions?.[1].programAddress).toBe(TOKEN_PROGRAM_ADDRESS); + expect(response.signer_pubkey).toBeDefined(); }); it('should estimate transaction fee', async () => { @@ -190,7 +170,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without amount: 1000000, token: usdcMint, source: testWalletAddress, - destination: testWalletAddress, + destination: koraAddress, }; const { transaction } = await client.transferTransaction(transferRequest); @@ -204,13 +184,11 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without }); it('should sign transaction', async () => { - const config = await client.getConfig(); - const paymentAddress = config.fee_payers[0]; const transferRequest = { amount: 1000000, token: usdcMint, source: testWalletAddress, - destination: paymentAddress, + destination: koraAddress, }; const { transaction } = await client.transferTransaction(transferRequest); @@ -224,21 +202,20 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without }); it('should sign and send transaction', async () => { - const config = await client.getConfig(); - const paymentAddress = config.fee_payers[0]; const transferRequest = { amount: 1000000, token: usdcMint, source: testWalletAddress, - destination: paymentAddress, + destination: koraAddress, }; const { transaction: transactionString } = await client.transferTransaction(transferRequest); const transaction = transactionFromBase64(transactionString); - // Sign transaction with test wallet before sending - const signedTransaction = await signTransaction([testWallet.keyPair], transaction); - const base64SignedTransaction = getBase64EncodedWireTransaction(signedTransaction); + // Partially sign transaction with test wallet before sending + // Kora will add fee payer signature via signAndSendTransaction + const signedTransaction = await partiallySignTransaction([testWallet.keyPair], transaction); + const base64SignedTransaction = transactionToBase64(signedTransaction); const signResult = await client.signAndSendTransaction({ transaction: base64SignedTransaction, }); @@ -252,7 +229,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without amount: 1000000, token: usdcMint, source: testWalletAddress, - destination: destinationAddress, + destination: koraAddress, }; const [expectedSenderAta] = await findAssociatedTokenPda({ owner: testWalletAddress, @@ -298,7 +275,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without amount: 1000000, token: 'InvalidTokenAddress', source: testWalletAddress, - destination: destinationAddress, + destination: koraAddress, }; await expect(client.transferTransaction(request)).rejects.toThrow(); @@ -309,7 +286,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without amount: -1, // Invalid amount token: usdcMint, source: testWalletAddress, - destination: destinationAddress, + destination: koraAddress, }; await expect(client.transferTransaction(request)).rejects.toThrow(); @@ -334,7 +311,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without amount: 1000000, token: usdcMint, source: testWalletAddress, - destination: destinationAddress, + destination: koraAddress, }; // TODO: API has an error. this endpoint should verify the provided fee token is supported @@ -348,13 +325,11 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without describe('End-to-End Flows', () => { it('should handle transfer and sign flow', async () => { - const config = await client.getConfig(); - const paymentAddress = config.fee_payers[0]; const request = { amount: 1000000, token: usdcMint, source: testWalletAddress, - destination: paymentAddress, + destination: koraAddress, }; // Create and sign the transaction @@ -370,7 +345,7 @@ describe(`KoraClient Integration Tests (${AUTH_ENABLED ? 'with auth' : 'without amount: 1000000, token: invalidTokenMint, source: testWalletAddress, - destination: destinationAddress, + destination: koraAddress, }; await expect(client.transferTransaction(request)).rejects.toThrow(); diff --git a/sdks/ts/test/unit.test.ts b/sdks/ts/test/unit.test.ts index 6c8cced0..6be4a03f 100644 --- a/sdks/ts/test/unit.test.ts +++ b/sdks/ts/test/unit.test.ts @@ -294,7 +294,7 @@ describe('KoraClient Unit Tests', () => { }); }); - describe('transferTransaction', () => { + describe('transferTransaction (DEPRECATED)', () => { it('should create transfer transaction', async () => { const request: TransferTransactionRequest = { amount: 1000000, @@ -317,61 +317,6 @@ describe('KoraClient Unit Tests', () => { request, ); }); - - it('should parse instructions from transfer transaction message', async () => { - const request: TransferTransactionRequest = { - amount: 1000000, - token: 'SOL', - source: 'source_address', - destination: 'destination_address', - }; - - // This is a real base64 encoded message for testing - // In production, this would come from the RPC response - const mockMessage = - 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDAAEMAgAAAAEAAAAAAAAA'; - - const mockResponse: TransferTransactionResponse = { - transaction: 'base64_encoded_transaction', - message: mockMessage, - blockhash: 'test_blockhash', - signer_pubkey: 'test_signer_pubkey', - instructions: [], - }; - - mockSuccessfulResponse(mockResponse); - - const result = await client.transferTransaction(request); - - expect(result.instructions).toBeDefined(); - expect(Array.isArray(result.instructions)).toBe(true); - // The instructions array should be populated from the parsed message - expect(result.instructions).not.toBeNull(); - }); - - it('should handle transfer transaction with empty message gracefully', async () => { - const request: TransferTransactionRequest = { - amount: 1000000, - token: 'SOL', - source: 'source_address', - destination: 'destination_address', - }; - - const mockResponse: TransferTransactionResponse = { - transaction: 'base64_encoded_transaction', - message: '', - blockhash: 'test_blockhash', - signer_pubkey: 'test_signer_pubkey', - instructions: [], - }; - - mockSuccessfulResponse(mockResponse); - - const result = await client.transferTransaction(request); - - // Should handle empty message gracefully - expect(result.instructions).toEqual([]); - }); }); describe('getPaymentInstruction', () => { const mockConfig: Config = { diff --git a/tests/multi_signer/signer_management.rs b/tests/multi_signer/signer_management.rs index 54f4f7e0..78c177ec 100644 --- a/tests/multi_signer/signer_management.rs +++ b/tests/multi_signer/signer_management.rs @@ -88,7 +88,7 @@ async fn test_signer_key_consistency() { // Verify the same signer was used assert_eq!(estimate_signer, first_signer_pubkey, "Estimate should use signer keyed signer"); - // Call transferTransaction with the same signer key + // Call transferTransaction with the same signer key (DEPRECATED endpoint) let transfer_response: serde_json::Value = ctx .rpc_call( "transferTransaction", @@ -97,7 +97,7 @@ async fn test_signer_key_consistency() { "11111111111111111111111111111111", // Native SOL SenderTestHelper::get_test_sender_keypair().pubkey().to_string(), RecipientTestHelper::get_recipient_pubkey().to_string(), - &first_signer_pubkey + &first_signer_pubkey // signer_key ], ) .await diff --git a/tests/rpc/transfers.rs b/tests/rpc/transfers.rs index 8598e0d9..bfe97742 100644 --- a/tests/rpc/transfers.rs +++ b/tests/rpc/transfers.rs @@ -1,9 +1,9 @@ use crate::common::*; use jsonrpsee::rpc_params; use kora_lib::transaction::TransactionUtil; -use solana_sdk::signature::{Keypair, Signer}; +use solana_sdk::signature::Signer; -/// Test transferTransaction with SPL token transfer +/// Test transferTransaction with SPL token transfer (DEPRECATED endpoint) #[tokio::test] async fn test_transfer_transaction_spl_token_legacy() { let ctx = TestContext::new().await.expect("Failed to create test context"); @@ -27,22 +27,22 @@ async fn test_transfer_transaction_spl_token_legacy() { response.assert_success(); - // transferTransaction returns unsigned transaction data, not a signed transaction + // transferTransaction returns unsigned transaction data assert!(response["transaction"].as_str().is_some(), "Expected transaction in response"); assert!(response["message"].as_str().is_some(), "Expected message in response"); assert!(response["blockhash"].as_str().is_some(), "Expected blockhash in response"); + assert!(response["signer_pubkey"].as_str().is_some(), "Expected signer_pubkey in response"); } -/// Test transfer transaction with automatic ATA creation +/// Test transfer transaction returns valid unsigned transaction (DEPRECATED endpoint) +/// Note: ATA creation for destination is handled automatically by Kora #[tokio::test] async fn test_transfer_transaction_with_ata_legacy() { let ctx = TestContext::new().await.expect("Failed to create test context"); - let rpc_client = ctx.rpc_client(); - let random_keypair = Keypair::new(); - let random_pubkey = random_keypair.pubkey(); - let sender = SenderTestHelper::get_test_sender_keypair(); + let recipient = RecipientTestHelper::get_recipient_pubkey(); + let response: serde_json::Value = ctx .rpc_call( "transferTransaction", @@ -50,7 +50,7 @@ async fn test_transfer_transaction_with_ata_legacy() { 10, &USDCMintTestHelper::get_test_usdc_mint_pubkey().to_string(), sender.pubkey().to_string(), - random_pubkey.to_string() + recipient.to_string() ], ) .await @@ -60,15 +60,10 @@ async fn test_transfer_transaction_with_ata_legacy() { assert!(response["transaction"].as_str().is_some(), "Expected transaction in response"); assert!(response["message"].as_str().is_some(), "Expected message in response"); assert!(response["blockhash"].as_str().is_some(), "Expected blockhash in response"); + assert!(response["signer_pubkey"].as_str().is_some(), "Expected signer_pubkey in response"); + // Verify we can decode the unsigned transaction let transaction_string = response["transaction"].as_str().unwrap(); - let transaction = TransactionUtil::decode_b64_transaction(transaction_string) + let _transaction = TransactionUtil::decode_b64_transaction(transaction_string) .expect("Failed to decode transaction from base64"); - - let simulated_tx = rpc_client - .simulate_transaction(&transaction) - .await - .expect("Failed to simulate transaction"); - - assert!(simulated_tx.value.err.is_none(), "Transaction simulation failed"); } diff --git a/tests/tokens/token_2022_test.rs b/tests/tokens/token_2022_test.rs index 9c7a11f5..683767a6 100644 --- a/tests/tokens/token_2022_test.rs +++ b/tests/tokens/token_2022_test.rs @@ -3,16 +3,13 @@ use std::str::FromStr; use crate::common::*; use jsonrpsee::rpc_params; use kora_lib::transaction::TransactionUtil; -use solana_sdk::{ - pubkey::Pubkey, - signature::{Keypair, Signer}, -}; +use solana_sdk::{pubkey::Pubkey, signature::Signer}; // ************************************************************************************** // Token 2022 Transfer Tests // ************************************************************************************** -/// Test transferTransaction with Token 2022 transfer +/// Test transferTransaction with Token 2022 transfer (DEPRECATED endpoint) #[tokio::test] async fn test_transfer_transaction_token_2022_legacy() { let ctx = TestContext::new().await.expect("Failed to create test context"); @@ -36,22 +33,22 @@ async fn test_transfer_transaction_token_2022_legacy() { response.assert_success(); - // transferTransaction returns unsigned transaction data, not a signed transaction + // transferTransaction returns unsigned transaction data assert!(response["transaction"].as_str().is_some(), "Expected transaction in response"); assert!(response["message"].as_str().is_some(), "Expected message in response"); assert!(response["blockhash"].as_str().is_some(), "Expected blockhash in response"); + assert!(response["signer_pubkey"].as_str().is_some(), "Expected signer_pubkey in response"); } -/// Test Token 2022 transfer transaction with automatic ATA creation +/// Test Token 2022 transfer transaction returns valid unsigned transaction (DEPRECATED endpoint) +/// Note: ATA creation for destination is handled automatically by Kora #[tokio::test] async fn test_transfer_transaction_token_2022_with_ata_legacy() { let ctx = TestContext::new().await.expect("Failed to create test context"); - let rpc_client = ctx.rpc_client(); - let random_keypair = Keypair::new(); - let random_pubkey = random_keypair.pubkey(); - let sender = SenderTestHelper::get_test_sender_keypair(); + let recipient = RecipientTestHelper::get_recipient_pubkey(); + let response: serde_json::Value = ctx .rpc_call( "transferTransaction", @@ -59,7 +56,7 @@ async fn test_transfer_transaction_token_2022_with_ata_legacy() { 10, &USDCMint2022TestHelper::get_test_usdc_mint_2022_pubkey().to_string(), sender.pubkey().to_string(), - random_pubkey.to_string() + recipient.to_string() ], ) .await @@ -69,17 +66,12 @@ async fn test_transfer_transaction_token_2022_with_ata_legacy() { assert!(response["transaction"].as_str().is_some(), "Expected transaction in response"); assert!(response["message"].as_str().is_some(), "Expected message in response"); assert!(response["blockhash"].as_str().is_some(), "Expected blockhash in response"); + assert!(response["signer_pubkey"].as_str().is_some(), "Expected signer_pubkey in response"); + // Verify we can decode the unsigned transaction let transaction_string = response["transaction"].as_str().unwrap(); - let transaction = TransactionUtil::decode_b64_transaction(transaction_string) + let _transaction = TransactionUtil::decode_b64_transaction(transaction_string) .expect("Failed to decode transaction from base64"); - - let simulated_tx = rpc_client - .simulate_transaction(&transaction) - .await - .expect("Failed to simulate Token 2022 transaction"); - - assert!(simulated_tx.value.err.is_none(), "Token 2022 transaction simulation failed"); } // **************************************************************************************