Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: explorer api pagination #2002

Merged
merged 8 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { NftStandard } from '@core/nfts/enums'
import { TokenStandard } from '@core/token/enums'
import { QueryParameters } from '@core/utils'
import { BaseApi } from '@core/utils/api'
import { DEFAULT_EXPLORER_URLS } from '@core/network/constants'
import { SupportedNetworkId } from '@core/network/enums'
import { IBlockscoutApi, IBlockscoutAsset, IBlockscoutAssetMetadata, IBlockscoutTransaction } from '../interfaces'
import { NetworkId } from '@core/network/types'

interface INextPageParams {
block_number: number
index: number
items_count: number
}

interface IPaginationResponse<T> {
items: T[]
next_page_params: INextPageParams | null
}

export class BlockscoutApi extends BaseApi implements IBlockscoutApi {
constructor(networkId: NetworkId) {
const explorerUrl = DEFAULT_EXPLORER_URLS[networkId as SupportedNetworkId]
super(`${explorerUrl}/api/v2`)
}

private async makePaginatedGetRequest<T>(
path: string,
queryParameters?: QueryParameters,
items: T[] = [],
nextPageParameters?: INextPageParams | null
): Promise<T[]> {
if (nextPageParameters === null) {
return Promise.resolve(items)
}
return this.get<IPaginationResponse<T>>(path, { ...queryParameters, ...nextPageParameters }).then(
(response) => {
if (!response) {
return Promise.resolve(items)
}
return this.makePaginatedGetRequest(
path,
queryParameters,
items.concat(response.items),
response.next_page_params
)
}
)
}

async getAssetMetadata(assetAddress: string): Promise<IBlockscoutAssetMetadata | undefined> {
const response = await this.get<IBlockscoutAssetMetadata>(`tokens/${assetAddress}`)
if (response) {
response.type = response.type.replace('-', '') as TokenStandard.Erc20 | NftStandard.Erc721
return response
}
}

async getAssetsForAddress(
address: string,
standard: TokenStandard.Erc20 | NftStandard.Erc721 = TokenStandard.Erc20
): Promise<IBlockscoutAsset[]> {
const tokenType = standard.replace('ERC', 'ERC-')
const path = `addresses/${address}/tokens`
const response = await this.get<IPaginationResponse<IBlockscoutAsset>>(path, { type: tokenType })
if (response) {
return (response?.items ?? []).map((asset) => ({
...asset,
token: {
...asset.token,
type: asset.token.type.replace('-', ''),
},
}))
} else {
return []
}
}

async getTransactionsForAddress(address: string): Promise<IBlockscoutTransaction[]> {
const path = `addresses/${address}/transactions`
const items = await this.makePaginatedGetRequest<IBlockscoutTransaction>(path)
return items
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './blockscout.api'
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { NftStandard } from '@core/nfts/enums'
import { TokenStandard } from '@core/token/enums'
import { IBlockscoutAsset } from './blockscout-asset.interface'
import { IBlockscoutAssetMetadata } from './blockscout-asset-metadata.interface'
import { IBlockscoutTransaction } from './blockscout-transaction.interface'

export interface IBlockscoutApi {
getAssetMetadata(assetAddress: string): Promise<IBlockscoutAssetMetadata | undefined>
getAssetsForAddress(
address: string,
tokenStandard?: TokenStandard.Erc20 | NftStandard.Erc721
): Promise<IBlockscoutAsset[]>
getTransactionsForAddress(address: string): Promise<IBlockscoutTransaction[]>
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// snake_case returned by the API
export interface IExplorerAssetMetadata {
export interface IBlockscoutAssetMetadata {
address: string
circulating_market_cap: string
decimals: number
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IBlockscoutAssetMetadata } from './blockscout-asset-metadata.interface'

// snake_case returned by the API
export interface IBlockscoutAsset {
token: IBlockscoutAssetMetadata
token_id: string
token_instance: unknown
value: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { IBlockscoutAssetMetadata } from './blockscout-asset-metadata.interface'

interface IFee {
type: string
value: string
}

interface IAddressTag {
address_hash: string
display_name: string
label: string
}

interface IWatchlistName {
display_name: string
label: string
}

interface IAddressParam {
hash: string
implementation_name: string
name: string
is_contract: boolean
private_tags: IAddressTag[]
watchlist_names: IWatchlistName[]
public_tags: IAddressTag[]
is_verified: boolean
}

interface IDecodedInput {
method_call: string
method_id: string
parameters: Record<string, string> // IDecodedInputParameters
}

interface ITokenTransfer {
block_hash: string
from: IAddressParam
log_index: string
method: string
timestamp: string
to: IAddressParam
token: IBlockscoutAssetMetadata
}

export interface IBlockscoutTransaction {
timestamp: string
fee: IFee
gas_limit: number
block: number
status: string // e.g ok | error
method: string // e.g transferFrom
confirmations: number
type: number
exchange_rate: string
to: IAddressParam
tx_burnt_fee: string
max_fee_per_gas: string
result: string
hash: string
gas_price: string
priority_fee: string
base_fee_per_gas: string
from: IAddressParam
token_transfers: ITokenTransfer[]
tx_types: string[]
gas_used: string
created_contract: IAddressParam
position: number
nonce: number
has_error_in_internal_txs: boolean
actions: unknown // TransactionAction
decoded_input: IDecodedInput
token_transfers_overflow: boolean
raw_input: string
value: string
max_priority_fee_per_gas: string
revert_reason: string
confirmation_duration: string
tx_tag: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './blockscout-api.interface'
export * from './blockscout-asset.interface'
export * from './blockscout-asset-metadata.interface'
export * from './blockscout-transaction.interface'
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { IAccountState } from '@core/account/interfaces'
import { EvmExplorerApi, EvmNetworkId } from '@core/network'
import { EvmNetworkId } from '@core/network'
import { getNetwork } from '@core/network/stores'
import { TokenStandard, TokenTrackingStatus } from '@core/token'
import { addNewTrackedTokenToActiveProfile, hasTokenBeenUntracked } from '@core/wallet/actions'
import { BASE_TOKEN_CONTRACT_ADDRESS } from '../constants'
import { BlockscoutApi } from '@auxiliary/blockscout/api'

export function checkForUntrackedTokens(account: IAccountState, addPreviouslyUntracked?: boolean): void {
const chains = getNetwork()?.getChains()
Expand All @@ -14,9 +15,9 @@ export function checkForUntrackedTokens(account: IAccountState, addPreviouslyUnt
return
}
const networkId = chain.getConfiguration().id
const explorerApi = new EvmExplorerApi(networkId)
const blockscoutApi = new BlockscoutApi(networkId)

const tokens = await explorerApi.getAssetsForAddress(evmAddress)
const tokens = await blockscoutApi.getAssetsForAddress(evmAddress)
const untrackedTokensToTrack = tokens.filter(
({ token }) => addPreviouslyUntracked || !hasTokenBeenUntracked(token.address.toLowerCase(), networkId)
)
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion packages/shared/src/lib/core/network/classes/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './evm-explorer-api.class'
export * from './iscp-chain.class'
export * from './stardust-network.class'

This file was deleted.

This file was deleted.

3 changes: 0 additions & 3 deletions packages/shared/src/lib/core/network/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ export * from './chain.interface'
export * from './client-options.interface'
export * from './connected-chain.interface'
export * from './evm-addresses.interface'
export * from './explorer-api.interface'
export * from './explorer-asset-metadata.interface'
export * from './explorer-asset.interface'
export * from './gas-fee-policy.interface'
export * from './gas-limits.interface'
export * from './network-status.interface'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { IAccountState } from '@core/account/interfaces'
import { ContractType } from '@core/layer-2/enums'
import { EvmExplorerApi } from '@core/network/classes'
import { getNetwork } from '@core/network/stores'
import { IChain, IExplorerAsset } from '@core/network/interfaces'
import { IChain } from '@core/network/interfaces'
import features from '@features/features'

import { NftStandard } from '../enums'
Expand All @@ -13,6 +12,8 @@ import { addNftsToDownloadQueue } from './addNftsToDownloadQueue'
import { Nft } from '../interfaces'
import { addNewTrackedNftToActiveProfile } from './addNewTrackedNftToActiveProfile'
import { TokenTrackingStatus } from '@core/token'
import { IBlockscoutAsset } from '@auxiliary/blockscout/interfaces'
import { BlockscoutApi } from '@auxiliary/blockscout/api'

export async function checkForUntrackedNfts(account: IAccountState): Promise<void> {
if (!features?.collectibles?.erc721?.enabled) {
Expand All @@ -28,9 +29,9 @@ export async function checkForUntrackedNfts(account: IAccountState): Promise<voi
return
}
const networkId = chain.getConfiguration().id
const explorerApi = new EvmExplorerApi(networkId)
const blockscoutApi = new BlockscoutApi(networkId)

const explorerNfts = await explorerApi.getAssetsForAddress(evmAddress, NftStandard.Erc721)
const explorerNfts = await blockscoutApi.getAssetsForAddress(evmAddress, NftStandard.Erc721)
for (const explorerNft of explorerNfts) {
await persistNftsFromExplorerAsset(account, evmAddress, explorerNft, chain)
}
Expand All @@ -40,7 +41,7 @@ export async function checkForUntrackedNfts(account: IAccountState): Promise<voi
async function persistNftsFromExplorerAsset(
account: IAccountState,
evmAddress: string,
asset: IExplorerAsset,
asset: IBlockscoutAsset,
chain: IChain
): Promise<void> {
const { token, value } = asset
Expand Down
8 changes: 3 additions & 5 deletions packages/shared/src/lib/core/tide/apis/tide.api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { INftAttribute } from '@core/nfts'
import { BaseApi, buildQueryParametersFromObject } from '@core/utils'
import { BaseApi } from '@core/utils'
import { TIDE_API_BASE_URL } from '../constants'
import { TideApiEndpoint } from '../enums'
import { ITideLeaderboardItem, ITideUserPosition } from '../interfaces'
Expand Down Expand Up @@ -82,10 +82,8 @@ export class TideApi extends BaseApi {
projectId: number,
queryParams?: ProjectLeaderboardQueryParams
): Promise<IProjectLeaderboardResponse | undefined> {
const path = `${TideApiEndpoint.Project}/${projectId}/leaderboard?${
queryParams ? buildQueryParametersFromObject(queryParams) : ''
}`
const response = await this.get<IProjectLeaderboardResponse>(path)
const path = `${TideApiEndpoint.Project}/${projectId}/leaderboard`
const response = await this.get<IProjectLeaderboardResponse>(path, queryParams)
return response
}

Expand Down
Loading
Loading