diff --git a/mobile-app/App.tsx b/mobile-app/App.tsx index 8dd0926798..b841265c61 100644 --- a/mobile-app/App.tsx +++ b/mobile-app/App.tsx @@ -128,7 +128,7 @@ export default function App(): JSX.Element | null { renderType={customToast} > - +
diff --git a/mobile-app/app/api/transaction/transfer_domain.ts b/mobile-app/app/api/transaction/transfer_domain.ts index 277b3f79d8..0eced51a21 100644 --- a/mobile-app/app/api/transaction/transfer_domain.ts +++ b/mobile-app/app/api/transaction/transfer_domain.ts @@ -13,10 +13,8 @@ import { fromAddress, Eth } from "@defichain/jellyfish-address"; import { NetworkName } from "@defichain/jellyfish-network"; import { ConvertDirection } from "@screens/enum"; import TransferDomainV1 from "@shared-contracts/TransferDomainV1.json"; -import { getEthRpcUrl } from "@contexts/CustomServiceProvider"; -import { SecuredStoreAPI } from "@api/secured"; -const TD_CONTRACT_ADDR = "0xdf00000000000000000000000000000000000001"; +export const TD_CONTRACT_ADDR = "0xdf00000000000000000000000000000000000001"; const TRANSFER_DOMAIN_TYPE = { DVM: 2, @@ -38,7 +36,9 @@ interface TransferDomainSigner { convertDirection: ConvertDirection; dvmAddress: string; evmAddress: string; + chainId?: number; networkName: NetworkName; + nonce: number; } export async function transferDomainSigner({ @@ -49,7 +49,9 @@ export async function transferDomainSigner({ convertDirection, dvmAddress, evmAddress, + chainId, networkName, + nonce, }: TransferDomainSigner): Promise { const dvmScript = fromAddress(dvmAddress, networkName)?.script as Script; const evmScript = Eth.fromAddress(evmAddress) as Script; @@ -65,15 +67,6 @@ export async function transferDomainSigner({ ? [TRANSFER_DOMAIN_TYPE.EVM, TRANSFER_DOMAIN_TYPE.DVM] : [TRANSFER_DOMAIN_TYPE.DVM, TRANSFER_DOMAIN_TYPE.EVM]; - const privateKey = await account.privateKey(); - const accountEvmAddress = await account.getEvmAddress(); - - // TODO (lyka): Check android issue with null eth provider - const network = await SecuredStoreAPI.getNetwork(); - const ethRpc = new providers.JsonRpcProvider(getEthRpcUrl(network)); - const nonce = await ethRpc.getTransactionCount(accountEvmAddress); - const chainId = (await ethRpc.getNetwork()).chainId; - const signedEvmTxData = await createSignedEvmTx({ isEvmToDvm, sourceTokenId: stripEvmSuffixFromTokenId(sourceTokenId).toString(), @@ -81,8 +74,8 @@ export async function transferDomainSigner({ amount, dvmAddress, evmAddress, - accountEvmAddress, - privateKey, + accountEvmAddress: await account.getEvmAddress(), + privateKey: await account.privateKey(), chainId, nonce, }); @@ -159,9 +152,11 @@ export function transferDomainCrafter({ networkName, onBroadcast, onConfirmation, + chainId, submitButtonLabel, dvmAddress, evmAddress, + nonce, }: { amount: BigNumber; convertDirection: ConvertDirection; @@ -170,9 +165,11 @@ export function transferDomainCrafter({ networkName: NetworkName; onBroadcast: () => any; onConfirmation: () => void; + chainId?: number; submitButtonLabel?: string; dvmAddress: string; evmAddress: string; + nonce: number; }): DfTxSigner { if ( ![ConvertDirection.evmToDvm, ConvertDirection.dvmToEvm].includes( @@ -204,6 +201,8 @@ export function transferDomainCrafter({ targetTokenId: targetToken.tokenId, dvmAddress, evmAddress, + chainId, + nonce, }), title: translate( "screens/ConvertConfirmScreen", @@ -305,7 +304,7 @@ async function createSignedEvmTx({ return new Uint8Array(Buffer.from(evmtxSigned, "hex") || []); } -function stripEvmSuffixFromTokenId(tokenId: string) { +export function stripEvmSuffixFromTokenId(tokenId: string) { if (tokenId.includes("_evm")) { return Number(tokenId.replace("_evm", "")); } diff --git a/mobile-app/app/contexts/CustomServiceProvider.tsx b/mobile-app/app/contexts/CustomServiceProvider.tsx index 9a085b9fb4..c9911fd2ff 100644 --- a/mobile-app/app/contexts/CustomServiceProvider.tsx +++ b/mobile-app/app/contexts/CustomServiceProvider.tsx @@ -102,7 +102,7 @@ function getBlockscoutUrl(network: EnvironmentNetwork) { } } -export function getEthRpcUrl(network: EnvironmentNetwork) { +function getEthRpcUrl(network: EnvironmentNetwork) { // TODO: Add proper ethereum RPC URLs for each network switch (network) { case EnvironmentNetwork.LocalPlayground: @@ -111,11 +111,11 @@ export function getEthRpcUrl(network: EnvironmentNetwork) { case EnvironmentNetwork.DevNet: case EnvironmentNetwork.Changi: return "http://34.34.156.49:20551"; // TODO: add final eth rpc url for changi, devnet and remote playground - case EnvironmentNetwork.TestNet: - return "http://34.38.30.102:18551"; // TODO: add final eth rpc url for testnet, with proper domain name case EnvironmentNetwork.MainNet: - default: return "https://changi.dfi.team"; // TODO: add final eth rpc url for mainnet, with proper domain name + case EnvironmentNetwork.TestNet: + default: + return "http://34.38.30.102:18551"; // TODO: add final eth rpc url for testnet, with proper domain name } } diff --git a/mobile-app/app/contexts/EVMProvider.tsx b/mobile-app/app/contexts/EVMProvider.tsx index 91ca10dd2f..b9eee0a68e 100644 --- a/mobile-app/app/contexts/EVMProvider.tsx +++ b/mobile-app/app/contexts/EVMProvider.tsx @@ -7,6 +7,7 @@ import React, { } from "react"; import { providers } from "ethers"; import { useNetworkContext } from "@waveshq/walletkit-ui"; +import { BaseLogger } from "@waveshq/walletkit-ui/dist/contexts/logger"; import { useCustomServiceProviderContext } from "./CustomServiceProvider"; interface EVMProviderContextI { @@ -21,7 +22,8 @@ export function useEVMProvider(): EVMProviderContextI { export function EVMProvider({ children, -}: React.PropsWithChildren): JSX.Element | null { + logger, +}: React.PropsWithChildren<{ logger: BaseLogger }>): JSX.Element | null { const { ethRpcUrl } = useCustomServiceProviderContext(); const { network } = useNetworkContext(); const [chainId, setChainId] = useState(); @@ -35,7 +37,10 @@ export function EVMProvider({ const { chainId } = await provider.getNetwork(); setChainId(chainId); setProvider(provider); + logger.info(`ChainID: ${chainId}`); } catch (e) { + logger.info(`Eth rpc url: ${ethRpcUrl}`); + logger.error(e); // Note: Added this for cases wherein eth rpc url is invalid or unreachable setChainId(0); setProvider(null); diff --git a/mobile-app/app/screens/AppNavigator/screens/Portfolio/components/PortfolioCard.tsx b/mobile-app/app/screens/AppNavigator/screens/Portfolio/components/PortfolioCard.tsx index 518bbb5604..85aabe9a43 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Portfolio/components/PortfolioCard.tsx +++ b/mobile-app/app/screens/AppNavigator/screens/Portfolio/components/PortfolioCard.tsx @@ -122,7 +122,6 @@ function PortfolioItemRow({ displaySymbol={token.displaySymbol} name={token.name} testID={testID} - isEvmDomain={isEvmDomain} /> - {tokenName} + {name} )} diff --git a/mobile-app/app/screens/AppNavigator/screens/Portfolio/hooks/EvmTokenBalances.ts b/mobile-app/app/screens/AppNavigator/screens/Portfolio/hooks/EvmTokenBalances.ts index 7d742e6668..6f5e7b4fef 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Portfolio/hooks/EvmTokenBalances.ts +++ b/mobile-app/app/screens/AppNavigator/screens/Portfolio/hooks/EvmTokenBalances.ts @@ -75,7 +75,7 @@ export function useEvmTokenBalances(): { evmTokens: WalletToken[] } { isDAT: tokenDetails.isDAT, isLPS: tokenDetails.isLPS, isLoanToken: tokenDetails.isLoanToken, - name: `${tokenDetails.name} for EVM`, + name: `${tokenDetails.name || tokenDetails.symbol} for EVM`, displaySymbol: tokenDetails.displaySymbol, avatarSymbol: tokenDetails.symbol, amount: utils.formatUnits(each.value, each?.token?.decimals), diff --git a/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/ConvertConfirmationScreen.tsx b/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/ConvertConfirmationScreen.tsx index 1ece2dc920..c5e8278818 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/ConvertConfirmationScreen.tsx +++ b/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/ConvertConfirmationScreen.tsx @@ -33,6 +33,7 @@ import { } from "@api/transaction/transfer_domain"; import { useNetworkContext } from "@waveshq/walletkit-ui"; import { NetworkName } from "@defichain/jellyfish-network"; +import { useEVMProvider } from "@contexts/EVMProvider"; import { PortfolioParamList } from "../PortfolioNavigator"; type Props = StackScreenProps; @@ -48,6 +49,7 @@ export function ConvertConfirmationScreen({ route }: Props): JSX.Element { } = route.params; const { networkName } = useNetworkContext(); const { address, evmAddress } = useWalletContext(); + const { provider, chainId } = useEVMProvider(); const addressLabel = useAddressLabel(address); const hasPendingJob = useSelector((state: RootState) => hasTxQueued(state.transactionQueue), @@ -120,6 +122,9 @@ export function ConvertConfirmationScreen({ route }: Props): JSX.Element { logger, ); } else { + const nonce = provider + ? await provider.getTransactionCount(evmAddress) + : 0; await constructSignedTransferDomain( { amount, @@ -127,6 +132,8 @@ export function ConvertConfirmationScreen({ route }: Props): JSX.Element { sourceToken, targetToken, networkName, + chainId, + nonce, evmAddress, dvmAddress: address, }, @@ -193,7 +200,11 @@ export function ConvertConfirmationScreen({ route }: Props): JSX.Element { ? "DFI (EVM)" : targetToken.displaySymbol } - fromAddress={address} + fromAddress={ + convertDirection === ConvertDirection.evmToDvm + ? evmAddress + : address + } fromAddressLabel={addressLabel} isEvmToken={convertDirection === ConvertDirection.dvmToEvm} /> @@ -348,16 +359,20 @@ async function constructSignedTransferDomain( sourceToken, targetToken, networkName, + chainId, dvmAddress, evmAddress, + nonce, }: { convertDirection: ConvertDirection; sourceToken: TransferDomainToken; targetToken: TransferDomainToken; amount: BigNumber; networkName: NetworkName; + chainId?: number; dvmAddress: string; evmAddress: string; + nonce: number; }, dispatch: Dispatch, onBroadcast: () => void, @@ -374,8 +389,10 @@ async function constructSignedTransferDomain( networkName, onBroadcast, onConfirmation: () => {}, + chainId, dvmAddress, evmAddress, + nonce, }), ), ); diff --git a/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/SendConfirmationScreen.tsx b/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/SendConfirmationScreen.tsx index 144cecee2b..e2ecddfc61 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/SendConfirmationScreen.tsx +++ b/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/SendConfirmationScreen.tsx @@ -50,6 +50,7 @@ import { AddressType as JellyfishAddressType, } from "@waveshq/walletkit-core"; import { DomainType, useDomainContext } from "@contexts/DomainContext"; +import { useEVMProvider } from "@contexts/EVMProvider"; import { PortfolioParamList } from "../PortfolioNavigator"; type Props = StackScreenProps; @@ -80,6 +81,7 @@ export function SendConfirmationScreen({ route }: Props): JSX.Element { hasOceanTXQueued(state.ocean), ); const dispatch = useAppDispatch(); + const { provider, chainId } = useEVMProvider(); const [isSubmitting, setIsSubmitting] = useState(false); const navigation = useNavigation>(); const [isOnPage, setIsOnPage] = useState(true); @@ -99,12 +101,15 @@ export function SendConfirmationScreen({ route }: Props): JSX.Element { return; } setIsSubmitting(true); + const nonce = provider ? await provider.getTransactionCount(evmAddress) : 0; await send( { address: destination, token, amount, domain, + chainId, + nonce, networkName: network.networkName, }, dispatch, @@ -359,11 +364,13 @@ interface SendForm { address: string; token: WalletToken; domain: DomainType; + nonce: number; + chainId?: number; networkName: NetworkName; } async function send( - { address, token, amount, domain, networkName }: SendForm, + { address, token, amount, domain, networkName, nonce, chainId }: SendForm, dispatch: Dispatch, onBroadcast: () => void, logger: NativeLoggingProps, @@ -417,6 +424,8 @@ async function send( dvmAddress, evmAddress, networkName, + nonce, + chainId, convertDirection: sendDirection, }); } diff --git a/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/TokenDetailScreen.tsx b/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/TokenDetailScreen.tsx index f161b02d27..d92d7a6674 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/TokenDetailScreen.tsx +++ b/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/TokenDetailScreen.tsx @@ -13,7 +13,10 @@ import { unifiedDFISelector, WalletToken, } from "@waveshq/walletkit-ui/dist/store"; -import { useDeFiScanContext } from "@shared-contexts/DeFiScanContext"; +import { + getMetaScanTokenUrl, + useDeFiScanContext, +} from "@shared-contexts/DeFiScanContext"; import { PoolPairData } from "@defichain/whale-api-client/dist/api/poolpairs"; import { View } from "@components"; import { @@ -31,6 +34,7 @@ import { InfoTextLinkV2 } from "@components/InfoTextLink"; import { ThemedTouchableListItem } from "@components/themed/ThemedTouchableListItem"; import { ConvertDirection } from "@screens/enum"; import { DomainType, useDomainContext } from "@contexts/DomainContext"; +import { useNetworkContext } from "@waveshq/walletkit-ui"; import { PortfolioParamList } from "../PortfolioNavigator"; import { useTokenPrice } from "../hooks/TokenPrice"; import { useDenominationCurrency } from "../hooks/PortfolioCurrency"; @@ -447,6 +451,7 @@ function TokenSummary(props: { }): JSX.Element { const { denominationCurrency } = useDenominationCurrency(); const { getTokenUrl } = useDeFiScanContext(); + const { network } = useNetworkContext(); const onTokenUrlPressed = async (): Promise => { const id = props.token.id === "0_utxo" || @@ -454,7 +459,9 @@ function TokenSummary(props: { props.token.id === "0_evm" ? 0 : props.token.id; - const url = getTokenUrl(id); + const url = props.token.id.includes("_evm") + ? getMetaScanTokenUrl(network, props.token.id) + : getTokenUrl(id); await Linking.openURL(url); }; @@ -488,7 +495,7 @@ function TokenSummary(props: { dark={tailwind("text-mono-dark-v2-700")} style={tailwind("text-sm font-normal-v2")} > - {props.token.name} + {props.token.name || props.token.symbol} diff --git a/shared/contexts/DeFiScanContext.tsx b/shared/contexts/DeFiScanContext.tsx index c841872127..48c7cd813b 100644 --- a/shared/contexts/DeFiScanContext.tsx +++ b/shared/contexts/DeFiScanContext.tsx @@ -1,6 +1,11 @@ import React, { createContext, useContext, useMemo } from "react"; import { EnvironmentNetwork } from "@waveshq/walletkit-core"; import { useNetworkContext } from "@waveshq/walletkit-ui"; +import { + TD_CONTRACT_ADDR, + getAddressFromDST20TokenId, + stripEvmSuffixFromTokenId, +} from "@api/transaction/transfer_domain"; interface DeFiScanContextI { getTransactionUrl: (txid: string, rawtx?: string) => string; @@ -14,13 +19,14 @@ interface DeFiScanContextI { const DeFiScanContext = createContext(undefined as any); const baseDefiScanUrl = "https://defiscan.live"; +const baseMetaScanUrl = "https://meta.defiscan.live"; export function useDeFiScanContext(): DeFiScanContextI { return useContext(DeFiScanContext); } export function DeFiScanProvider( - props: React.PropsWithChildren + props: React.PropsWithChildren, ): JSX.Element | null { const { network } = useNetworkContext(); @@ -70,6 +76,8 @@ function getNetworkParams(network: EnvironmentNetwork): string { case EnvironmentNetwork.LocalPlayground: case EnvironmentNetwork.RemotePlayground: return `?network=${EnvironmentNetwork.RemotePlayground}`; + case EnvironmentNetwork.Changi: + return `?network=${EnvironmentNetwork.Changi}`; default: return ""; } @@ -78,7 +86,7 @@ function getNetworkParams(network: EnvironmentNetwork): string { export function getTxURLByNetwork( network: EnvironmentNetwork, txid: string, - rawtx?: string + rawtx?: string, ): string { let baseUrl = `${baseDefiScanUrl}/transactions/${txid}`; @@ -98,7 +106,24 @@ export function getTxURLByNetwork( export function getURLByNetwork( path: string, network: EnvironmentNetwork, - id: number | string + id: number | string, ): string { return `${baseDefiScanUrl}/${path}/${id}${getNetworkParams(network)}`; } + +export function getMetaScanTokenUrl( + network: EnvironmentNetwork, + id: string, +): string { + const networkParams = getNetworkParams(network); + const tokenId = stripEvmSuffixFromTokenId(id); + + // DFI token + if (tokenId === 0) { + return `${baseMetaScanUrl}/token/${TD_CONTRACT_ADDR}${networkParams}`; + } + + // DST20 token + const tokenAddress = getAddressFromDST20TokenId(tokenId); + return `${baseMetaScanUrl}/token/${tokenAddress}${networkParams}`; +}