diff --git a/src/background/models.ts b/src/background/models.ts index 8a230869..0fc4e633 100644 --- a/src/background/models.ts +++ b/src/background/models.ts @@ -1,3 +1,5 @@ +import type { Avalanche } from '@avalabs/core-wallets-sdk'; + export interface DomainMetadataRequest { method: 'avalanche_sendDomainMetadata'; params: DomainMetadata; @@ -80,3 +82,8 @@ export type Never = { export type ArrayElement = A extends readonly (infer T)[] ? T : never; export const ACTION_HANDLED_BY_MODULE = '__handled.via.vm.modules__'; + +export type AddressResolutionOptions = { + isMainnet: boolean; + providerXP: Avalanche.JsonRpcProvider; +}; diff --git a/src/background/services/accounts/AccountsService.test.ts b/src/background/services/accounts/AccountsService.test.ts index 78cb24b5..79cf8642 100644 --- a/src/background/services/accounts/AccountsService.test.ts +++ b/src/background/services/accounts/AccountsService.test.ts @@ -141,6 +141,8 @@ describe('background/services/accounts/AccountsService', () => { }; }; + const providerXP = { getAddress: jest.fn() } as any; + beforeEach(() => { jest.resetAllMocks(); (storageService.load as jest.Mock).mockResolvedValue(emptyAccounts); @@ -154,6 +156,9 @@ describe('background/services/accounts/AccountsService', () => { }); networkService.developerModeChanged.add = jest.fn(); networkService.developerModeChanged.remove = jest.fn(); + jest + .spyOn(networkService, 'getAvalanceProviderXP') + .mockResolvedValue(providerXP); accountsService = new AccountsService( storageService, networkService, @@ -267,13 +272,13 @@ describe('background/services/accounts/AccountsService', () => { 1, 0, walletId, - networkService, + { isMainnet: true, providerXP }, ); expect(secretsService.getAddresses).toHaveBeenNthCalledWith( 2, 1, walletId, - networkService, + { isMainnet: true, providerXP }, ); expect(secretsService.getImportedAddresses).toBeCalledTimes(3); @@ -378,7 +383,7 @@ describe('background/services/accounts/AccountsService', () => { expect(secretsService.getImportedAddresses).toHaveBeenCalledWith( 'fb-acc', - networkService, + { isMainnet: true, providerXP }, ); expect(secretsService.getAddresses).toHaveBeenCalledTimes(0); expect(accountsService.getAccounts().imported['fb-acc']).toEqual({ @@ -500,7 +505,7 @@ describe('background/services/accounts/AccountsService', () => { expect(secretsService.addAddress).toBeCalledWith({ index: 0, walletId: WALLET_ID, - networkService, + options: { isMainnet: true, providerXP }, ledgerService, }); @@ -552,7 +557,7 @@ describe('background/services/accounts/AccountsService', () => { expect(secretsService.addAddress).toBeCalledWith({ index: 2, walletId: WALLET_ID, - networkService, + options: { isMainnet: true, providerXP }, ledgerService, }); expect(permissionsService.addWhitelistDomains).toBeCalledTimes(1); @@ -593,7 +598,7 @@ describe('background/services/accounts/AccountsService', () => { expect(secretsService.addAddress).toBeCalledWith({ index: 2, walletId: WALLET_ID, - networkService, + options: { isMainnet: true, providerXP }, ledgerService, }); expect(permissionsService.addWhitelistDomains).toBeCalledTimes(1); @@ -680,10 +685,10 @@ describe('background/services/accounts/AccountsService', () => { options, }); expect(secretsService.addImportedWallet).toBeCalledTimes(1); - expect(secretsService.addImportedWallet).toBeCalledWith( - options, - networkService, - ); + expect(secretsService.addImportedWallet).toBeCalledWith(options, { + isMainnet: true, + providerXP, + }); expect(commitMock).toHaveBeenCalled(); expect(permissionsService.addWhitelistDomains).toBeCalledTimes(1); expect(permissionsService.addWhitelistDomains).toBeCalledWith( @@ -738,10 +743,10 @@ describe('background/services/accounts/AccountsService', () => { await accountsService.addImportedAccount({ options }); expect(secretsService.addImportedWallet).toBeCalledTimes(1); - expect(secretsService.addImportedWallet).toBeCalledWith( - options, - networkService, - ); + expect(secretsService.addImportedWallet).toBeCalledWith(options, { + isMainnet: true, + providerXP, + }); expect(commitMock).toHaveBeenCalled(); expect(permissionsService.addWhitelistDomains).toBeCalledTimes(1); expect(permissionsService.addWhitelistDomains).toBeCalledWith( @@ -839,10 +844,10 @@ describe('background/services/accounts/AccountsService', () => { '0x1', ); expect(secretsService.addImportedWallet).toBeCalledTimes(1); - expect(secretsService.addImportedWallet).toBeCalledWith( - options, - networkService, - ); + expect(secretsService.addImportedWallet).toBeCalledWith(options, { + isMainnet: true, + providerXP, + }); expect(commitMock).not.toHaveBeenCalled(); expect(permissionsService.addWhitelistDomains).not.toHaveBeenCalled(); }); diff --git a/src/background/services/accounts/AccountsService.ts b/src/background/services/accounts/AccountsService.ts index b74edffc..65119e65 100644 --- a/src/background/services/accounts/AccountsService.ts +++ b/src/background/services/accounts/AccountsService.ts @@ -28,6 +28,7 @@ import { LedgerService } from '../ledger/LedgerService'; import { WalletConnectService } from '../walletConnect/WalletConnectService'; import { Network } from '../network/models'; import { isDevnet } from '@src/utils/isDevnet'; +import { getAddressResolutionOptions } from '@src/background/utils/getAddressResolutionOptions'; type AddAccountParams = { walletId: string; @@ -224,17 +225,21 @@ export class AccountsService implements OnLock, OnUnlock { }; async getAddressesForAccount(account: Account): Promise { + const addressResolutionOptions = await getAddressResolutionOptions( + this.networkService, + ); + if (account.type !== AccountType.PRIMARY) { return this.secretsService.getImportedAddresses( account.id, - this.networkService, + addressResolutionOptions, ); } const addresses = await this.secretsService.getAddresses( account.index, account.walletId, - this.networkService, + addressResolutionOptions, ); return { @@ -370,8 +375,8 @@ export class AccountsService implements OnLock, OnUnlock { const addresses = await this.secretsService.addAddress({ index: nextIndex, walletId, - networkService: this.networkService, ledgerService: this.ledgerService, + options: await getAddressResolutionOptions(this.networkService), }); const id = crypto.randomUUID(); @@ -416,7 +421,7 @@ export class AccountsService implements OnLock, OnUnlock { try { const { account, commit } = await this.secretsService.addImportedWallet( options, - this.networkService, + await getAddressResolutionOptions(this.networkService), ); const existingAccount = this.#findAccountByAddress(account.addressC); diff --git a/src/background/services/onboarding/handlers/seedlessOnboardingHandler.test.ts b/src/background/services/onboarding/handlers/seedlessOnboardingHandler.test.ts index b7484a2b..c6b425f0 100644 --- a/src/background/services/onboarding/handlers/seedlessOnboardingHandler.test.ts +++ b/src/background/services/onboarding/handlers/seedlessOnboardingHandler.test.ts @@ -77,6 +77,8 @@ describe('src/background/services/onboarding/handlers/seedlessOnboardingHandler. addFavoriteNetwork: jest.fn(), getAvalancheNetwork: jest.fn(), setNetwork: jest.fn(), + isMainnet: () => false, + getAvalanceProviderXP: jest.fn(), } as unknown as NetworkService; const secretsServiceMock = { getWalletAccountsSecretsById: jest.fn(), diff --git a/src/background/services/onboarding/handlers/seedlessOnboardingHandler.ts b/src/background/services/onboarding/handlers/seedlessOnboardingHandler.ts index 31e549b9..4cb849ca 100644 --- a/src/background/services/onboarding/handlers/seedlessOnboardingHandler.ts +++ b/src/background/services/onboarding/handlers/seedlessOnboardingHandler.ts @@ -20,6 +20,7 @@ import { OnboardingService } from '../OnboardingService'; import { LockService } from '../../lock/LockService'; import { finalizeOnboarding } from '../finalizeOnboarding'; import { startOnboarding } from '../startOnboarding'; +import { getAddressResolutionOptions } from '@src/background/utils/getAddressResolutionOptions'; type HandlerType = ExtensionRequestHandler< ExtensionRequest.SEEDLESS_ONBOARDING_SUBMIT, @@ -72,7 +73,9 @@ export class SeedlessOnboardingHandler implements HandlerType { const memorySessionStorage = new MemorySessionStorage(seedlessSignerToken); const seedlessWallet = new SeedlessWallet({ - networkService: this.networkService, + addressResolutionOptions: await getAddressResolutionOptions( + this.networkService, + ), sessionStorage: memorySessionStorage, }); diff --git a/src/background/services/secrets/SecretsService.test.ts b/src/background/services/secrets/SecretsService.test.ts index a49e979f..a7cf8151 100644 --- a/src/background/services/secrets/SecretsService.test.ts +++ b/src/background/services/secrets/SecretsService.test.ts @@ -1156,13 +1156,12 @@ describe('src/background/services/secrets/SecretsService.ts', () => { index: 1, walletId: ACTIVE_WALLET_ID, ledgerService, - networkService, + options: { isMainnet: true, providerXP: getDefaultFujiProviderMock() }, + }); + expect(getAddressesSpy).toHaveBeenCalledWith(1, ACTIVE_WALLET_ID, { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), }); - expect(getAddressesSpy).toHaveBeenCalledWith( - 1, - ACTIVE_WALLET_ID, - networkService, - ); expect(result).toStrictEqual(addressesMock); }); @@ -1180,7 +1179,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { index: 1, walletId: ACTIVE_WALLET_ID, ledgerService, - networkService, + options: { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }, }), ).rejects.toThrow('Ledger transport not available'); }); @@ -1204,7 +1206,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { index: 1, walletId: ACTIVE_WALLET_ID, ledgerService, - networkService, + options: { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }, }), ).rejects.toThrow('Failed to get public key from device.'); expect(getPubKeyFromTransport).toHaveBeenCalledWith( @@ -1232,7 +1237,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { index: 1, walletId: ACTIVE_WALLET_ID, ledgerService, - networkService, + options: { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }, }), ).rejects.toThrow('Failed to get public key from device.'); expect(getPubKeyFromTransport).toHaveBeenCalledWith( @@ -1259,13 +1267,15 @@ describe('src/background/services/secrets/SecretsService.ts', () => { index: 0, walletId: ACTIVE_WALLET_ID, ledgerService, - networkService, + options: { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }, + }); + expect(getAddressesSpy).toHaveBeenCalledWith(0, ACTIVE_WALLET_ID, { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), }); - expect(getAddressesSpy).toHaveBeenCalledWith( - 0, - ACTIVE_WALLET_ID, - networkService, - ); secretsService.updateSecrets = jest.fn(); expect(getPubKeyFromTransport).not.toHaveBeenCalled(); expect(result).toStrictEqual(addressesMock); @@ -1292,13 +1302,15 @@ describe('src/background/services/secrets/SecretsService.ts', () => { index: 0, walletId: ACTIVE_WALLET_ID, ledgerService, - networkService, + options: { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }, + }); + expect(getAddressesSpy).toHaveBeenCalledWith(0, ACTIVE_WALLET_ID, { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), }); - expect(getAddressesSpy).toHaveBeenCalledWith( - 0, - ACTIVE_WALLET_ID, - networkService, - ); expect(result).toStrictEqual(addressesMock); expect(secretsService.updateSecrets).toHaveBeenCalledWith( @@ -1342,12 +1354,18 @@ describe('src/background/services/secrets/SecretsService.ts', () => { const result = await secretsService.addAddress({ index: 1, walletId: ACTIVE_WALLET_ID, - networkService, + options: { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }, ledgerService, }); expect(SeedlessWallet).toHaveBeenCalledWith({ - networkService, + addressResolutionOptions: { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }, sessionStorage: expect.any(SeedlessTokenStorage), addressPublicKey: { evm: 'evm', xp: 'xp' }, }); @@ -1359,11 +1377,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { }, ACTIVE_WALLET_ID, ); - expect(getAddressesSpy).toHaveBeenCalledWith( - 1, - ACTIVE_WALLET_ID, - networkService, - ); + expect(getAddressesSpy).toHaveBeenCalledWith(1, ACTIVE_WALLET_ID, { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }); expect(result).toStrictEqual(addressesMock); }); }); @@ -1390,18 +1407,20 @@ describe('src/background/services/secrets/SecretsService.ts', () => { const result = await secretsService.addAddress({ index: 1, walletId: ACTIVE_WALLET_ID, - networkService, + options: { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }, ledgerService, }); expect(SeedlessWallet).not.toHaveBeenCalled(); expect(secretsService.updateSecrets).not.toHaveBeenCalled(); - expect(getAddressesSpy).toHaveBeenCalledWith( - 1, - ACTIVE_WALLET_ID, - networkService, - ); + expect(getAddressesSpy).toHaveBeenCalledWith(1, ACTIVE_WALLET_ID, { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }); expect(result).toStrictEqual(addressesMock); }); }); @@ -1419,7 +1438,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { it('throws error if walletId is not provided', async () => { await expect( - secretsService.getAddresses(0, '', networkService), + secretsService.getAddresses(0, '', { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }), ).rejects.toThrow('Wallet id not provided'); }); @@ -1429,7 +1451,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { { secretType: 'unknown', id: 'seedless-wallet-id' }, ); await expect( - secretsService.getAddresses(0, 'seedless-wallet-id', networkService), + secretsService.getAddresses(0, 'seedless-wallet-id', { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }), ).rejects.toThrow('No public key available'); }); @@ -1438,7 +1463,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { (getAddressFromXPub as jest.Mock).mockReturnValueOnce('0x1'); (getBech32AddressFromXPub as jest.Mock).mockReturnValueOnce('0x2'); await expect( - secretsService.getAddresses(0, ACTIVE_WALLET_ID, networkService), + secretsService.getAddresses(0, ACTIVE_WALLET_ID, { + isMainnet: false, + providerXP: getDefaultFujiProviderMock(), + }), ).resolves.toStrictEqual(addressesMock('0x1', '0x2')); expect(Avalanche.getAddressPublicKeyFromXpub).toBeCalledWith('xpubXP', 0); expect(getAddressFromXPub).toHaveBeenCalledWith('xpub', 0); @@ -1456,7 +1484,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { (networkService.isMainnet as jest.Mock).mockReturnValueOnce(false); await expect( - secretsService.getAddresses(0, ACTIVE_WALLET_ID, networkService), + secretsService.getAddresses(0, ACTIVE_WALLET_ID, { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }), ).rejects.toThrow('Account not added'); }); @@ -1470,7 +1501,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { (getBtcAddressFromPubKey as jest.Mock).mockReturnValueOnce('0x2'); await expect( - secretsService.getAddresses(0, ACTIVE_WALLET_ID, networkService), + secretsService.getAddresses(0, ACTIVE_WALLET_ID, { + isMainnet: false, + providerXP: getDefaultFujiProviderMock(), + }), ).resolves.toStrictEqual(addressesMock('0x1', '0x2')); expect(getEvmAddressFromPubKey).toHaveBeenCalledWith(pubKeyBuff); @@ -1520,7 +1554,7 @@ describe('src/background/services/secrets/SecretsService.ts', () => { importType: ImportType.PRIVATE_KEY, data: 'privateKey', }, - networkService, + { isMainnet: false, providerXP: getDefaultFujiProviderMock() }, ); expect(result).toStrictEqual({ @@ -1559,7 +1593,7 @@ describe('src/background/services/secrets/SecretsService.ts', () => { importType: ImportType.PRIVATE_KEY, data: 'privateKey', }, - networkService, + { isMainnet: false, providerXP: getDefaultFujiProviderMock() }, ), ).rejects.toThrow('Error while calculating addresses'); }); @@ -1579,7 +1613,7 @@ describe('src/background/services/secrets/SecretsService.ts', () => { importType: ImportType.PRIVATE_KEY, data: 'privateKey', }, - networkService, + { isMainnet: false, providerXP: getDefaultFujiProviderMock() }, ), ).rejects.toThrow('Error while calculating addresses'); }); @@ -1599,7 +1633,7 @@ describe('src/background/services/secrets/SecretsService.ts', () => { importType: ImportType.PRIVATE_KEY, data: 'privateKey', }, - networkService, + { isMainnet: false, providerXP: getDefaultFujiProviderMock() }, ), ).rejects.toThrow('Error while calculating addresses'); }); @@ -1622,7 +1656,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { ); await expect( - secretsService.getImportedAddresses('id', networkService), + secretsService.getImportedAddresses('id', { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }), ).rejects.toThrow('No secrets found for imported account'); }); @@ -1633,7 +1670,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { ); await expect( - secretsService.getImportedAddresses('id', networkService), + secretsService.getImportedAddresses('id', { + isMainnet: true, + providerXP: getDefaultFujiProviderMock(), + }), ).rejects.toThrow('Unsupported import type'); }); @@ -1647,7 +1687,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { (getBtcAddressFromPubKey as jest.Mock).mockReturnValueOnce(''); await expect( - secretsService.getImportedAddresses('id', networkService), + secretsService.getImportedAddresses('id', { + isMainnet: false, + providerXP: getDefaultFujiProviderMock(), + }), ).rejects.toThrow('Missing address'); }); @@ -1658,10 +1701,10 @@ describe('src/background/services/secrets/SecretsService.ts', () => { { secretType: SecretType.PrivateKey, secret: 'secret' }, ); - const result = await secretsService.getImportedAddresses( - 'id', - networkService, - ); + const result = await secretsService.getImportedAddresses('id', { + isMainnet: false, + providerXP: getDefaultFujiProviderMock(), + }); expect(result).toStrictEqual({ addressBTC: '0x2', diff --git a/src/background/services/secrets/SecretsService.ts b/src/background/services/secrets/SecretsService.ts index 98d49ffb..1978dbcf 100644 --- a/src/background/services/secrets/SecretsService.ts +++ b/src/background/services/secrets/SecretsService.ts @@ -39,10 +39,10 @@ import { NetworkVMType } from '@avalabs/core-chains-sdk'; import { SeedlessWallet } from '../seedless/SeedlessWallet'; import { SeedlessTokenStorage } from '../seedless/SeedlessTokenStorage'; import { LedgerService } from '../ledger/LedgerService'; -import { NetworkService } from '../network/NetworkService'; import { WalletConnectService } from '../walletConnect/WalletConnectService'; import EventEmitter from 'events'; import { OnUnlock } from '@src/background/runtime/lifecycleCallbacks'; +import { AddressResolutionOptions } from '@src/background/models'; /** * Use this service to fetch, save or delete account secrets. @@ -554,7 +554,7 @@ export class SecretsService implements OnUnlock { async addImportedWallet( importData: ImportData, - networkService: NetworkService, + options: AddressResolutionOptions, ) { const id = crypto.randomUUID(); @@ -599,7 +599,7 @@ export class SecretsService implements OnUnlock { if (importData.importType === ImportType.PRIVATE_KEY) { const addresses = await this.#calculateAddressesForPrivateKey( importData.data, - networkService, + options, ); return { account: { @@ -615,7 +615,7 @@ export class SecretsService implements OnUnlock { async #calculateAddressesForPrivateKey( privateKey: string, - networkService: NetworkService, + { isMainnet, providerXP }: AddressResolutionOptions, ) { const addresses = { addressBTC: '', @@ -625,18 +625,16 @@ export class SecretsService implements OnUnlock { addressCoreEth: '', }; - const provXP = await networkService.getAvalanceProviderXP(); - try { const publicKey = getPublicKeyFromPrivateKey(privateKey); addresses.addressC = getEvmAddressFromPubKey(publicKey); addresses.addressBTC = getBtcAddressFromPubKey( publicKey, - networkService.isMainnet() ? networks.bitcoin : networks.testnet, + isMainnet ? networks.bitcoin : networks.testnet, ); - addresses.addressAVM = provXP.getAddress(publicKey, 'X'); - addresses.addressPVM = provXP.getAddress(publicKey, 'P'); - addresses.addressCoreEth = provXP.getAddress(publicKey, 'C'); + addresses.addressAVM = providerXP.getAddress(publicKey, 'X'); + addresses.addressPVM = providerXP.getAddress(publicKey, 'P'); + addresses.addressCoreEth = providerXP.getAddress(publicKey, 'C'); } catch (_err) { throw new Error('Error while calculating addresses'); } @@ -658,12 +656,12 @@ export class SecretsService implements OnUnlock { index, walletId, ledgerService, - networkService, + options, }: { index: number; walletId: string; ledgerService: LedgerService; - networkService: NetworkService; + options: AddressResolutionOptions; }): Promise> { const secrets = await this.getWalletAccountsSecretsById(walletId); @@ -717,7 +715,7 @@ export class SecretsService implements OnUnlock { if (secrets.secretType === SecretType.Seedless && !secrets.pubKeys[index]) { const wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions: options, sessionStorage: new SeedlessTokenStorage(this), addressPublicKey: secrets.pubKeys[0], }); @@ -733,13 +731,13 @@ export class SecretsService implements OnUnlock { ); } - return this.getAddresses(index, walletId, networkService); + return this.getAddresses(index, walletId, options); } async getAddresses( index: number, walletId: string, - networkService: NetworkService, + { isMainnet, providerXP }: AddressResolutionOptions, ): Promise | never> { if (!walletId) { throw new Error('Wallet id not provided'); @@ -751,9 +749,6 @@ export class SecretsService implements OnUnlock { throw new Error('Wallet is not initialized'); } - const isMainnet = networkService.isMainnet(); - const providerXP = await networkService.getAvalanceProviderXP(); - if ( secrets.secretType === SecretType.Ledger || secrets.secretType === SecretType.Mnemonic || @@ -825,7 +820,7 @@ export class SecretsService implements OnUnlock { throw new Error('No public key available'); } - async getImportedAddresses(id: string, networkService: NetworkService) { + async getImportedAddresses(id: string, options: AddressResolutionOptions) { const secrets = await this.getImportedAccountSecrets(id); if ( @@ -836,10 +831,7 @@ export class SecretsService implements OnUnlock { } if (secrets.secretType === SecretType.PrivateKey) { - return this.#calculateAddressesForPrivateKey( - secrets.secret, - networkService, - ); + return this.#calculateAddressesForPrivateKey(secrets.secret, options); } throw new Error('Unsupported import type'); diff --git a/src/background/services/seedless/SeedlessWallet.test.ts b/src/background/services/seedless/SeedlessWallet.test.ts index 4e56ed15..1a3f7c08 100644 --- a/src/background/services/seedless/SeedlessWallet.test.ts +++ b/src/background/services/seedless/SeedlessWallet.test.ts @@ -12,8 +12,6 @@ import { Signer } from '@cubist-labs/cubesigner-sdk-ethers-v6'; import { networks } from 'bitcoinjs-lib'; import { JsonRpcProvider, getBytes, hashMessage } from 'ethers'; -import { NetworkService } from '../network/NetworkService'; - import { validKeySet, avaKey, @@ -45,7 +43,6 @@ import { getProviderForNetwork } from '@src/utils/network/getProviderForNetwork' jest.mock('@cubist-labs/cubesigner-sdk'); jest.mock('@cubist-labs/cubesigner-sdk-ethers-v6'); jest.mock('@avalabs/core-wallets-sdk'); -jest.mock('../network/NetworkService'); jest.mock('./SeedlessBtcSigner'); jest.mock('./SeedlessMfaService'); jest.mock('@src/utils/network/getProviderForNetwork'); @@ -54,9 +51,6 @@ describe('src/background/services/seedless/SeedlessWallet', () => { const sessionStorage = jest.mocked( new SeedlessTokenStorage({} as any), ); - const networkService = jest.mocked( - new NetworkService({} as any, {} as any), - ); const sessionManager = jest.mocked({ notifyTokenExpired: jest.fn(), } as any); @@ -69,6 +63,14 @@ describe('src/background/services/seedless/SeedlessWallet', () => { let wallet: SeedlessWallet; + const providerXP = { + getAddress: jest.fn(), + } as any; + const addressResolutionOptions = { + isMainnet: false, + providerXP, + }; + const itCorrectlyHandlesExpiredSession = ({ additionalSetup, executeSigning, @@ -112,7 +114,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { .mocked(cs.SignerSession.loadSignerSession) .mockRejectedValue(connectionError); - wallet = new SeedlessWallet({ networkService, sessionStorage }); + wallet = new SeedlessWallet({ addressResolutionOptions, sessionStorage }); }); it('fails the requests', async () => { @@ -127,7 +129,10 @@ describe('src/background/services/seedless/SeedlessWallet', () => { keys: jest.fn().mockResolvedValue([]), } as any); - wallet = new SeedlessWallet({ networkService, sessionStorage }); + wallet = new SeedlessWallet({ + addressResolutionOptions, + sessionStorage, + }); }); it('raises an error', async () => { @@ -143,7 +148,10 @@ describe('src/background/services/seedless/SeedlessWallet', () => { keys: jest.fn().mockResolvedValue([evmKey]), } as any); - wallet = new SeedlessWallet({ networkService, sessionStorage }); + wallet = new SeedlessWallet({ + addressResolutionOptions, + sessionStorage, + }); }); it('raises an error', async () => { @@ -159,7 +167,10 @@ describe('src/background/services/seedless/SeedlessWallet', () => { keys: jest.fn().mockResolvedValue(validKeySet), } as any); - wallet = new SeedlessWallet({ networkService, sessionStorage }); + wallet = new SeedlessWallet({ + addressResolutionOptions, + sessionStorage, + }); }); it('correctly extracts the public keys', async () => { @@ -178,7 +189,10 @@ describe('src/background/services/seedless/SeedlessWallet', () => { keys: jest.fn().mockResolvedValue(validKeySetWithTwoAccounts), } as any); - wallet = new SeedlessWallet({ networkService, sessionStorage }); + wallet = new SeedlessWallet({ + addressResolutionOptions, + sessionStorage, + }); }); it(`sorts them by derivation path's account index`, async () => { @@ -207,7 +221,10 @@ describe('src/background/services/seedless/SeedlessWallet', () => { ]), } as any); - wallet = new SeedlessWallet({ networkService, sessionStorage }); + wallet = new SeedlessWallet({ + addressResolutionOptions, + sessionStorage, + }); }); it('extracts the public keys from the first valid set', async () => { @@ -230,7 +247,10 @@ describe('src/background/services/seedless/SeedlessWallet', () => { describe('when public key is not provided', () => { beforeEach(() => { - wallet = new SeedlessWallet({ networkService, sessionStorage }); + wallet = new SeedlessWallet({ + addressResolutionOptions, + sessionStorage, + }); }); it('raises an error', async () => { @@ -243,7 +263,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { describe('when target network is not provided', () => { beforeEach(() => { wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: 'la la la', @@ -262,7 +282,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { beforeEach(() => { jest.mocked(getProviderForNetwork).mockReturnValue({} as any); wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: 'la la la', @@ -295,7 +315,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { jest.mocked(Signer).mockImplementation(signerConstructorSpy); wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: evmKey.publicKey, @@ -370,7 +390,10 @@ describe('src/background/services/seedless/SeedlessWallet', () => { describe('when public key is not provided', () => { beforeEach(() => { - wallet = new SeedlessWallet({ networkService, sessionStorage }); + wallet = new SeedlessWallet({ + addressResolutionOptions, + sessionStorage, + }); }); it('raises an error', async () => { @@ -395,7 +418,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { beforeEach(() => { wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: strip0x(evmKey.publicKey), @@ -465,7 +488,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { beforeEach(() => { wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: strip0x(evmKey.publicKey), @@ -486,9 +509,6 @@ describe('src/background/services/seedless/SeedlessWallet', () => { }); describe('in testnet mode', () => { - beforeEach(() => { - networkService.isMainnet.mockReturnValue(false); - }); it('uses the testnet Avalanche keys', async () => { await wallet.signAvalancheTx(txRequest as any); @@ -501,8 +521,21 @@ describe('src/background/services/seedless/SeedlessWallet', () => { describe('in mainnet mode', () => { beforeEach(() => { - networkService.isMainnet.mockReturnValue(true); + wallet = new SeedlessWallet({ + addressResolutionOptions: { + ...addressResolutionOptions, + isMainnet: true, + }, + sessionStorage, + addressPublicKey: { + evm: strip0x(evmKey.publicKey), + xp: strip0x(avaKey.publicKey), + }, + network: {} as any, + sessionManager, + }); }); + it('uses the mainnet Avalanche keys', async () => { await wallet.signAvalancheTx(txRequest as any); @@ -565,7 +598,10 @@ describe('src/background/services/seedless/SeedlessWallet', () => { describe('when public key is not provided', () => { beforeEach(() => { - wallet = new SeedlessWallet({ networkService, sessionStorage }); + wallet = new SeedlessWallet({ + addressResolutionOptions, + sessionStorage, + }); }); it('raises an error', async () => { @@ -578,7 +614,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { describe('when network is not provided', () => { beforeEach(() => { wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: {} as any, }); @@ -594,7 +630,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { describe('with EVM messages', () => { beforeEach(() => { wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: strip0x(evmKey.publicKey), @@ -707,12 +743,12 @@ describe('src/background/services/seedless/SeedlessWallet', () => { describe('with Avalanche messages', () => { beforeEach(() => { - networkService.getAvalanceProviderXP.mockReturnValue({ - getAddress: () => `X-${avaKey.materialId}`, - } as any); + jest + .mocked(providerXP.getAddress) + .mockReturnValue(`X-${avaKey.materialId}`); wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: strip0x(evmKey.publicKey), @@ -724,7 +760,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { it('validates presence of X/P public key', async () => { wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: strip0x(evmKey.publicKey), @@ -762,7 +798,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { it('returns the obtained signature', async () => { wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: strip0x(evmKey.publicKey), @@ -800,7 +836,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { .mockResolvedValue(session); wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: strip0x(evmKey.publicKey), @@ -834,7 +870,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { beforeEach(() => { global.fetch = jest.fn().mockRejectedValue(new Error('Timeout')); wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: 'unpaired-public-key', @@ -919,7 +955,10 @@ describe('src/background/services/seedless/SeedlessWallet', () => { describe('when no network is provided', () => { beforeEach(() => { - wallet = new SeedlessWallet({ networkService, sessionStorage }); + wallet = new SeedlessWallet({ + addressResolutionOptions, + sessionStorage, + }); }); it('raises an error', async () => { @@ -932,7 +971,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { describe('when non-Bitcoin network is provided', () => { beforeEach(() => { wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, network: { chainId: ChainId.ETHEREUM_HOMESTEAD, @@ -951,7 +990,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { beforeEach(() => { jest.mocked(getProviderForNetwork).mockReturnValue({} as any); wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: 'la la la', @@ -974,7 +1013,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { .mocked(getProviderForNetwork) .mockResolvedValue(new BitcoinProvider()); wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, network: { chainId: ChainId.BITCOIN, @@ -1002,7 +1041,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { .mockReturnValue(networks.bitcoin); jest.mocked(getProviderForNetwork).mockResolvedValue(bitcoinProvider); wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: pubKey, network, @@ -1108,7 +1147,7 @@ describe('src/background/services/seedless/SeedlessWallet', () => { .mockResolvedValue(session); wallet = new SeedlessWallet({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey: { evm: strip0x(evmKey.publicKey), diff --git a/src/background/services/seedless/SeedlessWallet.ts b/src/background/services/seedless/SeedlessWallet.ts index 4c5e6fae..85ee4e20 100644 --- a/src/background/services/seedless/SeedlessWallet.ts +++ b/src/background/services/seedless/SeedlessWallet.ts @@ -24,7 +24,6 @@ import { typedSignatureHash, } from '@metamask/eth-sig-util'; -import { NetworkService } from '../network/NetworkService'; import { PubKeyType } from '../wallet/models'; import { MessageParams, MessageType } from '../messages/models'; import { SeedlessBtcSigner } from './SeedlessBtcSigner'; @@ -37,9 +36,10 @@ import { isTokenExpiredError } from './utils'; import { SeedlessMfaService } from './SeedlessMfaService'; import { toUtf8 } from 'ethereumjs-util'; import { getProviderForNetwork } from '@src/utils/network/getProviderForNetwork'; +import { AddressResolutionOptions } from '@src/background/models'; type ConstructorOpts = { - networkService: NetworkService; + addressResolutionOptions: AddressResolutionOptions; sessionStorage: cs.SessionStorage; addressPublicKey?: PubKeyType; network?: Network; @@ -48,7 +48,7 @@ type ConstructorOpts = { }; export class SeedlessWallet { - #networkService: NetworkService; + #addressResolutionOptions: AddressResolutionOptions; #sessionStorage: cs.SessionStorage; #addressPublicKey?: PubKeyType; #network?: Network; @@ -57,14 +57,14 @@ export class SeedlessWallet { #mfaService?: SeedlessMfaService; constructor({ - networkService, + addressResolutionOptions, sessionStorage, addressPublicKey, network, sessionManager, mfaService, }: ConstructorOpts) { - this.#networkService = networkService; + this.#addressResolutionOptions = addressResolutionOptions; this.#sessionStorage = sessionStorage; this.#addressPublicKey = addressPublicKey; this.#network = network; @@ -396,12 +396,13 @@ export class SeedlessWallet { } const isEvmTx = request.tx.getVM() === 'EVM'; - const isMainnet = this.#networkService.isMainnet(); const session = await this.#getSession(); const key = isEvmTx ? await this.#getSigningKey(cs.Secp256k1.Evm, this.#addressPublicKey.evm) : await this.#getSigningKey( - isMainnet ? cs.Secp256k1.Ava : cs.Secp256k1.AvaTest, + this.#addressResolutionOptions.isMainnet + ? cs.Secp256k1.Ava + : cs.Secp256k1.AvaTest, this.#addressPublicKey.xp, ); @@ -486,8 +487,8 @@ export class SeedlessWallet { throw new Error('X/P public key not available'); } - const xpProvider = await this.#networkService.getAvalanceProviderXP(); - const addressAVM = await xpProvider + const { providerXP } = this.#addressResolutionOptions; + const addressAVM = await providerXP .getAddress(Buffer.from(this.#addressPublicKey.xp, 'hex'), 'X') .slice(2); // remove chain prefix const message = toUtf8(messageParams.data); diff --git a/src/background/services/seedless/handlers/cancelRecoveryPhraseExport.test.ts b/src/background/services/seedless/handlers/cancelRecoveryPhraseExport.test.ts index b925371f..9188d29a 100644 --- a/src/background/services/seedless/handlers/cancelRecoveryPhraseExport.test.ts +++ b/src/background/services/seedless/handlers/cancelRecoveryPhraseExport.test.ts @@ -14,7 +14,10 @@ describe('src/background/services/seedless/handlers/cancelRecoveryPhraseExport', const secretsService = jest.mocked({ getPrimaryAccountSecrets: jest.fn(), } as any); - const networkService = jest.mocked({} as any); + const networkService = jest.mocked({ + isMainnet: jest.fn(), + getAvalanceProviderXP: jest.fn(), + } as any); const mfaService = jest.mocked({} as any); const accountsService = jest.mocked({} as any); diff --git a/src/background/services/seedless/handlers/cancelRecoveryPhraseExport.ts b/src/background/services/seedless/handlers/cancelRecoveryPhraseExport.ts index 2d026b37..a2211c5e 100644 --- a/src/background/services/seedless/handlers/cancelRecoveryPhraseExport.ts +++ b/src/background/services/seedless/handlers/cancelRecoveryPhraseExport.ts @@ -10,6 +10,7 @@ import { SeedlessTokenStorage } from '../SeedlessTokenStorage'; import { NetworkService } from '../../network/NetworkService'; import { SeedlessMfaService } from '../SeedlessMfaService'; import { AccountsService } from '../../accounts/AccountsService'; +import { getAddressResolutionOptions } from '@src/background/utils/getAddressResolutionOptions'; type HandlerType = ExtensionRequestHandler< ExtensionRequest.SEEDLESS_CANCEL_RECOVERY_PHRASE_EXPORT, @@ -40,7 +41,9 @@ export class CancelRecoveryPhraseExportHandler implements HandlerType { } const wallet = new SeedlessWallet({ - networkService: this.networkService, + addressResolutionOptions: await getAddressResolutionOptions( + this.networkService, + ), sessionStorage: new SeedlessTokenStorage(this.secretsService), addressPublicKey: secrets.pubKeys[0], mfaService: this.seedlessMfaService, diff --git a/src/background/services/seedless/handlers/completeRecoveryPhraseExport.test.ts b/src/background/services/seedless/handlers/completeRecoveryPhraseExport.test.ts index 7b261d9a..8358df96 100644 --- a/src/background/services/seedless/handlers/completeRecoveryPhraseExport.test.ts +++ b/src/background/services/seedless/handlers/completeRecoveryPhraseExport.test.ts @@ -25,7 +25,10 @@ describe('src/background/services/seedless/handlers/completeRecoveryPhraseExport const secretsService = jest.mocked({ getPrimaryAccountSecrets: jest.fn(), } as any); - const networkService = jest.mocked({} as any); + const networkService = jest.mocked({ + isMainnet: jest.fn(), + getAvalanceProviderXP: jest.fn(), + } as any); const mfaService = jest.mocked({} as any); const accountsService = jest.mocked({} as any); diff --git a/src/background/services/seedless/handlers/completeRecoveryPhraseExport.ts b/src/background/services/seedless/handlers/completeRecoveryPhraseExport.ts index 84d10958..2ffccf63 100644 --- a/src/background/services/seedless/handlers/completeRecoveryPhraseExport.ts +++ b/src/background/services/seedless/handlers/completeRecoveryPhraseExport.ts @@ -17,6 +17,7 @@ import sentryCaptureException, { SentryExceptionTypes, } from '@src/monitoring/sentryCaptureException'; import { AccountsService } from '../../accounts/AccountsService'; +import { getAddressResolutionOptions } from '@src/background/utils/getAddressResolutionOptions'; type HandlerType = ExtensionRequestHandler< ExtensionRequest.SEEDLESS_COMPLETE_RECOVERY_PHRASE_EXPORT, @@ -47,7 +48,9 @@ export class CompleteRecoveryPhraseExportHandler implements HandlerType { } const wallet = new SeedlessWallet({ - networkService: this.networkService, + addressResolutionOptions: await getAddressResolutionOptions( + this.networkService, + ), sessionStorage: new SeedlessTokenStorage(this.secretsService), addressPublicKey: secrets.pubKeys[0], mfaService: this.seedlessMfaService, diff --git a/src/background/services/seedless/handlers/getRecoveryPhraseExportState.test.ts b/src/background/services/seedless/handlers/getRecoveryPhraseExportState.test.ts index 08fb2635..c58f05fc 100644 --- a/src/background/services/seedless/handlers/getRecoveryPhraseExportState.test.ts +++ b/src/background/services/seedless/handlers/getRecoveryPhraseExportState.test.ts @@ -10,11 +10,14 @@ import { AccountsService } from '../../accounts/AccountsService'; jest.mock('../SeedlessWallet'); -describe('src/background/services/seedless/handlers/ge', () => { +describe('src/background/services/seedless/handlers/getRecoveryPhraseExportState', () => { const secretsService = jest.mocked({ getPrimaryAccountSecrets: jest.fn(), } as any); - const networkService = jest.mocked({} as any); + const networkService = jest.mocked({ + isMainnet: jest.fn(), + getAvalanceProviderXP: jest.fn(), + } as any); const mfaService = jest.mocked({} as any); const accountsService = jest.mocked({} as any); diff --git a/src/background/services/seedless/handlers/getRecoveryPhraseExportState.ts b/src/background/services/seedless/handlers/getRecoveryPhraseExportState.ts index bc24e444..5b5dbec9 100644 --- a/src/background/services/seedless/handlers/getRecoveryPhraseExportState.ts +++ b/src/background/services/seedless/handlers/getRecoveryPhraseExportState.ts @@ -12,6 +12,7 @@ import { SeedlessMfaService } from '../SeedlessMfaService'; import { UserExportInitResponse } from '@cubist-labs/cubesigner-sdk'; import { isExportRequestOutdated } from '../utils'; import { AccountsService } from '../../accounts/AccountsService'; +import { getAddressResolutionOptions } from '@src/background/utils/getAddressResolutionOptions'; type HandlerType = ExtensionRequestHandler< ExtensionRequest.SEEDLESS_GET_RECOVERY_PHRASE_EXPORT_STATE, @@ -42,7 +43,9 @@ export class GetRecoveryPhraseExportStateHandler implements HandlerType { } const wallet = new SeedlessWallet({ - networkService: this.networkService, + addressResolutionOptions: await getAddressResolutionOptions( + this.networkService, + ), sessionStorage: new SeedlessTokenStorage(this.secretsService), addressPublicKey: secrets.pubKeys[0], mfaService: this.seedlessMfaService, diff --git a/src/background/services/seedless/handlers/initRecoveryPhraseExport.test.ts b/src/background/services/seedless/handlers/initRecoveryPhraseExport.test.ts index ce311477..d75f2496 100644 --- a/src/background/services/seedless/handlers/initRecoveryPhraseExport.test.ts +++ b/src/background/services/seedless/handlers/initRecoveryPhraseExport.test.ts @@ -14,7 +14,10 @@ describe('src/background/services/seedless/handlers/initRecoveryPhraseExport', ( const secretsService = jest.mocked({ getPrimaryAccountSecrets: jest.fn(), } as any); - const networkService = jest.mocked({} as any); + const networkService = jest.mocked({ + isMainnet: jest.fn(), + getAvalanceProviderXP: jest.fn(), + } as any); const mfaService = jest.mocked({} as any); const accountsService = jest.mocked({} as any); diff --git a/src/background/services/seedless/handlers/initRecoveryPhraseExport.ts b/src/background/services/seedless/handlers/initRecoveryPhraseExport.ts index 043bb84a..b028a21f 100644 --- a/src/background/services/seedless/handlers/initRecoveryPhraseExport.ts +++ b/src/background/services/seedless/handlers/initRecoveryPhraseExport.ts @@ -12,6 +12,7 @@ import { SeedlessMfaService } from '../SeedlessMfaService'; import { UserExportInitResponse } from '@cubist-labs/cubesigner-sdk'; import { isExportRequestOutdated } from '../utils'; import { AccountsService } from '../../accounts/AccountsService'; +import { getAddressResolutionOptions } from '@src/background/utils/getAddressResolutionOptions'; type HandlerType = ExtensionRequestHandler< ExtensionRequest.SEEDLESS_INIT_RECOVERY_PHRASE_EXPORT, @@ -42,7 +43,9 @@ export class InitRecoveryPhraseExportHandler implements HandlerType { } const wallet = new SeedlessWallet({ - networkService: this.networkService, + addressResolutionOptions: await getAddressResolutionOptions( + this.networkService, + ), sessionStorage: new SeedlessTokenStorage(this.secretsService), addressPublicKey: secrets.pubKeys[0], mfaService: this.seedlessMfaService, diff --git a/src/background/services/wallet/WalletService.ts b/src/background/services/wallet/WalletService.ts index ec4facd6..72c91f16 100644 --- a/src/background/services/wallet/WalletService.ts +++ b/src/background/services/wallet/WalletService.ts @@ -62,6 +62,7 @@ import { Network } from '../network/models'; import { AccountsService } from '../accounts/AccountsService'; import { utils } from '@avalabs/avalanchejs'; import { Account } from '../accounts/models'; +import { getAddressResolutionOptions } from '@src/background/utils/getAddressResolutionOptions'; @singleton() export class WalletService implements OnUnlock { @@ -181,7 +182,9 @@ export class WalletService implements OnUnlock { } const wallet = new SeedlessWallet({ - networkService: this.networkService, + addressResolutionOptions: await getAddressResolutionOptions( + this.networkService, + ), sessionStorage: new SeedlessTokenStorage(this.secretService), addressPublicKey, network, diff --git a/src/background/utils/getAddressResolutionOptions.ts b/src/background/utils/getAddressResolutionOptions.ts new file mode 100644 index 00000000..dcb4bc08 --- /dev/null +++ b/src/background/utils/getAddressResolutionOptions.ts @@ -0,0 +1,9 @@ +import { NetworkService } from '../services/network/NetworkService'; +import { AddressResolutionOptions } from '../models'; + +export const getAddressResolutionOptions = async ( + networkService: NetworkService, +): Promise => ({ + isMainnet: networkService.isMainnet(), + providerXP: await networkService.getAvalanceProviderXP(), +});