Skip to content

Commit

Permalink
Merge branch 'ag/feat/fe-827' of github.com:FuelLabs/fuels-wallet int…
Browse files Browse the repository at this point in the history
…o ag/feat/fe-827
  • Loading branch information
LuizAsFight committed Oct 31, 2024
2 parents 4967cae + 6b1a8d9 commit 565eb50
Show file tree
Hide file tree
Showing 13 changed files with 904 additions and 21 deletions.
6 changes: 6 additions & 0 deletions .changeset/chatty-llamas-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-wallet/types": minor
"fuels-wallet": minor
---

added SRC20 custom assets name, symbol and decimal resolve from indexer
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export const BalanceAssets = ({
function toggle() {
setShowUnknown((s) => !s);
}

return (
<CardList>
{balancesToShow.map((balance) => (
Expand Down
29 changes: 24 additions & 5 deletions packages/app/src/systems/Account/services/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
CoinAsset,
} from '@fuel-wallet/types';
import { Address, type Provider, bn } from 'fuels';
import { AssetsCache } from '~/systems/Asset/cache/AssetsCache';
import { AssetService } from '~/systems/Asset/services';
import { getFuelAssetByAssetId } from '~/systems/Asset/utils';
import type { Maybe } from '~/systems/Core/types';
Expand Down Expand Up @@ -109,16 +110,34 @@ export class AccountService {
const nextBalancesWithAssets = await balances.reduce(
async (acc, balance) => {
const prev = await acc;
const asset = await getFuelAssetByAssetId({
assets,
assetId: balance.assetId,
});
const asset = {
fuel: await getFuelAssetByAssetId({
assets,
assetId: balance.assetId,
}),
};
try {
const assetCached = await AssetsCache.getInstance().getAsset({
chainId: provider.getChainId(),
assetId: balance.assetId,
provider,
});

if (assetCached && asset.fuel) {
asset.fuel = {
...asset.fuel,
...assetCached,
indexed: true,
};
}
} catch (_) {}

return [
...prev,
{
...balance,
amount: balance.amount,
asset,
asset: asset.fuel,
},
];
},
Expand Down
119 changes: 119 additions & 0 deletions packages/app/src/systems/Asset/cache/AssetsCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import type { AssetData } from '@fuel-wallet/types';
import type { Asset, Provider } from 'fuels';
import { db } from '~/systems/Core/utils/database';
import { isNft } from '../utils/isNft';

type Endpoint = {
chainId: number;
url: string;
};

export class AssetsCache {
private cache: { [chainId: number]: { [assetId: string]: Asset } };
private static instance: AssetsCache;
private endpoints: Endpoint[] = [
{
chainId: 9889,
url: 'https://explorer-indexer-mainnet.fuel.network',
},
{
chainId: 0,
url: 'https://explorer-indexer-testnet.fuel.network',
},
];
private storage: IndexedAssetsDB;

private constructor() {
this.cache = {};
this.storage = new IndexedAssetsDB();
}

private getIndexerEndpoint(chainId: number) {
return this.endpoints.find(
(endpoint: Endpoint) => endpoint.chainId === chainId
);
}

private async fetchAssetFromIndexer(url: string, assetId: string) {
try {
const timeout = new Promise<null>((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 2000)
);
// limit on 2000ms request time
const response = await Promise.race([
fetch(`${url}/assets/${assetId}`),
timeout,
]);
if (response instanceof Response) {
return response.json();
}
} catch (_e: unknown) {}
}

async getAsset({
chainId,
assetId,
provider,
}: { chainId: number; assetId: string; provider: Provider }) {
if (chainId == null || !assetId) {
return;
}
const endpoint = this.getIndexerEndpoint(chainId);
if (!endpoint) return;
// try to get from memory cache first
this.cache[endpoint.chainId] = this.cache[endpoint.chainId] || {};
if (this.cache[endpoint.chainId][assetId]) {
return this.cache[endpoint.chainId][assetId];
}
// get from indexed db if not in memory
const savedAsset = await this.storage.getItem(
`${endpoint.chainId}/${assetId}`
);
if (savedAsset) {
this.cache[endpoint.chainId][assetId] = savedAsset;
return savedAsset;
}
const assetFromIndexer = await this.fetchAssetFromIndexer(
endpoint.url,
assetId
);
if (!assetFromIndexer) return;

const isNftAsset = await isNft({
assetId,
contractId: assetFromIndexer.contractId,
provider,
});

const asset = {
...assetFromIndexer,
isNft: isNftAsset,
};

this.cache[endpoint.chainId][assetId] = asset;
this.storage.setItem(`${endpoint.chainId}/${assetId}`, asset);
return asset;
}

static getInstance() {
if (!AssetsCache.instance) {
AssetsCache.instance = new AssetsCache();
}
return AssetsCache.instance;
}
}

class IndexedAssetsDB {
async getItem(key: string) {
return db.transaction('r', db.indexedAssets, async () => {
const asset = await db.indexedAssets.get({ key });
return asset;
});
}

async setItem(key: string, data: AssetData) {
await db.transaction('rw', db.indexedAssets, async () => {
await db.indexedAssets.put({ key, ...data });
});
}
}
59 changes: 50 additions & 9 deletions packages/app/src/systems/Asset/components/AssetItem/AssetItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cssObj } from '@fuel-ui/css';
import {
Avatar,
Badge,
Box,
Button,
CardList,
Expand Down Expand Up @@ -126,7 +127,9 @@ export const AssetItem: AssetItemComponent = ({
delayDuration={0}
open={visibility && tooltip ? undefined : false}
>
<Text css={{ fontSize: '$sm', fontWeight: '$normal' }}>
<Text
css={{ fontSize: '$sm', fontWeight: '$normal', textAlign: 'right' }}
>
<AmountVisibility
value={amount}
units={decimals}
Expand All @@ -141,10 +144,23 @@ export const AssetItem: AssetItemComponent = ({
return null;
}

function goToAsset() {
navigate(Pages.assetsAdd({ assetId }));
function goToAsset(asset: {
assetId?: string;
name?: string;
symbol?: string;
decimals?: number;
}) {
navigate(Pages.assetsAdd({ assetId }), { state: asset });
}

const suspiciousTooltipContent = (
<div style={{ textAlign: 'center' }}>
This asset is flagged as suspicious,
<br /> it may mimicking another asset.
<br /> Proceed with caution.
</div>
);

return (
<CardList.Item rightEl={getRightEl()} css={{ alignItems: 'center' }}>
{icon ? (
Expand All @@ -161,20 +177,35 @@ export const AssetItem: AssetItemComponent = ({
)}
<Box.Flex direction="column">
<Heading as="h6" css={styles.assetName}>
{name || (
<Box.Flex>
Unknown
<Box.Flex>
{name || 'Unknown'}
{asset.suspicious ? (
<Tooltip content={suspiciousTooltipContent}>
<Icon
css={styles.assetSuspicious}
icon={Icon.is('AlertTriangle')}
/>
</Tooltip>
) : (
''
)}
{asset.isNft && (
<Badge variant="ghost" intent="primary" css={styles.assetNft}>
NFT
</Badge>
)}
{(!name || asset.indexed) && !asset.isNft && (
<Button
size="xs"
intent="primary"
variant="link"
onPress={goToAsset}
onPress={() => goToAsset(asset)}
css={styles.addAssetBtn}
>
(Add)
</Button>
</Box.Flex>
)}
)}
</Box.Flex>
</Heading>
<Box.Flex direction="row">
{symbol ? (
Expand Down Expand Up @@ -207,6 +238,11 @@ const styles = {
textSize: 'sm',
fontWeight: '$normal',
}),
assetSuspicious: cssObj({
marginLeft: 5,
marginRight: 5,
color: 'orange',
}),
addAssetBtn: cssObj({
p: '0',
ml: '$1',
Expand All @@ -229,4 +265,9 @@ const styles = {
color: '$intentsBase11 !important',
},
}),
assetNft: cssObj({
ml: '$2',
fontSize: '$sm',
lineHeight: 'normal',
}),
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cssObj, cx } from '@fuel-ui/css';
import {
Avatar,
Badge,
Box,
Button,
Dropdown,
Expand Down Expand Up @@ -107,7 +108,7 @@ function AssetSelectBase({ items, selected, onSelect }: AssetSelectProps) {
{(items || []).map((item) => {
const assetId = item.assetId?.toString();
const itemAsset = items?.find((a) => a.assetId === assetId);
const { name, symbol, icon } = itemAsset || {};
const { name, symbol, icon, isNft } = itemAsset || {};

return (
<Dropdown.MenuItem
Expand All @@ -122,6 +123,15 @@ function AssetSelectBase({ items, selected, onSelect }: AssetSelectProps) {
<Box className="asset-info">
<Text as="span" className="asset-name">
{name || 'Unknown'}
{isNft && (
<Badge
variant="ghost"
intent="primary"
css={styles.assetNft}
>
NFT
</Badge>
)}
</Text>
<Text as="span" className="asset-symbol">
{symbol || shortAddress(assetId)}
Expand Down Expand Up @@ -230,4 +240,9 @@ const styles = {
dropdownRoot: cssObj({
zIndex: '1 !important',
}),
assetNft: cssObj({
ml: '$2',
fontSize: '$sm',
lineHeight: 'normal',
}),
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cssObj } from '@fuel-ui/css';
import { Box, Button, Focus, Text } from '@fuel-ui/react';
import { isB256 } from 'fuels';
import { useNavigate, useParams } from 'react-router-dom';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { Layout } from '~/systems/Core';

import { AssetItem } from '../../components';
Expand All @@ -13,6 +13,7 @@ import useFuelAsset from '../../hooks/useFuelAsset';

export function UpsertAsset() {
const navigate = useNavigate();
const { state } = useLocation();
const params = useParams<{ name: string; assetId: string }>();
const name = params.name;

Expand All @@ -22,11 +23,11 @@ export function UpsertAsset() {
const { handlers, isLoading } = useAssets();
const form = useAssetForm({
defaultValues: {
name: '',
symbol: '',
name: state?.name || '',
symbol: state?.symbol || '',
icon: '',
...asset,
decimals: fuelAsset?.decimals || 0,
decimals: state?.decimals || fuelAsset?.decimals || 0,
assetId: fuelAsset?.assetId || params.assetId || '',
},
});
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/systems/Asset/services/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,12 @@ export class AssetService {
});
}

static async getAssetById(assetId: string) {
return db.transaction('r', db.assets, async () => {
return db.assets.get({ assetId });
});
}

static async clearAssets() {
return db.transaction('rw', db.assets, async () => {
return db.assets.clear();
Expand Down
Loading

0 comments on commit 565eb50

Please sign in to comment.