Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/snap/src/core/handlers/onKeyringRequest/Keyring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,23 @@ describe('SolanaKeyring', () => {
'Error creating account: Error listing accounts',
);
});

describe('state consistency', () => {
it('rolls back the account creation operation if the client fails to be informed', async () => {
const emitEventSpy = jest.spyOn(keyring, 'emitEvent');
const mockErrorMessage =
'Could not digest event KeyringEvent.AccountCreated';
emitEventSpy.mockRejectedValueOnce(new Error(mockErrorMessage));
const stateDeleteKeySpy = jest.spyOn(mockState, 'deleteKey');

await expect(keyring.createAccount()).rejects.toThrow(
`Error creating account: ${mockErrorMessage}`,
);

// We should remove the account from the snap's state to ensure state consistency between the snap and the client
expect(stateDeleteKeySpy).toHaveBeenCalledTimes(3);
});
});
});

describe('deleteAccount', () => {
Expand Down
18 changes: 13 additions & 5 deletions packages/snap/src/core/handlers/onKeyringRequest/Keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,16 @@ export class SolanaKeyring implements Keyring {
],
};

const keyringAccount: KeyringAccount =
asStrictKeyringAccount(solanaKeyringAccount);

// Save the account in the snap state
await this.#state.setKey(
`keyringAccounts.${solanaKeyringAccount.id}`,
solanaKeyringAccount,
);

const keyringAccount: KeyringAccount =
asStrictKeyringAccount(solanaKeyringAccount);

// Inform the client about the new account
await this.emitEvent(KeyringEvent.AccountCreated, {
/**
* We can't pass the `keyringAccount` object because it contains the index
Expand All @@ -317,15 +319,21 @@ export class SolanaKeyring implements Keyring {
metamask: metamaskOptions,
}
: {}),
}).catch(async (error: any) => {
// Rollback the saving of the account in the snap state to ensure data consistency between the snap and the client
this.#logger.warn(
'Could not inform the client about the account creation. Rolling back the account creation operation.',
{ error },
);
await this.#deleteAccountFromState(id);
throw error;
});

await endTrace(this.#traceName);

return keyringAccount;
} catch (error: any) {
this.#logger.error({ error }, 'Error creating account');
await this.#deleteAccountFromState(id);

throw new Error(`Error creating account: ${error.message}`);
}
}
Expand Down
Loading