Add full monorepo: virtual-banker, backend, frontend, docs, scripts, deployment
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
104
backend/analytics/token_distribution.go
Normal file
104
backend/analytics/token_distribution.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// TokenDistribution provides token distribution analytics
|
||||
type TokenDistribution struct {
|
||||
db *pgxpool.Pool
|
||||
chainID int
|
||||
}
|
||||
|
||||
// NewTokenDistribution creates a new token distribution analyzer
|
||||
func NewTokenDistribution(db *pgxpool.Pool, chainID int) *TokenDistribution {
|
||||
return &TokenDistribution{
|
||||
db: db,
|
||||
chainID: chainID,
|
||||
}
|
||||
}
|
||||
|
||||
// DistributionStats represents token distribution statistics
|
||||
type DistributionStats struct {
|
||||
Contract string
|
||||
Symbol string
|
||||
TotalSupply string
|
||||
Holders int
|
||||
Distribution map[string]string
|
||||
TopHolders []HolderInfo
|
||||
}
|
||||
|
||||
// HolderInfo represents holder information
|
||||
type HolderInfo struct {
|
||||
Address string
|
||||
Balance string
|
||||
Percentage string
|
||||
}
|
||||
|
||||
// GetTokenDistribution gets token distribution for a contract
|
||||
func (td *TokenDistribution) GetTokenDistribution(ctx context.Context, contract string, topN int) (*DistributionStats, error) {
|
||||
// Refresh materialized view
|
||||
_, err := td.db.Exec(ctx, `REFRESH MATERIALIZED VIEW CONCURRENTLY token_distribution`)
|
||||
if err != nil {
|
||||
// Ignore error if view doesn't exist yet
|
||||
}
|
||||
|
||||
// Get distribution from materialized view
|
||||
query := `
|
||||
SELECT holder_count, total_balance
|
||||
FROM token_distribution
|
||||
WHERE token_contract = $1 AND chain_id = $2
|
||||
`
|
||||
|
||||
var holders int
|
||||
var totalSupply string
|
||||
err = td.db.QueryRow(ctx, query, contract, td.chainID).Scan(&holders, &totalSupply)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get distribution: %w", err)
|
||||
}
|
||||
|
||||
// Get top holders
|
||||
topHoldersQuery := `
|
||||
SELECT address, balance
|
||||
FROM token_balances
|
||||
WHERE token_contract = $1 AND chain_id = $2 AND balance > 0
|
||||
ORDER BY balance DESC
|
||||
LIMIT $3
|
||||
`
|
||||
|
||||
rows, err := td.db.Query(ctx, topHoldersQuery, contract, td.chainID, topN)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get top holders: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
topHolders := []HolderInfo{}
|
||||
for rows.Next() {
|
||||
var holder HolderInfo
|
||||
if err := rows.Scan(&holder.Address, &holder.Balance); err != nil {
|
||||
continue
|
||||
}
|
||||
// Calculate percentage (simplified)
|
||||
holder.Percentage = "0.0" // TODO: Calculate from total supply
|
||||
topHolders = append(topHolders, holder)
|
||||
}
|
||||
|
||||
stats := &DistributionStats{
|
||||
Contract: contract,
|
||||
Holders: holders,
|
||||
TotalSupply: totalSupply,
|
||||
Distribution: make(map[string]string),
|
||||
TopHolders: topHolders,
|
||||
}
|
||||
|
||||
// Calculate distribution metrics
|
||||
stats.Distribution["top_10_percent"] = "0.0" // TODO: Calculate
|
||||
stats.Distribution["top_1_percent"] = "0.0" // TODO: Calculate
|
||||
stats.Distribution["gini_coefficient"] = "0.0" // TODO: Calculate
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user