diff --git a/.env b/.env index cb485083b..f06350b06 100644 --- a/.env +++ b/.env @@ -10,3 +10,5 @@ DEFAULT_CHAIN_TYPE=TESTNET RIF_WALLET_KEY=RIF_WALLET WALLETCONNECT2_PROJECT_ID=d9224e919473fd749ba8298879ce7569 + +USE_RELAY=false diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 000000000..8d8e30f30 --- /dev/null +++ b/env.d.ts @@ -0,0 +1,14 @@ +declare module 'react-native-config' { + export interface NativeConfig { + APP_ENV: string + RIF_WALLET_SERVICE_URL: string + RIF_WALLET_SERVICE_PUBLIC_KEY: string + DEFAULT_CHAIN_TYPE: string + RIF_WALLET_KEY: string + WALLETCONNECT2_PROJECT_ID: string + USE_RELAY: string + } + + export const Config: NativeConfig + export default Config +} diff --git a/package.json b/package.json index a86c01f75..1ed900e91 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,10 @@ "@react-navigation/stack": "^6.0.9", "@reduxjs/toolkit": "^1.9.0", "@rsksmart/rif-id-mnemonic": "^0.1.1", - "@rsksmart/rif-relay-light-sdk": "^1.0.17", + "@rsksmart/rif-relay-light-sdk": "1.1.1", "@rsksmart/rif-wallet-abi-enhancer": "^1.0.10", "@rsksmart/rif-wallet-adapters": "^1.0.4", "@rsksmart/rif-wallet-bitcoin": "^1.2.3", - "@rsksmart/rif-wallet-core": "^1.0.5", "@rsksmart/rif-wallet-eip681": "1.0.1", "@rsksmart/rif-wallet-services": "^1.2.2", "@rsksmart/rif-wallet-token": "^1.0.2", diff --git a/src/components/accounts/AccountBox.tsx b/src/components/accounts/AccountBox.tsx index f1584f771..2c8ed6a22 100644 --- a/src/components/accounts/AccountBox.tsx +++ b/src/components/accounts/AccountBox.tsx @@ -30,7 +30,7 @@ import { CheckIcon } from '../icons/CheckIcon' interface AccountBoxProps { address: string - smartWalletAddress: string + smartWalletAddress: string | null chainId: ChainTypesByIdType walletIsDeployed: WalletIsDeployed publicKeys: PublicKeyItemType[] @@ -54,10 +54,8 @@ export const AccountBox = ({ useState(false) const eoaAddressObject = getAddressDisplayText(address ?? '', chainId) - const smartWalletAddressObject = getAddressDisplayText( - smartWalletAddress ?? '', - chainId, - ) + const smartWalletAddressObject = + smartWalletAddress && getAddressDisplayText(smartWalletAddress, chainId) const onEdit = () => setShowAccountInput(true) const onChangeAccountName = (text: string) => { @@ -93,7 +91,7 @@ export const AccountBox = ({ - {t('settings_screen_edit_name_label')} + {t('accounts_screen_edit_name_label')} @@ -118,12 +116,12 @@ export const AccountBox = ({ )} - {t('settings_screen_status_label')} + {t('accounts_screen_status_label')} {walletIsDeployed.isDeployed - ? t('settings_screen_deployed_label') - : t('settings_screen_not_deployed_label')} + ? t('accounts_screen_deployed_label') + : t('accounts_screen_not_deployed_label')} {walletIsDeployed.isDeployed ? ( - - Clipboard.setString( - smartWalletAddressObject.checksumAddress || '', - ) - } - /> - } - placeholder={smartWalletAddressObject.displayAddress} - isReadOnly - testID={'TestID.smartWalletAddress'} - /> + {smartWalletAddressObject && ( + + Clipboard.setString( + smartWalletAddressObject.checksumAddress || '', + ) + } + /> + } + placeholder={smartWalletAddressObject.displayAddress} + isReadOnly + testID={'TestID.smartWalletAddress'} + /> + )} {publicKeys.map(publicKey => ( { smartWalletAddress: testnetCase.lower, } test('same address', () => - expect(isMyAddress(wallet, testnetCase.checksummed)).toBeTruthy()) + expect( + isMyAddress(wallet.smartWalletAddress, testnetCase.checksummed), + ).toBeTruthy()) test('different address', () => - expect(isMyAddress(wallet, '0x1234567890')).toBeFalsy()) + expect( + isMyAddress(wallet.smartWalletAddress, '0x1234567890'), + ).toBeFalsy()) }) }) diff --git a/src/components/address/lib.ts b/src/components/address/lib.ts index 99117e0be..e2b29ecd8 100644 --- a/src/components/address/lib.ts +++ b/src/components/address/lib.ts @@ -55,15 +55,11 @@ export const validateAddress = ( } export const isMyAddress = ( - wallet: { smartWalletAddress: string } | null, - address: string, + userAddress: string, + compareAddress: string, ): boolean => { - if (wallet) { - const myAddress = toChecksumAddress(wallet.smartWalletAddress) - return myAddress.toLowerCase() === address?.toLowerCase() - } - - return false + const myAddress = toChecksumAddress(userAddress) + return myAddress.toLowerCase() === compareAddress.toLowerCase() } export const getAddressDisplayText = ( diff --git a/src/components/loading/LoadingScreen.tsx b/src/components/loading/LoadingScreen.tsx index 677503e55..2facb1022 100644 --- a/src/components/loading/LoadingScreen.tsx +++ b/src/components/loading/LoadingScreen.tsx @@ -4,9 +4,13 @@ import { sharedColors, sharedStyles } from 'shared/constants' import { castStyle } from 'shared/utils' import { AppSpinner } from 'components/index' -export const LoadingScreen = () => { +interface Props { + isVisible: boolean +} + +export const LoadingScreen = ({ isVisible }: Props) => { return ( - + {hide ? '\u002A\u002A\u002A\u002A\u002A\u002A' - : secondValue?.balance} + : secondValue + ? formatTokenValues(secondValue.balance) + : ''} )} {error && ( diff --git a/src/core/Core.tsx b/src/core/Core.tsx index de593b444..6f136b0d1 100644 --- a/src/core/Core.tsx +++ b/src/core/Core.tsx @@ -11,13 +11,11 @@ import { RootTabsParamsList, } from 'navigation/rootNavigator' import { RequestHandler } from 'src/ux/requestsModal/RequestHandler' -import { LoadingScreen } from 'components/loading/LoadingScreen' import { useAppDispatch, useAppSelector } from 'store/storeUtils' import { closeRequest, selectRequests, selectTopColor, - selectWholeSettingsState, unlockApp, } from 'store/slices/settingsSlice' import { sharedStyles } from 'shared/constants' @@ -33,11 +31,10 @@ export const navigationContainerRef = export const Core = () => { const dispatch = useAppDispatch() - const settings = useAppSelector(selectWholeSettingsState) const requests = useAppSelector(selectRequests) const topColor = useAppSelector(selectTopColor) const isOffline = useIsOffline() - const { unlocked, active } = useStateSubscription() + const { active } = useStateSubscription() const { wallet, initializeWallet } = useContext(WalletContext) const unlockAppFn = useCallback(async () => { @@ -59,19 +56,15 @@ export const Core = () => { {!active && } - {settings.loading && !unlocked ? ( - - ) : ( - <> - - {requests.length !== 0 && ( - dispatch(closeRequest())} - /> - )} - - )} + <> + + {requests.length !== 0 && ( + dispatch(closeRequest())} + /> + )} + diff --git a/src/core/config.ts b/src/core/config.ts index 27fb00d3e..c644d3c43 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -2,9 +2,14 @@ import testnetContracts from '@rsksmart/rsk-testnet-contract-metadata' import mainnetContracts from '@rsksmart/rsk-contract-metadata' import config from 'config.json' import ReactNativeConfig from 'react-native-config' +import { constants } from 'ethers' -import { ChainTypeEnum } from 'shared/constants/chainConstants' +import { + ChainTypeEnum, + ChainTypesByIdType, +} from 'shared/constants/chainConstants' import { SETTINGS } from 'core/types' +import { TokenSymbol } from 'screens/home/TokenImage' /** * This function will get the environment settings from the config.json @@ -33,9 +38,11 @@ export const getWalletSetting = ( */ export const getEnvSetting = (setting: SETTINGS) => ReactNativeConfig[setting] -export const getTokenAddress = (symbol: string, chainType: ChainTypeEnum) => { - const contracts = - chainType === ChainTypeEnum.TESTNET ? testnetContracts : mainnetContracts +export const getTokenAddress = ( + symbol: TokenSymbol, + chainId: ChainTypesByIdType, +) => { + const contracts = chainId === 31 ? testnetContracts : mainnetContracts const result = Object.keys(contracts).find( (address: string) => @@ -43,8 +50,12 @@ export const getTokenAddress = (symbol: string, chainType: ChainTypeEnum) => { ) if (!result) { + if (symbol === TokenSymbol.TRBTC || symbol === TokenSymbol.RBTC) { + return constants.AddressZero.toLowerCase() + } + throw new Error( - `Token with the symbol ${symbol} not found on ${chainType}. Did you forget a t?`, + `Token with the symbol ${symbol} not found on ${chainId}. Did you forget a t?`, ) } return result.toLowerCase() diff --git a/src/core/operations.ts b/src/core/operations.ts deleted file mode 100644 index 619b67c3a..000000000 --- a/src/core/operations.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Wallet, providers } from 'ethers' -import { RifRelayConfig } from '@rsksmart/rif-relay-light-sdk' -import { OnRequest, RIFWallet } from '@rsksmart/rif-wallet-core' - -import { KeyManagementSystem } from 'lib/core' - -import { saveKeys } from 'storage/SecureStorage' -import { - ChainTypesByIdType, - chainTypesById, -} from 'shared/constants/chainConstants' -import { MMKVStorage } from 'storage/MMKVStorage' -import { AppDispatch } from 'src/redux' -import { onRequest } from 'src/redux/slices/settingsSlice' - -import { getWalletSetting } from './config' -import { SETTINGS } from './types' - -export const getRifRelayConfig = (chainId: 30 | 31): RifRelayConfig => { - return { - smartWalletFactoryAddress: getWalletSetting( - SETTINGS.SMART_WALLET_FACTORY_ADDRESS, - chainTypesById[chainId], - ), - relayVerifierAddress: getWalletSetting( - SETTINGS.RELAY_VERIFIER_ADDRESS, - chainTypesById[chainId], - ), - deployVerifierAddress: getWalletSetting( - SETTINGS.DEPLOY_VERIFIER_ADDRESS, - chainTypesById[chainId], - ), - relayServer: getWalletSetting( - SETTINGS.RIF_RELAY_SERVER, - chainTypesById[chainId], - ), - } -} -// function creates RIF Wallet instance -// along with necessary confings -const createRIFWallet = async ( - chainId: 30 | 31, - wallet: Wallet, - onRequestFn: OnRequest, -) => { - const jsonRpcProvider = new providers.StaticJsonRpcProvider( - getWalletSetting(SETTINGS.RPC_URL, chainTypesById[chainId]), - ) - - const rifRelayConfig: RifRelayConfig = getRifRelayConfig(chainId) - - return await RIFWallet.create( - wallet.connect(jsonRpcProvider), - onRequestFn, - rifRelayConfig, - ) -} - -// gets the wallet from KeyManagementSystem -// re-creates the RIFWallet out it -// return kms, rifWallet and rifWalletIsDeployed bool -export const loadExistingWallet = async ( - serializedKeys: string, - chainId: ChainTypesByIdType, - dispatch: AppDispatch, -) => { - try { - const { kms, wallets } = KeyManagementSystem.fromSerialized( - serializedKeys, - chainId, - ) - - const rifWallet = await createRIFWallet(chainId, wallets[0], request => - dispatch(onRequest({ request })), - ) - const rifWalletIsDeployed = await rifWallet.smartWalletFactory.isDeployed() - - return { kms, rifWallet, rifWalletIsDeployed } - } catch (err) { - console.log('ERROR IN loadExistingWallet', err) - } - return null -} - -// creates KeyManagementSystem instance using mnemonic phrase -// using chainId gets save function and Wallet instance -// creates RIFWallet instance out of it -// saves the kms state in encrypted storage of the device -// returns rifWallet and rifWalletIsDeployed bool -export const createKMS = async ( - chainId: ChainTypesByIdType, - mnemonic: string, - dispatch: AppDispatch, -) => { - try { - const kms = KeyManagementSystem.import(mnemonic) - const { save, wallet } = kms.nextWallet(chainId) - - const rifWallet = await createRIFWallet(chainId, wallet, request => - dispatch(onRequest({ request })), - ) - const rifWalletIsDeployed = await rifWallet.smartWalletFactory.isDeployed() - - save() - const result = await saveKeys(kms.serialize()) - - if (!result) { - throw new Error('Could not save keys') - } - - return { - rifWallet, - rifWalletIsDeployed, - } - } catch (err) { - console.log('ERROR IN createKMS', err) - } - return null -} - -// Not currently used, we'll be needed when support -// for multiple wallets -// export const addNextWallet = async ( -// kms: KeyManagementSystem, -// createRIFWallet: CreateRIFWallet, -// chainId: number, -// ) => { -// const { wallet, save } = kms.nextWallet(chainId) - -// // save wallet in KSM -// save() -// // save serialized wallet in storage -// const result = await saveKeys(kms.serialize()) -// if (!result) { -// throw new Error('Could not save keys') -// } -// const rifWallet = await createRIFWallet(wallet) -// const isDeloyed = await rifWallet.smartWalletFactory.isDeployed() - -// return { -// rifWallet, -// isDeloyed, -// } -// } - -export const deleteCache = () => { - const cache = new MMKVStorage('txs') - cache.deleteAll() -} diff --git a/src/core/setup.ts b/src/core/setup.ts index 749c7c577..ea30f56e8 100644 --- a/src/core/setup.ts +++ b/src/core/setup.ts @@ -3,7 +3,6 @@ import { AddrResolver } from '@rsksmart/rns-sdk' import mainnetContracts from '@rsksmart/rsk-contract-metadata' import testnetContracts from '@rsksmart/rsk-testnet-contract-metadata' import axios from 'axios' -import { RIFWallet } from '@rsksmart/rif-wallet-core' import { SETTINGS } from 'core/types' import { MAINNET, TESTNET } from 'screens/rnsManager/addresses.json' @@ -13,6 +12,7 @@ import { chainTypesById, } from 'shared/constants/chainConstants' import { ITokenWithoutLogo } from 'store/slices/balancesSlice/types' +import { Wallet } from 'shared/wallet' import { getWalletSetting } from './config' @@ -26,10 +26,7 @@ export const createPublicAxios = (chainId: ChainTypesByIdType) => export const abiEnhancer = new AbiEnhancer() -export const getRnsResolver = ( - chainId: ChainTypesByIdType, - wallet: RIFWallet, -) => { +export const getRnsResolver = (chainId: ChainTypesByIdType, wallet: Wallet) => { const isMainnet = chainTypesById[chainId] === ChainTypeEnum.MAINNET const rnsRegistryAddress = isMainnet ? MAINNET.rnsRegistryAddress diff --git a/src/lib/eoaWallet/index.ts b/src/lib/eoaWallet/index.ts new file mode 100644 index 000000000..2ba220b51 --- /dev/null +++ b/src/lib/eoaWallet/index.ts @@ -0,0 +1,196 @@ +import { + TransactionRequest, + TransactionResponse, +} from '@ethersproject/abstract-provider' +import { TypedDataSigner } from '@ethersproject/abstract-signer' +import { fromSeed, mnemonicToSeedSync } from '@rsksmart/rif-id-mnemonic' +import { RelayPayment } from '@rsksmart/rif-relay-light-sdk' +import { getDPathByChainId } from '@rsksmart/rlogin-dpath' +import { + BigNumberish, + Bytes, + BytesLike, + TypedDataDomain, + TypedDataField, + Wallet, + providers, +} from 'ethers' + +export type ChainID = 30 | 31 +export type CacheFunction = (privateKey: string, mnemonic?: string) => void + +export interface WalletState { + privateKey: string + mnemonic?: string +} + +const generatePrivateKey = (mnemonic: string, chainId: ChainID) => { + const seed = mnemonicToSeedSync(mnemonic) + const derivationPath = getDPathByChainId(chainId, 0) + const hdKey = fromSeed(seed).derivePath(derivationPath) + const privateKey = hdKey.privateKey!.toString('hex') + + return privateKey +} + +export type Request = + | SendTransactionRequest + | SignMessageRequest + | SignTypedDataRequest +export type OnRequest = (request: Request) => void + +export interface IncomingRequest { + type: Type + payload: Payload + confirm: (args?: ConfirmArgs) => Promise + reject: (reason?: any) => void +} + +export type SignMessageRequest = IncomingRequest<'signMessage', BytesLike, void> + +export interface OverriddableTransactionOptions { + gasLimit: BigNumberish + gasPrice: BigNumberish + tokenPayment?: RelayPayment + pendingTxsCount?: number +} + +export type SendTransactionRequest = IncomingRequest< + 'sendTransaction', + TransactionRequest, + Partial +> + +export type SignTypedDataArgs = Parameters + +export type SignTypedDataRequest = IncomingRequest< + 'signTypedData', + SignTypedDataArgs, + void +> + +export class EOAWallet extends Wallet { + protected chainId: ChainID + protected onRequest: OnRequest + + get isDeployed(): Promise { + return Promise.resolve(true) + } + + protected constructor( + privateKey: string, + chainId: ChainID, + jsonRpcProvider: providers.JsonRpcProvider, + onRequest: OnRequest, + ) { + super(privateKey, jsonRpcProvider) + this.chainId = chainId + this.onRequest = onRequest + } + + public static create( + mnemonic: string, + chainId: ChainID, + jsonRpcProvider: providers.JsonRpcProvider, + onRequest: OnRequest, + cache?: CacheFunction, + ) { + const privateKey = generatePrivateKey(mnemonic, chainId) + + cache?.(privateKey, mnemonic) + + return new EOAWallet(privateKey, chainId, jsonRpcProvider, onRequest) + } + + public static fromWalletState( + keys: WalletState, + chainId: ChainID, + jsonRpcProvider: providers.JsonRpcProvider, + onRequest: OnRequest, + ) { + let privateKey = keys.privateKey + + if (this.chainId !== chainId && keys.mnemonic) { + privateKey = generatePrivateKey(keys.mnemonic, chainId) + } + + return new EOAWallet(privateKey, chainId, jsonRpcProvider, onRequest) + } + + async sendTransaction( + transactionRequest: TransactionRequest, + ): Promise { + // waits for confirm() + + return new Promise((resolve, reject) => { + const nextRequest = Object.freeze({ + type: 'sendTransaction', + payload: transactionRequest, + confirm: async () => { + try { + const obj = await super.sendTransaction(transactionRequest) + resolve(obj) + } catch (err) { + reject(err) + } + }, + reject: (reason?: any) => { + reject(new Error(reason)) + }, + }) + + // emits onRequest with reference to the transactionRequest + this.onRequest(nextRequest) + }) + } + + _signTypedData( + domain: TypedDataDomain, + types: Record, + value: Record, + ): Promise { + return new Promise((resolve, reject) => { + const nextRequest = Object.freeze({ + type: 'signTypedData', + payload: [domain, types, value], + confirm: async () => { + try { + const string = await super._signTypedData(domain, types, value) + resolve(string) + } catch (err) { + reject(err) + } + }, + reject: (reason?: any) => { + reject(new Error(reason)) + }, + }) + + // emits onRequest with reference to the signTypedDataRequest + this.onRequest(nextRequest) + }) + } + + signMessage(message: string | Bytes): Promise { + return new Promise((resolve, reject) => { + const nextRequest = Object.freeze({ + type: 'signMessage', + payload: message, + confirm: async () => { + try { + const string = await super.signMessage(message) + resolve(string) + } catch (err) { + reject(err) + } + }, + reject: (reason?: any) => { + reject(new Error(reason)) + }, + }) + + // emits onRequest with reference to the signTypedDataRequest + this.onRequest(nextRequest) + }) + } +} diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index 88754d4c4..7ca008a6f 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -234,6 +234,7 @@ const resources = { settings_screen_title: 'Settings', settings_screen_account: 'Account', settings_screen_accounts: 'Accounts', + settings_screen_switch_to: 'Switch to', settings_screen_wallet_backup: 'Wallet Backup', settings_screen_change_pin: 'Change PIN', settings_screen_deploy_wallet: 'Deploy Wallet', @@ -242,13 +243,14 @@ const resources = { settings_screen_smart_wallet_factory: 'Smart Wallet Factory', settings_screen_rpc_url: 'RPC URL', settings_screen_backend_url: 'Backend URL', - settings_screen_status_label: 'Status', - settings_screen_deployed_label: 'Deployed', - settings_screen_not_deployed_label: 'Not Deployed', - settings_screen_eoa_account_label: 'EOA Address', - settings_screen_smart_wallet_address_label: 'Smart Wallet Address', - settings_screen_public_key_label: 'Public Key', - settings_screen_edit_name_label: 'Edit name', + accounts_screen_status_label: 'Status', + accounts_screen_deployed_label: 'Deployed', + accounts_screen_not_deployed_label: 'Not Deployed', + accounts_screen_address_label: 'Address', + accounts_screen_eoa_account_label: 'EOA Address', + accounts_screen_smart_wallet_address_label: 'Smart Wallet Address', + accounts_screen_public_key_label: 'Public Key', + accounts_screen_edit_name_label: 'Edit name', contacts_screen_title: 'Contacts', contacts_empty_list: 'Your contact list is empty.', contacts_empty_start: 'Start by creating a new contact.', diff --git a/src/lib/relayWallet/index.ts b/src/lib/relayWallet/index.ts new file mode 100644 index 000000000..936a8ca56 --- /dev/null +++ b/src/lib/relayWallet/index.ts @@ -0,0 +1,187 @@ +import { + TransactionRequest, + TransactionResponse, +} from '@ethersproject/abstract-provider' +import { + RIFRelaySDK, + RelayPayment, + RifRelayConfig, +} from '@rsksmart/rif-relay-light-sdk' +import { Wallet, providers } from 'ethers' +import { BlockchainAuthenticatorConfig } from '@json-rpc-tools/utils' +import { defineReadOnly } from 'ethers/lib/utils' + +import { + ChainID, + EOAWallet, + OnRequest, + SendTransactionRequest, + WalletState, +} from '../eoaWallet' + +export const AddressZero = '0x0000000000000000000000000000000000000000' +export const HashZero = + '0x0000000000000000000000000000000000000000000000000000000000000000' + +const filterTxOptions = (transactionRequest: TransactionRequest) => + Object.keys(transactionRequest) + .filter(key => !['from', 'to', 'data'].includes(key)) + .reduce((obj: any, key: any) => { + obj[key] = (transactionRequest as any)[key] + return obj + }, {}) + +export class RelayWallet extends EOAWallet { + public rifRelaySdk: RIFRelaySDK + + get isDeployed(): Promise { + return this.rifRelaySdk.smartWalletFactory.isDeployed() + } + + get smartWalletAddress(): string { + return this.rifRelaySdk.smartWallet.smartWalletAddress + } + + protected constructor( + privateKey: string, + chainId: ChainID, + jsonRpcProvider: providers.JsonRpcProvider, + sdk: RIFRelaySDK, + onRequest: OnRequest, + ) { + super(privateKey, chainId, jsonRpcProvider, onRequest) + this.rifRelaySdk = sdk + + defineReadOnly(this, 'provider', this.provider) + } + + public static async create( + mnemonic: string, + chainId: ChainID, + jsonRpcProvider: providers.JsonRpcProvider, + onRequest: OnRequest, + config: RifRelayConfig, + cache?: (privateKey: string, mnemonic?: string) => void, + ) { + const wallet = super.create( + mnemonic, + chainId, + jsonRpcProvider, + onRequest, + cache, + ) + + // bypass the EOAWallet's _signTypedData since the consent is already given + wallet._signTypedData = Wallet.prototype._signTypedData + + const rifRelaySdk = await RIFRelaySDK.create(wallet, config) + + return new RelayWallet( + wallet.privateKey, + chainId, + jsonRpcProvider, + rifRelaySdk, + onRequest, + ) + } + + // calls via smart wallet + call( + transactionRequest: TransactionRequest, + blockTag?: BlockchainAuthenticatorConfig, + ): Promise { + return this.rifRelaySdk.smartWallet.callStaticDirectExecute( + transactionRequest.to!, + transactionRequest.data!, + { ...filterTxOptions(transactionRequest), blockTag }, + ) + } + + deploySmartWallet = (payment: RelayPayment) => + this.rifRelaySdk.sendDeployTransaction(payment) + + sendTransaction( + transactionRequest: TransactionRequest, + ): Promise { + return new Promise((resolve, reject) => { + const nextRequest = Object.freeze({ + type: 'sendTransaction', + payload: transactionRequest, + confirm: async overriddenOptions => { + // check if paying with tokens: + if (overriddenOptions && overriddenOptions.tokenPayment) { + console.log('sendRelayTransaction', transactionRequest) + return resolve( + await this.rifRelaySdk.sendRelayTransaction( + { + ...transactionRequest, + gasPrice: overriddenOptions.gasPrice, + gasLimit: overriddenOptions.gasLimit, + }, + overriddenOptions.tokenPayment, + ), + ) + } + + // direct execute transaction paying gas with EOA wallet: + const txOptions = { + ...filterTxOptions(transactionRequest), + ...(overriddenOptions || {}), + } + + console.log('txOptions', txOptions) + + return resolve( + await this.rifRelaySdk.smartWallet.directExecute( + transactionRequest.to!, + transactionRequest.data ?? HashZero, + txOptions, + ), + ) + }, + reject, + }) + + // emits onRequest + this.onRequest(nextRequest) + }) + } + + override async estimateGas( + txRequest: TransactionRequest, + tokenContract?: string, + ) { + if (tokenContract) { + return this.rifRelaySdk.estimateTransactionCost(txRequest, tokenContract) + } + + return super.estimateGas(txRequest) + } + + public static override async fromWalletState( + keys: WalletState, + chainId: ChainID, + jsonRpcProvider: providers.StaticJsonRpcProvider, + onRequest: OnRequest, + config: RifRelayConfig, + ) { + const wallet = EOAWallet.fromWalletState( + keys, + chainId, + jsonRpcProvider, + onRequest, + ) + + // bypass the EOAWallet's _signTypedData since the consent is already given + wallet._signTypedData = Wallet.prototype._signTypedData + + const sdk = await RIFRelaySDK.create(wallet, config) + return new RelayWallet( + wallet.privateKey, + chainId, + jsonRpcProvider, + sdk, + onRequest, + ) + } +} diff --git a/src/lib/rns/RnsProcessor.ts b/src/lib/rns/RnsProcessor.ts index ab72d3bbe..27cba8dd6 100644 --- a/src/lib/rns/RnsProcessor.ts +++ b/src/lib/rns/RnsProcessor.ts @@ -1,5 +1,4 @@ import { RSKRegistrar } from '@rsksmart/rns-sdk' -import { RIFWallet } from '@rsksmart/rif-wallet-core' import { BigNumber } from 'ethers' import { @@ -9,24 +8,29 @@ import { } from 'storage/RnsProcessorStore' import { OnSetTransactionStatusChange } from 'screens/send/types' import { RNS_ADDRESSES_TYPE } from 'screens/rnsManager/types' +import { Wallet } from 'shared/wallet' + +interface RNSProcessorConstructor { + wallet: Wallet + address: string + onSetTransactionStatusChange?: OnSetTransactionStatusChange + rnsAddresses: RNS_ADDRESSES_TYPE +} export class RnsProcessor { private rskRegistrar - private wallet + private address private index: IDomainRegistrationProcessIndex = {} private rnsAddresses: RNS_ADDRESSES_TYPE onSetTransactionStatusChange?: OnSetTransactionStatusChange constructor({ wallet, + address, onSetTransactionStatusChange, rnsAddresses, - }: { - wallet: RIFWallet - onSetTransactionStatusChange?: OnSetTransactionStatusChange - rnsAddresses: RNS_ADDRESSES_TYPE - }) { - this.wallet = wallet + }: RNSProcessorConstructor) { + this.address = address this.rnsAddresses = rnsAddresses this.rskRegistrar = new RSKRegistrar( this.rnsAddresses.rskOwnerAddress, @@ -56,10 +60,7 @@ export class RnsProcessor { try { if (!this.index[domain]?.commitmentRequested) { const { makeCommitmentTransaction, secret, hash } = - await this.rskRegistrar.commitToRegister( - domain, - this.wallet.smartWallet.smartWalletAddress, - ) + await this.rskRegistrar.commitToRegister(domain, this.address) this.onSetTransactionStatusChange?.({ ...makeCommitmentTransaction, @@ -173,7 +174,7 @@ export class RnsProcessor { const tx = await this.rskRegistrar.register( domain, - this.wallet.smartWallet.smartWalletAddress, + this.address, this.index[domain].secret, durationToRegister, price, diff --git a/src/navigation/createKeysNavigator/index.tsx b/src/navigation/createKeysNavigator/index.tsx index 6e1ae97e8..22985baa0 100644 --- a/src/navigation/createKeysNavigator/index.tsx +++ b/src/navigation/createKeysNavigator/index.tsx @@ -10,7 +10,6 @@ import { SecurityInformation, RetryLogin, } from 'screens/createKeys' -import { selectIsUnlocked } from 'store/slices/settingsSlice' import { selectKeysExist } from 'store/slices/persistentDataSlice' import { useAppSelector } from 'store/storeUtils' import { PinScreen } from 'screens/pinScreen' @@ -26,24 +25,22 @@ export const CreateKeysNavigation = () => { const keysExist = useAppSelector(selectKeysExist) const { top } = useSafeAreaInsets() const { t } = useTranslation() - const unlocked = useAppSelector(selectIsUnlocked) return ( - {!unlocked && - (!keysExist ? ( - - ) : ( - - ))} + {!keysExist ? ( + + ) : ( + + )} { options={screenOptionsWithHeader(top, t('header_import_wallet'))} /> diff --git a/src/navigation/createKeysNavigator/types.ts b/src/navigation/createKeysNavigator/types.ts index 3c617950f..8fd029673 100644 --- a/src/navigation/createKeysNavigator/types.ts +++ b/src/navigation/createKeysNavigator/types.ts @@ -11,7 +11,7 @@ export enum createKeysRouteNames { ConfirmNewMasterKey = 'ConfirmNewMasterKey', ImportMasterKey = 'ImportMasterKey', RevealMasterKey = 'RevealMasterKey', - CreatePIN = 'CreatePIN', + PinScreen = 'PinScreen', RetryLogin = 'RetryLogin', } @@ -27,8 +27,8 @@ export type CreateKeysStackParamList = { [createKeysRouteNames.ConfirmNewMasterKey]: { mnemonic: string } [createKeysRouteNames.ImportMasterKey]: undefined [createKeysRouteNames.RevealMasterKey]: undefined - [createKeysRouteNames.CreatePIN]: { - isChangeRequested: true + [createKeysRouteNames.PinScreen]: { + isChangeRequested: boolean backScreen?: null } [createKeysRouteNames.RetryLogin]: undefined diff --git a/src/navigation/rootNavigator/index.tsx b/src/navigation/rootNavigator/index.tsx index e96ec6dca..87829537d 100644 --- a/src/navigation/rootNavigator/index.tsx +++ b/src/navigation/rootNavigator/index.tsx @@ -9,17 +9,22 @@ import BootSplash from 'react-native-bootsplash' import { CreateKeysNavigation } from 'navigation/createKeysNavigator' import { ConfirmationModal } from 'components/modal' import { useAppSelector } from 'store/storeUtils' -import { selectFullscreen, selectIsUnlocked } from 'store/slices/settingsSlice' +import { + selectFullscreen, + selectIsUnlocked, + selectSettingsIsLoading, +} from 'store/slices/settingsSlice' import { TransactionSummaryScreen } from 'screens/transactionSummary' import { AppFooterMenu } from 'src/ux/appFooter' import { sharedStyles } from 'shared/constants' import { ActivityScreen, ScanQRScreen, - PinScreen, WalletConnectScreenWithProvider, } from 'screens/index' import { OfflineScreen } from 'core/components/OfflineScreen' +import { LoadingScreen } from 'components/loading/LoadingScreen' +import { ConfirmNewMasterKeyScreen } from 'screens/createKeys' import { RootTabsParamsList, rootTabsRouteNames } from './types' import { HomeNavigator } from '../homeNavigator' @@ -41,6 +46,7 @@ export const RootNavigationComponent = () => { const [isWarningVisible, setIsWarningVisible] = useState(isDeviceRooted) const unlocked = useAppSelector(selectIsUnlocked) const fullscreen = useAppSelector(selectFullscreen) + const settingsLoading = useAppSelector(selectSettingsIsLoading) const isShown = unlocked && !fullscreen @@ -53,31 +59,13 @@ export const RootNavigationComponent = () => { (!isShown ? null : )}> {!unlocked ? ( - <> - - - - + ) : ( - + <> { name={rootTabsRouteNames.Profile} component={ProfileNavigator} /> + { component={TransactionSummaryScreen} /> - - + )} + { okText={t('ok')} onOk={() => setIsWarningVisible(false)} /> + ) } diff --git a/src/navigation/rootNavigator/types.ts b/src/navigation/rootNavigator/types.ts index 768984ac1..9afaf0c9b 100644 --- a/src/navigation/rootNavigator/types.ts +++ b/src/navigation/rootNavigator/types.ts @@ -26,6 +26,7 @@ export enum rootTabsRouteNames { TransactionSummary = 'TransactionSummary', InitialPinScreen = 'InitialPinScreen', OfflineScreen = 'OfflineScreen', + ConfirmNewMasterKey = 'ConfirmNewMasterKey', } export type RootTabsParamsList = { @@ -49,6 +50,8 @@ export type RootTabsParamsList = { [rootTabsRouteNames.Profile]: | NavigatorScreenParams | undefined - [rootTabsRouteNames.InitialPinScreen]: undefined [rootTabsRouteNames.OfflineScreen]: undefined + [rootTabsRouteNames.ConfirmNewMasterKey]: { + mnemonic: string + } } diff --git a/src/redux/slices/contactsSlice/constants.ts b/src/redux/slices/contactsSlice/constants.ts index 652c3b3b1..1f5b39104 100644 --- a/src/redux/slices/contactsSlice/constants.ts +++ b/src/redux/slices/contactsSlice/constants.ts @@ -8,6 +8,12 @@ export const testnetContacts: Record = { displayAddress: TESTNET.rBTCFaucet, isEditable: false, }, + [TESTNET.rifFaucet]: { + address: TESTNET.rifFaucet, + name: 'RIF Faucet', + displayAddress: TESTNET.rifFaucet, + isEditable: false, + }, [TESTNET.fifsAddrRegistrarAddress]: { address: TESTNET.fifsAddrRegistrarAddress, name: 'RNS Manager', diff --git a/src/redux/slices/profileSlice/index.ts b/src/redux/slices/profileSlice/index.ts index 74f178c41..fa26b701f 100644 --- a/src/redux/slices/profileSlice/index.ts +++ b/src/redux/slices/profileSlice/index.ts @@ -6,14 +6,16 @@ import { ProfileStatus } from 'navigation/profileNavigator/types' import { ProfileStore } from './types' +interface RequestUsernamePayload { + rnsProcessor: RnsProcessor + alias: string + duration: number +} + export const requestUsername = createAsyncThunk( 'profile/requestUsername', async ( - { - rnsProcessor, - alias, - duration, - }: { rnsProcessor: RnsProcessor; alias: string; duration: number }, + { rnsProcessor, alias, duration }: RequestUsernamePayload, thunkAPI, ) => { try { diff --git a/src/redux/slices/settingsSlice/index.ts b/src/redux/slices/settingsSlice/index.ts index 3b2bc8058..6b8c0427d 100644 --- a/src/redux/slices/settingsSlice/index.ts +++ b/src/redux/slices/settingsSlice/index.ts @@ -2,16 +2,16 @@ import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit' import { getSupportedBiometryType } from 'react-native-keychain' import { ColorValue, Platform } from 'react-native' import { initializeSslPinning } from 'react-native-ssl-public-key-pinning' -import { RIFWallet } from '@rsksmart/rif-wallet-core' import { RifWalletServicesFetcher } from '@rsksmart/rif-wallet-services' +import { providers } from 'ethers' +import { RifRelayConfig } from '@rsksmart/rif-relay-light-sdk' -import { KeyManagementSystem } from 'lib/core' +import { ChainID, WalletState } from 'lib/eoaWallet' -import { createKMS, deleteCache, loadExistingWallet } from 'core/operations' import { deleteDomains } from 'storage/DomainsStore' import { deleteContacts as deleteContactsFromRedux } from 'store/slices/contactsSlice' import { resetMainStorage } from 'storage/MainStorage' -import { deleteKeys, getKeys } from 'storage/SecureStorage' +import { deleteKeys, getKeys, saveKeys } from 'storage/SecureStorage' import { sharedColors } from 'shared/constants' import { createPublicAxios } from 'core/setup' import { resetSocketState } from 'store/shared/actions/resetSocketState' @@ -22,7 +22,7 @@ import { getWalletSetting } from 'core/config' import { SETTINGS } from 'core/types' import { rootTabsRouteNames } from 'navigation/rootNavigator' import { createKeysRouteNames } from 'navigation/createKeysNavigator' -import { AsyncThunkWithTypes } from 'store/store' +import { AppDispatch, AsyncThunkWithTypes } from 'store/store' import { rifSockets, SocketsEvents, @@ -39,6 +39,10 @@ import { setKeysExist, setPinState, } from 'store/slices/persistentDataSlice' +import { Wallet } from 'shared/wallet' +import { addressToUse } from 'shared/hooks' +import { createAppWallet, loadAppWallet } from 'src/shared/utils' +import { MMKVStorage } from 'storage/MMKVStorage' import { Bitcoin, @@ -47,6 +51,34 @@ import { SettingsSlice, UnlockAppAction, } from './types' +import { UsdPricesState } from '../usdPricesSlice' +import { BalanceState } from '../balancesSlice/types' + +export const deleteCache = () => { + const cache = new MMKVStorage('txs') + cache.deleteAll() +} + +export const getRifRelayConfig = (chainId: 30 | 31): RifRelayConfig => { + return { + smartWalletFactoryAddress: getWalletSetting( + SETTINGS.SMART_WALLET_FACTORY_ADDRESS, + chainTypesById[chainId], + ), + relayVerifierAddress: getWalletSetting( + SETTINGS.RELAY_VERIFIER_ADDRESS, + chainTypesById[chainId], + ), + deployVerifierAddress: getWalletSetting( + SETTINGS.DEPLOY_VERIFIER_ADDRESS, + chainTypesById[chainId], + ), + relayServer: getWalletSetting( + SETTINGS.RIF_RELAY_SERVER, + chainTypesById[chainId], + ), + } +} const sslPinning = async (chainId: ChainTypesByIdType) => { const rifWalletServiceDomain = getWalletSetting( @@ -58,7 +90,6 @@ const sslPinning = async (chainId: ChainTypesByIdType) => { SETTINGS.RIF_WALLET_SERVICE_PUBLIC_KEY, chainTypesById[chainId], ).split(',') - console.log(rifWalletServicePk) const rifRelayDomain = getWalletSetting( SETTINGS.RIF_RELAY_SERVER, chainTypesById[chainId], @@ -81,21 +112,76 @@ const sslPinning = async (chainId: ChainTypesByIdType) => { }) } +const initializeApp = async ( + mnemonic: string, + wallet: Wallet, + chainId: ChainID, + usdPrices: UsdPricesState, + balances: BalanceState, + dispatch: AppDispatch, + rejectWithValue: (err: string) => void, +) => { + const fetcherInstance = new RifWalletServicesFetcher( + createPublicAxios(chainId), + { + defaultChainId: chainId.toString(), + resultsLimit: 10, + }, + ) + + await sslPinning(chainId) + + // connect to sockets + rifSockets({ + address: addressToUse(wallet), + fetcher: fetcherInstance, + dispatch, + setGlobalError: rejectWithValue, + usdPrices, + chainId, + balances: balances.tokenBalances, + }) + + socketsEvents.emit(SocketsEvents.CONNECT) + + // initialize bitcoin + const bitcoin = initializeBitcoin( + mnemonic, + dispatch, + fetcherInstance, + chainId, + ) + + // set bitcoin in redux + dispatch(setBitcoinState(bitcoin)) +} + export const createWallet = createAsyncThunk< - RIFWallet, + Wallet, CreateFirstWalletAction, AsyncThunkWithTypes >('settings/createWallet', async ({ mnemonic, initializeWallet }, thunkAPI) => { try { const { chainId } = thunkAPI.getState().settings - const kms = await createKMS(chainId, mnemonic, thunkAPI.dispatch) + + const url = getWalletSetting(SETTINGS.RPC_URL, chainTypesById[chainId]) + const jsonRpcProvider = new providers.StaticJsonRpcProvider(url) + + const wallet = await createAppWallet( + mnemonic, + chainId, + jsonRpcProvider, + request => thunkAPI.dispatch(onRequest({ request })), + getRifRelayConfig(chainId), + saveKeys, + ) const supportedBiometry = await getSupportedBiometryType() if (Platform.OS === 'android' && !supportedBiometry) { setTimeout(() => { navigationContainerRef.navigate(rootTabsRouteNames.CreateKeysUX, { - screen: createKeysRouteNames.CreatePIN, + screen: createKeysRouteNames.PinScreen, params: { isChangeRequested: true, backScreen: null, @@ -104,75 +190,55 @@ export const createWallet = createAsyncThunk< }, 100) } - if (!kms) { - return thunkAPI.rejectWithValue('Failed to createKMS') + if (!wallet) { + return thunkAPI.rejectWithValue('Failed to create a Wallet') } // set wallet and walletIsDeployed in WalletContext - initializeWallet(kms.rifWallet, { - isDeployed: kms.rifWalletIsDeployed, + initializeWallet(wallet, { + isDeployed: await wallet.isDeployed, loading: false, txHash: null, }) // unclock the app - thunkAPI.dispatch(setUnlocked(true)) - + if (supportedBiometry || (Platform.OS === 'ios' && __DEV__)) { + thunkAPI.dispatch(setUnlocked(true)) + } + // set keysExist thunkAPI.dispatch(setKeysExist(true)) // create fetcher //@TODO: refactor socket initialization, it repeats several times thunkAPI.dispatch(setChainId(chainId)) - const fetcherInstance = new RifWalletServicesFetcher( - createPublicAxios(chainId), - { - defaultChainId: chainId.toString(), - resultsLimit: 10, - }, - ) - const { usdPrices, balances } = thunkAPI.getState() - await sslPinning(chainId) - - // connect to sockets - rifSockets({ - address: kms.rifWallet.smartWalletAddress, - dispatch: thunkAPI.dispatch, - setGlobalError: thunkAPI.rejectWithValue, - usdPrices, - chainId, - balances: balances.tokenBalances, - }) - - socketsEvents.emit(SocketsEvents.CONNECT) - - // initialize bitcoin - const bitcoin = initializeBitcoin( + await initializeApp( mnemonic, - thunkAPI.dispatch, - fetcherInstance, + wallet, chainId, + usdPrices, + balances, + thunkAPI.dispatch, + thunkAPI.rejectWithValue, ) - // set bitcoin in redux - thunkAPI.dispatch(setBitcoinState(bitcoin)) - - return kms.rifWallet + return wallet } catch (err) { return thunkAPI.rejectWithValue(err) } }) export const unlockApp = createAsyncThunk< - KeyManagementSystem, + WalletState, UnlockAppAction, AsyncThunkWithTypes >('settings/unlockApp', async (payload, thunkAPI) => { try { const { persistentData: { isFirstLaunch }, + settings: { chainId }, } = thunkAPI.getState() // if previously installed the app, remove stored encryted keys if (isFirstLaunch && !__DEV__) { @@ -181,10 +247,9 @@ export const unlockApp = createAsyncThunk< return thunkAPI.rejectWithValue('FIRST LAUNCH, DELETE PREVIOUS KEYS') } - const serializedKeys = await getKeys() - const { chainId } = thunkAPI.getState().settings + const keys = await getKeys() - if (!serializedKeys) { + if (!keys) { // if keys do not exist, set to false thunkAPI.dispatch(setKeysExist(false)) return thunkAPI.rejectWithValue('No Existing Keys') @@ -208,9 +273,15 @@ export const unlockApp = createAsyncThunk< if (isOffline) { navigationContainerRef.navigate(rootTabsRouteNames.OfflineScreen) } else { - navigationContainerRef.navigate(rootTabsRouteNames.InitialPinScreen) + navigationContainerRef.navigate(rootTabsRouteNames.CreateKeysUX, { + screen: createKeysRouteNames.PinScreen, + params: { + isChangeRequested: false, + }, + }) } }, 100) + return thunkAPI.rejectWithValue('Move to unlock with PIN') } @@ -221,64 +292,43 @@ export const unlockApp = createAsyncThunk< return thunkAPI.rejectWithValue('Move to Offline Screen') } - // set wallets in the store - const existingWallet = await loadExistingWallet( - serializedKeys, + const url = getWalletSetting(SETTINGS.RPC_URL, chainTypesById[chainId]) + const jsonRpcProvider = new providers.StaticJsonRpcProvider(url) + + const wallet = await loadAppWallet( + keys, chainId, - thunkAPI.dispatch, + jsonRpcProvider, + request => thunkAPI.dispatch(onRequest({ request })), + getRifRelayConfig(chainId), ) - if (!existingWallet) { + if (!wallet) { return thunkAPI.rejectWithValue('No Existing Wallet') } - const { kms, rifWallet, rifWalletIsDeployed } = existingWallet - // set wallet and walletIsDeployed in WalletContext - initializeWallet(rifWallet, { - isDeployed: rifWalletIsDeployed, + initializeWallet(wallet, { + isDeployed: await wallet.isDeployed, loading: false, txHash: null, }) thunkAPI.dispatch(setUnlocked(true)) - // create fetcher - const fetcherInstance = new RifWalletServicesFetcher( - createPublicAxios(chainId), - { - defaultChainId: chainId.toString(), - resultsLimit: 10, - }, - ) - const { usdPrices, balances } = thunkAPI.getState() - await sslPinning(chainId) - - // connect to sockets - rifSockets({ - address: rifWallet.smartWalletAddress, - dispatch: thunkAPI.dispatch, - setGlobalError: thunkAPI.rejectWithValue, - usdPrices, + await initializeApp( + keys.mnemonic ?? keys.privateKey, + wallet, chainId, - balances: balances.tokenBalances, - }) - - socketsEvents.emit(SocketsEvents.CONNECT) - - // initialize bitcoin - const bitcoin = initializeBitcoin( - kms.mnemonic, + usdPrices, + balances, thunkAPI.dispatch, - fetcherInstance, - chainId, + thunkAPI.rejectWithValue, ) - // set bitcoin in redux - thunkAPI.dispatch(setBitcoinState(bitcoin)) - return kms + return keys } catch (err) { return thunkAPI.rejectWithValue(err) } @@ -304,39 +354,6 @@ export const resetApp = createAsyncThunk( }, ) -// Not currently used, we'll be needed when support -// for multiple wallets -// export const addNewWallet = createAsyncThunk( -// 'settings/addNewWallet', -// async ({ networkId }: AddNewWalletAction, thunkAPI) => { -// try { -// const keys = await getKeys() -// const { chainId } = thunkAPI.getState().settings -// if (!keys) { -// return thunkAPI.rejectWithValue( -// 'Can not add new wallet because no KMS created.', -// ) -// } -// const { kms } = KeyManagementSystem.fromSerialized(keys) -// const { rifWallet, isDeloyed } = await addNextWallet( -// kms, -// createRIFWalletFactory( -// request => thunkAPI.dispatch(onRequest({ request })), -// chainId, -// ), -// networkId, -// ) -// thunkAPI.dispatch(setNewWallet({ rifWallet, isDeployed: isDeloyed })) -// return { -// rifWallet, -// isDeloyed, -// } -// } catch (err) { -// return thunkAPI.rejectWithValue(err) -// } -// }, -// ) - const initialState: SettingsSlice = { isSetup: false, topColor: sharedColors.primary, @@ -387,11 +404,6 @@ const settingsSlice = createSlice({ setPreviouslyUnlocked: (state, { payload }: PayloadAction) => { state.previouslyUnlocked = payload }, - // Not currently used, we'll be needed when support - // for multiple wallets - // switchSelectedWallet: (state, { payload }: PayloadAction) => { - // state.selectedWallet = payload - // }, resetKeysAndPin: () => { deleteKeys() deleteDomains() @@ -433,15 +445,6 @@ const settingsSlice = createSlice({ builder.addCase(unlockApp.fulfilled, state => { state.loading = false }) - // builder.addCase(addNewWallet.pending, state => { - // state.loading = true - // }) - // builder.addCase(addNewWallet.rejected, state => { - // state.loading = false - // }) - // builder.addCase(addNewWallet.fulfilled, state => { - // state.loading = false - // }) }, }) diff --git a/src/redux/slices/settingsSlice/types.ts b/src/redux/slices/settingsSlice/types.ts index 3b048d971..c9d9e253e 100644 --- a/src/redux/slices/settingsSlice/types.ts +++ b/src/redux/slices/settingsSlice/types.ts @@ -1,14 +1,9 @@ import { BitcoinNetworkWithBIPRequest } from '@rsksmart/rif-wallet-bitcoin' import { ColorValue } from 'react-native' -import { RIFWallet } from '@rsksmart/rif-wallet-core' import { RequestWithBitcoin } from 'shared/types' import { ChainTypesByIdType } from 'shared/constants/chainConstants' -import { InitializeWallet } from 'shared/wallet' - -export interface Wallets { - [id: string]: RIFWallet -} +import { InitializeWallet, Wallet } from 'shared/wallet' export interface WalletIsDeployed { loading: boolean @@ -38,7 +33,7 @@ export interface UnlockAppAction { } export interface SetKeysAction { - wallet: RIFWallet + wallet: Wallet walletIsDeployed: WalletIsDeployed } @@ -52,7 +47,7 @@ export interface SetWalletIsDeployedAction { } export interface SetNewWalletAction { - rifWallet: RIFWallet + rifWallet: Wallet isDeployed: boolean } diff --git a/src/redux/slices/transactionsSlice/index.ts b/src/redux/slices/transactionsSlice/index.ts index 3f75361e7..512d11cb2 100644 --- a/src/redux/slices/transactionsSlice/index.ts +++ b/src/redux/slices/transactionsSlice/index.ts @@ -33,6 +33,7 @@ import { ChainTypesByIdType, } from 'shared/constants/chainConstants' import { TokenSymbol } from 'screens/home/TokenImage' +import { rbtcMap } from 'shared/utils' export const activityDeserializer: ( activityTransaction: ActivityMixedType, @@ -86,9 +87,7 @@ export const activityDeserializer: ( ? TokenSymbol.RBTC : TokenSymbol.TRBTC const rbtcAddress = constants.AddressZero - const feeRbtc = BigNumber.from(tx.gasPrice).mul( - BigNumber.from(tx.receipt?.gasUsed || 1), - ) + const feeRbtc = BigNumber.from(tx.receipt?.gasUsed || 0) // Token const tokenValue = etx?.value || balanceToDisplay(tx.value, 18) @@ -98,7 +97,7 @@ export const activityDeserializer: ( tokenContract = etx?.symbol === rbtcSymbol ? rbtcAddress - : getTokenAddress(tokenSymbol, chainTypesById[chainId]) + : getTokenAddress(tokenSymbol as TokenSymbol, chainId) } catch {} const tokenQuote = prices[tokenContract.toLowerCase()]?.price || 0 const tokenUsd = convertTokenToUSD(Number(tokenValue), tokenQuote) @@ -106,15 +105,18 @@ export const activityDeserializer: ( // Fee const feeValue = etx?.feeValue || balanceToDisplay(feeRbtc, 18) const feeSymbol = etx?.feeSymbol || rbtcSymbol + const feeTokenValue = rbtcMap.get(feeSymbol as TokenSymbol) + ? feeRbtc.toString() + : feeValue let feeContract = '' try { feeContract = etx?.feeSymbol === rbtcSymbol ? rbtcAddress - : getTokenAddress(feeSymbol, chainTypesById[chainId]) + : getTokenAddress(feeSymbol as TokenSymbol, chainId) } catch {} const feeQuote = prices[feeContract.toLowerCase()]?.price || 0 - const feeUsd = convertTokenToUSD(Number(feeValue), feeQuote).toFixed(2) + const feeUsd = convertTokenToUSD(Number(feeValue), feeQuote).toString() return { id: tx.hash, @@ -127,7 +129,7 @@ export const activityDeserializer: ( symbol: tokenSymbol, price: Number(tokenUsd), fee: { - tokenValue: feeValue, + tokenValue: feeTokenValue.toString(), symbol: feeSymbol, usdValue: feeUsd, }, diff --git a/src/redux/slices/transactionsSlice/types.ts b/src/redux/slices/transactionsSlice/types.ts index 5dcfcf4f4..2c9a7dd06 100644 --- a/src/redux/slices/transactionsSlice/types.ts +++ b/src/redux/slices/transactionsSlice/types.ts @@ -35,7 +35,7 @@ export interface IBitcoinTransaction { } export interface TokenFeeValueObject { - tokenValue?: string + tokenValue: string usdValue: string symbol?: TokenSymbol | string } diff --git a/src/screens/accounts/AccountsScreen.tsx b/src/screens/accounts/AccountsScreen.tsx index f43493240..37188f9a0 100644 --- a/src/screens/accounts/AccountsScreen.tsx +++ b/src/screens/accounts/AccountsScreen.tsx @@ -2,6 +2,7 @@ import { useMemo, useEffect } from 'react' import { View } from 'react-native' import { shortAddress } from 'lib/utils' +import { RelayWallet } from 'lib/relayWallet' import { AccountBox } from 'components/accounts/AccountBox' import { useAppSelector } from 'store/storeUtils' @@ -44,7 +45,9 @@ export const AccountsScreen = ({ diff --git a/src/screens/activity/ActivityRow.tsx b/src/screens/activity/ActivityRow.tsx index 3be327ee2..bf545c1a7 100644 --- a/src/screens/activity/ActivityRow.tsx +++ b/src/screens/activity/ActivityRow.tsx @@ -1,21 +1,22 @@ import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { StyleProp, ViewStyle } from 'react-native' -import { RIFWallet } from '@rsksmart/rif-wallet-core' import { ZERO_ADDRESS } from '@rsksmart/rif-relay-light-sdk' import { roundBalance, shortAddress } from 'lib/utils' +import { isMyAddress } from 'components/address/lib' import { StatusEnum } from 'components/BasicRow' import { BasicRowWithContact } from 'components/BasicRow/BasicRowWithContact' import { AppTouchable } from 'components/appTouchable' import { rootTabsRouteNames } from 'navigation/rootNavigator/types' import { TransactionSummaryScreenProps } from 'screens/transactionSummary' import { ActivityMainScreenProps } from 'shared/types' -import { isMyAddress } from 'src/components/address/lib' -import { useAppSelector } from 'src/redux/storeUtils' +import { useAppSelector } from 'store/storeUtils' import { getContactByAddress } from 'store/slices/contactsSlice' import { ActivityRowPresentationObject } from 'store/slices/transactionsSlice' +import { Wallet } from 'shared/wallet' +import { useAddress } from 'shared/hooks' const getStatus = (status: string) => { switch (status) { @@ -30,7 +31,7 @@ const getStatus = (status: string) => { interface Props { index?: number - wallet: RIFWallet + wallet: Wallet activityDetails: ActivityRowPresentationObject navigation: ActivityMainScreenProps['navigation'] backScreen?: rootTabsRouteNames @@ -56,11 +57,12 @@ export const ActivityBasicRow = ({ price, id, } = activityDetails - + const walletAddress = useAddress(wallet) const { t } = useTranslation() // Contact - const amIReceiver = activityDetails.amIReceiver ?? isMyAddress(wallet, to) + const amIReceiver = + activityDetails.amIReceiver ?? isMyAddress(walletAddress, to) const address = amIReceiver ? from : to const contact = useAppSelector(getContactByAddress(address.toLowerCase())) @@ -70,7 +72,7 @@ export const ActivityBasicRow = ({ let label = `${firstLabel} ${secondLabel}` if ( to === ZERO_ADDRESS && - from.toLowerCase() === wallet.smartWalletFactory.address.toLowerCase() + from.toLowerCase() === wallet?.smartWalletFactory?.address.toLowerCase() ) { label = t('wallet_deployment_label') } @@ -89,12 +91,18 @@ export const ActivityBasicRow = ({ usdValue: { symbol: usdBalance ? '$' : '<', symbolType: 'usd', - balance: usdBalance ? usdBalance.toFixed(2) : '0.01', + balance: usdBalance ? usdBalance : '0.01', }, + totalToken: + symbol === fee.symbol + ? Number(value) + Number(fee.tokenValue) + : Number(value), + totalUsd: Number(value) + Number(fee.usdValue), status, fee: { ...fee, symbol: fee.symbol || symbol, + usdValue: fee.usdValue, }, amIReceiver, from, diff --git a/src/screens/activity/ActivityScreen.tsx b/src/screens/activity/ActivityScreen.tsx index a34a7227e..da36123ac 100644 --- a/src/screens/activity/ActivityScreen.tsx +++ b/src/screens/activity/ActivityScreen.tsx @@ -1,7 +1,7 @@ import { EnhancedResult } from '@rsksmart/rif-wallet-abi-enhancer' import { IApiTransaction } from '@rsksmart/rif-wallet-services' import { ethers } from 'ethers' -import { useContext, useEffect } from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { FlatList, Image, RefreshControl, StyleSheet, View } from 'react-native' import { useIsFocused } from '@react-navigation/native' @@ -19,7 +19,7 @@ import { selectTransactionsLoading, } from 'store/slices/transactionsSlice/selectors' import { useAppDispatch, useAppSelector } from 'store/storeUtils' -import { WalletContext } from 'shared/wallet' +import { useWallet } from 'shared/wallet' import { ChainTypesByIdType } from 'src/shared/constants/chainConstants' import { ActivityBasicRow } from './ActivityRow' @@ -27,7 +27,7 @@ import { ActivityBasicRow } from './ActivityRow' export const ActivityScreen = ({ navigation }: ActivityMainScreenProps) => { const dispatch = useAppDispatch() const { t } = useTranslation() - const { wallet } = useContext(WalletContext) + const wallet = useWallet() const transactions = useAppSelector(selectTransactions) const loading = useAppSelector(selectTransactionsLoading) const isFocused = useIsFocused() diff --git a/src/screens/createKeys/new/ConfirmNewMasterKeyScreen.tsx b/src/screens/createKeys/new/ConfirmNewMasterKeyScreen.tsx index d53ffa520..14d2d699e 100644 --- a/src/screens/createKeys/new/ConfirmNewMasterKeyScreen.tsx +++ b/src/screens/createKeys/new/ConfirmNewMasterKeyScreen.tsx @@ -15,6 +15,14 @@ import { useAppDispatch } from 'store/storeUtils' import { sharedColors, sharedStyles } from 'shared/constants' import { StepperComponent } from 'src/components/profile' import { useInitializeWallet } from 'shared/wallet' +import { + rootTabsRouteNames, + RootTabsScreenProps, +} from 'navigation/rootNavigator' + +type ConfirmNewMasterKey = + | CreateKeysScreenProps + | RootTabsScreenProps type MnemonicWordNumber = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 @@ -72,9 +80,7 @@ const onRandomWordChoice = (_mnemonicWords: string[]) => { } } -export const ConfirmNewMasterKeyScreen = ({ - route, -}: CreateKeysScreenProps) => { +export const ConfirmNewMasterKeyScreen = ({ route }: ConfirmNewMasterKey) => { const initializeWallet = useInitializeWallet() const { t } = useTranslation() const methods = useForm({ diff --git a/src/screens/createKeys/new/NewMasterKeyScreen.tsx b/src/screens/createKeys/new/NewMasterKeyScreen.tsx index 48e8f40d4..a4ccec978 100644 --- a/src/screens/createKeys/new/NewMasterKeyScreen.tsx +++ b/src/screens/createKeys/new/NewMasterKeyScreen.tsx @@ -2,8 +2,7 @@ import { CompositeScreenProps } from '@react-navigation/native' import { useCallback, useMemo, useState } from 'react' import { StyleSheet, View } from 'react-native' import { useTranslation } from 'react-i18next' - -import { KeyManagementSystem } from 'lib/core' +import { generateMnemonic } from '@rsksmart/rif-id-mnemonic' import { AppButton, @@ -35,24 +34,24 @@ type Props = CompositeScreenProps< enum TestID { SecureLaterButton = 'SecureLater', } + export const NewMasterKeyScreen = ({ navigation }: Props) => { const initializeWallet = useInitializeWallet() const dispatch = useAppDispatch() const { t } = useTranslation() usePreventScreenshot(t) - const mnemonic = useMemo(() => KeyManagementSystem.create().mnemonic, []) - const mnemonicArray = mnemonic ? mnemonic.split(' ') : [] + const mnemonic = useMemo(() => generateMnemonic(12), []) const [isMnemonicVisible, setIsMnemonicVisible] = useState(false) const onSecureLater = useCallback(async () => { saveKeyVerificationReminder(true) dispatch( createWallet({ - mnemonic: KeyManagementSystem.create().mnemonic, + mnemonic, initializeWallet, }), ) - }, [dispatch, initializeWallet]) + }, [dispatch, initializeWallet, mnemonic]) return ( @@ -70,7 +69,7 @@ export const NewMasterKeyScreen = ({ navigation }: Props) => { ) => { - const { wallet } = useContext(WalletContext) + const wallet = useWallet() const { t } = useTranslation() const isFocused = useIsFocused() diff --git a/src/screens/pinScreen/index.tsx b/src/screens/pinScreen/index.tsx index 340b51d9a..45bfff9c2 100644 --- a/src/screens/pinScreen/index.tsx +++ b/src/screens/pinScreen/index.tsx @@ -12,10 +12,6 @@ import Icon from 'react-native-vector-icons/FontAwesome5' import { useIsFocused } from '@react-navigation/native' import { useSafeAreaInsets } from 'react-native-safe-area-context' -import { - rootTabsRouteNames, - RootTabsScreenProps, -} from 'navigation/rootNavigator/types' import { screenOptionsWithHeader } from 'navigation/index' import { AppButton, @@ -23,7 +19,12 @@ import { Typography, } from 'components/index' import { useAppDispatch, useAppSelector } from 'store/storeUtils' -import { setFullscreen, unlockApp } from 'store/slices/settingsSlice' +import { + selectIsUnlocked, + setFullscreen, + setUnlocked, + unlockApp, +} from 'store/slices/settingsSlice' import { selectPin, setPinState } from 'store/slices/persistentDataSlice' import { sharedColors, sharedStyles } from 'shared/constants' import { castStyle } from 'shared/utils' @@ -118,8 +119,7 @@ const getInitialPinSettings = ( type Props = | SettingsScreenProps - | RootTabsScreenProps - | CreateKeysScreenProps + | CreateKeysScreenProps export const PinScreen = ({ navigation, route }: Props) => { const initializeWallet = useInitializeWallet() @@ -132,6 +132,7 @@ export const PinScreen = ({ navigation, route }: Props) => { const backScreen = route.params?.backScreen const dispatch = useAppDispatch() const statePIN = useAppSelector(selectPin) + const unlocked = useAppSelector(selectIsUnlocked) const textInputRef = useRef(null) const [PIN, setPIN] = useState(defaultPin) @@ -286,12 +287,14 @@ export const PinScreen = ({ navigation, route }: Props) => { // if pin change requested set new pin setTimeout(() => { dispatch(setPinState(PIN.join(''))) + !unlocked && dispatch(setUnlocked(true)) navigation.goBack() }, 1000) } resetPin() }, [ + unlocked, isChangeRequested, isPinEqual, resetPin, diff --git a/src/screens/profile/ProfileCreateScreen.tsx b/src/screens/profile/ProfileCreateScreen.tsx index 5e5285026..ac6284f6a 100644 --- a/src/screens/profile/ProfileCreateScreen.tsx +++ b/src/screens/profile/ProfileCreateScreen.tsx @@ -1,4 +1,4 @@ -import { useCallback, useContext, useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { FormProvider, useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { ScrollView, Share, StyleSheet, View } from 'react-native' @@ -6,7 +6,6 @@ import Clipboard from '@react-native-community/clipboard' import Icon from 'react-native-vector-icons/FontAwesome' import MaterialIcon from 'react-native-vector-icons/MaterialIcons' -import { shortAddress } from 'lib/utils' import { RnsProcessor } from 'lib/rns' import { @@ -47,14 +46,16 @@ import { AppSpinner } from 'components/index' import { AvatarIcon } from 'components/icons/AvatarIcon' import { rootTabsRouteNames } from 'navigation/rootNavigator' import { RNS_ADDRESSES_BY_CHAIN_ID } from 'screens/rnsManager/types' -import { WalletContext } from 'shared/wallet' +import { useWallet } from 'shared/wallet' +import { useAddress } from 'shared/hooks' import { rnsManagerStyles } from '../rnsManager/rnsManagerStyles' export const ProfileCreateScreen = ({ navigation, }: ProfileStackScreenProps) => { - const { wallet } = useContext(WalletContext) + const wallet = useWallet() + const address = useAddress(wallet) const dispatch = useAppDispatch() const profile = useAppSelector(selectProfile) @@ -70,10 +71,7 @@ export const ProfileCreateScreen = ({ const { resetField, setValue } = methods const { t } = useTranslation() - const { displayAddress } = getAddressDisplayText( - wallet?.smartWallet.smartWalletAddress ?? '', - chainId, - ) + const { displayAddress } = getAddressDisplayText(address, chainId) const onSetEmail = useCallback( (_email: string) => { @@ -89,6 +87,10 @@ export const ProfileCreateScreen = ({ [dispatch, profile], ) + const onCopyAddress = useCallback(() => { + Clipboard.setString(address) + }, [address]) + const resetPhone = useCallback(() => { resetField('phone') dispatch(setProfile({ ...profile, phone: '' })) @@ -142,6 +144,7 @@ export const ProfileCreateScreen = ({ ) { const rns = new RnsProcessor({ wallet, + address, rnsAddresses: RNS_ADDRESSES_BY_CHAIN_ID[chainId], }) commitment( @@ -230,14 +233,10 @@ export const ProfileCreateScreen = ({ style={styles.copyIcon} color={sharedColors.white} size={defaultIconSize} - onPress={() => - Clipboard.setString( - wallet?.smartWallet.smartWalletAddress || '', - ) - } + onPress={onCopyAddress} /> } - placeholder={shortAddress(wallet?.smartWallet.smartWalletAddress)} + placeholder={displayAddress} isReadOnly testID={'TestID.AddressText'} /> diff --git a/src/screens/receive/ReceiveScreen.tsx b/src/screens/receive/ReceiveScreen.tsx index 3c29baab7..227084c4c 100644 --- a/src/screens/receive/ReceiveScreen.tsx +++ b/src/screens/receive/ReceiveScreen.tsx @@ -1,4 +1,4 @@ -import { useCallback, useContext, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { ScrollView, StyleSheet, @@ -32,7 +32,8 @@ import { selectProfile, selectUsername } from 'store/slices/profileSlice' import { getIconSource } from 'screens/home/TokenImage' import { ProfileStatus } from 'navigation/profileNavigator/types' import { getPopupMessage } from 'shared/popupMessage' -import { WalletContext } from 'shared/wallet' +import { useWallet } from 'shared/wallet' +import { useAddress } from 'shared/hooks' import { rootTabsRouteNames } from 'navigation/rootNavigator' export enum TestID { @@ -67,16 +68,17 @@ export const ReceiveScreen = ({ const [shouldShowAssets, setShouldShowAssets] = useState(false) - const { wallet } = useContext(WalletContext) + const wallet = useWallet() + const walletAddress = useAddress(wallet) const chainId = useAppSelector(selectChainId) const profile = useAppSelector(selectProfile) const rskAddress = useMemo(() => { - if (wallet && chainId) { - return getAddressDisplayText(wallet.smartWalletAddress, chainId) + if (chainId) { + return getAddressDisplayText(walletAddress, chainId) } return null - }, [wallet, chainId]) + }, [walletAddress, chainId]) const onShareUsername = useCallback(() => { Share.share({ diff --git a/src/screens/rnsManager/DomainInput.tsx b/src/screens/rnsManager/DomainInput.tsx index 34769ed24..8b1d30a0c 100644 --- a/src/screens/rnsManager/DomainInput.tsx +++ b/src/screens/rnsManager/DomainInput.tsx @@ -1,4 +1,3 @@ -import { RIFWallet } from '@rsksmart/rif-wallet-core' import { RSKRegistrar } from '@rsksmart/rns-sdk' import debounce from 'lodash.debounce' import { useCallback, useEffect, useMemo, useState } from 'react' @@ -14,14 +13,12 @@ import { Input, Typography } from 'components/index' import { sharedColors } from 'shared/constants' import { castStyle } from 'shared/utils' import { colors } from 'src/styles' -import { useAppSelector } from 'store/storeUtils' -import { selectChainId } from 'store/slices/settingsSlice' -import { RNS_ADDRESSES_BY_CHAIN_ID } from 'screens/rnsManager/types' import { minDomainLength } from './SearchDomainScreen' interface Props { - wallet: RIFWallet + address: string + rskRegistrar: RSKRegistrar inputName: string domainValue: string error: FieldError | undefined @@ -47,7 +44,8 @@ const labelColorMap = new Map([ ]) export const DomainInput = ({ - wallet, + address, + rskRegistrar, inputName, domainValue, onDomainAvailable, @@ -58,21 +56,10 @@ export const DomainInput = ({ const [domainAvailability, setDomainAvailability] = useState( DomainStatus.NONE, ) - const chainId = useAppSelector(selectChainId) const { t } = useTranslation() const errorType = error?.type const errorMessage = error?.message - const rskRegistrar = useMemo( - () => - new RSKRegistrar( - RNS_ADDRESSES_BY_CHAIN_ID[chainId].rskOwnerAddress, - RNS_ADDRESSES_BY_CHAIN_ID[chainId].fifsAddrRegistrarAddress, - RNS_ADDRESSES_BY_CHAIN_ID[chainId].rifTokenAddress, - wallet, - ), - [wallet, chainId], - ) const searchDomain = useCallback( async (domain: string) => { if (errorType === 'matches' && errorMessage) { @@ -86,9 +73,8 @@ export const DomainInput = ({ if (!available) { const ownerAddress = await rskRegistrar.ownerOf(domain) - const currentWallet = wallet.smartWallet.smartWalletAddress - if (currentWallet === ownerAddress) { + if (address === ownerAddress) { setDomainAvailability(DomainStatus.OWNED) onDomainOwned(true) } else { @@ -106,7 +92,7 @@ export const DomainInput = ({ errorMessage, onDomainAvailable, rskRegistrar, - wallet.smartWallet.smartWalletAddress, + address, onDomainOwned, ], ) diff --git a/src/screens/rnsManager/PurchaseDomainScreen.tsx b/src/screens/rnsManager/PurchaseDomainScreen.tsx index 2818cec3c..59cd98c76 100644 --- a/src/screens/rnsManager/PurchaseDomainScreen.tsx +++ b/src/screens/rnsManager/PurchaseDomainScreen.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState, useCallback, useContext } from 'react' +import { useEffect, useMemo, useState, useCallback } from 'react' import { FormProvider, useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { StyleSheet, View } from 'react-native' @@ -38,7 +38,8 @@ import { rootTabsRouteNames } from 'navigation/rootNavigator' import { handleDomainTransactionStatusChange } from 'screens/rnsManager/utils' import { selectChainId } from 'store/slices/settingsSlice' import { RNS_ADDRESSES_BY_CHAIN_ID } from 'screens/rnsManager/types' -import { WalletContext } from 'shared/wallet' +import { useWallet } from 'shared/wallet' +import { useAddress } from 'shared/hooks' import { rnsManagerStyles } from './rnsManagerStyles' @@ -52,7 +53,8 @@ export enum TestID { export const PurchaseDomainScreen = ({ navigation }: Props) => { const dispatch = useAppDispatch() const rifToken = useRifToken() - const { wallet } = useContext(WalletContext) + const wallet = useWallet() + const address = useAddress(wallet) const profile = useAppSelector(selectProfile) const chainId = useAppSelector(selectChainId) const alias = profile.alias @@ -64,13 +66,14 @@ export const PurchaseDomainScreen = ({ navigation }: Props) => { wallet && new RnsProcessor({ wallet, + address, onSetTransactionStatusChange: handleDomainTransactionStatusChange( dispatch, wallet, ), rnsAddresses: RNS_ADDRESSES_BY_CHAIN_ID[chainId], }), - [dispatch, wallet, chainId], + [dispatch, wallet, address, chainId], ) const methods = useForm() diff --git a/src/screens/rnsManager/SearchDomainScreen.tsx b/src/screens/rnsManager/SearchDomainScreen.tsx index 286f6d22e..b21a2ee0a 100644 --- a/src/screens/rnsManager/SearchDomainScreen.tsx +++ b/src/screens/rnsManager/SearchDomainScreen.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next' import { Alert, ScrollView, StyleSheet, View } from 'react-native' import Icon from 'react-native-vector-icons/Entypo' import * as yup from 'yup' +import { RSKRegistrar } from '@rsksmart/rns-sdk' import { RnsProcessor, @@ -38,6 +39,7 @@ import { ConfirmationModal } from 'components/modal' import { selectChainId } from 'store/slices/settingsSlice' import { RNS_ADDRESSES_BY_CHAIN_ID } from 'screens/rnsManager/types' import { useWalletState } from 'shared/wallet' +import { useAddress } from 'src/shared/hooks' import { DomainInput } from './DomainInput' import { rnsManagerStyles } from './rnsManagerStyles' @@ -53,6 +55,7 @@ interface FormValues { export const SearchDomainScreen = ({ navigation }: Props) => { const { wallet, walletIsDeployed } = useWalletState() + const address = useAddress(wallet) const chainId = useAppSelector(selectChainId) const { isDeployed, loading } = walletIsDeployed @@ -112,13 +115,27 @@ export const SearchDomainScreen = ({ navigation }: Props) => { () => new RnsProcessor({ wallet, + address, onSetTransactionStatusChange: handleDomainTransactionStatusChange( dispatch, wallet, ), rnsAddresses: RNS_ADDRESSES_BY_CHAIN_ID[chainId], }), - [dispatch, wallet, chainId], + [dispatch, wallet, address, chainId], + ) + + // @TODO: figure out if RNS_ADDRESSES_BY_CHAIN_ID + // can be put to the lib by passing it chainID + const rskRegistrar = useMemo( + () => + new RSKRegistrar( + RNS_ADDRESSES_BY_CHAIN_ID[chainId].rskOwnerAddress, + RNS_ADDRESSES_BY_CHAIN_ID[chainId].fifsAddrRegistrarAddress, + RNS_ADDRESSES_BY_CHAIN_ID[chainId].rifTokenAddress, + wallet, + ), + [wallet, chainId], ) const onSubmit = useCallback( @@ -237,7 +254,8 @@ export const SearchDomainScreen = ({ navigation }: Props) => { + (dispatch: AppDispatch, wallet: Wallet) => async (tx: Parameters[0]) => { const txTransformed = tx if (txTransformed.txStatus === 'PENDING') { diff --git a/src/screens/send/transferTokens.ts b/src/screens/send/transferTokens.ts index 2829ab2a4..b6c14281a 100644 --- a/src/screens/send/transferTokens.ts +++ b/src/screens/send/transferTokens.ts @@ -1,10 +1,11 @@ import { convertToERC20Token, makeRBTCToken } from '@rsksmart/rif-wallet-token' import { BigNumber, utils } from 'ethers' import { ITokenWithBalance } from '@rsksmart/rif-wallet-services' -import { RIFWallet } from '@rsksmart/rif-wallet-core' import { sanitizeMaxDecimalText } from 'lib/utils' +import { Wallet } from 'shared/wallet' + import { OnSetCurrentTransactionFunction, OnSetErrorFunction, @@ -12,11 +13,11 @@ import { TransactionInformation, } from './types' -interface IRifTransfer { +interface RifTransfer { token: ITokenWithBalance amount: string to: string - wallet: RIFWallet + wallet: Wallet chainId: number onSetError?: OnSetErrorFunction onSetCurrentTransaction?: OnSetCurrentTransactionFunction @@ -32,7 +33,7 @@ export const transfer = async ({ onSetError, onSetCurrentTransaction, onSetTransactionStatusChange, -}: IRifTransfer) => { +}: RifTransfer) => { onSetError?.(null) onSetCurrentTransaction?.({ status: 'USER_CONFIRM' }) diff --git a/src/screens/send/usePaymentExecutor.ts b/src/screens/send/usePaymentExecutor.ts index 0a05f72a4..394c4471d 100644 --- a/src/screens/send/usePaymentExecutor.ts +++ b/src/screens/send/usePaymentExecutor.ts @@ -3,7 +3,6 @@ import { convertBtcToSatoshi, UnspentTransactionType, } from '@rsksmart/rif-wallet-bitcoin' -import { RIFWallet } from '@rsksmart/rif-wallet-core' import { ITokenWithBalance } from '@rsksmart/rif-wallet-services' import { useTranslation } from 'react-i18next' @@ -26,11 +25,20 @@ import { addAddressToUsedBitcoinAddresses, selectWholeSettingsState, } from 'store/slices/settingsSlice' +import { Wallet } from 'shared/wallet' import { transferBitcoin } from './transferBitcoin' import { transfer } from './transferTokens' import { OnSetTransactionStatusChange, TransactionInformation } from './types' +interface ExecutePayment { + token: TokenBalanceObject + amount: number + to: string + wallet: Wallet + chainId: number +} + // Update transaction based on status // Pending will add a pendingTransaction // When it's done waiting, it'll modifyTransaction to update it with the receipt @@ -161,13 +169,7 @@ export const usePaymentExecutor = ( to, wallet, chainId, - }: { - token: TokenBalanceObject - amount: number - to: string - wallet: RIFWallet - chainId: number - }) => { + }: ExecutePayment) => { if ('bips' in token) { const hasError = checkBitcoinPaymentForErrors(utxos, amount) if (hasError) { diff --git a/src/screens/settings/RelayDeployScreen.tsx b/src/screens/settings/RelayDeployScreen.tsx index fc7b8ee8d..394b5113d 100644 --- a/src/screens/settings/RelayDeployScreen.tsx +++ b/src/screens/settings/RelayDeployScreen.tsx @@ -4,10 +4,11 @@ import { ScrollView } from 'react-native-gesture-handler' import { BigNumber } from 'ethers' import { useTranslation } from 'react-i18next' +import { RelayWallet } from 'lib/relayWallet' + import { AppButton, Typography, AppSpinner } from 'components/index' import { selectChainId } from 'store/slices/settingsSlice' import { useAppSelector } from 'store/storeUtils' -import { ChainTypeEnum } from 'store/slices/settingsSlice/types' import { getTokenAddress } from 'core/config' import { sharedColors, sharedStyles } from 'shared/constants' import { castStyle } from 'shared/utils' @@ -18,9 +19,10 @@ import { import { sharedHeaderLeftOptions } from 'navigation/index' import { rootTabsRouteNames } from 'navigation/rootNavigator' import { homeStackRouteNames } from 'navigation/homeNavigator/types' -import { chainTypesById } from 'shared/constants/chainConstants' import { useWholeWalletWithSetters } from 'shared/wallet' +import { TokenSymbol } from '../home/TokenImage' + export const RelayDeployScreen = ({ route, navigation, @@ -38,53 +40,55 @@ export const RelayDeployScreen = ({ const deploy = useCallback(async () => { try { - updateErrorState(null) - setWalletIsDeployed(prev => { - return prev && { ...prev, loading: true } - }) + if (wallet instanceof RelayWallet) { + updateErrorState(null) + setWalletIsDeployed(prev => { + return prev && { ...prev, loading: true } + }) - const freePayment = { - tokenContract: getTokenAddress( - chainTypesById[chainId] === ChainTypeEnum.MAINNET ? 'RIF' : 'tRIF', - chainTypesById[chainId], - ), - tokenAmount: BigNumber.from(0), - } + const freePayment = { + tokenContract: getTokenAddress( + chainId === 30 ? TokenSymbol.RIF : TokenSymbol.TRIF, + chainId, + ), + tokenAmount: BigNumber.from(0), + } - const result = await wallet.deploySmartWallet(freePayment) + const result = await wallet.deploySmartWallet(freePayment) - setWalletIsDeployed(prev => { - return ( - prev && { - ...prev, - txHash: result.hash, - } - ) - }) + setWalletIsDeployed(prev => { + return ( + prev && { + ...prev, + txHash: result.hash, + } + ) + }) - const receipt = await result.wait() + const receipt = await result.wait() - if (receipt.status) { + if (receipt.status) { + setWalletIsDeployed(prev => { + return ( + prev && { + ...prev, + isDeployed: true, + } + ) + }) + } else { + console.log('Deploy Error,', receipt) + updateErrorState(t('wallet_deploy_error')) + } setWalletIsDeployed(prev => { return ( prev && { ...prev, - isDeployed: true, + loading: false, } ) }) - } else { - console.log('Deploy Error,', receipt) - updateErrorState(t('wallet_deploy_error')) } - setWalletIsDeployed(prev => { - return ( - prev && { - ...prev, - loading: false, - } - ) - }) } catch (error) { console.log('DEPLOY FAILED', error) updateErrorState(error.toString()) diff --git a/src/screens/settings/SettingsScreen.tsx b/src/screens/settings/SettingsScreen.tsx index 0b08a3c78..d30a10e81 100644 --- a/src/screens/settings/SettingsScreen.tsx +++ b/src/screens/settings/SettingsScreen.tsx @@ -146,7 +146,9 @@ export const SettingsScreen = ({ style={styles.settingsItem} onPress={onSwitchChains}> - Switch to {ChainTypesInversed[chainTypesById[chainId]]} + {`${t('settings_screen_switch_to')} ${ + ChainTypesInversed[chainTypesById[chainId]] + }`} diff --git a/src/screens/settings/WalletBackup.tsx b/src/screens/settings/WalletBackup.tsx index 19f771b06..1b7db2182 100644 --- a/src/screens/settings/WalletBackup.tsx +++ b/src/screens/settings/WalletBackup.tsx @@ -2,8 +2,6 @@ import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { ScrollView, StyleSheet, View } from 'react-native' -import { KeyManagementSystem } from 'lib/core' - import { sharedColors } from 'shared/constants' import { MnemonicComponent, Typography } from 'components/index' import { @@ -12,7 +10,6 @@ import { } from 'navigation/settingsNavigator/types' import { castStyle, usePreventScreenshot } from 'shared/utils' import { getKeys } from 'storage/SecureStorage' -import { getCurrentChainId } from 'storage/ChainStorage' type Props = SettingsScreenProps @@ -27,11 +24,7 @@ export const WalletBackup = (_: Props) => { const fn = async () => { const keys = await getKeys() if (keys) { - const { kms } = KeyManagementSystem.fromSerialized( - keys, - getCurrentChainId(), - ) - setMnemonic(kms.mnemonic) + setMnemonic(keys.mnemonic) } } fn() diff --git a/src/screens/transactionSummary/TransactionSummaryComponent.tsx b/src/screens/transactionSummary/TransactionSummaryComponent.tsx index 0977e50a5..4a1711c5c 100644 --- a/src/screens/transactionSummary/TransactionSummaryComponent.tsx +++ b/src/screens/transactionSummary/TransactionSummaryComponent.tsx @@ -3,11 +3,8 @@ import { useTranslation } from 'react-i18next' import { Linking, ScrollView, StyleSheet, View } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import Icon from 'react-native-vector-icons/FontAwesome5' -import { RIFWallet } from '@rsksmart/rif-wallet-core' import { isBitcoinAddressValid } from '@rsksmart/rif-wallet-bitcoin' -import { displayRoundBalance } from 'lib/utils' - import { TokenBalance } from 'components/token' import { WINDOW_HEIGHT, @@ -15,7 +12,7 @@ import { sharedColors, sharedStyles, } from 'shared/constants' -import { castStyle } from 'shared/utils' +import { castStyle, formatTokenValues } from 'shared/utils' import { AppButton, AppTouchable, Typography } from 'components/index' import { useAppSelector } from 'store/storeUtils' import { isMyAddress } from 'components/address/lib' @@ -37,7 +34,7 @@ import { import { TransactionSummaryScreenProps } from '.' interface Props { - wallet: RIFWallet + address: string goBack?: () => void } @@ -48,7 +45,7 @@ type TransactionSummaryComponentProps = Omit< Props export const TransactionSummaryComponent = ({ - wallet, + address, transaction, buttons, functionName, @@ -59,13 +56,23 @@ export const TransactionSummaryComponent = ({ const chainId = useAppSelector(selectChainId) const { bottom } = useSafeAreaInsets() const { t } = useTranslation() - const { status, tokenValue, fee, usdValue, time, hashId, to, from } = - transaction + const { + status, + tokenValue, + fee, + usdValue, + time, + hashId, + to, + from, + totalToken, + totalUsd, + } = transaction const iconObject = transactionStatusToIconPropsMap.get(status) const transactionStatusText = transactionStatusDisplayText.get(status) - const amIReceiver = transaction.amIReceiver ?? isMyAddress(wallet, to) + const amIReceiver = transaction.amIReceiver ?? isMyAddress(address, to) const contactAddress = amIReceiver ? from || '' : to const contact = useAppSelector( getContactByAddress(contactAddress.toLowerCase()), @@ -89,21 +96,6 @@ export const TransactionSummaryComponent = ({ return t('transaction_summary_send_title') }, [amIReceiver, t, status]) - const totalToken = useMemo(() => { - if (tokenValue.symbol === fee.symbol) { - return Number(tokenValue.balance) + Number(fee.tokenValue) - } - return Number(tokenValue.balance) - }, [tokenValue, fee]) - - const totalUsd = useMemo( - () => - amIReceiver - ? usdValue.balance - : (Number(usdValue.balance) + Number(fee.usdValue)).toFixed(2), - [amIReceiver, usdValue.balance, fee.usdValue], - ) - const openTransactionHash = () => { const setting = isBitcoinAddressValid(to) ? SETTINGS.BTC_EXPLORER_ADDRESS_URL @@ -183,7 +175,7 @@ export const TransactionSummaryComponent = ({ size={12} /> - {displayRoundBalance(Number(fee.tokenValue))} {fee.symbol} + {formatTokenValues(fee.tokenValue)} {fee.symbol} @@ -196,7 +188,7 @@ export const TransactionSummaryComponent = ({ sharedStyles.textRight, { color: sharedColors.labelLight }, ]}> - {fee.usdValue} + {formatTokenValues(fee.usdValue)} @@ -217,8 +209,7 @@ export const TransactionSummaryComponent = ({ - {displayRoundBalance(totalToken, tokenValue.symbol)}{' '} - {tokenValue.symbol}{' '} + {formatTokenValues(totalToken)} {tokenValue.symbol}{' '} {tokenValue.symbol !== fee.symbol && !amIReceiver && t('transaction_summary_plus_fees')} @@ -238,7 +229,7 @@ export const TransactionSummaryComponent = ({ sharedStyles.textRight, { color: sharedColors.labelLight }, ]}> - {totalUsd} + {formatTokenValues(totalUsd)} {/* arrive value */} diff --git a/src/screens/transactionSummary/index.tsx b/src/screens/transactionSummary/index.tsx index 080ddac70..e1b382c51 100644 --- a/src/screens/transactionSummary/index.tsx +++ b/src/screens/transactionSummary/index.tsx @@ -14,6 +14,7 @@ import { setFullscreen } from 'store/slices/settingsSlice' import { TokenFeeValueObject } from 'store/slices/transactionsSlice' import { useAppDispatch } from 'store/storeUtils' import { WalletContext } from 'shared/wallet' +import { useAddress } from 'shared/hooks' import { TransactionStatus } from './transactionSummaryUtils' @@ -24,6 +25,8 @@ export interface TransactionSummaryScreenProps { usdValue: CurrencyValue fee: TokenFeeValueObject time: string + totalToken: number + totalUsd: number hashId?: string status?: TransactionStatus amIReceiver?: boolean @@ -41,6 +44,7 @@ export const TransactionSummaryScreen = ({ navigation, }: RootTabsScreenProps) => { const { wallet } = useContext(WalletContext) + const address = useAddress(wallet) const dispatch = useAppDispatch() const isFocused = useIsFocused() const { backScreen } = route.params @@ -80,7 +84,7 @@ export const TransactionSummaryScreen = ({ ) } diff --git a/src/screens/walletConnect/WalletConnect2Context.tsx b/src/screens/walletConnect/WalletConnect2Context.tsx index df591a584..28d5c0d4a 100644 --- a/src/screens/walletConnect/WalletConnect2Context.tsx +++ b/src/screens/walletConnect/WalletConnect2Context.tsx @@ -9,7 +9,6 @@ import { getSdkError, parseUri } from '@walletconnect/utils' import Web3Wallet, { Web3WalletTypes } from '@walletconnect/web3wallet' import { IWeb3Wallet } from '@walletconnect/web3wallet' import { WalletConnectAdapter } from '@rsksmart/rif-wallet-adapters' -import { RIFWallet } from '@rsksmart/rif-wallet-core' import { buildRskAllowedNamespaces, @@ -105,7 +104,7 @@ export const WalletConnect2Context = interface WalletConnect2ProviderProps { children: ReactElement - wallet: RIFWallet | null + wallet: Wallet | null } export const WalletConnect2Provider = ({ diff --git a/src/shared/hooks/index.ts b/src/shared/hooks/index.ts new file mode 100644 index 000000000..44ff0f3f9 --- /dev/null +++ b/src/shared/hooks/index.ts @@ -0,0 +1,12 @@ +import { useMemo } from 'react' + +import { RelayWallet } from 'lib/relayWallet' + +import { Wallet } from '../wallet' + +export const addressToUse = (wallet: Wallet) => + !(wallet instanceof RelayWallet) ? wallet.address : wallet.smartWalletAddress + +export const useAddress = (wallet: Wallet): string => { + return useMemo(() => addressToUse(wallet), [wallet]) +} diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts index 6e1a3088b..0f5206273 100644 --- a/src/shared/types/index.ts +++ b/src/shared/types/index.ts @@ -1,5 +1,6 @@ import { SendBitcoinRequest } from '@rsksmart/rif-wallet-bitcoin' -import { Request } from '@rsksmart/rif-wallet-core' + +import { Request } from 'lib/eoaWallet' import { rootTabsRouteNames, diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts index 9bf978986..0de96a1c9 100644 --- a/src/shared/utils/index.ts +++ b/src/shared/utils/index.ts @@ -14,9 +14,82 @@ import { } from 'react-native-screenshot-prevent' import { useTranslation } from 'react-i18next' import { useIsFocused } from '@react-navigation/native' +import Config from 'react-native-config' +import { providers } from 'ethers' +import { RifRelayConfig } from '@rsksmart/rif-relay-light-sdk' +import { ChainID, EOAWallet, OnRequest, WalletState } from 'lib/eoaWallet' +import { RelayWallet } from 'lib/relayWallet' + +import { Wallet } from '../wallet' import { ErrorWithMessage } from '../types' +const tiniestAmount = 0.000001 + +const formatWithDigits = (number: string) => { + const splitArr = number.split('.') + const secondPart = + splitArr[1].length > 8 ? splitArr[1].slice(0, 9) : splitArr[1] + + return [splitArr[0], secondPart].join('.') +} + +export const formatSmallNumbers = (smallNumber: string | number) => { + if (isNaN(Number(smallNumber))) { + return smallNumber.toString() + } + + smallNumber = smallNumber.toString() + const asNumber = Number(smallNumber) + + if (asNumber >= tiniestAmount) { + return formatWithDigits(smallNumber) + } + + return asNumber !== 0 ? `< ${tiniestAmount}` : '0.00' +} + +// this needs to be here because of the failing tests +enum TokenSymbol { + TRBTC = 'TRBTC', + RBTC = 'RBTC', +} + +export const rbtcMap = new Map([ + [TokenSymbol.TRBTC, true], + [TokenSymbol.RBTC, true], + [undefined, false], +]) + +export const formatTokenValues = (number: string | number) => { + // make sure to use this only at the end when showing values + if (isNaN(Number(number))) { + return number.toString() + } + number = number.toString() + const asNumber = Number(number) + + if (asNumber < 1) { + return formatSmallNumbers(number) + } + + if (number.includes('.') && asNumber > 1) { + return formatWithDigits(number) + } + + if (number.length <= 3) { + return number + } + + const longNumberArr = number.split('') + + for (let i = number.length - 3; i > 0; i -= 3) { + longNumberArr.splice(i, 0, ',') + } + + return longNumberArr.join('') +} + export const errorHandler = (error: unknown) => { if (typeof error === 'object' && Object.hasOwn(error as object, 'message')) { const err = error as ErrorWithMessage @@ -83,3 +156,106 @@ export const usePreventScreenshot = ( disableSecureView() }, [isFocused]) } + +export const getFormattedTokenValue = (tokenValue: string) => { + if (!tokenValue.includes('.')) { + return tokenValue + } + const tokenValueArr = tokenValue.split('.') + const decimals = tokenValueArr[1].length + + if (decimals < 8) { + return tokenValue + } + + const restDecimals = tokenValueArr[1].split('').slice(7, decimals) + + let moreThanZeroIndex = 0 + + for (let i = 0; i < restDecimals.length; i++) { + if (Number(restDecimals[i]) > 0) { + moreThanZeroIndex = i + break + } + } + + const hasOneMoreDigit = !!restDecimals[moreThanZeroIndex + 1] + const lastAfterZero = restDecimals.slice( + 0, + hasOneMoreDigit ? moreThanZeroIndex + 2 : moreThanZeroIndex + 1, + ) + const ending = restDecimals[moreThanZeroIndex + 2] ? '...' : '' + + return ( + tokenValueArr[0] + + '.' + + tokenValueArr[1].slice(0, 7).concat(...lastAfterZero) + + ending + ) +} + +export const createAppWallet = async ( + mnemonic: string, + chainId: ChainID, + jsonRpcProvider: providers.StaticJsonRpcProvider, + onRequest: OnRequest, + config: RifRelayConfig, + cache?: (privateKey: string, mnemonic?: string) => void, +) => { + const useRelay = Config.USE_RELAY === 'true' + console.log('USE RELAY createAppWallet', useRelay) + let wallet: Wallet + + if (useRelay) { + wallet = await RelayWallet.create( + mnemonic, + chainId, + jsonRpcProvider, + onRequest, + config, + cache, + ) + } else { + wallet = EOAWallet.create( + mnemonic, + chainId, + jsonRpcProvider, + onRequest, + cache, + ) + } + + return wallet +} + +export const loadAppWallet = async ( + keys: WalletState, + chainId: ChainID, + jsonRpcProvider: providers.StaticJsonRpcProvider, + onRequest: OnRequest, + config: RifRelayConfig, +) => { + const useRelay = Config.USE_RELAY === 'true' + console.log('USE RELAY loadAppWallet', useRelay) + + let wallet: Wallet + + if (useRelay) { + wallet = await RelayWallet.fromWalletState( + keys, + chainId, + jsonRpcProvider, + onRequest, + config, + ) + } else { + wallet = EOAWallet.fromWalletState( + keys, + chainId, + jsonRpcProvider, + onRequest, + ) + } + + return wallet +} diff --git a/src/shared/wallet/index.tsx b/src/shared/wallet/index.tsx index bd4c861e4..95e18d9f0 100644 --- a/src/shared/wallet/index.tsx +++ b/src/shared/wallet/index.tsx @@ -1,4 +1,3 @@ -import { RIFWallet } from '@rsksmart/rif-wallet-core' import { Dispatch, PropsWithChildren, @@ -6,13 +5,15 @@ import { createContext, useCallback, useContext, - useEffect, useState, } from 'react' +import { EOAWallet } from 'lib/eoaWallet' +import { RelayWallet } from 'lib/relayWallet' + // preparing for having different types of Wallets // like EOA Wallet, MAGIC etc -export type Wallet = RIFWallet +export type Wallet = RelayWallet | EOAWallet interface WalletIsDeployed { loading: boolean @@ -52,14 +53,6 @@ export const WalletProvider = ({ const [walletIsDeployed, setWalletIsDeployed] = useState(null) - useEffect(() => { - console.log('wallet updated', wallet) - }, [wallet]) - - useEffect(() => { - console.log('walletIsDeployed updated', walletIsDeployed) - }, [walletIsDeployed]) - const initializeWallet = useCallback( (walletArg: Wallet, walletIsDeployedArg: WalletIsDeployed) => { setWallet(walletArg) diff --git a/src/storage/SecureStorage.ts b/src/storage/SecureStorage.ts index 8cc2e0289..713fdbc89 100644 --- a/src/storage/SecureStorage.ts +++ b/src/storage/SecureStorage.ts @@ -11,11 +11,13 @@ import { } from 'react-native-keychain' import DeviceInfo from 'react-native-device-info' +import { WalletState } from 'lib/eoaWallet' + import { getKeysFromMMKV, saveKeysInMMKV } from './MainStorage' const keyManagement = 'KEY_MANAGEMENT' -export const getKeys = async () => { +export const getKeys = async (): Promise => { try { const isEmulator = await DeviceInfo.isEmulator() @@ -36,14 +38,14 @@ export const getKeys = async () => { if (!keys) { return null } - return keys.password + return JSON.parse(keys.password) } else { const keys = getKeysFromMMKV() if (!keys) { return null } - return keys + return JSON.parse(keys) } } catch (err) { console.log('ERROR GETTING KEYS', err.message) @@ -51,8 +53,10 @@ export const getKeys = async () => { } } -export const saveKeys = async (keysValue: string) => { +export const saveKeys = async (privateKey: string, mnemonic?: string) => { try { + const keysObject = JSON.stringify({ privateKey, mnemonic }) + const isEmulator = await DeviceInfo.isEmulator() if (!isEmulator) { @@ -64,7 +68,7 @@ export const saveKeys = async (keysValue: string) => { }) : Boolean(supportedBiometry) - return setGenericPassword(keyManagement, keysValue, { + return setGenericPassword(keyManagement, keysObject, { accessible: ACCESSIBLE.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, authenticationType: AUTHENTICATION_TYPE.DEVICE_PASSCODE_OR_BIOMETRICS, accessControl: @@ -73,7 +77,7 @@ export const saveKeys = async (keysValue: string) => { : ACCESS_CONTROL.BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE, }) } else { - saveKeysInMMKV(keysValue) + saveKeysInMMKV(keysObject) return { service: 'MMKV', storage: 'MAIN_STORAGE', diff --git a/src/subscriptions/types.ts b/src/subscriptions/types.ts index 94997e308..2dc177c46 100644 --- a/src/subscriptions/types.ts +++ b/src/subscriptions/types.ts @@ -86,3 +86,7 @@ export interface TransactionsServerResponseWithActivityTransactions extends TransactionsServerResponse { activityTransactions: IActivityTransaction[] } + +export interface AbiWallet { + abiEnhancer: IAbiEnhancer +} diff --git a/src/ux/requestsModal/ReviewBitcoinTransactionContainer.tsx b/src/ux/requestsModal/ReviewBitcoinTransactionContainer.tsx index 155934cdf..68976827c 100644 --- a/src/ux/requestsModal/ReviewBitcoinTransactionContainer.tsx +++ b/src/ux/requestsModal/ReviewBitcoinTransactionContainer.tsx @@ -19,6 +19,8 @@ import { sharedColors } from 'shared/constants' import { AppButtonBackgroundVarietyEnum, Input } from 'components/index' import { TransactionSummaryScreenProps } from 'screens/transactionSummary' import { WalletContext } from 'shared/wallet' +import { useAddress } from 'shared/hooks' +import { formatTokenValues } from 'shared/utils' import { BitcoinMiningFeeContainer } from './BitcoinMiningFeeContainer' @@ -34,6 +36,7 @@ export const ReviewBitcoinTransactionContainer = ({ onCancel, }: ReviewBitcoinTransactionContainerProps) => { const { wallet } = useContext(WalletContext) + const address = useAddress(wallet) const insets = useSafeAreaInsets() const { t } = useTranslation() const tokenPrices = useAppSelector(selectUsdPrices) @@ -102,7 +105,7 @@ export const ReviewBitcoinTransactionContainer = ({ time: 'approx 1 min', total: { tokenValue: amountToPay, - usdValue: Number(amountToPayUsd) + Number(feeUsd), + usdValue: formatTokenValues(Number(amountToPayUsd) + Number(feeUsd)), }, to: addressToPay, }, @@ -142,7 +145,7 @@ export const ReviewBitcoinTransactionContainer = ({ return ( {/*Without a Wallet it's not possible to initiate a transaction */} - + ) } diff --git a/src/ux/requestsModal/ReviewRelayTransaction/ReviewTransactionContainer.tsx b/src/ux/requestsModal/ReviewRelayTransaction/ReviewTransactionContainer.tsx index 0bf9c59ce..5135024de 100644 --- a/src/ux/requestsModal/ReviewRelayTransaction/ReviewTransactionContainer.tsx +++ b/src/ux/requestsModal/ReviewRelayTransaction/ReviewTransactionContainer.tsx @@ -1,8 +1,4 @@ -import { - OverriddableTransactionOptions, - SendTransactionRequest, -} from '@rsksmart/rif-wallet-core' -import { BigNumber, constants } from 'ethers' +import { BigNumber } from 'ethers' import { useCallback, useContext, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { Alert, StyleSheet, View } from 'react-native' @@ -10,6 +6,11 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context' import { isAddress } from '@rsksmart/rsk-utils' import { balanceToDisplay, convertTokenToUSD } from 'lib/utils' +import { RelayWallet } from 'lib/relayWallet' +import { + OverriddableTransactionOptions, + SendTransactionRequest, +} from 'lib/eoaWallet' import { AppButtonBackgroundVarietyEnum } from 'components/index' import { getTokenAddress } from 'core/config' @@ -18,7 +19,12 @@ import { TransactionSummaryScreenProps } from 'screens/transactionSummary' import { TransactionSummaryComponent } from 'screens/transactionSummary/TransactionSummaryComponent' import { sharedColors } from 'shared/constants' import { chainTypesById } from 'shared/constants/chainConstants' -import { castStyle, errorHandler } from 'shared/utils' +import { + castStyle, + errorHandler, + formatTokenValues, + rbtcMap, +} from 'shared/utils' import { selectChainId } from 'store/slices/settingsSlice' import { ChainTypeEnum } from 'store/slices/settingsSlice/types' import { selectUsdPrices } from 'store/slices/usdPricesSlice' @@ -27,8 +33,9 @@ import { addRecentContact } from 'store/slices/contactsSlice' import { selectBalances } from 'store/slices/balancesSlice' import { selectRecentRskTransactions } from 'store/slices/transactionsSlice' import { WalletContext } from 'shared/wallet' +import { useAddress } from 'shared/hooks' -import useEnhancedWithGas from '../useEnhancedWithGas' +import { useEnhancedWithGas } from '../useEnhancedWithGas' const tokenToBoolMap = new Map([ [TokenSymbol.RIF, true], @@ -42,6 +49,16 @@ interface Props { onCancel: () => void } +const getFeeSymbol = (isMainnet: boolean, isRelayWallet: boolean) => { + switch (isMainnet) { + case false: + return !isRelayWallet ? TokenSymbol.TRBTC : TokenSymbol.TRIF + + case true: + return !isRelayWallet ? TokenSymbol.RBTC : TokenSymbol.RIF + } +} + export const ReviewTransactionContainer = ({ request, onCancel, @@ -52,10 +69,11 @@ export const ReviewTransactionContainer = ({ const tokenPrices = useAppSelector(selectUsdPrices) // enhance the transaction to understand what it is: const { wallet } = useContext(WalletContext) + const address = useAddress(wallet) const chainId = useAppSelector(selectChainId) const balances = useAppSelector(selectBalances) const pendingTransactions = useAppSelector(selectRecentRskTransactions) - const [txCostInRif, setTxCostInRif] = useState() + const [txCost, setTxCost] = useState() const { t } = useTranslation() // this is for typescript, and should not happen as the transaction was created by the wallet instance. @@ -63,10 +81,11 @@ export const ReviewTransactionContainer = ({ throw new Error('no wallet') } - const txRequest = request.payload[0] + const txRequest = request.payload const { enhancedTransactionRequest, isLoaded } = useEnhancedWithGas( wallet, txRequest, + chainId, ) const { @@ -79,10 +98,8 @@ export const ReviewTransactionContainer = ({ } = enhancedTransactionRequest const isMainnet = chainTypesById[chainId] === ChainTypeEnum.MAINNET - - const rbtcSymbol = isMainnet ? TokenSymbol.RBTC : TokenSymbol.TRBTC - const feeSymbol = isMainnet ? TokenSymbol.RIF : TokenSymbol.TRIF - const feeContract = getTokenAddress(feeSymbol, chainTypesById[chainId]) + const feeSymbol = getFeeSymbol(isMainnet, wallet instanceof RelayWallet) + const feeContract = getTokenAddress(feeSymbol, chainId) const getTokenBySymbol = useCallback( (symb: string) => { @@ -100,19 +117,15 @@ export const ReviewTransactionContainer = ({ ) const tokenContract = useMemo(() => { - const rbtcAddress = constants.AddressZero - if (symbol === rbtcSymbol) { - return rbtcAddress - } if (symbol) { try { - return getTokenAddress(symbol, chainTypesById[chainId]) + return getTokenAddress(symbol, chainId) } catch { return getTokenBySymbol(symbol).contractAddress } } return feeContract - }, [symbol, rbtcSymbol, feeContract, chainId, getTokenBySymbol]) + }, [symbol, feeContract, chainId, getTokenBySymbol]) const tokenQuote = tokenPrices[tokenContract]?.price const feeQuote = tokenPrices[feeContract]?.price @@ -125,30 +138,15 @@ export const ReviewTransactionContainer = ({ }, [onCancel, txRequest.to]) useEffect(() => { - wallet.rifRelaySdk - .estimateTransactionCost( - txRequest, - feeContract, - pendingTransactions.length, - ) - .then(setTxCostInRif) - .catch(err => { - console.log('Server Error', err) - request.reject('There is an error connecting to the RIF Relay Server.') - onCancel() - }) - }, [ - txRequest, - wallet.rifRelaySdk, - feeContract, - request, - onCancel, - pendingTransactions.length, - ]) + wallet + .estimateGas(txRequest, feeContract) + .then(setTxCost) + .catch(err => errorHandler(err)) + }, [txRequest, wallet, feeContract]) const confirmTransaction = useCallback(async () => { dispatch(addRecentContact(to)) - if (!txCostInRif) { + if (!txCost) { throw new Error('token cost has not been estimated') } @@ -157,7 +155,7 @@ export const ReviewTransactionContainer = ({ gasLimit: BigNumber.from(gasLimit), tokenPayment: { tokenContract: feeContract, - tokenAmount: txCostInRif, + tokenAmount: txCost, }, pendingTxsCount: pendingTransactions.length, } @@ -170,7 +168,7 @@ export const ReviewTransactionContainer = ({ } }, [ dispatch, - txCostInRif, + txCost, gasPrice, gasLimit, feeContract, @@ -186,14 +184,17 @@ export const ReviewTransactionContainer = ({ }, [onCancel, request]) const data: TransactionSummaryScreenProps = useMemo(() => { - const convertToUSD = (tokenValue: number, quote = 0) => - convertTokenToUSD(tokenValue, quote, true).toFixed(2) - - const feeValue = txCostInRif ? balanceToDisplay(txCostInRif, 18, 0) : '0' - + const feeValue = txCost ? balanceToDisplay(txCost, 18) : '0' + const rbtcFeeValue = + txCost && rbtcMap.get(feeSymbol) + ? formatTokenValues(txCost.toString()) + : undefined let insufficientFunds = false - if (tokenToBoolMap.get(symbol as TokenSymbol)) { + if ( + tokenToBoolMap.get(symbol as TokenSymbol) && + wallet instanceof RelayWallet + ) { insufficientFunds = Number(value) + Number(feeValue) > Number(balances[feeContract].balance) } else { @@ -205,11 +206,16 @@ export const ReviewTransactionContainer = ({ Alert.alert(t('transaction_summary_insufficient_funds')) } - // usd values - const tokenUsd = convertToUSD(Number(value), tokenQuote) - const feeUsd = convertToUSD(Number(feeValue), feeQuote) + // get usd values + const tokenUsd = convertTokenToUSD(Number(value), tokenQuote) + const feeUsd = convertTokenToUSD(Number(feeValue), feeQuote) const isAmountSmall = !Number(tokenUsd) && !!Number(value) + const totalToken = + symbol === feeSymbol ? Number(value) + Number(feeValue) : Number(value) + + const totalUsd = (Number(tokenUsd) + Number(feeUsd)).toFixed(2) + return { transaction: { tokenValue: { @@ -223,10 +229,12 @@ export const ReviewTransactionContainer = ({ balance: isAmountSmall ? '0.01' : tokenUsd, }, fee: { - tokenValue: feeValue, - usdValue: feeUsd, + tokenValue: rbtcFeeValue ?? feeValue, + usdValue: formatTokenValues(feeUsd), symbol: feeSymbol, }, + totalToken, + totalUsd, time: 'approx 1 min', to, }, @@ -252,7 +260,7 @@ export const ReviewTransactionContainer = ({ }, [ feeContract, balances, - txCostInRif, + txCost, value, tokenQuote, feeQuote, @@ -263,14 +271,15 @@ export const ReviewTransactionContainer = ({ confirmTransaction, cancelTransaction, functionName, + wallet, ]) return ( ) diff --git a/src/ux/requestsModal/SignRequestHandlerContainer.tsx b/src/ux/requestsModal/SignRequestHandlerContainer.tsx index 462dfc170..b46bdf5b8 100644 --- a/src/ux/requestsModal/SignRequestHandlerContainer.tsx +++ b/src/ux/requestsModal/SignRequestHandlerContainer.tsx @@ -1,11 +1,9 @@ import { StyleSheet, View } from 'react-native' -import { - SignMessageRequest, - SignTypedDataRequest, -} from '@rsksmart/rif-wallet-core' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useTranslation } from 'react-i18next' +import { SignMessageRequest, SignTypedDataRequest } from 'lib/eoaWallet' + import { castStyle } from 'shared/utils' import { sharedColors } from 'shared/constants' import { AppButton, Typography } from 'src/components' diff --git a/src/ux/requestsModal/SignTypedDataComponent.tsx b/src/ux/requestsModal/SignTypedDataComponent.tsx index f67d418e3..82bed6f7b 100644 --- a/src/ux/requestsModal/SignTypedDataComponent.tsx +++ b/src/ux/requestsModal/SignTypedDataComponent.tsx @@ -1,5 +1,6 @@ import { ScrollView } from 'react-native' -import { SignTypedDataArgs } from '@rsksmart/rif-wallet-core/dist/types' + +import { SignTypedDataArgs } from 'lib/eoaWallet' import { Typography } from 'src/components' diff --git a/src/ux/requestsModal/useEnhancedWithGas.test.ts b/src/ux/requestsModal/useEnhancedWithGas.test.ts index ffca65d88..bfe140c70 100644 --- a/src/ux/requestsModal/useEnhancedWithGas.test.ts +++ b/src/ux/requestsModal/useEnhancedWithGas.test.ts @@ -3,7 +3,7 @@ import { renderHook, act } from '@testing-library/react-hooks' import { BigNumber, Wallet, providers } from 'ethers' import { useSelector } from 'react-redux' -import useEnhancedWithGas from './useEnhancedWithGas' +import { useEnhancedWithGas } from './useEnhancedWithGas' jest.mock('react-redux') @@ -37,7 +37,7 @@ describe('hook: useEnhancedWithGas', function (this: { const runHook = async (tx: TransactionRequest) => { const { result, waitForNextUpdate } = renderHook(() => - useEnhancedWithGas(this.rifWallet, tx), + useEnhancedWithGas(this.rifWallet, tx, 31), ) await waitForNextUpdate() diff --git a/src/ux/requestsModal/useEnhancedWithGas.ts b/src/ux/requestsModal/useEnhancedWithGas.ts index 069583bc2..a4a45ddf6 100644 --- a/src/ux/requestsModal/useEnhancedWithGas.ts +++ b/src/ux/requestsModal/useEnhancedWithGas.ts @@ -1,12 +1,14 @@ import { useEffect, useState } from 'react' import { TransactionRequest } from '@ethersproject/providers' import { BigNumber } from 'ethers' -import { RIFWallet } from '@rsksmart/rif-wallet-core' +import { AbiEnhancer } from '@rsksmart/rif-wallet-abi-enhancer' import { isAddress } from '@rsksmart/rsk-utils' -import { useAppSelector } from 'store/storeUtils' -import { selectChainId } from 'store/slices/settingsSlice' -import { abiEnhancer } from 'core/setup' +import { TokenSymbol } from 'screens/home/TokenImage' +import { Wallet } from 'shared/wallet' +import { ChainTypesByIdType } from 'shared/constants/chainConstants' + +const abiEnhancer = new AbiEnhancer() const convertValueToString = (value?: object | boolean | string) => value ? value.toString() : '' @@ -23,13 +25,16 @@ const convertTransactionToStrings = (tx: TransactionRequest) => ({ }) export interface EnhancedTransactionRequest extends TransactionRequest { - symbol?: string + symbol?: TokenSymbol functionName?: string functionParameters?: string[] } -const useEnhancedWithGas = (wallet: RIFWallet, tx: TransactionRequest) => { - const chainId = useAppSelector(selectChainId) +export const useEnhancedWithGas = ( + wallet: Wallet, + tx: TransactionRequest, + chainId: ChainTypesByIdType, +) => { const [enhancedTransactionRequest, setEnhancedTransactionRequest] = useState({ gasPrice: '0', @@ -84,5 +89,3 @@ const useEnhancedWithGas = (wallet: RIFWallet, tx: TransactionRequest) => { return { enhancedTransactionRequest, isLoaded, setGasLimit, setGasPrice } } - -export default useEnhancedWithGas diff --git a/testLib/setup.ts b/testLib/setup.ts index 54eb8b38e..e6e6f4b69 100644 --- a/testLib/setup.ts +++ b/testLib/setup.ts @@ -1,30 +1,42 @@ -import { RIFWallet, OnRequest, Request } from '@rsksmart/rif-wallet-core' +import { Wallet } from 'ethers' -import { rifRelayConfig } from 'core/setup' +import { RelayWallet } from 'lib/relayWallet' +import { OnRequest, Request } from 'lib/eoaWallet' -import { createNewTestWallet } from './utils' +import { Wallet as WalletType } from 'shared/wallet' +import { getCurrentChainId } from 'storage/ChainStorage' +import { getRifRelayConfig } from 'store/slices/settingsSlice' + +import { testJsonRpcProvider } from './utils' export const setupTest = async ( - privateKey?: string, + mnemonic: string, ): Promise<{ navigation: { navigate: () => ReturnType } route: object - rifWallet: RIFWallet + wallet: WalletType }> => { jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }), })) - const wallet = await createNewTestWallet(privateKey) const onRequest: OnRequest = (nextRequest: Request) => nextRequest.confirm() - const rifWallet = await RIFWallet.create(wallet, onRequest, rifRelayConfig) + const wallet = await RelayWallet.create( + !mnemonic + ? (Wallet.createRandom().mnemonic as unknown as string) + : mnemonic, + getCurrentChainId(), + testJsonRpcProvider, + onRequest, + getRifRelayConfig(getCurrentChainId()), + ) - const deployTx = await rifWallet.smartWalletFactory.deploy() + const deployTx = await wallet.rifRelaySdk.smartWalletFactory.deploy() await deployTx.wait() return { - rifWallet, + wallet, navigation: { navigate: jest.fn(), }, diff --git a/yarn.lock b/yarn.lock index b45a7f85a..3dca98b25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2055,6 +2055,15 @@ axios "^1.2.3" ethers "^5.7.2" +"@rsksmart/rif-relay-light-sdk@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@rsksmart/rif-relay-light-sdk/-/rif-relay-light-sdk-1.1.1.tgz#8b1c413b70ca725d5339fb81532968133b254b67" + integrity sha512-5WpGEP8tfzQO/9d+N7xPy0U0A7kXNfytP1yHF0XKxMDqz2dWKrNC6sww1PolyqDBObrhQMjalSdnuVJ+y1g64g== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + axios "^1.2.3" + ethers "^5.7.2" + "@rsksmart/rif-wallet-abi-enhancer@*": version "1.0.8" resolved "https://registry.yarnpkg.com/@rsksmart/rif-wallet-abi-enhancer/-/rif-wallet-abi-enhancer-1.0.8.tgz#91b237c9b72730a6b13cab7038e34a3281ffba32" @@ -2072,9 +2081,9 @@ axios "^0.27.2" "@rsksmart/rif-wallet-abi-enhancer@^1.0.10": - version "1.0.10" - resolved "https://registry.yarnpkg.com/@rsksmart/rif-wallet-abi-enhancer/-/rif-wallet-abi-enhancer-1.0.10.tgz#7f3e27ad6f8be0daba4d64a99c27a1d0db57b1dc" - integrity sha512-5bPK5pn/HLF3YP+zv/eDsXND7zv0wq2EOTj8tCl9HXxcMTVOeumW+6vtHwPs8PMB5zArlCGk5I/Iu0z5hChbwg== + version "1.0.11" + resolved "https://registry.yarnpkg.com/@rsksmart/rif-wallet-abi-enhancer/-/rif-wallet-abi-enhancer-1.0.11.tgz#19a5209c42d3f3359919f346e9c9433dc77cecbf" + integrity sha512-kbGT200zt+tFAi4HUCwv57Ctbd19+rp8boNE38XTLWwQnp/QWE+EgBKT+Pclzh0cAaSm2ggIdUGvjF/UZ5ZoJw== dependencies: "@ethersproject/abi" "^5.7.0" "@ethersproject/abstract-provider" "^5.7.0" @@ -2134,17 +2143,6 @@ "@ethersproject/bytes" "^5.7.0" "@rsksmart/rif-relay-light-sdk" "*" -"@rsksmart/rif-wallet-core@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@rsksmart/rif-wallet-core/-/rif-wallet-core-1.0.5.tgz#aa5adf52e5bd34e3ea0cd7433f057c1191bb1842" - integrity sha512-84fY8w/lgVi2GGMb5+GDAHuE4WCoaUi6N8kn8ROpClkOjFdA/RWMNJKrVdx0auGjfPUOI58Vp0bAl/g75xCeig== - dependencies: - "@ethersproject/abstract-provider" "^5.7.0" - "@ethersproject/abstract-signer" "^5.7.0" - "@ethersproject/bignumber" "^5.7.0" - "@ethersproject/bytes" "^5.7.0" - "@rsksmart/rif-relay-light-sdk" "*" - "@rsksmart/rif-wallet-eip681@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@rsksmart/rif-wallet-eip681/-/rif-wallet-eip681-1.0.1.tgz#c78781ad664ac46167e1b84c9e649b8873035cc8"