Skip to content

Commit

Permalink
feat: P-chain dynamic fees (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
meeh0w authored Nov 21, 2024
1 parent 1969006 commit 780b9b5
Show file tree
Hide file tree
Showing 56 changed files with 1,288 additions and 590 deletions.
32 changes: 16 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,25 @@
"sentry": "node sentryscript.js"
},
"dependencies": {
"@avalabs/avalanche-module": "0.11.9",
"@avalabs/avalanchejs": "4.1.0-alpha.15",
"@avalabs/bitcoin-module": "0.11.9",
"@avalabs/avalanche-module": "0.11.12",
"@avalabs/avalanchejs": "4.1.0-alpha.25",
"@avalabs/bitcoin-module": "0.11.12",
"@avalabs/bridge-unified": "0.0.0-feat-ictt-configs-20241009072139",
"@avalabs/core-bridge-sdk": "3.1.0-alpha.11",
"@avalabs/core-chains-sdk": "3.1.0-alpha.11",
"@avalabs/core-coingecko-sdk": "3.1.0-alpha.11",
"@avalabs/core-covalent-sdk": "3.1.0-alpha.11",
"@avalabs/core-etherscan-sdk": "3.1.0-alpha.11",
"@avalabs/core-bridge-sdk": "3.1.0-alpha.19",
"@avalabs/core-chains-sdk": "3.1.0-alpha.19",
"@avalabs/core-coingecko-sdk": "3.1.0-alpha.19",
"@avalabs/core-covalent-sdk": "3.1.0-alpha.19",
"@avalabs/core-etherscan-sdk": "3.1.0-alpha.19",
"@avalabs/core-k2-components": "4.18.0-alpha.47",
"@avalabs/core-snowtrace-sdk": "3.1.0-alpha.11",
"@avalabs/core-token-prices-sdk": "3.1.0-alpha.11",
"@avalabs/core-utils-sdk": "3.1.0-alpha.11",
"@avalabs/core-wallets-sdk": "3.1.0-alpha.11",
"@avalabs/evm-module": "0.11.9",
"@avalabs/glacier-sdk": "3.1.0-alpha.11",
"@avalabs/core-snowtrace-sdk": "3.1.0-alpha.19",
"@avalabs/core-token-prices-sdk": "3.1.0-alpha.19",
"@avalabs/core-utils-sdk": "3.1.0-alpha.19",
"@avalabs/core-wallets-sdk": "3.1.0-alpha.19",
"@avalabs/evm-module": "0.11.12",
"@avalabs/glacier-sdk": "3.1.0-alpha.19",
"@avalabs/hw-app-avalanche": "0.14.1",
"@avalabs/types": "3.1.0-alpha.11",
"@avalabs/vm-module-types": "0.11.9",
"@avalabs/types": "3.1.0-alpha.19",
"@avalabs/vm-module-types": "0.11.12",
"@blockaid/client": "0.10.0",
"@coinbase/cbpay-js": "1.6.0",
"@cubist-labs/cubesigner-sdk": "0.3.28",
Expand Down
12 changes: 4 additions & 8 deletions src/background/services/accounts/AccountsService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,6 @@ describe('background/services/accounts/AccountsService', () => {
});

it('correctly updates addresses for selected imported account', async () => {
const isMainnet = true;
jest
.mocked(secretsService.getImportedAddresses)
.mockImplementation((id) => {
Expand All @@ -379,7 +378,7 @@ describe('background/services/accounts/AccountsService', () => {

expect(secretsService.getImportedAddresses).toHaveBeenCalledWith(
'fb-acc',
isMainnet
networkService
);
expect(secretsService.getAddresses).toHaveBeenCalledTimes(0);
expect(accountsService.getAccounts().imported['fb-acc']).toEqual({
Expand Down Expand Up @@ -658,7 +657,6 @@ describe('background/services/accounts/AccountsService', () => {
const commitMock = jest.fn();

it('adds account to the imported list correctly', async () => {
const isMainnet = true;
const options: ImportData = {
importType: ImportType.PRIVATE_KEY,
data: 'privateKey',
Expand All @@ -684,7 +682,7 @@ describe('background/services/accounts/AccountsService', () => {
expect(secretsService.addImportedWallet).toBeCalledTimes(1);
expect(secretsService.addImportedWallet).toBeCalledWith(
options,
isMainnet
networkService
);
expect(commitMock).toHaveBeenCalled();
expect(permissionsService.addWhitelistDomains).toBeCalledTimes(1);
Expand Down Expand Up @@ -718,7 +716,6 @@ describe('background/services/accounts/AccountsService', () => {
});

it('sets default name when no name is given', async () => {
const isMainnet = true;
const options: ImportData = {
importType: ImportType.PRIVATE_KEY,
data: 'privateKey',
Expand All @@ -743,7 +740,7 @@ describe('background/services/accounts/AccountsService', () => {
expect(secretsService.addImportedWallet).toBeCalledTimes(1);
expect(secretsService.addImportedWallet).toBeCalledWith(
options,
isMainnet
networkService
);
expect(commitMock).toHaveBeenCalled();
expect(permissionsService.addWhitelistDomains).toBeCalledTimes(1);
Expand Down Expand Up @@ -818,7 +815,6 @@ describe('background/services/accounts/AccountsService', () => {
});

it('returns the existing account id on duplicated accounts imports', async () => {
const isMainnet = true;
const options: ImportData = {
importType: ImportType.PRIVATE_KEY,
data: 'privateKey',
Expand All @@ -845,7 +841,7 @@ describe('background/services/accounts/AccountsService', () => {
expect(secretsService.addImportedWallet).toBeCalledTimes(1);
expect(secretsService.addImportedWallet).toBeCalledWith(
options,
isMainnet
networkService
);
expect(commitMock).not.toHaveBeenCalled();
expect(permissionsService.addWhitelistDomains).not.toHaveBeenCalled();
Expand Down
33 changes: 29 additions & 4 deletions src/background/services/accounts/AccountsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import getAllAddressesForAccount from '@src/utils/getAllAddressesForAccount';
import { SecretsService } from '../secrets/SecretsService';
import { LedgerService } from '../ledger/LedgerService';
import { WalletConnectService } from '../walletConnect/WalletConnectService';
import { Network } from '../network/models';
import { isDevnet } from '@src/utils/isDevnet';

type AddAccountParams = {
walletId: string;
Expand Down Expand Up @@ -98,8 +100,27 @@ export class AccountsService implements OnLock, OnUnlock {
// refresh addresses so in case the user switches to testnet mode,
// as the BTC address needs to be updated
this.networkService.developerModeChanged.add(this.onDeveloperModeChanged);

// TODO(@meeh0w):
// Remove this listener after E-upgrade activation on Fuji. It will be no longer needed.
this.networkService.uiActiveNetworkChanged.add(
this.#onActiveNetworkChanged
);
}

#wasDevnet = false;

#onActiveNetworkChanged = async (network?: Network) => {
if (!network) {
return;
}

if (isDevnet(network) !== this.#wasDevnet) {
this.#wasDevnet = isDevnet(network);
await this.onDeveloperModeChanged(network?.isTestnet);
}
};

onLock() {
this.accounts = {
active: undefined,
Expand All @@ -110,6 +131,9 @@ export class AccountsService implements OnLock, OnUnlock {
this.networkService.developerModeChanged.remove(
this.onDeveloperModeChanged
);
this.networkService.uiActiveNetworkChanged.remove(
this.#onActiveNetworkChanged
);
}

private onDeveloperModeChanged = async (isTestnet?: boolean) => {
Expand Down Expand Up @@ -201,8 +225,10 @@ export class AccountsService implements OnLock, OnUnlock {

async getAddressesForAccount(account: Account): Promise<DerivedAddresses> {
if (account.type !== AccountType.PRIMARY) {
const isMainnet = this.networkService.isMainnet();
return this.secretsService.getImportedAddresses(account.id, isMainnet);
return this.secretsService.getImportedAddresses(
account.id,
this.networkService
);
}

const addresses = await this.secretsService.getAddresses(
Expand Down Expand Up @@ -385,10 +411,9 @@ export class AccountsService implements OnLock, OnUnlock {
name?: string;
}) {
try {
const isMainnet = this.networkService.isMainnet();
const { account, commit } = await this.secretsService.addImportedWallet(
options,
isMainnet
this.networkService
);

const existingAccount = this.#findAccountByAddress(account.addressC);
Expand Down
68 changes: 68 additions & 0 deletions src/background/services/balances/BalanceAggregatorService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,5 +414,73 @@ describe('src/background/services/balances/BalanceAggregatorService.ts', () => {

expect(balances).toEqual(freshBalances);
});

it('emits the BalanceServiceEvents.UPDATED if balances did change', async () => {
// Cached balances include two accounts: account1, account2
const cachedBalances = {
[network2.chainId]: {
...balanceForNetwork2,
[account2.addressC]: network1TokenBalance,
},
};

(storageService.load as jest.Mock).mockResolvedValue({
balances: cachedBalances,
});

await service.onUnlock();

// Fresh balances include only one account (account2) and the values for it HAVE changed
balancesServiceMock.getBalancesForNetwork.mockResolvedValueOnce({
[account2.addressC]: {
[networkToken2.symbol]: {
...network1TokenBalance,
balance: 200n,
balanceDisplayValue: '0.00002',
},
},
});

const updatesListener = jest.fn();
service.addListener(BalanceServiceEvents.UPDATED, updatesListener);

await service.getBalancesForNetworks([network2.chainId], [account2], []);
await new Promise(process.nextTick);

// The fresh balances include new information, therefore an event should be emitted.
expect(updatesListener).toHaveBeenCalled();
});

it('DOES NOT emit the BalanceServiceEvents.UPDATED if balances did not change', async () => {
// Cached balances include two accounts: account1, account2
const cachedBalances = {
[network2.chainId]: {
...balanceForNetwork2,
[account2.addressC]: balanceForNetwork1,
},
};

(storageService.load as jest.Mock).mockResolvedValue({
balances: cachedBalances,
});

await service.onUnlock();

// Fresh balances include only one account (account1) and the values for it DID NOT change
balancesServiceMock.getBalancesForNetwork.mockResolvedValueOnce(
balanceForNetwork2
);

const updatesListener = jest.fn();
service.addListener(BalanceServiceEvents.UPDATED, updatesListener);

await service.getBalancesForNetworks([network2.chainId], [account1], []);
await new Promise(process.nextTick);

// Cached & fresh balances as a whole are different,
// but the fresh balances do not include any new information,
// therefore no event should be emitted.
expect(updatesListener).not.toHaveBeenCalled();
});
});
});
39 changes: 32 additions & 7 deletions src/background/services/balances/BalanceAggregatorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { BalancesService } from './BalancesService';
import { NetworkService } from '../network/NetworkService';
import { EventEmitter } from 'events';
import * as Sentry from '@sentry/browser';
import { isEqual, pick } from 'lodash';

import { LockService } from '../lock/LockService';
import { StorageService } from '../storage/StorageService';
import { CachedBalancesInfo } from './models';
import { isEqual, merge } from 'lodash';
import {
PriceChangesData,
TOKENS_PRICE_DATA,
Expand Down Expand Up @@ -89,10 +89,18 @@ export class BalanceAggregatorService implements OnLock, OnUnlock {
.map(({ value }) => value);

const networksWithChanges = updatedNetworks
.filter(
({ chainId, networkBalances }) =>
!isEqual(networkBalances, this.balances[chainId])
)
.filter(({ chainId, networkBalances }) => {
// We may have balances of other accounts cached for this chain ID,
// so to check for updates we need to only compare against a subsection
// of the cached balances.
const fetchedAddresses = Object.keys(networkBalances);
const cachedBalances = pick(
this.balances[chainId] ?? {},
fetchedAddresses
);

return !isEqual(networkBalances, cachedBalances);
})
.map(({ chainId }) => chainId);

const freshBalances = updatedNetworks.reduce<{
Expand All @@ -111,7 +119,6 @@ export class BalanceAggregatorService implements OnLock, OnUnlock {
{ tokens: {}, nfts: {} }
);

const aggregatedBalances = merge({}, this.balances, freshBalances.tokens);
// NFTs don't have balance = 0, if they are sent they should be removed
// from the list, hence deep merge doesn't work
const hasFetchedNfts =
Expand All @@ -123,7 +130,25 @@ export class BalanceAggregatorService implements OnLock, OnUnlock {
...freshBalances.nfts,
}
: this.nfts;
const hasChanges = networksWithChanges.length > 0;
const hasBalanceChanges = networksWithChanges.length > 0;
const hasNftChanges = !isEqual(aggregatedNfts, this.nfts);
const hasChanges = hasBalanceChanges || hasNftChanges;

const aggregatedBalances = { ...this.balances };
if (hasBalanceChanges) {
const freshData = Object.entries(freshBalances.tokens);
// We don't want to merge the account's balances, but overwrite them.
// Merging will result in wrong values when there are nested properties,
// such as UTXOs or "balanceByType" for X/P chains.
for (const [chainId, chainBalances] of freshData) {
for (const [address, addressBalance] of Object.entries(chainBalances)) {
aggregatedBalances[chainId] = {
...chainBalances,
[address]: addressBalance,
};
}
}
}

if (hasChanges && !this.lockService.locked) {
this.#balances = aggregatedBalances;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class GetAvaxBalanceHandler implements HandlerType {
const params = request.params || [];
const [address] = params;
const avalancheNetwork = await this.networkService.getAvalancheNetwork();
const provider = getProviderForNetwork(avalancheNetwork);
const provider = await getProviderForNetwork(avalancheNetwork);
if (
provider instanceof BitcoinProvider ||
provider instanceof Avalanche.JsonRpcProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,9 +426,9 @@ export class AvalancheBridgeAsset extends DAppRequestHandler<BridgeActionParams>
signAndSendEVM: async (txData) => {
const tx = txData as ContractTransaction; // TODO: update types in the SDK?

const provider = getProviderForNetwork(
const provider = (await getProviderForNetwork(
network
) as JsonRpcBatchInternal;
)) as JsonRpcBatchInternal;

const nonce = await provider.getTransactionCount(
this.#getSourceAccount().addressC
Expand Down
Loading

0 comments on commit 780b9b5

Please sign in to comment.