Skip to content

Commit 780b9b5

Browse files
authored
feat: P-chain dynamic fees (#74)
1 parent 1969006 commit 780b9b5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1288
-590
lines changed

package.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,25 @@
2323
"sentry": "node sentryscript.js"
2424
},
2525
"dependencies": {
26-
"@avalabs/avalanche-module": "0.11.9",
27-
"@avalabs/avalanchejs": "4.1.0-alpha.15",
28-
"@avalabs/bitcoin-module": "0.11.9",
26+
"@avalabs/avalanche-module": "0.11.12",
27+
"@avalabs/avalanchejs": "4.1.0-alpha.25",
28+
"@avalabs/bitcoin-module": "0.11.12",
2929
"@avalabs/bridge-unified": "0.0.0-feat-ictt-configs-20241009072139",
30-
"@avalabs/core-bridge-sdk": "3.1.0-alpha.11",
31-
"@avalabs/core-chains-sdk": "3.1.0-alpha.11",
32-
"@avalabs/core-coingecko-sdk": "3.1.0-alpha.11",
33-
"@avalabs/core-covalent-sdk": "3.1.0-alpha.11",
34-
"@avalabs/core-etherscan-sdk": "3.1.0-alpha.11",
30+
"@avalabs/core-bridge-sdk": "3.1.0-alpha.19",
31+
"@avalabs/core-chains-sdk": "3.1.0-alpha.19",
32+
"@avalabs/core-coingecko-sdk": "3.1.0-alpha.19",
33+
"@avalabs/core-covalent-sdk": "3.1.0-alpha.19",
34+
"@avalabs/core-etherscan-sdk": "3.1.0-alpha.19",
3535
"@avalabs/core-k2-components": "4.18.0-alpha.47",
36-
"@avalabs/core-snowtrace-sdk": "3.1.0-alpha.11",
37-
"@avalabs/core-token-prices-sdk": "3.1.0-alpha.11",
38-
"@avalabs/core-utils-sdk": "3.1.0-alpha.11",
39-
"@avalabs/core-wallets-sdk": "3.1.0-alpha.11",
40-
"@avalabs/evm-module": "0.11.9",
41-
"@avalabs/glacier-sdk": "3.1.0-alpha.11",
36+
"@avalabs/core-snowtrace-sdk": "3.1.0-alpha.19",
37+
"@avalabs/core-token-prices-sdk": "3.1.0-alpha.19",
38+
"@avalabs/core-utils-sdk": "3.1.0-alpha.19",
39+
"@avalabs/core-wallets-sdk": "3.1.0-alpha.19",
40+
"@avalabs/evm-module": "0.11.12",
41+
"@avalabs/glacier-sdk": "3.1.0-alpha.19",
4242
"@avalabs/hw-app-avalanche": "0.14.1",
43-
"@avalabs/types": "3.1.0-alpha.11",
44-
"@avalabs/vm-module-types": "0.11.9",
43+
"@avalabs/types": "3.1.0-alpha.19",
44+
"@avalabs/vm-module-types": "0.11.12",
4545
"@blockaid/client": "0.10.0",
4646
"@coinbase/cbpay-js": "1.6.0",
4747
"@cubist-labs/cubesigner-sdk": "0.3.28",

src/background/services/accounts/AccountsService.test.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,6 @@ describe('background/services/accounts/AccountsService', () => {
361361
});
362362

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

380379
expect(secretsService.getImportedAddresses).toHaveBeenCalledWith(
381380
'fb-acc',
382-
isMainnet
381+
networkService
383382
);
384383
expect(secretsService.getAddresses).toHaveBeenCalledTimes(0);
385384
expect(accountsService.getAccounts().imported['fb-acc']).toEqual({
@@ -658,7 +657,6 @@ describe('background/services/accounts/AccountsService', () => {
658657
const commitMock = jest.fn();
659658

660659
it('adds account to the imported list correctly', async () => {
661-
const isMainnet = true;
662660
const options: ImportData = {
663661
importType: ImportType.PRIVATE_KEY,
664662
data: 'privateKey',
@@ -684,7 +682,7 @@ describe('background/services/accounts/AccountsService', () => {
684682
expect(secretsService.addImportedWallet).toBeCalledTimes(1);
685683
expect(secretsService.addImportedWallet).toBeCalledWith(
686684
options,
687-
isMainnet
685+
networkService
688686
);
689687
expect(commitMock).toHaveBeenCalled();
690688
expect(permissionsService.addWhitelistDomains).toBeCalledTimes(1);
@@ -718,7 +716,6 @@ describe('background/services/accounts/AccountsService', () => {
718716
});
719717

720718
it('sets default name when no name is given', async () => {
721-
const isMainnet = true;
722719
const options: ImportData = {
723720
importType: ImportType.PRIVATE_KEY,
724721
data: 'privateKey',
@@ -743,7 +740,7 @@ describe('background/services/accounts/AccountsService', () => {
743740
expect(secretsService.addImportedWallet).toBeCalledTimes(1);
744741
expect(secretsService.addImportedWallet).toBeCalledWith(
745742
options,
746-
isMainnet
743+
networkService
747744
);
748745
expect(commitMock).toHaveBeenCalled();
749746
expect(permissionsService.addWhitelistDomains).toBeCalledTimes(1);
@@ -818,7 +815,6 @@ describe('background/services/accounts/AccountsService', () => {
818815
});
819816

820817
it('returns the existing account id on duplicated accounts imports', async () => {
821-
const isMainnet = true;
822818
const options: ImportData = {
823819
importType: ImportType.PRIVATE_KEY,
824820
data: 'privateKey',
@@ -845,7 +841,7 @@ describe('background/services/accounts/AccountsService', () => {
845841
expect(secretsService.addImportedWallet).toBeCalledTimes(1);
846842
expect(secretsService.addImportedWallet).toBeCalledWith(
847843
options,
848-
isMainnet
844+
networkService
849845
);
850846
expect(commitMock).not.toHaveBeenCalled();
851847
expect(permissionsService.addWhitelistDomains).not.toHaveBeenCalled();

src/background/services/accounts/AccountsService.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import getAllAddressesForAccount from '@src/utils/getAllAddressesForAccount';
2626
import { SecretsService } from '../secrets/SecretsService';
2727
import { LedgerService } from '../ledger/LedgerService';
2828
import { WalletConnectService } from '../walletConnect/WalletConnectService';
29+
import { Network } from '../network/models';
30+
import { isDevnet } from '@src/utils/isDevnet';
2931

3032
type AddAccountParams = {
3133
walletId: string;
@@ -98,8 +100,27 @@ export class AccountsService implements OnLock, OnUnlock {
98100
// refresh addresses so in case the user switches to testnet mode,
99101
// as the BTC address needs to be updated
100102
this.networkService.developerModeChanged.add(this.onDeveloperModeChanged);
103+
104+
// TODO(@meeh0w):
105+
// Remove this listener after E-upgrade activation on Fuji. It will be no longer needed.
106+
this.networkService.uiActiveNetworkChanged.add(
107+
this.#onActiveNetworkChanged
108+
);
101109
}
102110

111+
#wasDevnet = false;
112+
113+
#onActiveNetworkChanged = async (network?: Network) => {
114+
if (!network) {
115+
return;
116+
}
117+
118+
if (isDevnet(network) !== this.#wasDevnet) {
119+
this.#wasDevnet = isDevnet(network);
120+
await this.onDeveloperModeChanged(network?.isTestnet);
121+
}
122+
};
123+
103124
onLock() {
104125
this.accounts = {
105126
active: undefined,
@@ -110,6 +131,9 @@ export class AccountsService implements OnLock, OnUnlock {
110131
this.networkService.developerModeChanged.remove(
111132
this.onDeveloperModeChanged
112133
);
134+
this.networkService.uiActiveNetworkChanged.remove(
135+
this.#onActiveNetworkChanged
136+
);
113137
}
114138

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

202226
async getAddressesForAccount(account: Account): Promise<DerivedAddresses> {
203227
if (account.type !== AccountType.PRIMARY) {
204-
const isMainnet = this.networkService.isMainnet();
205-
return this.secretsService.getImportedAddresses(account.id, isMainnet);
228+
return this.secretsService.getImportedAddresses(
229+
account.id,
230+
this.networkService
231+
);
206232
}
207233

208234
const addresses = await this.secretsService.getAddresses(
@@ -385,10 +411,9 @@ export class AccountsService implements OnLock, OnUnlock {
385411
name?: string;
386412
}) {
387413
try {
388-
const isMainnet = this.networkService.isMainnet();
389414
const { account, commit } = await this.secretsService.addImportedWallet(
390415
options,
391-
isMainnet
416+
this.networkService
392417
);
393418

394419
const existingAccount = this.#findAccountByAddress(account.addressC);

src/background/services/balances/BalanceAggregatorService.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,5 +414,73 @@ describe('src/background/services/balances/BalanceAggregatorService.ts', () => {
414414

415415
expect(balances).toEqual(freshBalances);
416416
});
417+
418+
it('emits the BalanceServiceEvents.UPDATED if balances did change', async () => {
419+
// Cached balances include two accounts: account1, account2
420+
const cachedBalances = {
421+
[network2.chainId]: {
422+
...balanceForNetwork2,
423+
[account2.addressC]: network1TokenBalance,
424+
},
425+
};
426+
427+
(storageService.load as jest.Mock).mockResolvedValue({
428+
balances: cachedBalances,
429+
});
430+
431+
await service.onUnlock();
432+
433+
// Fresh balances include only one account (account2) and the values for it HAVE changed
434+
balancesServiceMock.getBalancesForNetwork.mockResolvedValueOnce({
435+
[account2.addressC]: {
436+
[networkToken2.symbol]: {
437+
...network1TokenBalance,
438+
balance: 200n,
439+
balanceDisplayValue: '0.00002',
440+
},
441+
},
442+
});
443+
444+
const updatesListener = jest.fn();
445+
service.addListener(BalanceServiceEvents.UPDATED, updatesListener);
446+
447+
await service.getBalancesForNetworks([network2.chainId], [account2], []);
448+
await new Promise(process.nextTick);
449+
450+
// The fresh balances include new information, therefore an event should be emitted.
451+
expect(updatesListener).toHaveBeenCalled();
452+
});
453+
454+
it('DOES NOT emit the BalanceServiceEvents.UPDATED if balances did not change', async () => {
455+
// Cached balances include two accounts: account1, account2
456+
const cachedBalances = {
457+
[network2.chainId]: {
458+
...balanceForNetwork2,
459+
[account2.addressC]: balanceForNetwork1,
460+
},
461+
};
462+
463+
(storageService.load as jest.Mock).mockResolvedValue({
464+
balances: cachedBalances,
465+
});
466+
467+
await service.onUnlock();
468+
469+
// Fresh balances include only one account (account1) and the values for it DID NOT change
470+
balancesServiceMock.getBalancesForNetwork.mockResolvedValueOnce(
471+
balanceForNetwork2
472+
);
473+
474+
const updatesListener = jest.fn();
475+
service.addListener(BalanceServiceEvents.UPDATED, updatesListener);
476+
477+
await service.getBalancesForNetworks([network2.chainId], [account1], []);
478+
await new Promise(process.nextTick);
479+
480+
// Cached & fresh balances as a whole are different,
481+
// but the fresh balances do not include any new information,
482+
// therefore no event should be emitted.
483+
expect(updatesListener).not.toHaveBeenCalled();
484+
});
417485
});
418486
});

src/background/services/balances/BalanceAggregatorService.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { BalancesService } from './BalancesService';
66
import { NetworkService } from '../network/NetworkService';
77
import { EventEmitter } from 'events';
88
import * as Sentry from '@sentry/browser';
9+
import { isEqual, pick } from 'lodash';
910

1011
import { LockService } from '../lock/LockService';
1112
import { StorageService } from '../storage/StorageService';
1213
import { CachedBalancesInfo } from './models';
13-
import { isEqual, merge } from 'lodash';
1414
import {
1515
PriceChangesData,
1616
TOKENS_PRICE_DATA,
@@ -89,10 +89,18 @@ export class BalanceAggregatorService implements OnLock, OnUnlock {
8989
.map(({ value }) => value);
9090

9191
const networksWithChanges = updatedNetworks
92-
.filter(
93-
({ chainId, networkBalances }) =>
94-
!isEqual(networkBalances, this.balances[chainId])
95-
)
92+
.filter(({ chainId, networkBalances }) => {
93+
// We may have balances of other accounts cached for this chain ID,
94+
// so to check for updates we need to only compare against a subsection
95+
// of the cached balances.
96+
const fetchedAddresses = Object.keys(networkBalances);
97+
const cachedBalances = pick(
98+
this.balances[chainId] ?? {},
99+
fetchedAddresses
100+
);
101+
102+
return !isEqual(networkBalances, cachedBalances);
103+
})
96104
.map(({ chainId }) => chainId);
97105

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

114-
const aggregatedBalances = merge({}, this.balances, freshBalances.tokens);
115122
// NFTs don't have balance = 0, if they are sent they should be removed
116123
// from the list, hence deep merge doesn't work
117124
const hasFetchedNfts =
@@ -123,7 +130,25 @@ export class BalanceAggregatorService implements OnLock, OnUnlock {
123130
...freshBalances.nfts,
124131
}
125132
: this.nfts;
126-
const hasChanges = networksWithChanges.length > 0;
133+
const hasBalanceChanges = networksWithChanges.length > 0;
134+
const hasNftChanges = !isEqual(aggregatedNfts, this.nfts);
135+
const hasChanges = hasBalanceChanges || hasNftChanges;
136+
137+
const aggregatedBalances = { ...this.balances };
138+
if (hasBalanceChanges) {
139+
const freshData = Object.entries(freshBalances.tokens);
140+
// We don't want to merge the account's balances, but overwrite them.
141+
// Merging will result in wrong values when there are nested properties,
142+
// such as UTXOs or "balanceByType" for X/P chains.
143+
for (const [chainId, chainBalances] of freshData) {
144+
for (const [address, addressBalance] of Object.entries(chainBalances)) {
145+
aggregatedBalances[chainId] = {
146+
...chainBalances,
147+
[address]: addressBalance,
148+
};
149+
}
150+
}
151+
}
127152

128153
if (hasChanges && !this.lockService.locked) {
129154
this.#balances = aggregatedBalances;

src/background/services/balances/handlers/getAvaxBalance.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export class GetAvaxBalanceHandler implements HandlerType {
2727
const params = request.params || [];
2828
const [address] = params;
2929
const avalancheNetwork = await this.networkService.getAvalancheNetwork();
30-
const provider = getProviderForNetwork(avalancheNetwork);
30+
const provider = await getProviderForNetwork(avalancheNetwork);
3131
if (
3232
provider instanceof BitcoinProvider ||
3333
provider instanceof Avalanche.JsonRpcProvider

src/background/services/bridge/handlers/avalanche_bridgeAsset.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,9 @@ export class AvalancheBridgeAsset extends DAppRequestHandler<BridgeActionParams>
426426
signAndSendEVM: async (txData) => {
427427
const tx = txData as ContractTransaction; // TODO: update types in the SDK?
428428

429-
const provider = getProviderForNetwork(
429+
const provider = (await getProviderForNetwork(
430430
network
431-
) as JsonRpcBatchInternal;
431+
)) as JsonRpcBatchInternal;
432432

433433
const nonce = await provider.getTransactionCount(
434434
this.#getSourceAccount().addressC

0 commit comments

Comments
 (0)