Skip to content

Commit

Permalink
Merge pull request #1256 from madfish-solutions/TW-1626-deploy-add-to…
Browse files Browse the repository at this point in the history
…ken-add-network-confirmations

TW-1626: Add token / network from dApp confirmations
  • Loading branch information
lourenc authored Feb 12, 2025
2 parents 1b9f7b6 + 878b47b commit 5db4543
Show file tree
Hide file tree
Showing 42 changed files with 1,071 additions and 324 deletions.
8 changes: 7 additions & 1 deletion public/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1777,6 +1777,9 @@
"mustBeValidURL": {
"message": "Must be a valid URL"
},
"rpcURL": {
"message": "RPC URL"
},
"rpcBaseURL": {
"message": "RPC base URL",
"description": "RPC = Remote Procedure Call (https://en.wikipedia.org/wiki/Remote_procedure_call)"
Expand All @@ -1785,7 +1788,7 @@
"message": "Must be unique"
},
"addNetwork": {
"message": "Add network"
"message": "Add Network"
},
"currentNetworks": {
"message": "Current networks"
Expand Down Expand Up @@ -2030,6 +2033,9 @@
"contract": {
"message": "Contract"
},
"contractAddress": {
"message": "Contract Address"
},
"contractAddressInputDescription": {
"message": "Address of the contract to connect."
},
Expand Down
2 changes: 1 addition & 1 deletion public/_locales/en_GB/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@
"message": "Must be unique"
},
"addNetwork": {
"message": "Add network"
"message": "Add Network"
},
"currentNetworks": {
"message": "Current networks"
Expand Down
5 changes: 4 additions & 1 deletion src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ export const App: FC<Props> = ({ env }) => (
<AwaitFonts>
<BootAnimation>
{env.confirmWindow ? (
<ConfirmPage />
<>
<ConfirmPage />
<ToasterProvider />
</>
) : (
<>
<AppRootHooks />
Expand Down
115 changes: 115 additions & 0 deletions src/app/ConfirmPage/add-asset/add-asset-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, { memo, useCallback, useEffect, useMemo } from 'react';

import { CaptionAlert } from 'app/atoms';
import { HashChip } from 'app/atoms/HashChip';
import { EvmNetworkLogo } from 'app/atoms/NetworkLogo';
import Spinner from 'app/atoms/Spinner/Spinner';
import { EvmAssetIconWithNetwork } from 'app/templates/AssetIcon';
import { fetchEvmTokenMetadataFromChain } from 'lib/evm/on-chain/metadata';
import { EvmAssetStandard } from 'lib/evm/types';
import { T } from 'lib/i18n';
import { EvmTokenMetadata } from 'lib/metadata/types';
import { useTypedSWR } from 'lib/swr';
import { EvmAssetToAddMetadata } from 'lib/temple/types';
import { useEvmChainByChainId } from 'temple/front/chains';

import { useAddAsset } from './context';

interface Props {
metadata: EvmAssetToAddMetadata;
}

export const AddAssetView = memo<Props>(({ metadata }) => {
const { errorMessage, setErrorMessage, setAssetMetadata } = useAddAsset();

const network = useEvmChainByChainId(metadata.chainId);

const loadAssetMetadata = useCallback(() => {
if (!network) {
setErrorMessage('Network is not supported.');

return;
}

return fetchEvmTokenMetadataFromChain(network, metadata.address);
}, [metadata.address, network, setErrorMessage]);

const { data: metadataResponse, isValidating: isChainMetadataLoading } = useTypedSWR(
['add-dApp-asset', metadata.chainId, metadata.address],
loadAssetMetadata,
{
shouldRetryOnError: false,
focusThrottleInterval: 10_000,
dedupingInterval: 10_000
}
);

const chainMetadata = useMemo<EvmTokenMetadata>(
() => ({ ...metadataResponse, address: metadata.address, standard: EvmAssetStandard.ERC20 }),
[metadataResponse, metadata.address]
);

useEffect(() => {
if (!isChainMetadataLoading && !metadataResponse) setErrorMessage('Failed to load asset metadata.');
if (metadataResponse) {
setErrorMessage(null);
setAssetMetadata(chainMetadata);
}
}, [metadataResponse, isChainMetadataLoading, setAssetMetadata, chainMetadata, setErrorMessage]);

if (!metadataResponse && isChainMetadataLoading) {
return (
<div className="flex justify-center my-20">
<Spinner theme="gray" className="w-20" />
</div>
);
}

const name = chainMetadata?.name || metadata.name || 'Unknown Asset';
const symbol = chainMetadata?.symbol || metadata.symbol || '???';
const decimals = chainMetadata?.decimals || metadata.decimals || 0;

return (
<div className="flex flex-col gap-6 mb-6">
<div className="flex flex-col justify-center items-center text-center">
<EvmAssetIconWithNetwork metadata={chainMetadata} evmChainId={metadata.chainId} assetSlug={metadata.address} />

<span className="text-font-medium-bold mt-2">{symbol}</span>
<span className="text-font-description text-grey-1">{name}</span>
</div>

<div className="flex flex-col gap-4">
{errorMessage && <CaptionAlert type="error" message={errorMessage} className="items-center" />}

<div className="flex flex-col px-4 py-2 rounded-8 shadow-bottom border-0.5 border-transparent">
{network && (
<div className="py-2 flex flex-row justify-between items-center border-b-0.5 border-lines">
<p className="p-1 text-font-description text-grey-1">
<T id="network" />
</p>

<div className="flex flex-row items-center">
<span className="p-1 text-font-num-bold-12">{network.name}</span>
<EvmNetworkLogo chainId={network.chainId} chainName={network.name} size={16} />
</div>
</div>
)}

<div className="py-2 flex flex-row justify-between items-center border-b-0.5 border-lines">
<p className="p-1 text-font-description text-grey-1">
<T id="contractAddress" />
</p>
<HashChip hash={metadata.address} />
</div>

<div className="py-2 flex flex-row justify-between items-center">
<p className="p-1 text-font-description text-grey-1">
<T id="decimals" />
</p>
<p className="p-1 text-font-num-bold-12">{decimals}</p>
</div>
</div>
</div>
</div>
);
});
57 changes: 57 additions & 0 deletions src/app/ConfirmPage/add-asset/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useCallback, useState } from 'react';

import constate from 'constate';

import { dispatch, persistor } from 'app/store';
import { putNewEvmTokenAction } from 'app/store/evm/assets/actions';
import { putEvmTokensMetadataAction } from 'app/store/evm/tokens-metadata/actions';
import { toTokenSlug } from 'lib/assets';
import { EvmTokenMetadata } from 'lib/metadata/types';
import { useTempleClient } from 'lib/temple/front';
import { EvmAssetToAddMetadata } from 'lib/temple/types';
import { useAccountAddressForEvm } from 'temple/front';

export const [AddAssetProvider, useAddAsset] = constate(() => {
const { confirmDAppEvmAssetAdding } = useTempleClient();
const accountPkh = useAccountAddressForEvm();

const [errorMessage, setErrorMessage] = useState<string | nullish>(null);
const [assetMetadata, setAssetMetadata] = useState<EvmTokenMetadata | nullish>(null);

const handleConfirm = useCallback(
async (id: string, confirmed: boolean, dAppAssetMetadata: EvmAssetToAddMetadata) => {
if (confirmed) {
if (assetMetadata && accountPkh) {
const assetSlug = toTokenSlug(assetMetadata.address);

dispatch(
putNewEvmTokenAction({
publicKeyHash: accountPkh,
chainId: dAppAssetMetadata.chainId,
assetSlug
})
);

dispatch(
putEvmTokensMetadataAction({
chainId: dAppAssetMetadata.chainId,
records: { [assetSlug]: assetMetadata }
})
);

// ensuring the last changes to the store will be persisted before window closes
await persistor.flush();

confirmDAppEvmAssetAdding(id, confirmed);
} else {
setErrorMessage('Something’s not right. Please try again later.');
}
} else {
confirmDAppEvmAssetAdding(id, confirmed);
}
},
[accountPkh, assetMetadata, confirmDAppEvmAssetAdding]
);

return { errorMessage, setErrorMessage, assetMetadata, setAssetMetadata, handleConfirm };
});
98 changes: 98 additions & 0 deletions src/app/ConfirmPage/add-chain/add-chain-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, { memo, useCallback, useMemo } from 'react';

import clsx from 'clsx';

import { Anchor, IconBase } from 'app/atoms';
import { EvmNetworkLogo } from 'app/atoms/NetworkLogo';
import { SettingsCheckbox } from 'app/atoms/SettingsCheckbox';
import { ReactComponent as CopyIcon } from 'app/icons/base/copy.svg';
import { ReactComponent as OutLinkIcon } from 'app/icons/base/outLink.svg';
import { toastSuccess } from 'app/toaster';
import { t, T } from 'lib/i18n';
import { EvmChainToAddMetadata } from 'lib/temple/types';

import { useAddChainDataState } from './context';

interface Props {
metadata: EvmChainToAddMetadata;
}

export const AddChainView = memo<Props>(({ metadata }) => {
const chainId = Number(metadata.chainId);
const { testnet, setTestnet } = useAddChainDataState();

const displayRpcUrl = useMemo(() => new URL(metadata.rpcUrl).hostname, [metadata.rpcUrl]);
const displayBlockExplorerUrl = useMemo(
() => metadata.blockExplorerUrl && new URL(metadata.blockExplorerUrl).hostname,
[metadata.blockExplorerUrl]
);

const handleCopyRpcUrl = useCallback(() => {
window.navigator.clipboard.writeText(metadata.rpcUrl);
toastSuccess(t('copiedAddress'));
}, [metadata.rpcUrl]);

const handleTestnetChange = useCallback((checked: boolean) => setTestnet(checked), [setTestnet]);

return (
<div className="flex flex-col gap-4 mb-6">
<div className="flex flex-col px-4 py-2 rounded-8 shadow-bottom border-0.5 border-transparent">
<div className="py-2 flex flex-row justify-between items-center border-b-0.5 border-lines">
<p className="p-1 text-font-description text-grey-1">
<T id="network" />
</p>
<div className="flex flex-row items-center">
<span className="p-1 text-font-num-bold-12">{metadata.name}</span>
<EvmNetworkLogo chainId={chainId} chainName={metadata.name} size={16} />
</div>
</div>

<div className="py-2 flex flex-row justify-between items-center border-b-0.5 border-lines">
<p className="p-1 text-font-description text-grey-1">
<T id="rpcURL" />
</p>
<div className="flex flex-row px-1 py-0.5 gap-x-0.5 text-secondary cursor-pointer" onClick={handleCopyRpcUrl}>
<p className="text-font-description max-w-52 truncate">{displayRpcUrl}</p>
<IconBase Icon={CopyIcon} size={12} />
</div>
</div>

<div className="py-2 flex flex-row justify-between items-center border-b-0.5 border-lines">
<p className="p-1 text-font-description text-grey-1">
<T id="chainId" />
</p>
<p className="p-1 text-font-description-bold">{chainId}</p>
</div>

<div
className={clsx(
'py-2 flex flex-row justify-between items-center',
metadata.blockExplorerUrl && 'border-b-0.5 border-lines'
)}
>
<p className="p-1 text-font-description text-grey-1">
<T id="symbol" />
</p>
<p className="p-1 text-font-description-bold">{metadata.nativeCurrency.symbol}</p>
</div>

{metadata.blockExplorerUrl && (
<div className="py-2 flex flex-row justify-between items-center">
<p className="p-1 text-font-description text-grey-1">
<T id="blockExplorer" />
</p>
<Anchor
href={metadata.blockExplorerUrl}
className="flex flex-row px-1 py-0.5 gap-x-0.5 text-secondary cursor-pointer"
>
<p className="text-font-description max-w-48 truncate">{displayBlockExplorerUrl}</p>
<IconBase Icon={OutLinkIcon} size={12} />
</Anchor>
</div>
)}
</div>

<SettingsCheckbox checked={testnet} onChange={handleTestnetChange} label={<T id="testnet" />} />
</div>
);
});
9 changes: 9 additions & 0 deletions src/app/ConfirmPage/add-chain/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useState } from 'react';

import constate from 'constate';

export const [AddChainDataProvider, useAddChainDataState] = constate(() => {
const [testnet, setTestnet] = useState(false);

return { testnet, setTestnet };
});
Loading

0 comments on commit 5db4543

Please sign in to comment.