Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recactor display and add network, asset type wise stats #14

Merged
merged 1 commit into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@

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 @@
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] + "..."
}
Loading