wallet: towards db-less benchmarking, some refactoring
This commit is contained in:
parent
9e7ee06ad1
commit
332745862e
@ -785,6 +785,22 @@ export function setupRefreshTransferPub(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param paytoUri
|
||||||
|
* @param salt 16-byte salt
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function hashWire(paytoUri: string, salt: string): string {
|
||||||
|
const r = kdf(
|
||||||
|
64,
|
||||||
|
stringToBytes(paytoUri + "\0"),
|
||||||
|
decodeCrock(salt),
|
||||||
|
stringToBytes("merchant-wire-signature"),
|
||||||
|
);
|
||||||
|
return encodeCrock(r);
|
||||||
|
}
|
||||||
|
|
||||||
export enum TalerSignaturePurpose {
|
export enum TalerSignaturePurpose {
|
||||||
MERCHANT_TRACK_TRANSACTION = 1103,
|
MERCHANT_TRACK_TRANSACTION = 1103,
|
||||||
WALLET_RESERVE_WITHDRAW = 1200,
|
WALLET_RESERVE_WITHDRAW = 1200,
|
||||||
|
@ -951,6 +951,15 @@ export interface MerchantPayResponse {
|
|||||||
sig: string;
|
sig: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExchangeMeltRequest {
|
||||||
|
coin_pub: CoinPublicKeyString;
|
||||||
|
confirm_sig: EddsaSignatureString;
|
||||||
|
denom_pub_hash: HashCodeString;
|
||||||
|
denom_sig: UnblindedSignature;
|
||||||
|
rc: string;
|
||||||
|
value_with_fee: AmountString;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ExchangeMeltResponse {
|
export interface ExchangeMeltResponse {
|
||||||
/**
|
/**
|
||||||
* Which of the kappa indices does the client not have to reveal.
|
* Which of the kappa indices does the client not have to reveal.
|
||||||
@ -1710,3 +1719,40 @@ export interface ExchangeRefreshRevealRequest {
|
|||||||
|
|
||||||
link_sigs: EddsaSignatureString[];
|
link_sigs: EddsaSignatureString[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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");
|
||||||
|
@ -78,6 +78,9 @@ export namespace Duration {
|
|||||||
return Math.ceil(d.d_ms / 1000 / 60 / 60 / 24 / 365);
|
return Math.ceil(d.d_ms / 1000 / 60 / 60 / 24 / 365);
|
||||||
}
|
}
|
||||||
export const fromSpec = durationFromSpec;
|
export const fromSpec = durationFromSpec;
|
||||||
|
export function getForever(): Duration {
|
||||||
|
return { d_ms: "forever" };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Timestamp {
|
export namespace Timestamp {
|
||||||
|
@ -458,7 +458,7 @@ export interface TalerErrorDetails {
|
|||||||
details: unknown;
|
details: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlanchetCreationResult {
|
export interface WithdrawalPlanchet {
|
||||||
coinPub: string;
|
coinPub: string;
|
||||||
coinPriv: string;
|
coinPriv: string;
|
||||||
reservePub: string;
|
reservePub: string;
|
||||||
|
106
packages/taler-wallet-cli/src/bench2.ts
Normal file
106
packages/taler-wallet-cli/src/bench2.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
buildCodecForObject,
|
||||||
|
codecForNumber,
|
||||||
|
codecForString,
|
||||||
|
codecOptional,
|
||||||
|
j2s,
|
||||||
|
Logger,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import {
|
||||||
|
getDefaultNodeWallet2,
|
||||||
|
NodeHttpLib,
|
||||||
|
WalletApiOperation,
|
||||||
|
Wallet,
|
||||||
|
AccessStats,
|
||||||
|
downloadExchangeInfo,
|
||||||
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for the benchmark.
|
||||||
|
*
|
||||||
|
* The benchmark runs against an existing Taler deployment and does not
|
||||||
|
* set up its own services.
|
||||||
|
*/
|
||||||
|
export async function runBench2(configJson: any): Promise<void> {
|
||||||
|
const logger = new Logger("Bench1");
|
||||||
|
|
||||||
|
// Validate the configuration file for this benchmark.
|
||||||
|
const benchConf = codecForBench1Config().decode(configJson);
|
||||||
|
|
||||||
|
const myHttpLib = new NodeHttpLib();
|
||||||
|
myHttpLib.setThrottling(false);
|
||||||
|
|
||||||
|
const exchangeInfo = await downloadExchangeInfo(
|
||||||
|
benchConf.exchange,
|
||||||
|
myHttpLib,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format of the configuration file passed to the benchmark
|
||||||
|
*/
|
||||||
|
interface Bench2Config {
|
||||||
|
/**
|
||||||
|
* Base URL of the bank.
|
||||||
|
*/
|
||||||
|
bank: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payto url for deposits.
|
||||||
|
*/
|
||||||
|
payto: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base URL of the exchange.
|
||||||
|
*/
|
||||||
|
exchange: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many withdraw/deposit iterations should be made?
|
||||||
|
* Defaults to 1.
|
||||||
|
*/
|
||||||
|
iterations?: number;
|
||||||
|
|
||||||
|
currency: string;
|
||||||
|
|
||||||
|
deposits?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How any iterations run until the wallet db gets purged
|
||||||
|
* Defaults to 20.
|
||||||
|
*/
|
||||||
|
restartAfter?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema validation codec for Bench1Config.
|
||||||
|
*/
|
||||||
|
const codecForBench1Config = () =>
|
||||||
|
buildCodecForObject<Bench2Config>()
|
||||||
|
.property("bank", codecForString())
|
||||||
|
.property("payto", codecForString())
|
||||||
|
.property("exchange", codecForString())
|
||||||
|
.property("iterations", codecOptional(codecForNumber()))
|
||||||
|
.property("deposits", codecOptional(codecForNumber()))
|
||||||
|
.property("currency", codecForString())
|
||||||
|
.property("restartAfter", codecOptional(codecForNumber()))
|
||||||
|
.build("Bench1Config");
|
@ -45,6 +45,9 @@ import {
|
|||||||
MerchantInstancesResponse,
|
MerchantInstancesResponse,
|
||||||
} from "./merchantApiTypes";
|
} from "./merchantApiTypes";
|
||||||
import {
|
import {
|
||||||
|
BankServiceHandle,
|
||||||
|
HarnessExchangeBankAccount,
|
||||||
|
NodeHttpLib,
|
||||||
openPromise,
|
openPromise,
|
||||||
OperationFailedError,
|
OperationFailedError,
|
||||||
WalletCoreApiClient,
|
WalletCoreApiClient,
|
||||||
@ -468,164 +471,6 @@ export async function pingProc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HarnessExchangeBankAccount {
|
|
||||||
accountName: string;
|
|
||||||
accountPassword: string;
|
|
||||||
accountPaytoUri: string;
|
|
||||||
wireGatewayApiBaseUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BankServiceInterface {
|
|
||||||
readonly baseUrl: string;
|
|
||||||
readonly port: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CreditDebitIndicator {
|
|
||||||
Credit = "credit",
|
|
||||||
Debit = "debit",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BankAccountBalanceResponse {
|
|
||||||
balance: {
|
|
||||||
amount: AmountString;
|
|
||||||
credit_debit_indicator: CreditDebitIndicator;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace BankAccessApi {
|
|
||||||
export async function getAccountBalance(
|
|
||||||
bank: BankServiceInterface,
|
|
||||||
bankUser: BankUser,
|
|
||||||
): Promise<BankAccountBalanceResponse> {
|
|
||||||
const url = new URL(`accounts/${bankUser.username}`, bank.baseUrl);
|
|
||||||
const resp = await axios.get(url.href, {
|
|
||||||
auth: bankUser,
|
|
||||||
});
|
|
||||||
return resp.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createWithdrawalOperation(
|
|
||||||
bank: BankServiceInterface,
|
|
||||||
bankUser: BankUser,
|
|
||||||
amount: string,
|
|
||||||
): Promise<WithdrawalOperationInfo> {
|
|
||||||
const url = new URL(
|
|
||||||
`accounts/${bankUser.username}/withdrawals`,
|
|
||||||
bank.baseUrl,
|
|
||||||
);
|
|
||||||
const resp = await axios.post(
|
|
||||||
url.href,
|
|
||||||
{
|
|
||||||
amount,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
auth: bankUser,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return codecForWithdrawalOperationInfo().decode(resp.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace BankApi {
|
|
||||||
export async function registerAccount(
|
|
||||||
bank: BankServiceInterface,
|
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
): Promise<BankUser> {
|
|
||||||
const url = new URL("testing/register", bank.baseUrl);
|
|
||||||
let resp = await axios.post(url.href, {
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
let paytoUri = `payto://x-taler-bank/localhost/${username}`;
|
|
||||||
if (process.env.WALLET_HARNESS_WITH_EUFIN) {
|
|
||||||
paytoUri = resp.data.paytoUri;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
password,
|
|
||||||
username,
|
|
||||||
accountPaytoUri: paytoUri,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createRandomBankUser(
|
|
||||||
bank: BankServiceInterface,
|
|
||||||
): 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: BankServiceInterface,
|
|
||||||
params: {
|
|
||||||
exchangeBankAccount: HarnessExchangeBankAccount;
|
|
||||||
amount: string;
|
|
||||||
reservePub: string;
|
|
||||||
debitAccountPayto: string;
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
let maybeBaseUrl = bank.baseUrl;
|
|
||||||
if (process.env.WALLET_HARNESS_WITH_EUFIN) {
|
|
||||||
maybeBaseUrl = (bank as EufinBankService).baseUrlDemobank;
|
|
||||||
}
|
|
||||||
let url = new URL(
|
|
||||||
`taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`,
|
|
||||||
maybeBaseUrl,
|
|
||||||
);
|
|
||||||
await axios.post(
|
|
||||||
url.href,
|
|
||||||
{
|
|
||||||
amount: params.amount,
|
|
||||||
reserve_pub: params.reservePub,
|
|
||||||
debit_account: params.debitAccountPayto,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
auth: {
|
|
||||||
username: params.exchangeBankAccount.accountName,
|
|
||||||
password: params.exchangeBankAccount.accountPassword,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function confirmWithdrawalOperation(
|
|
||||||
bank: BankServiceInterface,
|
|
||||||
bankUser: BankUser,
|
|
||||||
wopi: WithdrawalOperationInfo,
|
|
||||||
): Promise<void> {
|
|
||||||
const url = new URL(
|
|
||||||
`accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/confirm`,
|
|
||||||
bank.baseUrl,
|
|
||||||
);
|
|
||||||
await axios.post(
|
|
||||||
url.href,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
auth: bankUser,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function abortWithdrawalOperation(
|
|
||||||
bank: BankServiceInterface,
|
|
||||||
bankUser: BankUser,
|
|
||||||
wopi: WithdrawalOperationInfo,
|
|
||||||
): Promise<void> {
|
|
||||||
const url = new URL(
|
|
||||||
`accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/abort`,
|
|
||||||
bank.baseUrl,
|
|
||||||
);
|
|
||||||
await axios.post(
|
|
||||||
url.href,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
auth: bankUser,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BankServiceBase {
|
class BankServiceBase {
|
||||||
proc: ProcessWrapper | undefined;
|
proc: ProcessWrapper | undefined;
|
||||||
|
|
||||||
@ -640,10 +485,12 @@ class BankServiceBase {
|
|||||||
* Work in progress. The key point is that both Sandbox and Nexus
|
* Work in progress. The key point is that both Sandbox and Nexus
|
||||||
* will be configured and started by this class.
|
* will be configured and started by this class.
|
||||||
*/
|
*/
|
||||||
class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
class EufinBankService extends BankServiceBase implements BankServiceHandle {
|
||||||
sandboxProc: ProcessWrapper | undefined;
|
sandboxProc: ProcessWrapper | undefined;
|
||||||
nexusProc: ProcessWrapper | undefined;
|
nexusProc: ProcessWrapper | undefined;
|
||||||
|
|
||||||
|
http = new NodeHttpLib();
|
||||||
|
|
||||||
static async create(
|
static async create(
|
||||||
gc: GlobalTestState,
|
gc: GlobalTestState,
|
||||||
bc: BankConfig,
|
bc: BankConfig,
|
||||||
@ -914,9 +761,11 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PybankService extends BankServiceBase implements BankServiceInterface {
|
class PybankService extends BankServiceBase implements BankServiceHandle {
|
||||||
proc: ProcessWrapper | undefined;
|
proc: ProcessWrapper | undefined;
|
||||||
|
|
||||||
|
http = new NodeHttpLib();
|
||||||
|
|
||||||
static async create(
|
static async create(
|
||||||
gc: GlobalTestState,
|
gc: GlobalTestState,
|
||||||
bc: BankConfig,
|
bc: BankConfig,
|
||||||
@ -955,6 +804,7 @@ class PybankService extends BankServiceBase implements BankServiceInterface {
|
|||||||
const config = Configuration.load(this.configFile);
|
const config = Configuration.load(this.configFile);
|
||||||
config.setString("bank", "suggested_exchange", e.baseUrl);
|
config.setString("bank", "suggested_exchange", e.baseUrl);
|
||||||
config.setString("bank", "suggested_exchange_payto", exchangePayto);
|
config.setString("bank", "suggested_exchange_payto", exchangePayto);
|
||||||
|
config.write(this.configFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
get baseUrl(): string {
|
get baseUrl(): string {
|
||||||
@ -1087,23 +937,6 @@ export class FakeBankService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BankUser {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
accountPaytoUri: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WithdrawalOperationInfo {
|
|
||||||
withdrawal_id: string;
|
|
||||||
taler_withdraw_uri: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const codecForWithdrawalOperationInfo = (): Codec<WithdrawalOperationInfo> =>
|
|
||||||
buildCodecForObject<WithdrawalOperationInfo>()
|
|
||||||
.property("withdrawal_id", codecForString())
|
|
||||||
.property("taler_withdraw_uri", codecForString())
|
|
||||||
.build("WithdrawalOperationInfo");
|
|
||||||
|
|
||||||
export interface ExchangeConfig {
|
export interface ExchangeConfig {
|
||||||
name: string;
|
name: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
|
@ -30,22 +30,19 @@ import {
|
|||||||
Duration,
|
Duration,
|
||||||
PreparePayResultType,
|
PreparePayResultType,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { BankAccessApi, BankApi, HarnessExchangeBankAccount, WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
import { CoinConfig, defaultCoinConfig } from "./denomStructures.js";
|
import { CoinConfig, defaultCoinConfig } from "./denomStructures.js";
|
||||||
import {
|
import {
|
||||||
FaultInjectedExchangeService,
|
FaultInjectedExchangeService,
|
||||||
FaultInjectedMerchantService,
|
FaultInjectedMerchantService,
|
||||||
} from "./faultInjection.js";
|
} from "./faultInjection.js";
|
||||||
import {
|
import {
|
||||||
BankAccessApi,
|
|
||||||
BankApi,
|
|
||||||
BankService,
|
BankService,
|
||||||
DbInfo,
|
DbInfo,
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
ExchangeServiceInterface,
|
ExchangeServiceInterface,
|
||||||
getPayto,
|
getPayto,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
HarnessExchangeBankAccount,
|
|
||||||
MerchantPrivateApi,
|
MerchantPrivateApi,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
MerchantServiceInterface,
|
MerchantServiceInterface,
|
||||||
|
@ -24,13 +24,15 @@ import {
|
|||||||
setupDb,
|
setupDb,
|
||||||
BankService,
|
BankService,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
BankApi,
|
getPayto,
|
||||||
BankAccessApi,
|
|
||||||
CreditDebitIndicator,
|
|
||||||
getPayto
|
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import { createEddsaKeyPair, encodeCrock } from "@gnu-taler/taler-util";
|
import { createEddsaKeyPair, encodeCrock } from "@gnu-taler/taler-util";
|
||||||
import { defaultCoinConfig } from "../harness/denomStructures";
|
import { defaultCoinConfig } from "../harness/denomStructures";
|
||||||
|
import {
|
||||||
|
BankApi,
|
||||||
|
BankAccessApi,
|
||||||
|
CreditDebitIndicator,
|
||||||
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run test for basic, bank-integrated withdrawal.
|
* Run test for basic, bank-integrated withdrawal.
|
||||||
@ -97,8 +99,6 @@ export async function runBankApiTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
|
||||||
const wallet = new WalletCli(t);
|
|
||||||
|
|
||||||
const bankUser = await BankApi.registerAccount(bank, "user1", "pw1");
|
const bankUser = await BankApi.registerAccount(bank, "user1", "pw1");
|
||||||
|
|
||||||
// Make sure that registering twice results in a 409 Conflict
|
// Make sure that registering twice results in a 409 Conflict
|
||||||
|
@ -24,11 +24,13 @@ import {
|
|||||||
BankService,
|
BankService,
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
|
getPayto,
|
||||||
|
} from "../harness/harness.js";
|
||||||
|
import {
|
||||||
|
WalletApiOperation,
|
||||||
BankApi,
|
BankApi,
|
||||||
BankAccessApi,
|
BankAccessApi,
|
||||||
getPayto
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
} from "../harness/harness.js";
|
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
|
||||||
import {
|
import {
|
||||||
ExchangesListRespose,
|
ExchangesListRespose,
|
||||||
URL,
|
URL,
|
||||||
|
@ -19,15 +19,16 @@
|
|||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
ContractTerms,
|
ContractTerms,
|
||||||
CoreApiResponse,
|
|
||||||
getTimestampNow,
|
getTimestampNow,
|
||||||
timestampTruncateToSecond,
|
timestampTruncateToSecond,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import {
|
||||||
|
WalletApiOperation,
|
||||||
|
HarnessExchangeBankAccount,
|
||||||
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures";
|
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures";
|
||||||
import {
|
import {
|
||||||
DbInfo,
|
DbInfo,
|
||||||
HarnessExchangeBankAccount,
|
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
@ -233,13 +234,8 @@ export async function createLibeufinTestEnvironment(
|
|||||||
export async function runLibeufinBasicTest(t: GlobalTestState) {
|
export async function runLibeufinBasicTest(t: GlobalTestState) {
|
||||||
// Set up test environment
|
// Set up test environment
|
||||||
|
|
||||||
const {
|
const { wallet, exchange, merchant, libeufinSandbox, libeufinNexus } =
|
||||||
wallet,
|
await createLibeufinTestEnvironment(t);
|
||||||
exchange,
|
|
||||||
merchant,
|
|
||||||
libeufinSandbox,
|
|
||||||
libeufinNexus,
|
|
||||||
} = await createLibeufinTestEnvironment(t);
|
|
||||||
|
|
||||||
await wallet.client.call(WalletApiOperation.AddExchange, {
|
await wallet.client.call(WalletApiOperation.AddExchange, {
|
||||||
exchangeBaseUrl: exchange.baseUrl,
|
exchangeBaseUrl: exchange.baseUrl,
|
||||||
|
@ -20,25 +20,30 @@
|
|||||||
import {
|
import {
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantPrivateApi,
|
MerchantPrivateApi,
|
||||||
BankServiceInterface,
|
|
||||||
MerchantServiceInterface,
|
MerchantServiceInterface,
|
||||||
WalletCli,
|
WalletCli,
|
||||||
ExchangeServiceInterface,
|
ExchangeServiceInterface,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import { createSimpleTestkudosEnvironment, withdrawViaBank } from "../harness/helpers.js";
|
import {
|
||||||
|
createSimpleTestkudosEnvironment,
|
||||||
|
withdrawViaBank,
|
||||||
|
} from "../harness/helpers.js";
|
||||||
import {
|
import {
|
||||||
URL,
|
URL,
|
||||||
durationFromSpec,
|
durationFromSpec,
|
||||||
PreparePayResultType,
|
PreparePayResultType,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import {
|
||||||
|
WalletApiOperation,
|
||||||
|
BankServiceHandle,
|
||||||
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
|
||||||
async function testRefundApiWithFulfillmentUrl(
|
async function testRefundApiWithFulfillmentUrl(
|
||||||
t: GlobalTestState,
|
t: GlobalTestState,
|
||||||
env: {
|
env: {
|
||||||
merchant: MerchantServiceInterface;
|
merchant: MerchantServiceInterface;
|
||||||
bank: BankServiceInterface;
|
bank: BankServiceHandle;
|
||||||
wallet: WalletCli;
|
wallet: WalletCli;
|
||||||
exchange: ExchangeServiceInterface;
|
exchange: ExchangeServiceInterface;
|
||||||
},
|
},
|
||||||
@ -152,7 +157,7 @@ async function testRefundApiWithFulfillmentMessage(
|
|||||||
t: GlobalTestState,
|
t: GlobalTestState,
|
||||||
env: {
|
env: {
|
||||||
merchant: MerchantServiceInterface;
|
merchant: MerchantServiceInterface;
|
||||||
bank: BankServiceInterface;
|
bank: BankServiceHandle;
|
||||||
wallet: WalletCli;
|
wallet: WalletCli;
|
||||||
exchange: ExchangeServiceInterface;
|
exchange: ExchangeServiceInterface;
|
||||||
},
|
},
|
||||||
@ -267,12 +272,8 @@ async function testRefundApiWithFulfillmentMessage(
|
|||||||
export async function runMerchantRefundApiTest(t: GlobalTestState) {
|
export async function runMerchantRefundApiTest(t: GlobalTestState) {
|
||||||
// Set up test environment
|
// Set up test environment
|
||||||
|
|
||||||
const {
|
const { wallet, bank, exchange, merchant } =
|
||||||
wallet,
|
await createSimpleTestkudosEnvironment(t);
|
||||||
bank,
|
|
||||||
exchange,
|
|
||||||
merchant,
|
|
||||||
} = await createSimpleTestkudosEnvironment(t);
|
|
||||||
|
|
||||||
// Withdraw digital cash into the wallet.
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
|
@ -29,9 +29,7 @@ import {
|
|||||||
BankService,
|
BankService,
|
||||||
WalletCli,
|
WalletCli,
|
||||||
MerchantPrivateApi,
|
MerchantPrivateApi,
|
||||||
BankApi,
|
getPayto,
|
||||||
BankAccessApi,
|
|
||||||
getPayto
|
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import {
|
import {
|
||||||
FaultInjectedExchangeService,
|
FaultInjectedExchangeService,
|
||||||
@ -40,7 +38,11 @@ import {
|
|||||||
} from "../harness/faultInjection";
|
} from "../harness/faultInjection";
|
||||||
import { CoreApiResponse } from "@gnu-taler/taler-util";
|
import { CoreApiResponse } from "@gnu-taler/taler-util";
|
||||||
import { defaultCoinConfig } from "../harness/denomStructures";
|
import { defaultCoinConfig } from "../harness/denomStructures";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import {
|
||||||
|
WalletApiOperation,
|
||||||
|
BankApi,
|
||||||
|
BankAccessApi,
|
||||||
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run test for basic, bank-integrated withdrawal.
|
* Run test for basic, bank-integrated withdrawal.
|
||||||
@ -146,7 +148,6 @@ export async function runPaymentFaultTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
await wallet.runUntilDone();
|
await wallet.runUntilDone();
|
||||||
|
|
||||||
|
|
||||||
// Check balance
|
// Check balance
|
||||||
|
|
||||||
await wallet.client.call(WalletApiOperation.GetBalances, {});
|
await wallet.client.call(WalletApiOperation.GetBalances, {});
|
||||||
|
@ -17,31 +17,33 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
|
import { GlobalTestState, WalletCli } from "../harness/harness.js";
|
||||||
|
import { makeTestPayment } from "../harness/helpers.js";
|
||||||
import {
|
import {
|
||||||
GlobalTestState,
|
WalletApiOperation,
|
||||||
BankApi,
|
BankApi,
|
||||||
WalletCli,
|
BankAccessApi,
|
||||||
BankAccessApi
|
BankServiceHandle,
|
||||||
} from "../harness/harness.js";
|
NodeHttpLib,
|
||||||
import {
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
makeTestPayment,
|
|
||||||
} from "../harness/helpers.js";
|
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run test for basic, bank-integrated withdrawal and payment.
|
* Run test for basic, bank-integrated withdrawal and payment.
|
||||||
*/
|
*/
|
||||||
export async function runPaymentDemoTest(t: GlobalTestState) {
|
export async function runPaymentDemoTest(t: GlobalTestState) {
|
||||||
|
|
||||||
// Withdraw digital cash into the wallet.
|
// Withdraw digital cash into the wallet.
|
||||||
let bankInterface = {
|
let bankInterface: BankServiceHandle = {
|
||||||
baseUrl: "https://bank.demo.taler.net/",
|
baseUrl: "https://bank.demo.taler.net/",
|
||||||
port: 0 // unused.
|
http: new NodeHttpLib(),
|
||||||
};
|
};
|
||||||
let user = await BankApi.createRandomBankUser(bankInterface);
|
let user = await BankApi.createRandomBankUser(bankInterface);
|
||||||
let wop = await BankAccessApi.createWithdrawalOperation(bankInterface, user, "KUDOS:20");
|
let wop = await BankAccessApi.createWithdrawalOperation(
|
||||||
|
bankInterface,
|
||||||
|
user,
|
||||||
|
"KUDOS:20",
|
||||||
|
);
|
||||||
|
|
||||||
let wallet = new WalletCli(t);
|
let wallet = new WalletCli(t);
|
||||||
await wallet.client.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
|
await wallet.client.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
|
||||||
talerWithdrawUri: wop.taler_withdraw_uri,
|
talerWithdrawUri: wop.taler_withdraw_uri,
|
||||||
});
|
});
|
||||||
@ -60,7 +62,10 @@ export async function runPaymentDemoTest(t: GlobalTestState) {
|
|||||||
});
|
});
|
||||||
await wallet.runUntilDone();
|
await wallet.runUntilDone();
|
||||||
|
|
||||||
let balanceBefore = await wallet.client.call(WalletApiOperation.GetBalances, {});
|
let balanceBefore = await wallet.client.call(
|
||||||
|
WalletApiOperation.GetBalances,
|
||||||
|
{},
|
||||||
|
);
|
||||||
t.assertTrue(balanceBefore["balances"].length == 1);
|
t.assertTrue(balanceBefore["balances"].length == 1);
|
||||||
|
|
||||||
const order = {
|
const order = {
|
||||||
@ -70,7 +75,7 @@ export async function runPaymentDemoTest(t: GlobalTestState) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let merchant = {
|
let merchant = {
|
||||||
makeInstanceBaseUrl: function(instanceName?: string) {
|
makeInstanceBaseUrl: function (instanceName?: string) {
|
||||||
return "https://backend.demo.taler.net/instances/donations/";
|
return "https://backend.demo.taler.net/instances/donations/";
|
||||||
},
|
},
|
||||||
port: 0,
|
port: 0,
|
||||||
@ -82,17 +87,26 @@ export async function runPaymentDemoTest(t: GlobalTestState) {
|
|||||||
await makeTestPayment(
|
await makeTestPayment(
|
||||||
t,
|
t,
|
||||||
{
|
{
|
||||||
merchant, wallet, order
|
merchant,
|
||||||
|
wallet,
|
||||||
|
order,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Authorization": `Bearer ${process.env["TALER_ENV_FRONTENDS_APITOKEN"]}`,
|
Authorization: `Bearer ${process.env["TALER_ENV_FRONTENDS_APITOKEN"]}`,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await wallet.runUntilDone();
|
await wallet.runUntilDone();
|
||||||
|
|
||||||
let balanceAfter = await wallet.client.call(WalletApiOperation.GetBalances, {});
|
let balanceAfter = await wallet.client.call(
|
||||||
|
WalletApiOperation.GetBalances,
|
||||||
|
{},
|
||||||
|
);
|
||||||
t.assertTrue(balanceAfter["balances"].length == 1);
|
t.assertTrue(balanceAfter["balances"].length == 1);
|
||||||
t.assertTrue(balanceBefore["balances"][0]["available"] > balanceAfter["balances"][0]["available"]);
|
t.assertTrue(
|
||||||
|
balanceBefore["balances"][0]["available"] >
|
||||||
|
balanceAfter["balances"][0]["available"],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
runPaymentDemoTest.excludeByDefault = true;
|
runPaymentDemoTest.excludeByDefault = true;
|
||||||
|
@ -17,8 +17,12 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation, BankApi } from "@gnu-taler/taler-wallet-core";
|
||||||
import { GlobalTestState, MerchantPrivateApi, BankApi, getWireMethod } from "../harness/harness.js";
|
import {
|
||||||
|
GlobalTestState,
|
||||||
|
MerchantPrivateApi,
|
||||||
|
getWireMethod,
|
||||||
|
} from "../harness/harness.js";
|
||||||
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,13 +31,8 @@ import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
|||||||
export async function runTippingTest(t: GlobalTestState) {
|
export async function runTippingTest(t: GlobalTestState) {
|
||||||
// Set up test environment
|
// Set up test environment
|
||||||
|
|
||||||
const {
|
const { wallet, bank, exchange, merchant, exchangeBankAccount } =
|
||||||
wallet,
|
await createSimpleTestkudosEnvironment(t);
|
||||||
bank,
|
|
||||||
exchange,
|
|
||||||
merchant,
|
|
||||||
exchangeBankAccount,
|
|
||||||
} = await createSimpleTestkudosEnvironment(t);
|
|
||||||
|
|
||||||
const mbu = await BankApi.createRandomBankUser(bank);
|
const mbu = await BankApi.createRandomBankUser(bank);
|
||||||
|
|
||||||
|
@ -0,0 +1,358 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2020 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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
AmountJson,
|
||||||
|
AmountLike,
|
||||||
|
Amounts,
|
||||||
|
AmountString,
|
||||||
|
codecForBankWithdrawalOperationPostResponse,
|
||||||
|
codecForDepositSuccess,
|
||||||
|
codecForExchangeMeltResponse,
|
||||||
|
codecForWithdrawResponse,
|
||||||
|
DenominationPubKey,
|
||||||
|
eddsaGetPublic,
|
||||||
|
encodeCrock,
|
||||||
|
ExchangeMeltRequest,
|
||||||
|
ExchangeProtocolVersion,
|
||||||
|
ExchangeWithdrawRequest,
|
||||||
|
getRandomBytes,
|
||||||
|
getTimestampNow,
|
||||||
|
hashWire,
|
||||||
|
j2s,
|
||||||
|
Timestamp,
|
||||||
|
UnblindedSignature,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import {
|
||||||
|
BankAccessApi,
|
||||||
|
BankApi,
|
||||||
|
BankServiceHandle,
|
||||||
|
CryptoApi,
|
||||||
|
DenominationRecord,
|
||||||
|
downloadExchangeInfo,
|
||||||
|
ExchangeInfo,
|
||||||
|
getBankWithdrawalInfo,
|
||||||
|
HttpRequestLibrary,
|
||||||
|
isWithdrawableDenom,
|
||||||
|
NodeHttpLib,
|
||||||
|
OperationFailedError,
|
||||||
|
readSuccessResponseJsonOrThrow,
|
||||||
|
SynchronousCryptoWorkerFactory,
|
||||||
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
import { GlobalTestState } from "../harness/harness.js";
|
||||||
|
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
||||||
|
|
||||||
|
const httpLib = new NodeHttpLib();
|
||||||
|
|
||||||
|
export interface ReserveKeypair {
|
||||||
|
reservePub: string;
|
||||||
|
reservePriv: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denormalized info about a coin.
|
||||||
|
*/
|
||||||
|
export interface CoinInfo {
|
||||||
|
coinPub: string;
|
||||||
|
coinPriv: string;
|
||||||
|
exchangeBaseUrl: string;
|
||||||
|
denomSig: UnblindedSignature;
|
||||||
|
denomPub: DenominationPubKey;
|
||||||
|
denomPubHash: string;
|
||||||
|
feeDeposit: string;
|
||||||
|
feeRefresh: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateReserveKeypair(): ReserveKeypair {
|
||||||
|
const priv = getRandomBytes(32);
|
||||||
|
const pub = eddsaGetPublic(priv);
|
||||||
|
return {
|
||||||
|
reservePriv: encodeCrock(priv),
|
||||||
|
reservePub: encodeCrock(pub),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function topupReserveWithDemobank(
|
||||||
|
reservePub: string,
|
||||||
|
bankBaseUrl: string,
|
||||||
|
exchangeInfo: ExchangeInfo,
|
||||||
|
amount: AmountString,
|
||||||
|
) {
|
||||||
|
const bankHandle: BankServiceHandle = {
|
||||||
|
baseUrl: bankBaseUrl,
|
||||||
|
http: httpLib,
|
||||||
|
};
|
||||||
|
const bankUser = await BankApi.createRandomBankUser(bankHandle);
|
||||||
|
const wopi = await BankAccessApi.createWithdrawalOperation(
|
||||||
|
bankHandle,
|
||||||
|
bankUser,
|
||||||
|
amount,
|
||||||
|
);
|
||||||
|
const bankInfo = await getBankWithdrawalInfo(
|
||||||
|
httpLib,
|
||||||
|
wopi.taler_withdraw_uri,
|
||||||
|
);
|
||||||
|
const bankStatusUrl = bankInfo.extractedStatusUrl;
|
||||||
|
if (!bankInfo.suggestedExchange) {
|
||||||
|
throw Error("no suggested exchange");
|
||||||
|
}
|
||||||
|
const plainPaytoUris =
|
||||||
|
exchangeInfo.wire.accounts.map((x) => x.payto_uri) ?? [];
|
||||||
|
if (plainPaytoUris.length <= 0) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const httpResp = await httpLib.postJson(bankStatusUrl, {
|
||||||
|
reserve_pub: reservePub,
|
||||||
|
selected_exchange: plainPaytoUris[0],
|
||||||
|
});
|
||||||
|
await readSuccessResponseJsonOrThrow(
|
||||||
|
httpResp,
|
||||||
|
codecForBankWithdrawalOperationPostResponse(),
|
||||||
|
);
|
||||||
|
await BankApi.confirmWithdrawalOperation(bankHandle, bankUser, wopi);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withdrawCoin(args: {
|
||||||
|
http: HttpRequestLibrary;
|
||||||
|
cryptoApi: CryptoApi;
|
||||||
|
reserveKeyPair: ReserveKeypair;
|
||||||
|
denom: DenominationRecord;
|
||||||
|
exchangeBaseUrl: string;
|
||||||
|
}): Promise<CoinInfo> {
|
||||||
|
const { http, cryptoApi, reserveKeyPair, denom, exchangeBaseUrl } = args;
|
||||||
|
const planchet = await cryptoApi.createPlanchet({
|
||||||
|
coinIndex: 0,
|
||||||
|
denomPub: denom.denomPub,
|
||||||
|
feeWithdraw: denom.feeWithdraw,
|
||||||
|
reservePriv: reserveKeyPair.reservePriv,
|
||||||
|
reservePub: reserveKeyPair.reservePub,
|
||||||
|
secretSeed: encodeCrock(getRandomBytes(32)),
|
||||||
|
value: denom.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
const reqBody: ExchangeWithdrawRequest = {
|
||||||
|
denom_pub_hash: planchet.denomPubHash,
|
||||||
|
reserve_sig: planchet.withdrawSig,
|
||||||
|
coin_ev: planchet.coinEv,
|
||||||
|
};
|
||||||
|
const reqUrl = new URL(
|
||||||
|
`reserves/${planchet.reservePub}/withdraw`,
|
||||||
|
exchangeBaseUrl,
|
||||||
|
).href;
|
||||||
|
|
||||||
|
const resp = await http.postJson(reqUrl, reqBody);
|
||||||
|
const r = await readSuccessResponseJsonOrThrow(
|
||||||
|
resp,
|
||||||
|
codecForWithdrawResponse(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const ubSig = await cryptoApi.unblindDenominationSignature({
|
||||||
|
planchet,
|
||||||
|
evSig: r.ev_sig,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
coinPriv: planchet.coinPriv,
|
||||||
|
coinPub: planchet.coinPub,
|
||||||
|
denomSig: ubSig,
|
||||||
|
denomPub: denom.denomPub,
|
||||||
|
denomPubHash: denom.denomPubHash,
|
||||||
|
feeDeposit: Amounts.stringify(denom.feeDeposit),
|
||||||
|
feeRefresh: Amounts.stringify(denom.feeRefresh),
|
||||||
|
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function findDenomOrThrow(
|
||||||
|
exchangeInfo: ExchangeInfo,
|
||||||
|
amount: AmountString,
|
||||||
|
): DenominationRecord {
|
||||||
|
for (const d of exchangeInfo.keys.currentDenominations) {
|
||||||
|
if (Amounts.cmp(d.value, amount) === 0 && isWithdrawableDenom(d)) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("no matching denomination found");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function depositCoin(args: {
|
||||||
|
http: HttpRequestLibrary;
|
||||||
|
cryptoApi: CryptoApi;
|
||||||
|
exchangeBaseUrl: string;
|
||||||
|
coin: CoinInfo;
|
||||||
|
amount: AmountString;
|
||||||
|
}) {
|
||||||
|
const { coin, http, cryptoApi } = args;
|
||||||
|
const depositPayto = "payto://x-taler-bank/localhost/foo";
|
||||||
|
const wireSalt = encodeCrock(getRandomBytes(16));
|
||||||
|
const contractTermsHash = encodeCrock(getRandomBytes(64));
|
||||||
|
const depositTimestamp = getTimestampNow();
|
||||||
|
const refundDeadline = getTimestampNow();
|
||||||
|
const merchantPub = encodeCrock(getRandomBytes(32));
|
||||||
|
const dp = await cryptoApi.signDepositPermission({
|
||||||
|
coinPriv: coin.coinPriv,
|
||||||
|
coinPub: coin.coinPub,
|
||||||
|
contractTermsHash,
|
||||||
|
denomKeyType: coin.denomPub.cipher,
|
||||||
|
denomPubHash: coin.denomPubHash,
|
||||||
|
denomSig: coin.denomSig,
|
||||||
|
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||||
|
feeDeposit: Amounts.parseOrThrow(coin.feeDeposit),
|
||||||
|
merchantPub,
|
||||||
|
spendAmount: Amounts.parseOrThrow(args.amount),
|
||||||
|
timestamp: depositTimestamp,
|
||||||
|
refundDeadline: refundDeadline,
|
||||||
|
wireInfoHash: hashWire(depositPayto, wireSalt),
|
||||||
|
});
|
||||||
|
const requestBody = {
|
||||||
|
contribution: Amounts.stringify(dp.contribution),
|
||||||
|
merchant_payto_uri: depositPayto,
|
||||||
|
wire_salt: wireSalt,
|
||||||
|
h_contract_terms: contractTermsHash,
|
||||||
|
ub_sig: coin.denomSig,
|
||||||
|
timestamp: depositTimestamp,
|
||||||
|
wire_transfer_deadline: getTimestampNow(),
|
||||||
|
refund_deadline: refundDeadline,
|
||||||
|
coin_sig: dp.coin_sig,
|
||||||
|
denom_pub_hash: dp.h_denom,
|
||||||
|
merchant_pub: merchantPub,
|
||||||
|
};
|
||||||
|
const url = new URL(`coins/${dp.coin_pub}/deposit`, dp.exchange_url);
|
||||||
|
const httpResp = await http.postJson(url.href, requestBody);
|
||||||
|
await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshCoin(req: {
|
||||||
|
http: HttpRequestLibrary;
|
||||||
|
cryptoApi: CryptoApi;
|
||||||
|
oldCoin: CoinInfo;
|
||||||
|
newDenoms: DenominationRecord[];
|
||||||
|
}): Promise<void> {
|
||||||
|
const { cryptoApi, oldCoin, http } = req;
|
||||||
|
const refreshSessionSeed = encodeCrock(getRandomBytes(32));
|
||||||
|
const session = await cryptoApi.deriveRefreshSession({
|
||||||
|
exchangeProtocolVersion: ExchangeProtocolVersion.V12,
|
||||||
|
feeRefresh: Amounts.parseOrThrow(oldCoin.feeRefresh),
|
||||||
|
kappa: 3,
|
||||||
|
meltCoinDenomPubHash: oldCoin.denomPubHash,
|
||||||
|
meltCoinPriv: oldCoin.coinPriv,
|
||||||
|
meltCoinPub: oldCoin.coinPub,
|
||||||
|
sessionSecretSeed: refreshSessionSeed,
|
||||||
|
newCoinDenoms: req.newDenoms.map((x) => ({
|
||||||
|
count: 1,
|
||||||
|
denomPub: x.denomPub,
|
||||||
|
feeWithdraw: x.feeWithdraw,
|
||||||
|
value: x.value,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
const meltReqBody: ExchangeMeltRequest = {
|
||||||
|
coin_pub: oldCoin.coinPub,
|
||||||
|
confirm_sig: session.confirmSig,
|
||||||
|
denom_pub_hash: oldCoin.denomPubHash,
|
||||||
|
denom_sig: oldCoin.denomSig,
|
||||||
|
rc: session.hash,
|
||||||
|
value_with_fee: Amounts.stringify(session.meltValueWithFee),
|
||||||
|
};
|
||||||
|
|
||||||
|
const reqUrl = new URL(
|
||||||
|
`coins/${oldCoin.coinPub}/melt`,
|
||||||
|
oldCoin.exchangeBaseUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
const resp = await http.postJson(reqUrl.href, meltReqBody);
|
||||||
|
|
||||||
|
const meltResponse = await readSuccessResponseJsonOrThrow(
|
||||||
|
resp,
|
||||||
|
codecForExchangeMeltResponse(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const norevealIndex = meltResponse.noreveal_index;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run test for basic, bank-integrated withdrawal and payment.
|
||||||
|
*/
|
||||||
|
export async function runWalletDblessTest(t: GlobalTestState) {
|
||||||
|
// Set up test environment
|
||||||
|
|
||||||
|
const { bank, exchange } = await createSimpleTestkudosEnvironment(t);
|
||||||
|
|
||||||
|
const http = new NodeHttpLib();
|
||||||
|
const cryptoApi = new CryptoApi(new SynchronousCryptoWorkerFactory());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
|
const exchangeInfo = await downloadExchangeInfo(exchange.baseUrl, http);
|
||||||
|
|
||||||
|
const reserveKeyPair = generateReserveKeypair();
|
||||||
|
|
||||||
|
await topupReserveWithDemobank(
|
||||||
|
reserveKeyPair.reservePub,
|
||||||
|
bank.baseUrl,
|
||||||
|
exchangeInfo,
|
||||||
|
"TESTKUDOS:10",
|
||||||
|
);
|
||||||
|
|
||||||
|
await exchange.runWirewatchOnce();
|
||||||
|
|
||||||
|
const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8");
|
||||||
|
|
||||||
|
const coin = await withdrawCoin({
|
||||||
|
http,
|
||||||
|
cryptoApi,
|
||||||
|
reserveKeyPair,
|
||||||
|
denom: d1,
|
||||||
|
exchangeBaseUrl: exchange.baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
await depositCoin({
|
||||||
|
amount: "TESTKUDOS:4",
|
||||||
|
coin: coin,
|
||||||
|
cryptoApi,
|
||||||
|
exchangeBaseUrl: exchange.baseUrl,
|
||||||
|
http,
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshDenoms = [
|
||||||
|
findDenomOrThrow(exchangeInfo, "TESTKUDOS:1"),
|
||||||
|
findDenomOrThrow(exchangeInfo, "TESTKUDOS:1"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const freshCoins = await refreshCoin({
|
||||||
|
oldCoin: coin,
|
||||||
|
cryptoApi,
|
||||||
|
http,
|
||||||
|
newDenoms: refreshDenoms,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof OperationFailedError) {
|
||||||
|
console.log(e);
|
||||||
|
console.log(j2s(e.operationError));
|
||||||
|
} else {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runWalletDblessTest.suites = ["wallet"];
|
@ -18,8 +18,12 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { TalerErrorCode } from "@gnu-taler/taler-util";
|
import { TalerErrorCode } from "@gnu-taler/taler-util";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import {
|
||||||
import { GlobalTestState, BankApi, BankAccessApi } from "../harness/harness.js";
|
WalletApiOperation,
|
||||||
|
BankApi,
|
||||||
|
BankAccessApi,
|
||||||
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
import { GlobalTestState } from "../harness/harness.js";
|
||||||
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,10 +17,13 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { GlobalTestState, BankApi, BankAccessApi } from "../harness/harness.js";
|
import { GlobalTestState } from "../harness/harness.js";
|
||||||
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
||||||
import { codecForBalancesResponse } from "@gnu-taler/taler-util";
|
import {
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
WalletApiOperation,
|
||||||
|
BankApi,
|
||||||
|
BankAccessApi,
|
||||||
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run test for basic, bank-integrated withdrawal.
|
* Run test for basic, bank-integrated withdrawal.
|
||||||
@ -41,18 +44,24 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
// Hand it to the wallet
|
// Hand it to the wallet
|
||||||
|
|
||||||
const r1 = await wallet.client.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
|
const r1 = await wallet.client.call(
|
||||||
talerWithdrawUri: wop.taler_withdraw_uri,
|
WalletApiOperation.GetWithdrawalDetailsForUri,
|
||||||
});
|
{
|
||||||
|
talerWithdrawUri: wop.taler_withdraw_uri,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await wallet.runPending();
|
await wallet.runPending();
|
||||||
|
|
||||||
// Withdraw
|
// Withdraw
|
||||||
|
|
||||||
const r2 = await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, {
|
const r2 = await wallet.client.call(
|
||||||
exchangeBaseUrl: exchange.baseUrl,
|
WalletApiOperation.AcceptBankIntegratedWithdrawal,
|
||||||
talerWithdrawUri: wop.taler_withdraw_uri,
|
{
|
||||||
});
|
exchangeBaseUrl: exchange.baseUrl,
|
||||||
|
talerWithdrawUri: wop.taler_withdraw_uri,
|
||||||
|
},
|
||||||
|
);
|
||||||
await wallet.runPending();
|
await wallet.runPending();
|
||||||
|
|
||||||
// Confirm it
|
// Confirm it
|
||||||
|
@ -19,13 +19,11 @@
|
|||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
BankApi,
|
|
||||||
WalletCli,
|
WalletCli,
|
||||||
setupDb,
|
setupDb,
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
FakeBankService,
|
FakeBankService,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
|
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
|
||||||
import { URL } from "@gnu-taler/taler-util";
|
import { URL } from "@gnu-taler/taler-util";
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { GlobalTestState, BankApi } from "../harness/harness.js";
|
import { GlobalTestState } from "../harness/harness.js";
|
||||||
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation, BankApi } from "@gnu-taler/taler-wallet-core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run test for basic, bank-integrated withdrawal.
|
* Run test for basic, bank-integrated withdrawal.
|
||||||
@ -27,12 +27,8 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
|||||||
export async function runTestWithdrawalManualTest(t: GlobalTestState) {
|
export async function runTestWithdrawalManualTest(t: GlobalTestState) {
|
||||||
// Set up test environment
|
// Set up test environment
|
||||||
|
|
||||||
const {
|
const { wallet, bank, exchange, exchangeBankAccount } =
|
||||||
wallet,
|
await createSimpleTestkudosEnvironment(t);
|
||||||
bank,
|
|
||||||
exchange,
|
|
||||||
exchangeBankAccount,
|
|
||||||
} = await createSimpleTestkudosEnvironment(t);
|
|
||||||
|
|
||||||
// Create a withdrawal operation
|
// Create a withdrawal operation
|
||||||
|
|
||||||
@ -42,11 +38,13 @@ export async function runTestWithdrawalManualTest(t: GlobalTestState) {
|
|||||||
exchangeBaseUrl: exchange.baseUrl,
|
exchangeBaseUrl: exchange.baseUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const wres = await wallet.client.call(
|
||||||
const wres = await wallet.client.call(WalletApiOperation.AcceptManualWithdrawal, {
|
WalletApiOperation.AcceptManualWithdrawal,
|
||||||
exchangeBaseUrl: exchange.baseUrl,
|
{
|
||||||
amount: "TESTKUDOS:10",
|
exchangeBaseUrl: exchange.baseUrl,
|
||||||
});
|
amount: "TESTKUDOS:10",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const reservePub: string = wres.reservePub;
|
const reservePub: string = wres.reservePub;
|
||||||
|
|
||||||
|
@ -87,6 +87,7 @@ import { runExchangeTimetravelTest } from "./test-exchange-timetravel.js";
|
|||||||
import { runDenomUnofferedTest } from "./test-denom-unoffered.js";
|
import { runDenomUnofferedTest } from "./test-denom-unoffered.js";
|
||||||
import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js";
|
import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js";
|
||||||
import { runClauseSchnorrTest } from "./test-clause-schnorr.js";
|
import { runClauseSchnorrTest } from "./test-clause-schnorr.js";
|
||||||
|
import { runWalletDblessTest } from "./test-wallet-dbless.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test runner.
|
* Test runner.
|
||||||
@ -162,6 +163,7 @@ const allTests: TestMainFunction[] = [
|
|||||||
runWalletBackupBasicTest,
|
runWalletBackupBasicTest,
|
||||||
runWalletBackupDoublespendTest,
|
runWalletBackupDoublespendTest,
|
||||||
runWallettestingTest,
|
runWallettestingTest,
|
||||||
|
runWalletDblessTest,
|
||||||
runWithdrawalAbortBankTest,
|
runWithdrawalAbortBankTest,
|
||||||
runWithdrawalBankIntegratedTest,
|
runWithdrawalBankIntegratedTest,
|
||||||
];
|
];
|
||||||
|
249
packages/taler-wallet-core/src/bank-api-client.ts
Normal file
249
packages/taler-wallet-core/src/bank-api-client.ts
Normal file
@ -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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -22,20 +22,22 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { CoinRecord, DenominationRecord, WireFee } from "../../db.js";
|
import { DenominationRecord, WireFee } from "../../db.js";
|
||||||
|
|
||||||
import { CryptoWorker } from "./cryptoWorkerInterface.js";
|
import { CryptoWorker } from "./cryptoWorkerInterface.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
BlindedDenominationSignature,
|
||||||
CoinDepositPermission,
|
CoinDepositPermission,
|
||||||
CoinEnvelope,
|
CoinEnvelope,
|
||||||
RecoupRefreshRequest,
|
RecoupRefreshRequest,
|
||||||
RecoupRequest,
|
RecoupRequest,
|
||||||
|
UnblindedSignature,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BenchmarkResult,
|
BenchmarkResult,
|
||||||
PlanchetCreationResult,
|
WithdrawalPlanchet,
|
||||||
PlanchetCreationRequest,
|
PlanchetCreationRequest,
|
||||||
DepositInfo,
|
DepositInfo,
|
||||||
MakeSyncSignatureRequest,
|
MakeSyncSignatureRequest,
|
||||||
@ -324,10 +326,19 @@ export class CryptoApi {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
createPlanchet(
|
createPlanchet(req: PlanchetCreationRequest): Promise<WithdrawalPlanchet> {
|
||||||
req: PlanchetCreationRequest,
|
return this.doRpc<WithdrawalPlanchet>("createPlanchet", 1, req);
|
||||||
): Promise<PlanchetCreationResult> {
|
}
|
||||||
return this.doRpc<PlanchetCreationResult>("createPlanchet", 1, req);
|
|
||||||
|
unblindDenominationSignature(req: {
|
||||||
|
planchet: WithdrawalPlanchet;
|
||||||
|
evSig: BlindedDenominationSignature;
|
||||||
|
}): Promise<UnblindedSignature> {
|
||||||
|
return this.doRpc<UnblindedSignature>(
|
||||||
|
"unblindDenominationSignature",
|
||||||
|
1,
|
||||||
|
req,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createTipPlanchet(req: DeriveTipRequest): Promise<DerivedTipPlanchet> {
|
createTipPlanchet(req: DeriveTipRequest): Promise<DerivedTipPlanchet> {
|
||||||
|
@ -53,7 +53,7 @@ import {
|
|||||||
Logger,
|
Logger,
|
||||||
MakeSyncSignatureRequest,
|
MakeSyncSignatureRequest,
|
||||||
PlanchetCreationRequest,
|
PlanchetCreationRequest,
|
||||||
PlanchetCreationResult,
|
WithdrawalPlanchet,
|
||||||
randomBytes,
|
randomBytes,
|
||||||
RecoupRefreshRequest,
|
RecoupRefreshRequest,
|
||||||
RecoupRequest,
|
RecoupRequest,
|
||||||
@ -70,6 +70,9 @@ import {
|
|||||||
Timestamp,
|
Timestamp,
|
||||||
timestampTruncateToSecond,
|
timestampTruncateToSecond,
|
||||||
typedArrayConcat,
|
typedArrayConcat,
|
||||||
|
BlindedDenominationSignature,
|
||||||
|
RsaUnblindedSignature,
|
||||||
|
UnblindedSignature,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import bigint from "big-integer";
|
import bigint from "big-integer";
|
||||||
import { DenominationRecord, WireFee } from "../../db.js";
|
import { DenominationRecord, WireFee } from "../../db.js";
|
||||||
@ -169,7 +172,7 @@ export class CryptoImplementation {
|
|||||||
*/
|
*/
|
||||||
async createPlanchet(
|
async createPlanchet(
|
||||||
req: PlanchetCreationRequest,
|
req: PlanchetCreationRequest,
|
||||||
): Promise<PlanchetCreationResult> {
|
): Promise<WithdrawalPlanchet> {
|
||||||
const denomPub = req.denomPub;
|
const denomPub = req.denomPub;
|
||||||
if (denomPub.cipher === DenomKeyType.Rsa) {
|
if (denomPub.cipher === DenomKeyType.Rsa) {
|
||||||
const reservePub = decodeCrock(req.reservePub);
|
const reservePub = decodeCrock(req.reservePub);
|
||||||
@ -200,7 +203,7 @@ export class CryptoImplementation {
|
|||||||
priv: req.reservePriv,
|
priv: req.reservePriv,
|
||||||
});
|
});
|
||||||
|
|
||||||
const planchet: PlanchetCreationResult = {
|
const planchet: WithdrawalPlanchet = {
|
||||||
blindingKey: encodeCrock(derivedPlanchet.bks),
|
blindingKey: encodeCrock(derivedPlanchet.bks),
|
||||||
coinEv,
|
coinEv,
|
||||||
coinPriv: encodeCrock(derivedPlanchet.coinPriv),
|
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.
|
* Unblind a blindly signed value.
|
||||||
*/
|
*/
|
||||||
|
@ -36,7 +36,7 @@ export * from "./db-utils.js";
|
|||||||
export { CryptoImplementation } from "./crypto/workers/cryptoImplementation.js";
|
export { CryptoImplementation } from "./crypto/workers/cryptoImplementation.js";
|
||||||
export type { CryptoWorker } from "./crypto/workers/cryptoWorkerInterface.js";
|
export type { CryptoWorker } from "./crypto/workers/cryptoWorkerInterface.js";
|
||||||
export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi.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";
|
export * from "./pending-types.js";
|
||||||
|
|
||||||
@ -47,3 +47,12 @@ export * from "./wallet.js";
|
|||||||
|
|
||||||
export * from "./operations/backup/index.js";
|
export * from "./operations/backup/index.js";
|
||||||
export { makeEventId } from "./operations/transactions.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";
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
buildCodecForObject,
|
buildCodecForObject,
|
||||||
canonicalJson,
|
canonicalJson,
|
||||||
Codec,
|
Codec,
|
||||||
|
codecForDepositSuccess,
|
||||||
codecForString,
|
codecForString,
|
||||||
codecForTimestamp,
|
codecForTimestamp,
|
||||||
codecOptional,
|
codecOptional,
|
||||||
@ -32,6 +33,7 @@ import {
|
|||||||
GetFeeForDepositRequest,
|
GetFeeForDepositRequest,
|
||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
getTimestampNow,
|
getTimestampNow,
|
||||||
|
hashWire,
|
||||||
Logger,
|
Logger,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
parsePaytoUri,
|
parsePaytoUri,
|
||||||
@ -57,7 +59,6 @@ import {
|
|||||||
generateDepositPermissions,
|
generateDepositPermissions,
|
||||||
getCandidatePayCoins,
|
getCandidatePayCoins,
|
||||||
getTotalPaymentCost,
|
getTotalPaymentCost,
|
||||||
hashWire,
|
|
||||||
} from "./pay.js";
|
} from "./pay.js";
|
||||||
import { getTotalRefreshCost } from "./refresh.js";
|
import { getTotalRefreshCost } from "./refresh.js";
|
||||||
|
|
||||||
@ -66,43 +67,6 @@ import { getTotalRefreshCost } from "./refresh.js";
|
|||||||
*/
|
*/
|
||||||
const logger = new Logger("deposits.ts");
|
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(
|
async function resetDepositGroupRetry(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
depositGroupId: string,
|
depositGroupId: string,
|
||||||
@ -202,7 +166,6 @@ async function processDepositGroupImpl(
|
|||||||
}
|
}
|
||||||
const perm = depositPermissions[i];
|
const perm = depositPermissions[i];
|
||||||
let requestBody: any;
|
let requestBody: any;
|
||||||
logger.info("creating v10 deposit request");
|
|
||||||
requestBody = {
|
requestBody = {
|
||||||
contribution: Amounts.stringify(perm.contribution),
|
contribution: Amounts.stringify(perm.contribution),
|
||||||
merchant_payto_uri: depositGroup.wire.payto_uri,
|
merchant_payto_uri: depositGroup.wire.payto_uri,
|
||||||
|
@ -43,6 +43,7 @@ import {
|
|||||||
codecForAny,
|
codecForAny,
|
||||||
DenominationPubKey,
|
DenominationPubKey,
|
||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
|
ExchangeKeysJson,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util";
|
import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util";
|
||||||
import { CryptoApi } from "../crypto/workers/cryptoApi.js";
|
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.
|
* Fetch wire information for an exchange.
|
||||||
*
|
*
|
||||||
* @param exchangeBaseUrl Exchange base URL, assumed to be already normalized.
|
* @param exchangeBaseUrl Exchange base URL, assumed to be already normalized.
|
||||||
*/
|
*/
|
||||||
async function downloadExchangeWithWireInfo(
|
async function downloadExchangeWireInfo(
|
||||||
exchangeBaseUrl: string,
|
exchangeBaseUrl: string,
|
||||||
http: HttpRequestLibrary,
|
http: HttpRequestLibrary,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
@ -374,7 +400,7 @@ interface ExchangeKeysDownloadResult {
|
|||||||
/**
|
/**
|
||||||
* Download and validate an exchange's /keys data.
|
* Download and validate an exchange's /keys data.
|
||||||
*/
|
*/
|
||||||
async function downloadKeysInfo(
|
async function downloadExchangeKeysInfo(
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
http: HttpRequestLibrary,
|
http: HttpRequestLibrary,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
@ -526,10 +552,10 @@ async function updateExchangeFromUrlImpl(
|
|||||||
|
|
||||||
const timeout = getExchangeRequestTimeout();
|
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");
|
logger.info("updating exchange /wire info");
|
||||||
const wireInfoDownload = await downloadExchangeWithWireInfo(
|
const wireInfoDownload = await downloadExchangeWireInfo(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
ws.http,
|
ws.http,
|
||||||
timeout,
|
timeout,
|
||||||
|
@ -112,19 +112,6 @@ import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js";
|
|||||||
*/
|
*/
|
||||||
const logger = new Logger("pay.ts");
|
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.
|
* Compute the total cost of a payment to the customer.
|
||||||
*
|
*
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import {
|
import {
|
||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
|
ExchangeMeltRequest,
|
||||||
ExchangeProtocolVersion,
|
ExchangeProtocolVersion,
|
||||||
ExchangeRefreshRevealRequest,
|
ExchangeRefreshRevealRequest,
|
||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
@ -394,17 +395,14 @@ async function refreshMelt(
|
|||||||
`coins/${oldCoin.coinPub}/melt`,
|
`coins/${oldCoin.coinPub}/melt`,
|
||||||
oldCoin.exchangeBaseUrl,
|
oldCoin.exchangeBaseUrl,
|
||||||
);
|
);
|
||||||
let meltReqBody: any;
|
const meltReqBody: ExchangeMeltRequest = {
|
||||||
if (oldDenom.denomPub.cipher === DenomKeyType.Rsa) {
|
coin_pub: oldCoin.coinPub,
|
||||||
meltReqBody = {
|
confirm_sig: derived.confirmSig,
|
||||||
coin_pub: oldCoin.coinPub,
|
denom_pub_hash: oldCoin.denomPubHash,
|
||||||
confirm_sig: derived.confirmSig,
|
denom_sig: oldCoin.denomSig,
|
||||||
denom_pub_hash: oldCoin.denomPubHash,
|
rc: derived.hash,
|
||||||
denom_sig: oldCoin.denomSig,
|
value_with_fee: Amounts.stringify(derived.meltValueWithFee),
|
||||||
rc: derived.hash,
|
};
|
||||||
value_with_fee: Amounts.stringify(derived.meltValueWithFee),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
|
const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
|
||||||
return await ws.http.postJson(reqUrl.href, meltReqBody, {
|
return await ws.http.postJson(reqUrl.href, meltReqBody, {
|
||||||
|
@ -780,7 +780,7 @@ export async function createTalerWithdrawReserve(
|
|||||||
selectedExchange: string,
|
selectedExchange: string,
|
||||||
): Promise<AcceptWithdrawalResponse> {
|
): Promise<AcceptWithdrawalResponse> {
|
||||||
await updateExchangeFromUrl(ws, selectedExchange);
|
await updateExchangeFromUrl(ws, selectedExchange);
|
||||||
const withdrawInfo = await getBankWithdrawalInfo(ws, talerWithdrawUri);
|
const withdrawInfo = await getBankWithdrawalInfo(ws.http, talerWithdrawUri);
|
||||||
const exchangePaytoUri = await getExchangePaytoUri(
|
const exchangePaytoUri = await getExchangePaytoUri(
|
||||||
ws,
|
ws,
|
||||||
selectedExchange,
|
selectedExchange,
|
||||||
|
@ -74,7 +74,7 @@ function makeId(length: number): string {
|
|||||||
/**
|
/**
|
||||||
* Helper function to generate the "Authorization" HTTP header.
|
* 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 auth = `${username}:${password}`;
|
||||||
const authEncoded: string = Buffer.from(auth).toString("base64");
|
const authEncoded: string = Buffer.from(auth).toString("base64");
|
||||||
return `Basic ${authEncoded}`;
|
return `Basic ${authEncoded}`;
|
||||||
@ -89,7 +89,7 @@ export async function withdrawTestBalance(
|
|||||||
const bankUser = await registerRandomBankUser(ws.http, bankBaseUrl);
|
const bankUser = await registerRandomBankUser(ws.http, bankBaseUrl);
|
||||||
logger.trace(`Registered bank user ${JSON.stringify(bankUser)}`);
|
logger.trace(`Registered bank user ${JSON.stringify(bankUser)}`);
|
||||||
|
|
||||||
const wresp = await createBankWithdrawalUri(
|
const wresp = await createDemoBankWithdrawalUri(
|
||||||
ws.http,
|
ws.http,
|
||||||
bankBaseUrl,
|
bankBaseUrl,
|
||||||
bankUser,
|
bankUser,
|
||||||
@ -119,7 +119,11 @@ function getMerchantAuthHeader(m: MerchantBackendInfo): Record<string, string> {
|
|||||||
return {};
|
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,
|
http: HttpRequestLibrary,
|
||||||
bankBaseUrl: string,
|
bankBaseUrl: string,
|
||||||
bankUser: BankUser,
|
bankUser: BankUser,
|
||||||
@ -136,7 +140,7 @@ async function createBankWithdrawalUri(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: makeAuth(bankUser.username, bankUser.password),
|
Authorization: makeBasicAuthHeader(bankUser.username, bankUser.password),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -159,7 +163,7 @@ async function confirmBankWithdrawalUri(
|
|||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: makeAuth(bankUser.username, bankUser.password),
|
Authorization: makeBasicAuthHeader(bankUser.username, bankUser.password),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -59,7 +59,10 @@ import {
|
|||||||
WithdrawalGroupRecord,
|
WithdrawalGroupRecord,
|
||||||
} from "../db.js";
|
} from "../db.js";
|
||||||
import { walletCoreDebugFlags } from "../util/debugFlags.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 { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
|
||||||
import {
|
import {
|
||||||
guardOperationException,
|
guardOperationException,
|
||||||
@ -271,9 +274,11 @@ export function selectWithdrawalDenominations(
|
|||||||
/**
|
/**
|
||||||
* Get information about a withdrawal from
|
* Get information about a withdrawal from
|
||||||
* a taler://withdraw URI by asking the bank.
|
* a taler://withdraw URI by asking the bank.
|
||||||
|
*
|
||||||
|
* FIXME: Move into bank client.
|
||||||
*/
|
*/
|
||||||
export async function getBankWithdrawalInfo(
|
export async function getBankWithdrawalInfo(
|
||||||
ws: InternalWalletState,
|
http: HttpRequestLibrary,
|
||||||
talerWithdrawUri: string,
|
talerWithdrawUri: string,
|
||||||
): Promise<BankWithdrawDetails> {
|
): Promise<BankWithdrawDetails> {
|
||||||
const uriResult = parseWithdrawUri(talerWithdrawUri);
|
const uriResult = parseWithdrawUri(talerWithdrawUri);
|
||||||
@ -283,7 +288,7 @@ export async function getBankWithdrawalInfo(
|
|||||||
|
|
||||||
const configReqUrl = new URL("config", uriResult.bankIntegrationApiBaseUrl);
|
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(
|
const config = await readSuccessResponseJsonOrThrow(
|
||||||
configResp,
|
configResp,
|
||||||
codecForTalerConfigResponse(),
|
codecForTalerConfigResponse(),
|
||||||
@ -309,7 +314,7 @@ export async function getBankWithdrawalInfo(
|
|||||||
`withdrawal-operation/${uriResult.withdrawalOperationId}`,
|
`withdrawal-operation/${uriResult.withdrawalOperationId}`,
|
||||||
uriResult.bankIntegrationApiBaseUrl,
|
uriResult.bankIntegrationApiBaseUrl,
|
||||||
);
|
);
|
||||||
const resp = await ws.http.get(reqUrl.href);
|
const resp = await http.get(reqUrl.href);
|
||||||
const status = await readSuccessResponseJsonOrThrow(
|
const status = await readSuccessResponseJsonOrThrow(
|
||||||
resp,
|
resp,
|
||||||
codecForWithdrawOperationStatusResponse(),
|
codecForWithdrawOperationStatusResponse(),
|
||||||
@ -1076,7 +1081,7 @@ export async function getWithdrawalDetailsForUri(
|
|||||||
talerWithdrawUri: string,
|
talerWithdrawUri: string,
|
||||||
): Promise<WithdrawUriInfoResponse> {
|
): Promise<WithdrawUriInfoResponse> {
|
||||||
logger.trace(`getting withdrawal details for URI ${talerWithdrawUri}`);
|
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`);
|
logger.trace(`got bank info`);
|
||||||
if (info.suggestedExchange) {
|
if (info.suggestedExchange) {
|
||||||
// FIXME: right now the exchange gets permanently added,
|
// FIXME: right now the exchange gets permanently added,
|
||||||
|
@ -34,6 +34,7 @@ import {
|
|||||||
timestampMax,
|
timestampMax,
|
||||||
TalerErrorDetails,
|
TalerErrorDetails,
|
||||||
Codec,
|
Codec,
|
||||||
|
j2s,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { TalerErrorCode } 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 errJson = await httpResponse.json();
|
||||||
const talerErrorCode = errJson.code;
|
const talerErrorCode = errJson.code;
|
||||||
if (typeof talerErrorCode !== "number") {
|
if (typeof talerErrorCode !== "number") {
|
||||||
|
logger.warn(
|
||||||
|
`malformed error response (status ${httpResponse.status}): ${j2s(
|
||||||
|
errJson,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
throw new OperationFailedError(
|
throw new OperationFailedError(
|
||||||
makeErrorDetails(
|
makeErrorDetails(
|
||||||
TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
|
TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
|
||||||
|
@ -0,0 +1,366 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2016-11-23 00:00+0100\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:86
|
||||||
|
#, c-format
|
||||||
|
msgid "Balance"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:87
|
||||||
|
#, c-format
|
||||||
|
msgid "Pending"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:88
|
||||||
|
#, c-format
|
||||||
|
msgid "Backup"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:89
|
||||||
|
#, c-format
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:90
|
||||||
|
#, c-format
|
||||||
|
msgid "Dev"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx:127
|
||||||
|
#, c-format
|
||||||
|
msgid "Add provider"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx:137
|
||||||
|
#, c-format
|
||||||
|
msgid "Sync all backups"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx:139
|
||||||
|
#, c-format
|
||||||
|
msgid "Sync now"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/popup/BalancePage.tsx:79
|
||||||
|
#, c-format
|
||||||
|
msgid "You have no balance to show. Need some %1$s getting started?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:145
|
||||||
|
#, c-format
|
||||||
|
msgid "< Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:156
|
||||||
|
#, c-format
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:210
|
||||||
|
#, c-format
|
||||||
|
msgid "< Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:213
|
||||||
|
#, c-format
|
||||||
|
msgid "Add provider"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:57
|
||||||
|
#, c-format
|
||||||
|
msgid "Loading..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:64
|
||||||
|
#, c-format
|
||||||
|
msgid "There was an error loading the provider detail for "%1$s""
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:75
|
||||||
|
#, c-format
|
||||||
|
msgid "There is not known provider with url "%1$s". Redirecting back..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:131
|
||||||
|
#, c-format
|
||||||
|
msgid "Back up"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:142
|
||||||
|
#, c-format
|
||||||
|
msgid "Extend"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:148
|
||||||
|
#, c-format
|
||||||
|
msgid ""
|
||||||
|
"terms has changed, extending the service will imply accepting the new terms of "
|
||||||
|
"service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:158
|
||||||
|
#, c-format
|
||||||
|
msgid "old"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:162
|
||||||
|
#, c-format
|
||||||
|
msgid "new"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:169
|
||||||
|
#, c-format
|
||||||
|
msgid "fee"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:177
|
||||||
|
#, c-format
|
||||||
|
msgid "storage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:190
|
||||||
|
#, c-format
|
||||||
|
msgid "< back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:194
|
||||||
|
#, c-format
|
||||||
|
msgid "remove provider"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:213
|
||||||
|
#, c-format
|
||||||
|
msgid "There is conflict with another backup from %1$s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:228
|
||||||
|
#, c-format
|
||||||
|
msgid "Unknown backup problem: %1$s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:247
|
||||||
|
#, c-format
|
||||||
|
msgid "service paid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/popup/Settings.tsx:46
|
||||||
|
#, c-format
|
||||||
|
msgid "Permissions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:37
|
||||||
|
#, c-format
|
||||||
|
msgid "Exchange doesn't have terms of service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:49
|
||||||
|
#, c-format
|
||||||
|
msgid "Exchange doesn't have terms of service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:56
|
||||||
|
#, c-format
|
||||||
|
msgid "Review exchange terms of service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:63
|
||||||
|
#, c-format
|
||||||
|
msgid "Review new version of terms of service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:75
|
||||||
|
#, c-format
|
||||||
|
msgid "Show terms of service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:83
|
||||||
|
#, c-format
|
||||||
|
msgid "I accept the exchange terms of service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:127
|
||||||
|
#, c-format
|
||||||
|
msgid "Hide terms of service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:136
|
||||||
|
#, c-format
|
||||||
|
msgid "I accept the exchange terms of service"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:110
|
||||||
|
#, c-format
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:114
|
||||||
|
#, c-format
|
||||||
|
msgid "Loading terms.."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:121
|
||||||
|
#, c-format
|
||||||
|
msgid "Add exchange"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:126
|
||||||
|
#, c-format
|
||||||
|
msgid "Add exchange"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:131
|
||||||
|
#, c-format
|
||||||
|
msgid "Add exchange anyway"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx:133
|
||||||
|
#, c-format
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx:149
|
||||||
|
#, c-format
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx:83
|
||||||
|
#, c-format
|
||||||
|
msgid "You have no balance to show. Need some %1$s getting started?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx:104
|
||||||
|
#, c-format
|
||||||
|
msgid "Add exchange"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx:144
|
||||||
|
#, c-format
|
||||||
|
msgid "Add exchange"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Settings.tsx:84
|
||||||
|
#, c-format
|
||||||
|
msgid "Permissions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Settings.tsx:95
|
||||||
|
#, c-format
|
||||||
|
msgid "Known exchanges"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:154
|
||||||
|
#, c-format
|
||||||
|
msgid "< Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:159
|
||||||
|
#, c-format
|
||||||
|
msgid "retry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:163
|
||||||
|
#, c-format
|
||||||
|
msgid "Forget"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:194
|
||||||
|
#, c-format
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:198
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:211
|
||||||
|
#, c-format
|
||||||
|
msgid "Pay with a mobile phone"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:211
|
||||||
|
#, c-format
|
||||||
|
msgid "Hide QR"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:241
|
||||||
|
#, c-format
|
||||||
|
msgid "Pay"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:265
|
||||||
|
#, c-format
|
||||||
|
msgid "Withdraw digital cash"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:295
|
||||||
|
#, c-format
|
||||||
|
msgid "Digital cash payment"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:101
|
||||||
|
#, c-format
|
||||||
|
msgid "Digital cash withdrawal"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:149
|
||||||
|
#, c-format
|
||||||
|
msgid "Cancel exchange selection"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:150
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm exchange selection"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:155
|
||||||
|
#, c-format
|
||||||
|
msgid "Switch exchange"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:174
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm withdrawal"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:183
|
||||||
|
#, c-format
|
||||||
|
msgid "Withdraw anyway"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:310
|
||||||
|
#, c-format
|
||||||
|
msgid "missing withdraw uri"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Deposit.tsx:119
|
||||||
|
#, c-format
|
||||||
|
msgid "Digital cash payment"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Deposit.tsx:133
|
||||||
|
#, c-format
|
||||||
|
msgid "Digital cash payment"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Deposit.tsx:186
|
||||||
|
#, c-format
|
||||||
|
msgid "Digital cash deposit"
|
||||||
|
msgstr ""
|
||||||
|
|
@ -38,7 +38,7 @@ import {
|
|||||||
RemoveBackupProviderRequest
|
RemoveBackupProviderRequest
|
||||||
} from "@gnu-taler/taler-wallet-core";
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits";
|
import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits";
|
||||||
import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw";
|
import type { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw";
|
||||||
import { MessageFromBackend } from "./wxBackend";
|
import { MessageFromBackend } from "./wxBackend";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user