mirror of
https://github.com/GrassrootsEconomics/cic-dw.git
synced 2024-12-30 13:27:32 +01:00
add: (feat) individual token stats
- volume chart - token holders - token tx count * minor refactors
This commit is contained in:
parent
f00e97cc94
commit
7bda44c169
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
.idea
|
||||
.env
|
||||
dev/dev-data
|
||||
migrations/*.env
|
||||
migrations/*prod*
|
||||
|
||||
dist/
|
||||
|
@ -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
2
go.mod
@ -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
2
go.sum
@ -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=
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user