Skip to content

Commit

Permalink
DAO-741 Implemented fetch token holders endpoint from blockscout
Browse files Browse the repository at this point in the history
  • Loading branch information
Freshenext authored and sleyter93 committed Oct 23, 2024
1 parent 8cf09f4 commit 14ed345
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 4 deletions.
27 changes: 25 additions & 2 deletions src/blockscoutApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import {
TokenInfoResponse,
TokenServerResponse, TokenTransferApi, TransactionServerResponse,
TransactionsServerResponse, BlockscoutTransactionResponseTxResult,
NftTokenHoldersResponse
NftTokenHoldersResponse, TokenHoldersResponse
} from './types'
import {
fromApiToInternalTransaction, fromApiToNft, fromApiToNftOwner, fromApiToRtbcBalance, fromApiToTEvents,
fromApiToTokenWithBalance, fromApiToTokens, fromApiToTransaction, transformResponseToNftHolder
} from './utils'
import { GetEventLogsByAddressAndTopic0, GetNftHoldersData } from '../service/address/AddressService'
import { GetEventLogsByAddressAndTopic0, GetNftHoldersData, GetTokenHoldersByAddress } from '../service/address/AddressService'

export class BlockscoutAPI extends DataSource {
private chainId: number
Expand Down Expand Up @@ -173,6 +173,29 @@ export class BlockscoutAPI extends DataSource {
.catch(() => [])
}

async getTokenHoldersByAddress ({ address, nextPageParams }: GetTokenHoldersByAddress) {
try {
const url = `${this.url}/v2/tokens/${address}/holders`
const response = await this.axios?.get<ServerResponseV2<TokenHoldersResponse>>(url, { params: nextPageParams })
if (response?.status === 200) {
return response.data
}
return {
items: [],
next_page_params: null,
error: `Blockscout error with status ${response?.status}`
}
} catch (error) {
console.log(typeof error, error)
// @TODO handle error
return {
items: [],
next_page_params: null,
error: 'Blockscout error'
}
}
}

async getNftHoldersData ({ address, nextPageParams }: GetNftHoldersData) {
const url = `${this.url}/v2/tokens/${address.toLowerCase()}/instances`
try {
Expand Down
34 changes: 34 additions & 0 deletions src/blockscoutApi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,37 @@ export interface NftTokenHoldersTransformedResponse {
name: string;
}
}
export interface TokenHolderAddress {
ens_domain_name: string;
hash: string;
implementations: any[];
is_contract: boolean;
is_verified: boolean;
metadata: null;
name: null;
private_tags: any[];
proxy_type: null;
public_tags: any[];
watchlist_names: any[];
}

export interface TokenHolderToken {
address: string;
circulating_market_cap: null;
decimals: string;
exchange_rate: null;
holders: string;
icon_url: null;
name: string;
symbol: string;
total_supply: string;
type: string;
volume_24h: null;
}

export interface TokenHoldersResponse {
address: TokenHolderAddress;
token: Token;
token_id: null;
value: string;
}
19 changes: 19 additions & 0 deletions src/controller/httpsAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,25 @@ export class HttpsAPI {
}
})

this.app.get('/address/:address/holders',
async ({ params: { address }, query: { chainId = '31', ...rest } } : Request, res: Response,
nextFunction: NextFunction) => {
try {
chainIdSchema.validateSync({ chainId })
addressSchema.validateSync({ address })
const result = await this.addressService
.getTokenHoldersByAddress({
chainId: chainId as string,
address: address as string,
...rest
})
.catch(nextFunction)
return this.responseJsonOk(res)(result)
} catch (e) {
this.handleValidationError(e, res)
}
})

this.app.get(
'/price',
async (req: Request<{}, {}, {}, PricesQueryParams>, res: Response) => {
Expand Down
4 changes: 3 additions & 1 deletion src/repository/DataSource.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import _axios from 'axios'
import { ethers } from 'ethers'
import BitcoinCore from '../service/bitcoin/BitcoinCore'
import { GetEventLogsByAddressAndTopic0, GetNftHoldersData } from '../service/address/AddressService'
import { GetEventLogsByAddressAndTopic0, GetNftHoldersData,
GetTokenHoldersByAddress } from '../service/address/AddressService'

export abstract class DataSource {
readonly url: string
Expand Down Expand Up @@ -32,6 +33,7 @@ export abstract class DataSource {
Omit<GetEventLogsByAddressAndTopic0, 'chainId'>);

abstract getNftHoldersData({ address }: Omit<GetNftHoldersData, 'chainId'>);
abstract getTokenHoldersByAddress({ address }: Omit<GetTokenHoldersByAddress, 'chainId'>)
}

export type RSKDatasource = {
Expand Down
6 changes: 5 additions & 1 deletion src/rskExplorerApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,11 @@ export class RSKExplorerAPI extends DataSource {
throw new Error('Feature not supported')
}

getNftHoldersData () {
getNftHoldersData () {
throw new Error('Feature not supported')
}

getTokenHoldersByAddress () {
throw new Error('Feature not supported')
}
}
6 changes: 6 additions & 0 deletions src/service/address/AddressService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface GetEventLogsByAddressAndTopic0 {
}

export interface GetNftHoldersData { address: string, nextPageParams?: NextPageParams, chainId: string }
export interface GetTokenHoldersByAddress { address: string, nextPageParams?: NextPageParams, chainId: string }

type GetBalancesTransactionsPricesByAddress = {
chainId: string
Expand Down Expand Up @@ -160,4 +161,9 @@ export class AddressService {
const dataSource = this.dataSourceMapping[chainId]
return dataSource.getNftHoldersData(rest)
}

async getTokenHoldersByAddress ({ chainId, ...rest }: GetTokenHoldersByAddress) {
const dataSource = this.dataSourceMapping[chainId]
return dataSource.getTokenHoldersByAddress(rest)
}
}

0 comments on commit 14ed345

Please sign in to comment.