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

wallet friendly names #317

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
5 changes: 5 additions & 0 deletions harmony/harmonydb/sql/20241105-walletnames.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE wallet_names (
id SERIAL PRIMARY KEY,
wallet_id VARCHAR NOT NULL,
name VARCHAR(60) NOT NULL
);
110 changes: 110 additions & 0 deletions web/api/webrpc/wallet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package webrpc

import (
"context"
"fmt"
"strings"
"sync"

"golang.org/x/exp/maps"

"github.com/filecoin-project/go-address"

"github.com/filecoin-project/curio/deps/config"
"github.com/filecoin-project/curio/harmony/harmonydb"
)

var walletOnce sync.Once

func (a *WebRPC) WalletName(ctx context.Context, id string) (string, error) {
walletOnce.Do(func() {
populateWalletFriendlyNames(a.deps.Cfg.Addresses, a.deps.DB)
})
walletFriendlyNamesLock.Lock()
defer walletFriendlyNamesLock.Unlock()
return walletFriendlyNames[id], nil
}

func (a *WebRPC) WalletNameChange(ctx context.Context, id string, newName string) error {
_, err := a.deps.DB.Exec(ctx, `UDPATE wallet_names SET name = $1 WHERE id = $2`, newName, id)
if err != nil {
log.Errorf("failed to set wallet name for %s: %s", id, err)
return err
}
walletFriendlyNamesLock.Lock()
defer walletFriendlyNamesLock.Unlock()
walletFriendlyNames[id] = newName
return nil
}

var walletFriendlyNames = map[string]string{}
var walletFriendlyNamesLock sync.Mutex

func populateWalletFriendlyNames(addrGrps []config.CurioAddresses, db *harmonydb.DB) {
type nx struct {
purposes map[string]bool
miner map[string]bool
}
ex := map[string]*nx{}
all_purposes := []string{"PC_Ctrl", "Cmt_Ctrl", "Term_Ctrl"}
allMiners := map[string]bool{}
for _, addrList := range addrGrps {
n := map[string][]string{
"PC_Ctrl": addrList.PreCommitControl,
"Cmt_Ctrl": addrList.CommitControl,
"Term_Ctrl": addrList.TerminateControl,
}
for name, addrs := range n {
for i, addr := range addrs {
a, err := address.NewFromString(addr)
if err != nil {
continue
}
allMiners[a.String()] = true

if len(addrs) > 1 {
name = fmt.Sprintf("%s%d", name, i)
}
if res, ok := ex[a.String()]; ok {
res.purposes[name] = true
for _, miner := range addrList.MinerAddresses {
res.miner[miner] = true
}
} else {
var t = map[string]bool{}
for _, miner := range addrList.MinerAddresses {
t[miner] = true
}
ex[a.String()] = &nx{purposes: map[string]bool{name: true}, miner: t}
}
}
}
}
for addr, nx := range ex {
purpose := ""
miner := ""
if len(nx.purposes) != len(all_purposes) { // impossible to be 0
purpose = strings.Join(maps.Keys(nx.purposes), ",")
}
if len(nx.miner) != len(allMiners) { // impoossible to be 0
miner = "_" + strings.Join(maps.Keys(nx.miner), ",")
}
if purpose == "" && miner == "" {
purpose = "all"
}
walletFriendlyNames[addr] = purpose + miner + "Wallet"
}

var idNames []struct {
ID string
Name string
}
err := db.Select(context.Background(), &idNames, `SELECT wallet_id as ID, name FROM wallet_names`)
if err != nil {
log.Errorf("failed to get wallet names: %s", err)
return
}
for _, idName := range idNames {
walletFriendlyNames[idName.ID] = idName.Name
}
}
3 changes: 2 additions & 1 deletion web/static/actor-summary.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js';
import RPCCall from '/lib/jsonrpc.mjs';
import '/lib/cu-wallet.mjs';

class Expirations extends LitElement {
static properties = {
Expand Down Expand Up @@ -249,7 +250,7 @@ class ActorSummary extends LitElement {
<tbody>
${this.data.map(entry => html`
<tr>
<td>${entry.Address}</td>
<td><cu-wallet .wallet_id="${entry.Address}" /></td>
<td>
${entry.CLayers.map(layer => html`<span>${layer} </span>`)}
</td>
Expand Down
63 changes: 63 additions & 0 deletions web/static/lib/cu-wallet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { LitElement, html } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js';
import RPCCall from '/lib/jsonrpc.mjs';

class CuWallet extends LitElement {
static get properties() {
return {
wallet_id: { type: String }
};
}

constructor() {
super();
this.wallet_id = '';
}

updated(changedProperties) {
if (changedProperties.has('wallet_id') && this.wallet_id) {
this.handleIdUpdate();
}
}

async handleIdUpdate() {
this.isProcessing = true;
try {
this.name = await RPCCall('WalletName', [this.wallet_id]);
} catch (error) {
console.error('Error during WalletName operation:', error);
} finally {
this.isProcessing = false;
}
this.requestUpdate();
}

nice_id() {
if (!this.wallet_id) return 'Not Set';
if (this.name != '') return this.name;
return this.wallet_id.substring(0, 6) + '...' + this.wallet_id.substring(this.wallet_id.length - 6);
}
static styles = css`
@keyframes spinner {
to {transform: rotate(360deg);}
}
.spinner:before {
content: url('/favicon.svg');
display: inline-block;
animation: spinner 1s linear infinite;
height: 12px;
}
`;

render() {
return html`
<div class="wallet">
${this.isProcessing ? html`<span class="spinner"></span>` : ''}
<a href="/pages/wallet/?id=${this.wallet_id}">📛</a>
<span>${this.nice_id()}</span>
<a @click=${() => navigator.clipboard.writeText(this.wallet_id)}>📋</a>
</div>
`;
}
}

customElements.define('cu-wallet', CuWallet);
19 changes: 19 additions & 0 deletions web/static/pages/wallet/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wallet</title>
<script type="module" src="./wallet.mjs"></script>
<script type="module" src="/ux/curio-ux.mjs"></script>
<body style="visibility:hidden" data-bs-theme="dark">
<curio-ux>
<wallet-component></wallet-component>
</curio-ux>
<script>
const walletComponent = document.querySelector('wallet-component');
walletComponent.wallet_id = new URLSearchParams(window.location.search).get('id');
document.body.appendChild(walletComponent);
</script>
</body>
</html>
81 changes: 81 additions & 0 deletions web/static/pages/wallet/wallet.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { LitElement, html } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js';
import RPCCall from '/lib/jsonrpc.mjs';

class CuWallet extends LitElement {
static get properties() {
return {
wallet_id: { type: String }
};
}

constructor() {
super();
this.wallet_id = '';
}

updated(changedProperties) {
if (changedProperties.has('wallet_id') && this.wallet_id) {
this.handleIdUpdate();
}
}

async handleIdUpdate() {
this.isProcessing = true;
try {
let res = await RPCCall('WalletName', [this.wallet_id]); // TODO API call
this.name = res.name;
this.requestUpdate();
} catch (error) {
console.error('Error during WalletName operation:', error);
} finally {
this.isProcessing = false;
}
}

nice_id() {
if (!this.wallet_id) return 'Not Set';
if (this.name != '') return this.name;
return this.wallet_id.substring(0, 6) + '...' + this.wallet_id.substring(this.wallet_id.length - 6);
}
async handleNameChange(new_nice_id) {
this.isProcessing = true;
this.name = new_nice_id;
this.requestUpdate();
try {
await RPCCall('WalletNameChange', [this.wallet_id, new_nice_id]); // TODO API call
} catch (error) {
console.error('Error during WalletName operation:', error);
} finally {
this.isProcessing = false;
}
this.requestUpdate();
}
static styles = css`
@keyframes spinner {
to {transform: rotate(360deg);}
}
.spinner:before {
content: url('/favicon.svg');
display: inline-block;
animation: spinner 1s linear infinite;
height: 12px;
}
`;

render() {
return html`
<div class="wallet">
${this.isProcessing ? html`<span class="spinner"></span>` : ''}
Full ID: <span>${this.wallet_id}</span><br>
<form @submit=${(e) => {
e.preventDefault()
this.handleNameChange(e.target.walletName.value)
}}>
<input type="text" name="walletName" .value=${this.nice_id()}>
<button type="submit">Change Name</button>
</form>
</div> `;
}
}

customElements.define('wallet-component', CuWallet);