Skip to content

Commit

Permalink
Merge pull request #14 from vitwit/anil/display
Browse files Browse the repository at this point in the history
Recactor display and add network, asset type wise stats
  • Loading branch information
anilcse authored Dec 12, 2024
2 parents fdde90e + bb43ba6 commit be1bd7d
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 62 deletions.
19 changes: 3 additions & 16 deletions cmd/cosmoscope/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"fmt"
"sync"
"time"

"github.com/anilcse/cosmoscope/internal/config"
"github.com/anilcse/cosmoscope/internal/cosmos"
Expand All @@ -14,7 +13,7 @@ import (
)

func main() {
printHeader()
portfolio.PrintHeader()

// Load configuration
cfg := config.Load()
Expand Down Expand Up @@ -71,19 +70,7 @@ func main() {

// Collect and display balances
balances := portfolio.CollectBalances(balanceChan)
portfolio.DisplayBalances(balances)
portfolio.DisplaySummary(balances)
}

func printHeader() {
fmt.Println("\n\n\n*******************************************************************************")
fmt.Println("* *")
fmt.Println("* *")
fmt.Printf("* BALANCES REPORT (%s) *\n", time.Now().Format("2006-01-02 15:04:05"))
fmt.Println("* *")
fmt.Println("* *")
fmt.Println("*******************************************************************************")
fmt.Println("")
fmt.Println("")
fmt.Println("")
// Print the report
portfolio.PrintBalanceReport(balances)
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.21
require (
github.com/cosmos/cosmos-sdk v0.50.3
github.com/ethereum/go-ethereum v1.13.8
github.com/fatih/color v1.15.0
github.com/olekukonko/tablewriter v0.0.5
)

Expand All @@ -23,6 +24,8 @@ require (
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/holiman/uint256 v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R
github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
github.com/ethereum/go-ethereum v1.13.8 h1:1od+thJel3tM52ZUNQwvpYOeRHlbkVFZ5S8fhi0Lgsg=
github.com/ethereum/go-ethereum v1.13.8/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZzNAmIKnceogzsA=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
Expand Down Expand Up @@ -102,6 +104,7 @@ github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
Expand Down Expand Up @@ -168,6 +171,8 @@ golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
Expand Down
17 changes: 17 additions & 0 deletions internal/cosmos/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,20 @@ func getActiveEndpoint(endpoints []RestEndpoint) string {

return ""
}

func isValidJSONResponse(resp *http.Response, body []byte) bool {

Check failure on line 352 in internal/cosmos/client.go

View workflow job for this annotation

GitHub Actions / Test and Coverage

func `isValidJSONResponse` is unused (unused)
contentType := resp.Header.Get("Content-Type")
if !strings.Contains(strings.ToLower(contentType), "application/json") {
return false
}

// Check if the response looks like HTML
if strings.Contains(strings.ToLower(string(body)), "<!doctype html") ||
strings.Contains(strings.ToLower(string(body)), "<html") {
return false
}

// Try to parse as JSON
var js json.RawMessage
return json.Unmarshal(body, &js) == nil
}
244 changes: 198 additions & 46 deletions internal/portfolio/display.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,73 +3,225 @@ package portfolio
import (
"fmt"
"os"
"sort"
"strings"
"time"

"github.com/anilcse/cosmoscope/pkg/utils"
"github.com/fatih/color"
"github.com/olekukonko/tablewriter"
)

func DisplayBalances(balances []Balance) {
var (
// Only keep essential color definitions
headerColor = color.New(color.FgGreen, color.Bold) // For the main header box
titleColor = color.New(color.FgRed, color.Bold) // For section titles
timeColor = color.New(color.FgHiBlue) // For timestamp
totalValueColor = color.New(color.FgGreen, color.Bold) // For timestamp
)

var totalValue float64

var tokens = make(map[string]*struct {
amount float64
usdValue float64
})

func PrintBalanceReport(balances []Balance) {
printDetailedView(balances)
printPortfolioSummary(balances)
printNetworkDistribution(balances)
printAssetTypes(balances)
PrintFooter(balances)
}

func PrintHeader() {
headerColor.Println("\n╔════════════════════════════════════════════════════════════╗")
headerColor.Printf("║ %s", strings.Repeat(" ", 59))
headerColor.Println("║")
headerColor.Printf("║ BALANCES REPORT - ")
timeColor.Printf("%s", time.Now().Format("2006-01-02 15:04:05"))
headerColor.Printf(" ║\n")
headerColor.Printf("║ %s", strings.Repeat(" ", 59))
headerColor.Println("║")
headerColor.Println("╚════════════════════════════════════════════════════════════╝\n")

Check failure on line 45 in internal/portfolio/display.go

View workflow job for this annotation

GitHub Actions / Test and Coverage

printf: `(*github.com/fatih/color.Color).Println` arg list ends with redundant newline (govet)
}

func PrintFooter(balances []Balance) {
totalValue = 0
for _, b := range balances {
if _, exists := tokens[b.Token]; !exists {
tokens[b.Token] = &struct {
amount float64
usdValue float64
}{}
}

totalValue += b.USDValue
}

headerColor.Println("\n╔════════════════════════════════════════════════════════════╗")
headerColor.Printf("║ %s", strings.Repeat(" ", 59))
headerColor.Println("║")
headerColor.Printf("║ Total USD value - ")
timeColor.Printf("$%.2f", totalValue)
headerColor.Printf(" ║\n")
headerColor.Printf("║ %s", strings.Repeat(" ", 59))
headerColor.Println("║")
headerColor.Println("╚════════════════════════════════════════════════════════════╝\n")

Check failure on line 69 in internal/portfolio/display.go

View workflow job for this annotation

GitHub Actions / Test and Coverage

printf: `(*github.com/fatih/color.Color).Println` arg list ends with redundant newline (govet)
}

func printDetailedView(balances []Balance) {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Account", "Network", "Token", "Amount", "USD Value"})
table.SetBorder(true)

groupedBalances := GroupBalancesByHexAddr(balances)
for _, groupedBalance := range groupedBalances {
for _, balance := range groupedBalance {
table.Append([]string{
balance.Account,
balance.Network,
balance.Token,
utils.FormatAmount(balance.Amount, balance.Decimals),
fmt.Sprintf("$%.2f", balance.USDValue),
})
}
table.SetAutoMergeCells(false)
table.SetRowLine(true)

// Set all headers to bold
table.SetHeaderColor(
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.Bold},
)

for _, b := range balances {
table.Append([]string{
truncateString(b.Account, 20),
b.Network,
b.Token,
fmt.Sprintf("%.4f", b.Amount),
fmt.Sprintf("$%.2f", b.USDValue),
})
}

titleColor.Println("Detailed Balance View:")
table.Render()
fmt.Println()
}

func DisplaySummary(balances []Balance) {
tokenSummaries := make(map[string]*TokenSummary)
totalValue := 0.0

for _, balance := range balances {
if summary, exists := tokenSummaries[balance.Token]; exists {
summary.Balance += balance.Amount
summary.USDValue += balance.USDValue
} else {
tokenSummaries[balance.Token] = &TokenSummary{
TokenName: balance.Token,
Balance: balance.Amount,
USDValue: balance.USDValue,
}
func printPortfolioSummary(balances []Balance) {
for _, b := range balances {
if _, exists := tokens[b.Token]; !exists {
tokens[b.Token] = &struct {
amount float64
usdValue float64
}{}
}
totalValue += balance.USDValue
tokens[b.Token].amount += b.Amount
tokens[b.Token].usdValue += b.USDValue
totalValue += b.USDValue
}

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Token", "Amount", "USD Value", "Share %"})
table.SetAutoMergeCells(false)
table.SetRowLine(true)

// Set all headers to bold
table.SetHeaderColor(
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.Bold},
)

for token, sum := range tokens {
share := (sum.usdValue / totalValue) * 100
table.Append([]string{
token,
fmt.Sprintf("%.4f", sum.amount),
fmt.Sprintf("$%.2f", sum.usdValue),
fmt.Sprintf("%.2f%%", share),
})
}

titleColor.Println("Portfolio Summary:")
table.Render()
fmt.Printf("Total Portfolio Value: ")
totalValueColor.Printf("$%.2f\n\n", totalValue)
}

func printNetworkDistribution(balances []Balance) {
networks := make(map[string]float64)
var totalValue float64

for _, b := range balances {
network := strings.Split(b.Network, "-")[0]
networks[network] += b.USDValue
totalValue += b.USDValue
}

var summaries []TokenSummary
for _, summary := range tokenSummaries {
summary.Share = (summary.USDValue / totalValue) * 100
summaries = append(summaries, *summary)
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Network", "USD Value", "Share %"})
table.SetAutoMergeCells(false)
table.SetRowLine(true)

// Set all headers to bold
table.SetHeaderColor(
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.Bold},
)

for network, value := range networks {
share := (value / totalValue) * 100
table.Append([]string{
network,
fmt.Sprintf("$%.2f", value),
fmt.Sprintf("%.2f%%", share),
})
}

sort.Slice(summaries, func(i, j int) bool {
return summaries[i].USDValue > summaries[j].USDValue
})
titleColor.Println("Network Distribution:")
table.Render()
fmt.Println()
}

func printAssetTypes(balances []Balance) {
types := make(map[string]float64)
var totalValue float64

for _, b := range balances {
assetType := "Bank"
if strings.Contains(b.Network, "staking") {
assetType = "Staking"
} else if strings.Contains(b.Network, "rewards") {
assetType = "Rewards"
} else if strings.Contains(b.Network, "Fixed") {
assetType = "Fixed"
}
types[assetType] += b.USDValue
totalValue += b.USDValue
}

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Token Name", "Balance", "USD Value", "Share %"})
table.SetBorder(true)
table.SetHeader([]string{"Type", "USD Value", "Share %"})
table.SetAutoMergeCells(false)
table.SetRowLine(true)

// Set all headers to bold
table.SetHeaderColor(
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.Bold},
)

for _, summary := range summaries {
for assetType, value := range types {
share := (value / totalValue) * 100
table.Append([]string{
summary.TokenName,
utils.FormatAmount(summary.Balance, 6),
fmt.Sprintf("$%.2f", summary.USDValue),
fmt.Sprintf("%.2f%%", summary.Share),
assetType,
fmt.Sprintf("$%.2f", value),
fmt.Sprintf("%.2f%%", share),
})
}

table.SetFooter([]string{"Total", "", fmt.Sprintf("$%.2f", totalValue), "100.00%"})
titleColor.Println("Asset Types:")
table.Render()
}

func truncateString(s string, length int) string {
if len(s) <= length {
return s
}
return s[:length-3] + "..."
}

0 comments on commit be1bd7d

Please sign in to comment.