diff options
Diffstat (limited to 'packages/taler-wallet-core')
12 files changed, 373 insertions, 88 deletions
diff --git a/packages/taler-wallet-core/src/bank-api-client.ts b/packages/taler-wallet-core/src/bank-api-client.ts new file mode 100644 index 000000000..744c3b833 --- /dev/null +++ b/packages/taler-wallet-core/src/bank-api-client.ts @@ -0,0 +1,249 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Client for the Taler (demo-)bank. + */ + +/** + * Imports. + */ +import { + AmountString, + buildCodecForObject, + Codec, + codecForString, + encodeCrock, + getRandomBytes, +} from "@gnu-taler/taler-util"; +import { + HttpRequestLibrary, + readSuccessResponseJsonOrErrorCode, + readSuccessResponseJsonOrThrow, +} from "./index.browser.js"; + +export enum CreditDebitIndicator { + Credit = "credit", + Debit = "debit", +} + +export interface BankAccountBalanceResponse { + balance: { + amount: AmountString; + credit_debit_indicator: CreditDebitIndicator; + }; +} + +export interface BankServiceHandle { + readonly baseUrl: string; + readonly http: HttpRequestLibrary; +} + +export interface BankUser { + username: string; + password: string; + accountPaytoUri: string; +} + +export interface WithdrawalOperationInfo { + withdrawal_id: string; + taler_withdraw_uri: string; +} + +/** + * FIXME: Rename, this is not part of the integration test harness anymore. + */ +export interface HarnessExchangeBankAccount { + accountName: string; + accountPassword: string; + accountPaytoUri: string; + wireGatewayApiBaseUrl: string; +} + +/** + * Helper function to generate the "Authorization" HTTP header. + */ +function makeBasicAuthHeader(username: string, password: string): string { + const auth = `${username}:${password}`; + const authEncoded: string = Buffer.from(auth).toString("base64"); + return `Basic ${authEncoded}`; +} + +const codecForWithdrawalOperationInfo = (): Codec<WithdrawalOperationInfo> => + buildCodecForObject<WithdrawalOperationInfo>() + .property("withdrawal_id", codecForString()) + .property("taler_withdraw_uri", codecForString()) + .build("WithdrawalOperationInfo"); + +export namespace BankApi { + export async function registerAccount( + bank: BankServiceHandle, + username: string, + password: string, + ): Promise<BankUser> { + const url = new URL("testing/register", bank.baseUrl); + const resp = await bank.http.postJson(url.href, { username, password }); + let paytoUri = `payto://x-taler-bank/localhost/${username}`; + if (resp.status !== 200 && resp.status !== 202) { + throw new Error(); + } + try { + const respJson = await resp.json(); + // LibEuFin demobank returns payto URI in response + if (respJson.paytoUri) { + paytoUri = respJson.paytoUri; + } + } catch (e) {} + return { + password, + username, + accountPaytoUri: paytoUri, + }; + } + + export async function createRandomBankUser( + bank: BankServiceHandle, + ): Promise<BankUser> { + const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase(); + const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase(); + return await registerAccount(bank, username, password); + } + + export async function adminAddIncoming( + bank: BankServiceHandle, + params: { + exchangeBankAccount: HarnessExchangeBankAccount; + amount: string; + reservePub: string; + debitAccountPayto: string; + }, + ) { + let maybeBaseUrl = bank.baseUrl; + let url = new URL( + `taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`, + maybeBaseUrl, + ); + await bank.http.postJson( + url.href, + { + amount: params.amount, + reserve_pub: params.reservePub, + debit_account: params.debitAccountPayto, + }, + { + headers: { + Authorization: makeBasicAuthHeader( + params.exchangeBankAccount.accountName, + params.exchangeBankAccount.accountPassword, + ), + }, + }, + ); + } + + export async function confirmWithdrawalOperation( + bank: BankServiceHandle, + bankUser: BankUser, + wopi: WithdrawalOperationInfo, + ): Promise<void> { + const url = new URL( + `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/confirm`, + bank.baseUrl, + ); + await bank.http.postJson( + url.href, + {}, + { + headers: { + Authorization: makeBasicAuthHeader( + bankUser.username, + bankUser.password, + ), + }, + }, + ); + } + + export async function abortWithdrawalOperation( + bank: BankServiceHandle, + bankUser: BankUser, + wopi: WithdrawalOperationInfo, + ): Promise<void> { + const url = new URL( + `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/abort`, + bank.baseUrl, + ); + await bank.http.postJson( + url.href, + {}, + { + headers: { + Authorization: makeBasicAuthHeader( + bankUser.username, + bankUser.password, + ), + }, + }, + ); + } +} + +export namespace BankAccessApi { + export async function getAccountBalance( + bank: BankServiceHandle, + bankUser: BankUser, + ): Promise<BankAccountBalanceResponse> { + const url = new URL(`accounts/${bankUser.username}`, bank.baseUrl); + const resp = await bank.http.get(url.href, { + headers: { + Authorization: makeBasicAuthHeader( + bankUser.username, + bankUser.password, + ), + }, + }); + return await resp.json(); + } + + export async function createWithdrawalOperation( + bank: BankServiceHandle, + bankUser: BankUser, + amount: string, + ): Promise<WithdrawalOperationInfo> { + const url = new URL( + `accounts/${bankUser.username}/withdrawals`, + bank.baseUrl, + ); + const resp = await bank.http.postJson( + url.href, + { + amount, + }, + { + headers: { + Authorization: makeBasicAuthHeader( + bankUser.username, + bankUser.password, + ), + }, + }, + ); + return readSuccessResponseJsonOrThrow( + resp, + codecForWithdrawalOperationInfo(), + ); + } +} diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts index 16446bb9e..b5a5950b1 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts @@ -22,20 +22,22 @@ /** * Imports. */ -import { CoinRecord, DenominationRecord, WireFee } from "../../db.js"; +import { DenominationRecord, WireFee } from "../../db.js"; import { CryptoWorker } from "./cryptoWorkerInterface.js"; import { + BlindedDenominationSignature, CoinDepositPermission, CoinEnvelope, RecoupRefreshRequest, RecoupRequest, + UnblindedSignature, } from "@gnu-taler/taler-util"; import { BenchmarkResult, - PlanchetCreationResult, + WithdrawalPlanchet, PlanchetCreationRequest, DepositInfo, MakeSyncSignatureRequest, @@ -324,10 +326,19 @@ export class CryptoApi { return p; } - createPlanchet( - req: PlanchetCreationRequest, - ): Promise<PlanchetCreationResult> { - return this.doRpc<PlanchetCreationResult>("createPlanchet", 1, req); + createPlanchet(req: PlanchetCreationRequest): Promise<WithdrawalPlanchet> { + return this.doRpc<WithdrawalPlanchet>("createPlanchet", 1, req); + } + + unblindDenominationSignature(req: { + planchet: WithdrawalPlanchet; + evSig: BlindedDenominationSignature; + }): Promise<UnblindedSignature> { + return this.doRpc<UnblindedSignature>( + "unblindDenominationSignature", + 1, + req, + ); } createTipPlanchet(req: DeriveTipRequest): Promise<DerivedTipPlanchet> { diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts index af77e2be4..15a086ae1 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -53,7 +53,7 @@ import { Logger, MakeSyncSignatureRequest, PlanchetCreationRequest, - PlanchetCreationResult, + WithdrawalPlanchet, randomBytes, RecoupRefreshRequest, RecoupRequest, @@ -70,6 +70,9 @@ import { Timestamp, timestampTruncateToSecond, typedArrayConcat, + BlindedDenominationSignature, + RsaUnblindedSignature, + UnblindedSignature, } from "@gnu-taler/taler-util"; import bigint from "big-integer"; import { DenominationRecord, WireFee } from "../../db.js"; @@ -169,7 +172,7 @@ export class CryptoImplementation { */ async createPlanchet( req: PlanchetCreationRequest, - ): Promise<PlanchetCreationResult> { + ): Promise<WithdrawalPlanchet> { const denomPub = req.denomPub; if (denomPub.cipher === DenomKeyType.Rsa) { const reservePub = decodeCrock(req.reservePub); @@ -200,7 +203,7 @@ export class CryptoImplementation { priv: req.reservePriv, }); - const planchet: PlanchetCreationResult = { + const planchet: WithdrawalPlanchet = { blindingKey: encodeCrock(derivedPlanchet.bks), coinEv, coinPriv: encodeCrock(derivedPlanchet.coinPriv), @@ -428,6 +431,30 @@ export class CryptoImplementation { }; } + unblindDenominationSignature(req: { + planchet: WithdrawalPlanchet; + evSig: BlindedDenominationSignature; + }): UnblindedSignature { + if (req.evSig.cipher === DenomKeyType.Rsa) { + if (req.planchet.denomPub.cipher !== DenomKeyType.Rsa) { + throw new Error( + "planchet cipher does not match blind signature cipher", + ); + } + const denomSig = rsaUnblind( + decodeCrock(req.evSig.blinded_rsa_signature), + decodeCrock(req.planchet.denomPub.rsa_public_key), + decodeCrock(req.planchet.blindingKey), + ); + return { + cipher: DenomKeyType.Rsa, + rsa_signature: encodeCrock(denomSig), + }; + } else { + throw Error(`unblinding for cipher ${req.evSig.cipher} not implemented`); + } + } + /** * Unblind a blindly signed value. */ diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts index 179ba6b8f..c657290f1 100644 --- a/packages/taler-wallet-core/src/index.ts +++ b/packages/taler-wallet-core/src/index.ts @@ -36,7 +36,7 @@ export * from "./db-utils.js"; export { CryptoImplementation } from "./crypto/workers/cryptoImplementation.js"; export type { CryptoWorker } from "./crypto/workers/cryptoWorkerInterface.js"; export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi.js"; -export { SynchronousCryptoWorker } from "./crypto/workers/synchronousWorker.js" +export { SynchronousCryptoWorker } from "./crypto/workers/synchronousWorker.js"; export * from "./pending-types.js"; @@ -47,3 +47,12 @@ export * from "./wallet.js"; export * from "./operations/backup/index.js"; export { makeEventId } from "./operations/transactions.js"; + +export * from "./operations/exchanges.js"; + +export * from "./bank-api-client.js"; + +export * from "./operations/reserves.js"; +export * from "./operations/withdraw.js"; + +export * from "./crypto/workers/synchronousWorkerFactory.js"; diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index e45da7b4c..a5d6c93cf 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -20,6 +20,7 @@ import { buildCodecForObject, canonicalJson, Codec, + codecForDepositSuccess, codecForString, codecForTimestamp, codecOptional, @@ -32,6 +33,7 @@ import { GetFeeForDepositRequest, getRandomBytes, getTimestampNow, + hashWire, Logger, NotificationType, parsePaytoUri, @@ -57,7 +59,6 @@ import { generateDepositPermissions, getCandidatePayCoins, getTotalPaymentCost, - hashWire, } from "./pay.js"; import { getTotalRefreshCost } from "./refresh.js"; @@ -66,43 +67,6 @@ import { getTotalRefreshCost } from "./refresh.js"; */ const logger = new Logger("deposits.ts"); -interface DepositSuccess { - // Optional base URL of the exchange for looking up wire transfers - // associated with this transaction. If not given, - // the base URL is the same as the one used for this request. - // Can be used if the base URL for /transactions/ differs from that - // for /coins/, i.e. for load balancing. Clients SHOULD - // respect the transaction_base_url if provided. Any HTTP server - // belonging to an exchange MUST generate a 307 or 308 redirection - // to the correct base URL should a client uses the wrong base - // URL, or if the base URL has changed since the deposit. - transaction_base_url?: string; - - // timestamp when the deposit was received by the exchange. - exchange_timestamp: Timestamp; - - // the EdDSA signature of TALER_DepositConfirmationPS using a current - // signing key of the exchange affirming the successful - // deposit and that the exchange will transfer the funds after the refund - // deadline, or as soon as possible if the refund deadline is zero. - exchange_sig: string; - - // public EdDSA key of the exchange that was used to - // generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: string; -} - -const codecForDepositSuccess = (): Codec<DepositSuccess> => - buildCodecForObject<DepositSuccess>() - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("exchange_timestamp", codecForTimestamp) - .property("transaction_base_url", codecOptional(codecForString())) - .build("DepositSuccess"); - async function resetDepositGroupRetry( ws: InternalWalletState, depositGroupId: string, @@ -202,7 +166,6 @@ async function processDepositGroupImpl( } const perm = depositPermissions[i]; let requestBody: any; - logger.info("creating v10 deposit request"); requestBody = { contribution: Amounts.stringify(perm.contribution), merchant_payto_uri: depositGroup.wire.payto_uri, diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 9d4a56fff..2006b792f 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -43,6 +43,7 @@ import { codecForAny, DenominationPubKey, DenomKeyType, + ExchangeKeysJson, } from "@gnu-taler/taler-util"; import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util"; import { CryptoApi } from "../crypto/workers/cryptoApi.js"; @@ -292,12 +293,37 @@ async function validateWireInfo( }; } +export interface ExchangeInfo { + wire: ExchangeWireJson; + keys: ExchangeKeysDownloadResult; +} + +export async function downloadExchangeInfo( + exchangeBaseUrl: string, + http: HttpRequestLibrary, +): Promise<ExchangeInfo> { + const wireInfo = await downloadExchangeWireInfo( + exchangeBaseUrl, + http, + Duration.getForever(), + ); + const keysInfo = await downloadExchangeKeysInfo( + exchangeBaseUrl, + http, + Duration.getForever(), + ); + return { + keys: keysInfo, + wire: wireInfo, + }; +} + /** * Fetch wire information for an exchange. * * @param exchangeBaseUrl Exchange base URL, assumed to be already normalized. */ -async function downloadExchangeWithWireInfo( +async function downloadExchangeWireInfo( exchangeBaseUrl: string, http: HttpRequestLibrary, timeout: Duration, @@ -374,7 +400,7 @@ interface ExchangeKeysDownloadResult { /** * Download and validate an exchange's /keys data. */ -async function downloadKeysInfo( +async function downloadExchangeKeysInfo( baseUrl: string, http: HttpRequestLibrary, timeout: Duration, @@ -526,10 +552,10 @@ async function updateExchangeFromUrlImpl( const timeout = getExchangeRequestTimeout(); - const keysInfo = await downloadKeysInfo(baseUrl, ws.http, timeout); + const keysInfo = await downloadExchangeKeysInfo(baseUrl, ws.http, timeout); logger.info("updating exchange /wire info"); - const wireInfoDownload = await downloadExchangeWithWireInfo( + const wireInfoDownload = await downloadExchangeWireInfo( baseUrl, ws.http, timeout, diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 6001cac4f..9844dc52e 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -113,19 +113,6 @@ import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js"; const logger = new Logger("pay.ts"); /** - * FIXME: Move this to crypto worker or at least talerCrypto.ts - */ -export function hashWire(paytoUri: string, salt: string): string { - const r = kdf( - 64, - stringToBytes(paytoUri + "\0"), - decodeCrock(salt), - stringToBytes("merchant-wire-signature"), - ); - return encodeCrock(r); -} - -/** * Compute the total cost of a payment to the customer. * * This includes the amount taken by the merchant, fees (wire/deposit) contributed diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 550119de1..cc2a1c566 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -17,6 +17,7 @@ import { DenomKeyType, encodeCrock, + ExchangeMeltRequest, ExchangeProtocolVersion, ExchangeRefreshRevealRequest, getRandomBytes, @@ -394,17 +395,14 @@ async function refreshMelt( `coins/${oldCoin.coinPub}/melt`, oldCoin.exchangeBaseUrl, ); - let meltReqBody: any; - if (oldDenom.denomPub.cipher === DenomKeyType.Rsa) { - meltReqBody = { - coin_pub: oldCoin.coinPub, - confirm_sig: derived.confirmSig, - denom_pub_hash: oldCoin.denomPubHash, - denom_sig: oldCoin.denomSig, - rc: derived.hash, - value_with_fee: Amounts.stringify(derived.meltValueWithFee), - }; - } + const meltReqBody: ExchangeMeltRequest = { + coin_pub: oldCoin.coinPub, + confirm_sig: derived.confirmSig, + denom_pub_hash: oldCoin.denomPubHash, + denom_sig: oldCoin.denomSig, + rc: derived.hash, + value_with_fee: Amounts.stringify(derived.meltValueWithFee), + }; const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => { return await ws.http.postJson(reqUrl.href, meltReqBody, { diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts index a16d3ec31..d91ce89f1 100644 --- a/packages/taler-wallet-core/src/operations/reserves.ts +++ b/packages/taler-wallet-core/src/operations/reserves.ts @@ -780,7 +780,7 @@ export async function createTalerWithdrawReserve( selectedExchange: string, ): Promise<AcceptWithdrawalResponse> { await updateExchangeFromUrl(ws, selectedExchange); - const withdrawInfo = await getBankWithdrawalInfo(ws, talerWithdrawUri); + const withdrawInfo = await getBankWithdrawalInfo(ws.http, talerWithdrawUri); const exchangePaytoUri = await getExchangePaytoUri( ws, selectedExchange, diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index d6f0626dd..93f48fb83 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -74,7 +74,7 @@ function makeId(length: number): string { /** * Helper function to generate the "Authorization" HTTP header. */ -function makeAuth(username: string, password: string): string { +function makeBasicAuthHeader(username: string, password: string): string { const auth = `${username}:${password}`; const authEncoded: string = Buffer.from(auth).toString("base64"); return `Basic ${authEncoded}`; @@ -89,7 +89,7 @@ export async function withdrawTestBalance( const bankUser = await registerRandomBankUser(ws.http, bankBaseUrl); logger.trace(`Registered bank user ${JSON.stringify(bankUser)}`); - const wresp = await createBankWithdrawalUri( + const wresp = await createDemoBankWithdrawalUri( ws.http, bankBaseUrl, bankUser, @@ -119,7 +119,11 @@ function getMerchantAuthHeader(m: MerchantBackendInfo): Record<string, string> { return {}; } -async function createBankWithdrawalUri( +/** + * Use the testing API of a demobank to create a taler://withdraw URI + * that the wallet can then use to make a withdrawal. + */ +export async function createDemoBankWithdrawalUri( http: HttpRequestLibrary, bankBaseUrl: string, bankUser: BankUser, @@ -136,7 +140,7 @@ async function createBankWithdrawalUri( }, { headers: { - Authorization: makeAuth(bankUser.username, bankUser.password), + Authorization: makeBasicAuthHeader(bankUser.username, bankUser.password), }, }, ); @@ -159,7 +163,7 @@ async function confirmBankWithdrawalUri( {}, { headers: { - Authorization: makeAuth(bankUser.username, bankUser.password), + Authorization: makeBasicAuthHeader(bankUser.username, bankUser.password), }, }, ); diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index ae3763a02..392cecf0b 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -59,7 +59,10 @@ import { WithdrawalGroupRecord, } from "../db.js"; import { walletCoreDebugFlags } from "../util/debugFlags.js"; -import { readSuccessResponseJsonOrThrow } from "../util/http.js"; +import { + HttpRequestLibrary, + readSuccessResponseJsonOrThrow, +} from "../util/http.js"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; import { guardOperationException, @@ -271,9 +274,11 @@ export function selectWithdrawalDenominations( /** * Get information about a withdrawal from * a taler://withdraw URI by asking the bank. + * + * FIXME: Move into bank client. */ export async function getBankWithdrawalInfo( - ws: InternalWalletState, + http: HttpRequestLibrary, talerWithdrawUri: string, ): Promise<BankWithdrawDetails> { const uriResult = parseWithdrawUri(talerWithdrawUri); @@ -283,7 +288,7 @@ export async function getBankWithdrawalInfo( const configReqUrl = new URL("config", uriResult.bankIntegrationApiBaseUrl); - const configResp = await ws.http.get(configReqUrl.href); + const configResp = await http.get(configReqUrl.href); const config = await readSuccessResponseJsonOrThrow( configResp, codecForTalerConfigResponse(), @@ -309,7 +314,7 @@ export async function getBankWithdrawalInfo( `withdrawal-operation/${uriResult.withdrawalOperationId}`, uriResult.bankIntegrationApiBaseUrl, ); - const resp = await ws.http.get(reqUrl.href); + const resp = await http.get(reqUrl.href); const status = await readSuccessResponseJsonOrThrow( resp, codecForWithdrawOperationStatusResponse(), @@ -1076,7 +1081,7 @@ export async function getWithdrawalDetailsForUri( talerWithdrawUri: string, ): Promise<WithdrawUriInfoResponse> { logger.trace(`getting withdrawal details for URI ${talerWithdrawUri}`); - const info = await getBankWithdrawalInfo(ws, talerWithdrawUri); + const info = await getBankWithdrawalInfo(ws.http, talerWithdrawUri); logger.trace(`got bank info`); if (info.suggestedExchange) { // FIXME: right now the exchange gets permanently added, diff --git a/packages/taler-wallet-core/src/util/http.ts b/packages/taler-wallet-core/src/util/http.ts index 3a7062c99..43fe29bba 100644 --- a/packages/taler-wallet-core/src/util/http.ts +++ b/packages/taler-wallet-core/src/util/http.ts @@ -34,6 +34,7 @@ import { timestampMax, TalerErrorDetails, Codec, + j2s, } from "@gnu-taler/taler-util"; import { TalerErrorCode } from "@gnu-taler/taler-util"; @@ -131,6 +132,11 @@ export async function readTalerErrorResponse( const errJson = await httpResponse.json(); const talerErrorCode = errJson.code; if (typeof talerErrorCode !== "number") { + logger.warn( + `malformed error response (status ${httpResponse.status}): ${j2s( + errJson, + )}`, + ); throw new OperationFailedError( makeErrorDetails( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, |