Merge pull request #587 from keerifox/decentralized-aliases
Decentralized aliases
This commit is contained in:
commit
6d8a5aa3c7
9 changed files with 313 additions and 13 deletions
|
|
@ -744,14 +744,18 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
|||
|
||||
const regexp = new RegExp('^(Account|' + this.translocoService.translate('general.account') + ') #\\d+$', 'g');
|
||||
if ( regexp.test(this.addressBookModel) === true ) {
|
||||
return this.notifications.sendError(`This name is reserved for wallet accounts without a label`);
|
||||
return this.notifications.sendError(this.translocoService.translate('address-book.this-name-is-reserved-for-wallet-accounts-without-a-label'));
|
||||
}
|
||||
|
||||
if ( this.addressBookModel.startsWith('@') === true ) {
|
||||
return this.notifications.sendError(this.translocoService.translate('address-book.this-name-is-reserved-for-decentralized-aliases'));
|
||||
}
|
||||
|
||||
// Make sure no other entries are using that name
|
||||
const accountIdWithSameName = this.addressBook.getAccountIdByName(this.addressBookModel);
|
||||
|
||||
if ( (accountIdWithSameName !== null) && (accountIdWithSameName !== this.accountID) ) {
|
||||
return this.notifications.sendError(`This name is already in use! Please use a unique name`);
|
||||
return this.notifications.sendError(this.translocoService.translate('address-book.this-name-is-already-in-use-please-use-a-unique-name'));
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -759,11 +763,11 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
|||
const currentTransactionTracking = this.addressBook.getTransactionTrackingById(this.accountID);
|
||||
await this.addressBook.saveAddress(this.accountID, this.addressBookModel, currentBalanceTracking, currentTransactionTracking);
|
||||
} catch (err) {
|
||||
this.notifications.sendError(`Unable to save entry: ${err.message}`);
|
||||
this.notifications.sendError(this.translocoService.translate('address-book.unable-to-save-entry', { message: err.message }));
|
||||
return;
|
||||
}
|
||||
|
||||
this.notifications.sendSuccess(`Address book entry saved successfully!`);
|
||||
this.notifications.sendSuccess(this.translocoService.translate('address-book.address-book-entry-saved-successfully'));
|
||||
|
||||
this.addressBookEntry = this.addressBookModel;
|
||||
this.showEditAddressBook = false;
|
||||
|
|
|
|||
|
|
@ -281,6 +281,10 @@ export class AddressBookComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
return this.notificationService.sendError(this.translocoService.translate('address-book.this-name-is-reserved-for-wallet-accounts-without-a-label'));
|
||||
}
|
||||
|
||||
if ( this.newAddressName.startsWith('@') === true ) {
|
||||
return this.notificationService.sendError(this.translocoService.translate('address-book.this-name-is-reserved-for-decentralized-aliases'));
|
||||
}
|
||||
|
||||
// Remove spaces and convert to nano prefix
|
||||
this.newAddressAccount = this.newAddressAccount.replace(/ /g, '').replace('xrb_', 'nano_');
|
||||
|
||||
|
|
|
|||
|
|
@ -197,6 +197,23 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-width-1-1">
|
||||
<div class="uk-form-horizontal">
|
||||
|
||||
<div class="uk-margin">
|
||||
<label class="uk-form-label">{{ 'configure-app.decentralized-aliases' | transloco }} <span uk-icon="icon: info;" uk-tooltip [title]="t('configure-app.decentralized-aliases-require-external-requests')"></span></label>
|
||||
<div class="uk-form-controls">
|
||||
|
||||
<div class="uk-inline uk-width-1-1">
|
||||
<select class="uk-select" [(ngModel)]="selectedDecentralizedAliasesOption">
|
||||
<option *ngFor="let decentralizedAliasesOption of decentralizedAliasesOptions" [value]="decentralizedAliasesOption.value">{{ decentralizedAliasesOption.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-width-1-1">
|
||||
<div class="uk-form-horizontal">
|
||||
|
||||
|
|
|
|||
|
|
@ -148,6 +148,12 @@ export class ConfigureAppComponent implements OnInit {
|
|||
];
|
||||
selectedPendingOption = this.pendingOptions[0].value;
|
||||
|
||||
decentralizedAliasesOptions = [
|
||||
{ name: this.translocoService.translate('configure-app.decentralized-aliases-options.disabled'), value: 'disabled' },
|
||||
{ name: this.translocoService.translate('configure-app.decentralized-aliases-options.enabled'), value: 'enabled' },
|
||||
];
|
||||
selectedDecentralizedAliasesOption = this.decentralizedAliasesOptions[0].value;
|
||||
|
||||
// prefixOptions = [
|
||||
// { name: 'xrb_', value: 'xrb' },
|
||||
// { name: 'nano_', value: 'nano' },
|
||||
|
|
@ -282,6 +288,9 @@ export class ConfigureAppComponent implements OnInit {
|
|||
const matchingPendingOption = this.pendingOptions.find(d => d.value === settings.pendingOption);
|
||||
this.selectedPendingOption = matchingPendingOption ? matchingPendingOption.value : this.pendingOptions[0].value;
|
||||
|
||||
const matchingDecentralizedAliasesOption = this.decentralizedAliasesOptions.find(d => d.value === settings.decentralizedAliasesOption);
|
||||
this.selectedDecentralizedAliasesOption = matchingDecentralizedAliasesOption ? matchingDecentralizedAliasesOption.value : this.decentralizedAliasesOptions[0].value;
|
||||
|
||||
this.serverOptions = this.appSettings.serverOptions;
|
||||
this.selectedServer = settings.serverName;
|
||||
this.serverAPI = settings.serverAPI;
|
||||
|
|
@ -371,6 +380,8 @@ export class ConfigureAppComponent implements OnInit {
|
|||
minReceive = this.minimumReceive;
|
||||
}
|
||||
|
||||
const decentralizedAliasesOption = this.selectedDecentralizedAliasesOption;
|
||||
|
||||
// reload pending if threshold changes or if receive priority changes from manual to auto
|
||||
let reloadPending = this.appSettings.settings.minimumReceive !== this.minimumReceive
|
||||
|| (pendingOption !== 'manual' && pendingOption !== this.appSettings.settings.pendingOption);
|
||||
|
|
@ -432,6 +443,7 @@ export class ConfigureAppComponent implements OnInit {
|
|||
multiplierSource: Number(this.selectedMultiplierOption),
|
||||
customWorkServer: this.customWorkServer,
|
||||
pendingOption: pendingOption,
|
||||
decentralizedAliasesOption: decentralizedAliasesOption,
|
||||
minimumReceive: minReceive,
|
||||
defaultRepresentative: this.defaultRepresentative || null,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,24 +32,49 @@
|
|||
<div class="uk-form-controls">
|
||||
<div class="form-input-destination uk-inline uk-width-1-1">
|
||||
<a class="hide-on-small-viewports uk-form-icon uk-form-icon-flip" uk-icon="icon: camera" (click)="openQR('account1','account')" uk-tooltip title="Scan from QR code"></a>
|
||||
<input (blur)="validateDestination()" (input)="onDestinationAddressInput()" (focus)="searchAddressBook()" [(ngModel)]="toAccountID" [ngClass]="{ 'uk-form-success': toAccountStatus === 2, 'uk-form-danger': toAccountStatus === 0 }" class="uk-input" id="form-horizontal-text2" type="text" placeholder="Address to send to / nano:.." autocomplete="off">
|
||||
<input (blur)="validateDestination()" (input)="onDestinationAddressInput()" (keydown.enter)="lookupAlias()" (focus)="searchAddressBook()" [(ngModel)]="toAccountID" [ngClass]="{ 'uk-form-success': toAccountStatus === 2, 'uk-form-danger': toAccountStatus === 0 }" class="uk-input" id="form-horizontal-text2" type="text" placeholder="Address to send to / Alias / nano:.." autocomplete="off">
|
||||
|
||||
<div *ngIf="(addressBookResults$ | async).length" [hidden]="!showAddressBook" class="nlt-dropdown uk-animation-slide-down-small uk-width-1-1 uk-card uk-card-default uk-card-body uk-position-absolute" style="z-index: 15000">
|
||||
<div *ngIf="(aliasResults$ | async).length || (addressBookResults$ | async).length" [hidden]="!showAddressBook && !isDestinationAccountAlias" class="nlt-dropdown uk-animation-slide-down-small uk-width-1-1 uk-card uk-card-default uk-card-body uk-position-absolute" style="z-index: 15000">
|
||||
<ul class="uk-nav uk-nav-default">
|
||||
<li class="uk-nav-header">Address Book Results</li>
|
||||
<li class="uk-nav-divider"></li>
|
||||
<li *ngFor="let book of addressBookResults$ | async">
|
||||
<a (click)="selectBookEntry(book.account)">{{ book.name }}</a>
|
||||
</li>
|
||||
<ng-container *ngIf="(aliasResults$ | async).length">
|
||||
<li class="uk-nav-header">Alias Lookup</li>
|
||||
<li class="uk-nav-divider"></li>
|
||||
<li *ngIf="(aliasLookupInProgress.domain !== '')">
|
||||
<a>
|
||||
<span class="spinner uk-margin-small-right" uk-spinner="ratio: 0.5;"></span><span class="text-half-muted">Looking up {{ aliasLookupInProgress.fullText }}...</span>
|
||||
</a>
|
||||
</li>
|
||||
<ng-container *ngIf="(aliasLookupInProgress.domain === '')">
|
||||
<li *ngFor="let alias of aliasResults$ | async">
|
||||
<a (click)="lookupAlias()" class="uk-padding-remove-bottom">
|
||||
<span class="text-half-muted">Press Enter or click here to request alias from</span>
|
||||
</a>
|
||||
<a (click)="lookupAlias()">
|
||||
<span class="uk-text-primary">{{ alias.domain }}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ng-container>
|
||||
<li class="uk-nav-divider" *ngIf="(addressBookResults$ | async).length"></li>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="(addressBookResults$ | async).length">
|
||||
<li class="uk-nav-header">Address Book Results</li>
|
||||
<li class="uk-nav-divider"></li>
|
||||
<li *ngFor="let book of addressBookResults$ | async">
|
||||
<a (click)="selectBookEntry(book.account)">{{ book.name }}</a>
|
||||
</li>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-form-controls" *ngIf="addressBookMatch">
|
||||
<div class="uk-inline uk-width-1-1">
|
||||
<div class="uk-form-controls" *ngIf="addressBookMatch || addressAliasMatch">
|
||||
<div class="uk-inline uk-width-auto uk-margin-small-right" *ngIf="addressBookMatch">
|
||||
<span class="account-label blue uk-margin-small-top">{{ addressBookMatch }}</span>
|
||||
</div>
|
||||
<div class="uk-inline uk-width-auto" *ngIf="addressAliasMatch">
|
||||
<span class="account-label alias uk-margin-small-top">{{ addressAliasMatch }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {NanoBlockService} from '../../services/nano-block.service';
|
|||
import { QrModalService } from '../../services/qr-modal.service';
|
||||
import { environment } from 'environments/environment';
|
||||
import { TranslocoService } from '@ngneat/transloco';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import * as nanocurrency from 'nanocurrency';
|
||||
|
||||
const nacl = window['nacl'];
|
||||
|
|
@ -30,9 +31,29 @@ export class SendComponent implements OnInit {
|
|||
sendDestinationType = 'external-address';
|
||||
|
||||
accounts = this.walletService.wallet.accounts;
|
||||
|
||||
ALIAS_LOOKUP_DEFAULT_STATE = {
|
||||
fullText: '',
|
||||
name: '',
|
||||
domain: '',
|
||||
}
|
||||
|
||||
aliasLookup = {
|
||||
...this.ALIAS_LOOKUP_DEFAULT_STATE,
|
||||
}
|
||||
aliasLookupInProgress = {
|
||||
...this.ALIAS_LOOKUP_DEFAULT_STATE,
|
||||
}
|
||||
aliasLookupLatestSuccessful = {
|
||||
...this.ALIAS_LOOKUP_DEFAULT_STATE,
|
||||
address: '',
|
||||
}
|
||||
aliasResults$ = new BehaviorSubject([]);
|
||||
addressBookResults$ = new BehaviorSubject([]);
|
||||
isDestinationAccountAlias = false;
|
||||
showAddressBook = false;
|
||||
addressBookMatch = '';
|
||||
addressAliasMatch = '';
|
||||
|
||||
amounts = [
|
||||
{ name: 'XNO', shortName: 'XNO', value: 'mnano' },
|
||||
|
|
@ -70,6 +91,7 @@ export class SendComponent implements OnInit {
|
|||
public settings: AppSettingsService,
|
||||
private util: UtilService,
|
||||
private qrModalService: QrModalService,
|
||||
private http: HttpClient,
|
||||
private translocoService: TranslocoService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
|
|
@ -134,6 +156,7 @@ export class SendComponent implements OnInit {
|
|||
|
||||
if (params && params.to) {
|
||||
this.toAccountID = params.to;
|
||||
this.offerLookupIfDestinationIsAlias();
|
||||
this.validateDestination();
|
||||
this.sendDestinationType = 'external-address';
|
||||
}
|
||||
|
|
@ -194,6 +217,10 @@ export class SendComponent implements OnInit {
|
|||
}
|
||||
|
||||
onDestinationAddressInput() {
|
||||
this.addressAliasMatch = '';
|
||||
this.addressBookMatch = '';
|
||||
|
||||
this.offerLookupIfDestinationIsAlias();
|
||||
this.searchAddressBook();
|
||||
|
||||
const destinationAddress = this.toAccountID || '';
|
||||
|
|
@ -243,9 +270,188 @@ export class SendComponent implements OnInit {
|
|||
this.addressBookResults$.next(matches);
|
||||
}
|
||||
|
||||
offerLookupIfDestinationIsAlias() {
|
||||
const destinationAddress = this.toAccountID || '';
|
||||
|
||||
const mayBeAnAlias = (
|
||||
( destinationAddress.startsWith('@') === true )
|
||||
&& ( destinationAddress.includes('.') === true )
|
||||
&& ( destinationAddress.endsWith('.') === false )
|
||||
&& ( destinationAddress.includes('/') === false )
|
||||
&& ( destinationAddress.includes('?') === false )
|
||||
);
|
||||
|
||||
if (mayBeAnAlias === false) {
|
||||
this.isDestinationAccountAlias = false;
|
||||
this.aliasLookup = {
|
||||
...this.ALIAS_LOOKUP_DEFAULT_STATE,
|
||||
};
|
||||
this.aliasResults$.next([]);
|
||||
return
|
||||
}
|
||||
|
||||
this.isDestinationAccountAlias = true;
|
||||
|
||||
let aliasWithoutFirstSymbol = destinationAddress.slice(1).toLowerCase();
|
||||
|
||||
if (aliasWithoutFirstSymbol.startsWith('_@') === true ) {
|
||||
aliasWithoutFirstSymbol = aliasWithoutFirstSymbol.slice(2);
|
||||
}
|
||||
|
||||
const aliasSplitResults = aliasWithoutFirstSymbol.split('@');
|
||||
|
||||
let aliasName = ''
|
||||
let aliasDomain = ''
|
||||
|
||||
if (aliasSplitResults.length === 2) {
|
||||
aliasName = aliasSplitResults[0]
|
||||
aliasDomain = aliasSplitResults[1]
|
||||
} else {
|
||||
aliasDomain = aliasSplitResults[0]
|
||||
}
|
||||
|
||||
this.aliasLookup = {
|
||||
fullText: `@${aliasWithoutFirstSymbol}`,
|
||||
name: aliasName,
|
||||
domain: aliasDomain,
|
||||
};
|
||||
|
||||
this.aliasResults$.next([{ ...this.aliasLookup }]);
|
||||
|
||||
this.toAccountStatus = 1; // Neutral state
|
||||
}
|
||||
|
||||
async lookupAlias() {
|
||||
if (this.aliasLookup.domain === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.settings.settings.decentralizedAliasesOption === 'disabled') {
|
||||
const UIkit = window['UIkit'];
|
||||
try {
|
||||
await UIkit.modal.confirm(
|
||||
`<p class="uk-alert uk-alert-warning"><br><span class="uk-flex"><span uk-icon="icon: warning; ratio: 3;" class="uk-align-center"></span></span>
|
||||
<span style="font-size: 18px;">
|
||||
${ this.translocoService.translate('configure-app.decentralized-aliases-require-external-requests') }
|
||||
</span>`,
|
||||
{
|
||||
labels: {
|
||||
cancel: this.translocoService.translate('general.cancel'),
|
||||
ok: this.translocoService.translate('configure-app.allow-external-requests'),
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.settings.setAppSetting('decentralizedAliasesOption', 'enabled');
|
||||
} catch (err) {
|
||||
// pressed cancel, or a different error
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.toAccountStatus = 1; // Neutral state
|
||||
|
||||
const aliasLookup = { ...this.aliasLookup };
|
||||
|
||||
const aliasFullText = aliasLookup.fullText;
|
||||
const aliasDomain = aliasLookup.domain;
|
||||
|
||||
const aliasName = (
|
||||
(aliasLookup.name !== '')
|
||||
? aliasLookup.name
|
||||
: '_'
|
||||
);
|
||||
|
||||
const lookupUrl =
|
||||
`https://${ aliasDomain }/.well-known/nano-currency.json?names=${ aliasName }`;
|
||||
|
||||
this.aliasLookupInProgress = {
|
||||
...aliasLookup,
|
||||
};
|
||||
|
||||
await this.http.get<any>(lookupUrl).toPromise()
|
||||
.then(res => {
|
||||
const isOutdatedRequest = (
|
||||
this.aliasLookupInProgress.fullText
|
||||
!== aliasFullText
|
||||
);
|
||||
|
||||
if (isOutdatedRequest === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.aliasLookupInProgress = {
|
||||
...this.ALIAS_LOOKUP_DEFAULT_STATE,
|
||||
};
|
||||
|
||||
try {
|
||||
const aliasesInJsonCount = (
|
||||
( Array.isArray(res.names) === true )
|
||||
? res.names.length
|
||||
: 0
|
||||
);
|
||||
|
||||
if (aliasesInJsonCount === 0) {
|
||||
this.toAccountStatus = 0; // Error state
|
||||
this.notificationService.sendWarning(`Alias @${aliasName} not found on ${aliasDomain}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const matchingAccount =
|
||||
res.names.find(
|
||||
(account) =>
|
||||
(account.name === aliasName)
|
||||
);
|
||||
|
||||
if (matchingAccount == null) {
|
||||
this.toAccountStatus = 0; // Error state
|
||||
this.notificationService.sendWarning(`Alias @${aliasName} not found on ${aliasDomain}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.util.account.isValidAccount(matchingAccount.address)) {
|
||||
this.toAccountStatus = 0; // Error state
|
||||
this.notificationService.sendWarning(`Alias ${aliasFullText} does not have a valid address`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.toAccountID = matchingAccount.address;
|
||||
|
||||
this.aliasLookupLatestSuccessful = {
|
||||
...aliasLookup,
|
||||
address: this.toAccountID,
|
||||
};
|
||||
|
||||
this.onDestinationAddressInput();
|
||||
this.validateDestination();
|
||||
|
||||
return;
|
||||
} catch(err) {
|
||||
this.toAccountStatus = 0; // Error state
|
||||
this.notificationService.sendWarning(`Unknown error has occurred while trying to lookup ${aliasFullText}`);
|
||||
return;
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.aliasLookupInProgress = {
|
||||
...this.ALIAS_LOOKUP_DEFAULT_STATE,
|
||||
};
|
||||
this.toAccountStatus = 0; // Error state
|
||||
|
||||
if (err.status === 404) {
|
||||
this.notificationService.sendWarning(`No aliases found on ${aliasDomain}`);
|
||||
} else {
|
||||
this.notificationService.sendWarning(`Could not reach domain ${aliasDomain}`);
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
selectBookEntry(account) {
|
||||
this.showAddressBook = false;
|
||||
this.toAccountID = account;
|
||||
this.isDestinationAccountAlias = false;
|
||||
this.searchAddressBook();
|
||||
this.validateDestination();
|
||||
}
|
||||
|
|
@ -261,6 +467,21 @@ export class SendComponent implements OnInit {
|
|||
// Remove spaces from the account id
|
||||
this.toAccountID = this.toAccountID.replace(/ /g, '');
|
||||
|
||||
this.addressAliasMatch = (
|
||||
(
|
||||
(this.aliasLookupLatestSuccessful.address !== '')
|
||||
&& (this.aliasLookupLatestSuccessful.address === this.toAccountID)
|
||||
)
|
||||
? this.aliasLookupLatestSuccessful.fullText
|
||||
: null
|
||||
);
|
||||
|
||||
if (this.isDestinationAccountAlias === true) {
|
||||
this.addressBookMatch = null;
|
||||
this.toAccountStatus = 1; // Neutral state
|
||||
return;
|
||||
}
|
||||
|
||||
this.addressBookMatch = (
|
||||
this.addressBookService.getAccountName(this.toAccountID)
|
||||
|| this.getAccountLabel(this.toAccountID, null)
|
||||
|
|
@ -426,6 +647,7 @@ export class SendComponent implements OnInit {
|
|||
this.fromAddressBook = '';
|
||||
this.toAddressBook = '';
|
||||
this.addressBookMatch = '';
|
||||
this.addressAliasMatch = '';
|
||||
} else {
|
||||
if (!this.walletService.isLedgerWallet()) {
|
||||
this.notificationService.sendError(`There was an error sending your transaction, please try again.`);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ interface AppSettings {
|
|||
multiplierSource: number;
|
||||
customWorkServer: string;
|
||||
pendingOption: string;
|
||||
decentralizedAliasesOption: string;
|
||||
serverName: string;
|
||||
serverAPI: string | null;
|
||||
serverWS: string | null;
|
||||
|
|
@ -48,6 +49,7 @@ export class AppSettingsService {
|
|||
multiplierSource: 1,
|
||||
customWorkServer: '',
|
||||
pendingOption: 'amount',
|
||||
decentralizedAliasesOption: 'disabled',
|
||||
serverName: 'random',
|
||||
serverAPI: null,
|
||||
serverWS: null,
|
||||
|
|
@ -233,6 +235,7 @@ export class AppSettingsService {
|
|||
multiplierSource: 1,
|
||||
customWorkServer: '',
|
||||
pendingOption: 'amount',
|
||||
decentralizedAliasesOption: 'disabled',
|
||||
serverName: 'random',
|
||||
serverAPI: null,
|
||||
serverWS: null,
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@
|
|||
"the-balance-will-be-displayed-in-the-address-book": "The balance will be displayed in the address book.",
|
||||
"this-name-is-already-in-use-please-use-a-unique-name": "This name is already in use! Please use a unique name",
|
||||
"this-name-is-reserved-for-wallet-accounts-without-a-label": "This name is reserved for wallet accounts without a label",
|
||||
"this-name-is-reserved-for-decentralized-aliases": "Names starting with @ are reserved for decentralized aliases",
|
||||
"track-balance": "Track Balance",
|
||||
"track-transactions": "Track Transactions",
|
||||
"unable-to-delete-entry": "Unable to delete entry: {{ message }}",
|
||||
|
|
@ -133,6 +134,7 @@
|
|||
},
|
||||
"configure-app": {
|
||||
"advanced-options": "Advanced Options",
|
||||
"allow-external-requests": "Allow External Requests",
|
||||
"api-server": "API Server",
|
||||
"app-display-settings-successfully-updated": "App display settings successfully updated!",
|
||||
"app-wallet-settings-successfully-updated": "App wallet settings successfully updated!",
|
||||
|
|
@ -158,6 +160,12 @@
|
|||
},
|
||||
"custom-api-server-has-an-invalid-address": "Custom API Server has an invalid address.",
|
||||
"custom-update-server-has-an-invalid-address": "Custom Update Server has an invalid address.",
|
||||
"decentralized-aliases": "Decentralized Aliases",
|
||||
"decentralized-aliases-options": {
|
||||
"disabled": "Disabled",
|
||||
"enabled": "Enabled - Allow External Requests to Alias Domains"
|
||||
},
|
||||
"decentralized-aliases-require-external-requests": "Decentralized aliases require external requests to alias domains. When you try to lookup the address of @madeline@example.com, the domain example.com may record your IP address.",
|
||||
"default-representative": "Default Representative",
|
||||
"default-representative-is-not-a-valid-account": "Default representative is not a valid account",
|
||||
"delete-all-data": "Delete ALL Data",
|
||||
|
|
|
|||
|
|
@ -347,6 +347,11 @@ input[type=number] {
|
|||
background: @rep-label-color;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
&.alias {
|
||||
text-transform: none;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.rep-donation-icon {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue