@@ -8,9 +8,11 @@ import { TransactionBuilder } from './transactionBuilder';
88import { validateAddress } from './utils' ;
99
1010export class CloseAtaBuilder extends TransactionBuilder {
11- protected _accountAddress : string ;
12- protected _destinationAddress : string ;
13- protected _authorityAddress : string ;
11+ // Unified storage for all close entries (single or bulk)
12+ protected _closeAtaEntries : { accountAddress : string ; destinationAddress : string ; authorityAddress : string } [ ] = [ ] ;
13+
14+ // Track whether the legacy single-ATA API is being used
15+ private _usingSingleAtaApi = false ;
1416
1517 constructor ( _coinConfig : Readonly < CoinConfig > ) {
1618 super ( _coinConfig ) ;
@@ -21,21 +23,79 @@ export class CloseAtaBuilder extends TransactionBuilder {
2123 return TransactionType . CloseAssociatedTokenAccount ;
2224 }
2325
26+ /**
27+ * Sets the ATA account address to close (single-ATA API, backward compatible).
28+ * Cannot be mixed with addCloseAtaInstruction().
29+ */
2430 accountAddress ( accountAddress : string ) : this {
2531 validateAddress ( accountAddress , 'accountAddress' ) ;
26- this . _accountAddress = accountAddress ;
32+ this . _usingSingleAtaApi = true ;
33+ this . _ensureSingleEntry ( ) ;
34+ this . _closeAtaEntries [ 0 ] . accountAddress = accountAddress ;
2735 return this ;
2836 }
2937
38+ /**
39+ * Sets the destination address for rent SOL (single-ATA API, backward compatible).
40+ * Cannot be mixed with addCloseAtaInstruction().
41+ */
3042 destinationAddress ( destinationAddress : string ) : this {
3143 validateAddress ( destinationAddress , 'destinationAddress' ) ;
32- this . _destinationAddress = destinationAddress ;
44+ this . _usingSingleAtaApi = true ;
45+ this . _ensureSingleEntry ( ) ;
46+ this . _closeAtaEntries [ 0 ] . destinationAddress = destinationAddress ;
3347 return this ;
3448 }
3549
50+ /**
51+ * Sets the authority address / ATA owner (single-ATA API, backward compatible).
52+ * Cannot be mixed with addCloseAtaInstruction().
53+ */
3654 authorityAddress ( authorityAddress : string ) : this {
3755 validateAddress ( authorityAddress , 'authorityAddress' ) ;
38- this . _authorityAddress = authorityAddress ;
56+ this . _usingSingleAtaApi = true ;
57+ this . _ensureSingleEntry ( ) ;
58+ this . _closeAtaEntries [ 0 ] . authorityAddress = authorityAddress ;
59+ return this ;
60+ }
61+
62+ /**
63+ * Ensures a single entry exists in _closeAtaEntries for the legacy API.
64+ */
65+ private _ensureSingleEntry ( ) : void {
66+ if ( this . _closeAtaEntries . length === 0 ) {
67+ this . _closeAtaEntries . push ( { accountAddress : '' , destinationAddress : '' , authorityAddress : '' } ) ;
68+ }
69+ }
70+
71+ /**
72+ * Add an ATA to close in this transaction (for bulk closure).
73+ * Cannot be mixed with the single-ATA API (accountAddress/destinationAddress/authorityAddress).
74+ *
75+ * @param {string } accountAddress - the ATA address to close
76+ * @param {string } destinationAddress - where rent SOL goes (root wallet address)
77+ * @param {string } authorityAddress - ATA owner who must sign
78+ */
79+ addCloseAtaInstruction ( accountAddress : string , destinationAddress : string , authorityAddress : string ) : this {
80+ if ( this . _usingSingleAtaApi ) {
81+ throw new BuildTransactionError (
82+ 'Cannot mix addCloseAtaInstruction() with single-ATA API (accountAddress/destinationAddress/authorityAddress)'
83+ ) ;
84+ }
85+
86+ validateAddress ( accountAddress , 'accountAddress' ) ;
87+ validateAddress ( destinationAddress , 'destinationAddress' ) ;
88+ validateAddress ( authorityAddress , 'authorityAddress' ) ;
89+
90+ if ( accountAddress === destinationAddress ) {
91+ throw new BuildTransactionError ( 'Account address to close cannot be the same as the destination address' ) ;
92+ }
93+
94+ if ( this . _closeAtaEntries . some ( ( entry ) => entry . accountAddress === accountAddress ) ) {
95+ throw new BuildTransactionError ( 'Duplicate ATA address: ' + accountAddress ) ;
96+ }
97+
98+ this . _closeAtaEntries . push ( { accountAddress, destinationAddress, authorityAddress } ) ;
3999 return this ;
40100 }
41101
@@ -45,33 +105,39 @@ export class CloseAtaBuilder extends TransactionBuilder {
45105 for ( const instruction of this . _instructionsData ) {
46106 if ( instruction . type === InstructionBuilderTypes . CloseAssociatedTokenAccount ) {
47107 const ataCloseInstruction : AtaClose = instruction ;
48- this . accountAddress ( ataCloseInstruction . params . accountAddress ) ;
49- this . destinationAddress ( ataCloseInstruction . params . destinationAddress ) ;
50- this . authorityAddress ( ataCloseInstruction . params . authorityAddress ) ;
108+ this . _closeAtaEntries . push ( {
109+ accountAddress : ataCloseInstruction . params . accountAddress ,
110+ destinationAddress : ataCloseInstruction . params . destinationAddress ,
111+ authorityAddress : ataCloseInstruction . params . authorityAddress ,
112+ } ) ;
51113 }
52114 }
53115 }
54116
55117 /** @inheritdoc */
56118 protected async buildImplementation ( ) : Promise < Transaction > {
57- assert ( this . _accountAddress , 'Account Address must be set before building the transaction' ) ;
58- assert ( this . _destinationAddress , 'Destination Address must be set before building the transaction' ) ;
59- assert ( this . _authorityAddress , 'Authority Address must be set before building the transaction' ) ;
119+ assert ( this . _closeAtaEntries . length > 0 , 'At least one ATA must be specified before building the transaction' ) ;
60120
61- if ( this . _accountAddress === this . _destinationAddress ) {
62- throw new BuildTransactionError ( 'Account address to close cannot be the same as the destination address' ) ;
63- }
121+ for ( const entry of this . _closeAtaEntries ) {
122+ assert ( entry . accountAddress , 'Account Address must be set before building the transaction' ) ;
123+ assert ( entry . destinationAddress , 'Destination Address must be set before building the transaction' ) ;
124+ assert ( entry . authorityAddress , 'Authority Address must be set before building the transaction' ) ;
64125
65- const closeAssociatedTokenAccountData : AtaClose = {
66- type : InstructionBuilderTypes . CloseAssociatedTokenAccount ,
67- params : {
68- accountAddress : this . _accountAddress ,
69- destinationAddress : this . _destinationAddress ,
70- authorityAddress : this . _authorityAddress ,
71- } ,
72- } ;
126+ if ( entry . accountAddress === entry . destinationAddress ) {
127+ throw new BuildTransactionError ( 'Account address to close cannot be the same as the destination address' ) ;
128+ }
129+ }
73130
74- this . _instructionsData = [ closeAssociatedTokenAccountData ] ;
131+ this . _instructionsData = this . _closeAtaEntries . map (
132+ ( entry ) : AtaClose => ( {
133+ type : InstructionBuilderTypes . CloseAssociatedTokenAccount ,
134+ params : {
135+ accountAddress : entry . accountAddress ,
136+ destinationAddress : entry . destinationAddress ,
137+ authorityAddress : entry . authorityAddress ,
138+ } ,
139+ } )
140+ ) ;
75141
76142 return await super . buildImplementation ( ) ;
77143 }
0 commit comments