add: (feat) individual token stats

- volume chart
- token holders
- token tx count

* minor refactors
This commit is contained in:
Mohamed Sohail 2022-05-23 15:41:24 +03:00
parent f00e97cc94
commit 7bda44c169
Signed by: kamikazechaser
GPG Key ID: 7DD45520C01CD85D
11 changed files with 214 additions and 14 deletions

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
.idea
.env
dev/dev-data
migrations/*.env
migrations/*prod*
dist/

View File

@ -20,7 +20,7 @@ func initHTTPServer() *echo.Echo {
}))
dashboard.InitDashboardApi(server, db, preparedQueries.dashboard)
public.InitPublicApi(server, db, batchBalance, preparedQueries.public)
public.InitPublicApi(server, db, batchBalance, cicnetClient, preparedQueries.public)
return server
}

2
go.mod
View File

@ -6,7 +6,7 @@ require (
github.com/ethereum/go-ethereum v1.10.17
github.com/georgysavva/scany v0.3.0
github.com/golang-module/carbon/v2 v2.1.6
github.com/grassrootseconomics/cic-go v1.4.1
github.com/grassrootseconomics/cic-go v1.5.0
github.com/hibiken/asynq v0.23.0
github.com/jackc/pgx/v4 v4.16.1
github.com/knadh/koanf v1.4.1

2
go.sum
View File

@ -228,6 +228,8 @@ github.com/grassrootseconomics/cic-go v1.4.0 h1:ydGp9pVrAhpq45KUSkPOHjP1PyGlBsRC
github.com/grassrootseconomics/cic-go v1.4.0/go.mod h1:cQcLMsuhCirTVO5ccG37S4pGS1vnkSepoi+eYZvdOEY=
github.com/grassrootseconomics/cic-go v1.4.1 h1:fFthl73ZJydubPOP48nMtDIl0TgvjOXGUMBYn4JXIsI=
github.com/grassrootseconomics/cic-go v1.4.1/go.mod h1:cQcLMsuhCirTVO5ccG37S4pGS1vnkSepoi+eYZvdOEY=
github.com/grassrootseconomics/cic-go v1.5.0 h1:XnOPxMq3hFwdSwse749dfO1biPH5KjsJk+gbm1bcCz0=
github.com/grassrootseconomics/cic-go v1.5.0/go.mod h1:cQcLMsuhCirTVO5ccG37S4pGS1vnkSepoi+eYZvdOEY=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=

View File

@ -26,4 +26,6 @@ func InitDashboardApi(e *echo.Echo, db *pgxpool.Pool, queries goyesql.Queries) {
g.GET("/new-registrations", handleNewRegistrations)
g.GET("/transactions-count", handleTransactionsCount)
g.GET("/token-transactions-count/:address", handleTokenTransactionsCount)
g.GET("/token-volume/:address", handleTokenVolume)
}

View File

@ -2,10 +2,11 @@ package dashboard
import (
"context"
"github.com/georgysavva/scany/pgxscan"
"github.com/labstack/echo/v4"
"net/http"
"time"
"github.com/georgysavva/scany/pgxscan"
"github.com/labstack/echo/v4"
)
type lineChartRes struct {
@ -15,7 +16,8 @@ type lineChartRes struct {
func handleNewRegistrations(c echo.Context) error {
var (
api = c.Get("api").(*api)
api = c.Get("api").(*api)
data []lineChartRes
)
@ -35,7 +37,8 @@ func handleNewRegistrations(c echo.Context) error {
func handleTransactionsCount(c echo.Context) error {
var (
api = c.Get("api").(*api)
api = c.Get("api").(*api)
data []lineChartRes
)
@ -52,3 +55,48 @@ func handleTransactionsCount(c echo.Context) error {
return c.JSON(http.StatusOK, data)
}
func handleTokenTransactionsCount(c echo.Context) error {
var (
api = c.Get("api").(*api)
token = c.Param("address")
data []lineChartRes
)
from, to := parseDateRange(c.QueryParams())
rows, err := api.db.Query(context.Background(), api.q["token-transactions-count"], from, to, token)
if err != nil {
return err
}
if err := pgxscan.ScanAll(&data, rows); err != nil {
return err
}
return c.JSON(http.StatusOK, data)
}
func handleTokenVolume(c echo.Context) error {
var (
api = c.Get("api").(*api)
token = c.Param("address")
data []lineChartRes
)
from, to := parseDateRange(c.QueryParams())
rows, err := api.db.Query(context.Background(), api.q["token-volume"], from, to, token)
if err != nil {
return err
}
if err := pgxscan.ScanAll(&data, rows); err != nil {
return err
}
return c.JSON(http.StatusOK, data)
}

View File

@ -2,6 +2,7 @@ package public
import (
batch_balance "github.com/grassrootseconomics/cic-go/batch_balance"
cic_net "github.com/grassrootseconomics/cic-go/net"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/labstack/echo/v4"
"github.com/nleof/goyesql"
@ -10,10 +11,11 @@ import (
type api struct {
db *pgxpool.Pool
q goyesql.Queries
c *batch_balance.BatchBalance
bb *batch_balance.BatchBalance
cn *cic_net.CicNet
}
func InitPublicApi(e *echo.Echo, db *pgxpool.Pool, batchBalance *batch_balance.BatchBalance, queries goyesql.Queries) {
func InitPublicApi(e *echo.Echo, db *pgxpool.Pool, batchBalance *batch_balance.BatchBalance, cicnet *cic_net.CicNet, queries goyesql.Queries) {
g := e.Group("/public")
g.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
@ -21,7 +23,8 @@ func InitPublicApi(e *echo.Echo, db *pgxpool.Pool, batchBalance *batch_balance.B
c.Set("api", &api{
db: db,
q: queries,
c: batchBalance,
cn: cicnet,
bb: batchBalance,
})
return next(c)
}
@ -32,4 +35,6 @@ func InitPublicApi(e *echo.Echo, db *pgxpool.Pool, batchBalance *batch_balance.B
g.GET("/balances/:address", handleBalancesQuery)
g.GET("/tokens-count", handleTokensCountQuery)
g.GET("/tokens", handleTokenListQuery)
g.GET("/token/:address", handleTokenInfo)
g.GET("/token-summary/:address", handleTokenSummary)
}

View File

@ -48,7 +48,7 @@ func handleBalancesQuery(c echo.Context) error {
tokenAddresses = append(tokenAddresses, w3.A(address.Checksum(rowData.TokenAddress)))
}
balances, err := api.c.TokensBalance(context.Background(), w3.A(address.Checksum(qAddress)), tokenAddresses)
balances, err := api.bb.TokensBalance(context.Background(), w3.A(address.Checksum(qAddress)), tokenAddresses)
if err != nil {
return err
}

View File

@ -1,12 +1,14 @@
package public
import (
"cic-dw/pkg/address"
"cic-dw/pkg/pagination"
"context"
"net/http"
"github.com/georgysavva/scany/pgxscan"
"github.com/labstack/echo/v4"
"github.com/lmittmann/w3"
)
type tokensRes struct {
@ -20,10 +22,23 @@ type tokenCountRes struct {
Count int `db:"count" json:"count"`
}
type TokenInfoRes struct {
IsDemurrage bool `json:"is_demurrage"`
Name string `json:"token_name"`
Symbol string `json:"token_symbol"`
TotalSupply int64 `json:"token_total_supply"`
}
type tokenSummaryRes struct {
TotalHolders int64 `db:"count" json:"token_holders"`
TotalTransactions int64 `db:"count" json:"token_transactions"`
}
func handleTokenListQuery(c echo.Context) error {
var (
api = c.Get("api").(*api)
pg = pagination.GetPagination(c.QueryParams())
res []tokensRes
q string
)
@ -49,6 +64,7 @@ func handleTokenListQuery(c echo.Context) error {
func handleTokensCountQuery(c echo.Context) error {
var (
api = c.Get("api").(*api)
res tokenCountRes
)
@ -63,3 +79,61 @@ func handleTokensCountQuery(c echo.Context) error {
return c.JSON(http.StatusOK, res)
}
func handleTokenInfo(c echo.Context) error {
var (
api = c.Get("api").(*api)
tokenAddress = c.Param("address")
rCtx = context.Background()
res TokenInfoRes
)
_, err := api.cn.DemurrageTokenInfo(rCtx, w3.A(address.Checksum(tokenAddress)))
if err != nil {
res.IsDemurrage = false
} else {
res.IsDemurrage = true
}
tokenInfo, err := api.cn.ERC20TokenInfo(rCtx, w3.A(address.Checksum(tokenAddress)))
if err != nil {
return err
}
res.Name = tokenInfo.Name
res.Symbol = tokenInfo.Symbol
res.TotalSupply = tokenInfo.TotalSupply.Int64() / 1000000
return c.JSON(http.StatusOK, res)
}
func handleTokenSummary(c echo.Context) error {
var (
api = c.Get("api").(*api)
token = c.Param("address")
data tokenSummaryRes
)
uniqueTokenHoldersrRow, err := api.db.Query(context.Background(), api.q["unique-token-holders"], token)
if err != nil {
return err
}
if err := pgxscan.ScanOne(&data.TotalHolders, uniqueTokenHoldersrRow); err != nil {
return err
}
tokenTxRow, err := api.db.Query(context.Background(), api.q["all-time-token-transactions-count"], token)
if err != nil {
return err
}
if err := pgxscan.ScanOne(&data.TotalTransactions, tokenTxRow); err != nil {
return err
}
return c.JSON(http.StatusOK, data)
}

View File

@ -25,7 +25,47 @@ exclude AS (
SELECT date_range.day AS x, COUNT(transactions.id) AS y
FROM date_range
LEFT JOIN transactions ON date_range.day = CAST(transactions.date_block AS date)
WHERE transactions.sender_address NOT IN (SELECT sys_address FROM exclude) AND transactions.recipient_address NOT IN (SELECT sys_address FROM exclude)
AND transactions.sender_address NOT IN (SELECT sys_address FROM exclude) AND transactions.recipient_address NOT IN (SELECT sys_address FROM exclude)
AND transactions.success = true
GROUP BY date_range.day
ORDER BY date_range.day
LIMIT 730;
LIMIT 730;
-- name: token-transactions-count
-- This query gets transactions for a specific token for a given date range
WITH date_range AS (
SELECT day::date FROM generate_series($1, $2, INTERVAL '1 day') day
),
exclude AS (
SELECT sys_address FROM sys_accounts WHERE sys_address IS NOT NULL
)
SELECT date_range.day AS x, COUNT(transactions.id) AS y
FROM date_range
LEFT JOIN transactions ON date_range.day = CAST(transactions.date_block AS date)
AND transactions.sender_address NOT IN (SELECT sys_address FROM exclude) AND transactions.recipient_address NOT IN (SELECT sys_address FROM exclude)
AND transactions.token_address = $3
AND transactions.success = true
GROUP BY date_range.day
ORDER BY date_range.day
LIMIT 730;
--name: token-volume
-- This query rteurns daily token volume
-- Assumes erc20 token is 6 decimals
WITH date_range AS (
SELECT day::date FROM generate_series($1, $2, INTERVAL '1 day') day
),
exclude AS (
SELECT sys_address FROM sys_accounts WHERE sys_address IS NOT NULL
)
SELECT date_range.day AS x, COALESCE(SUM(transactions.tx_value / 1000000), 0) AS y
FROM date_range
LEFT JOIN transactions ON date_range.day = CAST(transactions.date_block AS date)
AND transactions.sender_address NOT IN (SELECT sys_address FROM exclude) AND transactions.recipient_address NOT IN (SELECT sys_address FROM exclude)
AND transactions.token_address = $3
AND transactions.success = true
GROUP BY date_range.day
ORDER BY date_range.day
LIMIT 730;

View File

@ -16,4 +16,33 @@ WHERE tokens.id < $1 ORDER BY tokens.id ASC LIMIT $2;
-- name: tokens-count
-- Return total record count from individual i= tables/views
SELECT COUNT(*) FROM tokens;
SELECT COUNT(*) FROM tokens;
--name: unique-token-holders
-- Returns the unique token holders based on seen transactions
WITH unique_holders AS (
SELECT sender_address AS holding_address FROM transactions
WHERE token_address = $1
UNION
SELECT recipient_address AS holding_address FROM transactions
WHERE token_address = $1
),
exclude AS (
SELECT sys_address FROM sys_accounts WHERE sys_address IS NOT NULL
)
SELECT COUNT(holding_address) FROM unique_holders
WHERE holding_address NOT IN (SELECT sys_address FROM exclude);
--name: all-time-token-transactions-count
-- Returns transactions of individual tokens
WITH exclude AS (
SELECT sys_address FROM sys_accounts WHERE sys_address IS NOT NULL
)
SELECT COUNT(*) FROM transactions
WHERE token_address = $1
AND transactions.sender_address NOT IN (SELECT sys_address FROM exclude)
AND transactions.recipient_address NOT IN (SELECT sys_address FROM exclude)
AND transactions.success = true;