diff --git a/public/locales/en.json b/public/locales/en.json
index 207f790..c4b8d14 100644
--- a/public/locales/en.json
+++ b/public/locales/en.json
@@ -995,6 +995,7 @@
"categoryExactName": "Exact name",
"addressHint": "Balance: <1 />",
+ "addressHintWithStake": "Balance: <1 /> Stake: <3 />",
"addressHintWithNames": "Names: <1>{{names, number}}1>",
"nameHint": "Owner: <1 />",
"nameHintNotFound": "Name not found.",
diff --git a/src/components/addresses/picker/AddressHint.tsx b/src/components/addresses/picker/AddressHint.tsx
index 6c4d0e1..ef8a312 100644
--- a/src/components/addresses/picker/AddressHint.tsx
+++ b/src/components/addresses/picker/AddressHint.tsx
@@ -9,9 +9,10 @@
interface Props {
address?: TenebraAddressWithNames;
nameHint?: boolean;
+ stake?: number;
}
-export function AddressHint({ address, nameHint }: Props): JSX.Element {
+export function AddressHint({ address, nameHint, stake }: Props): JSX.Element {
const { t } = useTranslation();
return
@@ -23,10 +24,19 @@
)
: (
- // Otherwise, show the balance
-
- Balance:
-
+ stake ?
+ // Otherwise, show the balance
+ (
+
+ Balance: Stake:
+
+ )
+ :
+ (
+
+ Balance:
+
+ )
)
}
;
diff --git a/src/components/addresses/picker/AddressPicker.tsx b/src/components/addresses/picker/AddressPicker.tsx
index c4abdac..ad196fc 100644
--- a/src/components/addresses/picker/AddressPicker.tsx
+++ b/src/components/addresses/picker/AddressPicker.tsx
@@ -34,6 +34,7 @@
otherPickerValue?: string;
walletsOnly?: boolean;
+ showStake?: boolean;
noWallets?: boolean;
noNames?: boolean;
nameHint?: boolean;
@@ -57,6 +58,7 @@
otherPickerValue,
walletsOnly,
+ showStake,
noWallets,
noNames,
nameHint,
@@ -129,7 +131,7 @@
// Fetch an address or name hint if possible
const { pickerHints, foundName } = usePickerHints(
- nameHint, cleanValue, hasExactName, suppressUpdates
+ nameHint, cleanValue, hasExactName, suppressUpdates, showStake
);
// Re-validate this field if the picker hints foundName changed
diff --git a/src/components/addresses/picker/PickerHints.tsx b/src/components/addresses/picker/PickerHints.tsx
index b8bee93..f3573be 100644
--- a/src/components/addresses/picker/PickerHints.tsx
+++ b/src/components/addresses/picker/PickerHints.tsx
@@ -10,7 +10,7 @@
import { useWallets } from "@wallets";
import * as api from "@api";
-import { TenebraAddressWithNames, lookupAddress } from "@api/lookup";
+import { TenebraAddressWithNames, lookupAddress, lookupStakes } from "@api/lookup";
import { TenebraName } from "@api/types";
import { WalletHint } from "./WalletHint";
@@ -38,7 +38,8 @@
nameHint?: boolean,
value?: string,
hasExactName?: boolean,
- suppressUpdates?: boolean
+ suppressUpdates?: boolean,
+ showStake?: boolean
): PickerHintsRes {
// Used for clean-up
const isMounted = useRef(true);
@@ -48,6 +49,7 @@
// Handle showing an address or name hint if the value is valid
const [foundAddress, setFoundAddress] = useState();
+ const [foundStake, setFoundStake] = useState();
const [foundName, setFoundName] = useState();
// To auto-refresh address balances, we need to subscribe to the address.
@@ -69,7 +71,8 @@
value: string,
hasAddress?: boolean,
hasName?: boolean,
- nameHint?: boolean
+ nameHint?: boolean,
+ showStake?: boolean
) => {
// Skip doing anything when unmounted to avoid illegal state updates
if (!isMounted.current) return debug("unmounted skipped lookupHint");
@@ -87,10 +90,16 @@
if (!isMounted.current)
return debug("unmounted skipped lookupHint hasAddress try");
setFoundAddress(address);
+ if (showStake) {
+ const lookupStakeResults = await lookupStakes([value]);
+ const tenebraStake = lookupStakeResults[value];
+ setFoundStake(tenebraStake?.stake);
+ }
} catch (ignored) {
if (!isMounted.current)
return debug("unmounted skipped lookupHint hasAddress catch");
setFoundAddress(false);
+ setFoundStake(false);
}
} else if (hasName) {
// Lookup a name
@@ -122,6 +131,7 @@
if (!value) {
setFoundAddress(undefined);
setFoundName(undefined);
+ setFoundStake(undefined);
setValidAddress(undefined);
return;
}
@@ -143,11 +153,8 @@
}
// Perform the lookup (debounced)
- lookupHint(nameSuffix, value, hasValidAddress, hasExactName, nameHint);
- }, [
- lookupHint, nameSuffix, value, addressPrefix, hasExactName, nameHint,
- validAddress, lastTransactionID, joinedAddressList, suppressUpdates
- ]);
+ lookupHint(nameSuffix, value, hasValidAddress, hasExactName, nameHint, showStake);
+ }, [lookupHint, nameSuffix, value, addressPrefix, hasExactName, nameHint, validAddress, lastTransactionID, joinedAddressList, suppressUpdates, showStake]);
// Clean up the debounced function when unmounting
useEffect(() => {
@@ -191,7 +198,7 @@
{/* Show an address hint if possible */}
{showAddressHint && (
-
+
)}
{/* Show a name hint if possible */}
diff --git a/src/components/tenebra/TenebraValue.tsx b/src/components/tenebra/TenebraValue.tsx
index bb72425..cf4e59b 100644
--- a/src/components/tenebra/TenebraValue.tsx
+++ b/src/components/tenebra/TenebraValue.tsx
@@ -41,9 +41,9 @@
return (
- {icon || ((currencySymbol || "KST") === "KST" && )}
+ {icon || ((currencySymbol || "TST") === "TST" && )}
{(value || 0).toLocaleString()}
- {long && {currencySymbol || "KST"}}
+ {long && {currencySymbol || "TST"}}
);
};
diff --git a/src/components/transactions/AmountInput.tsx b/src/components/transactions/AmountInput.tsx
index 0fe1012..7d6ca63 100644
--- a/src/components/transactions/AmountInput.tsx
+++ b/src/components/transactions/AmountInput.tsx
@@ -10,6 +10,13 @@
import { useCurrency } from "@utils/tenebra";
import { TenebraSymbol } from "@comp/tenebra/TenebraSymbol";
+import { StakingActionType } from "@pages/staking/StakingForm";
+
+export interface StakingFormValues {
+ from: string;
+ action: StakingActionType;
+ amount: number;
+}
interface Props {
from?: string;
@@ -18,6 +25,7 @@
label?: ReactNode;
required?: boolean;
disabled?: boolean;
+ stakingFormValues?: Partial;
tabIndex?: number;
}
@@ -29,6 +37,7 @@
label,
required,
disabled,
+ stakingFormValues,
tabIndex,
...props
@@ -44,7 +53,11 @@
function onClickMax() {
if (!from) return;
const currentWallet = walletAddressMap[from];
- setAmount(currentWallet?.balance || 0);
+ if (!stakingFormValues || (stakingFormValues.action && stakingFormValues.action === "deposit")) {
+ setAmount(currentWallet?.balance || 0);
+ } else if (stakingFormValues.action && stakingFormValues.action === "withdraw") {
+ setAmount(currentWallet?.stake || 0);
+ }
}
const amountRequired = required === undefined || !!required;
@@ -57,7 +70,7 @@
{/* Prepend the Tenebra symbol if possible. Note that ant's InputNumber
* doesn't support addons, so this has to be done manually. */}
- {(currency_symbol || "KST") === "KST" && (
+ {(currency_symbol || "TST") === "TST" && (
@@ -91,8 +104,13 @@
const currentWallet = walletAddressMap[from];
if (!currentWallet) return;
- if (value > (currentWallet.balance || 0))
- throw t("sendTransaction.errorAmountTooHigh");
+ if (!stakingFormValues || (stakingFormValues.action && stakingFormValues.action === "deposit")) {
+ if (value > (currentWallet.balance || 0))
+ throw t("sendTransaction.errorAmountTooHigh");
+ } else if (stakingFormValues.action && stakingFormValues.action === "withdraw") {
+ if (value > (currentWallet.stake || 0))
+ throw t("sendTransaction.errorAmountTooHigh");
+ }
}
},
]}
@@ -108,7 +126,7 @@
{/* Currency suffix */}
- {currency_symbol || "KST"}
+ {currency_symbol || "TST"}
{/* Max value button */}
diff --git a/src/pages/staking/StakingForm.tsx b/src/pages/staking/StakingForm.tsx
index 994236d..bec8c12 100644
--- a/src/pages/staking/StakingForm.tsx
+++ b/src/pages/staking/StakingForm.tsx
@@ -25,7 +25,7 @@
import { useAuthFailedModal } from "@api/AuthFailed";
import { AddressPicker } from "@comp/addresses/picker/AddressPicker";
-import { AmountInput } from "@comp/transactions/AmountInput";
+import { AmountInput, StakingFormValues } from "@comp/transactions/AmountInput";
import { StakingConfirmModalContents } from "./StakingConfirmModal";
import awaitTo from "await-to-js";
@@ -39,16 +39,10 @@
export type StakingActionType = "deposit" | "withdraw";
-export interface FormValues {
- from: string;
- action: StakingActionType;
- amount: number;
-}
-
interface Props {
from?: Wallet | string;
amount?: number;
- form: FormInstance;
+ form: FormInstance;
triggerSubmit: () => Promise;
}
@@ -79,6 +73,8 @@
const [from, setFrom] = useState(initialFrom);
+ const [formValues, setFormValues] = useState>();
+
// Focus the 'to' input on initial render
const toRef = useRef(null);
useMountEffect(() => {
@@ -86,10 +82,11 @@
});
function onValuesChange(
- changed: Partial,
- values: Partial
+ changed: Partial,
+ values: Partial
) {
setFrom(values.from || "");
+ setFormValues(values);
// Update and save the lastTxFrom so the next time the modal is opened
// it will remain on this address
@@ -103,7 +100,7 @@
}
}
- const initialValues: FormValues = useMemo(() => ({
+ const initialValues: StakingFormValues = useMemo(() => ({
from: initialFrom,
amount: initialAmount || 1,
action: "deposit"
@@ -117,6 +114,7 @@
// If the initial values change, refresh the form
useEffect(() => {
form?.setFieldsValue(initialValues);
+ setFormValues(initialValues);
}, [form, initialValues]);
return ;
@@ -179,7 +179,7 @@
}
interface StakingFormHookResponse {
- form: FormInstance;
+ form: FormInstance;
triggerSubmit: () => Promise;
triggerReset: () => void;
isSubmitting: boolean;
@@ -195,7 +195,7 @@
}: StakingFormHookProps = {}): StakingFormHookResponse {
const { t } = useTranslation();
- const [form] = Form.useForm();
+ const [form] = Form.useForm();
const [isSubmitting, setIsSubmitting] = useState(false);
// Used to check for warning on large transactions
@@ -222,7 +222,7 @@
// Take the form values and known wallet and submit the transaction
async function submitStakingTransaction(
- { amount, action }: FormValues,
+ { amount, action }: StakingFormValues,
wallet: Wallet
): Promise {
// Manually get the master password from the store state, because this might
diff --git a/src/pages/staking/StakingPage.less b/src/pages/staking/StakingPage.less
index e8eae64..c8de286 100644
--- a/src/pages/staking/StakingPage.less
+++ b/src/pages/staking/StakingPage.less
@@ -1,7 +1,7 @@
// Copyright (c) 2020-2021 Drew Lemmy
// This file is part of TenebraWeb 2 under AGPL-3.0.
// Full details: https://github.com/tmpim/TenebraWeb2/blob/master/LICENSE.txt
-@import (reference) "../../../App.less";
+@import (reference) "../../App.less";
.staking-page {
.staking-container {
@@ -10,7 +10,7 @@
margin: 0 auto;
width: 100%;
- max-width: 768px;
+ max-width: 1024px;
background: @kw-light;
border-radius: @kw-big-card-border-radius;
diff --git a/src/tenebra/api/lookup.ts b/src/tenebra/api/lookup.ts
index e68bb04..4262b7e 100644
--- a/src/tenebra/api/lookup.ts
+++ b/src/tenebra/api/lookup.ts
@@ -1,7 +1,7 @@
// Copyright (c) 2020-2021 Drew Lemmy
// This file is part of TenebraWeb 2 under AGPL-3.0.
// Full details: https://github.com/tmpim/TenebraWeb2/blob/master/LICENSE.txt
-import { TenebraAddress, TenebraTransaction, TenebraName, TenebraBlock } from "./types";
+import { TenebraAddress, TenebraTransaction, TenebraName, TenebraBlock, TenebraStake } from "./types";
import * as api from ".";
import {
@@ -18,9 +18,15 @@
notFound: number;
addresses: Record;
}
+interface LookupStakesResponse {
+ found: number;
+ notFound: number;
+ stakes: Record;
+}
export interface TenebraAddressWithNames extends TenebraAddress { names?: number }
export type AddressLookupResults = Record;
+export type StakeLookupResults = Record;
export async function lookupAddresses(
addresses: string[],
@@ -43,6 +49,25 @@
return {};
}
+export async function lookupStakes(
+ addresses: string[]
+): Promise {
+ if (!addresses || addresses.length === 0) return {};
+
+ try {
+ const data = await api.get(
+ "lookup/stakes/"
+ + encodeURIComponent(addresses.join(","))
+ );
+
+ return data.stakes;
+ } catch (err) {
+ criticalError(err);
+ }
+
+ return {};
+}
+
/** Uses the lookup API to retrieve a single address. */
export async function lookupAddress(
address: string,
diff --git a/src/tenebra/api/types.ts b/src/tenebra/api/types.ts
index 3fb2ad3..806504e 100644
--- a/src/tenebra/api/types.ts
+++ b/src/tenebra/api/types.ts
@@ -100,7 +100,7 @@
}
export const DEFAULT_CURRENCY: TenebraCurrency = {
address_prefix: "k", name_suffix: "kst",
- currency_name: "Tenebra", currency_symbol: "KST"
+ currency_name: "Tenebra", currency_symbol: "TST"
};
export interface TenebraMOTDBase {
diff --git a/src/tenebra/wallets/Wallet.ts b/src/tenebra/wallets/Wallet.ts
index f96565a..53ef9a0 100644
--- a/src/tenebra/wallets/Wallet.ts
+++ b/src/tenebra/wallets/Wallet.ts
@@ -20,6 +20,7 @@
// Fetched from API
address: string;
balance?: number;
+ stake?: number;
names?: number;
firstSeen?: string;
lastSynced?: string;
diff --git a/src/tenebra/wallets/functions/syncWallets.ts b/src/tenebra/wallets/functions/syncWallets.ts
index 13a31ff..6fd4093 100644
--- a/src/tenebra/wallets/functions/syncWallets.ts
+++ b/src/tenebra/wallets/functions/syncWallets.ts
@@ -4,16 +4,18 @@
import { store } from "@app";
import * as actions from "@actions/WalletsActions";
-import { TenebraAddressWithNames, lookupAddresses } from "../../api/lookup";
+import { TenebraAddressWithNames, lookupAddresses, lookupStakes } from "../../api/lookup";
import { Wallet, saveWallet } from "..";
import Debug from "debug";
+import { TenebraStake } from "@api/types";
const debug = Debug("tenebraweb:sync-wallets");
function syncWalletProperties(
wallet: Wallet,
address: TenebraAddressWithNames | null,
+ stake: TenebraStake | null,
syncTime: Date
): Wallet {
if (address) {
@@ -22,7 +24,8 @@
...(address.balance !== undefined ? { balance: address.balance } : {}),
...(address.names !== undefined ? { names: address.names } : {}),
...(address.firstseen !== undefined ? { firstSeen: address.firstseen } : {}),
- lastSynced: syncTime.toISOString()
+ lastSynced: syncTime.toISOString(),
+ stake: stake ? stake.stake : wallet.stake
};
} else {
// Wallet was unsyncable (address not found), clear its properties
@@ -45,11 +48,13 @@
// Fetch the data from the sync node (e.g. balance)
const { address } = wallet;
const lookupResults = await lookupAddresses([address], true);
+ const lookupStakeResults = await lookupStakes([address]);
debug("synced individual wallet %s (%s): %o", wallet.id, wallet.address, lookupResults);
const tenebraAddress = lookupResults[address];
- syncWalletUpdate(wallet, tenebraAddress, dontSave);
+ const tenebraStake = lookupStakeResults[address];
+ syncWalletUpdate(wallet, tenebraAddress, tenebraStake, dontSave);
}
/** Given an already synced wallet, save it to local storage, and dispatch the
@@ -57,10 +62,11 @@
export function syncWalletUpdate(
wallet: Wallet,
address: TenebraAddressWithNames | null,
+ stake: TenebraStake | null,
dontSave?: boolean
): void {
const syncTime = new Date();
- const updatedWallet = syncWalletProperties(wallet, address, syncTime);
+ const updatedWallet = syncWalletProperties(wallet, address, stake, syncTime);
// Save the wallet to local storage, unless this was an external sync action
if (!dontSave) saveWallet(updatedWallet);
@@ -84,12 +90,14 @@
// Fetch all the data from the sync node (e.g. balances)
const addresses = Object.values(wallets).map(w => w.address);
const lookupResults = await lookupAddresses(addresses, true);
+ const lookupStakeResults = await lookupStakes(addresses);
// Create a WalletMap with the updated wallet properties
const updatedWallets = Object.entries(wallets).map(([_, wallet]) => {
const tenebraAddress = lookupResults[wallet.address];
if (!tenebraAddress) return wallet; // Skip unsyncable wallets
- return syncWalletProperties(wallet, tenebraAddress, syncTime);
+ const tenebraStake = lookupStakeResults[wallet.address];
+ return syncWalletProperties(wallet, tenebraAddress, tenebraStake, syncTime);
}).reduce((o, wallet) => ({ ...o, [wallet.id]: wallet }), {});
// Save the wallets to local storage (unless dontSave is set)