diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts index 4709eb96d..090193d67 100644 --- a/src/app/admin/admin.component.ts +++ b/src/app/admin/admin.component.ts @@ -1176,7 +1176,7 @@ export class AdminComponent implements OnInit { this.userProfileEntryResponseToUpdate?.StakeMultipleBasisPoints || 1.25 * 100 * 100; return this.backendApi .UpdateProfile( - environment.verificationEndpointHostname, + environment.verificationEndpointHostname ? environment.verificationEndpointHostname : this.globalVars.localNode, this.globalVars.loggedInUser.PublicKeyBase58Check /*UpdaterPublicKeyBase58Check*/, this.changeUsernamePublicKey /*ProfilePublicKeyBase58Check*/, // Start params diff --git a/src/app/backend-api.service.ts b/src/app/backend-api.service.ts index cc673c58b..64e86d0a2 100644 --- a/src/app/backend-api.service.ts +++ b/src/app/backend-api.service.ts @@ -480,6 +480,27 @@ export class BackendApiService { this.identityService.identityServicePublicKeyAdded = publicKeyAdded; } + // Launch Approve window in identity if necessary + launchApproveWindow(signed, res): Observable { + if (signed.approvalRequired) { + return this.identityService + .launch("/approve", { + tx: res.TransactionHex, + btcTx: res.btcTx, + ethTx: res.ethTx, + publicKey: res.publicKey, + }) + .pipe( + map((approved) => { + this.setIdentityServiceUsers(approved.users); + return { ...res, ...approved }; + }) + ); + } else { + return of({ ...res, ...signed }); + } + } + signAndSubmitTransaction(endpoint: string, request: Observable, PublicKeyBase58Check: string): Observable { return request .pipe( @@ -489,24 +510,7 @@ export class BackendApiService { transactionHex: res.TransactionHex, ...this.identityService.identityServiceParamsForKey(PublicKeyBase58Check), }) - .pipe( - switchMap((signed) => { - if (signed.approvalRequired) { - return this.identityService - .launch("/approve", { - tx: res.TransactionHex, - }) - .pipe( - map((approved) => { - this.setIdentityServiceUsers(approved.users); - return { ...res, ...approved }; - }) - ); - } else { - return of({ ...res, ...signed }); - } - }) - ) + .pipe(switchMap((signed) => this.launchApproveWindow(signed, res))) ) ) .pipe( @@ -626,7 +630,11 @@ export class BackendApiService { ...this.identityService.identityServiceParamsForKey(PublicKeyBase58Check), unsignedHashes: res.UnsignedHashes, }) - .pipe(map((signed) => ({ ...res, ...signed }))) + .pipe( + map((signed) => + this.launchApproveWindow(signed, { btcTx: res.UnsignedHashes[0], publicKey: PublicKeyBase58Check }) + ) + ) ) ); diff --git a/src/app/buy-deso-page/buy-deso-eth/buy-deso-eth.component.ts b/src/app/buy-deso-page/buy-deso-eth/buy-deso-eth.component.ts index b412c4d2d..c0dbb45a2 100644 --- a/src/app/buy-deso-page/buy-deso-eth/buy-deso-eth.component.ts +++ b/src/app/buy-deso-page/buy-deso-eth/buy-deso-eth.component.ts @@ -12,6 +12,8 @@ import { FeeMarketEIP1559Transaction, Transaction as LegacyTransaction, TxData, import { FeeMarketEIP1559TxData } from "@ethereumjs/tx/src/types"; import { Transaction, TransactionOptions } from "ethereumjs-tx"; import { BN } from "ethereumjs-util"; +import { map, switchMap } from "rxjs/operators"; +import { Observable, of, Subscription, zip } from "rxjs"; class Messages { static INCORRECT_PASSWORD = `The password you entered was incorrect.`; @@ -207,19 +209,19 @@ export class BuyDeSoEthComponent implements OnInit { }); } - signAndSubmitETH(retry: boolean = false): Promise { + signAndSubmitETH(retry: boolean = false): Subscription { return (BuyDeSoEthComponent.useLegacyTransaction ? this.constructLegacyTransactionOld() : this.constructFeeMarketTransaction() - ).then((res) => { + ).subscribe((res) => { if (!res?.signedTx) { console.error("No signedTx found - aborting"); - return; + return of(null); } const signedHash = res.signedTx.serialize().toString("hex"); // Submit the transaction. this.parentComponent.waitingOnTxnConfirmation = true; - this.backendApi + return this.backendApi .SubmitETHTx( this.globalVars.localNode, this.globalVars.loggedInUser.PublicKeyBase58Check, @@ -264,7 +266,7 @@ export class BuyDeSoEthComponent implements OnInit { // using the maintained ethereumjs/tx library. Sometimes EIP-1559 transactions are taking too long to mine or are not // mining at all due to gas calculations so this function is not currently used. Upgrading to using this function in // the future is preferred as we'll lower the amount of gas paid per transaction. - constructFeeMarketTransaction(): Promise> { + constructFeeMarketTransaction(): Observable> { return this.generateSignedTransaction(FeeMarketEIP1559Transaction); } @@ -272,14 +274,14 @@ export class BuyDeSoEthComponent implements OnInit { // library. There is an issue generating a valid signature for legacy transactions using the new library so this // is not actively used. However, upgrading to using this once that is resolved would be preferred as the old // ethereumjs-tx library is no longer maintained. - constructLegacyTransaction(): Promise> { + constructLegacyTransaction(): Observable> { return this.generateSignedTransaction(LegacyTransaction); } // constructLegacyTransactionOld creates a LegacyTransaction using the deprecated ethereum-tx library. This deprecated // library is being used as the updated version of this library causes issues generating a valid signature in identity // for legacy transactions. In the future, we should move to using constructFeeMarketTransaction. - constructLegacyTransactionOld(): Promise> { + constructLegacyTransactionOld(): Observable> { return this.generateSignedTransaction(Transaction); } @@ -287,55 +289,57 @@ export class BuyDeSoEthComponent implements OnInit { // transaction with the appropriate transaction data and sign it using identity. generateSignedTransaction( type: TypeOfTransactionType - ): Promise> { - return this.getNonceValueAndFees().then(({ nonce, value, fees }) => { - let txData: TxData; - let feeMarketTxData: FeeMarketEIP1559TxData; - txData = { - nonce, - value, - gasLimit: toHex(BuyDeSoEthComponent.instructionsPerBasicTransfer), - to: this.globalVars.buyETHAddress, - }; - if (BuyDeSoEthComponent.useLegacyTransaction) { - txData.gasPrice = fees.maxLegacyGasPriceHex; - } else { - feeMarketTxData = txData as FeeMarketEIP1559TxData; - feeMarketTxData.maxPriorityFeePerGas = fees.maxPriorityFeePerGasHex; - feeMarketTxData.maxFeePerGas = fees.maxFeePerGasHex; - feeMarketTxData.chainId = toHex(this.getChain()); - } - let toSign: string[]; - switch (type) { - case Transaction: { - let tx = new Transaction(txData, this.getOldOptions()); - // Poached from the sign method on Transaction in deprecated ethereumjs-tx library which demonstrates how to - // get the equivalent of getMessageToSign in the new ethereumjs tx library. - tx.v = new Buffer([]); - tx.r = new Buffer([]); - tx.s = new Buffer([]); - toSign = [tx.hash(false).toString("hex")]; - break; - } - case LegacyTransaction: { - // Generate an Unsigned Legacy Transaction from the data and generated a hash message to sign. - const tx = LegacyTransaction.fromTxData(txData, { common: this.common }); - toSign = [tx.getMessageToSign(true).toString("hex")]; - break; + ): Observable> { + return this.getNonceValueAndFees().pipe( + switchMap(({ nonce, value, fees }) => { + let txData: TxData; + let feeMarketTxData: FeeMarketEIP1559TxData; + txData = { + nonce, + value, + gasLimit: toHex(BuyDeSoEthComponent.instructionsPerBasicTransfer), + to: this.globalVars.buyETHAddress, + }; + if (BuyDeSoEthComponent.useLegacyTransaction) { + txData.gasPrice = fees.maxLegacyGasPriceHex; + } else { + feeMarketTxData = txData as FeeMarketEIP1559TxData; + feeMarketTxData.maxPriorityFeePerGas = fees.maxPriorityFeePerGasHex; + feeMarketTxData.maxFeePerGas = fees.maxFeePerGasHex; + feeMarketTxData.chainId = toHex(this.getChain()); } - case FeeMarketEIP1559Transaction: { - // Generate an Unsigned EIP 1559 Fee Market Transaction from the data and generated a hash message to sign. - let tx = FeeMarketEIP1559Transaction.fromTxData(feeMarketTxData, { common: this.common }); - toSign = [tx.getMessageToSign(true).toString("hex")]; - break; + let toSign: string[]; + switch (type) { + case Transaction: { + let tx = new Transaction(txData, this.getOldOptions()); + // Poached from the sign method on Transaction in deprecated ethereumjs-tx library which demonstrates how to + // get the equivalent of getMessageToSign in the new ethereumjs tx library. + tx.v = new Buffer([]); + tx.r = new Buffer([]); + tx.s = new Buffer([]); + toSign = [tx.hash(false).toString("hex")]; + break; + } + case LegacyTransaction: { + // Generate an Unsigned Legacy Transaction from the data and generated a hash message to sign. + const tx = LegacyTransaction.fromTxData(txData, { common: this.common }); + toSign = [tx.getMessageToSign(true).toString("hex")]; + break; + } + case FeeMarketEIP1559Transaction: { + // Generate an Unsigned EIP 1559 Fee Market Transaction from the data and generated a hash message to sign. + let tx = FeeMarketEIP1559Transaction.fromTxData(feeMarketTxData, { common: this.common }); + toSign = [tx.getMessageToSign(true).toString("hex")]; + break; + } } - } - return this.getSignedTransactionFromUnsignedHex( - type === FeeMarketEIP1559Transaction ? feeMarketTxData : txData, - toSign, - type - ); - }); + return this.getSignedTransactionFromUnsignedHex( + type === FeeMarketEIP1559Transaction ? feeMarketTxData : txData, + toSign, + type + ); + }) + ); } getTotalFee(fees: FeeDetails): BN { @@ -343,9 +347,9 @@ export class BuyDeSoEthComponent implements OnInit { } // getNonceValueAndFees is a helper to get the nonce, transaction value, and current fees when constructing a tx. - getNonceValueAndFees(): Promise<{ nonce: Hex; value: Hex; fees: FeeDetails }> { - return Promise.all([this.getTransactionCount(this.ethDepositAddress(), "pending"), this.getFees()]).then( - ([transactionCount, fees]) => { + getNonceValueAndFees(): Observable<{ nonce: Hex; value: Hex; fees: FeeDetails }> { + return zip(this.getTransactionCount(this.ethDepositAddress(), "pending"), this.getFees()).pipe( + map(([transactionCount, fees]) => { const nonce = toHex(transactionCount); let value = this.getValue(fees); return { @@ -353,7 +357,7 @@ export class BuyDeSoEthComponent implements OnInit { value, fees, }; - } + }) ); } @@ -363,55 +367,65 @@ export class BuyDeSoEthComponent implements OnInit { txData: TxData | FeeMarketEIP1559TxData, toSign: string[], signedTxType: TypeOfTransactionType - ): Promise> { + ): Observable> { return this.identityService .signETH({ ...this.identityService.identityServiceParamsForKey(this.globalVars.loggedInUser.PublicKeyBase58Check), unsignedHashes: toSign, }) - .toPromise() - .then( - (res) => { - // Get the signature and merge it into the TxData defined above. - const signature: { s: any; r: any; v: number | null } = res.signatures[0]; - // For Legacy transaction using the old library, we need to modify V to satisfy EIP 155 constraints. - if (signedTxType === Transaction && this.common.gteHardfork("spuriousDragon")) { - signature.v = signature.v === 0 ? this.getChain() * 2 + 35 : this.getChain() * 2 + 36; - } - // Merge the signature into the transaction data. - const signedTxData = { - ...txData, - ...signature, - }; - let signedTx: Transaction | LegacyTransaction | FeeMarketEIP1559Transaction; - - switch (signedTxType) { - case Transaction: { - // Create a signed Legacy transaction using the deprecated ethereumjs-tx library. - signedTx = new Transaction(signedTxData, this.getOldOptions()); - break; - } - case LegacyTransaction: { - // Create a signed Legacy transaction using the maintained ethereumjs/tx library. - const legacyTxData = txData as TxData; - signedTx = LegacyTransaction.fromTxData(legacyTxData, this.getOptions()); - break; - } - case FeeMarketEIP1559Transaction: { - // Create a Fee Market EIP-1559 transaction using the maintained ethereumjs/tx library. - const feeMarketTxdata = txData as FeeMarketEIP1559TxData; - signedTx = FeeMarketEIP1559Transaction.fromTxData(feeMarketTxdata, this.getOptions()); - break; - } - } - // Construct and serialize the transaction. - return >{ signedTx, toSign }; - }, - (err) => { - console.error(err); - this.globalVars._alertError(err); - return null; - } + .pipe( + switchMap((signed) => { + return this.backendApi + .launchApproveWindow(signed, { + ethTx: toSign[0], + publicKey: this.globalVars.loggedInUser?.PublicKeyBase58Check, + }) + .pipe( + map( + (res) => { + // Get the signature and merge it into the TxData defined above. + const signature: { s: any; r: any; v: number | null } = res.signatures[0]; + // For Legacy transaction using the old library, we need to modify V to satisfy EIP 155 constraints. + if (signedTxType === Transaction && this.common.gteHardfork("spuriousDragon")) { + signature.v = signature.v === 0 ? this.getChain() * 2 + 35 : this.getChain() * 2 + 36; + } + // Merge the signature into the transaction data. + const signedTxData = { + ...txData, + ...signature, + }; + let signedTx: Transaction | LegacyTransaction | FeeMarketEIP1559Transaction; + + switch (signedTxType) { + case Transaction: { + // Create a signed Legacy transaction using the deprecated ethereumjs-tx library. + signedTx = new Transaction(signedTxData, this.getOldOptions()); + break; + } + case LegacyTransaction: { + // Create a signed Legacy transaction using the maintained ethereumjs/tx library. + const legacyTxData = txData as TxData; + signedTx = LegacyTransaction.fromTxData(legacyTxData, this.getOptions()); + break; + } + case FeeMarketEIP1559Transaction: { + // Create a Fee Market EIP-1559 transaction using the maintained ethereumjs/tx library. + const feeMarketTxdata = txData as FeeMarketEIP1559TxData; + signedTx = FeeMarketEIP1559Transaction.fromTxData(feeMarketTxdata, this.getOptions()); + break; + } + } + // Construct and serialize the transaction. + return >{ signedTx, toSign }; + }, + (err) => { + console.error(err); + this.globalVars._alertError(err); + return null; + } + ) + ); + }) ); } @@ -446,7 +460,7 @@ export class BuyDeSoEthComponent implements OnInit { } clickMaxDESO() { - this.getFees().then((res) => { + this.getFees().subscribe((res) => { this.weiFeeEstimate = this.getTotalFee(res); this.ethFeeEstimate = Number(fromWei(this.weiFeeEstimate)); this.weiToExchange = this.weiBalance; @@ -516,17 +530,17 @@ export class BuyDeSoEthComponent implements OnInit { if (!this.loadingBalance) { this.loadingBalance = true; this.getBalance(this.ethDepositAddress(), "latest") - .then((res) => { + .subscribe((res) => { this.weiBalance = toBN(res); this.ethBalance = Number(fromWei(this.weiBalance)); }) - .finally(() => { + .add(() => { this.loadingBalance = false; }); } if (!this.loadingFee) { this.loadingFee = true; - this.getFees().then((res) => { + this.getFees().subscribe((res) => { this.weiFeeEstimate = this.getTotalFee(res); this.ethFeeEstimate = Number(fromWei(this.weiFeeEstimate)); this.weiToExchange = this.weiFeeEstimate; @@ -535,47 +549,48 @@ export class BuyDeSoEthComponent implements OnInit { } } - queryETHRPC(method: string, params: any[]): Promise { + queryETHRPC(method: string, params: any[]): Observable { return this.backendApi .QueryETHRPC(this.globalVars.localNode, method, params, this.globalVars.loggedInUser?.PublicKeyBase58Check) - .toPromise() - .then( - (res) => { - return res.result; - }, - (err) => { - console.error(err); - } + .pipe( + map( + (res) => res.result, + (err) => { + console.error(err); + } + ) ); } // Get current gas price. - getGasPrice(): Promise { + getGasPrice(): Observable { return this.queryETHRPC("eth_gasPrice", []); } // Gets the data about the pending block. - getBlock(block: string): Promise { + getBlock(block: string): Observable { return this.queryETHRPC("eth_getBlockByNumber", [block, false]); } - getTransactionCount(address: string, block: string = "pending"): Promise { - return this.queryETHRPC("eth_getTransactionCount", [address, block]).then((result) => hexToNumber(result)); + getTransactionCount(address: string, block: string = "pending"): Observable { + return this.queryETHRPC("eth_getTransactionCount", [address, block]).pipe( + map((result) => hexToNumber(result)) + ); } // Gets balance for address. - getBalance(address: string, block: string = "latest"): Promise { + getBalance(address: string, block: string = "latest"): Observable { return this.queryETHRPC("eth_getBalance", [address, block]); } - getMaxPriorityFeePerGas(): Promise { + getMaxPriorityFeePerGas(): Observable { return this.queryETHRPC("eth_maxPriorityFeePerGas", []); } // getFees returns all the numbers and hex-strings necessary for computing eth gas. - getFees(): Promise { - return Promise.all([this.getBlock("pending"), this.getMaxPriorityFeePerGas(), this.getGasPrice()]).then( - ([block, maxPriorityFeePerGasHex, gasPriceHex]) => { + getFees(): Observable { + return zip(this.getBlock("pending"), this.getMaxPriorityFeePerGas(), this.getGasPrice()).pipe( + map(([block, maxPriorityFeePerGasHex, gasPriceHex]) => { const baseFeePerGas = toBN(block.baseFeePerGas); // Add a gwei to make this transaction more attractive to miners. @@ -611,7 +626,7 @@ export class BuyDeSoEthComponent implements OnInit { maxLegacyGasPriceHex: toHex(maxFeePerGas.gt(gasPrice) ? maxFeePerGas : gasPrice), maxTotalFeesLegacy: totalFeesEIP1559.gt(totalFeesLegacy) ? totalFeesEIP1559 : totalFeesLegacy, }; - } + }) ); } diff --git a/src/app/global-vars.service.ts b/src/app/global-vars.service.ts index 7d4c3601d..846a27cfe 100755 --- a/src/app/global-vars.service.ts +++ b/src/app/global-vars.service.ts @@ -325,7 +325,10 @@ export class GlobalVarsService { // Fetch referralLinks for the userList before completing the load. this.backendApi - .GetReferralInfoForUser(environment.verificationEndpointHostname, this.loggedInUser.PublicKeyBase58Check) + .GetReferralInfoForUser( + environment.verificationEndpointHostname ? environment.verificationEndpointHostname : this.localNode, + this.loggedInUser.PublicKeyBase58Check + ) .subscribe( (res: any) => { this.loggedInUser.ReferralInfoResponses = res.ReferralInfoResponses; @@ -1136,7 +1139,10 @@ export class GlobalVarsService { return; } this.backendApi - .GetJumioStatusForPublicKey(environment.verificationEndpointHostname, publicKey) + .GetJumioStatusForPublicKey( + environment.verificationEndpointHostname ? environment.verificationEndpointHostname : this.localNode, + publicKey + ) .subscribe( (res: any) => { if (res.JumioVerified) { @@ -1184,7 +1190,7 @@ export class GlobalVarsService { const referralHash = localStorage.getItem("referralCode"); if (referralHash) { this.backendApi - .GetReferralInfoForReferralHash(environment.verificationEndpointHostname, referralHash) + .GetReferralInfoForReferralHash(environment.verificationEndpointHostname ? environment.verificationEndpointHostname : this.localNode, referralHash) .subscribe((res) => { const referralInfo = res.ReferralInfoResponse.Info; if ( diff --git a/src/app/identity.service.ts b/src/app/identity.service.ts index 1cd7c4173..47c655db6 100644 --- a/src/app/identity.service.ts +++ b/src/app/identity.service.ts @@ -49,6 +49,8 @@ export class IdentityService { public_key?: string; hideJumio?: boolean; accessLevelRequest?: number; + btcTx?: string; + ethTx?: string; } ): Observable { let url = this.identityServiceURL as string; @@ -85,6 +87,14 @@ export class IdentityService { httpParams = httpParams.append("accessLevelRequest", params.accessLevelRequest.toString()); } + if (params?.btcTx) { + httpParams = httpParams.append("btcTx", params.btcTx); + } + + if (params?.ethTx) { + httpParams = httpParams.append("ethTx", params.ethTx); + } + const paramsStr = httpParams.toString(); if (paramsStr) { url += `?${paramsStr}`; diff --git a/src/app/update-profile-page/update-profile/update-profile.component.ts b/src/app/update-profile-page/update-profile/update-profile.component.ts index 21bd7305b..b59f63bf8 100644 --- a/src/app/update-profile-page/update-profile/update-profile.component.ts +++ b/src/app/update-profile-page/update-profile/update-profile.component.ts @@ -168,7 +168,7 @@ export class UpdateProfileComponent implements OnInit, OnChanges { // This is a standalone function in case we decide we want to confirm fees before doing a real transaction. _callBackendUpdateProfile() { return this.backendApi.UpdateProfile( - environment.verificationEndpointHostname, + environment.verificationEndpointHostname ? environment.verificationEndpointHostname : this.globalVars.localNode, this.globalVars.loggedInUser.PublicKeyBase58Check /*UpdaterPublicKeyBase58Check*/, "" /*ProfilePublicKeyBase58Check*/, // Start params diff --git a/src/app/wallet/wallet.component.ts b/src/app/wallet/wallet.component.ts index 8780800bb..51365d8ba 100644 --- a/src/app/wallet/wallet.component.ts +++ b/src/app/wallet/wallet.component.ts @@ -301,7 +301,9 @@ export class WalletComponent implements OnInit, OnDestroy { if (res.isConfirmed) { return this.backendApi .UpdateProfile( - environment.verificationEndpointHostname, + environment.verificationEndpointHostname + ? environment.verificationEndpointHostname + : this.globalVars.localNode, this.globalVars.loggedInUser.PublicKeyBase58Check, "", "",