aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-09-09 21:18:51 +0200
committerFlorian Dold <florian@dold.me>2024-09-09 21:18:54 +0200
commit049b369f745edb5f8a2d7ec57d5af98ca81b05fa (patch)
tree285eb6d4de04712f52c9473b1e9cc15854dc5db7
parenta5e24b8df44d28f5868f1f4279d54a34001c553f (diff)
wallet-core: move getMaxDepositAmount to coinSelection.ts
The function is now using the normal wallet-core coin selection infrastructure, leading to automatic support for credit/debit restrictions and less code duplication.
-rw-r--r--packages/taler-util/src/types-taler-merchant.ts46
-rw-r--r--packages/taler-util/src/types-taler-wallet.ts2
-rw-r--r--packages/taler-util/src/types.test.ts12
-rw-r--r--packages/taler-wallet-core/src/coinSelection.test.ts168
-rw-r--r--packages/taler-wallet-core/src/coinSelection.ts121
-rw-r--r--packages/taler-wallet-core/src/deposits.ts15
-rw-r--r--packages/taler-wallet-core/src/instructedAmountConversion.test.ts143
-rw-r--r--packages/taler-wallet-core/src/instructedAmountConversion.ts42
-rw-r--r--packages/taler-wallet-core/src/wallet.ts10
9 files changed, 295 insertions, 264 deletions
diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts
index de3664b67..2d24e2d72 100644
--- a/packages/taler-util/src/types-taler-merchant.ts
+++ b/packages/taler-util/src/types-taler-merchant.ts
@@ -335,18 +335,6 @@ export interface MerchantAbortPayRefundSuccessStatus {
exchange_pub: string;
}
-/**
- * Information about an exchange as stored inside a
- * merchant's contract terms.
- */
-export interface ExchangeHandle {
- // The exchange's base URL.
- url: string;
-
- // Master public key of the exchange.
- master_pub: EddsaPublicKeyString;
-}
-
export interface AuditorHandle {
/**
* Official name of the auditor.
@@ -531,7 +519,7 @@ export interface MerchantContractTerms {
delivery_location?: Location;
// Exchanges that the merchant accepts even if it does not accept any auditors that audit them.
- exchanges: ExchangeHandle[];
+ exchanges: Exchange[];
// List of products that are part of the purchase (see Product).
products?: Product[];
@@ -667,12 +655,6 @@ export interface MerchantBlindSigWrapperV1 {
blind_sig: string;
}
-export const codecForExchangeHandle = (): Codec<ExchangeHandle> =>
- buildCodecForObject<ExchangeHandle>()
- .property("master_pub", codecForString())
- .property("url", codecForString())
- .build("ExchangeHandle");
-
export const codecForAuditorHandle = (): Codec<AuditorHandle> =>
buildCodecForObject<AuditorHandle>()
.property("name", codecForString())
@@ -727,7 +709,7 @@ export const codecForMerchantContractTerms = (): Codec<MerchantContractTerms> =>
.property("max_fee", codecForAmountString())
.property("merchant", codecForMerchantInfo())
.property("merchant_pub", codecForString())
- .property("exchanges", codecForList(codecForExchangeHandle()))
+ .property("exchanges", codecForList(codecForExchange()))
.property("products", codecOptional(codecForList(codecForProduct())))
.property("extra", codecForAny())
.property("minimum_age", codecOptional(codecForNumber()))
@@ -815,16 +797,13 @@ export interface PaymentResponse {
pos_confirmation?: string;
}
export interface PaymentDeniedLegallyResponse {
-
// Base URL of the exchanges that denied the payment.
// The wallet should refresh the coins from these
// exchanges, but may try to pay with coins from
// other exchanges.
exchange_base_urls: string[];
-
}
-
export interface PaymentStatusRequestParams {
// Hash of the order’s contract terms (this is used to
// authenticate the wallet/customer in case
@@ -1541,7 +1520,6 @@ export interface CategoryProductList {
export interface ProductSummary {
// Product ID to use.
product_id: string;
-
}
export interface CategoryCreateRequest {
@@ -2698,6 +2676,7 @@ export interface Tax {
// Amount paid in tax.
tax: AmountString;
}
+
export interface Merchant {
// The merchant's legal name of business.
name: string;
@@ -2718,6 +2697,7 @@ export interface Merchant {
// Some of the typical fields for a location (such as a street address) may be absent.
jurisdiction?: Location;
}
+
// Delivery location, loosely modeled as a subset of
// ISO20022's PostalAddress25.
export interface Location {
@@ -2752,16 +2732,7 @@ export interface Location {
// Free-form address lines, should not exceed 7 elements.
address_lines?: string[];
}
-interface Auditor {
- // Official name.
- name: string;
-
- // Auditor's public key.
- auditor_pub: EddsaPublicKey;
- // Base URL of the auditor.
- url: string;
-}
export interface Exchange {
// The exchange's base URL.
url: string;
@@ -2873,10 +2844,11 @@ export const codecForPaymentResponse = (): Codec<PaymentResponse> =>
.property("sig", codecForString())
.build("TalerMerchantApi.PaymentResponse");
-export const codecForPaymentDeniedLegallyResponse = (): Codec<PaymentDeniedLegallyResponse> =>
- buildCodecForObject<PaymentDeniedLegallyResponse>()
- .property("exchange_base_urls", codecForList(codecForString()))
- .build("TalerMerchantApi.PaymentDeniedLegallyResponse");
+export const codecForPaymentDeniedLegallyResponse =
+ (): Codec<PaymentDeniedLegallyResponse> =>
+ buildCodecForObject<PaymentDeniedLegallyResponse>()
+ .property("exchange_base_urls", codecForList(codecForString()))
+ .build("TalerMerchantApi.PaymentDeniedLegallyResponse");
export const codecForStatusPaid = (): Codec<StatusPaid> =>
buildCodecForObject<StatusPaid>()
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
index 244640115..b788155c2 100644
--- a/packages/taler-util/src/types-taler-wallet.ts
+++ b/packages/taler-util/src/types-taler-wallet.ts
@@ -235,11 +235,13 @@ export const codecForConvertAmountRequest =
export interface GetMaxDepositAmountRequest {
currency: string;
+ depositPaytoUri?: string;
}
export const codecForGetMaxDepositAmountRequest =
buildCodecForObject<GetMaxDepositAmountRequest>()
.property("currency", codecForString())
+ .property("depositPaytoUri", codecOptional(codecForString()))
.build("GetAmountRequest");
export interface GetMaxDepositAmountResponse {
diff --git a/packages/taler-util/src/types.test.ts b/packages/taler-util/src/types.test.ts
index ed55fd16c..bea352128 100644
--- a/packages/taler-util/src/types.test.ts
+++ b/packages/taler-util/src/types.test.ts
@@ -15,15 +15,14 @@
*/
import test from "ava";
-import { codecForContractTerms } from "./index.js";
+import { codecForContractTerms, MerchantContractTerms } from "./index.js";
test("contract terms validation", (t) => {
const c = {
nonce: "123123123",
h_wire: "123",
amount: "EUR:1.5",
- auditors: [],
- exchanges: [{ master_pub: "foo", url: "foo" }],
+ exchanges: [{ master_pub: "foo", priority: 1, url: "foo" }],
fulfillment_url: "foo",
max_fee: "EUR:1.5",
merchant_pub: "12345",
@@ -37,7 +36,7 @@ test("contract terms validation", (t) => {
summary: "hello",
timestamp: { t_s: 42 },
wire_method: "test",
- };
+ } satisfies MerchantContractTerms;
codecForContractTerms().decode(c);
@@ -59,8 +58,7 @@ test("contract terms validation (locations)", (t) => {
nonce: "123123123",
h_wire: "123",
amount: "EUR:1.5",
- auditors: [],
- exchanges: [{ master_pub: "foo", url: "foo" }],
+ exchanges: [{ master_pub: "foo", priority: 1, url: "foo" }],
fulfillment_url: "foo",
max_fee: "EUR:1.5",
merchant_pub: "12345",
@@ -83,7 +81,7 @@ test("contract terms validation (locations)", (t) => {
country: "FR",
town: "Rennes",
},
- };
+ } satisfies MerchantContractTerms;
const r = codecForContractTerms().decode(c);
diff --git a/packages/taler-wallet-core/src/coinSelection.test.ts b/packages/taler-wallet-core/src/coinSelection.test.ts
index c7cb2857e..a13838034 100644
--- a/packages/taler-wallet-core/src/coinSelection.test.ts
+++ b/packages/taler-wallet-core/src/coinSelection.test.ts
@@ -15,17 +15,21 @@
*/
import {
AbsoluteTime,
+ AmountJson,
AmountString,
Amounts,
DenomKeyType,
+ DenominationPubKey,
Duration,
+ TalerProtocolTimestamp,
j2s,
} from "@gnu-taler/taler-util";
import test from "ava";
import {
- AvailableDenom,
+ AvailableCoinsOfDenom,
CoinSelectionTally,
emptyTallyForPeerPayment,
+ testing_getMaxDepositAmountForAvailableCoins,
testing_selectGreedy,
} from "./coinSelection.js";
@@ -180,7 +184,7 @@ function createCandidates(
numAvailable: number;
fromExchange: string;
}[],
-): AvailableDenom[] {
+): AvailableCoinsOfDenom[] {
return ar.map((r, idx) => {
return {
denomPub: {
@@ -279,3 +283,163 @@ test("p2p: regression STATER", (t) => {
);
t.assert(!!res);
});
+
+function makeCurrencyHelper(currency: string) {
+ return (sx: TemplateStringsArray, ...vx: any[]) => {
+ const s = String.raw({ raw: sx }, ...vx);
+ return Amounts.parseOrThrow(`${currency}:${s}`);
+ };
+}
+
+type TestCoin = [AmountJson, number];
+
+const kudos = makeCurrencyHelper("kudos");
+
+function defaultFeeConfig(
+ value: AmountJson,
+ totalAvailable: number,
+): AvailableCoinsOfDenom {
+ return {
+ denomPub: undefined as any as DenominationPubKey,
+ denomPubHash: "123",
+ feeDeposit: `KUDOS:0.01`,
+ feeRefresh: `KUDOS:0.01`,
+ feeRefund: `KUDOS:0.01`,
+ feeWithdraw: `KUDOS:0.01`,
+ exchangeBaseUrl: "2",
+ maxAge: 0,
+ numAvailable: totalAvailable,
+ stampExpireDeposit: TalerProtocolTimestamp.never(),
+ stampExpireLegal: TalerProtocolTimestamp.never(),
+ stampExpireWithdraw: TalerProtocolTimestamp.never(),
+ stampStart: TalerProtocolTimestamp.never(),
+ value: Amounts.stringify(value),
+ };
+}
+
+test("deposit max 35", (t) => {
+ const coinList: TestCoin[] = [
+ [kudos`2`, 5],
+ [kudos`5`, 5],
+ ];
+ const result = testing_getMaxDepositAmountForAvailableCoins(
+ {
+ currency: "KUDOS",
+ },
+ {
+ coinAvailability: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
+ currentWireFeePerExchange: {
+ "2": kudos`0`,
+ },
+ },
+ );
+ t.is(Amounts.stringifyValue(result.rawAmount), "34.9");
+ t.is(Amounts.stringifyValue(result.effectiveAmount), "35");
+});
+
+test("deposit max 35 with wirefee", (t) => {
+ const coinList: TestCoin[] = [
+ [kudos`2`, 5],
+ [kudos`5`, 5],
+ ];
+ const result = testing_getMaxDepositAmountForAvailableCoins(
+ {
+ currency: "KUDOS",
+ },
+ {
+ coinAvailability: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
+ currentWireFeePerExchange: {
+ "2": kudos`1`,
+ },
+ },
+ );
+ t.is(Amounts.stringifyValue(result.rawAmount), "33.9");
+ t.is(Amounts.stringifyValue(result.effectiveAmount), "35");
+});
+
+test("deposit max repeated denom", (t) => {
+ const coinList: TestCoin[] = [
+ [kudos`2`, 1],
+ [kudos`2`, 1],
+ [kudos`5`, 1],
+ ];
+ const result = testing_getMaxDepositAmountForAvailableCoins(
+ {
+ currency: "KUDOS",
+ },
+ {
+ coinAvailability: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
+ currentWireFeePerExchange: {
+ "2": kudos`0`,
+ },
+ },
+ );
+ t.is(Amounts.stringifyValue(result.rawAmount), "8.97");
+ t.is(Amounts.stringifyValue(result.effectiveAmount), "9");
+});
+
+test("demo: deposit max after withdraw raw 25", (t) => {
+ const coinList: TestCoin[] = [
+ [kudos`0.1`, 8],
+ [kudos`1`, 0],
+ [kudos`2`, 2],
+ [kudos`5`, 0],
+ [kudos`10`, 2],
+ ];
+ const result = testing_getMaxDepositAmountForAvailableCoins(
+ {
+ currency: "KUDOS",
+ },
+ {
+ coinAvailability: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
+ currentWireFeePerExchange: {
+ "2": kudos`0.01`,
+ },
+ },
+ );
+ t.is(Amounts.stringifyValue(result.effectiveAmount), "24.8");
+ t.is(Amounts.stringifyValue(result.rawAmount), "24.67");
+
+ // 8 x 0.1
+ // 2 x 0.2
+ // 2 x 10.0
+ // total effective 24.8
+ // deposit fee 12 x 0.01 = 0.12
+ // wire fee 0.01
+ // total raw: 24.8 - 0.13 = 24.67
+
+ // current wallet impl fee 0.14
+});
+
+test("demo: deposit max after withdraw raw 13", (t) => {
+ const coinList: TestCoin[] = [
+ [kudos`0.1`, 8],
+ [kudos`1`, 0],
+ [kudos`2`, 1],
+ [kudos`5`, 0],
+ [kudos`10`, 1],
+ ];
+ const result = testing_getMaxDepositAmountForAvailableCoins(
+ {
+ currency: "KUDOS",
+ },
+ {
+ coinAvailability: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
+ currentWireFeePerExchange: {
+ "2": kudos`0.01`,
+ },
+ },
+ );
+ t.is(Amounts.stringifyValue(result.effectiveAmount), "12.8");
+ t.is(Amounts.stringifyValue(result.rawAmount), "12.69");
+
+ // 8 x 0.1
+ // 1 x 0.2
+ // 1 x 10.0
+ // total effective 12.8
+ // deposit fee 10 x 0.01 = 0.10
+ // wire fee 0.01
+ // total raw: 12.8 - 0.11 = 12.69
+
+ // current wallet impl fee 0.14
+});
diff --git a/packages/taler-wallet-core/src/coinSelection.ts b/packages/taler-wallet-core/src/coinSelection.ts
index e6e7892ce..bf24a883a 100644
--- a/packages/taler-wallet-core/src/coinSelection.ts
+++ b/packages/taler-wallet-core/src/coinSelection.ts
@@ -38,6 +38,8 @@ import {
DenominationInfo,
ExchangeGlobalFees,
ForcedCoinSel,
+ GetMaxDepositAmountRequest,
+ GetMaxDepositAmountResponse,
j2s,
Logger,
parsePaytoUri,
@@ -180,18 +182,17 @@ async function internalSelectPayCoins(
| undefined
> {
const { contractTermsAmount, depositFeeLimit } = req;
- const [candidateDenoms, wireFeesPerExchange] = await selectPayCandidates(
- wex,
- tx,
- {
- restrictExchanges: req.restrictExchanges,
- instructedAmount: req.contractTermsAmount,
- restrictWireMethod: req.restrictWireMethod,
- depositPaytoUri: req.depositPaytoUri,
- requiredMinimumAge: req.requiredMinimumAge,
- includePendingCoins,
- },
- );
+ const candidateRes = await selectPayCandidates(wex, tx, {
+ currency: Amounts.currencyOf(req.contractTermsAmount),
+ restrictExchanges: req.restrictExchanges,
+ restrictWireMethod: req.restrictWireMethod,
+ depositPaytoUri: req.depositPaytoUri,
+ requiredMinimumAge: req.requiredMinimumAge,
+ includePendingCoins,
+ });
+
+ const wireFeesPerExchange = candidateRes.currentWireFeePerExchange;
+ const candidateDenoms = candidateRes.coinAvailability;
if (logger.shouldLogTrace()) {
logger.trace(
@@ -589,7 +590,7 @@ export interface SelectGreedyRequest {
function selectGreedy(
req: SelectGreedyRequest,
- candidateDenoms: AvailableDenom[],
+ candidateDenoms: AvailableCoinsOfDenom[],
tally: CoinSelectionTally,
): SelResult | undefined {
const selectedDenom: SelResult = {};
@@ -652,7 +653,7 @@ function selectGreedy(
function selectForced(
req: SelectPayCoinRequestNg,
- candidateDenoms: AvailableDenom[],
+ candidateDenoms: AvailableCoinsOfDenom[],
): SelResult | undefined {
const selectedDenom: SelResult = {};
@@ -713,7 +714,7 @@ export interface SelectPayCoinRequestNg {
depositPaytoUri?: string;
}
-export type AvailableDenom = DenominationInfo & {
+export type AvailableCoinsOfDenom = DenominationInfo & {
maxAge: number;
numAvailable: number;
};
@@ -794,7 +795,7 @@ function checkExchangeAccepted(
}
interface SelectPayCandidatesRequest {
- instructedAmount: AmountJson;
+ currency: string;
restrictWireMethod: string | undefined;
depositPaytoUri?: string;
restrictExchanges: ExchangeRestrictionSpec | undefined;
@@ -808,18 +809,23 @@ interface SelectPayCandidatesRequest {
includePendingCoins: boolean;
}
+export interface PayCoinCandidates {
+ coinAvailability: AvailableCoinsOfDenom[];
+ currentWireFeePerExchange: Record<string, AmountJson>;
+}
+
async function selectPayCandidates(
wex: WalletExecutionContext,
tx: WalletDbReadOnlyTransaction<
["exchanges", "coinAvailability", "exchangeDetails", "denominations"]
>,
req: SelectPayCandidatesRequest,
-): Promise<[AvailableDenom[], Record<string, AmountJson>]> {
+): Promise<PayCoinCandidates> {
// FIXME: Use the existing helper (from balance.ts) to
// get acceptable exchanges.
logger.shouldLogTrace() &&
logger.trace(`selecting available coin candidates for ${j2s(req)}`);
- const denoms: AvailableDenom[] = [];
+ const denoms: AvailableCoinsOfDenom[] = [];
const exchanges = await tx.exchanges.iter().toArray();
const wfPerExchange: Record<string, AmountJson> = {};
for (const exchange of exchanges) {
@@ -828,7 +834,7 @@ async function selectPayCandidates(
exchange.baseUrl,
);
// 1. exchange has same currency
- if (exchangeDetails?.currency !== req.instructedAmount.currency) {
+ if (exchangeDetails?.currency !== req.currency) {
logger.shouldLogTrace() &&
logger.trace(`skipping ${exchange.baseUrl} due to currency mismatch`);
continue;
@@ -935,7 +941,10 @@ async function selectPayCandidates(
Amounts.cmp(o1.feeDeposit, o2.feeDeposit) ||
strcmp(o1.denomPubHash, o2.denomPubHash),
);
- return [denoms, wfPerExchange];
+ return {
+ coinAvailability: denoms,
+ currentWireFeePerExchange: wfPerExchange,
+ };
}
export interface PeerCoinSelectionDetails {
@@ -1072,7 +1081,7 @@ async function internalSelectPeerCoins(
| undefined
> {
const candidatesRes = await selectPayCandidates(wex, tx, {
- instructedAmount: req.instructedAmount,
+ currency: Amounts.currencyOf(req.instructedAmount),
restrictExchanges: {
auditors: [],
exchanges: [
@@ -1085,7 +1094,7 @@ async function internalSelectPeerCoins(
restrictWireMethod: undefined,
includePendingCoins,
});
- const candidates = candidatesRes[0];
+ const candidates = candidatesRes.coinAvailability;
if (logger.shouldLogTrace()) {
logger.trace(`peer payment candidate coins: ${j2s(candidates)}`);
}
@@ -1258,3 +1267,71 @@ export async function selectPeerCoins(
},
);
}
+
+function getMaxDepositAmountForAvailableCoins(
+ req: GetMaxDepositAmountRequest,
+ candidateRes: PayCoinCandidates,
+): GetMaxDepositAmountResponse {
+ const wireFeeCoveredForExchange = new Set<string>();
+
+ let amountEffective = Amounts.zeroOfCurrency(req.currency);
+ let fees = Amounts.zeroOfCurrency(req.currency);
+
+ for (const cc of candidateRes.coinAvailability) {
+ if (!wireFeeCoveredForExchange.has(cc.exchangeBaseUrl)) {
+ fees = Amounts.add(
+ fees,
+ candidateRes.currentWireFeePerExchange[cc.exchangeBaseUrl],
+ ).amount;
+ wireFeeCoveredForExchange.add(cc.exchangeBaseUrl);
+ }
+
+ amountEffective = Amounts.add(
+ amountEffective,
+ Amounts.mult(cc.value, cc.numAvailable).amount,
+ ).amount;
+
+ fees = Amounts.add(
+ fees,
+ Amounts.mult(cc.feeDeposit, cc.numAvailable).amount,
+ ).amount;
+ }
+
+ return {
+ effectiveAmount: Amounts.stringify(amountEffective),
+ rawAmount: Amounts.stringify(Amounts.sub(amountEffective, fees).amount),
+ };
+}
+
+/**
+ * Only used for unit testing getMaxDepositAmountForAvailableCoins.
+ */
+export const testing_getMaxDepositAmountForAvailableCoins =
+ getMaxDepositAmountForAvailableCoins;
+
+export async function getMaxDepositAmount(
+ wex: WalletExecutionContext,
+ req: GetMaxDepositAmountRequest,
+): Promise<GetMaxDepositAmountResponse> {
+ return await wex.db.runReadOnlyTx(
+ {
+ storeNames: [
+ "exchanges",
+ "coinAvailability",
+ "denominations",
+ "exchangeDetails",
+ ],
+ },
+ async (tx): Promise<GetMaxDepositAmountResponse> => {
+ const candidateRes = await selectPayCandidates(wex, tx, {
+ currency: req.currency,
+ restrictExchanges: undefined,
+ restrictWireMethod: undefined,
+ depositPaytoUri: req.depositPaytoUri,
+ requiredMinimumAge: undefined,
+ includePendingCoins: true,
+ });
+ return getMaxDepositAmountForAvailableCoins(req, candidateRes);
+ },
+ );
+}
diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts
index b27b0157f..0ca595e50 100644
--- a/packages/taler-wallet-core/src/deposits.ts
+++ b/packages/taler-wallet-core/src/deposits.ts
@@ -27,21 +27,21 @@ import {
Amounts,
BatchDepositRequestCoin,
CancellationToken,
+ CheckDepositRequest,
+ CheckDepositResponse,
CoinRefreshRequest,
CreateDepositGroupRequest,
CreateDepositGroupResponse,
DepositGroupFees,
DepositTransactionTrackingState,
Duration,
+ Exchange,
ExchangeBatchDepositRequest,
- ExchangeHandle,
ExchangeRefundRequest,
HttpStatusCode,
Logger,
MerchantContractTerms,
NotificationType,
- CheckDepositRequest,
- CheckDepositResponse,
RefreshReason,
SelectedProspectiveCoin,
TalerError,
@@ -1740,7 +1740,7 @@ export async function internalCheckDepositGroup(
const amount = Amounts.parseOrThrow(req.amount);
const currency = Amounts.currencyOf(amount);
- const exchangeInfos: ExchangeHandle[] = [];
+ const exchangeInfos: Exchange[] = [];
await wex.db.runReadOnlyTx(
{ storeNames: ["exchangeDetails", "exchanges"] },
@@ -1753,6 +1753,7 @@ export async function internalCheckDepositGroup(
}
exchangeInfos.push({
master_pub: details.masterPublicKey,
+ priority: 1,
url: e.baseUrl,
});
}
@@ -1948,7 +1949,11 @@ export async function createDepositGroup(
const wireSalt = encodeCrock(getRandomBytes(16));
const wireHash = hashWire(req.depositPaytoUri, wireSalt);
const contractTerms: MerchantContractTerms = {
- exchanges: exchangeInfos,
+ exchanges: exchangeInfos.map((x) => ({
+ master_pub: x.master_pub,
+ priority: 1,
+ url: x.url,
+ })),
amount: req.amount,
max_fee: Amounts.stringify(amount),
wire_method: depositPayto.targetType,
diff --git a/packages/taler-wallet-core/src/instructedAmountConversion.test.ts b/packages/taler-wallet-core/src/instructedAmountConversion.test.ts
index 03e702568..17c2439c7 100644
--- a/packages/taler-wallet-core/src/instructedAmountConversion.test.ts
+++ b/packages/taler-wallet-core/src/instructedAmountConversion.test.ts
@@ -26,7 +26,6 @@ import {
CoinInfo,
convertDepositAmountForAvailableCoins,
convertWithdrawalAmountFromAvailableCoins,
- getMaxDepositAmountForAvailableCoins,
} from "./instructedAmountConversion.js";
function makeCurrencyHelper(currency: string) {
@@ -254,76 +253,6 @@ test("deposit with wire fee raw 2", (t) => {
*
*/
-test("deposit max 35", (t) => {
- const coinList: Coin[] = [
- [kudos`2`, 5],
- [kudos`5`, 5],
- ];
- const result = getMaxDepositAmountForAvailableCoins(
- {
- list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
- exchanges: {
- "2": {
- wireFee: kudos`0.00`,
- purseFee: kudos`0.00`,
- creditDeadline: AbsoluteTime.never(),
- debitDeadline: AbsoluteTime.never(),
- },
- },
- },
- "KUDOS",
- );
- t.is(Amounts.stringifyValue(result.raw), "34.9");
- t.is(Amounts.stringifyValue(result.effective), "35");
-});
-
-test("deposit max 35 with wirefee", (t) => {
- const coinList: Coin[] = [
- [kudos`2`, 5],
- [kudos`5`, 5],
- ];
- const result = getMaxDepositAmountForAvailableCoins(
- {
- list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
- exchanges: {
- "2": {
- wireFee: kudos`1`,
- purseFee: kudos`0.00`,
- creditDeadline: AbsoluteTime.never(),
- debitDeadline: AbsoluteTime.never(),
- },
- },
- },
- "KUDOS",
- );
- t.is(Amounts.stringifyValue(result.raw), "33.9");
- t.is(Amounts.stringifyValue(result.effective), "35");
-});
-
-test("deposit max repeated denom", (t) => {
- const coinList: Coin[] = [
- [kudos`2`, 1],
- [kudos`2`, 1],
- [kudos`5`, 1],
- ];
- const result = getMaxDepositAmountForAvailableCoins(
- {
- list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
- exchanges: {
- "2": {
- wireFee: kudos`0.00`,
- purseFee: kudos`0.00`,
- creditDeadline: AbsoluteTime.never(),
- debitDeadline: AbsoluteTime.never(),
- },
- },
- },
- "KUDOS",
- );
- t.is(Amounts.stringifyValue(result.raw), "8.97");
- t.is(Amounts.stringifyValue(result.effective), "9");
-});
-
/**
* Making a withdrawal with effective amount
*
@@ -663,42 +592,6 @@ test("demo: withdraw raw 25", (t) => {
//shows fee = 0.2
});
-test("demo: deposit max after withdraw raw 25", (t) => {
- const coinList: Coin[] = [
- [kudos`0.1`, 8],
- [kudos`1`, 0],
- [kudos`2`, 2],
- [kudos`5`, 0],
- [kudos`10`, 2],
- ];
- const result = getMaxDepositAmountForAvailableCoins(
- {
- list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
- exchanges: {
- one: {
- wireFee: kudos`0.01`,
- purseFee: kudos`0.00`,
- creditDeadline: AbsoluteTime.never(),
- debitDeadline: AbsoluteTime.never(),
- },
- },
- },
- "KUDOS",
- );
- t.is(Amounts.stringifyValue(result.effective), "24.8");
- t.is(Amounts.stringifyValue(result.raw), "24.67");
-
- // 8 x 0.1
- // 2 x 0.2
- // 2 x 10.0
- // total effective 24.8
- // deposit fee 12 x 0.01 = 0.12
- // wire fee 0.01
- // total raw: 24.8 - 0.13 = 24.67
-
- // current wallet impl fee 0.14
-});
-
test("demo: withdraw raw 13", (t) => {
const coinList: Coin[] = [
[kudos`0.1`, 0],
@@ -729,39 +622,3 @@ test("demo: withdraw raw 13", (t) => {
//current wallet impl: hides the left in reserve fee
//shows fee = 0.2
});
-
-test("demo: deposit max after withdraw raw 13", (t) => {
- const coinList: Coin[] = [
- [kudos`0.1`, 8],
- [kudos`1`, 0],
- [kudos`2`, 1],
- [kudos`5`, 0],
- [kudos`10`, 1],
- ];
- const result = getMaxDepositAmountForAvailableCoins(
- {
- list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
- exchanges: {
- one: {
- wireFee: kudos`0.01`,
- purseFee: kudos`0.00`,
- creditDeadline: AbsoluteTime.never(),
- debitDeadline: AbsoluteTime.never(),
- },
- },
- },
- "KUDOS",
- );
- t.is(Amounts.stringifyValue(result.effective), "12.8");
- t.is(Amounts.stringifyValue(result.raw), "12.69");
-
- // 8 x 0.1
- // 1 x 0.2
- // 1 x 10.0
- // total effective 12.8
- // deposit fee 10 x 0.01 = 0.10
- // wire fee 0.01
- // total raw: 12.8 - 0.11 = 12.69
-
- // current wallet impl fee 0.14
-});
diff --git a/packages/taler-wallet-core/src/instructedAmountConversion.ts b/packages/taler-wallet-core/src/instructedAmountConversion.ts
index cbdd643a0..c0f835dfa 100644
--- a/packages/taler-wallet-core/src/instructedAmountConversion.ts
+++ b/packages/taler-wallet-core/src/instructedAmountConversion.ts
@@ -23,7 +23,6 @@ import {
Amounts,
ConvertAmountRequest,
Duration,
- GetMaxDepositAmountRequest,
TransactionAmountMode,
TransactionType,
checkDbInvariant,
@@ -429,47 +428,6 @@ export function convertDepositAmountForAvailableCoins(
return result;
}
-export async function getMaxDepositAmount(
- wex: WalletExecutionContext,
- req: GetMaxDepositAmountRequest,
-): Promise<AmountResponse> {
- const denoms = await getAvailableCoins(
- wex,
- TransactionType.Deposit,
- req.currency,
- {},
- );
-
- const result = getMaxDepositAmountForAvailableCoins(denoms, req.currency);
- return {
- effectiveAmount: Amounts.stringify(result.effective),
- rawAmount: Amounts.stringify(result.raw),
- };
-}
-
-export function getMaxDepositAmountForAvailableCoins(
- denoms: AvailableCoins,
- currency: string,
-): AmountWithFee {
- const zero = Amounts.zeroOfCurrency(currency);
- if (!denoms.list.length) {
- // no coins in the database
- return { effective: zero, raw: zero };
- }
-
- const result = getTotalEffectiveAndRawForDeposit(
- denoms.list.map((info) => {
- return { info, size: info.totalAvailable ?? 0 };
- }),
- currency,
- );
-
- const wireFee = Object.values(denoms.exchanges)[0]?.wireFee ?? zero;
- result.raw = Amounts.sub(result.raw, wireFee).amount;
-
- return result;
-}
-
export async function convertWithdrawalAmount(
wex: WalletExecutionContext,
req: ConvertAmountRequest,
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index a4ed7db1c..4b01cec60 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -133,6 +133,7 @@ import {
codecForAny,
codecForApplyDevExperiment,
codecForCanonicalizeBaseUrlRequest,
+ codecForCheckDepositRequest,
codecForCheckPayTemplateRequest,
codecForCheckPeerPullPaymentRequest,
codecForCheckPeerPushDebitRequest,
@@ -148,7 +149,6 @@ import {
codecForFailTransactionRequest,
codecForForceRefreshRequest,
codecForForgetKnownBankAccounts,
- codecForGetMaxDepositAmountRequest,
codecForGetBalanceDetailRequest,
codecForGetBankingChoicesForPaytoRequest,
codecForGetContractTermsDetails,
@@ -157,6 +157,7 @@ import {
codecForGetExchangeEntryByUrlRequest,
codecForGetExchangeResourcesRequest,
codecForGetExchangeTosRequest,
+ codecForGetMaxDepositAmountRequest,
codecForGetQrCodesForPaytoRequest,
codecForGetWithdrawalDetailsForAmountRequest,
codecForGetWithdrawalDetailsForUri,
@@ -170,7 +171,6 @@ import {
codecForListExchangesForScopedCurrencyRequest,
codecForListKnownBankAccounts,
codecForPrepareBankIntegratedWithdrawalRequest,
- codecForCheckDepositRequest,
codecForPreparePayRequest,
codecForPreparePayTemplateRequest,
codecForPreparePeerPullPaymentRequest,
@@ -233,6 +233,7 @@ import {
setWalletDeviceId,
} from "./backup/index.js";
import { getBalanceDetail, getBalances } from "./balance.js";
+import { getMaxDepositAmount } from "./coinSelection.js";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import {
CryptoDispatcher,
@@ -276,10 +277,7 @@ import {
listExchanges,
lookupExchangeByUri,
} from "./exchanges.js";
-import {
- convertDepositAmount,
- getMaxDepositAmount,
-} from "./instructedAmountConversion.js";
+import { convertDepositAmount } from "./instructedAmountConversion.js";
import {
ObservableDbAccess,
ObservableTaskScheduler,