mirror of
https://github.com/GrassrootsEconomics/cic-dw.git
synced 2025-01-02 14:57:31 +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
|
.idea
|
||||||
.env
|
.env
|
||||||
dev/dev-data
|
dev/dev-data
|
||||||
migrations/*.env
|
migrations/*prod*
|
||||||
|
|
||||||
dist/
|
dist/
|
||||||
|
@ -20,7 +20,7 @@ func initHTTPServer() *echo.Echo {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
dashboard.InitDashboardApi(server, db, preparedQueries.dashboard)
|
dashboard.InitDashboardApi(server, db, preparedQueries.dashboard)
|
||||||
public.InitPublicApi(server, db, batchBalance, preparedQueries.public)
|
public.InitPublicApi(server, db, batchBalance, cicnetClient, preparedQueries.public)
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -6,7 +6,7 @@ require (
|
|||||||
github.com/ethereum/go-ethereum v1.10.17
|
github.com/ethereum/go-ethereum v1.10.17
|
||||||
github.com/georgysavva/scany v0.3.0
|
github.com/georgysavva/scany v0.3.0
|
||||||
github.com/golang-module/carbon/v2 v2.1.6
|
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/hibiken/asynq v0.23.0
|
||||||
github.com/jackc/pgx/v4 v4.16.1
|
github.com/jackc/pgx/v4 v4.16.1
|
||||||
github.com/knadh/koanf v1.4.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.0/go.mod h1:cQcLMsuhCirTVO5ccG37S4pGS1vnkSepoi+eYZvdOEY=
|
||||||
github.com/grassrootseconomics/cic-go v1.4.1 h1:fFthl73ZJydubPOP48nMtDIl0TgvjOXGUMBYn4JXIsI=
|
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.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/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-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
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("/new-registrations", handleNewRegistrations)
|
||||||
g.GET("/transactions-count", handleTransactionsCount)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/georgysavva/scany/pgxscan"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/georgysavva/scany/pgxscan"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type lineChartRes struct {
|
type lineChartRes struct {
|
||||||
@ -15,7 +16,8 @@ type lineChartRes struct {
|
|||||||
|
|
||||||
func handleNewRegistrations(c echo.Context) error {
|
func handleNewRegistrations(c echo.Context) error {
|
||||||
var (
|
var (
|
||||||
api = c.Get("api").(*api)
|
api = c.Get("api").(*api)
|
||||||
|
|
||||||
data []lineChartRes
|
data []lineChartRes
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +37,8 @@ func handleNewRegistrations(c echo.Context) error {
|
|||||||
|
|
||||||
func handleTransactionsCount(c echo.Context) error {
|
func handleTransactionsCount(c echo.Context) error {
|
||||||
var (
|
var (
|
||||||
api = c.Get("api").(*api)
|
api = c.Get("api").(*api)
|
||||||
|
|
||||||
data []lineChartRes
|
data []lineChartRes
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,3 +55,48 @@ func handleTransactionsCount(c echo.Context) error {
|
|||||||
|
|
||||||
return c.JSON(http.StatusOK, data)
|
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 (
|
import (
|
||||||
batch_balance "github.com/grassrootseconomics/cic-go/batch_balance"
|
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/jackc/pgx/v4/pgxpool"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/nleof/goyesql"
|
"github.com/nleof/goyesql"
|
||||||
@ -10,10 +11,11 @@ import (
|
|||||||
type api struct {
|
type api struct {
|
||||||
db *pgxpool.Pool
|
db *pgxpool.Pool
|
||||||
q goyesql.Queries
|
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 := e.Group("/public")
|
||||||
|
|
||||||
g.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
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{
|
c.Set("api", &api{
|
||||||
db: db,
|
db: db,
|
||||||
q: queries,
|
q: queries,
|
||||||
c: batchBalance,
|
cn: cicnet,
|
||||||
|
bb: batchBalance,
|
||||||
})
|
})
|
||||||
return next(c)
|
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("/balances/:address", handleBalancesQuery)
|
||||||
g.GET("/tokens-count", handleTokensCountQuery)
|
g.GET("/tokens-count", handleTokensCountQuery)
|
||||||
g.GET("/tokens", handleTokenListQuery)
|
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)))
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package public
|
package public
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cic-dw/pkg/address"
|
||||||
"cic-dw/pkg/pagination"
|
"cic-dw/pkg/pagination"
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/georgysavva/scany/pgxscan"
|
"github.com/georgysavva/scany/pgxscan"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/lmittmann/w3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tokensRes struct {
|
type tokensRes struct {
|
||||||
@ -20,10 +22,23 @@ type tokenCountRes struct {
|
|||||||
Count int `db:"count" json:"count"`
|
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 {
|
func handleTokenListQuery(c echo.Context) error {
|
||||||
var (
|
var (
|
||||||
api = c.Get("api").(*api)
|
api = c.Get("api").(*api)
|
||||||
pg = pagination.GetPagination(c.QueryParams())
|
pg = pagination.GetPagination(c.QueryParams())
|
||||||
|
|
||||||
res []tokensRes
|
res []tokensRes
|
||||||
q string
|
q string
|
||||||
)
|
)
|
||||||
@ -49,6 +64,7 @@ func handleTokenListQuery(c echo.Context) error {
|
|||||||
func handleTokensCountQuery(c echo.Context) error {
|
func handleTokensCountQuery(c echo.Context) error {
|
||||||
var (
|
var (
|
||||||
api = c.Get("api").(*api)
|
api = c.Get("api").(*api)
|
||||||
|
|
||||||
res tokenCountRes
|
res tokenCountRes
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -63,3 +79,61 @@ func handleTokensCountQuery(c echo.Context) error {
|
|||||||
|
|
||||||
return c.JSON(http.StatusOK, res)
|
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
|
SELECT date_range.day AS x, COUNT(transactions.id) AS y
|
||||||
FROM date_range
|
FROM date_range
|
||||||
LEFT JOIN transactions ON date_range.day = CAST(transactions.date_block AS date)
|
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
|
GROUP BY date_range.day
|
||||||
ORDER 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
|
-- name: tokens-count
|
||||||
-- Return total record count from individual i= tables/views
|
-- 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