Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: P-chain dynamic fees #74

Merged
merged 15 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.10",
"@avalabs/avalanchejs": "4.1.0-alpha.21",
"@avalabs/bitcoin-module": "0.11.10",
"@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.13",
"@avalabs/core-chains-sdk": "3.1.0-alpha.13",
"@avalabs/core-coingecko-sdk": "3.1.0-alpha.13",
"@avalabs/core-covalent-sdk": "3.1.0-alpha.13",
"@avalabs/core-etherscan-sdk": "3.1.0-alpha.13",
"@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.13",
"@avalabs/core-token-prices-sdk": "3.1.0-alpha.13",
"@avalabs/core-utils-sdk": "3.1.0-alpha.13",
"@avalabs/core-wallets-sdk": "3.1.0-alpha.13",
"@avalabs/evm-module": "0.11.10",
"@avalabs/glacier-sdk": "3.1.0-alpha.13",
"@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.13",
"@avalabs/vm-module-types": "0.11.10",
"@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(
meeh0w marked this conversation as resolved.
Show resolved Hide resolved
this.#onActiveNetworkChanged
);
}

#wasDevnet = false;

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

if (isDevnet(network) !== this.#wasDevnet) {
meeh0w marked this conversation as resolved.
Show resolved Hide resolved
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 }) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes a bug I've noticed in the P-Chain balances fetching.

Basically, when user has multiple accounts derived, we would always detect changes here because we only poll for the active account and we'd have balances for multiple addresses cached.

// 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,
};
}
}
}
meeh0w marked this conversation as resolved.
Show resolved Hide resolved

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
Loading