automatic refresh
This commit is contained in:
parent
6262af4ad7
commit
8c0c4b5331
@ -282,7 +282,9 @@ namespace TalerNotify {
|
|||||||
addHandler("taler-payment-failed", (msg: any, sendResponse: any) => {
|
addHandler("taler-payment-failed", (msg: any, sendResponse: any) => {
|
||||||
const walletMsg = {
|
const walletMsg = {
|
||||||
type: "payment-failed",
|
type: "payment-failed",
|
||||||
detail: {},
|
detail: {
|
||||||
|
contractHash: msg.H_contract
|
||||||
|
},
|
||||||
};
|
};
|
||||||
chrome.runtime.sendMessage(walletMsg, (resp) => {
|
chrome.runtime.sendMessage(walletMsg, (resp) => {
|
||||||
sendResponse();
|
sendResponse();
|
||||||
@ -290,8 +292,20 @@ namespace TalerNotify {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addHandler("taler-payment-succeeded", (msg: any, sendResponse: any) => {
|
addHandler("taler-payment-succeeded", (msg: any, sendResponse: any) => {
|
||||||
|
if (!msg.H_contract) {
|
||||||
|
console.error("H_contract missing in taler-payment-succeeded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log("got taler-payment-succeeded");
|
console.log("got taler-payment-succeeded");
|
||||||
sendResponse();
|
const walletMsg = {
|
||||||
|
type: "payment-succeeded",
|
||||||
|
detail: {
|
||||||
|
contractHash: msg.H_contract,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
chrome.runtime.sendMessage(walletMsg, (resp) => {
|
||||||
|
sendResponse();
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
addHandler("taler-get-payment", (msg: any, sendResponse: any) => {
|
addHandler("taler-get-payment", (msg: any, sendResponse: any) => {
|
||||||
|
23
gulpfile.js
23
gulpfile.js
@ -58,6 +58,9 @@ const paths = {
|
|||||||
"pages/*.{ts,tsx}",
|
"pages/*.{ts,tsx}",
|
||||||
"!**/*.d.ts",
|
"!**/*.d.ts",
|
||||||
],
|
],
|
||||||
|
decl: [
|
||||||
|
"lib/refs.d.ts",
|
||||||
|
],
|
||||||
dev: [
|
dev: [
|
||||||
"test/tests/*.{ts,tsx}",
|
"test/tests/*.{ts,tsx}",
|
||||||
],
|
],
|
||||||
@ -73,10 +76,10 @@ const paths = {
|
|||||||
"lib/module-trampoline.js",
|
"lib/module-trampoline.js",
|
||||||
"popup/**/*.{html,css}",
|
"popup/**/*.{html,css}",
|
||||||
"pages/**/*.{html,css}",
|
"pages/**/*.{html,css}",
|
||||||
"lib/**/*.d.ts",
|
|
||||||
"background/*.html",
|
"background/*.html",
|
||||||
],
|
],
|
||||||
extra: [
|
extra: [
|
||||||
|
"lib/**/*.d.ts",
|
||||||
"AUTHORS",
|
"AUTHORS",
|
||||||
"README",
|
"README",
|
||||||
"COPYING",
|
"COPYING",
|
||||||
@ -220,7 +223,12 @@ gulp.task("compile-prod", ["clean"], function () {
|
|||||||
tsArgs.outDir = ".";
|
tsArgs.outDir = ".";
|
||||||
// We don't want source maps for production
|
// We don't want source maps for production
|
||||||
tsArgs.sourceMap = undefined;
|
tsArgs.sourceMap = undefined;
|
||||||
return gulp.src(paths.ts.release, {base: "."})
|
let opts = {base: "."};
|
||||||
|
const files = concatStreams(
|
||||||
|
gulp.src(paths.ts.release, opts),
|
||||||
|
gulp.src(paths.ts.decl, opts));
|
||||||
|
|
||||||
|
return files
|
||||||
.pipe(ts(tsArgs))
|
.pipe(ts(tsArgs))
|
||||||
.pipe(gulp.dest("build/ext/"));
|
.pipe(gulp.dest("build/ext/"));
|
||||||
});
|
});
|
||||||
@ -274,6 +282,7 @@ gulp.task("srcdist", [], function () {
|
|||||||
// We can't just concat patterns due to exclude patterns
|
// We can't just concat patterns due to exclude patterns
|
||||||
const files = concatStreams(
|
const files = concatStreams(
|
||||||
gulp.src(paths.ts.release, opts),
|
gulp.src(paths.ts.release, opts),
|
||||||
|
gulp.src(paths.ts.decl, opts),
|
||||||
gulp.src(paths.ts.dev, opts),
|
gulp.src(paths.ts.dev, opts),
|
||||||
gulp.src(paths.dist, opts),
|
gulp.src(paths.dist, opts),
|
||||||
gulp.src(paths.extra, opts));
|
gulp.src(paths.extra, opts));
|
||||||
@ -346,9 +355,13 @@ function tsconfig(confBase) {
|
|||||||
// Generate the tsconfig file
|
// Generate the tsconfig file
|
||||||
// that should be used during development.
|
// that should be used during development.
|
||||||
gulp.task("tsconfig", function() {
|
gulp.task("tsconfig", function() {
|
||||||
return gulp.src(Array.prototype.concat(paths.ts.release, paths.ts.dev), {base: "."})
|
let opts = {base: "."};
|
||||||
.pipe(tsconfig(tsBaseArgs))
|
const files = concatStreams(
|
||||||
.pipe(gulp.dest("."));
|
gulp.src(paths.ts.release, opts),
|
||||||
|
gulp.src(paths.ts.dev, opts),
|
||||||
|
gulp.src(paths.ts.decl, opts));
|
||||||
|
return files.pipe(tsconfig(tsBaseArgs))
|
||||||
|
.pipe(gulp.dest("."));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
|
|
||||||
// Help the TypeScript compiler find declarations.
|
|
||||||
|
|
||||||
/// <reference path="decl/lib.es6.d.ts" />
|
|
||||||
/// <reference path="decl/urijs/URIjs.d.ts" />
|
|
||||||
/// <reference path="decl/systemjs/systemjs.d.ts" />
|
|
@ -203,6 +203,8 @@ namespace RpcFunctions {
|
|||||||
let newAmount = new native.Amount(cd.coin.currentAmount);
|
let newAmount = new native.Amount(cd.coin.currentAmount);
|
||||||
newAmount.sub(coinSpend);
|
newAmount.sub(coinSpend);
|
||||||
cd.coin.currentAmount = newAmount.toJson();
|
cd.coin.currentAmount = newAmount.toJson();
|
||||||
|
cd.coin.dirty = true;
|
||||||
|
cd.coin.transactionPending = true;
|
||||||
|
|
||||||
let d = new native.DepositRequestPS({
|
let d = new native.DepositRequestPS({
|
||||||
h_contract: native.HashCode.fromCrock(offer.H_contract),
|
h_contract: native.HashCode.fromCrock(offer.H_contract),
|
||||||
@ -338,4 +340,8 @@ namespace RpcFunctions {
|
|||||||
return refreshSession;
|
return refreshSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hashString(str: string): string {
|
||||||
|
const b = native.ByteArray.fromStringWithNull(str);
|
||||||
|
return b.hash().toCrock();
|
||||||
|
}
|
||||||
}
|
}
|
@ -25,7 +25,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const DB_NAME = "taler";
|
const DB_NAME = "taler";
|
||||||
const DB_VERSION = 8;
|
const DB_VERSION = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a promise that resolves
|
* Return a promise that resolves
|
||||||
@ -59,14 +59,15 @@ export function openTalerDb(): Promise<IDBDatabase> {
|
|||||||
"contract.repurchase_correlation_id"
|
"contract.repurchase_correlation_id"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
db.createObjectStore("precoins",
|
db.createObjectStore("precoins", {keyPath: "coinPub"});
|
||||||
{keyPath: "coinPub", autoIncrement: true});
|
|
||||||
const history = db.createObjectStore("history",
|
const history = db.createObjectStore("history",
|
||||||
{
|
{
|
||||||
keyPath: "id",
|
keyPath: "id",
|
||||||
autoIncrement: true
|
autoIncrement: true
|
||||||
});
|
});
|
||||||
history.createIndex("timestamp", "timestamp");
|
history.createIndex("timestamp", "timestamp");
|
||||||
|
db.createObjectStore("refresh",
|
||||||
|
{keyPath: "meltCoinPub"});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (e.oldVersion != DB_VERSION) {
|
if (e.oldVersion != DB_VERSION) {
|
||||||
|
@ -279,6 +279,19 @@ export interface Coin {
|
|||||||
* to fix it.
|
* to fix it.
|
||||||
*/
|
*/
|
||||||
suspended?: boolean;
|
suspended?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Was the coin revealed in a transaction?
|
||||||
|
*/
|
||||||
|
dirty: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the coin currently involved in a transaction?
|
||||||
|
*
|
||||||
|
* This delays refreshing until the transaction is finished or
|
||||||
|
* aborted.
|
||||||
|
*/
|
||||||
|
transactionPending: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import {
|
|||||||
IExchangeInfo,
|
IExchangeInfo,
|
||||||
Denomination,
|
Denomination,
|
||||||
Notifier,
|
Notifier,
|
||||||
WireInfo, RefreshSession, ReserveRecord
|
WireInfo, RefreshSession, ReserveRecord, CoinPaySig
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import {HttpResponse, RequestException} from "./http";
|
import {HttpResponse, RequestException} from "./http";
|
||||||
import {QueryRoot} from "./query";
|
import {QueryRoot} from "./query";
|
||||||
@ -135,11 +135,22 @@ interface ExchangeCoins {
|
|||||||
[exchangeUrl: string]: CoinWithDenom[];
|
[exchangeUrl: string]: CoinWithDenom[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PayReq {
|
||||||
|
amount: AmountJson;
|
||||||
|
coins: CoinPaySig[];
|
||||||
|
H_contract: string;
|
||||||
|
max_fee: AmountJson;
|
||||||
|
merchant_sig: string;
|
||||||
|
exchange: string;
|
||||||
|
refund_deadline: string;
|
||||||
|
timestamp: string;
|
||||||
|
transaction_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface Transaction {
|
interface Transaction {
|
||||||
contractHash: string;
|
contractHash: string;
|
||||||
contract: Contract;
|
contract: Contract;
|
||||||
payReq: any;
|
payReq: PayReq;
|
||||||
merchantSig: string;
|
merchantSig: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,16 +503,17 @@ export class Wallet {
|
|||||||
private async recordConfirmPay(offer: Offer,
|
private async recordConfirmPay(offer: Offer,
|
||||||
payCoinInfo: PayCoinInfo,
|
payCoinInfo: PayCoinInfo,
|
||||||
chosenExchange: string): Promise<void> {
|
chosenExchange: string): Promise<void> {
|
||||||
let payReq: any = {};
|
let payReq: PayReq = {
|
||||||
payReq["amount"] = offer.contract.amount;
|
amount: offer.contract.amount,
|
||||||
payReq["coins"] = payCoinInfo.map((x) => x.sig);
|
coins: payCoinInfo.map((x) => x.sig),
|
||||||
payReq["H_contract"] = offer.H_contract;
|
H_contract: offer.H_contract,
|
||||||
payReq["max_fee"] = offer.contract.max_fee;
|
max_fee: offer.contract.max_fee,
|
||||||
payReq["merchant_sig"] = offer.merchant_sig;
|
merchant_sig: offer.merchant_sig,
|
||||||
payReq["exchange"] = URI(chosenExchange).href();
|
exchange: URI(chosenExchange).href(),
|
||||||
payReq["refund_deadline"] = offer.contract.refund_deadline;
|
refund_deadline: offer.contract.refund_deadline,
|
||||||
payReq["timestamp"] = offer.contract.timestamp;
|
timestamp: offer.contract.timestamp,
|
||||||
payReq["transaction_id"] = offer.contract.transaction_id;
|
transaction_id: offer.contract.transaction_id,
|
||||||
|
};
|
||||||
let t: Transaction = {
|
let t: Transaction = {
|
||||||
contractHash: offer.H_contract,
|
contractHash: offer.H_contract,
|
||||||
contract: offer.contract,
|
contract: offer.contract,
|
||||||
@ -792,6 +804,8 @@ export class Wallet {
|
|||||||
denomSig: denomSig,
|
denomSig: denomSig,
|
||||||
currentAmount: pc.coinValue,
|
currentAmount: pc.coinValue,
|
||||||
exchangeBaseUrl: pc.exchangeBaseUrl,
|
exchangeBaseUrl: pc.exchangeBaseUrl,
|
||||||
|
dirty: false,
|
||||||
|
transactionPending: false,
|
||||||
};
|
};
|
||||||
return coin;
|
return coin;
|
||||||
}
|
}
|
||||||
@ -1127,18 +1141,53 @@ export class Wallet {
|
|||||||
let newCoinDenoms = getWithdrawDenomList(availableAmount,
|
let newCoinDenoms = getWithdrawDenomList(availableAmount,
|
||||||
availableDenoms);
|
availableDenoms);
|
||||||
|
|
||||||
newCoinDenoms = [newCoinDenoms[0]];
|
|
||||||
console.log("refreshing into", newCoinDenoms);
|
console.log("refreshing into", newCoinDenoms);
|
||||||
|
|
||||||
|
if (newCoinDenoms.length == 0) {
|
||||||
|
console.log("not refreshing, value too small");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let refreshSession: RefreshSession = await (
|
let refreshSession: RefreshSession = await (
|
||||||
this.cryptoApi.createRefreshSession(exchange.baseUrl,
|
this.cryptoApi.createRefreshSession(exchange.baseUrl,
|
||||||
3,
|
3,
|
||||||
coin,
|
coin,
|
||||||
newCoinDenoms,
|
newCoinDenoms,
|
||||||
oldDenom.fee_refresh));
|
oldDenom.fee_refresh));
|
||||||
|
|
||||||
let reqUrl = URI("refresh/melt").absoluteTo(exchange!.baseUrl);
|
coin.currentAmount = Amounts.sub(coin.currentAmount,
|
||||||
|
refreshSession.valueWithFee).amount;
|
||||||
|
|
||||||
|
// FIXME: we should check whether the amount still matches!
|
||||||
|
await this.q()
|
||||||
|
.put("refresh", refreshSession)
|
||||||
|
.put("coins", coin)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
await this.refreshMelt(refreshSession);
|
||||||
|
|
||||||
|
let r = await this.q().get<RefreshSession>("refresh", oldCoinPub);
|
||||||
|
if (!r) {
|
||||||
|
throw Error("refresh session does not exist anymore");
|
||||||
|
}
|
||||||
|
await this.refreshReveal(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async refreshMelt(refreshSession: RefreshSession): Promise<void> {
|
||||||
|
if (refreshSession.norevealIndex != undefined) {
|
||||||
|
console.error("won't melt again");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let coin = await this.q().get<Coin>("coins", refreshSession.meltCoinPub);
|
||||||
|
if (!coin) {
|
||||||
|
console.error("can't melt coin, it does not exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reqUrl = URI("refresh/melt").absoluteTo(refreshSession.exchangeBaseUrl);
|
||||||
let meltCoin = {
|
let meltCoin = {
|
||||||
coin_pub: coin.coinPub,
|
coin_pub: coin.coinPub,
|
||||||
denom_pub: coin.denomPub,
|
denom_pub: coin.denomPub,
|
||||||
@ -1148,7 +1197,7 @@ export class Wallet {
|
|||||||
};
|
};
|
||||||
let coinEvs = refreshSession.preCoinsForGammas.map((x) => x.map((y) => y.coinEv));
|
let coinEvs = refreshSession.preCoinsForGammas.map((x) => x.map((y) => y.coinEv));
|
||||||
let req = {
|
let req = {
|
||||||
"new_denoms": newCoinDenoms.map((d) => d.denom_pub),
|
"new_denoms": refreshSession.newDenoms,
|
||||||
"melt_coin": meltCoin,
|
"melt_coin": meltCoin,
|
||||||
"transfer_pubs": refreshSession.transferPubs,
|
"transfer_pubs": refreshSession.transferPubs,
|
||||||
"coin_evs": coinEvs,
|
"coin_evs": coinEvs,
|
||||||
@ -1178,11 +1227,10 @@ export class Wallet {
|
|||||||
|
|
||||||
refreshSession.norevealIndex = norevealIndex;
|
refreshSession.norevealIndex = norevealIndex;
|
||||||
|
|
||||||
this.refreshReveal(refreshSession);
|
await this.q().put("refresh", refreshSession).finish();
|
||||||
|
|
||||||
// FIXME: implement rest
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async refreshReveal(refreshSession: RefreshSession): Promise<void> {
|
async refreshReveal(refreshSession: RefreshSession): Promise<void> {
|
||||||
let norevealIndex = refreshSession.norevealIndex;
|
let norevealIndex = refreshSession.norevealIndex;
|
||||||
if (norevealIndex == undefined) {
|
if (norevealIndex == undefined) {
|
||||||
@ -1196,12 +1244,54 @@ export class Wallet {
|
|||||||
"transfer_privs": privs,
|
"transfer_privs": privs,
|
||||||
};
|
};
|
||||||
|
|
||||||
let reqUrl = URI("refresh/reveal").absoluteTo(refreshSession.exchangeBaseUrl);
|
let reqUrl = URI("refresh/reveal")
|
||||||
|
.absoluteTo(refreshSession.exchangeBaseUrl);
|
||||||
console.log("reveal request:", req);
|
console.log("reveal request:", req);
|
||||||
let resp = await this.http.postJson(reqUrl, req);
|
let resp = await this.http.postJson(reqUrl, req);
|
||||||
|
|
||||||
console.log("session:", refreshSession);
|
console.log("session:", refreshSession);
|
||||||
console.log("reveal response:", resp);
|
console.log("reveal response:", resp);
|
||||||
|
|
||||||
|
if (resp.status != 200) {
|
||||||
|
console.log("error: /refresh/reveal returned status " + resp.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let respJson = JSON.parse(resp.responseText);
|
||||||
|
|
||||||
|
if (!respJson.ev_sigs || !Array.isArray(respJson.ev_sigs)) {
|
||||||
|
console.log("/refresh/reveal did not contain ev_sigs");
|
||||||
|
}
|
||||||
|
|
||||||
|
let exchange = await this.q().get<IExchangeInfo>("exchanges", refreshSession.exchangeBaseUrl);
|
||||||
|
if (!exchange) {
|
||||||
|
console.error(`exchange ${refreshSession.exchangeBaseUrl} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < respJson.ev_sigs.length; i++) {
|
||||||
|
let denom = exchange.all_denoms.find((d) => d.denom_pub == refreshSession.newDenoms[i]);
|
||||||
|
if (!denom) {
|
||||||
|
console.error("denom not found");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let pc = refreshSession.preCoinsForGammas[refreshSession.norevealIndex!][i];
|
||||||
|
let denomSig = await this.cryptoApi.rsaUnblind(respJson.ev_sigs[i].ev_sig,
|
||||||
|
pc.blindingKey,
|
||||||
|
denom.denom_pub);
|
||||||
|
let coin: Coin = {
|
||||||
|
coinPub: pc.publicKey,
|
||||||
|
coinPriv: pc.privateKey,
|
||||||
|
denomPub: denom.denom_pub,
|
||||||
|
denomSig: denomSig,
|
||||||
|
currentAmount: denom.value,
|
||||||
|
exchangeBaseUrl: refreshSession.exchangeBaseUrl,
|
||||||
|
dirty: false,
|
||||||
|
transactionPending: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.q().put("coins", coin).finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1283,4 +1373,29 @@ export class Wallet {
|
|||||||
return {isRepurchase: false};
|
return {isRepurchase: false};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async paymentSucceeded(contractHash: string): Promise<any> {
|
||||||
|
const doPaymentSucceeded = async () => {
|
||||||
|
let t = await this.q().get<Transaction>("transactions", contractHash);
|
||||||
|
if (!t) {
|
||||||
|
console.error("contract not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let pc of t.payReq.coins) {
|
||||||
|
let c = await this.q().get<Coin>("coins", pc.coin_pub);
|
||||||
|
if (!c) {
|
||||||
|
console.error("coin not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
c.transactionPending = false;
|
||||||
|
await this.q().put("coins", c).finish();
|
||||||
|
}
|
||||||
|
for (let c of t.payReq.coins) {
|
||||||
|
this.refresh(c.coin_pub);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
doPaymentSucceeded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,6 +218,13 @@ function makeHandlers(db: IDBDatabase,
|
|||||||
wallet.updateExchanges();
|
wallet.updateExchanges();
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
|
["payment-succeeded"]: function (detail, sender) {
|
||||||
|
let contractHash = detail.contractHash;
|
||||||
|
if (!contractHash) {
|
||||||
|
return Promise.reject(Error("contractHash missing"));
|
||||||
|
}
|
||||||
|
return wallet.paymentSucceeded(contractHash);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,8 +13,10 @@
|
|||||||
"noImplicitAny": true
|
"noImplicitAny": true
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
"lib/components.ts",
|
||||||
|
"test/tests/taler.ts",
|
||||||
|
"lib/refs.d.ts",
|
||||||
"lib/i18n.ts",
|
"lib/i18n.ts",
|
||||||
"lib/refs.ts",
|
|
||||||
"lib/shopApi.ts",
|
"lib/shopApi.ts",
|
||||||
"lib/taler-wallet-lib.ts",
|
"lib/taler-wallet-lib.ts",
|
||||||
"lib/wallet/checkable.ts",
|
"lib/wallet/checkable.ts",
|
||||||
@ -38,7 +40,6 @@
|
|||||||
"pages/show-db.ts",
|
"pages/show-db.ts",
|
||||||
"pages/confirm-contract.tsx",
|
"pages/confirm-contract.tsx",
|
||||||
"pages/confirm-create-reserve.tsx",
|
"pages/confirm-create-reserve.tsx",
|
||||||
"pages/tree.tsx",
|
"pages/tree.tsx"
|
||||||
"test/tests/taler.ts"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit e9eb34dfb56c8e5a18fa7d555aa651a4532e8f3c
|
Subproject commit 02271055cbe9e62cffb3713d109a77015df625d1
|
Loading…
Reference in New Issue
Block a user