Skip to content

Commit

Permalink
Update Staking UI (hacky)
Browse files Browse the repository at this point in the history
Request parsing should happen in SignStakingApi, not ad-hoc in the UI logic.
  • Loading branch information
sisou committed Nov 23, 2024
1 parent 7110403 commit 37db1c0
Show file tree
Hide file tree
Showing 16 changed files with 240 additions and 147 deletions.
5 changes: 5 additions & 0 deletions client/src/PublicRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ export type SignStakingRequest = SimpleRequest & {
transaction: Uint8Array | Uint8Array[], // An array is only allowed for retire_stake + remove_stake transactions
senderLabel?: string,
recipientLabel?: string,
validatorAddress?: string,
validatorImageUrl?: string,
fromValidatorAddress?: string,
fromValidatorImageUrl?: string,
amount?: number,
};

export type SignBtcTransactionRequestStandard = SimpleRequest & BitcoinTransactionInfo & {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/RequestParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ class RequestParser { // eslint-disable-line no-unused-vars
*/
_parseUrl(url, parameterName) {
const parsedUrl = new URL(url);
const whitelistedProtocols = ['https:', 'http:', 'chrome-extension:', 'moz-extension:'];
const whitelistedProtocols = ['https:', 'http:', 'chrome-extension:', 'moz-extension:', 'data:'];
if (!whitelistedProtocols.includes(parsedUrl.protocol)) {
const protocolString = whitelistedProtocols.join(', ');
throw new Errors.InvalidRequestError(`${parameterName} protocol must be one of: ${protocolString}`);
Expand Down
10 changes: 0 additions & 10 deletions src/request/sign-staking/SignStaking.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
flex-direction: column;
justify-content: center;
align-items: center;
padding-top: 2rem;
flex-grow: 1;
}

Expand Down Expand Up @@ -118,8 +117,6 @@
#confirm-staking .accounts {
flex-direction: row;
align-items: flex-start;
padding-bottom: 2rem;
border-bottom: 1px solid rgba(31, 35, 72, 0.1);
flex-shrink: 0;
}

Expand Down Expand Up @@ -147,10 +144,3 @@
opacity: 0.5;
margin-bottom: 0.25rem;
}

#confirm-staking .data-section {
margin: 1rem 3rem;
text-align: center;
max-width: 100%;
overflow-wrap: break-word;
}
295 changes: 163 additions & 132 deletions src/request/sign-staking/SignStaking.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
/* global AddressInfo */
/* global NumberFormatting */
/* global lunasToCoins */
/* global I18n */

/**
* @callback SignStaking.resolve
Expand All @@ -24,29 +25,175 @@ class SignStaking {
this._request = request;
this.$el = /** @type {HTMLElement} */ (document.getElementById(SignStaking.Pages.CONFIRM_STAKING));

const transaction = request.plain[request.plain.length - 1];

this.$headline = /** @type {HTMLElement} */ (this.$el.querySelector('#headline'));
this.$accountDetails = /** @type {HTMLElement} */ (this.$el.querySelector('#account-details'));

const $sender = /** @type {HTMLLinkElement} */ (this.$el.querySelector('.accounts .sender'));
this._senderAddressInfo = new AddressInfo({
userFriendlyAddress: transaction.sender,
label: request.senderLabel || null,
imageUrl: null,
accountLabel: request.keyLabel || null,
});
const $recipient = /** @type {HTMLLinkElement} */ (this.$el.querySelector('.accounts .recipient'));

const transaction = request.plain[request.plain.length - 1];

/** @type {Nimiq.Address | undefined} */
let validatorAddress;

let displayValue = transaction.value;

if (transaction.recipientType === 'staking') {
switch (transaction.data.type) {
case 'create-staker':
if (transaction.data.delegation) {
validatorAddress = Nimiq.Address.fromUserFriendlyAddress(transaction.data.delegation);
}
case 'add-stake': // eslint-disable-line no-fallthrough
validatorAddress = validatorAddress || request.validatorAddress;

if (!validatorAddress) {
throw new Errors.InvalidRequestError('No delegation or validatorAddress provided');
}

this.$headline.textContent = I18n.translatePhrase('sign-staking-heading-stake');
this._senderAddressInfo = new AddressInfo({ // From user
userFriendlyAddress: transaction.sender,
label: request.senderLabel || null,
imageUrl: null,
accountLabel: request.keyLabel || null,
});
this._recipientAddressInfo = new AddressInfo({ // To validator
userFriendlyAddress: validatorAddress.toUserFriendlyAddress(),
label: request.recipientLabel || null,
imageUrl: request.validatorImageUrl || null,
accountLabel: null,
});
break;
case 'update-staker': { // Change validator
if (transaction.data.newDelegation) {
validatorAddress = Nimiq.Address.fromUserFriendlyAddress(transaction.data.newDelegation);
}

if (!validatorAddress) {
throw new Errors.InvalidRequestError('No newDelegation provided');
}

if (!request.amount) {
throw new Errors.InvalidRequestError('No amount provided');
}
displayValue = request.amount;

const fromValidatorAddress = request.validatorAddress;
if (!fromValidatorAddress) {
throw new Errors.InvalidRequestError('No fromValidatorAddress provided');
}

this.$headline.textContent = I18n.translatePhrase('sign-staking-heading-change');
this._senderAddressInfo = new AddressInfo({ // From previous validator
userFriendlyAddress: fromValidatorAddress.toUserFriendlyAddress(),
label: request.senderLabel || null,
imageUrl: request.fromValidatorImageUrl || null,
accountLabel: request.keyLabel || null,
});
this._recipientAddressInfo = new AddressInfo({ // To new validator
userFriendlyAddress: validatorAddress.toUserFriendlyAddress(),
label: request.recipientLabel || null,
imageUrl: request.validatorImageUrl || null,
accountLabel: null,
});
break;
}
case 'set-active-stake':
case 'retire-stake':
validatorAddress = request.validatorAddress;

if (!validatorAddress) {
throw new Errors.InvalidRequestError('No validatorAddress provided');
}

if (!request.amount) {
throw new Errors.InvalidRequestError('No amount provided');
}
displayValue = request.amount;

this.$headline.textContent = I18n.translatePhrase('sign-staking-heading-unstake');
this._senderAddressInfo = new AddressInfo({ // From validator
userFriendlyAddress: validatorAddress.toUserFriendlyAddress(),
label: request.recipientLabel || null,
imageUrl: request.validatorImageUrl || null,
accountLabel: null,
});
this._recipientAddressInfo = new AddressInfo({ // To User
userFriendlyAddress: transaction.sender,
label: request.senderLabel || null,
imageUrl: null,
accountLabel: request.keyLabel || null,
});
break;
case 'create-validator':
case 'update-validator':
case 'deactivate-validator':
case 'reactivate-validator':
case 'retire-validator':
default:
this.$headline.textContent = I18n.translatePhrase('sign-tx-heading-tx');
this._senderAddressInfo = new AddressInfo({
userFriendlyAddress: transaction.sender,
label: request.senderLabel || null,
imageUrl: null,
accountLabel: request.keyLabel || null,
});
this._recipientAddressInfo = new AddressInfo({
userFriendlyAddress: transaction.recipient,
label: request.recipientLabel || null,
imageUrl: null,
accountLabel: null,
});
break;
}
} else {
switch (transaction.senderData.type) {
case 'remove-stake':
validatorAddress = request.validatorAddress;

if (!validatorAddress) {
throw new Errors.InvalidRequestError('No validatorAddress provided');
}

this.$headline.textContent = I18n.translatePhrase('sign-staking-heading-unstake');
this._senderAddressInfo = new AddressInfo({ // From validator
userFriendlyAddress: validatorAddress.toUserFriendlyAddress(),
label: request.senderLabel || null,
imageUrl: request.validatorImageUrl || null,
accountLabel: null,
});
this._recipientAddressInfo = new AddressInfo({ // To User
userFriendlyAddress: transaction.recipient,
label: request.recipientLabel || null,
imageUrl: null,
accountLabel: request.keyLabel || null,
});
break;
case 'delete-validator':
default:
this.$headline.textContent = I18n.translatePhrase('sign-tx-heading-tx');
this._senderAddressInfo = new AddressInfo({
userFriendlyAddress: transaction.sender,
label: request.senderLabel || null,
imageUrl: null,
accountLabel: null,
});
this._recipientAddressInfo = new AddressInfo({
userFriendlyAddress: transaction.recipient,
label: request.recipientLabel || null,
imageUrl: null,
accountLabel: request.keyLabel || null,
});
break;
}
}

this._senderAddressInfo.renderTo($sender);
$sender.addEventListener('click', () => {
this._openDetails(this._senderAddressInfo);
});

const $recipient = /** @type {HTMLLinkElement} */ (this.$el.querySelector('.accounts .recipient'));
this._recipientAddressInfo = new AddressInfo({
userFriendlyAddress: transaction.recipient,
label: request.recipientLabel || null,
imageUrl: null,
accountLabel: null,
});
this._recipientAddressInfo.renderTo($recipient);
$recipient.addEventListener('click', () => {
this._openDetails(this._recipientAddressInfo);
Expand All @@ -57,23 +204,15 @@ class SignStaking {

const $value = /** @type {HTMLDivElement} */ (this.$el.querySelector('#value'));
const $fee = /** @type {HTMLDivElement} */ (this.$el.querySelector('#fee'));
const $data = /** @type {HTMLDivElement} */ (this.$el.querySelector('#data'));

// Set value and fee.
$value.textContent = NumberFormatting.formatNumber(lunasToCoins(transaction.value));
$value.textContent = NumberFormatting.formatNumber(lunasToCoins(displayValue));
if ($fee && transaction.fee > 0) {
$fee.textContent = NumberFormatting.formatNumber(lunasToCoins(transaction.fee));
const $feeSection = /** @type {HTMLDivElement} */ (this.$el.querySelector('.fee-section'));
$feeSection.classList.remove('display-none');
}

if ($data && transaction.data.raw.length) {
// Set transaction extra data.
$data.textContent = this._formatData(transaction);
const $dataSection = /** @type {HTMLDivElement} */ (this.$el.querySelector('.data-section'));
$dataSection.classList.remove('display-none');
}

// Set up password box.
const $passwordBox = /** @type {HTMLFormElement} */ (document.querySelector('#password-box'));
this._passwordBox = new PasswordBox($passwordBox, {
Expand Down Expand Up @@ -159,114 +298,6 @@ class SignStaking {
// Go to start page
window.location.hash = SignStaking.Pages.CONFIRM_STAKING;
}

/**
* @param {Nimiq.PlainTransaction} plain
* @returns {string}
*/
_formatData(plain) {
console.log(plain);
// That either the recipient or the sender is a staking account type is validated in SignStakingApi
if (plain.recipientType === 'basic') {
switch (plain.data.type) {
case 'create-staker': {
let text = 'Start staking';
const { delegation } = plain.data;
if (delegation) {
text += ` with validator ${delegation}`;
} else {
text += ' with no validator';
}
return text;
}
case 'update-staker': {
let text = 'Change validator';
const { newDelegation, reactivateAllStake } = plain.data;
if (newDelegation) {
text += ` to validator ${newDelegation}`;
} else {
text += ' to no validator';
}
if (reactivateAllStake) {
text += ' and reactivate all stake';
}
return text;
}
case 'add-stake': {
const { staker } = plain.data;
return `Add stake to ${staker}`;
}
case 'set-active-stake': {
const { newActiveBalance } = plain.data;
return `Set active stake to ${newActiveBalance / 1e5} NIM`;
}
case 'retire-stake': {
const { retireStake } = plain.data;
return `Retire ${retireStake / 1e5} NIM stake`;
}
case 'create-validator': {
let text = `Create validator ${plain.sender}`;
const { rewardAddress } = plain.data;
if (rewardAddress !== plain.sender) {
text += ` with reward address ${rewardAddress}`;
}
// TODO: Somehow let users see validator key, signing key, and signal data that they are signing
return text;
}
case 'update-validator': {
let text = `Update validator ${plain.sender}`;
const {
newRewardAddress,
newVotingKey,
newSigningKey,
newSignalData,
} = plain.data;
text += ` ${plain.sender}`;
if (newRewardAddress) {
text += `, updating reward address to ${newRewardAddress}`;
}
if (newVotingKey) {
text += ', updating voting key';
}
if (newSigningKey) {
text += ', updating signing key';
}
if (newSignalData) {
text += ', updating signal data';
}
return text;
}
case 'deactivate-validator': {
const { validator } = plain.data;
return `Deactivate validator ${validator}`;
}
case 'reactivate-validator': {
const { validator } = plain.data;
return `Reactivate validator ${validator}`;
}
case 'retire-validator': {
return `Retire validator ${plain.sender}`;
}
default: {
return `Unrecognized in-staking data: ${plain.data.type} - ${plain.data.raw}`;
}
}
} else {
switch (plain.senderData.type) {
case 'remove-stake': {
return 'Unstake';
}
case 'delete-validator': {
// Cannot show the validator address here, as the recipient can be any address and the validator
// address is the signer, which the Keyguard only knows after password entry.
return 'Delete validator';
}
default: {
return `Unrecognized out-staking data: ${plain.senderData.type} - ${plain.senderData.raw}`;
}
}
}
}
}

SignStaking.Pages = {
Expand Down
Loading

0 comments on commit 37db1c0

Please sign in to comment.