diff --git a/.detoxrc.js b/.detoxrc.js
index 0b5b837895f..dae464e8324 100644
--- a/.detoxrc.js
+++ b/.detoxrc.js
@@ -4,6 +4,8 @@ module.exports = {
args: {
config: 'e2e/jest.e2e.config.js',
_: ['e2e'],
+ bail: true,
+ forceExit: true,
},
},
devices: {
diff --git a/.eslintrc.js b/.eslintrc.js
index 6df1e6e1e52..f8cb2150123 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -39,5 +39,6 @@ module.exports = {
],
'jest/expect-expect': 'off',
'jest/no-disabled-tests': 'off',
+ 'no-nested-ternary': 'off',
},
};
diff --git a/.github/workflows/macstadium-tests.yml b/.github/workflows/macstadium-tests.yml
index b4dfe0d643e..4748f7b8df8 100644
--- a/.github/workflows/macstadium-tests.yml
+++ b/.github/workflows/macstadium-tests.yml
@@ -154,7 +154,7 @@ jobs:
run: |
rm -rf /Users/administrator/.cocoapods/repos/cocoapods/.git/index.lock
yarn install-bundle && yarn install-pods
-
+
- uses: irgaly/xcode-cache@v1
with:
key: xcode-cache-deriveddata-${{ github.workflow }}-${{ github.sha }}
@@ -164,11 +164,12 @@ jobs:
run: |
sed -i'' -e "s/IS_TESTING=false/IS_TESTING=true/" .env && rm -f .env-e
yarn detox build --configuration ios.sim.release
+ - name: Detox iOS e2e tests serial
+ run: |
+ ./scripts/run-serial-e2e.sh 3
- name: Detox iOS e2e tests parallel
run: |
- ./scripts/run-parallel-e2e.sh 3
+ ./scripts/run-parallel-e2e.sh
- - name: Detox iOS e2e tests serial
- run: |
- ./scripts/run-serial-e2e.sh 3
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4569b2c3837..946a7d3dbb3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/)
### Fixed
+## [1.9.50] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.50)
+
+### Fixed
+
+- Fixed an issue with unlocking app icons (#6342, #6345)
+
## [1.9.49] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.49)
### Added
diff --git a/android/app/build.gradle b/android/app/build.gradle
index d335045ad7b..9007b06bd09 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -135,8 +135,8 @@ android {
applicationId "me.rainbow"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 243
- versionName "1.9.50"
+ versionCode 244
+ versionName "1.9.51"
missingDimensionStrategy 'react-native-camera', 'general'
renderscriptTargetApi 23
renderscriptSupportModeEnabled true
diff --git a/e2e/Disabled/hardhatTransactionFlowSendAndWC.disable.js b/e2e/Disabled/anvilTransactionFlowSendAndWC.disable.js
similarity index 97%
rename from e2e/Disabled/hardhatTransactionFlowSendAndWC.disable.js
rename to e2e/Disabled/anvilTransactionFlowSendAndWC.disable.js
index 078e7ef09d2..d31a2bd1c3d 100644
--- a/e2e/Disabled/hardhatTransactionFlowSendAndWC.disable.js
+++ b/e2e/Disabled/anvilTransactionFlowSendAndWC.disable.js
@@ -40,11 +40,11 @@ const getOnchainBalance = async (address, tokenContractAddress) => {
};
beforeAll(async () => {
- await Helpers.startHardhat();
+ await Helpers.startAnvil();
await Helpers.startIosSimulator();
});
-describe.skip('Hardhat Transaction Flow', () => {
+describe.skip('Anvil Transaction Flow', () => {
it('Should show the welcome screen', async () => {
await Helpers.checkIfVisible('welcome-screen');
});
@@ -85,9 +85,9 @@ describe.skip('Hardhat Transaction Flow', () => {
await Helpers.sendETHtoTestWallet();
});
- it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => {
- await Helpers.waitAndTap('dev-button-hardhat');
- await Helpers.checkIfVisible('testnet-toast-Hardhat');
+ it('Should show Anvil Toast after pressing Connect To Anvil', async () => {
+ await Helpers.waitAndTap('dev-button-anvil');
+ await Helpers.checkIfVisible('testnet-toast-Anvil');
});
it('Should open send sheet after tapping send fab', async () => {
@@ -392,6 +392,6 @@ describe.skip('Hardhat Transaction Flow', () => {
await connector?.killSession();
connector = null;
await device.clearKeychain();
- await Helpers.killHardhat();
+ await Helpers.killAnvil();
});
});
diff --git a/e2e/Disabled/hardhatTransactionFlowSwaps.disabled.js b/e2e/Disabled/anvilTransactionFlowSwaps.disabled.js
similarity index 98%
rename from e2e/Disabled/hardhatTransactionFlowSwaps.disabled.js
rename to e2e/Disabled/anvilTransactionFlowSwaps.disabled.js
index 1ff90c981af..7564b4a96ca 100644
--- a/e2e/Disabled/hardhatTransactionFlowSwaps.disabled.js
+++ b/e2e/Disabled/anvilTransactionFlowSwaps.disabled.js
@@ -6,7 +6,7 @@ const ios = device.getPlatform() === 'ios';
const android = device.getPlatform() === 'android';
beforeAll(async () => {
- await Helpers.startHardhat();
+ await Helpers.startAnvil();
await Helpers.startIosSimulator();
});
@@ -29,11 +29,11 @@ const checkIfSwapCompleted = async (assetName, amount) => {
};
// FIXME: Mainnet DAI doesn't show up in the swap search results
-// This might be related to @Jin's latest work on changes to hardhat as
+// This might be related to @Jin's latest work on changes to anvil as
// part of the addy's REST API migration
//
// marking the test as SKIP for now
-describe.skip('Hardhat Transaction Flow', () => {
+describe.skip('Anvil Transaction Flow', () => {
it('Should show the welcome screen', async () => {
await Helpers.checkIfVisible('welcome-screen');
});
@@ -74,9 +74,9 @@ describe.skip('Hardhat Transaction Flow', () => {
await Helpers.sendETHtoTestWallet();
});
- it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => {
- await Helpers.waitAndTap('dev-button-hardhat');
- await Helpers.checkIfVisible('testnet-toast-Hardhat');
+ it('Should show Anvil Toast after pressing Connect To Anvil', async () => {
+ await Helpers.waitAndTap('dev-button-anvil');
+ await Helpers.checkIfVisible('testnet-toast-Anvil');
});
it.skip('Should deposit DAI (via Compound)', async () => {
@@ -414,6 +414,6 @@ describe.skip('Hardhat Transaction Flow', () => {
await connector?.killSession();
connector = null;
await device.clearKeychain();
- await Helpers.killHardhat();
+ await Helpers.killAnvil();
});
});
diff --git a/e2e/Disabled/registerENSFlow.disabled.js b/e2e/Disabled/registerENSFlow.disabled.js
index a177ab85c65..a6677f74b85 100644
--- a/e2e/Disabled/registerENSFlow.disabled.js
+++ b/e2e/Disabled/registerENSFlow.disabled.js
@@ -110,7 +110,7 @@ const validatePrimaryName = async name => {
};
beforeAll(async () => {
- await Helpers.startHardhat();
+ await Helpers.startAnvil();
await Helpers.startIosSimulator();
});
@@ -155,9 +155,9 @@ describe.skip('Register ENS Flow', () => {
await Helpers.sendETHtoTestWallet();
});
- it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => {
- await Helpers.waitAndTap('dev-button-hardhat');
- await Helpers.checkIfVisible('testnet-toast-Hardhat');
+ it('Should show Anvil Toast after pressing Connect To Anvil', async () => {
+ await Helpers.waitAndTap('dev-button-anvil');
+ await Helpers.checkIfVisible('testnet-toast-Anvil');
});
it('Should navigate to the Discover sheet screen after swiping left', async () => {
@@ -637,6 +637,6 @@ describe.skip('Register ENS Flow', () => {
afterAll(async () => {
// Reset the app state
await device.clearKeychain();
- await Helpers.killHardhat();
+ await Helpers.killAnvil();
});
});
diff --git a/e2e/Disabled/sendSheetFlow.disabled.ts b/e2e/Disabled/sendSheetFlow.disabled.ts
index 93b50f7091a..8c18386033e 100644
--- a/e2e/Disabled/sendSheetFlow.disabled.ts
+++ b/e2e/Disabled/sendSheetFlow.disabled.ts
@@ -1,7 +1,7 @@
import { device } from 'detox';
import {
- startHardhat,
- killHardhat,
+ startAnvil,
+ killAnvil,
importWalletFlow,
sendETHtoTestWallet,
waitAndTap,
@@ -16,11 +16,11 @@ import {
describe.skip('Send Sheet Interaction Flow', () => {
beforeAll(async () => {
await device.reloadReactNative();
- await startHardhat();
+ await startAnvil();
});
afterAll(async () => {
await device.clearKeychain();
- await killHardhat();
+ await killAnvil();
});
it('Import a wallet and go to welcome', async () => {
@@ -31,9 +31,9 @@ describe.skip('Send Sheet Interaction Flow', () => {
await sendETHtoTestWallet();
});
- it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => {
- await waitAndTap('dev-button-hardhat');
- await checkIfVisible('testnet-toast-Hardhat');
+ it('Should show Anvil Toast after pressing Connect To Anvil', async () => {
+ await waitAndTap('dev-button-anvil');
+ await checkIfVisible('testnet-toast-Anvil');
});
it('Should open send sheet after tapping send button', async () => {
diff --git a/e2e/Disabled/swapSheetFlow1.disabled.js b/e2e/Disabled/swapSheetFlow1.disabled.js
index a7ed282b8dc..f9e77663162 100644
--- a/e2e/Disabled/swapSheetFlow1.disabled.js
+++ b/e2e/Disabled/swapSheetFlow1.disabled.js
@@ -9,11 +9,11 @@ const android = device.getPlatform() === 'android';
describe.skip('Swap Sheet Interaction Flow', () => {
beforeAll(async () => {
- await Helpers.startHardhat();
+ await Helpers.startAnvil();
});
afterAll(async () => {
await device.clearKeychain();
- await Helpers.killHardhat();
+ await Helpers.killAnvil();
});
it('Import a wallet and go to welcome', async () => {
@@ -24,14 +24,14 @@ describe.skip('Swap Sheet Interaction Flow', () => {
await Helpers.sendETHtoTestWallet();
});
- it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => {
+ it('Should show Anvil Toast after pressing Connect To Anvil', async () => {
await Helpers.delayTime('very-long');
- await Helpers.waitAndTap('dev-button-hardhat');
- await Helpers.checkIfVisible('testnet-toast-Hardhat');
+ await Helpers.waitAndTap('dev-button-anvil');
+ await Helpers.checkIfVisible('testnet-toast-Anvil');
});
// FIXME: Mainnet DAI doesn't show up in the swap search results
- // This might be related to @Jin's latest work on changes to hardhat as
+ // This might be related to @Jin's latest work on changes to anvil as
// part of the addy's REST API migration
//
// marking the test as SKIP for now
diff --git a/e2e/Disabled/swapSheetFlow2.disabled.js b/e2e/Disabled/swapSheetFlow2.disabled.js
index 7c91840a432..97b13a821d8 100644
--- a/e2e/Disabled/swapSheetFlow2.disabled.js
+++ b/e2e/Disabled/swapSheetFlow2.disabled.js
@@ -2,14 +2,14 @@ import * as Helpers from '../helpers';
import { device } from 'detox';
beforeAll(async () => {
- await Helpers.startHardhat();
+ await Helpers.startAnvil();
});
const ios = device.getPlatform() === 'ios';
const android = device.getPlatform() === 'android';
// FIXME: Mainnet DAI doesn't show up in the swap search results
-// This might be related to @Jin's latest work on changes to hardhat as
+// This might be related to @Jin's latest work on changes to anvil as
// part of the addy's REST API migration
//
// marking the test as SKIP for now
@@ -54,14 +54,14 @@ describe.skip('Swap Sheet Interaction Flow', () => {
await Helpers.sendETHtoTestWallet();
});
- it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => {
+ it('Should show Anvil Toast after pressing Connect To Anvil', async () => {
// need to wait for balances to be fetched
await Helpers.delay(10000);
- await Helpers.waitAndTap('dev-button-hardhat');
- await Helpers.checkIfVisible('testnet-toast-Hardhat');
+ await Helpers.waitAndTap('dev-button-anvil');
+ await Helpers.checkIfVisible('testnet-toast-Anvil');
});
- it('Should connect to hardhat', async () => {
+ it('Should connect to anvil', async () => {
await Helpers.swipe('wallet-screen', 'right', 'slow');
await Helpers.checkIfVisible('profile-screen');
await Helpers.waitAndTap('settings-button');
@@ -69,8 +69,8 @@ describe.skip('Swap Sheet Interaction Flow', () => {
await Helpers.scrollTo('settings-menu-container', 'bottom');
await Helpers.waitAndTap('developer-section');
await Helpers.swipeUntilVisible('alert-section', 'developer-settings-sheet', 'up');
- await Helpers.waitAndTap('hardhat-section');
- await Helpers.checkIfVisible('testnet-toast-Hardhat');
+ await Helpers.waitAndTap('anvil-section');
+ await Helpers.checkIfVisible('testnet-toast-Anvil');
await Helpers.swipe('profile-screen', 'left', 'slow');
});
@@ -474,6 +474,6 @@ describe.skip('Swap Sheet Interaction Flow', () => {
afterAll(async () => {
// Reset the app state
await device.clearKeychain();
- await Helpers.killHardhat();
+ await Helpers.killAnvil();
});
});
diff --git a/e2e/helpers.ts b/e2e/helpers.ts
index 441a9d0144d..2b0a39e880a 100644
--- a/e2e/helpers.ts
+++ b/e2e/helpers.ts
@@ -4,7 +4,7 @@ import { exec } from 'child_process';
import { JsonRpcProvider } from '@ethersproject/providers';
import { Wallet } from '@ethersproject/wallet';
import { expect, device, element, by, waitFor } from 'detox';
-import { parseEther } from '@ethersproject/units';
+import { formatEther, parseEther } from '@ethersproject/units';
import { IosElementAttributes, AndroidElementAttributes } from 'detox/detox';
const TESTING_WALLET = '0x3637f053D542E6D00Eee42D656dD7C59Fa33a62F';
@@ -20,12 +20,12 @@ interface ProviderFunction {
_instance?: JsonRpcProvider;
}
-export async function startHardhat() {
+export async function startAnvil() {
await delayTime('short');
- exec('yarn hardhat');
+ exec('yarn anvil');
}
-export async function killHardhat() {
+export async function killAnvil() {
await delayTime('short');
exec('kill $(lsof -t -i:8545)');
}
@@ -53,14 +53,14 @@ export async function importWalletFlow(customSeed?: string) {
await checkIfVisible('wallet-screen');
}
-export async function beforeAllcleanApp({ hardhat }: { hardhat?: boolean }) {
+export async function beforeAllcleanApp({ anvil }: { anvil?: boolean }) {
jest.resetAllMocks();
- hardhat && (await startHardhat());
+ anvil && (await startAnvil());
}
-export async function afterAllcleanApp({ hardhat }: { hardhat?: boolean }) {
+export async function afterAllcleanApp({ anvil }: { anvil?: boolean }) {
await device.clearKeychain();
- hardhat && (await killHardhat());
+ anvil && (await killAnvil());
}
export async function tap(elementId: string | RegExp) {
@@ -467,16 +467,23 @@ export const getProvider: ProviderFunction = () => {
};
export async function sendETHtoTestWallet() {
+ console.log('getting provider');
const provider = getProvider();
- // Hardhat account 0 that has 10000 ETH
+ console.log('got provider', provider);
+ // anvil account 0 that has 10000 ETH
const wallet = new Wallet('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', provider);
+ console.log('got wallet', wallet);
// Sending 20 ETH so we have enough to pay the tx fees even when the gas is too high
+ console.log('sending eth');
await wallet.sendTransaction({
to: TESTING_WALLET,
value: parseEther('20'),
});
+ console.log('sent eth');
await delayTime('long');
+ console.log('checking balance');
const balance = await provider.getBalance(TESTING_WALLET);
+ console.log('got balance', formatEther(balance));
if (balance.lt(parseEther('20'))) {
throw Error('Error sending ETH to test wallet');
}
diff --git a/e2e/init.js b/e2e/init.js
index a8567e849b3..86d68eb47ad 100644
--- a/e2e/init.js
+++ b/e2e/init.js
@@ -8,7 +8,7 @@ beforeAll(async () => {
if (device.getPlatform() === 'android') {
// connecting to metro
await device.reverseTcpPort(8081);
- // connecting to hardhat
+ // connecting to anvil
await device.reverseTcpPort(8545); // TODO: WIP for android connecting in dev
// make sure we don't have gesture navigation what might cause collisions
diff --git a/e2e/parallel/1_importAndWatchWalletsFlow.spec.ts b/e2e/parallel/1_importAndWatchWalletsFlow.spec.ts
index 0e9b30cdaab..2259062c196 100644
--- a/e2e/parallel/1_importAndWatchWalletsFlow.spec.ts
+++ b/e2e/parallel/1_importAndWatchWalletsFlow.spec.ts
@@ -16,10 +16,10 @@ const android = device.getPlatform() === 'android';
describe('Import from private key flow', () => {
beforeAll(async () => {
- await beforeAllcleanApp({ hardhat: false });
+ await beforeAllcleanApp({ anvil: false });
});
afterAll(async () => {
- await afterAllcleanApp({ hardhat: false });
+ await afterAllcleanApp({ anvil: false });
});
it('with 0x - Should show the welcome screen', async () => {
await checkIfVisible('welcome-screen');
diff --git a/e2e/parallel/2_newWalletFlow.spec.ts b/e2e/parallel/2_newWalletFlow.spec.ts
index 241dcb4e5d1..5db07d964f6 100644
--- a/e2e/parallel/2_newWalletFlow.spec.ts
+++ b/e2e/parallel/2_newWalletFlow.spec.ts
@@ -5,10 +5,10 @@ const android = device.getPlatform() === 'android';
describe('New Wallet flow', () => {
beforeAll(async () => {
- await beforeAllcleanApp({ hardhat: false });
+ await beforeAllcleanApp({ anvil: false });
});
afterAll(async () => {
- await afterAllcleanApp({ hardhat: false });
+ await afterAllcleanApp({ anvil: false });
});
it('should show the welcome screen', async () => {
diff --git a/e2e/parallel/3_homeScreen.spec.ts b/e2e/parallel/3_homeScreen.spec.ts
index 1e32821ace8..8f78e63cfb5 100644
--- a/e2e/parallel/3_homeScreen.spec.ts
+++ b/e2e/parallel/3_homeScreen.spec.ts
@@ -14,10 +14,10 @@ const RAINBOW_TEST_WALLET = 'rainbowtestwallet.eth';
describe('Home Screen', () => {
beforeAll(async () => {
- await beforeAllcleanApp({ hardhat: false });
+ await beforeAllcleanApp({ anvil: false });
});
afterAll(async () => {
- await afterAllcleanApp({ hardhat: false });
+ await afterAllcleanApp({ anvil: false });
});
it('imports wallet', async () => {
diff --git a/e2e/parallel/4_discoverSheetFlow.spec.ts b/e2e/parallel/4_discoverSheetFlow.spec.ts
index 0e2f9d632a3..5af4b8abd7c 100644
--- a/e2e/parallel/4_discoverSheetFlow.spec.ts
+++ b/e2e/parallel/4_discoverSheetFlow.spec.ts
@@ -17,10 +17,10 @@ const ios = device.getPlatform() === 'ios';
describe('Discover Screen Flow', () => {
beforeAll(async () => {
- await beforeAllcleanApp({ hardhat: false });
+ await beforeAllcleanApp({ anvil: false });
});
afterAll(async () => {
- await afterAllcleanApp({ hardhat: false });
+ await afterAllcleanApp({ anvil: false });
});
it('Should import wallet and go to wallet screen', async () => {
await importWalletFlow();
diff --git a/e2e/parallel/5_watchedWalletCollectionActionsFlow.spec.ts b/e2e/parallel/5_watchedWalletCollectionActionsFlow.spec.ts
index 03fa4a5ab3e..b2c56b8656f 100644
--- a/e2e/parallel/5_watchedWalletCollectionActionsFlow.spec.ts
+++ b/e2e/parallel/5_watchedWalletCollectionActionsFlow.spec.ts
@@ -13,10 +13,10 @@ import {
describe('Watched showcase and hidden actions flow', () => {
beforeAll(async () => {
- await beforeAllcleanApp({ hardhat: false });
+ await beforeAllcleanApp({ anvil: false });
});
afterAll(async () => {
- await afterAllcleanApp({ hardhat: false });
+ await afterAllcleanApp({ anvil: false });
});
it('watches a wallet and loads wallet screen', async () => {
diff --git a/e2e/parallel/6_maliciousDappConnection.spec.ts b/e2e/parallel/6_maliciousDappConnection.spec.ts
index ad13c226403..2a3e1b890b9 100644
--- a/e2e/parallel/6_maliciousDappConnection.spec.ts
+++ b/e2e/parallel/6_maliciousDappConnection.spec.ts
@@ -15,11 +15,11 @@ import { WALLET_VARS } from '../testVariables';
describe('Check malicious dapp warning', () => {
beforeAll(async () => {
- await beforeAllcleanApp({ hardhat: false });
+ await beforeAllcleanApp({ anvil: false });
});
afterAll(async () => {
- await afterAllcleanApp({ hardhat: false });
+ await afterAllcleanApp({ anvil: false });
});
it('Should be able to watch a wallet and load the wallet screen', async () => {
diff --git a/e2e/parallel/7_manualBackup.spec.ts b/e2e/parallel/7_manualBackup.spec.ts
index 680e042fc4d..9fd08830bca 100644
--- a/e2e/parallel/7_manualBackup.spec.ts
+++ b/e2e/parallel/7_manualBackup.spec.ts
@@ -14,10 +14,10 @@ import {
describe('Backups', () => {
beforeAll(async () => {
- await beforeAllcleanApp({ hardhat: false });
+ await beforeAllcleanApp({ anvil: false });
});
afterAll(async () => {
- await afterAllcleanApp({ hardhat: false });
+ await afterAllcleanApp({ anvil: false });
});
it('Imports wallet', async () => {
diff --git a/e2e/serial/1_sendSheetFlowContacts.spec.ts b/e2e/serial/1_sendSheetFlowContacts.spec.ts
index ece0ee36771..005173d66a3 100644
--- a/e2e/serial/1_sendSheetFlowContacts.spec.ts
+++ b/e2e/serial/1_sendSheetFlowContacts.spec.ts
@@ -18,10 +18,10 @@ const android = device.getPlatform() === 'android';
describe('Send Sheet Interaction Flow Contacts', () => {
beforeAll(async () => {
- await beforeAllcleanApp({ hardhat: true });
+ await beforeAllcleanApp({});
});
afterAll(async () => {
- await afterAllcleanApp({ hardhat: true });
+ await afterAllcleanApp({});
});
it('Import a wallet and go to welcome', async () => {
@@ -32,9 +32,9 @@ describe('Send Sheet Interaction Flow Contacts', () => {
await sendETHtoTestWallet();
});
- it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => {
- await waitAndTap('dev-button-hardhat');
- await checkIfVisible('testnet-toast-Hardhat');
+ it('Should show Anvil Toast after pressing Connect To Anvil', async () => {
+ await waitAndTap('dev-button-anvil');
+ await checkIfVisible('testnet-toast-Anvil');
});
it('Should open send sheet after tapping send fab', async () => {
diff --git a/e2e/serial/2_swaps.spec.ts b/e2e/serial/2_swaps.spec.ts
index 9a4e3ed972c..f18f2c87fab 100644
--- a/e2e/serial/2_swaps.spec.ts
+++ b/e2e/serial/2_swaps.spec.ts
@@ -30,10 +30,10 @@ import { WALLET_VARS } from '../testVariables';
describe('Swap Sheet Interaction Flow', () => {
beforeAll(async () => {
- await beforeAllcleanApp({ hardhat: true });
+ await beforeAllcleanApp({});
});
afterAll(async () => {
- await afterAllcleanApp({ hardhat: true });
+ await afterAllcleanApp({});
});
it('Import a wallet and go to welcome', async () => {
@@ -45,9 +45,9 @@ describe('Swap Sheet Interaction Flow', () => {
await sendETHtoTestWallet();
});
- it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => {
- await tap('dev-button-hardhat');
- await checkIfVisible('testnet-toast-Hardhat');
+ it('Should show Anvil Toast after pressing Connect To Anvil', async () => {
+ await tap('dev-button-anvil');
+ await checkIfVisible('testnet-toast-Anvil');
// doesn't work atm
// validate it has the expected funds of 20 eth
diff --git a/globals.d.ts b/globals.d.ts
index bd6bee49caa..3c401e51846 100644
--- a/globals.d.ts
+++ b/globals.d.ts
@@ -49,8 +49,8 @@ declare module 'react-native-dotenv' {
export const NFT_API_KEY: string;
export const NFT_API_URL: string;
export const ETHERSCAN_API_KEY: string;
- export const HARDHAT_URL_ANDROID: string;
- export const HARDHAT_URL_IOS: string;
+ export const ANVIL_URL_ANDROID: string;
+ export const ANVIL_URL_IOS: string;
export const RAINBOW_MASTER_KEY: string;
export const SECURE_WALLET_HASH_KEY: string;
export const TEST_SEEDS: string;
diff --git a/hardhat.config.js b/hardhat.config.js
deleted file mode 100644
index b7aecb87c58..00000000000
--- a/hardhat.config.js
+++ /dev/null
@@ -1,17 +0,0 @@
-require('@nomiclabs/hardhat-waffle');
-
-// You need to export an object to set up your config
-// Go to https://hardhat.org/config/ to learn more
-
-/**
- * @type import('hardhat/config').HardhatUserConfig
- */
-module.exports = {
- networks: {
- hardhat: {
- chainId: 1,
- initialBaseFeePerGas: 100000000, // 0.1 gwei
- },
- },
- solidity: '0.8.4',
-};
diff --git a/ios/Images.xcassets/badges/gnosis.imageset/Contents.json b/ios/Images.xcassets/badges/gnosis.imageset/Contents.json
new file mode 100644
index 00000000000..834dad265bf
--- /dev/null
+++ b/ios/Images.xcassets/badges/gnosis.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gnosis.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gnosis@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gnosis@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gnosis.imageset/gnosis.png b/ios/Images.xcassets/badges/gnosis.imageset/gnosis.png
new file mode 100644
index 00000000000..afb08cb40e6
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosis.imageset/gnosis.png differ
diff --git a/ios/Images.xcassets/badges/gnosis.imageset/gnosis@2x.png b/ios/Images.xcassets/badges/gnosis.imageset/gnosis@2x.png
new file mode 100644
index 00000000000..3a021929690
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosis.imageset/gnosis@2x.png differ
diff --git a/ios/Images.xcassets/badges/gnosis.imageset/gnosis@3x.png b/ios/Images.xcassets/badges/gnosis.imageset/gnosis@3x.png
new file mode 100644
index 00000000000..09946eb88a5
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosis.imageset/gnosis@3x.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadge.imageset/Contents.json b/ios/Images.xcassets/badges/gnosisBadge.imageset/Contents.json
new file mode 100644
index 00000000000..a8ae4f3b2ee
--- /dev/null
+++ b/ios/Images.xcassets/badges/gnosisBadge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gnosisBadge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gnosisBadge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gnosisBadge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gnosisBadge.imageset/gnosisBadge.png b/ios/Images.xcassets/badges/gnosisBadge.imageset/gnosisBadge.png
new file mode 100644
index 00000000000..961610867dd
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadge.imageset/gnosisBadge.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadge.imageset/gnosisBadge@2x.png b/ios/Images.xcassets/badges/gnosisBadge.imageset/gnosisBadge@2x.png
new file mode 100644
index 00000000000..1355360aca0
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadge.imageset/gnosisBadge@2x.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadge.imageset/gnosisBadge@3x.png b/ios/Images.xcassets/badges/gnosisBadge.imageset/gnosisBadge@3x.png
new file mode 100644
index 00000000000..19592fc8d64
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadge.imageset/gnosisBadge@3x.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/Contents.json b/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/Contents.json
new file mode 100644
index 00000000000..3e640c6e39e
--- /dev/null
+++ b/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gnosisBadgeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gnosisBadgeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gnosisBadgeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/gnosisBadgeDark.png b/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/gnosisBadgeDark.png
new file mode 100644
index 00000000000..cd7d26152c2
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/gnosisBadgeDark.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/gnosisBadgeDark@2x.png b/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/gnosisBadgeDark@2x.png
new file mode 100644
index 00000000000..029c07d9ddb
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/gnosisBadgeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/gnosisBadgeDark@3x.png b/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/gnosisBadgeDark@3x.png
new file mode 100644
index 00000000000..5c8ed229375
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeDark.imageset/gnosisBadgeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/Contents.json b/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/Contents.json
new file mode 100644
index 00000000000..5f89716e5e9
--- /dev/null
+++ b/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gnosisBadgeLarge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gnosisBadgeLarge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gnosisBadgeLarge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/gnosisBadgeLarge.png b/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/gnosisBadgeLarge.png
new file mode 100644
index 00000000000..9cf5511d8f1
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/gnosisBadgeLarge.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/gnosisBadgeLarge@2x.png b/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/gnosisBadgeLarge@2x.png
new file mode 100644
index 00000000000..301ddfb2914
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/gnosisBadgeLarge@2x.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/gnosisBadgeLarge@3x.png b/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/gnosisBadgeLarge@3x.png
new file mode 100644
index 00000000000..e787754b713
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeLarge.imageset/gnosisBadgeLarge@3x.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/Contents.json b/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/Contents.json
new file mode 100644
index 00000000000..ee423b90122
--- /dev/null
+++ b/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gnosisBadgeLargeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gnosisBadgeLargeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gnosisBadgeLargeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/gnosisBadgeLargeDark.png b/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/gnosisBadgeLargeDark.png
new file mode 100644
index 00000000000..4f3b0c859cb
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/gnosisBadgeLargeDark.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/gnosisBadgeLargeDark@2x.png b/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/gnosisBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..79768e5d97e
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/gnosisBadgeLargeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/gnosisBadgeLargeDark@3x.png b/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/gnosisBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..4975e795ef0
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeLargeDark.imageset/gnosisBadgeLargeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/Contents.json b/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/Contents.json
new file mode 100644
index 00000000000..f322a845147
--- /dev/null
+++ b/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gnosisBadgeNoShadow.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gnosisBadgeNoShadow@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gnosisBadgeNoShadow@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/gnosisBadgeNoShadow.png b/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/gnosisBadgeNoShadow.png
new file mode 100644
index 00000000000..baa5ee94397
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/gnosisBadgeNoShadow.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/gnosisBadgeNoShadow@2x.png b/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/gnosisBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..57e1e098b6a
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/gnosisBadgeNoShadow@2x.png differ
diff --git a/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/gnosisBadgeNoShadow@3x.png b/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/gnosisBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..42a52730e27
Binary files /dev/null and b/ios/Images.xcassets/badges/gnosisBadgeNoShadow.imageset/gnosisBadgeNoShadow@3x.png differ
diff --git a/ios/Images.xcassets/badges/gravity.imageset/Contents.json b/ios/Images.xcassets/badges/gravity.imageset/Contents.json
new file mode 100644
index 00000000000..5e1cbb0a037
--- /dev/null
+++ b/ios/Images.xcassets/badges/gravity.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gravity.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gravity@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gravity@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gravity.imageset/gravity.png b/ios/Images.xcassets/badges/gravity.imageset/gravity.png
new file mode 100644
index 00000000000..a515d5551c5
Binary files /dev/null and b/ios/Images.xcassets/badges/gravity.imageset/gravity.png differ
diff --git a/ios/Images.xcassets/badges/gravity.imageset/gravity@2x.png b/ios/Images.xcassets/badges/gravity.imageset/gravity@2x.png
new file mode 100644
index 00000000000..29e60cbad1c
Binary files /dev/null and b/ios/Images.xcassets/badges/gravity.imageset/gravity@2x.png differ
diff --git a/ios/Images.xcassets/badges/gravity.imageset/gravity@3x.png b/ios/Images.xcassets/badges/gravity.imageset/gravity@3x.png
new file mode 100644
index 00000000000..55108967dea
Binary files /dev/null and b/ios/Images.xcassets/badges/gravity.imageset/gravity@3x.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadge.imageset/Contents.json b/ios/Images.xcassets/badges/gravityBadge.imageset/Contents.json
new file mode 100644
index 00000000000..83f2a19de8d
--- /dev/null
+++ b/ios/Images.xcassets/badges/gravityBadge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gravityBadge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gravityBadge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gravityBadge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gravityBadge.imageset/gravityBadge.png b/ios/Images.xcassets/badges/gravityBadge.imageset/gravityBadge.png
new file mode 100644
index 00000000000..ad033ad3691
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadge.imageset/gravityBadge.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadge.imageset/gravityBadge@2x.png b/ios/Images.xcassets/badges/gravityBadge.imageset/gravityBadge@2x.png
new file mode 100644
index 00000000000..2264da68ec9
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadge.imageset/gravityBadge@2x.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadge.imageset/gravityBadge@3x.png b/ios/Images.xcassets/badges/gravityBadge.imageset/gravityBadge@3x.png
new file mode 100644
index 00000000000..8d6bdddc381
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadge.imageset/gravityBadge@3x.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeDark.imageset/Contents.json b/ios/Images.xcassets/badges/gravityBadgeDark.imageset/Contents.json
new file mode 100644
index 00000000000..652b4b30a25
--- /dev/null
+++ b/ios/Images.xcassets/badges/gravityBadgeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gravityBadgeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gravityBadgeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gravityBadgeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gravityBadgeDark.imageset/gravityBadgeDark.png b/ios/Images.xcassets/badges/gravityBadgeDark.imageset/gravityBadgeDark.png
new file mode 100644
index 00000000000..02fb9e0677f
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeDark.imageset/gravityBadgeDark.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeDark.imageset/gravityBadgeDark@2x.png b/ios/Images.xcassets/badges/gravityBadgeDark.imageset/gravityBadgeDark@2x.png
new file mode 100644
index 00000000000..9aadd9549ae
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeDark.imageset/gravityBadgeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeDark.imageset/gravityBadgeDark@3x.png b/ios/Images.xcassets/badges/gravityBadgeDark.imageset/gravityBadgeDark@3x.png
new file mode 100644
index 00000000000..5218bf5b88e
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeDark.imageset/gravityBadgeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/Contents.json b/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/Contents.json
new file mode 100644
index 00000000000..1a4bb0120b1
--- /dev/null
+++ b/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gravityBadgeLarge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gravityBadgeLarge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gravityBadgeLarge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/gravityBadgeLarge.png b/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/gravityBadgeLarge.png
new file mode 100644
index 00000000000..ccfed23d64b
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/gravityBadgeLarge.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/gravityBadgeLarge@2x.png b/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/gravityBadgeLarge@2x.png
new file mode 100644
index 00000000000..51e00178021
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/gravityBadgeLarge@2x.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/gravityBadgeLarge@3x.png b/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/gravityBadgeLarge@3x.png
new file mode 100644
index 00000000000..805d9fcd7b9
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeLarge.imageset/gravityBadgeLarge@3x.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/Contents.json b/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/Contents.json
new file mode 100644
index 00000000000..0d735b28d3a
--- /dev/null
+++ b/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gravityBadgeLargeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gravityBadgeLargeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gravityBadgeLargeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/gravityBadgeLargeDark.png b/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/gravityBadgeLargeDark.png
new file mode 100644
index 00000000000..ab9ee2fcbae
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/gravityBadgeLargeDark.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/gravityBadgeLargeDark@2x.png b/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/gravityBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..067b373a7e4
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/gravityBadgeLargeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/gravityBadgeLargeDark@3x.png b/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/gravityBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..362d6f3d772
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeLargeDark.imageset/gravityBadgeLargeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/Contents.json b/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/Contents.json
new file mode 100644
index 00000000000..33f754ed1be
--- /dev/null
+++ b/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "gravityBadgeNoShadow.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "gravityBadgeNoShadow@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "gravityBadgeNoShadow@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/gravityBadgeNoShadow.png b/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/gravityBadgeNoShadow.png
new file mode 100644
index 00000000000..9492a525822
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/gravityBadgeNoShadow.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/gravityBadgeNoShadow@2x.png b/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/gravityBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..5052d829f1e
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/gravityBadgeNoShadow@2x.png differ
diff --git a/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/gravityBadgeNoShadow@3x.png b/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/gravityBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..474f948bf99
Binary files /dev/null and b/ios/Images.xcassets/badges/gravityBadgeNoShadow.imageset/gravityBadgeNoShadow@3x.png differ
diff --git a/ios/Images.xcassets/badges/linea.imageset/Contents.json b/ios/Images.xcassets/badges/linea.imageset/Contents.json
new file mode 100644
index 00000000000..673ca809472
--- /dev/null
+++ b/ios/Images.xcassets/badges/linea.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "linea.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "linea@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "linea@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/linea.imageset/linea.png b/ios/Images.xcassets/badges/linea.imageset/linea.png
new file mode 100644
index 00000000000..14e4a7fface
Binary files /dev/null and b/ios/Images.xcassets/badges/linea.imageset/linea.png differ
diff --git a/ios/Images.xcassets/badges/linea.imageset/linea@2x.png b/ios/Images.xcassets/badges/linea.imageset/linea@2x.png
new file mode 100644
index 00000000000..42f41b35f9d
Binary files /dev/null and b/ios/Images.xcassets/badges/linea.imageset/linea@2x.png differ
diff --git a/ios/Images.xcassets/badges/linea.imageset/linea@3x.png b/ios/Images.xcassets/badges/linea.imageset/linea@3x.png
new file mode 100644
index 00000000000..4cc7abd0cdc
Binary files /dev/null and b/ios/Images.xcassets/badges/linea.imageset/linea@3x.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadge.imageset/Contents.json b/ios/Images.xcassets/badges/lineaBadge.imageset/Contents.json
new file mode 100644
index 00000000000..01ecb7425f8
--- /dev/null
+++ b/ios/Images.xcassets/badges/lineaBadge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "lineaBadge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "lineaBadge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "lineaBadge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/lineaBadge.imageset/lineaBadge.png b/ios/Images.xcassets/badges/lineaBadge.imageset/lineaBadge.png
new file mode 100644
index 00000000000..267723587ca
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadge.imageset/lineaBadge.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadge.imageset/lineaBadge@2x.png b/ios/Images.xcassets/badges/lineaBadge.imageset/lineaBadge@2x.png
new file mode 100644
index 00000000000..70bb69fae24
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadge.imageset/lineaBadge@2x.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadge.imageset/lineaBadge@3x.png b/ios/Images.xcassets/badges/lineaBadge.imageset/lineaBadge@3x.png
new file mode 100644
index 00000000000..814892f7fdb
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadge.imageset/lineaBadge@3x.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeDark.imageset/Contents.json b/ios/Images.xcassets/badges/lineaBadgeDark.imageset/Contents.json
new file mode 100644
index 00000000000..18b85aa99d7
--- /dev/null
+++ b/ios/Images.xcassets/badges/lineaBadgeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "lineaBadgeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "lineaBadgeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "lineaBadgeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/lineaBadgeDark.imageset/lineaBadgeDark.png b/ios/Images.xcassets/badges/lineaBadgeDark.imageset/lineaBadgeDark.png
new file mode 100644
index 00000000000..1fa46075d00
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeDark.imageset/lineaBadgeDark.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeDark.imageset/lineaBadgeDark@2x.png b/ios/Images.xcassets/badges/lineaBadgeDark.imageset/lineaBadgeDark@2x.png
new file mode 100644
index 00000000000..152eb8d6177
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeDark.imageset/lineaBadgeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeDark.imageset/lineaBadgeDark@3x.png b/ios/Images.xcassets/badges/lineaBadgeDark.imageset/lineaBadgeDark@3x.png
new file mode 100644
index 00000000000..a58ff5b93ac
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeDark.imageset/lineaBadgeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/Contents.json b/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/Contents.json
new file mode 100644
index 00000000000..ed3cb5016ee
--- /dev/null
+++ b/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "lineaBadgeLarge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "lineaBadgeLarge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "lineaBadgeLarge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/lineaBadgeLarge.png b/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/lineaBadgeLarge.png
new file mode 100644
index 00000000000..93a23a92347
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/lineaBadgeLarge.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/lineaBadgeLarge@2x.png b/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/lineaBadgeLarge@2x.png
new file mode 100644
index 00000000000..0fe5d25acdf
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/lineaBadgeLarge@2x.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/lineaBadgeLarge@3x.png b/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/lineaBadgeLarge@3x.png
new file mode 100644
index 00000000000..4f3a3c0ce1c
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeLarge.imageset/lineaBadgeLarge@3x.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/Contents.json b/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/Contents.json
new file mode 100644
index 00000000000..0a56a80d868
--- /dev/null
+++ b/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "lineaBadgeLargeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "lineaBadgeLargeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "lineaBadgeLargeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/lineaBadgeLargeDark.png b/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/lineaBadgeLargeDark.png
new file mode 100644
index 00000000000..f463c5abcc7
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/lineaBadgeLargeDark.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/lineaBadgeLargeDark@2x.png b/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/lineaBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..f417f8f1f16
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/lineaBadgeLargeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/lineaBadgeLargeDark@3x.png b/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/lineaBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..86c51a9e20b
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeLargeDark.imageset/lineaBadgeLargeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/Contents.json b/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/Contents.json
new file mode 100644
index 00000000000..1e5565d1c2c
--- /dev/null
+++ b/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "lineaBadgeNoShadow.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "lineaBadgeNoShadow@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "lineaBadgeNoShadow@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/lineaBadgeNoShadow.png b/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/lineaBadgeNoShadow.png
new file mode 100644
index 00000000000..1af3c5fd42d
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/lineaBadgeNoShadow.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/lineaBadgeNoShadow@2x.png b/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/lineaBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..cddfa99c67f
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/lineaBadgeNoShadow@2x.png differ
diff --git a/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/lineaBadgeNoShadow@3x.png b/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/lineaBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..22007385103
Binary files /dev/null and b/ios/Images.xcassets/badges/lineaBadgeNoShadow.imageset/lineaBadgeNoShadow@3x.png differ
diff --git a/ios/Images.xcassets/badges/sankBadgeDark.imageset/Contents.json b/ios/Images.xcassets/badges/sankBadgeDark.imageset/Contents.json
new file mode 100644
index 00000000000..da70d3d22ef
--- /dev/null
+++ b/ios/Images.xcassets/badges/sankBadgeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "sankBadgeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "sankBadgeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "sankBadgeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/sankBadgeDark.imageset/sankBadgeDark.png b/ios/Images.xcassets/badges/sankBadgeDark.imageset/sankBadgeDark.png
new file mode 100644
index 00000000000..6ab341257ab
Binary files /dev/null and b/ios/Images.xcassets/badges/sankBadgeDark.imageset/sankBadgeDark.png differ
diff --git a/ios/Images.xcassets/badges/sankBadgeDark.imageset/sankBadgeDark@2x.png b/ios/Images.xcassets/badges/sankBadgeDark.imageset/sankBadgeDark@2x.png
new file mode 100644
index 00000000000..d4e5beded3f
Binary files /dev/null and b/ios/Images.xcassets/badges/sankBadgeDark.imageset/sankBadgeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/sankBadgeDark.imageset/sankBadgeDark@3x.png b/ios/Images.xcassets/badges/sankBadgeDark.imageset/sankBadgeDark@3x.png
new file mode 100644
index 00000000000..9b5eba8fe7a
Binary files /dev/null and b/ios/Images.xcassets/badges/sankBadgeDark.imageset/sankBadgeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/sanko.imageset/Contents.json b/ios/Images.xcassets/badges/sanko.imageset/Contents.json
new file mode 100644
index 00000000000..ba52a5b3aeb
--- /dev/null
+++ b/ios/Images.xcassets/badges/sanko.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "sanko.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "sanko@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "sanko@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/sanko.imageset/sanko.png b/ios/Images.xcassets/badges/sanko.imageset/sanko.png
new file mode 100644
index 00000000000..e0eff636cac
Binary files /dev/null and b/ios/Images.xcassets/badges/sanko.imageset/sanko.png differ
diff --git a/ios/Images.xcassets/badges/sanko.imageset/sanko@2x.png b/ios/Images.xcassets/badges/sanko.imageset/sanko@2x.png
new file mode 100644
index 00000000000..399f9ad13e6
Binary files /dev/null and b/ios/Images.xcassets/badges/sanko.imageset/sanko@2x.png differ
diff --git a/ios/Images.xcassets/badges/sanko.imageset/sanko@3x.png b/ios/Images.xcassets/badges/sanko.imageset/sanko@3x.png
new file mode 100644
index 00000000000..c5c5e42e575
Binary files /dev/null and b/ios/Images.xcassets/badges/sanko.imageset/sanko@3x.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadge.imageset/Contents.json b/ios/Images.xcassets/badges/sankoBadge.imageset/Contents.json
new file mode 100644
index 00000000000..e34f3e90e70
--- /dev/null
+++ b/ios/Images.xcassets/badges/sankoBadge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "sankoBadge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "sankoBadge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "sankoBadge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/sankoBadge.imageset/sankoBadge.png b/ios/Images.xcassets/badges/sankoBadge.imageset/sankoBadge.png
new file mode 100644
index 00000000000..3e44d659f3a
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadge.imageset/sankoBadge.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadge.imageset/sankoBadge@2x.png b/ios/Images.xcassets/badges/sankoBadge.imageset/sankoBadge@2x.png
new file mode 100644
index 00000000000..39a74fcdc8d
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadge.imageset/sankoBadge@2x.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadge.imageset/sankoBadge@3x.png b/ios/Images.xcassets/badges/sankoBadge.imageset/sankoBadge@3x.png
new file mode 100644
index 00000000000..a9729e07af3
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadge.imageset/sankoBadge@3x.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/Contents.json b/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/Contents.json
new file mode 100644
index 00000000000..8d3d05e697d
--- /dev/null
+++ b/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "sankoBadgeLarge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "sankoBadgeLarge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "sankoBadgeLarge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/sankoBadgeLarge.png b/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/sankoBadgeLarge.png
new file mode 100644
index 00000000000..e2ead662090
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/sankoBadgeLarge.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/sankoBadgeLarge@2x.png b/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/sankoBadgeLarge@2x.png
new file mode 100644
index 00000000000..726dfc00978
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/sankoBadgeLarge@2x.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/sankoBadgeLarge@3x.png b/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/sankoBadgeLarge@3x.png
new file mode 100644
index 00000000000..897a700db31
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadgeLarge.imageset/sankoBadgeLarge@3x.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/Contents.json b/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/Contents.json
new file mode 100644
index 00000000000..969adc60502
--- /dev/null
+++ b/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "sankoBadgeLargeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "sankoBadgeLargeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "sankoBadgeLargeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/sankoBadgeLargeDark.png b/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/sankoBadgeLargeDark.png
new file mode 100644
index 00000000000..0218596d5bb
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/sankoBadgeLargeDark.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/sankoBadgeLargeDark@2x.png b/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/sankoBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..40c50ffecf6
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/sankoBadgeLargeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/sankoBadgeLargeDark@3x.png b/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/sankoBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..f683ef22d4b
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadgeLargeDark.imageset/sankoBadgeLargeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/Contents.json b/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/Contents.json
new file mode 100644
index 00000000000..3b1ea006654
--- /dev/null
+++ b/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "sankoBadgeNoShadow.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "sankoBadgeNoShadow@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "sankoBadgeNoShadow@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/sankoBadgeNoShadow.png b/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/sankoBadgeNoShadow.png
new file mode 100644
index 00000000000..b0b2c0385ac
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/sankoBadgeNoShadow.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/sankoBadgeNoShadow@2x.png b/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/sankoBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..990e00a88b0
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/sankoBadgeNoShadow@2x.png differ
diff --git a/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/sankoBadgeNoShadow@3x.png b/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/sankoBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..f79b957c903
Binary files /dev/null and b/ios/Images.xcassets/badges/sankoBadgeNoShadow.imageset/sankoBadgeNoShadow@3x.png differ
diff --git a/ios/Images.xcassets/badges/scroll.imageset/Contents.json b/ios/Images.xcassets/badges/scroll.imageset/Contents.json
new file mode 100644
index 00000000000..2496dd78ca3
--- /dev/null
+++ b/ios/Images.xcassets/badges/scroll.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "scroll.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "scroll@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "scroll@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/scroll.imageset/scroll.png b/ios/Images.xcassets/badges/scroll.imageset/scroll.png
new file mode 100644
index 00000000000..b074e4e54f3
Binary files /dev/null and b/ios/Images.xcassets/badges/scroll.imageset/scroll.png differ
diff --git a/ios/Images.xcassets/badges/scroll.imageset/scroll@2x.png b/ios/Images.xcassets/badges/scroll.imageset/scroll@2x.png
new file mode 100644
index 00000000000..1dab422416c
Binary files /dev/null and b/ios/Images.xcassets/badges/scroll.imageset/scroll@2x.png differ
diff --git a/ios/Images.xcassets/badges/scroll.imageset/scroll@3x.png b/ios/Images.xcassets/badges/scroll.imageset/scroll@3x.png
new file mode 100644
index 00000000000..04482628c63
Binary files /dev/null and b/ios/Images.xcassets/badges/scroll.imageset/scroll@3x.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadge.imageset/Contents.json b/ios/Images.xcassets/badges/scrollBadge.imageset/Contents.json
new file mode 100644
index 00000000000..764ef1d584d
--- /dev/null
+++ b/ios/Images.xcassets/badges/scrollBadge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "scrollBadge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "scrollBadge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "scrollBadge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/scrollBadge.imageset/scrollBadge.png b/ios/Images.xcassets/badges/scrollBadge.imageset/scrollBadge.png
new file mode 100644
index 00000000000..3266ab33b57
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadge.imageset/scrollBadge.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadge.imageset/scrollBadge@2x.png b/ios/Images.xcassets/badges/scrollBadge.imageset/scrollBadge@2x.png
new file mode 100644
index 00000000000..aea2e050f18
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadge.imageset/scrollBadge@2x.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadge.imageset/scrollBadge@3x.png b/ios/Images.xcassets/badges/scrollBadge.imageset/scrollBadge@3x.png
new file mode 100644
index 00000000000..40f77762f36
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadge.imageset/scrollBadge@3x.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeDark.imageset/Contents.json b/ios/Images.xcassets/badges/scrollBadgeDark.imageset/Contents.json
new file mode 100644
index 00000000000..a11922fe560
--- /dev/null
+++ b/ios/Images.xcassets/badges/scrollBadgeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "scrollBadgeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "scrollBadgeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "scrollBadgeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/scrollBadgeDark.imageset/scrollBadgeDark.png b/ios/Images.xcassets/badges/scrollBadgeDark.imageset/scrollBadgeDark.png
new file mode 100644
index 00000000000..71927e891e8
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeDark.imageset/scrollBadgeDark.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeDark.imageset/scrollBadgeDark@2x.png b/ios/Images.xcassets/badges/scrollBadgeDark.imageset/scrollBadgeDark@2x.png
new file mode 100644
index 00000000000..53095e21774
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeDark.imageset/scrollBadgeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeDark.imageset/scrollBadgeDark@3x.png b/ios/Images.xcassets/badges/scrollBadgeDark.imageset/scrollBadgeDark@3x.png
new file mode 100644
index 00000000000..5bfd03fdd2b
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeDark.imageset/scrollBadgeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/Contents.json b/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/Contents.json
new file mode 100644
index 00000000000..94fc4d4b6e8
--- /dev/null
+++ b/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "scrollBadgeLarge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "scrollBadgeLarge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "scrollBadgeLarge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/scrollBadgeLarge.png b/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/scrollBadgeLarge.png
new file mode 100644
index 00000000000..cc1dbc31e63
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/scrollBadgeLarge.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/scrollBadgeLarge@2x.png b/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/scrollBadgeLarge@2x.png
new file mode 100644
index 00000000000..c60eca2ff99
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/scrollBadgeLarge@2x.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/scrollBadgeLarge@3x.png b/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/scrollBadgeLarge@3x.png
new file mode 100644
index 00000000000..8be695f8247
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeLarge.imageset/scrollBadgeLarge@3x.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/Contents.json b/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/Contents.json
new file mode 100644
index 00000000000..e6b3dcb2f44
--- /dev/null
+++ b/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "scrollBadgeLargeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "scrollBadgeLargeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "scrollBadgeLargeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/scrollBadgeLargeDark.png b/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/scrollBadgeLargeDark.png
new file mode 100644
index 00000000000..5ecf4f2dff7
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/scrollBadgeLargeDark.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/scrollBadgeLargeDark@2x.png b/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/scrollBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..5c6b5d6a50c
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/scrollBadgeLargeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/scrollBadgeLargeDark@3x.png b/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/scrollBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..0e508374d35
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeLargeDark.imageset/scrollBadgeLargeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/Contents.json b/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/Contents.json
new file mode 100644
index 00000000000..6a6f34f28ca
--- /dev/null
+++ b/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "scrollBadgeNoShadow.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "scrollBadgeNoShadow@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "scrollBadgeNoShadow@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/scrollBadgeNoShadow.png b/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/scrollBadgeNoShadow.png
new file mode 100644
index 00000000000..cfdef2d77c9
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/scrollBadgeNoShadow.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/scrollBadgeNoShadow@2x.png b/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/scrollBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..79c1aa6e3b0
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/scrollBadgeNoShadow@2x.png differ
diff --git a/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/scrollBadgeNoShadow@3x.png b/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/scrollBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..d140debde92
Binary files /dev/null and b/ios/Images.xcassets/badges/scrollBadgeNoShadow.imageset/scrollBadgeNoShadow@3x.png differ
diff --git a/ios/Images.xcassets/badges/zkSyncBadge.imageset/Contents.json b/ios/Images.xcassets/badges/zkSyncBadge.imageset/Contents.json
new file mode 100644
index 00000000000..0a103fcddc5
--- /dev/null
+++ b/ios/Images.xcassets/badges/zkSyncBadge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "zkSyncBadge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "zkSyncBadge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "zkSyncBadge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/zkSyncBadge.imageset/zkSyncBadge.png b/ios/Images.xcassets/badges/zkSyncBadge.imageset/zkSyncBadge.png
new file mode 100644
index 00000000000..f84a89c233f
Binary files /dev/null and b/ios/Images.xcassets/badges/zkSyncBadge.imageset/zkSyncBadge.png differ
diff --git a/ios/Images.xcassets/badges/zkSyncBadge.imageset/zkSyncBadge@2x.png b/ios/Images.xcassets/badges/zkSyncBadge.imageset/zkSyncBadge@2x.png
new file mode 100644
index 00000000000..02a0f753727
Binary files /dev/null and b/ios/Images.xcassets/badges/zkSyncBadge.imageset/zkSyncBadge@2x.png differ
diff --git a/ios/Images.xcassets/badges/zkSyncBadge.imageset/zkSyncBadge@3x.png b/ios/Images.xcassets/badges/zkSyncBadge.imageset/zkSyncBadge@3x.png
new file mode 100644
index 00000000000..db578e63d79
Binary files /dev/null and b/ios/Images.xcassets/badges/zkSyncBadge.imageset/zkSyncBadge@3x.png differ
diff --git a/ios/Images.xcassets/badges/zksync.imageset/Contents.json b/ios/Images.xcassets/badges/zksync.imageset/Contents.json
new file mode 100644
index 00000000000..c6ca19bfaf8
--- /dev/null
+++ b/ios/Images.xcassets/badges/zksync.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "zksync.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "zksync@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "zksync@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/zksync.imageset/zksync.png b/ios/Images.xcassets/badges/zksync.imageset/zksync.png
new file mode 100644
index 00000000000..1225b4e6c6e
Binary files /dev/null and b/ios/Images.xcassets/badges/zksync.imageset/zksync.png differ
diff --git a/ios/Images.xcassets/badges/zksync.imageset/zksync@2x.png b/ios/Images.xcassets/badges/zksync.imageset/zksync@2x.png
new file mode 100644
index 00000000000..b437d430a93
Binary files /dev/null and b/ios/Images.xcassets/badges/zksync.imageset/zksync@2x.png differ
diff --git a/ios/Images.xcassets/badges/zksync.imageset/zksync@3x.png b/ios/Images.xcassets/badges/zksync.imageset/zksync@3x.png
new file mode 100644
index 00000000000..2e433247e12
Binary files /dev/null and b/ios/Images.xcassets/badges/zksync.imageset/zksync@3x.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/Contents.json b/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/Contents.json
new file mode 100644
index 00000000000..c595c48304b
--- /dev/null
+++ b/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "zksyncBadgeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "zksyncBadgeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "zksyncBadgeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/zksyncBadgeDark.png b/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/zksyncBadgeDark.png
new file mode 100644
index 00000000000..b6947dbcd88
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/zksyncBadgeDark.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/zksyncBadgeDark@2x.png b/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/zksyncBadgeDark@2x.png
new file mode 100644
index 00000000000..77f075eb22e
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/zksyncBadgeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/zksyncBadgeDark@3x.png b/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/zksyncBadgeDark@3x.png
new file mode 100644
index 00000000000..a7ea118d5ce
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeDark.imageset/zksyncBadgeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/Contents.json b/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/Contents.json
new file mode 100644
index 00000000000..8df41d41eeb
--- /dev/null
+++ b/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "zksyncBadgeLarge.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "zksyncBadgeLarge@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "zksyncBadgeLarge@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/zksyncBadgeLarge.png b/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/zksyncBadgeLarge.png
new file mode 100644
index 00000000000..02190afd30f
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/zksyncBadgeLarge.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/zksyncBadgeLarge@2x.png b/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/zksyncBadgeLarge@2x.png
new file mode 100644
index 00000000000..420b0f8531e
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/zksyncBadgeLarge@2x.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/zksyncBadgeLarge@3x.png b/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/zksyncBadgeLarge@3x.png
new file mode 100644
index 00000000000..25aef546ecf
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeLarge.imageset/zksyncBadgeLarge@3x.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/Contents.json b/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/Contents.json
new file mode 100644
index 00000000000..aae27eaf7dc
--- /dev/null
+++ b/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "zksyncBadgeLargeDark.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "zksyncBadgeLargeDark@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "zksyncBadgeLargeDark@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/zksyncBadgeLargeDark.png b/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/zksyncBadgeLargeDark.png
new file mode 100644
index 00000000000..19a58296e9a
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/zksyncBadgeLargeDark.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/zksyncBadgeLargeDark@2x.png b/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/zksyncBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..ea8d6389607
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/zksyncBadgeLargeDark@2x.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/zksyncBadgeLargeDark@3x.png b/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/zksyncBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..71a30293a2e
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeLargeDark.imageset/zksyncBadgeLargeDark@3x.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/Contents.json b/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/Contents.json
new file mode 100644
index 00000000000..caf3f48aeb6
--- /dev/null
+++ b/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "zksyncBadgeNoShadow.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "zksyncBadgeNoShadow@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "zksyncBadgeNoShadow@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/zksyncBadgeNoShadow.png b/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/zksyncBadgeNoShadow.png
new file mode 100644
index 00000000000..47b5500b5d0
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/zksyncBadgeNoShadow.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/zksyncBadgeNoShadow@2x.png b/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/zksyncBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..97f1bc72954
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/zksyncBadgeNoShadow@2x.png differ
diff --git a/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/zksyncBadgeNoShadow@3x.png b/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/zksyncBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..ed087b5f411
Binary files /dev/null and b/ios/Images.xcassets/badges/zksyncBadgeNoShadow.imageset/zksyncBadgeNoShadow@3x.png differ
diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj
index 35371be0d3b..b1df86d0d36 100644
--- a/ios/Rainbow.xcodeproj/project.pbxproj
+++ b/ios/Rainbow.xcodeproj/project.pbxproj
@@ -1883,7 +1883,7 @@
"$(PROJECT_DIR)",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 1.9.50;
+ MARKETING_VERSION = 1.9.51;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1951,7 +1951,7 @@
"$(PROJECT_DIR)",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 1.9.50;
+ MARKETING_VERSION = 1.9.51;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -2072,7 +2072,7 @@
"$(PROJECT_DIR)",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 1.9.50;
+ MARKETING_VERSION = 1.9.51;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
@@ -2192,7 +2192,7 @@
"$(PROJECT_DIR)",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 1.9.50;
+ MARKETING_VERSION = 1.9.51;
OTHER_CFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
diff --git a/package.json b/package.json
index 2e6e6e933ac..016bfd20be7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "Rainbow",
- "version": "1.9.50-1",
+ "version": "1.9.51-1",
"private": true,
"scripts": {
"setup": "yarn graphql-codegen:install && yarn ds:install && yarn allow-scripts && yarn graphql-codegen && yarn fetch:networks",
@@ -33,7 +33,7 @@
"ds": "cd src/design-system/docs && yarn dev",
"fast": "yarn install && yarn setup && yarn install-pods-fast",
"gradle": "cd android && ./gradlew",
- "hardhat": "sh ./scripts/hardhat.sh",
+ "anvil": "sh ./scripts/anvil.sh",
"install-all": "yarn install && yarn setup && yarn install-pods",
"install-all-no-flipper": "yarn install && yarn setup && yarn install-pods-no-flipper",
"install-bundle": "cd ios && bundle install && cd ..",
@@ -329,8 +329,6 @@
"@babel/preset-env": "7.22.0",
"@babel/runtime": "7.22.0",
"@lavamoat/allow-scripts": "3.0.1",
- "@nomiclabs/hardhat-ethers": "2.2.3",
- "@nomiclabs/hardhat-waffle": "2.0.6",
"@react-native/babel-preset": "0.74.83",
"@react-native/typescript-config": "0.74.83",
"@rnx-kit/align-deps": "2.2.4",
@@ -365,7 +363,6 @@
"eslint": "8.22.0",
"eslint-config-rainbow": "4.3.0",
"graphql": "15.3.0",
- "hardhat": "2.18.1",
"husky": "8.0.1",
"image-size": "1.0.0",
"jest": "29.7.0",
@@ -479,16 +476,14 @@
"detox>bunyan>dtrace-provider": false,
"detox>ws>bufferutil": false,
"detox>ws>utf-8-validate": false,
- "hardhat>@ethereumjs/vm>core-js-pure": false,
"ethereumjs-util>ethereum-cryptography>secp256k1": false,
- "hardhat>@nomicfoundation/ethereumjs-blockchain>level>classic-level": false,
- "hardhat>keccak": false,
"react-native-storage": false,
"react-native-storage>opencollective>babel-polyfill>core-js": false,
"viem>ws>bufferutil": false,
"viem>ws>utf-8-validate": false,
"@types/detox>detox": false,
- "react-native-bootsplash>sharp": false
+ "react-native-bootsplash>sharp": false,
+ "ethereumjs-util>ethereum-cryptography>keccak": false
}
},
"lint-staged": {
diff --git a/scripts/add_network.sh b/scripts/add_network.sh
deleted file mode 100755
index 6750607038f..00000000000
--- a/scripts/add_network.sh
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/bin/bash
-
-# Prompt for network details
-read -p "Enter the network name (case sensitive): " networkName
-read -p "Enter the chain ID (number): " chainId
-read -p "Enter the light mode color (hex): " lightColor
-read -p "Enter the dark mode color (hex): " darkColor
-
-# Create imagesets
-mkdir -p "ios/Images.xcassets/badges/${networkName}.imageset"
-mkdir -p "ios/Images.xcassets/badges/${networkName}Badge.imageset"
-mkdir -p "ios/Images.xcassets/badges/${networkName}BadgeDark.imageset"
-mkdir -p "ios/Images.xcassets/badges/${networkName}BadgeLarge.imageset"
-mkdir -p "ios/Images.xcassets/badges/${networkName}BadgeLargeDark.imageset"
-mkdir -p "ios/Images.xcassets/badges/${networkName}BadgeNoShadow.imageset"
-
-# Create Contents.json for each imageset
-for suffix in "" "Badge" "BadgeDark" "BadgeLarge" "BadgeLargeDark" "BadgeNoShadow"; do
- cat > "ios/Images.xcassets/badges/${networkName}${suffix}.imageset/Contents.json" << EOF
-{
- "images" : [
- {
- "filename" : "${networkName}${suffix}.png",
- "idiom" : "universal",
- "scale" : "1x"
- },
- {
- "filename" : "${networkName}${suffix}@2x.png",
- "idiom" : "universal",
- "scale" : "2x"
- },
- {
- "filename" : "${networkName}${suffix}@3x.png",
- "idiom" : "universal",
- "scale" : "3x"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
-EOF
-done
-
-# Update en_US.json
-# Using perl for more precise JSON manipulation
-perl -i -0pe 's/("explain":\s*{)/$1\n "'$networkName'": {\n "text": "",\n "title": "What'\''s '$networkName'?"\n },/m' src/languages/en_US.json
-
-# Update types.ts
-# Add to Network enum
-sed -i '' "/export enum Network {/a\\
- ${networkName} = '${networkName}',
-" src/chains/types.ts
-
-# Add to ChainId enum
-sed -i '' "/export enum ChainId {/a\\
- ${networkName} = ${chainId},
-" src/chains/types.ts
-
-# Update colors.ts for light mode - look for the first networkColors declaration
-sed -i '' "/^ let networkColors = {/a\\
- [ChainId.${networkName}]: '${lightColor}',
-" src/styles/colors.ts
-
-# Update colors.ts for dark mode - look specifically in the darkMode if block
-sed -i '' "/if (darkMode) {/,/^ }/ {
- /networkColors = {/a\\
- [ChainId.${networkName}]: '${darkColor}',
-}" src/styles/colors.ts
-
-echo "Network ${networkName} has been added!"
-echo "Note: You'll need to add the actual badge images to the imageset directories"
-echo "Don't forget to run prettier to format the modified files"
\ No newline at end of file
diff --git a/scripts/anvil.sh b/scripts/anvil.sh
new file mode 100644
index 00000000000..9ec2c5e7956
--- /dev/null
+++ b/scripts/anvil.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+source .env
+anvil --fork-url $ETHEREUM_MAINNET_RPC_DEV
diff --git a/scripts/hardhat.sh b/scripts/hardhat.sh
deleted file mode 100644
index 1ef0a0e1ce3..00000000000
--- a/scripts/hardhat.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-source .env
-npx hardhat node --fork $ETHEREUM_MAINNET_RPC_DEV
\ No newline at end of file
diff --git a/scripts/run-parallel-e2e.sh b/scripts/run-parallel-e2e.sh
index 71961395466..3932bdcf69a 100755
--- a/scripts/run-parallel-e2e.sh
+++ b/scripts/run-parallel-e2e.sh
@@ -1,17 +1,14 @@
#!/bin/bash
-max_retries="$1"
-count=0
-until (( count >= max_retries ))
-do
- ./node_modules/.bin/detox test parallel/ -c ios.sim.release --maxWorkers 2 -- --forceExit --bail 1
- ret_val=$?
- if [ $ret_val -eq 0 ]; then
- exit 0
- fi
- ((count++))
- echo "Test failed, attempt $count/$max_retries..."
-done
+# 0) Read build type from first argument; default to "release" if not specified
+BUILD_TYPE=${1:-release} # can be "debug" or "release"
+# 0.1) Decide Detox config based on BUILD_TYPE
+if [ "$BUILD_TYPE" = "debug" ]; then
+ DETOX_CONFIG="ios.sim.debug"
+else
+ DETOX_CONFIG="ios.sim.release"
+fi
+./node_modules/.bin/detox test ./e2e/parallel/ -c "$DETOX_CONFIG" --maxWorkers 2 --R 3
diff --git a/scripts/run-serial-e2e.sh b/scripts/run-serial-e2e.sh
index dc1a1f6640e..922729eaeaf 100755
--- a/scripts/run-serial-e2e.sh
+++ b/scripts/run-serial-e2e.sh
@@ -1,18 +1,72 @@
#!/bin/bash
+MAX_RETRIES=${1:-3} # number of times that a test should be retried in case of failure. default to 3 if not specified
+BUILD_TYPE=${2:-release} # can be "debug" or "release". default to "release" if not specified
-max_retries="$1"
-count=0
-
-until (( count >= max_retries ))
-do
- ./node_modules/.bin/detox test serial/ -c ios.sim.release --maxWorkers 1 -- --forceExit --bail 1
- ret_val=$?
- if [ $ret_val -eq 0 ]; then
- exit 0
- fi
- ((count++))
- echo "Test failed, attempt $count/$max_retries..."
+
+# 0.1) Decide Detox config based on BUILD_TYPE
+if [ "$BUILD_TYPE" = "debug" ]; then
+ DETOX_CONFIG="ios.sim.debug"
+else
+ DETOX_CONFIG="ios.sim.release"
+fi
+
+SUCCESS=false
+
+# Loop through each file in the ./e2e/serial/ directory
+for test_file in ./e2e/serial/*.ts; do
+ COUNT=0
+ until (( $COUNT >= MAX_RETRIES ))
+ do
+ echo "====================================="
+ echo "Running test file: $test_file (attempt ${COUNT+1}/$MAX_RETRIES)"
+ echo "====================================="
+
+ echo "Starting anvil..."
+ # 1) Start Anvil in the background (show logs in terminal + save to file)
+ yarn anvil 2>&1 | grep -v "eth_" | tee anvil.log &
+ ANVIL_PID=$!
+ echo "Anvil started (PID: $ANVIL_PID)"
+
+ # 2) Wait for Anvil to initialize
+ sleep 5
+
+ # 3) Run Detox for this single file with retries
+ ./node_modules/.bin/detox test "$test_file" -c "$DETOX_CONFIG" --maxWorkers 1
+ ret_val=$?
+
+ # 4) Kill the Anvil process
+ echo "Killing Anvil (PID: $ANVIL_PID)"
+ kill "$ANVIL_PID" 2>/dev/null || true
+ # kill any other processes using port 8545
+ kill $(lsof -t -i:8545) 2>/dev/null || true
+
+ # 5) Remove the Anvil log file
+ rm -rf anvil.log
+
+
+ # 6) Decide what to do if Detox failed or succeeded
+ if [ $ret_val -eq 0 ]; then
+ echo "✅ Tests passed for $test_file"
+ SUCCESS=true
+ break
+ fi
+ ((COUNT++))
+ echo "❌ Test failed, attempt $COUNT/$MAX_RETRIES..."
+
+ if(($COUNT >= MAX_RETRIES))
+ then
+ SUCCESS=false
+ fi
+ done
+
+ if [ "$SUCCESS" = "false" ]; then
+ echo "❌ Tests failed after $MAX_RETRIES attempts. Bailing out!"
+ exit 1
+ fi
done
-echo "Tests failed after $max_retries attempts."
-exit 1
\ No newline at end of file
+echo "✅ All tests passed for every file!"
+exit 0
+
+
+
diff --git a/src/App.tsx b/src/App.tsx
index 157ff68b606..1a749b8ce6d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,6 +1,6 @@
import '@/languages';
import * as Sentry from '@sentry/react-native';
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useState, memo } from 'react';
import { AppRegistry, Dimensions, LogBox, StyleSheet, View } from 'react-native';
import { Toaster } from 'sonner-native';
import { MobileWalletProtocolProvider } from '@coinbase/mobile-wallet-protocol-host';
@@ -9,9 +9,8 @@ import { useApplicationSetup } from '@/hooks/useApplicationSetup';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
import { enableScreens } from 'react-native-screens';
-import { connect, Provider as ReduxProvider } from 'react-redux';
+import { connect, Provider as ReduxProvider, shallowEqual } from 'react-redux';
import { RecoilRoot } from 'recoil';
-import PortalConsumer from '@/components/PortalConsumer';
import ErrorBoundary from '@/components/error-boundary/ErrorBoundary';
import { OfflineToast } from '@/components/toasts';
import { designSystemPlaygroundEnabled, reactNativeDisableYellowBox, showNetworkRequests, showNetworkResponses } from '@/config/debug';
@@ -24,7 +23,6 @@ import store, { AppDispatch, type AppState } from '@/redux/store';
import { MainThemeProvider } from '@/theme/ThemeContext';
import { SharedValuesProvider } from '@/helpers/SharedValuesContext';
import { InitialRouteContext } from '@/navigation/initialRoute';
-import { Portal } from '@/react-native-cool-modals/Portal';
import { NotificationsHandler } from '@/notifications/NotificationsHandler';
import { analyticsV2 } from '@/analytics';
import { getOrCreateDeviceId } from '@/analytics/utils';
@@ -39,7 +37,9 @@ import { RootStackParamList } from '@/navigation/types';
import { IS_ANDROID, IS_DEV } from '@/env';
import { prefetchDefaultFavorites } from '@/resources/favorites';
import Routes from '@/navigation/Routes';
+import { BackupsSync } from '@/state/sync/BackupsSync';
import { BackendNetworks } from '@/components/BackendNetworks';
+import { AbsolutePortalRoot } from './components/AbsolutePortal';
if (IS_DEV) {
reactNativeDisableYellowBox && LogBox.ignoreAllLogs();
@@ -67,12 +67,11 @@ function App({ walletReady }: AppProps) {
}, []);
return (
-
+ <>
{initialRoute && (
-
)}
@@ -80,14 +79,27 @@ function App({ walletReady }: AppProps) {
+
-
+
+ >
);
}
-const AppWithRedux = connect(state => ({
- walletReady: state.appState.walletReady,
-}))(App);
+const AppWithRedux = connect(
+ state => ({
+ walletReady: state.appState.walletReady,
+ }),
+ null,
+ null,
+ {
+ areStatesEqual: (next, prev) => {
+ // Only update if walletReady actually changed
+ return next.appState.walletReady === prev.appState.walletReady;
+ },
+ areOwnPropsEqual: shallowEqual,
+ }
+)(memo(App));
function Root() {
const [initializing, setInitializing] = useState(true);
diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx
index fef6de976fd..b8e24ae905a 100644
--- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx
+++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx
@@ -10,15 +10,15 @@ const BlastBadge = require('@/assets/badges/blast.png');
const BscBadge = require('@/assets/badges/bsc.png');
const DegenBadge = require('@/assets/badges/degen.png');
const EthereumBadge = require('@/assets/badges/ethereum.png');
-// const GnosisBadge = require('@/assets/badges/gnosis.png');
-// const GravityBadge = require('@/assets/badges/gravity.png');
+const GnosisBadge = require('@/assets/badges/gnosis.png');
+const GravityBadge = require('@/assets/badges/gravity.png');
const InkBadge = require('@/assets/badges/ink.png');
-// const LineaBadge = require('@/assets/badges/linea.png');
+const LineaBadge = require('@/assets/badges/linea.png');
const OptimismBadge = require('@/assets/badges/optimism.png');
const PolygonBadge = require('@/assets/badges/polygon.png');
-// const SankoBadge = require('@/assets/badges/sanko.png');
-// const ScrollBadge = require('@/assets/badges/scroll.png');
-// const ZksyncBadge = require('@/assets/badges/zksync.png');
+const SankoBadge = require('@/assets/badges/sanko.png');
+const ScrollBadge = require('@/assets/badges/scroll.png');
+const ZksyncBadge = require('@/assets/badges/zksync.png');
const ZoraBadge = require('@/assets/badges/zora.png');
import { ChainId } from '@/state/backendNetworks/types';
@@ -39,20 +39,20 @@ const networkBadges = {
[ChainId.bsc]: BscBadge,
[ChainId.bscTestnet]: BscBadge,
[ChainId.degen]: DegenBadge,
- // [ChainId.gnosis]: GnosisBadge,
- // [ChainId.gravity]: GravityBadge,
+ [ChainId.gnosis]: GnosisBadge,
+ [ChainId.gravity]: GravityBadge,
[ChainId.holesky]: EthereumBadge,
[ChainId.ink]: InkBadge,
- // [ChainId.linea]: LineaBadge,
+ [ChainId.linea]: LineaBadge,
[ChainId.mainnet]: EthereumBadge,
[ChainId.optimism]: OptimismBadge,
[ChainId.optimismSepolia]: OptimismBadge,
[ChainId.polygon]: PolygonBadge,
[ChainId.polygonAmoy]: PolygonBadge,
- // [ChainId.sanko]: SankoBadge,
- // [ChainId.scroll]: ScrollBadge,
+ [ChainId.sanko]: SankoBadge,
+ [ChainId.scroll]: ScrollBadge,
[ChainId.sepolia]: EthereumBadge,
- // [ChainId.zksync]: ZksyncBadge,
+ [ChainId.zksync]: ZksyncBadge,
[ChainId.zora]: ZoraBadge,
[ChainId.zoraSepolia]: ZoraBadge,
};
diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx
index bf382327e4e..15285091a15 100644
--- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx
+++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx
@@ -17,15 +17,15 @@ import BlastBadge from '@/assets/badges/blast.png';
import BscBadge from '@/assets/badges/bsc.png';
import DegenBadge from '@/assets/badges/degen.png';
import EthereumBadge from '@/assets/badges/ethereum.png';
-// import GnosisBadge from '@/assets/badges/gnosis.png';
-// import GravityBadge from '@/assets/badges/gravity.png';
+import GnosisBadge from '@/assets/badges/gnosis.png';
+import GravityBadge from '@/assets/badges/gravity.png';
import InkBadge from '@/assets/badges/ink.png';
-// import LineaBadge from '@/assets/badges/linea.png';
+import LineaBadge from '@/assets/badges/linea.png';
import OptimismBadge from '@/assets/badges/optimism.png';
import PolygonBadge from '@/assets/badges/polygon.png';
-// import SankoBadge from '@/assets/badges/sanko.png';
-// import ScrollBadge from '@/assets/badges/scroll.png';
-// import ZksyncBadge from '@/assets/badges/zksync.png';
+import SankoBadge from '@/assets/badges/sanko.png';
+import ScrollBadge from '@/assets/badges/scroll.png';
+import ZksyncBadge from '@/assets/badges/zksync.png';
import ZoraBadge from '@/assets/badges/zora.png';
const networkBadges = {
@@ -41,20 +41,20 @@ const networkBadges = {
[ChainId.bsc]: Image.resolveAssetSource(BscBadge).uri,
[ChainId.bscTestnet]: Image.resolveAssetSource(BscBadge).uri,
[ChainId.degen]: Image.resolveAssetSource(DegenBadge).uri,
- // [ChainId.gnosis]: Image.resolveAssetSource(GnosisBadge).uri,
- // [ChainId.gravity]: Image.resolveAssetSource(GravityBadge).uri,
+ [ChainId.gnosis]: Image.resolveAssetSource(GnosisBadge).uri,
+ [ChainId.gravity]: Image.resolveAssetSource(GravityBadge).uri,
[ChainId.holesky]: Image.resolveAssetSource(EthereumBadge).uri,
[ChainId.ink]: Image.resolveAssetSource(InkBadge).uri,
- // [ChainId.linea]: Image.resolveAssetSource(LineaBadge).uri,
+ [ChainId.linea]: Image.resolveAssetSource(LineaBadge).uri,
[ChainId.mainnet]: Image.resolveAssetSource(EthereumBadge).uri,
[ChainId.optimism]: Image.resolveAssetSource(OptimismBadge).uri,
[ChainId.optimismSepolia]: Image.resolveAssetSource(OptimismBadge).uri,
[ChainId.polygon]: Image.resolveAssetSource(PolygonBadge).uri,
[ChainId.polygonAmoy]: Image.resolveAssetSource(PolygonBadge).uri,
- // [ChainId.sanko]: Image.resolveAssetSource(SankoBadge).uri,
- // [ChainId.scroll]: Image.resolveAssetSource(ScrollBadge).uri,
+ [ChainId.sanko]: Image.resolveAssetSource(SankoBadge).uri,
+ [ChainId.scroll]: Image.resolveAssetSource(ScrollBadge).uri,
[ChainId.sepolia]: Image.resolveAssetSource(EthereumBadge).uri,
- // [ChainId.zksync]: Image.resolveAssetSource(ZksyncBadge).uri,
+ [ChainId.zksync]: Image.resolveAssetSource(ZksyncBadge).uri,
[ChainId.zora]: Image.resolveAssetSource(ZoraBadge).uri,
[ChainId.zoraSepolia]: Image.resolveAssetSource(ZoraBadge).uri,
};
diff --git a/src/__swaps__/screens/Swap/components/AnimatedSwapCoinIcon.tsx b/src/__swaps__/screens/Swap/components/AnimatedSwapCoinIcon.tsx
index 2c8ba92b2ea..7d8ec66e8e1 100644
--- a/src/__swaps__/screens/Swap/components/AnimatedSwapCoinIcon.tsx
+++ b/src/__swaps__/screens/Swap/components/AnimatedSwapCoinIcon.tsx
@@ -14,37 +14,19 @@ import { IS_ANDROID, IS_IOS } from '@/env';
import { PIXEL_RATIO } from '@/utils/deviceUtils';
import { useSwapContext } from '../providers/swap-provider';
-const fallbackIconStyle = {
- ...borders.buildCircleAsObject(32),
- position: 'absolute' as ViewStyle['position'],
-};
-
-const largeFallbackIconStyle = {
- ...borders.buildCircleAsObject(36),
- position: 'absolute' as ViewStyle['position'],
-};
-
-const smallFallbackIconStyle = {
- ...borders.buildCircleAsObject(16),
- position: 'absolute' as ViewStyle['position'],
-};
-
export const AnimatedSwapCoinIcon = memo(function AnimatedSwapCoinIcon({
assetType,
- large = true,
- small,
+ size = 32,
showBadge = true,
}: {
assetType: 'input' | 'output';
- large?: boolean;
- small?: boolean;
+ size?: number;
showBadge?: boolean;
}) {
const { isDarkMode, colors } = useTheme();
const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext();
const asset = assetType === 'input' ? internalSelectedInputAsset : internalSelectedOutputAsset;
- const size = small ? 16 : large ? 36 : 32;
const didErrorForUniqueId = useSharedValue(undefined);
@@ -91,15 +73,8 @@ export const AnimatedSwapCoinIcon = memo(function AnimatedSwapCoinIcon({
}));
return (
-
-
+
+
{/* ⚠️ TODO: This works but we should figure out how to type this correctly to avoid this error */}
{/* @ts-expect-error: Doesn't pick up that it's getting a source prop via animatedProps */}
@@ -122,29 +97,14 @@ export const AnimatedSwapCoinIcon = memo(function AnimatedSwapCoinIcon({
/>
-
-
+
+
@@ -153,28 +113,28 @@ export const AnimatedSwapCoinIcon = memo(function AnimatedSwapCoinIcon({
);
});
+const fallbackIconStyle = (size: number) => ({
+ ...borders.buildCircleAsObject(size),
+ position: 'absolute' as ViewStyle['position'],
+});
+
+const coinIconFallbackStyle = (size: number) => ({
+ borderRadius: size / 2,
+ height: size,
+ width: size,
+ overflow: 'visible' as const,
+});
+
+const containerStyle = (size: number) => ({
+ elevation: 6,
+ height: size,
+ overflow: 'visible' as const,
+});
+
const sx = StyleSheet.create({
coinIcon: {
overflow: 'hidden',
},
- coinIconFallback: {
- borderRadius: 16,
- height: 32,
- overflow: 'visible',
- width: 32,
- },
- coinIconFallbackLarge: {
- borderRadius: 18,
- height: 36,
- overflow: 'visible',
- width: 36,
- },
- coinIconFallbackSmall: {
- borderRadius: 8,
- height: 16,
- overflow: 'visible',
- width: 16,
- },
container: {
elevation: 6,
height: 32,
diff --git a/src/__swaps__/screens/Swap/components/CoinRow.tsx b/src/__swaps__/screens/Swap/components/CoinRow.tsx
index c3fb98919b1..dc1894bddf1 100644
--- a/src/__swaps__/screens/Swap/components/CoinRow.tsx
+++ b/src/__swaps__/screens/Swap/components/CoinRow.tsx
@@ -131,7 +131,7 @@ export function CoinRow({ isFavorite, onPress, output, uniqueId, testID, ...asse
iconUrl={icon_url}
address={address}
mainnetAddress={mainnetAddress}
- large
+ size={36}
chainId={chainId}
symbol={symbol || ''}
color={colors?.primary}
diff --git a/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx b/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx
index bb5069c5092..f5e15d41c8d 100644
--- a/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx
+++ b/src/__swaps__/screens/Swap/components/SwapCoinIcon.tsx
@@ -24,20 +24,10 @@ const fallbackTextStyles = {
textAlign: 'center',
};
-const fallbackIconStyle = {
- ...borders.buildCircleAsObject(32),
+const fallbackIconStyle = (size: number) => ({
+ ...borders.buildCircleAsObject(size),
position: 'absolute',
-};
-
-const largeFallbackIconStyle = {
- ...borders.buildCircleAsObject(36),
- position: 'absolute',
-};
-
-const smallFallbackIconStyle = {
- ...borders.buildCircleAsObject(16),
- position: 'absolute',
-};
+});
/**
* If mainnet asset is available, get the token under /ethereum/ (token) url.
@@ -63,22 +53,22 @@ export const SwapCoinIcon = React.memo(function FeedCoinIcon({
iconUrl,
disableShadow = true,
forceDarkMode,
- large,
mainnetAddress,
chainId,
- small,
symbol,
+ size = 32,
+ chainSize,
}: {
address: string;
color?: string;
iconUrl?: string;
disableShadow?: boolean;
forceDarkMode?: boolean;
- large?: boolean;
mainnetAddress?: string;
chainId: ChainId;
- small?: boolean;
symbol: string;
+ size?: number;
+ chainSize?: number;
}) {
const theme = useTheme();
@@ -92,52 +82,52 @@ export const SwapCoinIcon = React.memo(function FeedCoinIcon({
const eth = isETH(resolvedAddress);
return (
-
+
{eth ? (
-
+
) : (
-
+
{() => (
)}
)}
- {chainId && chainId !== ChainId.mainnet && !small && (
+ {chainId && chainId !== ChainId.mainnet && size > 16 && (
-
+
)}
);
});
+const styles = {
+ container: (size: number) => ({
+ elevation: 6,
+ height: size,
+ overflow: 'visible' as const,
+ }),
+ coinIcon: (size: number) => ({
+ borderRadius: size / 2,
+ height: size,
+ width: size,
+ overflow: 'visible' as const,
+ }),
+};
+
const sx = StyleSheet.create({
badge: {
bottom: -0,
@@ -151,39 +141,6 @@ const sx = StyleSheet.create({
shadowRadius: 6,
shadowOpacity: 0.2,
},
- coinIconFallback: {
- borderRadius: 16,
- height: 32,
- overflow: 'visible',
- width: 32,
- },
- coinIconFallbackLarge: {
- borderRadius: 18,
- height: 36,
- overflow: 'visible',
- width: 36,
- },
- coinIconFallbackSmall: {
- borderRadius: 8,
- height: 16,
- overflow: 'visible',
- width: 16,
- },
- container: {
- elevation: 6,
- height: 32,
- overflow: 'visible',
- },
- containerLarge: {
- elevation: 6,
- height: 36,
- overflow: 'visible',
- },
- containerSmall: {
- elevation: 6,
- height: 16,
- overflow: 'visible',
- },
reactCoinIconContainer: {
alignItems: 'center',
justifyContent: 'center',
diff --git a/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx
index af94a152e8a..23734d39ce8 100644
--- a/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx
+++ b/src/__swaps__/screens/Swap/components/SwapInputAsset.tsx
@@ -96,7 +96,7 @@ function SwapInputAmount() {
function SwapInputIcon() {
return (
-
+
);
}
diff --git a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx
index de15b46bee6..93130066142 100644
--- a/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx
+++ b/src/__swaps__/screens/Swap/components/SwapOutputAsset.tsx
@@ -108,7 +108,7 @@ function SwapOutputAmount({ handleTapWhileDisabled }: { handleTapWhileDisabled:
function SwapOutputIcon() {
return (
-
+
);
}
diff --git a/src/__swaps__/screens/Swap/components/SwapSlider.tsx b/src/__swaps__/screens/Swap/components/SwapSlider.tsx
index 572f9eb6e80..90ac3ac724c 100644
--- a/src/__swaps__/screens/Swap/components/SwapSlider.tsx
+++ b/src/__swaps__/screens/Swap/components/SwapSlider.tsx
@@ -412,7 +412,7 @@ export const SwapSlider = ({
-
+
0 ? query : undefined,
@@ -417,33 +418,34 @@ export function useSearchCurrencyLists() {
{
enabled: memoizedData.enableUnverifiedSearch,
select: (data: TokenSearchResult) => {
- return getExactMatches(data, query).slice(0, MAX_UNVERIFIED_RESULTS);
+ return isAddress(query) ? getExactMatches(data, query).slice(0, MAX_UNVERIFIED_RESULTS) : data.slice(0, MAX_UNVERIFIED_RESULTS);
},
}
);
- return useMemo(() => {
+ const searchCurrencyLists = useMemo(() => {
const toChainId = selectedOutputChainId.value ?? ChainId.mainnet;
const bridgeResult = memoizedData.filteredBridgeAsset ?? undefined;
const crosschainMatches = query === '' ? undefined : verifiedAssets?.filter(asset => asset.chainId !== toChainId);
const verifiedResults = query === '' ? verifiedAssets : verifiedAssets?.filter(asset => asset.chainId === toChainId);
const unverifiedResults = memoizedData.enableUnverifiedSearch ? unverifiedAssets : undefined;
- return {
- results: buildListSectionsData({
- combinedData: {
- bridgeAsset: bridgeResult,
- crosschainExactMatches: crosschainMatches,
- unverifiedAssets: unverifiedResults,
- verifiedAssets: verifiedResults,
- recentSwaps: recentsForChain,
- popularAssets: popularAssetsForChain,
- },
- favoritesList,
- filteredBridgeAssetAddress: memoizedData.filteredBridgeAsset?.address,
- }),
- isLoading: isLoadingVerifiedAssets || isLoadingUnverifiedAssets || isLoadingPopularAssets,
- };
+ const results = buildListSectionsData({
+ combinedData: {
+ bridgeAsset: bridgeResult,
+ crosschainExactMatches: crosschainMatches,
+ unverifiedAssets: unverifiedResults,
+ verifiedAssets: verifiedResults,
+ recentSwaps: recentsForChain,
+ popularAssets: popularAssetsForChain,
+ },
+ favoritesList,
+ filteredBridgeAssetAddress: memoizedData.filteredBridgeAsset?.address,
+ });
+
+ const isLoading = isLoadingVerifiedAssets || isLoadingUnverifiedAssets || isLoadingPopularAssets;
+
+ return { results, isLoading };
}, [
favoritesList,
isLoadingUnverifiedAssets,
@@ -458,4 +460,17 @@ export function useSearchCurrencyLists() {
recentsForChain,
popularAssetsForChain,
]);
+
+ useEffect(() => {
+ if (searchCurrencyLists.isLoading) return;
+ const params = { screen: 'swap' as const, total_tokens: 0, no_icon: 0, query };
+ for (const assetOrHeader of searchCurrencyLists.results) {
+ if (assetOrHeader.listItemType === 'header') continue;
+ if (!assetOrHeader.icon_url) params.no_icon += 1;
+ params.total_tokens += 1;
+ }
+ analyticsV2.track(analyticsV2.event.tokenList, params);
+ }, [searchCurrencyLists.results, searchCurrencyLists.isLoading, query]);
+
+ return searchCurrencyLists;
}
diff --git a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx
index beee83e8b3e..715ff149768 100644
--- a/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx
+++ b/src/__swaps__/screens/Swap/providers/SyncSwapStateAndSharedValues.tsx
@@ -15,7 +15,6 @@ import {
import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets';
import { ChainId } from '@/state/backendNetworks/types';
import { ParsedAddressAsset } from '@/entities';
-import { useUserNativeNetworkAsset } from '@/resources/assets/useUserAsset';
import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps';
import { deepEqualWorklet } from '@/worklets/comparisons';
import { debounce } from 'lodash';
@@ -26,7 +25,10 @@ import { GasSettings } from '../hooks/useCustomGas';
import { useSelectedGas } from '../hooks/useSelectedGas';
import { useSwapEstimatedGasLimit } from '../hooks/useSwapEstimatedGasLimit';
import { useSwapContext } from './swap-provider';
+import { useUserAssetsStore } from '@/state/assets/userAssets';
+import { getUniqueId } from '@/utils/ethereumUtils';
import { useSwapsStore } from '@/state/swaps/swapsStore';
+import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
const BUFFER_RATIO = 0.5;
@@ -143,7 +145,12 @@ export function SyncGasStateToSharedValues() {
const { assetToSell, chainId = initialChainId, quote } = useSyncedSwapQuoteStore();
const gasSettings = useSelectedGas(chainId);
- const { data: userNativeNetworkAsset, isLoading: isLoadingNativeNetworkAsset } = useUserNativeNetworkAsset(chainId);
+
+ const { userNativeNetworkAsset, isLoadingNativeNetworkAsset } = useUserAssetsStore(state => {
+ const { address: nativeCurrencyAddress } = useBackendNetworksStore.getState().getChainsNativeAsset()[chainId];
+ const uniqueId = getUniqueId(nativeCurrencyAddress, chainId);
+ return { userNativeNetworkAsset: state.getLegacyUserAsset(uniqueId), isLoadingNativeNetworkAsset: state.isLoadingUserAssets };
+ });
const { data: estimatedGasLimit } = useSwapEstimatedGasLimit({ chainId, assetToSell, quote });
const gasFeeRange = useSharedValue<[string, string] | null>(null);
diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx
index da16527c726..c7ef1482127 100644
--- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx
+++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx
@@ -22,7 +22,7 @@ import { NavigationSteps, useSwapNavigation } from '@/__swaps__/screens/Swap/hoo
import { useSwapSettings } from '@/__swaps__/screens/Swap/hooks/useSwapSettings';
import { useSwapTextStyles } from '@/__swaps__/screens/Swap/hooks/useSwapTextStyles';
import { SwapWarningType, useSwapWarning } from '@/__swaps__/screens/Swap/hooks/useSwapWarning';
-import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets';
+import { userAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets';
import { AddressOrEth, ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets';
import { ChainId } from '@/state/backendNetworks/types';
import { SwapAssetType, inputKeys } from '@/__swaps__/types/swap';
@@ -41,7 +41,6 @@ import Routes from '@/navigation/routesNames';
import { walletExecuteRap } from '@/raps/execute';
import { QuoteTypeMap, RapSwapActionParameters } from '@/raps/references';
import { queryClient } from '@/react-query';
-import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery';
import { userAssetsStore } from '@/state/assets/userAssets';
import { swapsStore } from '@/state/swaps/swapsStore';
import { getNextNonce } from '@/state/nonces';
@@ -57,7 +56,7 @@ import { useSwapOutputQuotesDisabled } from '../hooks/useSwapOutputQuotesDisable
import { SyncGasStateToSharedValues, SyncQuoteSharedValuesToState } from './SyncSwapStateAndSharedValues';
import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance';
import { getRemoteConfig } from '@/model/remoteConfig';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
import { useBackendNetworksStore, getChainsNativeAssetWorklet } from '@/state/backendNetworks/backendNetworks';
import { getSwapsNavigationParams } from '../navigateToSwaps';
import { LedgerSigner } from '@/handlers/LedgerSigner';
@@ -227,11 +226,14 @@ export const SwapProvider = ({ children }: SwapProviderProps) => {
NotificationManager?.postNotification('rapInProgress');
const provider = getProvider({ chainId: parameters.chainId });
- const connectedToHardhat = useConnectedToHardhatStore.getState().connectedToHardhat;
+ const connectedToAnvil = useConnectedToAnvilStore.getState().connectedToAnvil;
const isBridge = swapsStore.getState().inputAsset?.mainnetAddress === swapsStore.getState().outputAsset?.mainnetAddress;
const isDegenModeEnabled = swapsStore.getState().degenMode;
const isSwappingToPopularAsset = swapsStore.getState().outputAsset?.sectionId === 'popular';
+ const lastNavigatedTrendingToken = swapsStore.getState().lastNavigatedTrendingToken;
+ const isSwappingToTrendingAsset =
+ lastNavigatedTrendingToken === parameters.assetToBuy.uniqueId || lastNavigatedTrendingToken === parameters.assetToSell.uniqueId;
const selectedGas = getSelectedGas(parameters.chainId);
if (!selectedGas) {
@@ -284,7 +286,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => {
};
}
- const chainId = connectedToHardhat ? ChainId.hardhat : parameters.chainId;
+ const chainId = connectedToAnvil ? ChainId.anvil : parameters.chainId;
const nonce = await getNextNonce({ address: parameters.quote.from, chainId });
const { errorMessage } = await performanceTracking.getState().executeFn({
@@ -326,6 +328,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => {
tradeAmountUSD: parameters.quote.tradeAmountUSD,
degenMode: isDegenModeEnabled,
isSwappingToPopularAsset,
+ isSwappingToTrendingAsset,
errorMessage,
isHardwareWallet,
});
@@ -342,14 +345,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => {
userAssetsQueryKey({
address: parameters.quote.from,
currency: nativeCurrency,
- connectedToHardhat,
- })
- );
- queryClient.invalidateQueries(
- swapsUserAssetsQueryKey({
- address: parameters.quote.from as Address,
- currency: nativeCurrency,
- testnetMode: !!connectedToHardhat,
+ testnetMode: connectedToAnvil,
})
);
@@ -397,6 +393,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => {
tradeAmountUSD: parameters.quote.tradeAmountUSD,
degenMode: isDegenModeEnabled,
isSwappingToPopularAsset,
+ isSwappingToTrendingAsset,
isHardwareWallet,
});
} catch (error) {
@@ -411,6 +408,11 @@ export const SwapProvider = ({ children }: SwapProviderProps) => {
},
});
}
+
+ // reset the last navigated trending token after a swap has taken place
+ swapsStore.setState({
+ lastNavigatedTrendingToken: undefined,
+ });
};
const executeSwap = performanceTracking.getState().executeFn({
diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts
index 76e685a1545..429b6c4f745 100644
--- a/src/__swaps__/screens/Swap/resources/assets/userAssets.ts
+++ b/src/__swaps__/screens/Swap/resources/assets/userAssets.ts
@@ -14,9 +14,11 @@ import { parseUserAsset } from '@/__swaps__/utils/assets';
import { greaterThan } from '@/helpers/utilities';
import { fetchUserAssetsByChain } from './userAssetsByChain';
-import { fetchHardhatBalancesByChainId } from '@/resources/assets/hardhatAssets';
+import { fetchAnvilBalancesByChainId } from '@/resources/assets/anvilAssets';
import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
+import { staleBalancesStore } from '@/state/staleBalances';
+import { IS_TEST } from '@/env';
import store from '@/redux/store';
const addysHttp = new RainbowFetchClient({
@@ -28,7 +30,7 @@ const addysHttp = new RainbowFetchClient({
const USER_ASSETS_REFETCH_INTERVAL = 60000;
const USER_ASSETS_TIMEOUT_DURATION = 20000;
-export const USER_ASSETS_STALE_INTERVAL = 30000;
+const USER_ASSETS_STALE_INTERVAL = 30000;
// ///////////////////////////////////////////////
// Query Types
@@ -104,7 +106,7 @@ async function userAssetsQueryFunction({
return {};
}
if (testnetMode) {
- const { assets, chainIdsInResponse } = await fetchHardhatBalancesByChainId(address);
+ const { assets, chainIdsInResponse } = await fetchAnvilBalancesByChainId(address);
const parsedAssets: Array<{
asset: ZerionAsset;
quantity: string;
@@ -123,20 +125,23 @@ async function userAssetsQueryFunction({
return parsedAssetsDict;
}
+
const cache = queryClient.getQueryCache();
const cachedUserAssets = (cache.find(userAssetsQueryKey({ address, currency, testnetMode }))?.state?.data ||
{}) as ParsedAssetsDictByChain;
try {
- const url = `/${useBackendNetworksStore.getState().getSupportedChainIds().join(',')}/${address}/assets`;
+ staleBalancesStore.getState().clearExpiredData(address);
+ const staleBalanceParam = staleBalancesStore.getState().getStaleBalancesQueryParam(address);
+ let url = `/${useBackendNetworksStore.getState().getSupportedChainIds().join(',')}/${address}/assets?currency=${currency.toLowerCase()}`;
+ if (staleBalanceParam) {
+ url += staleBalanceParam;
+ }
const res = await addysHttp.get(url, {
- params: {
- currency: currency.toLowerCase(),
- },
timeout: USER_ASSETS_TIMEOUT_DURATION,
});
const chainIdsInResponse = res?.data?.meta?.chain_ids || [];
const chainIdsWithErrorsInResponse = res?.data?.meta?.chain_ids_with_errors || [];
- const assets = res?.data?.payload?.assets || [];
+ const assets = res?.data?.payload?.assets?.filter(asset => !asset.asset.defi_position) || [];
if (address) {
if (chainIdsWithErrorsInResponse.length) {
userAssetsQueryFunctionRetryByChain({
@@ -170,7 +175,7 @@ async function userAssetsQueryFunction({
}
}
-type UserAssetsResult = QueryFunctionResult;
+export type UserAssetsResult = QueryFunctionResult;
async function userAssetsQueryFunctionRetryByChain({
address,
@@ -253,11 +258,11 @@ export function useUserAssets(
{ address, currency }: UserAssetsArgs,
config: QueryConfigWithSelect = {}
) {
- const { connectedToHardhat } = useConnectedToHardhatStore();
- return useQuery(userAssetsQueryKey({ address, currency, testnetMode: connectedToHardhat }), userAssetsQueryFunction, {
+ const { connectedToAnvil } = useConnectedToAnvilStore();
+ return useQuery(userAssetsQueryKey({ address, currency, testnetMode: connectedToAnvil }), userAssetsQueryFunction, {
...config,
enabled: !!address && !!currency,
refetchInterval: USER_ASSETS_REFETCH_INTERVAL,
- staleTime: process.env.IS_TESTING === 'true' ? 0 : 1000,
+ staleTime: IS_TEST ? 0 : USER_ASSETS_STALE_INTERVAL,
});
}
diff --git a/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts b/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts
index 723334cce51..032b5ef2eff 100644
--- a/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts
+++ b/src/__swaps__/screens/Swap/resources/assets/userAssetsByChain.ts
@@ -70,7 +70,7 @@ async function userAssetsByChainQueryFunction({
},
});
const chainIdsInResponse = res?.data?.meta?.chain_ids || [];
- const assets = res?.data?.payload?.assets || [];
+ const assets = res?.data?.payload?.assets?.filter(asset => !asset.asset.defi_position) || [];
if (assets.length && chainIdsInResponse.length) {
const parsedAssetsDict = await parseUserAssets({
assets,
diff --git a/src/__swaps__/screens/Swap/resources/search/discovery.ts b/src/__swaps__/screens/Swap/resources/search/discovery.ts
index ebb15d0f59b..40496e0d2d7 100644
--- a/src/__swaps__/screens/Swap/resources/search/discovery.ts
+++ b/src/__swaps__/screens/Swap/resources/search/discovery.ts
@@ -7,7 +7,7 @@ import { useQuery } from '@tanstack/react-query';
import { parseTokenSearch } from './utils';
const tokenSearchHttp = new RainbowFetchClient({
- baseURL: 'https://token-search.rainbow.me/v3/discovery',
+ baseURL: 'https://token-search.rainbow.me/v3/trending/swaps',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
diff --git a/src/__swaps__/screens/Swap/resources/search/search.ts b/src/__swaps__/screens/Swap/resources/search/search.ts
index b270d55c6cd..52ed1d53443 100644
--- a/src/__swaps__/screens/Swap/resources/search/search.ts
+++ b/src/__swaps__/screens/Swap/resources/search/search.ts
@@ -12,7 +12,7 @@ import { parseTokenSearch } from './utils';
const ALL_VERIFIED_TOKENS_PARAM = '/?list=verifiedAssets';
const tokenSearchHttp = new RainbowFetchClient({
- baseURL: 'https://token-search.rainbow.me/v2',
+ baseURL: 'https://token-search.rainbow.me/v3/tokens',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
@@ -30,13 +30,19 @@ export type TokenSearchArgs = {
list: TokenSearchListId;
threshold?: TokenSearchThreshold;
query?: string;
+ shouldPersist?: boolean;
};
// ///////////////////////////////////////////////
// Query Key
-const tokenSearchQueryKey = ({ chainId, fromChainId, keys, list, threshold, query }: TokenSearchArgs) =>
- createQueryKey('TokenSearch', { chainId, fromChainId, keys, list, threshold, query }, { persisterVersion: 2 });
+const tokenSearchQueryKey = ({ chainId, fromChainId, keys, list, threshold, query, shouldPersist }: TokenSearchArgs) => {
+ return createQueryKey(
+ 'TokenSearch',
+ { chainId, fromChainId, keys, list, threshold, query },
+ { persisterVersion: shouldPersist ? 3 : undefined }
+ );
+};
type TokenSearchQueryKey = ReturnType;
@@ -77,6 +83,7 @@ async function tokenSearchQueryFunction({
return parseTokenSearch(tokenSearch.data.data, chainId);
}
+ // search for address on other chains
const allVerifiedTokens = await tokenSearchHttp.get<{ data: SearchAsset[] }>(ALL_VERIFIED_TOKENS_PARAM);
const addressQuery = query.trim().toLowerCase();
@@ -104,8 +111,9 @@ export async function fetchTokenSearch(
{ chainId, fromChainId, keys, list, threshold, query }: TokenSearchArgs,
config: QueryConfigWithSelect = {}
) {
+ const shouldPersist = query === undefined;
return await queryClient.fetchQuery(
- tokenSearchQueryKey({ chainId, fromChainId, keys, list, threshold, query }),
+ tokenSearchQueryKey({ chainId, fromChainId, keys, list, threshold, query, shouldPersist }),
tokenSearchQueryFunction,
config
);
@@ -130,7 +138,8 @@ export function useTokenSearch(
{ chainId, fromChainId, keys, list, threshold, query }: TokenSearchArgs,
config: QueryConfigWithSelect = {}
) {
- return useQuery(tokenSearchQueryKey({ chainId, fromChainId, keys, list, threshold, query }), tokenSearchQueryFunction, {
+ const shouldPersist = query === undefined;
+ return useQuery(tokenSearchQueryKey({ chainId, fromChainId, keys, list, threshold, query, shouldPersist }), tokenSearchQueryFunction, {
...config,
keepPreviousData: true,
});
diff --git a/src/__swaps__/types/assets.ts b/src/__swaps__/types/assets.ts
index c6f7bcd84a5..f473d907900 100644
--- a/src/__swaps__/types/assets.ts
+++ b/src/__swaps__/types/assets.ts
@@ -140,6 +140,7 @@ export interface ZerionAsset {
bridgeable: boolean;
networks: { [id in ChainId]?: { bridgeable: boolean } };
};
+ defi_position?: boolean;
}
// protocols https://github.com/rainbow-me/go-utils-lib/blob/master/pkg/enums/token_type.go#L44
diff --git a/src/__swaps__/utils/assets.ts b/src/__swaps__/utils/assets.ts
index c2fa5dfd935..8c87ad572bb 100644
--- a/src/__swaps__/utils/assets.ts
+++ b/src/__swaps__/utils/assets.ts
@@ -234,37 +234,6 @@ export function parseUserAssetBalances({
};
}
-export function parseParsedUserAsset({
- parsedAsset,
- currency,
- quantity,
-}: {
- parsedAsset: ParsedUserAsset;
- currency: SupportedCurrencyKey;
- quantity: string;
-}): ParsedUserAsset {
- const amount = convertRawAmountToDecimalFormat(quantity, parsedAsset?.decimals);
- return {
- ...parsedAsset,
- balance: {
- amount,
- display: convertAmountToBalanceDisplay(amount, {
- decimals: parsedAsset?.decimals,
- symbol: parsedAsset?.symbol,
- }),
- },
- native: {
- ...parsedAsset.native,
- balance: getNativeAssetBalance({
- currency,
- decimals: parsedAsset?.decimals,
- priceUnit: parsedAsset?.price?.value || 0,
- value: amount,
- }),
- },
- };
-}
-
export const parseSearchAsset = ({
assetWithPrice,
searchAsset,
diff --git a/src/analytics/event.ts b/src/analytics/event.ts
index adfcbee8a6d..3696b908736 100644
--- a/src/analytics/event.ts
+++ b/src/analytics/event.ts
@@ -9,6 +9,7 @@ import { RequestSource } from '@/utils/requestNavigationHandlers';
import { CrosschainQuote, Quote, QuoteError } from '@rainbow-me/swaps';
import { AnyPerformanceLog, Screen } from '../state/performance/operations';
import { FavoritedSite } from '@/state/browser/favoriteDappsStore';
+import { TrendingToken } from '@/resources/trendingTokens/trendingTokens';
/**
* All events, used by `analytics.track()`
@@ -167,6 +168,17 @@ export const event = {
// token details
tokenDetailsErc20: 'token_details.erc20',
tokenDetailsNFT: 'token_details.nft',
+
+ // token lists (wallet, swap, send)
+ tokenList: 'token_list',
+
+ // trending tokens
+ viewTrendingToken: 'trending_tokens.view_trending_token',
+ viewRankedCategory: 'trending_tokens.view_ranked_category',
+ changeNetworkFilter: 'trending_tokens.change_network_filter',
+ changeTimeframeFilter: 'trending_tokens.change_timeframe_filter',
+ changeSortFilter: 'trending_tokens.change_sort_filter',
+ hasLinkedFarcaster: 'trending_tokens.has_linked_farcaster',
} as const;
type SwapEventParameters = {
@@ -186,6 +198,7 @@ type SwapEventParameters = {
tradeAmountUSD: number;
degenMode: boolean;
isSwappingToPopularAsset: boolean;
+ isSwappingToTrendingAsset: boolean;
isHardwareWallet: boolean;
};
@@ -706,4 +719,45 @@ export type EventProperties = {
eventSentAfterMs: number;
available_data: { description: boolean; image_url: boolean; floorPrice: boolean };
};
+
+ [event.tokenList]: {
+ screen: 'wallet' | 'swap' | 'send' | 'discover';
+ total_tokens: number;
+ no_icon: number;
+ no_price?: number;
+ query?: string; // query is only sent for the swap screen
+ };
+
+ [event.viewTrendingToken]: {
+ address: TrendingToken['address'];
+ chainId: TrendingToken['chainId'];
+ symbol: TrendingToken['symbol'];
+ name: TrendingToken['name'];
+ highlightedFriends: number;
+ };
+
+ [event.viewRankedCategory]: {
+ category: string;
+ chainId: ChainId | undefined;
+ isLimited: boolean;
+ isEmpty: boolean;
+ };
+
+ [event.changeNetworkFilter]: {
+ chainId: ChainId | undefined;
+ };
+
+ [event.changeTimeframeFilter]: {
+ timeframe: string;
+ };
+
+ [event.changeSortFilter]: {
+ sort: string | undefined;
+ };
+
+ [event.hasLinkedFarcaster]: {
+ hasFarcaster: boolean;
+ personalizedTrending: boolean;
+ walletHash: string;
+ };
};
diff --git a/src/analytics/index.ts b/src/analytics/index.ts
index 222cf513b34..89aaa0a696a 100644
--- a/src/analytics/index.ts
+++ b/src/analytics/index.ts
@@ -1,13 +1,12 @@
import rudderClient from '@rudderstack/rudder-sdk-react-native';
-import { REACT_NATIVE_RUDDERSTACK_WRITE_KEY, RUDDERSTACK_DATA_PLANE_URL, IS_TESTING } from 'react-native-dotenv';
+import { REACT_NATIVE_RUDDERSTACK_WRITE_KEY, RUDDERSTACK_DATA_PLANE_URL } from 'react-native-dotenv';
import { EventProperties, event } from '@/analytics/event';
import { UserProperties } from '@/analytics/userProperties';
import { logger, RainbowError } from '@/logger';
import { device } from '@/storage';
import { WalletContext } from './utils';
-
-const isTesting = IS_TESTING === 'true';
+import { IS_TEST } from '@/env';
export class Analytics {
client: typeof rudderClient;
@@ -19,8 +18,8 @@ export class Analytics {
constructor() {
this.client = rudderClient;
- this.disabled = isTesting || !!device.get(['doNotTrack']);
- if (isTesting) {
+ this.disabled = IS_TEST || !!device.get(['doNotTrack']);
+ if (IS_TEST) {
logger.debug('[Analytics]: disabled for testing');
} else {
logger.debug('[Analytics]: client initialized');
diff --git a/src/analytics/userProperties.ts b/src/analytics/userProperties.ts
index b42d5518a61..8a467e4b09a 100644
--- a/src/analytics/userProperties.ts
+++ b/src/analytics/userProperties.ts
@@ -1,3 +1,4 @@
+import { ChainId } from '@/state/backendNetworks/types';
import { NativeCurrencyKey } from '@/entities';
import { Language } from '@/languages';
@@ -36,6 +37,9 @@ export interface UserProperties {
hiddenCOins?: string[];
appIcon?: string;
+ // most used networks at the time the user first opens the network switcher
+ mostUsedNetworks?: ChainId[];
+
// assets
NFTs?: number;
poaps?: number;
diff --git a/src/assets/badges/gnosis.png b/src/assets/badges/gnosis.png
new file mode 100644
index 00000000000..afb08cb40e6
Binary files /dev/null and b/src/assets/badges/gnosis.png differ
diff --git a/src/assets/badges/gnosis@2x.png b/src/assets/badges/gnosis@2x.png
new file mode 100644
index 00000000000..3a021929690
Binary files /dev/null and b/src/assets/badges/gnosis@2x.png differ
diff --git a/src/assets/badges/gnosis@3x.png b/src/assets/badges/gnosis@3x.png
new file mode 100644
index 00000000000..09946eb88a5
Binary files /dev/null and b/src/assets/badges/gnosis@3x.png differ
diff --git a/src/assets/badges/gnosisBadge.png b/src/assets/badges/gnosisBadge.png
new file mode 100644
index 00000000000..961610867dd
Binary files /dev/null and b/src/assets/badges/gnosisBadge.png differ
diff --git a/src/assets/badges/gnosisBadge@2x.png b/src/assets/badges/gnosisBadge@2x.png
new file mode 100644
index 00000000000..1355360aca0
Binary files /dev/null and b/src/assets/badges/gnosisBadge@2x.png differ
diff --git a/src/assets/badges/gnosisBadge@3x.png b/src/assets/badges/gnosisBadge@3x.png
new file mode 100644
index 00000000000..19592fc8d64
Binary files /dev/null and b/src/assets/badges/gnosisBadge@3x.png differ
diff --git a/src/assets/badges/gnosisBadgeDark.png b/src/assets/badges/gnosisBadgeDark.png
new file mode 100644
index 00000000000..cd7d26152c2
Binary files /dev/null and b/src/assets/badges/gnosisBadgeDark.png differ
diff --git a/src/assets/badges/gnosisBadgeDark@2x.png b/src/assets/badges/gnosisBadgeDark@2x.png
new file mode 100644
index 00000000000..029c07d9ddb
Binary files /dev/null and b/src/assets/badges/gnosisBadgeDark@2x.png differ
diff --git a/src/assets/badges/gnosisBadgeDark@3x.png b/src/assets/badges/gnosisBadgeDark@3x.png
new file mode 100644
index 00000000000..5c8ed229375
Binary files /dev/null and b/src/assets/badges/gnosisBadgeDark@3x.png differ
diff --git a/src/assets/badges/gnosisBadgeLarge.png b/src/assets/badges/gnosisBadgeLarge.png
new file mode 100644
index 00000000000..9cf5511d8f1
Binary files /dev/null and b/src/assets/badges/gnosisBadgeLarge.png differ
diff --git a/src/assets/badges/gnosisBadgeLarge@2x.png b/src/assets/badges/gnosisBadgeLarge@2x.png
new file mode 100644
index 00000000000..301ddfb2914
Binary files /dev/null and b/src/assets/badges/gnosisBadgeLarge@2x.png differ
diff --git a/src/assets/badges/gnosisBadgeLarge@3x.png b/src/assets/badges/gnosisBadgeLarge@3x.png
new file mode 100644
index 00000000000..e787754b713
Binary files /dev/null and b/src/assets/badges/gnosisBadgeLarge@3x.png differ
diff --git a/src/assets/badges/gnosisBadgeLargeDark.png b/src/assets/badges/gnosisBadgeLargeDark.png
new file mode 100644
index 00000000000..4f3b0c859cb
Binary files /dev/null and b/src/assets/badges/gnosisBadgeLargeDark.png differ
diff --git a/src/assets/badges/gnosisBadgeLargeDark@2x.png b/src/assets/badges/gnosisBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..79768e5d97e
Binary files /dev/null and b/src/assets/badges/gnosisBadgeLargeDark@2x.png differ
diff --git a/src/assets/badges/gnosisBadgeLargeDark@3x.png b/src/assets/badges/gnosisBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..4975e795ef0
Binary files /dev/null and b/src/assets/badges/gnosisBadgeLargeDark@3x.png differ
diff --git a/src/assets/badges/gnosisBadgeNoShadow.png b/src/assets/badges/gnosisBadgeNoShadow.png
new file mode 100644
index 00000000000..baa5ee94397
Binary files /dev/null and b/src/assets/badges/gnosisBadgeNoShadow.png differ
diff --git a/src/assets/badges/gnosisBadgeNoShadow@2x.png b/src/assets/badges/gnosisBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..57e1e098b6a
Binary files /dev/null and b/src/assets/badges/gnosisBadgeNoShadow@2x.png differ
diff --git a/src/assets/badges/gnosisBadgeNoShadow@3x.png b/src/assets/badges/gnosisBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..42a52730e27
Binary files /dev/null and b/src/assets/badges/gnosisBadgeNoShadow@3x.png differ
diff --git a/src/assets/badges/gravity.png b/src/assets/badges/gravity.png
new file mode 100644
index 00000000000..a515d5551c5
Binary files /dev/null and b/src/assets/badges/gravity.png differ
diff --git a/src/assets/badges/gravity@2x.png b/src/assets/badges/gravity@2x.png
new file mode 100644
index 00000000000..29e60cbad1c
Binary files /dev/null and b/src/assets/badges/gravity@2x.png differ
diff --git a/src/assets/badges/gravity@3x.png b/src/assets/badges/gravity@3x.png
new file mode 100644
index 00000000000..55108967dea
Binary files /dev/null and b/src/assets/badges/gravity@3x.png differ
diff --git a/src/assets/badges/gravityBadge.png b/src/assets/badges/gravityBadge.png
new file mode 100644
index 00000000000..ad033ad3691
Binary files /dev/null and b/src/assets/badges/gravityBadge.png differ
diff --git a/src/assets/badges/gravityBadge@2x.png b/src/assets/badges/gravityBadge@2x.png
new file mode 100644
index 00000000000..2264da68ec9
Binary files /dev/null and b/src/assets/badges/gravityBadge@2x.png differ
diff --git a/src/assets/badges/gravityBadge@3x.png b/src/assets/badges/gravityBadge@3x.png
new file mode 100644
index 00000000000..8d6bdddc381
Binary files /dev/null and b/src/assets/badges/gravityBadge@3x.png differ
diff --git a/src/assets/badges/gravityBadgeDark.png b/src/assets/badges/gravityBadgeDark.png
new file mode 100644
index 00000000000..02fb9e0677f
Binary files /dev/null and b/src/assets/badges/gravityBadgeDark.png differ
diff --git a/src/assets/badges/gravityBadgeDark@2x.png b/src/assets/badges/gravityBadgeDark@2x.png
new file mode 100644
index 00000000000..9aadd9549ae
Binary files /dev/null and b/src/assets/badges/gravityBadgeDark@2x.png differ
diff --git a/src/assets/badges/gravityBadgeDark@3x.png b/src/assets/badges/gravityBadgeDark@3x.png
new file mode 100644
index 00000000000..5218bf5b88e
Binary files /dev/null and b/src/assets/badges/gravityBadgeDark@3x.png differ
diff --git a/src/assets/badges/gravityBadgeLarge.png b/src/assets/badges/gravityBadgeLarge.png
new file mode 100644
index 00000000000..ccfed23d64b
Binary files /dev/null and b/src/assets/badges/gravityBadgeLarge.png differ
diff --git a/src/assets/badges/gravityBadgeLarge@2x.png b/src/assets/badges/gravityBadgeLarge@2x.png
new file mode 100644
index 00000000000..51e00178021
Binary files /dev/null and b/src/assets/badges/gravityBadgeLarge@2x.png differ
diff --git a/src/assets/badges/gravityBadgeLarge@3x.png b/src/assets/badges/gravityBadgeLarge@3x.png
new file mode 100644
index 00000000000..805d9fcd7b9
Binary files /dev/null and b/src/assets/badges/gravityBadgeLarge@3x.png differ
diff --git a/src/assets/badges/gravityBadgeLargeDark.png b/src/assets/badges/gravityBadgeLargeDark.png
new file mode 100644
index 00000000000..ab9ee2fcbae
Binary files /dev/null and b/src/assets/badges/gravityBadgeLargeDark.png differ
diff --git a/src/assets/badges/gravityBadgeLargeDark@2x.png b/src/assets/badges/gravityBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..067b373a7e4
Binary files /dev/null and b/src/assets/badges/gravityBadgeLargeDark@2x.png differ
diff --git a/src/assets/badges/gravityBadgeLargeDark@3x.png b/src/assets/badges/gravityBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..362d6f3d772
Binary files /dev/null and b/src/assets/badges/gravityBadgeLargeDark@3x.png differ
diff --git a/src/assets/badges/gravityBadgeNoShadow.png b/src/assets/badges/gravityBadgeNoShadow.png
new file mode 100644
index 00000000000..9492a525822
Binary files /dev/null and b/src/assets/badges/gravityBadgeNoShadow.png differ
diff --git a/src/assets/badges/gravityBadgeNoShadow@2x.png b/src/assets/badges/gravityBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..5052d829f1e
Binary files /dev/null and b/src/assets/badges/gravityBadgeNoShadow@2x.png differ
diff --git a/src/assets/badges/gravityBadgeNoShadow@3x.png b/src/assets/badges/gravityBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..474f948bf99
Binary files /dev/null and b/src/assets/badges/gravityBadgeNoShadow@3x.png differ
diff --git a/src/assets/badges/linea.png b/src/assets/badges/linea.png
new file mode 100644
index 00000000000..14e4a7fface
Binary files /dev/null and b/src/assets/badges/linea.png differ
diff --git a/src/assets/badges/linea@2x.png b/src/assets/badges/linea@2x.png
new file mode 100644
index 00000000000..42f41b35f9d
Binary files /dev/null and b/src/assets/badges/linea@2x.png differ
diff --git a/src/assets/badges/linea@3x.png b/src/assets/badges/linea@3x.png
new file mode 100644
index 00000000000..4cc7abd0cdc
Binary files /dev/null and b/src/assets/badges/linea@3x.png differ
diff --git a/src/assets/badges/lineaBadge.png b/src/assets/badges/lineaBadge.png
new file mode 100644
index 00000000000..267723587ca
Binary files /dev/null and b/src/assets/badges/lineaBadge.png differ
diff --git a/src/assets/badges/lineaBadge@2x.png b/src/assets/badges/lineaBadge@2x.png
new file mode 100644
index 00000000000..70bb69fae24
Binary files /dev/null and b/src/assets/badges/lineaBadge@2x.png differ
diff --git a/src/assets/badges/lineaBadge@3x.png b/src/assets/badges/lineaBadge@3x.png
new file mode 100644
index 00000000000..814892f7fdb
Binary files /dev/null and b/src/assets/badges/lineaBadge@3x.png differ
diff --git a/src/assets/badges/lineaBadgeDark.png b/src/assets/badges/lineaBadgeDark.png
new file mode 100644
index 00000000000..1fa46075d00
Binary files /dev/null and b/src/assets/badges/lineaBadgeDark.png differ
diff --git a/src/assets/badges/lineaBadgeDark@2x.png b/src/assets/badges/lineaBadgeDark@2x.png
new file mode 100644
index 00000000000..152eb8d6177
Binary files /dev/null and b/src/assets/badges/lineaBadgeDark@2x.png differ
diff --git a/src/assets/badges/lineaBadgeDark@3x.png b/src/assets/badges/lineaBadgeDark@3x.png
new file mode 100644
index 00000000000..a58ff5b93ac
Binary files /dev/null and b/src/assets/badges/lineaBadgeDark@3x.png differ
diff --git a/src/assets/badges/lineaBadgeLarge.png b/src/assets/badges/lineaBadgeLarge.png
new file mode 100644
index 00000000000..93a23a92347
Binary files /dev/null and b/src/assets/badges/lineaBadgeLarge.png differ
diff --git a/src/assets/badges/lineaBadgeLarge@2x.png b/src/assets/badges/lineaBadgeLarge@2x.png
new file mode 100644
index 00000000000..0fe5d25acdf
Binary files /dev/null and b/src/assets/badges/lineaBadgeLarge@2x.png differ
diff --git a/src/assets/badges/lineaBadgeLarge@3x.png b/src/assets/badges/lineaBadgeLarge@3x.png
new file mode 100644
index 00000000000..4f3a3c0ce1c
Binary files /dev/null and b/src/assets/badges/lineaBadgeLarge@3x.png differ
diff --git a/src/assets/badges/lineaBadgeLargeDark.png b/src/assets/badges/lineaBadgeLargeDark.png
new file mode 100644
index 00000000000..f463c5abcc7
Binary files /dev/null and b/src/assets/badges/lineaBadgeLargeDark.png differ
diff --git a/src/assets/badges/lineaBadgeLargeDark@2x.png b/src/assets/badges/lineaBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..f417f8f1f16
Binary files /dev/null and b/src/assets/badges/lineaBadgeLargeDark@2x.png differ
diff --git a/src/assets/badges/lineaBadgeLargeDark@3x.png b/src/assets/badges/lineaBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..86c51a9e20b
Binary files /dev/null and b/src/assets/badges/lineaBadgeLargeDark@3x.png differ
diff --git a/src/assets/badges/lineaBadgeNoShadow.png b/src/assets/badges/lineaBadgeNoShadow.png
new file mode 100644
index 00000000000..1af3c5fd42d
Binary files /dev/null and b/src/assets/badges/lineaBadgeNoShadow.png differ
diff --git a/src/assets/badges/lineaBadgeNoShadow@2x.png b/src/assets/badges/lineaBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..cddfa99c67f
Binary files /dev/null and b/src/assets/badges/lineaBadgeNoShadow@2x.png differ
diff --git a/src/assets/badges/lineaBadgeNoShadow@3x.png b/src/assets/badges/lineaBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..22007385103
Binary files /dev/null and b/src/assets/badges/lineaBadgeNoShadow@3x.png differ
diff --git a/src/assets/badges/sankBadgeDark.png b/src/assets/badges/sankBadgeDark.png
new file mode 100644
index 00000000000..6ab341257ab
Binary files /dev/null and b/src/assets/badges/sankBadgeDark.png differ
diff --git a/src/assets/badges/sankBadgeDark@2x.png b/src/assets/badges/sankBadgeDark@2x.png
new file mode 100644
index 00000000000..d4e5beded3f
Binary files /dev/null and b/src/assets/badges/sankBadgeDark@2x.png differ
diff --git a/src/assets/badges/sankBadgeDark@3x.png b/src/assets/badges/sankBadgeDark@3x.png
new file mode 100644
index 00000000000..9b5eba8fe7a
Binary files /dev/null and b/src/assets/badges/sankBadgeDark@3x.png differ
diff --git a/src/assets/badges/sanko.png b/src/assets/badges/sanko.png
new file mode 100644
index 00000000000..e0eff636cac
Binary files /dev/null and b/src/assets/badges/sanko.png differ
diff --git a/src/assets/badges/sanko@2x.png b/src/assets/badges/sanko@2x.png
new file mode 100644
index 00000000000..399f9ad13e6
Binary files /dev/null and b/src/assets/badges/sanko@2x.png differ
diff --git a/src/assets/badges/sanko@3x.png b/src/assets/badges/sanko@3x.png
new file mode 100644
index 00000000000..c5c5e42e575
Binary files /dev/null and b/src/assets/badges/sanko@3x.png differ
diff --git a/src/assets/badges/sankoBadge.png b/src/assets/badges/sankoBadge.png
new file mode 100644
index 00000000000..3e44d659f3a
Binary files /dev/null and b/src/assets/badges/sankoBadge.png differ
diff --git a/src/assets/badges/sankoBadge@2x.png b/src/assets/badges/sankoBadge@2x.png
new file mode 100644
index 00000000000..39a74fcdc8d
Binary files /dev/null and b/src/assets/badges/sankoBadge@2x.png differ
diff --git a/src/assets/badges/sankoBadge@3x.png b/src/assets/badges/sankoBadge@3x.png
new file mode 100644
index 00000000000..a9729e07af3
Binary files /dev/null and b/src/assets/badges/sankoBadge@3x.png differ
diff --git a/src/assets/badges/sankoBadgeLarge.png b/src/assets/badges/sankoBadgeLarge.png
new file mode 100644
index 00000000000..e2ead662090
Binary files /dev/null and b/src/assets/badges/sankoBadgeLarge.png differ
diff --git a/src/assets/badges/sankoBadgeLarge@2x.png b/src/assets/badges/sankoBadgeLarge@2x.png
new file mode 100644
index 00000000000..726dfc00978
Binary files /dev/null and b/src/assets/badges/sankoBadgeLarge@2x.png differ
diff --git a/src/assets/badges/sankoBadgeLarge@3x.png b/src/assets/badges/sankoBadgeLarge@3x.png
new file mode 100644
index 00000000000..897a700db31
Binary files /dev/null and b/src/assets/badges/sankoBadgeLarge@3x.png differ
diff --git a/src/assets/badges/sankoBadgeLargeDark.png b/src/assets/badges/sankoBadgeLargeDark.png
new file mode 100644
index 00000000000..0218596d5bb
Binary files /dev/null and b/src/assets/badges/sankoBadgeLargeDark.png differ
diff --git a/src/assets/badges/sankoBadgeLargeDark@2x.png b/src/assets/badges/sankoBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..40c50ffecf6
Binary files /dev/null and b/src/assets/badges/sankoBadgeLargeDark@2x.png differ
diff --git a/src/assets/badges/sankoBadgeLargeDark@3x.png b/src/assets/badges/sankoBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..f683ef22d4b
Binary files /dev/null and b/src/assets/badges/sankoBadgeLargeDark@3x.png differ
diff --git a/src/assets/badges/sankoBadgeNoShadow.png b/src/assets/badges/sankoBadgeNoShadow.png
new file mode 100644
index 00000000000..b0b2c0385ac
Binary files /dev/null and b/src/assets/badges/sankoBadgeNoShadow.png differ
diff --git a/src/assets/badges/sankoBadgeNoShadow@2x.png b/src/assets/badges/sankoBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..990e00a88b0
Binary files /dev/null and b/src/assets/badges/sankoBadgeNoShadow@2x.png differ
diff --git a/src/assets/badges/sankoBadgeNoShadow@3x.png b/src/assets/badges/sankoBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..f79b957c903
Binary files /dev/null and b/src/assets/badges/sankoBadgeNoShadow@3x.png differ
diff --git a/src/assets/badges/scroll.png b/src/assets/badges/scroll.png
new file mode 100644
index 00000000000..b074e4e54f3
Binary files /dev/null and b/src/assets/badges/scroll.png differ
diff --git a/src/assets/badges/scroll@2x.png b/src/assets/badges/scroll@2x.png
new file mode 100644
index 00000000000..1dab422416c
Binary files /dev/null and b/src/assets/badges/scroll@2x.png differ
diff --git a/src/assets/badges/scroll@3x.png b/src/assets/badges/scroll@3x.png
new file mode 100644
index 00000000000..04482628c63
Binary files /dev/null and b/src/assets/badges/scroll@3x.png differ
diff --git a/src/assets/badges/scrollBadge.png b/src/assets/badges/scrollBadge.png
new file mode 100644
index 00000000000..3266ab33b57
Binary files /dev/null and b/src/assets/badges/scrollBadge.png differ
diff --git a/src/assets/badges/scrollBadge@2x.png b/src/assets/badges/scrollBadge@2x.png
new file mode 100644
index 00000000000..aea2e050f18
Binary files /dev/null and b/src/assets/badges/scrollBadge@2x.png differ
diff --git a/src/assets/badges/scrollBadge@3x.png b/src/assets/badges/scrollBadge@3x.png
new file mode 100644
index 00000000000..40f77762f36
Binary files /dev/null and b/src/assets/badges/scrollBadge@3x.png differ
diff --git a/src/assets/badges/scrollBadgeDark.png b/src/assets/badges/scrollBadgeDark.png
new file mode 100644
index 00000000000..71927e891e8
Binary files /dev/null and b/src/assets/badges/scrollBadgeDark.png differ
diff --git a/src/assets/badges/scrollBadgeDark@2x.png b/src/assets/badges/scrollBadgeDark@2x.png
new file mode 100644
index 00000000000..53095e21774
Binary files /dev/null and b/src/assets/badges/scrollBadgeDark@2x.png differ
diff --git a/src/assets/badges/scrollBadgeDark@3x.png b/src/assets/badges/scrollBadgeDark@3x.png
new file mode 100644
index 00000000000..5bfd03fdd2b
Binary files /dev/null and b/src/assets/badges/scrollBadgeDark@3x.png differ
diff --git a/src/assets/badges/scrollBadgeLarge.png b/src/assets/badges/scrollBadgeLarge.png
new file mode 100644
index 00000000000..cc1dbc31e63
Binary files /dev/null and b/src/assets/badges/scrollBadgeLarge.png differ
diff --git a/src/assets/badges/scrollBadgeLarge@2x.png b/src/assets/badges/scrollBadgeLarge@2x.png
new file mode 100644
index 00000000000..c60eca2ff99
Binary files /dev/null and b/src/assets/badges/scrollBadgeLarge@2x.png differ
diff --git a/src/assets/badges/scrollBadgeLarge@3x.png b/src/assets/badges/scrollBadgeLarge@3x.png
new file mode 100644
index 00000000000..8be695f8247
Binary files /dev/null and b/src/assets/badges/scrollBadgeLarge@3x.png differ
diff --git a/src/assets/badges/scrollBadgeLargeDark.png b/src/assets/badges/scrollBadgeLargeDark.png
new file mode 100644
index 00000000000..5ecf4f2dff7
Binary files /dev/null and b/src/assets/badges/scrollBadgeLargeDark.png differ
diff --git a/src/assets/badges/scrollBadgeLargeDark@2x.png b/src/assets/badges/scrollBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..5c6b5d6a50c
Binary files /dev/null and b/src/assets/badges/scrollBadgeLargeDark@2x.png differ
diff --git a/src/assets/badges/scrollBadgeLargeDark@3x.png b/src/assets/badges/scrollBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..0e508374d35
Binary files /dev/null and b/src/assets/badges/scrollBadgeLargeDark@3x.png differ
diff --git a/src/assets/badges/scrollBadgeNoShadow.png b/src/assets/badges/scrollBadgeNoShadow.png
new file mode 100644
index 00000000000..cfdef2d77c9
Binary files /dev/null and b/src/assets/badges/scrollBadgeNoShadow.png differ
diff --git a/src/assets/badges/scrollBadgeNoShadow@2x.png b/src/assets/badges/scrollBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..79c1aa6e3b0
Binary files /dev/null and b/src/assets/badges/scrollBadgeNoShadow@2x.png differ
diff --git a/src/assets/badges/scrollBadgeNoShadow@3x.png b/src/assets/badges/scrollBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..d140debde92
Binary files /dev/null and b/src/assets/badges/scrollBadgeNoShadow@3x.png differ
diff --git a/src/assets/badges/zkSyncBadge.png b/src/assets/badges/zkSyncBadge.png
new file mode 100644
index 00000000000..f84a89c233f
Binary files /dev/null and b/src/assets/badges/zkSyncBadge.png differ
diff --git a/src/assets/badges/zkSyncBadge@2x.png b/src/assets/badges/zkSyncBadge@2x.png
new file mode 100644
index 00000000000..02a0f753727
Binary files /dev/null and b/src/assets/badges/zkSyncBadge@2x.png differ
diff --git a/src/assets/badges/zkSyncBadge@3x.png b/src/assets/badges/zkSyncBadge@3x.png
new file mode 100644
index 00000000000..db578e63d79
Binary files /dev/null and b/src/assets/badges/zkSyncBadge@3x.png differ
diff --git a/src/assets/badges/zksync.png b/src/assets/badges/zksync.png
new file mode 100644
index 00000000000..1225b4e6c6e
Binary files /dev/null and b/src/assets/badges/zksync.png differ
diff --git a/src/assets/badges/zksync@2x.png b/src/assets/badges/zksync@2x.png
new file mode 100644
index 00000000000..b437d430a93
Binary files /dev/null and b/src/assets/badges/zksync@2x.png differ
diff --git a/src/assets/badges/zksync@3x.png b/src/assets/badges/zksync@3x.png
new file mode 100644
index 00000000000..2e433247e12
Binary files /dev/null and b/src/assets/badges/zksync@3x.png differ
diff --git a/src/assets/badges/zksyncBadgeDark.png b/src/assets/badges/zksyncBadgeDark.png
new file mode 100644
index 00000000000..b6947dbcd88
Binary files /dev/null and b/src/assets/badges/zksyncBadgeDark.png differ
diff --git a/src/assets/badges/zksyncBadgeDark@2x.png b/src/assets/badges/zksyncBadgeDark@2x.png
new file mode 100644
index 00000000000..77f075eb22e
Binary files /dev/null and b/src/assets/badges/zksyncBadgeDark@2x.png differ
diff --git a/src/assets/badges/zksyncBadgeDark@3x.png b/src/assets/badges/zksyncBadgeDark@3x.png
new file mode 100644
index 00000000000..a7ea118d5ce
Binary files /dev/null and b/src/assets/badges/zksyncBadgeDark@3x.png differ
diff --git a/src/assets/badges/zksyncBadgeLarge.png b/src/assets/badges/zksyncBadgeLarge.png
new file mode 100644
index 00000000000..02190afd30f
Binary files /dev/null and b/src/assets/badges/zksyncBadgeLarge.png differ
diff --git a/src/assets/badges/zksyncBadgeLarge@2x.png b/src/assets/badges/zksyncBadgeLarge@2x.png
new file mode 100644
index 00000000000..420b0f8531e
Binary files /dev/null and b/src/assets/badges/zksyncBadgeLarge@2x.png differ
diff --git a/src/assets/badges/zksyncBadgeLarge@3x.png b/src/assets/badges/zksyncBadgeLarge@3x.png
new file mode 100644
index 00000000000..25aef546ecf
Binary files /dev/null and b/src/assets/badges/zksyncBadgeLarge@3x.png differ
diff --git a/src/assets/badges/zksyncBadgeLargeDark.png b/src/assets/badges/zksyncBadgeLargeDark.png
new file mode 100644
index 00000000000..19a58296e9a
Binary files /dev/null and b/src/assets/badges/zksyncBadgeLargeDark.png differ
diff --git a/src/assets/badges/zksyncBadgeLargeDark@2x.png b/src/assets/badges/zksyncBadgeLargeDark@2x.png
new file mode 100644
index 00000000000..ea8d6389607
Binary files /dev/null and b/src/assets/badges/zksyncBadgeLargeDark@2x.png differ
diff --git a/src/assets/badges/zksyncBadgeLargeDark@3x.png b/src/assets/badges/zksyncBadgeLargeDark@3x.png
new file mode 100644
index 00000000000..71a30293a2e
Binary files /dev/null and b/src/assets/badges/zksyncBadgeLargeDark@3x.png differ
diff --git a/src/assets/badges/zksyncBadgeNoShadow.png b/src/assets/badges/zksyncBadgeNoShadow.png
new file mode 100644
index 00000000000..47b5500b5d0
Binary files /dev/null and b/src/assets/badges/zksyncBadgeNoShadow.png differ
diff --git a/src/assets/badges/zksyncBadgeNoShadow@2x.png b/src/assets/badges/zksyncBadgeNoShadow@2x.png
new file mode 100644
index 00000000000..97f1bc72954
Binary files /dev/null and b/src/assets/badges/zksyncBadgeNoShadow@2x.png differ
diff --git a/src/assets/badges/zksyncBadgeNoShadow@3x.png b/src/assets/badges/zksyncBadgeNoShadow@3x.png
new file mode 100644
index 00000000000..ed087b5f411
Binary files /dev/null and b/src/assets/badges/zksyncBadgeNoShadow@3x.png differ
diff --git a/src/components/AbsolutePortal.tsx b/src/components/AbsolutePortal.tsx
index b578234bd0a..a992e84c11c 100644
--- a/src/components/AbsolutePortal.tsx
+++ b/src/components/AbsolutePortal.tsx
@@ -1,5 +1,5 @@
import React, { PropsWithChildren, ReactNode, useEffect, useState } from 'react';
-import { View } from 'react-native';
+import { StyleProp, ViewStyle, View } from 'react-native';
const absolutePortal = {
nodes: [] as ReactNode[],
@@ -24,7 +24,7 @@ const absolutePortal = {
},
};
-export const AbsolutePortalRoot = () => {
+export const AbsolutePortalRoot = ({ style }: { style?: StyleProp }) => {
const [nodes, setNodes] = useState(absolutePortal.nodes);
useEffect(() => {
@@ -32,17 +32,15 @@ export const AbsolutePortalRoot = () => {
return () => unsubscribe();
}, []);
- return (
-
- {nodes}
-
- );
+ return {nodes};
};
export const AbsolutePortal = ({ children }: PropsWithChildren) => {
useEffect(() => {
absolutePortal.addNode(children);
- return () => absolutePortal.removeNode(children);
+ return () => {
+ absolutePortal.removeNode(children);
+ };
}, [children]);
return null;
diff --git a/src/components/ContactRowInfoButton.js b/src/components/ContactRowInfoButton.js
index 28ce8a1f159..fd93f487012 100644
--- a/src/components/ContactRowInfoButton.js
+++ b/src/components/ContactRowInfoButton.js
@@ -2,7 +2,6 @@ import lang from 'i18n-js';
import { startCase } from 'lodash';
import React from 'react';
import { View } from 'react-native';
-import { IS_TESTING } from 'react-native-dotenv';
import { ContextMenuButton } from 'react-native-ios-context-menu';
import RadialGradient from 'react-native-radial-gradient';
import { ButtonPressAnimation } from './animations';
@@ -12,7 +11,7 @@ import { Text } from './text';
import { useClipboard } from '@/hooks';
import styled from '@/styled-thing';
import { fonts, fontWithWidth, padding } from '@/styles';
-
+import { IS_TEST } from '@/env';
import { abbreviations, ethereumUtils, haptics, showActionSheetWithOptions } from '@/utils';
const InfoButton = styled(Centered)({
@@ -27,7 +26,7 @@ const InfoButton = styled(Centered)({
...padding.object(0, 0),
});
-const Circle = styled(IS_TESTING === 'true' ? View : RadialGradient).attrs(({ theme: { colors } }) => ({
+const Circle = styled(IS_TEST ? View : RadialGradient).attrs(({ theme: { colors } }) => ({
center: [0, 15],
colors: colors.gradients.lightestGrey,
}))({
diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx
index 99c24dd0597..94977e35f14 100644
--- a/src/components/DappBrowser/control-panel/ControlPanel.tsx
+++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx
@@ -311,7 +311,7 @@ export const ControlPanel = () => {
);
};
-const TapToDismiss = memo(function TapToDismiss() {
+export const TapToDismiss = memo(function TapToDismiss() {
const { goBack } = useNavigation();
return (
diff --git a/src/screens/discover/components/DiscoverFeaturedResultsCard.tsx b/src/components/Discover/DiscoverFeaturedResultsCard.tsx
similarity index 100%
rename from src/screens/discover/components/DiscoverFeaturedResultsCard.tsx
rename to src/components/Discover/DiscoverFeaturedResultsCard.tsx
diff --git a/src/screens/discover/components/DiscoverHome.tsx b/src/components/Discover/DiscoverHome.tsx
similarity index 86%
rename from src/screens/discover/components/DiscoverHome.tsx
rename to src/components/Discover/DiscoverHome.tsx
index 7132c19c016..cb9fd14fed0 100644
--- a/src/screens/discover/components/DiscoverHome.tsx
+++ b/src/components/Discover/DiscoverHome.tsx
@@ -6,9 +6,10 @@ import useExperimentalFlag, {
MINTS,
NFT_OFFERS,
FEATURED_RESULTS,
+ TRENDING_TOKENS,
} from '@rainbow-me/config/experimentalHooks';
import { isTestnetChain } from '@/handlers/web3';
-import { Inline, Inset, Stack, Box } from '@/design-system';
+import { Inline, Inset, Stack, Box, Separator } from '@/design-system';
import { useAccountSettings, useWallets } from '@/hooks';
import { ENSCreateProfileCard } from '@/components/cards/ENSCreateProfileCard';
import { ENSSearchCard } from '@/components/cards/ENSSearchCard';
@@ -28,11 +29,12 @@ import { FeaturedResultStack } from '@/components/FeaturedResult/FeaturedResultS
import Routes from '@/navigation/routesNames';
import { useNavigation } from '@/navigation';
import { DiscoverFeaturedResultsCard } from './DiscoverFeaturedResultsCard';
+import { TrendingTokens } from '@/components/Discover/TrendingTokens';
export const HORIZONTAL_PADDING = 20;
export default function DiscoverHome() {
- const { profiles_enabled, mints_enabled, op_rewards_enabled, featured_results } = useRemoteConfig();
+ const { profiles_enabled, mints_enabled, op_rewards_enabled, featured_results, trending_tokens_enabled } = useRemoteConfig();
const { chainId } = useAccountSettings();
const profilesEnabledLocalFlag = useExperimentalFlag(PROFILES);
const profilesEnabledRemoteFlag = profiles_enabled;
@@ -42,6 +44,7 @@ export default function DiscoverHome() {
const mintsEnabled = (useExperimentalFlag(MINTS) || mints_enabled) && !IS_TEST;
const opRewardsLocalFlag = useExperimentalFlag(OP_REWARDS);
const opRewardsRemoteFlag = op_rewards_enabled;
+ const trendingTokensEnabled = (useExperimentalFlag(TRENDING_TOKENS) || trending_tokens_enabled) && !IS_TEST;
const testNetwork = isTestnetChain({ chainId });
const { navigate } = useNavigation();
const isProfilesEnabled = profilesEnabledLocalFlag && profilesEnabledRemoteFlag;
@@ -67,6 +70,13 @@ export default function DiscoverHome() {
{isProfilesEnabled && }
+
+ {trendingTokensEnabled && (
+ <>
+
+
+ >
+ )}
{mintsEnabled && (
@@ -76,7 +86,7 @@ export default function DiscoverHome() {
)}
- {/* FIXME: IS_TESTING disables nftOffers this makes some DETOX tests hang forever at exit - investigate */}
+ {/* FIXME: IS_TEST disables nftOffers this makes some DETOX tests hang forever at exit - investigate */}
{!IS_TEST && nftOffersEnabled && }
{/* We have both flags here to be able to override the remote flag and show the card anyway in Dev*/}
{featuredResultsEnabled && (
diff --git a/src/screens/discover/components/DiscoverScreenContent.tsx b/src/components/Discover/DiscoverScreenContent.tsx
similarity index 76%
rename from src/screens/discover/components/DiscoverScreenContent.tsx
rename to src/components/Discover/DiscoverScreenContent.tsx
index 1e3a7650013..99271271ded 100644
--- a/src/screens/discover/components/DiscoverScreenContent.tsx
+++ b/src/components/Discover/DiscoverScreenContent.tsx
@@ -1,12 +1,12 @@
import React from 'react';
import { View } from 'react-native';
import { FlexItem, Page } from '@/components/layout';
-import DiscoverHome from './DiscoverHome';
-import DiscoverSearch from './DiscoverSearch';
-import DiscoverSearchContainer from './DiscoverSearchContainer';
+import DiscoverHome from '@/components/Discover/DiscoverHome';
+import DiscoverSearch from '@/components/Discover/DiscoverSearch';
+import DiscoverSearchContainer from '@/components/Discover/DiscoverSearchContainer';
import { Box, Inset } from '@/design-system';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
-import { useDiscoverScreenContext } from '../DiscoverScreenContext';
+import { useDiscoverScreenContext } from '@/components/Discover/DiscoverScreenContext';
function Switcher({ children }: { children: React.ReactNode[] }) {
const { isSearching } = useDiscoverScreenContext();
diff --git a/src/screens/discover/DiscoverScreenContext.tsx b/src/components/Discover/DiscoverScreenContext.tsx
similarity index 96%
rename from src/screens/discover/DiscoverScreenContext.tsx
rename to src/components/Discover/DiscoverScreenContext.tsx
index eb9b276443d..31a8e89106b 100644
--- a/src/screens/discover/DiscoverScreenContext.tsx
+++ b/src/components/Discover/DiscoverScreenContext.tsx
@@ -2,6 +2,7 @@ import { analytics } from '@/analytics';
import React, { createContext, Dispatch, SetStateAction, RefObject, useState, useRef, useCallback } from 'react';
import { SectionList, TextInput } from 'react-native';
import Animated from 'react-native-reanimated';
+import { useTrackDiscoverScreenTime } from './useTrackDiscoverScreenTime';
type DiscoverScreenContextType = {
scrollViewRef: RefObject;
@@ -80,6 +81,8 @@ const DiscoverScreenProvider = ({ children }: { children: React.ReactNode }) =>
setIsSearching(false);
}, [searchQuery]);
+ useTrackDiscoverScreenTime();
+
return (
{
+ const assets = currencyList
+ .filter(a => a.key !== 'profiles')
+ .map(asset => asset.data)
+ .flat();
+ if (assets.length === 0) return;
+ const params = {
+ screen: 'discover' as const,
+ no_icon: 0,
+ no_price: 0,
+ total_tokens: assets.length,
+ query: searchQueryForSearch,
+ };
+ for (const asset of assets) {
+ if (!asset.icon_url) params.no_icon += 1;
+ if (!isNaN(asset.price?.value)) params.no_price += 1;
+ }
+ analyticsV2.track(analyticsV2.event.tokenList, params);
+ },
+ { timeout: 3000, enabled: !isLoading }
+ );
+
return (
diff --git a/src/screens/discover/components/DiscoverSearchContainer.tsx b/src/components/Discover/DiscoverSearchContainer.tsx
similarity index 92%
rename from src/screens/discover/components/DiscoverSearchContainer.tsx
rename to src/components/Discover/DiscoverSearchContainer.tsx
index fa7fabff669..2e5a425b631 100644
--- a/src/screens/discover/components/DiscoverSearchContainer.tsx
+++ b/src/components/Discover/DiscoverSearchContainer.tsx
@@ -3,8 +3,8 @@ import React, { useEffect } from 'react';
import { ButtonPressAnimation } from '@/components/animations';
import { Column, Row } from '@/components/layout';
import { Text } from '@/components/text';
-import DiscoverSearchInput from '@/screens/discover/components/DiscoverSearchInput';
-import { useDiscoverScreenContext } from '../DiscoverScreenContext';
+import DiscoverSearchInput from '@/components/Discover/DiscoverSearchInput';
+import { useDiscoverScreenContext } from '@/components/Discover/DiscoverScreenContext';
import { deviceUtils } from '@/utils';
import { useDelayedValueWithLayoutAnimation } from '@/hooks';
import styled from '@/styled-thing';
diff --git a/src/screens/discover/components/DiscoverSearchInput.tsx b/src/components/Discover/DiscoverSearchInput.tsx
similarity index 98%
rename from src/screens/discover/components/DiscoverSearchInput.tsx
rename to src/components/Discover/DiscoverSearchInput.tsx
index 2d21419dbd5..f8dc62ae696 100644
--- a/src/screens/discover/components/DiscoverSearchInput.tsx
+++ b/src/components/Discover/DiscoverSearchInput.tsx
@@ -13,7 +13,7 @@ import styled from '@/styled-thing';
import { margin, padding } from '@/styles';
import { deviceUtils } from '@/utils';
import { ThemeContextProps } from '@/theme';
-import { useDiscoverScreenContext } from '@/screens/discover/DiscoverScreenContext';
+import { useDiscoverScreenContext } from '@/components/Discover/DiscoverScreenContext';
import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
import { ChainId } from '@/state/backendNetworks/types';
diff --git a/src/components/Discover/TrendingTokens.tsx b/src/components/Discover/TrendingTokens.tsx
new file mode 100644
index 00000000000..223a8041981
--- /dev/null
+++ b/src/components/Discover/TrendingTokens.tsx
@@ -0,0 +1,714 @@
+import { DropdownMenu } from '@/components/DropdownMenu';
+import { globalColors, Text, TextIcon, useBackgroundColor, useColorMode } from '@/design-system';
+import { useForegroundColor } from '@/design-system/color/useForegroundColor';
+
+import { SwapCoinIcon } from '@/__swaps__/screens/Swap/components/SwapCoinIcon';
+import { analyticsV2 } from '@/analytics';
+import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
+import { ChainId } from '@/state/backendNetworks/types';
+import { ChainImage } from '@/components/coin-icon/ChainImage';
+import Skeleton, { FakeAvatar, FakeText } from '@/components/skeleton/Skeleton';
+import { SortDirection, Timeframe, TrendingCategory, TrendingSort } from '@/graphql/__generated__/arc';
+import { formatCurrency, formatNumber } from '@/helpers/strings';
+import * as i18n from '@/languages';
+import { Navigation } from '@/navigation';
+import Routes from '@/navigation/routesNames';
+import { FarcasterUser, TrendingToken, useTrendingTokens } from '@/resources/trendingTokens/trendingTokens';
+import { useNavigationStore } from '@/state/navigation/navigationStore';
+import { swapsStore } from '@/state/swaps/swapsStore';
+import { sortFilters, timeFilters, useTrendingTokensStore } from '@/state/trendingTokens/trendingTokens';
+import { colors } from '@/styles';
+import { darkModeThemeColors } from '@/styles/colors';
+import { useCallback, useEffect, useMemo } from 'react';
+import React, { Dimensions, FlatList, View } from 'react-native';
+import LinearGradient from 'react-native-linear-gradient';
+import Animated, { runOnJS, useSharedValue } from 'react-native-reanimated';
+import { ButtonPressAnimation } from '../animations';
+import { useFarcasterAccountForWallets } from '@/hooks/useFarcasterAccountForWallets';
+import { ImgixImage } from '../images';
+import { useRemoteConfig } from '@/model/remoteConfig';
+import { useAccountSettings } from '@/hooks';
+import { getColorWorklet, getMixedColor, opacity } from '@/__swaps__/utils/swaps';
+import { THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants';
+import { IS_IOS } from '@/env';
+
+const t = i18n.l.trending_tokens;
+
+function FilterButton({
+ icon,
+ label,
+ onPress,
+ selected,
+ iconColor,
+ highlightedBackgroundColor,
+}: {
+ onPress?: VoidFunction;
+ label: string;
+ icon: string | JSX.Element;
+ selected: boolean;
+ iconColor?: string;
+ highlightedBackgroundColor?: string;
+}) {
+ const { isDarkMode } = useColorMode();
+ const fillTertiary = useBackgroundColor('fillTertiary');
+ const separatorSecondary = useForegroundColor('separatorSecondary');
+ const borderColor = selected && isDarkMode ? globalColors.white80 : separatorSecondary;
+ const defaultIconColor = getColorWorklet('labelSecondary', selected ? false : isDarkMode);
+
+ const gradientColors = useMemo(() => {
+ if (!selected) return [fillTertiary, fillTertiary];
+ return highlightedBackgroundColor
+ ? [highlightedBackgroundColor, globalColors.white100]
+ : [
+ isDarkMode ? opacity(globalColors.white100, 0.72) : opacity(fillTertiary, 0.2),
+ isDarkMode ? opacity(globalColors.white100, 0.92) : opacity(fillTertiary, 0),
+ ];
+ }, [fillTertiary, highlightedBackgroundColor, selected, isDarkMode]);
+
+ return (
+
+
+ {typeof icon === 'string' ? (
+
+ {icon}
+
+ ) : (
+ icon
+ )}
+
+ {/* This first Text element sets the width of the container */}
+
+ {label}
+
+ {/* This second Text element is the visible label, positioned absolutely within the established frame */}
+
+ {label}
+
+
+
+
+
+
+
+ );
+}
+
+function useTrendingTokensData() {
+ const { nativeCurrency } = useAccountSettings();
+ const remoteConfig = useRemoteConfig();
+ const { chainId, category, timeframe, sort } = useTrendingTokensStore(state => ({
+ chainId: state.chainId,
+ category: state.category,
+ timeframe: state.timeframe,
+ sort: state.sort,
+ }));
+
+ const walletAddress = useFarcasterAccountForWallets();
+
+ return useTrendingTokens({
+ chainId,
+ category,
+ timeframe,
+ sortBy: sort,
+ sortDirection: SortDirection.Desc,
+ limit: remoteConfig.trending_tokens_limit,
+ walletAddress: walletAddress,
+ currency: nativeCurrency,
+ });
+}
+
+function ReportAnalytics() {
+ const activeSwipeRoute = useNavigationStore(state => state.activeSwipeRoute);
+ const { category, chainId } = useTrendingTokensStore(state => ({ category: state.category, chainId: state.chainId }));
+ const { data: trendingTokens, isLoading } = useTrendingTokensData();
+
+ useEffect(() => {
+ if (isLoading || activeSwipeRoute !== Routes.DISCOVER_SCREEN) return;
+
+ const isEmpty = (trendingTokens?.length ?? 0) === 0;
+ const isLimited = !isEmpty && (trendingTokens?.length ?? 0) < 6;
+
+ analyticsV2.track(analyticsV2.event.viewRankedCategory, {
+ category,
+ chainId,
+ isLimited,
+ isEmpty,
+ });
+ }, [isLoading, activeSwipeRoute, trendingTokens?.length, category, chainId]);
+
+ return null;
+}
+
+function CategoryFilterButton({
+ category,
+ icon,
+ iconWidth = 16,
+ iconColor,
+ label,
+ highlightedBackgroundColor,
+}: {
+ category: TrendingCategory;
+ icon: string;
+ iconColor: string | { default: string; selected: string };
+ highlightedBackgroundColor: string;
+ iconWidth?: number;
+ label: string;
+}) {
+ const { isDarkMode } = useColorMode();
+ const fillTertiary = useBackgroundColor('fillTertiary');
+ const separatorSecondary = useForegroundColor('separatorSecondary');
+
+ const selected = useTrendingTokensStore(state => state.category === category);
+
+ const borderColor = selected && isDarkMode ? globalColors.white80 : separatorSecondary;
+
+ const gradientColors = useMemo(() => {
+ if (!selected) return [fillTertiary, fillTertiary];
+ return [highlightedBackgroundColor, globalColors.white100];
+ }, [fillTertiary, highlightedBackgroundColor, selected]);
+
+ const selectCategory = useCallback(() => {
+ useTrendingTokensStore.getState().setCategory(category);
+ }, [category]);
+
+ return (
+
+
+
+ {icon}
+
+
+ {/* This first Text element sets the width of the container */}
+
+ {label}
+
+ {/* This second Text element is the visible label, positioned absolutely within the established frame */}
+
+ {label}
+
+
+
+
+ );
+}
+
+function FriendPfp({ pfp_url }: { pfp_url: string }) {
+ const backgroundColor = useBackgroundColor('surfacePrimary');
+ return (
+
+ );
+}
+function FriendHolders({ friends }: { friends: FarcasterUser[] }) {
+ if (friends.length === 0) return null;
+ const howManyOthers = Math.max(1, friends.length - 2);
+ const separator = howManyOthers === 1 && friends.length === 2 ? ` ${i18n.t(t.and)} ` : ', ';
+
+ return (
+
+
+
+ {friends[1] && }
+
+
+
+
+ {friends[0].username}
+ {friends[1] && (
+ <>
+
+ {separator}
+
+ {friends[1].username}
+ >
+ )}
+
+ {friends.length > 2 && (
+
+ {' '}
+ {i18n.t(t.and_others[howManyOthers === 1 ? 'one' : 'other'], { count: howManyOthers })}
+
+ )}
+
+
+ );
+}
+
+function TrendingTokenLoadingRow() {
+ const backgroundColor = useBackgroundColor('surfacePrimary');
+ const { isDarkMode } = useColorMode();
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function getPriceChangeColor(priceChange: number) {
+ if (priceChange === 0) return 'labelTertiary';
+ return priceChange > 0 ? 'green' : 'red';
+}
+
+function TrendingTokenRow({ token }: { token: TrendingToken }) {
+ const separatorColor = useForegroundColor('separator');
+
+ const price = formatCurrency(token.price);
+ const marketCap = formatNumber(token.marketCap, { useOrderSuffix: true, decimals: 1, style: '$' });
+ const volume = formatNumber(token.volume, { useOrderSuffix: true, decimals: 1, style: '$' });
+
+ const handleNavigateToToken = useCallback(() => {
+ analyticsV2.track(analyticsV2.event.viewTrendingToken, {
+ address: token.address,
+ chainId: token.chainId,
+ symbol: token.symbol,
+ name: token.name,
+ highlightedFriends: token.highlightedFriends.length,
+ });
+
+ swapsStore.setState({
+ lastNavigatedTrendingToken: token.uniqueId,
+ });
+
+ Navigation.handleAction(Routes.EXPANDED_ASSET_SHEET, {
+ asset: token,
+ type: 'token',
+ });
+ }, [token]);
+
+ if (!token) return null;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {token.name}
+
+
+ {token.symbol}
+
+
+ {price}
+
+
+
+
+
+
+ VOL
+
+
+ {volume}
+
+
+
+
+ |
+
+
+
+
+ MCAP
+
+
+ {marketCap}
+
+
+
+
+
+
+
+
+ {formatNumber(token.priceChange.day, { decimals: 2, useOrderSuffix: true })}%
+
+
+
+
+ 1H
+
+
+ {formatNumber(token.priceChange.hr, { decimals: 2, useOrderSuffix: true })}%
+
+
+
+
+
+
+
+ );
+}
+
+function NoResults() {
+ const { isDarkMode } = useColorMode();
+ const fillQuaternary = useBackgroundColor('fillQuaternary');
+ const backgroundColor = isDarkMode ? '#191A1C' : fillQuaternary;
+
+ return (
+
+
+
+ {i18n.t(t.no_results.title)}
+
+
+ {i18n.t(t.no_results.body)}
+
+
+
+
+
+
+
+
+ );
+}
+
+function NetworkFilter() {
+ const { isDarkMode } = useColorMode();
+
+ const selected = useSharedValue(undefined);
+ const chainId = useTrendingTokensStore(state => state.chainId);
+ const setChainId = useTrendingTokensStore(state => state.setChainId);
+
+ const { icon, label, lightenedNetworkColor } = useMemo(() => {
+ if (!chainId) return { icon: '', label: i18n.t(t.all), lightenedNetworkColor: undefined };
+ const lightenedNetworkColor = useBackendNetworksStore.getState().getColorsForChainId(chainId, isDarkMode);
+
+ return {
+ icon: (
+
+
+
+ ),
+ label: useBackendNetworksStore.getState().getChainsLabel()[chainId],
+ lightenedNetworkColor: lightenedNetworkColor
+ ? getMixedColor(lightenedNetworkColor, globalColors.white100, isDarkMode ? 0.55 : 0.6)
+ : undefined,
+ };
+ }, [chainId, isDarkMode]);
+
+ const setSelected = useCallback(
+ (chainId: ChainId | undefined) => {
+ 'worklet';
+ selected.value = chainId;
+ runOnJS(setChainId)(chainId);
+ },
+ [selected, setChainId]
+ );
+
+ const navigateToNetworkSelector = useCallback(() => {
+ Navigation.handleAction(Routes.NETWORK_SELECTOR, {
+ selected,
+ setSelected,
+ });
+ }, [selected, setSelected]);
+
+ return (
+
+ );
+}
+
+function TimeFilter() {
+ const timeframe = useTrendingTokensStore(state => state.timeframe);
+ const shouldAbbreviate = timeframe === Timeframe.H24 || timeframe === Timeframe.H12;
+
+ return (
+ ({
+ actionTitle: i18n.t(t.filters.time[time]),
+ menuState: time === timeframe ? 'on' : 'off',
+ actionKey: time,
+ })),
+ }}
+ side="bottom"
+ onPressMenuItem={timeframe => useTrendingTokensStore.getState().setTimeframe(timeframe)}
+ >
+
+
+ );
+}
+
+function SortFilter() {
+ const sort = useTrendingTokensStore(state => state.sort);
+ const selected = sort !== TrendingSort.Recommended;
+
+ const iconColor = useForegroundColor(selected ? 'labelSecondary' : 'labelTertiary');
+
+ const sortLabel = useMemo(() => {
+ if (sort === TrendingSort.Recommended) return i18n.t(t.filters.sort.RECOMMENDED.label);
+ return i18n.t(t.filters.sort[sort]);
+ }, [sort]);
+
+ return (
+ ({
+ actionTitle: s === TrendingSort.Recommended ? i18n.t(t.filters.sort.RECOMMENDED.menuOption) : i18n.t(t.filters.sort[s]),
+ menuState: s === sort ? 'on' : 'off',
+ actionKey: s,
+ })),
+ }}
+ side="bottom"
+ onPressMenuItem={selection => {
+ if (selection === sort) return useTrendingTokensStore.getState().setSort(TrendingSort.Recommended);
+ useTrendingTokensStore.getState().setSort(selection);
+ }}
+ >
+
+
+
+ }
+ />
+
+ );
+}
+
+function TrendingTokensLoader() {
+ const { trending_tokens_limit } = useRemoteConfig();
+
+ return (
+
+ {Array.from({ length: trending_tokens_limit }).map((_, index) => (
+
+ ))}
+
+ );
+}
+
+function TrendingTokenData() {
+ const { data: trendingTokens, isLoading } = useTrendingTokensData();
+ if (isLoading) return ;
+
+ return (
+ }
+ data={trendingTokens}
+ renderItem={({ item }) => }
+ />
+ );
+}
+
+const padding = 20;
+
+export function TrendingTokens() {
+ const { isDarkMode } = useColorMode();
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/Discover/useTrackDiscoverScreenTime.ts b/src/components/Discover/useTrackDiscoverScreenTime.ts
new file mode 100644
index 00000000000..b6f9e02f6cf
--- /dev/null
+++ b/src/components/Discover/useTrackDiscoverScreenTime.ts
@@ -0,0 +1,21 @@
+import { useNavigationStore } from '@/state/navigation/navigationStore';
+import { useEffect } from 'react';
+import Routes from '@/navigation/routesNames';
+import { PerformanceTracking, currentlyTrackedMetrics } from '@/performance/tracking';
+import { PerformanceMetrics } from '@/performance/tracking/types/PerformanceMetrics';
+
+export const useTrackDiscoverScreenTime = () => {
+ const isOnDiscoverScreen = useNavigationStore(state => state.isRouteActive(Routes.DISCOVER_SCREEN));
+
+ useEffect(() => {
+ const data = currentlyTrackedMetrics.get(PerformanceMetrics.timeSpentOnDiscoverScreen);
+
+ if (!isOnDiscoverScreen && data?.startTimestamp) {
+ PerformanceTracking.finishMeasuring(PerformanceMetrics.timeSpentOnDiscoverScreen);
+ }
+
+ if (isOnDiscoverScreen) {
+ PerformanceTracking.startMeasuring(PerformanceMetrics.timeSpentOnDiscoverScreen);
+ }
+ }, [isOnDiscoverScreen]);
+};
diff --git a/src/components/ExchangeTokenRow.tsx b/src/components/ExchangeTokenRow.tsx
index 0e509debbe4..9fabd7219f5 100644
--- a/src/components/ExchangeTokenRow.tsx
+++ b/src/components/ExchangeTokenRow.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import isEqual from 'react-fast-compare';
import { Box, Column, Columns, Inline, Stack, Text } from '@/design-system';
import { isNativeAsset } from '@/handlers/assets';
-import { useAsset, useDimensions } from '@/hooks';
+import { useAsset } from '@/hooks';
import { ButtonPressAnimation } from '@/components/animations';
import { FloatingEmojis } from '@/components/floating-emojis';
import { IS_IOS } from '@/env';
@@ -34,7 +34,6 @@ export default React.memo(function ExchangeTokenRow({
disabled,
},
}: ExchangeTokenRowProps) {
- const { width: deviceWidth } = useDimensions();
const item = useAsset({
address,
chainId,
@@ -101,10 +100,8 @@ export default React.memo(function ExchangeTokenRow({
{isInfoButtonVisible && }
{showFavoriteButton &&
(IS_IOS ? (
- // @ts-ignore
{
// there's an e2e modifying this panel so I needed values that aren't dependent on the network conditions
- const maxBaseFeeToValidate = IS_TESTING === 'true' ? 100 : currentBaseFee;
+ const maxBaseFeeToValidate = IS_TEST ? 100 : currentBaseFee;
if (!maxBaseFee || isZero(maxBaseFee) || greaterThan(multiply(0.1, maxBaseFeeToValidate), maxBaseFee)) {
setMaxBaseFeeError({
@@ -404,10 +403,7 @@ export default function FeesPanel({ currentGasTrend, colorForAsset, setCanGoBack
}
// there's an e2e modifying this panel so I needed values that aren't dependant on the network conditions
if (
- greaterThan(
- multiply(MINER_TIP_RANGE[0], IS_TESTING === 'true' ? 1 : gasFeeParamsBySpeed?.[NORMAL]?.maxPriorityFeePerGas?.gwei),
- maxPriorityFee
- )
+ greaterThan(multiply(MINER_TIP_RANGE[0], IS_TEST ? 1 : gasFeeParamsBySpeed?.[NORMAL]?.maxPriorityFeePerGas?.gwei), maxPriorityFee)
) {
setMaxPriorityFeeWarning({
message: lang.t('gas.lower_than_suggested'),
@@ -415,10 +411,7 @@ export default function FeesPanel({ currentGasTrend, colorForAsset, setCanGoBack
});
} else if (
// there's an e2e modifying this panel so I needed values that aren't dependant on the network conditions
- greaterThan(
- maxPriorityFee,
- multiply(MINER_TIP_RANGE[1], IS_TESTING === 'true' ? 1 : gasFeeParamsBySpeed?.[URGENT]?.maxPriorityFeePerGas?.gwei)
- )
+ greaterThan(maxPriorityFee, multiply(MINER_TIP_RANGE[1], IS_TEST ? 1 : gasFeeParamsBySpeed?.[URGENT]?.maxPriorityFeePerGas?.gwei))
) {
setMaxPriorityFeeWarning({
message: lang.t('gas.higher_than_suggested'),
diff --git a/src/components/GweiInputPill.tsx b/src/components/GweiInputPill.tsx
index a1f9657de32..2c61642f224 100644
--- a/src/components/GweiInputPill.tsx
+++ b/src/components/GweiInputPill.tsx
@@ -1,5 +1,4 @@
import React, { useCallback } from 'react';
-import { IS_TESTING } from 'react-native-dotenv';
import LinearGradient from 'react-native-linear-gradient';
// @ts-expect-error - no declaration file
import TextInputMask from 'react-native-text-input-mask';
@@ -9,7 +8,7 @@ import { buildTextStyles, margin, padding } from '@/styles';
import { useTheme } from '@/theme';
import { TextInput } from 'react-native';
import { Box, Inline, Inset, Text } from '@/design-system';
-import { IS_ANDROID } from '@/env';
+import { IS_ANDROID, IS_TEST } from '@/env';
const ANDROID_EXTRA_LINE_HEIGHT = 6;
@@ -98,7 +97,7 @@ function GweiInputPill(
testID={testID}
value={value}
/>
- {IS_TESTING !== 'true' && (
+ {!IS_TEST && (
Gwei
diff --git a/src/components/NetworkSwitcher.tsx b/src/components/NetworkSwitcher.tsx
new file mode 100644
index 00000000000..2b385abb1fe
--- /dev/null
+++ b/src/components/NetworkSwitcher.tsx
@@ -0,0 +1,872 @@
+/* eslint-disable @typescript-eslint/no-non-null-assertion */
+import { opacity } from '@/__swaps__/utils/swaps';
+import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
+import { ChainId } from '@/state/backendNetworks/types';
+import { AnimatedBlurView } from '@/components/AnimatedComponents/AnimatedBlurView';
+import { ButtonPressAnimation } from '@/components/animations';
+import { SPRING_CONFIGS, TIMING_CONFIGS } from '@/components/animations/animationConfigs';
+import { ChainImage } from '@/components/coin-icon/ChainImage';
+import { AnimatedText, Box, DesignSystemProvider, globalColors, Separator, Text, useBackgroundColor, useColorMode } from '@/design-system';
+import { useForegroundColor } from '@/design-system/color/useForegroundColor';
+import * as i18n from '@/languages';
+import deviceUtils, { DEVICE_WIDTH } from '@/utils/deviceUtils';
+import MaskedView from '@react-native-masked-view/masked-view';
+import chroma from 'chroma-js';
+import { PropsWithChildren, useEffect } from 'react';
+import React, { Pressable, StyleSheet, View } from 'react-native';
+import { RouteProp, useRoute } from '@react-navigation/native';
+import { Gesture, GestureDetector } from 'react-native-gesture-handler';
+import LinearGradient from 'react-native-linear-gradient';
+import Animated, {
+ Easing,
+ FadeIn,
+ FadeOutUp,
+ LinearTransition,
+ runOnJS,
+ SharedValue,
+ useAnimatedReaction,
+ useAnimatedStyle,
+ useDerivedValue,
+ useSharedValue,
+ withDelay,
+ withSequence,
+ withSpring,
+ withTiming,
+} from 'react-native-reanimated';
+import Svg, { Path } from 'react-native-svg';
+import {
+ customizeNetworksBannerStore,
+ defaultPinnedNetworks,
+ dismissCustomizeNetworksBanner,
+ networkSwitcherStore,
+ shouldShowCustomizeNetworksBanner,
+} from '@/state/networkSwitcher/networkSwitcher';
+import { RootStackParamList } from '@/navigation/types';
+import { IS_IOS } from '@/env';
+import { safeAreaInsetValues } from '@/utils';
+import { noop } from 'lodash';
+import { TapToDismiss } from './DappBrowser/control-panel/ControlPanel';
+import { THICK_BORDER_WIDTH } from '@/__swaps__/screens/Swap/constants';
+import { GestureHandlerButton } from '@/__swaps__/screens/Swap/components/GestureHandlerButton';
+import { useTheme } from '@/theme';
+
+const t = i18n.l.network_switcher;
+
+const translations = {
+ edit: i18n.t(t.edit),
+ done: i18n.t(i18n.l.done),
+ networks: i18n.t(t.networks),
+ more: i18n.t(t.more),
+ show_more: i18n.t(t.show_more),
+ show_less: i18n.t(t.show_less),
+ drag_to_rearrange: i18n.t(t.drag_to_rearrange),
+};
+
+function EditButton({ editing }: { editing: SharedValue }) {
+ const blue = useForegroundColor('blue');
+ const borderColor = chroma(blue).alpha(0.08).hex();
+
+ const text = useDerivedValue(() => (editing.value ? translations.done : translations.edit));
+
+ return (
+ {
+ 'worklet';
+ editing.value = !editing.value;
+ }}
+ scaleTo={0.95}
+ style={{
+ borderColor,
+ borderCurve: 'continuous',
+ borderRadius: 14,
+ borderWidth: THICK_BORDER_WIDTH,
+ height: 28,
+ justifyContent: 'center',
+ overflow: 'hidden',
+ paddingHorizontal: 10,
+ position: 'absolute',
+ right: 0,
+ }}
+ >
+
+ {text}
+
+
+ );
+}
+
+function Header({ editing }: { editing: SharedValue }) {
+ const separatorTertiary = useForegroundColor('separatorTertiary');
+ const fill = useForegroundColor('fill');
+
+ const title = useDerivedValue(() => {
+ return editing.value ? translations.edit : translations.networks;
+ });
+
+ return (
+
+
+
+
+
+
+
+ {title}
+
+
+
+
+
+ );
+}
+
+const CustomizeNetworksBanner = !shouldShowCustomizeNetworksBanner(customizeNetworksBannerStore.getState().dismissedAt)
+ ? () => null
+ : function CustomizeNetworksBanner({ editing }: { editing: SharedValue }) {
+ useAnimatedReaction(
+ () => editing.value,
+ (editing, prev) => {
+ if (!prev && editing) runOnJS(dismissCustomizeNetworksBanner)();
+ }
+ );
+
+ const dismissedAt = customizeNetworksBannerStore(s => s.dismissedAt);
+ if (!shouldShowCustomizeNetworksBanner(dismissedAt)) return null;
+
+ const height = 75;
+ const blue = '#268FFF';
+
+ return (
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+ {i18n.t(t.customize_networks_banner.title)}
+
+
+ {i18n.t(t.customize_networks_banner.tap_the)}{' '}
+
+ {i18n.t(t.edit)}
+ {' '}
+ {i18n.t(t.customize_networks_banner.button_to_set_up)}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ };
+
+const BADGE_BORDER_COLORS = {
+ default: {
+ dark: globalColors.white10,
+ light: '#F2F3F4',
+ },
+ selected: {
+ dark: '#1E2E40',
+ light: '#D7E9FD',
+ },
+};
+
+const useNetworkOptionStyle = (isSelected: SharedValue, color?: string) => {
+ const { isDarkMode } = useColorMode();
+ const label = useForegroundColor('labelTertiary');
+
+ const surfacePrimary = useBackgroundColor('surfacePrimary');
+ const networkSwitcherBackgroundColor = isDarkMode ? '#191A1C' : surfacePrimary;
+ const separatorTertiary = useForegroundColor('separatorTertiary');
+
+ const defaultStyle = {
+ backgroundColor: isDarkMode ? globalColors.white10 : globalColors.grey20,
+ borderColor: isDarkMode ? opacity(separatorTertiary, 0.02) : separatorTertiary,
+ };
+ const selectedStyle = {
+ backgroundColor: chroma
+ .scale([networkSwitcherBackgroundColor, color || label])(0.16)
+ .hex(),
+ borderColor: chroma(color || label)
+ .alpha(0.16)
+ .hex(),
+ };
+
+ const scale = useSharedValue(1);
+ useAnimatedReaction(
+ () => isSelected.value,
+ (current, prev) => {
+ if (current === true && prev === false) {
+ scale.value = withSequence(
+ withTiming(0.9, { duration: 120, easing: Easing.bezier(0.25, 0.46, 0.45, 0.94) }),
+ withTiming(1, TIMING_CONFIGS.fadeConfig)
+ );
+ }
+ }
+ );
+
+ const animatedStyle = useAnimatedStyle(() => {
+ const colors = isSelected.value ? selectedStyle : defaultStyle;
+ return {
+ backgroundColor: colors.backgroundColor,
+ borderColor: colors.borderColor,
+ transform: [{ scale: scale.value }],
+ };
+ });
+
+ return {
+ animatedStyle,
+ selectedStyle,
+ defaultStyle,
+ };
+};
+
+function AllNetworksOption({
+ selected,
+ setSelected,
+}: {
+ selected: SharedValue;
+ setSelected: (chainId: ChainId | undefined) => void;
+}) {
+ const { isDarkMode } = useColorMode();
+ const blue = useForegroundColor('blue');
+
+ const isSelected = useDerivedValue(() => selected.value === undefined);
+ const { animatedStyle } = useNetworkOptionStyle(isSelected, blue);
+
+ const overlappingBadge = useAnimatedStyle(() => {
+ return {
+ borderColor: isSelected.value
+ ? BADGE_BORDER_COLORS.selected[isDarkMode ? 'dark' : 'light']
+ : BADGE_BORDER_COLORS.default[isDarkMode ? 'dark' : 'light'],
+ };
+ });
+
+ return (
+ {
+ 'worklet';
+ setSelected(undefined);
+ }}
+ scaleTo={0.95}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {i18n.t(t.all_networks)}
+
+
+
+ );
+}
+
+function AllNetworksSection({
+ editing,
+ setSelected,
+ selected,
+}: {
+ editing: SharedValue;
+ setSelected: (chainId: ChainId | undefined) => void;
+ selected: SharedValue;
+}) {
+ const style = useAnimatedStyle(() => ({
+ opacity: editing.value ? withTiming(0, TIMING_CONFIGS.fastFadeConfig) : withTiming(1, TIMING_CONFIGS.fastFadeConfig),
+ height: withTiming(
+ editing.value ? 0 : ITEM_HEIGHT + 14, // 14 is the gap to the separator
+ TIMING_CONFIGS.fastFadeConfig
+ ),
+ marginTop: editing.value ? 0 : 14,
+ pointerEvents: editing.value ? 'none' : 'auto',
+ }));
+ return (
+
+
+
+
+ );
+}
+
+function NetworkOption({ chainId, selected }: { chainId: ChainId; selected: SharedValue }) {
+ const { isDarkMode } = useColorMode();
+ const getColorsForChainId = useBackendNetworksStore(state => state.getColorsForChainId);
+ const chainName = useBackendNetworksStore.getState().getChainsLabel()[chainId];
+ const chainColor = getColorsForChainId(chainId, isDarkMode);
+ const isSelected = useDerivedValue(() => selected.value === chainId);
+ const { animatedStyle } = useNetworkOptionStyle(isSelected, chainColor);
+
+ return (
+
+
+
+ {chainName}
+
+
+ );
+}
+
+const SHEET_OUTER_INSET = 8;
+const SHEET_INNER_PADDING = 16;
+const GAP = 12;
+const ITEM_WIDTH = (DEVICE_WIDTH - SHEET_INNER_PADDING * 2 - SHEET_OUTER_INSET * 2 - GAP) / 2;
+const ITEM_HEIGHT = 48;
+const SEPARATOR_HEIGHT = 68;
+
+const ALL_NETWORKS_BADGE_SIZE = 16;
+const THICKER_BORDER_WIDTH = 5 / 3;
+
+const enum Section {
+ pinned,
+ separator,
+ unpinned,
+}
+
+function Draggable({
+ children,
+ dragging,
+ chainId,
+ networks,
+ sectionsOffsets,
+ isUnpinnedHidden,
+}: PropsWithChildren<{
+ chainId: ChainId;
+ dragging: SharedValue;
+ networks: SharedValue>;
+ sectionsOffsets: SharedValue>;
+ isUnpinnedHidden: SharedValue;
+}>) {
+ const zIndex = useSharedValue(0);
+ useAnimatedReaction(
+ () => dragging.value?.chainId,
+ (current, prev) => {
+ if (current === prev) return;
+ if (current === chainId) zIndex.value = 2;
+ if (prev === chainId) zIndex.value = 1;
+ }
+ );
+
+ const draggableStyles = useAnimatedStyle(() => {
+ const section = networks.value[Section.pinned].includes(chainId) ? Section.pinned : Section.unpinned;
+ const itemIndex = networks.value[section].indexOf(chainId);
+ const slotPosition = positionFromIndex(itemIndex, sectionsOffsets.value[section]);
+
+ const opacity =
+ section === Section.unpinned && isUnpinnedHidden.value
+ ? withTiming(0, TIMING_CONFIGS.fastFadeConfig)
+ : withDelay(100, withTiming(1, TIMING_CONFIGS.fadeConfig));
+
+ const isBeingDragged = dragging.value?.chainId === chainId;
+ const position = isBeingDragged ? dragging.value!.position : slotPosition;
+
+ return {
+ opacity,
+ zIndex: zIndex.value,
+ transform: [
+ { scale: withSpring(isBeingDragged ? 1.05 : 1, SPRING_CONFIGS.springConfig) },
+ { translateX: isBeingDragged ? position.x : withSpring(position.x, SPRING_CONFIGS.springConfig) },
+ { translateY: isBeingDragged ? position.y : withSpring(position.y, SPRING_CONFIGS.springConfig) },
+ ],
+ };
+ });
+
+ return {children};
+}
+
+const indexFromPosition = (x: number, y: number, offset: { y: number }) => {
+ 'worklet';
+ const yoffsets = y > offset.y ? offset.y : 0;
+ const column = x > ITEM_WIDTH + GAP / 2 ? 1 : 0;
+ const row = Math.floor((y - yoffsets) / (ITEM_HEIGHT + GAP));
+ const index = row * 2 + column;
+ return index < 0 ? 0 : index; // row can be negative if the dragged item is above the first row
+};
+
+const positionFromIndex = (index: number, offset: { y: number }) => {
+ 'worklet';
+ const column = index % 2;
+ const row = Math.floor(index / 2);
+ const position = { x: column * (ITEM_WIDTH + GAP), y: row * (ITEM_HEIGHT + GAP) + offset.y };
+ return position;
+};
+
+type Point = { x: number; y: number };
+type DraggingState = {
+ chainId: ChainId;
+ position: Point;
+};
+
+function SectionSeparator({
+ sectionsOffsets,
+ editing,
+ expanded,
+ networks,
+}: {
+ sectionsOffsets: SharedValue>;
+ editing: SharedValue;
+ expanded: SharedValue;
+ networks: SharedValue>;
+}) {
+ const pressed = useSharedValue(false);
+
+ const showExpandButtonAsNetworkChip = useDerivedValue(() => {
+ return !expanded.value && !editing.value && networks.value[Section.pinned].length % 2 !== 0;
+ });
+
+ const visible = useDerivedValue(() => {
+ return networks.value[Section.unpinned].length > 0 || editing.value;
+ });
+
+ const tapExpand = Gesture.Tap()
+ .onTouchesDown((e, s) => {
+ if (editing.value || !visible.value) return s.fail();
+ pressed.value = true;
+ })
+ .onEnd(() => {
+ pressed.value = false;
+ expanded.value = !expanded.value;
+ });
+
+ const text = useDerivedValue(() => {
+ if (editing.value) return translations.drag_to_rearrange;
+ if (showExpandButtonAsNetworkChip.value) return translations.more;
+ return expanded.value ? translations.show_less : translations.show_more;
+ });
+
+ const unpinnedNetworksLength = useDerivedValue(() => networks.value[Section.unpinned].length.toString());
+ const showMoreAmountStyle = useAnimatedStyle(() => ({
+ opacity: expanded.value || editing.value ? 0 : 1,
+ }));
+ const showMoreOrLessIcon = useDerivedValue(() => (expanded.value ? '' : '') as string);
+ const showMoreOrLessIconStyle = useAnimatedStyle(() => ({ opacity: editing.value ? 0 : 1 }));
+
+ const { isDarkMode } = useColorMode();
+
+ const separatorContainerStyles = useAnimatedStyle(() => {
+ if (showExpandButtonAsNetworkChip.value) {
+ const position = positionFromIndex(networks.value[Section.pinned].length, sectionsOffsets.value[Section.pinned]);
+ return {
+ backgroundColor: isDarkMode ? globalColors.white10 : globalColors.grey20,
+ borderColor: '#F5F8FF05',
+ height: ITEM_HEIGHT,
+ width: ITEM_WIDTH,
+ flexDirection: 'row',
+ alignItems: 'center',
+ borderRadius: 24,
+ borderWidth: THICK_BORDER_WIDTH,
+ transform: [{ translateX: position.x }, { translateY: position.y }],
+ };
+ }
+
+ return {
+ backgroundColor: 'transparent',
+ opacity: visible.value ? 1 : 0,
+ transform: [{ translateY: sectionsOffsets.value[Section.separator].y }, { scale: withTiming(pressed.value ? 0.95 : 1) }],
+ position: 'absolute',
+ width: '100%',
+ height: SEPARATOR_HEIGHT,
+ };
+ });
+
+ return (
+
+
+
+
+ {unpinnedNetworksLength}
+
+
+
+ {text}
+
+
+
+ {showMoreOrLessIcon}
+
+
+
+
+ );
+}
+
+function EmptyUnpinnedPlaceholder({
+ sectionsOffsets,
+ networks,
+ isUnpinnedHidden,
+}: {
+ sectionsOffsets: SharedValue>;
+ networks: SharedValue>;
+ isUnpinnedHidden: SharedValue;
+}) {
+ const styles = useAnimatedStyle(() => {
+ const isVisible = networks.value[Section.unpinned].length === 0 && !isUnpinnedHidden.value;
+ return {
+ opacity: isVisible ? withTiming(1, { duration: 800 }) : 0,
+ transform: [{ translateY: sectionsOffsets.value[Section.unpinned].y }],
+ };
+ });
+ const { isDarkMode } = useColorMode();
+ return (
+
+
+ {i18n.t(t.drag_here_to_unpin)}
+
+
+ );
+}
+
+function NetworksGrid({
+ editing,
+ setSelected,
+ selected,
+}: {
+ editing: SharedValue;
+ setSelected: (chainId: ChainId | undefined) => void;
+ selected: SharedValue;
+}) {
+ const initialPinned = networkSwitcherStore.getState().pinnedNetworks;
+ const sortedSupportedChainIds = useBackendNetworksStore.getState().getSortedSupportedChainIds();
+ const initialUnpinned = sortedSupportedChainIds.filter(chainId => !initialPinned.includes(chainId));
+ const networks = useSharedValue({ [Section.pinned]: initialPinned, [Section.unpinned]: initialUnpinned });
+
+ useEffect(() => {
+ // persists pinned networks when closing the sheet
+ // should be the only time this component is unmounted
+ return () => {
+ if (networks.value[Section.pinned].length > 0) {
+ networkSwitcherStore.setState({ pinnedNetworks: networks.value[Section.pinned] });
+ } else {
+ networkSwitcherStore.setState({ pinnedNetworks: defaultPinnedNetworks });
+ }
+ };
+ }, [networks]);
+
+ const expanded = useSharedValue(false);
+ const isUnpinnedHidden = useDerivedValue(() => !expanded.value && !editing.value);
+
+ const dragging = useSharedValue(null);
+
+ const sectionsOffsets = useDerivedValue(() => {
+ const pinnedHeight = Math.ceil(networks.value[Section.pinned].length / 2) * (ITEM_HEIGHT + GAP) - GAP;
+ return {
+ [Section.pinned]: { y: 0 },
+ [Section.separator]: { y: pinnedHeight },
+ [Section.unpinned]: { y: pinnedHeight + SEPARATOR_HEIGHT },
+ };
+ });
+ const containerHeight = useDerivedValue(() => {
+ const length = networks.value[Section.unpinned].length;
+ const paddingBottom = 32;
+ const unpinnedHeight = isUnpinnedHidden.value
+ ? length === 0
+ ? -SEPARATOR_HEIGHT + paddingBottom
+ : 0
+ : length === 0
+ ? ITEM_HEIGHT + paddingBottom
+ : Math.ceil((length + 1) / 2) * (ITEM_HEIGHT + GAP) - GAP + paddingBottom;
+ const height = sectionsOffsets.value[Section.unpinned].y + unpinnedHeight;
+ return height;
+ });
+ const containerStyle = useAnimatedStyle(() => ({
+ height: withDelay(expanded.value ? 0 : 25, withTiming(containerHeight.value, TIMING_CONFIGS.slowerFadeConfig)),
+ }));
+
+ const dragNetwork = Gesture.Pan()
+ .maxPointers(1)
+ .onTouchesDown((e, s) => {
+ if (!editing.value) {
+ s.fail();
+ return;
+ }
+ const touch = e.allTouches[0];
+ const section = touch.y > sectionsOffsets.value[Section.unpinned].y ? Section.unpinned : Section.pinned;
+ const sectionOffset = sectionsOffsets.value[section];
+ const index = indexFromPosition(touch.x, touch.y, sectionOffset);
+ const sectionNetworks = networks.value[section];
+ const chainId = sectionNetworks[index];
+
+ if (!chainId || (section === Section.pinned && sectionNetworks.length === 1)) {
+ s.fail();
+ return;
+ }
+
+ const position = positionFromIndex(index, sectionOffset);
+ dragging.value = { chainId, position };
+ })
+ .onChange(e => {
+ if (!dragging.value) return;
+ const chainId = dragging.value.chainId;
+ if (!chainId) return;
+
+ const section = e.y > sectionsOffsets.value[Section.unpinned].y - SEPARATOR_HEIGHT / 2 ? Section.unpinned : Section.pinned;
+ const sectionArray = networks.value[section];
+
+ const currentIndex = sectionArray.indexOf(chainId);
+ const newIndex = Math.min(indexFromPosition(e.x, e.y, sectionsOffsets.value[section]), sectionArray.length - 1);
+
+ networks.modify(networks => {
+ if (currentIndex === -1) {
+ // Pin/Unpin
+ if (section === Section.unpinned) networks[Section.pinned].splice(currentIndex, 1);
+ else networks[Section.pinned].push(chainId);
+ networks[Section.unpinned] = sortedSupportedChainIds.filter(chainId => !networks[Section.pinned].includes(chainId));
+ } else if (section === Section.pinned && newIndex !== currentIndex) {
+ // Reorder
+ networks[Section.pinned].splice(currentIndex, 1);
+ networks[Section.pinned].splice(newIndex, 0, chainId);
+ }
+ return networks;
+ });
+ dragging.modify(dragging => {
+ if (!dragging) return dragging;
+ dragging.position.x += e.changeX;
+ dragging.position.y += e.changeY;
+ return dragging;
+ });
+ })
+ .onFinalize(() => {
+ dragging.value = null;
+ });
+
+ const tapNetwork = Gesture.Tap()
+ .onTouchesDown((e, s) => {
+ if (editing.value) return s.fail();
+ })
+ .onEnd(e => {
+ const section = e.y > sectionsOffsets.value[Section.unpinned].y ? Section.unpinned : Section.pinned;
+ const index = indexFromPosition(e.x, e.y, sectionsOffsets.value[section]);
+ const chainId = networks.value[section][index];
+ if (!chainId) return;
+
+ setSelected(chainId);
+ });
+
+ const gridGesture = Gesture.Exclusive(dragNetwork, tapNetwork);
+
+ return (
+
+
+ {initialPinned.map(chainId => (
+
+
+
+ ))}
+
+
+
+
+
+ {initialUnpinned.map(chainId => (
+
+
+
+ ))}
+
+
+ );
+}
+
+function Sheet({ children, editing, onClose }: PropsWithChildren<{ editing: SharedValue; onClose: VoidFunction }>) {
+ const { isDarkMode } = useColorMode();
+ const surfacePrimary = useBackgroundColor('surfacePrimary');
+ const backgroundColor = isDarkMode ? '#191A1C' : surfacePrimary;
+ const separatorSecondary = useForegroundColor('separatorSecondary');
+
+ // make sure the onClose function is called when the sheet unmounts
+ useEffect(() => {
+ return () => onClose?.();
+ }, [onClose]);
+
+ return (
+ <>
+
+
+ {children}
+
+
+ >
+ );
+}
+
+export function NetworkSelector() {
+ const {
+ params: { onClose = noop, selected, setSelected },
+ } = useRoute>();
+
+ const editing = useSharedValue(false);
+
+ return (
+
+
+
+
+
+ );
+}
+
+const sx = StyleSheet.create({
+ overlappingBadge: {
+ borderWidth: THICKER_BORDER_WIDTH,
+ borderRadius: ALL_NETWORKS_BADGE_SIZE,
+ marginLeft: -9,
+ width: ALL_NETWORKS_BADGE_SIZE + THICKER_BORDER_WIDTH * 2,
+ height: ALL_NETWORKS_BADGE_SIZE + THICKER_BORDER_WIDTH * 2,
+ },
+ sheet: {
+ flex: 1,
+ width: deviceUtils.dimensions.width - 16,
+ bottom: Math.max(safeAreaInsetValues.bottom + 5, IS_IOS ? 8 : 30),
+ pointerEvents: 'box-none',
+ position: 'absolute',
+ zIndex: 30000,
+ left: 8,
+ right: 8,
+ paddingHorizontal: 16,
+ borderCurve: 'continuous',
+ borderRadius: 42,
+ borderWidth: THICK_BORDER_WIDTH,
+ overflow: 'hidden',
+ },
+});
diff --git a/src/components/PortalConsumer.js b/src/components/PortalConsumer.js
deleted file mode 100644
index 351b2271f02..00000000000
--- a/src/components/PortalConsumer.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React, { useEffect } from 'react';
-import { LoadingOverlay } from './modal';
-import { useWallets } from '@/hooks';
-import { sheetVerticalOffset } from '@/navigation/effects';
-import { usePortal } from '@/react-native-cool-modals/Portal';
-
-export default function PortalConsumer() {
- const { isWalletLoading } = useWallets();
- const { setComponent, hide } = usePortal();
- useEffect(() => {
- if (isWalletLoading) {
- setComponent(, true);
- }
- return hide;
- }, [hide, isWalletLoading, setComponent]);
-
- return null;
-}
diff --git a/src/components/Spinner.tsx b/src/components/Spinner.tsx
index d33af18643b..9e32d25576e 100644
--- a/src/components/Spinner.tsx
+++ b/src/components/Spinner.tsx
@@ -1,12 +1,12 @@
import React from 'react';
import { StyleProp, ViewStyle } from 'react-native';
-import { IS_TESTING } from 'react-native-dotenv';
import SpinnerImageSource from '../assets/spinner.png';
import { useTheme } from '../theme/ThemeContext';
import { SpinAnimation } from './animations';
import { Centered } from './layout';
import { ImgixImage } from '@/components/images';
import { position } from '@/styles';
+import { IS_TEST } from '@/env';
type SpinnerProps = {
color?: string;
@@ -32,7 +32,7 @@ const Spinner = ({ color = '', duration = 1500, size = 20, ...props }: SpinnerPr
return (
- {IS_TESTING !== 'true' && (
+ {!IS_TEST && (
diff --git a/src/components/WalletLoadingListener.tsx b/src/components/WalletLoadingListener.tsx
new file mode 100644
index 00000000000..6a9e605ab4f
--- /dev/null
+++ b/src/components/WalletLoadingListener.tsx
@@ -0,0 +1,17 @@
+import React, { useEffect } from 'react';
+import { LoadingOverlay } from './modal';
+import { sheetVerticalOffset } from '@/navigation/effects';
+import { walletLoadingStore } from '@/state/walletLoading/walletLoading';
+
+export default function WalletLoadingListener() {
+ const loadingState = walletLoadingStore(state => state.loadingState);
+
+ useEffect(() => {
+ if (loadingState) {
+ walletLoadingStore.getState().setComponent();
+ }
+ return walletLoadingStore.getState().hide;
+ }, [loadingState]);
+
+ return null;
+}
diff --git a/src/components/animations/ShimmerAnimation.js b/src/components/animations/ShimmerAnimation.js
index 7d17fdbf480..c988721f6c8 100644
--- a/src/components/animations/ShimmerAnimation.js
+++ b/src/components/animations/ShimmerAnimation.js
@@ -3,7 +3,7 @@ import LinearGradient from 'react-native-linear-gradient';
import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
import styled from '@/styled-thing';
import { position } from '@/styles';
-import { IS_TESTING } from 'react-native-dotenv';
+import { IS_TEST } from '@/env';
const timingConfig = {
duration: 2500,
@@ -59,7 +59,7 @@ export default function ShimmerAnimation({
transform: [{ translateX: positionX.value }],
}));
- if (IS_TESTING === 'true') {
+ if (IS_TEST) {
return null;
}
diff --git a/src/components/asset-list/AssetListHeader.js b/src/components/asset-list/AssetListHeader.js
index d35ee0bd3a8..6d6203ba608 100644
--- a/src/components/asset-list/AssetListHeader.js
+++ b/src/components/asset-list/AssetListHeader.js
@@ -1,5 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import { IS_TESTING } from 'react-native-dotenv';
import LinearGradient from 'react-native-linear-gradient';
import { abbreviations, magicMemo, measureText } from '../../utils';
import { ButtonPressAnimation } from '../animations';
@@ -12,11 +11,12 @@ import { StickyHeader } from './RecyclerAssetList2/core/StickyHeaders';
import { useAccountProfile, useDimensions } from '@/hooks';
import { useNavigation } from '@/navigation';
import Routes from '@/navigation/routesNames';
-import { useUserAssetCount } from '@/resources/assets/useUserAssetCount';
import styled from '@/styled-thing';
import { fonts, position } from '@/styles';
import { useTheme } from '@/theme';
import * as lang from '@/languages';
+import { useUserAssetsStore } from '@/state/assets/userAssets';
+import { IS_TEST } from '@/env';
export const AssetListHeaderHeight = ListHeaderHeight;
@@ -82,7 +82,7 @@ const WalletSelectButton = ({ accountName, onChangeWallet, deviceWidth, textWidt
{truncatedAccountName ? (
- {IS_TESTING !== 'true' && (
+ {!IS_TEST && (
state.isLoadingUserAssets);
const onChangeWallet = useCallback(() => {
navigate(Routes.CHANGE_WALLET_SHEET);
diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx
index 4ea959efb7c..3239c50dbda 100644
--- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx
+++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCoinBadge.tsx
@@ -16,24 +16,23 @@ import BscBadge from '@/assets/badges/bscBadge.png';
import BscBadgeDark from '@/assets/badges/bscBadgeDark.png';
import DegenBadge from '@/assets/badges/degenBadge.png';
import DegenBadgeDark from '@/assets/badges/degenBadgeDark.png';
-// import GnosisBadge from '@/assets/badges/gnosisBadge.png';
-// import GnosisBadgeDark from '@/assets/badges/gnosisBadgeDark.png';
-// import GravityBadge from '@/assets/badges/gravityBadge.png';
-// import GravityBadgeDark from '@/assets/badges/gravityBadgeDark.png';
+import GnosisBadge from '@/assets/badges/gnosisBadge.png';
+import GnosisBadgeDark from '@/assets/badges/gnosisBadgeDark.png';
+import GravityBadge from '@/assets/badges/gravityBadge.png';
+import GravityBadgeDark from '@/assets/badges/gravityBadgeDark.png';
import InkBadge from '@/assets/badges/inkBadge.png';
import InkBadgeDark from '@/assets/badges/inkBadgeDark.png';
-// import LineaBadge from '@/assets/badges/lineaBadge.png';
-// import LineaBadgeDark from '@/assets/badges/lineaBadgeDark.png';
+import LineaBadge from '@/assets/badges/lineaBadge.png';
+import LineaBadgeDark from '@/assets/badges/lineaBadgeDark.png';
import OptimismBadge from '@/assets/badges/optimismBadge.png';
import OptimismBadgeDark from '@/assets/badges/optimismBadgeDark.png';
import PolygonBadge from '@/assets/badges/polygonBadge.png';
import PolygonBadgeDark from '@/assets/badges/polygonBadgeDark.png';
-// import SankoBadge from '@/assets/badges/sankoBadge.png';
-// import SankoBadgeDark from '@/assets/badges/sankoBadgeDark.png';
-// import ScrollBadge from '@/assets/badges/scrollBadge.png';
-// import ScrollBadgeDark from '@/assets/badges/scrollBadgeDark.png';
-// import ZksyncBadge from '@/assets/badges/zksyncBadge.png';
-// import ZksyncBadgeDark from '@/assets/badges/zksyncBadgeDark.png';
+import SankoBadge from '@/assets/badges/sankoBadge.png';
+import ScrollBadge from '@/assets/badges/scrollBadge.png';
+import ScrollBadgeDark from '@/assets/badges/scrollBadgeDark.png';
+import ZksyncBadge from '@/assets/badges/zkSyncBadge.png';
+import ZksyncBadgeDark from '@/assets/badges/zksyncBadgeDark.png';
import ZoraBadge from '@/assets/badges/zoraBadge.png';
import ZoraBadgeDark from '@/assets/badges/zoraBadgeDark.png';
@@ -76,22 +75,22 @@ const AssetIconsByTheme: {
dark: DegenBadgeDark,
light: DegenBadge,
},
- // [ChainId.gnosis]: {
- // dark: GnosisBadgeDark,
- // light: GnosisBadge,
- // },
- // [ChainId.gravity]: {
- // dark: GravityBadgeDark,
- // light: GravityBadge,
- // },
+ [ChainId.gnosis]: {
+ dark: GnosisBadgeDark,
+ light: GnosisBadge,
+ },
+ [ChainId.gravity]: {
+ dark: GravityBadgeDark,
+ light: GravityBadge,
+ },
[ChainId.ink]: {
dark: InkBadgeDark,
light: InkBadge,
},
- // [ChainId.linea]: {
- // dark: LineaBadgeDark,
- // light: LineaBadge,
- // },
+ [ChainId.linea]: {
+ dark: LineaBadgeDark,
+ light: LineaBadge,
+ },
[ChainId.optimism]: {
dark: OptimismBadgeDark,
light: OptimismBadge,
@@ -100,18 +99,18 @@ const AssetIconsByTheme: {
dark: PolygonBadgeDark,
light: PolygonBadge,
},
- // [ChainId.sanko]: {
- // dark: SankoBadgeDark,
- // light: SankoBadge,
- // },
- // [ChainId.scroll]: {
- // dark: ScrollBadgeDark,
- // light: ScrollBadge,
- // },
- // [ChainId.zksync]: {
- // dark: ZksyncBadgeDark,
- // light: ZksyncBadge,
- // },
+ [ChainId.sanko]: {
+ dark: SankoBadge,
+ light: SankoBadge,
+ },
+ [ChainId.scroll]: {
+ dark: ScrollBadgeDark,
+ light: ScrollBadge,
+ },
+ [ChainId.zksync]: {
+ dark: ZksyncBadgeDark,
+ light: ZksyncBadge,
+ },
[ChainId.zora]: {
dark: ZoraBadgeDark,
light: ZoraBadge,
diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx
index d122b9a622e..b947bfbc24d 100644
--- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx
+++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx
@@ -1,7 +1,6 @@
import React from 'react';
import isEqual from 'react-fast-compare';
import { Text as RNText, StyleSheet, View } from 'react-native';
-import { IS_TESTING } from 'react-native-dotenv';
import RadialGradient from 'react-native-radial-gradient';
import { ButtonPressAnimation } from '../../../animations';
import { CoinRowHeight } from '../../../coin-row';
@@ -14,8 +13,9 @@ import { deviceUtils } from '@/utils';
import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon';
import { useExternalToken } from '@/resources/assets/externalAssetsQuery';
import { ChainId } from '@/state/backendNetworks/types';
+import { IS_TEST } from '@/env';
-const SafeRadialGradient = (IS_TESTING === 'true' ? View : RadialGradient) as typeof RadialGradient;
+const SafeRadialGradient = (IS_TEST ? View : RadialGradient) as typeof RadialGradient;
interface FastCurrencySelectionRowProps {
item: any;
@@ -174,10 +174,8 @@ export default React.memo(function FastCurrencySelectionRow({
{showFavoriteButton &&
chainId === ChainId.mainnet &&
(ios ? (
- // @ts-ignore
-
+
@@ -216,13 +216,17 @@ function SendButton() {
);
}
-export function MoreButton() {
- // ////////////////////////////////////////////////////
- // Handlers
-
+export function CopyButton() {
const [isToastActive, setToastActive] = useRecoilState(addressCopiedToastAtom);
const { accountAddress } = useAccountProfile();
+ const { isDamaged } = useWallets();
+
const handlePressCopy = React.useCallback(() => {
+ if (isDamaged) {
+ showWalletErrorAlert();
+ return;
+ }
+
if (!isToastActive) {
setToastActive(true);
setTimeout(() => {
@@ -230,7 +234,7 @@ export function MoreButton() {
}, 2000);
}
Clipboard.setString(accountAddress);
- }, [accountAddress, isToastActive, setToastActive]);
+ }, [accountAddress, isDamaged, isToastActive, setToastActive]);
return (
<>
diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileNameRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileNameRow.tsx
index 616935fe6ff..9859a4ae105 100644
--- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileNameRow.tsx
+++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileNameRow.tsx
@@ -109,7 +109,6 @@ export function ProfileNameRow({
scaleTo={0}
size={50}
wiggleFactor={0}
- // @ts-expect-error – JS component
setOnNewEmoji={newOnNewEmoji => (onNewEmoji.current = newOnNewEmoji)}
/>
diff --git a/src/components/backup/AddWalletToCloudBackupStep.tsx b/src/components/backup/AddWalletToCloudBackupStep.tsx
deleted file mode 100644
index 62f92a99e2f..00000000000
--- a/src/components/backup/AddWalletToCloudBackupStep.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import React, { useCallback } from 'react';
-import { Bleed, Box, Inline, Inset, Separator, Stack, Text } from '@/design-system';
-import * as lang from '@/languages';
-import { ImgixImage } from '../images';
-import WalletsAndBackupIcon from '@/assets/WalletsAndBackup.png';
-import { Source } from 'react-native-fast-image';
-import { cloudPlatform } from '@/utils/platform';
-import { ButtonPressAnimation } from '../animations';
-import Routes from '@/navigation/routesNames';
-import { useNavigation } from '@/navigation';
-import { useWallets } from '@/hooks';
-import { WalletCountPerType, useVisibleWallets } from '@/screens/SettingsSheet/useVisibleWallets';
-import { format } from 'date-fns';
-import { useCreateBackup } from './useCreateBackup';
-import { login } from '@/handlers/cloudBackup';
-
-const imageSize = 72;
-
-export default function AddWalletToCloudBackupStep() {
- const { goBack } = useNavigation();
- const { wallets, selectedWallet } = useWallets();
-
- const walletTypeCount: WalletCountPerType = {
- phrase: 0,
- privateKey: 0,
- };
-
- const { lastBackupDate } = useVisibleWallets({ wallets, walletTypeCount });
-
- const { onSubmit } = useCreateBackup({
- walletId: selectedWallet.id,
- navigateToRoute: {
- route: Routes.SETTINGS_SHEET,
- params: {
- screen: Routes.SETTINGS_SECTION_BACKUP,
- },
- },
- });
-
- const potentiallyLoginAndSubmit = useCallback(async () => {
- await login();
- return onSubmit({});
- }, [onSubmit]);
-
- const onMaybeLater = useCallback(() => goBack(), [goBack]);
-
- return (
-
-
-
-
-
- {lang.t(lang.l.back_up.cloud.add_wallet_to_cloud_backups)}
-
-
-
-
-
-
-
-
- potentiallyLoginAndSubmit().then(success => success && goBack())}>
-
-
-
-
- {' '}
- {lang.t(lang.l.back_up.cloud.back_to_cloud_platform_now, {
- cloudPlatform,
- })}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {lang.t(lang.l.back_up.cloud.mayber_later)}
-
-
-
-
-
-
-
-
-
-
- {lastBackupDate && (
-
-
-
-
- {lang.t(lang.l.back_up.cloud.latest_backup, {
- date: format(lastBackupDate, "M/d/yy 'at' h:mm a"),
- })}
-
-
-
-
- )}
-
- );
-}
diff --git a/src/components/backup/BackupManuallyStep.tsx b/src/components/backup/BackupManuallyStep.tsx
deleted file mode 100644
index da18d73806a..00000000000
--- a/src/components/backup/BackupManuallyStep.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import React, { useCallback } from 'react';
-import { Bleed, Box, Inline, Inset, Separator, Stack, Text } from '@/design-system';
-import * as lang from '@/languages';
-import { ImgixImage } from '../images';
-import ManuallyBackedUpIcon from '@/assets/ManuallyBackedUp.png';
-import { Source } from 'react-native-fast-image';
-import { ButtonPressAnimation } from '../animations';
-import { useNavigation } from '@/navigation';
-import Routes from '@/navigation/routesNames';
-import { useWallets } from '@/hooks';
-import walletTypes from '@/helpers/walletTypes';
-import { SETTINGS_BACKUP_ROUTES } from '@/screens/SettingsSheet/components/Backups/routes';
-import walletBackupTypes from '@/helpers/walletBackupTypes';
-
-const imageSize = 72;
-
-export default function BackupManuallyStep() {
- const { navigate, goBack } = useNavigation();
- const { selectedWallet } = useWallets();
-
- const onManualBackup = async () => {
- const title =
- selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey
- ? (selectedWallet.addresses || [])[0].label
- : selectedWallet.name;
-
- goBack();
- navigate(Routes.SETTINGS_SHEET, {
- screen: SETTINGS_BACKUP_ROUTES.SECRET_WARNING,
- params: {
- isBackingUp: true,
- title,
- backupType: walletBackupTypes.manual,
- walletId: selectedWallet.id,
- },
- });
- };
-
- const onMaybeLater = useCallback(() => goBack(), [goBack]);
-
- return (
-
-
-
-
-
- {lang.t(lang.l.back_up.manual.backup_manually_now)}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {lang.t(lang.l.back_up.manual.back_up_now)}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {lang.t(lang.l.back_up.manual.already_backed_up)}
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/backup/BackupSheet.tsx b/src/components/backup/BackupSheet.tsx
index 5b6a0a4300a..12e15c60190 100644
--- a/src/components/backup/BackupSheet.tsx
+++ b/src/components/backup/BackupSheet.tsx
@@ -2,13 +2,10 @@ import { RouteProp, useRoute } from '@react-navigation/native';
import React, { useCallback } from 'react';
import { BackupCloudStep, RestoreCloudStep } from '.';
import WalletBackupStepTypes from '@/helpers/walletBackupStepTypes';
-import BackupChooseProviderStep from '@/components/backup/BackupChooseProviderStep';
+import BackupWalletPrompt from '@/components/backup/BackupWalletPrompt';
import { BackgroundProvider } from '@/design-system';
import { SimpleSheet } from '@/components/sheet/SimpleSheet';
-import AddWalletToCloudBackupStep from '@/components/backup/AddWalletToCloudBackupStep';
-import BackupManuallyStep from './BackupManuallyStep';
import { getHeightForStep } from '@/navigation/config';
-import { CloudBackupProvider } from './CloudBackupProvider';
type BackupSheetParams = {
BackupSheet: {
@@ -21,38 +18,32 @@ type BackupSheetParams = {
};
export default function BackupSheet() {
- const { params: { step = WalletBackupStepTypes.no_provider } = {} } = useRoute>();
+ const { params: { step = WalletBackupStepTypes.backup_prompt } = {} } = useRoute>();
const renderStep = useCallback(() => {
switch (step) {
- case WalletBackupStepTypes.backup_now_to_cloud:
- return ;
- case WalletBackupStepTypes.backup_now_manually:
- return ;
case WalletBackupStepTypes.backup_cloud:
return ;
case WalletBackupStepTypes.restore_from_backup:
return ;
- case WalletBackupStepTypes.no_provider:
+ case WalletBackupStepTypes.backup_prompt:
default:
- return ;
+ return ;
}
}, [step]);
return (
-
-
- {({ backgroundColor }) => (
-
- {renderStep()}
-
- )}
-
-
+
+ {({ backgroundColor }) => (
+
+ {renderStep()}
+
+ )}
+
);
}
diff --git a/src/components/backup/BackupChooseProviderStep.tsx b/src/components/backup/BackupWalletPrompt.tsx
similarity index 66%
rename from src/components/backup/BackupChooseProviderStep.tsx
rename to src/components/backup/BackupWalletPrompt.tsx
index 38325639704..77071534f48 100644
--- a/src/components/backup/BackupChooseProviderStep.tsx
+++ b/src/components/backup/BackupWalletPrompt.tsx
@@ -1,7 +1,6 @@
-import React from 'react';
-import { useCreateBackup } from '@/components/backup/useCreateBackup';
+import React, { useCallback, useMemo } from 'react';
import { Bleed, Box, Inline, Inset, Separator, Stack, Text } from '@/design-system';
-import * as lang from '@/languages';
+import * as i18n from '@/languages';
import { ImgixImage } from '../images';
import WalletsAndBackupIcon from '@/assets/WalletsAndBackup.png';
import ManuallyBackedUpIcon from '@/assets/ManuallyBackedUp.png';
@@ -14,13 +13,13 @@ import { useNavigation } from '@/navigation';
import Routes from '@/navigation/routesNames';
import { SETTINGS_BACKUP_ROUTES } from '@/screens/SettingsSheet/components/Backups/routes';
import { useWallets } from '@/hooks';
-import walletTypes from '@/helpers/walletTypes';
+import WalletTypes from '@/helpers/walletTypes';
import walletBackupTypes from '@/helpers/walletBackupTypes';
-import { IS_ANDROID } from '@/env';
-import { GoogleDriveUserData, getGoogleAccountUserData, isCloudBackupAvailable, login } from '@/handlers/cloudBackup';
-import { WrappedAlert as Alert } from '@/helpers/alert';
-import { RainbowError, logger } from '@/logger';
-import { Linking } from 'react-native';
+import { useCreateBackup } from '@/components/backup/useCreateBackup';
+import { backupsStore, CloudBackupState } from '@/state/backups/backups';
+import { executeFnIfCloudBackupAvailable } from '@/model/backup';
+import { TextColor } from '@/design-system/color/palettes';
+import { CustomColor } from '@/design-system/color/useForegroundColor';
const imageSize = 72;
@@ -28,67 +27,31 @@ export default function BackupSheetSectionNoProvider() {
const { colors } = useTheme();
const { navigate, goBack } = useNavigation();
const { selectedWallet } = useWallets();
+ const createBackup = useCreateBackup();
+ const { status } = backupsStore(state => ({
+ status: state.status,
+ }));
- const { onSubmit, loading } = useCreateBackup({
- walletId: selectedWallet.id,
- navigateToRoute: {
- route: Routes.SETTINGS_SHEET,
- params: {
- screen: Routes.SETTINGS_SECTION_BACKUP,
- },
- },
- });
-
- const onCloudBackup = async () => {
- if (loading !== 'none') {
- return;
- }
- // NOTE: On Android we need to make sure the user is signed into a Google account before trying to backup
- // otherwise we'll fake backup and it's confusing...
- if (IS_ANDROID) {
- try {
- await login();
- getGoogleAccountUserData().then((accountDetails: GoogleDriveUserData | undefined) => {
- if (!accountDetails) {
- Alert.alert(lang.t(lang.l.back_up.errors.no_account_found));
- return;
- }
- });
- } catch (e) {
- logger.error(new RainbowError('[BackupSheetSectionNoProvider]: No account found'), {
- error: e,
- });
- Alert.alert(lang.t(lang.l.back_up.errors.no_account_found));
- }
- } else {
- const isAvailable = await isCloudBackupAvailable();
- if (!isAvailable) {
- Alert.alert(
- lang.t(lang.l.modal.back_up.alerts.cloud_not_enabled.label),
- lang.t(lang.l.modal.back_up.alerts.cloud_not_enabled.description),
- [
- {
- onPress: () => {
- Linking.openURL('https://support.apple.com/en-us/HT204025');
- },
- text: lang.t(lang.l.modal.back_up.alerts.cloud_not_enabled.show_me),
- },
- {
- style: 'cancel',
- text: lang.t(lang.l.modal.back_up.alerts.cloud_not_enabled.no_thanks),
- },
- ]
- );
- return;
- }
- }
+ const onCloudBackup = useCallback(() => {
+ // pop the bottom sheet, and navigate to the backup section inside settings sheet
+ goBack();
+ navigate(Routes.SETTINGS_SHEET, {
+ screen: Routes.SETTINGS_SECTION_BACKUP,
+ initial: false,
+ });
- onSubmit({});
- };
+ executeFnIfCloudBackupAvailable({
+ fn: () =>
+ createBackup({
+ walletId: selectedWallet.id,
+ }),
+ logout: true,
+ });
+ }, [createBackup, goBack, navigate, selectedWallet.id]);
- const onManualBackup = async () => {
+ const onManualBackup = useCallback(async () => {
const title =
- selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey
+ selectedWallet?.imported && selectedWallet.type === WalletTypes.privateKey
? (selectedWallet.addresses || [])[0].label
: selectedWallet.name;
@@ -102,13 +65,38 @@ export default function BackupSheetSectionNoProvider() {
walletId: selectedWallet.id,
},
});
- };
+ }, [goBack, navigate, selectedWallet.addresses, selectedWallet.id, selectedWallet?.imported, selectedWallet.name, selectedWallet.type]);
+
+ const isCloudBackupDisabled = useMemo(() => {
+ return status !== CloudBackupState.Ready && status !== CloudBackupState.NotAvailable;
+ }, [status]);
+
+ const { color, text } = useMemo<{ text: string; color: TextColor | CustomColor }>(() => {
+ if (status === CloudBackupState.FailedToInitialize || status === CloudBackupState.NotAvailable) {
+ return {
+ text: i18n.t(i18n.l.back_up.cloud.statuses.not_enabled),
+ color: 'primary (Deprecated)',
+ };
+ }
+
+ if (status === CloudBackupState.Ready) {
+ return {
+ text: i18n.t(i18n.l.back_up.cloud.cloud_backup),
+ color: 'primary (Deprecated)',
+ };
+ }
+
+ return {
+ text: i18n.t(i18n.l.back_up.cloud.statuses.syncing),
+ color: 'yellow',
+ };
+ }, [status]);
return (
- {lang.t(lang.l.back_up.cloud.how_would_you_like_to_backup)}
+ {i18n.t(i18n.l.back_up.cloud.how_would_you_like_to_backup)}
@@ -116,8 +104,7 @@ export default function BackupSheetSectionNoProvider() {
- {/* replace this with BackUpMenuButton */}
-
+
@@ -133,18 +120,18 @@ export default function BackupSheetSectionNoProvider() {
marginRight={{ custom: -12 }}
marginTop={{ custom: 0 }}
marginBottom={{ custom: -8 }}
- source={WalletsAndBackupIcon as Source}
+ source={WalletsAndBackupIcon}
width={{ custom: imageSize }}
size={imageSize}
/>
-
- {lang.t(lang.l.back_up.cloud.cloud_backup)}
+
+ {text}
- {lang.t(lang.l.back_up.cloud.recommended_for_beginners)}
+ {i18n.t(i18n.l.back_up.cloud.recommended_for_beginners)}
{' '}
- {lang.t(lang.l.back_up.cloud.choose_backup_cloud_description, {
+ {i18n.t(i18n.l.back_up.cloud.choose_backup_cloud_description, {
cloudPlatform,
})}
@@ -192,10 +179,10 @@ export default function BackupSheetSectionNoProvider() {
size={imageSize}
/>
- {lang.t(lang.l.back_up.cloud.manual_backup)}
+ {i18n.t(i18n.l.back_up.cloud.manual_backup)}
- {lang.t(lang.l.back_up.cloud.choose_backup_manual_description)}
+ {i18n.t(i18n.l.back_up.cloud.choose_backup_manual_description)}
diff --git a/src/components/backup/ChooseBackupStep.tsx b/src/components/backup/ChooseBackupStep.tsx
index 2f7b68cedf6..d08d4cdb0e2 100644
--- a/src/components/backup/ChooseBackupStep.tsx
+++ b/src/components/backup/ChooseBackupStep.tsx
@@ -6,26 +6,24 @@ import { useDimensions } from '@/hooks';
import { useNavigation } from '@/navigation';
import styled from '@/styled-thing';
import { margin, padding } from '@/styles';
-import { Box, Stack, Text } from '@/design-system';
-import { RouteProp, useRoute } from '@react-navigation/native';
+import { Box, Stack } from '@/design-system';
import { sharedCoolModalTopOffset } from '@/navigation/config';
-import { ImgixImage } from '../images';
+import { ImgixImage } from '@/components/images';
import MenuContainer from '@/screens/SettingsSheet/components/MenuContainer';
import Menu from '@/screens/SettingsSheet/components/Menu';
import { format } from 'date-fns';
import MenuItem from '@/screens/SettingsSheet/components/MenuItem';
import Routes from '@/navigation/routesNames';
-import { Backup, parseTimestampFromFilename } from '@/model/backup';
-import { RestoreSheetParams } from '@/screens/RestoreSheet';
+import { BackupFile, parseTimestampFromFilename } from '@/model/backup';
import { Source } from 'react-native-fast-image';
import { IS_ANDROID } from '@/env';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
-import useCloudBackups, { CloudBackupStep } from '@/hooks/useCloudBackups';
-import { Centered } from '../layout';
-import { cloudPlatform } from '@/utils/platform';
-import Spinner from '../Spinner';
-import ActivityIndicator from '../ActivityIndicator';
+import { Page } from '@/components/layout';
+import Spinner from '@/components/Spinner';
+import ActivityIndicator from '@/components/ActivityIndicator';
import { useTheme } from '@/theme';
+import { backupsStore, CloudBackupState, LoadingStates } from '@/state/backups/backups';
+import { titleForBackupState } from '@/screens/SettingsSheet/utils';
const Title = styled(RNText).attrs({
align: 'left',
@@ -53,60 +51,27 @@ const Masthead = styled(Box).attrs({
});
export function ChooseBackupStep() {
- const {
- params: { fromSettings },
- } = useRoute>();
const { colors } = useTheme();
- const { isFetching, backups, userData, step, fetchBackups } = useCloudBackups();
+ const { status, backups, mostRecentBackup } = backupsStore(state => ({
+ status: state.status,
+ backups: state.backups,
+ mostRecentBackup: state.mostRecentBackup,
+ }));
+
+ const isLoading = LoadingStates.includes(status);
const { top } = useSafeAreaInsets();
const { height: deviceHeight } = useDimensions();
const { navigate } = useNavigation();
- const cloudBackups = backups.files
- .filter(backup => {
- if (IS_ANDROID) {
- return !backup.name.match(/UserData/i);
- }
-
- return backup.isFile && backup.size > 0 && !backup.name.match(/UserData/i);
- })
- .sort((a, b) => {
- return parseTimestampFromFilename(b.name) - parseTimestampFromFilename(a.name);
- });
-
- const mostRecentBackup = cloudBackups.reduce(
- (prev, current) => {
- if (!current) {
- return prev;
- }
-
- if (!prev) {
- return current;
- }
-
- const prevTimestamp = new Date(prev.lastModified).getTime();
- const currentTimestamp = new Date(current.lastModified).getTime();
- if (currentTimestamp > prevTimestamp) {
- return current;
- }
-
- return prev;
- },
- undefined as Backup | undefined
- );
-
const onSelectCloudBackup = useCallback(
- (selectedBackup: Backup) => {
+ (selectedBackup: BackupFile) => {
navigate(Routes.RESTORE_CLOUD_SHEET, {
- backups,
- userData,
selectedBackup,
- fromSettings,
});
},
- [navigate, userData, backups, fromSettings]
+ [navigate]
);
const height = IS_ANDROID ? deviceHeight - top : deviceHeight - sharedCoolModalTopOffset - 48;
@@ -132,7 +97,7 @@ export function ChooseBackupStep() {
- {!isFetching && step === CloudBackupStep.FAILED && (
+ {status === CloudBackupState.FailedToInitialize && (
)}
- {!isFetching && !cloudBackups.length && step !== CloudBackupStep.FAILED && (
+ {status === CloudBackupState.Ready && backups.files.length === 0 && (
+
+
+
+
)}
- {!isFetching && cloudBackups.length > 0 && (
+ {status === CloudBackupState.Ready && backups.files.length > 0 && (
{mostRecentBackup && (
-
+
+
+
)}
-
+
)}
- {isFetching && (
-
+ {isLoading && (
+
{android ? : }
-
- {lang.t(lang.l.back_up.cloud.fetching_backups, {
- cloudPlatformName: cloudPlatform,
- })}
-
-
+ {titleForBackupState[status]}
+
)}
diff --git a/src/components/backup/CloudBackupProvider.tsx b/src/components/backup/CloudBackupProvider.tsx
deleted file mode 100644
index 377e9d13a83..00000000000
--- a/src/components/backup/CloudBackupProvider.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import React, { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react';
-import type { BackupUserData, CloudBackups } from '@/model/backup';
-import {
- fetchAllBackups,
- fetchUserDataFromCloud,
- getGoogleAccountUserData,
- isCloudBackupAvailable,
- syncCloud,
-} from '@/handlers/cloudBackup';
-import { RainbowError, logger } from '@/logger';
-import { IS_ANDROID } from '@/env';
-
-type CloudBackupContext = {
- isFetching: boolean;
- backups: CloudBackups;
- fetchBackups: () => Promise;
- userData: BackupUserData | undefined;
-};
-
-const CloudBackupContext = createContext({} as CloudBackupContext);
-
-export function CloudBackupProvider({ children }: PropsWithChildren) {
- const [isFetching, setIsFetching] = useState(false);
- const [backups, setBackups] = useState({
- files: [],
- });
-
- const [userData, setUserData] = useState();
-
- const fetchBackups = async () => {
- try {
- setIsFetching(true);
- const isAvailable = await isCloudBackupAvailable();
- if (!isAvailable) {
- logger.debug('[CloudBackupProvider]: Cloud backup is not available');
- setIsFetching(false);
- return;
- }
-
- if (IS_ANDROID) {
- const gdata = await getGoogleAccountUserData();
- if (!gdata) {
- return;
- }
- }
-
- logger.debug('[CloudBackupProvider]: Syncing with cloud');
- await syncCloud();
-
- logger.debug('[CloudBackupProvider]: Fetching user data');
- const userData = await fetchUserDataFromCloud();
- setUserData(userData);
-
- logger.debug('[CloudBackupProvider]: Fetching all backups');
- const backups = await fetchAllBackups();
-
- logger.debug(`[CloudBackupProvider]: Retrieved ${backups.files.length} backup files`);
- setBackups(backups);
- } catch (e) {
- logger.error(new RainbowError('[CloudBackupProvider]: Failed to fetch all backups'), {
- error: e,
- });
- }
- setIsFetching(false);
- };
-
- useEffect(() => {
- fetchBackups();
- }, []);
-
- const value = {
- isFetching,
- backups,
- fetchBackups,
- userData,
- };
-
- return {children};
-}
-
-export function useCloudBackups() {
- const context = useContext(CloudBackupContext);
- if (context === null) {
- throw new Error('useCloudBackups must be used within a CloudBackupProvider');
- }
- return context;
-}
diff --git a/src/components/backup/RestoreCloudStep.tsx b/src/components/backup/RestoreCloudStep.tsx
index e8bd83aa7a3..ce0774f2ec3 100644
--- a/src/components/backup/RestoreCloudStep.tsx
+++ b/src/components/backup/RestoreCloudStep.tsx
@@ -6,8 +6,7 @@ import WalletAndBackup from '@/assets/WalletsAndBackup.png';
import { KeyboardArea } from 'react-native-keyboard-area';
import {
- Backup,
- fetchBackupPassword,
+ BackupFile,
getLocalBackupPassword,
restoreCloudBackup,
RestoreCloudBackupResultStates,
@@ -17,10 +16,10 @@ import { cloudPlatform } from '@/utils/platform';
import { PasswordField } from '../fields';
import { Text } from '../text';
import { WrappedAlert as Alert } from '@/helpers/alert';
-import { cloudBackupPasswordMinLength, isCloudBackupPasswordValid, normalizeAndroidBackupFilename } from '@/handlers/cloudBackup';
+import { isCloudBackupPasswordValid, normalizeAndroidBackupFilename } from '@/handlers/cloudBackup';
import walletBackupTypes from '@/helpers/walletBackupTypes';
import { useDimensions, useInitializeWallet } from '@/hooks';
-import { useNavigation } from '@/navigation';
+import { Navigation, useNavigation } from '@/navigation';
import { addressSetSelected, setAllWalletsWithIdsAsBackedUp, walletsLoadState, walletsSetSelected } from '@/redux/wallets';
import Routes from '@/navigation/routesNames';
import styled from '@/styled-thing';
@@ -35,8 +34,16 @@ import RainbowButtonTypes from '../buttons/rainbow-button/RainbowButtonTypes';
import { RouteProp, useRoute } from '@react-navigation/native';
import { RestoreSheetParams } from '@/screens/RestoreSheet';
import { Source } from 'react-native-fast-image';
-import { useTheme } from '@/theme';
-import useCloudBackups from '@/hooks/useCloudBackups';
+import { ThemeContextProps, useTheme } from '@/theme';
+import { WalletLoadingStates } from '@/helpers/walletLoadingStates';
+import { isEmpty } from 'lodash';
+import { backupsStore } from '@/state/backups/backups';
+import { walletLoadingStore } from '@/state/walletLoading/walletLoading';
+
+type ComponentProps = {
+ theme: ThemeContextProps;
+ color: ThemeContextProps['colors'][keyof ThemeContextProps['colors']];
+};
const Title = styled(Text).attrs({
size: 'big',
@@ -45,7 +52,7 @@ const Title = styled(Text).attrs({
...padding.object(12, 0, 0),
});
-const DescriptionText = styled(Text).attrs(({ theme: { colors }, color }: any) => ({
+const DescriptionText = styled(Text).attrs(({ theme: { colors }, color }: ComponentProps) => ({
align: 'left',
color: color || colors.alpha(colors.blueGreyDark, 0.5),
lineHeight: 'looser',
@@ -53,7 +60,7 @@ const DescriptionText = styled(Text).attrs(({ theme: { colors }, color }: any) =
weight: 'medium',
}))({});
-const ButtonText = styled(Text).attrs(({ theme: { colors }, color }: any) => ({
+const ButtonText = styled(Text).attrs(({ theme: { colors }, color }: ComponentProps) => ({
align: 'center',
letterSpacing: 'rounded',
color: color || colors.alpha(colors.blueGreyDark, 0.5),
@@ -71,38 +78,46 @@ const Masthead = styled(Box).attrs({
});
const KeyboardSizeView = styled(KeyboardArea)({
- backgroundColor: ({ theme: { colors } }: any) => colors.transparent,
+ backgroundColor: ({ theme: { colors } }: ComponentProps) => colors.transparent,
});
type RestoreCloudStepParams = {
RestoreSheet: {
- selectedBackup: Backup;
+ selectedBackup: BackupFile;
};
};
export default function RestoreCloudStep() {
const { params } = useRoute>();
+ const { password } = backupsStore(state => ({
+ password: state.password,
+ }));
- const { userData } = useCloudBackups();
+ const loadingState = walletLoadingStore(state => state.loadingState);
const { selectedBackup } = params;
const { isDarkMode } = useTheme();
- const [loading, setLoading] = useState(false);
+ const { canGoBack, goBack } = useNavigation();
+
+ const onRestoreSuccess = useCallback(() => {
+ while (canGoBack()) {
+ goBack();
+ }
+ }, [canGoBack, goBack]);
const dispatch = useDispatch();
const { width: deviceWidth, height: deviceHeight } = useDimensions();
- const { replace, navigate, getState: dangerouslyGetState, goBack } = useNavigation();
const [validPassword, setValidPassword] = useState(false);
const [incorrectPassword, setIncorrectPassword] = useState(false);
- const [password, setPassword] = useState('');
const passwordRef = useRef(null);
const initializeWallet = useInitializeWallet();
useEffect(() => {
const fetchPasswordIfPossible = async () => {
- const pwd = await fetchBackupPassword();
+ const pwd = await getLocalBackupPassword();
if (pwd) {
- setPassword(pwd);
+ backupsStore.getState().setStoredPassword(pwd);
+ backupsStore.getState().setPassword(pwd);
}
};
fetchPasswordIfPossible();
@@ -118,35 +133,42 @@ export default function RestoreCloudStep() {
}, [incorrectPassword, password]);
const onPasswordChange = useCallback(({ nativeEvent: { text: inputText } }: { nativeEvent: { text: string } }) => {
- setPassword(inputText);
+ backupsStore.getState().setPassword(inputText);
setIncorrectPassword(false);
}, []);
const onSubmit = useCallback(async () => {
- setLoading(true);
+ // NOTE: Localizing password to prevent an empty string from being saved if we re-render
+ const pwd = password.trim();
+ let filename = selectedBackup.name;
+
+ const prevWalletsState = await dispatch(walletsLoadState());
+
try {
if (!selectedBackup.name) {
throw new Error('No backup file selected');
}
- const prevWalletsState = await dispatch(walletsLoadState());
-
+ walletLoadingStore.setState({
+ loadingState: WalletLoadingStates.RESTORING_WALLET,
+ });
const status = await restoreCloudBackup({
- password,
- userData,
- nameOfSelectedBackupFile: selectedBackup.name,
+ password: pwd,
+ backupFilename: filename,
});
-
if (status === RestoreCloudBackupResultStates.success) {
// Store it in the keychain in case it was missing
- const hasSavedPassword = await getLocalBackupPassword();
- if (!hasSavedPassword) {
- await saveLocalBackupPassword(password);
+ if (backupsStore.getState().storedPassword !== pwd) {
+ await saveLocalBackupPassword(pwd);
+ }
+
+ // Reset the storedPassword state for next restoration process
+ if (backupsStore.getState().storedPassword) {
+ backupsStore.getState().setStoredPassword('');
}
InteractionManager.runAfterInteractions(async () => {
const newWalletsState = await dispatch(walletsLoadState());
- let filename = selectedBackup.name;
if (IS_ANDROID && filename) {
filename = normalizeAndroidBackupFilename(filename);
}
@@ -188,14 +210,21 @@ export default function RestoreCloudStep() {
const p2 = dispatch(addressSetSelected(firstAddress));
await Promise.all([p1, p2]);
await initializeWallet(null, null, null, false, false, null, true, null);
-
- const operation = dangerouslyGetState()?.index === 1 ? navigate : replace;
- operation(Routes.SWIPE_LAYOUT, {
- screen: Routes.WALLET_SCREEN,
- });
-
- setLoading(false);
});
+
+ onRestoreSuccess();
+ backupsStore.getState().setPassword('');
+ if (isEmpty(prevWalletsState)) {
+ Navigation.handleAction(
+ Routes.SWIPE_LAYOUT,
+ {
+ screen: Routes.WALLET_SCREEN,
+ },
+ true
+ );
+ } else {
+ Navigation.handleAction(Routes.WALLET_SCREEN, {});
+ }
} else {
switch (status) {
case RestoreCloudBackupResultStates.incorrectPassword:
@@ -211,18 +240,17 @@ export default function RestoreCloudStep() {
}
} catch (e) {
Alert.alert(lang.t('back_up.restore_cloud.error_while_restoring'));
+ } finally {
+ walletLoadingStore.setState({
+ loadingState: null,
+ });
}
-
- setLoading(false);
- }, [selectedBackup.name, password, userData, dispatch, initializeWallet, dangerouslyGetState, navigate, replace]);
+ }, [password, selectedBackup.name, dispatch, onRestoreSuccess, initializeWallet]);
const onPasswordSubmit = useCallback(() => {
validPassword && onSubmit();
}, [onSubmit, validPassword]);
- const isPasswordValid =
- (password !== '' && password.length < cloudBackupPasswordMinLength && !passwordRef?.current?.isFocused()) || incorrectPassword;
-
return (
@@ -248,8 +276,8 @@ export default function RestoreCloudStep() {
;
};
};
-export type useCreateBackupStateType = 'none' | 'loading' | 'success' | 'error';
+type ConfirmBackupProps = {
+ password: string;
+} & UseCreateBackupProps;
-export enum BackupTypes {
- Single = 'single',
- All = 'all',
-}
-
-export const useCreateBackup = ({ walletId, navigateToRoute }: UseCreateBackupProps) => {
+export const useCreateBackup = () => {
const dispatch = useDispatch();
const { navigate } = useNavigation();
- const { fetchBackups } = useCloudBackups();
const walletCloudBackup = useWalletCloudBackup();
const { wallets } = useWallets();
- const latestBackup = useMemo(() => findLatestBackUp(wallets), [wallets]);
- const [loading, setLoading] = useState('none');
-
- const [password, setPassword] = useState('');
const setLoadingStateWithTimeout = useCallback(
- (state: useCreateBackupStateType, resetInMS = 2500) => {
- setLoading(state);
+ ({ state, outOfSync = false, failInMs = 10_000 }: { state: CloudBackupState; outOfSync?: boolean; failInMs?: number }) => {
+ backupsStore.getState().setStatus(state);
+ if (outOfSync) {
+ setTimeout(() => {
+ backupsStore.getState().setStatus(CloudBackupState.Syncing);
+ }, 1_000);
+ }
setTimeout(() => {
- setLoading('none');
- }, resetInMS);
+ const currentState = backupsStore.getState().status;
+ if (currentState === state) {
+ backupsStore.getState().setStatus(CloudBackupState.Ready);
+ }
+ }, failInMs);
},
- [setLoading]
+ []
+ );
+
+ const onSuccess = useCallback(
+ async (password: string) => {
+ if (backupsStore.getState().storedPassword !== password) {
+ await saveLocalBackupPassword(password);
+ }
+ // Reset the storedPassword state for next backup
+ backupsStore.getState().setStoredPassword('');
+ analytics.track('Backup Complete', {
+ category: 'backup',
+ label: cloudPlatform,
+ });
+ setLoadingStateWithTimeout({
+ state: CloudBackupState.Success,
+ outOfSync: true,
+ });
+ backupsStore.getState().syncAndFetchBackups();
+ },
+ [setLoadingStateWithTimeout]
);
- const onSuccess = useCallback(async () => {
- const hasSavedPassword = await getLocalBackupPassword();
- if (!hasSavedPassword) {
- await saveLocalBackupPassword(password);
- }
- analytics.track('Backup Complete', {
- category: 'backup',
- label: cloudPlatform,
- });
- setLoadingStateWithTimeout('success');
- fetchBackups();
- }, [setLoadingStateWithTimeout, fetchBackups, password]);
const onError = useCallback(
- (msg: string) => {
+ (msg: string, isDamaged?: boolean) => {
InteractionManager.runAfterInteractions(async () => {
- DelayedAlert({ title: msg }, 500);
- setLoadingStateWithTimeout('error', 5000);
+ if (isDamaged) {
+ showWalletErrorAlert();
+ } else {
+ DelayedAlert({ title: msg }, 500);
+ }
+ setLoadingStateWithTimeout({ state: CloudBackupState.Error });
});
},
[setLoadingStateWithTimeout]
);
const onConfirmBackup = useCallback(
- async ({ password, type }: { password: string; type: BackupTypes }) => {
+ async ({ password, walletId, navigateToRoute }: ConfirmBackupProps) => {
analytics.track('Tapped "Confirm Backup"');
- setLoading('loading');
+ backupsStore.getState().setStatus(CloudBackupState.InProgress);
- if (type === BackupTypes.All) {
+ if (typeof walletId === 'undefined') {
if (!wallets) {
- onError('Error loading wallets. Please try again.');
- setLoading('error');
+ onError(i18n.t(i18n.l.back_up.errors.no_keys_found));
+ backupsStore.getState().setStatus(CloudBackupState.Error);
+ return;
+ }
+
+ const validWallets = Object.fromEntries(Object.entries(wallets).filter(([_, wallet]) => !wallet.damaged));
+ if (Object.keys(validWallets).length === 0) {
+ onError(i18n.t(i18n.l.back_up.errors.no_keys_found), true);
+ backupsStore.getState().setStatus(CloudBackupState.Error);
return;
}
+
backupAllWalletsToCloud({
- wallets: wallets as AllRainbowWallets,
+ wallets: validWallets,
password,
- latestBackup,
onError,
onSuccess,
dispatch,
@@ -94,12 +114,6 @@ export const useCreateBackup = ({ walletId, navigateToRoute }: UseCreateBackupPr
return;
}
- if (!walletId) {
- onError('Wallet not found. Please try again.');
- setLoading('error');
- return;
- }
-
await walletCloudBackup({
onError,
onSuccess,
@@ -111,13 +125,13 @@ export const useCreateBackup = ({ walletId, navigateToRoute }: UseCreateBackupPr
navigate(navigateToRoute.route, navigateToRoute.params || {});
}
},
- [walletId, walletCloudBackup, onError, onSuccess, navigateToRoute, wallets, latestBackup, dispatch, navigate]
+ [walletCloudBackup, onError, wallets, onSuccess, dispatch, navigate]
);
- const getPassword = useCallback(async (): Promise => {
+ const getPassword = useCallback(async (props: UseCreateBackupProps): Promise => {
const password = await getLocalBackupPassword();
if (password) {
- setPassword(password);
+ backupsStore.getState().setStoredPassword(password);
return password;
}
@@ -126,32 +140,36 @@ export const useCreateBackup = ({ walletId, navigateToRoute }: UseCreateBackupPr
nativeScreen: true,
step: walletBackupStepTypes.backup_cloud,
onSuccess: async (password: string) => {
- setPassword(password);
- resolve(password);
+ return resolve(password);
},
onCancel: async () => {
- resolve(null);
+ return resolve(null);
},
- walletId,
+ ...props,
});
});
- }, [walletId]);
+ }, []);
- const onSubmit = useCallback(
- async ({ type = BackupTypes.Single }: { type?: BackupTypes }) => {
- const password = await getPassword();
- if (password) {
- onConfirmBackup({
- password,
- type,
+ const createBackup = useCallback(
+ async (props: UseCreateBackupProps) => {
+ if (backupsStore.getState().status !== CloudBackupState.Ready) {
+ return false;
+ }
+ const password = await getPassword(props);
+ if (!password) {
+ setLoadingStateWithTimeout({
+ state: CloudBackupState.Ready,
});
- return true;
+ return false;
}
- setLoadingStateWithTimeout('error');
- return false;
+ onConfirmBackup({
+ password,
+ ...props,
+ });
+ return true;
},
[getPassword, onConfirmBackup, setLoadingStateWithTimeout]
);
- return { onSuccess, onError, onSubmit, loading };
+ return createBackup;
};
diff --git a/src/components/cards/OpRewardsCard.tsx b/src/components/cards/OpRewardsCard.tsx
index 3c1251010b3..b823be5e198 100644
--- a/src/components/cards/OpRewardsCard.tsx
+++ b/src/components/cards/OpRewardsCard.tsx
@@ -1,14 +1,14 @@
import React from 'react';
import { GenericCard, Gradient } from './GenericCard';
-import { AccentColorProvider, Box, Cover, globalColors, Stack, Text } from '@/design-system';
+import { AccentColorProvider, Box, Cover, globalColors, Stack, Text, useColorMode } from '@/design-system';
import { ButtonPressAnimation } from '@/components/animations';
-import { Image } from 'react-native';
+import { ImageBackground } from 'react-native';
import OpRewardsCardBackgroundImage from '../../assets/opRewardsCardBackgroundImage.png';
import * as i18n from '@/languages';
import { useNavigation } from '@/navigation';
import Routes from '@/navigation/routesNames';
-import { colors } from '@/styles';
import { ChainId } from '@/state/backendNetworks/types';
+import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
const GRADIENT: Gradient = {
colors: ['#520907', '#B22824'],
@@ -18,17 +18,20 @@ const GRADIENT: Gradient = {
export const OpRewardsCard: React.FC = () => {
const { navigate } = useNavigation();
+ const { isDarkMode } = useColorMode();
+
+ const color = useBackendNetworksStore.getState().getColorsForChainId(ChainId.optimism, isDarkMode);
const navigateToRewardsSheet = () => {
navigate(Routes.OP_REWARDS_SHEET);
};
return (
-
+
{
-
+
{i18n.t(i18n.l.discover.op_rewards.button_title)}
diff --git a/src/components/coin-icon/ChainBadge.js b/src/components/coin-icon/ChainBadge.js
index 8babe5015a6..2dbd8061cf2 100644
--- a/src/components/coin-icon/ChainBadge.js
+++ b/src/components/coin-icon/ChainBadge.js
@@ -29,22 +29,22 @@ import DegenBadge from '@/assets/badges/degenBadge.png';
import DegenBadgeDark from '@/assets/badges/degenBadgeDark.png';
import DegenBadgeLarge from '@/assets/badges/degenBadgeLarge.png';
import DegenBadgeLargeDark from '@/assets/badges/degenBadgeLargeDark.png';
-// import GnosisBadge from '@/assets/badges/gnosisBadge.png';
-// import GnosisBadgeDark from '@/assets/badges/gnosisBadgeDark.png';
-// import GnosisBadgeLarge from '@/assets/badges/gnosisBadgeLarge.png';
-// import GnosisBadgeLargeDark from '@/assets/badges/gnosisBadgeLargeDark.png';
-// import GravityBadge from '@/assets/badges/gravityBadge.png';
-// import GravityBadgeDark from '@/assets/badges/gravityBadgeDark.png';
-// import GravityBadgeLarge from '@/assets/badges/gravityBadgeLarge.png';
-// import GravityBadgeLargeDark from '@/assets/badges/gravityBadgeLargeDark.png';
+import GnosisBadge from '@/assets/badges/gnosisBadge.png';
+import GnosisBadgeDark from '@/assets/badges/gnosisBadgeDark.png';
+import GnosisBadgeLarge from '@/assets/badges/gnosisBadgeLarge.png';
+import GnosisBadgeLargeDark from '@/assets/badges/gnosisBadgeLargeDark.png';
+import GravityBadge from '@/assets/badges/gravityBadge.png';
+import GravityBadgeDark from '@/assets/badges/gravityBadgeDark.png';
+import GravityBadgeLarge from '@/assets/badges/gravityBadgeLarge.png';
+import GravityBadgeLargeDark from '@/assets/badges/gravityBadgeLargeDark.png';
import InkBadge from '@/assets/badges/inkBadge.png';
import InkBadgeDark from '@/assets/badges/inkBadgeDark.png';
import InkBadgeLarge from '@/assets/badges/inkBadgeLarge.png';
import InkBadgeLargeDark from '@/assets/badges/inkBadgeLargeDark.png';
-// import LineaBadge from '@/assets/badges/lineaBadge.png';
-// import LineaBadgeDark from '@/assets/badges/lineaBadgeDark.png';
-// import LineaBadgeLarge from '@/assets/badges/lineaBadgeLarge.png';
-// import LineaBadgeLargeDark from '@/assets/badges/lineaBadgeLargeDark.png';
+import LineaBadge from '@/assets/badges/lineaBadge.png';
+import LineaBadgeDark from '@/assets/badges/lineaBadgeDark.png';
+import LineaBadgeLarge from '@/assets/badges/lineaBadgeLarge.png';
+import LineaBadgeLargeDark from '@/assets/badges/lineaBadgeLargeDark.png';
import OptimismBadge from '@/assets/badges/optimismBadge.png';
import OptimismBadgeDark from '@/assets/badges/optimismBadgeDark.png';
import OptimismBadgeLarge from '@/assets/badges/optimismBadgeLarge.png';
@@ -53,18 +53,17 @@ import PolygonBadge from '@/assets/badges/polygonBadge.png';
import PolygonBadgeDark from '@/assets/badges/polygonBadgeDark.png';
import PolygonBadgeLarge from '@/assets/badges/polygonBadgeLarge.png';
import PolygonBadgeLargeDark from '@/assets/badges/polygonBadgeLargeDark.png';
-// import SankoBadge from '@/assets/badges/sankoBadge.png';
-// import SankoBadgeDark from '@/assets/badges/sankoBadgeDark.png';
-// import SankoBadgeLarge from '@/assets/badges/sankoBadgeLarge.png';
-// import SankoBadgeLargeDark from '@/assets/badges/sankoBadgeLargeDark.png';
-// import ScrollBadge from '@/assets/badges/scrollBadge.png';
-// import ScrollBadgeDark from '@/assets/badges/scrollBadgeDark.png';
-// import ScrollBadgeLarge from '@/assets/badges/scrollBadgeLarge.png';
-// import ScrollBadgeLargeDark from '@/assets/badges/scrollBadgeLargeDark.png';
-// import ZksyncBadge from '@/assets/badges/zksyncBadge.png';
-// import ZksyncBadgeDark from '@/assets/badges/zksyncBadgeDark.png';
-// import ZksyncBadgeLarge from '@/assets/badges/zksyncBadgeLarge.png';
-// import ZksyncBadgeLargeDark from '@/assets/badges/zksyncBadgeLargeDark.png';
+import SankoBadge from '@/assets/badges/sankoBadge.png';
+import SankoBadgeLarge from '@/assets/badges/sankoBadgeLarge.png';
+import SankoBadgeLargeDark from '@/assets/badges/sankoBadgeLargeDark.png';
+import ScrollBadge from '@/assets/badges/scrollBadge.png';
+import ScrollBadgeDark from '@/assets/badges/scrollBadgeDark.png';
+import ScrollBadgeLarge from '@/assets/badges/scrollBadgeLarge.png';
+import ScrollBadgeLargeDark from '@/assets/badges/scrollBadgeLargeDark.png';
+import ZksyncBadge from '@/assets/badges/zkSyncBadge.png';
+import ZksyncBadgeDark from '@/assets/badges/zksyncBadgeDark.png';
+import ZksyncBadgeLarge from '@/assets/badges/zksyncBadgeLarge.png';
+import ZksyncBadgeLargeDark from '@/assets/badges/zksyncBadgeLargeDark.png';
import ZoraBadge from '@/assets/badges/zoraBadge.png';
import ZoraBadgeDark from '@/assets/badges/zoraBadgeDark.png';
import ZoraBadgeLarge from '@/assets/badges/zoraBadgeLarge.png';
@@ -124,24 +123,24 @@ export default function ChainBadge({
val = isDarkMode ? BscBadgeLargeDark : BscBadgeLarge;
} else if (chainId === ChainId.degen) {
val = isDarkMode ? DegenBadgeLargeDark : DegenBadgeLarge;
- // } else if (chainId === ChainId.gnosis) {
- // val = isDarkMode ? GnosisBadgeLargeDark : GnosisBadgeLarge;
- // } else if (chainId === ChainId.gravity) {
- // val = isDarkMode ? GravityBadgeLargeDark : GravityBadgeLarge;
+ } else if (chainId === ChainId.gnosis) {
+ val = isDarkMode ? GnosisBadgeLargeDark : GnosisBadgeLarge;
+ } else if (chainId === ChainId.gravity) {
+ val = isDarkMode ? GravityBadgeLargeDark : GravityBadgeLarge;
} else if (chainId === ChainId.ink) {
val = isDarkMode ? InkBadgeLargeDark : InkBadgeLarge;
- // } else if (chainId === ChainId.linea) {
- // val = isDarkMode ? LineaBadgeLargeDark : LineaBadgeLarge;
+ } else if (chainId === ChainId.linea) {
+ val = isDarkMode ? LineaBadgeLargeDark : LineaBadgeLarge;
} else if (chainId === ChainId.optimism) {
val = isDarkMode ? OptimismBadgeLargeDark : OptimismBadgeLarge;
} else if (chainId === ChainId.polygon) {
val = isDarkMode ? PolygonBadgeLargeDark : PolygonBadgeLarge;
- // } else if (chainId === ChainId.sanko) {
- // val = isDarkMode ? SankoBadgeLargeDark : SankoBadgeLarge;
- // } else if (chainId === ChainId.scroll) {
- // val = isDarkMode ? ScrollBadgeLargeDark : ScrollBadgeLarge;
- // } else if (chainId === ChainId.zksync) {
- // val = isDarkMode ? ZksyncBadgeLargeDark : ZksyncBadgeLarge;
+ } else if (chainId === ChainId.sanko) {
+ val = isDarkMode ? SankoBadgeLargeDark : SankoBadgeLarge;
+ } else if (chainId === ChainId.scroll) {
+ val = isDarkMode ? ScrollBadgeLargeDark : ScrollBadgeLarge;
+ } else if (chainId === ChainId.zksync) {
+ val = isDarkMode ? ZksyncBadgeLargeDark : ZksyncBadgeLarge;
} else if (chainId === ChainId.zora) {
val = isDarkMode ? ZoraBadgeLargeDark : ZoraBadgeLarge;
}
@@ -160,24 +159,24 @@ export default function ChainBadge({
val = isDarkMode ? BscBadgeDark : BscBadge;
} else if (chainId === ChainId.degen) {
val = isDarkMode ? DegenBadgeDark : DegenBadge;
- // } else if (chainId === ChainId.gnosis) {
- // val = isDarkMode ? GnosisBadgeDark : GnosisBadge;
- // } else if (chainId === ChainId.gravity) {
- // val = isDarkMode ? GravityBadgeDark : GravityBadge;
+ } else if (chainId === ChainId.gnosis) {
+ val = isDarkMode ? GnosisBadgeDark : GnosisBadge;
+ } else if (chainId === ChainId.gravity) {
+ val = isDarkMode ? GravityBadgeDark : GravityBadge;
} else if (chainId === ChainId.ink) {
val = isDarkMode ? InkBadgeDark : InkBadge;
- // } else if (chainId === ChainId.linea) {
- // val = isDarkMode ? LineaBadgeDark : LineaBadge;
+ } else if (chainId === ChainId.linea) {
+ val = isDarkMode ? LineaBadgeDark : LineaBadge;
} else if (chainId === ChainId.optimism) {
val = isDarkMode ? OptimismBadgeDark : OptimismBadge;
} else if (chainId === ChainId.polygon) {
val = isDarkMode ? PolygonBadgeDark : PolygonBadge;
- // } else if (chainId === ChainId.sanko) {
- // val = isDarkMode ? SankoBadgeDark : SankoBadge;
- // } else if (chainId === ChainId.scroll) {
- // val = isDarkMode ? ScrollBadgeDark : ScrollBadge;
- // } else if (chainId === ChainId.zksync) {
- // val = isDarkMode ? ZksyncBadgeDark : ZksyncBadge;
+ } else if (chainId === ChainId.sanko) {
+ val = SankoBadge;
+ } else if (chainId === ChainId.scroll) {
+ val = isDarkMode ? ScrollBadgeDark : ScrollBadge;
+ } else if (chainId === ChainId.zksync) {
+ val = isDarkMode ? ZksyncBadgeDark : ZksyncBadge;
} else if (chainId === ChainId.zora) {
val = isDarkMode ? ZoraBadgeDark : ZoraBadge;
}
diff --git a/src/components/coin-icon/ChainImage.tsx b/src/components/coin-icon/ChainImage.tsx
index 1aa5c479b8e..90f8ffbc960 100644
--- a/src/components/coin-icon/ChainImage.tsx
+++ b/src/components/coin-icon/ChainImage.tsx
@@ -1,4 +1,4 @@
-import React, { useMemo } from 'react';
+import React, { useMemo, forwardRef } from 'react';
import { ChainId } from '@/state/backendNetworks/types';
import ApechainBadge from '@/assets/badges/apechain.png';
@@ -9,20 +9,31 @@ import BlastBadge from '@/assets/badges/blast.png';
import BscBadge from '@/assets/badges/bsc.png';
import DegenBadge from '@/assets/badges/degen.png';
import EthereumBadge from '@/assets/badges/ethereum.png';
-// import GnosisBadge from '@/assets/badges/gnosis.png';
-// import GravityBadge from '@/assets/badges/gravity.png';
+import GnosisBadge from '@/assets/badges/gnosis.png';
+import GravityBadge from '@/assets/badges/gravity.png';
import InkBadge from '@/assets/badges/ink.png';
-// import LineaBadge from '@/assets/badges/linea.png';
+import LineaBadge from '@/assets/badges/linea.png';
import OptimismBadge from '@/assets/badges/optimism.png';
import PolygonBadge from '@/assets/badges/polygon.png';
-// import SankoBadge from '@/assets/badges/sanko.png';
-// import ScrollBadge from '@/assets/badges/scroll.png';
-// import ZksyncBadge from '@/assets/badges/zksync.png';
+import SankoBadge from '@/assets/badges/sanko.png';
+import ScrollBadge from '@/assets/badges/scroll.png';
+import ZksyncBadge from '@/assets/badges/zksync.png';
import ZoraBadge from '@/assets/badges/zora.png';
+import FastImage, { FastImageProps, Source } from 'react-native-fast-image';
+import Animated from 'react-native-reanimated';
-import FastImage, { Source } from 'react-native-fast-image';
-
-export function ChainImage({ chainId, size = 20 }: { chainId: ChainId | null | undefined; size?: number }) {
+export const ChainImage = forwardRef(function ChainImage(
+ {
+ chainId,
+ size = 20,
+ style,
+ }: {
+ chainId: ChainId | null | undefined;
+ size?: number;
+ style?: FastImageProps['style'];
+ },
+ ref
+) {
const source = useMemo(() => {
switch (chainId) {
case ChainId.apechain:
@@ -39,26 +50,26 @@ export function ChainImage({ chainId, size = 20 }: { chainId: ChainId | null | u
return BscBadge;
case ChainId.degen:
return DegenBadge;
- // case ChainId.gnosis:
- // return GnosisBadge;
- // case ChainId.gravity:
- // return GravityBadge;
+ case ChainId.gnosis:
+ return GnosisBadge;
+ case ChainId.gravity:
+ return GravityBadge;
case ChainId.ink:
return InkBadge;
- // case ChainId.linea:
- // return LineaBadge;
+ case ChainId.linea:
+ return LineaBadge;
case ChainId.mainnet:
return EthereumBadge;
case ChainId.optimism:
return OptimismBadge;
case ChainId.polygon:
return PolygonBadge;
- // case ChainId.sanko:
- // return SankoBadge;
- // case ChainId.scroll:
- // return ScrollBadge;
- // case ChainId.zksync:
- // return ZksyncBadge;
+ case ChainId.sanko:
+ return SankoBadge;
+ case ChainId.scroll:
+ return ScrollBadge;
+ case ChainId.zksync:
+ return ZksyncBadge;
case ChainId.zora:
return ZoraBadge;
default:
@@ -69,6 +80,12 @@ export function ChainImage({ chainId, size = 20 }: { chainId: ChainId | null | u
if (!chainId) return null;
return (
-
+
);
-}
+});
diff --git a/src/components/coin-row/CoinRowAddButton.js b/src/components/coin-row/CoinRowAddButton.js
index 2b602ef8363..a96d17e26ed 100644
--- a/src/components/coin-row/CoinRowAddButton.js
+++ b/src/components/coin-row/CoinRowAddButton.js
@@ -1,6 +1,5 @@
import React from 'react';
import { View } from 'react-native';
-import { IS_TESTING } from 'react-native-dotenv';
import RadialGradient from 'react-native-radial-gradient';
import { ButtonPressAnimation } from '../animations';
import { Centered } from '../layout';
@@ -9,6 +8,7 @@ import { CoinRowHeight } from './CoinRow';
import styled from '@/styled-thing';
import { padding } from '@/styles';
import { magicMemo } from '@/utils';
+import { IS_TEST } from '@/env';
const AddButtonPadding = 19;
@@ -23,7 +23,7 @@ const AddButton = styled(Centered)({
width: 68,
});
-const Circle = styled(IS_TESTING === 'true' ? View : RadialGradient).attrs(({ theme: { colors } }) => ({
+const Circle = styled(IS_TEST ? View : RadialGradient).attrs(({ theme: { colors } }) => ({
center: [0, 15],
colors: colors.gradients.lightestGrey,
}))({
diff --git a/src/components/coin-row/CoinRowFavoriteButton.js b/src/components/coin-row/CoinRowFavoriteButton.js
index 303b2efd735..007434274c5 100644
--- a/src/components/coin-row/CoinRowFavoriteButton.js
+++ b/src/components/coin-row/CoinRowFavoriteButton.js
@@ -1,6 +1,5 @@
import React from 'react';
import { View } from 'react-native';
-import { IS_TESTING } from 'react-native-dotenv';
import { BaseButton } from 'react-native-gesture-handler';
import RadialGradient from 'react-native-radial-gradient';
import { useTheme } from '../../theme/ThemeContext';
@@ -10,6 +9,7 @@ import { CoinRowHeight } from './CoinRow';
import styled from '@/styled-thing';
import { padding } from '@/styles';
import { magicMemo } from '@/utils';
+import { IS_TEST } from '@/env';
const FavoriteButtonPadding = 19;
@@ -24,7 +24,7 @@ const FavoriteButton = styled(Centered)({
width: 68,
});
-const Circle = styled(IS_TESTING === 'true' ? View : RadialGradient).attrs(({ isFavorited, theme: { colors, isDarkMode } }) => ({
+const Circle = styled(IS_TEST ? View : RadialGradient).attrs(({ isFavorited, theme: { colors, isDarkMode } }) => ({
center: [0, 15],
colors: isFavorited
? [colors.alpha('#FFB200', isDarkMode ? 0.15 : 0), colors.alpha('#FFB200', isDarkMode ? 0.05 : 0.2)]
diff --git a/src/components/ens-registration/RegistrationAvatar/RegistrationAvatar.tsx b/src/components/ens-registration/RegistrationAvatar/RegistrationAvatar.tsx
index 603f8258d6c..80a2afcfb47 100644
--- a/src/components/ens-registration/RegistrationAvatar/RegistrationAvatar.tsx
+++ b/src/components/ens-registration/RegistrationAvatar/RegistrationAvatar.tsx
@@ -1,6 +1,5 @@
import ConditionalWrap from 'conditional-wrap';
import React, { useCallback, useEffect, useState } from 'react';
-import { IS_TESTING } from 'react-native-dotenv';
import { Image } from 'react-native-image-crop-picker';
import { atom, useSetRecoilState } from 'recoil';
import ButtonPressAnimation from '../../animations/ButtonPressAnimation';
@@ -13,6 +12,7 @@ import { useENSModifiedRegistration, useENSRegistration, useENSRegistrationForm,
import { ImgixImage } from '@/components/images';
import { magicMemo, stringifyENSNFTRecord } from '@/utils';
import { ENS_RECORDS } from '@/helpers/ens';
+import { IS_TEST } from '@/env';
export const avatarMetadataAtom = atom({
default: undefined,
@@ -20,7 +20,6 @@ export const avatarMetadataAtom = atom({
});
const size = 70;
-const isTesting = IS_TESTING === 'true';
const RegistrationAvatar = ({
hasSeenExplainSheet,
@@ -131,11 +130,11 @@ const RegistrationAvatar = ({
) : (
{children}}
>
diff --git a/src/components/expanded-state/UniqueTokenExpandedState.tsx b/src/components/expanded-state/UniqueTokenExpandedState.tsx
index a321d1d8ac4..136f1e1affa 100644
--- a/src/components/expanded-state/UniqueTokenExpandedState.tsx
+++ b/src/components/expanded-state/UniqueTokenExpandedState.tsx
@@ -417,18 +417,17 @@ const UniqueTokenExpandedState = ({ asset: passedAsset, external }: UniqueTokenE
const hideNftMarketplaceAction = isPoap || !slug;
- const mountedAt = useRef(Date.now());
useTimeoutEffect(
- () => {
+ ({ elapsedTime }) => {
const { address, chainId } = getAddressAndChainIdFromUniqueId(uniqueId);
const { name, description, image_url } = asset;
analyticsV2.track(analyticsV2.event.tokenDetailsNFT, {
- eventSentAfterMs: Date.now() - mountedAt.current,
+ eventSentAfterMs: elapsedTime,
token: { isPoap, isParty: !!isParty, isENS, address, chainId, name, image_url },
available_data: { description: !!description, image_url: !!image_url, floorPrice: !!offer?.floorPrice },
});
},
- 5 * 1000 // 5s
+ { timeout: 5 * 1000 }
);
return (
<>
diff --git a/src/components/expanded-state/asset/ChartExpandedState.js b/src/components/expanded-state/asset/ChartExpandedState.js
index d53e0308b0c..438f8a5c966 100644
--- a/src/components/expanded-state/asset/ChartExpandedState.js
+++ b/src/components/expanded-state/asset/ChartExpandedState.js
@@ -257,17 +257,16 @@ export default function ChartExpandedState({ asset }) {
[nativeCurrency]
);
- const mountedAt = useRef(Date.now());
useTimeoutEffect(
- () => {
+ ({ elapsedTime }) => {
const { address, chainId, symbol, name, icon_url, price } = assetWithPrice;
analyticsV2.track(analyticsV2.event.tokenDetailsErc20, {
- eventSentAfterMs: Date.now() - mountedAt.current,
+ eventSentAfterMs: elapsedTime,
token: { address, chainId, symbol, name, icon_url, price },
available_data: { chart: showChart, description: !!data?.description, iconUrl: !!icon_url },
});
},
- 5 * 1000 // 5s
+ { timeout: 5 * 1000 }
);
return (
diff --git a/src/components/fields/PasswordField.tsx b/src/components/fields/PasswordField.tsx
index 0925b29862c..6d28e81e802 100644
--- a/src/components/fields/PasswordField.tsx
+++ b/src/components/fields/PasswordField.tsx
@@ -1,14 +1,37 @@
import React, { forwardRef, useCallback, Ref } from 'react';
-import { useTheme } from '../../theme/ThemeContext';
+import { ThemeContextProps, useTheme } from '../../theme/ThemeContext';
import { Input } from '../inputs';
import { cloudBackupPasswordMinLength } from '@/handlers/cloudBackup';
import { useDimensions } from '@/hooks';
import styled from '@/styled-thing';
-import { padding } from '@/styles';
+import { padding, position } from '@/styles';
import ShadowStack from '@/react-native-shadow-stack';
import { Box } from '@/design-system';
import { TextInput, TextInputProps, View } from 'react-native';
import { IS_IOS, IS_ANDROID } from '@/env';
+import { Icon } from '../icons';
+
+const FieldAccessoryBadgeSize = 22;
+const FieldAccessoryBadgeWrapper = styled(ShadowStack).attrs(
+ ({ theme: { colors, isDarkMode }, color }: { theme: ThemeContextProps; color: string }) => ({
+ ...position.sizeAsObject(FieldAccessoryBadgeSize),
+ borderRadius: FieldAccessoryBadgeSize,
+ shadows: [[0, 4, 12, isDarkMode ? colors.shadow : color, isDarkMode ? 0.1 : 0.4]],
+ })
+)({
+ marginBottom: 12,
+ position: 'absolute',
+ right: 12,
+ top: 12,
+});
+
+function FieldAccessoryBadge({ color, name }: { color: string; name: string }) {
+ return (
+
+
+
+ );
+}
const Container = styled(Box)({
width: '100%',
@@ -53,9 +76,9 @@ interface PasswordFieldProps extends TextInputProps {
}
const PasswordField = forwardRef(
- ({ password, returnKeyType = 'done', style, textContentType, ...props }, ref: Ref) => {
+ ({ password, isInvalid, returnKeyType = 'done', style, textContentType, ...props }, ref: Ref) => {
const { width: deviceWidth } = useDimensions();
- const { isDarkMode } = useTheme();
+ const { isDarkMode, colors } = useTheme();
const handleFocus = useCallback(() => {
if (ref && 'current' in ref && ref.current) {
@@ -67,6 +90,7 @@ const PasswordField = forwardRef(
+ {isInvalid && }
);
diff --git a/src/components/floating-emojis/FloatingEmojis.js b/src/components/floating-emojis/FloatingEmojis.js
deleted file mode 100644
index f4dc8342f50..00000000000
--- a/src/components/floating-emojis/FloatingEmojis.js
+++ /dev/null
@@ -1,156 +0,0 @@
-import PropTypes from 'prop-types';
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import { Animated, View } from 'react-native';
-import FloatingEmoji from './FloatingEmoji';
-import GravityEmoji from './GravityEmoji';
-import { useTimeout } from '@/hooks';
-import { position } from '@/styles';
-
-const EMPTY_ARRAY = [];
-const getEmoji = emojis => Math.floor(Math.random() * emojis.length);
-const getRandomNumber = (min, max) => Math.random() * (max - min) + min;
-
-const FloatingEmojis = ({
- centerVertically,
- children,
- disableHorizontalMovement,
- disableRainbow,
- disableVerticalMovement,
- distance,
- duration,
- emojis,
- fadeOut,
- gravityEnabled,
- marginTop,
- opacity,
- opacityThreshold,
- range,
- scaleTo,
- setOnNewEmoji,
- size,
- wiggleFactor,
- ...props
-}) => {
- const emojisArray = useMemo(() => (Array.isArray(emojis) ? emojis : [emojis]), [emojis]);
- const [floatingEmojis, setEmojis] = useState(EMPTY_ARRAY);
- const [startTimeout, stopTimeout] = useTimeout();
- const clearEmojis = useCallback(() => setEmojis(EMPTY_ARRAY), []);
-
- // 🚧️ TODO: 🚧️
- // Clear emojis if page navigatorPosition falls below 0.93 (which we should call like `pageTransitionThreshold` or something)
- // otherwise, the FloatingEmojis look weird during stack transitions
-
- const onNewEmoji = useCallback(
- (x, y) => {
- // Set timeout to automatically clearEmojis after the latest one has finished animating
- stopTimeout();
- startTimeout(clearEmojis, duration * 1.1);
-
- setEmojis(existingEmojis => {
- const newEmoji = {
- // if a user has smashed the button 7 times, they deserve a 🌈 rainbow
- emojiToRender:
- (existingEmojis.length + 1) % 7 === 0 && !disableRainbow
- ? 'rainbow'
- : emojisArray.length === 1
- ? emojisArray[0]
- : emojisArray[getEmoji(emojisArray)],
- x: x ? x - getRandomNumber(-20, 20) : getRandomNumber(...range),
- y: y || 0,
- };
- return [...existingEmojis, newEmoji];
- });
- },
- [clearEmojis, disableRainbow, duration, emojisArray, range, startTimeout, stopTimeout]
- );
-
- useEffect(() => {
- setOnNewEmoji?.(onNewEmoji);
- return () => setOnNewEmoji?.(undefined);
- }, [setOnNewEmoji, onNewEmoji]);
-
- return (
-
- {typeof children === 'function' ? children({ onNewEmoji }) : children}
-
- {gravityEnabled
- ? floatingEmojis.map(({ emojiToRender, x, y }, index) => (
-
- ))
- : floatingEmojis.map(({ emojiToRender, x, y }, index) => (
-
- ))}
-
-
- );
-};
-
-FloatingEmojis.propTypes = {
- centerVertically: PropTypes.bool,
- children: PropTypes.node,
- disableHorizontalMovement: PropTypes.bool,
- disableRainbow: PropTypes.bool,
- disableVerticalMovement: PropTypes.bool,
- distance: PropTypes.number,
- duration: PropTypes.number,
- emojis: PropTypes.arrayOf(PropTypes.string).isRequired,
- fadeOut: PropTypes.bool,
- gravityEnabled: PropTypes.bool,
- marginTop: PropTypes.number,
- opacity: PropTypes.oneOfType([PropTypes.number, PropTypes.object]),
- opacityThreshold: PropTypes.number,
- range: PropTypes.arrayOf(PropTypes.number),
- scaleTo: PropTypes.number,
- setOnNewEmoji: PropTypes.func,
- size: PropTypes.string.isRequired,
- wiggleFactor: PropTypes.number,
-};
-
-FloatingEmojis.defaultProps = {
- distance: 130,
- duration: 2000,
- // Defaults the emoji to 👍️ (thumbs up).
- // To view complete list of emojis compatible with this component,
- // head to https://github.com/muan/unicode-emoji-json/blob/master/data-by-emoji.json
- emojis: ['thumbs_up'],
- fadeOut: true,
- opacity: 1,
- range: [0, 80],
- scaleTo: 1,
- size: 30,
- wiggleFactor: 0.5,
-};
-
-export default FloatingEmojis;
diff --git a/src/components/floating-emojis/FloatingEmojis.tsx b/src/components/floating-emojis/FloatingEmojis.tsx
new file mode 100644
index 00000000000..7eccc8b69de
--- /dev/null
+++ b/src/components/floating-emojis/FloatingEmojis.tsx
@@ -0,0 +1,151 @@
+import React, { useCallback, useEffect, useMemo, useState, ReactNode } from 'react';
+import { Animated, View, ViewProps } from 'react-native';
+import FloatingEmoji from './FloatingEmoji';
+import GravityEmoji from './GravityEmoji';
+import { useTimeout } from '@/hooks';
+import { position } from '@/styles';
+import { DebugLayout } from '@/design-system';
+import { DEVICE_HEIGHT, DEVICE_WIDTH } from '@/utils/deviceUtils';
+import { AbsolutePortal } from '../AbsolutePortal';
+
+interface Emoji {
+ emojiToRender: string;
+ x: number;
+ y: number;
+}
+
+interface FloatingEmojisProps extends Omit {
+ centerVertically?: boolean;
+ children?: ReactNode | ((props: { onNewEmoji: (x?: number, y?: number) => void }) => ReactNode);
+ disableHorizontalMovement?: boolean;
+ disableRainbow?: boolean;
+ disableVerticalMovement?: boolean;
+ distance?: number;
+ duration?: number;
+ emojis: string[];
+ fadeOut?: boolean;
+ gravityEnabled?: boolean;
+ marginTop?: number;
+ opacity?: number | Animated.AnimatedInterpolation;
+ opacityThreshold?: number;
+ range?: [number, number];
+ scaleTo?: number;
+ setOnNewEmoji?: (fn: ((x?: number, y?: number) => void) | undefined) => void;
+ size: number;
+ wiggleFactor?: number;
+}
+
+const EMPTY_ARRAY: Emoji[] = [];
+const getEmoji = (emojis: string[]) => Math.floor(Math.random() * emojis.length);
+const getRandomNumber = (min: number, max: number) => Math.random() * (max - min) + min;
+
+const FloatingEmojis: React.FC = ({
+ centerVertically,
+ children,
+ disableHorizontalMovement,
+ disableRainbow,
+ disableVerticalMovement,
+ distance = 130,
+ duration = 2000,
+ emojis,
+ fadeOut = true,
+ gravityEnabled,
+ marginTop,
+ opacity = 1,
+ opacityThreshold,
+ range: [rangeMin, rangeMax] = [0, 80],
+ scaleTo = 1,
+ setOnNewEmoji,
+ size = 30,
+ wiggleFactor = 0.5,
+ style,
+ ...props
+}) => {
+ const emojisArray = useMemo(() => (Array.isArray(emojis) ? emojis : [emojis]), [emojis]);
+ const [floatingEmojis, setEmojis] = useState(EMPTY_ARRAY);
+ const [startTimeout, stopTimeout] = useTimeout();
+ const clearEmojis = useCallback(() => setEmojis(EMPTY_ARRAY), []);
+
+ // 🚧️ TODO: 🚧️
+ // Clear emojis if page navigatorPosition falls below 0.93 (which we should call like `pageTransitionThreshold` or something)
+ // otherwise, the FloatingEmojis look weird during stack transitions
+
+ const onNewEmoji = useCallback(
+ (x?: number, y?: number) => {
+ // Set timeout to automatically clearEmojis after the latest one has finished animating
+ stopTimeout();
+ startTimeout(clearEmojis, duration * 1.1);
+
+ setEmojis(existingEmojis => {
+ const newEmoji = {
+ emojiToRender:
+ (existingEmojis.length + 1) % 7 === 0 && !disableRainbow
+ ? 'rainbow'
+ : emojisArray.length === 1
+ ? emojisArray[0]
+ : emojisArray[getEmoji(emojisArray)],
+ x: x !== undefined ? x - getRandomNumber(-20, 20) : getRandomNumber(rangeMin, rangeMax),
+ y: y || 0,
+ };
+ return [...existingEmojis, newEmoji];
+ });
+ },
+ [clearEmojis, disableRainbow, duration, emojisArray, rangeMin, rangeMax, startTimeout, stopTimeout]
+ );
+
+ useEffect(() => {
+ setOnNewEmoji?.(onNewEmoji);
+ return () => setOnNewEmoji?.(undefined);
+ }, [setOnNewEmoji, onNewEmoji]);
+
+ return (
+
+ {typeof children === 'function' ? children({ onNewEmoji }) : children}
+
+
+ {gravityEnabled
+ ? floatingEmojis.map(({ emojiToRender, x, y }, index) => (
+
+ ))
+ : floatingEmojis.map(({ emojiToRender, x, y }, index) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default FloatingEmojis;
diff --git a/src/components/floating-emojis/GravityEmoji.tsx b/src/components/floating-emojis/GravityEmoji.tsx
index 2bf06a3901f..0b1de95b47c 100644
--- a/src/components/floating-emojis/GravityEmoji.tsx
+++ b/src/components/floating-emojis/GravityEmoji.tsx
@@ -4,7 +4,9 @@ import { Emoji } from '../text';
interface GravityEmojiProps {
distance: number;
+ duration: number;
emoji: string;
+ index: number;
left: number;
size: number;
top: number;
diff --git a/src/components/info-alert/info-alert.tsx b/src/components/info-alert/info-alert.tsx
index bb17325be89..dbfa3aef5f1 100644
--- a/src/components/info-alert/info-alert.tsx
+++ b/src/components/info-alert/info-alert.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Box, Text, useForegroundColor } from '@/design-system';
+import { Box, Text } from '@/design-system';
type InfoAlertProps = {
title: string;
diff --git a/src/components/list/NoResults.tsx b/src/components/list/NoResults.tsx
index ef610f7b36e..ff9b3789bd7 100644
--- a/src/components/list/NoResults.tsx
+++ b/src/components/list/NoResults.tsx
@@ -4,7 +4,7 @@ import { neverRerender } from '@/utils';
import { Inset, Stack, Text } from '@/design-system';
import { useTheme } from '@/theme';
import { logger } from '@/logger';
-import { useUserAssetCount } from '@/resources/assets/useUserAssetCount';
+import { useUserAssetsStore } from '@/state/assets/userAssets';
export enum NoResultsType {
Discover = 'discover',
@@ -14,7 +14,7 @@ export enum NoResultsType {
export const NoResults = ({ onL2, type }: { onL2?: boolean; type: NoResultsType }) => {
const { colors } = useTheme();
- const { data: assetCount } = useUserAssetCount();
+ const assetCount = useUserAssetsStore(state => state.userAssets.size);
let title;
let description;
diff --git a/src/components/remote-promo-sheet/check-fns/hasNonZeroTotalBalance.ts b/src/components/remote-promo-sheet/check-fns/hasNonZeroTotalBalance.ts
index 2d0976b21df..0a8414475a0 100644
--- a/src/components/remote-promo-sheet/check-fns/hasNonZeroTotalBalance.ts
+++ b/src/components/remote-promo-sheet/check-fns/hasNonZeroTotalBalance.ts
@@ -1,16 +1,20 @@
+import { selectorFilterByUserChains, selectUserAssetsList } from '@/__swaps__/screens/Swap/resources/_selectors/assets';
+import { userAssetsFetchQuery } from '@/__swaps__/screens/Swap/resources/assets/userAssets';
import store from '@/redux/store';
-import { fetchUserAssets } from '@/resources/assets/UserAssetsQuery';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
export const hasNonZeroTotalBalance = async (): Promise => {
const { accountAddress, nativeCurrency } = store.getState().settings;
- const assets = await fetchUserAssets({
+ const userAssetsDictByChain = await userAssetsFetchQuery({
address: accountAddress,
currency: nativeCurrency,
- connectedToHardhat: false,
+ testnetMode: useConnectedToAnvilStore.getState().connectedToAnvil,
});
- if (!assets || Object.keys(assets).length === 0) return false;
+ const assets = selectorFilterByUserChains({ data: userAssetsDictByChain, selector: selectUserAssetsList });
- return Object.values(assets).some(asset => Number(asset.balance?.amount) > 0);
+ if (!assets?.length) return false;
+
+ return assets.some(asset => Number(asset.balance?.amount) > 0);
};
diff --git a/src/components/remote-promo-sheet/runChecks.ts b/src/components/remote-promo-sheet/runChecks.ts
index f83170eecce..9167de41182 100644
--- a/src/components/remote-promo-sheet/runChecks.ts
+++ b/src/components/remote-promo-sheet/runChecks.ts
@@ -1,7 +1,6 @@
import { IS_TEST } from '@/env';
-import { runFeatureUnlockChecks } from '@/handlers/walletReadyEvents';
+import { runFeaturesLocalCampaignAndBackupChecks } from '@/handlers/walletReadyEvents';
import { logger } from '@/logger';
-import { runLocalCampaignChecks } from '@/components/remote-promo-sheet/localCampaignChecks';
import { checkForRemotePromoSheet } from '@/components/remote-promo-sheet/checkForRemotePromoSheet';
import { useCallback, useEffect } from 'react';
import { InteractionManager } from 'react-native';
@@ -19,11 +18,7 @@ export const useRunChecks = ({ runChecksOnMount = true, walletReady }: { runChec
return;
}
- const showedFeatureUnlock = await runFeatureUnlockChecks();
- if (showedFeatureUnlock) return;
-
- const showedLocalPromo = await runLocalCampaignChecks();
- if (showedLocalPromo) return;
+ if (await runFeaturesLocalCampaignAndBackupChecks()) return;
if (!remotePromoSheets) {
logger.debug('[useRunChecks]: remote promo sheets is disabled');
diff --git a/src/components/secret-display/SecretDisplaySection.tsx b/src/components/secret-display/SecretDisplaySection.tsx
index 0ef93ba05e6..3cd37f05611 100644
--- a/src/components/secret-display/SecretDisplaySection.tsx
+++ b/src/components/secret-display/SecretDisplaySection.tsx
@@ -1,5 +1,4 @@
import { RouteProp, useRoute } from '@react-navigation/native';
-import { captureException } from '@sentry/react-native';
import React, { ReactNode, useCallback, useEffect, useState } from 'react';
import { createdWithBiometricError, identifyWalletType, loadPrivateKey, loadSeedPhraseAndMigrateIfNeeded } from '@/model/wallet';
import ActivityIndicator from '../ActivityIndicator';
@@ -25,6 +24,7 @@ import { useNavigation } from '@/navigation';
import { ImgixImage } from '../images';
import RoutesWithPlatformDifferences from '@/navigation/routesNames';
import { Source } from 'react-native-fast-image';
+import { backupsStore } from '@/state/backups/backups';
const MIN_HEIGHT = 740;
@@ -63,6 +63,9 @@ export function SecretDisplaySection({ onSecretLoaded, onWalletTypeIdentified }:
const { colors } = useTheme();
const { params } = useRoute>();
const { selectedWallet, wallets } = useWallets();
+ const { backupProvider } = backupsStore(state => ({
+ backupProvider: state.backupProvider,
+ }));
const { onManuallyBackupWalletId } = useWalletManualBackup();
const { navigate } = useNavigation();
@@ -124,9 +127,12 @@ export function SecretDisplaySection({ onSecretLoaded, onWalletTypeIdentified }:
const handleConfirmSaved = useCallback(() => {
if (backupType === WalletBackupTypes.manual) {
onManuallyBackupWalletId(walletId);
+ if (!backupProvider) {
+ backupsStore.getState().setBackupProvider(WalletBackupTypes.manual);
+ }
navigate(RoutesWithPlatformDifferences.SETTINGS_SECTION_BACKUP);
}
- }, [backupType, walletId, onManuallyBackupWalletId, navigate]);
+ }, [backupType, onManuallyBackupWalletId, walletId, backupProvider, navigate]);
const getIconForBackupType = useCallback(() => {
if (isBackingUp) {
diff --git a/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx b/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx
index 9a7ea341748..81f82de844b 100644
--- a/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx
+++ b/src/components/sheet/sheet-action-buttons/SwapActionButton.tsx
@@ -31,7 +31,7 @@ function SwapActionButton({ asset, color: givenColor, inputType, label, weight =
const goToSwap = useCallback(async () => {
const chainsIdByName = useBackendNetworksStore.getState().getChainsIdByName();
const chainsName = useBackendNetworksStore.getState().getChainsName();
- const chainId = chainsIdByName[asset.network];
+ const chainId = asset.chainId || chainsIdByName[asset.network];
const uniqueId = `${asset.address}_${chainId}`;
const userAsset = userAssetsStore.getState().userAssets.get(uniqueId);
diff --git a/src/components/skeleton/Skeleton.tsx b/src/components/skeleton/Skeleton.tsx
index 628090fc8a6..afd02fa4532 100644
--- a/src/components/skeleton/Skeleton.tsx
+++ b/src/components/skeleton/Skeleton.tsx
@@ -1,7 +1,6 @@
import MaskedView from '@react-native-masked-view/masked-view';
import React from 'react';
import { View, ViewProps } from 'react-native';
-import { IS_TESTING } from 'react-native-dotenv';
import { ThemeContextProps, withThemeContext } from '../../theme/ThemeContext';
import { deviceUtils } from '../../utils';
import { ShimmerAnimation } from '../animations';
@@ -9,6 +8,7 @@ import { CoinRowHeight } from '../coin-row';
import { Row } from '../layout';
import styled from '@/styled-thing';
import { position } from '@/styles';
+import { IS_TEST } from '@/env';
export const AssetListItemSkeletonHeight = CoinRowHeight;
@@ -75,7 +75,7 @@ function Skeleton({
skeletonColor?: string;
width?: number;
}) {
- if (animated && IS_TESTING !== 'true') {
+ if (animated && !IS_TEST) {
return (
{children}} style={{ flex: 1 }}>
diff --git a/src/components/toasts/OfflineToast.js b/src/components/toasts/OfflineToast.js
index 053791ce854..266455625ca 100644
--- a/src/components/toasts/OfflineToast.js
+++ b/src/components/toasts/OfflineToast.js
@@ -3,13 +3,13 @@ import React from 'react';
import Toast from './Toast';
import { useAccountSettings, useInternetStatus } from '@/hooks';
import { ChainId } from '@/state/backendNetworks/types';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
const OfflineToast = () => {
const isConnected = useInternetStatus();
const { chainId } = useAccountSettings();
- const { connectedToHardhat } = useConnectedToHardhatStore();
- const isMainnet = chainId === ChainId.mainnet && !connectedToHardhat;
+ const { connectedToAnvil } = useConnectedToAnvilStore();
+ const isMainnet = chainId === ChainId.mainnet && !connectedToAnvil;
return ;
};
diff --git a/src/components/toasts/TestnetToast.js b/src/components/toasts/TestnetToast.js
index 5618d0706d4..136fc021d78 100644
--- a/src/components/toasts/TestnetToast.js
+++ b/src/components/toasts/TestnetToast.js
@@ -4,11 +4,11 @@ import { Nbsp, Text } from '../text';
import Toast from './Toast';
import { useInternetStatus } from '@/hooks';
import { ChainId } from '@/state/backendNetworks/types';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
const TestnetToast = ({ chainId }) => {
- const { connectedToHardhat } = useConnectedToHardhatStore();
+ const { connectedToAnvil } = useConnectedToAnvilStore();
const isConnected = useInternetStatus();
const nativeAsset = useBackendNetworksStore.getState().getChainsNativeAsset()[chainId];
const name = useBackendNetworksStore.getState().getChainsName()[chainId];
@@ -18,9 +18,9 @@ const TestnetToast = ({ chainId }) => {
useEffect(() => {
if (chainId === ChainId.mainnet) {
- if (connectedToHardhat) {
+ if (connectedToAnvil) {
setVisible(true);
- setNetworkName('Hardhat');
+ setNetworkName('Anvil');
} else {
setVisible(false);
}
@@ -28,7 +28,7 @@ const TestnetToast = ({ chainId }) => {
setVisible(true);
setNetworkName(name + (isConnected ? '' : ' (offline)'));
}
- }, [isConnected, chainId, connectedToHardhat, name]);
+ }, [isConnected, chainId, connectedToAnvil, name]);
const { colors, isDarkMode } = useTheme();
diff --git a/src/config/defaultDebug.ts b/src/config/defaultDebug.ts
index e3e349d1203..e39b0a01b45 100644
--- a/src/config/defaultDebug.ts
+++ b/src/config/defaultDebug.ts
@@ -9,7 +9,7 @@ export const debugLayoutAnimations = false;
export const alwaysRequireApprove = false;
export const showReloadButton = false;
export const showSwitchModeButton = false;
-export const showConnectToHardhatButton = false;
+export const showConnectToAnvilButton = false;
export const parseAllTxnsOnReceive = false;
export const reactNativeDisableYellowBox = true;
export const showNetworkRequests = false;
diff --git a/src/config/experimental.ts b/src/config/experimental.ts
index b68e23260ff..cef7e04d7d7 100644
--- a/src/config/experimental.ts
+++ b/src/config/experimental.ts
@@ -29,6 +29,7 @@ export const DEGEN_MODE = 'Degen Mode';
export const FEATURED_RESULTS = 'Featured Results';
export const CLAIMABLES = 'Claimables';
export const NFTS_ENABLED = 'Nfts Enabled';
+export const TRENDING_TOKENS = 'Trending Tokens';
/**
* A developer setting that pushes log lines to an array in-memory so that
@@ -66,6 +67,7 @@ export const defaultConfig: Record = {
[FEATURED_RESULTS]: { settings: true, value: false },
[CLAIMABLES]: { settings: true, value: false },
[NFTS_ENABLED]: { settings: true, value: !!IS_TEST },
+ [TRENDING_TOKENS]: { settings: true, value: false },
};
export const defaultConfigValues: Record = Object.fromEntries(
diff --git a/src/design-system/components/Inline/Inline.tsx b/src/design-system/components/Inline/Inline.tsx
index 5754bae6a93..3f93791cbc8 100644
--- a/src/design-system/components/Inline/Inline.tsx
+++ b/src/design-system/components/Inline/Inline.tsx
@@ -51,7 +51,7 @@ export function Inline({
>
{wrap || !separator
? children
- : Children.map(children, (child, index) => {
+ : Children.toArray(children).map((child, index) => {
if (!child) return null;
return (
<>
diff --git a/src/featuresToUnlock/unlockableAppIconCheck.ts b/src/featuresToUnlock/unlockableAppIconCheck.ts
index ddab655a722..0eaef6b9514 100644
--- a/src/featuresToUnlock/unlockableAppIconCheck.ts
+++ b/src/featuresToUnlock/unlockableAppIconCheck.ts
@@ -70,6 +70,26 @@ export const unlockableAppIconCheck = async (appIconKey: UnlockableAppIconKey, w
unlockableAppIconStorage.set(appIconKey, true);
logger.debug(`[unlockableAppIconCheck]: Feature check ${appIconKey} set to true. Wont show up anymore!`);
+ // Temporarily ignore some icons
+ // We can get rid of this in 2025!
+ const iconsToIgnore = [
+ 'optimism',
+ 'smol',
+ 'zora',
+ 'golddoge',
+ 'raindoge',
+ 'pooly',
+ 'finiliar',
+ 'zorb',
+ 'poolboy',
+ 'adworld',
+ 'farcaster',
+ ];
+
+ if (iconsToIgnore.includes(appIconKey)) {
+ return false;
+ }
+
Navigation.handleAction(Routes.APP_ICON_UNLOCK_SHEET, { appIconKey });
return true;
}
diff --git a/src/graphql/queries/arc.graphql b/src/graphql/queries/arc.graphql
index 68e797864e5..17dccca539c 100644
--- a/src/graphql/queries/arc.graphql
+++ b/src/graphql/queries/arc.graphql
@@ -522,6 +522,7 @@ query trendingTokens(
$sortBy: TrendingSort
$sortDirection: SortDirection
$walletAddress: String
+ $limit: Int
) {
trendingTokens(
chainId: $chainId
@@ -531,6 +532,7 @@ query trendingTokens(
sortBy: $sortBy
sortDirection: $sortDirection
walletAddress: $walletAddress
+ limit: $limit
) {
data {
colors {
diff --git a/src/handlers/cloudBackup.ts b/src/handlers/cloudBackup.ts
index 1eb3f5be795..14347c42a75 100644
--- a/src/handlers/cloudBackup.ts
+++ b/src/handlers/cloudBackup.ts
@@ -1,14 +1,15 @@
import { sortBy } from 'lodash';
// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module 'reac... Remove this comment to see the full error message
import RNCloudFs from 'react-native-cloud-fs';
-import { RAINBOW_MASTER_KEY } from 'react-native-dotenv';
import RNFS from 'react-native-fs';
import AesEncryptor from '../handlers/aesEncryption';
import { logger, RainbowError } from '@/logger';
import { IS_ANDROID, IS_IOS } from '@/env';
-import { CloudBackups } from '@/model/backup';
+import { BackupFile, CloudBackups } from '@/model/backup';
+
const REMOTE_BACKUP_WALLET_DIR = 'rainbow.me/wallet-backups';
-const USERDATA_FILE = 'UserData.json';
+export const USERDATA_FILE = 'UserData.json';
+
const encryptor = new AesEncryptor();
export const CLOUD_BACKUP_ERRORS = {
@@ -65,13 +66,18 @@ export async function fetchAllBackups(): Promise {
if (android) {
await RNCloudFs.loginIfNeeded();
}
- return RNCloudFs.listFiles({
+
+ const files = await RNCloudFs.listFiles({
scope: 'hidden',
targetPath: REMOTE_BACKUP_WALLET_DIR,
});
+
+ return {
+ files: files?.files?.filter((file: BackupFile) => normalizeAndroidBackupFilename(file.name) !== USERDATA_FILE) || [],
+ };
}
-export async function encryptAndSaveDataToCloud(data: any, password: any, filename: any) {
+export async function encryptAndSaveDataToCloud(data: Record, password: string, filename: string) {
// Encrypt the data
try {
const encryptedData = await encryptor.encrypt(password, JSON.stringify(data));
@@ -100,6 +106,7 @@ export async function encryptAndSaveDataToCloud(data: any, password: any, filena
scope,
sourcePath: sourceUri,
targetPath: destinationPath,
+ update: true,
});
// Now we need to verify the file has been stored in the cloud
const exists = await RNCloudFs.fileExists(
@@ -201,19 +208,6 @@ export async function getDataFromCloud(backupPassword: any, filename: string | n
throw error;
}
-export async function backupUserDataIntoCloud(data: any) {
- const filename = USERDATA_FILE;
- const password = RAINBOW_MASTER_KEY;
- return encryptAndSaveDataToCloud(data, password, filename);
-}
-
-export async function fetchUserDataFromCloud() {
- const filename = USERDATA_FILE;
- const password = RAINBOW_MASTER_KEY;
-
- return getDataFromCloud(password, filename);
-}
-
export const cloudBackupPasswordMinLength = 8;
export function isCloudBackupPasswordValid(password: any) {
diff --git a/src/handlers/swap.ts b/src/handlers/swap.ts
index 7d811afc0d4..877dc315e40 100644
--- a/src/handlers/swap.ts
+++ b/src/handlers/swap.ts
@@ -3,7 +3,6 @@ import { Block, StaticJsonRpcProvider } from '@ethersproject/providers';
import { CrosschainQuote, getQuoteExecutionDetails, getRainbowRouterContractAddress, Quote } from '@rainbow-me/swaps';
import { Contract } from '@ethersproject/contracts';
import { MaxUint256 } from '@ethersproject/constants';
-import { IS_TESTING } from 'react-native-dotenv';
import { Token } from '../entities/tokens';
import { estimateGasWithPadding, getProvider, toHexNoLeadingZeros } from './web3';
import { getRemoteConfig } from '@/model/remoteConfig';
@@ -13,6 +12,7 @@ import { erc20ABI, ethUnits } from '@/references';
import { ethereumUtils } from '@/utils';
import { logger, RainbowError } from '@/logger';
import { ChainId } from '@/state/backendNetworks/types';
+import { IS_TEST } from '@/env';
export enum Field {
INPUT = 'INPUT',
@@ -191,7 +191,7 @@ export const estimateCrosschainSwapGasLimit = async ({
}
try {
if (requiresApprove) {
- if (CHAIN_IDS_WITH_TRACE_SUPPORT.includes(chainId) && IS_TESTING !== 'true') {
+ if (CHAIN_IDS_WITH_TRACE_SUPPORT.includes(chainId) && !IS_TEST) {
try {
const gasLimitWithFakeApproval = await getSwapGasLimitWithFakeApproval(chainId, provider, tradeDetails);
logger.debug('[swap]: Got gasLimitWithFakeApproval!', {
diff --git a/src/handlers/walletReadyEvents.ts b/src/handlers/walletReadyEvents.ts
index 1cfa62be144..b8e53674c66 100644
--- a/src/handlers/walletReadyEvents.ts
+++ b/src/handlers/walletReadyEvents.ts
@@ -1,4 +1,3 @@
-import { IS_TESTING } from 'react-native-dotenv';
import { triggerOnSwipeLayout } from '../navigation/onNavigationStateChange';
import { getKeychainIntegrityState } from './localstorage/globalSettings';
import { runLocalCampaignChecks } from '@/components/remote-promo-sheet/localCampaignChecks';
@@ -6,18 +5,15 @@ import { EthereumAddress } from '@/entities';
import WalletBackupStepTypes from '@/helpers/walletBackupStepTypes';
import WalletTypes from '@/helpers/walletTypes';
import { featureUnlockChecks } from '@/featuresToUnlock';
-import { AllRainbowWallets, RainbowAccount, RainbowWallet } from '@/model/wallet';
+import { AllRainbowWallets, RainbowAccount } from '@/model/wallet';
import { Navigation } from '@/navigation';
import store from '@/redux/store';
import { checkKeychainIntegrity } from '@/redux/wallets';
import Routes from '@/navigation/routesNames';
import { logger } from '@/logger';
-import { checkWalletsForBackupStatus } from '@/screens/SettingsSheet/utils';
-import walletBackupTypes from '@/helpers/walletBackupTypes';
-import { InteractionManager } from 'react-native';
-
-const BACKUP_SHEET_DELAY_MS = 3000;
+import { IS_TEST } from '@/env';
+import { backupsStore, LoadingStates } from '@/state/backups/backups';
export const runKeychainIntegrityChecks = async () => {
const keychainIntegrityState = await getKeychainIntegrityState();
@@ -26,60 +22,36 @@ export const runKeychainIntegrityChecks = async () => {
}
};
-export const runWalletBackupStatusChecks = () => {
- const {
- selected,
- wallets,
- }: {
- wallets: AllRainbowWallets | null;
- selected: RainbowWallet | undefined;
- } = store.getState().wallets;
-
- // count how many visible, non-imported and non-readonly wallets are not backed up
- if (!wallets) return;
-
- const { backupProvider } = checkWalletsForBackupStatus(wallets);
-
- const rainbowWalletsNotBackedUp = Object.values(wallets).filter(wallet => {
- const hasVisibleAccount = wallet.addresses?.find((account: RainbowAccount) => account.visible);
- return (
- !wallet.imported &&
- !!hasVisibleAccount &&
- wallet.type !== WalletTypes.readOnly &&
- wallet.type !== WalletTypes.bluetooth &&
- !wallet.backedUp
- );
+const delay = (ms: number) =>
+ new Promise(resolve => {
+ setTimeout(resolve, ms);
});
- if (!rainbowWalletsNotBackedUp.length) return;
-
- logger.debug('[walletReadyEvents]: there is a rainbow wallet not backed up');
+const promptForBackupOnceReadyOrNotAvailable = async (): Promise => {
+ let { status } = backupsStore.getState();
+ while (LoadingStates.includes(status)) {
+ await delay(1000);
+ status = backupsStore.getState().status;
+ }
- const hasSelectedWallet = rainbowWalletsNotBackedUp.find(notBackedUpWallet => notBackedUpWallet.id === selected!.id);
- logger.debug('[walletReadyEvents]: rainbow wallet not backed up that is selected?', {
- hasSelectedWallet,
- });
+ logger.debug(`[walletReadyEvents]: BackupSheet: showing backup now sheet for selected wallet`);
+ triggerOnSwipeLayout(() =>
+ Navigation.handleAction(Routes.BACKUP_SHEET, {
+ step: WalletBackupStepTypes.backup_prompt,
+ })
+ );
+ return true;
+};
- // if one of them is selected, show the default BackupSheet
- if (selected && hasSelectedWallet && IS_TESTING !== 'true') {
- let stepType: string = WalletBackupStepTypes.no_provider;
- if (backupProvider === walletBackupTypes.cloud) {
- stepType = WalletBackupStepTypes.backup_now_to_cloud;
- } else if (backupProvider === walletBackupTypes.manual) {
- stepType = WalletBackupStepTypes.backup_now_manually;
- }
+export const runWalletBackupStatusChecks = async (): Promise => {
+ const { selected } = store.getState().wallets;
+ if (!selected || IS_TEST) return false;
- setTimeout(() => {
- logger.debug(`[walletReadyEvents]: showing ${stepType} backup sheet for selected wallet`);
- triggerOnSwipeLayout(() =>
- Navigation.handleAction(Routes.BACKUP_SHEET, {
- step: stepType,
- })
- );
- }, BACKUP_SHEET_DELAY_MS);
- return;
+ if (!selected.backedUp && !selected.damaged && selected.type !== WalletTypes.readOnly && selected.type !== WalletTypes.bluetooth) {
+ logger.debug('[walletReadyEvents]: Selected wallet is not backed up, prompting backup sheet');
+ return promptForBackupOnceReadyOrNotAvailable();
}
- return;
+ return false;
};
export const runFeatureUnlockChecks = async (): Promise => {
@@ -107,19 +79,24 @@ export const runFeatureUnlockChecks = async (): Promise => {
// short circuits once the first feature is unlocked
for (const featureUnlockCheck of featureUnlockChecks) {
- InteractionManager.runAfterInteractions(async () => {
- const unlockNow = await featureUnlockCheck(walletsToCheck);
- if (unlockNow) {
- return true;
- }
- });
+ const unlockNow = await featureUnlockCheck(walletsToCheck);
+ if (unlockNow) {
+ return true;
+ }
}
return false;
};
-export const runFeatureAndLocalCampaignChecks = async () => {
- const showingFeatureUnlock: boolean = await runFeatureUnlockChecks();
- if (!showingFeatureUnlock) {
- await runLocalCampaignChecks();
+export const runFeaturesLocalCampaignAndBackupChecks = async () => {
+ if (await runFeatureUnlockChecks()) {
+ return true;
}
+ if (await runLocalCampaignChecks()) {
+ return true;
+ }
+ if (await runWalletBackupStatusChecks()) {
+ return true;
+ }
+
+ return false;
};
diff --git a/src/handlers/web3.ts b/src/handlers/web3.ts
index 96f099109a2..1e5c7ae00d8 100644
--- a/src/handlers/web3.ts
+++ b/src/handlers/web3.ts
@@ -24,9 +24,9 @@ import {
import { ethereumUtils } from '@/utils';
import { logger, RainbowError } from '@/logger';
import { IS_IOS, RPC_PROXY_API_KEY, RPC_PROXY_BASE_URL } from '@/env';
-import { ChainId, chainHardhat } from '@/state/backendNetworks/types';
+import { ChainId, chainAnvil } from '@/state/backendNetworks/types';
import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
export enum TokenStandard {
ERC1155 = 'ERC1155',
@@ -111,8 +111,8 @@ export const getCachedProviderForNetwork = (chainId: ChainId = ChainId.mainnet):
};
export const getBatchedProvider = ({ chainId = ChainId.mainnet }: { chainId?: number }): JsonRpcBatchProvider => {
- if (useConnectedToHardhatStore.getState().connectedToHardhat) {
- const provider = new JsonRpcBatchProvider(chainHardhat.rpcUrls.default.http[0], ChainId.mainnet);
+ if (useConnectedToAnvilStore.getState().connectedToAnvil) {
+ const provider = new JsonRpcBatchProvider(chainAnvil.rpcUrls.default.http[0], ChainId.mainnet);
chainsBatchProviders.set(chainId, provider);
return provider;
@@ -131,8 +131,8 @@ export const getBatchedProvider = ({ chainId = ChainId.mainnet }: { chainId?: nu
};
export const getProvider = ({ chainId = ChainId.mainnet }: { chainId?: number }): StaticJsonRpcProvider => {
- if (useConnectedToHardhatStore.getState().connectedToHardhat) {
- const provider = new StaticJsonRpcProvider(chainHardhat.rpcUrls.default.http[0], ChainId.mainnet);
+ if (useConnectedToAnvilStore.getState().connectedToAnvil) {
+ const provider = new StaticJsonRpcProvider(chainAnvil.rpcUrls.default.http[0], ChainId.mainnet);
chainsProviders.set(chainId, provider);
return provider;
diff --git a/src/helpers/RainbowContext.tsx b/src/helpers/RainbowContext.tsx
index 5fe0d9f4f38..3a0f90586fa 100644
--- a/src/helpers/RainbowContext.tsx
+++ b/src/helpers/RainbowContext.tsx
@@ -3,17 +3,17 @@ import { MMKV } from 'react-native-mmkv';
import { useSharedValue } from 'react-native-reanimated';
import DevButton from '../components/dev-buttons/DevButton';
import Emoji from '../components/text/Emoji';
-import { showReloadButton, showSwitchModeButton, showConnectToHardhatButton } from '../config/debug';
+import { showReloadButton, showSwitchModeButton, showConnectToAnvilButton } from '../config/debug';
import { defaultConfig } from '@/config/experimental';
import { useDispatch } from 'react-redux';
import { useTheme } from '../theme/ThemeContext';
import { STORAGE_IDS } from '@/model/mmkv';
-import { IS_TESTING } from 'react-native-dotenv';
import { logger, RainbowError } from '@/logger';
import { Navigation } from '@/navigation';
import Routes from '@rainbow-me/routes';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
+import { IS_TEST } from '@/env';
export type RainbowContextType = {
config: Record;
@@ -37,7 +37,7 @@ export default function RainbowContextWrapper({ children }: PropsWithChildren) {
// This value is hold here to prevent JS VM from shutting down
// on unmounting all shared values.
useSharedValue(0);
- const { setConnectedToHardhat } = useConnectedToHardhatStore();
+ const { setConnectedToAnvil } = useConnectedToAnvilStore();
const [config, setConfig] = useState>(
Object.entries(defaultConfig).reduce((acc, [key, { value }]) => ({ ...acc, [key]: value }), {})
);
@@ -71,26 +71,26 @@ export default function RainbowContextWrapper({ children }: PropsWithChildren) {
const dispatch = useDispatch();
- const connectToHardhat = useCallback(async () => {
+ const connectToAnvil = useCallback(async () => {
try {
- setConnectedToHardhat(true);
- logger.debug('connected to hardhat');
+ setConnectedToAnvil(true);
+ logger.debug('connected to anvil');
} catch (e: any) {
- setConnectedToHardhat(false);
- logger.error(new RainbowError('error connecting to hardhat'), {
+ setConnectedToAnvil(false);
+ logger.error(new RainbowError('error connecting to anvil'), {
message: e.message,
});
}
Navigation.handleAction(Routes.WALLET_SCREEN, {});
- }, [dispatch, setConnectedToHardhat]);
+ }, [dispatch, setConnectedToAnvil]);
return (
{children}
{/* @ts-expect-error ts-migrate(2741) FIXME: Property 'color' is missing in type... Remove this comment to see the full error message */}
{showReloadButton && __DEV__ && }
- {((showConnectToHardhatButton && __DEV__) || IS_TESTING === 'true') && (
-
+ {((showConnectToAnvilButton && __DEV__) || IS_TEST) && (
+
{/* @ts-ignore */}
👷
diff --git a/src/helpers/buildWalletSections.tsx b/src/helpers/buildWalletSections.tsx
index b498081c0f5..b7ad6089c6c 100644
--- a/src/helpers/buildWalletSections.tsx
+++ b/src/helpers/buildWalletSections.tsx
@@ -223,7 +223,7 @@ const withBriefBalanceSection = (
type: 'PROFILE_NAME_ROW_SPACE_AFTER',
uid: 'profile-name-space-after',
},
- ...(!hasTokens && !isLoadingUserAssets && !isLoadingBalance
+ ...(!hasTokens && !isLoadingBalance
? []
: [
{
diff --git a/src/helpers/strings.ts b/src/helpers/strings.ts
index 4e7fd76ab91..b53bb781d40 100644
--- a/src/helpers/strings.ts
+++ b/src/helpers/strings.ts
@@ -1,4 +1,8 @@
+import store from '@/redux/store';
import { memoFn } from '../utils/memoFn';
+import { supportedNativeCurrencies } from '@/references';
+import { NativeCurrencyKey } from '@/entities';
+import { convertAmountToNativeDisplayWorklet } from './utilities';
/**
* @desc subtracts two numbers
* @param {String} str
@@ -10,3 +14,128 @@ export const containsEmoji = memoFn(str => {
// @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.
return !!str.match(ranges.join('|'));
});
+
+/*
+ * Return the given number as a formatted string. The default format is a plain
+ * integer with thousands-separator commas. The optional parameters facilitate
+ * other formats:
+ * - decimals = the number of decimals places to round to and show
+ * - valueIfNaN = the value to show for non-numeric input
+ * - style
+ * - '%': multiplies by 100 and appends a percent symbol
+ * - '$': prepends a dollar sign
+ * - useOrderSuffix = whether to use suffixes like k for 1,000, etc.
+ * - orderSuffixes = the list of suffixes to use
+ * - minOrder and maxOrder allow the order to be constrained. Examples:
+ * - minOrder = 1 means the k suffix should be used for numbers < 1,000
+ * - maxOrder = 1 means the k suffix should be used for numbers >= 1,000,000
+ */
+export function formatNumber(
+ number: string | number,
+ {
+ decimals = 0,
+ valueIfNaN = '',
+ style = '',
+ useOrderSuffix = false,
+ orderSuffixes = ['', 'K', 'M', 'B', 'T'],
+ minOrder = 0,
+ maxOrder = Infinity,
+ } = {}
+) {
+ let x = parseFloat(`${number}`);
+
+ if (isNaN(x)) return valueIfNaN;
+
+ if (style === '%') x *= 100.0;
+
+ let order;
+ if (!isFinite(x) || !useOrderSuffix) order = 0;
+ else if (minOrder === maxOrder) order = minOrder;
+ else {
+ const unboundedOrder = Math.floor(Math.log10(Math.abs(x)) / 3);
+ order = Math.max(0, minOrder, Math.min(unboundedOrder, maxOrder, orderSuffixes.length - 1));
+ }
+
+ const orderSuffix = orderSuffixes[order];
+ if (order !== 0) x /= Math.pow(10, order * 3);
+
+ return (
+ (style === '$' ? '$' : '') +
+ x.toLocaleString('en-US', {
+ style: 'decimal',
+ minimumFractionDigits: decimals,
+ maximumFractionDigits: decimals,
+ }) +
+ orderSuffix +
+ (style === '%' ? '%' : '')
+ );
+}
+
+type CurrencyFormatterOptions = {
+ decimals?: number;
+ valueIfNaN?: string;
+ currency?: NativeCurrencyKey;
+};
+
+const toSubscript = (str: string | number) => str.toString().replace(/[0-9]/g, num => String.fromCharCode(0x2080 + +num));
+
+/*
+ converts 6.9e-7 to 0.00000069
+*/
+const toDecimalString = (num: number): string => {
+ const [coefficient, exponent] = num.toExponential(20).split('e');
+ const exp = parseInt(exponent);
+ const digits = coefficient.replace('.', '').replace(/0+$/, '');
+
+ if (exp >= 0) {
+ const position = exp + 1;
+ if (position >= digits.length) return digits + '0'.repeat(position - digits.length);
+ return digits.slice(0, position) + (digits.slice(position) && '.' + digits.slice(position));
+ }
+ return '0.' + '0'.repeat(Math.abs(exp) - 1) + digits;
+};
+
+/*
+ formats a numeric string like 0000069 to 0₅69
+*/
+function formatFraction(fraction: string) {
+ const leadingZeros = fraction.match(/^[0]+/)?.[0].length || 0;
+ if (+fraction === 0) return '00';
+
+ const significantDigits = fraction.slice(leadingZeros, leadingZeros + 2);
+ if (+significantDigits === 0) return '00';
+
+ if (leadingZeros >= 4) return `0${toSubscript(leadingZeros)}${significantDigits}`;
+ return `${'0'.repeat(leadingZeros)}${significantDigits}`;
+}
+
+export function formatCurrency(
+ value: string | number,
+ { valueIfNaN = '', currency = store.getState().settings.nativeCurrency }: CurrencyFormatterOptions = {}
+): string {
+ const numericString = typeof value === 'number' ? toDecimalString(value) : String(value);
+ if (isNaN(+numericString)) return valueIfNaN;
+
+ const currencySymbol = supportedNativeCurrencies[currency].symbol;
+ const [whole, fraction = ''] = numericString.split('.');
+
+ const numericalWholeNumber = +whole;
+ if (numericalWholeNumber > 0) {
+ // if the fraction is empty and the numeric string is less than 6 characters, we can just run it through our native currency display worklet
+ if (whole.length <= 6) {
+ return convertAmountToNativeDisplayWorklet(numericString, currency, false, true);
+ }
+
+ const decimals = supportedNativeCurrencies[currency].decimals;
+ // otherwise for > 6 figs native value we need to format in compact notation
+ const formattedWhole = formatNumber(numericString, { decimals, useOrderSuffix: true });
+ return `${currencySymbol}${formattedWhole}`;
+ }
+
+ const formattedWhole = formatNumber(whole, { decimals: 0, useOrderSuffix: true });
+ const formattedFraction = formatFraction(fraction);
+ // if it ends with a non-numeric character, it's in compact notation like '1.2K'
+ if (isNaN(+formattedWhole[formattedWhole.length - 1])) return `${currencySymbol}${formattedWhole}`;
+
+ return `${currencySymbol}${formattedWhole}.${formattedFraction}`;
+}
diff --git a/src/helpers/walletBackupStepTypes.ts b/src/helpers/walletBackupStepTypes.ts
index d3afc9598a2..2fbf0cb8f9e 100644
--- a/src/helpers/walletBackupStepTypes.ts
+++ b/src/helpers/walletBackupStepTypes.ts
@@ -1,5 +1,5 @@
export default {
- no_provider: 'no_provider',
+ backup_prompt: 'backup_prompt',
backup_manual: 'backup_manual',
backup_cloud: 'backup_cloud',
restore_from_backup: 'restore_from_backup',
diff --git a/src/helpers/walletLoadingStates.ts b/src/helpers/walletLoadingStates.ts
new file mode 100644
index 00000000000..a9cdd674d2e
--- /dev/null
+++ b/src/helpers/walletLoadingStates.ts
@@ -0,0 +1,10 @@
+import * as i18n from '@/languages';
+
+export const WalletLoadingStates = {
+ BACKING_UP_WALLET: i18n.t('loading.backing_up'),
+ CREATING_WALLET: i18n.t('loading.creating_wallet'),
+ IMPORTING_WALLET: i18n.t('loading.importing_wallet'),
+ RESTORING_WALLET: i18n.t('loading.restoring'),
+} as const;
+
+export type WalletLoadingStates = (typeof WalletLoadingStates)[keyof typeof WalletLoadingStates];
diff --git a/src/hooks/reanimated/useSyncSharedValue.ts b/src/hooks/reanimated/useSyncSharedValue.ts
index f8c19c71a0c..c48f83a3643 100644
--- a/src/hooks/reanimated/useSyncSharedValue.ts
+++ b/src/hooks/reanimated/useSyncSharedValue.ts
@@ -9,14 +9,14 @@ interface BaseSyncParams {
/** A boolean or shared value boolean that controls whether synchronization is paused. */
pauseSync?: DerivedValue | SharedValue | boolean;
/** The JS state to be synchronized. */
- state: T | undefined;
+ state: T;
}
interface SharedToStateParams extends BaseSyncParams {
/** The setter function for the JS state (only applicable when `syncDirection` is `'sharedValueToState'`). */
setState: (value: T) => void;
/** The shared value to be synchronized. */
- sharedValue: DerivedValue | DerivedValue | SharedValue | SharedValue;
+ sharedValue: DerivedValue | SharedValue;
/** The direction of synchronization. */
syncDirection: 'sharedValueToState';
}
@@ -24,7 +24,7 @@ interface SharedToStateParams extends BaseSyncParams {
interface StateToSharedParams extends BaseSyncParams {
setState?: never;
/** The shared value to be synchronized. */
- sharedValue: SharedValue | SharedValue;
+ sharedValue: SharedValue;
/** The direction of synchronization. */
syncDirection: 'stateToSharedValue';
}
@@ -73,7 +73,7 @@ export function useSyncSharedValue({ compareDepth = 'deep', pauseSync, setSta
},
shouldSync => {
if (shouldSync) {
- if (syncDirection === 'sharedValueToState' && sharedValue.value !== undefined) {
+ if (syncDirection === 'sharedValueToState') {
runOnJS(setState)(sharedValue.value);
} else if (syncDirection === 'stateToSharedValue') {
sharedValue.value = state;
diff --git a/src/hooks/useAccountAsset.ts b/src/hooks/useAccountAsset.ts
index 2d2bccc2699..0e5ba4a00e3 100644
--- a/src/hooks/useAccountAsset.ts
+++ b/src/hooks/useAccountAsset.ts
@@ -1,11 +1,11 @@
import { NativeCurrencyKey } from '@/entities';
import useAccountSettings from './useAccountSettings';
import { parseAssetNative } from '@/parsers';
-import { useUserAsset } from '@/resources/assets/useUserAsset';
+import { useUserAssetsStore } from '@/state/assets/userAssets';
// this is meant to be used for assets contained in the current wallet
export default function useAccountAsset(uniqueId: string, nativeCurrency: NativeCurrencyKey | undefined = undefined) {
- const { data: accountAsset } = useUserAsset(uniqueId);
+ const accountAsset = useUserAssetsStore(state => state.getLegacyUserAsset(uniqueId));
// this is temporary for FastBalanceCoinRow to make a tiny bit faster
// we pass nativeCurrency only in that case
diff --git a/src/hooks/useAccountENSDomains.ts b/src/hooks/useAccountENSDomains.ts
index c9923dfa606..f50aaef778d 100644
--- a/src/hooks/useAccountENSDomains.ts
+++ b/src/hooks/useAccountENSDomains.ts
@@ -14,7 +14,7 @@ const STALE_TIME = 10000;
async function fetchAccountENSDomains({ accountAddress }: { accountAddress: string }) {
const result = await fetchAccountDomains(accountAddress);
- if (!result.account) return [];
+ if (!result?.account) return [];
const { domains: controlledDomains, registrations } = result.account;
const registarDomains = registrations?.map(({ domain }) => domain);
diff --git a/src/hooks/useActiveRoute.ts b/src/hooks/useActiveRoute.ts
new file mode 100644
index 00000000000..523eb741004
--- /dev/null
+++ b/src/hooks/useActiveRoute.ts
@@ -0,0 +1,16 @@
+import { Navigation, useNavigation } from '@/navigation';
+import { useEffect, useState } from 'react';
+
+export const useActiveRoute = () => {
+ const { addListener } = useNavigation();
+ const [activeRoute, setActiveRoute] = useState(Navigation.getActiveRoute());
+
+ useEffect(() => {
+ const unsubscribe = addListener('state', () => {
+ setActiveRoute(Navigation.getActiveRoute());
+ });
+ return unsubscribe;
+ }, [addListener]);
+
+ return activeRoute?.name;
+};
diff --git a/src/hooks/useCloudBackups.ts b/src/hooks/useCloudBackups.ts
deleted file mode 100644
index 506e669c682..00000000000
--- a/src/hooks/useCloudBackups.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { useEffect, useState } from 'react';
-import type { BackupUserData, CloudBackups } from '../model/backup';
-import { fetchAllBackups, fetchUserDataFromCloud, isCloudBackupAvailable, syncCloud } from '@/handlers/cloudBackup';
-import { RainbowError, logger } from '@/logger';
-
-export const enum CloudBackupStep {
- IDLE,
- SYNCING,
- FETCHING_USER_DATA,
- FETCHING_ALL_BACKUPS,
- FAILED,
-}
-
-export default function useCloudBackups() {
- const [isFetching, setIsFetching] = useState(false);
- const [backups, setBackups] = useState({
- files: [],
- });
-
- const [step, setStep] = useState(CloudBackupStep.SYNCING);
-
- const [userData, setUserData] = useState();
-
- const fetchBackups = async () => {
- try {
- setIsFetching(true);
- const isAvailable = isCloudBackupAvailable();
- if (!isAvailable) {
- logger.debug('[useCloudBackups]: Cloud backup is not available');
- setIsFetching(false);
- setStep(CloudBackupStep.IDLE);
- return;
- }
-
- setStep(CloudBackupStep.SYNCING);
- logger.debug('[useCloudBackups]: Syncing with cloud');
- await syncCloud();
-
- setStep(CloudBackupStep.FETCHING_USER_DATA);
- logger.debug('[useCloudBackups]: Fetching user data');
- const userData = await fetchUserDataFromCloud();
- setUserData(userData);
-
- setStep(CloudBackupStep.FETCHING_ALL_BACKUPS);
- logger.debug('[useCloudBackups]: Fetching all backups');
- const backups = await fetchAllBackups();
-
- logger.debug(`[useCloudBackups]: Retrieved ${backups.files.length} backup files`);
- setBackups(backups);
- setStep(CloudBackupStep.IDLE);
- } catch (e) {
- setStep(CloudBackupStep.FAILED);
- logger.error(new RainbowError('[useCloudBackups]: Failed to fetch all backups'), {
- error: e,
- });
- }
- setIsFetching(false);
- };
-
- useEffect(() => {
- fetchBackups();
- }, []);
-
- return {
- isFetching,
- backups,
- fetchBackups,
- userData,
- step,
- };
-}
diff --git a/src/hooks/useColorForAsset.ts b/src/hooks/useColorForAsset.ts
index bfaf954e388..21eab85420b 100644
--- a/src/hooks/useColorForAsset.ts
+++ b/src/hooks/useColorForAsset.ts
@@ -19,6 +19,10 @@ export default function useColorForAsset(
const isDarkMode = forceLightMode || isDarkModeTheme;
const colorDerivedFromAddress = useMemo(() => {
+ if (!resolvedAddress) {
+ return undefined;
+ }
+
const color = isETH(resolvedAddress)
? isDarkMode
? forceETHColor
diff --git a/src/hooks/useENSRegistrationStepHandler.tsx b/src/hooks/useENSRegistrationStepHandler.tsx
index d99183c9bec..8072546ac9d 100644
--- a/src/hooks/useENSRegistrationStepHandler.tsx
+++ b/src/hooks/useENSRegistrationStepHandler.tsx
@@ -15,16 +15,16 @@ import {
} from '@/helpers/ens';
import { updateTransactionRegistrationParameters } from '@/redux/ensRegistration';
import { ChainId } from '@/state/backendNetworks/types';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
const checkRegisterBlockTimestamp = async ({
registrationParameters,
secondsSinceCommitConfirmed,
- connectedToHardhat,
+ connectedToAnvil,
}: {
registrationParameters: RegistrationParameters;
secondsSinceCommitConfirmed: number;
- connectedToHardhat: boolean;
+ connectedToAnvil: boolean;
}) => {
try {
const provider = getProvider({ chainId: ChainId.mainnet });
@@ -35,7 +35,7 @@ const checkRegisterBlockTimestamp = async ({
(secs > ENS_SECONDS_WAIT_WITH_PADDING && secondsSinceCommitConfirmed > ENS_SECONDS_WAIT_WITH_PADDING) ||
// sometimes the provider.getBlock('latest) takes a long time to update to newest block
secondsSinceCommitConfirmed > ENS_SECONDS_WAIT_PROVIDER_PADDING ||
- connectedToHardhat
+ connectedToAnvil
) {
return true;
}
@@ -62,12 +62,12 @@ export default function useENSRegistrationStepHandler(observer = true) {
-1
);
- const { connectedToHardhat } = useConnectedToHardhatStore();
+ const { connectedToAnvil } = useConnectedToAnvilStore();
const [readyToRegister, setReadyToRegister] = useState(secondsSinceCommitConfirmed > ENS_SECONDS_WAIT);
// flag to wait 10 secs before we get the tx block, to be able to simulate not confirmed tx when testing
- const shouldLoopForConfirmation = useRef(connectedToHardhat);
+ const shouldLoopForConfirmation = useRef(connectedToAnvil);
const registrationStep = useMemo(() => {
if (mode === REGISTRATION_MODES.EDIT) return REGISTRATION_STEPS.EDIT;
@@ -100,8 +100,8 @@ export default function useENSRegistrationStepHandler(observer = true) {
if (!shouldLoopForConfirmation.current && block?.timestamp) {
const now = Date.now();
const msBlockTimestamp = getBlockMsTimestamp(block);
- // hardhat block timestamp is behind
- const timeDifference = connectedToHardhat ? now - msBlockTimestamp : 0;
+ // anvil block timestamp is behind
+ const timeDifference = connectedToAnvil ? now - msBlockTimestamp : 0;
const commitTransactionConfirmedAt = msBlockTimestamp + timeDifference;
const secs = differenceInSeconds(now, commitTransactionConfirmedAt);
setSecondsSinceCommitConfirmed(secondsSinceCommitConfirmed < 0 ? 0 : secs);
@@ -115,7 +115,7 @@ export default function useENSRegistrationStepHandler(observer = true) {
shouldLoopForConfirmation.current = false;
}
return confirmed;
- }, [observer, commitTransactionHash, connectedToHardhat, secondsSinceCommitConfirmed, dispatch]);
+ }, [observer, commitTransactionHash, connectedToAnvil, secondsSinceCommitConfirmed, dispatch]);
const startPollingWatchCommitTransaction = useCallback(async () => {
if (observer) return;
@@ -168,7 +168,7 @@ export default function useENSRegistrationStepHandler(observer = true) {
if (!observer && secondsSinceCommitConfirmed % 2 === 0 && secondsSinceCommitConfirmed >= ENS_SECONDS_WAIT && !readyToRegister) {
const checkIfReadyToRegister = async () => {
const readyToRegister = await checkRegisterBlockTimestamp({
- connectedToHardhat,
+ connectedToAnvil,
registrationParameters,
secondsSinceCommitConfirmed,
});
@@ -176,7 +176,7 @@ export default function useENSRegistrationStepHandler(observer = true) {
};
checkIfReadyToRegister();
}
- }, [connectedToHardhat, observer, readyToRegister, registrationParameters, secondsSinceCommitConfirmed]);
+ }, [connectedToAnvil, observer, readyToRegister, registrationParameters, secondsSinceCommitConfirmed]);
useEffect(
() => () => {
diff --git a/src/hooks/useFarcasterAccountForWallets.ts b/src/hooks/useFarcasterAccountForWallets.ts
index 4c5702051b7..80873b867e3 100644
--- a/src/hooks/useFarcasterAccountForWallets.ts
+++ b/src/hooks/useFarcasterAccountForWallets.ts
@@ -12,12 +12,12 @@ import { AllRainbowWallets } from '@/model/wallet';
type SummaryData = ReturnType['data'];
-const getWalletForAddress = (wallets: AllRainbowWallets, address: string) => {
+const getWalletForAddress = (wallets: AllRainbowWallets | null, address: string) => {
return Object.values(wallets || {}).find(wallet => wallet.addresses.some(addr => isLowerCaseMatch(addr.address, address)));
};
-export const useFarcasterWalletAddress = () => {
- const [farcasterWalletAddress, setFarcasterWalletAddress] = useState(null);
+export const useFarcasterAccountForWallets = () => {
+ const [farcasterWalletAddress, setFarcasterWalletAddress] = useState();
const { accountAddress } = useAccountSettings();
const { wallets } = useWallets();
@@ -33,31 +33,32 @@ export const useFarcasterWalletAddress = () => {
currency: store.getState().settings.nativeCurrency,
})
);
- if (isEmpty(summaryData?.data.addresses) || isEmpty(wallets)) {
- setFarcasterWalletAddress(null);
+ const addresses = summaryData?.data.addresses;
+
+ if (!addresses || isEmpty(addresses) || isEmpty(wallets)) {
+ setFarcasterWalletAddress(undefined);
return;
}
- const selectedAddressFid = summaryData?.data.addresses[accountAddress as Address]?.meta?.farcaster?.fid;
-
- if (selectedAddressFid && getWalletForAddress(wallets || {}, accountAddress)?.type !== walletTypes.readOnly) {
+ const selectedAddressFid = addresses[accountAddress]?.meta?.farcaster?.fid;
+ if (selectedAddressFid && getWalletForAddress(wallets, accountAddress)?.type !== walletTypes.readOnly) {
setFarcasterWalletAddress(accountAddress);
return;
}
- const farcasterWalletAddress = Object.keys(summaryData?.data.addresses || {}).find(addr => {
+ const farcasterWalletAddress = Object.keys(addresses).find(addr => {
const address = addr as Address;
const faracsterId = summaryData?.data.addresses[address]?.meta?.farcaster?.fid;
- if (faracsterId && getWalletForAddress(wallets || {}, address)?.type !== walletTypes.readOnly) {
- return faracsterId;
+ if (faracsterId && getWalletForAddress(wallets, address)?.type !== walletTypes.readOnly) {
+ return address;
}
- });
+ }) as Address | undefined;
if (farcasterWalletAddress) {
setFarcasterWalletAddress(farcasterWalletAddress);
return;
}
- setFarcasterWalletAddress(null);
+ setFarcasterWalletAddress(undefined);
}, [wallets, allAddresses, accountAddress]);
return farcasterWalletAddress;
diff --git a/src/hooks/useImportingWallet.ts b/src/hooks/useImportingWallet.ts
index d0f92e12a3a..096099c8f61 100644
--- a/src/hooks/useImportingWallet.ts
+++ b/src/hooks/useImportingWallet.ts
@@ -3,7 +3,6 @@ import lang from 'i18n-js';
import { keys } from 'lodash';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { InteractionManager, Keyboard, TextInput } from 'react-native';
-import { IS_TESTING } from 'react-native-dotenv';
import { useDispatch } from 'react-redux';
import useAccountSettings from './useAccountSettings';
import { fetchENSAvatar } from './useENSAvatar';
@@ -29,9 +28,9 @@ import { deriveAccountFromWalletInput } from '@/utils/wallet';
import { logger, RainbowError } from '@/logger';
import { handleReviewPromptAction } from '@/utils/reviewAlert';
import { ReviewPromptAction } from '@/storage/schema';
-import { checkWalletsForBackupStatus } from '@/screens/SettingsSheet/utils';
-import walletBackupTypes from '@/helpers/walletBackupTypes';
import { ChainId } from '@/state/backendNetworks/types';
+import { backupsStore } from '@/state/backups/backups';
+import { IS_TEST } from '@/env';
export default function useImportingWallet({ showImportModal = true } = {}) {
const { accountAddress } = useAccountSettings();
@@ -52,6 +51,10 @@ export default function useImportingWallet({ showImportModal = true } = {}) {
const { updateWalletENSAvatars } = useWalletENSAvatar();
const profilesEnabled = useExperimentalFlag(PROFILES);
+ const { backupProvider } = backupsStore(state => ({
+ backupProvider: state.backupProvider,
+ }));
+
const inputRef = useRef(null);
const { handleFocus } = useMagicAutofocus(inputRef);
@@ -291,7 +294,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) {
image,
true
);
- await dispatch(walletsLoadState(profilesEnabled));
+ await dispatch(walletsLoadState());
handleSetImporting(false);
} else {
const previousWalletCount = keys(wallets).length;
@@ -336,32 +339,6 @@ export default function useImportingWallet({ showImportModal = true } = {}) {
});
}, 1_000);
- setTimeout(() => {
- // If it's not read only or hardware, show the backup sheet
- if (
- !(
- isENSAddressFormat(input) ||
- isUnstoppableAddressFormat(input) ||
- isValidAddress(input) ||
- isValidBluetoothDeviceId(input)
- )
- ) {
- const { backupProvider } = checkWalletsForBackupStatus(wallets);
-
- let stepType: string = WalletBackupStepTypes.no_provider;
- if (backupProvider === walletBackupTypes.cloud) {
- stepType = WalletBackupStepTypes.backup_now_to_cloud;
- } else if (backupProvider === walletBackupTypes.manual) {
- stepType = WalletBackupStepTypes.backup_now_manually;
- }
-
- IS_TESTING !== 'true' &&
- Navigation.handleAction(Routes.BACKUP_SHEET, {
- step: stepType,
- });
- }
- }, 1000);
-
analytics.track('Imported seed phrase', {
isWalletEthZero,
});
@@ -414,6 +391,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) {
showImportModal,
profilesEnabled,
dangerouslyGetParent,
+ backupProvider,
]);
return {
diff --git a/src/hooks/useInitializeWallet.ts b/src/hooks/useInitializeWallet.ts
index 5f934050e9d..80aa4e903ea 100644
--- a/src/hooks/useInitializeWallet.ts
+++ b/src/hooks/useInitializeWallet.ts
@@ -68,7 +68,7 @@ export default function useInitializeWallet() {
if (shouldRunMigrations && !seedPhrase) {
logger.debug('[useInitializeWallet]: shouldRunMigrations && !seedPhrase? => true');
- await dispatch(walletsLoadState(profilesEnabled));
+ await dispatch(walletsLoadState());
logger.debug('[useInitializeWallet]: walletsLoadState call #1');
await runMigrations();
logger.debug('[useInitializeWallet]: done with migrations');
@@ -110,7 +110,7 @@ export default function useInitializeWallet() {
if (seedPhrase || isNew) {
logger.debug('[useInitializeWallet]: walletsLoadState call #2');
- await dispatch(walletsLoadState(profilesEnabled));
+ await dispatch(walletsLoadState());
}
if (isNil(walletAddress)) {
diff --git a/src/hooks/useManageCloudBackups.ts b/src/hooks/useManageCloudBackups.ts
index 141f26b7f4e..323fd1d62db 100644
--- a/src/hooks/useManageCloudBackups.ts
+++ b/src/hooks/useManageCloudBackups.ts
@@ -3,12 +3,21 @@ import lang from 'i18n-js';
import { useDispatch } from 'react-redux';
import { cloudPlatform } from '../utils/platform';
import { WrappedAlert as Alert } from '@/helpers/alert';
-import { GoogleDriveUserData, getGoogleAccountUserData, deleteAllBackups, logoutFromGoogleDrive } from '@/handlers/cloudBackup';
-import { clearAllWalletsBackupStatus, updateWalletBackupStatusesBasedOnCloudUserData } from '@/redux/wallets';
+import {
+ GoogleDriveUserData,
+ getGoogleAccountUserData,
+ deleteAllBackups,
+ logoutFromGoogleDrive as logout,
+ login,
+} from '@/handlers/cloudBackup';
+import { clearAllWalletsBackupStatus } from '@/redux/wallets';
import { showActionSheetWithOptions } from '@/utils';
import { IS_ANDROID } from '@/env';
import { RainbowError, logger } from '@/logger';
import * as i18n from '@/languages';
+import { backupsStore, CloudBackupState } from '@/state/backups/backups';
+import * as keychain from '@/keychain';
+import { authenticateWithPIN } from '@/handlers/authentication';
export default function useManageCloudBackups() {
const dispatch = useDispatch();
@@ -48,10 +57,21 @@ export default function useManageCloudBackups() {
await dispatch(clearAllWalletsBackupStatus());
};
+ const logoutFromGoogleDrive = async () => {
+ await logout();
+ backupsStore.setState({
+ backupProvider: undefined,
+ backups: { files: [] },
+ mostRecentBackup: undefined,
+ status: CloudBackupState.NotAvailable,
+ });
+ };
+
const loginToGoogleDrive = async () => {
- await dispatch(updateWalletBackupStatusesBasedOnCloudUserData());
try {
+ await login();
const accountDetails = await getGoogleAccountUserData();
+ backupsStore.getState().syncAndFetchBackups();
setAccountDetails(accountDetails ?? undefined);
} catch (error) {
logger.error(new RainbowError(`[useManageCloudBackups]: Logging into Google Drive failed.`), {
@@ -78,14 +98,36 @@ export default function useManageCloudBackups() {
},
async (buttonIndex: any) => {
if (buttonIndex === 0) {
- if (IS_ANDROID) {
- logoutFromGoogleDrive();
- setAccountDetails(undefined);
- }
- removeBackupStateFromAllWallets();
+ try {
+ let userPIN: string | undefined;
+ const hasBiometricsEnabled = await keychain.getSupportedBiometryType();
+ if (IS_ANDROID && !hasBiometricsEnabled) {
+ try {
+ userPIN = (await authenticateWithPIN()) ?? undefined;
+ } catch (e) {
+ Alert.alert(i18n.t(i18n.l.back_up.wrong_pin));
+ return;
+ }
+ }
- await deleteAllBackups();
- Alert.alert(lang.t('back_up.backup_deleted_successfully'));
+ // Prompt for authentication before allowing them to delete backups
+ await keychain.getAllKeys();
+
+ if (IS_ANDROID) {
+ logoutFromGoogleDrive();
+ setAccountDetails(undefined);
+ }
+ removeBackupStateFromAllWallets();
+
+ await deleteAllBackups();
+ Alert.alert(lang.t('back_up.backup_deleted_successfully'));
+ } catch (e) {
+ logger.error(new RainbowError(`[useManageCloudBackups]: Error deleting all backups`), {
+ error: (e as Error).message,
+ });
+
+ Alert.alert(lang.t('back_up.errors.keychain_access'));
+ }
}
}
);
@@ -94,7 +136,7 @@ export default function useManageCloudBackups() {
if (_buttonIndex === 1 && IS_ANDROID) {
logoutFromGoogleDrive();
setAccountDetails(undefined);
- removeBackupStateFromAllWallets().then(() => loginToGoogleDrive());
+ loginToGoogleDrive();
}
}
);
diff --git a/src/hooks/useRefreshAccountData.ts b/src/hooks/useRefreshAccountData.ts
index 388dfc2fcc3..d38b57ac45b 100644
--- a/src/hooks/useRefreshAccountData.ts
+++ b/src/hooks/useRefreshAccountData.ts
@@ -6,22 +6,21 @@ import useAccountSettings from './useAccountSettings';
import { PROFILES, useExperimentalFlag } from '@/config';
import { logger, RainbowError } from '@/logger';
import { queryClient } from '@/react-query';
-import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery';
-import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets';
+import { userAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets';
import { invalidateAddressNftsQueries } from '@/resources/nfts';
import { positionsQueryKey } from '@/resources/defi/PositionsQuery';
import { Address } from 'viem';
import { addysSummaryQueryKey } from '@/resources/summary/summary';
import useWallets from './useWallets';
import { claimablesQueryKey } from '@/resources/addys/claimables/query';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
export default function useRefreshAccountData() {
const dispatch = useDispatch();
const { accountAddress, nativeCurrency } = useAccountSettings();
const [isRefreshing, setIsRefreshing] = useState(false);
const profilesEnabled = useExperimentalFlag(PROFILES);
- const { connectedToHardhat } = useConnectedToHardhatStore();
+ const { connectedToAnvil } = useConnectedToAnvilStore();
const { wallets } = useWallets();
@@ -35,10 +34,7 @@ export default function useRefreshAccountData() {
queryClient.invalidateQueries(positionsQueryKey({ address: accountAddress as Address, currency: nativeCurrency }));
queryClient.invalidateQueries(claimablesQueryKey({ address: accountAddress, currency: nativeCurrency }));
queryClient.invalidateQueries(addysSummaryQueryKey({ addresses: allAddresses, currency: nativeCurrency }));
- queryClient.invalidateQueries(userAssetsQueryKey({ address: accountAddress, currency: nativeCurrency, connectedToHardhat }));
- queryClient.invalidateQueries(
- swapsUserAssetsQueryKey({ address: accountAddress as Address, currency: nativeCurrency, testnetMode: !!connectedToHardhat })
- );
+ queryClient.invalidateQueries(userAssetsQueryKey({ address: accountAddress, currency: nativeCurrency, testnetMode: connectedToAnvil }));
try {
const getWalletNames = dispatch(fetchWalletNames());
@@ -52,7 +48,7 @@ export default function useRefreshAccountData() {
logger.error(new RainbowError(`[useRefreshAccountData]: Error refreshing data: ${error}`));
throw error;
}
- }, [accountAddress, allAddresses, connectedToHardhat, dispatch, nativeCurrency, profilesEnabled]);
+ }, [accountAddress, allAddresses, connectedToAnvil, dispatch, nativeCurrency, profilesEnabled]);
const refresh = useCallback(async () => {
if (isRefreshing) return;
diff --git a/src/hooks/useTimeout.ts b/src/hooks/useTimeout.ts
index 3c663ea779c..a7d6d44b505 100644
--- a/src/hooks/useTimeout.ts
+++ b/src/hooks/useTimeout.ts
@@ -18,7 +18,10 @@ export default function useTimeout(): [(func: () => void, ms?: number) => void,
return [start, stop, handle];
}
-export function useTimeoutEffect(onTimeout: (cancelled: boolean) => void, delay: number) {
+export function useTimeoutEffect(
+ onTimeout: (e: { cancelled: boolean; elapsedTime: number }) => void,
+ { timeout, enabled = true }: { timeout: number; enabled?: boolean }
+) {
const callback = useRef(onTimeout);
useLayoutEffect(() => {
callback.current = onTimeout;
@@ -26,12 +29,21 @@ export function useTimeoutEffect(onTimeout: (cancelled: boolean) => void, delay:
const timeoutRef = useRef();
useEffect(() => {
+ if (!enabled) return;
const startedAt = Date.now();
- timeoutRef.current = setTimeout(() => callback.current(false), delay);
- const timeout = timeoutRef.current;
+ timeoutRef.current = setTimeout(() => {
+ callback.current({
+ cancelled: false,
+ elapsedTime: Date.now() - startedAt,
+ });
+ }, timeout);
return () => {
- clearTimeout(timeout);
- if (Date.now() - startedAt < delay) callback.current(true);
+ if (!timeoutRef.current) return;
+ clearTimeout(timeoutRef.current);
+ const elapsedTime = Date.now() - startedAt;
+ if (elapsedTime < timeout) {
+ callback.current({ cancelled: true, elapsedTime });
+ }
};
- }, [delay]);
+ }, [timeout, enabled]);
}
diff --git a/src/hooks/useUpdateEmoji.ts b/src/hooks/useUpdateEmoji.ts
index d38f229ae20..7a6781788b0 100644
--- a/src/hooks/useUpdateEmoji.ts
+++ b/src/hooks/useUpdateEmoji.ts
@@ -17,11 +17,11 @@ export default function useUpdateEmoji() {
const saveInfo = useCallback(
async (name: string, color: number) => {
const walletId = selectedWallet.id;
- const newWallets: typeof wallets = {
+ const newWallets = {
...wallets,
[walletId]: {
...wallets![walletId],
- addresses: wallets![walletId].addresses.map((singleAddress: { address: string }) =>
+ addresses: wallets![walletId].addresses.map(singleAddress =>
singleAddress.address.toLowerCase() === accountAddress.toLowerCase()
? {
...singleAddress,
diff --git a/src/hooks/useWalletBalances.ts b/src/hooks/useWalletBalances.ts
index fbafc1c909e..9f1e912e437 100644
--- a/src/hooks/useWalletBalances.ts
+++ b/src/hooks/useWalletBalances.ts
@@ -3,11 +3,7 @@ import { useMemo } from 'react';
import { Address } from 'viem';
import useAccountSettings from './useAccountSettings';
import { useAddysSummary } from '@/resources/summary/summary';
-import { useQueries } from '@tanstack/react-query';
-import { fetchPositions, positionsQueryKey } from '@/resources/defi/PositionsQuery';
-import { RainbowPositions } from '@/resources/defi/types';
import { add, convertAmountToNativeDisplay } from '@/helpers/utilities';
-import { queryClient } from '@/react-query';
const QUERY_CONFIG = {
staleTime: 60_000, // 1 minute
@@ -43,7 +39,7 @@ const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => {
[wallets]
);
- const { data: summaryData, isLoading: isSummaryLoading } = useAddysSummary(
+ const { data: summaryData, isLoading } = useAddysSummary(
{
addresses: allAddresses,
currency: nativeCurrency,
@@ -51,17 +47,6 @@ const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => {
QUERY_CONFIG
);
- const positionQueries = useQueries({
- queries: allAddresses.map(address => ({
- queryKey: positionsQueryKey({ address, currency: nativeCurrency }),
- queryFn: () => fetchPositions({ address, currency: nativeCurrency }),
- enabled: !!address,
- ...QUERY_CONFIG,
- })),
- });
-
- const isLoading = isSummaryLoading || positionQueries.some(query => query.isLoading);
-
const balances = useMemo(() => {
const result: Record = {};
@@ -70,9 +55,10 @@ const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => {
for (const address of allAddresses) {
const lowerCaseAddress = address.toLowerCase() as Address;
const assetBalance = summaryData?.data?.addresses?.[lowerCaseAddress]?.summary?.asset_value?.toString() || '0';
- const positionData = queryClient.getQueryData(positionsQueryKey({ address, currency: nativeCurrency }));
- const positionsBalance = positionData ? positionData.totals.total.amount : '0';
- const totalAccountBalance = add(assetBalance, positionsBalance);
+ const positionsBalance = summaryData?.data?.addresses?.[lowerCaseAddress]?.summary?.positions_value?.toString() || '0';
+ const claimablesBalance = summaryData?.data?.addresses?.[lowerCaseAddress]?.summary?.claimables_value?.toString() || '0';
+
+ const totalAccountBalance = add(assetBalance, add(positionsBalance, claimablesBalance));
result[lowerCaseAddress] = {
assetBalanceAmount: assetBalance,
diff --git a/src/hooks/useWalletCloudBackup.ts b/src/hooks/useWalletCloudBackup.ts
index 57b9caac681..cb5d6350a5e 100644
--- a/src/hooks/useWalletCloudBackup.ts
+++ b/src/hooks/useWalletCloudBackup.ts
@@ -1,16 +1,14 @@
-import { captureException } from '@sentry/react-native';
-import lang from 'i18n-js';
import { values } from 'lodash';
-import { useCallback, useMemo } from 'react';
+import { useCallback } from 'react';
import { Linking } from 'react-native';
import { useDispatch } from 'react-redux';
-import { addWalletToCloudBackup, backupWalletToCloud, findLatestBackUp } from '../model/backup';
+import { backupWalletToCloud } from '../model/backup';
import { setWalletBackedUp } from '../redux/wallets';
import { cloudPlatform } from '../utils/platform';
import useWallets from './useWallets';
import { WrappedAlert as Alert } from '@/helpers/alert';
import { analytics } from '@/analytics';
-import { CLOUD_BACKUP_ERRORS, isCloudBackupAvailable } from '@/handlers/cloudBackup';
+import { CLOUD_BACKUP_ERRORS, getGoogleAccountUserData, isCloudBackupAvailable, login } from '@/handlers/cloudBackup';
import WalletBackupTypes from '@/helpers/walletBackupTypes';
import { logger, RainbowError } from '@/logger';
import { getSupportedBiometryType } from '@/keychain';
@@ -41,7 +39,6 @@ export function getUserError(e: Error) {
export default function useWalletCloudBackup() {
const dispatch = useDispatch();
const { wallets } = useWallets();
- const latestBackup = useMemo(() => findLatestBackUp(wallets), [wallets]);
const walletCloudBackup = useCallback(
async ({
@@ -52,36 +49,63 @@ export default function useWalletCloudBackup() {
}: {
handleNoLatestBackup?: () => void;
handlePasswordNotFound?: () => void;
- onError?: (error: string) => void;
- onSuccess?: () => void;
+ onError?: (error: string, isDamaged?: boolean) => void;
+ onSuccess?: (password: string) => void;
password: string;
walletId: string;
}): Promise => {
- const isAvailable = await isCloudBackupAvailable();
- if (!isAvailable) {
- analytics.track('iCloud not enabled', {
- category: 'backup',
- });
- Alert.alert(lang.t('modal.back_up.alerts.cloud_not_enabled.label'), lang.t('modal.back_up.alerts.cloud_not_enabled.description'), [
- {
- onPress: () => {
- Linking.openURL('https://support.apple.com/en-us/HT204025');
- analytics.track('View how to Enable iCloud', {
- category: 'backup',
- });
- },
- text: lang.t('modal.back_up.alerts.cloud_not_enabled.show_me'),
- },
- {
- onPress: () => {
- analytics.track('Ignore how to enable iCloud', {
- category: 'backup',
- });
- },
- style: 'cancel',
- text: lang.t('modal.back_up.alerts.cloud_not_enabled.no_thanks'),
- },
- ]);
+ if (IS_ANDROID) {
+ try {
+ await login();
+ const userData = await getGoogleAccountUserData();
+ if (!userData) {
+ Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found));
+ return false;
+ }
+ } catch (e) {
+ logger.error(new RainbowError('[BackupSheetSectionNoProvider]: No account found'), {
+ error: e,
+ });
+ Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found));
+ return false;
+ }
+ } else {
+ const isAvailable = await isCloudBackupAvailable();
+ if (!isAvailable) {
+ analytics.track('iCloud not enabled', {
+ category: 'backup',
+ });
+ Alert.alert(
+ i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.label),
+ i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.description),
+ [
+ {
+ onPress: () => {
+ Linking.openURL('https://support.apple.com/en-us/HT204025');
+ analytics.track('View how to Enable iCloud', {
+ category: 'backup',
+ });
+ },
+ text: i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.show_me),
+ },
+ {
+ onPress: () => {
+ analytics.track('Ignore how to enable iCloud', {
+ category: 'backup',
+ });
+ },
+ style: 'cancel',
+ text: i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.no_thanks),
+ },
+ ]
+ );
+ return false;
+ }
+ }
+
+ const wallet = wallets?.[walletId];
+ if (wallet?.damaged) {
+ onError?.(i18n.t(i18n.l.back_up.errors.damaged_wallet), true);
return false;
}
@@ -101,23 +125,14 @@ export default function useWalletCloudBackup() {
logger.debug('[useWalletCloudBackup]: password fetched correctly');
let updatedBackupFile = null;
+
try {
- if (!latestBackup) {
- logger.debug(`[useWalletCloudBackup]: backing up to ${cloudPlatform}: ${wallets![walletId]}`);
- updatedBackupFile = await backupWalletToCloud({
- password,
- wallet: wallets![walletId],
- userPIN,
- });
- } else {
- logger.debug(`[useWalletCloudBackup]: adding wallet to ${cloudPlatform} backup: ${wallets![walletId]}`);
- updatedBackupFile = await addWalletToCloudBackup({
- password,
- wallet: wallets![walletId],
- filename: latestBackup,
- userPIN,
- });
- }
+ logger.debug(`[useWalletCloudBackup]: backing up to ${cloudPlatform}: ${(wallets || {})[walletId]}`);
+ updatedBackupFile = await backupWalletToCloud({
+ password,
+ wallet: (wallets || {})[walletId],
+ userPIN,
+ });
} catch (e: any) {
const userError = getUserError(e);
!!onError && onError(userError);
@@ -134,7 +149,7 @@ export default function useWalletCloudBackup() {
logger.debug('[useWalletCloudBackup]: backup completed!');
await dispatch(setWalletBackedUp(walletId, WalletBackupTypes.cloud, updatedBackupFile));
logger.debug('[useWalletCloudBackup]: backup saved everywhere!');
- !!onSuccess && onSuccess();
+ !!onSuccess && onSuccess(password);
return true;
} catch (e) {
logger.error(new RainbowError(`[useWalletCloudBackup]: error while trying to save wallet backup state: ${e}`));
@@ -148,7 +163,7 @@ export default function useWalletCloudBackup() {
return false;
},
- [dispatch, latestBackup, wallets]
+ [dispatch, wallets]
);
return walletCloudBackup;
diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts
index db186edc714..9013f7106c2 100644
--- a/src/hooks/useWalletSectionsData.ts
+++ b/src/hooks/useWalletSectionsData.ts
@@ -9,14 +9,13 @@ import useSendableUniqueTokens from './useSendableUniqueTokens';
import useShowcaseTokens from './useShowcaseTokens';
import useWallets from './useWallets';
import { buildBriefWalletSectionsSelector } from '@/helpers/buildWalletSections';
-import { useSortedUserAssets } from '@/resources/assets/useSortedUserAssets';
import { useLegacyNFTs } from '@/resources/nfts';
import useWalletsWithBalancesAndNames from './useWalletsWithBalancesAndNames';
+import { useUserAssetsStore } from '@/state/assets/userAssets';
import { useRemoteConfig } from '@/model/remoteConfig';
import { usePositions } from '@/resources/defi/PositionsQuery';
import { useClaimables } from '@/resources/addys/claimables/query';
import { useExperimentalConfig } from '@/config/experimentalHooks';
-import { useUserAssetsStore } from '@/state/assets/userAssets';
import { analyticsV2 } from '@/analytics';
import { Claimable } from '@/resources/addys/claimables/types';
import { throttle } from 'lodash';
@@ -55,13 +54,16 @@ export default function useWalletSectionsData({
}: {
type?: string;
} = {}) {
+ const { accountAddress, language, network, nativeCurrency } = useAccountSettings();
const { selectedWallet, isReadOnlyWallet } = useWallets();
- const { isLoading: isLoadingUserAssets, data: sortedAssets = [] } = useSortedUserAssets();
+ const { isLoadingUserAssets, sortedAssets = [] } = useUserAssetsStore(state => ({
+ sortedAssets: state.legacyUserAssets,
+ isLoadingUserAssets: state.isLoadingUserAssets,
+ }));
const isWalletEthZero = useIsWalletEthZero();
const { nftSort, nftSortDirection } = useNftSort();
- const { accountAddress, language, network, nativeCurrency } = useAccountSettings();
const { sendableUniqueTokens } = useSendableUniqueTokens();
const {
data: { nfts: allUniqueTokens },
@@ -121,6 +123,16 @@ export default function useWalletSectionsData({
const { isCoinListEdited } = useCoinListEdited();
+ useEffect(() => {
+ if (isLoadingUserAssets || type !== 'wallet') return;
+ const params = { screen: 'wallet' as const, no_icon: 0, no_price: 0, total_tokens: sortedAssets.length };
+ for (const asset of sortedAssets) {
+ if (!asset.icon_url) params.no_icon += 1;
+ if (!asset.price?.relative_change_24h) params.no_price += 1;
+ }
+ analyticsV2.track(analyticsV2.event.tokenList, params);
+ }, [isLoadingUserAssets, sortedAssets, type]);
+
const walletSections = useMemo(() => {
const accountInfo = {
hiddenAssets,
diff --git a/src/hooks/useWallets.ts b/src/hooks/useWallets.ts
index 38363886917..20de06f22a1 100644
--- a/src/hooks/useWallets.ts
+++ b/src/hooks/useWallets.ts
@@ -5,14 +5,12 @@ import { RainbowWallet } from '@/model/wallet';
import { AppState } from '@/redux/store';
const walletSelector = createSelector(
- ({ wallets: { isWalletLoading, selected = {} as RainbowWallet, walletNames, wallets } }: AppState) => ({
- isWalletLoading,
- selectedWallet: selected as any,
+ ({ wallets: { selected = {} as RainbowWallet, walletNames, wallets } }: AppState) => ({
+ selectedWallet: selected,
walletNames,
wallets,
}),
- ({ isWalletLoading, selectedWallet, walletNames, wallets }) => ({
- isWalletLoading,
+ ({ selectedWallet, walletNames, wallets }) => ({
selectedWallet,
walletNames,
wallets,
@@ -20,13 +18,12 @@ const walletSelector = createSelector(
);
export default function useWallets() {
- const { isWalletLoading, selectedWallet, walletNames, wallets } = useSelector(walletSelector);
+ const { selectedWallet, walletNames, wallets } = useSelector(walletSelector);
return {
isDamaged: selectedWallet?.damaged,
isReadOnlyWallet: selectedWallet.type === WalletTypes.readOnly,
isHardwareWallet: !!selectedWallet.deviceId,
- isWalletLoading,
selectedWallet,
walletNames,
wallets,
diff --git a/src/hooks/useWalletsHiddenBalances.ts b/src/hooks/useWalletsHiddenBalances.ts
index d12b3292f22..fccff352bd9 100644
--- a/src/hooks/useWalletsHiddenBalances.ts
+++ b/src/hooks/useWalletsHiddenBalances.ts
@@ -1,12 +1,8 @@
import { AllRainbowWallets } from '@/model/wallet';
import { useMemo, useState, useEffect, useCallback } from 'react';
import { Address } from 'viem';
-import useAccountSettings from './useAccountSettings';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
-import { NativeCurrencyKey } from '@/entities/nativeCurrencyTypes';
import { userAssetsStore } from '@/state/assets/userAssets';
import { queryClient } from '@/react-query';
-import { userAssetsQueryKey, UserAssetsResult } from '@/resources/assets/UserAssetsQuery';
import { add, isEqual, multiply } from '@/helpers/utilities';
import { isEqual as _isEqual } from 'lodash';
@@ -14,22 +10,11 @@ export type WalletBalanceResult = {
hiddenBalances: Record;
};
-const getHiddenAssetBalance = ({
- address,
- nativeCurrency,
- connectedToHardhat,
-}: {
- address: Address;
- nativeCurrency: NativeCurrencyKey;
- connectedToHardhat: boolean;
-}) => {
+const getHiddenAssetBalance = ({ address }: { address: Address }) => {
const hiddenAssetIds = userAssetsStore.getState(address).getHiddenAssetsIds();
- const assetData = queryClient.getQueryData(
- userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat })
- );
const balance = hiddenAssetIds.reduce((acc, uniqueId) => {
- const asset = assetData?.[uniqueId];
+ const asset = userAssetsStore.getState(address).getUserAsset(uniqueId);
if (!asset) return acc;
const assetNativeBalance = multiply(asset.price?.value || 0, asset.balance?.amount || 0);
return add(acc, assetNativeBalance);
@@ -39,8 +24,6 @@ const getHiddenAssetBalance = ({
};
const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResult => {
- const { nativeCurrency } = useAccountSettings();
- const connectedToHardhat = useConnectedToHardhatStore(state => state.connectedToHardhat);
const [hiddenBalances, setHiddenBalances] = useState>({});
const allAddresses = useMemo(
@@ -48,24 +31,21 @@ const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResu
[wallets]
);
- const calculateHiddenBalanceForAddress = useCallback(
- (address: Address) => {
- const lowerCaseAddress = address.toLowerCase() as Address;
- const hiddenAssetBalance = getHiddenAssetBalance({ address, nativeCurrency, connectedToHardhat });
+ const calculateHiddenBalanceForAddress = useCallback((address: Address) => {
+ const lowerCaseAddress = address.toLowerCase() as Address;
+ const hiddenAssetBalance = getHiddenAssetBalance({ address });
- setHiddenBalances(prev => {
- const newBalance = hiddenAssetBalance;
- if (!prev[lowerCaseAddress] || !isEqual(prev[lowerCaseAddress], newBalance)) {
- return {
- ...prev,
- [lowerCaseAddress]: newBalance,
- };
- }
- return prev;
- });
- },
- [nativeCurrency, connectedToHardhat]
- );
+ setHiddenBalances(prev => {
+ const newBalance = hiddenAssetBalance;
+ if (!prev[lowerCaseAddress] || !isEqual(prev[lowerCaseAddress], newBalance)) {
+ return {
+ ...prev,
+ [lowerCaseAddress]: newBalance,
+ };
+ }
+ return prev;
+ });
+ }, []);
useEffect(() => {
allAddresses.forEach(address => {
@@ -97,7 +77,7 @@ const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResu
subscriptions.forEach(sub => sub());
unsubscribeFromQueryCache();
};
- }, [allAddresses, calculateHiddenBalanceForAddress, connectedToHardhat, nativeCurrency]);
+ }, [allAddresses, calculateHiddenBalanceForAddress]);
return { hiddenBalances };
};
diff --git a/src/hooks/useWatchPendingTxs.ts b/src/hooks/useWatchPendingTxs.ts
index f939d6e408b..4c90fcfa16c 100644
--- a/src/hooks/useWatchPendingTxs.ts
+++ b/src/hooks/useWatchPendingTxs.ts
@@ -1,8 +1,7 @@
import { useMemo, useCallback } from 'react';
import useAccountSettings from './useAccountSettings';
+import { userAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets';
import { RainbowTransaction, MinedTransaction, TransactionStatus } from '@/entities';
-import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery';
-import { userAssetsQueryKey as swapsUserAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets';
import { transactionFetchQuery } from '@/resources/transactions/transaction';
import { RainbowError, logger } from '@/logger';
import { consolidatedTransactionsQueryKey } from '@/resources/transactions/consolidatedTransactions';
@@ -11,7 +10,7 @@ import { invalidateAddressNftsQueries } from '@/resources/nfts';
import { usePendingTransactionsStore } from '@/state/pendingTransactions';
import { Address } from 'viem';
import { staleBalancesStore } from '@/state/staleBalances';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
export const useWatchPendingTransactions = ({ address }: { address: string }) => {
@@ -19,7 +18,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) =>
storePendingTransactions: state.pendingTransactions,
setPendingTransactions: state.setPendingTransactions,
}));
- const { connectedToHardhat } = useConnectedToHardhatStore();
+ const { connectedToAnvil } = useConnectedToAnvilStore();
const pendingTransactions = useMemo(() => storePendingTransactions[address] || [], [address, storePendingTransactions]);
@@ -32,19 +31,12 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) =>
userAssetsQueryKey({
address,
currency: nativeCurrency,
- connectedToHardhat,
- })
- );
- queryClient.invalidateQueries(
- swapsUserAssetsQueryKey({
- address: address as Address,
- currency: nativeCurrency,
- testnetMode: !!connectedToHardhat,
+ testnetMode: connectedToAnvil,
})
);
invalidateAddressNftsQueries(address);
},
- [address, connectedToHardhat, nativeCurrency]
+ [address, connectedToAnvil, nativeCurrency]
);
const processSupportedNetworkTransaction = useCallback(
@@ -123,7 +115,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) =>
});
queryClient.refetchQueries({
- queryKey: userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat }),
+ queryKey: userAssetsQueryKey({ address, currency: nativeCurrency, testnetMode: connectedToAnvil }),
});
const supportedMainnetChainIds = useBackendNetworksStore.getState().getSupportedMainnetChainIds();
@@ -151,7 +143,7 @@ export const useWatchPendingTransactions = ({ address }: { address: string }) =>
address,
pendingTransactions: newPendingTransactions,
});
- }, [address, connectedToHardhat, nativeCurrency, pendingTransactions, processPendingTransaction, setPendingTransactions]);
+ }, [address, connectedToAnvil, nativeCurrency, pendingTransactions, processPendingTransaction, setPendingTransactions]);
return { watchPendingTransactions };
};
diff --git a/src/languages/ar_AR.json b/src/languages/ar_AR.json
index 581387865ed..680154a854b 100644
--- a/src/languages/ar_AR.json
+++ b/src/languages/ar_AR.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "مسح ذاكرة التخزين المؤقت لبيانات الصور",
"clear_local_storage": "مسح التخزين المحلي",
"clear_mmkv_storage": "مسح التخزين MMKV",
- "connect_to_hardhat": "الاتصال بـ hardhat",
- "disconnect_to_hardhat": "افصل عن Hardhat",
+ "connect_to_anvil": "الاتصال بـ anvil",
+ "disconnect_to_anvil": "افصل عن Anvil",
"crash_app_render_error": "تعطيل التطبيق (خطأ في التصيير)",
"enable_testnets": "تمكين شبكات الاختبار",
"installing_update": "تثبيت التحديث",
diff --git a/src/languages/en_US.json b/src/languages/en_US.json
index 725d9b674d1..66bdb0dec0d 100644
--- a/src/languages/en_US.json
+++ b/src/languages/en_US.json
@@ -96,7 +96,8 @@
"generic": "Error while trying to backup. Error code: %{errorCodes}",
"no_keys_found": "No keys found. Please try again.",
"backup_not_found": "Backup not found. Please try again.",
- "no_account_found": "Unable to retrieve backup files. Make sure you're logged in."
+ "no_account_found": "Unable to retrieve backup files. Make sure you're logged in.",
+ "damaged_wallet": "Unable to backup wallet. Missing keychain data."
},
"wrong_pin": "The PIN code you entered was incorrect and we can't make a backup. Please try again with the correct code.",
"already_backed_up": {
@@ -115,6 +116,8 @@
"no_backups": "No backups found",
"failed_to_fetch_backups": "Failed to fetch backups",
"retry": "Retry",
+ "refresh": "Refresh",
+ "syncing_cloud_store": "Syncing to %{cloudPlatformName}",
"fetching_backups": "Retrieving backups from %{cloudPlatformName}",
"back_up_to_platform": "Back up to %{cloudPlatformName}",
"restore_from_platform": "Restore from %{cloudPlatformName}",
@@ -137,7 +140,7 @@
"choose_backup_cloud_description": "Securely back up your wallet to %{cloudPlatform} so you can restore it if you lose your device or get a new one.",
"choose_backup_manual_description": "Back up your wallet manually by saving your secret phrase in a secure location.",
"enable_cloud_backups_description": "If you prefer to back up your wallets manually, you can do so below.",
- "latest_backup": "Last Backup: %{date}",
+ "latest_backup": "Latest Backup: %{date}",
"back_up_all_wallets_to_cloud": "Back Up All Wallets to %{cloudPlatformName}",
"most_recent_backup": "Most Recent Backup",
"out_of_date": "Out of Date",
@@ -145,6 +148,12 @@
"older_backups": "Older Backups",
"no_older_backups": "No Older Backups",
"older_backups_title": "%{date} at %{time}",
+ "statuses": {
+ "not_enabled": "Not Enabled",
+ "syncing": "Syncing",
+ "out_of_date": "Out of Date",
+ "up_to_date": "Up to Date"
+ },
"password": {
"a_password_youll_remember_part_one": "This password is",
"not": "not",
@@ -514,8 +523,8 @@
"clear_image_metadata_cache": "Clear Image Metadata Cache",
"clear_local_storage": "Clear local storage",
"clear_mmkv_storage": "Clear MMKV storage",
- "connect_to_hardhat": "Connect to hardhat",
- "disconnect_to_hardhat": "Disconnect from hardhat",
+ "connect_to_anvil": "Connect to anvil",
+ "disconnect_to_anvil": "Disconnect from anvil",
"crash_app_render_error": "Crash app (render error)",
"enable_testnets": "Enable Testnets",
"installing_update": "Installing update",
@@ -1220,6 +1229,12 @@
"check_out_this_wallet": "Check out this wallet's collectibles on 🌈 Rainbow at %{showcaseUrl}"
}
},
+ "loading": {
+ "backing_up": "Backing up...",
+ "creating_wallet": "Creating wallet...",
+ "importing_wallet": "Importing...",
+ "restoring": "Restoring..."
+ },
"message": {
"click_to_copy_to_clipboard": "Click to copy to clipboard",
"coming_soon": "Coming soon...",
@@ -3018,6 +3033,59 @@
"new_tab": "New Tab"
}
},
+ "trending_tokens": {
+ "all": "All",
+ "no_results": {
+ "title": "No results",
+ "body": "Try browsing a larger timeframe or a different network or category."
+ },
+ "and": "and",
+ "and_others": {
+ "one": "and %{count} other",
+ "other": "and %{count} others"
+ },
+ "filters": {
+ "categories": {
+ "TRENDING": "Trending",
+ "NEW": "New",
+ "FARCASTER": "Farcaster"
+ },
+ "sort": {
+ "RECOMMENDED": {
+ "label": "Sort",
+ "menuOption": "Default"
+ },
+ "VOLUME": "Volume",
+ "MARKET_CAP": "Market Cap",
+ "TOP_GAINERS": "Top Gainers",
+ "TOP_LOSERS": "Top Losers"
+ },
+ "time": {
+ "H12": "12 Hours",
+ "H12_ABBREVIATED": "12h",
+ "H24": "24 Hours",
+ "H24_ABBREVIATED": "24h",
+ "D7": "1 Week",
+ "D3": "3 Days"
+ }
+ }
+ },
+ "network_switcher": {
+ "customize_networks_banner": {
+ "title": "Customize Networks",
+ "tap_the": "Tap the",
+ "button_to_set_up": "button below to set up"
+ },
+ "drag_here_to_unpin": "Drop Here to Unpin",
+ "edit": "Edit",
+ "networks": "Networks",
+ "drag_to_rearrange": "Drag to Rearrange",
+ "show_less": "Show Less",
+ "more": "More",
+ "show_more": "More Networks",
+ "all_networks": "All Networks"
+ },
+ "done": "Done",
"copy": "Copy",
"paste": "Paste"
}
diff --git a/src/languages/es_419.json b/src/languages/es_419.json
index e75f3ecab54..be671fb2e61 100644
--- a/src/languages/es_419.json
+++ b/src/languages/es_419.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "Borrar Cache de Metadatos de Imagen",
"clear_local_storage": "Borrar almacenamiento local",
"clear_mmkv_storage": "Borrar almacenamiento de MMKV",
- "connect_to_hardhat": "Conectar a Hardhat",
- "disconnect_to_hardhat": "Desconectar de hardhat",
+ "connect_to_anvil": "Conectar a Anvil",
+ "disconnect_to_anvil": "Desconectar de anvil",
"crash_app_render_error": "Crash de la aplicación (error de renderización)",
"enable_testnets": "Habilitar Testnets",
"installing_update": "Instalando actualización",
diff --git a/src/languages/fr_FR.json b/src/languages/fr_FR.json
index 5219996ea32..2d36b80ad5e 100644
--- a/src/languages/fr_FR.json
+++ b/src/languages/fr_FR.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "Effacer le cache des métadonnées d'image",
"clear_local_storage": "Effacer le stockage local",
"clear_mmkv_storage": "Effacer le stockage MMKV",
- "connect_to_hardhat": "Se connecter à hardhat",
- "disconnect_to_hardhat": "Déconnecter de hardhat",
+ "connect_to_anvil": "Se connecter à anvil",
+ "disconnect_to_anvil": "Déconnecter de anvil",
"crash_app_render_error": "Crash app (render error) :)",
"enable_testnets": "Activer Testnets",
"installing_update": "Installation de la mise à jour",
diff --git a/src/languages/hi_IN.json b/src/languages/hi_IN.json
index 9f1e63ce207..6225599e370 100644
--- a/src/languages/hi_IN.json
+++ b/src/languages/hi_IN.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "छवि मेटाडाटा कैश साफ करें",
"clear_local_storage": "लोकल स्टोरेज को साफ करें",
"clear_mmkv_storage": "MMKV स्टोरेज को साफ करें",
- "connect_to_hardhat": "Hardhat से कनेक्ट करें",
- "disconnect_to_hardhat": "हार्डहैट से डिस्कनेक्ट करें",
+ "connect_to_anvil": "Anvil से कनेक्ट करें",
+ "disconnect_to_anvil": "हार्डहैट से डिस्कनेक्ट करें",
"crash_app_render_error": "ऐप्र्प्लिकेशन क्रैश करें (रेंडर त्रुटि)",
"enable_testnets": "टेस्टनेट्स सक्षम करें",
"installing_update": "अद्यतन स्थापित कर रहा है",
diff --git a/src/languages/id_ID.json b/src/languages/id_ID.json
index 2d46f0dc67e..33f73e497d8 100644
--- a/src/languages/id_ID.json
+++ b/src/languages/id_ID.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "Hapus Cache Metadata Gambar",
"clear_local_storage": "Hapus penyimpanan lokal",
"clear_mmkv_storage": "Hapus penyimpanan MMKV",
- "connect_to_hardhat": "Hubungkan ke hardhat",
- "disconnect_to_hardhat": "Putuskan hubungan dari hardhat",
+ "connect_to_anvil": "Hubungkan ke anvil",
+ "disconnect_to_anvil": "Putuskan hubungan dari anvil",
"crash_app_render_error": "Aplikasi crash (kesalahan render)",
"enable_testnets": "Aktifkan Testnets",
"installing_update": "Menginstal pembaruan",
diff --git a/src/languages/ja_JP.json b/src/languages/ja_JP.json
index 0087d9cc208..a9df28a7a1d 100644
--- a/src/languages/ja_JP.json
+++ b/src/languages/ja_JP.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "クリアイメージメタデータキャッシュ",
"clear_local_storage": "ローカルストレージをクリア",
"clear_mmkv_storage": "MMKVストレージをクリア",
- "connect_to_hardhat": "Hardhatに接続する",
- "disconnect_to_hardhat": "hardhatから切断する",
+ "connect_to_anvil": "Anvilに接続する",
+ "disconnect_to_anvil": "anvilから切断する",
"crash_app_render_error": "アプリをクラッシュさせる(レンダーエラー)",
"enable_testnets": "テストネットを有効にする",
"installing_update": "アップデートをインストール中",
diff --git a/src/languages/ko_KR.json b/src/languages/ko_KR.json
index 90da37af11c..4e9d0b3ac39 100644
--- a/src/languages/ko_KR.json
+++ b/src/languages/ko_KR.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "이미지 메타데이터 캐시 지우기",
"clear_local_storage": "로컬 저장소 지우기",
"clear_mmkv_storage": "MMKV 저장소 지우기",
- "connect_to_hardhat": "하드햇에 연결",
- "disconnect_to_hardhat": "hardhat 연결 해제",
+ "connect_to_anvil": "하드햇에 연결",
+ "disconnect_to_anvil": "anvil 연결 해제",
"crash_app_render_error": "애플리케이션 종료 (렌더링 오류)",
"enable_testnets": "테스트넷 활성화",
"installing_update": "업데이트 설치 중",
diff --git a/src/languages/pt_BR.json b/src/languages/pt_BR.json
index 2216e16b584..38db975c1f1 100644
--- a/src/languages/pt_BR.json
+++ b/src/languages/pt_BR.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "Limpar Cache de Metadados da Imagem",
"clear_local_storage": "Limpar Armazenamento Local",
"clear_mmkv_storage": "Limpar Armazenamento MMKV",
- "connect_to_hardhat": "Conectar ao Hardhat",
- "disconnect_to_hardhat": "Desconectar do hardhat",
+ "connect_to_anvil": "Conectar ao Anvil",
+ "disconnect_to_anvil": "Desconectar do anvil",
"crash_app_render_error": "Crash no app (erro de renderização)",
"enable_testnets": "Ativar Testnets",
"installing_update": "Instalando atualização",
diff --git a/src/languages/ru_RU.json b/src/languages/ru_RU.json
index 2867e9180ef..2bc33887870 100644
--- a/src/languages/ru_RU.json
+++ b/src/languages/ru_RU.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "Очистить кэш метаданных изображения",
"clear_local_storage": "Очистить локальное хранилище",
"clear_mmkv_storage": "Очистить хранилище MMKV",
- "connect_to_hardhat": "Подключиться к hardhat",
- "disconnect_to_hardhat": "Отключиться от hardhat",
+ "connect_to_anvil": "Подключиться к anvil",
+ "disconnect_to_anvil": "Отключиться от anvil",
"crash_app_render_error": "Падение приложения (ошибка отображения)",
"enable_testnets": "Включить тестовые сети",
"installing_update": "Установка обновления",
diff --git a/src/languages/th_TH.json b/src/languages/th_TH.json
index 80714c9a455..343029d2ee7 100644
--- a/src/languages/th_TH.json
+++ b/src/languages/th_TH.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "ล้างแคชข้อมูลภาพ",
"clear_local_storage": "ล้าง local storage",
"clear_mmkv_storage": "ล้าง MMKV storage",
- "connect_to_hardhat": "เชื่อมต่อกับ hardhat",
- "disconnect_to_hardhat": "ตัดการเชื่อมต่อจาก Hardhat",
+ "connect_to_anvil": "เชื่อมต่อกับ anvil",
+ "disconnect_to_anvil": "ตัดการเชื่อมต่อจาก Anvil",
"crash_app_render_error": "คว่ำ app (render error)",
"enable_testnets": "เปิดใช้งาน Testnets",
"installing_update": "กำลังติดตั้งการอัปเดต",
diff --git a/src/languages/tr_TR.json b/src/languages/tr_TR.json
index d7233d4a4ae..ffaa85ee5a8 100644
--- a/src/languages/tr_TR.json
+++ b/src/languages/tr_TR.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "Resim Metaveri Önbelleğini Temizle",
"clear_local_storage": "Yerel depolamayı temizle",
"clear_mmkv_storage": "MMKV depolamasını temizle",
- "connect_to_hardhat": "Hardhat'a bağlan",
- "disconnect_to_hardhat": "Hardhat'tan bağlantıyı kes",
+ "connect_to_anvil": "Anvil'a bağlan",
+ "disconnect_to_anvil": "Anvil'tan bağlantıyı kes",
"crash_app_render_error": "Uygulamayı çökert (render hatası)",
"enable_testnets": "Testnetleri Etkinleştir",
"installing_update": "Güncelleme Yükleniyor",
diff --git a/src/languages/zh_CN.json b/src/languages/zh_CN.json
index 92b9ff64e78..0d2540fa6e0 100644
--- a/src/languages/zh_CN.json
+++ b/src/languages/zh_CN.json
@@ -503,8 +503,8 @@
"clear_image_metadata_cache": "清除图片内元数据缓存",
"clear_local_storage": "清除本地存储",
"clear_mmkv_storage": "清除MMKV存储",
- "connect_to_hardhat": "连接到hardhat",
- "disconnect_to_hardhat": "断开硬帽连接",
+ "connect_to_anvil": "连接到anvil",
+ "disconnect_to_anvil": "断开硬帽连接",
"crash_app_render_error": "崩溃应用程序(渲染错误)",
"enable_testnets": "启用Testnets",
"installing_update": "正在安装更新",
diff --git a/src/model/backup.ts b/src/model/backup.ts
index 2eb50a7c297..c838796664d 100644
--- a/src/model/backup.ts
+++ b/src/model/backup.ts
@@ -1,15 +1,23 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
-import { NativeModules } from 'react-native';
+import { NativeModules, Linking } from 'react-native';
import { captureException } from '@sentry/react-native';
import { endsWith } from 'lodash';
-import { CLOUD_BACKUP_ERRORS, encryptAndSaveDataToCloud, getDataFromCloud } from '@/handlers/cloudBackup';
+import {
+ CLOUD_BACKUP_ERRORS,
+ encryptAndSaveDataToCloud,
+ getDataFromCloud,
+ isCloudBackupAvailable,
+ getGoogleAccountUserData,
+ login,
+ logoutFromGoogleDrive,
+ normalizeAndroidBackupFilename,
+} from '@/handlers/cloudBackup';
+import { Alert as NativeAlert } from '@/components/alerts';
import WalletBackupTypes from '../helpers/walletBackupTypes';
-import WalletTypes from '../helpers/walletTypes';
-import { Alert } from '@/components/alerts';
import { allWalletsKey, pinKey, privateKeyKey, seedPhraseKey, selectedWalletKey, identifierForVendorKey } from '@/utils/keychainConstants';
import * as keychain from '@/model/keychain';
import * as kc from '@/keychain';
-import { AllRainbowWallets, allWalletsVersion, createWallet, RainbowWallet } from './wallet';
+import { AllRainbowWallets, createWallet, RainbowWallet } from './wallet';
import { analytics } from '@/analytics';
import { logger, RainbowError } from '@/logger';
import { IS_ANDROID, IS_DEV } from '@/env';
@@ -24,16 +32,19 @@ import Routes from '@/navigation/routesNames';
import { clearAllStorages } from './mmkv';
import walletBackupStepTypes from '@/helpers/walletBackupStepTypes';
import { getRemoteConfig } from './remoteConfig';
+import { WrappedAlert as Alert } from '@/helpers/alert';
+import { AppDispatch } from '@/redux/store';
+import { backupsStore, CloudBackupState } from '@/state/backups/backups';
const { DeviceUUID } = NativeModules;
const encryptor = new AesEncryptor();
const PIN_REGEX = /^\d{4}$/;
export interface CloudBackups {
- files: Backup[];
+ files: BackupFile[];
}
-export interface Backup {
+export interface BackupFile {
isDirectory: boolean;
isFile: boolean;
lastModified: string;
@@ -44,8 +55,9 @@ export interface Backup {
}
export const parseTimestampFromFilename = (filename: string) => {
+ const name = normalizeAndroidBackupFilename(filename);
return Number(
- filename
+ name
.replace('.backup_', '')
.replace('backup_', '')
.replace('.json', '')
@@ -54,6 +66,27 @@ export const parseTimestampFromFilename = (filename: string) => {
);
};
+/**
+ * Parse the timestamp from a backup file name
+ * @param filename - The name of the backup file backup_${now}.json
+ * @returns The timestamp as a number
+ */
+export const parseTimestampFromBackupFile = (filename: string | null): number | undefined => {
+ if (!filename) {
+ return;
+ }
+ const match = filename.match(/backup_(\d+)\.json/);
+ if (!match) {
+ return;
+ }
+
+ if (Number.isNaN(Number(match[1]))) {
+ return;
+ }
+
+ return Number(match[1]);
+};
+
type BackupPassword = string;
interface BackedUpData {
@@ -63,9 +96,72 @@ interface BackedUpData {
export interface BackupUserData {
wallets: AllRainbowWallets;
}
+type MaybePromise = T | Promise;
+
+export const executeFnIfCloudBackupAvailable = async ({ fn, logout = false }: { fn: () => MaybePromise; logout?: boolean }) => {
+ backupsStore.getState().setStatus(CloudBackupState.InProgress);
+
+ if (IS_ANDROID) {
+ try {
+ if (logout) {
+ await logoutFromGoogleDrive();
+ }
+
+ const currentUser = await getGoogleAccountUserData();
+ if (!currentUser) {
+ await login();
+ await backupsStore.getState().syncAndFetchBackups();
+ }
+
+ const userData = await getGoogleAccountUserData();
+ if (!userData) {
+ Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found));
+ backupsStore.getState().setStatus(CloudBackupState.NotAvailable);
+ return;
+ }
+ // execute the function
+
+ // NOTE: Set this back to ready in order to process the backup
+ backupsStore.getState().setStatus(CloudBackupState.Ready);
+ return await fn();
+ } catch (e) {
+ logger.error(new RainbowError('[BackupSheetSectionNoProvider]: No account found'), {
+ error: e,
+ });
+ Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found));
+ backupsStore.getState().setStatus(CloudBackupState.NotAvailable);
+ }
+ } else {
+ const isAvailable = await isCloudBackupAvailable();
+ if (!isAvailable) {
+ Alert.alert(
+ i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.label),
+ i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.description),
+ [
+ {
+ onPress: () => {
+ Linking.openURL('https://support.apple.com/en-us/HT204025');
+ },
+ text: i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.show_me),
+ },
+ {
+ style: 'cancel',
+ text: i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.no_thanks),
+ },
+ ]
+ );
+ backupsStore.getState().setStatus(CloudBackupState.NotAvailable);
+ return;
+ }
+
+ // NOTE: Set this back to ready in order to process the backup
+ backupsStore.getState().setStatus(CloudBackupState.Ready);
+ return await fn();
+ }
+};
async function extractSecretsForWallet(wallet: RainbowWallet) {
- const allKeys = await keychain.loadAllKeys();
+ const allKeys = await kc.getAllKeys();
if (!allKeys) throw new Error(CLOUD_BACKUP_ERRORS.KEYCHAIN_ACCESS_ERROR);
const secrets = {} as { [key: string]: string };
@@ -100,17 +196,15 @@ async function extractSecretsForWallet(wallet: RainbowWallet) {
export async function backupAllWalletsToCloud({
wallets,
password,
- latestBackup,
onError,
onSuccess,
dispatch,
}: {
wallets: AllRainbowWallets;
password: BackupPassword;
- latestBackup: string | null;
onError?: (message: string) => void;
- onSuccess?: () => void;
- dispatch: any;
+ onSuccess?: (password: BackupPassword) => void;
+ dispatch: AppDispatch;
}) {
let userPIN: string | undefined;
const hasBiometricsEnabled = await kc.getSupportedBiometryType();
@@ -126,11 +220,9 @@ export async function backupAllWalletsToCloud({
try {
/**
* Loop over all keys and decrypt if necessary for android
- * if no latest backup, create first backup with all secrets
- * if latest backup, update updatedAt and add new secrets to the backup
*/
- const allKeys = await keychain.loadAllKeys();
+ const allKeys = await kc.getAllKeys();
if (!allKeys) {
onError?.(i18n.t(i18n.l.back_up.errors.no_keys_found));
return;
@@ -157,49 +249,21 @@ export async function backupAllWalletsToCloud({
label: cloudPlatform,
});
- let updatedBackupFile: any = null;
- if (!latestBackup) {
- const data = {
- createdAt: now,
- secrets: {},
- };
- const promises = Object.entries(allSecrets).map(async ([username, password]) => {
- const processedNewSecrets = await decryptAllPinEncryptedSecretsIfNeeded({ [username]: password }, userPIN);
-
- data.secrets = {
- ...data.secrets,
- ...processedNewSecrets,
- };
- });
-
- await Promise.all(promises);
- updatedBackupFile = await encryptAndSaveDataToCloud(data, password, `backup_${now}.json`);
- } else {
- // if we have a latest backup file, we need to update the updatedAt and add new secrets to the backup file..
- const backup = await getDataFromCloud(password, latestBackup);
- if (!backup) {
- onError?.(i18n.t(i18n.l.back_up.errors.backup_not_found));
- return;
- }
+ const data = {
+ createdAt: now,
+ secrets: {},
+ };
+ const promises = Object.entries(allSecrets).map(async ([username, password]) => {
+ const processedNewSecrets = await decryptAllPinEncryptedSecretsIfNeeded({ [username]: password }, userPIN);
- const data = {
- createdAt: backup.createdAt,
- secrets: backup.secrets,
+ data.secrets = {
+ ...data.secrets,
+ ...processedNewSecrets,
};
+ });
- const promises = Object.entries(allSecrets).map(async ([username, password]) => {
- const processedNewSecrets = await decryptAllPinEncryptedSecretsIfNeeded({ [username]: password }, userPIN);
-
- data.secrets = {
- ...data.secrets,
- ...processedNewSecrets,
- };
- });
-
- await Promise.all(promises);
- updatedBackupFile = await encryptAndSaveDataToCloud(data, password, latestBackup);
- }
-
+ await Promise.all(promises);
+ const updatedBackupFile = await encryptAndSaveDataToCloud(data, password, `backup_${now}.json`);
const walletIdsToUpdate = Object.keys(wallets);
await dispatch(setAllWalletsWithIdsAsBackedUp(walletIdsToUpdate, WalletBackupTypes.cloud, updatedBackupFile));
@@ -209,16 +273,18 @@ export async function backupAllWalletsToCloud({
label: cloudPlatform,
});
- onSuccess?.();
- } catch (error: any) {
- const userError = getUserError(error);
- onError?.(userError);
- captureException(error);
- analytics.track(`Error backing up all wallets to ${cloudPlatform}`, {
- category: 'backup',
- error: userError,
- label: cloudPlatform,
- });
+ onSuccess?.(password);
+ } catch (error) {
+ if (error instanceof Error) {
+ const userError = getUserError(error);
+ onError?.(userError);
+ captureException(error);
+ analytics.track(`Error backing up all wallets to ${cloudPlatform}`, {
+ category: 'backup',
+ error: userError,
+ label: cloudPlatform,
+ });
+ }
}
}
@@ -251,9 +317,15 @@ export async function addWalletToCloudBackup({
wallet: RainbowWallet;
filename: string;
userPIN?: string;
-}): Promise {
- // @ts-ignore
+}): Promise {
const backup = await getDataFromCloud(password, filename);
+ if (!backup) {
+ logger.error(new RainbowError('[backup]: Unable to get backup data for filename'), {
+ filename,
+ });
+ return null;
+ }
+
const now = Date.now();
const newSecretsToBeAddedToBackup = await extractSecretsForWallet(wallet);
const processedNewSecrets = await decryptAllPinEncryptedSecretsIfNeeded(newSecretsToBeAddedToBackup, userPIN);
@@ -321,25 +393,6 @@ export async function decryptAllPinEncryptedSecretsIfNeeded(secrets: Record {
- // Check if there's a wallet backed up
- if (wallet.backedUp && wallet.backupDate && wallet.backupFile && wallet.backupType === WalletBackupTypes.cloud) {
- // If there is one, let's grab the latest backup
- if (!latestBackup || Number(wallet.backupDate) > latestBackup) {
- filename = wallet.backupFile;
- latestBackup = Number(wallet.backupDate);
- }
- }
- });
- }
- return filename;
-}
-
export const RestoreCloudBackupResultStates = {
success: 'success',
failedWhenRestoring: 'failedWhenRestoring',
@@ -368,16 +421,14 @@ const sanitizeFilename = (filename: string) => {
*/
export async function restoreCloudBackup({
password,
- userData,
- nameOfSelectedBackupFile,
+ backupFilename,
}: {
password: BackupPassword;
- userData: BackupUserData | undefined;
- nameOfSelectedBackupFile: string;
+ backupFilename: string;
}): Promise {
try {
// 1 - sanitize filename to remove extra things we don't care about
- const filename = sanitizeFilename(nameOfSelectedBackupFile);
+ const filename = sanitizeFilename(backupFilename);
if (!filename) {
return RestoreCloudBackupResultStates.failedWhenRestoring;
}
@@ -402,26 +453,6 @@ export async function restoreCloudBackup({
}
}
- if (userData) {
- // Restore only wallets that were backed up in cloud
- // or wallets that are read-only
- const walletsToRestore: AllRainbowWallets = {};
- Object.values(userData?.wallets ?? {}).forEach(wallet => {
- if (
- (wallet.backedUp && wallet.backupDate && wallet.backupFile && wallet.backupType === WalletBackupTypes.cloud) ||
- wallet.type === WalletTypes.readOnly
- ) {
- walletsToRestore[wallet.id] = wallet;
- }
- });
-
- // All wallets
- dataToRestore[allWalletsKey] = {
- version: allWalletsVersion,
- wallets: walletsToRestore,
- };
- }
-
const restoredSuccessfully = await restoreSpecificBackupIntoKeychain(dataToRestore, userPIN);
return restoredSuccessfully ? RestoreCloudBackupResultStates.success : RestoreCloudBackupResultStates.failedWhenRestoring;
} catch (error) {
@@ -525,74 +556,6 @@ async function restoreSpecificBackupIntoKeychain(backedUpData: BackedUpData, use
}
}
-async function restoreCurrentBackupIntoKeychain(backedUpData: BackedUpData, newPIN?: string): Promise {
- try {
- // Access control config per each type of key
- const privateAccessControlOptions = await keychain.getPrivateAccessControlOptions();
- const encryptedBackupPinData = backedUpData[pinKey];
- const backupPIN = await decryptPIN(encryptedBackupPinData);
-
- await Promise.all(
- Object.keys(backedUpData).map(async key => {
- let value = backedUpData[key];
- const theKeyIsASeedPhrase = endsWith(key, seedPhraseKey);
- const theKeyIsAPrivateKey = endsWith(key, privateKeyKey);
- const accessControl: typeof kc.publicAccessControlOptions =
- theKeyIsASeedPhrase || theKeyIsAPrivateKey ? privateAccessControlOptions : kc.publicAccessControlOptions;
-
- /*
- * Backups that were saved encrypted with PIN to the cloud need to be
- * decrypted with the backup PIN first, and then if we still need
- * to store them as encrypted,
- * we need to re-encrypt them with a new PIN
- */
- if (theKeyIsASeedPhrase) {
- const parsedValue = JSON.parse(value);
- parsedValue.seedphrase = await decryptSecretFromBackupPin({
- secret: parsedValue.seedphrase,
- backupPIN,
- });
- value = JSON.stringify(parsedValue);
- } else if (theKeyIsAPrivateKey) {
- const parsedValue = JSON.parse(value);
- parsedValue.privateKey = await decryptSecretFromBackupPin({
- secret: parsedValue.privateKey,
- backupPIN,
- });
- value = JSON.stringify(parsedValue);
- }
-
- /*
- * Since we're decrypting the data that was saved as PIN code encrypted,
- * we will allow the user to create a new PIN code.
- * We store the old PIN code in the backup, but we don't want to restore it,
- * since it will override the new PIN code that we just saved to keychain.
- */
- if (key === pinKey) {
- return;
- }
-
- if (typeof value === 'string') {
- return kc.set(key, value, {
- ...accessControl,
- androidEncryptionPin: newPIN,
- });
- } else {
- return kc.setObject(key, value, {
- ...accessControl,
- androidEncryptionPin: newPIN,
- });
- }
- })
- );
-
- return true;
- } catch (e) {
- logger.error(new RainbowError(`[backup]: Error restoring current backup into keychain: ${e}`));
- return false;
- }
-}
-
async function decryptSecretFromBackupPin({ secret, backupPIN }: { secret?: string; backupPIN?: string }) {
let processedSecret = secret;
@@ -638,13 +601,9 @@ export async function saveBackupPassword(password: BackupPassword): Promise {
- const rainbowBackupPassword = await keychain.loadString('RainbowBackupPassword');
- if (typeof rainbowBackupPassword === 'number') {
- return null;
- }
-
- if (rainbowBackupPassword) {
- return rainbowBackupPassword;
+ const { value } = await kc.get('RainbowBackupPassword');
+ if (value) {
+ return value;
}
return await fetchBackupPassword();
@@ -653,7 +612,7 @@ export async function getLocalBackupPassword(): Promise {
export async function saveLocalBackupPassword(password: string) {
const privateAccessControlOptions = await keychain.getPrivateAccessControlOptions();
- await keychain.saveString('RainbowBackupPassword', password, privateAccessControlOptions);
+ await kc.set('RainbowBackupPassword', password, privateAccessControlOptions);
saveBackupPassword(password);
}
@@ -666,7 +625,7 @@ export async function fetchBackupPassword(): Promise {
try {
const { value: results } = await kc.getSharedWebCredentials();
if (results) {
- return results.password as BackupPassword;
+ return results.password;
}
return null;
} catch (e) {
@@ -695,7 +654,7 @@ export async function getDeviceUUID(): Promise {
}
const FailureAlert = () =>
- Alert({
+ NativeAlert({
buttons: [
{
style: 'cancel',
diff --git a/src/model/migrations.ts b/src/model/migrations.ts
index c4d2c706db7..a30d22d401a 100644
--- a/src/model/migrations.ts
+++ b/src/model/migrations.ts
@@ -39,8 +39,13 @@ import { EthereumAddress, RainbowToken } from '@/entities';
import { standardizeUrl, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore';
import { useLegacyFavoriteDappsStore } from '@/state/legacyFavoriteDapps';
import { getAddressAndChainIdFromUniqueId, getUniqueId, getUniqueIdNetwork } from '@/utils/ethereumUtils';
-import { UniqueId } from '@/__swaps__/types/assets';
+import { ParsedAssetsDictByChain, ParsedSearchAsset, UniqueId } from '@/__swaps__/types/assets';
import { userAssetsStore } from '@/state/assets/userAssets';
+import { userAssetsQueryKey } from '@/__swaps__/screens/Swap/resources/assets/userAssets';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
+import { selectorFilterByUserChains, selectUserAssetsList } from '@/__swaps__/screens/Swap/resources/_selectors/assets';
+import { UnlockableAppIconKey, unlockableAppIcons } from '@/appIcons/appIcons';
+import { unlockableAppIconStorage } from '@/featuresToUnlock/unlockableAppIconCheck';
export default async function runMigrations() {
// get current version
@@ -696,6 +701,50 @@ export default async function runMigrations() {
migrations.push(v21);
+ /**
+ *************** Migration v22 ******************
+ * Reset icon checks
+ */
+ const v22 = async () => {
+ // For each appIcon, delete the handled flag
+ (Object.keys(unlockableAppIcons) as UnlockableAppIconKey[]).map(appIconKey => {
+ unlockableAppIconStorage.delete(appIconKey);
+ logger.debug('Resetting icon status for ' + appIconKey);
+ });
+ };
+
+ migrations.push(v22);
+
+ /**
+ *************** Migration v23 ******************
+ * Populate `legacyUserAssets` attribute in `userAssetsStore`
+ */
+ const v23 = async () => {
+ const state = store.getState();
+ const { wallets } = state.wallets;
+ const { nativeCurrency } = state.settings;
+
+ if (!wallets) return;
+
+ for (const wallet of Object.values(wallets)) {
+ for (const { address } of (wallet as RainbowWallet).addresses) {
+ const { connectedToAnvil } = useConnectedToAnvilStore.getState();
+ const queryKey = userAssetsQueryKey({ address, currency: nativeCurrency, testnetMode: connectedToAnvil });
+ const queryData: ParsedAssetsDictByChain | undefined = queryClient.getQueryData(queryKey);
+
+ if (!queryData) continue;
+
+ const userAssets = selectorFilterByUserChains({
+ data: queryData,
+ selector: selectUserAssetsList,
+ });
+ userAssetsStore.getState(address).setUserAssets(userAssets as ParsedSearchAsset[]);
+ }
+ }
+ };
+
+ migrations.push(v23);
+
logger.debug(`[runMigrations]: ready to run migrations starting on number ${currentVersion}`);
// await setMigrationVersion(17);
if (migrations.length === currentVersion) {
diff --git a/src/model/remoteConfig.ts b/src/model/remoteConfig.ts
index 40382af707b..3341a3a0000 100644
--- a/src/model/remoteConfig.ts
+++ b/src/model/remoteConfig.ts
@@ -57,6 +57,8 @@ export interface RainbowConfig extends Record
featured_results: boolean;
claimables: boolean;
nfts_enabled: boolean;
+
+ trending_tokens_limit: number;
}
export const DEFAULT_CONFIG: RainbowConfig = {
@@ -147,6 +149,8 @@ export const DEFAULT_CONFIG: RainbowConfig = {
featured_results: true,
claimables: true,
nfts_enabled: true,
+
+ trending_tokens_limit: 10,
};
export async function fetchRemoteConfig(): Promise {
@@ -205,6 +209,8 @@ export async function fetchRemoteConfig(): Promise {
key === 'nfts_enabled'
) {
config[key] = entry.asBoolean();
+ } else if (key === 'trending_tokens_limit') {
+ config[key] = entry.asNumber();
} else {
config[key] = entry.asString();
}
diff --git a/src/navigation/HardwareWalletTxNavigator.tsx b/src/navigation/HardwareWalletTxNavigator.tsx
index 28e290065dc..4b209dabe30 100644
--- a/src/navigation/HardwareWalletTxNavigator.tsx
+++ b/src/navigation/HardwareWalletTxNavigator.tsx
@@ -63,7 +63,7 @@ export const HardwareWalletTxNavigator = () => {
const { navigate } = useNavigation();
- const deviceId = selectedWallet?.deviceId;
+ const deviceId = selectedWallet.deviceId ?? '';
const [isReady, setIsReady] = useRecoilState(LedgerIsReadyAtom);
const [readyForPolling, setReadyForPolling] = useRecoilState(readyForPollingAtom);
const [triggerPollerCleanup, setTriggerPollerCleanup] = useRecoilState(triggerPollerCleanupAtom);
diff --git a/src/navigation/Routes.android.tsx b/src/navigation/Routes.android.tsx
index d246a004f6a..1e03c2d96a3 100644
--- a/src/navigation/Routes.android.tsx
+++ b/src/navigation/Routes.android.tsx
@@ -90,6 +90,9 @@ import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPane
import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel';
import { ClaimClaimablePanel } from '@/screens/claimables/ClaimPanel';
import { RootStackParamList } from './types';
+import WalletLoadingListener from '@/components/WalletLoadingListener';
+import { Portal as CMPortal } from '@/react-native-cool-modals/Portal';
+import { NetworkSelector } from '@/components/NetworkSwitcher';
const Stack = createStackNavigator();
const OuterStack = createStackNavigator();
@@ -212,7 +215,7 @@ function BSNavigator() {
step === walletBackupStepTypes.restore_from_backup
) {
heightForStep = backupSheetSizes.long;
- } else if (step === walletBackupStepTypes.no_provider) {
+ } else if (step === walletBackupStepTypes.backup_prompt) {
heightForStep = backupSheetSizes.medium;
}
@@ -242,6 +245,7 @@ function BSNavigator() {
+
@@ -272,6 +276,10 @@ const AppContainerWithAnalytics = React.forwardRef
+
+ {/* NOTE: Internally, these use some navigational checks */}
+
+
));
diff --git a/src/navigation/Routes.ios.tsx b/src/navigation/Routes.ios.tsx
index e65e309538a..201eb3aa374 100644
--- a/src/navigation/Routes.ios.tsx
+++ b/src/navigation/Routes.ios.tsx
@@ -70,6 +70,7 @@ import {
swapConfig,
checkIdentifierSheetConfig,
recieveModalSheetConfig,
+ networkSelectorConfig,
} from './config';
import { addCashSheet, emojiPreset, emojiPresetWallet, overlayExpandedPreset, sheetPreset } from './effects';
import { InitialRouteContext } from './initialRoute';
@@ -102,6 +103,9 @@ import { ControlPanel } from '@/components/DappBrowser/control-panel/ControlPane
import { ClaimRewardsPanel } from '@/screens/points/claim-flow/ClaimRewardsPanel';
import { ClaimClaimablePanel } from '@/screens/claimables/ClaimPanel';
import { RootStackParamList } from './types';
+import WalletLoadingListener from '@/components/WalletLoadingListener';
+import { Portal as CMPortal } from '@/react-native-cool-modals/Portal';
+import { NetworkSelector } from '@/components/NetworkSwitcher';
const Stack = createStackNavigator();
const NativeStack = createNativeStackNavigator();
@@ -273,6 +277,7 @@ function NativeStackNavigator() {
+
@@ -286,6 +291,10 @@ const AppContainerWithAnalytics = React.forwardRef
+
+ {/* NOTE: Internally, these use some navigational checks */}
+
+
));
diff --git a/src/navigation/SwipeNavigator.tsx b/src/navigation/SwipeNavigator.tsx
index 33f84504828..781ca8932d4 100644
--- a/src/navigation/SwipeNavigator.tsx
+++ b/src/navigation/SwipeNavigator.tsx
@@ -15,7 +15,7 @@ import RecyclerListViewScrollToTopProvider, {
useRecyclerListViewScrollToTopContext,
} from '@/navigation/RecyclerListViewScrollToTopContext';
import DappBrowserScreen from '@/screens/dapp-browser/DappBrowserScreen';
-import { discoverOpenSearchFnRef } from '@/screens/discover/components/DiscoverSearchContainer';
+import { discoverOpenSearchFnRef } from '@/components/Discover/DiscoverSearchContainer';
import { PointsScreen } from '@/screens/points/PointsScreen';
import WalletScreen from '@/screens/WalletScreen';
import { useTheme } from '@/theme';
@@ -39,7 +39,7 @@ import { TIMING_CONFIGS } from '@/components/animations/animationConfigs';
import { useBrowserStore } from '@/state/browser/browserStore';
import { opacityWorklet } from '@/__swaps__/utils/swaps';
import ProfileScreen from '../screens/ProfileScreen';
-import DiscoverScreen, { discoverScrollToTopFnRef } from '../screens/discover/DiscoverScreen';
+import DiscoverScreen, { discoverScrollToTopFnRef } from '@/screens/DiscoverScreen';
import { ScrollPositionContext } from './ScrollPositionContext';
import SectionListScrollToTopProvider, { useSectionListScrollToTopContext } from './SectionListScrollToTopContext';
import Routes from './routesNames';
diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx
index 5428d27e37b..9097c84e1d6 100644
--- a/src/navigation/config.tsx
+++ b/src/navigation/config.tsx
@@ -103,12 +103,10 @@ export const getHeightForStep = (step: string) => {
case WalletBackupStepTypes.backup_manual:
case WalletBackupStepTypes.restore_from_backup:
return backupSheetSizes.long;
- case WalletBackupStepTypes.no_provider:
+ case WalletBackupStepTypes.backup_prompt:
return backupSheetSizes.medium;
case WalletBackupStepTypes.check_identifier:
return backupSheetSizes.check_identifier;
- case WalletBackupStepTypes.backup_now_manually:
- return backupSheetSizes.shorter;
default:
return backupSheetSizes.short;
}
@@ -248,6 +246,20 @@ export const consoleSheetConfig = {
}),
};
+export const networkSelectorConfig = {
+ options: ({ route: { params = {} } }) => ({
+ ...buildCoolModalConfig({
+ ...params,
+ backgroundColor: '#000000B2',
+ backgroundOpacity: 0.7,
+ cornerRadius: 0,
+ springDamping: 1,
+ topOffset: 0,
+ transitionDuration: 0.3,
+ }),
+ }),
+};
+
export const panelConfig = {
options: ({ route: { params = {} } }) => ({
...buildCoolModalConfig({
diff --git a/src/navigation/routesNames.ts b/src/navigation/routesNames.ts
index 96cc67fb146..ff4372906e3 100644
--- a/src/navigation/routesNames.ts
+++ b/src/navigation/routesNames.ts
@@ -100,6 +100,7 @@ const Routes = {
SETTINGS_SECTION_NOTIFICATIONS: 'NotificationsSection',
SETTINGS_SECTION_PRIVACY: 'PrivacySection',
DAPP_BROWSER_CONTROL_PANEL: 'DappBrowserControlPanel',
+ NETWORK_SELECTOR: 'NetworkSelector',
CLAIM_REWARDS_PANEL: 'ClaimRewardsPanel',
} as const;
diff --git a/src/navigation/types.ts b/src/navigation/types.ts
index cc2c8842ab5..c1877d015da 100644
--- a/src/navigation/types.ts
+++ b/src/navigation/types.ts
@@ -10,6 +10,9 @@ import { Claimable } from '@/resources/addys/claimables/types';
import { WalletconnectApprovalSheetRouteParams, WalletconnectResultType } from '@/walletConnect/types';
import { WalletConnectApprovalSheetType } from '@/helpers/walletConnectApprovalSheetTypes';
import { RainbowPosition } from '@/resources/defi/types';
+import { Address } from 'viem';
+import { SharedValue } from 'react-native-reanimated';
+import { ChainId } from '@/state/backendNetworks/types';
export type PartialNavigatorConfigOptions = Pick['Screen']>[0]>, 'options'>;
@@ -31,7 +34,7 @@ export type RootStackParamList = {
[Routes.CHANGE_WALLET_SHEET]: {
watchOnly: boolean;
currentAccountAddress: string;
- onChangeWallet: (address: string) => void;
+ onChangeWallet: (address: Address) => void;
};
[Routes.SPEED_UP_AND_CANCEL_BOTTOM_SHEET]: {
accentColor?: string;
@@ -104,4 +107,9 @@ export type RootStackParamList = {
[Routes.POSITION_SHEET]: {
position: RainbowPosition;
};
+ [Routes.NETWORK_SELECTOR]: {
+ onClose?: VoidFunction;
+ selected: SharedValue;
+ setSelected: (chainId: ChainId | undefined) => void;
+ };
};
diff --git a/src/performance/tracking/index.ts b/src/performance/tracking/index.ts
index 425faba867e..a0b3ebbcf3a 100644
--- a/src/performance/tracking/index.ts
+++ b/src/performance/tracking/index.ts
@@ -1,15 +1,16 @@
-import { IS_TESTING, SENTRY_ENVIRONMENT } from 'react-native-dotenv';
+import { SENTRY_ENVIRONMENT } from 'react-native-dotenv';
import { PerformanceMetricData } from './types/PerformanceMetricData';
import { PerformanceMetricsType } from './types/PerformanceMetrics';
import { PerformanceTagsType } from './types/PerformanceTags';
import { analytics } from '@/analytics';
+import { IS_TEST } from '@/env';
/*
This will be a version for all performance tracking events.
If we make breaking changes we will be able to take it into consideration when doing analytics
*/
const performanceTrackingVersion = 2;
const shouldLogToConsole = __DEV__ || SENTRY_ENVIRONMENT === 'LocalRelease';
-const shouldReportMeasurement = IS_TESTING === 'false' && !__DEV__ && SENTRY_ENVIRONMENT !== 'LocalRelease';
+const shouldReportMeasurement = !IS_TEST && !__DEV__ && SENTRY_ENVIRONMENT !== 'LocalRelease';
const logTag = '[PERFORMANCE]: ';
function logDurationIfAppropriate(metric: PerformanceMetricsType, durationInMs: number, ...additionalArgs: any[]) {
@@ -18,7 +19,7 @@ function logDurationIfAppropriate(metric: PerformanceMetricsType, durationInMs:
}
}
-const currentlyTrackedMetrics = new Map();
+export const currentlyTrackedMetrics = new Map();
interface AdditionalParams extends Record {
tag?: PerformanceTagsType;
diff --git a/src/performance/tracking/types/PerformanceMetrics.ts b/src/performance/tracking/types/PerformanceMetrics.ts
index 3baf050eb54..3d272e1e71b 100644
--- a/src/performance/tracking/types/PerformanceMetrics.ts
+++ b/src/performance/tracking/types/PerformanceMetrics.ts
@@ -11,6 +11,7 @@ export const PerformanceMetrics = {
initializeWalletconnect: 'Performance WalletConnect Initialize Time',
quoteFetching: 'Performance Quote Fetching Time',
+ timeSpentOnDiscoverScreen: 'Time spent on the Discover screen',
} as const;
export type PerformanceMetricsType = (typeof PerformanceMetrics)[keyof typeof PerformanceMetrics];
diff --git a/src/raps/actions/ens.ts b/src/raps/actions/ens.ts
index e4e315c98a4..ca380e1d0da 100644
--- a/src/raps/actions/ens.ts
+++ b/src/raps/actions/ens.ts
@@ -1,5 +1,4 @@
import { Signer } from '@ethersproject/abstract-signer';
-import { IS_TESTING } from 'react-native-dotenv';
import { ENSActionParameters, ENSRap, ENSRapActionType, RapENSAction, RapENSActionParameters } from '@/raps/common';
import { analytics } from '@/analytics';
import { ENSRegistrationRecords, NewTransaction, TransactionGasParamAmounts, TransactionStatus } from '@/entities';
@@ -23,6 +22,7 @@ import {
} from '../registerENS';
import { Logger } from '@ethersproject/logger';
import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance';
+import { IS_TEST } from '@/env';
export interface ENSRapActionResponse {
baseNonce?: number | null;
@@ -327,7 +327,7 @@ const ensAction = async (
// (MULTICALL || SET_TEXT) it's going to fail if we put the account address
// since the account doesn't have the ENS yet
const notUseOwnerAddress =
- IS_TESTING !== 'true' &&
+ !IS_TEST &&
mode === REGISTRATION_MODES.CREATE &&
(type === ENSRegistrationTransactionType.MULTICALL || type === ENSRegistrationTransactionType.SET_TEXT);
diff --git a/src/react-native-cool-modals/Portal.js b/src/react-native-cool-modals/Portal.js
deleted file mode 100644
index 5d03cdadeb8..00000000000
--- a/src/react-native-cool-modals/Portal.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
-import { Platform, requireNativeComponent, StyleSheet, View } from 'react-native';
-
-const NativePortalContext = createContext();
-
-export function usePortal() {
- return useContext(NativePortalContext);
-}
-
-const NativePortal = Platform.OS === 'ios' ? requireNativeComponent('WindowPortal') : View;
-
-const Wrapper = Platform.OS === 'ios' ? ({ children }) => children : View;
-
-export function Portal({ children }) {
- const [Component, setComponentState] = useState(null);
- const [blockTouches, setBlockTouches] = useState(false);
-
- const hide = useCallback(() => {
- setComponentState();
- setBlockTouches(false);
- }, []);
-
- const setComponent = useCallback((value, blockTouches) => {
- setComponentState(value);
- setBlockTouches(blockTouches);
- }, []);
-
- const contextValue = useMemo(
- () => ({
- hide,
- setComponent,
- }),
- [hide, setComponent]
- );
-
- return (
-
-
- {children}
-
- {Component}
-
-
-
- );
-}
diff --git a/src/react-native-cool-modals/Portal.tsx b/src/react-native-cool-modals/Portal.tsx
new file mode 100644
index 00000000000..dd2830ee0b4
--- /dev/null
+++ b/src/react-native-cool-modals/Portal.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { IS_IOS } from '@/env';
+import { walletLoadingStore } from '@/state/walletLoading/walletLoading';
+import { requireNativeComponent, StyleSheet, View } from 'react-native';
+import Routes from '@/navigation/routesNames';
+import { useActiveRoute } from '@/hooks/useActiveRoute';
+
+const NativePortal = IS_IOS ? requireNativeComponent('WindowPortal') : View;
+const Wrapper = IS_IOS ? ({ children }: { children: React.ReactNode }) => children : View;
+
+export function Portal() {
+ const activeRoute = useActiveRoute();
+
+ const { blockTouches, Component } = walletLoadingStore(state => ({
+ blockTouches: state.blockTouches,
+ Component: state.Component,
+ }));
+
+ if (!Component || (activeRoute === Routes.PIN_AUTHENTICATION_SCREEN && !IS_IOS)) {
+ return null;
+ }
+
+ console.log('blockTouches', blockTouches);
+
+ return (
+
+
+ {Component}
+
+
+ );
+}
diff --git a/src/redux/gas.ts b/src/redux/gas.ts
index 496bec67a67..3ef75dbe5db 100644
--- a/src/redux/gas.ts
+++ b/src/redux/gas.ts
@@ -1,7 +1,6 @@
import { Mutex } from 'async-mutex';
import BigNumber from 'bignumber.js';
import { isEmpty } from 'lodash';
-import { IS_TESTING } from 'react-native-dotenv';
import { AppDispatch, AppGetState } from './store';
import { analytics } from '@/analytics';
import { logger, RainbowError } from '@/logger';
@@ -37,10 +36,11 @@ import {
import { ethUnits } from '@/references';
import { ethereumUtils, gasUtils } from '@/utils';
import { ChainId } from '@/state/backendNetworks/types';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
import { MeteorologyLegacyResponse, MeteorologyResponse } from '@/entities/gas';
import { addBuffer } from '@/helpers/utilities';
+import { IS_TEST } from '@/env';
const { CUSTOM, NORMAL, URGENT } = gasUtils;
@@ -368,9 +368,9 @@ export const gasPricesStartPolling =
meteorologyGasParams as MeterologyGasParams;
// Set a really gas estimate to guarantee that we're gonna be over
- // the basefee at the time we fork mainnet during our hardhat tests
+ // the basefee at the time we fork mainnet during our anvil tests
let baseFee = baseFeePerGas;
- if (chainId === ChainId.mainnet && IS_TESTING === 'true' && useConnectedToHardhatStore.getState().connectedToHardhat) {
+ if (chainId === ChainId.mainnet && IS_TEST && useConnectedToAnvilStore.getState().connectedToAnvil) {
baseFee = parseGasFeeParam(gweiToWei(1000));
}
diff --git a/src/redux/settings.ts b/src/redux/settings.ts
index ce19a5f6131..4535437ea7c 100644
--- a/src/redux/settings.ts
+++ b/src/redux/settings.ts
@@ -24,6 +24,7 @@ import { getProvider } from '@/handlers/web3';
import { AppState } from '@/redux/store';
import { logger, RainbowError } from '@/logger';
import { Network, ChainId } from '@/state/backendNetworks/types';
+import { Address } from 'viem';
// -- Constants ------------------------------------------------------------- //
const SETTINGS_UPDATE_SETTINGS_ADDRESS = 'settings/SETTINGS_UPDATE_SETTINGS_ADDRESS';
@@ -41,7 +42,7 @@ const SETTINGS_UPDATE_ACCOUNT_SETTINGS_SUCCESS = 'settings/SETTINGS_UPDATE_ACCOU
*/
interface SettingsState {
appIcon: string;
- accountAddress: string;
+ accountAddress: Address;
chainId: number;
language: Language;
nativeCurrency: NativeCurrencyKey;
@@ -205,7 +206,7 @@ export const settingsChangeAppIcon = (appIcon: string) => (dispatch: Dispatch async (dispatch: Dispatch) => {
dispatch({
- payload: accountAddress,
+ payload: accountAddress as Address,
type: SETTINGS_UPDATE_SETTINGS_ADDRESS,
});
};
@@ -254,7 +255,7 @@ export const settingsChangeNativeCurrency =
// -- Reducer --------------------------------------------------------------- //
export const INITIAL_STATE: SettingsState = {
- accountAddress: '',
+ accountAddress: '' as Address,
appIcon: 'og',
chainId: 1,
language: Language.EN_US,
diff --git a/src/redux/wallets.ts b/src/redux/wallets.ts
index deb49a5ea9b..d17f8b4c0d8 100644
--- a/src/redux/wallets.ts
+++ b/src/redux/wallets.ts
@@ -3,10 +3,8 @@ import { toChecksumAddress } from 'ethereumjs-util';
import { isEmpty, keys } from 'lodash';
import { Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
-import { backupUserDataIntoCloud, fetchUserDataFromCloud } from '../handlers/cloudBackup';
import { saveKeychainIntegrityState } from '../handlers/localstorage/globalSettings';
import { getWalletNames, saveWalletNames } from '../handlers/localstorage/walletNames';
-import WalletBackupTypes from '../helpers/walletBackupTypes';
import WalletTypes from '../helpers/walletTypes';
import { fetchENSAvatar } from '../hooks/useENSAvatar';
import { hasKey } from '../model/keychain';
@@ -30,6 +28,7 @@ import { AppGetState, AppState } from './store';
import { fetchReverseRecord } from '@/handlers/ens';
import { lightModeThemeColors } from '@/styles';
import { RainbowError, logger } from '@/logger';
+import { parseTimestampFromBackupFile } from '@/model/backup';
// -- Types ---------------------------------------- //
@@ -37,11 +36,6 @@ import { RainbowError, logger } from '@/logger';
* The current state of the `wallets` reducer.
*/
interface WalletsState {
- /**
- * The current loading state of the wallet.
- */
- isWalletLoading: any;
-
/**
* The currently selected wallet.
*/
@@ -62,21 +56,12 @@ interface WalletsState {
* An action for the `wallets` reducer.
*/
type WalletsAction =
- | WalletsSetIsLoadingAction
| WalletsSetSelectedAction
| WalletsUpdateAction
| WalletsUpdateNamesAction
| WalletsLoadAction
| WalletsAddedAccountAction;
-/**
- * An action that sets the wallet loading state.
- */
-interface WalletsSetIsLoadingAction {
- type: typeof WALLETS_SET_IS_LOADING;
- payload: WalletsState['isWalletLoading'];
-}
-
/**
* An action that sets the selected wallet.
*/
@@ -130,90 +115,88 @@ const WALLETS_SET_SELECTED = 'wallets/SET_SELECTED';
/**
* Loads wallet information from storage and updates state accordingly.
*/
-export const walletsLoadState =
- (profilesEnabled = false) =>
- async (dispatch: ThunkDispatch, getState: AppGetState) => {
- try {
- const { accountAddress } = getState().settings;
- let addressFromKeychain: string | null = accountAddress;
- const allWalletsResult = await getAllWallets();
- const wallets = allWalletsResult?.wallets || {};
- if (isEmpty(wallets)) return;
- const selected = await getSelectedWallet();
- // Prevent irrecoverable state (no selected wallet)
- let selectedWallet = selected?.wallet;
- // Check if the selected wallet is among all the wallets
- if (selectedWallet && !wallets[selectedWallet.id]) {
- // If not then we should clear it and default to the first one
- const firstWalletKey = Object.keys(wallets)[0];
- selectedWallet = wallets[firstWalletKey];
- await setSelectedWallet(selectedWallet);
- }
+export const walletsLoadState = () => async (dispatch: ThunkDispatch, getState: AppGetState) => {
+ try {
+ const { accountAddress } = getState().settings;
+ let addressFromKeychain: string | null = accountAddress;
+ const allWalletsResult = await getAllWallets();
+ const wallets = allWalletsResult?.wallets || {};
+ if (isEmpty(wallets)) return;
+ const selected = await getSelectedWallet();
+ // Prevent irrecoverable state (no selected wallet)
+ let selectedWallet = selected?.wallet;
+ // Check if the selected wallet is among all the wallets
+ if (selectedWallet && !wallets[selectedWallet.id]) {
+ // If not then we should clear it and default to the first one
+ const firstWalletKey = Object.keys(wallets)[0];
+ selectedWallet = wallets[firstWalletKey];
+ await setSelectedWallet(selectedWallet);
+ }
- if (!selectedWallet) {
- const address = await loadAddress();
- if (!address) {
- selectedWallet = wallets[Object.keys(wallets)[0]];
- } else {
- keys(wallets).some(key => {
- const someWallet = wallets[key];
- const found = (someWallet.addresses || []).some(account => {
- return toChecksumAddress(account.address) === toChecksumAddress(address!);
- });
- if (found) {
- selectedWallet = someWallet;
- logger.debug('[redux/wallets]: Found selected wallet based on loadAddress result');
- }
- return found;
+ if (!selectedWallet) {
+ const address = await loadAddress();
+ if (!address) {
+ selectedWallet = wallets[Object.keys(wallets)[0]];
+ } else {
+ keys(wallets).some(key => {
+ const someWallet = wallets[key];
+ const found = (someWallet.addresses || []).some(account => {
+ return toChecksumAddress(account.address) === toChecksumAddress(address!);
});
- }
+ if (found) {
+ selectedWallet = someWallet;
+ logger.debug('[redux/wallets]: Found selected wallet based on loadAddress result');
+ }
+ return found;
+ });
}
+ }
- // Recover from broken state (account address not in selected wallet)
- if (!addressFromKeychain) {
- addressFromKeychain = await loadAddress();
- logger.debug("[redux/wallets]: addressFromKeychain wasn't set on settings so it is being loaded from loadAddress");
- }
+ // Recover from broken state (account address not in selected wallet)
+ if (!addressFromKeychain) {
+ addressFromKeychain = await loadAddress();
+ logger.debug("[redux/wallets]: addressFromKeychain wasn't set on settings so it is being loaded from loadAddress");
+ }
- const selectedAddress = selectedWallet?.addresses.find(a => {
- return a.visible && a.address === addressFromKeychain;
- });
+ const selectedAddress = selectedWallet?.addresses.find(a => {
+ return a.visible && a.address === addressFromKeychain;
+ });
- // Let's select the first visible account if we don't have a selected address
- if (!selectedAddress) {
- const allWallets = Object.values(allWalletsResult?.wallets || {});
- let account = null;
- for (const wallet of allWallets) {
- for (const rainbowAccount of wallet.addresses || []) {
- if (rainbowAccount.visible) {
- account = rainbowAccount;
- break;
- }
+ // Let's select the first visible account if we don't have a selected address
+ if (!selectedAddress) {
+ const allWallets = Object.values(allWalletsResult?.wallets || {});
+ let account = null;
+ for (const wallet of allWallets) {
+ for (const rainbowAccount of wallet.addresses || []) {
+ if (rainbowAccount.visible) {
+ account = rainbowAccount;
+ break;
}
}
- if (!account) return;
- await dispatch(settingsUpdateAccountAddress(account.address));
- await saveAddress(account.address);
- logger.debug('[redux/wallets]: Selected the first visible address because there was not selected one');
}
+ if (!account) return;
+ await dispatch(settingsUpdateAccountAddress(account.address));
+ await saveAddress(account.address);
+ logger.debug('[redux/wallets]: Selected the first visible address because there was not selected one');
+ }
- const walletNames = await getWalletNames();
- dispatch({
- payload: {
- selected: selectedWallet,
- walletNames,
- wallets,
- },
- type: WALLETS_LOAD,
- });
+ const walletNames = await getWalletNames();
+ dispatch({
+ payload: {
+ selected: selectedWallet,
+ walletNames,
+ wallets,
+ },
+ type: WALLETS_LOAD,
+ });
- return wallets;
- } catch (error) {
- logger.error(new RainbowError('[redux/wallets]: Exception during walletsLoadState'), {
- message: (error as Error)?.message,
- });
- }
- };
+ return wallets;
+ } catch (error) {
+ logger.error(new RainbowError('[redux/wallets]: Exception during walletsLoadState'), {
+ message: (error as Error)?.message,
+ });
+ }
+};
/**
* Saves new wallets to storage and updates state accordingly.
@@ -252,21 +235,21 @@ export const walletsSetSelected = (wallet: RainbowWallet) => async (dispatch: Di
* @param updateUserMetadata Whether to update user metadata.
*/
export const setAllWalletsWithIdsAsBackedUp =
- (
- walletIds: RainbowWallet['id'][],
- method: RainbowWallet['backupType'],
- backupFile: RainbowWallet['backupFile'] = null,
- updateUserMetadata = true
- ) =>
+ (walletIds: RainbowWallet['id'][], method: RainbowWallet['backupType'], backupFile: RainbowWallet['backupFile'] = null) =>
async (dispatch: ThunkDispatch, getState: AppGetState) => {
const { wallets, selected } = getState().wallets;
const newWallets = { ...wallets };
+ let backupDate = Date.now();
+ if (backupFile) {
+ backupDate = parseTimestampFromBackupFile(backupFile) ?? Date.now();
+ }
+
walletIds.forEach(walletId => {
newWallets[walletId] = {
...newWallets[walletId],
backedUp: true,
- backupDate: Date.now(),
+ backupDate,
backupFile,
backupType: method,
};
@@ -276,17 +259,6 @@ export const setAllWalletsWithIdsAsBackedUp =
if (selected?.id && walletIds.includes(selected?.id)) {
await dispatch(walletsSetSelected(newWallets[selected.id]));
}
-
- if (method === WalletBackupTypes.cloud && updateUserMetadata) {
- try {
- await backupUserDataIntoCloud({ wallets: newWallets });
- } catch (e) {
- logger.error(new RainbowError('[redux/wallets]: Saving multiple wallets UserData to cloud failed.'), {
- message: (e as Error)?.message,
- });
- throw e;
- }
- }
};
/**
@@ -296,122 +268,28 @@ export const setAllWalletsWithIdsAsBackedUp =
* @param walletId The ID of the wallet to modify.
* @param method The backup type used.
* @param backupFile The backup file, if present.
- * @param updateUserMetadata Whether to update user metadata.
*/
export const setWalletBackedUp =
- (
- walletId: RainbowWallet['id'],
- method: RainbowWallet['backupType'],
- backupFile: RainbowWallet['backupFile'] = null,
- updateUserMetadata = true
- ) =>
+ (walletId: RainbowWallet['id'], method: RainbowWallet['backupType'], backupFile: RainbowWallet['backupFile'] = null) =>
async (dispatch: ThunkDispatch, getState: AppGetState) => {
const { wallets, selected } = getState().wallets;
const newWallets = { ...wallets };
+ let backupDate = Date.now();
+ if (backupFile) {
+ backupDate = parseTimestampFromBackupFile(backupFile) ?? Date.now();
+ }
newWallets[walletId] = {
...newWallets[walletId],
backedUp: true,
- backupDate: Date.now(),
+ backupDate,
backupFile,
backupType: method,
};
await dispatch(walletsUpdate(newWallets));
- if (selected!.id === walletId) {
+ if (selected?.id === walletId) {
await dispatch(walletsSetSelected(newWallets[walletId]));
}
-
- if (method === WalletBackupTypes.cloud && updateUserMetadata) {
- try {
- await backupUserDataIntoCloud({ wallets: newWallets });
- } catch (e) {
- logger.error(new RainbowError('[redux/wallets]: Saving wallet UserData to cloud failed.'), {
- message: (e as Error)?.message,
- });
- throw e;
- }
- }
- };
-
-/**
- * Grabs user data stored in the cloud and based on this data marks wallets
- * as backed up or not
- */
-export const updateWalletBackupStatusesBasedOnCloudUserData =
- () => async (dispatch: ThunkDispatch, getState: AppGetState) => {
- const { wallets, selected } = getState().wallets;
- const newWallets = { ...wallets };
-
- let currentUserData: { wallets: { [p: string]: RainbowWallet } } | undefined;
- try {
- currentUserData = await fetchUserDataFromCloud();
- } catch (error) {
- logger.error(new RainbowError('[redux/wallets]: There was an error when trying to update wallet backup statuses'), {
- error: (error as Error).message,
- });
- return;
- }
- if (currentUserData === undefined) {
- return;
- }
-
- // build hashmap of address to wallet based on backup metadata
- const addressToWalletLookup = new Map();
- Object.values(currentUserData.wallets).forEach(wallet => {
- wallet.addresses?.forEach(account => {
- addressToWalletLookup.set(account.address, wallet);
- });
- });
-
- /*
- marking wallet as already backed up if all addresses are backed up properly
- and linked to the same wallet
-
- we assume it's not backed up if:
- * we don't have an address in the backup metadata
- * we have an address in the backup metadata, but it's linked to multiple
- wallet ids (should never happen, but that's a sanity check)
- */
- Object.values(newWallets).forEach(wallet => {
- const localWalletId = wallet.id;
-
- let relatedCloudWalletId: string | null = null;
- for (const account of wallet.addresses || []) {
- const walletDataForCurrentAddress = addressToWalletLookup.get(account.address);
- if (!walletDataForCurrentAddress) {
- return;
- }
- if (relatedCloudWalletId === null) {
- relatedCloudWalletId = walletDataForCurrentAddress.id;
- } else if (relatedCloudWalletId !== walletDataForCurrentAddress.id) {
- logger.warn(
- '[redux/wallets]: Wallet address is linked to multiple or different accounts in the cloud backup metadata. It could mean that there is an issue with the cloud backup metadata.'
- );
- return;
- }
- }
-
- if (relatedCloudWalletId === null) {
- return;
- }
-
- // update only if we checked the wallet is actually backed up
- const cloudBackupData = currentUserData?.wallets[relatedCloudWalletId];
- if (cloudBackupData) {
- newWallets[localWalletId] = {
- ...newWallets[localWalletId],
- backedUp: cloudBackupData.backedUp,
- backupDate: cloudBackupData.backupDate,
- backupFile: cloudBackupData.backupFile,
- backupType: cloudBackupData.backupType,
- };
- }
- });
-
- await dispatch(walletsUpdate(newWallets));
- if (selected?.id) {
- await dispatch(walletsSetSelected(newWallets[selected.id]));
- }
};
/**
@@ -706,7 +584,6 @@ export const checkKeychainIntegrity = () => async (dispatch: ThunkDispatch {
switch (action.type) {
- case WALLETS_SET_IS_LOADING:
- return { ...state, isWalletLoading: action.payload };
case WALLETS_SET_SELECTED:
return { ...state, selected: action.payload };
case WALLETS_UPDATE:
diff --git a/src/resources/assets/UserAssetsQuery.ts b/src/resources/assets/UserAssetsQuery.ts
deleted file mode 100644
index 5616163e903..00000000000
--- a/src/resources/assets/UserAssetsQuery.ts
+++ /dev/null
@@ -1,222 +0,0 @@
-import isEmpty from 'lodash/isEmpty';
-import { ADDYS_API_KEY } from 'react-native-dotenv';
-import { NativeCurrencyKey } from '@/entities';
-import { saveAccountEmptyState } from '@/handlers/localstorage/accountLocal';
-import { greaterThan } from '@/helpers/utilities';
-import { rainbowFetch } from '@/rainbow-fetch';
-import { createQueryKey, queryClient, QueryConfigWithSelect, QueryFunctionArgs, QueryFunctionResult } from '@/react-query';
-import { useQuery } from '@tanstack/react-query';
-import { filterPositionsData, parseAddressAsset } from './assets';
-import { fetchHardhatBalances } from './hardhatAssets';
-import { AddysAccountAssetsMeta, AddysAccountAssetsResponse, RainbowAddressAssets } from './types';
-import { Network } from '@/state/backendNetworks/types';
-import { staleBalancesStore } from '@/state/staleBalances';
-import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
-
-// ///////////////////////////////////////////////
-// Query Types
-
-export type UserAssetsArgs = {
- address: string; // Address;
- currency: NativeCurrencyKey;
- connectedToHardhat: boolean;
-};
-
-// ///////////////////////////////////////////////
-// Query Key
-
-export const userAssetsQueryKey = ({ address, currency, connectedToHardhat }: UserAssetsArgs) =>
- createQueryKey('userAssets', { address, currency, connectedToHardhat }, { persisterVersion: 1 });
-
-type UserAssetsQueryKey = ReturnType;
-
-// ///////////////////////////////////////////////
-// Query Function
-
-const fetchUserAssetsForChainIds = async ({
- address,
- currency,
- chainIds,
- staleBalanceParam,
-}: {
- address: string;
- currency: NativeCurrencyKey;
- chainIds: number[];
- staleBalanceParam?: string;
-}) => {
- const chainIdsString = chainIds.join(',');
- let url = `https://addys.p.rainbow.me/v3/${chainIdsString}/${address}/assets?currency=${currency.toLowerCase()}`;
-
- if (staleBalanceParam) {
- url = url + staleBalanceParam;
- }
-
- const response = await rainbowFetch(url, {
- method: 'get',
- headers: {
- Authorization: `Bearer ${ADDYS_API_KEY}`,
- },
- });
-
- return response.data;
-};
-
-async function userAssetsQueryFunction({
- queryKey: [{ address, currency, connectedToHardhat }],
-}: QueryFunctionArgs) {
- const cache = queryClient.getQueryCache();
- const cachedAddressAssets = (cache.find(userAssetsQueryKey({ address, currency, connectedToHardhat }))?.state?.data ||
- {}) as RainbowAddressAssets;
-
- if (connectedToHardhat) {
- const parsedHardhatResults = await fetchHardhatBalances(address);
- return parsedHardhatResults;
- }
-
- try {
- staleBalancesStore.getState().clearExpiredData(address);
- const staleBalanceParam = staleBalancesStore.getState().getStaleBalancesQueryParam(address);
-
- const { erroredChainIds, results } = await fetchAndParseUserAssetsForChainIds({
- address,
- currency,
- chainIds: useBackendNetworksStore.getState().getSupportedMainnetChainIds(),
- staleBalanceParam,
- });
- let parsedSuccessResults = results;
-
- // grab cached data for chain IDs with errors
- if (!isEmpty(erroredChainIds)) {
- const cachedDataForErroredChainIds = Object.keys(cachedAddressAssets)
- .filter(uniqueId => {
- const cachedAsset = cachedAddressAssets[uniqueId];
- return erroredChainIds?.find((chainId: number) => chainId === cachedAsset.chainId);
- })
- .reduce((cur, uniqueId) => {
- return Object.assign(cur, {
- [uniqueId]: cachedAddressAssets[uniqueId],
- });
- }, {});
-
- parsedSuccessResults = {
- ...parsedSuccessResults,
- ...cachedDataForErroredChainIds,
- };
-
- retryErroredChainIds(address, currency, connectedToHardhat, erroredChainIds);
- }
-
- return parsedSuccessResults;
- } catch (e) {
- return cachedAddressAssets;
- }
-}
-
-const retryErroredChainIds = async (
- address: string,
- currency: NativeCurrencyKey,
- connectedToHardhat: boolean,
- erroredChainIds: number[]
-) => {
- const { meta, results } = await fetchAndParseUserAssetsForChainIds({ address, currency, chainIds: erroredChainIds });
- let parsedSuccessResults = results;
- const successChainIds = meta?.chain_ids;
-
- if (isEmpty(successChainIds)) {
- return;
- }
-
- // grab cached data without data that will be replaced
- const cache = queryClient.getQueryCache();
- const cachedAddressAssets = (cache.find(userAssetsQueryKey({ address, currency, connectedToHardhat }))?.state?.data ||
- {}) as RainbowAddressAssets;
-
- const cachedData = Object.keys(cachedAddressAssets)
- .filter(uniqueId => {
- const cachedAsset = cachedAddressAssets[uniqueId];
- return successChainIds?.find((chainId: number) => chainId !== cachedAsset.chainId);
- })
- .reduce((cur, uniqueId) => {
- return Object.assign(cur, {
- [uniqueId]: cachedAddressAssets[uniqueId],
- });
- }, {});
-
- parsedSuccessResults = {
- ...cachedData,
- ...parsedSuccessResults,
- };
-
- queryClient.setQueryData(userAssetsQueryKey({ address, currency, connectedToHardhat }), parsedSuccessResults);
-};
-
-export type UserAssetsResult = QueryFunctionResult;
-
-interface AssetsAndMetadata {
- erroredChainIds: number[];
- meta: AddysAccountAssetsMeta;
- results: RainbowAddressAssets;
-}
-
-const fetchAndParseUserAssetsForChainIds = async ({
- address,
- currency,
- chainIds,
- staleBalanceParam,
-}: {
- address: string;
- currency: NativeCurrencyKey;
- chainIds: number[];
- staleBalanceParam?: string;
-}): Promise => {
- const data = await fetchUserAssetsForChainIds({ address, currency, chainIds, staleBalanceParam });
- let parsedSuccessResults = parseUserAssetsByChain(data);
-
- // filter out positions data
- parsedSuccessResults = filterPositionsData(address, currency, parsedSuccessResults);
-
- // update account empty state
- if (!isEmpty(parsedSuccessResults)) {
- saveAccountEmptyState(false, address, Network.mainnet);
- }
-
- const erroredChainIds = data?.meta?.chain_ids_with_errors;
- return { erroredChainIds, meta: data?.meta, results: parsedSuccessResults };
-};
-
-function parseUserAssetsByChain(message: AddysAccountAssetsResponse) {
- return Object.values(message?.payload?.assets || {}).reduce((dict, assetData) => {
- if (greaterThan(assetData?.quantity, 0)) {
- const parsedAsset = parseAddressAsset({
- assetData,
- });
- dict[parsedAsset?.uniqueId] = parsedAsset;
- }
- return dict;
- }, {} as RainbowAddressAssets);
-}
-
-// ///////////////////////////////////////////////
-// Query Fetcher (Optional)
-
-export async function fetchUserAssets(
- { address, currency, connectedToHardhat }: UserAssetsArgs,
- config: QueryConfigWithSelect = {}
-) {
- return await queryClient.fetchQuery(userAssetsQueryKey({ address, currency, connectedToHardhat }), userAssetsQueryFunction, config);
-}
-
-// ///////////////////////////////////////////////
-// Query Hook
-
-export function useUserAssets(
- { address, currency, connectedToHardhat }: UserAssetsArgs,
- config: QueryConfigWithSelect = {}
-) {
- return useQuery(userAssetsQueryKey({ address, currency, connectedToHardhat }), userAssetsQueryFunction, {
- enabled: !!address && !!currency,
- staleTime: 60_000, // 1 minute
- refetchInterval: 120_000, // 2 minutes
- ...config,
- });
-}
diff --git a/src/resources/assets/hardhatAssets.ts b/src/resources/assets/anvilAssets.ts
similarity index 85%
rename from src/resources/assets/hardhatAssets.ts
rename to src/resources/assets/anvilAssets.ts
index 409a3ee1a1c..e14963668a9 100644
--- a/src/resources/assets/hardhatAssets.ts
+++ b/src/resources/assets/anvilAssets.ts
@@ -13,7 +13,7 @@ import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks
const MAINNET_BALANCE_CHECKER = '0x4dcf4562268dd384fe814c00fad239f06c2a0c2b';
-const fetchHardhatBalancesWithBalanceChecker = async (
+const fetchAnvilBalancesWithBalanceChecker = async (
tokens: string[],
address: string
): Promise<{ [tokenAddress: string]: string } | null> => {
@@ -32,19 +32,19 @@ const fetchHardhatBalancesWithBalanceChecker = async (
});
return balances;
} catch (e) {
- logger.error(new RainbowError(`[hardhatAssets]: Error fetching balances from balanceCheckerContract: ${e}`));
+ logger.error(new RainbowError(`[anvilAssets]: Error fetching balances from balanceCheckerContract: ${e}`));
return null;
}
};
/**
* @deprecated - to be removed once rest of the app is converted to new userAssetsStore
- * Fetches the balances of the hardhat assets for the given account address and network.
+ * Fetches the balances of the anvil assets for the given account address and network.
* @param accountAddress - The address of the account to fetch the balances for.
* @param network - The network to fetch the balances for.
- * @returns The balances of the hardhat assets for the given account address and network.
+ * @returns The balances of the anvil assets for the given account address and network.
*/
-export const fetchHardhatBalances = async (accountAddress: string, chainId: ChainId = ChainId.mainnet): Promise => {
+export const fetchAnvilBalances = async (accountAddress: string, chainId: ChainId = ChainId.mainnet): Promise => {
const chainAssetsMap = keyBy(
chainAssets[`${chainId}` as keyof typeof chainAssets],
({ asset }) => `${asset.asset_code}_${asset.chainId}`
@@ -53,7 +53,7 @@ export const fetchHardhatBalances = async (accountAddress: string, chainId: Chai
const tokenAddresses = Object.values(chainAssetsMap).map(({ asset: { asset_code } }) =>
asset_code === ETH_ADDRESS ? AddressZero : asset_code.toLowerCase()
);
- const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress);
+ const balances = await fetchAnvilBalancesWithBalanceChecker(tokenAddresses, accountAddress);
if (!balances) return {};
const updatedAssets = mapValues(chainAssetsMap, chainAsset => {
@@ -70,7 +70,7 @@ export const fetchHardhatBalances = async (accountAddress: string, chainId: Chai
return updatedAssets;
};
-export const fetchHardhatBalancesByChainId = async (
+export const fetchAnvilBalancesByChainId = async (
accountAddress: string,
chainId: ChainId = ChainId.mainnet
): Promise<{
@@ -88,7 +88,7 @@ export const fetchHardhatBalancesByChainId = async (
asset.asset_code === ETH_ADDRESS ? AddressZero : asset.asset_code.toLowerCase()
);
- const balances = await fetchHardhatBalancesWithBalanceChecker(tokenAddresses, accountAddress);
+ const balances = await fetchAnvilBalancesWithBalanceChecker(tokenAddresses, accountAddress);
if (!balances)
return {
assets: {},
diff --git a/src/resources/assets/assetSelectors.ts b/src/resources/assets/assetSelectors.ts
deleted file mode 100644
index 41d6d2ba9e5..00000000000
--- a/src/resources/assets/assetSelectors.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities';
-import { parseAssetsNative } from '@/parsers';
-import isEmpty from 'lodash/isEmpty';
-import isNil from 'lodash/isNil';
-import { RainbowAddressAssets } from './types';
-
-const EMPTY_ARRAY: any = [];
-
-export function selectUserAssetWithUniqueId(uniqueId: string) {
- return (accountAssets: RainbowAddressAssets) => {
- return accountAssets?.[uniqueId];
- };
-}
-
-export function selectSortedUserAssets(nativeCurrency: NativeCurrencyKey) {
- return (accountAssets: RainbowAddressAssets) => {
- return sortAssetsByNativeAmount(accountAssets, nativeCurrency);
- };
-}
-
-const sortAssetsByNativeAmount = (accountAssets: RainbowAddressAssets, nativeCurrency: NativeCurrencyKey): ParsedAddressAsset[] => {
- let assetsNativePrices = Object.values(accountAssets);
-
- if (!isEmpty(assetsNativePrices)) {
- assetsNativePrices = parseAssetsNative(assetsNativePrices, nativeCurrency);
- }
- const { hasValue = EMPTY_ARRAY, noValue = EMPTY_ARRAY } = groupAssetsByMarketValue(assetsNativePrices);
-
- const sortedAssetsNoShitcoins = hasValue.sort((a: any, b: any) => {
- const itemA = Number(a.native?.balance?.amount) ?? 0;
- const itemB = Number(b.native?.balance?.amount) ?? 0;
-
- return itemA < itemB ? 1 : -1;
- });
-
- const sortedShitcoins = noValue.sort((a: any, b: any) => {
- const itemA = a.name;
- const itemB = b.name;
-
- return itemA > itemB ? 1 : -1;
- });
-
- const sortedAssets = sortedAssetsNoShitcoins.concat(sortedShitcoins);
-
- return sortedAssets;
-};
-
-const groupAssetsByMarketValue = (assets: any) =>
- assets.reduce(
- (acc: any, asset: any) => {
- if (isNil(asset.native)) {
- acc.noValue.push(asset);
- } else {
- acc.hasValue.push(asset);
- }
-
- return acc;
- },
- {
- hasValue: [],
- noValue: [],
- }
- );
diff --git a/src/resources/assets/assets.ts b/src/resources/assets/assets.ts
index 057963856bb..d64dacd9948 100644
--- a/src/resources/assets/assets.ts
+++ b/src/resources/assets/assets.ts
@@ -1,8 +1,8 @@
import lang from 'i18n-js';
-import isEmpty from 'lodash/isEmpty';
-import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities';
import { isNativeAsset } from '@/handlers/assets';
import { convertRawAmountToBalance } from '@/helpers/utilities';
+import isEmpty from 'lodash/isEmpty';
+import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities';
import { queryClient } from '@/react-query';
import { positionsQueryKey } from '@/resources/defi/PositionsQuery';
import { RainbowPositions } from '@/resources/defi/types';
diff --git a/src/resources/assets/useSortedUserAssets.ts b/src/resources/assets/useSortedUserAssets.ts
deleted file mode 100644
index b7e80d92c71..00000000000
--- a/src/resources/assets/useSortedUserAssets.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useAccountSettings } from '@/hooks';
-import { selectSortedUserAssets } from '@/resources/assets/assetSelectors';
-import { useUserAssets } from '@/resources/assets/UserAssetsQuery';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
-
-export function useSortedUserAssets() {
- const { accountAddress, nativeCurrency } = useAccountSettings();
- const { connectedToHardhat } = useConnectedToHardhatStore();
-
- return useUserAssets(
- {
- address: accountAddress,
- currency: nativeCurrency,
- connectedToHardhat,
- },
- {
- select: selectSortedUserAssets(nativeCurrency),
- }
- );
-}
diff --git a/src/resources/assets/useUserAsset.ts b/src/resources/assets/useUserAsset.ts
deleted file mode 100644
index 4ee1dea9de1..00000000000
--- a/src/resources/assets/useUserAsset.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { ChainId } from '@/state/backendNetworks/types';
-import { useAccountSettings } from '@/hooks';
-import { useUserAssets } from '@/resources/assets/UserAssetsQuery';
-import { selectUserAssetWithUniqueId } from '@/resources/assets/assetSelectors';
-import { getUniqueId } from '@/utils/ethereumUtils';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
-import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
-
-export function useUserAsset(uniqueId: string) {
- const { accountAddress, nativeCurrency } = useAccountSettings();
- const { connectedToHardhat } = useConnectedToHardhatStore();
-
- return useUserAssets(
- {
- address: accountAddress,
- currency: nativeCurrency,
- connectedToHardhat,
- },
- {
- select: selectUserAssetWithUniqueId(uniqueId),
- }
- );
-}
-
-export function useUserNativeNetworkAsset(chainId: ChainId) {
- const nativeCurrency = useBackendNetworksStore.getState().getChainsNativeAsset()[chainId];
- const { address } = nativeCurrency;
- const uniqueId = getUniqueId(address, chainId);
- return useUserAsset(uniqueId);
-}
diff --git a/src/resources/assets/useUserAssetCount.ts b/src/resources/assets/useUserAssetCount.ts
deleted file mode 100644
index 7cf00ced409..00000000000
--- a/src/resources/assets/useUserAssetCount.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useAccountSettings } from '@/hooks';
-import { useUserAssets } from '@/resources/assets/UserAssetsQuery';
-import { RainbowAddressAssets } from './types';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
-
-const countSelector = (accountAssets: RainbowAddressAssets) => accountAssets?.length;
-
-export function useUserAssetCount() {
- const { accountAddress, nativeCurrency } = useAccountSettings();
- const { connectedToHardhat } = useConnectedToHardhatStore();
-
- return useUserAssets(
- {
- address: accountAddress,
- currency: nativeCurrency,
- connectedToHardhat,
- },
- {
- select: countSelector,
- }
- );
-}
diff --git a/src/resources/metadata/sharedQueries.js b/src/resources/metadata/sharedQueries.js
index 5937062f69b..4c348349eb4 100644
--- a/src/resources/metadata/sharedQueries.js
+++ b/src/resources/metadata/sharedQueries.js
@@ -4,6 +4,10 @@ const BACKEND_NETWORKS_QUERY = `
id
name
label
+ colors {
+ light
+ dark
+ }
icons {
badgeURL
}
diff --git a/src/resources/summary/summary.ts b/src/resources/summary/summary.ts
index cd0ef1d8542..71c68ff92b5 100644
--- a/src/resources/summary/summary.ts
+++ b/src/resources/summary/summary.ts
@@ -51,6 +51,8 @@ interface AddysSummary {
num_erc20s: number;
last_activity: number;
asset_value: number | null;
+ claimables_value: number | null;
+ positions_value: number | null;
};
};
summary_by_chain: {
@@ -63,6 +65,8 @@ interface AddysSummary {
num_erc20s: number;
last_activity: number;
asset_value: number | null;
+ claimables_value: number | null;
+ positions_value: number | null;
};
};
};
diff --git a/src/resources/transactions/consolidatedTransactions.ts b/src/resources/transactions/consolidatedTransactions.ts
index a749e2f72d0..26db739a248 100644
--- a/src/resources/transactions/consolidatedTransactions.ts
+++ b/src/resources/transactions/consolidatedTransactions.ts
@@ -135,6 +135,7 @@ export function useConsolidatedTransactions(
keepPreviousData: true,
getNextPageParam: lastPage => lastPage?.nextPage,
refetchInterval: CONSOLIDATED_TRANSACTIONS_INTERVAL,
+ enabled: !!address,
retry: 3,
}
);
diff --git a/src/resources/trendingTokens/trendingTokens.ts b/src/resources/trendingTokens/trendingTokens.ts
index 169a24ab6f4..4bbb62e9308 100644
--- a/src/resources/trendingTokens/trendingTokens.ts
+++ b/src/resources/trendingTokens/trendingTokens.ts
@@ -2,23 +2,117 @@ import { QueryConfigWithSelect, createQueryKey } from '@/react-query';
import { useQuery } from '@tanstack/react-query';
import { arcClient } from '@/graphql';
-export type TrendingTokensVariables = Parameters['0'];
-export type TrendingTokens = Awaited>;
+import { TrendingCategory, TrendingSort, TrendingTimeframe } from '@/state/trendingTokens/trendingTokens';
+import { Address } from 'viem';
+import { NativeCurrencyKey } from '@/entities';
+import store from '@/redux/store';
+import { SortDirection } from '@/graphql/__generated__/arc';
+import { UniqueId } from '@/__swaps__/types/assets';
+import { ChainId } from '@/state/backendNetworks/types';
+
+export type FarcasterUser = {
+ username: string;
+ pfp_url: string;
+};
+export type TrendingToken = {
+ uniqueId: UniqueId;
+ chainId: ChainId;
+ address: string;
+ name: string;
+ symbol: string;
+ decimals: number;
+ price: number;
+ priceChange: {
+ hr: number;
+ day: number;
+ };
+ marketCap: number;
+ volume: number;
+ highlightedFriends: FarcasterUser[];
+ colors: {
+ primary: string;
+ };
+ icon_url: string;
+};
// ///////////////////////////////////////////////
// Query Key
-export const trendingTokensQueryKey = (props: TrendingTokensVariables) => createQueryKey('trending-tokens', props, { persisterVersion: 0 });
+export const trendingTokensQueryKey = (props: FetchTrendingTokensArgs) => createQueryKey('trending-tokens', props, { persisterVersion: 2 });
export type TrendingTokensQueryKey = ReturnType;
+type FetchTrendingTokensArgs = {
+ chainId?: ChainId;
+ category: TrendingCategory;
+ sortBy: TrendingSort;
+ sortDirection: SortDirection | undefined;
+ timeframe: TrendingTimeframe;
+ walletAddress: Address | undefined;
+ limit?: number;
+ currency?: NativeCurrencyKey;
+};
+
+async function fetchTrendingTokens({
+ queryKey: [
+ { currency = store.getState().settings.nativeCurrency, category, sortBy, sortDirection, timeframe, walletAddress, chainId, limit },
+ ],
+}: {
+ queryKey: TrendingTokensQueryKey;
+}) {
+ const response = await arcClient.trendingTokens({
+ category,
+ sortBy,
+ sortDirection,
+ timeframe,
+ walletAddress,
+ limit,
+ chainId,
+ currency: currency.toLowerCase(),
+ });
+ const trendingTokens: TrendingToken[] = [];
+
+ for (const token of response.trendingTokens.data) {
+ const { uniqueId, address, name, symbol, chainId, decimals, trending, market, icon_url, colors } = token;
+ const { bought_stats } = trending.swap_data;
+ const highlightedFriends = (bought_stats.farcaster_users || []).reduce((friends, friend) => {
+ const { username, pfp_url } = friend;
+ if (username && pfp_url) friends.push({ username, pfp_url });
+ return friends;
+ }, [] as FarcasterUser[]);
+
+ trendingTokens.push({
+ uniqueId,
+ chainId: chainId as ChainId,
+ address,
+ name,
+ symbol,
+ decimals,
+ price: market.price?.value || 0,
+ priceChange: {
+ hr: trending.pool_data.h1_price_change || 0,
+ day: trending.pool_data.h24_price_change || 0,
+ },
+ marketCap: market.market_cap?.value || 0,
+ volume: market.volume_24h || 0,
+ highlightedFriends,
+ icon_url,
+ colors: {
+ primary: colors.primary,
+ },
+ });
+ }
+
+ return trendingTokens;
+}
+
// ///////////////////////////////////////////////
// Query Hook
-export function useTrendingTokens(
- props: TrendingTokensVariables,
- config: QueryConfigWithSelect = {}
+export function useTrendingTokens(
+ args: FetchTrendingTokensArgs,
+ config: QueryConfigWithSelect = {}
) {
- return useQuery(trendingTokensQueryKey(props), () => arcClient.trendingTokens(props), {
+ return useQuery(trendingTokensQueryKey(args), fetchTrendingTokens, {
...config,
staleTime: 60_000, // 1 minute
cacheTime: 60_000 * 30, // 30 minutes
diff --git a/src/screens/AddWalletSheet.tsx b/src/screens/AddWalletSheet.tsx
index 4b010ff3f16..92712d4241d 100644
--- a/src/screens/AddWalletSheet.tsx
+++ b/src/screens/AddWalletSheet.tsx
@@ -5,11 +5,10 @@ import { useNavigation } from '@/navigation';
import Routes from '@/navigation/routesNames';
import React, { useRef } from 'react';
import * as i18n from '@/languages';
-import { HARDWARE_WALLETS, PROFILES, useExperimentalFlag } from '@/config';
+import { HARDWARE_WALLETS, useExperimentalFlag } from '@/config';
import { analytics, analyticsV2 } from '@/analytics';
-import { InteractionManager, Linking } from 'react-native';
+import { InteractionManager } from 'react-native';
import { createAccountForWallet, walletsLoadState } from '@/redux/wallets';
-import WalletBackupTypes from '@/helpers/walletBackupTypes';
import { createWallet } from '@/model/wallet';
import WalletTypes from '@/helpers/walletTypes';
import { logger, RainbowError } from '@/logger';
@@ -19,20 +18,13 @@ import PairHairwareWallet from '@/assets/PairHardwareWallet.png';
import ImportSecretPhraseOrPrivateKey from '@/assets/ImportSecretPhraseOrPrivateKey.png';
import WatchWalletIcon from '@/assets/watchWallet.png';
import { useDispatch } from 'react-redux';
-import {
- backupUserDataIntoCloud,
- getGoogleAccountUserData,
- GoogleDriveUserData,
- isCloudBackupAvailable,
- login,
- logoutFromGoogleDrive,
-} from '@/handlers/cloudBackup';
import showWalletErrorAlert from '@/helpers/support';
import { cloudPlatform } from '@/utils/platform';
-import { IS_ANDROID } from '@/env';
import { RouteProp, useRoute } from '@react-navigation/native';
-import { WrappedAlert as Alert } from '@/helpers/alert';
import { useInitializeWallet, useWallets } from '@/hooks';
+import { WalletLoadingStates } from '@/helpers/walletLoadingStates';
+import { executeFnIfCloudBackupAvailable } from '@/model/backup';
+import { walletLoadingStore } from '@/state/walletLoading/walletLoading';
const TRANSLATIONS = i18n.l.wallet.new.add_wallet_sheet;
@@ -52,7 +44,6 @@ export const AddWalletSheet = () => {
const { goBack, navigate } = useNavigation();
const hardwareWalletsEnabled = useExperimentalFlag(HARDWARE_WALLETS);
- const profilesEnabled = useExperimentalFlag(PROFILES);
const dispatch = useDispatch();
const initializeWallet = useInitializeWallet();
const creatingWallet = useRef();
@@ -83,6 +74,10 @@ export const AddWalletSheet = () => {
},
onCloseModal: async (args: any) => {
if (args) {
+ walletLoadingStore.setState({
+ loadingState: WalletLoadingStates.CREATING_WALLET,
+ });
+
const name = args?.name ?? '';
const color = args?.color ?? null;
// Check if the selected wallet is the primary
@@ -113,31 +108,18 @@ export const AddWalletSheet = () => {
try {
// If we found it and it's not damaged use it to create the new account
if (primaryWalletKey && !wallets?.[primaryWalletKey].damaged) {
- const newWallets = await dispatch(createAccountForWallet(primaryWalletKey, color, name));
+ await dispatch(createAccountForWallet(primaryWalletKey, color, name));
// @ts-ignore
await initializeWallet();
- // If this wallet was previously backed up to the cloud
- // We need to update userData backup so it can be restored too
- if (wallets?.[primaryWalletKey].backedUp && wallets[primaryWalletKey].backupType === WalletBackupTypes.cloud) {
- try {
- await backupUserDataIntoCloud({ wallets: newWallets });
- } catch (e) {
- logger.error(new RainbowError('[AddWalletSheet]: Updating wallet userdata failed after new account creation'), {
- error: e,
- });
- throw e;
- }
- }
-
- // If doesn't exist, we need to create a new wallet
} else {
+ // If doesn't exist, we need to create a new wallet
await createWallet({
color,
name,
clearCallbackOnStartCreation: true,
});
- await dispatch(walletsLoadState(profilesEnabled));
- // @ts-ignore
+ await dispatch(walletsLoadState());
+ // @ts-expect-error - needs refactor to object params
await initializeWallet();
}
} catch (e) {
@@ -149,6 +131,10 @@ export const AddWalletSheet = () => {
showWalletErrorAlert();
}, 1000);
}
+ } finally {
+ walletLoadingStore.setState({
+ loadingState: null,
+ });
}
}
creatingWallet.current = false;
@@ -197,47 +183,11 @@ export const AddWalletSheet = () => {
isFirstWallet,
type: 'seed',
});
- if (IS_ANDROID) {
- try {
- await logoutFromGoogleDrive();
- await login();
-
- getGoogleAccountUserData().then((accountDetails: GoogleDriveUserData | undefined) => {
- if (accountDetails) {
- return navigate(Routes.RESTORE_SHEET);
- }
- Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found));
- });
- } catch (e) {
- Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found));
- logger.error(new RainbowError('[AddWalletSheet]: Error while trying to restore from cloud'), {
- error: e,
- });
- }
- } else {
- const isAvailable = await isCloudBackupAvailable();
- if (!isAvailable) {
- Alert.alert(
- i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.label),
- i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.description),
- [
- {
- onPress: () => {
- Linking.openURL('https://support.apple.com/en-us/HT204025');
- },
- text: i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.show_me),
- },
- {
- style: 'cancel',
- text: i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.no_thanks),
- },
- ]
- );
- return;
- }
- navigate(Routes.RESTORE_SHEET);
- }
+ executeFnIfCloudBackupAvailable({
+ fn: () => navigate(Routes.RESTORE_SHEET),
+ logout: true,
+ });
};
const restoreFromCloudDescription = i18n.t(TRANSLATIONS.options.cloud.description_restore_sheet, {
diff --git a/src/screens/discover/DiscoverScreen.tsx b/src/screens/DiscoverScreen.tsx
similarity index 95%
rename from src/screens/discover/DiscoverScreen.tsx
rename to src/screens/DiscoverScreen.tsx
index 601066d6260..2a0d43df6a2 100644
--- a/src/screens/discover/DiscoverScreen.tsx
+++ b/src/screens/DiscoverScreen.tsx
@@ -4,7 +4,7 @@ import { useIsFocused } from '@react-navigation/native';
import { Box } from '@/design-system';
import { Page } from '@/components/layout';
import { Navbar } from '@/components/navbar/Navbar';
-import DiscoverScreenContent from './components/DiscoverScreenContent';
+import DiscoverScreenContent from '@/components/Discover/DiscoverScreenContent';
import { ButtonPressAnimation } from '@/components/animations';
import { ContactAvatar } from '@/components/contacts';
import ImageAvatar from '@/components/contacts/ImageAvatar';
@@ -14,7 +14,7 @@ import { useNavigation } from '@/navigation';
import { safeAreaInsetValues } from '@/utils';
import * as i18n from '@/languages';
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated';
-import DiscoverScreenProvider, { useDiscoverScreenContext } from './DiscoverScreenContext';
+import DiscoverScreenProvider, { useDiscoverScreenContext } from '@/components/Discover/DiscoverScreenContext';
export let discoverScrollToTopFnRef: () => number | null = () => null;
@@ -30,18 +30,18 @@ const Content = () => {
navigate(Routes.CHANGE_WALLET_SHEET);
}, [navigate]);
- React.useEffect(() => {
- if (isSearching && !isFocused) {
- Keyboard.dismiss();
- }
- }, [isFocused, isSearching]);
-
const scrollHandler = useAnimatedScrollHandler({
onScroll: event => {
scrollY.value = event.contentOffset.y;
},
});
+ useEffect(() => {
+ if (isSearching && !isFocused) {
+ Keyboard.dismiss();
+ }
+ }, [isFocused, isSearching]);
+
useEffect(() => {
discoverScrollToTopFnRef = scrollToTop;
}, [scrollToTop]);
diff --git a/src/screens/ENSIntroSheet.tsx b/src/screens/ENSIntroSheet.tsx
index edcd087e44f..c9d82e6b9f3 100644
--- a/src/screens/ENSIntroSheet.tsx
+++ b/src/screens/ENSIntroSheet.tsx
@@ -1,6 +1,5 @@
import MaskedView from '@react-native-masked-view/masked-view';
import { useRoute } from '@react-navigation/native';
-import { IS_TESTING } from 'react-native-dotenv';
import lang from 'i18n-js';
import React, { useCallback, useMemo } from 'react';
import { InteractionManager, View } from 'react-native';
@@ -16,7 +15,7 @@ import { REGISTRATION_MODES } from '@/helpers/ens';
import { useAccountENSDomains, useDimensions, useENSAvatar, useENSRecords, useENSRegistration } from '@/hooks';
import Routes from '@/navigation/routesNames';
import { useTheme } from '@/theme';
-import { IS_ANDROID } from '@/env';
+import { IS_ANDROID, IS_TEST } from '@/env';
import ContextMenu from '@/components/context-menu/ContextMenu.android';
enum AnotherENSEnum {
@@ -187,7 +186,7 @@ export default function ENSIntroSheet() {
- {IS_TESTING !== 'true' && }
+ {!IS_TEST && }
diff --git a/src/screens/ExplainSheet.js b/src/screens/ExplainSheet.js
index d8945923c99..b63fa165f32 100644
--- a/src/screens/ExplainSheet.js
+++ b/src/screens/ExplainSheet.js
@@ -172,6 +172,9 @@ export const explainers = (params, theme) => {
const chainId = params?.chainId;
const fromChainId = params?.fromChainId;
const toChainId = params?.toChainId;
+ const isDarkMode = theme?.isDarkMode;
+
+ const color = useBackendNetworksStore.getState().getColorsForChainId(chainId, isDarkMode);
const chainsLabel = useBackendNetworksStore.getState().getChainsLabel();
@@ -432,8 +435,8 @@ export const explainers = (params, theme) => {
swapResetInputs: {
button: {
label: `Continue with ${chainsLabel[chainId]}`,
- bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.06),
- textColor: colors?.networkColors?.[chainId],
+ bgColor: color && colors?.alpha(color, 0.06),
+ textColor: color,
},
emoji: '🔐',
extraHeight: -90,
@@ -730,7 +733,7 @@ export const explainers = (params, theme) => {
},
swap_refuel_add: {
logo: (
-
+
{
networkName: params?.networkName,
gasToken: params?.gasToken,
}),
- textColor: colors?.networkColors[chainId],
- bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.05),
+ textColor: color,
+ bgColor: color && colors?.alpha(color, 0.05),
onPress: params?.onRefuel,
},
},
swap_refuel_deduct: {
logo: (
-
+
{
networkName: params?.networkName,
gasToken: params?.gasToken,
}),
- textColor: colors?.networkColors[chainId],
- bgColor: colors?.networkColors[chainId] && colors?.alpha(colors?.networkColors[chainId], 0.05),
+ textColor: color,
+ bgColor: color && colors?.alpha(color, 0.05),
onPress: params?.onRefuel,
},
},
swap_refuel_notice: {
extraHeight: 50,
logo: (
-
+
{
+ const { wallets } = store.getState().wallets;
+ // check for if the recipient is in a damaged wallet state and prevent
+ if (wallets) {
+ const internalWallet = Object.values(wallets).find(wallet =>
+ wallet.addresses.some(address => isLowerCaseMatch(address.address, toAddress))
+ );
+ if (internalWallet?.damaged) {
+ return false;
+ }
+ }
+
if (!toAddress || toAddress?.toLowerCase() === tokenAddress?.toLowerCase()) {
return false;
}
@@ -117,7 +128,8 @@ type OnSubmitProps = {
export default function SendSheet() {
const { goBack, navigate } = useNavigation();
- const { data: sortedAssets } = useSortedUserAssets();
+ const sortedAssets = useUserAssetsStore(state => state.legacyUserAssets);
+ const isLoadingUserAssets = useUserAssetsStore(state => state.isLoadingUserAssets);
const {
gasFeeParamsBySpeed,
gasLimit,
@@ -883,6 +895,16 @@ export default function SendSheet() {
isUniqueAsset,
]);
+ useEffect(() => {
+ if (isLoadingUserAssets || !sortedAssets) return;
+ const params = { screen: 'send' as const, no_icon: 0, no_price: 0, total_tokens: sortedAssets.length };
+ for (const asset of sortedAssets) {
+ if (!asset.icon_url) params.no_icon += 1;
+ if (!asset.price?.relative_change_24h) params.no_price += 1;
+ }
+ analyticsV2.track(analyticsV2.event.tokenList, params);
+ }, [isLoadingUserAssets, sortedAssets]);
+
const sendContactListDataKey = useMemo(() => `${ensSuggestions?.[0]?.address || '_'}`, [ensSuggestions]);
const isEmptyWallet = !sortedAssets?.length && !sendableUniqueTokens?.length;
diff --git a/src/screens/SettingsSheet/SettingsSheet.tsx b/src/screens/SettingsSheet/SettingsSheet.tsx
index 7a68ad83d86..094cdc17456 100644
--- a/src/screens/SettingsSheet/SettingsSheet.tsx
+++ b/src/screens/SettingsSheet/SettingsSheet.tsx
@@ -21,7 +21,6 @@ import { useDimensions } from '@/hooks';
import { SETTINGS_BACKUP_ROUTES } from './components/Backups/routes';
import { IS_ANDROID } from '@/env';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
-import { CloudBackupProvider } from '@/components/backup/CloudBackupProvider';
const Stack = createStackNavigator();
@@ -52,102 +51,100 @@ export function SettingsSheet() {
const memoSettingsOptions = useMemo(() => settingsOptions(colors), [colors]);
return (
-
-
- {({ backgroundColor }) => (
-
+ {({ backgroundColor }) => (
+
+
-
-
- {() => (
-
- )}
-
- {Object.values(SettingsPages).map(
- ({ component, getTitle, key }) =>
- component && (
-
- )
+ {() => (
+
)}
- ({
- cardStyleInterpolator: settingsCardStyleInterpolator,
- title: route.params?.title,
- })}
- />
- ({
- cardStyleInterpolator: settingsCardStyleInterpolator,
- title: route.params?.title,
- })}
- />
- ({
- cardStyleInterpolator: settingsCardStyleInterpolator,
- title: route.params?.title,
- })}
- />
- ({
- cardStyleInterpolator: settingsCardStyleInterpolator,
- title: route.params?.title,
- })}
- />
- ({
- cardStyleInterpolator: settingsCardStyleInterpolator,
- title: route.params?.title,
- })}
- />
-
-
- )}
-
-
+
+ {Object.values(SettingsPages).map(
+ ({ component, getTitle, key }) =>
+ component && (
+
+ )
+ )}
+ ({
+ cardStyleInterpolator: settingsCardStyleInterpolator,
+ title: route.params?.title,
+ })}
+ />
+ ({
+ cardStyleInterpolator: settingsCardStyleInterpolator,
+ title: route.params?.title,
+ })}
+ />
+ ({
+ cardStyleInterpolator: settingsCardStyleInterpolator,
+ title: route.params?.title,
+ })}
+ />
+ ({
+ cardStyleInterpolator: settingsCardStyleInterpolator,
+ title: route.params?.title,
+ })}
+ />
+ ({
+ cardStyleInterpolator: settingsCardStyleInterpolator,
+ title: route.params?.title,
+ })}
+ />
+
+
+ )}
+
);
}
diff --git a/src/screens/SettingsSheet/components/Backups/BackUpMenuButton.tsx b/src/screens/SettingsSheet/components/Backups/BackUpMenuButton.tsx
index 1b2f4334e8e..ba33ae5da99 100644
--- a/src/screens/SettingsSheet/components/Backups/BackUpMenuButton.tsx
+++ b/src/screens/SettingsSheet/components/Backups/BackUpMenuButton.tsx
@@ -1,4 +1,3 @@
-import { useCreateBackupStateType } from '@/components/backup/useCreateBackup';
import { useTheme } from '@/theme';
import React, { useState, useMemo, useEffect } from 'react';
import * as i18n from '@/languages';
@@ -6,102 +5,103 @@ import MenuItem from '../MenuItem';
import Spinner from '@/components/Spinner';
import { FloatingEmojis } from '@/components/floating-emojis';
import { useDimensions } from '@/hooks';
+import { CloudBackupState } from '@/state/backups/backups';
export const BackUpMenuItem = ({
icon = '',
- loading,
+ backupState,
onPress,
title,
+ disabled,
}: {
icon?: string;
- loading: useCreateBackupStateType;
+ backupState: CloudBackupState;
title: string;
onPress: () => void;
+ disabled?: boolean;
}) => {
const { colors } = useTheme();
const { width: deviceWidth } = useDimensions();
const [emojiTrigger, setEmojiTrigger] = useState void)>(null);
useEffect(() => {
- if (loading === 'success') {
+ if (backupState === CloudBackupState.Success) {
for (let i = 0; i < 20; i++) {
setTimeout(() => {
emojiTrigger?.();
}, 100 * i);
}
}
- }, [emojiTrigger, loading]);
+ }, [emojiTrigger, backupState]);
const accentColor = useMemo(() => {
- switch (loading) {
- case 'success':
+ switch (backupState) {
+ case CloudBackupState.Success:
return colors.green;
- case 'error':
+ case CloudBackupState.Error:
return colors.red;
default:
return undefined;
}
- }, [colors, loading]);
+ }, [colors, backupState]);
const titleText = useMemo(() => {
- switch (loading) {
- case 'loading':
+ switch (backupState) {
+ case CloudBackupState.InProgress:
return i18n.t(i18n.l.back_up.cloud.backing_up);
- case 'success':
+ case CloudBackupState.Success:
return i18n.t(i18n.l.back_up.cloud.backup_success);
- case 'error':
+ case CloudBackupState.Error:
return i18n.t(i18n.l.back_up.cloud.backup_failed);
default:
return title;
}
- }, [loading, title]);
+ }, [backupState, title]);
const localIcon = useMemo(() => {
- switch (loading) {
- case 'success':
+ switch (backupState) {
+ case CloudBackupState.Success:
return '';
- case 'error':
+ case CloudBackupState.Error:
return '';
default:
return icon;
}
- }, [icon, loading]);
+ }, [icon, backupState]);
return (
- <>
- {/* @ts-ignore js */}
-
- {({ onNewEmoji }: { onNewEmoji: () => void }) => (
-
- ) : (
-
- )
- }
- onPress={() => {
- setEmojiTrigger(() => onNewEmoji);
- onPress();
- }}
- size={52}
- titleComponent={}
- />
- )}
-
- >
+
+ {({ onNewEmoji }) => (
+
+ ) : (
+
+ )
+ }
+ onPress={() => {
+ setEmojiTrigger(() => onNewEmoji);
+ onPress();
+ }}
+ size={52}
+ titleComponent={}
+ />
+ )}
+
);
};
diff --git a/src/screens/SettingsSheet/components/Backups/ViewCloudBackups.tsx b/src/screens/SettingsSheet/components/Backups/ViewCloudBackups.tsx
index 1842d3fae2a..90cbdddeff3 100644
--- a/src/screens/SettingsSheet/components/Backups/ViewCloudBackups.tsx
+++ b/src/screens/SettingsSheet/components/Backups/ViewCloudBackups.tsx
@@ -5,19 +5,18 @@ import { Text as RNText } from '@/components/text';
import Menu from '../Menu';
import MenuContainer from '../MenuContainer';
import MenuItem from '../MenuItem';
-import { Backup, parseTimestampFromFilename } from '@/model/backup';
+import { BackupFile, parseTimestampFromFilename } from '@/model/backup';
import { format } from 'date-fns';
-import { Stack } from '@/design-system';
import { useNavigation } from '@/navigation';
import Routes from '@/navigation/routesNames';
-import { IS_ANDROID } from '@/env';
import walletBackupStepTypes from '@/helpers/walletBackupStepTypes';
-import { useCloudBackups } from '@/components/backup/CloudBackupProvider';
-import { Centered } from '@/components/layout';
+import { Page } from '@/components/layout';
import Spinner from '@/components/Spinner';
import ActivityIndicator from '@/components/ActivityIndicator';
-import { cloudPlatform } from '@/utils/platform';
import { useTheme } from '@/theme';
+import { CloudBackupState, LoadingStates, backupsStore } from '@/state/backups/backups';
+import { titleForBackupState } from '../../utils';
+import { Box } from '@/design-system';
const LoadingText = styled(RNText).attrs(({ theme: { colors } }: any) => ({
color: colors.blueGreyDark,
@@ -32,43 +31,14 @@ const ViewCloudBackups = () => {
const { navigate } = useNavigation();
const { colors } = useTheme();
- const { isFetching, backups } = useCloudBackups();
-
- const cloudBackups = backups.files
- .filter(backup => {
- if (IS_ANDROID) {
- return !backup.name.match(/UserData/i);
- }
-
- return backup.isFile && backup.size > 0 && !backup.name.match(/UserData/i);
- })
- .sort((a, b) => {
- return parseTimestampFromFilename(b.name) - parseTimestampFromFilename(a.name);
- });
-
- const mostRecentBackup = cloudBackups.reduce(
- (prev, current) => {
- if (!current) {
- return prev;
- }
-
- if (!prev) {
- return current;
- }
-
- const prevTimestamp = new Date(prev.lastModified).getTime();
- const currentTimestamp = new Date(current.lastModified).getTime();
- if (currentTimestamp > prevTimestamp) {
- return current;
- }
-
- return prev;
- },
- undefined as Backup | undefined
- );
+ const { status, backups, mostRecentBackup } = backupsStore(state => ({
+ status: state.status,
+ backups: state.backups,
+ mostRecentBackup: state.mostRecentBackup,
+ }));
const onSelectCloudBackup = useCallback(
- async (selectedBackup: Backup) => {
+ async (selectedBackup: BackupFile) => {
navigate(Routes.BACKUP_SHEET, {
step: walletBackupStepTypes.restore_from_backup,
selectedBackup,
@@ -77,80 +47,110 @@ const ViewCloudBackups = () => {
[navigate]
);
- return (
-
-
- {!isFetching && !cloudBackups.length && (
-
- } />
-
- )}
+ const renderNoBackupsState = () => (
+ <>
+
+ } />
+
+ >
+ );
+
+ const renderMostRecentBackup = () => {
+ if (!mostRecentBackup) {
+ return null;
+ }
+
+ return (
+
+
+ }
+ onPress={() => onSelectCloudBackup(mostRecentBackup)}
+ size={52}
+ width="full"
+ titleComponent={}
+ />
+
+
+ );
+ };
+
+ const renderOlderBackups = () => (
+ <>
+
+
+ {backups.files
+ .filter(backup => backup.name !== mostRecentBackup?.name)
+ .sort((a, b) => {
+ const timestampA = new Date(parseTimestampFromFilename(a.name)).getTime();
+ const timestampB = new Date(parseTimestampFromFilename(b.name)).getTime();
+ return timestampB - timestampA;
+ })
+ .map(backup => (
+
+
+
+
+ backupsStore.getState().syncAndFetchBackups()}
+ titleComponent={}
+ />
+
+ >
+ );
- {!isFetching && cloudBackups.length && (
- <>
- {mostRecentBackup && (
-
- }
- onPress={() => onSelectCloudBackup(mostRecentBackup)}
- size={52}
- width="full"
- titleComponent={}
- />
-
- )}
+ const renderBackupsList = () => (
+ <>
+ {renderMostRecentBackup()}
+ {renderOlderBackups()}
+ >
+ );
-
- {cloudBackups.map(
- backup =>
- backup.name !== mostRecentBackup?.name && (
- onSelectCloudBackup(backup)}
- size={52}
- width="full"
- titleComponent={
-
- }
- />
- )
- )}
+ const isLoading = LoadingStates.includes(status);
- {cloudBackups.length === 1 && (
- }
- />
- )}
-
- >
- )}
+ if (isLoading) {
+ return (
+
+ {android ? : }
+ {titleForBackupState[status]}
+
+ );
+ }
- {isFetching && (
-
- {android ? : }
- {
-
- {i18n.t(i18n.l.back_up.cloud.fetching_backups, {
- cloudPlatformName: cloudPlatform,
- })}
-
- }
-
- )}
-
+ return (
+
+ {status === CloudBackupState.Ready && !backups.files.length && renderNoBackupsState()}
+ {status === CloudBackupState.Ready && backups.files.length > 0 && renderBackupsList()}
);
};
diff --git a/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx b/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx
index d085c3f62fd..9fddd15964d 100644
--- a/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx
+++ b/src/screens/SettingsSheet/components/Backups/ViewWalletBackup.tsx
@@ -29,31 +29,23 @@ import Routes from '@/navigation/routesNames';
import walletBackupTypes from '@/helpers/walletBackupTypes';
import { SETTINGS_BACKUP_ROUTES } from './routes';
import { analyticsV2 } from '@/analytics';
-import { InteractionManager, Linking } from 'react-native';
+import { InteractionManager } from 'react-native';
import { useDispatch } from 'react-redux';
-import { createAccountForWallet, walletsLoadState } from '@/redux/wallets';
-import {
- GoogleDriveUserData,
- backupUserDataIntoCloud,
- getGoogleAccountUserData,
- isCloudBackupAvailable,
- login,
-} from '@/handlers/cloudBackup';
+import { createAccountForWallet } from '@/redux/wallets';
import { logger, RainbowError } from '@/logger';
-import { RainbowAccount, createWallet } from '@/model/wallet';
-import { PROFILES, useExperimentalFlag } from '@/config';
+import { RainbowAccount } from '@/model/wallet';
import showWalletErrorAlert from '@/helpers/support';
-import { IS_ANDROID, IS_IOS } from '@/env';
+import { IS_IOS } from '@/env';
import ImageAvatar from '@/components/contacts/ImageAvatar';
-import { useCreateBackup } from '@/components/backup/useCreateBackup';
import { BackUpMenuItem } from './BackUpMenuButton';
-import { checkWalletsForBackupStatus } from '../../utils';
-import { useCloudBackups } from '@/components/backup/CloudBackupProvider';
-import { WalletCountPerType, useVisibleWallets } from '../../useVisibleWallets';
import { format } from 'date-fns';
import { removeFirstEmojiFromString } from '@/helpers/emojiHandler';
-import { Backup, parseTimestampFromFilename } from '@/model/backup';
-import { WrappedAlert as Alert } from '@/helpers/alert';
+import { useCreateBackup } from '@/components/backup/useCreateBackup';
+import { backupsStore } from '@/state/backups/backups';
+import { WalletLoadingStates } from '@/helpers/walletLoadingStates';
+import { executeFnIfCloudBackupAvailable } from '@/model/backup';
+import { isWalletBackedUpForCurrentAccount } from '../../utils';
+import { walletLoadingStore } from '@/state/walletLoading/walletLoading';
type ViewWalletBackupParams = {
ViewWalletBackup: { walletId: string; title: string; imported?: boolean };
@@ -126,107 +118,38 @@ const ContextMenuWrapper = ({ children, account, menuConfig, onPressMenuItem }:
const ViewWalletBackup = () => {
const { params } = useRoute>();
- const { backups } = useCloudBackups();
+ const createBackup = useCreateBackup();
+ const { status, backupProvider, mostRecentBackup } = backupsStore(state => ({
+ status: state.status,
+ backupProvider: state.backupProvider,
+ mostRecentBackup: state.mostRecentBackup,
+ }));
const { walletId, title: incomingTitle } = params;
const creatingWallet = useRef();
const { isDamaged, wallets } = useWallets();
const wallet = wallets?.[walletId];
const dispatch = useDispatch();
const initializeWallet = useInitializeWallet();
- const profilesEnabled = useExperimentalFlag(PROFILES);
-
- const walletTypeCount: WalletCountPerType = {
- phrase: 0,
- privateKey: 0,
- };
-
- const { lastBackupDate } = useVisibleWallets({ wallets, walletTypeCount });
-
- const cloudBackups = backups.files
- .filter(backup => {
- if (IS_ANDROID) {
- return !backup.name.match(/UserData/i);
- }
-
- return backup.isFile && backup.size > 0 && !backup.name.match(/UserData/i);
- })
- .sort((a, b) => {
- return parseTimestampFromFilename(b.name) - parseTimestampFromFilename(a.name);
- });
-
- const mostRecentBackup = cloudBackups.reduce(
- (prev, current) => {
- if (!current) {
- return prev;
- }
-
- if (!prev) {
- return current;
- }
-
- const prevTimestamp = new Date(prev.lastModified).getTime();
- const currentTimestamp = new Date(current.lastModified).getTime();
- if (currentTimestamp > prevTimestamp) {
- return current;
- }
-
- return prev;
- },
- undefined as Backup | undefined
- );
-
- const { backupProvider } = useMemo(() => checkWalletsForBackupStatus(wallets), [wallets]);
const isSecretPhrase = WalletTypes.mnemonic === wallet?.type;
-
const title = wallet?.type === WalletTypes.privateKey ? wallet?.addresses[0].label : incomingTitle;
+ const isBackedUp = isWalletBackedUpForCurrentAccount({
+ backupType: wallet?.backupType,
+ backedUp: wallet?.backedUp,
+ backupFile: wallet?.backupFile,
+ });
const { navigate } = useNavigation();
const [isToastActive, setToastActive] = useRecoilState(addressCopiedToastAtom);
- const { onSubmit, loading } = useCreateBackup({
- walletId,
- });
const backupWalletsToCloud = useCallback(async () => {
- if (IS_ANDROID) {
- try {
- await login();
-
- getGoogleAccountUserData().then((accountDetails: GoogleDriveUserData | undefined) => {
- if (accountDetails) {
- return onSubmit({});
- }
- Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found));
- });
- } catch (e) {
- Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found));
- logger.error(new RainbowError(`[ViewWalletBackup]: Logging into Google Drive failed`), { error: e });
- }
- } else {
- const isAvailable = await isCloudBackupAvailable();
- if (!isAvailable) {
- Alert.alert(
- i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.label),
- i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.description),
- [
- {
- onPress: () => {
- Linking.openURL('https://support.apple.com/en-us/HT204025');
- },
- text: i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.show_me),
- },
- {
- style: 'cancel',
- text: i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.no_thanks),
- },
- ]
- );
- return;
- }
- }
-
- onSubmit({});
- }, [onSubmit]);
+ executeFnIfCloudBackupAvailable({
+ fn: () =>
+ createBackup({
+ walletId,
+ }),
+ });
+ }, [createBackup, walletId]);
const onNavigateToSecretWarning = useCallback(() => {
navigate(SETTINGS_BACKUP_ROUTES.SECRET_WARNING, {
@@ -265,36 +188,17 @@ const ViewWalletBackup = () => {
},
onCloseModal: async (args: any) => {
if (args) {
+ walletLoadingStore.setState({
+ loadingState: WalletLoadingStates.CREATING_WALLET,
+ });
+
const name = args?.name ?? '';
const color = args?.color ?? null;
// Check if the selected wallet is the primary
try {
// If we found it and it's not damaged use it to create the new account
if (wallet && !wallet.damaged) {
- const newWallets = await dispatch(createAccountForWallet(wallet.id, color, name));
- // @ts-expect-error - no params
- await initializeWallet();
- // If this wallet was previously backed up to the cloud
- // We need to update userData backup so it can be restored too
- if (wallet.backedUp && wallet.backupType === walletBackupTypes.cloud) {
- try {
- await backupUserDataIntoCloud({ wallets: newWallets });
- } catch (e) {
- logger.error(new RainbowError(`[ViewWalletBackup]: Updating wallet userdata failed after new account creation`), {
- error: e,
- });
- throw e;
- }
- }
-
- // If doesn't exist, we need to create a new wallet
- } else {
- await createWallet({
- color,
- name,
- clearCallbackOnStartCreation: true,
- });
- await dispatch(walletsLoadState(profilesEnabled));
+ await dispatch(createAccountForWallet(wallet.id, color, name));
// @ts-expect-error - no params
await initializeWallet();
}
@@ -307,6 +211,10 @@ const ViewWalletBackup = () => {
showWalletErrorAlert();
}, 1000);
}
+ } finally {
+ walletLoadingStore.setState({
+ loadingState: null,
+ });
}
}
creatingWallet.current = false;
@@ -324,7 +232,7 @@ const ViewWalletBackup = () => {
error: e,
});
}
- }, [creatingWallet, dispatch, isDamaged, navigate, initializeWallet, profilesEnabled, wallet]);
+ }, [creatingWallet, dispatch, isDamaged, navigate, initializeWallet, wallet]);
const handleCopyAddress = React.useCallback(
(address: string) => {
@@ -386,7 +294,7 @@ const ViewWalletBackup = () => {
return (
- {!wallet?.backedUp && (
+ {!isBackedUp && (
<>
{
/>
- {backupProvider === walletBackupTypes.cloud && (
+
{
title={i18n.t(i18n.l.back_up.cloud.back_up_all_wallets_to_cloud, {
cloudPlatformName: cloudPlatform,
})}
- loading={loading}
+ backupState={status}
onPress={backupWalletsToCloud}
/>
-
- )}
-
- {backupProvider !== walletBackupTypes.cloud && (
-
}
@@ -456,20 +355,12 @@ const ViewWalletBackup = () => {
titleComponent={}
testID={'back-up-manually'}
/>
-
- )}
+
>
)}
- {wallet?.backedUp && (
+ {isBackedUp && (
<>
{
paddingBottom={{ custom: 24 }}
iconComponent={
}
titleComponent={
{
{
>
)}
-
- }
- onPress={onNavigateToSecretWarning}
- size={52}
- titleComponent={
-
+
+
- }
- />
-
+
+
+ )}
+
+
+
+ }
+ onPress={onNavigateToSecretWarning}
+ size={52}
+ titleComponent={
+
+ }
+ />
+
+
{wallet?.addresses
.filter(a => a.visible)
- .map((account: RainbowAccount) => (
-
- }
- labelComponent={
- account.label.endsWith('.eth') || account.label !== '' ? (
-
- ) : null
- }
- titleComponent={
-
- }
- rightComponent={}
- />
-
- ))}
+ .map((account: RainbowAccount) => {
+ const isNamedOrEns = account.label.endsWith('.eth') || removeFirstEmojiFromString(account.label) !== '';
+ const label = isNamedOrEns ? abbreviations.address(account.address, 3, 5) : undefined;
+ const title = isNamedOrEns
+ ? abbreviations.abbreviateEnsForDisplay(removeFirstEmojiFromString(account.label), 20) ?? ''
+ : abbreviations.address(account.address, 3, 5) ?? '';
+
+ return (
+
+ }
+ labelComponent={label ? : null}
+ titleComponent={}
+ rightComponent={}
+ />
+
+ );
+ })}
{wallet?.type !== WalletTypes.privateKey && (
-
- }
- onPress={onCreateNewWallet}
- size={52}
- titleComponent={}
- />
-
+
+
+ }
+ onPress={onCreateNewWallet}
+ size={52}
+ titleComponent={}
+ />
+
+
)}
diff --git a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx
index 9823fd2555f..f1a38d7ec24 100644
--- a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx
+++ b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx
@@ -1,5 +1,4 @@
-/* eslint-disable no-nested-ternary */
-import React, { useCallback, useMemo } from 'react';
+import React, { useCallback, useMemo, useRef } from 'react';
import { cloudPlatform } from '@/utils/platform';
import Menu from '../Menu';
import MenuContainer from '../MenuContainer';
@@ -12,11 +11,11 @@ import WalletTypes, { EthereumWalletType } from '@/helpers/walletTypes';
import ImageAvatar from '@/components/contacts/ImageAvatar';
import { useENSAvatar, useInitializeWallet, useManageCloudBackups, useWallets } from '@/hooks';
import { useNavigation } from '@/navigation';
-import { abbreviations } from '@/utils';
+import { abbreviations, deviceUtils } from '@/utils';
import { addressHashedEmoji } from '@/utils/profileUtils';
import * as i18n from '@/languages';
-import MenuHeader from '../MenuHeader';
-import { checkWalletsForBackupStatus } from '../../utils';
+import MenuHeader, { StatusType } from '../MenuHeader';
+import { checkLocalWalletsForBackupStatus, isWalletBackedUpForCurrentAccount } from '../../utils';
import { Inline, Text, Box, Stack } from '@/design-system';
import { ContactAvatar } from '@/components/contacts';
import { useTheme } from '@/theme';
@@ -25,26 +24,40 @@ import { backupsCard } from '@/components/cards/utils/constants';
import { WalletCountPerType, useVisibleWallets } from '../../useVisibleWallets';
import { SETTINGS_BACKUP_ROUTES } from './routes';
import { RainbowAccount, createWallet } from '@/model/wallet';
-import { PROFILES, useExperimentalFlag } from '@/config';
import { useDispatch } from 'react-redux';
import { walletsLoadState } from '@/redux/wallets';
import { RainbowError, logger } from '@/logger';
import { IS_ANDROID, IS_IOS } from '@/env';
-import { BackupTypes, useCreateBackup } from '@/components/backup/useCreateBackup';
+import { useCreateBackup } from '@/components/backup/useCreateBackup';
import { BackUpMenuItem } from './BackUpMenuButton';
import { format } from 'date-fns';
import { removeFirstEmojiFromString } from '@/helpers/emojiHandler';
-import { Backup, parseTimestampFromFilename } from '@/model/backup';
-import { useCloudBackups } from '@/components/backup/CloudBackupProvider';
-import { GoogleDriveUserData, getGoogleAccountUserData, isCloudBackupAvailable, login } from '@/handlers/cloudBackup';
-import { WrappedAlert as Alert } from '@/helpers/alert';
-import { Linking } from 'react-native';
-import { noop } from 'lodash';
+import { backupsStore, CloudBackupState } from '@/state/backups/backups';
+import { WalletLoadingStates } from '@/helpers/walletLoadingStates';
+import { executeFnIfCloudBackupAvailable } from '@/model/backup';
+import { walletLoadingStore } from '@/state/walletLoading/walletLoading';
+import { AbsolutePortalRoot } from '@/components/AbsolutePortal';
+import { FlatList, ScrollView } from 'react-native';
type WalletPillProps = {
account: RainbowAccount;
};
+// constants for the account section
+const menuContainerPadding = 19.5; // 19px is the padding on the left and right of the container but we need 1px more to account for the shadows on each container
+const accountsContainerWidth = deviceUtils.dimensions.width - menuContainerPadding * 4;
+const spaceBetweenAccounts = 4;
+const accountsItemWidth = accountsContainerWidth / 3;
+const basePadding = 16;
+const rowHeight = 36;
+
+const getAccountSectionHeight = (numAccounts: number) => {
+ const rows = Math.ceil(Math.max(1, numAccounts) / 3);
+ const paddingBetween = (rows - 1) * 4;
+
+ return basePadding + rows * rowHeight - paddingBetween;
+};
+
const WalletPill = ({ account }: WalletPillProps) => {
const label = useMemo(() => removeFirstEmojiFromString(account.label), [account.label]);
@@ -58,7 +71,7 @@ const WalletPill = ({ account }: WalletPillProps) => {
key={account.address}
flexDirection="row"
alignItems="center"
- backgroundColor={colors.alpha(colors.grey, 0.4)}
+ backgroundColor={colors.alpha(colors.grey, 0.24)}
borderRadius={23}
shadowColor={isDarkMode ? colors.shadow : colors.alpha(colors.blueGreyDark, 0.1)}
elevation={12}
@@ -67,6 +80,7 @@ const WalletPill = ({ account }: WalletPillProps) => {
paddingLeft={{ custom: 4 }}
paddingRight={{ custom: 8 }}
padding={{ custom: 4 }}
+ width={{ custom: accountsItemWidth }}
>
{ENSAvatar?.imageUrl ? (
@@ -82,27 +96,22 @@ const WalletPill = ({ account }: WalletPillProps) => {
);
};
-const getAccountSectionHeight = (numAccounts: number) => {
- const basePadding = 16;
- const rowHeight = 36;
- const rows = Math.ceil(Math.max(1, numAccounts) / 3);
- const paddingBetween = (rows - 1) * 4;
-
- return basePadding + rows * rowHeight - paddingBetween;
-};
-
export const WalletsAndBackup = () => {
const { navigate } = useNavigation();
const { wallets } = useWallets();
- const profilesEnabled = useExperimentalFlag(PROFILES);
- const { backups } = useCloudBackups();
const dispatch = useDispatch();
- const initializeWallet = useInitializeWallet();
+ const scrollviewRef = useRef(null);
- const { onSubmit, loading } = useCreateBackup({
- walletId: undefined, // NOTE: This is not used when backing up All wallets
- });
+ const createBackup = useCreateBackup();
+ const { status, backupProvider, backups, mostRecentBackup } = backupsStore(state => ({
+ status: state.status,
+ backupProvider: state.backupProvider,
+ backups: state.backups,
+ mostRecentBackup: state.mostRecentBackup,
+ }));
+
+ const initializeWallet = useInitializeWallet();
const { manageCloudBackups } = useManageCloudBackups();
@@ -111,52 +120,15 @@ export const WalletsAndBackup = () => {
privateKey: 0,
};
- const { allBackedUp, backupProvider } = useMemo(() => checkWalletsForBackupStatus(wallets), [wallets]);
+ const { allBackedUp } = useMemo(() => checkLocalWalletsForBackupStatus(wallets, backups), [wallets, backups]);
- const { visibleWallets, lastBackupDate } = useVisibleWallets({ wallets, walletTypeCount });
-
- const cloudBackups = backups.files
- .filter(backup => {
- if (IS_ANDROID) {
- return !backup.name.match(/UserData/i);
- }
-
- return backup.isFile && backup.size > 0 && !backup.name.match(/UserData/i);
- })
- .sort((a, b) => {
- return parseTimestampFromFilename(b.name) - parseTimestampFromFilename(a.name);
- });
-
- const mostRecentBackup = cloudBackups.reduce(
- (prev, current) => {
- if (!current) {
- return prev;
- }
-
- if (!prev) {
- return current;
- }
-
- const prevTimestamp = new Date(prev.lastModified).getTime();
- const currentTimestamp = new Date(current.lastModified).getTime();
- if (currentTimestamp > prevTimestamp) {
- return current;
- }
-
- return prev;
- },
- undefined as Backup | undefined
- );
+ const visibleWallets = useVisibleWallets({ wallets, walletTypeCount });
const sortedWallets = useMemo(() => {
- const notBackedUpSecretPhraseWallets = visibleWallets.filter(
- wallet => !wallet.isBackedUp && wallet.type === EthereumWalletType.mnemonic
- );
- const notBackedUpPrivateKeyWallets = visibleWallets.filter(
- wallet => !wallet.isBackedUp && wallet.type === EthereumWalletType.privateKey
- );
- const backedUpSecretPhraseWallets = visibleWallets.filter(wallet => wallet.isBackedUp && wallet.type === EthereumWalletType.mnemonic);
- const backedUpPrivateKeyWallets = visibleWallets.filter(wallet => wallet.isBackedUp && wallet.type === EthereumWalletType.privateKey);
+ const notBackedUpSecretPhraseWallets = visibleWallets.filter(wallet => !wallet.backedUp && wallet.type === EthereumWalletType.mnemonic);
+ const notBackedUpPrivateKeyWallets = visibleWallets.filter(wallet => !wallet.backedUp && wallet.type === EthereumWalletType.privateKey);
+ const backedUpSecretPhraseWallets = visibleWallets.filter(wallet => wallet.backedUp && wallet.type === EthereumWalletType.mnemonic);
+ const backedUpPrivateKeyWallets = visibleWallets.filter(wallet => wallet.backedUp && wallet.type === EthereumWalletType.privateKey);
return [
...notBackedUpSecretPhraseWallets,
@@ -166,48 +138,28 @@ export const WalletsAndBackup = () => {
];
}, [visibleWallets]);
- const backupAllNonBackedUpWalletsTocloud = useCallback(async () => {
- if (IS_ANDROID) {
- try {
- await login();
-
- getGoogleAccountUserData().then((accountDetails: GoogleDriveUserData | undefined) => {
- if (accountDetails) {
- return onSubmit({ type: BackupTypes.All });
+ const backupAllNonBackedUpWalletsTocloud = useCallback(() => {
+ executeFnIfCloudBackupAvailable({
+ fn: () => createBackup({}),
+ });
+ }, [createBackup]);
+
+ const enableCloudBackups = useCallback(() => {
+ executeFnIfCloudBackupAvailable({
+ fn: async () => {
+ // NOTE: For Android we could be coming from a not-logged-in state, so we
+ // need to check if we have any wallets to back up first.
+ if (IS_ANDROID) {
+ const currentBackups = backupsStore.getState().backups;
+ if (checkLocalWalletsForBackupStatus(wallets, currentBackups).allBackedUp) {
+ return;
}
- Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found));
- });
- } catch (e) {
- Alert.alert(i18n.t(i18n.l.back_up.errors.no_account_found));
- logger.error(new RainbowError(`[WalletsAndBackup]: Logging into Google Drive failed`), {
- error: e,
- });
- }
- } else {
- const isAvailable = await isCloudBackupAvailable();
- if (!isAvailable) {
- Alert.alert(
- i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.label),
- i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.description),
- [
- {
- onPress: () => {
- Linking.openURL('https://support.apple.com/en-us/HT204025');
- },
- text: i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.show_me),
- },
- {
- style: 'cancel',
- text: i18n.t(i18n.l.modal.back_up.alerts.cloud_not_enabled.no_thanks),
- },
- ]
- );
- return;
- }
- }
-
- onSubmit({ type: BackupTypes.All });
- }, [onSubmit]);
+ }
+ return createBackup({});
+ },
+ logout: true,
+ });
+ }, [createBackup, wallets]);
const onViewCloudBackups = useCallback(async () => {
navigate(SETTINGS_BACKUP_ROUTES.VIEW_CLOUD_BACKUPS, {
@@ -223,13 +175,17 @@ export const WalletsAndBackup = () => {
onCloseModal: async ({ name }: { name: string }) => {
const nameValue = name.trim() !== '' ? name.trim() : '';
try {
+ walletLoadingStore.setState({
+ loadingState: WalletLoadingStates.CREATING_WALLET,
+ });
+
await createWallet({
color: null,
name: nameValue,
clearCallbackOnStartCreation: true,
});
- await dispatch(walletsLoadState(profilesEnabled));
+ await dispatch(walletsLoadState());
// @ts-expect-error - no params
await initializeWallet();
@@ -237,10 +193,15 @@ export const WalletsAndBackup = () => {
logger.error(new RainbowError(`[WalletsAndBackup]: Failed to create new secret phrase`), {
error: err,
});
+ } finally {
+ walletLoadingStore.setState({
+ loadingState: null,
+ });
+ scrollviewRef.current?.scrollTo({ y: 0, animated: true });
}
},
});
- }, [dispatch, initializeWallet, navigate, profilesEnabled, walletTypeCount.phrase]);
+ }, [dispatch, initializeWallet, navigate, walletTypeCount.phrase]);
const onPressLearnMoreAboutCloudBackups = useCallback(() => {
navigate(Routes.LEARN_WEB_VIEW_SCREEN, {
@@ -263,6 +224,66 @@ export const WalletsAndBackup = () => {
[navigate, wallets]
);
+ const { status: iconStatusType, text } = useMemo<{ status: StatusType; text: string }>(() => {
+ if (!backupProvider) {
+ if (status === CloudBackupState.FailedToInitialize || status === CloudBackupState.NotAvailable) {
+ return {
+ status: 'not-enabled',
+ text: i18n.t(i18n.l.back_up.cloud.statuses.not_enabled),
+ };
+ }
+
+ if (status !== CloudBackupState.Ready) {
+ return {
+ status: 'out-of-sync',
+ text: i18n.t(i18n.l.back_up.cloud.statuses.syncing),
+ };
+ }
+
+ if (!allBackedUp) {
+ return {
+ status: 'out-of-date',
+ text: i18n.t(i18n.l.back_up.cloud.statuses.out_of_date),
+ };
+ }
+
+ return {
+ status: 'up-to-date',
+ text: i18n.t(i18n.l.back_up.cloud.statuses.up_to_date),
+ };
+ }
+
+ if (status === CloudBackupState.FailedToInitialize || status === CloudBackupState.NotAvailable) {
+ return {
+ status: 'not-enabled',
+ text: i18n.t(i18n.l.back_up.cloud.statuses.not_enabled),
+ };
+ }
+
+ if (status !== CloudBackupState.Ready) {
+ return {
+ status: 'out-of-sync',
+ text: i18n.t(i18n.l.back_up.cloud.statuses.syncing),
+ };
+ }
+
+ if (!allBackedUp) {
+ return {
+ status: 'out-of-date',
+ text: i18n.t(i18n.l.back_up.cloud.statuses.out_of_date),
+ };
+ }
+
+ return {
+ status: 'up-to-date',
+ text: i18n.t(i18n.l.back_up.cloud.statuses.up_to_date),
+ };
+ }, [backupProvider, status, allBackedUp]);
+
+ const isCloudBackupDisabled = useMemo(() => {
+ return status !== CloudBackupState.Ready && status !== CloudBackupState.NotAvailable;
+ }, [status]);
+
const renderView = useCallback(() => {
switch (backupProvider) {
default:
@@ -275,7 +296,7 @@ export const WalletsAndBackup = () => {
paddingTop={{ custom: 8 }}
iconComponent={}
titleComponent={}
- statusComponent={}
+ statusComponent={}
labelComponent={
{
/>
-
-
-
+
+
+
+
+
- {sortedWallets.map(({ name, isBackedUp, accounts, key, numAccounts, backedUp, imported }) => {
+ {sortedWallets.map(({ id, name, backedUp, backupFile, backupType, imported, addresses }) => {
+ const isBackedUp = isWalletBackedUpForCurrentAccount({ backedUp, backupFile, backupType });
+
return (
-
+
{
}
>
- {!backedUp && (
+ {!isBackedUp && (
@@ -330,37 +356,43 @@ export const WalletsAndBackup = () => {
{imported && }
1
+ addresses.length > 1
? i18n.t(i18n.l.wallet.back_ups.wallet_count_gt_one, {
- numAccounts,
+ numAccounts: addresses.length,
})
: i18n.t(i18n.l.wallet.back_ups.wallet_count, {
- numAccounts,
+ numAccounts: addresses.length,
})
}
/>
}
leftComponent={}
- onPress={() => onNavigateToWalletView(key, name)}
+ onPress={() => onNavigateToWalletView(id, name)}
size={60}
titleComponent={}
/>
- {accounts.map(account => (
-
- ))}
-
+ }
+ keyExtractor={item => item.address}
+ numColumns={3}
+ scrollEnabled={false}
+ />
}
/>
);
})}
+
{
titleComponent={}
/>
-
-
- }
- onPress={onViewCloudBackups}
- size={52}
- titleComponent={
-
- }
- />
- }
- onPress={manageCloudBackups}
- size={52}
- titleComponent={
-
- }
- />
-
);
@@ -416,12 +417,7 @@ export const WalletsAndBackup = () => {
paddingTop={{ custom: 8 }}
iconComponent={}
titleComponent={}
- statusComponent={
-
- }
+ statusComponent={}
labelComponent={
allBackedUp ? (
{
/>
-
+
-
-
+ }
+ >
+
+
+
- {sortedWallets.map(({ name, isBackedUp, accounts, key, numAccounts, backedUp, imported }) => {
+ {sortedWallets.map(({ id, name, backedUp, backupFile, backupType, imported, addresses }) => {
+ const isBackedUp = isWalletBackedUpForCurrentAccount({ backedUp, backupFile, backupType });
+
return (
-
+
{
}
>
- {!backedUp && }
+ {!isBackedUp && (
+
+ )}
{imported && }
1
+ addresses.length > 1
? i18n.t(i18n.l.wallet.back_ups.wallet_count_gt_one, {
- numAccounts,
+ numAccounts: addresses.length,
})
: i18n.t(i18n.l.wallet.back_ups.wallet_count, {
- numAccounts,
+ numAccounts: addresses.length,
})
}
/>
}
leftComponent={}
- onPress={() => onNavigateToWalletView(key, name)}
+ onPress={() => onNavigateToWalletView(id, name)}
size={60}
titleComponent={}
/>
- {accounts.map(account => (
-
- ))}
-
+ }
+ keyExtractor={item => item.address}
+ numColumns={3}
+ scrollEnabled={false}
+ />
}
/>
@@ -581,12 +588,13 @@ export const WalletsAndBackup = () => {
case WalletBackupTypes.manual: {
return (
- {sortedWallets.map(({ name, isBackedUp, accounts, key, numAccounts, backedUp, imported }) => {
+ {sortedWallets.map(({ id, name, backedUp, backupType, backupFile, imported, addresses }) => {
+ const isBackedUp = isWalletBackedUpForCurrentAccount({ backedUp, backupType, backupFile });
return (
-
+
{
}
>
- {!backedUp && }
+ {!isBackedUp && (
+
+ )}
{imported && }
1
+ addresses.length > 1
? i18n.t(i18n.l.wallet.back_ups.wallet_count_gt_one, {
- numAccounts,
+ numAccounts: addresses.length,
})
: i18n.t(i18n.l.wallet.back_ups.wallet_count, {
- numAccounts,
+ numAccounts: addresses.length,
})
}
/>
}
leftComponent={}
- onPress={() => onNavigateToWalletView(key, name)}
+ onPress={() => onNavigateToWalletView(id, name)}
size={60}
titleComponent={}
/>
- {accounts.map(account => (
-
- ))}
-
+ }
+ keyExtractor={item => item.address}
+ numColumns={3}
+ scrollEnabled={false}
+ />
}
/>
@@ -645,26 +664,29 @@ export const WalletsAndBackup = () => {
/>
-
- {i18n.t(i18n.l.wallet.back_ups.cloud_backup_description, {
- cloudPlatform,
- })}
+
+
+ {i18n.t(i18n.l.wallet.back_ups.cloud_backup_description, {
+ cloudPlatform,
+ })}
-
- {' '}
- {i18n.t(i18n.l.wallet.back_ups.cloud_backup_link)}
+
+ {' '}
+ {i18n.t(i18n.l.wallet.back_ups.cloud_backup_link)}
+
-
- }
- >
-
-
+ }
+ >
+
+
+
);
@@ -672,21 +694,29 @@ export const WalletsAndBackup = () => {
}
}, [
backupProvider,
- loading,
- backupAllNonBackedUpWalletsTocloud,
+ iconStatusType,
+ text,
+ status,
+ isCloudBackupDisabled,
+ enableCloudBackups,
sortedWallets,
onCreateNewSecretPhrase,
- onViewCloudBackups,
- manageCloudBackups,
navigate,
onNavigateToWalletView,
allBackedUp,
mostRecentBackup,
- lastBackupDate,
+ backupAllNonBackedUpWalletsTocloud,
+ onViewCloudBackups,
+ manageCloudBackups,
onPressLearnMoreAboutCloudBackups,
]);
- return {renderView()};
+ return (
+
+
+ {renderView()}
+
+ );
};
export default WalletsAndBackup;
diff --git a/src/screens/SettingsSheet/components/DevSection.tsx b/src/screens/SettingsSheet/components/DevSection.tsx
index 78bcdecae6b..f6919041c0f 100644
--- a/src/screens/SettingsSheet/components/DevSection.tsx
+++ b/src/screens/SettingsSheet/components/DevSection.tsx
@@ -33,7 +33,7 @@ import { isAuthenticated } from '@/utils/authentication';
import { getFCMToken } from '@/notifications/tokens';
import { nonceStore } from '@/state/nonces';
import { pendingTransactionsStore } from '@/state/pendingTransactions';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
import { addDefaultNotificationGroupSettings } from '@/notifications/settings/initialization';
import { unsubscribeAllNotifications } from '@/notifications/settings/settings';
@@ -41,7 +41,7 @@ const DevSection = () => {
const { navigate } = useNavigation();
const { config, setConfig } = useContext(RainbowContext) as any;
const { wallets } = useWallets();
- const setConnectedToHardhat = useConnectedToHardhatStore.getState().setConnectedToHardhat;
+ const setConnectedToAnvil = useConnectedToAnvilStore.getState().setConnectedToAnvil;
const dispatch = useDispatch();
const [loadingStates, setLoadingStates] = useState({
@@ -61,17 +61,17 @@ const DevSection = () => {
[config, setConfig]
);
- const connectToHardhat = useCallback(async () => {
+ const connectToAnvil = useCallback(async () => {
try {
- const connectToHardhat = useConnectedToHardhatStore.getState().connectedToHardhat;
- setConnectedToHardhat(!connectToHardhat);
- logger.debug(`[DevSection] connected to hardhat`);
+ const connectToAnvil = useConnectedToAnvilStore.getState().connectedToAnvil;
+ setConnectedToAnvil(!connectToAnvil);
+ logger.debug(`[DevSection] connected to anvil`);
} catch (e) {
- setConnectedToHardhat(false);
- logger.error(new RainbowError(`[DevSection] error connecting to hardhat: ${e}`));
+ setConnectedToAnvil(false);
+ logger.error(new RainbowError(`[DevSection] error connecting to anvil: ${e}`));
}
navigate(Routes.PROFILE_SCREEN);
- }, [dispatch, navigate, setConnectedToHardhat]);
+ }, [dispatch, navigate, setConnectedToAnvil]);
const checkAlert = useCallback(async () => {
try {
@@ -291,15 +291,15 @@ const DevSection = () => {
/>
}
- onPress={connectToHardhat}
+ onPress={connectToAnvil}
size={52}
- testID="hardhat-section"
+ testID="anvil-section"
titleComponent={
}
diff --git a/src/screens/SettingsSheet/components/GoogleAccountSection.tsx b/src/screens/SettingsSheet/components/GoogleAccountSection.tsx
index b415e1d4d30..10e28e6ebc6 100644
--- a/src/screens/SettingsSheet/components/GoogleAccountSection.tsx
+++ b/src/screens/SettingsSheet/components/GoogleAccountSection.tsx
@@ -3,14 +3,12 @@ import { getGoogleAccountUserData, GoogleDriveUserData, logoutFromGoogleDrive }
import ImageAvatar from '@/components/contacts/ImageAvatar';
import { showActionSheetWithOptions } from '@/utils';
import * as i18n from '@/languages';
-import { clearAllWalletsBackupStatus, updateWalletBackupStatusesBasedOnCloudUserData } from '@/redux/wallets';
-import { useDispatch } from 'react-redux';
import Menu from './Menu';
import MenuItem from './MenuItem';
import { logger, RainbowError } from '@/logger';
+import { backupsStore } from '@/state/backups/backups';
export const GoogleAccountSection: React.FC = () => {
- const dispatch = useDispatch();
const [accountDetails, setAccountDetails] = useState(undefined);
const [loading, setLoading] = useState(true);
@@ -29,12 +27,6 @@ export const GoogleAccountSection: React.FC = () => {
});
}, []);
- const removeBackupStateFromAllWallets = async () => {
- setLoading(true);
- await dispatch(clearAllWalletsBackupStatus());
- setLoading(false);
- };
-
const onGoogleAccountPress = () => {
showActionSheetWithOptions(
{
@@ -49,11 +41,10 @@ export const GoogleAccountSection: React.FC = () => {
if (buttonIndex === 0) {
logoutFromGoogleDrive();
setAccountDetails(undefined);
- removeBackupStateFromAllWallets().then(() => loginToGoogleDrive());
+ loginToGoogleDrive();
} else if (buttonIndex === 1) {
logoutFromGoogleDrive();
setAccountDetails(undefined);
- removeBackupStateFromAllWallets();
}
}
);
@@ -61,10 +52,10 @@ export const GoogleAccountSection: React.FC = () => {
const loginToGoogleDrive = async () => {
setLoading(true);
- await dispatch(updateWalletBackupStatusesBasedOnCloudUserData());
try {
const accountDetails = await getGoogleAccountUserData();
setAccountDetails(accountDetails ?? undefined);
+ backupsStore.getState().syncAndFetchBackups();
} catch (error) {
logger.error(new RainbowError(`[GoogleAccountSection]: Logging into Google Drive failed`), {
error: (error as Error).message,
diff --git a/src/screens/SettingsSheet/components/MenuContainer.tsx b/src/screens/SettingsSheet/components/MenuContainer.tsx
index 500960c55a5..cabb0157fb7 100644
--- a/src/screens/SettingsSheet/components/MenuContainer.tsx
+++ b/src/screens/SettingsSheet/components/MenuContainer.tsx
@@ -3,13 +3,14 @@ import { ScrollView } from 'react-native';
import { Box, Inset, Space, Stack } from '@/design-system';
interface MenuContainerProps {
+ scrollviewRef?: React.RefObject;
children: React.ReactNode;
Footer?: React.ReactNode;
testID?: string;
space?: Space;
}
-const MenuContainer = ({ children, testID, Footer, space = '36px' }: MenuContainerProps) => {
+const MenuContainer = ({ scrollviewRef, children, testID, Footer, space = '36px' }: MenuContainerProps) => {
return (
// ios scroll fix
(
);
-type StatusType = 'not-enabled' | 'out-of-date' | 'up-to-date';
+export type StatusType = 'not-enabled' | 'out-of-date' | 'up-to-date' | 'out-of-sync';
interface StatusIconProps {
status: StatusType;
@@ -87,6 +87,10 @@ const StatusIcon = ({ status, text }: StatusIconProps) => {
backgroundColor: isDarkMode ? colors.alpha(colors.blueGreyDark, 0.1) : colors.alpha(colors.blueGreyDark, 0.1),
color: isDarkMode ? colors.alpha(colors.blueGreyDark, 0.6) : colors.alpha(colors.blueGreyDark, 0.8),
},
+ 'out-of-sync': {
+ backgroundColor: colors.alpha(colors.yellow, 0.2),
+ color: colors.yellow,
+ },
'out-of-date': {
backgroundColor: colors.alpha(colors.brightRed, 0.2),
color: colors.brightRed,
diff --git a/src/screens/SettingsSheet/components/SettingsSection.tsx b/src/screens/SettingsSheet/components/SettingsSection.tsx
index 9fae44a89eb..095b88cbb85 100644
--- a/src/screens/SettingsSheet/components/SettingsSection.tsx
+++ b/src/screens/SettingsSheet/components/SettingsSection.tsx
@@ -28,9 +28,11 @@ import { showActionSheetWithOptions } from '@/utils';
import { handleReviewPromptAction } from '@/utils/reviewAlert';
import { ReviewPromptAction } from '@/storage/schema';
import { SettingsExternalURLs } from '../constants';
-import { capitalizeFirstLetter, checkWalletsForBackupStatus } from '../utils';
+import { checkLocalWalletsForBackupStatus } from '../utils';
import walletBackupTypes from '@/helpers/walletBackupTypes';
import { Box } from '@/design-system';
+import { capitalize } from 'lodash';
+import { backupsStore } from '@/state/backups/backups';
interface SettingsSectionProps {
onCloseModal: () => void;
@@ -59,10 +61,14 @@ const SettingsSection = ({
const isLanguageSelectionEnabled = useExperimentalFlag(LANGUAGE_SETTINGS);
const isNotificationsEnabled = useExperimentalFlag(NOTIFICATIONS);
+ const { backupProvider, backups } = backupsStore(state => ({
+ backupProvider: state.backupProvider,
+ backups: state.backups,
+ }));
+
const { isDarkMode, setTheme, colorScheme } = useTheme();
const onSendFeedback = useSendFeedback();
- const { backupProvider } = useMemo(() => checkWalletsForBackupStatus(wallets), [wallets]);
const onPressReview = useCallback(async () => {
if (ios) {
@@ -85,7 +91,7 @@ const SettingsSection = ({
const onPressLearn = useCallback(() => Linking.openURL(SettingsExternalURLs.rainbowLearn), []);
- const { allBackedUp, canBeBackedUp } = useMemo(() => checkWalletsForBackupStatus(wallets), [wallets]);
+ const { allBackedUp } = useMemo(() => checkLocalWalletsForBackupStatus(wallets, backups), [wallets, backups]);
const themeMenuConfig = useMemo(() => {
return {
@@ -170,21 +176,19 @@ const SettingsSection = ({
return (
}>
- {canBeBackedUp && (
- }
- onPress={onPressBackup}
- rightComponent={
-
-
-
- }
- size={60}
- testID={'backup-section'}
- titleComponent={}
- />
- )}
+ }
+ onPress={onPressBackup}
+ rightComponent={
+
+
+
+ }
+ size={60}
+ testID={'backup-section'}
+ titleComponent={}
+ />
{isNotificationsEnabled && (
}
- rightComponent={{colorScheme ? capitalizeFirstLetter(colorScheme) : ''}}
+ rightComponent={{colorScheme ? capitalize(colorScheme) : ''}}
size={60}
testID={`theme-section-${isDarkMode ? 'dark' : 'light'}`}
titleComponent={}
diff --git a/src/screens/SettingsSheet/useVisibleWallets.ts b/src/screens/SettingsSheet/useVisibleWallets.ts
index 64e73aa0929..c677dd738db 100644
--- a/src/screens/SettingsSheet/useVisibleWallets.ts
+++ b/src/screens/SettingsSheet/useVisibleWallets.ts
@@ -1,9 +1,7 @@
-import { useState } from 'react';
import * as i18n from '@/languages';
import WalletTypes, { EthereumWalletType } from '@/helpers/walletTypes';
-import { DEFAULT_WALLET_NAME, RainbowAccount, RainbowWallet } from '@/model/wallet';
-import walletBackupTypes from '@/helpers/walletBackupTypes';
+import { RainbowWallet } from '@/model/wallet';
type WalletByKey = {
[key: string]: RainbowWallet;
@@ -19,20 +17,6 @@ export type WalletCountPerType = {
privateKey: number;
};
-export type AmendedRainbowWallet = RainbowWallet & {
- name: string;
- isBackedUp: boolean | undefined;
- accounts: RainbowAccount[];
- key: string;
- label: string;
- numAccounts: number;
-};
-
-type UseVisibleWalletReturnType = {
- visibleWallets: AmendedRainbowWallet[];
- lastBackupDate: number | undefined;
-};
-
export const getTitleForWalletType = (type: EthereumWalletType, walletTypeCount: WalletCountPerType) => {
switch (type) {
case EthereumWalletType.mnemonic:
@@ -48,51 +32,26 @@ export const getTitleForWalletType = (type: EthereumWalletType, walletTypeCount:
}
};
-const isWalletGroupNamed = (wallet: RainbowWallet) => wallet.name && wallet.name.trim() !== '' && wallet.name !== DEFAULT_WALLET_NAME;
-
-export const useVisibleWallets = ({ wallets, walletTypeCount }: UseVisibleWalletProps): UseVisibleWalletReturnType => {
- const [lastBackupDate, setLastBackupDate] = useState(undefined);
-
+export const useVisibleWallets = ({ wallets, walletTypeCount }: UseVisibleWalletProps): RainbowWallet[] => {
if (!wallets) {
- return {
- visibleWallets: [],
- lastBackupDate,
- };
+ return [];
}
- return {
- visibleWallets: Object.keys(wallets)
- .filter(key => wallets[key].type !== WalletTypes.readOnly && wallets[key].type !== WalletTypes.bluetooth)
- .map(key => {
- const wallet = wallets[key];
- const visibleAccounts = (wallet.addresses || []).filter(a => a.visible);
- const totalAccounts = visibleAccounts.length;
-
- if (
- wallet.backedUp &&
- wallet.backupDate &&
- wallet.backupType === walletBackupTypes.cloud &&
- (!lastBackupDate || Number(wallet.backupDate) > lastBackupDate)
- ) {
- setLastBackupDate(Number(wallet.backupDate));
- }
-
- if (wallet.type === WalletTypes.mnemonic) {
- walletTypeCount.phrase += 1;
- } else if (wallet.type === WalletTypes.privateKey) {
- walletTypeCount.privateKey += 1;
- }
-
- return {
- ...wallet,
- name: isWalletGroupNamed(wallet) ? wallet.name : getTitleForWalletType(wallet.type, walletTypeCount),
- isBackedUp: wallet.backedUp,
- accounts: visibleAccounts,
- key,
- label: wallet.name,
- numAccounts: totalAccounts,
- };
- }),
- lastBackupDate,
- };
+ return Object.keys(wallets)
+ .filter(key => wallets[key].type !== WalletTypes.readOnly && wallets[key].type !== WalletTypes.bluetooth)
+ .map(key => {
+ const wallet = wallets[key];
+
+ if (wallet.type === WalletTypes.mnemonic) {
+ walletTypeCount.phrase += 1;
+ } else if (wallet.type === WalletTypes.privateKey) {
+ walletTypeCount.privateKey += 1;
+ }
+
+ return {
+ ...wallet,
+ name: getTitleForWalletType(wallet.type, walletTypeCount),
+ addresses: Object.values(wallet.addresses).filter(address => address.visible),
+ };
+ });
};
diff --git a/src/screens/SettingsSheet/utils.ts b/src/screens/SettingsSheet/utils.ts
index 08fa3e03e22..cda0da5ef72 100644
--- a/src/screens/SettingsSheet/utils.ts
+++ b/src/screens/SettingsSheet/utils.ts
@@ -1,118 +1,121 @@
import WalletBackupTypes from '@/helpers/walletBackupTypes';
import WalletTypes from '@/helpers/walletTypes';
+import { useWallets } from '@/hooks';
+import { isEmpty } from 'lodash';
+import { BackupFile, CloudBackups, parseTimestampFromFilename } from '@/model/backup';
+import * as i18n from '@/languages';
+import { cloudPlatform } from '@/utils/platform';
+import { backupsStore, CloudBackupState } from '@/state/backups/backups';
import { RainbowWallet } from '@/model/wallet';
-import { Navigation } from '@/navigation';
-import { BackupUserData, getLocalBackupPassword } from '@/model/backup';
-import Routes from '@/navigation/routesNames';
-import WalletBackupStepTypes from '@/helpers/walletBackupStepTypes';
-
-type WalletsByKey = {
- [key: string]: RainbowWallet;
-};
+import { IS_ANDROID, IS_IOS } from '@/env';
+import { normalizeAndroidBackupFilename } from '@/handlers/cloudBackup';
type WalletBackupStatus = {
allBackedUp: boolean;
areBackedUp: boolean;
canBeBackedUp: boolean;
- backupProvider: string | undefined;
};
-export const capitalizeFirstLetter = (str: string) => {
- return str.charAt(0).toUpperCase() + str.slice(1);
+export const hasManuallyBackedUpWallet = (wallets: ReturnType['wallets']) => {
+ if (!wallets) return false;
+ return Object.values(wallets).some(wallet => wallet.backupType === WalletBackupTypes.manual);
};
-export const checkUserDataForBackupProvider = (userData?: BackupUserData): { backupProvider: string | undefined } => {
- let backupProvider: string | undefined = undefined;
-
- if (!userData?.wallets) return { backupProvider };
-
- Object.values(userData.wallets).forEach(wallet => {
- if (wallet.backedUp && wallet.type !== WalletTypes.readOnly) {
- if (wallet.backupType === WalletBackupTypes.cloud) {
- backupProvider = WalletBackupTypes.cloud;
- } else if (backupProvider !== WalletBackupTypes.cloud && wallet.backupType === WalletBackupTypes.manual) {
- backupProvider = WalletBackupTypes.manual;
- }
- }
- });
-
- return { backupProvider };
-};
-
-export const checkWalletsForBackupStatus = (wallets: WalletsByKey | null): WalletBackupStatus => {
- if (!wallets)
+export const checkLocalWalletsForBackupStatus = (
+ wallets: ReturnType['wallets'],
+ backups: CloudBackups
+): WalletBackupStatus => {
+ if (!wallets || isEmpty(wallets)) {
return {
allBackedUp: false,
areBackedUp: false,
canBeBackedUp: false,
- backupProvider: undefined,
};
+ }
+
+ // FOR ANDROID, we need to check if the current google account also has the backup file
+ if (IS_ANDROID) {
+ return Object.values(wallets).reduce(
+ (acc, wallet) => {
+ const isBackupEligible = wallet.type !== WalletTypes.readOnly && wallet.type !== WalletTypes.bluetooth;
+ const hasBackupFile = backups.files.some(
+ file => normalizeAndroidBackupFilename(file.name) === normalizeAndroidBackupFilename(wallet.backupFile ?? '')
+ );
+
+ return {
+ allBackedUp: acc.allBackedUp && hasBackupFile && (wallet.backedUp || !isBackupEligible),
+ areBackedUp: acc.areBackedUp && hasBackupFile && (wallet.backedUp || !isBackupEligible),
+ canBeBackedUp: acc.canBeBackedUp && isBackupEligible,
+ };
+ },
+ { allBackedUp: true, areBackedUp: true, canBeBackedUp: false }
+ );
+ }
+
+ return Object.values(wallets).reduce(
+ (acc, wallet) => {
+ const isBackupEligible = wallet.type !== WalletTypes.readOnly && wallet.type !== WalletTypes.bluetooth;
+
+ return {
+ allBackedUp: acc.allBackedUp && (wallet.backedUp || !isBackupEligible),
+ areBackedUp: acc.areBackedUp && (wallet.backedUp || !isBackupEligible || wallet.imported),
+ canBeBackedUp: acc.canBeBackedUp && isBackupEligible,
+ };
+ },
+ { allBackedUp: true, areBackedUp: true, canBeBackedUp: false }
+ );
+};
- let backupProvider: string | undefined = undefined;
- let areBackedUp = true;
- let canBeBackedUp = false;
- let allBackedUp = true;
-
- Object.keys(wallets).forEach(key => {
- if (wallets[key].backedUp && wallets[key].type !== WalletTypes.readOnly && wallets[key].type !== WalletTypes.bluetooth) {
- if (wallets[key].backupType === WalletBackupTypes.cloud) {
- backupProvider = WalletBackupTypes.cloud;
- } else if (backupProvider !== WalletBackupTypes.cloud && wallets[key].backupType === WalletBackupTypes.manual) {
- backupProvider = WalletBackupTypes.manual;
- }
- }
+export const getMostRecentCloudBackup = (backups: BackupFile[]) => {
+ const cloudBackups = backups.sort((a, b) => {
+ return parseTimestampFromFilename(b.name) - parseTimestampFromFilename(a.name);
+ });
- if (!wallets[key].backedUp && wallets[key].type !== WalletTypes.readOnly && wallets[key].type !== WalletTypes.bluetooth) {
- allBackedUp = false;
+ return cloudBackups.reduce((prev, current) => {
+ if (!current) {
+ return prev;
}
- if (
- !wallets[key].backedUp &&
- wallets[key].type !== WalletTypes.readOnly &&
- wallets[key].type !== WalletTypes.bluetooth &&
- !wallets[key].imported
- ) {
- areBackedUp = false;
+ if (!prev) {
+ return current;
}
- if (wallets[key].type !== WalletTypes.bluetooth && wallets[key].type !== WalletTypes.readOnly) {
- canBeBackedUp = true;
+ const prevTimestamp = new Date(prev.lastModified).getTime();
+ const currentTimestamp = new Date(current.lastModified).getTime();
+ if (currentTimestamp > prevTimestamp) {
+ return current;
}
- });
- return {
- allBackedUp,
- areBackedUp,
- canBeBackedUp,
- backupProvider,
- };
+
+ return prev;
+ }, cloudBackups[0]);
};
-export const getWalletsThatNeedBackedUp = (wallets: { [key: string]: RainbowWallet } | null): RainbowWallet[] => {
- if (!wallets) return [];
- const walletsToBackup: RainbowWallet[] = [];
- Object.keys(wallets).forEach(key => {
- if (
- !wallets[key].backedUp &&
- wallets[key].type !== WalletTypes.readOnly &&
- wallets[key].type !== WalletTypes.bluetooth &&
- !wallets[key].imported
- ) {
- walletsToBackup.push(wallets[key]);
- }
- });
- return walletsToBackup;
+export const titleForBackupState: Partial> = {
+ [CloudBackupState.Initializing]: i18n.t(i18n.l.back_up.cloud.syncing_cloud_store, {
+ cloudPlatformName: cloudPlatform,
+ }),
+ [CloudBackupState.Syncing]: i18n.t(i18n.l.back_up.cloud.syncing_cloud_store, {
+ cloudPlatformName: cloudPlatform,
+ }),
+ [CloudBackupState.Fetching]: i18n.t(i18n.l.back_up.cloud.fetching_backups, {
+ cloudPlatformName: cloudPlatform,
+ }),
};
-export const fetchBackupPasswordAndNavigate = async () => {
- const password = await getLocalBackupPassword();
+export const isWalletBackedUpForCurrentAccount = ({ backupType, backedUp, backupFile }: Partial) => {
+ if (IS_IOS || backupType === WalletBackupTypes.manual) {
+ return backedUp;
+ }
- return new Promise(resolve => {
- return Navigation.handleAction(Routes.BACKUP_SHEET, {
- step: WalletBackupStepTypes.backup_cloud,
- password,
- onSuccess: async (password: string) => {
- resolve(password);
- },
- });
- });
+ if (!backupType || !backupFile) {
+ return false;
+ }
+
+ // NOTE: For Android, we also need to check if the current google account has the matching backup file
+ if (!backupFile) {
+ return false;
+ }
+
+ const backupFiles = backupsStore.getState().backups;
+ return backupFiles.files.some(file => normalizeAndroidBackupFilename(file.name) === normalizeAndroidBackupFilename(backupFile));
};
diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/WalletScreen/index.tsx
index acfcbcfb176..95cf97c05db 100644
--- a/src/screens/WalletScreen/index.tsx
+++ b/src/screens/WalletScreen/index.tsx
@@ -25,11 +25,11 @@ import { RemoteCardsSync } from '@/state/sync/RemoteCardsSync';
import { RemotePromoSheetSync } from '@/state/sync/RemotePromoSheetSync';
import { UserAssetsSync } from '@/state/sync/UserAssetsSync';
import { MobileWalletProtocolListener } from '@/components/MobileWalletProtocolListener';
-import { runWalletBackupStatusChecks } from '@/handlers/walletReadyEvents';
import { RouteProp, useRoute } from '@react-navigation/native';
import { RootStackParamList } from '@/navigation/types';
import { useNavigation } from '@/navigation';
import Routes from '@/navigation/Routes';
+import { BackendNetworks } from '@/components/BackendNetworks';
import walletTypes from '@/helpers/walletTypes';
enum WalletLoadingStates {
@@ -45,14 +45,18 @@ function WalletScreen() {
const walletState = useRef(WalletLoadingStates.IDLE);
const initializeWallet = useInitializeWallet();
const { network: currentNetwork, accountAddress, appIcon } = useAccountSettings();
-
const loadAccountLateData = useLoadAccountLateData();
const loadGlobalLateData = useLoadGlobalLateData();
const insets = useSafeAreaInsets();
const { wallets } = useWallets();
const walletReady = useSelector(({ appState: { walletReady } }: AppState) => walletReady);
- const { isWalletEthZero, isLoadingUserAssets, isLoadingBalance, briefSectionsData: walletBriefSectionsData } = useWalletSectionsData();
+ const {
+ isWalletEthZero,
+ isLoadingUserAssets,
+ isLoadingBalance,
+ briefSectionsData: walletBriefSectionsData,
+ } = useWalletSectionsData({ type: 'wallet' });
useEffect(() => {
if (!wallets) return;
@@ -149,7 +153,6 @@ function WalletScreen() {
if (walletReady) {
loadAccountLateData();
loadGlobalLateData();
- runWalletBackupStatusChecks();
}
}, [loadAccountLateData, loadGlobalLateData, walletReady]);
@@ -185,6 +188,7 @@ function WalletScreen() {
+
{/* NOTE: This component listens for Mobile Wallet Protocol requests and handles them */}
diff --git a/src/screens/claimables/transaction/context/TransactionClaimableContext.tsx b/src/screens/claimables/transaction/context/TransactionClaimableContext.tsx
index 63c1ce771be..d25448ea23e 100644
--- a/src/screens/claimables/transaction/context/TransactionClaimableContext.tsx
+++ b/src/screens/claimables/transaction/context/TransactionClaimableContext.tsx
@@ -15,7 +15,6 @@ import {
convertAmountToNativeDisplayWorklet,
add,
} from '@/helpers/utilities';
-import { useUserNativeNetworkAsset } from '@/resources/assets/useUserAsset';
import { GasSpeed } from '@/__swaps__/types/gas';
import { getGasSettingsBySpeed, useGasSettings } from '@/__swaps__/screens/Swap/hooks/useSelectedGas';
import { LegacyTransactionGasParamAmounts, TransactionGasParamAmounts } from '@/entities';
@@ -41,6 +40,7 @@ import { getRemoteConfig } from '@/model/remoteConfig';
import { estimateClaimUnlockSwapGasLimit } from '../estimateGas';
import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
import showWalletErrorAlert from '@/helpers/support';
+import { userAssetsStore } from '@/state/assets/userAssets';
enum ErrorMessages {
SWAP_ERROR = 'Failed to swap claimed asset due to swap action error',
@@ -141,8 +141,6 @@ export function TransactionClaimableContextProvider({
const gasSettings = useGasSettings(claimable.chainId, GasSpeed.FAST);
- const { data: userNativeNetworkAsset, isLoading: isLoadingNativeNetworkAsset } = useUserNativeNetworkAsset(claimable.chainId);
-
const updateQuoteState = useCallback(async () => {
if (!outputConfig?.token || !outputConfig.chainId || !outputTokenAddress) {
logger.warn('[TransactionClaimableContext]: Somehow entered unreachable state in updateQuote');
@@ -220,11 +218,7 @@ export function TransactionClaimableContextProvider({
const provider = useMemo(() => getProvider({ chainId: claimable.chainId }), [claimable.chainId]);
// make sure we have necessary data before attempting gas estimation
- const canEstimateGas = !!(
- !isLoadingNativeNetworkAsset &&
- gasSettings &&
- (!requiresSwap || (quoteState.quote && quoteState.status === 'success'))
- );
+ const canEstimateGas = !!(gasSettings && (!requiresSwap || (quoteState.quote && quoteState.status === 'success')));
const updateGasState = useCallback(async () => {
try {
@@ -252,12 +246,13 @@ export function TransactionClaimableContextProvider({
const gasFeeWei = calculateGasFeeWorklet(gasSettings, gasLimit);
const nativeAsset = useBackendNetworksStore.getState().getChainsNativeAsset()[claimable.chainId];
+ const userNativeAsset = userAssetsStore.getState().getNativeAssetForChain(claimable.chainId);
const gasFeeNativeToken = formatUnits(safeBigInt(gasFeeWei), nativeAsset.decimals);
- const userBalance = userNativeNetworkAsset?.balance?.amount || '0';
+ const userBalance = userNativeAsset?.balance?.amount || '0';
const sufficientGas = lessThanOrEqualToWorklet(gasFeeNativeToken, userBalance);
- const networkAssetPrice = userNativeNetworkAsset?.price?.value?.toString();
+ const networkAssetPrice = userNativeAsset?.price?.value?.toString();
let gasFeeNativeCurrencyDisplay;
if (!networkAssetPrice) {
@@ -290,8 +285,6 @@ export function TransactionClaimableContextProvider({
accountAddress,
quoteState.quote,
gasSettings,
- userNativeNetworkAsset?.balance?.amount,
- userNativeNetworkAsset?.price?.value,
gasState.status,
nativeCurrency,
]);
diff --git a/src/screens/rewards/components/RewardsEarnings.tsx b/src/screens/rewards/components/RewardsEarnings.tsx
index 48e70481cec..b351d4be895 100644
--- a/src/screens/rewards/components/RewardsEarnings.tsx
+++ b/src/screens/rewards/components/RewardsEarnings.tsx
@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
-import { Image } from 'react-native';
+import { Image, ImageBackground } from 'react-native';
import { RewardsSectionCard } from '@/screens/rewards/components/RewardsSectionCard';
import { AccentColorProvider, Box, Columns, Inline, Stack, Text } from '@/design-system';
import * as i18n from '@/languages';
@@ -63,7 +63,7 @@ export const RewardsEarnings: React.FC = ({
airdropTitle,
airdropTime,
};
- }, [pendingEarningsToken, tokenSymbol, totalEarnings.token, totalEarnings.usd, nextAirdropTimestamp]);
+ }, [pendingEarningsToken, tokenSymbol, totalEarnings.token, totalEarnings.usd, assetPrice, nativeCurrency, nextAirdropTimestamp]);
const navigateToTimingExplainer = () => {
analyticsV2.track(analyticsV2.event.rewardsPressedPendingEarningsCard);
@@ -85,7 +85,7 @@ export const RewardsEarnings: React.FC = ({
= ({
,
+ | 'currentAbortController'
+ | 'inputSearchQuery'
+ | 'searchCache'
+ | 'getBalanceSortedChainList'
+ | 'getChainsWithBalance'
+ | 'getFilteredUserAssetIds'
+ | 'getHighestValueNativeAsset'
+ | 'getUserAsset'
+ | 'getUserAssets'
+ | 'selectUserAssetIds'
+ | 'selectUserAssets'
+ | 'setSearchCache'
+ | 'setSearchQuery'
+ | 'setUserAssets'
+>;
const SEARCH_CACHE_MAX_ENTRIES = 50;
+const parsedSearchAssetToParsedAddressAsset = (asset: ParsedSearchAsset): ParsedAddressAsset => ({
+ address: asset.address,
+ balance: {
+ amount: asset.balance.amount,
+ display: asset.balance.display,
+ },
+ network: useBackendNetworksStore.getState().getChainsName()[asset.chainId],
+ name: asset.name,
+ chainId: asset.chainId,
+ color: asset.colors?.primary ?? asset.colors?.fallback,
+ colors: asset.colors?.primary
+ ? {
+ primary: asset.colors.primary,
+ fallback: asset.colors.fallback,
+ shadow: asset.colors.shadow,
+ }
+ : undefined,
+ decimals: asset.decimals,
+ highLiquidity: asset.highLiquidity,
+ icon_url: asset.icon_url,
+ id: asset.networks?.[ChainId.mainnet]?.address,
+ isNativeAsset: asset.isNativeAsset,
+ price: {
+ changed_at: undefined,
+ relative_change_24h: asset.price?.relative_change_24h,
+ value: asset.price?.value,
+ },
+ mainnet_address: asset.mainnetAddress,
+ native: {
+ balance: {
+ amount: asset.native.balance.amount,
+ display: asset.native.balance.display,
+ },
+ change: asset.native.price?.change,
+ price: {
+ amount: asset.native.price?.amount?.toString(),
+ display: asset.native.price?.display,
+ },
+ },
+ shadowColor: asset.colors?.shadow,
+ symbol: asset.symbol,
+ type: asset.type,
+ uniqueId: asset.uniqueId,
+});
+
const escapeRegExp = (string: string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+
const getSearchQueryKey = ({ filter, searchQuery }: { filter: UserAssetFilter; searchQuery: string }) => `${filter}${searchQuery}`;
const getDefaultCacheKeys = (): Set => {
@@ -35,41 +101,27 @@ export interface UserAssetsState {
inputSearchQuery: string;
searchCache: Map;
userAssets: Map;
+ legacyUserAssets: ParsedAddressAsset[];
+ isLoadingUserAssets: boolean;
getBalanceSortedChainList: () => ChainId[];
getChainsWithBalance: () => ChainId[];
getFilteredUserAssetIds: () => UniqueId[];
getHighestValueNativeAsset: () => ParsedSearchAsset | null;
getUserAsset: (uniqueId: UniqueId) => ParsedSearchAsset | null;
+ getLegacyUserAsset: (uniqueId: UniqueId) => ParsedAddressAsset | null;
+ getNativeAssetForChain: (chainId: ChainId) => ParsedSearchAsset | null;
getUserAssets: () => ParsedSearchAsset[];
selectUserAssetIds: (selector: (asset: ParsedSearchAsset) => boolean, filter?: UserAssetFilter) => Generator;
selectUserAssets: (selector: (asset: ParsedSearchAsset) => boolean) => Generator<[UniqueId, ParsedSearchAsset], void, unknown>;
setSearchCache: (queryKey: string, filteredIds: UniqueId[]) => void;
setSearchQuery: (query: string) => void;
- setUserAssets: (userAssets: Map | ParsedSearchAsset[]) => void;
+ setUserAssets: (userAssets: ParsedSearchAsset[]) => void;
hiddenAssets: Set;
getHiddenAssetsIds: () => UniqueId[];
setHiddenAssets: (uniqueIds: UniqueId[]) => void;
}
-type UserAssetsStateToPersist = Omit<
- Partial,
- | 'currentAbortController'
- | 'inputSearchQuery'
- | 'searchCache'
- | 'getBalanceSortedChainList'
- | 'getChainsWithBalance'
- | 'getFilteredUserAssetIds'
- | 'getHighestValueNativeAsset'
- | 'getUserAsset'
- | 'getUserAssets'
- | 'selectUserAssetIds'
- | 'selectUserAssets'
- | 'setSearchCache'
- | 'setSearchQuery'
- | 'setUserAssets'
->;
-
// NOTE: We are serializing Map as an Array<[UniqueId, ParsedSearchAsset]>
type UserAssetsStateToPersistWithTransforms = Omit<
UserAssetsStateToPersist,
@@ -170,6 +222,8 @@ export const createUserAssetsStore = (address: Address | string) =>
inputSearchQuery: '',
searchCache: new Map(),
userAssets: new Map(),
+ legacyUserAssets: [],
+ isLoadingUserAssets: true,
getBalanceSortedChainList: () => {
const chainBalances = [...get().chainBalances.entries()];
@@ -223,7 +277,7 @@ export const createUserAssetsStore = (address: Address | string) =>
const preferredNetwork = swapsStore.getState().preferredNetwork;
const assets = get().userAssets;
- let highestValueEth = null;
+ let highestValueNativeAsset = null;
for (const [, asset] of assets) {
if (!asset.isNativeAsset) continue;
@@ -232,16 +286,28 @@ export const createUserAssetsStore = (address: Address | string) =>
return asset;
}
- if (!highestValueEth || asset.balance > highestValueEth.balance) {
- highestValueEth = asset;
+ if (!highestValueNativeAsset || asset.balance > highestValueNativeAsset.balance) {
+ highestValueNativeAsset = asset;
}
}
- return highestValueEth;
+ return highestValueNativeAsset;
+ },
+
+ getNativeAssetForChain: (chainId: ChainId) => {
+ const nativeAssetAddress = useBackendNetworksStore.getState().getChainsNativeAsset()[chainId].address;
+ const nativeAssetUniqueId = getUniqueId(nativeAssetAddress, chainId);
+ return get().userAssets.get(nativeAssetUniqueId) || null;
},
getUserAsset: (uniqueId: UniqueId) => get().userAssets.get(uniqueId) || null,
+ getLegacyUserAsset: (uniqueId: UniqueId) => {
+ const asset = get().userAssets.get(uniqueId);
+ if (!asset) return null;
+ return parsedSearchAssetToParsedAddressAsset(asset);
+ },
+
getUserAssets: () => Array.from(get().userAssets.values()) || [],
selectUserAssetIds: function* (selector: (asset: ParsedSearchAsset) => boolean, filter?: UserAssetFilter) {
@@ -305,7 +371,7 @@ export const createUserAssetsStore = (address: Address | string) =>
});
},
- setUserAssets: (userAssets: Map | ParsedSearchAsset[]) =>
+ setUserAssets: (userAssets: ParsedSearchAsset[]) =>
set(() => {
const idsByChain = new Map();
const unsortedChainBalances = new Map();
@@ -336,9 +402,9 @@ export const createUserAssetsStore = (address: Address | string) =>
idsByChain.set(chainId, idsByChain.get(chainId) || []);
});
- const isMap = userAssets instanceof Map;
- const allIdsArray = isMap ? Array.from(userAssets.keys()) : userAssets.map(asset => asset.uniqueId);
- const userAssetsMap = isMap ? userAssets : new Map(userAssets.map(asset => [asset.uniqueId, asset]));
+ const allIdsArray = userAssets.map(asset => asset.uniqueId);
+ const userAssetsMap = new Map(userAssets.map(asset => [asset.uniqueId, asset]));
+ const legacyUserAssets = userAssets.map(asset => parsedSearchAssetToParsedAddressAsset(asset));
idsByChain.set('all', allIdsArray);
@@ -358,15 +424,14 @@ export const createUserAssetsStore = (address: Address | string) =>
searchCache.set('all', filteredAllIdsArray);
- if (isMap) {
- return { chainBalances, idsByChain, searchCache, userAssets };
- } else
- return {
- chainBalances,
- idsByChain,
- searchCache,
- userAssets: userAssetsMap,
- };
+ return {
+ chainBalances,
+ idsByChain,
+ legacyUserAssets,
+ searchCache,
+ userAssets: userAssetsMap,
+ isLoadingUserAssets: false,
+ };
}),
hiddenAssets: new Set(),
@@ -383,7 +448,6 @@ export const createUserAssetsStore = (address: Address | string) =>
hiddenAssets.add(uniqueId);
}
});
-
return { hiddenAssets };
});
},
@@ -395,6 +459,7 @@ export const createUserAssetsStore = (address: Address | string) =>
filter: state.filter,
idsByChain: state.idsByChain,
userAssets: state.userAssets,
+ legacyUserAssets: state.legacyUserAssets,
hiddenAssets: state.hiddenAssets,
}),
version: 1,
diff --git a/src/state/backendNetworks/backendNetworks.ts b/src/state/backendNetworks/backendNetworks.ts
index 274688889a7..b06d46c2e0e 100644
--- a/src/state/backendNetworks/backendNetworks.ts
+++ b/src/state/backendNetworks/backendNetworks.ts
@@ -6,9 +6,10 @@ import { createRainbowStore } from '@/state/internal/createRainbowStore';
import { Chain } from 'viem/chains';
import { transformBackendNetworksToChains } from '@/state/backendNetworks/utils';
import { IS_TEST } from '@/env';
-import { BackendNetwork, BackendNetworkServices, chainHardhat, chainHardhatOptimism, ChainId } from '@/state/backendNetworks/types';
+import { BackendNetwork, BackendNetworkServices, chainAnvil, chainAnvilOptimism, ChainId } from '@/state/backendNetworks/types';
import { GasSpeed } from '@/__swaps__/types/gas';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { useConnectedToAnvilStore } from '@/state/connectedToAnvil';
+import { colors as globalColors } from '@/styles';
const INITIAL_BACKEND_NETWORKS = queryClient.getQueryData(backendNetworksQueryKey()) ?? buildTimeNetworks;
const DEFAULT_PRIVATE_MEMPOOL_TIMEOUT = 2 * 60 * 1_000; // 2 minutes
@@ -19,6 +20,7 @@ export interface BackendNetworksState {
getBackendChains: () => Chain[];
getSupportedChains: () => Chain[];
+ getSortedSupportedChainIds: () => number[];
getDefaultChains: () => Record;
getSupportedChainIds: () => ChainId[];
@@ -32,6 +34,8 @@ export interface BackendNetworksState {
getChainsBadge: () => Record;
getChainsIdByName: () => Record;
+ getColorsForChainId: (chainId: ChainId, isDarkMode: boolean) => string;
+
defaultGasSpeeds: (chainId: ChainId) => GasSpeed[];
getChainsGasSpeeds: () => Record;
@@ -73,7 +77,12 @@ export const useBackendNetworksStore = createRainbowStore(
getSupportedChains: () => {
const backendChains = get().getBackendChains();
- return IS_TEST ? [...backendChains, chainHardhat, chainHardhatOptimism] : backendChains;
+ return IS_TEST ? [...backendChains, chainAnvil, chainAnvilOptimism] : backendChains;
+ },
+
+ getSortedSupportedChainIds: () => {
+ const supportedChains = get().getSupportedChains();
+ return supportedChains.sort((a, b) => a.name.localeCompare(b.name)).map(c => c.id);
},
getDefaultChains: () => {
@@ -175,6 +184,17 @@ export const useBackendNetworksStore = createRainbowStore(
);
},
+ getColorsForChainId: (chainId: ChainId, isDarkMode: boolean) => {
+ const { backendNetworks } = get();
+
+ const colors = backendNetworks.networks.find(chain => +chain.id === chainId)?.colors;
+ if (!colors) {
+ return isDarkMode ? globalColors.white : globalColors.black;
+ }
+
+ return isDarkMode ? colors.dark : colors.light;
+ },
+
// TODO: This should come from the backend at some point
defaultGasSpeeds: chainId => {
switch (chainId) {
@@ -355,8 +375,8 @@ export const useBackendNetworksStore = createRainbowStore(
const defaultChains = get().getDefaultChains();
switch (chainId) {
case ChainId.mainnet:
- return useConnectedToHardhatStore.getState().connectedToHardhat
- ? chainHardhat.rpcUrls.default.http[0]
+ return useConnectedToAnvilStore.getState().connectedToAnvil
+ ? chainAnvil.rpcUrls.default.http[0]
: defaultChains[ChainId.mainnet].rpcUrls.default.http[0];
default:
return defaultChains[chainId].rpcUrls.default.http[0];
@@ -383,7 +403,7 @@ export const getBackendChainsWorklet = (backendNetworks: SharedValue) => {
'worklet';
const backendChains = getBackendChainsWorklet(backendNetworks);
- return IS_TEST ? [...backendChains, chainHardhat, chainHardhatOptimism] : backendChains;
+ return IS_TEST ? [...backendChains, chainAnvil, chainAnvilOptimism] : backendChains;
};
export const getDefaultChainsWorklet = (backendNetworks: SharedValue) => {
@@ -663,7 +683,7 @@ export const getChainDefaultRpcWorklet = (backendNetworks: SharedValue void;
+
+ backupProvider: string | undefined;
+ setBackupProvider: (backupProvider: string | undefined) => void;
+
+ status: CloudBackupState;
+ setStatus: (status: CloudBackupState) => void;
+
+ backups: CloudBackups;
+ setBackups: (backups: CloudBackups) => void;
+
+ mostRecentBackup: BackupFile | undefined;
+ setMostRecentBackup: (backup: BackupFile | undefined) => void;
+
+ password: string;
+ setPassword: (password: string) => void;
+
+ syncAndFetchBackups: (
+ retryOnFailure?: boolean,
+ retryCount?: number
+ ) => Promise<{
+ success: boolean;
+ retry?: boolean;
+ }>;
+}
+
+const returnEarlyIfLockedStates = [CloudBackupState.Syncing, CloudBackupState.Fetching];
+
+export const backupsStore = createRainbowStore((set, get) => ({
+ storedPassword: '',
+ setStoredPassword: storedPassword => set({ storedPassword }),
+
+ backupProvider: undefined,
+ setBackupProvider: provider => set({ backupProvider: provider }),
+
+ status: CloudBackupState.Initializing,
+ setStatus: status => set({ status }),
+
+ backups: { files: [] },
+ setBackups: backups => set({ backups }),
+
+ mostRecentBackup: undefined,
+ setMostRecentBackup: backup => set({ mostRecentBackup: backup }),
+
+ password: '',
+ setPassword: password => set({ password }),
+
+ syncAndFetchBackups: async (retryOnFailure = true, retryCount = 0) => {
+ const { status } = get();
+
+ const timeoutPromise = new Promise<{ success: boolean; retry?: boolean }>(resolve => {
+ setTimeout(() => {
+ resolve({ success: false, retry: retryOnFailure });
+ }, DEFAULT_TIMEOUT);
+ });
+
+ const syncAndPullFiles = async (): Promise<{ success: boolean; retry?: boolean }> => {
+ try {
+ const isAvailable = await isCloudBackupAvailable();
+ if (!isAvailable) {
+ logger.debug('[backupsStore]: Cloud backup is not available');
+ set({ backupProvider: undefined, status: CloudBackupState.NotAvailable, backups: { files: [] }, mostRecentBackup: undefined });
+ return {
+ success: false,
+ retry: false,
+ };
+ }
+
+ if (IS_ANDROID) {
+ const gdata = await getGoogleAccountUserData();
+ if (!gdata) {
+ logger.debug('[backupsStore]: Google account is not available');
+ set({ backupProvider: undefined, status: CloudBackupState.NotAvailable, backups: { files: [] }, mostRecentBackup: undefined });
+ return {
+ success: false,
+ retry: false,
+ };
+ }
+ }
+
+ set({ status: CloudBackupState.Syncing });
+ logger.debug('[backupsStore]: Syncing with cloud');
+ await syncCloud();
+
+ set({ status: CloudBackupState.Fetching });
+ logger.debug('[backupsStore]: Fetching backups');
+ const backups = await fetchAllBackups();
+
+ set({ backups });
+
+ const { wallets } = store.getState().wallets;
+
+ // if the user has any cloud backups, set the provider to cloud
+ if (backups.files.length > 0) {
+ set({
+ backupProvider: walletBackupTypes.cloud,
+ mostRecentBackup: getMostRecentCloudBackup(backups.files),
+ });
+ } else if (hasManuallyBackedUpWallet(wallets)) {
+ set({ backupProvider: walletBackupTypes.manual });
+ } else {
+ set({ backupProvider: undefined });
+ }
+
+ logger.debug(`[backupsStore]: Retrieved ${backups.files.length} backup files`);
+
+ set({ status: CloudBackupState.Ready });
+ return {
+ success: true,
+ retry: false,
+ };
+ } catch (e) {
+ logger.error(new RainbowError('[backupsStore]: Failed to fetch all backups'), {
+ error: e,
+ });
+ set({ status: CloudBackupState.FailedToInitialize });
+ }
+
+ return {
+ success: false,
+ retry: retryOnFailure,
+ };
+ };
+
+ if (mutex.isLocked() || returnEarlyIfLockedStates.includes(status)) {
+ logger.debug('[backupsStore]: Mutex is locked or returnEarlyIfLockedStates includes status', {
+ status,
+ });
+ return {
+ success: false,
+ retry: false,
+ };
+ }
+
+ const releaser = await mutex.acquire();
+ logger.debug('[backupsStore]: Acquired mutex');
+ const { success, retry } = await Promise.race([syncAndPullFiles(), timeoutPromise]);
+ releaser();
+ logger.debug('[backupsStore]: Released mutex');
+ if (retry && retryCount < MAX_RETRIES) {
+ logger.debug(`[backupsStore]: Retrying sync and fetch backups attempt: ${retryCount + 1}`);
+ return get().syncAndFetchBackups(retryOnFailure, retryCount + 1);
+ }
+
+ if (retry && retryCount >= MAX_RETRIES) {
+ logger.error(new RainbowError('[backupsStore]: Max retry attempts reached. Sync failed.'));
+ }
+
+ return { success, retry };
+ },
+}));
diff --git a/src/state/connectedToAnvil/index.ts b/src/state/connectedToAnvil/index.ts
new file mode 100644
index 00000000000..cefbc2526d8
--- /dev/null
+++ b/src/state/connectedToAnvil/index.ts
@@ -0,0 +1,27 @@
+import { createRainbowStore } from '../internal/createRainbowStore';
+
+export interface ConnectedToAnvilState {
+ connectedToAnvil: boolean;
+ setConnectedToAnvil: (connectedToAnvil: boolean) => void;
+
+ connectedToAnvilOp: boolean;
+ setConnectedToAnvilOp: (connectedToAnvilOp: boolean) => void;
+}
+
+export const useConnectedToAnvilStore = createRainbowStore(
+ set => ({
+ connectedToAnvil: false,
+ setConnectedToAnvil: connectedToAnvil => {
+ set({ connectedToAnvil });
+ },
+
+ connectedToAnvilOp: false,
+ setConnectedToAnvilOp: connectedToAnvilOp => {
+ set({ connectedToAnvilOp });
+ },
+ }),
+ {
+ storageKey: 'connectedToAnvil',
+ version: 0,
+ }
+);
diff --git a/src/state/connectedToHardhat/index.ts b/src/state/connectedToHardhat/index.ts
deleted file mode 100644
index 362d70e577e..00000000000
--- a/src/state/connectedToHardhat/index.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { createRainbowStore } from '../internal/createRainbowStore';
-
-export interface ConnectedToHardhatState {
- connectedToHardhat: boolean;
- setConnectedToHardhat: (connectedToHardhat: boolean) => void;
-
- connectedToHardhatOp: boolean;
- setConnectedToHardhatOp: (connectedToHardhatOp: boolean) => void;
-}
-
-export const useConnectedToHardhatStore = createRainbowStore(
- set => ({
- connectedToHardhat: false,
- setConnectedToHardhat: connectedToHardhat => {
- set({ connectedToHardhat });
- },
-
- connectedToHardhatOp: false,
- setConnectedToHardhatOp: connectedToHardhatOp => {
- set({ connectedToHardhatOp });
- },
- }),
- {
- storageKey: 'connectedToHardhat',
- version: 0,
- }
-);
diff --git a/src/state/internal/createRainbowStore.ts b/src/state/internal/createRainbowStore.ts
index 9b7d9a39dd7..e0c14b79ead 100644
--- a/src/state/internal/createRainbowStore.ts
+++ b/src/state/internal/createRainbowStore.ts
@@ -43,6 +43,12 @@ interface RainbowPersistConfig {
* This function will be called when persisted state versions mismatch with the one specified here.
*/
migrate?: (persistedState: unknown, version: number) => S | Promise;
+ /**
+ * A function returning another (optional) function.
+ * The main function will be called before the state rehydration.
+ * The returned function will be called after the state rehydration or when an error occurred.
+ */
+ onRehydrateStorage?: PersistOptions>['onRehydrateStorage'];
}
/**
@@ -157,6 +163,7 @@ export function createRainbowStore(
storage: persistStorage,
version,
migrate: persistConfig.migrate,
+ onRehydrateStorage: persistConfig.onRehydrateStorage,
})
)
);
diff --git a/src/state/networkSwitcher/networkSwitcher.ts b/src/state/networkSwitcher/networkSwitcher.ts
new file mode 100644
index 00000000000..aa82dc85a44
--- /dev/null
+++ b/src/state/networkSwitcher/networkSwitcher.ts
@@ -0,0 +1,57 @@
+import { ChainId } from '@/state/backendNetworks/types';
+import { createRainbowStore } from '../internal/createRainbowStore';
+import { analyticsV2 } from '@/analytics';
+import { nonceStore } from '@/state/nonces';
+import { logger } from '@/logger';
+
+export const defaultPinnedNetworks = [ChainId.base, ChainId.mainnet, ChainId.optimism, ChainId.arbitrum, ChainId.polygon, ChainId.zora];
+
+function getMostUsedChains() {
+ try {
+ const noncesByAddress = nonceStore.getState().nonces;
+ const summedNoncesByChainId: Record = {};
+ for (const addressNonces of Object.values(noncesByAddress)) {
+ for (const [chainId, { currentNonce }] of Object.entries(addressNonces)) {
+ summedNoncesByChainId[chainId] ??= 0;
+ summedNoncesByChainId[chainId] += currentNonce || 0;
+ }
+ }
+
+ const mostUsedNetworks = Object.entries(summedNoncesByChainId)
+ .sort((a, b) => b[1] - a[1])
+ .map(([chainId]) => parseInt(chainId));
+
+ return mostUsedNetworks.length ? mostUsedNetworks.slice(0, 5) : defaultPinnedNetworks;
+ } catch (error) {
+ logger.warn('[networkSwitcher]: Error getting most used chains', { error });
+ return defaultPinnedNetworks;
+ }
+}
+
+export const networkSwitcherStore = createRainbowStore<{
+ pinnedNetworks: ChainId[];
+}>(() => ({ pinnedNetworks: getMostUsedChains().slice(0, 5) }), {
+ storageKey: 'network-switcher',
+ version: 0,
+ onRehydrateStorage(state) {
+ // if we are missing pinned networks, use the user most used chains
+ if (state.pinnedNetworks.length === 0) {
+ const mostUsedNetworks = getMostUsedChains();
+ state.pinnedNetworks = mostUsedNetworks.slice(0, 5);
+ analyticsV2.identify({ mostUsedNetworks: mostUsedNetworks.filter(Boolean) });
+ }
+ },
+});
+
+export const customizeNetworksBannerStore = createRainbowStore<{
+ dismissedAt: number; // timestamp
+}>(() => ({ dismissedAt: 0 }), {
+ storageKey: 'CustomizeNetworksBanner',
+ version: 0,
+});
+
+const twoWeeks = 1000 * 60 * 60 * 24 * 7 * 2;
+export const shouldShowCustomizeNetworksBanner = (dismissedAt: number) => Date.now() - dismissedAt > twoWeeks;
+export const dismissCustomizeNetworksBanner = () => {
+ customizeNetworksBannerStore.setState({ dismissedAt: Date.now() });
+};
diff --git a/src/state/swaps/swapsStore.ts b/src/state/swaps/swapsStore.ts
index 49cc99d0268..7d4d0d99f4b 100644
--- a/src/state/swaps/swapsStore.ts
+++ b/src/state/swaps/swapsStore.ts
@@ -1,5 +1,5 @@
import { INITIAL_SLIDER_POSITION } from '@/__swaps__/screens/Swap/constants';
-import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset } from '@/__swaps__/types/assets';
+import { ExtendedAnimatedAssetWithColors, ParsedSearchAsset, UniqueId } from '@/__swaps__/types/assets';
import { ChainId } from '@/state/backendNetworks/types';
import { RecentSwap } from '@/__swaps__/types/swap';
import { getDefaultSlippage } from '@/__swaps__/utils/swaps';
@@ -42,6 +42,8 @@ export interface SwapsState {
// degen mode preferences
preferredNetwork: ChainId | undefined;
setPreferredNetwork: (preferredNetwork: ChainId | undefined) => void;
+
+ lastNavigatedTrendingToken: UniqueId | undefined;
}
type StateWithTransforms = Omit, 'latestSwapAt' | 'recentSwaps'> & {
@@ -156,6 +158,8 @@ export const swapsStore = createRainbowStore(
latestSwapAt: new Map(latestSwapAt),
});
},
+
+ lastNavigatedTrendingToken: undefined,
}),
{
storageKey: 'swapsStore',
diff --git a/src/state/sync/BackupsSync.tsx b/src/state/sync/BackupsSync.tsx
new file mode 100644
index 00000000000..a409490c205
--- /dev/null
+++ b/src/state/sync/BackupsSync.tsx
@@ -0,0 +1,12 @@
+import { useEffect, memo } from 'react';
+import { backupsStore } from '@/state/backups/backups';
+
+const BackupsSyncComponent = () => {
+ useEffect(() => {
+ backupsStore.getState().syncAndFetchBackups();
+ }, []);
+
+ return null;
+};
+
+export const BackupsSync = memo(BackupsSyncComponent);
diff --git a/src/state/sync/UserAssetsSync.tsx b/src/state/sync/UserAssetsSync.tsx
index e8722cddfce..06d94eb832a 100644
--- a/src/state/sync/UserAssetsSync.tsx
+++ b/src/state/sync/UserAssetsSync.tsx
@@ -1,3 +1,4 @@
+import { memo } from 'react';
import { useAccountSettings } from '@/hooks';
import { userAssetsStore } from '@/state/assets/userAssets';
import { useSwapsStore } from '@/state/swaps/swapsStore';
@@ -5,8 +6,9 @@ import { selectUserAssetsList, selectorFilterByUserChains } from '@/__swaps__/sc
import { ParsedSearchAsset } from '@/__swaps__/types/assets';
import { useUserAssets } from '@/__swaps__/screens/Swap/resources/assets';
import { ChainId } from '@/state/backendNetworks/types';
+import { IS_TEST } from '@/env';
-export const UserAssetsSync = function UserAssetsSync() {
+function UserAssetsSyncComponent() {
const { accountAddress, nativeCurrency: currentCurrency } = useAccountSettings();
const isSwapsOpen = useSwapsStore(state => state.isSwapsOpen);
const isUserAssetsStoreMissingData = userAssetsStore.getState().getUserAssets()?.length === 0;
@@ -39,4 +41,6 @@ export const UserAssetsSync = function UserAssetsSync() {
);
return null;
-};
+}
+
+export const UserAssetsSync = IS_TEST ? UserAssetsSyncComponent : memo(UserAssetsSyncComponent);
diff --git a/src/state/trendingTokens/trendingTokens.ts b/src/state/trendingTokens/trendingTokens.ts
new file mode 100644
index 00000000000..514160352d1
--- /dev/null
+++ b/src/state/trendingTokens/trendingTokens.ts
@@ -0,0 +1,59 @@
+import { analyticsV2 } from '@/analytics';
+import { ChainId } from '@/state/backendNetworks/types';
+import { createRainbowStore } from '../internal/createRainbowStore';
+import {
+ TrendingCategory as ArcTrendingCategory,
+ Timeframe as ArcTimeframe,
+ TrendingSort as ArcTrendingSort,
+} from '@/graphql/__generated__/arc';
+
+export const categories = [ArcTrendingCategory.Trending, ArcTrendingCategory.New, ArcTrendingCategory.Farcaster] as const;
+export type TrendingCategory = (typeof categories)[number];
+export const sortFilters = [
+ ArcTrendingSort.Recommended,
+ ArcTrendingSort.Volume,
+ ArcTrendingSort.MarketCap,
+ ArcTrendingSort.TopGainers,
+ ArcTrendingSort.TopLosers,
+] as const;
+export type TrendingSort = (typeof sortFilters)[number];
+export const timeFilters = [ArcTimeframe.H12, ArcTimeframe.H24, ArcTimeframe.D3, ArcTimeframe.D7] as const;
+export type TrendingTimeframe = (typeof timeFilters)[number];
+
+type TrendingTokensState = {
+ category: (typeof categories)[number];
+ chainId: undefined | ChainId;
+ timeframe: (typeof timeFilters)[number];
+ sort: (typeof sortFilters)[number];
+
+ setCategory: (category: TrendingTokensState['category']) => void;
+ setChainId: (chainId: TrendingTokensState['chainId']) => void;
+ setTimeframe: (timeframe: TrendingTokensState['timeframe']) => void;
+ setSort: (sort: TrendingTokensState['sort']) => void;
+};
+
+export const useTrendingTokensStore = createRainbowStore(
+ set => ({
+ category: ArcTrendingCategory.Trending,
+ chainId: undefined,
+ timeframe: ArcTimeframe.D3,
+ sort: ArcTrendingSort.Recommended,
+ setCategory: category => set({ category }),
+ setChainId: chainId => {
+ analyticsV2.track(analyticsV2.event.changeNetworkFilter, { chainId });
+ set({ chainId });
+ },
+ setTimeframe: timeframe => {
+ analyticsV2.track(analyticsV2.event.changeTimeframeFilter, { timeframe });
+ set({ timeframe });
+ },
+ setSort: sort => {
+ analyticsV2.track(analyticsV2.event.changeSortFilter, { sort });
+ set({ sort });
+ },
+ }),
+ {
+ storageKey: 'trending-tokens',
+ version: 1,
+ }
+);
diff --git a/src/state/walletLoading/walletLoading.ts b/src/state/walletLoading/walletLoading.ts
new file mode 100644
index 00000000000..7391b78e760
--- /dev/null
+++ b/src/state/walletLoading/walletLoading.ts
@@ -0,0 +1,18 @@
+import { createRainbowStore } from '../internal/createRainbowStore';
+import { WalletLoadingStates } from '@/helpers/walletLoadingStates';
+
+type WalletLoadingState = {
+ loadingState: WalletLoadingStates | null;
+ blockTouches: boolean;
+ Component: JSX.Element | null;
+ hide: () => void;
+ setComponent: (Component: JSX.Element, blockTouches?: boolean) => void;
+};
+
+export const walletLoadingStore = createRainbowStore(set => ({
+ loadingState: null,
+ blockTouches: false,
+ Component: null,
+ hide: () => set({ blockTouches: false, Component: null }),
+ setComponent: (Component: JSX.Element, blockTouches = true) => set({ blockTouches, Component }),
+}));
diff --git a/src/styles/colors.ts b/src/styles/colors.ts
index 04d23086e48..2deaa5ba2f4 100644
--- a/src/styles/colors.ts
+++ b/src/styles/colors.ts
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import { globalColors } from '@/design-system';
import currentColors from '../theme/currentColors';
import { memoFn } from '../utils/memoFn';
-import { ChainId } from '@/state/backendNetworks/types';
export type Colors = ReturnType;
@@ -186,28 +185,6 @@ const getColorsByTheme = (darkMode?: boolean) => {
},
};
- let networkColors = {
- [ChainId.zksync]: '#25292E',
- [ChainId.sanko]: '#594BA7',
- [ChainId.scroll]: '#A6855D',
- [ChainId.linea]: '#25292E',
- [ChainId.gravity]: '#B75E2C',
- [ChainId.ink]: '#7132F5',
- [ChainId.arbitrum]: '#2D374B',
- [ChainId.base]: '#0052FF',
- [ChainId.goerli]: '#f6c343',
- [ChainId.gnosis]: '#133629',
- [ChainId.mainnet]: '#25292E',
- [ChainId.optimism]: '#FF4040',
- [ChainId.polygon]: '#8247E5',
- [ChainId.bsc]: '#F0B90B',
- [ChainId.zora]: '#2B5DF0',
- [ChainId.avalanche]: '#E84142',
- [ChainId.degen]: '#A36EFD',
- [ChainId.blast]: '#25292E',
- [ChainId.apechain]: '#0054FA',
- };
-
let gradients = {
appleBlueTintToAppleBlue: ['#15B1FE', base.appleBlue],
blueToGreen: ['#4764F7', '#23D67F'],
@@ -334,28 +311,6 @@ const getColorsByTheme = (darkMode?: boolean) => {
secondGradient: '#12131A80',
thirdGradient: '#12131Aff',
};
-
- networkColors = {
- [ChainId.zksync]: '#FFFFFF',
- [ChainId.sanko]: '#7F6FC9',
- [ChainId.scroll]: '#EBC28E',
- [ChainId.linea]: '#FFFFFF',
- [ChainId.gravity]: '#B75E2C',
- [ChainId.ink]: '#864DFF',
- [ChainId.arbitrum]: '#ADBFE3',
- [ChainId.base]: '#3979FF',
- [ChainId.goerli]: '#f6c343',
- [ChainId.gnosis]: '#F0EBDE',
- [ChainId.mainnet]: '#E0E8FF',
- [ChainId.optimism]: '#FF6A6A',
- [ChainId.polygon]: '#A275EE',
- [ChainId.bsc]: '#F0B90B',
- [ChainId.zora]: '#6183F0',
- [ChainId.avalanche]: '#FF5D5E',
- [ChainId.degen]: '#A36EFD',
- [ChainId.blast]: '#FCFC03',
- [ChainId.apechain]: '#397BFF',
- };
}
return {
@@ -370,7 +325,6 @@ const getColorsByTheme = (darkMode?: boolean) => {
isColorDark,
isColorLight,
listHeaders,
- networkColors,
sendScreen,
...base,
...transparent,
diff --git a/src/utils/branch.ts b/src/utils/branch.ts
index c0d47bdaa77..c44d0c8ca01 100644
--- a/src/utils/branch.ts
+++ b/src/utils/branch.ts
@@ -2,10 +2,10 @@
import pako from 'pako';
import qs from 'qs';
import branch from 'react-native-branch';
-import { IS_TESTING } from 'react-native-dotenv';
import { analyticsV2 } from '@/analytics';
import * as ls from '@/storage';
import { logger, RainbowError } from '@/logger';
+import { IS_TEST } from '@/env';
const isEmpty = (obj: T | undefined): obj is undefined => !obj || Object.keys(obj).length === 0;
@@ -86,7 +86,7 @@ export const branchListener = async (handleOpenLinkingURL: (url: string) => void
*/
logger.debug(`[branchListener]: handling event where no link was opened`, {}, logger.DebugContext.deeplinks);
- if (IS_TESTING === 'true' && !!uri) {
+ if (IS_TEST && !!uri) {
handleOpenLinkingURL(uri);
}
} else if (params.uri && typeof params.uri === 'string') {
diff --git a/src/utils/ethereumUtils.ts b/src/utils/ethereumUtils.ts
index 7c095eb3676..74dbcb14969 100644
--- a/src/utils/ethereumUtils.ts
+++ b/src/utils/ethereumUtils.ts
@@ -1,8 +1,6 @@
import { BigNumberish } from '@ethersproject/bignumber';
import { StaticJsonRpcProvider, TransactionRequest } from '@ethersproject/providers';
import { serialize } from '@ethersproject/transactions';
-import { RainbowAddressAssets } from '@/resources/assets/types';
-import { userAssetsQueryKey } from '@/resources/assets/UserAssetsQuery';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { queryClient } from '@/react-query';
// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module 'eth-... Remove this comment to see the full error message
@@ -42,7 +40,7 @@ import {
import { ChainId, Network } from '@/state/backendNetworks/types';
import { AddressOrEth } from '@/__swaps__/types/assets';
import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks';
-import { useConnectedToHardhatStore } from '@/state/connectedToHardhat';
+import { userAssetsStore } from '@/state/assets/userAssets';
/**
* @deprecated - use `getUniqueId` instead for chainIds
@@ -148,23 +146,6 @@ const getAsset = (accountAssets: Record, uniqueId: E
const loweredUniqueId = uniqueId.toLowerCase();
return accountAssets[loweredUniqueId];
};
-
-const getUserAssetFromCache = (uniqueId: string) => {
- const { accountAddress, nativeCurrency } = store.getState().settings;
- const connectedToHardhat = useConnectedToHardhatStore.getState().connectedToHardhat;
-
- const cache = queryClient.getQueryCache();
-
- const cachedAddressAssets = (cache.find(
- userAssetsQueryKey({
- address: accountAddress,
- currency: nativeCurrency,
- connectedToHardhat,
- })
- )?.state?.data || {}) as RainbowAddressAssets;
- return cachedAddressAssets?.[uniqueId];
-};
-
const getExternalAssetFromCache = (uniqueId: string) => {
const { nativeCurrency } = store.getState().settings;
const { address, chainId } = getAddressAndChainIdFromUniqueId(uniqueId);
@@ -186,15 +167,14 @@ const getExternalAssetFromCache = (uniqueId: string) => {
const getAssetFromAllAssets = (uniqueId: EthereumAddress | undefined) => {
const loweredUniqueId = uniqueId?.toLowerCase() ?? '';
- const accountAsset = getUserAssetFromCache(loweredUniqueId);
+ const accountAsset = userAssetsStore.getState().getLegacyUserAsset(loweredUniqueId);
const externalAsset = getExternalAssetFromCache(loweredUniqueId);
return accountAsset ?? externalAsset;
};
const getAccountAsset = (uniqueId: EthereumAddress | undefined): ParsedAddressAsset | undefined => {
const loweredUniqueId = uniqueId?.toLowerCase() ?? '';
- const accountAsset = getUserAssetFromCache(loweredUniqueId);
- return accountAsset;
+ return userAssetsStore.getState().getLegacyUserAsset(loweredUniqueId) ?? undefined;
};
const getAssetPrice = (
diff --git a/src/utils/reviewAlert.ts b/src/utils/reviewAlert.ts
index dfbda01e918..767b305e2ba 100644
--- a/src/utils/reviewAlert.ts
+++ b/src/utils/reviewAlert.ts
@@ -3,8 +3,8 @@ import * as ls from '@/storage';
import { WrappedAlert as Alert } from '@/helpers/alert';
import { ReviewPromptAction } from '@/storage/schema';
import { logger, RainbowError } from '@/logger';
-import { IS_TESTING } from 'react-native-dotenv';
import * as StoreReview from 'expo-store-review';
+import { IS_TEST } from '@/env';
export const AppleReviewAddress = 'itms-apps://itunes.apple.com/us/app/appName/id1457119021?mt=8&action=write-review';
@@ -29,7 +29,7 @@ export const numberOfTimesBeforePrompt: {
export const handleReviewPromptAction = async (action: ReviewPromptAction) => {
logger.debug(`[reviewAlert]: handleReviewPromptAction: ${action}`);
- if (IS_TESTING === 'true') {
+ if (IS_TEST) {
return;
}
diff --git a/src/walletConnect/sheets/AuthRequest.tsx b/src/walletConnect/sheets/AuthRequest.tsx
index 338acad39b4..724cae00de2 100644
--- a/src/walletConnect/sheets/AuthRequest.tsx
+++ b/src/walletConnect/sheets/AuthRequest.tsx
@@ -149,7 +149,7 @@ export function AuthRequest({
navigate(Routes.CHANGE_WALLET_SHEET, {
watchOnly: true,
currentAccountAddress: address,
- onChangeWallet(address: string) {
+ onChangeWallet(address) {
setAddress(address);
goBack();
},
diff --git a/yarn.lock b/yarn.lock
index b5fbcfce5a1..0c47a4cd051 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2240,52 +2240,6 @@ __metadata:
languageName: node
linkType: hard
-"@chainsafe/as-sha256@npm:^0.3.1":
- version: 0.3.1
- resolution: "@chainsafe/as-sha256@npm:0.3.1"
- checksum: 10c0/72561fc6552a53e4d1fc28880b7f82ecb7a997670568333cb479f323db9482a6a59dd9d0f915210703e51c3a4ca2701ccdb4c66a0202abab4872d81184c9212e
- languageName: node
- linkType: hard
-
-"@chainsafe/persistent-merkle-tree@npm:^0.4.2":
- version: 0.4.2
- resolution: "@chainsafe/persistent-merkle-tree@npm:0.4.2"
- dependencies:
- "@chainsafe/as-sha256": "npm:^0.3.1"
- checksum: 10c0/9533e478a1a990e8cf8710a2eeb84c6f08c7b61726a43dbe2165316256839c29a2ff17923bce5e5effec446d832de8b0a5bc896ef5db80bce059af5d1bd20d8d
- languageName: node
- linkType: hard
-
-"@chainsafe/persistent-merkle-tree@npm:^0.5.0":
- version: 0.5.0
- resolution: "@chainsafe/persistent-merkle-tree@npm:0.5.0"
- dependencies:
- "@chainsafe/as-sha256": "npm:^0.3.1"
- checksum: 10c0/73c7a7536f49aceab61870fcc1dafef8a8be2ae0bfff2614846bb4b57a21939da75bca7bc5d1959cd312a5133be0acaf0e30fb323410c57592e9ec384758efe0
- languageName: node
- linkType: hard
-
-"@chainsafe/ssz@npm:^0.10.0":
- version: 0.10.2
- resolution: "@chainsafe/ssz@npm:0.10.2"
- dependencies:
- "@chainsafe/as-sha256": "npm:^0.3.1"
- "@chainsafe/persistent-merkle-tree": "npm:^0.5.0"
- checksum: 10c0/be427eba9f9c4a542326f9f3c20eb704c1c2500c4f124ba18febf6ffd5bb7bd5755228d99326bf6c4e4d969daa4b6ff2efb743688ec36ef86f20c0c673c0e967
- languageName: node
- linkType: hard
-
-"@chainsafe/ssz@npm:^0.9.2":
- version: 0.9.4
- resolution: "@chainsafe/ssz@npm:0.9.4"
- dependencies:
- "@chainsafe/as-sha256": "npm:^0.3.1"
- "@chainsafe/persistent-merkle-tree": "npm:^0.4.2"
- case: "npm:^1.6.3"
- checksum: 10c0/4ce4b867c60dbee98772fe075037c7ef9a7894f97a4fb04f3cfd57e11fa683b8c23a4d80b53592d10fbd4e2abac43c9099181cfaee587619366f49091b9e5fcb
- languageName: node
- linkType: hard
-
"@coinbase/mobile-wallet-protocol-host@npm:0.1.7":
version: 0.1.7
resolution: "@coinbase/mobile-wallet-protocol-host@npm:0.1.7"
@@ -2517,7 +2471,7 @@ __metadata:
languageName: node
linkType: hard
-"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.0.1, @ethersproject/abi@npm:^5.1.2, @ethersproject/abi@npm:^5.5.0, @ethersproject/abi@npm:^5.7.0":
+"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.0.1, @ethersproject/abi@npm:^5.5.0, @ethersproject/abi@npm:^5.7.0":
version: 5.7.0
resolution: "@ethersproject/abi@npm:5.7.0"
dependencies:
@@ -2772,7 +2726,7 @@ __metadata:
languageName: node
linkType: hard
-"@ethersproject/providers@npm:5.7.2, @ethersproject/providers@npm:^5.7.1, @ethersproject/providers@npm:^5.7.2":
+"@ethersproject/providers@npm:5.7.2":
version: 5.7.2
resolution: "@ethersproject/providers@npm:5.7.2"
dependencies:
@@ -3411,13 +3365,6 @@ __metadata:
languageName: node
linkType: hard
-"@fastify/busboy@npm:^2.0.0":
- version: 2.1.1
- resolution: "@fastify/busboy@npm:2.1.1"
- checksum: 10c0/6f8027a8cba7f8f7b736718b013f5a38c0476eea67034c94a0d3c375e2b114366ad4419e6a6fa7ffc2ef9c6d3e0435d76dd584a7a1cbac23962fda7650b579e3
- languageName: node
- linkType: hard
-
"@flatten-js/interval-tree@npm:^1.1.2":
version: 1.1.3
resolution: "@flatten-js/interval-tree@npm:1.1.3"
@@ -4315,19 +4262,6 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/eth-sig-util@npm:^4.0.0":
- version: 4.0.1
- resolution: "@metamask/eth-sig-util@npm:4.0.1"
- dependencies:
- ethereumjs-abi: "npm:^0.6.8"
- ethereumjs-util: "npm:^6.2.1"
- ethjs-util: "npm:^0.1.6"
- tweetnacl: "npm:^1.0.3"
- tweetnacl-util: "npm:^0.15.1"
- checksum: 10c0/957fa16e8f0454ad45203a8416e77181853de1c9e33697f1a1582d46f18da1cca26c803a4e08bee7091a697609fc8916f399210fd5d3d2fccc34bfd0a58715f0
- languageName: node
- linkType: hard
-
"@metamask/superstruct@npm:^3.0.0, @metamask/superstruct@npm:^3.1.0":
version: 3.1.0
resolution: "@metamask/superstruct@npm:3.1.0"
@@ -4468,13 +4402,6 @@ __metadata:
languageName: node
linkType: hard
-"@noble/hashes@npm:1.2.0, @noble/hashes@npm:~1.2.0":
- version: 1.2.0
- resolution: "@noble/hashes@npm:1.2.0"
- checksum: 10c0/8bd3edb7bb6a9068f806a9a5a208cc2144e42940a21c049d8e9a0c23db08bef5cf1cfd844a7e35489b5ab52c6fa6299352075319e7f531e0996d459c38cfe26a
- languageName: node
- linkType: hard
-
"@noble/hashes@npm:1.3.2":
version: 1.3.2
resolution: "@noble/hashes@npm:1.3.2"
@@ -4510,13 +4437,6 @@ __metadata:
languageName: node
linkType: hard
-"@noble/secp256k1@npm:1.7.1, @noble/secp256k1@npm:~1.7.0":
- version: 1.7.1
- resolution: "@noble/secp256k1@npm:1.7.1"
- checksum: 10c0/48091801d39daba75520012027d0ff0b1719338d96033890cfe0d287ad75af00d82769c0194a06e7e4fbd816ae3f204f4a59c9e26f0ad16b429f7e9b5403ccd5
- languageName: node
- linkType: hard
-
"@nodelib/fs.scandir@npm:2.1.5":
version: 2.1.5
resolution: "@nodelib/fs.scandir@npm:2.1.5"
@@ -4544,266 +4464,6 @@ __metadata:
languageName: node
linkType: hard
-"@nomicfoundation/ethereumjs-block@npm:5.0.2":
- version: 5.0.2
- resolution: "@nomicfoundation/ethereumjs-block@npm:5.0.2"
- dependencies:
- "@nomicfoundation/ethereumjs-common": "npm:4.0.2"
- "@nomicfoundation/ethereumjs-rlp": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-trie": "npm:6.0.2"
- "@nomicfoundation/ethereumjs-tx": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-util": "npm:9.0.2"
- ethereum-cryptography: "npm:0.1.3"
- ethers: "npm:^5.7.1"
- checksum: 10c0/9bbf524706c86b3741eab42a82bce723ef413f2ecd85bc96b6353f619559780995bc21fcf765558a3a7ab5eca5c77926ae7440fe2467774d896f67ec9bfcd63e
- languageName: node
- linkType: hard
-
-"@nomicfoundation/ethereumjs-blockchain@npm:7.0.2":
- version: 7.0.2
- resolution: "@nomicfoundation/ethereumjs-blockchain@npm:7.0.2"
- dependencies:
- "@nomicfoundation/ethereumjs-block": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-common": "npm:4.0.2"
- "@nomicfoundation/ethereumjs-ethash": "npm:3.0.2"
- "@nomicfoundation/ethereumjs-rlp": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-trie": "npm:6.0.2"
- "@nomicfoundation/ethereumjs-tx": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-util": "npm:9.0.2"
- abstract-level: "npm:^1.0.3"
- debug: "npm:^4.3.3"
- ethereum-cryptography: "npm:0.1.3"
- level: "npm:^8.0.0"
- lru-cache: "npm:^5.1.1"
- memory-level: "npm:^1.0.0"
- checksum: 10c0/388f938288396669108e6513c531e81d02d994dabcbf96261dd6672a882dfd4966cf9e05fd0c98d50c7aef847335a588b21dd0acba9d923cc734f4f61a7a77ba
- languageName: node
- linkType: hard
-
-"@nomicfoundation/ethereumjs-common@npm:4.0.2":
- version: 4.0.2
- resolution: "@nomicfoundation/ethereumjs-common@npm:4.0.2"
- dependencies:
- "@nomicfoundation/ethereumjs-util": "npm:9.0.2"
- crc-32: "npm:^1.2.0"
- checksum: 10c0/ce12038b8b3245a2a20b8a11fe19b4454a8179b7a1bb9185cd42a85b5a17f7fceacf0bf69517d095b52e3cede4eeda71a45044a5a8976f3f37e2d501f0adaea3
- languageName: node
- linkType: hard
-
-"@nomicfoundation/ethereumjs-ethash@npm:3.0.2":
- version: 3.0.2
- resolution: "@nomicfoundation/ethereumjs-ethash@npm:3.0.2"
- dependencies:
- "@nomicfoundation/ethereumjs-block": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-rlp": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-util": "npm:9.0.2"
- abstract-level: "npm:^1.0.3"
- bigint-crypto-utils: "npm:^3.0.23"
- ethereum-cryptography: "npm:0.1.3"
- checksum: 10c0/0a19f9243e9cc348e13bff0b0ec5ec9612c275550c5b0a3028b466f39e0959dd8c0eeeae7fc0c9af920023143658dbb18d87107167af344de92e76aaf683dcc4
- languageName: node
- linkType: hard
-
-"@nomicfoundation/ethereumjs-evm@npm:2.0.2":
- version: 2.0.2
- resolution: "@nomicfoundation/ethereumjs-evm@npm:2.0.2"
- dependencies:
- "@ethersproject/providers": "npm:^5.7.1"
- "@nomicfoundation/ethereumjs-common": "npm:4.0.2"
- "@nomicfoundation/ethereumjs-tx": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-util": "npm:9.0.2"
- debug: "npm:^4.3.3"
- ethereum-cryptography: "npm:0.1.3"
- mcl-wasm: "npm:^0.7.1"
- rustbn.js: "npm:~0.2.0"
- checksum: 10c0/a5711952d8afe1c61c3e8275217b7c3bd600de839ad784848ff556c498fee4d0c0a29834aa2c666263915ed6123a3191297c109bad8e50c135dd9de33d101cd7
- languageName: node
- linkType: hard
-
-"@nomicfoundation/ethereumjs-rlp@npm:5.0.2":
- version: 5.0.2
- resolution: "@nomicfoundation/ethereumjs-rlp@npm:5.0.2"
- bin:
- rlp: bin/rlp
- checksum: 10c0/46c7d317f59690973c41786b7a3ef3abe456efd085d55a0b202f6ead792e34e1a0749815911ab558b83f508c4ae5a6cba4d994aeae9c77c14ce0516f284ed34b
- languageName: node
- linkType: hard
-
-"@nomicfoundation/ethereumjs-statemanager@npm:2.0.2":
- version: 2.0.2
- resolution: "@nomicfoundation/ethereumjs-statemanager@npm:2.0.2"
- dependencies:
- "@nomicfoundation/ethereumjs-common": "npm:4.0.2"
- "@nomicfoundation/ethereumjs-rlp": "npm:5.0.2"
- debug: "npm:^4.3.3"
- ethereum-cryptography: "npm:0.1.3"
- ethers: "npm:^5.7.1"
- js-sdsl: "npm:^4.1.4"
- checksum: 10c0/d3b184adb1b8aaf4c87299194746fc343a94df6f500d677f04a36914f7673eee19c344eb88a6f78718dcb4ae15d63c2c3e87cb9a5076950b67843e5bf9321ace
- languageName: node
- linkType: hard
-
-"@nomicfoundation/ethereumjs-trie@npm:6.0.2":
- version: 6.0.2
- resolution: "@nomicfoundation/ethereumjs-trie@npm:6.0.2"
- dependencies:
- "@nomicfoundation/ethereumjs-rlp": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-util": "npm:9.0.2"
- "@types/readable-stream": "npm:^2.3.13"
- ethereum-cryptography: "npm:0.1.3"
- readable-stream: "npm:^3.6.0"
- checksum: 10c0/188c5d0a5793fb4512916091bde498e52ec6ecb374963213602f32e98c301d4d62f2daefd4ebefc0944d6d5b8346f104156fa544c0fc26ded488884a0424b2cc
- languageName: node
- linkType: hard
-
-"@nomicfoundation/ethereumjs-tx@npm:5.0.2":
- version: 5.0.2
- resolution: "@nomicfoundation/ethereumjs-tx@npm:5.0.2"
- dependencies:
- "@chainsafe/ssz": "npm:^0.9.2"
- "@ethersproject/providers": "npm:^5.7.2"
- "@nomicfoundation/ethereumjs-common": "npm:4.0.2"
- "@nomicfoundation/ethereumjs-rlp": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-util": "npm:9.0.2"
- ethereum-cryptography: "npm:0.1.3"
- checksum: 10c0/327c093656b4f6e845c3ef543b6ab54f6699436d95e18d0fca9df930dd2ddb975374b2499b3f98070cae4e6f54005b0484c1b40ff1838326cf5a631710116def
- languageName: node
- linkType: hard
-
-"@nomicfoundation/ethereumjs-util@npm:9.0.2":
- version: 9.0.2
- resolution: "@nomicfoundation/ethereumjs-util@npm:9.0.2"
- dependencies:
- "@chainsafe/ssz": "npm:^0.10.0"
- "@nomicfoundation/ethereumjs-rlp": "npm:5.0.2"
- ethereum-cryptography: "npm:0.1.3"
- checksum: 10c0/34b5b73f2e23bd883e53fd4d6810954d08451c84887b3d7c8910c093825686c499fe0edbb865db8d064c6790b447ce10f1aea030073befd64dbe62b75126dac6
- languageName: node
- linkType: hard
-
-"@nomicfoundation/ethereumjs-vm@npm:7.0.2":
- version: 7.0.2
- resolution: "@nomicfoundation/ethereumjs-vm@npm:7.0.2"
- dependencies:
- "@nomicfoundation/ethereumjs-block": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-blockchain": "npm:7.0.2"
- "@nomicfoundation/ethereumjs-common": "npm:4.0.2"
- "@nomicfoundation/ethereumjs-evm": "npm:2.0.2"
- "@nomicfoundation/ethereumjs-rlp": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-statemanager": "npm:2.0.2"
- "@nomicfoundation/ethereumjs-trie": "npm:6.0.2"
- "@nomicfoundation/ethereumjs-tx": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-util": "npm:9.0.2"
- debug: "npm:^4.3.3"
- ethereum-cryptography: "npm:0.1.3"
- mcl-wasm: "npm:^0.7.1"
- rustbn.js: "npm:~0.2.0"
- checksum: 10c0/759c16d471429e06b8a191b3b87c140690e6334586b5467587e7397e7e40dc0ec6aea4a73cea68a1ace125552beefc23624a6e667387031f5379000e56f83018
- languageName: node
- linkType: hard
-
-"@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.2":
- version: 0.1.2
- resolution: "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.2"
- checksum: 10c0/ef3b13bb2133fea6621db98f991036a3a84d2b240160edec50beafa6ce821fe2f0f5cd4aa61adb9685aff60cd0425982ffd15e0b868b7c768e90e26b8135b825
- languageName: node
- linkType: hard
-
-"@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.2":
- version: 0.1.2
- resolution: "@nomicfoundation/solidity-analyzer-darwin-x64@npm:0.1.2"
- checksum: 10c0/3cb6a00cd200b94efd6f59ed626c705c6f773b92ccf8b90471285cd0e81b35f01edb30c1aa5a4633393c2adb8f20fd34e90c51990dc4e30658e8a67c026d16c9
- languageName: node
- linkType: hard
-
-"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.2":
- version: 0.1.2
- resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-gnu@npm:0.1.2"
- checksum: 10c0/cb9725e7bdc3ba9c1feaef96dbf831c1a59c700ca633a9929fd97debdcb5ce06b5d7b4e6dbc076279978707214d01e2cd126d8e3f4cabc5c16525c031a47b95c
- languageName: node
- linkType: hard
-
-"@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.2":
- version: 0.1.2
- resolution: "@nomicfoundation/solidity-analyzer-linux-arm64-musl@npm:0.1.2"
- checksum: 10c0/82a90b1d09ad266ddc510ece2e397f51fdaf29abf7263d2a3a85accddcba2ac24cceb670a3120800611cdcc552eed04919d071e259fdda7564818359ed541f5d
- languageName: node
- linkType: hard
-
-"@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.2":
- version: 0.1.2
- resolution: "@nomicfoundation/solidity-analyzer-linux-x64-gnu@npm:0.1.2"
- checksum: 10c0/d1f20d4d55683bd041ead957e5461b2e43a39e959f905e8866de1d65f8d96118e9b861e994604d9002cb7f056be0844e36c241a6bb531c336b399609977c0998
- languageName: node
- linkType: hard
-
-"@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.2":
- version: 0.1.2
- resolution: "@nomicfoundation/solidity-analyzer-linux-x64-musl@npm:0.1.2"
- checksum: 10c0/6c17f9af3aaf184c0a217cf723076051c502d85e731dbc97f34b838f9ae1b597577abac54a2af49b3fd986b09131c52fa21fd5393b22d05e1ec7fee96a8249c2
- languageName: node
- linkType: hard
-
-"@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.2":
- version: 0.1.2
- resolution: "@nomicfoundation/solidity-analyzer-win32-x64-msvc@npm:0.1.2"
- checksum: 10c0/da198464f5ee0d19b6decdfaa65ee0df3097b8960b8483bb7080931968815a5d60f27191229d47a198955784d763d5996f0b92bfde3551612ad972c160b0b000
- languageName: node
- linkType: hard
-
-"@nomicfoundation/solidity-analyzer@npm:^0.1.0":
- version: 0.1.2
- resolution: "@nomicfoundation/solidity-analyzer@npm:0.1.2"
- dependencies:
- "@nomicfoundation/solidity-analyzer-darwin-arm64": "npm:0.1.2"
- "@nomicfoundation/solidity-analyzer-darwin-x64": "npm:0.1.2"
- "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "npm:0.1.2"
- "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "npm:0.1.2"
- "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "npm:0.1.2"
- "@nomicfoundation/solidity-analyzer-linux-x64-musl": "npm:0.1.2"
- "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "npm:0.1.2"
- dependenciesMeta:
- "@nomicfoundation/solidity-analyzer-darwin-arm64":
- optional: true
- "@nomicfoundation/solidity-analyzer-darwin-x64":
- optional: true
- "@nomicfoundation/solidity-analyzer-linux-arm64-gnu":
- optional: true
- "@nomicfoundation/solidity-analyzer-linux-arm64-musl":
- optional: true
- "@nomicfoundation/solidity-analyzer-linux-x64-gnu":
- optional: true
- "@nomicfoundation/solidity-analyzer-linux-x64-musl":
- optional: true
- "@nomicfoundation/solidity-analyzer-win32-x64-msvc":
- optional: true
- checksum: 10c0/e4f503e9287e18967535af669ca7e26e2682203c45a34ea85da53122da1dee1278f2b8c76c20c67fadd7c1b1a98eeecffd2cbc136860665e3afa133817c0de54
- languageName: node
- linkType: hard
-
-"@nomiclabs/hardhat-ethers@npm:2.2.3":
- version: 2.2.3
- resolution: "@nomiclabs/hardhat-ethers@npm:2.2.3"
- peerDependencies:
- ethers: ^5.0.0
- hardhat: ^2.0.0
- checksum: 10c0/cae46d1966108ab02b50fabe7945c8987fa1e9d5d0a7a06f79afc274ff1abc312e8a82375191a341b28571b897c22433d3a2826eb30077ed88d5983d01e381d0
- languageName: node
- linkType: hard
-
-"@nomiclabs/hardhat-waffle@npm:2.0.6":
- version: 2.0.6
- resolution: "@nomiclabs/hardhat-waffle@npm:2.0.6"
- peerDependencies:
- "@nomiclabs/hardhat-ethers": ^2.0.0
- "@types/sinon-chai": ^3.2.3
- ethereum-waffle: "*"
- ethers: ^5.0.0
- hardhat: ^2.0.0
- checksum: 10c0/9614ab1e76959cfccc586842d990de4c2aa74cea8e82a838d017d91d4c696df931af4a77af9c16325e037ec8438a8c98c9bae5d9e4d0d0fcdaa147c86bce01b5
- languageName: node
- linkType: hard
-
"@notifee/react-native@npm:7.8.2":
version: 7.8.2
resolution: "@notifee/react-native@npm:7.8.2"
@@ -6431,17 +6091,6 @@ __metadata:
languageName: node
linkType: hard
-"@scure/bip32@npm:1.1.5":
- version: 1.1.5
- resolution: "@scure/bip32@npm:1.1.5"
- dependencies:
- "@noble/hashes": "npm:~1.2.0"
- "@noble/secp256k1": "npm:~1.7.0"
- "@scure/base": "npm:~1.1.0"
- checksum: 10c0/d0521f6de28278e06f2d517307b4de6c9bcb3dbdf9a5844bb57a6e4916a180e4136129ccab295c27bd1196ef77757608255afcd7cf927e03baec4479b3df74fc
- languageName: node
- linkType: hard
-
"@scure/bip32@npm:1.3.2":
version: 1.3.2
resolution: "@scure/bip32@npm:1.3.2"
@@ -6475,16 +6124,6 @@ __metadata:
languageName: node
linkType: hard
-"@scure/bip39@npm:1.1.1":
- version: 1.1.1
- resolution: "@scure/bip39@npm:1.1.1"
- dependencies:
- "@noble/hashes": "npm:~1.2.0"
- "@scure/base": "npm:~1.1.0"
- checksum: 10c0/821dc9d5be8362a32277390526db064860c2216a079ba51d63def9289c2b290599e93681ebbeebf0e93540799eec35784c1dfcf5167d0b280ef148e5023ce01b
- languageName: node
- linkType: hard
-
"@scure/bip39@npm:1.2.1":
version: 1.2.1
resolution: "@scure/bip39@npm:1.2.1"
@@ -6681,19 +6320,6 @@ __metadata:
languageName: node
linkType: hard
-"@sentry/core@npm:5.30.0":
- version: 5.30.0
- resolution: "@sentry/core@npm:5.30.0"
- dependencies:
- "@sentry/hub": "npm:5.30.0"
- "@sentry/minimal": "npm:5.30.0"
- "@sentry/types": "npm:5.30.0"
- "@sentry/utils": "npm:5.30.0"
- tslib: "npm:^1.9.3"
- checksum: 10c0/6407b9c2a6a56f90c198f5714b3257df24d89d1b4ca6726bd44760d0adabc25798b69fef2c88ccea461c7e79e3c78861aaebfd51fd3cb892aee656c3f7e11801
- languageName: node
- linkType: hard
-
"@sentry/core@npm:7.119.0":
version: 7.119.0
resolution: "@sentry/core@npm:7.119.0"
@@ -6714,17 +6340,6 @@ __metadata:
languageName: node
linkType: hard
-"@sentry/hub@npm:5.30.0":
- version: 5.30.0
- resolution: "@sentry/hub@npm:5.30.0"
- dependencies:
- "@sentry/types": "npm:5.30.0"
- "@sentry/utils": "npm:5.30.0"
- tslib: "npm:^1.9.3"
- checksum: 10c0/386c91d06aa44be0465fc11330d748a113e464d41cd562a9e1d222a682cbcb14e697a3e640953e7a0239997ad8a02b223a0df3d9e1d8816cb823fd3613be3e2f
- languageName: node
- linkType: hard
-
"@sentry/hub@npm:7.119.0":
version: 7.119.0
resolution: "@sentry/hub@npm:7.119.0"
@@ -6760,34 +6375,6 @@ __metadata:
languageName: node
linkType: hard
-"@sentry/minimal@npm:5.30.0":
- version: 5.30.0
- resolution: "@sentry/minimal@npm:5.30.0"
- dependencies:
- "@sentry/hub": "npm:5.30.0"
- "@sentry/types": "npm:5.30.0"
- tslib: "npm:^1.9.3"
- checksum: 10c0/34ec05503de46d01f98c94701475d5d89cc044892c86ccce30e01f62f28344eb23b718e7cf573815e46f30a4ac9da3129bed9b3d20c822938acfb40cbe72437b
- languageName: node
- linkType: hard
-
-"@sentry/node@npm:^5.18.1":
- version: 5.30.0
- resolution: "@sentry/node@npm:5.30.0"
- dependencies:
- "@sentry/core": "npm:5.30.0"
- "@sentry/hub": "npm:5.30.0"
- "@sentry/tracing": "npm:5.30.0"
- "@sentry/types": "npm:5.30.0"
- "@sentry/utils": "npm:5.30.0"
- cookie: "npm:^0.4.1"
- https-proxy-agent: "npm:^5.0.0"
- lru_map: "npm:^0.3.3"
- tslib: "npm:^1.9.3"
- checksum: 10c0/c50db7c81ace57cac17692245c2ab3c84a6149183f81d5f2dfd157eaa7b66eb4d6a727dd13a754bb129c96711389eec2944cd94126722ee1d8b11f2b627b830d
- languageName: node
- linkType: hard
-
"@sentry/react-native@npm:5.35.0":
version: 5.35.0
resolution: "@sentry/react-native@npm:5.35.0"
@@ -6841,26 +6428,6 @@ __metadata:
languageName: node
linkType: hard
-"@sentry/tracing@npm:5.30.0":
- version: 5.30.0
- resolution: "@sentry/tracing@npm:5.30.0"
- dependencies:
- "@sentry/hub": "npm:5.30.0"
- "@sentry/minimal": "npm:5.30.0"
- "@sentry/types": "npm:5.30.0"
- "@sentry/utils": "npm:5.30.0"
- tslib: "npm:^1.9.3"
- checksum: 10c0/46830265bc54a3203d7d9f0d8d9f2f7d9d2c6a977e07ccdae317fa3ea29c388b904b3bef28f7a0ba9c074845d67feab63c6d3c0ddce9aeb275b6c966253fb415
- languageName: node
- linkType: hard
-
-"@sentry/types@npm:5.30.0":
- version: 5.30.0
- resolution: "@sentry/types@npm:5.30.0"
- checksum: 10c0/99c6e55c0a82c8ca95be2e9dbb35f581b29e4ff7af74b23bc62b690de4e35febfa15868184a2303480ef86babd4fea5273cf3b5ddf4a27685b841a72f13a0c88
- languageName: node
- linkType: hard
-
"@sentry/types@npm:7.119.0":
version: 7.119.0
resolution: "@sentry/types@npm:7.119.0"
@@ -6875,16 +6442,6 @@ __metadata:
languageName: node
linkType: hard
-"@sentry/utils@npm:5.30.0":
- version: 5.30.0
- resolution: "@sentry/utils@npm:5.30.0"
- dependencies:
- "@sentry/types": "npm:5.30.0"
- tslib: "npm:^1.9.3"
- checksum: 10c0/ca8eebfea7ac7db6d16f6c0b8a66ac62587df12a79ce9d0d8393f4d69880bb8d40d438f9810f7fb107a9880fe0d68bbf797b89cbafd113e89a0829eb06b205f8
- languageName: node
- linkType: hard
-
"@sentry/utils@npm:7.119.0":
version: 7.119.0
resolution: "@sentry/utils@npm:7.119.0"
@@ -7666,13 +7223,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/lru-cache@npm:^5.1.0":
- version: 5.1.1
- resolution: "@types/lru-cache@npm:5.1.1"
- checksum: 10c0/1f17ec9b202c01a89337cc5528198a690be6b61a6688242125fbfb7fa17770e453e00e4685021abf5ae605860ca0722209faac5c254b780d0104730bb0b9e354
- languageName: node
- linkType: hard
-
"@types/minimatch@npm:^3.0.3":
version: 3.0.5
resolution: "@types/minimatch@npm:3.0.5"
@@ -7854,16 +7404,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/readable-stream@npm:^2.3.13":
- version: 2.3.15
- resolution: "@types/readable-stream@npm:2.3.15"
- dependencies:
- "@types/node": "npm:*"
- safe-buffer: "npm:~5.1.1"
- checksum: 10c0/789e0948a8fd2f2cbf880a0f8c95601ac2fd8782a5a8fe653f68fad7fc3a74f44e8559484e331c1ff5d5b00fa467bec97557bb683aa833a3b29a69506f7aee59
- languageName: node
- linkType: hard
-
"@types/scheduler@npm:*":
version: 0.23.0
resolution: "@types/scheduler@npm:0.23.0"
@@ -9182,8 +8722,6 @@ __metadata:
"@ledgerhq/hw-app-eth": "npm:6.39.0"
"@ledgerhq/react-native-hw-transport-ble": "npm:6.33.4"
"@metamask/eth-sig-util": "npm:7.0.2"
- "@nomiclabs/hardhat-ethers": "npm:2.2.3"
- "@nomiclabs/hardhat-waffle": "npm:2.0.6"
"@notifee/react-native": "npm:7.8.2"
"@rainbow-me/provider": "npm:0.1.1"
"@rainbow-me/react-native-animated-number": "npm:0.0.2"
@@ -9296,7 +8834,6 @@ __metadata:
graphql-request: "npm:5.0.0"
graphql-tag: "npm:2.11.0"
gretchen: "npm:1.5.0"
- hardhat: "npm:2.18.1"
https-browserify: "npm:0.0.1"
husky: "npm:8.0.1"
i18n-js: "npm:3.8.0"
@@ -9509,21 +9046,6 @@ __metadata:
languageName: node
linkType: hard
-"abstract-level@npm:^1.0.0, abstract-level@npm:^1.0.2, abstract-level@npm:^1.0.3, abstract-level@npm:^1.0.4":
- version: 1.0.4
- resolution: "abstract-level@npm:1.0.4"
- dependencies:
- buffer: "npm:^6.0.3"
- catering: "npm:^2.1.0"
- is-buffer: "npm:^2.0.5"
- level-supports: "npm:^4.0.0"
- level-transcoder: "npm:^1.0.1"
- module-error: "npm:^1.0.1"
- queue-microtask: "npm:^1.2.3"
- checksum: 10c0/e89fad8924888b597ca84e6d003a424a670f225fd595ad1f4c2e2d38a5cea3c92877a44f5104cdede1717eb262dd6e7fbf7ef2375569c7395066521170b8d761
- languageName: node
- linkType: hard
-
"abstract-leveldown@npm:2.6.1":
version: 2.6.1
resolution: "abstract-leveldown@npm:2.6.1"
@@ -9588,13 +9110,6 @@ __metadata:
languageName: node
linkType: hard
-"adm-zip@npm:^0.4.16":
- version: 0.4.16
- resolution: "adm-zip@npm:0.4.16"
- checksum: 10c0/c56c6e138fd19006155fc716acae14d54e07c267ae19d78c8a8cdca04762bf20170a71a41aa8d8bad2f13b70d4f3e9a191009bafa5280e05a440ee506f871a55
- languageName: node
- linkType: hard
-
"aes-js@npm:3.0.0":
version: 3.0.0
resolution: "aes-js@npm:3.0.0"
@@ -9699,7 +9214,7 @@ __metadata:
languageName: node
linkType: hard
-"ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3":
+"ansi-colors@npm:^4.1.1":
version: 4.1.3
resolution: "ansi-colors@npm:4.1.3"
checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9
@@ -10784,13 +10299,6 @@ __metadata:
languageName: node
linkType: hard
-"bigint-crypto-utils@npm:^3.0.23":
- version: 3.3.0
- resolution: "bigint-crypto-utils@npm:3.3.0"
- checksum: 10c0/7d06fa01d63e8e9513eee629fe8a426993276b1bdca5aefd0eb3188cee7026334d29e801ef6187a5bc6105ebf26e6e79e6fab544a7da769ccf55b913e66d2a14
- languageName: node
- linkType: hard
-
"bignumber.js@npm:9.0.1":
version: 9.0.1
resolution: "bignumber.js@npm:9.0.1"
@@ -10992,18 +10500,6 @@ __metadata:
languageName: node
linkType: hard
-"browser-level@npm:^1.0.1":
- version: 1.0.1
- resolution: "browser-level@npm:1.0.1"
- dependencies:
- abstract-level: "npm:^1.0.2"
- catering: "npm:^2.1.1"
- module-error: "npm:^1.0.2"
- run-parallel-limit: "npm:^1.1.0"
- checksum: 10c0/10f874b05fb06092c4dc3f7b02c1bcff9b01b8eee2a7066837a10c4b0179d40dd9ecef03bfecb9acbd0b61abf67ccd250766ee18b48464cd9a4eeddda1b069b9
- languageName: node
- linkType: hard
-
"browser-process-hrtime@npm:^1.0.0":
version: 1.0.0
resolution: "browser-process-hrtime@npm:1.0.0"
@@ -11011,13 +10507,6 @@ __metadata:
languageName: node
linkType: hard
-"browser-stdout@npm:^1.3.1":
- version: 1.3.1
- resolution: "browser-stdout@npm:1.3.1"
- checksum: 10c0/c40e482fd82be872b6ea7b9f7591beafbf6f5ba522fe3dade98ba1573a1c29a11101564993e4eb44e5488be8f44510af072df9a9637c739217eb155ceb639205
- languageName: node
- linkType: hard
-
"browserify-aes@npm:^1.0.4, browserify-aes@npm:^1.2.0":
version: 1.2.0
resolution: "browserify-aes@npm:1.2.0"
@@ -11312,13 +10801,6 @@ __metadata:
languageName: node
linkType: hard
-"bytes@npm:3.1.2":
- version: 3.1.2
- resolution: "bytes@npm:3.1.2"
- checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e
- languageName: node
- linkType: hard
-
"cacache@npm:^16.1.0":
version: 16.1.3
resolution: "cacache@npm:16.1.3"
@@ -11478,20 +10960,6 @@ __metadata:
languageName: node
linkType: hard
-"case@npm:^1.6.3":
- version: 1.6.3
- resolution: "case@npm:1.6.3"
- checksum: 10c0/43fcbb1dff1c4add94dd2bc98bd923d6616f10bff6959adf686d192c3db7d7ced35410761e1ac94cc4a1f5c41c86406ad79d390805539e421e8a77e553f67223
- languageName: node
- linkType: hard
-
-"catering@npm:^2.1.0, catering@npm:^2.1.1":
- version: 2.1.1
- resolution: "catering@npm:2.1.1"
- checksum: 10c0/a69f946f82cba85509abcb399759ed4c39d2cc9e33ba35674f242130c1b3c56673da3c3e85804db6898dfd966c395aa128ba484b31c7b906cc2faca6a581e133
- languageName: node
- linkType: hard
-
"centra@npm:^2.7.0":
version: 2.7.0
resolution: "centra@npm:2.7.0"
@@ -11597,7 +11065,7 @@ __metadata:
languageName: node
linkType: hard
-"chokidar@npm:^3.4.0, chokidar@npm:^3.5.3, chokidar@npm:^3.6.0":
+"chokidar@npm:^3.6.0":
version: 3.6.0
resolution: "chokidar@npm:3.6.0"
dependencies:
@@ -11731,20 +11199,6 @@ __metadata:
languageName: node
linkType: hard
-"classic-level@npm:^1.2.0":
- version: 1.4.1
- resolution: "classic-level@npm:1.4.1"
- dependencies:
- abstract-level: "npm:^1.0.2"
- catering: "npm:^2.1.0"
- module-error: "npm:^1.0.1"
- napi-macros: "npm:^2.2.2"
- node-gyp: "npm:latest"
- node-gyp-build: "npm:^4.3.0"
- checksum: 10c0/ba769e0b558ab466cef9468f7494b67d8f0139768630ba184d67b33201e67aad274718f3b1b78aaf3c5f5ab4cc526971edf82c7060741031bbad357952e7304d
- languageName: node
- linkType: hard
-
"classnames@npm:^2.2.6":
version: 2.5.1
resolution: "classnames@npm:2.5.1"
@@ -12074,13 +11528,6 @@ __metadata:
languageName: node
linkType: hard
-"commander@npm:3.0.2":
- version: 3.0.2
- resolution: "commander@npm:3.0.2"
- checksum: 10c0/8a279b4bacde68f03664086260ccb623122d2bdae6f380a41c9e06b646e830372c30a4b88261238550e0ad69d53f7af8883cb705d8237fdd22947e84913b149c
- languageName: node
- linkType: hard
-
"commander@npm:^10.0.1":
version: 10.0.1
resolution: "commander@npm:10.0.1"
@@ -12271,13 +11718,6 @@ __metadata:
languageName: node
linkType: hard
-"cookie@npm:^0.4.1":
- version: 0.4.2
- resolution: "cookie@npm:0.4.2"
- checksum: 10c0/beab41fbd7c20175e3a2799ba948c1dcc71ef69f23fe14eeeff59fc09f50c517b0f77098db87dbb4c55da802f9d86ee86cdc1cd3efd87760341551838d53fca2
- languageName: node
- linkType: hard
-
"copy-descriptor@npm:^0.1.0":
version: 0.1.1
resolution: "copy-descriptor@npm:0.1.1"
@@ -12824,7 +12264,7 @@ __metadata:
languageName: node
linkType: hard
-"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:~4.3.1, debug@npm:~4.3.2":
+"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.1, debug@npm:~4.3.2":
version: 4.3.5
resolution: "debug@npm:4.3.5"
dependencies:
@@ -13416,13 +12856,6 @@ __metadata:
languageName: node
linkType: hard
-"diff@npm:^5.2.0":
- version: 5.2.0
- resolution: "diff@npm:5.2.0"
- checksum: 10c0/aed0941f206fe261ecb258dc8d0ceea8abbde3ace5827518ff8d302f0fc9cc81ce116c4d8f379151171336caf0516b79e01abdc1ed1201b6440d895a66689eb4
- languageName: node
- linkType: hard
-
"diffie-hellman@npm:^5.0.0":
version: 5.0.3
resolution: "diffie-hellman@npm:5.0.3"
@@ -13836,7 +13269,7 @@ __metadata:
languageName: node
linkType: hard
-"enquirer@npm:^2.3.0, enquirer@npm:^2.3.5, enquirer@npm:^2.3.6":
+"enquirer@npm:^2.3.5, enquirer@npm:^2.3.6":
version: 2.4.1
resolution: "enquirer@npm:2.4.1"
dependencies:
@@ -14556,7 +13989,7 @@ __metadata:
languageName: node
linkType: hard
-"ethereum-cryptography@npm:0.1.3, ethereum-cryptography@npm:^0.1.3":
+"ethereum-cryptography@npm:^0.1.3":
version: 0.1.3
resolution: "ethereum-cryptography@npm:0.1.3"
dependencies:
@@ -14579,18 +14012,6 @@ __metadata:
languageName: node
linkType: hard
-"ethereum-cryptography@npm:^1.0.3":
- version: 1.2.0
- resolution: "ethereum-cryptography@npm:1.2.0"
- dependencies:
- "@noble/hashes": "npm:1.2.0"
- "@noble/secp256k1": "npm:1.7.1"
- "@scure/bip32": "npm:1.1.5"
- "@scure/bip39": "npm:1.1.1"
- checksum: 10c0/93e486a4a8b266dc2f274b69252e751345ef47551163371939b01231afb7b519133e2572b5975bb9cb4cc77ac54ccd36002c7c759a72488abeeaf216e4d55b46
- languageName: node
- linkType: hard
-
"ethereum-cryptography@npm:^2.0.0, ethereum-cryptography@npm:^2.1.2":
version: 2.2.1
resolution: "ethereum-cryptography@npm:2.2.1"
@@ -14603,17 +14024,7 @@ __metadata:
languageName: node
linkType: hard
-"ethereumjs-abi@npm:^0.6.8":
- version: 0.6.8
- resolution: "ethereumjs-abi@npm:0.6.8"
- dependencies:
- bn.js: "npm:^4.11.8"
- ethereumjs-util: "npm:^6.0.0"
- checksum: 10c0/a7ff1917625e3c812cb3bca6c1231fc0ece282cc7d202d60545a9c31cd379fd751bfed5ff78dae4279cb1ba4d0e8967f9fdd4f135a334a38dbf04e7afd0c4bcf
- languageName: node
- linkType: hard
-
-"ethereumjs-util@npm:6.2.1, ethereumjs-util@npm:^6.0.0, ethereumjs-util@npm:^6.2.1":
+"ethereumjs-util@npm:6.2.1":
version: 6.2.1
resolution: "ethereumjs-util@npm:6.2.1"
dependencies:
@@ -14657,7 +14068,7 @@ __metadata:
languageName: node
linkType: hard
-"ethers@npm:5.7.2, ethers@npm:^5.7.1":
+"ethers@npm:5.7.2":
version: 5.7.2
resolution: "ethers@npm:5.7.2"
dependencies:
@@ -15416,15 +14827,6 @@ __metadata:
languageName: node
linkType: hard
-"find-up@npm:^2.1.0":
- version: 2.1.0
- resolution: "find-up@npm:2.1.0"
- dependencies:
- locate-path: "npm:^2.0.0"
- checksum: 10c0/c080875c9fe28eb1962f35cbe83c683796a0321899f1eed31a37577800055539815de13d53495049697d3ba313013344f843bb9401dd337a1b832be5edfc6840
- languageName: node
- linkType: hard
-
"find-up@npm:^3.0.0":
version: 3.0.0
resolution: "find-up@npm:3.0.0"
@@ -15539,7 +14941,7 @@ __metadata:
languageName: node
linkType: hard
-"follow-redirects@npm:^1.12.1, follow-redirects@npm:^1.15.6":
+"follow-redirects@npm:^1.15.6":
version: 1.15.6
resolution: "follow-redirects@npm:1.15.6"
peerDependenciesMeta:
@@ -15636,20 +15038,6 @@ __metadata:
languageName: node
linkType: hard
-"fp-ts@npm:1.19.3":
- version: 1.19.3
- resolution: "fp-ts@npm:1.19.3"
- checksum: 10c0/a016cfc98ad5e61564ab2d53a5379bbb8254642b66df13ced47e8c1d8d507abf4588d8bb43530198dfe1907211d8bae8f112cab52ba0ac6ab055da9168a6e260
- languageName: node
- linkType: hard
-
-"fp-ts@npm:^1.0.0":
- version: 1.19.5
- resolution: "fp-ts@npm:1.19.5"
- checksum: 10c0/2a330fa1779561307740c26a7255fdffeb1ca2d0c7448d4dc094b477b772b0c8f7da1dfc88569b6f13f6958169b63b5df7361e514535b46b2e215bbf03a3722d
- languageName: node
- linkType: hard
-
"fragment-cache@npm:^0.2.1":
version: 0.2.1
resolution: "fragment-cache@npm:0.2.1"
@@ -15740,19 +15128,6 @@ __metadata:
languageName: node
linkType: hard
-"fs-extra@npm:^0.30.0":
- version: 0.30.0
- resolution: "fs-extra@npm:0.30.0"
- dependencies:
- graceful-fs: "npm:^4.1.2"
- jsonfile: "npm:^2.1.0"
- klaw: "npm:^1.0.0"
- path-is-absolute: "npm:^1.0.0"
- rimraf: "npm:^2.2.8"
- checksum: 10c0/24f3c966018c7bf436bf38ca3a126f1d95bf0f82598302195c4f0c8887767f045dae308f92c53a39cead74631dabbc30fcf1c71dbe96f1f0148f6de8edd114bc
- languageName: node
- linkType: hard
-
"fs-extra@npm:^11.0.0":
version: 11.2.0
resolution: "fs-extra@npm:11.2.0"
@@ -15764,17 +15139,6 @@ __metadata:
languageName: node
linkType: hard
-"fs-extra@npm:^7.0.1":
- version: 7.0.1
- resolution: "fs-extra@npm:7.0.1"
- dependencies:
- graceful-fs: "npm:^4.1.2"
- jsonfile: "npm:^4.0.0"
- universalify: "npm:^0.1.0"
- checksum: 10c0/1943bb2150007e3739921b8d13d4109abdc3cc481e53b97b7ea7f77eda1c3c642e27ae49eac3af074e3496ea02fde30f411ef410c760c70a38b92e656e5da784
- languageName: node
- linkType: hard
-
"fs-extra@npm:^8.1.0, fs-extra@npm:~8.1.0":
version: 8.1.0
resolution: "fs-extra@npm:8.1.0"
@@ -16122,20 +15486,6 @@ __metadata:
languageName: node
linkType: hard
-"glob@npm:7.2.0":
- version: 7.2.0
- resolution: "glob@npm:7.2.0"
- dependencies:
- fs.realpath: "npm:^1.0.0"
- inflight: "npm:^1.0.4"
- inherits: "npm:2"
- minimatch: "npm:^3.0.4"
- once: "npm:^1.3.0"
- path-is-absolute: "npm:^1.0.0"
- checksum: 10c0/478b40e38be5a3d514e64950e1e07e0ac120585add6a37c98d0ed24d72d9127d734d2a125786073c8deb687096e84ae82b641c441a869ada3a9cc91b68978632
- languageName: node
- linkType: hard
-
"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.2":
version: 10.4.5
resolution: "glob@npm:10.4.5"
@@ -16179,7 +15529,7 @@ __metadata:
languageName: node
linkType: hard
-"glob@npm:^8.0.1, glob@npm:^8.0.3, glob@npm:^8.1.0":
+"glob@npm:^8.0.1, glob@npm:^8.0.3":
version: 8.1.0
resolution: "glob@npm:8.1.0"
dependencies:
@@ -16311,7 +15661,7 @@ __metadata:
languageName: node
linkType: hard
-"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9":
+"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9":
version: 4.2.11
resolution: "graceful-fs@npm:4.2.11"
checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2
@@ -16421,72 +15771,6 @@ __metadata:
languageName: node
linkType: hard
-"hardhat@npm:2.18.1":
- version: 2.18.1
- resolution: "hardhat@npm:2.18.1"
- dependencies:
- "@ethersproject/abi": "npm:^5.1.2"
- "@metamask/eth-sig-util": "npm:^4.0.0"
- "@nomicfoundation/ethereumjs-block": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-blockchain": "npm:7.0.2"
- "@nomicfoundation/ethereumjs-common": "npm:4.0.2"
- "@nomicfoundation/ethereumjs-evm": "npm:2.0.2"
- "@nomicfoundation/ethereumjs-rlp": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-statemanager": "npm:2.0.2"
- "@nomicfoundation/ethereumjs-trie": "npm:6.0.2"
- "@nomicfoundation/ethereumjs-tx": "npm:5.0.2"
- "@nomicfoundation/ethereumjs-util": "npm:9.0.2"
- "@nomicfoundation/ethereumjs-vm": "npm:7.0.2"
- "@nomicfoundation/solidity-analyzer": "npm:^0.1.0"
- "@sentry/node": "npm:^5.18.1"
- "@types/bn.js": "npm:^5.1.0"
- "@types/lru-cache": "npm:^5.1.0"
- adm-zip: "npm:^0.4.16"
- aggregate-error: "npm:^3.0.0"
- ansi-escapes: "npm:^4.3.0"
- chalk: "npm:^2.4.2"
- chokidar: "npm:^3.4.0"
- ci-info: "npm:^2.0.0"
- debug: "npm:^4.1.1"
- enquirer: "npm:^2.3.0"
- env-paths: "npm:^2.2.0"
- ethereum-cryptography: "npm:^1.0.3"
- ethereumjs-abi: "npm:^0.6.8"
- find-up: "npm:^2.1.0"
- fp-ts: "npm:1.19.3"
- fs-extra: "npm:^7.0.1"
- glob: "npm:7.2.0"
- immutable: "npm:^4.0.0-rc.12"
- io-ts: "npm:1.10.4"
- keccak: "npm:^3.0.2"
- lodash: "npm:^4.17.11"
- mnemonist: "npm:^0.38.0"
- mocha: "npm:^10.0.0"
- p-map: "npm:^4.0.0"
- raw-body: "npm:^2.4.1"
- resolve: "npm:1.17.0"
- semver: "npm:^6.3.0"
- solc: "npm:0.7.3"
- source-map-support: "npm:^0.5.13"
- stacktrace-parser: "npm:^0.1.10"
- tsort: "npm:0.0.1"
- undici: "npm:^5.14.0"
- uuid: "npm:^8.3.2"
- ws: "npm:^7.4.6"
- peerDependencies:
- ts-node: "*"
- typescript: "*"
- peerDependenciesMeta:
- ts-node:
- optional: true
- typescript:
- optional: true
- bin:
- hardhat: internal/cli/bootstrap.js
- checksum: 10c0/9d6186ea9f91fe63b46b6ae9009eea2bdf4edbf722c71becb7477f73a6824bf5b227b77107cc3dcc4915a3ce38c68706fd76a3537753923d9443016c8087af85
- languageName: node
- linkType: hard
-
"has-ansi@npm:^2.0.0":
version: 2.0.0
resolution: "has-ansi@npm:2.0.0"
@@ -16642,7 +15926,7 @@ __metadata:
languageName: node
linkType: hard
-"he@npm:1.2.0, he@npm:^1.2.0":
+"he@npm:1.2.0":
version: 1.2.0
resolution: "he@npm:1.2.0"
bin:
@@ -16882,7 +16166,7 @@ __metadata:
languageName: node
linkType: hard
-"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.17":
+"iconv-lite@npm:^0.4.17":
version: 0.4.24
resolution: "iconv-lite@npm:0.4.24"
dependencies:
@@ -16992,13 +16276,6 @@ __metadata:
languageName: node
linkType: hard
-"immutable@npm:^4.0.0-rc.12":
- version: 4.3.6
- resolution: "immutable@npm:4.3.6"
- checksum: 10c0/7d0952a768b4fadcee47230ed86dc9505a4517095eceaf5a47e65288571c42400c6e4a2ae21eca4eda957cb7bc50720213135b62cf6a181639111f8acae128c3
- languageName: node
- linkType: hard
-
"import-fresh@npm:^2.0.0":
version: 2.0.0
resolution: "import-fresh@npm:2.0.0"
@@ -17169,15 +16446,6 @@ __metadata:
languageName: node
linkType: hard
-"io-ts@npm:1.10.4":
- version: 1.10.4
- resolution: "io-ts@npm:1.10.4"
- dependencies:
- fp-ts: "npm:^1.0.0"
- checksum: 10c0/9370988a7e17fc23c194115808168ccd1ccf7b7ebe92c39c1cc2fd91c1dc641552a5428bb04fe28c01c826fa4f230e856eb4f7d27c774a1400af3fecf2936ab5
- languageName: node
- linkType: hard
-
"ip-address@npm:^9.0.5":
version: 9.0.5
resolution: "ip-address@npm:9.0.5"
@@ -17294,13 +16562,6 @@ __metadata:
languageName: node
linkType: hard
-"is-buffer@npm:^2.0.5":
- version: 2.0.5
- resolution: "is-buffer@npm:2.0.5"
- checksum: 10c0/e603f6fced83cf94c53399cff3bda1a9f08e391b872b64a73793b0928be3e5f047f2bcece230edb7632eaea2acdbfcb56c23b33d8a20c820023b230f1485679a
- languageName: node
- linkType: hard
-
"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7":
version: 1.2.7
resolution: "is-callable@npm:1.2.7"
@@ -18512,13 +17773,6 @@ __metadata:
languageName: node
linkType: hard
-"js-sdsl@npm:^4.1.4":
- version: 4.4.2
- resolution: "js-sdsl@npm:4.4.2"
- checksum: 10c0/50707728fc31642164f4d83c8087f3750aaa99c450b008b19e236a1f190c9e48f9fc799615c341f9ca2c0803b15ab6f48d92a9cc3e6ffd20065cba7d7e742b92
- languageName: node
- linkType: hard
-
"js-sha256@npm:^0.9.0":
version: 0.9.0
resolution: "js-sha256@npm:0.9.0"
@@ -18882,7 +18136,7 @@ __metadata:
languageName: node
linkType: hard
-"keccak@npm:^3.0.0, keccak@npm:^3.0.2, keccak@npm:^3.0.3":
+"keccak@npm:^3.0.0, keccak@npm:^3.0.3":
version: 3.0.4
resolution: "keccak@npm:3.0.4"
dependencies:
@@ -18967,18 +18221,6 @@ __metadata:
languageName: node
linkType: hard
-"klaw@npm:^1.0.0":
- version: 1.3.1
- resolution: "klaw@npm:1.3.1"
- dependencies:
- graceful-fs: "npm:^4.1.9"
- dependenciesMeta:
- graceful-fs:
- optional: true
- checksum: 10c0/da994768b02b3843cc994c99bad3cf1c8c67716beb4dd2834133c919e9e9ee788669fbe27d88ab0ad9a3991349c28280afccbde01c2318229b662dd7a05e4728
- languageName: node
- linkType: hard
-
"kleur@npm:^3.0.3":
version: 3.0.3
resolution: "kleur@npm:3.0.3"
@@ -19074,34 +18316,6 @@ __metadata:
languageName: node
linkType: hard
-"level-supports@npm:^4.0.0":
- version: 4.0.1
- resolution: "level-supports@npm:4.0.1"
- checksum: 10c0/a94aa591786845d17c9c62ad075ae33e0fce5be714baa6e16305ed14e2d3638d09e724247fa3f63951e36de57ffd168d63e159e79d03944ee648054b8c7c1684
- languageName: node
- linkType: hard
-
-"level-transcoder@npm:^1.0.1":
- version: 1.0.1
- resolution: "level-transcoder@npm:1.0.1"
- dependencies:
- buffer: "npm:^6.0.3"
- module-error: "npm:^1.0.1"
- checksum: 10c0/25936330676325f22c5143aff5c7fe3f1db156db99f9efb07a2642045c2c6ee565fcbfccbadc0600b3abf8bbe595632cacc3dd334009214069d1857daa57987e
- languageName: node
- linkType: hard
-
-"level@npm:^8.0.0":
- version: 8.0.1
- resolution: "level@npm:8.0.1"
- dependencies:
- abstract-level: "npm:^1.0.4"
- browser-level: "npm:^1.0.1"
- classic-level: "npm:^1.2.0"
- checksum: 10c0/52e3c18a3372f22b7c0c96a998a24099454f51952ba2b8f25eabc72b1f7bbc15a3ab480c92c94d3c931be370be5a235b804b16e565b73b22bea52cca4029a625
- languageName: node
- linkType: hard
-
"levelup@npm:^0.18.2":
version: 0.18.6
resolution: "levelup@npm:0.18.6"
@@ -19363,16 +18577,6 @@ __metadata:
languageName: node
linkType: hard
-"locate-path@npm:^2.0.0":
- version: 2.0.0
- resolution: "locate-path@npm:2.0.0"
- dependencies:
- p-locate: "npm:^2.0.0"
- path-exists: "npm:^3.0.0"
- checksum: 10c0/24efa0e589be6aa3c469b502f795126b26ab97afa378846cb508174211515633b770aa0ba610cab113caedab8d2a4902b061a08aaed5297c12ab6f5be4df0133
- languageName: node
- linkType: hard
-
"locate-path@npm:^3.0.0":
version: 3.0.0
resolution: "locate-path@npm:3.0.0"
@@ -19558,13 +18762,6 @@ __metadata:
languageName: node
linkType: hard
-"lru_map@npm:^0.3.3":
- version: 0.3.3
- resolution: "lru_map@npm:0.3.3"
- checksum: 10c0/d861f14a142a4a74ebf8d3ad57f2e768a5b820db4100ae53eed1a64eb6350912332e6ebc87cb7415ad6d0cd8f3ce6d20beab9a5e6042ccb5996ea0067a220448
- languageName: node
- linkType: hard
-
"ltgt@npm:^2.1.3":
version: 2.2.1
resolution: "ltgt@npm:2.2.1"
@@ -19757,13 +18954,6 @@ __metadata:
languageName: node
linkType: hard
-"mcl-wasm@npm:^0.7.1":
- version: 0.7.9
- resolution: "mcl-wasm@npm:0.7.9"
- checksum: 10c0/12acd074621741ac61f4b3d36d72da6317320b5db02734abaaf77c0c7886ced14926de2f637ca9ab70a458419200d7edb8e0a4f9f02c85feb8d5bbbe430e60ad
- languageName: node
- linkType: hard
-
"md5-file@npm:^3.2.3":
version: 3.2.3
resolution: "md5-file@npm:3.2.3"
@@ -19860,24 +19050,6 @@ __metadata:
languageName: node
linkType: hard
-"memory-level@npm:^1.0.0":
- version: 1.0.0
- resolution: "memory-level@npm:1.0.0"
- dependencies:
- abstract-level: "npm:^1.0.0"
- functional-red-black-tree: "npm:^1.0.1"
- module-error: "npm:^1.0.1"
- checksum: 10c0/b926b6ddc43065282c240cd7c0bf44abcfe43d556f6bb3d43d21f5f514b0095abcd8f9ba26b31ffdefa4ce4931afb937a1eaea1f15c45e76d7061086dbcf9148
- languageName: node
- linkType: hard
-
-"memorystream@npm:^0.3.1":
- version: 0.3.1
- resolution: "memorystream@npm:0.3.1"
- checksum: 10c0/4bd164657711d9747ff5edb0508b2944414da3464b7fe21ac5c67cf35bba975c4b446a0124bd0f9a8be54cfc18faf92e92bd77563a20328b1ccf2ff04e9f39b9
- languageName: node
- linkType: hard
-
"merge-deep@npm:^3.0.2":
version: 3.0.3
resolution: "merge-deep@npm:3.0.3"
@@ -20312,7 +19484,7 @@ __metadata:
languageName: node
linkType: hard
-"minimatch@npm:^5.0.1, minimatch@npm:^5.1.0, minimatch@npm:^5.1.6":
+"minimatch@npm:^5.0.1, minimatch@npm:^5.1.0":
version: 5.1.6
resolution: "minimatch@npm:5.1.6"
dependencies:
@@ -20550,46 +19722,6 @@ __metadata:
languageName: node
linkType: hard
-"mnemonist@npm:^0.38.0":
- version: 0.38.5
- resolution: "mnemonist@npm:0.38.5"
- dependencies:
- obliterator: "npm:^2.0.0"
- checksum: 10c0/a73a2718f88cd12c3b108ecc530619a1b0f2783d479c7f98e7367375102cc3a28811bab384e17eb731553dc8d7ee9d60283d694a9f676af5f306104e75027d4f
- languageName: node
- linkType: hard
-
-"mocha@npm:^10.0.0":
- version: 10.6.0
- resolution: "mocha@npm:10.6.0"
- dependencies:
- ansi-colors: "npm:^4.1.3"
- browser-stdout: "npm:^1.3.1"
- chokidar: "npm:^3.5.3"
- debug: "npm:^4.3.5"
- diff: "npm:^5.2.0"
- escape-string-regexp: "npm:^4.0.0"
- find-up: "npm:^5.0.0"
- glob: "npm:^8.1.0"
- he: "npm:^1.2.0"
- js-yaml: "npm:^4.1.0"
- log-symbols: "npm:^4.1.0"
- minimatch: "npm:^5.1.6"
- ms: "npm:^2.1.3"
- serialize-javascript: "npm:^6.0.2"
- strip-json-comments: "npm:^3.1.1"
- supports-color: "npm:^8.1.1"
- workerpool: "npm:^6.5.1"
- yargs: "npm:^16.2.0"
- yargs-parser: "npm:^20.2.9"
- yargs-unparser: "npm:^2.0.0"
- bin:
- _mocha: bin/_mocha
- mocha: bin/mocha.js
- checksum: 10c0/30b2f810014af6b5701563c6ee6ee78708dcfefc1551801c70018682bc6ca9327a6a27e93c101905a355d130a1ffe1f990975d51459c289bfcb72726ea5f7a50
- languageName: node
- linkType: hard
-
"module-definition@npm:^3.0.0, module-definition@npm:^3.3.0":
version: 3.4.0
resolution: "module-definition@npm:3.4.0"
@@ -20602,13 +19734,6 @@ __metadata:
languageName: node
linkType: hard
-"module-error@npm:^1.0.1, module-error@npm:^1.0.2":
- version: 1.0.2
- resolution: "module-error@npm:1.0.2"
- checksum: 10c0/584a43a1bb2720c6c6c771e257a308af4f042a17c17b1472a2c855130a1ad93ba516a82ae7ac2ce2d03062e521cc53c03ec0ce153795d895312d7747fb3bb99b
- languageName: node
- linkType: hard
-
"module-lookup-amd@npm:^6.1.0":
version: 6.2.0
resolution: "module-lookup-amd@npm:6.2.0"
@@ -20664,7 +19789,7 @@ __metadata:
languageName: node
linkType: hard
-"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3":
+"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1":
version: 2.1.3
resolution: "ms@npm:2.1.3"
checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48
@@ -20857,13 +19982,6 @@ __metadata:
languageName: node
linkType: hard
-"napi-macros@npm:^2.2.2":
- version: 2.2.2
- resolution: "napi-macros@npm:2.2.2"
- checksum: 10c0/cc85daaf82a4f585d30561047cef0f3e702be769b5cf2ffadc6242bc5a1033f6b8269012e54178baf66f022bd18aa9ebb619f1b530cc19c1f9b96f9689affd50
- languageName: node
- linkType: hard
-
"napi-wasm@npm:^1.1.0":
version: 1.1.0
resolution: "napi-wasm@npm:1.1.0"
@@ -21043,7 +20161,7 @@ __metadata:
languageName: node
linkType: hard
-"node-gyp-build@npm:^4.2.0, node-gyp-build@npm:^4.3.0":
+"node-gyp-build@npm:^4.2.0":
version: 4.8.1
resolution: "node-gyp-build@npm:4.8.1"
bin:
@@ -21453,13 +20571,6 @@ __metadata:
languageName: node
linkType: hard
-"obliterator@npm:^2.0.0":
- version: 2.0.4
- resolution: "obliterator@npm:2.0.4"
- checksum: 10c0/ff2c10d4de7d62cd1d588b4d18dfc42f246c9e3a259f60d5716f7f88e5b3a3f79856b3207db96ec9a836a01d0958a21c15afa62a3f4e73a1e0b75f2c2f6bab40
- languageName: node
- linkType: hard
-
"octal@npm:^1.0.0":
version: 1.0.0
resolution: "octal@npm:1.0.0"
@@ -21733,15 +20844,6 @@ __metadata:
languageName: node
linkType: hard
-"p-limit@npm:^1.1.0":
- version: 1.3.0
- resolution: "p-limit@npm:1.3.0"
- dependencies:
- p-try: "npm:^1.0.0"
- checksum: 10c0/5c1b1d53d180b2c7501efb04b7c817448e10efe1ba46f4783f8951994d5027e4cd88f36ad79af50546682594c4ebd11702ac4b9364c47f8074890e2acad0edee
- languageName: node
- linkType: hard
-
"p-limit@npm:^2.0.0, p-limit@npm:^2.2.0":
version: 2.3.0
resolution: "p-limit@npm:2.3.0"
@@ -21760,15 +20862,6 @@ __metadata:
languageName: node
linkType: hard
-"p-locate@npm:^2.0.0":
- version: 2.0.0
- resolution: "p-locate@npm:2.0.0"
- dependencies:
- p-limit: "npm:^1.1.0"
- checksum: 10c0/82da4be88fb02fd29175e66021610c881938d3cc97c813c71c1a605fac05617d57fd5d3b337494a6106c0edb2a37c860241430851411f1b265108cead34aee67
- languageName: node
- linkType: hard
-
"p-locate@npm:^3.0.0":
version: 3.0.0
resolution: "p-locate@npm:3.0.0"
@@ -21812,13 +20905,6 @@ __metadata:
languageName: node
linkType: hard
-"p-try@npm:^1.0.0":
- version: 1.0.0
- resolution: "p-try@npm:1.0.0"
- checksum: 10c0/757ba31de5819502b80c447826fac8be5f16d3cb4fbf9bc8bc4971dba0682e84ac33e4b24176ca7058c69e29f64f34d8d9e9b08e873b7b7bb0aa89d620fa224a
- languageName: node
- linkType: hard
-
"p-try@npm:^2.0.0":
version: 2.2.0
resolution: "p-try@npm:2.2.0"
@@ -22074,7 +21160,7 @@ __metadata:
languageName: node
linkType: hard
-"path-parse@npm:^1.0.5, path-parse@npm:^1.0.6, path-parse@npm:^1.0.7":
+"path-parse@npm:^1.0.5, path-parse@npm:^1.0.7":
version: 1.0.7
resolution: "path-parse@npm:1.0.7"
checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1
@@ -22889,7 +21975,7 @@ __metadata:
languageName: node
linkType: hard
-"queue-microtask@npm:^1.2.2, queue-microtask@npm:^1.2.3":
+"queue-microtask@npm:^1.2.2":
version: 1.2.3
resolution: "queue-microtask@npm:1.2.3"
checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102
@@ -22952,18 +22038,6 @@ __metadata:
languageName: node
linkType: hard
-"raw-body@npm:^2.4.1":
- version: 2.5.2
- resolution: "raw-body@npm:2.5.2"
- dependencies:
- bytes: "npm:3.1.2"
- http-errors: "npm:2.0.0"
- iconv-lite: "npm:0.4.24"
- unpipe: "npm:1.0.0"
- checksum: 10c0/b201c4b66049369a60e766318caff5cb3cc5a900efd89bdac431463822d976ad0670912c931fdbdcf5543207daf6f6833bca57aa116e1661d2ea91e12ca692c4
- languageName: node
- linkType: hard
-
"rc@npm:^1.2.7, rc@npm:~1.2.7":
version: 1.2.8
resolution: "rc@npm:1.2.8"
@@ -24492,7 +23566,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"require-from-string@npm:^2.0.0, require-from-string@npm:^2.0.2":
+"require-from-string@npm:^2.0.2":
version: 2.0.2
resolution: "require-from-string@npm:2.0.2"
checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2
@@ -24634,15 +23708,6 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"resolve@npm:1.17.0":
- version: 1.17.0
- resolution: "resolve@npm:1.17.0"
- dependencies:
- path-parse: "npm:^1.0.6"
- checksum: 10c0/4e6c76cc1a7b08bff637b092ce035d7901465067915605bc5a23ac0c10fe42ec205fc209d5d5f7a5f27f37ce71d687def7f656bbb003631cd46a8374f55ec73d
- languageName: node
- linkType: hard
-
"resolve@npm:1.22.8, resolve@npm:^1.11.1, resolve@npm:^1.14.2, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.2, resolve@npm:^1.22.3, resolve@npm:^1.22.4, resolve@npm:^1.22.8":
version: 1.22.8
resolution: "resolve@npm:1.22.8"
@@ -24678,15 +23743,6 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"resolve@patch:resolve@npm%3A1.17.0#optional!builtin":
- version: 1.17.0
- resolution: "resolve@patch:resolve@npm%3A1.17.0#optional!builtin::version=1.17.0&hash=c3c19d"
- dependencies:
- path-parse: "npm:^1.0.6"
- checksum: 10c0/e072e52be3c3dbfd086761115db4a5136753e7aefc0e665e66e7307ddcd9d6b740274516055c74aee44921625e95993f03570450aa310b8d73b1c9daa056c4cd
- languageName: node
- linkType: hard
-
"resolve@patch:resolve@npm%3A1.22.8#optional!builtin, resolve@patch:resolve@npm%3A^1.11.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin, resolve@patch:resolve@npm%3A^1.22.3#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin":
version: 1.22.8
resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d"
@@ -24871,15 +23927,6 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"run-parallel-limit@npm:^1.1.0":
- version: 1.1.0
- resolution: "run-parallel-limit@npm:1.1.0"
- dependencies:
- queue-microtask: "npm:^1.2.2"
- checksum: 10c0/9c78eb77e788d0ed803a7e80921412f6f6accfb2006de8c21699d9ebf7696df9cefaa313fe14d6169a3fc9f564b34fe91bfd9948cc3a58e2d24136a2390523ae
- languageName: node
- linkType: hard
-
"run-parallel@npm:^1.1.2, run-parallel@npm:^1.1.9":
version: 1.2.0
resolution: "run-parallel@npm:1.2.0"
@@ -24889,13 +23936,6 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"rustbn.js@npm:~0.2.0":
- version: 0.2.0
- resolution: "rustbn.js@npm:0.2.0"
- checksum: 10c0/be2d55d4a53465cfd5c7900153cfae54c904f0941acd30191009cf473cacbfcf45082ffd8dc473a354c8e3dcfe2c2bdf5d7ea9cc9b188d892b4aa8d012b94701
- languageName: node
- linkType: hard
-
"rx@npm:^4.1.0":
version: 4.1.0
resolution: "rx@npm:4.1.0"
@@ -25231,7 +24271,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"serialize-javascript@npm:^6.0.1, serialize-javascript@npm:^6.0.2":
+"serialize-javascript@npm:^6.0.1":
version: 6.0.2
resolution: "serialize-javascript@npm:6.0.2"
dependencies:
@@ -25677,25 +24717,6 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"solc@npm:0.7.3":
- version: 0.7.3
- resolution: "solc@npm:0.7.3"
- dependencies:
- command-exists: "npm:^1.2.8"
- commander: "npm:3.0.2"
- follow-redirects: "npm:^1.12.1"
- fs-extra: "npm:^0.30.0"
- js-sha3: "npm:0.8.0"
- memorystream: "npm:^0.3.1"
- require-from-string: "npm:^2.0.0"
- semver: "npm:^5.5.0"
- tmp: "npm:0.0.33"
- bin:
- solcjs: solcjs
- checksum: 10c0/28405adfba1f55603dc5b674630383bfbdbfab2d36deba2ff0a90c46cbc346bcabf0ed6175e12ae3c0b751ef082d0405ab42dcc24f88603a446e097a925d7425
- languageName: node
- linkType: hard
-
"sonic-boom@npm:^2.2.1":
version: 2.8.0
resolution: "sonic-boom@npm:2.8.0"
@@ -25761,7 +24782,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"source-map-support@npm:^0.5.13, source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.20, source-map-support@npm:~0.5.21":
+"source-map-support@npm:^0.5.16, source-map-support@npm:~0.5.20, source-map-support@npm:~0.5.21":
version: 0.5.21
resolution: "source-map-support@npm:0.5.21"
dependencies:
@@ -26423,7 +25444,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1":
+"supports-color@npm:^8.0.0":
version: 8.1.1
resolution: "supports-color@npm:8.1.1"
dependencies:
@@ -26857,7 +25878,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"tmp@npm:0.0.33, tmp@npm:^0.0.33":
+"tmp@npm:^0.0.33":
version: 0.0.33
resolution: "tmp@npm:0.0.33"
dependencies:
@@ -27130,7 +26151,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"tslib@npm:1.14.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3":
+"tslib@npm:1.14.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0":
version: 1.14.1
resolution: "tslib@npm:1.14.1"
checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2
@@ -27144,13 +26165,6 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"tsort@npm:0.0.1":
- version: 0.0.1
- resolution: "tsort@npm:0.0.1"
- checksum: 10c0/ea3d034ab341dd9282c972710496e98539408d77f1cd476ad0551a9731f40586b65ab917b39745f902bf32037a3161eee3821405f6ab15bcd2ce4cc0a52d1da6
- languageName: node
- linkType: hard
-
"tsutils@npm:3, tsutils@npm:^3.17.1, tsutils@npm:^3.21.0":
version: 3.21.0
resolution: "tsutils@npm:3.21.0"
@@ -27501,15 +26515,6 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"undici@npm:^5.14.0":
- version: 5.28.4
- resolution: "undici@npm:5.28.4"
- dependencies:
- "@fastify/busboy": "npm:^2.0.0"
- checksum: 10c0/08d0f2596553aa0a54ca6e8e9c7f45aef7d042c60918564e3a142d449eda165a80196f6ef19ea2ef2e6446959e293095d8e40af1236f0d67223b06afac5ecad7
- languageName: node
- linkType: hard
-
"unenv@npm:^1.9.0":
version: 1.10.0
resolution: "unenv@npm:1.10.0"
@@ -27648,7 +26653,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"unpipe@npm:1.0.0, unpipe@npm:~1.0.0":
+"unpipe@npm:~1.0.0":
version: 1.0.0
resolution: "unpipe@npm:1.0.0"
checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c
@@ -28499,13 +27504,6 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"workerpool@npm:^6.5.1":
- version: 6.5.1
- resolution: "workerpool@npm:6.5.1"
- checksum: 10c0/58e8e969782292cb3a7bfba823f1179a7615250a0cefb4841d5166234db1880a3d0fe83a31dd8d648329ec92c2d0cd1890ad9ec9e53674bb36ca43e9753cdeac
- languageName: node
- linkType: hard
-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0":
version: 7.0.0
resolution: "wrap-ansi@npm:7.0.0"
@@ -28657,7 +27655,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"ws@npm:^7, ws@npm:^7.0.0, ws@npm:^7.4.6, ws@npm:^7.5.1":
+"ws@npm:^7, ws@npm:^7.0.0, ws@npm:^7.5.1":
version: 7.5.10
resolution: "ws@npm:7.5.10"
peerDependencies:
@@ -28882,7 +27880,7 @@ react-native-safe-area-view@rainbow-me/react-native-safe-area-view:
languageName: node
linkType: hard
-"yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.9":
+"yargs-parser@npm:^20.2.2":
version: 20.2.9
resolution: "yargs-parser@npm:20.2.9"
checksum: 10c0/0685a8e58bbfb57fab6aefe03c6da904a59769bd803a722bb098bd5b0f29d274a1357762c7258fb487512811b8063fb5d2824a3415a0a4540598335b3b086c72