Skip to content

Commit

Permalink
fix: online/offline transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
PhearZero committed Nov 14, 2024
1 parent e3f2e8f commit 723007d
Show file tree
Hide file tree
Showing 15 changed files with 108 additions and 69 deletions.
12 changes: 7 additions & 5 deletions internal/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

// Account represents a user's account, including address, status, balance, and number of keys.
type Account struct {
Participation *api.AccountParticipation
// Account Address is the algorand encoded address
Address string
// Status is the Online/Offline/"NotParticipating" status of the account
Expand Down Expand Up @@ -148,11 +149,12 @@ func AccountsFromState(state *StateModel, t Time, client *api.ClientWithResponse
}

values[key.Address] = Account{
Address: key.Address,
Status: account.Status,
Balance: account.Amount / 1000000,
Expires: getExpiresTime(t, key, state),
Keys: 1,
Participation: account.Participation,
Address: key.Address,
Status: account.Status,
Balance: account.Amount / 1000000,
Expires: getExpiresTime(t, key, state),
Keys: 1,
}
} else {
val.Keys++
Expand Down
22 changes: 12 additions & 10 deletions internal/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,20 @@ func Test_AccountsFromState(t *testing.T) {
// Construct expected accounts
expectedAccounts := map[string]Account{
onlineAccounts[0].Address: {
Address: onlineAccounts[0].Address,
Status: onlineAccounts[0].Status,
Balance: onlineAccounts[0].Amount / 1_000_000,
Keys: 2,
Expires: expires,
Participation: onlineAccounts[0].Participation,
Address: onlineAccounts[0].Address,
Status: onlineAccounts[0].Status,
Balance: onlineAccounts[0].Amount / 1_000_000,
Keys: 2,
Expires: expires,
},
onlineAccounts[1].Address: {
Address: onlineAccounts[1].Address,
Status: onlineAccounts[1].Status,
Balance: onlineAccounts[1].Amount / 1_000_000,
Keys: 1,
Expires: expires,
Participation: onlineAccounts[1].Participation,
Address: onlineAccounts[1].Address,
Status: onlineAccounts[1].Status,
Balance: onlineAccounts[1].Amount / 1_000_000,
Keys: 1,
Expires: expires,
},
}

Expand Down
9 changes: 9 additions & 0 deletions internal/participation.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,12 @@ func RemovePartKeyByID(slice *[]api.ParticipationKey, id string) {
}
}
}

func FindParticipationIdForVoteKey(slice *[]api.ParticipationKey, votekey []byte) *string {
for _, item := range *slice {
if string(item.Key.VoteParticipationKey) == string(votekey) {
return &item.Id
}
}
return nil
}
1 change: 1 addition & 0 deletions ui/app/modal.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func EmitShowModal(modal ModalType) tea.Cmd {

type ModalEvent struct {
Key *api.ParticipationKey
Active bool
Address string
Err *error
Type ModalType
Expand Down
5 changes: 3 additions & 2 deletions ui/modal/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (*ViewModel, tea.Cmd) {
switch msg := msg.(type) {
case error:
m.Open = true
m.Type = app.ExceptionModal
m.exceptionModal.Message = msg.Error()
m.SetType(app.ExceptionModal)
case app.ModalEvent:
// On closing events
if msg.Type == app.CloseModal {
Expand All @@ -45,9 +45,10 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (*ViewModel, tea.Cmd) {
}

if msg.Type != app.CloseModal && msg.Type != app.CancelModal {
m.SetType(msg.Type)
m.SetKey(msg.Key)
m.SetAddress(msg.Address)
m.SetActive(msg.Active)
m.SetType(msg.Type)
}

// Handle Modal Type
Expand Down
14 changes: 10 additions & 4 deletions ui/modal/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,20 @@ type ViewModel struct {
Type app.ModalType
}

func (m ViewModel) SetAddress(address string) {
func (m *ViewModel) SetAddress(address string) {
m.Address = address
m.generateModal.SetAddress(address)
}
func (m ViewModel) SetKey(key *api.ParticipationKey) {
m.infoModal.ActiveKey = key
func (m *ViewModel) SetKey(key *api.ParticipationKey) {
m.infoModal.Participation = key
m.confirmModal.ActiveKey = key
m.transactionModal.ActiveKey = key
m.transactionModal.Participation = key
}
func (m *ViewModel) SetActive(active bool) {
m.infoModal.Active = active
m.infoModal.UpdateState()
m.transactionModal.Active = active
m.transactionModal.UpdateState()
}

func (m *ViewModel) SetType(modal app.ModalType) {
Expand Down
51 changes: 28 additions & 23 deletions ui/modals/info/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import (
)

type ViewModel struct {
Width int
Height int
Title string
Controls string
BorderColor string
ActiveKey *api.ParticipationKey
Data *internal.StateModel
Width int
Height int
Title string
Controls string
BorderColor string
Active bool
Participation *api.ParticipationKey
State *internal.StateModel
}

func New(state *internal.StateModel) *ViewModel {
Expand All @@ -28,7 +29,7 @@ func New(state *internal.StateModel) *ViewModel {
Title: "Key Information",
BorderColor: "3",
Controls: "( " + style.Red.Render("(d)elete") + " | " + style.Green.Render("(o)nline") + " )",
Data: state,
State: state,
}
}

Expand Down Expand Up @@ -61,29 +62,33 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (*ViewModel, tea.Cmd) {
return &m, nil
}
func (m *ViewModel) UpdateState() {
if m.ActiveKey == nil {
if m.Participation == nil {
return
}
accountStatus := m.Data.Accounts[m.ActiveKey.Address].Status
accountStatus := m.State.Accounts[m.Participation.Address].Status

if accountStatus == "Online" {
m.Controls = "( " + style.Red.Render("(d)elete") + " | " + style.Yellow.Render("(o)ffline") + " )"
} else {
m.Controls = "( " + style.Red.Render("(d)elete") + " | " + style.Green.Render("(o)nline") + " )"
if accountStatus == "Online" && m.Active {
m.BorderColor = "1"
m.Controls = "( take " + style.Red.Render(style.Red.Render("(o)ffline")) + " )"
}

if !m.Active {
m.BorderColor = "3"
m.Controls = "( " + style.Red.Render("(d)elete") + " | take " + style.Green.Render("(o)nline") + " )"
}
}
func (m ViewModel) View() string {
if m.ActiveKey == nil {
if m.Participation == nil {
return "No key selected"
}
account := style.Cyan.Render("Account: ") + m.ActiveKey.Address
id := style.Cyan.Render("Participation ID: ") + m.ActiveKey.Id
selection := style.Yellow.Render("Selection Key: ") + *utils.UrlEncodeBytesPtrOrNil(m.ActiveKey.Key.SelectionParticipationKey[:])
vote := style.Yellow.Render("Vote Key: ") + *utils.UrlEncodeBytesPtrOrNil(m.ActiveKey.Key.VoteParticipationKey[:])
stateProof := style.Yellow.Render("State Proof Key: ") + *utils.UrlEncodeBytesPtrOrNil(*m.ActiveKey.Key.StateProofKey)
voteFirstValid := style.Purple("Vote First Valid: ") + utils.IntToStr(m.ActiveKey.Key.VoteFirstValid)
voteLastValid := style.Purple("Vote Last Valid: ") + utils.IntToStr(m.ActiveKey.Key.VoteLastValid)
voteKeyDilution := style.Purple("Vote Key Dilution: ") + utils.IntToStr(m.ActiveKey.Key.VoteKeyDilution)
account := style.Cyan.Render("Account: ") + m.Participation.Address
id := style.Cyan.Render("Participation ID: ") + m.Participation.Id
selection := style.Yellow.Render("Selection Key: ") + *utils.UrlEncodeBytesPtrOrNil(m.Participation.Key.SelectionParticipationKey[:])
vote := style.Yellow.Render("Vote Key: ") + *utils.UrlEncodeBytesPtrOrNil(m.Participation.Key.VoteParticipationKey[:])
stateProof := style.Yellow.Render("State Proof Key: ") + *utils.UrlEncodeBytesPtrOrNil(*m.Participation.Key.StateProofKey)
voteFirstValid := style.Purple("Vote First Valid: ") + utils.IntToStr(m.Participation.Key.VoteFirstValid)
voteLastValid := style.Purple("Vote Last Valid: ") + utils.IntToStr(m.Participation.Key.VoteLastValid)
voteKeyDilution := style.Purple("Vote Key Dilution: ") + utils.IntToStr(m.Participation.Key.VoteKeyDilution)

return ansi.Hardwrap(lipgloss.JoinVertical(lipgloss.Left,
account,
Expand Down
19 changes: 9 additions & 10 deletions ui/modals/transaction/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,27 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (*ViewModel, tea.Cmd) {
}

func (m *ViewModel) UpdateState() {
if m.ActiveKey == nil {
if m.Participation == nil {
return
}
accountStatus := m.State.Accounts[m.ActiveKey.Address].Status

if m.ATxn == nil {
m.ATxn = &encoder.AUrlTxn{}
}
fee := uint64(1000)
m.ATxn.AUrlTxnKeyCommon.Sender = m.ActiveKey.Address
m.ATxn.AUrlTxnKeyCommon.Sender = m.Participation.Address
m.ATxn.AUrlTxnKeyCommon.Type = string(types.KeyRegistrationTx)
m.ATxn.AUrlTxnKeyCommon.Fee = &fee

if accountStatus != "Online" {
if !m.Active {
m.Title = string(OnlineTitle)
m.BorderColor = "2"
votePartKey := base64.RawURLEncoding.EncodeToString(m.ActiveKey.Key.VoteParticipationKey)
selPartKey := base64.RawURLEncoding.EncodeToString(m.ActiveKey.Key.SelectionParticipationKey)
spKey := base64.RawURLEncoding.EncodeToString(*m.ActiveKey.Key.StateProofKey)
firstValid := uint64(m.ActiveKey.Key.VoteFirstValid)
lastValid := uint64(m.ActiveKey.Key.VoteLastValid)
vkDilution := uint64(m.ActiveKey.Key.VoteKeyDilution)
votePartKey := base64.RawURLEncoding.EncodeToString(m.Participation.Key.VoteParticipationKey)
selPartKey := base64.RawURLEncoding.EncodeToString(m.Participation.Key.SelectionParticipationKey)
spKey := base64.RawURLEncoding.EncodeToString(*m.Participation.Key.StateProofKey)
firstValid := uint64(m.Participation.Key.VoteFirstValid)
lastValid := uint64(m.Participation.Key.VoteLastValid)
vkDilution := uint64(m.Participation.Key.VoteKeyDilution)

m.ATxn.AUrlTxnKeyreg.VotePK = &votePartKey
m.ATxn.AUrlTxnKeyreg.SelectionPK = &selPartKey
Expand Down
5 changes: 3 additions & 2 deletions ui/modals/transaction/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ type ViewModel struct {
Title string

// Active Participation Key
ActiveKey *api.ParticipationKey
Participation *api.ParticipationKey
Active bool

// Pointer to the State
State *internal.StateModel
Expand All @@ -33,7 +34,7 @@ type ViewModel struct {
}

func (m ViewModel) FormatedAddress() string {
return fmt.Sprintf("%s...%s", m.ActiveKey.Address[0:4], m.ActiveKey.Address[len(m.ActiveKey.Address)-4:])
return fmt.Sprintf("%s...%s", m.Participation.Address[0:4], m.Participation.Address[len(m.Participation.Address)-4:])
}

// New creates and instance of the ViewModel with a default controls.Model
Expand Down
2 changes: 1 addition & 1 deletion ui/modals/transaction/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func (m ViewModel) View() string {
if m.ActiveKey == nil {
if m.Participation == nil {
return "No key selected"
}
if m.ATxn == nil {
Expand Down
2 changes: 1 addition & 1 deletion ui/pages/accounts/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (m ViewModel) SelectedAccount() internal.Account {
return account
}
func (m ViewModel) makeColumns(width int) []table.Column {
avgWidth := (width - lipgloss.Width(style.Border.Render("")) - 14) / 5
avgWidth := (width - lipgloss.Width(style.Border.Render("")) - 9) / 5
return []table.Column{
{Title: "Account", Width: avgWidth},
{Title: "Keys", Width: avgWidth},
Expand Down
4 changes: 3 additions & 1 deletion ui/pages/keys/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (ViewModel, tea.Cmd) {
// When the Account is Selected
case app.AccountSelected:
m.Address = msg.Address
m.Participation = msg.Participation
m.table.SetRows(m.makeRows(m.Data))
// When a confirmation Modal is finished deleting
case app.DeleteFinished:
Expand All @@ -44,11 +45,12 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (ViewModel, tea.Cmd) {
return m, app.EmitShowPage(app.AccountsPage)
// Show the Info Modal
case "enter":
selKey := m.SelectedKey()
selKey, active := m.SelectedKey()
if selKey != nil {
// Show the Info Modal with the selected Key
return m, app.EmitModalEvent(app.ModalEvent{
Key: selKey,
Active: active,
Address: selKey.Address,
Type: app.InfoModal,
})
Expand Down
25 changes: 21 additions & 4 deletions ui/pages/keys/model.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keys

import (
"github.com/algorandfoundation/hack-tui/internal"
"sort"

"github.com/algorandfoundation/hack-tui/ui/style"
Expand All @@ -15,6 +16,9 @@ import (
type ViewModel struct {
// Address for or the filter condition in ViewModel.
Address string
// Participation represents the consensus protocol parameters used by this account.
Participation *api.AccountParticipation

// Data holds a pointer to a slice of ParticipationKey, representing the set of participation keys managed by the ViewModel.
Data *[]api.ParticipationKey

Expand Down Expand Up @@ -79,29 +83,32 @@ func New(address string, keys *[]api.ParticipationKey) ViewModel {
}

// SelectedKey returns the currently selected participation key from the ViewModel's data set, or nil if no key is selected.
func (m ViewModel) SelectedKey() *api.ParticipationKey {
func (m ViewModel) SelectedKey() (*api.ParticipationKey, bool) {
if m.Data == nil {
return nil
return nil, false
}
var partkey *api.ParticipationKey
var active bool
selected := m.table.SelectedRow()
for _, key := range *m.Data {
if len(selected) > 0 && key.Id == selected[0] {
partkey = &key
active = selected[2] == "YES"
}
}
return partkey
return partkey, active
}

// makeColumns generates a set of table columns suitable for displaying participation key data, based on the given `width`.
func (m ViewModel) makeColumns(width int) []table.Column {
// TODO: refine responsiveness
avgWidth := (width - lipgloss.Width(style.Border.Render("")) - 14) / 4
avgWidth := (width - lipgloss.Width(style.Border.Render("")) - 9) / 5

//avgWidth := 1
return []table.Column{
{Title: "ID", Width: avgWidth},
{Title: "Address", Width: avgWidth},
{Title: "Active", Width: avgWidth},
{Title: "Last Vote", Width: avgWidth},
{Title: "Last Block Proposal", Width: avgWidth},
}
Expand All @@ -114,11 +121,21 @@ func (m ViewModel) makeRows(keys *[]api.ParticipationKey) []table.Row {
if keys == nil || m.Address == "" {
return rows
}

var activeId *string
if m.Participation != nil {
activeId = internal.FindParticipationIdForVoteKey(keys, m.Participation.VoteParticipationKey)
}
for _, key := range *keys {
if key.Address == m.Address {
isActive := "NA"
if activeId != nil && *activeId == key.Id {
isActive = "YES"
}
rows = append(rows, table.Row{
key.Id,
key.Address,
isActive,
utils.StrOrNA(key.LastVote),
utils.StrOrNA(key.LastBlockProposal),
})
Expand Down
2 changes: 0 additions & 2 deletions ui/style/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ var (
ApplyBorder = func(width int, height int, color string) lipgloss.Style {
return Border.
Width(width).
Padding(0).
Margin(0).
Height(height).
BorderForeground(lipgloss.Color(color))
}
Expand Down
4 changes: 0 additions & 4 deletions ui/viewport.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ func (m ViewportViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.page = msg
// When the state updates
case internal.StateModel:
if m.errorMsg != nil {
m.errorMsg = nil
m.page = app.AccountsPage
}
m.Data = &msg
case tea.KeyMsg:
switch msg.String() {
Expand Down

0 comments on commit 723007d

Please sign in to comment.