@@ -3,7 +3,14 @@ import {
33 SOLANA_ERROR__TRANSACTION_ERROR__BLOCKHASH_NOT_FOUND ,
44 SolanaError ,
55} from '@solana/errors' ;
6- import { blockhash , signature as toSignature } from '@solana/kit' ;
6+ import {
7+ AccountRole ,
8+ address ,
9+ blockhash ,
10+ getBase58Encoder ,
11+ getCompiledTransactionMessageDecoder ,
12+ signature as toSignature ,
13+ } from '@solana/kit' ;
714import chai , { expect } from 'chai' ;
815import chaiAsPromised from 'chai-as-promised' ;
916import { afterEach , describe , it } from 'mocha' ;
@@ -776,4 +783,80 @@ describe('SvmSigner', () => {
776783 expect ( sendTxCalls ) . to . equal ( 1 ) ;
777784 } ) ;
778785 } ) ;
786+
787+ // ---- Fee payer derivation ----
788+
789+ // Deterministic fake addresses (valid base58, 32 bytes)
790+ const OWNER_ADDRESS = address ( '11111111111111111111111111111112' ) ;
791+ const SQUADS_VAULT_ADDRESS = address (
792+ 'zUeFx6cfxedG2JnFtMKkTXnxgPa5M44tyaF9RrPunCp' ,
793+ ) ;
794+ const PROGRAM_ADDRESS = address ( '11111111111111111111111111111113' ) ;
795+ const TOKEN_PDA_ADDRESS = address ( '11111111111111111111111111111114' ) ;
796+
797+ const base58Encoder = getBase58Encoder ( ) ;
798+ const messageDecoder = getCompiledTransactionMessageDecoder ( ) ;
799+
800+ /** Decode a base58-encoded compiled message and return the fee payer (first static account). */
801+ function feePayerFromMessageBase58 ( messageBase58 : string ) : string {
802+ const bytes = base58Encoder . encode ( messageBase58 ) ;
803+ const decoded = messageDecoder . decode ( bytes ) ;
804+ return decoded . staticAccounts [ 0 ] ;
805+ }
806+
807+ describe ( 'transactionToPrintableJson — fee payer derivation' , ( ) => {
808+ it ( 'uses explicit feePayer instead of local signer' , async ( ) => {
809+ const rpc = createMockRpc ( ) ;
810+ const signer = await createTestSigner ( rpc ) ;
811+ const signerAddress = signer . getSignerAddress ( ) ;
812+
813+ // feePayer differs from both the local signer and instruction accounts
814+ expect ( signerAddress ) . to . not . equal ( SQUADS_VAULT_ADDRESS ) ;
815+ expect ( OWNER_ADDRESS ) . to . not . equal ( SQUADS_VAULT_ADDRESS ) ;
816+
817+ const tx : SvmTransaction = {
818+ feePayer : SQUADS_VAULT_ADDRESS ,
819+ instructions : [
820+ {
821+ programAddress : PROGRAM_ADDRESS ,
822+ accounts : [
823+ { address : TOKEN_PDA_ADDRESS , role : AccountRole . WRITABLE } ,
824+ { address : OWNER_ADDRESS , role : AccountRole . READONLY_SIGNER } ,
825+ ] ,
826+ data : new Uint8Array ( [ 0 ] ) ,
827+ } ,
828+ ] ,
829+ } ;
830+
831+ const json = await signer . transactionToPrintableJson ( tx ) ;
832+
833+ expect ( feePayerFromMessageBase58 ( json . message_base58 ) ) . to . equal (
834+ SQUADS_VAULT_ADDRESS ,
835+ ) ;
836+ } ) ;
837+
838+ it ( 'falls back to local signer when instructions have no signers' , async ( ) => {
839+ const rpc = createMockRpc ( ) ;
840+ const signer = await createTestSigner ( rpc ) ;
841+ const signerAddress = signer . getSignerAddress ( ) ;
842+
843+ const tx : SvmTransaction = {
844+ instructions : [
845+ {
846+ programAddress : PROGRAM_ADDRESS ,
847+ accounts : [
848+ { address : TOKEN_PDA_ADDRESS , role : AccountRole . WRITABLE } ,
849+ ] ,
850+ data : new Uint8Array ( [ 0 ] ) ,
851+ } ,
852+ ] ,
853+ } ;
854+
855+ const json = await signer . transactionToPrintableJson ( tx ) ;
856+
857+ expect ( feePayerFromMessageBase58 ( json . message_base58 ) ) . to . equal (
858+ signerAddress ,
859+ ) ;
860+ } ) ;
861+ } ) ;
779862} ) ;
0 commit comments