@@ -19,6 +19,12 @@ type BalanceModel struct {
1919 Balance * big.Int `json:"balance" ch:"balance"`
2020}
2121
22+ type HolderModel struct {
23+ HolderAddress string `json:"holder_address" ch:"owner"`
24+ TokenId string `json:"token_id" ch:"token_id"`
25+ Balance * big.Int `json:"balance" ch:"balance"`
26+ }
27+
2228// @Summary Get token balances of an address by type
2329// @Description Retrieve token balances of an address by type
2430// @Tags balances
@@ -125,3 +131,106 @@ func serializeBalance(balance common.TokenBalance) BalanceModel {
125131 }(),
126132 }
127133}
134+
135+ // @Summary Get holders of a token
136+ // @Description Retrieve holders of a token
137+ // @Tags holders
138+ // @Accept json
139+ // @Produce json
140+ // @Security BasicAuth
141+ // @Param chainId path string true "Chain ID"
142+ // @Param address path string true "Address of the token"
143+ // @Param token_type path string false "Type of token"
144+ // @Param hide_zero_balances query bool true "Hide zero balances"
145+ // @Param page query int false "Page number for pagination"
146+ // @Param limit query int false "Number of items per page" default(5)
147+ // @Success 200 {object} api.QueryResponse{data=[]LogModel}
148+ // @Failure 400 {object} api.Error
149+ // @Failure 401 {object} api.Error
150+ // @Failure 500 {object} api.Error
151+ // @Router /{chainId}/holders/{address} [get]
152+ func GetTokenHoldersByType (c * gin.Context ) {
153+ chainId , err := api .GetChainId (c )
154+ if err != nil {
155+ api .BadRequestErrorHandler (c , err )
156+ return
157+ }
158+
159+ address := strings .ToLower (c .Param ("address" ))
160+ if ! strings .HasPrefix (address , "0x" ) {
161+ api .BadRequestErrorHandler (c , fmt .Errorf ("invalid address '%s'" , address ))
162+ return
163+ }
164+
165+ tokenType := c .Query ("token_type" )
166+ if tokenType != "" && tokenType != "erc20" && tokenType != "erc1155" && tokenType != "erc721" {
167+ api .BadRequestErrorHandler (c , fmt .Errorf ("invalid token type '%s'" , tokenType ))
168+ return
169+ }
170+ hideZeroBalances := c .Query ("hide_zero_balances" ) != "false"
171+
172+ columns := []string {"owner" , "sum(balance) as balance" }
173+ groupBy := []string {"owner" }
174+ if tokenType != "erc20" {
175+ columns = []string {"owner" , "token_id" , "sum(balance) as balance" }
176+ groupBy = []string {"owner" , "token_id" }
177+ }
178+
179+ qf := storage.BalancesQueryFilter {
180+ ChainId : chainId ,
181+ TokenType : tokenType ,
182+ TokenAddress : address ,
183+ ZeroBalance : hideZeroBalances ,
184+ GroupBy : groupBy ,
185+ SortBy : c .Query ("sort_by" ),
186+ SortOrder : c .Query ("sort_order" ),
187+ Page : api .ParseIntQueryParam (c .Query ("page" ), 0 ),
188+ Limit : api .ParseIntQueryParam (c .Query ("limit" ), 0 ),
189+ }
190+
191+ queryResult := api.QueryResponse {
192+ Meta : api.Meta {
193+ ChainId : chainId .Uint64 (),
194+ Page : qf .Page ,
195+ Limit : qf .Limit ,
196+ },
197+ }
198+
199+ mainStorage , err = getMainStorage ()
200+ if err != nil {
201+ log .Error ().Err (err ).Msg ("Error getting main storage" )
202+ api .InternalErrorHandler (c )
203+ return
204+ }
205+
206+ balancesResult , err := mainStorage .GetTokenBalances (qf , columns ... )
207+ if err != nil {
208+ log .Error ().Err (err ).Msg ("Error querying balances" )
209+ // TODO: might want to choose BadRequestError if it's due to not-allowed functions
210+ api .InternalErrorHandler (c )
211+ return
212+ }
213+ queryResult .Data = serializeHolders (balancesResult .Data )
214+ sendJSONResponse (c , queryResult )
215+ }
216+
217+ func serializeHolders (holders []common.TokenBalance ) []HolderModel {
218+ holderModels := make ([]HolderModel , len (holders ))
219+ for i , holder := range holders {
220+ holderModels [i ] = serializeHolder (holder )
221+ }
222+ return holderModels
223+ }
224+
225+ func serializeHolder (holder common.TokenBalance ) HolderModel {
226+ return HolderModel {
227+ HolderAddress : holder .Owner ,
228+ Balance : holder .Balance ,
229+ TokenId : func () string {
230+ if holder .TokenId != nil {
231+ return holder .TokenId .String ()
232+ }
233+ return ""
234+ }(),
235+ }
236+ }
0 commit comments