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 c4ef91ae..717e32a6 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 43bc8e0f..5e25f157 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 { @@ -367,8 +372,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(); @@ -413,7 +418,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 9def7483..2e73c635 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 4e3c15c4..c1d12a29 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 47a03a1c..011669fe 100644 --- a/src/background/services/secrets/SecretsService.test.ts +++ b/src/background/services/secrets/SecretsService.test.ts @@ -1166,13 +1166,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); }); @@ -1190,7 +1189,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'); }); @@ -1214,7 +1216,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( @@ -1242,7 +1247,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( @@ -1269,13 +1277,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); @@ -1302,13 +1312,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( @@ -1352,12 +1364,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' }, }); @@ -1369,11 +1387,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); }); }); @@ -1400,18 +1417,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); }); }); @@ -1429,7 +1448,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'); }); @@ -1439,7 +1461,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'); }); @@ -1448,7 +1473,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); @@ -1466,7 +1494,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'); }); @@ -1480,7 +1511,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); @@ -1530,7 +1564,7 @@ describe('src/background/services/secrets/SecretsService.ts', () => { importType: ImportType.PRIVATE_KEY, data: 'privateKey', }, - networkService + { isMainnet: false, providerXP: getDefaultFujiProviderMock() } ); expect(result).toStrictEqual({ @@ -1569,7 +1603,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'); }); @@ -1589,7 +1623,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'); }); @@ -1609,7 +1643,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'); }); @@ -1632,7 +1666,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'); }); @@ -1643,7 +1680,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'); }); @@ -1657,7 +1697,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'); }); @@ -1668,10 +1711,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 e43fce55..b3e4e940 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. @@ -556,7 +556,7 @@ export class SecretsService implements OnUnlock { async addImportedWallet( importData: ImportData, - networkService: NetworkService + options: AddressResolutionOptions ) { const id = crypto.randomUUID(); @@ -601,7 +601,7 @@ export class SecretsService implements OnUnlock { if (importData.importType === ImportType.PRIVATE_KEY) { const addresses = await this.#calculateAddressesForPrivateKey( importData.data, - networkService + options ); return { account: { @@ -617,7 +617,7 @@ export class SecretsService implements OnUnlock { async #calculateAddressesForPrivateKey( privateKey: string, - networkService: NetworkService + { isMainnet, providerXP }: AddressResolutionOptions ) { const addresses = { addressBTC: '', @@ -627,18 +627,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'); } @@ -660,12 +658,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); @@ -719,7 +717,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], }); @@ -735,13 +733,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'); @@ -753,9 +751,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 || @@ -827,7 +822,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 ( @@ -838,10 +833,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 c1bfcfc4..94c55ad1 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 787e801a..8db8d4aa 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; @@ -393,12 +393,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 ); @@ -483,8 +484,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 a7f0de4f..9c8bc203 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 f854c996..d8a00b76 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 3e108eae..083cbdae 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 0feea0c8..3dfcc9a0 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 8f5e4b80..2393d1cf 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 e31e05f2..bddf2397 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 38f02e6a..a11ce43c 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 0f6cfb4c..19b600f7 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 86a7d5bf..afc2feb5 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..3d805559 --- /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(), +});