@@ -69,6 +69,9 @@ export async function rotateVaultAccountAndAccessToken(props: {
6969 vaultClient,
7070 adminKey,
7171 rotationCode,
72+ // Skip wallet creation on rotation - preserve the existing project wallet
73+ skipWalletCreation : true ,
74+ existingProjectWalletAddress : service ?. projectWalletAddress ?? undefined ,
7275 } ) ;
7376
7477 return {
@@ -222,9 +225,18 @@ async function createAndEncryptVaultAccessTokens(props: {
222225 projectSecretHash ?: string ;
223226 adminKey : string ;
224227 rotationCode : string ;
228+ skipWalletCreation ?: boolean ;
229+ existingProjectWalletAddress ?: string ;
225230} ) {
226- const { project, projectSecretKey, vaultClient, adminKey, rotationCode } =
227- props ;
231+ const {
232+ project,
233+ projectSecretKey,
234+ vaultClient,
235+ adminKey,
236+ rotationCode,
237+ skipWalletCreation,
238+ existingProjectWalletAddress,
239+ } = props ;
228240
229241 const [ managementTokenResult , walletTokenResult ] = await Promise . all ( [
230242 createManagementAccessToken ( { project, adminKey, vaultClient } ) ,
@@ -246,12 +258,12 @@ async function createAndEncryptVaultAccessTokens(props: {
246258 const managementToken = managementTokenResult . data ;
247259 const walletToken = walletTokenResult . data ;
248260
249- // create a default project server wallet
250- const defaultProjectServerWallet = await createProjectServerWallet ( {
251- project ,
252- managementAccessToken : managementToken . accessToken ,
253- label : getProjectWalletLabel ( project . name ) ,
254- } ) ;
261+ // CRITICAL: Save credentials IMMEDIATELY after creating tokens.
262+ // This prevents a broken state if wallet creation or other operations fail.
263+ // The rotationCode is consumed when rotating, so if we don't save the new one ,
264+ // the project becomes unrecoverable.
265+ let encryptedAdminKey : string | null = null ;
266+ let encryptedWalletAccessToken : string | null = null ;
255267
256268 if ( projectSecretKey ) {
257269 // verify that the project secret key is valid
@@ -266,61 +278,53 @@ async function createAndEncryptVaultAccessTokens(props: {
266278 }
267279
268280 // encrypt admin key and wallet token with project secret key
269- const [ encryptedAdminKey , encryptedWalletAccessToken ] = await Promise . all ( [
281+ [ encryptedAdminKey , encryptedWalletAccessToken ] = await Promise . all ( [
270282 encrypt ( adminKey , projectSecretKey ) ,
271283 encrypt ( walletToken . accessToken , projectSecretKey ) ,
272284 ] ) ;
285+ }
273286
274- await updateProjectClient (
275- {
276- projectId : props . project . id ,
277- teamId : props . project . teamId ,
278- } ,
279- {
280- services : [
281- ...props . project . services . filter (
282- ( service ) => service . name !== "engineCloud" ,
283- ) ,
284- {
285- name : "engineCloud" ,
286- actions : [ ] ,
287- managementAccessToken : managementToken . accessToken ,
288- maskedAdminKey : maskSecret ( adminKey ) ,
289- encryptedAdminKey,
290- encryptedWalletAccessToken,
291- rotationCode : rotationCode ,
292- projectWalletAddress : defaultProjectServerWallet . address ,
293- } ,
294- ] ,
295- } ,
296- ) ;
297- } else {
298- // no secret key, only store the management token, remove any encrypted keys
299- await updateProjectClient (
300- {
301- projectId : props . project . id ,
302- teamId : props . project . teamId ,
303- } ,
304- {
305- services : [
306- ...props . project . services . filter (
307- ( service ) => service . name !== "engineCloud" ,
308- ) ,
309- {
310- name : "engineCloud" ,
311- actions : [ ] ,
312- managementAccessToken : managementToken . accessToken ,
313- maskedAdminKey : maskSecret ( adminKey ) ,
314- encryptedAdminKey : null ,
315- encryptedWalletAccessToken : null ,
316- rotationCode : rotationCode ,
317- projectWalletAddress : defaultProjectServerWallet . address ,
318- } ,
319- ] ,
320- } ,
321- ) ;
287+ // For rotation, preserve existing wallet address. For new creation, create a default wallet.
288+ let projectWalletAddress : string | null | undefined =
289+ existingProjectWalletAddress ??
290+ project . services . find ( ( s ) => s . name === "engineCloud" )
291+ ?. projectWalletAddress ;
292+
293+ // Only create a new wallet if we don't have one (initial setup, not rotation)
294+ if ( ! skipWalletCreation && ! projectWalletAddress ) {
295+ const defaultProjectServerWallet = await createProjectServerWallet ( {
296+ project,
297+ managementAccessToken : managementToken . accessToken ,
298+ label : getProjectWalletLabel ( project . name ) ,
299+ } ) ;
300+ projectWalletAddress = defaultProjectServerWallet . address ;
322301 }
323302
303+ // Save credentials
304+ await updateProjectClient (
305+ {
306+ projectId : props . project . id ,
307+ teamId : props . project . teamId ,
308+ } ,
309+ {
310+ services : [
311+ ...props . project . services . filter (
312+ ( service ) => service . name !== "engineCloud" ,
313+ ) ,
314+ {
315+ name : "engineCloud" ,
316+ actions : [ ] ,
317+ managementAccessToken : managementToken . accessToken ,
318+ maskedAdminKey : maskSecret ( adminKey ) ,
319+ encryptedAdminKey,
320+ encryptedWalletAccessToken,
321+ rotationCode : rotationCode ,
322+ projectWalletAddress : projectWalletAddress ?? null ,
323+ } ,
324+ ] ,
325+ } ,
326+ ) ;
327+
324328 return {
325329 managementToken,
326330 walletToken,
0 commit comments