@@ -7,10 +7,21 @@ import { Transaction } from './transaction';
77import { TransactionBuilder } from './transactionBuilder' ;
88import { validateAddress } from './utils' ;
99
10+ type CloseAtaApiMode = 'single' | 'bulk' ;
11+
12+ const MIX_API_ERROR_MESSAGE =
13+ 'Cannot mix single-ATA API (accountAddress/destinationAddress/authorityAddress) with bulk-ATA API (addCloseAtaInstruction)' ;
14+
1015export class CloseAtaBuilder extends TransactionBuilder {
11- protected _accountAddress : string ;
12- protected _destinationAddress : string ;
13- protected _authorityAddress : string ;
16+ // Unified storage for all close entries (single or bulk)
17+ protected _closeAtaEntries : { accountAddress : string ; destinationAddress : string ; authorityAddress : string } [ ] = [ ] ;
18+
19+ // Which API has been used on this builder instance. Locks in on first call so we can
20+ // reject attempts to mix the legacy single-ATA setters with the bulk addCloseAtaInstruction().
21+ // After initBuilder(): remains undefined for a single parsed close (either API may extend/edit);
22+ // set to 'bulk' when the parsed tx had multiple closes so legacy setters cannot partially
23+ // overwrite entry[0] while leaving other parsed entries in place.
24+ private _apiMode : CloseAtaApiMode | undefined = undefined ;
1425
1526 constructor ( _coinConfig : Readonly < CoinConfig > ) {
1627 super ( _coinConfig ) ;
@@ -21,21 +32,90 @@ export class CloseAtaBuilder extends TransactionBuilder {
2132 return TransactionType . CloseAssociatedTokenAccount ;
2233 }
2334
35+ /**
36+ * Sets the ATA account address to close (single-ATA API, backward compatible).
37+ * Cannot be mixed with addCloseAtaInstruction().
38+ */
2439 accountAddress ( accountAddress : string ) : this {
40+ this . _assertSingleAtaApiUsable ( ) ;
2541 validateAddress ( accountAddress , 'accountAddress' ) ;
26- this . _accountAddress = accountAddress ;
42+ this . _apiMode = 'single' ;
43+ this . _ensureSingleEntry ( ) ;
44+ this . _closeAtaEntries [ 0 ] . accountAddress = accountAddress ;
2745 return this ;
2846 }
2947
48+ /**
49+ * Sets the destination address for rent SOL (single-ATA API, backward compatible).
50+ * Cannot be mixed with addCloseAtaInstruction().
51+ */
3052 destinationAddress ( destinationAddress : string ) : this {
53+ this . _assertSingleAtaApiUsable ( ) ;
3154 validateAddress ( destinationAddress , 'destinationAddress' ) ;
32- this . _destinationAddress = destinationAddress ;
55+ this . _apiMode = 'single' ;
56+ this . _ensureSingleEntry ( ) ;
57+ this . _closeAtaEntries [ 0 ] . destinationAddress = destinationAddress ;
3358 return this ;
3459 }
3560
61+ /**
62+ * Sets the authority address / ATA owner (single-ATA API, backward compatible).
63+ * Cannot be mixed with addCloseAtaInstruction().
64+ */
3665 authorityAddress ( authorityAddress : string ) : this {
66+ this . _assertSingleAtaApiUsable ( ) ;
67+ validateAddress ( authorityAddress , 'authorityAddress' ) ;
68+ this . _apiMode = 'single' ;
69+ this . _ensureSingleEntry ( ) ;
70+ this . _closeAtaEntries [ 0 ] . authorityAddress = authorityAddress ;
71+ return this ;
72+ }
73+
74+ /**
75+ * Throws if the bulk-ATA API has already been used on this builder.
76+ */
77+ private _assertSingleAtaApiUsable ( ) : void {
78+ if ( this . _apiMode === 'bulk' ) {
79+ throw new BuildTransactionError ( MIX_API_ERROR_MESSAGE ) ;
80+ }
81+ }
82+
83+ /**
84+ * Ensures a single entry exists in _closeAtaEntries for the legacy API.
85+ */
86+ private _ensureSingleEntry ( ) : void {
87+ if ( this . _closeAtaEntries . length === 0 ) {
88+ this . _closeAtaEntries . push ( { accountAddress : '' , destinationAddress : '' , authorityAddress : '' } ) ;
89+ }
90+ }
91+
92+ /**
93+ * Add an ATA to close in this transaction (for bulk closure).
94+ * Cannot be mixed with the single-ATA API (accountAddress/destinationAddress/authorityAddress).
95+ *
96+ * @param {string } accountAddress - the ATA address to close
97+ * @param {string } destinationAddress - where rent SOL goes (root wallet address)
98+ * @param {string } authorityAddress - ATA owner who must sign
99+ */
100+ addCloseAtaInstruction ( accountAddress : string , destinationAddress : string , authorityAddress : string ) : this {
101+ if ( this . _apiMode === 'single' ) {
102+ throw new BuildTransactionError ( MIX_API_ERROR_MESSAGE ) ;
103+ }
104+
105+ validateAddress ( accountAddress , 'accountAddress' ) ;
106+ validateAddress ( destinationAddress , 'destinationAddress' ) ;
37107 validateAddress ( authorityAddress , 'authorityAddress' ) ;
38- this . _authorityAddress = authorityAddress ;
108+
109+ if ( accountAddress === destinationAddress ) {
110+ throw new BuildTransactionError ( 'Account address to close cannot be the same as the destination address' ) ;
111+ }
112+
113+ if ( this . _closeAtaEntries . some ( ( entry ) => entry . accountAddress === accountAddress ) ) {
114+ throw new BuildTransactionError ( 'Duplicate ATA address: ' + accountAddress ) ;
115+ }
116+
117+ this . _apiMode = 'bulk' ;
118+ this . _closeAtaEntries . push ( { accountAddress, destinationAddress, authorityAddress } ) ;
39119 return this ;
40120 }
41121
@@ -45,33 +125,42 @@ export class CloseAtaBuilder extends TransactionBuilder {
45125 for ( const instruction of this . _instructionsData ) {
46126 if ( instruction . type === InstructionBuilderTypes . CloseAssociatedTokenAccount ) {
47127 const ataCloseInstruction : AtaClose = instruction ;
48- this . accountAddress ( ataCloseInstruction . params . accountAddress ) ;
49- this . destinationAddress ( ataCloseInstruction . params . destinationAddress ) ;
50- this . authorityAddress ( ataCloseInstruction . params . authorityAddress ) ;
128+ this . _closeAtaEntries . push ( {
129+ accountAddress : ataCloseInstruction . params . accountAddress ,
130+ destinationAddress : ataCloseInstruction . params . destinationAddress ,
131+ authorityAddress : ataCloseInstruction . params . authorityAddress ,
132+ } ) ;
51133 }
52134 }
135+ if ( this . _closeAtaEntries . length > 1 ) {
136+ this . _apiMode = 'bulk' ;
137+ }
53138 }
54139
55140 /** @inheritdoc */
56141 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' ) ;
142+ assert ( this . _closeAtaEntries . length > 0 , 'At least one ATA must be specified before building the transaction' ) ;
60143
61- if ( this . _accountAddress === this . _destinationAddress ) {
62- throw new BuildTransactionError ( 'Account address to close cannot be the same as the destination address' ) ;
63- }
144+ for ( const entry of this . _closeAtaEntries ) {
145+ assert ( entry . accountAddress , 'Account Address must be set before building the transaction' ) ;
146+ assert ( entry . destinationAddress , 'Destination Address must be set before building the transaction' ) ;
147+ assert ( entry . authorityAddress , 'Authority Address must be set before building the transaction' ) ;
64148
65- const closeAssociatedTokenAccountData : AtaClose = {
66- type : InstructionBuilderTypes . CloseAssociatedTokenAccount ,
67- params : {
68- accountAddress : this . _accountAddress ,
69- destinationAddress : this . _destinationAddress ,
70- authorityAddress : this . _authorityAddress ,
71- } ,
72- } ;
149+ if ( entry . accountAddress === entry . destinationAddress ) {
150+ throw new BuildTransactionError ( 'Account address to close cannot be the same as the destination address' ) ;
151+ }
152+ }
73153
74- this . _instructionsData = [ closeAssociatedTokenAccountData ] ;
154+ this . _instructionsData = this . _closeAtaEntries . map (
155+ ( entry ) : AtaClose => ( {
156+ type : InstructionBuilderTypes . CloseAssociatedTokenAccount ,
157+ params : {
158+ accountAddress : entry . accountAddress ,
159+ destinationAddress : entry . destinationAddress ,
160+ authorityAddress : entry . authorityAddress ,
161+ } ,
162+ } )
163+ ) ;
75164
76165 return await super . buildImplementation ( ) ;
77166 }
0 commit comments