wallet robustness WIP
This commit is contained in:
parent
809fa18644
commit
aaf7e1338d
@ -60,8 +60,6 @@ const paths = {
|
||||
dist: [
|
||||
"dist/*-bundle.js",
|
||||
"dist/*-bundle.js.map",
|
||||
"emscripten/taler-emscripten-lib.js",
|
||||
"emscripten/taler-emscripten-lib.wasm",
|
||||
"img/icon.png",
|
||||
"img/logo.png",
|
||||
"src/webex/**/*.{js,css,html}",
|
||||
@ -149,7 +147,7 @@ function dist_prod() {
|
||||
}
|
||||
|
||||
function compile_prod(callback) {
|
||||
let config = require("./webpack.config.js")({ prod: true });
|
||||
let config = require("./webpack.config.js")({ mode: "production" });
|
||||
webpack(config, function(err, stats) {
|
||||
if (err) {
|
||||
throw new gutil.PluginError("webpack", err);
|
||||
|
@ -64,7 +64,7 @@
|
||||
"@types/urijs": "^1.19.3",
|
||||
"axios": "^0.19.0",
|
||||
"big-integer": "^1.6.48",
|
||||
"idb-bridge": "^0.0.14",
|
||||
"idb-bridge": "^0.0.15",
|
||||
"qrcode-generator": "^1.4.3",
|
||||
"source-map-support": "^0.5.12",
|
||||
"urijs": "^1.18.10"
|
||||
|
@ -27,7 +27,7 @@ import { AmountJson } from "../amounts";
|
||||
import {
|
||||
CoinRecord,
|
||||
DenominationRecord,
|
||||
PreCoinRecord,
|
||||
PlanchetRecord,
|
||||
RefreshSessionRecord,
|
||||
ReserveRecord,
|
||||
TipPlanchet,
|
||||
@ -38,7 +38,7 @@ import { CryptoWorker } from "./cryptoWorker";
|
||||
|
||||
import { ContractTerms, PaybackRequest } from "../talerTypes";
|
||||
|
||||
import { BenchmarkResult, CoinWithDenom, PayCoinInfo } from "../walletTypes";
|
||||
import { BenchmarkResult, CoinWithDenom, PayCoinInfo, PlanchetCreationResult } from "../walletTypes";
|
||||
|
||||
import * as timer from "../timer";
|
||||
|
||||
@ -173,6 +173,7 @@ export class CryptoApi {
|
||||
*/
|
||||
wake(ws: WorkerState, work: WorkItem): void {
|
||||
if (this.stopped) {
|
||||
console.log("cryptoApi is stopped");
|
||||
CryptoApi.enableTracing && console.log("not waking, as cryptoApi is stopped");
|
||||
return;
|
||||
}
|
||||
@ -299,7 +300,6 @@ export class CryptoApi {
|
||||
priority: number,
|
||||
...args: any[]
|
||||
): Promise<T> {
|
||||
CryptoApi.enableTracing && console.log("cryptoApi: doRpc called");
|
||||
const p: Promise<T> = new Promise<T>((resolve, reject) => {
|
||||
const rpcId = this.nextRpcId++;
|
||||
const workItem: WorkItem = {
|
||||
@ -332,16 +332,14 @@ export class CryptoApi {
|
||||
throw Error("assertion failed");
|
||||
});
|
||||
|
||||
return p.then((r: T) => {
|
||||
return r;
|
||||
});
|
||||
return p;
|
||||
}
|
||||
|
||||
createPreCoin(
|
||||
createPlanchet(
|
||||
denom: DenominationRecord,
|
||||
reserve: ReserveRecord,
|
||||
): Promise<PreCoinRecord> {
|
||||
return this.doRpc<PreCoinRecord>("createPreCoin", 1, denom, reserve);
|
||||
): Promise<PlanchetCreationResult> {
|
||||
return this.doRpc<PlanchetCreationResult>("createPlanchet", 1, denom, reserve);
|
||||
}
|
||||
|
||||
createTipPlanchet(denom: DenominationRecord): Promise<TipPlanchet> {
|
||||
|
@ -28,8 +28,7 @@ import {
|
||||
CoinRecord,
|
||||
CoinStatus,
|
||||
DenominationRecord,
|
||||
PreCoinRecord,
|
||||
RefreshPreCoinRecord,
|
||||
RefreshPlanchetRecord,
|
||||
RefreshSessionRecord,
|
||||
ReserveRecord,
|
||||
TipPlanchet,
|
||||
@ -42,6 +41,7 @@ import {
|
||||
CoinWithDenom,
|
||||
PayCoinInfo,
|
||||
Timestamp,
|
||||
PlanchetCreationResult,
|
||||
} from "../walletTypes";
|
||||
import { canonicalJson, getTalerStampSec } from "../helpers";
|
||||
import { AmountJson } from "../amounts";
|
||||
@ -154,10 +154,10 @@ export class CryptoImplementation {
|
||||
* Create a pre-coin of the given denomination to be withdrawn from then given
|
||||
* reserve.
|
||||
*/
|
||||
createPreCoin(
|
||||
createPlanchet(
|
||||
denom: DenominationRecord,
|
||||
reserve: ReserveRecord,
|
||||
): PreCoinRecord {
|
||||
): PlanchetCreationResult {
|
||||
const reservePub = decodeCrock(reserve.reservePub);
|
||||
const reservePriv = decodeCrock(reserve.reservePriv);
|
||||
const denomPub = decodeCrock(denom.denomPub);
|
||||
@ -179,7 +179,7 @@ export class CryptoImplementation {
|
||||
|
||||
const sig = eddsaSign(withdrawRequest, reservePriv);
|
||||
|
||||
const preCoin: PreCoinRecord = {
|
||||
const planchet: PlanchetCreationResult = {
|
||||
blindingKey: encodeCrock(blindingFactor),
|
||||
coinEv: encodeCrock(ev),
|
||||
coinPriv: encodeCrock(coinKeyPair.eddsaPriv),
|
||||
@ -188,11 +188,10 @@ export class CryptoImplementation {
|
||||
denomPub: encodeCrock(denomPub),
|
||||
denomPubHash: encodeCrock(denomPubHash),
|
||||
exchangeBaseUrl: reserve.exchangeBaseUrl,
|
||||
isFromTip: false,
|
||||
reservePub: encodeCrock(reservePub),
|
||||
withdrawSig: encodeCrock(sig),
|
||||
};
|
||||
return preCoin;
|
||||
return planchet;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -424,7 +423,7 @@ export class CryptoImplementation {
|
||||
const transferPubs: string[] = [];
|
||||
const transferPrivs: string[] = [];
|
||||
|
||||
const preCoinsForGammas: RefreshPreCoinRecord[][] = [];
|
||||
const planchetsForGammas: RefreshPlanchetRecord[][] = [];
|
||||
|
||||
for (let i = 0; i < kappa; i++) {
|
||||
const transferKeyPair = createEcdheKeyPair();
|
||||
@ -442,7 +441,7 @@ export class CryptoImplementation {
|
||||
sessionHc.update(amountToBuffer(valueWithFee));
|
||||
|
||||
for (let i = 0; i < kappa; i++) {
|
||||
const preCoins: RefreshPreCoinRecord[] = [];
|
||||
const planchets: RefreshPlanchetRecord[] = [];
|
||||
for (let j = 0; j < newCoinDenoms.length; j++) {
|
||||
const transferPriv = decodeCrock(transferPrivs[i]);
|
||||
const oldCoinPub = decodeCrock(meltCoin.coinPub);
|
||||
@ -456,16 +455,16 @@ export class CryptoImplementation {
|
||||
const pubHash = hash(coinPub);
|
||||
const denomPub = decodeCrock(newCoinDenoms[j].denomPub);
|
||||
const ev = rsaBlind(pubHash, blindingFactor, denomPub);
|
||||
const preCoin: RefreshPreCoinRecord = {
|
||||
const planchet: RefreshPlanchetRecord = {
|
||||
blindingKey: encodeCrock(blindingFactor),
|
||||
coinEv: encodeCrock(ev),
|
||||
privateKey: encodeCrock(coinPriv),
|
||||
publicKey: encodeCrock(coinPub),
|
||||
};
|
||||
preCoins.push(preCoin);
|
||||
planchets.push(planchet);
|
||||
sessionHc.update(ev);
|
||||
}
|
||||
preCoinsForGammas.push(preCoins);
|
||||
planchetsForGammas.push(planchets);
|
||||
}
|
||||
|
||||
const sessionHash = sessionHc.finish();
|
||||
@ -496,7 +495,7 @@ export class CryptoImplementation {
|
||||
newDenomHashes: newCoinDenoms.map(d => d.denomPubHash),
|
||||
newDenoms: newCoinDenoms.map(d => d.denomPub),
|
||||
norevealIndex: undefined,
|
||||
preCoinsForGammas,
|
||||
planchetsForGammas: planchetsForGammas,
|
||||
transferPrivs,
|
||||
transferPubs,
|
||||
valueOutput,
|
||||
|
@ -88,5 +88,5 @@ export function kdf(
|
||||
output.set(chunk, i * 32);
|
||||
}
|
||||
|
||||
return output;
|
||||
return output.slice(0, outputLength);
|
||||
}
|
||||
|
@ -237,6 +237,9 @@ function rsaFullDomainHash(hm: Uint8Array, rsaPub: RsaPub): bigint.BigInteger {
|
||||
function rsaPubDecode(rsaPub: Uint8Array): RsaPub {
|
||||
const modulusLength = (rsaPub[0] << 8) | rsaPub[1];
|
||||
const exponentLength = (rsaPub[2] << 8) | rsaPub[3];
|
||||
if (4 + exponentLength + modulusLength != rsaPub.length) {
|
||||
throw Error("invalid RSA public key (format wrong)");
|
||||
}
|
||||
const modulus = rsaPub.slice(4, 4 + modulusLength);
|
||||
const exponent = rsaPub.slice(
|
||||
4 + modulusLength,
|
||||
|
164
src/dbTypes.ts
164
src/dbTypes.ts
@ -57,6 +57,13 @@ export enum ReserveRecordStatus {
|
||||
*/
|
||||
REGISTERING_BANK = "registering-bank",
|
||||
|
||||
/**
|
||||
* We've registered reserve's information with the bank
|
||||
* and are now waiting for the user to confirm the withdraw
|
||||
* with the bank (typically 2nd factor auth).
|
||||
*/
|
||||
WAIT_CONFIRM_BANK = "wait-confirm-bank",
|
||||
|
||||
/**
|
||||
* Querying reserve status with the exchange.
|
||||
*/
|
||||
@ -117,22 +124,26 @@ export interface ReserveRecord {
|
||||
timestampConfirmed: Timestamp | undefined;
|
||||
|
||||
/**
|
||||
* Current amount left in the reserve
|
||||
* Amount that's still available for withdrawing
|
||||
* from this reserve.
|
||||
*/
|
||||
currentAmount: AmountJson | null;
|
||||
withdrawRemainingAmount: AmountJson;
|
||||
|
||||
/**
|
||||
* Amount allocated for withdrawing.
|
||||
* The corresponding withdraw operation may or may not
|
||||
* have been completed yet.
|
||||
*/
|
||||
withdrawAllocatedAmount: AmountJson;
|
||||
|
||||
withdrawCompletedAmount: AmountJson;
|
||||
|
||||
/**
|
||||
* Amount requested when the reserve was created.
|
||||
* When a reserve is re-used (rare!) the current_amount can
|
||||
* be higher than the requested_amount
|
||||
*/
|
||||
requestedAmount: AmountJson;
|
||||
|
||||
/**
|
||||
* What's the current amount that sits
|
||||
* in precoins?
|
||||
*/
|
||||
precoinAmount: AmountJson;
|
||||
initiallyRequestedAmount: AmountJson;
|
||||
|
||||
/**
|
||||
* We got some payback to this reserve. We'll cease to automatically
|
||||
@ -154,8 +165,19 @@ export interface ReserveRecord {
|
||||
|
||||
bankWithdrawStatusUrl?: string;
|
||||
|
||||
/**
|
||||
* URL that the bank gave us to redirect the customer
|
||||
* to in order to confirm a withdrawal.
|
||||
*/
|
||||
bankWithdrawConfirmUrl?: string;
|
||||
|
||||
reserveStatus: ReserveRecordStatus;
|
||||
|
||||
/**
|
||||
* Time of the last successful status query.
|
||||
*/
|
||||
lastStatusQuery: Timestamp | undefined;
|
||||
|
||||
lastError?: OperationError;
|
||||
}
|
||||
|
||||
@ -421,7 +443,16 @@ export interface ExchangeRecord {
|
||||
/**
|
||||
* A coin that isn't yet signed by an exchange.
|
||||
*/
|
||||
export interface PreCoinRecord {
|
||||
export interface PlanchetRecord {
|
||||
withdrawSessionId: string;
|
||||
/**
|
||||
* Index of the coin in the withdrawal session.
|
||||
*/
|
||||
coinIndex: number;
|
||||
|
||||
/**
|
||||
* Public key of the coin.
|
||||
*/
|
||||
coinPub: string;
|
||||
coinPriv: string;
|
||||
reservePub: string;
|
||||
@ -443,7 +474,7 @@ export interface PreCoinRecord {
|
||||
/**
|
||||
* Planchet for a coin during refrehs.
|
||||
*/
|
||||
export interface RefreshPreCoinRecord {
|
||||
export interface RefreshPlanchetRecord {
|
||||
/**
|
||||
* Public key for the coin.
|
||||
*/
|
||||
@ -485,6 +516,16 @@ export enum CoinStatus {
|
||||
* of the wallet database.
|
||||
*/
|
||||
export interface CoinRecord {
|
||||
/**
|
||||
* Withdraw session ID, or "" (empty string) if withdrawn via refresh.
|
||||
*/
|
||||
withdrawSessionId: string;
|
||||
|
||||
/**
|
||||
* Index of the coin in the withdrawal session.
|
||||
*/
|
||||
coinIndex: number;
|
||||
|
||||
/**
|
||||
* Public key of the coin.
|
||||
*/
|
||||
@ -546,11 +587,17 @@ export interface CoinRecord {
|
||||
status: CoinStatus;
|
||||
}
|
||||
|
||||
export enum ProposalStatus {
|
||||
PROPOSED = "proposed",
|
||||
ACCEPTED = "accepted",
|
||||
REJECTED = "rejected",
|
||||
}
|
||||
|
||||
/**
|
||||
* Proposal record, stored in the wallet's database.
|
||||
* Record for a downloaded order, stored in the wallet's database.
|
||||
*/
|
||||
@Checkable.Class()
|
||||
export class ProposalDownloadRecord {
|
||||
export class ProposalRecord {
|
||||
/**
|
||||
* URL where the proposal was downloaded.
|
||||
*/
|
||||
@ -576,10 +623,10 @@ export class ProposalDownloadRecord {
|
||||
contractTermsHash: string;
|
||||
|
||||
/**
|
||||
* Serial ID when the offer is stored in the wallet DB.
|
||||
* Unique ID when the order is stored in the wallet DB.
|
||||
*/
|
||||
@Checkable.Optional(Checkable.Number())
|
||||
id?: number;
|
||||
@Checkable.String()
|
||||
proposalId: string;
|
||||
|
||||
/**
|
||||
* Timestamp (in ms) of when the record
|
||||
@ -594,6 +641,9 @@ export class ProposalDownloadRecord {
|
||||
@Checkable.String()
|
||||
noncePriv: string;
|
||||
|
||||
@Checkable.String()
|
||||
proposalStatus: ProposalStatus;
|
||||
|
||||
/**
|
||||
* Session ID we got when downloading the contract.
|
||||
*/
|
||||
@ -604,7 +654,7 @@ export class ProposalDownloadRecord {
|
||||
* Verify that a value matches the schema of this class and convert it into a
|
||||
* member.
|
||||
*/
|
||||
static checked: (obj: any) => ProposalDownloadRecord;
|
||||
static checked: (obj: any) => ProposalRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -717,9 +767,9 @@ export interface RefreshSessionRecord {
|
||||
newDenoms: string[];
|
||||
|
||||
/**
|
||||
* Precoins for each cut-and-choose instance.
|
||||
* Planchets for each cut-and-choose instance.
|
||||
*/
|
||||
preCoinsForGammas: RefreshPreCoinRecord[][];
|
||||
planchetsForGammas: RefreshPlanchetRecord[][];
|
||||
|
||||
/**
|
||||
* The transfer keys, kappa of them.
|
||||
@ -933,7 +983,9 @@ export interface CoinsReturnRecord {
|
||||
wire: any;
|
||||
}
|
||||
|
||||
export interface WithdrawalRecord {
|
||||
export interface WithdrawalSessionRecord {
|
||||
withdrawSessionId: string;
|
||||
|
||||
/**
|
||||
* Reserve that we're withdrawing from.
|
||||
*/
|
||||
@ -956,9 +1008,29 @@ export interface WithdrawalRecord {
|
||||
*/
|
||||
withdrawalAmount: string;
|
||||
|
||||
numCoinsTotal: number;
|
||||
denoms: string[];
|
||||
|
||||
numCoinsWithdrawn: number;
|
||||
/**
|
||||
* Coins in this session that are withdrawn are set to true.
|
||||
*/
|
||||
withdrawn: boolean[];
|
||||
|
||||
/**
|
||||
* Coins in this session already have a planchet are set to true.
|
||||
*/
|
||||
planchetCreated: boolean[];
|
||||
}
|
||||
|
||||
export interface BankWithdrawUriRecord {
|
||||
/**
|
||||
* The withdraw URI we got from the bank.
|
||||
*/
|
||||
talerWithdrawUri: string;
|
||||
|
||||
/**
|
||||
* Reserve that was created for the withdraw URI.
|
||||
*/
|
||||
reservePub: string;
|
||||
}
|
||||
|
||||
/* tslint:disable:completed-docs */
|
||||
@ -967,7 +1039,7 @@ export interface WithdrawalRecord {
|
||||
* The stores and indices for the wallet database.
|
||||
*/
|
||||
export namespace Stores {
|
||||
class ExchangeStore extends Store<ExchangeRecord> {
|
||||
class ExchangesStore extends Store<ExchangeRecord> {
|
||||
constructor() {
|
||||
super("exchanges", { keyPath: "baseUrl" });
|
||||
}
|
||||
@ -988,16 +1060,18 @@ export namespace Stores {
|
||||
"denomPubIndex",
|
||||
"denomPub",
|
||||
);
|
||||
byWithdrawalWithIdx = new Index<any, CoinRecord>(
|
||||
this,
|
||||
"planchetsByWithdrawalWithIdxIndex",
|
||||
["withdrawSessionId", "coinIndex"],
|
||||
);
|
||||
}
|
||||
|
||||
class ProposalsStore extends Store<ProposalDownloadRecord> {
|
||||
class ProposalsStore extends Store<ProposalRecord> {
|
||||
constructor() {
|
||||
super("proposals", {
|
||||
autoIncrement: true,
|
||||
keyPath: "id",
|
||||
});
|
||||
super("proposals", { keyPath: "proposalId" });
|
||||
}
|
||||
urlIndex = new Index<string, ProposalDownloadRecord>(
|
||||
urlIndex = new Index<string, ProposalRecord>(
|
||||
this,
|
||||
"urlIndex",
|
||||
"url",
|
||||
@ -1084,28 +1158,39 @@ export namespace Stores {
|
||||
}
|
||||
}
|
||||
|
||||
class WithdrawalsStore extends Store<WithdrawalRecord> {
|
||||
class WithdrawalSessionsStore extends Store<WithdrawalSessionRecord> {
|
||||
constructor() {
|
||||
super("withdrawals", { keyPath: "id", autoIncrement: true });
|
||||
super("withdrawals", { keyPath: "withdrawSessionId" });
|
||||
}
|
||||
byReservePub = new Index<string, WithdrawalRecord>(
|
||||
byReservePub = new Index<string, WithdrawalSessionRecord>(
|
||||
this,
|
||||
"withdrawalsReservePubIndex",
|
||||
"reservePub",
|
||||
);
|
||||
}
|
||||
|
||||
class PreCoinsStore extends Store<PreCoinRecord> {
|
||||
class BankWithdrawUrisStore extends Store<BankWithdrawUriRecord> {
|
||||
constructor() {
|
||||
super("precoins", {
|
||||
super("bankWithdrawUris", { keyPath: "talerWithdrawUri" });
|
||||
}
|
||||
}
|
||||
|
||||
class PlanchetsStore extends Store<PlanchetRecord> {
|
||||
constructor() {
|
||||
super("planchets", {
|
||||
keyPath: "coinPub",
|
||||
});
|
||||
}
|
||||
byReservePub = new Index<string, PreCoinRecord>(
|
||||
byReservePub = new Index<string, PlanchetRecord>(
|
||||
this,
|
||||
"precoinsReservePubIndex",
|
||||
"planchetsReservePubIndex",
|
||||
"reservePub",
|
||||
);
|
||||
byWithdrawalWithIdx = new Index<any, PlanchetRecord>(
|
||||
this,
|
||||
"planchetsByWithdrawalWithIdxIndex",
|
||||
["withdrawSessionId", "coinIndex"],
|
||||
);
|
||||
}
|
||||
|
||||
export const coins = new CoinsStore();
|
||||
@ -1115,8 +1200,8 @@ export namespace Stores {
|
||||
export const config = new ConfigStore();
|
||||
export const currencies = new CurrenciesStore();
|
||||
export const denominations = new DenominationsStore();
|
||||
export const exchanges = new ExchangeStore();
|
||||
export const precoins = new PreCoinsStore();
|
||||
export const exchanges = new ExchangesStore();
|
||||
export const planchets = new PlanchetsStore();
|
||||
export const proposals = new ProposalsStore();
|
||||
export const refresh = new Store<RefreshSessionRecord>("refresh", {
|
||||
keyPath: "refreshSessionId",
|
||||
@ -1125,7 +1210,8 @@ export namespace Stores {
|
||||
export const purchases = new PurchasesStore();
|
||||
export const tips = new TipsStore();
|
||||
export const senderWires = new SenderWiresStore();
|
||||
export const withdrawals = new WithdrawalsStore();
|
||||
export const withdrawalSession = new WithdrawalSessionsStore();
|
||||
export const bankWithdrawUris = new BankWithdrawUrisStore();
|
||||
}
|
||||
|
||||
/* tslint:enable:completed-docs */
|
||||
|
@ -45,6 +45,37 @@ function makeId(length: number): string {
|
||||
export class Bank {
|
||||
constructor(private bankBaseUrl: string) {}
|
||||
|
||||
async generateWithdrawUri(bankUser: BankUser, amount: string): Promise<string> {
|
||||
const body = {
|
||||
amount,
|
||||
};
|
||||
|
||||
const reqUrl = new URI("api/withdraw-headless-uri")
|
||||
.absoluteTo(this.bankBaseUrl)
|
||||
.href();
|
||||
|
||||
const resp = await Axios({
|
||||
method: "post",
|
||||
url: reqUrl,
|
||||
data: body,
|
||||
responseType: "json",
|
||||
headers: {
|
||||
"X-Taler-Bank-Username": bankUser.username,
|
||||
"X-Taler-Bank-Password": bankUser.password,
|
||||
},
|
||||
});
|
||||
|
||||
if (resp.status != 200) {
|
||||
throw Error("failed to create bank reserve");
|
||||
}
|
||||
|
||||
const withdrawUri = resp.data["taler_withdraw_uri"];
|
||||
if (!withdrawUri) {
|
||||
throw Error("Bank's response did not include withdraw URI");
|
||||
}
|
||||
return withdrawUri;
|
||||
}
|
||||
|
||||
async createReserve(
|
||||
bankUser: BankUser,
|
||||
amount: string,
|
||||
|
@ -29,6 +29,7 @@ export let STRING: Converter<string> = new Converter<string>();
|
||||
export interface OptionArgs<T> {
|
||||
help?: string;
|
||||
default?: T;
|
||||
onPresentHandler?: (v: T) => void;
|
||||
}
|
||||
|
||||
export interface ArgumentArgs<T> {
|
||||
@ -269,9 +270,6 @@ export class CommandGroup<GN extends keyof any, TG> {
|
||||
}
|
||||
|
||||
printHelp(progName: string, parents: CommandGroup<any, any>[]) {
|
||||
const chain: CommandGroup<any, any>[] = Array.prototype.concat(parents, [
|
||||
this,
|
||||
]);
|
||||
let usageSpec = "";
|
||||
for (let p of parents) {
|
||||
usageSpec += (p.name ?? progName) + " ";
|
||||
@ -352,6 +350,7 @@ export class CommandGroup<GN extends keyof any, TG> {
|
||||
process.exit(-1);
|
||||
throw Error("not reached");
|
||||
}
|
||||
foundOptions[d.name] = true;
|
||||
myArgs[d.name] = true;
|
||||
} else {
|
||||
if (r.value === undefined) {
|
||||
@ -380,6 +379,7 @@ export class CommandGroup<GN extends keyof any, TG> {
|
||||
}
|
||||
if (opt.isFlag) {
|
||||
myArgs[opt.name] = true;
|
||||
foundOptions[opt.name] = true;
|
||||
} else {
|
||||
if (si == optShort.length - 1) {
|
||||
if (i === unparsedArgs.length - 1) {
|
||||
@ -449,6 +449,13 @@ export class CommandGroup<GN extends keyof any, TG> {
|
||||
}
|
||||
}
|
||||
|
||||
for (let option of this.options) {
|
||||
const ph = option.args.onPresentHandler;
|
||||
if (ph && foundOptions[option.name]) {
|
||||
ph(myArgs[option.name]);
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedArgs[this.argKey].help) {
|
||||
this.printHelp(progname, parents);
|
||||
process.exit(-1);
|
||||
@ -546,7 +553,7 @@ export class Program<PN extends keyof any, T> {
|
||||
name: N,
|
||||
flagspec: string[],
|
||||
args: OptionArgs<boolean> = {},
|
||||
): Program<N, T & SubRecord<PN, N, boolean>> {
|
||||
): Program<PN, T & SubRecord<PN, N, boolean>> {
|
||||
this.mainCommand.flag(name, flagspec, args);
|
||||
return this as any;
|
||||
}
|
||||
|
@ -34,35 +34,30 @@ import { Bank } from "./bank";
|
||||
|
||||
import fs = require("fs");
|
||||
import { NodeCryptoWorkerFactory } from "../crypto/nodeProcessWorker";
|
||||
import { Logger } from "../logging";
|
||||
|
||||
const logger = new Logger("helpers.ts");
|
||||
|
||||
const enableTracing = false;
|
||||
|
||||
class ConsoleBadge implements Badge {
|
||||
startBusy(): void {
|
||||
enableTracing && console.log("NOTIFICATION: busy");
|
||||
}
|
||||
stopBusy(): void {
|
||||
enableTracing && console.log("NOTIFICATION: busy end");
|
||||
}
|
||||
showNotification(): void {
|
||||
enableTracing && console.log("NOTIFICATION: show");
|
||||
}
|
||||
clearNotification(): void {
|
||||
enableTracing && console.log("NOTIFICATION: cleared");
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeHttpLib implements HttpRequestLibrary {
|
||||
async get(url: string): Promise<import("../http").HttpResponse> {
|
||||
enableTracing && console.log("making GET request to", url);
|
||||
try {
|
||||
const resp = await Axios({
|
||||
method: "get",
|
||||
url: url,
|
||||
responseType: "json",
|
||||
});
|
||||
enableTracing && console.log("got response", resp.data);
|
||||
enableTracing && console.log("resp type", typeof resp.data);
|
||||
return {
|
||||
responseJson: resp.data,
|
||||
status: resp.status,
|
||||
@ -76,7 +71,6 @@ export class NodeHttpLib implements HttpRequestLibrary {
|
||||
url: string,
|
||||
body: any,
|
||||
): Promise<import("../http").HttpResponse> {
|
||||
enableTracing && console.log("making POST request to", url);
|
||||
try {
|
||||
const resp = await Axios({
|
||||
method: "post",
|
||||
@ -84,8 +78,6 @@ export class NodeHttpLib implements HttpRequestLibrary {
|
||||
responseType: "json",
|
||||
data: body,
|
||||
});
|
||||
enableTracing && console.log("got response", resp.data);
|
||||
enableTracing && console.log("resp type", typeof resp.data);
|
||||
return {
|
||||
responseJson: resp.data,
|
||||
status: resp.status,
|
||||
@ -149,7 +141,6 @@ export async function getDefaultNodeWallet(
|
||||
}
|
||||
|
||||
myBackend.afterCommitCallback = async () => {
|
||||
console.log("DATABASE COMMITTED");
|
||||
// Allow caller to stop persisting the wallet.
|
||||
if (args.persistentStoragePath === undefined) {
|
||||
return;
|
||||
@ -219,7 +210,7 @@ export async function withdrawTestBalance(
|
||||
|
||||
const bankUser = await bank.registerRandomUser();
|
||||
|
||||
console.log("bank user", bankUser);
|
||||
logger.trace(`Registered bank user ${JSON.stringify(bankUser)}`)
|
||||
|
||||
const exchangePaytoUri = await myWallet.getExchangePaytoUri(
|
||||
exchangeBaseUrl,
|
||||
@ -234,6 +225,5 @@ export async function withdrawTestBalance(
|
||||
);
|
||||
|
||||
await myWallet.confirmReserve({ reservePub: reserveResponse.reservePub });
|
||||
|
||||
await myWallet.runUntilReserveDepleted(reservePub);
|
||||
}
|
||||
|
@ -19,9 +19,9 @@
|
||||
* Used mostly for integration tests.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import axios from "axios";
|
||||
import { CheckPaymentResponse } from "../talerTypes";
|
||||
import URI = require("urijs");
|
||||
@ -30,10 +30,60 @@ import URI = require("urijs");
|
||||
* Connection to the *internal* merchant backend.
|
||||
*/
|
||||
export class MerchantBackendConnection {
|
||||
constructor(
|
||||
public merchantBaseUrl: string,
|
||||
public apiKey: string,
|
||||
) {}
|
||||
async refund(
|
||||
orderId: string,
|
||||
reason: string,
|
||||
refundAmount: string,
|
||||
): Promise<void> {
|
||||
const reqUrl = new URI("refund").absoluteTo(this.merchantBaseUrl).href();
|
||||
const refundReq = {
|
||||
order_id: orderId,
|
||||
reason,
|
||||
refund: refundAmount,
|
||||
};
|
||||
const resp = await axios({
|
||||
method: "post",
|
||||
url: reqUrl,
|
||||
data: refundReq,
|
||||
responseType: "json",
|
||||
headers: {
|
||||
Authorization: `ApiKey ${this.apiKey}`,
|
||||
},
|
||||
});
|
||||
if (resp.status != 200) {
|
||||
throw Error("failed to do refund");
|
||||
}
|
||||
console.log("response", resp.data);
|
||||
const refundUri = resp.data.taler_refund_uri;
|
||||
if (!refundUri) {
|
||||
throw Error("no refund URI in response");
|
||||
}
|
||||
return refundUri;
|
||||
}
|
||||
|
||||
constructor(public merchantBaseUrl: string, public apiKey: string) {}
|
||||
|
||||
async authorizeTip(amount: string, justification: string) {
|
||||
const reqUrl = new URI("tip-authorize").absoluteTo(this.merchantBaseUrl).href();
|
||||
const tipReq = {
|
||||
amount,
|
||||
justification,
|
||||
};
|
||||
const resp = await axios({
|
||||
method: "post",
|
||||
url: reqUrl,
|
||||
data: tipReq,
|
||||
responseType: "json",
|
||||
headers: {
|
||||
Authorization: `ApiKey ${this.apiKey}`,
|
||||
},
|
||||
});
|
||||
const tipUri = resp.data.taler_tip_uri;
|
||||
if (!tipUri) {
|
||||
throw Error("response does not contain tip URI");
|
||||
}
|
||||
return tipUri;
|
||||
}
|
||||
|
||||
async createOrder(
|
||||
amount: string,
|
||||
|
@ -26,11 +26,16 @@ import { BridgeIDBFactory, MemoryBackend } from "idb-bridge";
|
||||
import { Logger } from "../logging";
|
||||
import * as Amounts from "../amounts";
|
||||
import { decodeCrock } from "../crypto/talerCrypto";
|
||||
import { Bank } from "./bank";
|
||||
|
||||
const logger = new Logger("taler-wallet-cli.ts");
|
||||
|
||||
const walletDbPath = os.homedir + "/" + ".talerwalletdb.json";
|
||||
|
||||
function assertUnreachable(x: never): never {
|
||||
throw new Error("Didn't expect to get here");
|
||||
}
|
||||
|
||||
async function doPay(
|
||||
wallet: Wallet,
|
||||
payUrl: string,
|
||||
@ -78,7 +83,7 @@ async function doPay(
|
||||
}
|
||||
|
||||
if (pay) {
|
||||
const payRes = await wallet.confirmPay(result.proposalId!, undefined);
|
||||
const payRes = await wallet.confirmPay(result.proposalId, undefined);
|
||||
console.log("paid!");
|
||||
} else {
|
||||
console.log("not paying");
|
||||
@ -93,6 +98,12 @@ function applyVerbose(verbose: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
function printVersion() {
|
||||
const info = require("../../../package.json");
|
||||
console.log(`${info.version}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const walletCli = clk
|
||||
.program("wallet", {
|
||||
help: "Command line interface for the GNU Taler wallet.",
|
||||
@ -101,6 +112,9 @@ const walletCli = clk
|
||||
help:
|
||||
"Inhibit running certain operations, useful for debugging and testing.",
|
||||
})
|
||||
.flag("version", ["-v", "--version"], {
|
||||
onPresentHandler: printVersion,
|
||||
})
|
||||
.flag("verbose", ["-V", "--verbose"], {
|
||||
help: "Enable verbose output.",
|
||||
});
|
||||
@ -133,12 +147,21 @@ async function withWallet<T>(
|
||||
}
|
||||
|
||||
walletCli
|
||||
.subcommand("", "balance", { help: "Show wallet balance." })
|
||||
.subcommand("balance", "balance", { help: "Show wallet balance." })
|
||||
.flag("json", ["--json"], {
|
||||
help: "Show raw JSON.",
|
||||
})
|
||||
.action(async args => {
|
||||
console.log("balance command called");
|
||||
await withWallet(args, async wallet => {
|
||||
const balance = await wallet.getBalances();
|
||||
console.log(JSON.stringify(balance, undefined, 2));
|
||||
if (args.balance.json) {
|
||||
console.log(JSON.stringify(balance, undefined, 2));
|
||||
} else {
|
||||
const currencies = Object.keys(balance.byCurrency).sort();
|
||||
for (const c of currencies) {
|
||||
console.log(Amounts.toString(balance.byCurrency[c].available));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -205,15 +228,8 @@ walletCli
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
const { confirmTransferUrl } = await wallet.acceptWithdrawal(
|
||||
uri,
|
||||
selectedExchange,
|
||||
);
|
||||
if (confirmTransferUrl) {
|
||||
console.log("please confirm the transfer at", confirmTransferUrl);
|
||||
}
|
||||
} else {
|
||||
console.error("unrecognized URI");
|
||||
const res = await wallet.acceptWithdrawal(uri, selectedExchange);
|
||||
await wallet.processReserve(res.reservePub);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -258,13 +274,39 @@ const advancedCli = walletCli.subcommand("advancedArgs", "advanced", {
|
||||
|
||||
advancedCli
|
||||
.subcommand("decode", "decode", {
|
||||
help: "Decode base32-crockford",
|
||||
help: "Decode base32-crockford.",
|
||||
})
|
||||
.action(args => {
|
||||
const enc = fs.readFileSync(0, 'utf8');
|
||||
fs.writeFileSync(1, decodeCrock(enc.trim()))
|
||||
const enc = fs.readFileSync(0, "utf8");
|
||||
fs.writeFileSync(1, decodeCrock(enc.trim()));
|
||||
});
|
||||
|
||||
advancedCli
|
||||
.subcommand("payPrepare", "pay-prepare", {
|
||||
help: "Claim an order but don't pay yet.",
|
||||
})
|
||||
.requiredArgument("url", clk.STRING)
|
||||
.action(async args => {
|
||||
await withWallet(args, async wallet => {
|
||||
const res = await wallet.preparePay(args.payPrepare.url);
|
||||
switch (res.status) {
|
||||
case "error":
|
||||
console.log("error:", res.error);
|
||||
break;
|
||||
case "insufficient-balance":
|
||||
console.log("insufficient balance");
|
||||
break;
|
||||
case "paid":
|
||||
console.log("already paid");
|
||||
break;
|
||||
case "payment-possible":
|
||||
console.log("payment possible");
|
||||
break;
|
||||
default:
|
||||
assertUnreachable(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
advancedCli
|
||||
.subcommand("refresh", "force-refresh", {
|
||||
@ -288,7 +330,9 @@ advancedCli
|
||||
console.log(`coin ${coin.coinPub}`);
|
||||
console.log(` status ${coin.status}`);
|
||||
console.log(` exchange ${coin.exchangeBaseUrl}`);
|
||||
console.log(` remaining amount ${Amounts.toString(coin.currentAmount)}`);
|
||||
console.log(
|
||||
` remaining amount ${Amounts.toString(coin.currentAmount)}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -324,12 +368,11 @@ testCli
|
||||
return;
|
||||
}
|
||||
console.log("taler pay URI:", talerPayUri);
|
||||
await withWallet(args, async (wallet) => {
|
||||
await withWallet(args, async wallet => {
|
||||
await doPay(wallet, talerPayUri, { alwaysYes: true });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
testCli
|
||||
.subcommand("integrationtestCmd", "integrationtest", {
|
||||
help: "Run integration test with bank, exchange and merchant.",
|
||||
@ -377,7 +420,74 @@ testCli
|
||||
});
|
||||
|
||||
testCli
|
||||
.subcommand("testMerchantQrcodeCmd", "test-merchant-qrcode")
|
||||
.subcommand("genTipUri", "gen-tip-uri", {
|
||||
help: "Generate a taler://tip URI.",
|
||||
})
|
||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
||||
default: "TESTKUDOS:10",
|
||||
})
|
||||
.action(async args => {
|
||||
const merchantBackend = new MerchantBackendConnection(
|
||||
"https://backend.test.taler.net/",
|
||||
"sandbox",
|
||||
);
|
||||
const tipUri = await merchantBackend.authorizeTip("TESTKUDOS:10", "test");
|
||||
console.log(tipUri);
|
||||
});
|
||||
|
||||
testCli
|
||||
.subcommand("genRefundUri", "gen-refund-uri", {
|
||||
help: "Generate a taler://refund URI.",
|
||||
})
|
||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
||||
default: "TESTKUDOS:5",
|
||||
})
|
||||
.requiredOption("refundAmount", ["-r", "--refund"], clk.STRING, {
|
||||
default: "TESTKUDOS:3",
|
||||
})
|
||||
.requiredOption("summary", ["-s", "--summary"], clk.STRING, {
|
||||
default: "Test Payment (for refund)",
|
||||
})
|
||||
.action(async args => {
|
||||
const cmdArgs = args.genRefundUri;
|
||||
const merchantBackend = new MerchantBackendConnection(
|
||||
"https://backend.test.taler.net/",
|
||||
"sandbox",
|
||||
);
|
||||
const orderResp = await merchantBackend.createOrder(
|
||||
cmdArgs.amount,
|
||||
cmdArgs.summary,
|
||||
"",
|
||||
);
|
||||
console.log("created new order with order ID", orderResp.orderId);
|
||||
const checkPayResp = await merchantBackend.checkPayment(orderResp.orderId);
|
||||
const talerPayUri = checkPayResp.taler_pay_uri;
|
||||
if (!talerPayUri) {
|
||||
console.error("fatal: no taler pay URI received from backend");
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
await withWallet(args, async wallet => {
|
||||
await doPay(wallet, talerPayUri, { alwaysYes: true });
|
||||
});
|
||||
const refundUri = await merchantBackend.refund(
|
||||
orderResp.orderId,
|
||||
"test refund",
|
||||
cmdArgs.refundAmount,
|
||||
);
|
||||
console.log(refundUri);
|
||||
});
|
||||
|
||||
testCli
|
||||
.subcommand("genPayUri", "gen-pay-uri", {
|
||||
help: "Generate a taler://pay URI.",
|
||||
})
|
||||
.flag("qrcode", ["--qr"], {
|
||||
help: "Show a QR code with the taler://pay URI",
|
||||
})
|
||||
.flag("wait", ["--wait"], {
|
||||
help: "Wait until payment has completed",
|
||||
})
|
||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
||||
default: "TESTKUDOS:1",
|
||||
})
|
||||
@ -385,8 +495,7 @@ testCli
|
||||
default: "Test Payment",
|
||||
})
|
||||
.action(async args => {
|
||||
const cmdArgs = args.testMerchantQrcodeCmd;
|
||||
applyVerbose(args.wallet.verbose);
|
||||
const cmdArgs = args.genPayUri;
|
||||
console.log("creating order");
|
||||
const merchantBackend = new MerchantBackendConnection(
|
||||
"https://backend.test.taler.net/",
|
||||
@ -399,7 +508,6 @@ testCli
|
||||
);
|
||||
console.log("created new order with order ID", orderResp.orderId);
|
||||
const checkPayResp = await merchantBackend.checkPayment(orderResp.orderId);
|
||||
const qrcode = qrcodeGenerator(0, "M");
|
||||
const talerPayUri = checkPayResp.taler_pay_uri;
|
||||
if (!talerPayUri) {
|
||||
console.error("fatal: no taler pay URI received from backend");
|
||||
@ -407,18 +515,23 @@ testCli
|
||||
return;
|
||||
}
|
||||
console.log("taler pay URI:", talerPayUri);
|
||||
qrcode.addData(talerPayUri);
|
||||
qrcode.make();
|
||||
console.log(qrcode.createASCII());
|
||||
console.log("waiting for payment ...");
|
||||
while (1) {
|
||||
await asyncSleep(500);
|
||||
const checkPayResp2 = await merchantBackend.checkPayment(
|
||||
orderResp.orderId,
|
||||
);
|
||||
if (checkPayResp2.paid) {
|
||||
console.log("payment successfully received!");
|
||||
break;
|
||||
if (cmdArgs.qrcode) {
|
||||
const qrcode = qrcodeGenerator(0, "M");
|
||||
qrcode.addData(talerPayUri);
|
||||
qrcode.make();
|
||||
console.log(qrcode.createASCII());
|
||||
}
|
||||
if (cmdArgs.wait) {
|
||||
console.log("waiting for payment ...");
|
||||
while (1) {
|
||||
await asyncSleep(500);
|
||||
const checkPayResp2 = await merchantBackend.checkPayment(
|
||||
orderResp.orderId,
|
||||
);
|
||||
if (checkPayResp2.paid) {
|
||||
console.log("payment successfully received!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -47,6 +47,8 @@ function fakeCwd(current: string, value: string, feeDeposit: string): types.Coin
|
||||
denomSig: "(mock)",
|
||||
exchangeBaseUrl: "(mock)",
|
||||
reservePub: "(mock)",
|
||||
coinIndex: -1,
|
||||
withdrawSessionId: "",
|
||||
status: dbTypes.CoinStatus.Fresh,
|
||||
},
|
||||
denom: {
|
||||
|
881
src/wallet.ts
881
src/wallet.ts
File diff suppressed because it is too large
Load Diff
@ -465,14 +465,14 @@ export type PreparePayResult =
|
||||
|
||||
export interface PreparePayResultPaymentPossible {
|
||||
status: "payment-possible";
|
||||
proposalId: number;
|
||||
proposalId: string;
|
||||
contractTerms: ContractTerms;
|
||||
totalFees: AmountJson;
|
||||
}
|
||||
|
||||
export interface PreparePayResultInsufficientBalance {
|
||||
status: "insufficient-balance";
|
||||
proposalId: number;
|
||||
proposalId: string;
|
||||
contractTerms: ContractTerms;
|
||||
}
|
||||
|
||||
@ -523,8 +523,10 @@ export interface WalletDiagnostics {
|
||||
|
||||
export interface PendingWithdrawOperation {
|
||||
type: "withdraw";
|
||||
stage: string;
|
||||
reservePub: string;
|
||||
withdrawSessionId: string;
|
||||
numCoinsWithdrawn: number;
|
||||
numCoinsTotal: number;
|
||||
}
|
||||
|
||||
export interface PendingRefreshOperation {
|
||||
@ -561,22 +563,47 @@ export interface PendingReserveOperation {
|
||||
stage: string;
|
||||
timestampCreated: Timestamp;
|
||||
reserveType: string;
|
||||
reservePub: string;
|
||||
bankWithdrawConfirmUrl?: string;
|
||||
}
|
||||
|
||||
export interface PendingRefreshOperation {
|
||||
type: "refresh";
|
||||
lastError?: OperationError;
|
||||
refreshSessionId: string;
|
||||
oldCoinPub: string;
|
||||
refreshStatus: string;
|
||||
refreshOutputSize: number;
|
||||
}
|
||||
|
||||
export interface PendingPlanchetOperation {
|
||||
type: "planchet";
|
||||
coinPub: string;
|
||||
reservePub: string;
|
||||
lastError?: OperationError;
|
||||
}
|
||||
|
||||
export interface PendingDirtyCoinOperation {
|
||||
type: "dirty-coin";
|
||||
coinPub: string;
|
||||
}
|
||||
|
||||
export interface PendingProposalOperation {
|
||||
type: "proposal";
|
||||
merchantBaseUrl: string;
|
||||
proposalTimestamp: Timestamp;
|
||||
proposalId: string;
|
||||
}
|
||||
|
||||
export type PendingOperationInfo =
|
||||
| PendingWithdrawOperation
|
||||
| PendingReserveOperation
|
||||
| PendingBugOperation
|
||||
| PendingPlanchetOperation
|
||||
| PendingDirtyCoinOperation
|
||||
| PendingExchangeUpdateOperation
|
||||
| PendingRefreshOperation;
|
||||
| PendingRefreshOperation
|
||||
| PendingProposalOperation;
|
||||
|
||||
export interface PendingOperationsResponse {
|
||||
pendingOperations: PendingOperationInfo[];
|
||||
@ -614,3 +641,17 @@ export function getTimestampNow(): Timestamp {
|
||||
t_ms: new Date().getTime(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface PlanchetCreationResult {
|
||||
coinPub: string;
|
||||
coinPriv: string;
|
||||
reservePub: string;
|
||||
denomPubHash: string;
|
||||
denomPub: string;
|
||||
blindingKey: string;
|
||||
withdrawSig: string;
|
||||
coinEv: string;
|
||||
exchangeBaseUrl: string;
|
||||
coinValue: AmountJson;
|
||||
}
|
@ -66,7 +66,7 @@ export interface MessageMap {
|
||||
response: void;
|
||||
};
|
||||
"confirm-pay": {
|
||||
request: { proposalId: number; sessionId?: string };
|
||||
request: { proposalId: string; sessionId?: string };
|
||||
response: walletTypes.ConfirmPayResult;
|
||||
};
|
||||
"exchange-info": {
|
||||
@ -113,9 +113,9 @@ export interface MessageMap {
|
||||
request: { reservePub: string };
|
||||
response: dbTypes.ReserveRecord[];
|
||||
};
|
||||
"get-precoins": {
|
||||
"get-planchets": {
|
||||
request: { exchangeBaseUrl: string };
|
||||
response: dbTypes.PreCoinRecord[];
|
||||
response: dbTypes.PlanchetRecord[];
|
||||
};
|
||||
"get-denoms": {
|
||||
request: { exchangeBaseUrl: string };
|
||||
|
@ -57,7 +57,7 @@ function Payback() {
|
||||
<div>
|
||||
{reserves.map(r => (
|
||||
<div>
|
||||
<h2>Reserve for ${renderAmount(r.currentAmount!)}</h2>
|
||||
<h2>Reserve for ${renderAmount(r.withdrawRemainingAmount)}</h2>
|
||||
<ul>
|
||||
<li>Exchange: ${r.exchangeBaseUrl}</li>
|
||||
</ul>
|
||||
|
@ -28,7 +28,7 @@ import {
|
||||
CurrencyRecord,
|
||||
DenominationRecord,
|
||||
ExchangeRecord,
|
||||
PreCoinRecord,
|
||||
PlanchetRecord,
|
||||
ReserveRecord,
|
||||
} from "../dbTypes";
|
||||
import {
|
||||
@ -174,10 +174,10 @@ export function getCoins(exchangeBaseUrl: string): Promise<CoinRecord[]> {
|
||||
|
||||
|
||||
/**
|
||||
* Get all precoins withdrawn from the given exchange.
|
||||
* Get all planchets withdrawn from the given exchange.
|
||||
*/
|
||||
export function getPreCoins(exchangeBaseUrl: string): Promise<PreCoinRecord[]> {
|
||||
return callBackend("get-precoins", { exchangeBaseUrl });
|
||||
export function getPlanchets(exchangeBaseUrl: string): Promise<PlanchetRecord[]> {
|
||||
return callBackend("get-planchets", { exchangeBaseUrl });
|
||||
}
|
||||
|
||||
|
||||
@ -207,7 +207,7 @@ export function payback(coinPub: string): Promise<void> {
|
||||
/**
|
||||
* Pay for a proposal.
|
||||
*/
|
||||
export function confirmPay(proposalId: number, sessionId: string | undefined): Promise<ConfirmPayResult> {
|
||||
export function confirmPay(proposalId: string, sessionId: string | undefined): Promise<ConfirmPayResult> {
|
||||
return callBackend("confirm-pay", { proposalId, sessionId });
|
||||
}
|
||||
|
||||
|
@ -117,8 +117,8 @@ async function handleMessage(
|
||||
return needsWallet().confirmReserve(req);
|
||||
}
|
||||
case "confirm-pay": {
|
||||
if (typeof detail.proposalId !== "number") {
|
||||
throw Error("proposalId must be number");
|
||||
if (typeof detail.proposalId !== "string") {
|
||||
throw Error("proposalId must be string");
|
||||
}
|
||||
return needsWallet().confirmPay(detail.proposalId, detail.sessionId);
|
||||
}
|
||||
@ -178,11 +178,11 @@ async function handleMessage(
|
||||
}
|
||||
return needsWallet().getCoinsForExchange(detail.exchangeBaseUrl);
|
||||
}
|
||||
case "get-precoins": {
|
||||
case "get-planchets": {
|
||||
if (typeof detail.exchangeBaseUrl !== "string") {
|
||||
return Promise.reject(Error("exchangBaseUrl missing"));
|
||||
}
|
||||
return needsWallet().getPreCoins(detail.exchangeBaseUrl);
|
||||
return needsWallet().getPlanchets(detail.exchangeBaseUrl);
|
||||
}
|
||||
case "get-denoms": {
|
||||
if (typeof detail.exchangeBaseUrl !== "string") {
|
||||
@ -658,8 +658,8 @@ export async function wxMain() {
|
||||
if (!wallet) {
|
||||
console.warn("wallet not available while handling header");
|
||||
}
|
||||
if (details.statusCode === 402) {
|
||||
console.log(`got 402 from ${details.url}`);
|
||||
if (details.statusCode === 402 || details.statusCode === 202) {
|
||||
console.log(`got 402/202 from ${details.url}`);
|
||||
for (let header of details.responseHeaders || []) {
|
||||
if (header.name.toLowerCase() === "taler") {
|
||||
const talerUri = header.value || "";
|
||||
@ -705,6 +705,15 @@ export async function wxMain() {
|
||||
talerRefundUri: talerUri,
|
||||
},
|
||||
);
|
||||
} else if (talerUri.startsWith("taler://notify-reserve/")) {
|
||||
Promise.resolve().then(() => {
|
||||
const w = currentWallet;
|
||||
if (!w) {
|
||||
return;
|
||||
}
|
||||
w.handleNotifyReserve();
|
||||
});
|
||||
|
||||
} else {
|
||||
console.warn("Unknown action in taler:// URI, ignoring.");
|
||||
}
|
||||
|
@ -3417,10 +3417,10 @@ iconv-lite@0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
idb-bridge@^0.0.14:
|
||||
version "0.0.14"
|
||||
resolved "https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.14.tgz#5fd50cd68b574df0eb6b1a960cef0cb984a21ded"
|
||||
integrity sha512-jc9ZYGhhIrW6nh/pWyycGWzCmsLTFQ0iMY61lN+y9YcIOCxREpAkZxdfmhwNL7H0RvsYp7iJv0GH7ujs7HPC+g==
|
||||
idb-bridge@^0.0.15:
|
||||
version "0.0.15"
|
||||
resolved "https://registry.yarnpkg.com/idb-bridge/-/idb-bridge-0.0.15.tgz#3fddc91b9aab775fae273d02b272205c6090d270"
|
||||
integrity sha512-xuZM/i4vCm/NkqyrKNJDEuBaZK7M2kyj+1F4hDGqtEJZSmQMSV3v9A6Ie3fR12VXDKIbMr7uV22eWjIKwSosOA==
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.13"
|
||||
|
Loading…
Reference in New Issue
Block a user