@@ -118,6 +118,61 @@ export class SimpleKeyringV2
118118 await this . inner . deserialize ( privateKeys ) ;
119119 }
120120
121+ /**
122+ * Executes a transactional update on the inner keyring state.
123+ * If the callback throws, the state is automatically rolled back.
124+ *
125+ * @param callback - A function that receives the current private keys and performs the update.
126+ * Should return the result on success, or throw to trigger rollback.
127+ * @returns The result of the callback.
128+ * @throws Error if the callback throws (after rollback).
129+ */
130+ async #withRollback< Result > (
131+ callback : ( currentPrivateKeys : string [ ] ) => Promise < Result > ,
132+ ) : Promise < Result > {
133+ const originalPrivateKeys = await this . #getPrivateKeys( ) ;
134+
135+ try {
136+ return await callback ( originalPrivateKeys ) ;
137+ } catch ( error ) {
138+ // Rollback on error
139+ await this . #setPrivateKeys( originalPrivateKeys ) ;
140+ throw error ;
141+ }
142+ }
143+
144+ /**
145+ * Import a private key and return the new address.
146+ * If the import fails (no new address added), rolls back to the original state.
147+ *
148+ * @param privateKey - The private key to import in hexadecimal format.
149+ * @returns The address of the newly imported account.
150+ * @throws Error if the import fails or no new address is added.
151+ */
152+ async #importPrivateKeyOrRollback( privateKey : string ) : Promise < Hex > {
153+ return this . #withRollback( async ( currentPrivateKeys ) => {
154+ // Get current addresses before import
155+ const addressesBefore = new Set ( await this . inner . getAccounts ( ) ) ;
156+
157+ // Import the new private key
158+ await this . #setPrivateKeys( [ ...currentPrivateKeys , privateKey ] ) ;
159+
160+ // Get addresses after import and find the newly added one
161+ const addressesAfter = await this . inner . getAccounts ( ) ;
162+
163+ // Find the new address by diffing the two sets
164+ const newAddresses = addressesAfter . filter (
165+ ( addr ) => ! addressesBefore . has ( addr ) ,
166+ ) ;
167+
168+ if ( newAddresses . length !== 1 || ! newAddresses [ 0 ] ) {
169+ throw new Error ( 'Failed to import private key' ) ;
170+ }
171+
172+ return newAddresses [ 0 ] ;
173+ } ) ;
174+ }
175+
121176 async getAccounts ( ) : Promise < KeyringAccount [ ] > {
122177 const addresses = await this . inner . getAccounts ( ) ;
123178
@@ -175,30 +230,8 @@ export class SimpleKeyringV2
175230 ) ;
176231 }
177232
178- // Get current addresses before import
179- const addressesBefore = new Set ( await this . inner . getAccounts ( ) ) ;
180-
181- // Get current accounts to preserve them (also used for rollback)
182- const currentAccounts = await this . #getPrivateKeys( ) ;
183-
184- // Import the new private key by deserializing with all accounts
185- await this . #setPrivateKeys( [ ...currentAccounts , privateKey ] ) ;
186-
187- // Get addresses after import and find the newly added one
188- const addressesAfter = await this . inner . getAccounts ( ) ;
189-
190- // Find the new address by diffing the two sets
191- const newAddresses = addressesAfter . filter (
192- ( addr ) => ! addressesBefore . has ( addr ) ,
193- ) ;
194-
195- if ( newAddresses . length !== 1 || ! newAddresses [ 0 ] ) {
196- // Rollback the inner keyring state to prevent corruption
197- await this . #setPrivateKeys( currentAccounts ) ;
198- throw new Error ( 'Failed to import private key' ) ;
199- }
200-
201- const newAddress = newAddresses [ 0 ] ;
233+ // Import the private key (with automatic rollback on failure)
234+ const newAddress = await this . #importPrivateKeyOrRollback( privateKey ) ;
202235
203236 // Create and return the new KeyringAccount
204237 const newAccount = this . #createKeyringAccount( newAddress ) ;
0 commit comments