@@ -36,6 +36,23 @@
{{unit}}
{{recipient.altAmountStr}} {{alternativeUnit}}
+
+
+
+
+
+
+
+ Fee: ~0.00055 XPI
+
+ = 206}">
+ {{(this.message).length}} / 206 bytes
+
+
+
+
{{'Send total selected amount' | translate}}
diff --git a/src/app/components/recipient/recipient.component.scss b/src/app/components/recipient/recipient.component.scss
index 93037ce2180..65e0320f7ad 100644
--- a/src/app/components/recipient/recipient.component.scss
+++ b/src/app/components/recipient/recipient.component.scss
@@ -7,6 +7,10 @@ recipient-component {
appearance: none;
margin: 0;
}
+
+ .sign-require {
+ color: var(--color-danger);
+ }
.check {
margin: 0 !important;
@@ -90,6 +94,12 @@ recipient-component {
font-size: 16px;
}
+ ion-textarea{
+ --placeholder-color: rgba(0, 30, 46, 0.38);
+ --color: #001E2E;
+ font-size: 16px;
+ }
+
.scan-icon {
width: 20px;
height: 20px;
@@ -122,6 +132,14 @@ recipient-component {
margin: 0;
}
}
+
+ .amount-header-onchain-message {
+ padding-top: 1rem !important;
+ }
+
+ .max-message-txt {
+ color: var(--color-danger);
+ }
.max-text {
cursor: pointer;
@@ -135,6 +153,11 @@ recipient-component {
margin-top: 5px;
margin-left: 18px;
margin-bottom: 0;
+ &.unit-alt-text-onchain-message {
+ margin: 5px 18px;
+ display: flex;
+ justify-content: space-between;
+ }
}
}
}
diff --git a/src/app/components/recipient/recipient.component.ts b/src/app/components/recipient/recipient.component.ts
index c32d79d242e..e9cfa45765c 100644
--- a/src/app/components/recipient/recipient.component.ts
+++ b/src/app/components/recipient/recipient.component.ts
@@ -52,6 +52,7 @@ import { Keyboard } from '@capacitor/keyboard';
export class RecipientComponent implements OnInit {
public search: string = '';
public amount: string = '';
+ public amountResult: number = 0;
navParamsData: any;
public isCordova: boolean;
public expression;
@@ -78,6 +79,7 @@ export class RecipientComponent implements OnInit {
public searchValue: string;
validAddress = false;
validAmount = false;
+ validMessage = false;
isSelectedTotalAmout: boolean = false;
remaining: number;
isShowReceiveLotus: boolean;
@@ -86,6 +88,8 @@ export class RecipientComponent implements OnInit {
formatRemaining: string;
messagesReceiveLotus: boolean = false;
+ message='';
+ Buffer = Buffer;
@Input()
recipient: RecipientModel;
@@ -113,8 +117,12 @@ export class RecipientComponent implements OnInit {
@Input()
isDonation?: boolean;
- @Output() deleteEvent? = new EventEmitter
();
- @Output() sendMaxEvent? = new EventEmitter();
+ @Input()
+ isShowMessage?: boolean;
+
+ @Output() deleteEvent?= new EventEmitter();
+ @Output() sendMaxEvent?= new EventEmitter();
+
@Output() sendOfficialInfo? = new EventEmitter();
private validDataTypeMap: string[] = [
'BitcoinAddress',
@@ -234,6 +242,17 @@ export class RecipientComponent implements OnInit {
this.updateUnitUI(!!isToken);
}
+ public changeMessage() {
+ if (this.message.trim().length > 0) {
+ this.validMessage = true;
+ this.recipient.message = this.message;
+ this.checkRecipientValid();
+ } else {
+ this.validMessage = false;
+ this.checkRecipientValid();
+ }
+ }
+
private updateAddressHandler: any = data => {
if (data.recipientId === this.recipient.id) {
this.searchValue = data.value;
@@ -506,6 +525,7 @@ export class RecipientComponent implements OnInit {
this.recipient.amount = parseInt(amount, 10);
}
this.validAmount = result > 0;
+ this.amountResult = result;
this.checkRecipientValid();
if (isSendMax) {
this.sendMaxEvent.emit(true);
@@ -749,8 +769,9 @@ export class RecipientComponent implements OnInit {
checkRecipientValid() {
if (!this.isDonation) {
- this.recipient.isValid = this.validAddress && this.validAmount;
+ this.recipient.isValid = this.validAddress && (this.validAmount || this.validMessage);
} else {
+
if (this.isShowReceiveLotus) {
this.recipient.isValid = this.validAddress && this.validAmount;
} else {
diff --git a/src/app/components/recipient/recipient.model.ts b/src/app/components/recipient/recipient.model.ts
index 61c897dc0c2..aeea4eff0c7 100644
--- a/src/app/components/recipient/recipient.model.ts
+++ b/src/app/components/recipient/recipient.model.ts
@@ -3,6 +3,7 @@ export class RecipientModel {
public amount: number;
public amountToShow: string;
public altAmountStr: string;
+ public message?: string;
public isOfficialInfo: boolean;
public isValid?: boolean;
public id?: number = Date.now();
diff --git a/src/app/constants.ts b/src/app/constants.ts
index 45948987a91..513d9e62036 100644
--- a/src/app/constants.ts
+++ b/src/app/constants.ts
@@ -1,3 +1,36 @@
export const CARD_IAB_CONFIG =
'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,hidden=yes,clearcache=yes,hidespinner=yes,disallowoverscroll=yes,zoom=no,transitionstyle=crossdissolve';
export const DUST_AMOUNT = 546;
+export const currency = {
+ name: 'Lotus',
+ ticker: 'XPI',
+ legacyPrefix: 'bitcoincash',
+ prefixes: ['lotus'],
+ coingeckoId: 'bitcoin-cash-abc-2',
+ defaultFee: 1.01,
+ dustSats: 550,
+ etokenSats: 546,
+ cashDecimals: 6,
+ blockExplorerUrl: 'https://explorer.givelotus.org',
+ tokenExplorerUrl: 'https://explorer.be.cash',
+ blockExplorerUrlTestnet: 'https://texplorer.bitcoinabc.org',
+ tokenName: 'eToken',
+ tokenTicker: 'eToken',
+ tokenPrefixes: ['etoken'],
+ tokenIconsUrl: '', // https://tokens.bitcoin.com/32 for BCH SLP
+ txHistoryCount: 10,
+ hydrateUtxoBatchSize: 20,
+ defaultSettings: { fiatCurrency: 'usd' },
+ opReturn: {
+ opReturnPrefixHex: '6a',
+ opReturnAppPrefixLengthHex: '04',
+ opPushDataOne: '4c',
+ appPrefixesHex: {
+ eToken: '534c5000',
+ lotusChat: '02020202',
+ lotusChatEncrypted: '03030303'
+ },
+ encryptedMsgByteLimit: 206,
+ unencryptedMsgByteLimit: 215
+ }
+};
\ No newline at end of file
diff --git a/src/app/pages/pages.ts b/src/app/pages/pages.ts
index f9d0b143ffc..20480a6f7c4 100644
--- a/src/app/pages/pages.ts
+++ b/src/app/pages/pages.ts
@@ -111,8 +111,10 @@ import { TokenInforPage } from './token-info/token-info';
import { AccountsPage } from './accounts/accounts';
import { SearchContactPage } from './search/search-contact/search-contact.component';
import { SelectFlowPage } from './onboarding/select-flow/select-flow';
+import { MessageReplyComponent } from '../components/message-reply/message-reply.component';
export const PAGES = [
+ MessageReplyComponent,
AddPage,
AddWalletPage,
AmountPage,
diff --git a/src/app/pages/send/confirm/confirm.html b/src/app/pages/send/confirm/confirm.html
index 2c26f6f90f7..a88267827e0 100644
--- a/src/app/pages/send/confirm/confirm.html
+++ b/src/app/pages/send/confirm/confirm.html
@@ -70,7 +70,7 @@
·
-
+
{{tx.txp[wallet.id].feeRatePerStr}}
of total amount
@@ -200,6 +200,21 @@
+
+
+
+ Message
+
+
+
+
+
+ {{tx.messageOnChainToShow}}
+
+
+
+
+
diff --git a/src/app/pages/send/confirm/confirm.scss b/src/app/pages/send/confirm/confirm.scss
index a6b4fa86c16..111c40249b1 100644
--- a/src/app/pages/send/confirm/confirm.scss
+++ b/src/app/pages/send/confirm/confirm.scss
@@ -72,6 +72,15 @@ page-confirm {
}
}
+ .warning-ico {
+ margin: unset;
+ padding: unset;
+ }
+
+ .label-message-onchain {
+ align-self: flex-start;
+ }
+
.padded-input {
padding-top: 10px;
padding-bottom: 15px;
@@ -376,6 +385,9 @@ page-confirm {
.address-note-custom {
display: flex;
letter-spacing: 0.4px;
+ .onchain-message-txt {
+ color: #001E2E !important;
+ }
}
.input-contact-custom {
diff --git a/src/app/pages/send/confirm/confirm.ts b/src/app/pages/send/confirm/confirm.ts
index 7bc1a56cfaf..8348e23ef86 100644
--- a/src/app/pages/send/confirm/confirm.ts
+++ b/src/app/pages/send/confirm/confirm.ts
@@ -33,14 +33,18 @@ import {
TransactionProposal,
WalletProvider
} from '../../../providers/wallet/wallet';
-import { IonRouterOutlet, ModalController, NavController, NavParams } from '@ionic/angular';
+import {
+ IonRouterOutlet,
+ ModalController,
+ NavController,
+ NavParams
+} from '@ionic/angular';
import { EventManagerService } from 'src/app/providers/event-manager.service';
import { Router } from '@angular/router';
import { ChooseFeeLevelModal } from '../../choose-fee-level/choose-fee-level';
-import { FinishModalPage } from '../../finish/finish';
-import { PreviousRouteService } from 'src/app/providers/previous-route/previous-route';
import { LoadingProvider } from 'src/app/providers/loading/loading';
-import { EventsService } from 'src/app/providers/events.service';
+import { OnchainMessageProvider } from 'src/app/providers';
+import { DUST_AMOUNT } from 'src/app/constants';
@Component({
selector: 'page-confirm',
templateUrl: 'confirm.html',
@@ -165,14 +169,21 @@ export class ConfirmPage {
private location: Location,
private routerOutlet: IonRouterOutlet,
private loadingProvider: LoadingProvider,
- private eventsService: EventsService
+ private onchainMessageProvider: OnchainMessageProvider
) {
if (this.router.getCurrentNavigation()) {
- this.navParamsData = this.router.getCurrentNavigation().extras.state ? this.router.getCurrentNavigation().extras.state : {};
+ this.navParamsData = this.router.getCurrentNavigation().extras.state
+ ? this.router.getCurrentNavigation().extras.state
+ : {};
} else {
this.navParamsData = history ? history.state : {};
}
- if (_.isEmpty(this.navParamsData) && this.navParams && !_.isEmpty(this.navParams.data)) this.navParamsData = this.navParams.data;
+ if (
+ _.isEmpty(this.navParamsData) &&
+ this.navParams &&
+ !_.isEmpty(this.navParams.data)
+ )
+ this.navParamsData = this.navParams.data;
this.wallet = this.profileProvider.getWallet(this.navParamsData.walletId);
this.isDonation = this.navParamsData.isDonation;
@@ -232,7 +243,7 @@ export class ConfirmPage {
this.routerOutlet.swipeGesture = true;
}
- loadInit() {
+ async loadInit() {
this.logger.info('Loaded: ConfirmPage');
this.routerOutlet.swipeGesture = false;
this.isOpenSelector = false;
@@ -248,12 +259,10 @@ export class ConfirmPage {
amount = this.navParamsData.amount
? this.navParamsData.amount
: this.navParamsData.totalInputsAmount;
- }
- else if (this.navParamsData.isSentXecToEtoken) {
+ } else if (this.navParamsData.isSentXecToEtoken) {
networkName = this.navParamsData.network;
amount = this.navParamsData.amount;
- }
- else {
+ } else {
amount = this.navParamsData.amount;
try {
networkName = this.addressProvider.getCoinAndNetwork(
@@ -282,7 +291,30 @@ export class ConfirmPage {
return;
}
}
-
+ let messageEncrypted = null;
+ if (
+ !!(
+ this.navParamsData &&
+ this.navParamsData.messageOnChain &&
+ this.navParamsData.messageOnChain.trim().length > 0
+ )
+ ) {
+ const result = await this.walletProvider.getMnemonicAndPassword(
+ this.wallet
+ );
+ try{
+ messageEncrypted =
+ await this.onchainMessageProvider.processEncryptMessageOnchain(
+ this.navParamsData.messageOnChain,
+ this.wallet,
+ result.mnemonic,
+ this.navParamsData.toAddress
+ );
+ } catch(e){
+ this.showErrorInfoSheet(e);
+ this.location.back();
+ }
+ }
this.tx = {
toAddress: this.navParamsData.toAddress,
description: this.navParamsData.description,
@@ -291,7 +323,8 @@ export class ConfirmPage {
invoiceID: this.navParamsData.invoiceID, // xrp
payProUrl: this.navParamsData.payProUrl,
spendUnconfirmed: this.config.wallet.spendUnconfirmed,
-
+ messageOnChain: !!messageEncrypted ? Array.from(messageEncrypted) : null,
+ messageOnChainToShow: this.navParamsData.messageOnChain,
// Vanity tx info (not in the real tx)
recipientType: this.navParamsData.recipientType,
name: this.navParamsData.name,
@@ -318,15 +351,15 @@ export class ConfirmPage {
? 0
: this.tx.coin == 'eth' ||
this.currencyProvider.isERCToken(this.tx.coin)
- ? Number(amount)
- : parseInt(amount, 10);
+ ? Number(amount)
+ : parseInt(amount, 10);
this.tx.origToAddress = this.tx.toAddress;
if (this.navParamsData.requiredFeeRate) {
this.usingMerchantFee = true;
- this.tx.feeRate = this.requiredFeeRate = +this.navParamsData
- .requiredFeeRate;
+ this.tx.feeRate = this.requiredFeeRate =
+ +this.navParamsData.requiredFeeRate;
} else if (this.isSpeedUpTx) {
this.usingCustomFee = true;
this.tx.feeLevel =
@@ -510,7 +543,8 @@ export class ConfirmPage {
this.wallet.credentials.multisigEthInfo &&
this.wallet.credentials.multisigEthInfo.multisigContractAddress
) {
- this.tx.multisigContractAddress = this.wallet.credentials.multisigEthInfo.multisigContractAddress;
+ this.tx.multisigContractAddress =
+ this.wallet.credentials.multisigEthInfo.multisigContractAddress;
}
this.setButtonText(
@@ -571,7 +605,7 @@ export class ConfirmPage {
return (
this.wallet.cachedStatus &&
this.wallet.cachedStatus.balance.totalAmount >=
- this.tx.amount + this.tx.feeRate &&
+ this.tx.amount + this.tx.feeRate &&
!this.tx.spendUnconfirmed
);
}
@@ -853,21 +887,20 @@ export class ConfirmPage {
}
showHighFeeSheet() {
- const minerFeeWarning = this.actionSheetProvider.createMinerFeeWarningComponent();
+ const minerFeeWarning =
+ this.actionSheetProvider.createMinerFeeWarningComponent();
minerFeeWarning.present({ maxHeight: '100%', minHeight: '100%' });
}
showTotalAmountSheet() {
- const totalAmountFeeInfoSheet = this.actionSheetProvider.createInfoSheet(
- 'total-amount'
- );
+ const totalAmountFeeInfoSheet =
+ this.actionSheetProvider.createInfoSheet('total-amount');
totalAmountFeeInfoSheet.present();
}
showSubtotalAmountSheet() {
- const subtotalAmountFeeInfoSheet = this.actionSheetProvider.createInfoSheet(
- 'subtotal-amount'
- );
+ const subtotalAmountFeeInfoSheet =
+ this.actionSheetProvider.createInfoSheet('subtotal-amount');
subtotalAmountFeeInfoSheet.present();
}
@@ -915,8 +948,8 @@ export class ConfirmPage {
if (!this.minAllowedGasLimit)
this.minAllowedGasLimit = this.tx.txp[wallet.id].gasLimit;
}
-
- if (txp.feeTooHigh) {
+ txp.messageOnChain = tx.messageOnChain;
+ if (txp.feeTooHigh && ((!this.navParamsData.messageOnChain && !txp.messageOnChain) || (txp.messageOnChain && txp.amount !== DUST_AMOUNT))) {
this.showHighFeeSheet();
}
@@ -937,9 +970,9 @@ export class ConfirmPage {
this.tx = tx;
this.logger.debug(
'Confirm. TX Fully Updated for wallet:' +
- wallet.id +
- ' Txp:' +
- txp.id
+ wallet.id +
+ ' Txp:' +
+ txp.id
);
this.getTotalAmountDetails(tx, wallet);
@@ -1162,7 +1195,7 @@ export class ConfirmPage {
}
txp.message = tx.description;
-
+ txp.messageOnChain = tx.messageOnChain;
if (tx.paypro) {
txp.payProUrl = tx.payProUrl;
tx.paypro.host = new URL(tx.payProUrl).host;
@@ -1273,8 +1306,7 @@ export class ConfirmPage {
.Transactions.get({ chain: 'ETHMULTISIG' })
.instantiateEncodeData({
addresses: this.navParamsData.multisigAddresses,
- requiredConfirmations: this.navParamsData
- .requiredConfirmations,
+ requiredConfirmations: this.navParamsData.requiredConfirmations,
multisigGnosisContractAddress: tx.multisigContractAddress,
dailyLimit: 0
});
@@ -1331,10 +1363,11 @@ export class ConfirmPage {
sender: txp.from,
txId: txp.txid
};
- multisigContractInstantiationInfo = await this.walletProvider.getMultisigContractInstantiationInfo(
- this.wallet,
- opts
- );
+ multisigContractInstantiationInfo =
+ await this.walletProvider.getMultisigContractInstantiationInfo(
+ this.wallet,
+ opts
+ );
if (multisigContractInstantiationInfo.length > 0) {
const multisigContract = multisigContractInstantiationInfo.filter(
multisigContract => {
@@ -1403,13 +1436,14 @@ export class ConfirmPage {
}
private showInsufficientFundsInfoSheet(): void {
- const insufficientFundsInfoSheet = this.actionSheetProvider.createInfoSheet(
- 'insufficient-funds'
- );
+ const insufficientFundsInfoSheet =
+ this.actionSheetProvider.createInfoSheet('insufficient-funds');
insufficientFundsInfoSheet.present();
insufficientFundsInfoSheet.onDidDismiss(option => {
if (option || typeof option === 'undefined') {
- this.fromWalletDetails ? this.location.back() : this.router.navigate(['']);
+ this.fromWalletDetails
+ ? this.location.back()
+ : this.router.navigate(['']);
} else {
this.tx.sendMax = true;
this.setWallet(this.wallet);
@@ -1453,7 +1487,9 @@ export class ConfirmPage {
return;
}
if (exit) {
- this.fromWalletDetails ? this.location.back() : this.router.navigate(['']);
+ this.fromWalletDetails
+ ? this.location.back()
+ : this.router.navigate(['']);
}
});
}
@@ -1484,11 +1520,11 @@ export class ConfirmPage {
if (error.toString().includes('500 - "{}"')) {
msg = this.tx.paypro
? this.translate.instant(
- 'There is a temporary problem with the merchant requesting the payment. Please try later'
- )
+ 'There is a temporary problem with the merchant requesting the payment. Please try later'
+ )
: this.translate.instant(
- 'Error 500 - There is a temporary problem, please try again later.'
- );
+ 'Error 500 - There is a temporary problem, please try again later.'
+ );
}
const infoSheetTitle = title ? title : this.translate.instant('Error');
@@ -1500,7 +1536,7 @@ export class ConfirmPage {
if (exit) {
this.fromWalletDetails
? // PopTo AmountPage case
- this.location.back()
+ this.location.back()
: this.router.navigate([''], { replaceUrl: true });
}
}
@@ -1516,7 +1552,7 @@ export class ConfirmPage {
else this.setWallet(option);
}
- public approve(tx, wallet): Promise {
+ public approve(tx, wallet): any {
if (!tx || (!wallet && !this.coinbaseAccount)) return undefined;
if (this.nameContact && this.nameContact.trim().length > 0) {
this.ab
@@ -1840,9 +1876,8 @@ export class ConfirmPage {
title: this.walletSelectorTitle,
coinbaseData
};
- const walletSelector = this.actionSheetProvider.createWalletSelector(
- params
- );
+ const walletSelector =
+ this.actionSheetProvider.createWalletSelector(params);
walletSelector.present();
walletSelector.onDidDismiss(option => {
this.onSelectWalletEvent(option);
@@ -1882,9 +1917,8 @@ export class ConfirmPage {
}
private showCustomFeeWarningSheet(data) {
- const warningSheet = this.actionSheetProvider.createInfoSheet(
- 'custom-fee-warning'
- );
+ const warningSheet =
+ this.actionSheetProvider.createInfoSheet('custom-fee-warning');
warningSheet.present();
warningSheet.onDidDismiss(option => {
option ? this.chooseFeeLevel() : this.onFeeModalDismiss(data);
@@ -1907,9 +1941,8 @@ export class ConfirmPage {
public setGasLimit(): void {
this.editGasLimit = !this.editGasLimit;
- this.tx.gasLimit = this.tx.txp[
- this.wallet.id
- ].gasLimit = this.customGasLimit;
+ this.tx.gasLimit = this.tx.txp[this.wallet.id].gasLimit =
+ this.customGasLimit;
const data = {
newFeeLevel: 'custom',
diff --git a/src/app/pages/send/send.html b/src/app/pages/send/send.html
index e9a8a0b1cfc..5e1f3e8d89b 100644
--- a/src/app/pages/send/send.html
+++ b/src/app/pages/send/send.html
@@ -75,7 +75,7 @@
+ [isShowDelete]="isShowDelete" [token]="token" (sendMaxEvent)="sendMax($event)" [isShowMessage]="isShowMessage && wallet.coin === 'xpi'" (sendOfficialInfo)="handleOfficialInfo($event)">
diff --git a/src/app/pages/send/send.ts b/src/app/pages/send/send.ts
index 662fbd1c6c5..fc215d7e44a 100644
--- a/src/app/pages/send/send.ts
+++ b/src/app/pages/send/send.ts
@@ -32,6 +32,7 @@ import { PageDto, PageModel } from 'src/app/providers/lixi-lotus/lixi-lotus';
import { EventsService } from 'src/app/providers/events.service';
import { Location } from '@angular/common';
+import { DUST_AMOUNT } from 'src/app/constants';
@Component({
@@ -71,6 +72,7 @@ export class SendPage {
walletId: string;
isShowSendMax: boolean = true;
isShowDelete: boolean = false;
+ isShowMessage: boolean = false;
toAddress: string = '';
formatRemaining: string;
recipientNotInit: RecipientModel;
@@ -149,6 +151,9 @@ export class SendPage {
this.onResumeSubscription = this.plt.resume.subscribe(() => {
this.setDataFromClipboard();
});
+ if(this.wallet.coin === 'xpi' && this.wallet.cachedStatus.wallet.singleAddress){
+ this.isShowMessage = true;
+ }
}
async handleScrolling(event) {
@@ -319,6 +324,9 @@ export class SendPage {
}))
this.isShowSendMax = this.listRecipient.length === 1;
this.isShowDelete = this.listRecipient.length > 1;
+ if(this.wallet.coin === 'xpi' && this.wallet.cachedStatus.wallet.singleAddress){
+ this.isShowMessage = this.listRecipient.length === 1;
+ }
this.content.scrollToBottom(1000);
}
@@ -326,6 +334,7 @@ export class SendPage {
this.listRecipient = this.listRecipient.filter(s => s.id !== id);
this.isShowSendMax = this.listRecipient.length === 1;
this.isShowDelete = this.listRecipient.length > 1;
+ this.isShowMessage = this.listRecipient.length === 1;
}
private goToConfirmToken(isSendMax?: boolean) {
@@ -370,6 +379,10 @@ export class SendPage {
if (this.isDonation) return this.goToConfirmDonation();
if (this.listRecipient.length === 1) {
const recipient = this.listRecipient[0];
+ if(!recipient.amount || recipient.amount === 0){
+ recipient.amount = DUST_AMOUNT;
+ }
+
this.router.navigate(['/confirm'], {
state: {
walletId: this.wallet.credentials.walletId,
@@ -383,7 +396,8 @@ export class SendPage {
name: recipient.name,
fromWalletDetails: true,
isSentXecToEtoken: recipient.isSentXecToEtoken,
- isSendFromHome: this.isSendFromHome
+ isSendFromHome: this.isSendFromHome,
+ messageOnChain: recipient.message
}
});
} else {
diff --git a/src/app/pages/tx-details/tx-details.html b/src/app/pages/tx-details/tx-details.html
index a673a30322f..c86cad45856 100644
--- a/src/app/pages/tx-details/tx-details.html
+++ b/src/app/pages/tx-details/tx-details.html
@@ -199,6 +199,25 @@
+
+
+ {{'Message' | translate}}
+
+
+
+ {{messageOnchain}}
+ 0 && btx.action == 'received'">
+
+
+ {{'Reply' | translate}}
+
+
+
+
+
+
+
{{'Memo' | translate}}
diff --git a/src/app/pages/tx-details/tx-details.scss b/src/app/pages/tx-details/tx-details.scss
index 58949e4b49b..86a37cc97bf 100644
--- a/src/app/pages/tx-details/tx-details.scss
+++ b/src/app/pages/tx-details/tx-details.scss
@@ -282,6 +282,21 @@ page-tx-details {
align-items: center;
}
+ .message-custom {
+ ion-label {
+ align-self: flex-start !important;
+ }
+
+ ion-note {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ }
+ .message-onchain {
+ margin: 0;
+ }
+ }
+
.wallet,
.multi-recip-title {
span {
@@ -481,4 +496,9 @@ page-tx-details {
font-weight: 600;
height: 2.5rem;
}
+
+ .btn-reply {
+ --padding-start: 0;
+ --padding-end: 0;
+ }
}
diff --git a/src/app/pages/tx-details/tx-details.ts b/src/app/pages/tx-details/tx-details.ts
index c802d596aad..1f2095a9c79 100644
--- a/src/app/pages/tx-details/tx-details.ts
+++ b/src/app/pages/tx-details/tx-details.ts
@@ -20,17 +20,21 @@ import { EventManagerService } from 'src/app/providers/event-manager.service';
import { ModalController, NavController, NavParams } from '@ionic/angular';
import { Location } from '@angular/common';
import { PersistenceProvider } from 'src/app/providers/persistence/persistence';
-import { AddressBookProvider, AppProvider, TokenProvider } from 'src/app/providers';
+import {
+ AddressBookProvider,
+ AppProvider,
+ TokenProvider
+} from 'src/app/providers';
import { Router } from '@angular/router';
import { DecimalFormatBalance } from 'src/app/providers/decimal-format.ts/decimal-format';
import { Token } from 'src/app/models/tokens/tokens.model';
export interface TokenData {
- amountToken: string,
- tokenId: string,
- symbolToken: string,
- name: string,
- addressToShow: string
+ amountToken: string;
+ tokenId: string;
+ symbolToken: string;
+ name: string;
+ addressToShow: string;
}
@Component({
@@ -39,9 +43,9 @@ export interface TokenData {
styleUrls: ['tx-details.scss'],
encapsulation: ViewEncapsulation.None
})
-
export class TxDetailsModal {
private txId: string;
+ public messageOnchain: string = '';
private config;
private blockexplorerUrl: string;
private blockexplorerUrlTestnet: string;
@@ -62,7 +66,6 @@ export class TxDetailsModal {
public isNegative: boolean;
public currentTheme;
public fiatRateStrToken;
-
public addressbook = [];
constructor(
@@ -88,14 +91,17 @@ export class TxDetailsModal {
private appProvider: AppProvider,
private router: Router,
private tokenProvider: TokenProvider,
- private addressbookProvider: AddressBookProvider,
- ) { }
-
+ private addressbookProvider: AddressBookProvider
+ ) {}
+
ngOnInit() {
this.events.subscribe('bwsEvent', this.bwsEventHandler);
this.config = this.configProvider.get();
this.currentTheme = this.appProvider.themeProvider.currentAppTheme;
this.txId = this.navParams.data.txid;
+ if (this.navParams.data && this.navParams.data.messageOnchain) {
+ this.messageOnchain = this.navParams.data.messageOnchain;
+ }
this.title = this.translate.instant('Transaction');
this.wallet = this.profileProvider.getWallet(this.navParams.data.walletId);
this.tokenData = this.navParams.data.tokenData;
@@ -186,6 +192,10 @@ export class TxDetailsModal {
});
}
+ public showReplyMessageModal() {
+ this.viewCtrl.dismiss(true);
+ }
+
private initActionList(): void {
this.actionList = [];
if (
@@ -238,7 +248,7 @@ export class TxDetailsModal {
leading: true
}
);
-
+
async updateInputAddress(txId: string) {
let inputAddresses = [];
try {
@@ -254,14 +264,17 @@ export class TxDetailsModal {
try {
const walletId = this.navParams.data.walletId;
const history = await this.walletProvider.getSavedTxs(walletId);
- if (!history) return ;
+ if (!history) return;
const historyByTxId = _.find(history, item => item.txid == txid);
if (historyByTxId) {
historyByTxId.inputAddresses = inputAddresses;
const historyToSave = JSON.stringify(history);
- return await this.persistenceProvider.setTxHistory(walletId, historyToSave);
+ return await this.persistenceProvider.setTxHistory(
+ walletId,
+ historyToSave
+ );
}
- } catch (error) { }
+ } catch (error) {}
}
private updateTx(opts?): void {
@@ -272,7 +285,6 @@ export class TxDetailsModal {
.then(async tx => {
this.retryGetTx = 0;
if (!opts.hideLoading) this.onGoingProcess.clear();
-
this.btx = this.txFormatProvider.processTx(this.wallet.coin, tx);
this.btx.network = this.wallet.credentials.network;
this.btx.coin = this.wallet.coin;
@@ -307,27 +319,26 @@ export class TxDetailsModal {
}
if (this.btx.action != 'invalid') {
-
if (this.btx.isGenesis) {
this.title = this.translate.instant('Genesis');
} else {
- if (this.btx.action == 'sent'){
+ if (this.btx.action == 'sent') {
this.title = this.translate.instant('Sent');
this.isNegative = true;
}
- if (this.btx.action == 'received'){
+ if (this.btx.action == 'received') {
this.title = this.translate.instant('Received');
this.isNegative = false;
}
- if (this.btx.action == 'moved'){
+ if (this.btx.action == 'moved') {
this.title = this.translate.instant('Sent to self');
this.isNegative = false;
}
- if (this.btx.action == 'immature'){
+ if (this.btx.action == 'immature') {
this.title = this.translate.instant('Immature');
this.isNegative = false;
}
- if (this.btx.action == 'mined'){
+ if (this.btx.action == 'mined') {
this.title = this.translate.instant('Mined');
this.isNegative = false;
}
@@ -338,7 +349,7 @@ export class TxDetailsModal {
this.initActionList();
this.updateFiatRate();
- if(this.token){
+ if (this.token) {
this.getFiatRateStrToken();
}
if (this.currencyProvider.isUtxoCoin(this.wallet.coin)) {
@@ -385,19 +396,26 @@ export class TxDetailsModal {
tokenInfo: {
symbol: this.btx?.symbolToken
}
- }
- const alternativeBalanceToken = this.tokenProvider.getAlternativeBalanceToken(token, this.wallet);
- let rate = this.rateProvider.getRate(this.wallet.cachedStatus.alternativeIsoCode, token.tokenInfo.symbol);
+ };
+ const alternativeBalanceToken =
+ this.tokenProvider.getAlternativeBalanceToken(token, this.wallet);
+ let rate = this.rateProvider.getRate(
+ this.wallet.cachedStatus.alternativeIsoCode,
+ token.tokenInfo.symbol
+ );
if (!rate) {
rate = 0;
}
- this.fiatRateStrToken =
- DecimalFormatBalance(alternativeBalanceToken) +
- ' ' +
- this.wallet.cachedStatus.alternativeIsoCode +
- ' @ ' + DecimalFormatBalance(rate) + ' ' +
- this.wallet.cachedStatus.alternativeIsoCode + ' per ' +
- token.tokenInfo.symbol.toUpperCase();
+ this.fiatRateStrToken =
+ DecimalFormatBalance(alternativeBalanceToken) +
+ ' ' +
+ this.wallet.cachedStatus.alternativeIsoCode +
+ ' @ ' +
+ DecimalFormatBalance(rate) +
+ ' ' +
+ this.wallet.cachedStatus.alternativeIsoCode +
+ ' per ' +
+ token.tokenInfo.symbol.toUpperCase();
}
public async saveMemoInfo(): Promise {
@@ -462,6 +480,10 @@ export class TxDetailsModal {
}
}
+ public handleClick(btx) {
+ this.viewCtrl.dismiss(true);
+ }
+
public openExternalLink(url: string): void {
const optIn = true;
const title = null;
@@ -494,7 +516,8 @@ export class TxDetailsModal {
this.getFiatStr(fiat) +
' ' +
settings.alternativeIsoCode +
- ' @ ' + this.getAlternativeIsoCode(fiat) +
+ ' @ ' +
+ this.getAlternativeIsoCode(fiat) +
` ${settings.alternativeIsoCode} per ` +
this.wallet.coin.toUpperCase();
} else {
@@ -504,13 +527,21 @@ export class TxDetailsModal {
}
getAlternativeIsoCode(fiat) {
- return this.btx.coin == 'xpi' || this.btx.coin == 'xec' ? parseFloat(fiat.rate).toFixed(6).toString() : this.filter.formatFiatAmount(fiat.rate);
+ return this.btx.coin == 'xpi' || this.btx.coin == 'xec'
+ ? parseFloat(fiat.rate).toFixed(6).toString()
+ : this.filter.formatFiatAmount(fiat.rate);
}
getFiatStr(fiat) {
return this.btx.coin == 'xpi' || this.btx.coin == 'xec'
- ? parseFloat((fiat.rate * this.btx.amountValueStr.replace(',', '')).toFixed(4)).toString()
- : this.filter.formatFiatAmount(parseFloat((fiat.rate * this.btx.amountValueStr.replace(',', '')).toFixed(2)));
+ ? parseFloat(
+ (fiat.rate * this.btx.amountValueStr.replace(',', '')).toFixed(4)
+ ).toString()
+ : this.filter.formatFiatAmount(
+ parseFloat(
+ (fiat.rate * this.btx.amountValueStr.replace(',', '')).toFixed(2)
+ )
+ );
}
close() {
@@ -522,7 +553,14 @@ export class TxDetailsModal {
this.router.navigate(['/send-page'], {
state: {
walletId: this.wallet.id,
- toAddress: btx.address || (btx.addressTo && btx.addressTo !== 'false') ? btx.addressTo : false || (this.tokenData && this.tokenData.addressToShow !== 'false' ? this.tokenData.addressToShow : btx.inputAddresses[0]) || btx.inputAddresses[0],
+ toAddress:
+ btx.address || (btx.addressTo && btx.addressTo !== 'false')
+ ? btx.addressTo
+ : false ||
+ (this.tokenData && this.tokenData.addressToShow !== 'false'
+ ? this.tokenData.addressToShow
+ : btx.inputAddresses[0]) ||
+ btx.inputAddresses[0],
token: this.token
}
});
diff --git a/src/app/pages/wallet-details/wallet-details.html b/src/app/pages/wallet-details/wallet-details.html
index 2c53c4315fe..31da40c0d1c 100644
--- a/src/app/pages/wallet-details/wallet-details.html
+++ b/src/app/pages/wallet-details/wallet-details.html
@@ -233,10 +233,10 @@
-
-
+
{{'To:
+ *ngIf="(!tx.note || (tx.note && !tx.note.body)) && (!addressbook || !tx.outputs[0] || !getContactName(tx.outputs[0].address)) && (!tx.customData || !tx.customData.toWalletName) && tx.addressTo !== 'false'">{{'To:
{address}' | translate: {address: tx.addressTo.slice(-8)} }}
+ {{'To:
+ {address}' | translate: {address: tx.outputs[0].address.slice(-8)} }}
{{ 'To: {walletName}' | translate: {walletName: tx.customData.toWalletName} | translate}}
@@ -292,9 +295,11 @@
{{tx.message}}
+
+ {{tx.messageOnchain}}
-
+
{{'Pending'| translate}}
@@ -307,6 +312,7 @@
Sent to self
{{'Burned'| translate}}
+
0">
Genesis
Mined
Immature
@@ -315,6 +321,10 @@
amTimeAgo}}
{{tx.time * 1000 |
amDateFormat:'MM/DD/YYYY'}}
+
+
+ {{'Reply' | translate}}
+
diff --git a/src/app/pages/wallet-details/wallet-details.scss b/src/app/pages/wallet-details/wallet-details.scss
index df1e90233c2..90b978cbc59 100644
--- a/src/app/pages/wallet-details/wallet-details.scss
+++ b/src/app/pages/wallet-details/wallet-details.scss
@@ -253,6 +253,26 @@ page-wallet-details {
.item-icon {
margin-right: 15px;
}
+ ion-note {
+ &.note-onchain-message {
+ height: 100%;
+ .tx-info {
+ height: 100%;
+ justify-content: center;
+ }
+ .status-onchain-white {
+ background: transparent;
+ }
+ }
+ &.onchain-message-received {
+ .tx-info {
+ justify-content: space-between;
+ p {
+ margin: 0;
+ }
+ }
+ }
+ }
}
img {
@@ -269,6 +289,8 @@ page-wallet-details {
margin-right: 2rem;
.memo {
font-size: 14px;
+ line-height: 20px;
+ letter-spacing: 0.25px;
color: rgba(0, 30, 46, 0.6);
display: -webkit-box;
-webkit-line-clamp: 2;
@@ -307,6 +329,11 @@ page-wallet-details {
letter-spacing: 0.4px;
}
+ .tx-action-custom {
+ font-size: 14px;
+ letter-spacing: 0.4px;
+ }
+
.proposal-container {
--inner-padding-end: 0;
--inner-padding-top: 0;
@@ -540,6 +567,11 @@ page-wallet-details {
.date {
margin: 0;
font-size: 12px;
+ &.btn-reply {
+ --color: var(--color-light-on-background-theme);
+ --padding-start: 0;
+ --padding-end: 0;
+ }
}
}
diff --git a/src/app/pages/wallet-details/wallet-details.ts b/src/app/pages/wallet-details/wallet-details.ts
index e11582be5d5..dc210491238 100644
--- a/src/app/pages/wallet-details/wallet-details.ts
+++ b/src/app/pages/wallet-details/wallet-details.ts
@@ -33,11 +33,17 @@ import { WalletProvider } from '../../providers/wallet/wallet';
import { TxDetailsModal } from '../../pages/tx-details/tx-details';
import { SearchTxModalPage } from './search-tx-modal/search-tx-modal';
import { WalletBalanceModal } from './wallet-balance/wallet-balance';
-import { LoadingController, ModalController, Platform, ToastController } from '@ionic/angular';
+import {
+ LoadingController,
+ ModalController,
+ Platform,
+ ToastController
+} from '@ionic/angular';
import { Router } from '@angular/router';
import { Location } from '@angular/common';
import { NgxQrcodeErrorCorrectionLevels } from '@techiediaries/ngx-qrcode';
import { EventsService } from 'src/app/providers/events.service';
+import { DUST_AMOUNT } from 'src/app/constants';
const HISTORY_SHOW_LIMIT = 10;
const MIN_UPDATE_TIME = 2000;
const TIMEOUT_FOR_REFRESHER = 1000;
@@ -115,7 +121,6 @@ export class WalletDetailsPage {
private actionSheetProvider: ActionSheetProvider,
private platform: Platform,
private profileProvider: ProfileProvider,
- private viewCtrl: ModalController,
public platformProvider: PlatformProvider,
private socialSharing: SocialSharing,
private bwcErrorProvider: BwcErrorProvider,
@@ -331,18 +336,24 @@ export class WalletDetailsPage {
}
private handleTxAddressEcash() {
- this.history.forEach((tx) => {
+ this.history.forEach(tx => {
if (tx.action == 'received' && !tx?.tokenId) {
const addressToken = tx.inputAddresses[0] || null;
if (addressToken) {
- const { prefix, type, hash } = this.addressProvider.decodeAddress(addressToken);
- const eCashAddess = this.addressProvider.encodeAddress('ecash', type, hash, addressToken);
+ const { prefix, type, hash } =
+ this.addressProvider.decodeAddress(addressToken);
+ const eCashAddess = this.addressProvider.encodeAddress(
+ 'ecash',
+ type,
+ hash,
+ addressToken
+ );
tx.inputAddresses[0] = eCashAddess;
}
}
- })
+ });
}
-
+
private async showHistory(loading?: boolean) {
if (!this.wallet.completeHistory) {
return;
@@ -351,12 +362,115 @@ export class WalletDetailsPage {
0,
(this.currentPage + 1) * HISTORY_SHOW_LIMIT
);
+ if (
+ this.wallet.coin === 'xpi' &&
+ this.wallet.cachedStatus.wallet.singleAddress
+ ) {
+ if (this.history && this.history.length > 0) {
+ for (let index = 0; index < this.history.length; index++) {
+ const tx = this.history[index];
+ this.history[index].outputs = tx.outputs.filter(
+ output => output.address !== 'false'
+ );
+ }
+ }
+ }
if (this.wallet.coin == 'xec') this.handleTxAddressEcash();
this.zone.run(() => {
this.groupedHistory = this.groupHistory(this.history);
});
if (loading) this.currentPage++;
}
+ handleClick(tx) {
+ this.showReplyMessageModal(tx);
+ }
+
+ getAddressFrom(tx): any {
+ if (
+ !this.addressbook ||
+ !tx.inputAddresses[0] ||
+ !this.getContactName(tx.inputAddresses[0])
+ ) {
+ return {
+ name: tx.inputAddresses[0].slice(-8),
+ address: tx.inputAddresses[0],
+ type: ''
+ };
+ } else
+ return {
+ name: this.getContactName(tx.inputAddresses[0]),
+ address: tx.inputAddresses[0],
+ type: 'contact'
+ };
+ }
+
+ getAddressTo(tx): any {
+ if (
+ (!tx.note || (tx.note && !tx.note.body)) &&
+ (!this.addressbook ||
+ !tx.outputs[0] ||
+ !this.getContactName(tx.outputs[0].address)) &&
+ (!tx.customData || !tx.customData.toWalletName)
+ ) {
+ return {
+ name: tx.addressTo.slice(-8),
+ address: tx.addressTo,
+ type: ''
+ };
+ } else if (
+ (!tx.note || (tx.note && !tx.note.body)) &&
+ (!this.addressbook ||
+ !tx.outputs[0] ||
+ !this.getContactName(tx.outputs[0].address)) &&
+ tx.customData &&
+ tx.customData.toWalletName
+ ) {
+ return {
+ name: tx.customData.toWalletName,
+ address: tx.outputs[0].address,
+ type: 'wallet'
+ };
+ } else
+ return {
+ name: this.getContactName(tx.outputs[0].address),
+ address: tx.outputs[0].address,
+ type: 'contact'
+ };
+ }
+
+ showReplyMessageModal(tx) {
+ const txInfo = this.getAddressFrom(tx);
+ const addContactModal =
+ this.actionSheetProvider.createMessageReplyComponent({
+ addressTo: this.getAddressFrom(tx).name,
+ messageOnChain: tx.messageOnchain
+ });
+ addContactModal.present({ maxHeight: '48%%', minHeight: '48%%' });
+ addContactModal.onDidDismiss(rs => {
+ if (rs) {
+ this.sendReplyMessage(rs, txInfo);
+ }
+ });
+ }
+
+ sendReplyMessage(rs, txInfo) {
+ this.router.navigate(['/confirm'], {
+ state: {
+ walletId: this.wallet.credentials.walletId,
+ recipientType: txInfo.type,
+ amount: DUST_AMOUNT,
+ currency: this.wallet.coin,
+ coin: this.wallet.coin,
+ network: this.wallet.network,
+ useSendMax: false,
+ toAddress: txInfo.address,
+ name: txInfo.type === 'contact' ? txInfo.name : null,
+ fromWalletDetails: true,
+ isSentXecToEtoken: false,
+ messageOnChain: rs
+ }
+ });
+ }
updateAddressToShowToken(tx) {
const outputAddr = tx.outputs[0].address;
@@ -579,7 +693,12 @@ export class WalletDetailsPage {
}
};
- public itemTapped(tx) {
+ public itemTapped(tx, itemTapped) {
+ if (itemTapped.target.innerText === 'Reply') {
+ itemTapped.preventDefault();
+ itemTapped.stopPropagation();
+ return;
+ }
if (tx.hasUnconfirmedInputs) {
const infoSheet =
this.actionSheetProvider.createInfoSheet('unconfirmed-inputs');
@@ -630,18 +749,20 @@ export class WalletDetailsPage {
});
}
- public goToTxDetails(tx) {
- const txDetailModal = this.modalCtrl
- .create({
- component: TxDetailsModal,
- componentProps: {
- walletId: this.wallet.credentials.walletId,
- txid: tx.txid
- }
- })
- .then(res => {
- res.present();
- });
+ public async goToTxDetails(tx) {
+ const txDetailModal = await this.modalCtrl.create({
+ component: TxDetailsModal,
+ componentProps: {
+ walletId: this.wallet.credentials.walletId,
+ txid: tx.txid,
+ messageOnchain: tx.messageOnchain
+ }
+ });
+ txDetailModal.present();
+ const { data } = await txDetailModal.onWillDismiss();
+ if (data) {
+ this.showReplyMessageModal(tx);
+ }
}
public openBackupModal(): void {
@@ -787,7 +908,7 @@ export class WalletDetailsPage {
}
public close() {
- this.viewCtrl.dismiss();
+ this.modalCtrl.dismiss();
}
public goToReceivePage() {
diff --git a/src/app/providers/action-sheet/action-sheet.ts b/src/app/providers/action-sheet/action-sheet.ts
index 8b2e6f5b622..5460647908f 100644
--- a/src/app/providers/action-sheet/action-sheet.ts
+++ b/src/app/providers/action-sheet/action-sheet.ts
@@ -6,6 +6,7 @@ import { FooterMenuComponent } from 'src/app/components/footer-menu/footer-menu'
import { IncomingDataMenuComponent } from 'src/app/components/incoming-data-menu/incoming-data-menu';
import { InfoSheetComponent } from 'src/app/components/info-sheet/info-sheet';
import { MemoComponent } from 'src/app/components/memo-component/memo-component';
+import { MessageReplyComponent } from 'src/app/components/message-reply/message-reply.component';
import { MinerFeeWarningComponent } from 'src/app/components/miner-fee-warning/miner-fee-warning';
import { MultisignInfoComponent } from 'src/app/components/multisign-info/multisign-info.component';
import { NeedsBackupComponent } from 'src/app/components/needs-backup/needs-backup';
@@ -224,6 +225,15 @@ export class ActionSheetProvider {
.instance;
}
+ public createMessageReplyComponent(params?): MessageReplyComponent {
+ return this.setupSheet(
+ MessageReplyComponent,
+ null,
+ params
+ )
+ .instance;
+ }
+
public createMinerFeeWarningComponent(): MinerFeeWarningComponent {
return this.setupSheet(MinerFeeWarningComponent)
.instance;
diff --git a/src/app/providers/index.ts b/src/app/providers/index.ts
index f15edb985b5..8101a3f226e 100644
--- a/src/app/providers/index.ts
+++ b/src/app/providers/index.ts
@@ -75,4 +75,5 @@ export { CustomErrorHandler } from './custom-error-handler.service';
export { RedirectGuard } from './redirect.service';
export { PreviousRouteService } from './previous-route/previous-route';
export { TokenProvider } from './token-sevice/token-sevice';
+export { OnchainMessageProvider } from './onchain-message/onchain-message';
export { LoadingProvider } from './loading/loading';
\ No newline at end of file
diff --git a/src/app/providers/onchain-message/onchain-message.ts b/src/app/providers/onchain-message/onchain-message.ts
new file mode 100644
index 00000000000..00b9c9d6e8f
--- /dev/null
+++ b/src/app/providers/onchain-message/onchain-message.ts
@@ -0,0 +1,319 @@
+import { PrivateKey, PublicKey } from '@abcpros/bitcore-lib-xpi';
+import BCHJS from '@abcpros/xpi-js';
+import { Injectable } from '@angular/core';
+import { currency } from 'src/app/constants';
+import { Logger } from '../logger/logger';
+import * as forge from 'node-forge';
+
+import { BitcoreLib } from '@abcpros/crypto-wallet-core';
+import { ChronikClient, TxHistoryPage } from 'chronik-client';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class OnchainMessageProvider {
+ bchjs;
+ Bitcore = BitcoreLib;
+ PrivateKey = this.Bitcore.PrivateKey;
+ PublicKey = this.Bitcore.PublicKey;
+ crypto2 = this.Bitcore.crypto;
+
+ constructor(private logger: Logger) {
+ this.logger.debug('TokenProvider initialized');
+ this.bchjs = new BCHJS({ restURL: '' });
+ }
+
+ ngOnInit() {}
+
+ async getPrivateWifKey(wallet, mnemonic) {
+ const rootSeedBuffer = await this.bchjs.Mnemonic.toSeed(mnemonic);
+ let masterHDNode;
+ masterHDNode = this.bchjs.HDNode.fromSeed(rootSeedBuffer);
+ const rootPath = wallet.getRootPath()
+ ? wallet.getRootPath()
+ : "m/44'/1899'/0'";
+ const node = this.bchjs.HDNode.derivePath(masterHDNode, rootPath);
+ const change = this.bchjs.HDNode.derivePath(node, '0/0');
+ return this.bchjs.HDNode.toWIF(change);
+ }
+
+ parseOpReturn(hexStr) {
+ if (
+ !hexStr ||
+ typeof hexStr !== 'string' ||
+ hexStr.substring(0, 2) !== currency.opReturn.opReturnPrefixHex
+ ) {
+ return false;
+ }
+
+ hexStr = hexStr.slice(2); // remove the first byte i.e. 6a
+
+ /*
+ * @Return: resultArray is structured as follows:
+ * resultArray[0] is the transaction type i.e. eToken prefix, cashtab prefix, external message itself if unrecognized prefix
+ * resultArray[1] is the actual cashtab message or the 2nd part of an external message
+ * resultArray[2 - n] are the additional messages for future protcols
+ */
+ let resultArray = [];
+ let message = '';
+ let hexStrLength = hexStr.length;
+
+ for (let i = 0; hexStrLength !== 0; i++) {
+ // part 1: check the preceding byte value for the subsequent message
+ let byteValue = hexStr.substring(0, 2);
+ let msgByteSize = 0;
+ if (byteValue === currency.opReturn.opPushDataOne) {
+ // if this byte is 4c then the next byte is the message byte size - retrieve the message byte size only
+ msgByteSize = parseInt(hexStr.substring(2, 4), 16); // hex base 16 to decimal base 10
+ hexStr = hexStr.slice(4); // strip the 4c + message byte size info
+ } else {
+ // take the byte as the message byte size
+ msgByteSize = parseInt(hexStr.substring(0, 2), 16); // hex base 16 to decimal base 10
+ hexStr = hexStr.slice(2); // strip the message byte size info
+ }
+
+ // part 2: parse the subsequent message based on bytesize
+ const msgCharLength = 2 * msgByteSize;
+ message = hexStr.substring(0, msgCharLength);
+ if (i === 0 && message === currency.opReturn.appPrefixesHex.eToken) {
+ // add the extracted eToken prefix to array then exit loop
+ resultArray[i] = currency.opReturn.appPrefixesHex.eToken;
+ break;
+ }
+ else {
+ // this is either an external message or a subsequent cashtab message loop to extract the message
+ resultArray[i] = message;
+ }
+
+ // strip out the parsed message
+ hexStr = hexStr.slice(msgCharLength);
+ hexStrLength = hexStr.length;
+ }
+ return resultArray;
+ }
+
+ async getRecipientPublicKey(
+ XPI,
+ chronik: ChronikClient,
+ recipientAddress: string
+ ): Promise {
+ let recipientAddressHash160: string;
+ try {
+ recipientAddressHash160 = XPI.Address.toHash160(recipientAddress);
+ } catch (err) {
+ console.log(
+ `Error determining XPI.Address.toHash160(${recipientAddress} in getRecipientPublicKey())`,
+ err
+ );
+ }
+
+ let chronikTxHistoryAtAddress: TxHistoryPage;
+ try {
+ // Get 20 txs. If no outgoing txs in those 20 txs, just don't send the tx
+ chronikTxHistoryAtAddress = await chronik
+ .script('p2pkh', recipientAddressHash160)
+ .history(/*page=*/ 0, /*page_size=*/ 20);
+ } catch (err) {
+ console.log(
+ `Error getting await chronik.script('p2pkh', ${recipientAddressHash160}).history();`,
+ err
+ );
+ throw new Error('Error fetching tx history to parse for public key');
+ }
+
+ let recipientPubKeyChronik;
+
+ // Iterate over tx history to find an outgoing tx
+ for (let i = 0; i < chronikTxHistoryAtAddress.txs.length; i += 1) {
+ const { inputs } = chronikTxHistoryAtAddress.txs[i];
+ for (let j = 0; j < inputs.length; j += 1) {
+ const thisInput = inputs[j];
+ const thisInputSendingHash160 = thisInput.outputScript;
+ if (thisInputSendingHash160.includes(recipientAddressHash160)) {
+ // Then this is an outgoing tx, you can get the public key from this tx
+ // Get the public key
+ try {
+ recipientPubKeyChronik =
+ chronikTxHistoryAtAddress.txs[i].inputs[j].inputScript.slice(-66);
+ } catch (err) {
+ throw new Error(
+ 'Cannot send an encrypted message to a wallet with no outgoing transactions'
+ );
+ }
+ return recipientPubKeyChronik;
+ }
+ }
+ }
+ // You get here if you find no outgoing txs in the chronik tx history
+ throw new Error(
+ 'Cannot send an encrypted message to a wallet with no outgoing transactions in the last 20 txs'
+ );
+ }
+
+ async processDecryptMessageOnchain(
+ outputScript,
+ wallet,
+ mnemonic,
+ addressRecepient
+ ): Promise {
+ const privateKeyWIF = await this.getPrivateWifKey(wallet, mnemonic);
+ let chronikClient = null;
+ if (wallet.coin === 'xpi') {
+ chronikClient = new ChronikClient('https://chronik.be.cash/xpi');
+ } else {
+ chronikClient = new ChronikClient('https://chronik.be.cash/xec');
+ }
+ const pubKeyHex = await this.getRecipientPublicKey(
+ this.bchjs,
+ chronikClient,
+ addressRecepient
+ );
+ const messageDecrypted = this.decryptMessageOnchain(
+ outputScript,
+ privateKeyWIF,
+ pubKeyHex
+ );
+ return messageDecrypted;
+ }
+
+ async processEncryptMessageOnchain(
+ plainText,
+ wallet,
+ mnemonic,
+ addressRecepient
+ ) {
+ const privateKeyWIF = await this.getPrivateWifKey(wallet, mnemonic);
+ let chronikClient = null;
+ if (wallet.coin === 'xpi') {
+ chronikClient = new ChronikClient('https://chronik.be.cash/xpi');
+ } else {
+ chronikClient = new ChronikClient('https://chronik.be.cash/xec');
+ }
+ let pubKeyHex = await this.getRecipientPublicKey(
+ this.bchjs,
+ chronikClient,
+ addressRecepient
+ );
+ if (!pubKeyHex) {
+ pubKeyHex = '';
+ }
+ const encryptedMessage = this.encryptMessageOnchain(
+ privateKeyWIF,
+ pubKeyHex,
+ plainText
+ );
+ return encryptedMessage;
+ }
+
+ encryptMessageOnchain = (
+ privateKeyWIF: string,
+ recipientPubKeyHex: string,
+ plainTextMsg: string
+ ): Uint8Array => {
+ let encryptedMsg;
+ try {
+ const sharedKey = this.createSharedKey(privateKeyWIF, recipientPubKeyHex);
+ encryptedMsg = this.encrypt(sharedKey, Buffer.from(plainTextMsg));
+ } catch (error) {
+ console.log('ENCRYPTION ERROR', error);
+ throw error;
+ }
+
+ return encryptedMsg;
+ };
+
+ decryptMessageOnchain(opReturnOutput, privateKeyWIF, publicKeyHex) {
+ let attachedMsg = null;
+ const opReturn = this.parseOpReturn(opReturnOutput);
+ switch (opReturn[0]) {
+ // unencrypted LotusChat
+ case currency.opReturn.appPrefixesHex.lotusChat:
+ attachedMsg = Buffer.from(opReturn[1], 'hex');
+ break;
+ case currency.opReturn.appPrefixesHex.lotusChatEncrypted:
+ // attachedMsg = 'Not yet implemented chat encrypted';
+ const sharedKey = this.createSharedKey(privateKeyWIF, publicKeyHex);
+ const decryptedMessage = this.decrypt(
+ sharedKey,
+ Uint8Array.from(Buffer.from(opReturn[1], 'hex'))
+ );
+ attachedMsg = Buffer.from(decryptedMessage).toString('utf8');
+ break;
+ default:
+ break;
+ }
+ return attachedMsg ? attachedMsg.toString() : null;
+ }
+
+ decrypt = (sharedKey: Buffer, cipherText: Uint8Array) => {
+ // Split shared key
+ const iv = forge.util.createBuffer(sharedKey.slice(0, 16));
+ const key = forge.util.createBuffer(sharedKey.slice(16));
+
+ // Encrypt entries
+ const cipher = forge.cipher.createDecipher('AES-CBC', key);
+ cipher.start({ iv });
+ const rawBuffer = forge.util.createBuffer(cipherText);
+ cipher.update(rawBuffer);
+ cipher.finish();
+ const plainText = Uint8Array.from(
+ Buffer.from(cipher.output.toHex(), 'hex')
+ );
+ return plainText;
+ };
+
+ encrypt = (sharedKey: Buffer, plainText: Uint8Array) => {
+ // Split shared key
+ const iv = forge.util.createBuffer(sharedKey.slice(0, 16));
+ const key = forge.util.createBuffer(sharedKey.slice(16));
+
+ // Encrypt entries
+ const cipher = forge.cipher.createCipher('AES-CBC', key);
+ cipher.start({ iv });
+ const rawBuffer = forge.util.createBuffer(plainText);
+ cipher.update(rawBuffer);
+ cipher.finish();
+ const cipherText = Uint8Array.from(
+ Buffer.from(cipher.output.toHex(), 'hex')
+ );
+
+ return cipherText;
+ };
+
+ createSharedKey = (privateKeyWIF: string, publicKeyHex: string): Buffer => {
+ const publicKeyObj = PublicKey.fromBuffer(Buffer.from(publicKeyHex, 'hex'));
+ const privateKeyObj = PrivateKey.fromWIF(privateKeyWIF);
+
+ const mergedKey = this.constructMergedKey(privateKeyObj, publicKeyObj);
+ // const rawMergedKey = mergedKey.toBuffer(); // this function throws assertion error sometimes
+ const rawMergedKey = this.publicKeyToBuffer(mergedKey);
+ const sharedKey = this.crypto2.Hash.sha256(rawMergedKey);
+ return sharedKey;
+ };
+
+ constructMergedKey = (privateKey, publicKey) => {
+ return PublicKey.fromPoint(publicKey.point.mul(privateKey.toBigNumber()));
+ };
+
+ publicKeyToBuffer = pubKey => {
+ const { x, y, compressed } = pubKey.toObject();
+ let xBuf = Buffer.from(x, 'hex');
+ let yBuf = Buffer.from(y, 'hex');
+ let prefix;
+ let buf;
+ if (!compressed) {
+ prefix = Buffer.from([0x04]);
+ buf = Buffer.concat([prefix, xBuf, yBuf]);
+ } else {
+ let odd = yBuf[yBuf.length - 1] % 2;
+ if (odd) {
+ prefix = Buffer.from([0x03]);
+ } else {
+ prefix = Buffer.from([0x02]);
+ }
+ buf = Buffer.concat([prefix, xBuf]);
+ }
+
+ return buf;
+ };
+}
diff --git a/src/app/providers/persistence/persistence.ts b/src/app/providers/persistence/persistence.ts
index 1c9781cf020..ad801971280 100644
--- a/src/app/providers/persistence/persistence.ts
+++ b/src/app/providers/persistence/persistence.ts
@@ -71,6 +71,7 @@ const Keys = {
PROFILE: 'profile',
PROFILE_OLD: 'profileOld',
REMOTE_PREF_STORED: 'remotePrefStored',
+ MIGRATE_MESSAGE_ONCHAIN: 'start',
TX_CONFIRM_NOTIF: txid => 'txConfirmNotif-' + txid,
TX_HISTORY: walletId => 'txsHistory-' + walletId,
ORDER_WALLET: walletId => 'order-' + walletId,
@@ -600,6 +601,14 @@ export class PersistenceProvider {
return this.storage.get(Keys.TX_CONFIRM_NOTIF(txid));
}
+ setMigrateMessageOnchainProcess(val) {
+ return this.storage.set(Keys.MIGRATE_MESSAGE_ONCHAIN, val);
+ }
+
+ getMigrateMessageOnchainProcess() {
+ return this.storage.get(Keys.MIGRATE_MESSAGE_ONCHAIN);
+ }
+
removeTxConfirmNotification(txid: string) {
return this.storage.remove(Keys.TX_CONFIRM_NOTIF(txid));
}
diff --git a/src/app/providers/wallet/wallet.ts b/src/app/providers/wallet/wallet.ts
index 3ac0cff8b0e..ccc3ba6026f 100644
--- a/src/app/providers/wallet/wallet.ts
+++ b/src/app/providers/wallet/wallet.ts
@@ -18,6 +18,7 @@ import { LanguageProvider } from '../language/language';
import { Logger } from '../logger/logger';
import { LogsProvider } from '../logs/logs';
import { OnGoingProcessProvider } from '../on-going-process/on-going-process';
+import { OnchainMessageProvider } from '../onchain-message/onchain-message';
import { PersistenceProvider } from '../persistence/persistence';
import { PlatformProvider } from '../platform/platform';
import { PopupProvider } from '../popup/popup';
@@ -69,11 +70,12 @@ export interface TransactionProposal {
data?: string;
gasLimit?: number;
}>;
- isDonation?: boolean
+ isDonation?: boolean;
receiveLotusAddress?: string;
inputs: any;
fee: any;
message: string;
+ messageOnChain: string;
customData?: {
service?: string;
giftCardName?: string;
@@ -139,7 +141,8 @@ export class WalletProvider {
private keyProvider: KeyProvider,
private platformProvider: PlatformProvider,
private logsProvider: LogsProvider,
- private appProvider: AppProvider
+ private appProvider: AppProvider,
+ private onchainMessageService: OnchainMessageProvider
) {
this.logger.debug('WalletProvider initialized');
this.isPopupOpen = false;
@@ -404,10 +407,10 @@ export class WalletProvider {
) {
this.logger.debug(
'Retrying update... ' +
- walletId +
- ' Try:' +
- tries +
- ' until:',
+ walletId +
+ ' Try:' +
+ tries +
+ ' until:',
opts.until
);
return setTimeout(() => {
@@ -446,7 +449,7 @@ export class WalletProvider {
if (WalletProvider.statusUpdateOnProgress[wallet.id] && !opts.until) {
this.logger.info(
'!! Status update already on progress for: ' +
- wallet.credentials.walletName
+ wallet.credentials.walletName
);
return reject('INPROGRESS');
}
@@ -527,22 +530,26 @@ export class WalletProvider {
this.calcTotalAmount(wallet, isoCode, lastDayRatesArray)
);
if (wallet.tokens) {
- totalAlternativeBalanceToken += _.sumBy(wallet.tokens, 'alternativeBalance')
+ totalAlternativeBalanceToken += _.sumBy(
+ wallet.tokens,
+ 'alternativeBalance'
+ );
}
});
- const totalBalanceAlternative = (_.sumBy(
- _.compact(totalAmountArray),
- b => b.walletTotalBalanceAlternative
- ) + totalAlternativeBalanceToken).toFixed(2);
-
-
- const totalBalanceAlternativeLastDay = (_.sumBy(
- _.compact(totalAmountArray),
- b => b.walletTotalBalanceAlternativeLastDay
- ) + totalAlternativeBalanceToken).toFixed(2);
-
+ const totalBalanceAlternative = (
+ _.sumBy(
+ _.compact(totalAmountArray),
+ b => b.walletTotalBalanceAlternative
+ ) + totalAlternativeBalanceToken
+ ).toFixed(2);
+ const totalBalanceAlternativeLastDay = (
+ _.sumBy(
+ _.compact(totalAmountArray),
+ b => b.walletTotalBalanceAlternativeLastDay
+ ) + totalAlternativeBalanceToken
+ ).toFixed(2);
const difference =
parseFloat(totalBalanceAlternative.replace(/,/g, '')) -
@@ -550,7 +557,7 @@ export class WalletProvider {
const totalBalanceChange =
(difference * 100) /
- (parseFloat(totalBalanceAlternative.replace(/,/g, '')));
+ parseFloat(totalBalanceAlternative.replace(/,/g, ''));
return {
totalBalanceAlternativeIsoCode: isoCode,
@@ -576,13 +583,24 @@ export class WalletProvider {
});
}
- public getAddressView(coin: Coin, network: string, address: string, isEtoken?: boolean): string {
+ public getAddressView(
+ coin: Coin,
+ network: string,
+ address: string,
+ isEtoken?: boolean
+ ): string {
if (coin != 'bch' && coin != 'xec') return address;
let protoAddr = this.getProtoAddress(coin, network, address);
if (isEtoken && coin == 'xec') {
try {
- const { prefix, type, hash } = this.addressProvider.decodeAddress(protoAddr);
- const etokenAddress = this.addressProvider.encodeAddress('etoken', type, hash, protoAddr);
+ const { prefix, type, hash } =
+ this.addressProvider.decodeAddress(protoAddr);
+ const etokenAddress = this.addressProvider.encodeAddress(
+ 'etoken',
+ type,
+ hash,
+ protoAddr
+ );
if (etokenAddress) protoAddr = etokenAddress;
} catch (error) {
protoAddr = 'false';
@@ -785,7 +803,7 @@ export class WalletProvider {
const LIMIT = 100;
let requestLimit = FIRST_LIMIT;
const walletId = wallet.credentials.walletId;
- WalletProvider.progressFn[walletId] = progressFn || (() => { });
+ WalletProvider.progressFn[walletId] = progressFn || (() => {});
let foundLimitTx: any = [];
const fixTxsUnit = (txs): void => {
@@ -867,11 +885,11 @@ export class WalletProvider {
skip = skip + requestLimit;
this.logger.debug(
'Syncing TXs for:' +
- walletId +
- '. Got:' +
- newTxs.length +
- ' Skip:' +
- skip,
+ walletId +
+ '. Got:' +
+ newTxs.length +
+ ' Skip:' +
+ skip,
' EndingTxid:',
endingTxid,
' Continue:',
@@ -893,7 +911,7 @@ export class WalletProvider {
if (!shouldContinue) {
this.logger.debug(
'Finished Sync: New / soft confirmed Txs: ' +
- newTxs.length
+ newTxs.length
);
return resolve(newTxs);
}
@@ -974,7 +992,7 @@ export class WalletProvider {
}
updateNotes()
- .then(() => {
+ .then(async () => {
//
if (!_.isEmpty(foundLimitTx)) {
this.logger.debug(
@@ -983,7 +1001,50 @@ export class WalletProvider {
return resolve(newHistory);
}
//
-
+ // encrypt message right here
+ if(wallet.coin === 'xpi' && wallet.cachedStatus.wallet.singleAddress){
+ const result =
+ await this.getMnemonicAndPassword(wallet);
+ for (let index = 0; index < newHistory.length; index++) {
+ const tx = newHistory[index] as any;
+ if (
+ tx.outputScript &&
+ tx.outputScript.length > 0 &&
+ !(tx.messageOnchain && tx.messageOnchain.length > 0)
+ ) {
+ const outputFound = _.find(
+ tx.outputs,
+ o =>
+ o.outputScript &&
+ o.outputScript.length > 0 &&
+ o.outputScript.includes('030303')
+ );
+ let addressRecepient = '';
+ if (tx.action === 'received') {
+ addressRecepient = tx.inputAddresses[0];
+ } else {
+ addressRecepient = _.find(
+ tx.outputs,
+ o => !o.outputScript
+ ).address;
+ }
+ if (outputFound) {
+ tx.messageOnchain =
+ await this.onchainMessageService.processDecryptMessageOnchain(
+ outputFound.outputScript,
+ wallet,
+ result.mnemonic,
+ addressRecepient
+ );
+ }
+ }
+ // newHistory[index].outputs = tx.outputs.filter(
+ // output => output.address !== 'false'
+ // );
+ }
+ }
+
+
const historyToSave = JSON.stringify(newHistory);
_.each(txs, tx => {
tx.recent = true;
@@ -998,9 +1059,9 @@ export class WalletProvider {
.then(() => {
this.logger.debug(
'History sync & saved for ' +
- wallet.id +
- ' Txs: ' +
- newHistory.length
+ wallet.id +
+ ' Txs: ' +
+ newHistory.length
);
return resolve(undefined);
@@ -1047,7 +1108,8 @@ export class WalletProvider {
return input.mintHeight < 0;
});
}
- const isTxCoinbase = tx.coinbase || tx.action == 'immature' || tx.action == 'mined';
+ const isTxCoinbase =
+ tx.coinbase || tx.action == 'immature' || tx.action == 'mined';
if (isTxCoinbase && tx.confirmations >= this.SAFE_CONFIRMATIONS_MINED) {
tx.safeConfirmed = this.SAFE_CONFIRMATIONS_MINED + '+';
} else if (!isTxCoinbase && tx.confirmations >= this.SAFE_CONFIRMATIONS) {
@@ -1062,6 +1124,18 @@ export class WalletProvider {
delete tx.note.encryptedBody;
}
+ const outputFound = _.find(
+ tx.outputs,
+ o =>
+ o.outputScript &&
+ o.outputScript.length > 0 &&
+ o.outputScript.includes('030303')
+ );
+
+ if (outputFound) {
+ tx.outputScript = outputFound.outputScript;
+ }
+
if (!txHistoryUnique[tx.txid]) {
ret.push(tx);
txHistoryUnique[tx.txid] = true;
@@ -1140,7 +1214,8 @@ export class WalletProvider {
const outputSize = 34;
nbInputs = nbInputs ? nbInputs : 1; // Assume 1 input
const outputReturn = !isAllFund ? 16 : 0;
- const size = overhead + inputSize * nbInputs + outputSize * nbOutputs + outputReturn;
+ const size =
+ overhead + inputSize * nbInputs + outputSize * nbOutputs + outputReturn;
return parseInt((size * (1 + safetyMargin)).toFixed(0), 10);
}
@@ -1322,7 +1397,9 @@ export class WalletProvider {
password
);
} catch (err) {
- const title = this.translate.instant('Your account is in a corrupt state. Please contact support and share the logs provided.');
+ const title = this.translate.instant(
+ 'Your account is in a corrupt state. Please contact support and share the logs provided.'
+ );
let message;
try {
message = err instanceof Error ? err.toString() : JSON.stringify(err);
@@ -1734,8 +1811,8 @@ export class WalletProvider {
err && err.message
? err.message
: this.translate.instant(
- 'The payment was created but could not be completed. Please try again from home screen'
- );
+ 'The payment was created but could not be completed. Please try again from home screen'
+ );
this.logger.error('Sign error: ' + msg);
this.events.publish('Local/TxAction', {
walletId: wallet.id,
@@ -1822,16 +1899,16 @@ export class WalletProvider {
return resolve(
info.type +
- '|' +
- info.data +
- '|' +
- wallet.credentials.network.toLowerCase() +
- '|' +
- derivationPath +
- '|' +
- mnemonicHasPassphrase +
- '|' +
- wallet.coin
+ '|' +
+ info.data +
+ '|' +
+ wallet.credentials.network.toLowerCase() +
+ '|' +
+ derivationPath +
+ '|' +
+ mnemonicHasPassphrase +
+ '|' +
+ wallet.coin
);
});
}
@@ -1967,10 +2044,14 @@ export class WalletProvider {
}
formatAmout(amount: number, coin: string) {
- if (_.isEmpty(coin)) coin = 'xpi' // lotus
- const precision = _.get(this.currencyProvider.getPrecision(coin as Coin), 'unitToSatoshi', 0);
+ if (_.isEmpty(coin)) coin = 'xpi'; // lotus
+ const precision = _.get(
+ this.currencyProvider.getPrecision(coin as Coin),
+ 'unitToSatoshi',
+ 0
+ );
if (precision == 0) return 0;
- return (amount / precision)
+ return amount / precision;
}
getDonationInfo() {
@@ -1980,14 +2061,23 @@ export class WalletProvider {
if (errLivenet) {
return reject(this.translate.instant('Could not get dynamic fee'));
}
- donation.donationSupportCoins = _.map(donation.donationToAddresses, item => {
- return {
- coin: item.coin,
- network: item.network || 'livenet'
+ donation.donationSupportCoins = _.map(
+ donation.donationToAddresses,
+ item => {
+ return {
+ coin: item.coin,
+ network: item.network || 'livenet'
+ };
}
- });
- donation.remaining = this.formatAmout(donation.remaining, donation.donationCoin);
- donation.receiveAmountLotus = this.formatAmout(donation.receiveAmountLotus, donation.donationCoin);
+ );
+ donation.remaining = this.formatAmout(
+ donation.remaining,
+ donation.donationCoin
+ );
+ donation.receiveAmountLotus = this.formatAmout(
+ donation.receiveAmountLotus,
+ donation.donationCoin
+ );
return resolve(donation);
});
});
diff --git a/src/assets/i18n/en.po b/src/assets/i18n/en.po
index 8195ad01f41..1adc0b6c7ca 100755
--- a/src/assets/i18n/en.po
+++ b/src/assets/i18n/en.po
@@ -24,6 +24,12 @@ msgstr "Password unmatched!"
msgid "Important"
msgstr "Important"
+msgid "Private message"
+msgstr "Private message"
+
+msgid "Type your private message"
+msgstr "Type your private message"
+
msgid "Account Addresses"
msgstr "Account Addresses"
@@ -5372,6 +5378,12 @@ msgstr "The account you are using does not match the network and/or the currency
msgid "{{newWalletsCount}} wallets found"
msgstr "{{newWalletsCount}} wallets found"
+msgid "Replying to "
+msgstr "Replying to "
+
+msgid "Reply"
+msgstr "Reply"
+
msgid ""
"{{params.countryCode === 'US' ? 'U.S.' : params.countryCode}} Phone Required"
msgstr ""
diff --git a/src/assets/i18n/vi.po b/src/assets/i18n/vi.po
index 874dd3344fd..f7c709f9796 100644
--- a/src/assets/i18n/vi.po
+++ b/src/assets/i18n/vi.po
@@ -24,6 +24,12 @@ msgstr "Mật khẩu không khớp!"
msgid "Important"
msgstr "Quan Trọng"
+msgid "Private message"
+msgstr "Tin nhắn riêng tư"
+
+msgid "Type your private message"
+msgstr "Nhập tin nhắn riêng tư của bạn"
+
msgid "{appName} cannot recovery your fund for you if you lose your recovery key. The loss of it will lead to permanent asset loss."
msgstr "{appName} không thể khôi phục số dư nếu bạn mất cụm từ khóa (12 từ). Đồng nghĩa với việc tài sản của bạn sẽ bị mất vĩnh viễn."
@@ -2767,6 +2773,12 @@ msgstr "Không phải bây giờ"
msgid "Not set"
msgstr "Không được thiết lập"
+msgid "Replying to "
+msgstr "Trả lời cho "
+
+msgid "Reply"
+msgstr "Trả lời"
+
msgid "Not valid bitcoin address"
msgstr "Địa chỉ bitcoin không hợp lệ"
diff --git a/src/assets/img/ico-reply-dark.svg b/src/assets/img/ico-reply-dark.svg
new file mode 100644
index 00000000000..cba36e842b3
--- /dev/null
+++ b/src/assets/img/ico-reply-dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/assets/img/ico-reply-light.svg b/src/assets/img/ico-reply-light.svg
new file mode 100644
index 00000000000..8bc472da296
--- /dev/null
+++ b/src/assets/img/ico-reply-light.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/index.html b/src/index.html
index 57faf6e32bb..d04787da541 100644
--- a/src/index.html
+++ b/src/index.html
@@ -16,7 +16,7 @@
-
+
diff --git a/src/theme/dark.scss b/src/theme/dark.scss
index 3f95fbc0ff1..c48ffb5dcdf 100644
--- a/src/theme/dark.scss
+++ b/src/theme/dark.scss
@@ -1800,6 +1800,9 @@
.note-container {
color: rgba(255, 184, 93, 1) !important;
}
+ .onchain-message-txt {
+ color: #fff !important;
+ }
}
.send-to-content {
.item-name {
@@ -2440,7 +2443,7 @@
--highlight-color-focused: rgba(224, 228, 230, 0.6);
--highlight-color-valid: rgba(224, 228, 230, 0.6);
--highlight-color-invalid: rgba(224, 228, 230, 0.6);
- ion-input {
+ ion-input, ion-textarea {
--placeholder-color: rgba(237, 239, 240, 0.38);
--color: #edeff0;
}
@@ -2456,6 +2459,12 @@
.item-title {
color: rgba(237, 239, 240, 0.6);
}
+ .sign-require {
+ color: #FFB4A9 !important;
+ }
+ .max-message-txt {
+ color: #FFB4A9 !important;
+ }
}
create-new-wallet {
@@ -3777,4 +3786,20 @@
background: var(--bg-color-dark-theme-8) !important;
}
}
+
+ message-reply-component {
+ .reply-to-container {
+ background: linear-gradient(0deg, rgba(126, 208, 255, 0.14), rgba(126, 208, 255, 0.14)), #001E2E;
+ border: 1px solid rgba(0, 101, 141, 0.08);
+ .reply-to-title {
+ color: #EDEFF0;
+ }
+ .reply-to-message {
+ color: #E0E4E6;
+ }
+ }
+ .max-message-txt {
+ color: #FFB4A9 !important;
+ }
+ }
}