Merge pull request #7850 from Gamboster/feat/cashaddrSupport

[V4] Feat: cashAddr support
This commit is contained in:
Gustavo Maximiliano Cortez 2018-01-16 14:57:05 -03:00 committed by GitHub
commit 65e9efdd22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 149 additions and 51 deletions

View File

@ -82,7 +82,7 @@
"ajv": "^5.5.2",
"angular2-moment": "^1.7.1",
"autoprefixer": "^7.2.4",
"bitcore-wallet-client": "^6.4.0",
"bitcore-wallet-client": "^6.5.0",
"buffer-compare": "^1.1.1",
"cordova-android": "6.4.0",
"cordova-clipboard": "^1.1.1",

View File

@ -6,10 +6,11 @@ import { NavParams, ViewController } from 'ionic-angular';
templateUrl: 'incoming-data-menu.html',
})
export class IncomingDataMenuPage {
public https: boolean;
public data: string;
public type: string;
public https: boolean;
public coin: string;
constructor(
private viewCtrl: ViewController,
@ -21,6 +22,7 @@ export class IncomingDataMenuPage {
ionViewDidLoad() {
this.data = this.navParams.data.data;
this.type = this.navParams.data.type;
this.coin = this.navParams.data.coin;
if (this.type === 'url') {
if (this.data.indexOf('https://') === 0) {
this.https = true;
@ -29,6 +31,11 @@ export class IncomingDataMenuPage {
}
public close(redirTo: string, value: string) {
if (redirTo == 'AmountPage') {
let coin = this.coin ? this.coin : 'btc';
this.viewCtrl.dismiss({ redirTo: redirTo, value: value, coin: coin });
return;
}
this.viewCtrl.dismiss({ redirTo: redirTo, value: value });
}
}

View File

@ -2,6 +2,9 @@ import { Component } from '@angular/core';
import { NavParams } from 'ionic-angular';
import { Logger } from '@nsalaun/ng-logger';
// Native
import { SocialSharing } from '@ionic-native/social-sharing';
//providers
import { ProfileProvider } from '../../../providers/profile/profile';
import { PlatformProvider } from '../../../providers/platform/platform';
@ -26,14 +29,17 @@ export class CustomAmountPage {
private profileProvider: ProfileProvider,
private platformProvider: PlatformProvider,
private walletProvider: WalletProvider,
private logger: Logger
private logger: Logger,
private socialSharing: SocialSharing
) {
this.address = this.navParams.data.toAddress;
this.amount = this.navParams.data.amount;
this.coin = this.navParams.data.coin;
let walletId = this.navParams.data.walletId;
this.wallet = this.profileProvider.getWallet(walletId);
this.showShareButton = this.platformProvider.isCordova;
let addr = this.navParams.data.toAddress;
this.address = this.walletProvider.getAddressView(this.wallet, addr);
}
ionViewDidLoad() {
@ -42,16 +48,11 @@ export class CustomAmountPage {
}
private updateQrAddress(): void {
this.setProtocolHandler();
this.qrAddress = this.protocolHandler + ":" + this.address + "?amount=" + this.amount;
this.qrAddress = this.walletProvider.getProtoAddress(this.wallet, this.address) + "?amount=" + this.amount;
}
private setProtocolHandler(): void {
this.protocolHandler = this.walletProvider.getProtocolHandler(this.wallet.coin);
}
public shareAddress = function () {
//window.plugins.socialsharing.share(this.qrAddress, null, null, null); TODO
public shareAddress(): void {
this.socialSharing.share(this.qrAddress);
}
}

View File

@ -2,15 +2,15 @@ import { Component } from '@angular/core';
import { Logger } from '@nsalaun/ng-logger';
import { NavController, Events, AlertController } from 'ionic-angular';
//native
// Native
import { SocialSharing } from '@ionic-native/social-sharing';
//pages
// Pages
import { AmountPage } from '../send/amount/amount';
import { CopayersPage } from './../add/copayers/copayers';
import { BackupGamePage } from '../backup/backup-game/backup-game';
//providers
// Providers
import { WalletProvider } from '../../providers/wallet/wallet';
import { ProfileProvider } from '../../providers/profile/profile';
import { PlatformProvider } from '../../providers/platform/platform';
@ -62,15 +62,10 @@ export class ReceivePage {
private onWalletSelect(wallet: any): any {
this.wallet = wallet;
if (this.wallet) {
this.setProtocolHandler();
this.setAddress();
}
}
private setProtocolHandler(): void {
this.protocolHandler = this.walletProvider.getProtocolHandler(this.wallet.coin);
}
private checkSelectedWallet(wallet: any, wallets: any): any {
if (!wallet) return wallets[0];
let w = _.find(wallets, (w: any) => {
@ -99,7 +94,7 @@ export class ReceivePage {
this.walletProvider.getAddress(this.wallet, newAddr).then((addr) => {
this.loading = false
this.address = addr;
this.address = this.walletProvider.getAddressView(this.wallet, addr);
this.updateQrAddress();
}).catch((err) => {
this.loading = false;
@ -108,13 +103,12 @@ export class ReceivePage {
}
private updateQrAddress(): void {
this.qrAddress = this.protocolHandler + ":" + this.address;
this.qrAddress = this.walletProvider.getProtoAddress(this.wallet, this.address);
}
public shareAddress(): void {
let protocol = 'bitcoin';
if (this.wallet.coin == 'bch') protocol += 'cash';
this.socialSharing.share(protocol + ':' + this.address);
if (!this.showShareButton) return;
this.socialSharing.share(this.qrAddress);
}
public showWallets(): void {

View File

@ -96,7 +96,7 @@ export class ScanPage {
this.modalIsOpen = false;
switch (data.redirTo) {
case 'AmountPage':
this.sendPaymentToAddress(data.value);
this.sendPaymentToAddress(data.value, data.coin);
break;
case 'AddressBookPage':
this.addToAddressBook(data.value);
@ -125,9 +125,9 @@ export class ScanPage {
this.externalLinkProvider.open(url);
}
private sendPaymentToAddress(bitcoinAddress: string): void {
private sendPaymentToAddress(bitcoinAddress: string, coin: string): void {
//this.navCtrl.parent.select(3); TODO go to send and then amount page
this.navCtrl.push(AmountPage, { toAddress: bitcoinAddress });
this.navCtrl.push(AmountPage, { toAddress: bitcoinAddress, coin: coin });
}
private addToAddressBook(bitcoinAddress: string): void {

View File

@ -55,9 +55,9 @@
<div class="payment-proposal-to" *ngIf="!tx.recipientType">
<img class="icon-bitcoin" src="assets/img/icon-bitcoin-small.svg">
<div *ngIf="!tx.paypro" copy-to-clipboard="{{ tx.toAddress }}">
<!--TODO: <contact *ngIf="tx.toAddress && !tx.name" address="{{tx.toAddress}}"></contact>-->
<span *ngIf="!tx.name">{{tx.toAddress}}</span>
<div *ngIf="!tx.paypro" copy-to-clipboard="{{ tx.origToAddress }}">
<!--TODO: <contact *ngIf="tx.origToAddress && !tx.name" address="{{tx.origToAddress}}"></contact>-->
<span *ngIf="!tx.name">{{tx.origToAddress}}</span>
<span *ngIf="tx.name">{{tx.name}}</span>
</div>
@ -76,9 +76,9 @@
<img *ngIf="tx.network != 'testnet'" [ngStyle]="{'background-color': tx.color}" src="assets/img/icon-wallet.svg" class="icon-wallet"
/>
</ion-icon>
<div copy-to-clipboard="{{ tx.toAddress }}">
<!--TODO: <contact ng-if="tx.toAddress && !tx.name" address="{{tx.toAddress}}"></contact>-->
<span *ngIf="!tx.name">{{tx.toAddress}}</span>
<div copy-to-clipboard="{{ tx.origToAddress }}">
<!--TODO: <contact ng-if="tx.origToAddress && !tx.name" address="{{tx.origToAddress}}"></contact>-->
<span *ngIf="!tx.name">{{tx.origToAddress}}</span>
<span *ngIf="tx.name">{{tx.name}}</span>
</div>
</div>
@ -128,4 +128,4 @@
<ion-footer>
<button ion-button block class="button-footer" (click)="approve(tx, wallet)" [disabled]="!wallet" translate>Click to send</button>
</ion-footer>
</ion-footer>

View File

@ -11,6 +11,7 @@ import { FeeWarningPage } from '../fee-warning/fee-warning';
import { SuccessModalPage } from '../../success/success';
// Providers
import { BwcProvider } from '../../../providers/bwc/bwc';
import { ConfigProvider } from '../../../providers/config/config';
import { PlatformProvider } from '../../../providers/platform/platform';
import { ProfileProvider } from '../../../providers/profile/profile';
@ -28,6 +29,8 @@ import { TxFormatProvider } from '../../../providers/tx-format/tx-format';
})
export class ConfirmPage {
private bitcoreCash: any;
public countDown = null;
public CONFIRM_LIMIT_USD: number;
public FEE_TOO_HIGH_LIMIT_PER: number;
@ -55,6 +58,7 @@ export class ConfirmPage {
public usingCustomFee: boolean = false;
constructor(
private bwcProvider: BwcProvider,
private navCtrl: NavController,
private navParams: NavParams,
private logger: Logger,
@ -71,6 +75,7 @@ export class ConfirmPage {
private txFormatProvider: TxFormatProvider,
private events: Events
) {
this.bitcoreCash = this.bwcProvider.getBitcoreCash();
this.CONFIRM_LIMIT_USD = 20;
this.FEE_TOO_HIGH_LIMIT_PER = 15;
this.config = this.configProvider.get();
@ -95,8 +100,15 @@ export class ConfirmPage {
coin: this.navParams.data.coin,
txp: {},
};
this.tx.origToAddress = this.tx.toAddress;
if (this.tx.coin && this.tx.coin == 'bch') {
this.tx.feeLevel = 'normal';
// Use legacy address
this.tx.toAddress = this.bitcoreCash.Address(this.tx.toAddress).toString();
}
if (this.tx.coin && this.tx.coin == 'bch') this.tx.feeLevel = 'normal';
this.tx.feeLevelName = this.feeProvider.feeOpts[this.tx.feeLevel];
this.showAddress = false;
this.walletSelectorTitle = 'Send from'; // TODO gettextCatalog

View File

@ -12,7 +12,7 @@
<ion-card-header>
<div class="toggle-header">
<ion-label>{{'Use Unconfirmed Funds' | translate}}</ion-label>
<ion-toggle [(ngModel)]="spendUnconfirmed" (ionChange)="spendUnconfirmedChange()" checked="true"></ion-toggle>
<ion-toggle [(ngModel)]="spendUnconfirmed" (ionChange)="spendUnconfirmedChange()"></ion-toggle>
</div>
</ion-card-header>
<ion-item-divider color="light" text-wrap>
@ -20,11 +20,23 @@
</ion-item-divider>
</ion-card>
<ion-card>
<ion-card-header>
<div class="toggle-header">
<ion-label>{{'Use Bitcoin Cash Copay Style Addresses' | translate}}</ion-label>
<ion-toggle [(ngModel)]="useLegacyAddress" (ionChange)="useLegacyAddressChange()"></ion-toggle>
</div>
</ion-card-header>
<ion-item-divider color="light" text-wrap>
<span translate>If enabled, Bitcoin Cash addresses will be shown using Copay style address, and not the new cashaddr format.</span>
</ion-item-divider>
</ion-card>
<ion-card>
<ion-card-header>
<div class="toggle-header">
<ion-label>{{'Recent Transaction Card' | translate}}</ion-label>
<ion-toggle [(ngModel)]="recentTransactionsEnabled" (ionChange)="recentTransactionsChange()" checked="true"></ion-toggle>
<ion-toggle [(ngModel)]="recentTransactionsEnabled" (ionChange)="recentTransactionsChange()"></ion-toggle>
</div>
</ion-card-header>
<ion-item-divider color="light" text-wrap>
@ -37,7 +49,7 @@
<ion-card-header>
<div class="toggle-header">
<ion-label>{{'Show Next Steps Card' | translate}}</ion-label>
<ion-toggle [(ngModel)]="showNextSteps" (ionChange)="nextStepsChange()" checked="true"></ion-toggle>
<ion-toggle [(ngModel)]="showNextSteps" (ionChange)="nextStepsChange()"></ion-toggle>
</div>
</ion-card-header>
<ion-item-divider color="light" text-wrap>

View File

@ -13,6 +13,7 @@ export class AdvancedPage {
public spendUnconfirmed: boolean;
public recentTransactionsEnabled: boolean;
public showNextSteps: boolean;
public useLegacyAddress: boolean;
constructor(
private configProvider: ConfigProvider,
@ -30,6 +31,7 @@ export class AdvancedPage {
this.spendUnconfirmed = config.wallet.spendUnconfirmed;
this.recentTransactionsEnabled = config.recentTransactions.enabled;
this.showNextSteps = config.showNextSteps.enabled;
this.useLegacyAddress = config.wallet.useLegacyAddress;
}
public spendUnconfirmedChange(): void {
@ -50,6 +52,15 @@ export class AdvancedPage {
this.configProvider.set(opts);
}
public useLegacyAddressChange(): void {
let opts = {
wallet: {
useLegacyAddress: this.useLegacyAddress
}
};
this.configProvider.set(opts);
}
public nextStepsChange(): void {
let opts = {
showNextSteps: {

View File

@ -63,7 +63,11 @@ export class TxDetailsPage {
this.txsUnsubscribedForNotifications = this.config.confirmedTxsNotifications ? !this.config.confirmedTxsNotifications.enabled : true;
if (this.wallet.coin == 'bch') {
this.blockexplorerUrl = 'bch-insight.bitpay.com';
if (this.walletProvider.useLegacyAddress()) {
this.blockexplorerUrl = 'bch-insight.bitpay.com';
} else {
this.blockexplorerUrl = 'blockdozer.com/insight';
}
} else {
this.blockexplorerUrl = 'insight.bitpay.com';
}
@ -153,7 +157,7 @@ export class TxDetailsPage {
this.walletProvider.getTx(this.wallet, this.txId).then((tx: any) => {
if (!opts.hideLoading) this.onGoingProcess.set('loadingTxInfo', false);
this.btx = this.txFormatProvider.processTx(this.wallet.coin, tx);
this.btx = this.txFormatProvider.processTx(this.wallet.coin, tx, this.walletProvider.useLegacyAddress());
let v: string = this.txFormatProvider.formatAlternativeStr(this.wallet.coin, tx.fees);
this.btx.feeFiatStr = v;
this.btx.feeRateStr = (this.btx.fees / (this.btx.amount + this.btx.fees) * 100).toFixed(2) + '%';

View File

@ -11,6 +11,7 @@ interface Config {
};
wallet: {
useLegacyAddress: boolean,
requiredCopayers: number;
totalCopayers: number;
spendUnconfirmed: boolean;
@ -101,6 +102,7 @@ const configDefault: Config = {
// wallet default config
wallet: {
useLegacyAddress: false,
requiredCopayers: 2,
totalCopayers: 3,
spendUnconfirmed: false,

View File

@ -80,6 +80,12 @@ export class IncomingDataProvider {
coin = 'bch';
parsed = this.bwcProvider.getBitcoreCash().URI(data);
addr = parsed.address ? parsed.address.toString() : '';
// keep address in original format
if (parsed.address && data.indexOf(addr) < 0) {
addr = parsed.address.toCashAddress();
};
message = parsed.message;
amount = parsed.amount ? parsed.amount : '';
@ -156,7 +162,8 @@ export class IncomingDataProvider {
if (this.navCtrl.getActive().name === 'ScanPage') {
this.showMenu({
data: data,
type: 'bitcoinAddress'
type: 'bitcoinAddress',
coin: 'btc'
});
} else {
let coin = 'btc';

View File

@ -10,17 +10,30 @@ import * as _ from "lodash";
@Injectable()
export class TxFormatProvider {
private bitcoreCash: any;
// TODO: implement configService
public pendingTxProposalsCountForUs: number
constructor(
private bwc: BwcProvider,
private bwcProvider: BwcProvider,
private rate: RateProvider,
private config: ConfigProvider,
private filter: FilterProvider,
private logger: Logger
) {
this.logger.info('TxFormatProvider initialized.');
this.bitcoreCash = this.bwcProvider.getBitcoreCash();
}
public toCashAddress(address: string, withPrefix?: boolean): string {
let cashAddr: string = (this.bitcoreCash.Address(address)).toCashAddress();
if (withPrefix) {
return cashAddr;
}
return cashAddr.split(':')[1]; // rm prefix
}
public formatAmount(satoshis: number, fullPrecision?: boolean): number {
@ -32,7 +45,7 @@ export class TxFormatProvider {
var opts = {
fullPrecision: !!fullPrecision
};
return this.bwc.getUtils().formatAmount(satoshis, settings.unitCode, opts);
return this.bwcProvider.getUtils().formatAmount(satoshis, settings.unitCode, opts);
}
public formatAmountStr(coin: string, satoshis: number): string {
@ -78,7 +91,7 @@ export class TxFormatProvider {
return val();
};
public processTx(coin: string, tx: any): any {
public processTx(coin: string, tx: any, useLegacyAddress?: boolean): any {
if (!tx || tx.action == 'invalid')
return tx;
@ -99,6 +112,11 @@ export class TxFormatProvider {
}, 0);
}
tx.toAddress = tx.outputs[0].toAddress;
// toDo: translate all tx.outputs[x].toAddress ?
if (tx.toAddress && coin == 'bch' && !useLegacyAddress) {
tx.toAddress = this.toCashAddress(tx.toAddress);
}
}
tx.amountStr = this.formatAmountStr(coin, tx.amount);
@ -110,6 +128,10 @@ export class TxFormatProvider {
tx.amountUnitStr = tx.amountStr.split(' ')[1];
}
if (tx.addressTo && coin == 'bch' && !useLegacyAddress) {
tx.addressTo = this.toCashAddress(tx.addressTo);
}
return tx;
};

View File

@ -1,7 +1,9 @@
import { Injectable } from '@angular/core';
import { Events } from 'ionic-angular';
import { Logger } from '@nsalaun/ng-logger';
import * as lodash from 'lodash';
// Providers
import { ConfigProvider } from '../config/config';
import { BwcProvider } from '../bwc/bwc';
import { TxFormatProvider } from '../tx-format/tx-format';
@ -12,8 +14,6 @@ import { FilterProvider } from '../filter/filter';
import { PopupProvider } from '../popup/popup';
import { OnGoingProcessProvider } from '../on-going-process/on-going-process';
import { TouchIdProvider } from '../touchid/touchid';
import * as lodash from 'lodash';
import { FeeProvider } from '../fee/fee';
@ -322,6 +322,29 @@ export class WalletProvider {
});
}
public useLegacyAddress(): boolean {
let config = this.configProvider.get();
let walletSettings = config.wallet;
return walletSettings.useLegacyAddress;
}
public getAddressView(wallet: any, address: string): string {
if (wallet.coin != 'bch' || this.useLegacyAddress()) return address;
return this.txFormatProvider.toCashAddress(address);
}
public getProtoAddress(wallet: any, address: string) {
let proto: string = this.getProtocolHandler(wallet);
let protoAddr: string = proto + ':' + address;
if (wallet.coin != 'bch' || this.useLegacyAddress()) {
return protoAddr;
} else {
return protoAddr.toUpperCase();
};
};
public getAddress(wallet: any, forceNew: boolean): Promise<any> {
return new Promise((resolve, reject) => {
this.persistenceProvider.getLastAddress(wallet.id).then((addr) => {
@ -1299,7 +1322,7 @@ export class WalletProvider {
return reject(err);
});
});
};
}
public getSendMaxInfo(wallet: any, opts: any): Promise<any> {
return new Promise((resolve, reject) => {
@ -1309,11 +1332,14 @@ export class WalletProvider {
return resolve(res);
});
});
};
}
public getProtocolHandler(coin: string): string {
if (coin == 'bch') return 'bitcoincash';
else return 'bitcoin';
if (coin == 'bch') {
return 'bitcoincash';
} else {
return 'bitcoin';
}
}
public copyCopayers(wallet: any, newWallet: any): Promise<any> {