Skip to content

Commit

Permalink
feat: src-7 and 9 support (#1655)
Browse files Browse the repository at this point in the history
Closes #1637
Closes #1636 
- Fixes intermittent long loading state on the wallet. FE-899
- Fetch and display NFT data directly from indexer, SRC-7,9 and 20.
FE-862 FE-863.

---------

Co-authored-by: Rodrigo Branas <[email protected]>
  • Loading branch information
arthurgeron and rodrigobranas authored Nov 15, 2024
1 parent 8c9edd3 commit 4e3be5d
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 710 deletions.
5 changes: 5 additions & 0 deletions .changeset/brown-badgers-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fuels-wallet": patch
---

Fetch and display NFT data directly from indexer.
5 changes: 5 additions & 0 deletions .changeset/light-cooks-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fuels-wallet": minor
---

Support SRC-7, 9 nft asset data.
5 changes: 5 additions & 0 deletions .changeset/rotten-grapes-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fuels-wallet": patch
---

Fixes intermittent long loading state on the wallet
30 changes: 6 additions & 24 deletions packages/app/src/systems/Account/services/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,40 +104,22 @@ export class AccountService {
try {
const provider = await createProvider(providerUrl!);
const balances = await getBalances(provider, account.publicKey);

const assets = await AssetService.getAssets();
const balanceAssets = await AssetsCache.fetchAllAssets(
provider.getChainId(),
balances.map((balance) => balance.assetId)
);
// includes "asset" prop in balance, centralizing the complexity here instead of in rest of UI
const nextBalancesWithAssets = await balances.reduce(
async (acc, balance) => {
const prev = await acc;
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 (_) {}
const cachedAsset = balanceAssets.get(balance.assetId);

return [
...prev,
{
...balance,
amount: balance.amount,
asset: asset.fuel,
asset: cachedAsset,
},
];
},
Expand Down
136 changes: 109 additions & 27 deletions packages/app/src/systems/Asset/cache/AssetsCache.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import type { AssetData } from '@fuel-wallet/types';
import type { Asset, Provider } from 'fuels';
import type { Asset, AssetFuel, Provider } from 'fuels';
import { AssetService } from '~/systems/Asset/services';
import { getFuelAssetByAssetId } from '~/systems/Asset/utils';
import { db } from '~/systems/Core/utils/database';
import { fetchNftData } from '../utils/nft';

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

const FIVE_MINUTES = 5 * 60 * 1000;
export class AssetsCache {
private cache: { [chainId: number]: { [assetId: string]: Asset } };
private cache: {
[chainId: number]: {
[assetId: string]: Asset & { fetchedAt?: number };
};
};
private dbAssetsCache: {
[chainId: number]: Array<AssetData>;
};
private static instance: AssetsCache;
private endpoints: Endpoint[] = [
{
Expand All @@ -25,15 +34,44 @@ export class AssetsCache {

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

asset = {
name: '',
symbol: '',
metadata: {},
};

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

static async fetchAllAssets(chainId: number, assetsIds: string[]) {
const instance = AssetsCache.getInstance();
const assetData = new Map<string, AssetFuel>();
const dbAssets = await AssetService.getAssets();
const promises = [];
for (const assetId of assetsIds) {
promises.push(
instance
.getAsset({ chainId, assetId, dbAssets })
.then((asset) => {
assetData.set(assetId, asset);
})
.catch((e) => {
console.error('Error fetching asset from indexer', e);
assetData.set(assetId, { name: '' } as AssetFuel);
})
);
}
await Promise.all(promises);
return assetData;
}

private async fetchAssetFromIndexer(url: string, assetId: string) {
try {
const timeout = new Promise<null>((_, reject) =>
Expand All @@ -51,60 +89,104 @@ export class AssetsCache {
} catch (_e: unknown) {}
}

assetIsValid(asset: AssetData) {
return (
asset.name != null && 'fetchedAt' in asset && asset.fetchedAt != null
);
}

async getAsset({
chainId,
assetId,
provider,
}: { chainId: number; assetId: string; provider: Provider }) {
dbAssets,
save = true,
}: {
chainId: number;
assetId: string;
dbAssets: AssetData[];
save?: boolean;
}) {
if (chainId == null || !assetId) {
return;
}
const endpoint = this.getIndexerEndpoint(chainId);
if (!endpoint) return;
// try to get from memory cache first
this.cache[chainId] = this.cache[chainId] || {};
const assetFromCache = this.cache[chainId][assetId];
if (assetFromCache?.name) {
return assetFromCache;
const cachedEntry = this.cache[chainId][assetId];
const now = Date.now();

if (dbAssets?.length) {
this.dbAssetsCache[chainId] = dbAssets;
}

if (
cachedEntry?.name !== undefined &&
cachedEntry.fetchedAt &&
now - cachedEntry.fetchedAt < FIVE_MINUTES
) {
return cachedEntry;
}

// get from indexed db if not in memory
const assetFromDb = await this.storage.getItem(`${chainId}/${assetId}`);
if (assetFromDb?.name) {
if (
assetFromDb?.name &&
assetFromDb.fetchedAt &&
now - assetFromDb.fetchedAt < FIVE_MINUTES
) {
this.cache[chainId][assetId] = assetFromDb;
return assetFromDb;
}

const dbAsset = await getFuelAssetByAssetId({
assets: dbAssets.length ? dbAssets : this.dbAssetsCache[chainId],
assetId: assetId,
chainId,
}).catch((e) => {
console.error('Error fetching asset from db', e);
return undefined;
});
const assetFromIndexer = await this.fetchAssetFromIndexer(
endpoint.url,
assetId
);
).catch((e) => {
console.error('Error fetching asset from indexer', e);
return undefined;
});

console.log('asd assetFromIndexer', assetFromIndexer);
if (!assetFromIndexer) return;

const {
isNFT,
metadata,
name: indexerAssetName,
symbol: indexerAssetSymbol,
...rest
} = assetFromIndexer ?? {};
const asset = {
...assetFromIndexer,
isNft: false,
...dbAsset,
isNft: !!isNFT,
...rest,
metadata,
fetchedAt: now,
name: indexerAssetName || dbAsset?.name,
symbol: indexerAssetSymbol || dbAsset?.symbol,
};

if (assetFromIndexer.contractId) {
const nftData = await fetchNftData({
assetId,
contractId: assetFromIndexer.contractId,
provider,
});
Object.assign(asset, nftData);
if (asset.name != null) {
asset.indexed = true;
} else {
// @TODO: Remove once we have a proper caching pattern/mechanism
asset.name = '';
}

this.cache[chainId][assetId] = asset;
this.storage.setItem(`${chainId}/${assetId}`, asset);
if (save) {
this.cache[chainId][assetId] = asset;
this.storage.setItem(`${chainId}/${assetId}`, asset);
}
return asset;
}
asset = {
name: '',
symbol: '',
metadata: {},
};

static getInstance() {
if (!AssetsCache.instance) {
Expand Down
Loading

0 comments on commit 4e3be5d

Please sign in to comment.