diff --git a/src/components/addresses/picker/AddressHint.tsx b/src/components/addresses/picker/AddressHint.tsx index ef8a312..efb19a6 100644 --- a/src/components/addresses/picker/AddressHint.tsx +++ b/src/components/addresses/picker/AddressHint.tsx @@ -5,14 +5,16 @@ import { TenebraAddressWithNames } from "@api/lookup"; import { TenebraValue } from "@comp/tenebra/TenebraValue"; +import { Wallet } from "@wallets"; interface Props { address?: TenebraAddressWithNames; nameHint?: boolean; stake?: number; + wallet?: Wallet; } -export function AddressHint({ address, nameHint, stake }: Props): JSX.Element { +export function AddressHint({ address, nameHint, stake, wallet}: Props): JSX.Element { const { t } = useTranslation(); return @@ -28,7 +30,7 @@ // Otherwise, show the balance ( - Balance: Stake: + Balance: Stake: ) : diff --git a/src/components/addresses/picker/PickerHints.tsx b/src/components/addresses/picker/PickerHints.tsx index f3573be..f85b223 100644 --- a/src/components/addresses/picker/PickerHints.tsx +++ b/src/components/addresses/picker/PickerHints.tsx @@ -198,7 +198,7 @@ {/* Show an address hint if possible */} {showAddressHint && ( - + )} {/* Show a name hint if possible */} diff --git a/src/components/transactions/AmountInput.tsx b/src/components/transactions/AmountInput.tsx index 7d6ca63..597e62b 100644 --- a/src/components/transactions/AmountInput.tsx +++ b/src/components/transactions/AmountInput.tsx @@ -53,6 +53,7 @@ function onClickMax() { if (!from) return; const currentWallet = walletAddressMap[from]; + console.log(currentWallet, stakingFormValues); if (!stakingFormValues || (stakingFormValues.action && stakingFormValues.action === "deposit")) { setAmount(currentWallet?.balance || 0); } else if (stakingFormValues.action && stakingFormValues.action === "withdraw") { diff --git a/src/global/ws/WebsocketConnection.ts b/src/global/ws/WebsocketConnection.ts index 682106b..1996966 100644 --- a/src/global/ws/WebsocketConnection.ts +++ b/src/global/ws/WebsocketConnection.ts @@ -10,7 +10,7 @@ import * as api from "@api"; import { TenebraAddress, TenebraBlock, TenebraTransaction, WSConnectionState, WSIncomingMessage, WSSubscriptionLevel } from "@api/types"; -import { Wallet, WalletMap, findWalletByAddress, syncWalletUpdate } from "@wallets"; +import { Wallet, WalletMap, findWalletByAddress, syncWalletUpdate, syncWalletStakeUpdate } from "@wallets"; import WebSocketAsPromised from "websocket-as-promised"; import { WSSubscription } from "./WebsocketSubscription"; @@ -34,7 +34,7 @@ private forceClosing = false; - private refreshThrottles: Record void> = {}; + private refreshThrottles: Record void> = {}; private subscriptions: Record = {}; @@ -130,6 +130,8 @@ this.subscribe("blocks"); this.subscribe("names"); this.subscribe("motd"); + this.subscribe("validators"); + this.subscribe("stakes"); this.setConnectionState("connected"); } else if (data.address && this.wallets) { @@ -139,7 +141,7 @@ if (!wallet) return; debug("syncing %s to %s (balance: %d)", address.address, wallet.id, address.balance); - syncWalletUpdate(wallet, address); + syncWalletUpdate(wallet, address, data.stake ?? undefined); } else if (data.type === "event" && data.event && this.wallets) { // Handle events switch (data.event) { @@ -162,6 +164,11 @@ if (toWallet) this.refreshBalance(toWallet.address, true); break; + case "staking": + if (fromWallet) this.refreshBalance(fromWallet.address, false, true); + if (toWallet) this.refreshBalance(toWallet.address, false, true); + break; + // Any other transaction; refresh the balances via the websocket default: if (fromWallet) this.refreshBalance(fromWallet.address); @@ -181,6 +188,13 @@ break; } + case "stake": { + const wallet = findWalletByAddress(this.wallets, data.stake.owner); + if (!wallet) return; + + syncWalletStakeUpdate(wallet, data.stake ?? undefined); + break; + } } } } @@ -244,10 +258,10 @@ /** Queues a command to re-fetch an address's balance. The response will be * handled in {@link handleMessage}. This is automatically throttled to * execute on the leading edge of 500ms (REFRESH_THROTTLE_MS). */ - refreshBalance(address: string, fetchNames?: boolean): void { + refreshBalance(address: string, fetchNames?: boolean, fetchStake?: boolean): void { if (this.refreshThrottles[address]) { // Use the existing throttled function if it exists - this.refreshThrottles[address](address); + this.refreshThrottles[address](address, fetchNames, fetchStake); } else { // Create and cache a new throttle function for this address const throttled = throttle( @@ -257,17 +271,18 @@ ); this.refreshThrottles[address] = throttled; - throttled(address, fetchNames); + throttled(address, fetchNames, fetchStake); } } - private _refreshBalance(address: string, fetchNames?: boolean) { + private _refreshBalance(address: string, fetchNames?: boolean, fetchStake?: boolean) { debug("refreshing balance of %s", address); this.ws?.sendPacked({ type: "address", id: this.messageID++, address, - fetchNames + fetchNames, + fetchStake }); } diff --git a/src/tenebra/api/types.ts b/src/tenebra/api/types.ts index 806504e..19979b2 100644 --- a/src/tenebra/api/types.ts +++ b/src/tenebra/api/types.ts @@ -155,8 +155,8 @@ } export type WSConnectionState = "connected" | "disconnected" | "connecting"; -export type WSSubscriptionLevel = "blocks" | "ownBlocks" | "transactions" | "ownTransactions" | "names" | "ownNames" | "motd"; -export type WSEvent = "block" | "transaction" | "name" | "motd"; +export type WSSubscriptionLevel = "blocks" | "ownBlocks" | "transactions" | "ownTransactions" | "names" | "ownNames" | "motd" | "validators" | "ownValidators" | "stakes" | "ownStakes"; +export type WSEvent = "block" | "transaction" | "name" | "motd" | "validator" | "stake"; export interface WSIncomingMessage { id?: number; ok?: boolean; diff --git a/src/tenebra/wallets/Wallet.ts b/src/tenebra/wallets/Wallet.ts index 53ef9a0..7efc4b5 100644 --- a/src/tenebra/wallets/Wallet.ts +++ b/src/tenebra/wallets/Wallet.ts @@ -36,14 +36,14 @@ /** Properties of Wallet that are allowed to be updated. */ export type WalletUpdatableKeys - = "label" | "category" | "encPassword" | "encPrivatekey" | "username" | "format" | "address"; + = "label" | "category" | "encPassword" | "encPrivatekey" | "username" | "format" | "address" | "stake"; export const WALLET_UPDATABLE_KEYS: WalletUpdatableKeys[] - = ["label", "category", "encPassword", "encPrivatekey", "username", "format", "address"]; + = ["label", "category", "encPassword", "encPrivatekey", "username", "format", "address", "stake"]; export type WalletUpdatable = Pick; /** Properties of Wallet that are allowed to be synced. */ export type WalletSyncableKeys - = "balance" | "names" | "firstSeen" | "lastSynced"; + = "balance" | "names" | "firstSeen" | "lastSynced" | "stake"; export const WALLET_SYNCABLE_KEYS: WalletSyncableKeys[] - = ["balance", "names", "firstSeen", "lastSynced"]; + = ["balance", "names", "firstSeen", "lastSynced", "stake"]; export type WalletSyncable = Pick; diff --git a/src/tenebra/wallets/functions/syncWallets.ts b/src/tenebra/wallets/functions/syncWallets.ts index dfceb91..a678ff7 100644 --- a/src/tenebra/wallets/functions/syncWallets.ts +++ b/src/tenebra/wallets/functions/syncWallets.ts @@ -39,6 +39,18 @@ } } +function syncWalletStakeProperties( + wallet: Wallet, + stake: TenebraStake | null, + syncTime: Date +): Wallet { + return { + ...wallet, + lastSynced: syncTime.toISOString(), + stake: stake ? stake.stake : wallet.stake + }; +} + /** Sync the data for a single wallet from the sync node, save it to local * storage, and dispatch the change to the Redux store. */ export async function syncWallet( @@ -80,6 +92,22 @@ } } +/** Given an already synced wallet, save it to local storage, and dispatch the + * change to the Redux store. */ +export function syncWalletStakeUpdate( + wallet: Wallet, + stake?: TenebraStake | null, + dontSave?: boolean +): void { + const syncTime = new Date(); + const updatedWallet = syncWalletStakeProperties(wallet, stake ?? null, syncTime); + + // Save the wallet to local storage, unless this was an external sync action + if (!dontSave) saveWallet(updatedWallet); + console.log("New wallet ", updatedWallet); + store.dispatch(actions.syncWallet(wallet.id, updatedWallet)); +} + /** Sync the data for all the wallets from the sync node, save it to local * storage, and dispatch the changes to the Redux store. */ export async function syncWallets(): Promise {