⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Open
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
8 changes: 7 additions & 1 deletion cmd/account/show/allowances.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

staking "github.com/oasisprotocol/oasis-core/go/staking/api"

"github.com/oasisprotocol/cli/cmd/common"

"github.com/oasisprotocol/oasis-sdk/client-sdk/go/config"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"
Expand Down Expand Up @@ -60,8 +62,12 @@ func prettyPrintAllowanceDescriptions(
// element so we can align all values.
lenLongest := lenLongestString(beneficiaryFieldName, amountFieldName)

// Precompute address formatting context for efficiency.
addrCtx := common.GenAddressFormatContext()

for _, desc := range allowDescriptions {
fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, beneficiaryFieldName, desc.beneficiary)
prettyAddr := common.PrettyAddressWith(addrCtx, desc.beneficiary.String())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think common.GenAddressFormatContext() can be called inside common.PrettyAddressWith and there's no need for an extra addrCtx. Same below for all other occurrences.

fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, beneficiaryFieldName, prettyAddr)
if desc.self {
fmt.Fprintf(w, " (self)")
}
Expand Down
14 changes: 13 additions & 1 deletion cmd/account/show/delegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/consensusaccounts"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"

"github.com/oasisprotocol/cli/cmd/common"
)

const amountFieldName = "Amount:"
Expand Down Expand Up @@ -87,6 +89,12 @@ func prettyPrintDelegationDescriptions(

fmt.Fprintf(w, "%sDelegations:\n", prefix)

// Guard against empty slice to prevent panic when accessing delDescriptions[0].
if len(delDescriptions) == 0 {
fmt.Fprintf(w, "%s <none>\n", prefix)
return
}

sort.Sort(byEndTimeAmountAddress(delDescriptions))

// Get the length of name of the longest field to display for each
Expand All @@ -102,8 +110,12 @@ func prettyPrintDelegationDescriptions(
lenLongest = lenLongestString(addressFieldName, amountFieldName, endTimeFieldName)
}

// Precompute address formatting context for efficiency.
addrCtx := common.GenAddressFormatContext()

for _, desc := range delDescriptions {
fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, addressFieldName, desc.address)
prettyAddr := common.PrettyAddressWith(addrCtx, desc.address.String())
fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, addressFieldName, prettyAddr)
if desc.self {
fmt.Fprintf(w, " (self)")
}
Expand Down
38 changes: 36 additions & 2 deletions cmd/account/show/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"golang.org/x/term"

consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
roothash "github.com/oasisprotocol/oasis-core/go/roothash/api"
Expand Down Expand Up @@ -64,13 +65,19 @@ var (
// Determine which address to show. If an explicit argument was given, use that
// otherwise use the default account.
var targetAddress string
var walletNameForEth string
switch {
case len(args) >= 1:
// Explicit argument given.
targetAddress = args[0]
if _, ok := cfg.Wallet.All[targetAddress]; ok {
// Wallet account selected by name.
walletNameForEth = targetAddress
}
case npa.Account != nil:
// Default account is selected.
targetAddress = npa.Account.Address
walletNameForEth = npa.AccountName
default:
// No address given and no wallet configured.
cobra.CheckErr("no address given and no wallet configured")
Expand All @@ -83,9 +90,36 @@ var (

nativeAddr, ethAddr, err := common.ResolveLocalAccountOrAddress(npa.Network, targetAddress)
cobra.CheckErr(err)
out.EthereumAddress = ethAddr
out.NativeAddress = nativeAddr
out.Name = common.FindAccountName(nativeAddr.String())
addrCtx := common.GenAddressFormatContext()
out.Name = addrCtx.Names[nativeAddr.String()]

// If eth address is not available, try to get it from locally-known mappings
// (wallet/addressbook/test accounts). No unlock required.
if ethAddr == nil {
if ethHex := addrCtx.Eth[nativeAddr.String()]; ethHex != "" && ethCommon.IsHexAddress(ethHex) {
eth := ethCommon.HexToAddress(ethHex)
ethAddr = &eth
}
}

// If eth address is still not available and the user selected a wallet account,
// load it (may require passphrase) to derive and persist eth_address metadata.
//
// NOTE: We only do this for an explicitly selected wallet account to avoid surprising
// interactive prompts when a user provides an arbitrary address.
if ethAddr == nil && walletNameForEth != "" {
if walletCfg, ok := cfg.Wallet.All[walletNameForEth]; ok && walletCfg.SupportsEthAddress() {
// Avoid prompting in non-interactive contexts (e.g. piping output).
if term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd())) {
acc := common.LoadAccount(cfg, walletNameForEth)
ethAddr = acc.EthAddress()
}
}
}

// Ensure output reflects the final resolved/derived Ethereum address (if any).
out.EthereumAddress = ethAddr

height, err := common.GetActualHeight(
ctx,
Expand Down
179 changes: 173 additions & 6 deletions cmd/common/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ package common
import (
"fmt"
"os"
"sort"

ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/spf13/cobra"

staking "github.com/oasisprotocol/oasis-core/go/staking/api"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/testing"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"

buildRoflProvider "github.com/oasisprotocol/cli/build/rofl/provider"
"github.com/oasisprotocol/cli/config"
)

Expand Down Expand Up @@ -41,19 +46,66 @@ func CheckForceErr(err interface{}) {
cobra.CheckErr(errMsg)
}

// GenAccountNames generates a map of all addresses -> account name for pretty printing.
// GenAccountNames generates a map of all known native addresses -> account name for pretty printing.
// It includes test accounts, configured networks (paratimes/ROFL defaults), addressbook and wallet.
//
// Priority order (later entries overwrite earlier):
// test accounts < network entries < addressbook < wallet.
func GenAccountNames() types.AccountNames {
an := types.AccountNames{}
for name, acc := range config.Global().Wallet.All {
an[acc.GetAddress().String()] = name

// Test accounts have lowest priority.
for name, acc := range testing.TestAccounts {
an[acc.Address.String()] = fmt.Sprintf("test:%s", name)
}

// Network-derived entries (paratimes, ROFL providers) have second-lowest priority.
cfg := config.Global()
netNames := make([]string, 0, len(cfg.Networks.All))
for name := range cfg.Networks.All {
netNames = append(netNames, name)
}
sort.Strings(netNames)
for _, netName := range netNames {
net := cfg.Networks.All[netName]
if net == nil {
continue
}

for name, acc := range config.Global().AddressBook.All {
// Include ParaTime runtime addresses as paratime:<name>.
ptNames := make([]string, 0, len(net.ParaTimes.All))
for ptName := range net.ParaTimes.All {
ptNames = append(ptNames, ptName)
}
sort.Strings(ptNames)
for _, ptName := range ptNames {
pt := net.ParaTimes.All[ptName]
if pt == nil {
continue
}

rtAddr := types.NewAddressFromConsensus(staking.NewRuntimeAddress(pt.Namespace()))
an[rtAddr.String()] = fmt.Sprintf("paratime:%s", ptName)

// Include ROFL default provider addresses as rofl:provider:<paratime>.
if svc, ok := buildRoflProvider.DefaultRoflServices[pt.ID]; ok {
if svc.Provider != "" {
if a, _, err := helpers.ResolveEthOrOasisAddress(svc.Provider); err == nil && a != nil {
an[a.String()] = fmt.Sprintf("rofl:provider:%s", ptName)
}
}
}
}
}

// Addressbook entries have medium priority.
for name, acc := range cfg.AddressBook.All {
an[acc.GetAddress().String()] = name
}

for name, acc := range testing.TestAccounts {
an[acc.Address.String()] = fmt.Sprintf("test:%s", name)
// Wallet entries have highest priority.
for name, acc := range cfg.Wallet.All {
an[acc.GetAddress().String()] = name
}

return an
Expand All @@ -64,3 +116,118 @@ func FindAccountName(address string) string {
an := GenAccountNames()
return an[address]
}

// AddressFormatContext contains precomputed maps for address formatting.
type AddressFormatContext struct {
// Names maps native address string to account name.
Names types.AccountNames
// Eth maps native address string to Ethereum hex address string, if known.
// This is optional metadata coming from wallet/addressbook/test accounts (and never derived from chain state).
Eth map[string]string
}

// GenAccountEthMap generates a map of native address string -> eth hex address (if known).
func GenAccountEthMap() map[string]string {
eth := make(map[string]string)

// From test accounts.
for _, acc := range testing.TestAccounts {
if acc.EthAddress != nil {
eth[acc.Address.String()] = acc.EthAddress.Hex()
}
}

// From address book entries (higher priority than test accounts).
for _, acc := range config.Global().AddressBook.All {
if ethAddr := acc.GetEthAddress(); ethAddr != nil {
eth[acc.GetAddress().String()] = ethAddr.Hex()
}
}

// From wallet entries (highest priority), when an Ethereum address is available in config.
for _, acc := range config.Global().Wallet.All {
if ethAddr := acc.GetEthAddress(); ethAddr != nil {
eth[acc.GetAddress().String()] = ethAddr.Hex()
}
}

return eth
}

// GenAddressFormatContext builds both name and eth address maps for formatting.
func GenAddressFormatContext() AddressFormatContext {
return AddressFormatContext{
Names: GenAccountNames(),
Eth: GenAccountEthMap(),
}
}

// PrettyAddressWith formats an address using a precomputed context.
// If the address is known (in wallet, addressbook, or test accounts), it returns "name (address)".
// For secp256k1 accounts with a known Ethereum address, the Ethereum hex format is preferred in parentheses.
// If the address is unknown, it returns the original address string unchanged.
func PrettyAddressWith(ctx AddressFormatContext, addr string) string {
// Try to parse the address to get canonical native form.
nativeAddr, ethFromInput, err := helpers.ResolveEthOrOasisAddress(addr)
if err != nil || nativeAddr == nil {
// Cannot parse, return unchanged.
return addr
}

nativeStr := nativeAddr.String()

// Look up the name.
name := ctx.Names[nativeStr]
if name == "" {
// Unknown address, return the original input.
return addr
}

// Determine which address to show in parentheses.
// Prefer Ethereum address if available (from input or from known eth addresses).
var parenAddr string
if ethFromInput != nil {
parenAddr = ethFromInput.Hex()
} else if ethHex := ctx.Eth[nativeStr]; ethHex != "" {
parenAddr = ethHex
} else {
parenAddr = nativeStr
}

// Guard against redundant "name (name)" output.
if name == parenAddr {
return parenAddr
}

return fmt.Sprintf("%s (%s)", name, parenAddr)
}

func preferredAddressString(nativeAddr *types.Address, ethAddr *ethCommon.Address) string {
if ethAddr != nil {
return ethAddr.Hex()
}
if nativeAddr == nil {
return ""
}
return nativeAddr.String()
}

// PrettyResolvedAddressWith formats a resolved address tuple (native, eth) using a precomputed context.
//
// If ethAddr is non-nil, it is preferred to preserve the "user provided eth" behavior even when
// the native-to-eth mapping is not available in the context.
func PrettyResolvedAddressWith(ctx AddressFormatContext, nativeAddr *types.Address, ethAddr *ethCommon.Address) string {
addrStr := preferredAddressString(nativeAddr, ethAddr)
if addrStr == "" {
return ""
}
return PrettyAddressWith(ctx, addrStr)
}

// PrettyAddress formats an address for display without network context.
// If the address is known (in wallet, addressbook, or test accounts), it returns "name (address)".
// For secp256k1 accounts with a known Ethereum address, the Ethereum hex format is preferred in parentheses.
// If the address is unknown, it returns the original address string unchanged.
func PrettyAddress(addr string) string {
return PrettyAddressWith(GenAddressFormatContext(), addr)
}
Loading
Loading