Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
KorbinianK committed Apr 30, 2024
1 parent c6beca0 commit 2e0b552
Show file tree
Hide file tree
Showing 17 changed files with 307 additions and 187 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { NFT } from '$api/domain/models/NFT';
import type { FetchNftArgs } from '$api/infrastructure/types/common';

export interface INFTRepository {
findByAddress({ address, chainId, refresh }: FetchNftArgs): Promise<NFT[]>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface NFT {
owner: Address;
imported?: boolean;
mintable?: boolean;
balance?: bigint;
balance: string | number;
tokenId: number | string;
uri?: string;
metadata?: NFTMetadata;
Expand Down
20 changes: 20 additions & 0 deletions packages/bridge-ui/src/api/domain/services/NFTService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Address } from 'viem';

import type { INFTRepository } from '../interfaces/INFTRepository';
import type { NFT } from '../models/NFT';

export class NFTService {
constructor(private repository: INFTRepository) {}

async fetchNFTsByAddress({
address,
chainId,
refresh,
}: {
address: Address;
chainId: number;
refresh: boolean;
}): Promise<NFT[]> {
return await this.repository.findByAddress({ address, chainId, refresh });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import Moralis from 'moralis';
import { type Address, zeroAddress } from 'viem';

import type { INFTRepository } from '$api/domain/interfaces/INFTRepository';
import type { NFT } from '$api/domain/models/NFT';
import { mapToNFTFromMoralis } from '$api/infrastructure/mappers/nft/MoralisNFTMapper';
import type { NFTApiData } from '$api/infrastructure/types/moralis';
import { moralisApiConfig } from '$config';
import { MORALIS_API_KEY } from '$env/static/private';

import type { FetchNftArgs } from '../types/common';

class MoralisNFTRepository implements INFTRepository {
private static instance: MoralisNFTRepository;

private cursor: string;
private lastFetchedAddress: Address;
private hasFetchedAll: boolean;

private nfts: NFT[] = [];

private constructor() {
Moralis.start({
apiKey: MORALIS_API_KEY,
}).catch(console.error);

this.cursor = '';
this.lastFetchedAddress = zeroAddress;
this.hasFetchedAll = false;
}

public static getInstance(): MoralisNFTRepository {
if (!MoralisNFTRepository.instance) {
MoralisNFTRepository.instance = new MoralisNFTRepository();
}
return MoralisNFTRepository.instance;
}

async findByAddress({ address, chainId, refresh = false }: FetchNftArgs): Promise<NFT[]> {
this.lastFetchedAddress = address;

if (refresh) {
this.reset();
}
if (this.hasFetchedAll) {
return this.nfts;
}

try {
const response = await Moralis.EvmApi.nft.getWalletNFTs({
cursor: this.getCursor(address, refresh),
chain: chainId,
excludeSpam: moralisApiConfig.excludeSpam,
mediaItems: moralisApiConfig.mediaItems,
address: address,
limit: moralisApiConfig.limit,
});

this.cursor = response.pagination.cursor || '';
this.hasFetchedAll = !this.cursor; // If there is no cursor, we have fetched all NFTs

const mappedData = response.result.map((nft) => mapToNFTFromMoralis(nft as unknown as NFTApiData, chainId));
this.nfts = [...this.nfts, ...mappedData];
return this.nfts;
} catch (e) {
console.error('Failed to fetch NFTs from Moralis:', e);
return [];
}
}

private reset(): void {
this.cursor = '';
this.hasFetchedAll = false;
this.nfts = [];
}

private getCursor(address: Address, refresh: boolean): string {
if (this.lastFetchedAddress !== address || refresh) {
return '';
}
return this.cursor || '';
}
}

export default MoralisNFTRepository.getInstance();
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { NFT } from 'src/thirdparty/domain/models/NFT';
import type { Address } from 'viem';

import type { NFT } from '$api/domain/models/NFT';
import type { NFTApiData } from '$api/infrastructure/types/moralis';
import type { TokenType } from '$libs/token';

import type { NFTApiData } from '../../types/moralis';

export function mapToNFTFromMoralis(apiData: NFTApiData, chainId: number): NFT {
return {
tokenId: apiData.tokenId,
Expand All @@ -13,6 +12,7 @@ export function mapToNFTFromMoralis(apiData: NFTApiData, chainId: number): NFT {
name: apiData.name,
symbol: apiData.symbol,
type: apiData.contractType as TokenType,
balance: apiData.amount,
addresses: {
[chainId]: apiData.tokenAddress as Address,
},
Expand Down
7 changes: 7 additions & 0 deletions packages/bridge-ui/src/api/infrastructure/types/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Address } from 'viem';

export type FetchNftArgs = {
address: Address;
chainId: number;
refresh: boolean;
};
7 changes: 7 additions & 0 deletions packages/bridge-ui/src/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,10 @@ export const ipfsConfig = {
gatewayTimeout: 200,
overallTimeout: 5000,
};

export const moralisApiConfig = {
limit: 10,
format: 'decimal',
excludeSpam: true,
mediaItems: false,
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,26 @@
import { selectedImportMethod } from './state';
let foundNFTs: NFT[] = [];
// States
let scanning = false;
let canProceed = false;
export let validating = false;
const scanForNFTs = async () => {
const nextPage = async () => {
await scanForNFTs(false);
};
const scanForNFTs = async (refresh: boolean) => {
scanning = true;
$selectedNFTs = [];
const accountAddress = $account?.address;
const srcChainId = $srcChain?.id;
const destChainId = $destChain?.id;
if (!accountAddress || !srcChainId || !destChainId) return;
const nftsFromAPIs = await fetchNFTs(accountAddress, srcChainId);
const nftsFromAPIs = await fetchNFTs({ address: accountAddress, chainId: srcChainId, refresh });
foundNFTs = nftsFromAPIs.nfts;
scanning = false;
Expand All @@ -47,9 +55,6 @@
reset();
};
// States
let scanning = false;
$: canImport = ($account?.isConnected && $srcChain?.id && $destChain && !scanning) || false;
$: {
Expand All @@ -74,9 +79,9 @@
{#if $selectedImportMethod === ImportMethod.MANUAL}
<ManualImport bind:validating />
{:else if $selectedImportMethod === ImportMethod.SCAN}
<ScannedImport {scanForNFTs} bind:foundNFTs bind:canProceed />
<ScannedImport refresh={() => scanForNFTs(true)} {nextPage} bind:foundNFTs bind:canProceed />
{:else}
<ImportActions bind:scanning {canImport} {scanForNFTs} />
<ImportActions bind:scanning {canImport} scanForNFTs={() => scanForNFTs(false)} />
{/if}

<OnAccount change={onAccountChange} />
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,45 @@
import { enteredAmount, selectedNFTs, tokenBalance } from '$components/Bridge/state';
import { ImportMethod } from '$components/Bridge/types';
import { ActionButton, Button } from '$components/Button';
import { IconFlipper } from '$components/Icon';
import { Icon, IconFlipper } from '$components/Icon';
import RotatingIcon from '$components/Icon/RotatingIcon.svelte';
import { NFTDisplay } from '$components/NFTs';
import { NFTView } from '$components/NFTs/types';
import type { NFT } from '$libs/token';
import { selectedImportMethod } from './state';
export let scanForNFTs: () => Promise<void>;
export let refresh: () => Promise<void>;
export let nextPage: () => Promise<void>;
export let foundNFTs: NFT[] = [];
export let canProceed = false;
let nftView: NFTView = NFTView.LIST;
let scanning = false;
let hasMoreNFTs = true;
let tokenAmountInput: TokenAmountInput;
function onScanClick() {
let previousNFTs: NFT[] = [];
const handleNextPage = () => {
previousNFTs = foundNFTs;
scanning = true;
scanForNFTs().finally(() => {
nextPage().finally(() => {
scanning = false;
});
if (previousNFTs.length === foundNFTs.length) {
hasMoreNFTs = false;
}
};
function onRefreshClick() {
scanning = true;
hasMoreNFTs = true;
refresh().finally(() => {
scanning = false;
});
}
Expand Down Expand Up @@ -81,7 +98,7 @@
type="neutral"
shape="circle"
class="bg-neutral rounded-full w-[28px] h-[28px] border-none"
on:click={onScanClick}>
on:click={onRefreshClick}>
<RotatingIcon loading={scanning} type="refresh" size={13} />
</Button>

Expand All @@ -97,6 +114,21 @@
</div>
<div>
<NFTDisplay loading={scanning} nfts={foundNFTs} {nftView} />
<div class="flex pt-[18px]">
<button
class="btn btn-sm rounded-full items-center {hasMoreNFTs
? 'border-primary-brand'
: 'border-none'} dark:text-white hover:bg-primary-interactive-hover btn-secondary bg-transparent light:text-black"
disabled={!hasMoreNFTs}
on:click={handleNextPage}>
{#if hasMoreNFTs}
<span class="text-primary-color">{$t('paginator.more')}</span>
{:else}
<Icon type="check-circle" class="text-primary-brand" />
<span class="text-primary-color">{$t('paginator.everything_loaded')}</span>
{/if}
</button>
</div>
</div>
</section>
{#if nftHasAmount}
Expand Down
4 changes: 3 additions & 1 deletion packages/bridge-ui/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,9 @@
},
"paginator": {
"of": "of",
"page": "Page"
"page": "Page",
"more": "Fetch more...",
"everything_loaded": "Everything loaded"
},
"paused_modal": {
"description": "The bridge is currently not available. Follow our official communication channels for more information. ",
Expand Down
Loading

0 comments on commit 2e0b552

Please sign in to comment.