gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 02/02: wallet-core: DB FIXMEs (amount format)


From: gnunet
Subject: [taler-wallet-core] 02/02: wallet-core: DB FIXMEs (amount format)
Date: Wed, 02 Nov 2022 18:23:22 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

commit d50294f76e0aa357d690a933bb6d696a2f6aef1b
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Nov 2 17:42:14 2022 +0100

    wallet-core: DB FIXMEs (amount format)
---
 packages/anastasis-core/src/index.ts               |  20 ++--
 packages/taler-util/src/amounts.ts                 |  78 +++++++++++-----
 packages/taler-util/src/wallet-types.ts            |  52 +++++------
 .../src/crypto/cryptoImplementation.ts             |  16 ++--
 .../taler-wallet-core/src/crypto/cryptoTypes.ts    |   4 +-
 packages/taler-wallet-core/src/db.ts               |  59 ++++++------
 packages/taler-wallet-core/src/dbless.ts           |   6 +-
 .../src/operations/backup/import.ts                |  80 +++++++---------
 .../taler-wallet-core/src/operations/balance.ts    |  16 ++--
 .../taler-wallet-core/src/operations/common.ts     |   2 +-
 .../taler-wallet-core/src/operations/deposits.ts   |  30 +++---
 .../taler-wallet-core/src/operations/exchanges.ts  |  18 ++--
 .../src/operations/pay-merchant.ts                 | 103 +++++++++++----------
 .../taler-wallet-core/src/operations/pay-peer.ts   |  12 +--
 .../taler-wallet-core/src/operations/recoup.ts     |   2 +-
 .../taler-wallet-core/src/operations/refresh.ts    |  32 ++++---
 packages/taler-wallet-core/src/operations/tip.ts   |   4 +-
 .../src/operations/transactions.ts                 |  41 +++++---
 .../src/operations/withdraw.test.ts                |  96 +++++++++----------
 .../taler-wallet-core/src/operations/withdraw.ts   |  26 +++---
 .../taler-wallet-core/src/util/coinSelection.ts    |   2 +-
 .../src/util/denominations.test.ts                 |   5 +-
 .../taler-wallet-core/src/util/denominations.ts    |  11 ++-
 packages/taler-wallet-core/src/wallet.ts           |   2 +-
 .../ShowFullContractTermPopup.stories.tsx          |  18 +---
 .../src/wallet/DepositPage/state.ts                |   6 +-
 .../src/wallet/DepositPage/stories.tsx             |   6 +-
 .../src/wallet/Transaction.tsx                     |   2 +-
 28 files changed, 400 insertions(+), 349 deletions(-)

diff --git a/packages/anastasis-core/src/index.ts 
b/packages/anastasis-core/src/index.ts
index eb2b41e50..8cb86cd85 100644
--- a/packages/anastasis-core/src/index.ts
+++ b/packages/anastasis-core/src/index.ts
@@ -169,8 +169,8 @@ export class ReducerError extends Error {
   constructor(public errorJson: ErrorDetails) {
     super(
       errorJson.message ??
-      errorJson.hint ??
-      `${TalerErrorCode[errorJson.code]}`,
+        errorJson.hint ??
+        `${TalerErrorCode[errorJson.code]}`,
     );
 
     // Set the prototype explicitly.
@@ -306,7 +306,7 @@ async function getProviderInfo(
         status: "error",
         code: TalerErrorCode.ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED,
         hint: "provider did not have provider salt",
-      }
+      };
     }
     return {
       status: "ok",
@@ -559,8 +559,8 @@ async function uploadSecret(
         "content-type": "application/json",
         ...(paySecret
           ? {
-            "Anastasis-Payment-Identifier": paySecret,
-          }
+              "Anastasis-Payment-Identifier": paySecret,
+            }
           : {}),
       },
       body: JSON.stringify(tur),
@@ -651,8 +651,8 @@ async function uploadSecret(
         [ANASTASIS_HTTP_HEADER_POLICY_META_DATA]: metadataEnc,
         ...(paySecret
           ? {
-            "Anastasis-Payment-Identifier": paySecret,
-          }
+              "Anastasis-Payment-Identifier": paySecret,
+            }
           : {}),
       },
       body: decodeCrock(encRecoveryDoc),
@@ -663,12 +663,12 @@ async function uploadSecret(
       let policyExpiration: TalerProtocolTimestamp = { t_s: 0 };
       try {
         policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0");
-      } catch (e) { }
+      } catch (e) {}
       try {
         policyExpiration = {
           t_s: Number(resp.headers.get("Anastasis-Policy-Expiration") ?? "0"),
         };
-      } catch (e) { }
+      } catch (e) {}
       successDetails[prov.provider_url] = {
         policy_version: policyVersion,
         policy_expiration: policyExpiration,
@@ -1469,7 +1469,7 @@ async function updateUploadFees(
   const addFee = (x: AmountLike) => {
     x = Amounts.jsonifyAmount(x);
     feePerCurrency[x.currency] = Amounts.add(
-      feePerCurrency[x.currency] ?? Amounts.getZero(x.currency),
+      feePerCurrency[x.currency] ?? Amounts.zeroOfAmount(x),
       x,
     ).amount;
   };
diff --git a/packages/taler-util/src/amounts.ts 
b/packages/taler-util/src/amounts.ts
index c9a78356e..f59e325b0 100644
--- a/packages/taler-util/src/amounts.ts
+++ b/packages/taler-util/src/amounts.ts
@@ -103,10 +103,24 @@ export class Amounts {
     throw Error("not instantiable");
   }
 
+  static currencyOf(amount: AmountLike) {
+    const amt = Amounts.parseOrThrow(amount);
+    return amt.currency;
+  }
+
+  static zeroOfAmount(amount: AmountLike): AmountJson {
+    const amt = Amounts.parseOrThrow(amount);
+    return {
+      currency: amt.currency,
+      fraction: 0,
+      value: 0,
+    };
+  }
+
   /**
    * Get an amount that represents zero units of a currency.
    */
-  static getZero(currency: string): AmountJson {
+  static zeroOfCurrency(currency: string): AmountJson {
     return {
       currency,
       fraction: 0,
@@ -132,7 +146,7 @@ export class Amounts {
   static sumOrZero(currency: string, amounts: AmountLike[]): Result {
     if (amounts.length <= 0) {
       return {
-        amount: Amounts.getZero(currency),
+        amount: Amounts.zeroOfCurrency(currency),
         saturated: false,
       };
     }
@@ -147,9 +161,11 @@ export class Amounts {
    *
    * Throws when currencies don't match.
    */
-  static add(first: AmountJson, ...rest: AmountJson[]): Result {
-    const currency = first.currency;
-    let value = first.value + Math.floor(first.fraction / 
amountFractionalBase);
+  static add(first: AmountLike, ...rest: AmountLike[]): Result {
+    const firstJ = Amounts.jsonifyAmount(first);
+    const currency = firstJ.currency;
+    let value =
+      firstJ.value + Math.floor(firstJ.fraction / amountFractionalBase);
     if (value > amountMaxValue) {
       return {
         amount: {
@@ -160,17 +176,18 @@ export class Amounts {
         saturated: true,
       };
     }
-    let fraction = first.fraction % amountFractionalBase;
+    let fraction = firstJ.fraction % amountFractionalBase;
     for (const x of rest) {
-      if (x.currency.toUpperCase() !== currency.toUpperCase()) {
-        throw Error(`Mismatched currency: ${x.currency} and ${currency}`);
+      const xJ = Amounts.jsonifyAmount(x);
+      if (xJ.currency.toUpperCase() !== currency.toUpperCase()) {
+        throw Error(`Mismatched currency: ${xJ.currency} and ${currency}`);
       }
 
       value =
         value +
-        x.value +
-        Math.floor((fraction + x.fraction) / amountFractionalBase);
-      fraction = Math.floor((fraction + x.fraction) % amountFractionalBase);
+        xJ.value +
+        Math.floor((fraction + xJ.fraction) / amountFractionalBase);
+      fraction = Math.floor((fraction + xJ.fraction) % amountFractionalBase);
       if (value > amountMaxValue) {
         return {
           amount: {
@@ -322,12 +339,27 @@ export class Amounts {
    * Parse amount in standard string form (like 'EUR:20.5'),
    * throw if the input is not a valid amount.
    */
-  static parseOrThrow(s: string): AmountJson {
-    const res = Amounts.parse(s);
-    if (!res) {
-      throw Error(`Can't parse amount: "${s}"`);
+  static parseOrThrow(s: AmountLike): AmountJson {
+    if (typeof s === "object") {
+      if (typeof s.currency !== "string") {
+        throw Error("invalid amount object");
+      }
+      if (typeof s.value !== "number") {
+        throw Error("invalid amount object");
+      }
+      if (typeof s.fraction !== "number") {
+        throw Error("invalid amount object");
+      }
+      return { currency: s.currency, value: s.value, fraction: s.fraction };
+    } else if (typeof s === "string") {
+      const res = Amounts.parse(s);
+      if (!res) {
+        throw Error(`Can't parse amount: "${s}"`);
+      }
+      return res;
+    } else {
+      throw Error("invalid amount (illegal type)");
     }
-    return res;
   }
 
   /**
@@ -371,10 +403,13 @@ export class Amounts {
       throw Error("amount can only be multiplied by a positive integer");
     }
     if (n == 0) {
-      return { amount: Amounts.getZero(a.currency), saturated: false };
+      return {
+        amount: Amounts.zeroOfCurrency(a.currency),
+        saturated: false,
+      };
     }
     let x = a;
-    let acc = Amounts.getZero(a.currency);
+    let acc = Amounts.zeroOfCurrency(a.currency);
     while (n > 1) {
       if (n % 2 == 0) {
         n = n / 2;
@@ -427,9 +462,10 @@ export class Amounts {
     return x1.currency.toUpperCase() === x2.currency.toUpperCase();
   }
 
-  static stringifyValue(a: AmountJson, minFractional = 0): string {
-    const av = a.value + Math.floor(a.fraction / amountFractionalBase);
-    const af = a.fraction % amountFractionalBase;
+  static stringifyValue(a: AmountLike, minFractional = 0): string {
+    const aJ = Amounts.jsonifyAmount(a);
+    const av = aJ.value + Math.floor(aJ.fraction / amountFractionalBase);
+    const af = aJ.fraction % amountFractionalBase;
     let s = av.toString();
 
     if (af) {
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index 5ff906faa..5d1c55b88 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -644,7 +644,7 @@ export enum RefreshReason {
  */
 export interface CoinRefreshRequest {
   readonly coinPub: string;
-  readonly amount: AmountJson;
+  readonly amount: AmountString;
 }
 
 /**
@@ -719,12 +719,12 @@ export interface WireFee {
   /**
    * Fee for wire transfers.
    */
-  wireFee: AmountJson;
+  wireFee: AmountString;
 
   /**
    * Fees to close and refund a reserve.
    */
-  closingFee: AmountJson;
+  closingFee: AmountString;
 
   /**
    * Start date of the fee.
@@ -761,9 +761,9 @@ export interface ExchangeGlobalFees {
   startDate: TalerProtocolTimestamp;
   endDate: TalerProtocolTimestamp;
 
-  historyFee: AmountJson;
-  accountFee: AmountJson;
-  purseFee: AmountJson;
+  historyFee: AmountString;
+  accountFee: AmountString;
+  purseFee: AmountString;
 
   historyTimeout: TalerProtocolDuration;
   purseTimeout: TalerProtocolDuration;
@@ -782,8 +782,8 @@ const codecForExchangeAccount = (): Codec<ExchangeAccount> 
=>
 const codecForWireFee = (): Codec<WireFee> =>
   buildCodecForObject<WireFee>()
     .property("sig", codecForString())
-    .property("wireFee", codecForAmountJson())
-    .property("closingFee", codecForAmountJson())
+    .property("wireFee", codecForAmountString())
+    .property("closingFee", codecForAmountString())
     .property("startStamp", codecForTimestamp)
     .property("endStamp", codecForTimestamp)
     .build("codecForWireFee");
@@ -798,7 +798,7 @@ export interface DenominationInfo {
   /**
    * Value of one coin of the denomination.
    */
-  value: AmountJson;
+  value: AmountString;
 
   /**
    * Hash of the denomination public key.
@@ -811,22 +811,22 @@ export interface DenominationInfo {
   /**
    * Fee for withdrawing.
    */
-  feeWithdraw: AmountJson;
+  feeWithdraw: AmountString;
 
   /**
    * Fee for depositing.
    */
-  feeDeposit: AmountJson;
+  feeDeposit: AmountString;
 
   /**
    * Fee for refreshing.
    */
-  feeRefresh: AmountJson;
+  feeRefresh: AmountString;
 
   /**
    * Fee for refunding.
    */
-  feeRefund: AmountJson;
+  feeRefund: AmountString;
 
   /**
    * Validity start date of the denomination.
@@ -858,21 +858,21 @@ export interface FeeDescription {
   group: string;
   from: AbsoluteTime;
   until: AbsoluteTime;
-  fee?: AmountJson;
+  fee?: AmountString;
 }
 
 export interface FeeDescriptionPair {
   group: string;
   from: AbsoluteTime;
   until: AbsoluteTime;
-  left?: AmountJson;
-  right?: AmountJson;
+  left?: AmountString;
+  right?: AmountString;
 }
 
 export interface TimePoint<T> {
   id: string;
   group: string;
-  fee: AmountJson;
+  fee: AmountString;
   type: "start" | "end";
   moment: AbsoluteTime;
   denom: T;
@@ -955,8 +955,8 @@ export const codecForFeeDescriptionPair = (): 
Codec<FeeDescriptionPair> =>
     .property("group", codecForString())
     .property("from", codecForAbsoluteTime)
     .property("until", codecForAbsoluteTime)
-    .property("left", codecOptional(codecForAmountJson()))
-    .property("right", codecOptional(codecForAmountJson()))
+    .property("left", codecOptional(codecForAmountString()))
+    .property("right", codecOptional(codecForAmountString()))
     .build("FeeDescriptionPair");
 
 export const codecForFeeDescription = (): Codec<FeeDescription> =>
@@ -964,7 +964,7 @@ export const codecForFeeDescription = (): 
Codec<FeeDescription> =>
     .property("group", codecForString())
     .property("from", codecForAbsoluteTime)
     .property("until", codecForAbsoluteTime)
-    .property("fee", codecOptional(codecForAmountJson()))
+    .property("fee", codecOptional(codecForAmountString()))
     .build("FeeDescription");
 
 export const codecForFeesByOperations = (): Codec<
@@ -1056,8 +1056,8 @@ export interface ManualWithdrawalDetails {
  * Selected denominations withn some extra info.
  */
 export interface DenomSelectionState {
-  totalCoinValue: AmountJson;
-  totalWithdrawCost: AmountJson;
+  totalCoinValue: AmountString;
+  totalWithdrawCost: AmountString;
   selectedDenoms: {
     denomPubHash: string;
     count: number;
@@ -1786,7 +1786,7 @@ export interface PayCoinSelection {
   /**
    * Amount requested by the merchant.
    */
-  paymentAmount: AmountJson;
+  paymentAmount: AmountString;
 
   /**
    * Public keys of the coins that were selected.
@@ -1796,17 +1796,17 @@ export interface PayCoinSelection {
   /**
    * Amount that each coin contributes.
    */
-  coinContributions: AmountJson[];
+  coinContributions: AmountString[];
 
   /**
    * How much of the wire fees is the customer paying?
    */
-  customerWireFees: AmountJson;
+  customerWireFees: AmountString;
 
   /**
    * How much of the deposit fees is the customer paying?
    */
-  customerDepositFees: AmountJson;
+  customerDepositFees: AmountString;
 }
 
 export interface InitiatePeerPushPaymentRequest {
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts 
b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index 8ba7d9298..d239270c8 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -28,6 +28,7 @@ import {
   AgeCommitmentProof,
   AgeRestriction,
   AmountJson,
+  AmountLike,
   Amounts,
   AmountString,
   BlindedDenominationSignature,
@@ -1155,8 +1156,8 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
       sessionSecretSeed: refreshSessionSecretSeed,
     } = req;
 
-    const currency = newCoinDenoms[0].value.currency;
-    let valueWithFee = Amounts.getZero(currency);
+    const currency = Amounts.currencyOf(newCoinDenoms[0].value);
+    let valueWithFee = Amounts.zeroOfCurrency(currency);
 
     for (const ncd of newCoinDenoms) {
       const t = Amounts.add(ncd.value, ncd.feeWithdraw).amount;
@@ -1627,21 +1628,22 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
   },
 };
 
-function amountToBuffer(amount: AmountJson): Uint8Array {
+function amountToBuffer(amount: AmountLike): Uint8Array {
+  const amountJ = Amounts.jsonifyAmount(amount);
   const buffer = new ArrayBuffer(8 + 4 + 12);
   const dvbuf = new DataView(buffer);
   const u8buf = new Uint8Array(buffer);
-  const curr = stringToBytes(amount.currency);
+  const curr = stringToBytes(amountJ.currency);
   if (typeof dvbuf.setBigUint64 !== "undefined") {
-    dvbuf.setBigUint64(0, BigInt(amount.value));
+    dvbuf.setBigUint64(0, BigInt(amountJ.value));
   } else {
-    const arr = bigint(amount.value).toArray(2 ** 8).value;
+    const arr = bigint(amountJ.value).toArray(2 ** 8).value;
     let offset = 8 - arr.length;
     for (let i = 0; i < arr.length; i++) {
       dvbuf.setUint8(offset++, arr[i]);
     }
   }
-  dvbuf.setUint32(8, amount.fraction);
+  dvbuf.setUint32(8, amountJ.fraction);
   u8buf.set(curr, 8 + 4);
 
   return u8buf;
diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts 
b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
index 0858cffa9..a083f453c 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
@@ -44,8 +44,8 @@ import {
 
 export interface RefreshNewDenomInfo {
   count: number;
-  value: AmountJson;
-  feeWithdraw: AmountJson;
+  value: AmountString;
+  feeWithdraw: AmountString;
   denomPub: DenominationPubKey;
   denomPubHash: string;
 }
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 12e7820c2..0b27b82dd 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -47,6 +47,7 @@ import {
   UnblindedSignature,
   WireInfo,
   HashCodeString,
+  Amounts,
 } from "@gnu-taler/taler-util";
 import {
   describeContents,
@@ -276,22 +277,22 @@ export interface DenomFees {
   /**
    * Fee for withdrawing.
    */
-  feeWithdraw: AmountJson;
+  feeWithdraw: AmountString;
 
   /**
    * Fee for depositing.
    */
-  feeDeposit: AmountJson;
+  feeDeposit: AmountString;
 
   /**
    * Fee for refreshing.
    */
-  feeRefresh: AmountJson;
+  feeRefresh: AmountString;
 
   /**
    * Fee for refunding.
    */
-  feeRefund: AmountJson;
+  feeRefund: AmountString;
 }
 
 /**
@@ -393,15 +394,15 @@ export namespace DenominationRecord {
     return {
       denomPub: d.denomPub,
       denomPubHash: d.denomPubHash,
-      feeDeposit: d.fees.feeDeposit,
-      feeRefresh: d.fees.feeRefresh,
-      feeRefund: d.fees.feeRefund,
-      feeWithdraw: d.fees.feeWithdraw,
+      feeDeposit: Amounts.stringify(d.fees.feeDeposit),
+      feeRefresh: Amounts.stringify(d.fees.feeRefresh),
+      feeRefund: Amounts.stringify(d.fees.feeRefund),
+      feeWithdraw: Amounts.stringify(d.fees.feeWithdraw),
       stampExpireDeposit: d.stampExpireDeposit,
       stampExpireLegal: d.stampExpireLegal,
       stampExpireWithdraw: d.stampExpireWithdraw,
       stampStart: d.stampStart,
-      value: DenominationRecord.getValue(d),
+      value: Amounts.stringify(DenominationRecord.getValue(d)),
       exchangeBaseUrl: d.exchangeBaseUrl,
     };
   }
@@ -527,7 +528,7 @@ export interface ExchangeRecord {
    * currency.
    *
    * We could use a rowID here, but having the currency in the
-   * details pointer lets us do fewer DB queries sometimes.
+   * details pointer lets us do fewer DB queries
    */
   detailsPointer: ExchangeDetailsPointer | undefined;
 
@@ -752,12 +753,12 @@ export interface TipRecord {
   /**
    * The tipped amount.
    */
-  tipAmountRaw: AmountJson;
+  tipAmountRaw: AmountString;
 
   /**
    * Effect on the balance (including fees etc).
    */
-  tipAmountEffective: AmountJson;
+  tipAmountEffective: AmountString;
 
   /**
    * Timestamp, the tip can't be picked up anymore after this deadline.
@@ -854,9 +855,9 @@ export interface RefreshGroupRecord {
   // object store for faster updates?
   refreshSessionPerCoin: (RefreshSessionRecord | undefined)[];
 
-  inputPerCoin: AmountJson[];
+  inputPerCoin: AmountString[];
 
-  estimatedOutputPerCoin: AmountJson[];
+  estimatedOutputPerCoin: AmountString[];
 
   /**
    * Flag for each coin whether refreshing finished.
@@ -888,7 +889,7 @@ export interface RefreshSessionRecord {
    * Sum of the value of denominations we want
    * to withdraw in this session, without fees.
    */
-  amountRefreshOutput: AmountJson;
+  amountRefreshOutput: AmountString;
 
   /**
    * Hashed denominations of the newly requested coins.
@@ -927,9 +928,9 @@ export interface WalletRefundItemCommon {
    */
   obtainedTime: TalerProtocolTimestamp;
 
-  refundAmount: AmountJson;
+  refundAmount: AmountString;
 
-  refundFee: AmountJson;
+  refundFee: AmountString;
 
   /**
    * Upper bound on the refresh cost incurred by
@@ -938,7 +939,7 @@ export interface WalletRefundItemCommon {
    * Might be lower in practice when two refunds on the same
    * coin are refreshed in the same refresh operation.
    */
-  totalRefreshCostBound: AmountJson;
+  totalRefreshCostBound: AmountString;
 
   coinPub: string;
 
@@ -1003,12 +1004,12 @@ export interface WalletContractData {
   merchantSig: string;
   merchantPub: string;
   merchant: MerchantInfo;
-  amount: AmountJson;
+  amount: AmountString;
   orderId: string;
   merchantBaseUrl: string;
   summary: string;
   autoRefund: TalerProtocolDuration | undefined;
-  maxWireFee: AmountJson;
+  maxWireFee: AmountString;
   wireFeeAmortization: number;
   payDeadline: TalerProtocolTimestamp;
   refundDeadline: TalerProtocolTimestamp;
@@ -1017,7 +1018,7 @@ export interface WalletContractData {
   timestamp: TalerProtocolTimestamp;
   wireMethod: string;
   wireInfoHash: string;
-  maxDepositFee: AmountJson;
+  maxDepositFee: AmountString;
   minimumAge?: number;
   deliveryDate: TalerProtocolTimestamp | undefined;
   deliveryLocation: Location | undefined;
@@ -1099,7 +1100,7 @@ export interface ProposalDownloadInfo {
 
 export interface PurchasePayInfo {
   payCoinSelection: PayCoinSelection;
-  totalPayCost: AmountJson;
+  totalPayCost: AmountString;
   payCoinSelectionUid: string;
 }
 
@@ -1216,7 +1217,7 @@ export interface PurchaseRecord {
    * How much merchant has refund to be taken but the wallet
    * did not picked up yet
    */
-  refundAmountAwaiting: AmountJson | undefined;
+  refundAmountAwaiting: AmountString | undefined;
 }
 
 export enum ConfigRecordKey {
@@ -1379,7 +1380,7 @@ export interface WithdrawalGroupRecord {
   /**
    * Amount that was sent by the user to fund the reserve.
    */
-  instructedAmount: AmountJson;
+  instructedAmount: AmountString;
 
   /**
    * Amount that was observed when querying the reserve that
@@ -1387,7 +1388,7 @@ export interface WithdrawalGroupRecord {
    *
    * Useful for diagnostics.
    */
-  reserveBalanceAmount?: AmountJson;
+  reserveBalanceAmount?: AmountString;
 
   /**
    * Amount including fees (i.e. the amount subtracted from the
@@ -1396,7 +1397,7 @@ export interface WithdrawalGroupRecord {
    * (Initial amount confirmed by the user, might differ with denomSel
    * on reselection.)
    */
-  rawWithdrawalAmount: AmountJson;
+  rawWithdrawalAmount: AmountString;
 
   /**
    * Amount that will be added to the balance when the withdrawal succeeds.
@@ -1404,7 +1405,7 @@ export interface WithdrawalGroupRecord {
    * (Initial amount confirmed by the user, might differ with denomSel
    * on reselection.)
    */
-  effectiveWithdrawalAmount: AmountJson;
+  effectiveWithdrawalAmount: AmountString;
 
   /**
    * Denominations selected for withdrawal.
@@ -1587,9 +1588,9 @@ export interface DepositGroupRecord {
 
   payCoinSelectionUid: string;
 
-  totalPayCost: AmountJson;
+  totalPayCost: AmountString;
 
-  effectiveDepositAmount: AmountJson;
+  effectiveDepositAmount: AmountString;
 
   depositedPerCoin: boolean[];
 
diff --git a/packages/taler-wallet-core/src/dbless.ts 
b/packages/taler-wallet-core/src/dbless.ts
index 076e5f215..544e2d458 100644
--- a/packages/taler-wallet-core/src/dbless.ts
+++ b/packages/taler-wallet-core/src/dbless.ts
@@ -160,7 +160,7 @@ export async function withdrawCoin(args: {
   const planchet = await cryptoApi.createPlanchet({
     coinIndex: 0,
     denomPub: denom.denomPub,
-    feeWithdraw: denom.fees.feeWithdraw,
+    feeWithdraw: Amounts.parseOrThrow(denom.fees.feeWithdraw),
     reservePriv: reserveKeyPair.reservePriv,
     reservePub: reserveKeyPair.reservePub,
     secretSeed: encodeCrock(getRandomBytes(32)),
@@ -294,11 +294,11 @@ export async function refreshCoin(req: {
       denomPub: x.denomPub,
       denomPubHash: x.denomPubHash,
       feeWithdraw: x.fees.feeWithdraw,
-      value: {
+      value: Amounts.stringify({
         currency: x.currency,
         fraction: x.amountFrac,
         value: x.amountVal,
-      },
+      }),
     })),
     meltCoinMaxAge: oldCoin.maxAge,
   });
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index 133699647..3159c60af 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -104,10 +104,8 @@ async function recoverPayCoinSelection(
 
   const coveredExchanges: Set<string> = new Set();
 
-  let totalWireFee: AmountJson = Amounts.getZero(contractData.amount.currency);
-  let totalDepositFees: AmountJson = Amounts.getZero(
-    contractData.amount.currency,
-  );
+  let totalWireFee: AmountJson = Amounts.zeroOfAmount(contractData.amount);
+  let totalDepositFees: AmountJson = Amounts.zeroOfAmount(contractData.amount);
 
   for (const coinPub of coinPubs) {
     const coinRecord = await tx.coins.get(coinPub);
@@ -136,7 +134,7 @@ async function recoverPayCoinSelection(
           fee.startStamp <= contractData.timestamp &&
           fee.endStamp >= contractData.timestamp
         ) {
-          wireFee = fee.wireFee;
+          wireFee = Amounts.parseOrThrow(fee.wireFee);
           break;
         }
       }
@@ -156,7 +154,7 @@ async function recoverPayCoinSelection(
   if (Amounts.cmp(contractData.maxWireFee, amortizedWireFee) < 0) {
     customerWireFee = amortizedWireFee;
   } else {
-    customerWireFee = Amounts.getZero(contractData.amount.currency);
+    customerWireFee = Amounts.zeroOfAmount(contractData.amount);
   }
 
   const customerDepositFees = Amounts.sub(
@@ -166,10 +164,10 @@ async function recoverPayCoinSelection(
 
   return {
     coinPubs,
-    coinContributions,
-    paymentAmount: contractData.amount,
-    customerWireFees: customerWireFee,
-    customerDepositFees,
+    coinContributions: coinContributions.map((x) => Amounts.stringify(x)),
+    paymentAmount: Amounts.stringify(contractData.amount),
+    customerWireFees: Amounts.stringify(customerWireFee),
+    customerDepositFees: Amounts.stringify(customerDepositFees),
   };
 }
 
@@ -183,8 +181,8 @@ async function getDenomSelStateFromBackup(
     denomPubHash: string;
     count: number;
   }[] = [];
-  let totalCoinValue = Amounts.getZero(currency);
-  let totalWithdrawCost = Amounts.getZero(currency);
+  let totalCoinValue = Amounts.zeroOfCurrency(currency);
+  let totalWithdrawCost = Amounts.zeroOfCurrency(currency);
   for (const s of sel) {
     const d = await tx.denominations.get([exchangeBaseUrl, s.denom_pub_hash]);
     checkBackupInvariant(!!d);
@@ -200,8 +198,8 @@ async function getDenomSelStateFromBackup(
   }
   return {
     selectedDenoms,
-    totalCoinValue,
-    totalWithdrawCost,
+    totalCoinValue: Amounts.stringify(totalCoinValue),
+    totalWithdrawCost: Amounts.stringify(totalCoinValue),
   };
 }
 
@@ -380,11 +378,11 @@ export async function importBackup(
           for (const fee of backupExchangeDetails.wire_fees) {
             const w = (wireInfo.feesForType[fee.wire_type] ??= []);
             w.push({
-              closingFee: Amounts.parseOrThrow(fee.closing_fee),
+              closingFee: Amounts.stringify(fee.closing_fee),
               endStamp: fee.end_stamp,
               sig: fee.sig,
               startStamp: fee.start_stamp,
-              wireFee: Amounts.parseOrThrow(fee.wire_fee),
+              wireFee: Amounts.stringify(fee.wire_fee),
             });
           }
           let tosAccepted = undefined;
@@ -412,9 +410,9 @@ export async function importBackup(
             tosCurrentEtag: backupExchangeDetails.tos_accepted_etag || "",
             tosAccepted,
             globalFees: backupExchangeDetails.global_fees.map((x) => ({
-              accountFee: Amounts.parseOrThrow(x.accountFee),
-              historyFee: Amounts.parseOrThrow(x.historyFee),
-              purseFee: Amounts.parseOrThrow(x.purseFee),
+              accountFee: Amounts.stringify(x.accountFee),
+              historyFee: Amounts.stringify(x.historyFee),
+              purseFee: Amounts.stringify(x.purseFee),
               endDate: x.endDate,
               historyTimeout: x.historyTimeout,
               signature: x.signature,
@@ -447,16 +445,10 @@ export async function importBackup(
               exchangeBaseUrl: backupExchangeDetails.base_url,
               exchangeMasterPub: backupExchangeDetails.master_public_key,
               fees: {
-                feeDeposit: Amounts.parseOrThrow(
-                  backupDenomination.fee_deposit,
-                ),
-                feeRefresh: Amounts.parseOrThrow(
-                  backupDenomination.fee_refresh,
-                ),
-                feeRefund: Amounts.parseOrThrow(backupDenomination.fee_refund),
-                feeWithdraw: Amounts.parseOrThrow(
-                  backupDenomination.fee_withdraw,
-                ),
+                feeDeposit: Amounts.stringify(backupDenomination.fee_deposit),
+                feeRefresh: Amounts.stringify(backupDenomination.fee_refresh),
+                feeRefund: Amounts.stringify(backupDenomination.fee_refund),
+                feeWithdraw: 
Amounts.stringify(backupDenomination.fee_withdraw),
               },
               isOffered: backupDenomination.is_offered,
               isRevoked: backupDenomination.is_revoked,
@@ -542,7 +534,7 @@ export async function importBackup(
         await tx.withdrawalGroups.put({
           withdrawalGroupId: backupWg.withdrawal_group_id,
           exchangeBaseUrl: backupWg.exchange_base_url,
-          instructedAmount,
+          instructedAmount: Amounts.stringify(instructedAmount),
           secretSeed: backupWg.secret_seed,
           denomsSel: await getDenomSelStateFromBackup(
             tx,
@@ -551,10 +543,10 @@ export async function importBackup(
             backupWg.selected_denoms,
           ),
           denomSelUid: backupWg.selected_denoms_uid,
-          rawWithdrawalAmount: Amounts.parseOrThrow(
+          rawWithdrawalAmount: Amounts.stringify(
             backupWg.raw_withdrawal_amount,
           ),
-          effectiveWithdrawalAmount: Amounts.parseOrThrow(
+          effectiveWithdrawalAmount: Amounts.stringify(
             backupWg.effective_withdrawal_amount,
           ),
           reservePriv: backupWg.reserve_priv,
@@ -618,10 +610,10 @@ export async function importBackup(
               coinPub: backupRefund.coin_pub,
               executionTime: backupRefund.execution_time,
               obtainedTime: backupRefund.obtained_time,
-              refundAmount: Amounts.parseOrThrow(backupRefund.refund_amount),
-              refundFee: denom.fees.feeRefund,
+              refundAmount: Amounts.stringify(backupRefund.refund_amount),
+              refundFee: Amounts.stringify(denom.fees.feeRefund),
               rtransactionId: backupRefund.rtransaction_id,
-              totalRefreshCostBound: Amounts.parseOrThrow(
+              totalRefreshCostBound: Amounts.stringify(
                 backupRefund.total_refresh_cost_bound,
               ),
             };
@@ -658,7 +650,7 @@ export async function importBackup(
           if (parsedContractTerms.max_wire_fee) {
             maxWireFee = 
Amounts.parseOrThrow(parsedContractTerms.max_wire_fee);
           } else {
-            maxWireFee = Amounts.getZero(amount.currency);
+            maxWireFee = Amounts.zeroOfCurrency(amount.currency);
           }
           const download: ProposalDownloadInfo = {
             contractTermsHash,
@@ -682,7 +674,7 @@ export async function importBackup(
                 backupPurchase.pay_info,
               ),
               payCoinSelectionUid: backupPurchase.pay_info.pay_coins_uid,
-              totalPayCost: Amounts.parseOrThrow(
+              totalPayCost: Amounts.stringify(
                 backupPurchase.pay_info.total_pay_cost,
               ),
             };
@@ -776,7 +768,7 @@ export async function importBackup(
                   count: x.count,
                   denomPubHash: x.denom_pub_hash,
                 })),
-                amountRefreshOutput: denomSel.totalCoinValue,
+                amountRefreshOutput: 
Amounts.stringify(denomSel.totalCoinValue),
               });
             } else {
               refreshSessionPerCoin.push(undefined);
@@ -797,11 +789,11 @@ export async function importBackup(
             operationStatus: backupRefreshGroup.timestamp_finish
               ? RefreshOperationStatus.Finished
               : RefreshOperationStatus.Pending,
-            inputPerCoin: backupRefreshGroup.old_coins.map((x) =>
-              Amounts.parseOrThrow(x.input_amount),
+            inputPerCoin: backupRefreshGroup.old_coins.map(
+              (x) => x.input_amount,
             ),
-            estimatedOutputPerCoin: backupRefreshGroup.old_coins.map((x) =>
-              Amounts.parseOrThrow(x.estimated_output_amount),
+            estimatedOutputPerCoin: backupRefreshGroup.old_coins.map(
+              (x) => x.estimated_output_amount,
             ),
             refreshSessionPerCoin,
           });
@@ -834,8 +826,8 @@ export async function importBackup(
             merchantTipId: backupTip.merchant_tip_id,
             pickedUpTimestamp: backupTip.timestamp_finished,
             secretSeed: backupTip.secret_seed,
-            tipAmountEffective: denomsSel.totalCoinValue,
-            tipAmountRaw,
+            tipAmountEffective: Amounts.stringify(denomsSel.totalCoinValue),
+            tipAmountRaw: Amounts.stringify(tipAmountRaw),
             tipExpiration: backupTip.timestamp_expiration,
             walletTipId: backupTip.wallet_tip_id,
             denomSelUid: backupTip.selected_denoms_uid,
diff --git a/packages/taler-wallet-core/src/operations/balance.ts 
b/packages/taler-wallet-core/src/operations/balance.ts
index 3db66b5d9..cd78b0360 100644
--- a/packages/taler-wallet-core/src/operations/balance.ts
+++ b/packages/taler-wallet-core/src/operations/balance.ts
@@ -57,9 +57,9 @@ export async function getBalancesInsideTransaction(
     const b = balanceStore[currency];
     if (!b) {
       balanceStore[currency] = {
-        available: Amounts.getZero(currency),
-        pendingIncoming: Amounts.getZero(currency),
-        pendingOutgoing: Amounts.getZero(currency),
+        available: Amounts.zeroOfCurrency(currency),
+        pendingIncoming: Amounts.zeroOfCurrency(currency),
+        pendingOutgoing: Amounts.zeroOfCurrency(currency),
       };
     }
     return balanceStore[currency];
@@ -85,7 +85,10 @@ export async function getBalancesInsideTransaction(
     for (let i = 0; i < r.oldCoinPubs.length; i++) {
       const session = r.refreshSessionPerCoin[i];
       if (session) {
-        const b = initBalance(session.amountRefreshOutput.currency);
+        const currency = Amounts.parseOrThrow(
+          session.amountRefreshOutput,
+        ).currency;
+        const b = initBalance(currency);
         // We are always assuming the refresh will succeed, thus we
         // report the output as available balance.
         b.available = Amounts.add(
@@ -93,7 +96,8 @@ export async function getBalancesInsideTransaction(
           session.amountRefreshOutput,
         ).amount;
       } else {
-        const b = initBalance(r.inputPerCoin[i].currency);
+        const currency = Amounts.parseOrThrow(r.inputPerCoin[i]).currency;
+        const b = initBalance(currency);
         b.available = Amounts.add(
           b.available,
           r.estimatedOutputPerCoin[i],
@@ -106,7 +110,7 @@ export async function getBalancesInsideTransaction(
     if (wds.timestampFinish) {
       return;
     }
-    const b = initBalance(wds.denomsSel.totalWithdrawCost.currency);
+    const b = initBalance(Amounts.currencyOf(wds.denomsSel.totalWithdrawCost));
     b.pendingIncoming = Amounts.add(
       b.pendingIncoming,
       wds.denomsSel.totalCoinValue,
diff --git a/packages/taler-wallet-core/src/operations/common.ts 
b/packages/taler-wallet-core/src/operations/common.ts
index f35556736..73d1ee4b0 100644
--- a/packages/taler-wallet-core/src/operations/common.ts
+++ b/packages/taler-wallet-core/src/operations/common.ts
@@ -160,7 +160,7 @@ export async function spendCoins(
       throw Error("not enough remaining balance on coin for payment");
     }
     refreshCoinPubs.push({
-      amount: remaining.amount,
+      amount: Amounts.stringify(remaining.amount),
       coinPub: coin.coinPub,
     });
     checkDbInvariant(!!coinAvailability);
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index b2bd18260..6ac4f3986 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -348,10 +348,10 @@ export async function prepareDepositGroup(
     auditors: contractData.allowedAuditors,
     exchanges: contractData.allowedExchanges,
     wireMethod: contractData.wireMethod,
-    contractTermsAmount: contractData.amount,
-    depositFeeLimit: contractData.maxDepositFee,
+    contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
+    depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
     wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
-    wireFeeLimit: contractData.maxWireFee,
+    wireFeeLimit: Amounts.parseOrThrow(contractData.maxWireFee),
     prevPayCoins: [],
   });
 
@@ -445,10 +445,10 @@ export async function createDepositGroup(
     auditors: contractData.allowedAuditors,
     exchanges: contractData.allowedExchanges,
     wireMethod: contractData.wireMethod,
-    contractTermsAmount: contractData.amount,
-    depositFeeLimit: contractData.maxDepositFee,
+    contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
+    depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
     wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
-    wireFeeLimit: contractData.maxWireFee,
+    wireFeeLimit: Amounts.parseOrThrow(contractData.maxWireFee),
     prevPayCoins: [],
   });
 
@@ -479,8 +479,8 @@ export async function createDepositGroup(
     depositedPerCoin: payCoinSel.coinPubs.map(() => false),
     merchantPriv: merchantPair.priv,
     merchantPub: merchantPair.pub,
-    totalPayCost: totalDepositCost,
-    effectiveDepositAmount,
+    totalPayCost: Amounts.stringify(totalDepositCost),
+    effectiveDepositAmount: Amounts.stringify(effectiveDepositAmount),
     wire: {
       payto_uri: req.depositPaytoUri,
       salt: wireSalt,
@@ -501,7 +501,9 @@ export async function createDepositGroup(
       await spendCoins(ws, tx, {
         allocationId: `txn:deposit:${depositGroup.depositGroupId}`,
         coinPubs: payCoinSel.coinPubs,
-        contributions: payCoinSel.coinContributions,
+        contributions: payCoinSel.coinContributions.map((x) =>
+          Amounts.parseOrThrow(x),
+        ),
         refreshReason: RefreshReason.PayDeposit,
       });
       await tx.depositGroups.put(depositGroup);
@@ -543,8 +545,8 @@ export async function getEffectiveDepositAmount(
         if (!denom) {
           throw Error("can't find denomination to calculate deposit amount");
         }
-        amt.push(pcs.coinContributions[i]);
-        fees.push(denom.feeDeposit);
+        amt.push(Amounts.parseOrThrow(pcs.coinContributions[i]));
+        fees.push(Amounts.parseOrThrow(denom.feeDeposit));
         exchangeSet.add(coin.exchangeBaseUrl);
       }
 
@@ -565,7 +567,7 @@ export async function getEffectiveDepositAmount(
           );
         })?.wireFee;
         if (fee) {
-          fees.push(fee);
+          fees.push(Amounts.parseOrThrow(fee));
         }
       }
     });
@@ -604,7 +606,7 @@ export async function getTotalFeesForDepositAmount(
         if (!denom) {
           throw Error("can't find denomination to calculate deposit amount");
         }
-        coinFee.push(denom.feeDeposit);
+        coinFee.push(Amounts.parseOrThrow(denom.feeDeposit));
         exchangeSet.add(coin.exchangeBaseUrl);
 
         const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
@@ -638,7 +640,7 @@ export async function getTotalFeesForDepositAmount(
           },
         )?.wireFee;
         if (fee) {
-          wireFee.push(fee);
+          wireFee.push(Amounts.parseOrThrow(fee));
         }
       }
     });
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index 23ff1479e..b6e2a9d73 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -98,10 +98,10 @@ function denominationRecordFromKeys(
     exchangeBaseUrl,
     exchangeMasterPub,
     fees: {
-      feeDeposit: Amounts.parseOrThrow(denomIn.fee_deposit),
-      feeRefresh: Amounts.parseOrThrow(denomIn.fee_refresh),
-      feeRefund: Amounts.parseOrThrow(denomIn.fee_refund),
-      feeWithdraw: Amounts.parseOrThrow(denomIn.fee_withdraw),
+      feeDeposit: Amounts.stringify(denomIn.fee_deposit),
+      feeRefresh: Amounts.stringify(denomIn.fee_refresh),
+      feeRefund: Amounts.stringify(denomIn.fee_refund),
+      feeWithdraw: Amounts.stringify(denomIn.fee_withdraw),
     },
     isOffered: true,
     isRevoked: false,
@@ -267,11 +267,11 @@ async function validateWireInfo(
       const startStamp = x.start_date;
       const endStamp = x.end_date;
       const fee: WireFee = {
-        closingFee: Amounts.parseOrThrow(x.closing_fee),
+        closingFee: Amounts.stringify(x.closing_fee),
         endStamp,
         sig: x.sig,
         startStamp,
-        wireFee: Amounts.parseOrThrow(x.wire_fee),
+        wireFee: Amounts.stringify(x.wire_fee),
       };
       let isValid = false;
       if (ws.insecureTrustExchange) {
@@ -321,9 +321,9 @@ async function validateGlobalFees(
       throw Error("exchange global fees signature invalid: " + gf.master_sig);
     }
     egf.push({
-      accountFee: Amounts.parseOrThrow(gf.account_fee),
-      historyFee: Amounts.parseOrThrow(gf.history_fee),
-      purseFee: Amounts.parseOrThrow(gf.purse_fee),
+      accountFee: Amounts.stringify(gf.account_fee),
+      historyFee: Amounts.stringify(gf.history_fee),
+      purseFee: Amounts.stringify(gf.purse_fee),
       startDate: gf.start_date,
       endDate: gf.end_date,
       signature: gf.master_sig,
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts 
b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index 2eb5b18e9..4483a57c0 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -182,10 +182,10 @@ export async function getTotalPaymentCost(
           DenominationRecord.toDenomInfo(denom),
           amountLeft,
         );
-        costs.push(pcs.coinContributions[i]);
+        costs.push(Amounts.parseOrThrow(pcs.coinContributions[i]));
         costs.push(refreshCost);
       }
-      const zero = Amounts.getZero(pcs.paymentAmount.currency);
+      const zero = Amounts.zeroOfAmount(pcs.paymentAmount);
       return Amounts.sum([zero, ...costs]).amount;
     });
 }
@@ -307,10 +307,10 @@ export function extractContractData(
   if (parsedContractTerms.max_wire_fee) {
     maxWireFee = Amounts.parseOrThrow(parsedContractTerms.max_wire_fee);
   } else {
-    maxWireFee = Amounts.getZero(amount.currency);
+    maxWireFee = Amounts.zeroOfCurrency(amount.currency);
   }
   return {
-    amount,
+    amount: Amounts.stringify(amount),
     contractTermsHash: contractTermsHash,
     fulfillmentUrl: parsedContractTerms.fulfillment_url ?? "",
     merchantBaseUrl: parsedContractTerms.merchant_base_url,
@@ -319,7 +319,7 @@ export function extractContractData(
     orderId: parsedContractTerms.order_id,
     summary: parsedContractTerms.summary,
     autoRefund: parsedContractTerms.auto_refund,
-    maxWireFee,
+    maxWireFee: Amounts.stringify(maxWireFee),
     payDeadline: parsedContractTerms.pay_deadline,
     refundDeadline: parsedContractTerms.refund_deadline,
     wireFeeAmortization: parsedContractTerms.wire_fee_amortization || 1,
@@ -334,7 +334,7 @@ export function extractContractData(
     timestamp: parsedContractTerms.timestamp,
     wireMethod: parsedContractTerms.wire_method,
     wireInfoHash: parsedContractTerms.h_wire,
-    maxDepositFee: Amounts.parseOrThrow(parsedContractTerms.max_fee),
+    maxDepositFee: Amounts.stringify(parsedContractTerms.max_fee),
     merchant: parsedContractTerms.merchant,
     products: parsedContractTerms.products,
     summaryI18n: parsedContractTerms.summary_i18n,
@@ -539,7 +539,7 @@ export async function processDownloadProposal(
       p.download = {
         contractTermsHash,
         contractTermsMerchantSig: contractData.merchantSig,
-        currency: contractData.amount.currency,
+        currency: Amounts.currencyOf(contractData.amount),
         fulfillmentUrl: contractData.fulfillmentUrl,
       };
       await tx.contractTerms.put({
@@ -825,9 +825,9 @@ async function handleInsufficientFunds(
         }
         prevPayCoins.push({
           coinPub,
-          contribution: contrib,
+          contribution: Amounts.parseOrThrow(contrib),
           exchangeBaseUrl: coin.exchangeBaseUrl,
-          feeDeposit: denom.fees.feeDeposit,
+          feeDeposit: Amounts.parseOrThrow(denom.fees.feeDeposit),
         });
       }
     });
@@ -836,10 +836,10 @@ async function handleInsufficientFunds(
     auditors: contractData.allowedAuditors,
     exchanges: contractData.allowedExchanges,
     wireMethod: contractData.wireMethod,
-    contractTermsAmount: contractData.amount,
-    depositFeeLimit: contractData.maxDepositFee,
+    contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
+    depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
     wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
-    wireFeeLimit: contractData.maxWireFee,
+    wireFeeLimit: Amounts.parseOrThrow(contractData.maxWireFee),
     prevPayCoins,
     requiredMinimumAge: contractData.minimumAge,
   });
@@ -875,7 +875,9 @@ async function handleInsufficientFunds(
       await spendCoins(ws, tx, {
         allocationId: `txn:proposal:${p.proposalId}`,
         coinPubs: payInfo.payCoinSelection.coinPubs,
-        contributions: payInfo.payCoinSelection.coinContributions,
+        contributions: payInfo.payCoinSelection.coinContributions.map((x) =>
+          Amounts.parseOrThrow(x),
+        ),
         refreshReason: RefreshReason.PayMerchant,
       });
     });
@@ -1068,7 +1070,7 @@ export function selectGreedy(
         wireFeesPerExchange,
         wireFeeAmortization,
         aci.exchangeBaseUrl,
-        aci.feeDeposit,
+        Amounts.parseOrThrow(aci.feeDeposit),
       );
 
       let coinSpend = Amounts.max(
@@ -1190,8 +1192,8 @@ export async function selectPayCoinsNew(
     amountPayRemaining: contractTermsAmount,
     amountWireFeeLimitRemaining: wireFeeLimit,
     amountDepositFeeLimitRemaining: depositFeeLimit,
-    customerDepositFees: Amounts.getZero(currency),
-    customerWireFees: Amounts.getZero(currency),
+    customerDepositFees: Amounts.zeroOfCurrency(currency),
+    customerWireFees: Amounts.zeroOfCurrency(currency),
     wireFeeCoveredForExchange: new Set(),
   };
 
@@ -1269,11 +1271,11 @@ export async function selectPayCoinsNew(
     });
 
   return {
-    paymentAmount: contractTermsAmount,
-    coinContributions,
+    paymentAmount: Amounts.stringify(contractTermsAmount),
+    coinContributions: coinContributions.map((x) => Amounts.stringify(x)),
     coinPubs,
-    customerDepositFees: tally.customerDepositFees,
-    customerWireFees: tally.customerWireFees,
+    customerDepositFees: Amounts.stringify(tally.customerDepositFees),
+    customerWireFees: Amounts.stringify(tally.customerWireFees),
   };
 }
 
@@ -1326,10 +1328,10 @@ export async function checkPaymentByProposalId(
     const res = await selectPayCoinsNew(ws, {
       auditors: contractData.allowedAuditors,
       exchanges: contractData.allowedExchanges,
-      contractTermsAmount: contractData.amount,
-      depositFeeLimit: contractData.maxDepositFee,
+      contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
+      depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
       wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
-      wireFeeLimit: contractData.maxWireFee,
+      wireFeeLimit: Amounts.parseOrThrow(contractData.maxWireFee),
       prevPayCoins: [],
       requiredMinimumAge: contractData.minimumAge,
       wireMethod: contractData.wireMethod,
@@ -1531,10 +1533,10 @@ export async function generateDepositPermissions(
       denomKeyType: denom.denomPub.cipher,
       denomSig: coin.denomSig,
       exchangeBaseUrl: coin.exchangeBaseUrl,
-      feeDeposit: denom.fees.feeDeposit,
+      feeDeposit: Amounts.parseOrThrow(denom.fees.feeDeposit),
       merchantPub: contractData.merchantPub,
       refundDeadline: contractData.refundDeadline,
-      spendAmount: payCoinSel.coinContributions[i],
+      spendAmount: Amounts.parseOrThrow(payCoinSel.coinContributions[i]),
       timestamp: contractData.timestamp,
       wireInfoHash,
       ageCommitmentProof: coin.ageCommitmentProof,
@@ -1684,10 +1686,10 @@ export async function confirmPay(
     auditors: contractData.allowedAuditors,
     exchanges: contractData.allowedExchanges,
     wireMethod: contractData.wireMethod,
-    contractTermsAmount: contractData.amount,
-    depositFeeLimit: contractData.maxDepositFee,
+    contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
+    depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
     wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
-    wireFeeLimit: contractData.maxWireFee,
+    wireFeeLimit: Amounts.parseOrThrow(contractData.maxWireFee),
     prevPayCoins: [],
     requiredMinimumAge: contractData.minimumAge,
     forcedSelection: forcedCoinSel,
@@ -1742,7 +1744,7 @@ export async function confirmPay(
           p.payInfo = {
             payCoinSelection: coinSelection,
             payCoinSelectionUid: encodeCrock(getRandomBytes(16)),
-            totalPayCost: payCostInfo,
+            totalPayCost: Amounts.stringify(payCostInfo),
           };
           p.lastSessionId = sessionId;
           p.timestampAccept = TalerProtocolTimestamp.now();
@@ -1751,7 +1753,9 @@ export async function confirmPay(
           await spendCoins(ws, tx, {
             allocationId: `txn:proposal:${p.proposalId}`,
             coinPubs: coinSelection.coinPubs,
-            contributions: coinSelection.coinContributions,
+            contributions: coinSelection.coinContributions.map((x) =>
+              Amounts.parseOrThrow(x),
+            ),
             refreshReason: RefreshReason.PayMerchant,
           });
           break;
@@ -2131,15 +2135,18 @@ async function applySuccessfulRefund(
     amountLeft,
   );
 
-  refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub, amount: amountLeft 
};
+  refreshCoinsMap[coin.coinPub] = {
+    coinPub: coin.coinPub,
+    amount: Amounts.stringify(amountLeft),
+  };
 
   p.refunds[refundKey] = {
     type: RefundState.Applied,
     obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
     executionTime: r.execution_time,
-    refundAmount: Amounts.parseOrThrow(r.refund_amount),
-    refundFee: denom.fees.feeRefund,
-    totalRefreshCostBound,
+    refundAmount: Amounts.stringify(r.refund_amount),
+    refundFee: Amounts.stringify(denom.fees.feeRefund),
+    totalRefreshCostBound: Amounts.stringify(totalRefreshCostBound),
     coinPub: r.coin_pub,
     rtransactionId: r.rtransaction_id,
   };
@@ -2189,9 +2196,9 @@ async function storePendingRefund(
     type: RefundState.Pending,
     obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
     executionTime: r.execution_time,
-    refundAmount: Amounts.parseOrThrow(r.refund_amount),
-    refundFee: denom.fees.feeRefund,
-    totalRefreshCostBound,
+    refundAmount: Amounts.stringify(r.refund_amount),
+    refundFee: Amounts.stringify(denom.fees.feeRefund),
+    totalRefreshCostBound: Amounts.stringify(totalRefreshCostBound),
     coinPub: r.coin_pub,
     rtransactionId: r.rtransaction_id,
   };
@@ -2241,9 +2248,9 @@ async function storeFailedRefund(
     type: RefundState.Failed,
     obtainedTime: TalerProtocolTimestamp.now(),
     executionTime: r.execution_time,
-    refundAmount: Amounts.parseOrThrow(r.refund_amount),
-    refundFee: denom.fees.feeRefund,
-    totalRefreshCostBound,
+    refundAmount: Amounts.stringify(r.refund_amount),
+    refundFee: Amounts.stringify(denom.fees.feeRefund),
+    totalRefreshCostBound: Amounts.stringify(totalRefreshCostBound),
     coinPub: r.coin_pub,
     rtransactionId: r.rtransaction_id,
   };
@@ -2274,13 +2281,13 @@ async function storeFailedRefund(
       let contrib: AmountJson | undefined;
       for (let i = 0; i < payCoinSelection.coinPubs.length; i++) {
         if (payCoinSelection.coinPubs[i] === r.coin_pub) {
-          contrib = payCoinSelection.coinContributions[i];
+          contrib = 
Amounts.parseOrThrow(payCoinSelection.coinContributions[i]);
         }
       }
       // FIXME: Is this case tested?!
       refreshCoinsMap[coin.coinPub] = {
         coinPub: coin.coinPub,
-        amount: amountLeft,
+        amount: Amounts.stringify(amountLeft),
       };
       await tx.coins.put(coin);
     }
@@ -2417,10 +2424,8 @@ async function calculateRefundSummary(
   p: PurchaseRecord,
 ): Promise<RefundSummary> {
   const download = await expectProposalDownload(ws, p);
-  let amountRefundGranted = Amounts.getZero(
-    download.contractData.amount.currency,
-  );
-  let amountRefundGone = 
Amounts.getZero(download.contractData.amount.currency);
+  let amountRefundGranted = Amounts.zeroOfAmount(download.contractData.amount);
+  let amountRefundGone = Amounts.zeroOfAmount(download.contractData.amount);
 
   let pendingAtExchange = false;
 
@@ -2454,7 +2459,7 @@ async function calculateRefundSummary(
     }
   });
   return {
-    amountEffectivePaid: payInfo.totalPayCost,
+    amountEffectivePaid: Amounts.parseOrThrow(payInfo.totalPayCost),
     amountRefundGone,
     amountRefundGranted,
     pendingAtExchange,
@@ -2598,7 +2603,7 @@ async function queryAndSaveAwaitingRefund(
   );
   if (!orderStatus.refunded) {
     // Wait for retry ...
-    return Amounts.getZero(download.contractData.amount.currency);
+    return Amounts.zeroOfAmount(download.contractData.amount);
   }
 
   const refundAwaiting = Amounts.sub(
@@ -2618,7 +2623,7 @@ async function queryAndSaveAwaitingRefund(
           logger.warn("purchase does not exist anymore");
           return;
         }
-        p.refundAmountAwaiting = refundAwaiting;
+        p.refundAmountAwaiting = Amounts.stringify(refundAwaiting);
         await tx.purchases.put(p);
       });
   }
diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts 
b/packages/taler-wallet-core/src/operations/pay-peer.ts
index 2eb6fe20d..b6acef2dc 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer.ts
@@ -158,8 +158,8 @@ export async function selectPeerCoins(
       }
       coinInfos.push({
         coinPub: coin.coinPub,
-        feeDeposit: denom.feeDeposit,
-        value: denom.value,
+        feeDeposit: Amounts.parseOrThrow(denom.feeDeposit),
+        value: Amounts.parseOrThrow(denom.value),
         denomPubHash: denom.denomPubHash,
         coinPriv: coin.coinPriv,
         denomSig: coin.denomSig,
@@ -175,8 +175,8 @@ export async function selectPeerCoins(
         -Amounts.cmp(o1.value, o2.value) ||
         strcmp(o1.denomPubHash, o2.denomPubHash),
     );
-    let amountAcc = Amounts.getZero(instructedAmount.currency);
-    let depositFeesAcc = Amounts.getZero(instructedAmount.currency);
+    let amountAcc = Amounts.zeroOfCurrency(instructedAmount.currency);
+    let depositFeesAcc = Amounts.zeroOfCurrency(instructedAmount.currency);
     const resCoins: {
       coinPub: string;
       coinPriv: string;
@@ -553,7 +553,7 @@ export async function acceptPeerPushPayment(
     mergeTimestamp: mergeTimestamp,
     purseAmount: Amounts.stringify(amount),
     purseExpiration: contractTerms.purse_expiration,
-    purseFee: Amounts.stringify(Amounts.getZero(amount.currency)),
+    purseFee: Amounts.stringify(Amounts.zeroOfCurrency(amount.currency)),
     pursePub: peerInc.pursePub,
     reservePayto,
     reservePriv: mergeReserveInfo.reservePriv,
@@ -796,7 +796,7 @@ export async function initiatePeerPullPayment(
   const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
 
   const purseFee = Amounts.stringify(
-    Amounts.getZero(Amounts.parseOrThrow(req.amount).currency),
+    Amounts.zeroOfCurrency(Amounts.parseOrThrow(req.amount).currency),
   );
 
   const sigRes = await ws.cryptoApi.signReservePurseCreate({
diff --git a/packages/taler-wallet-core/src/operations/recoup.ts 
b/packages/taler-wallet-core/src/operations/recoup.ts
index e92c805bd..4feb4430d 100644
--- a/packages/taler-wallet-core/src/operations/recoup.ts
+++ b/packages/taler-wallet-core/src/operations/recoup.ts
@@ -291,7 +291,7 @@ async function recoupRefreshCoin(
         ).amount;
         recoupGroup.scheduleRefreshCoins.push({
           coinPub: oldCoin.coinPub,
-          amount: residualAmount,
+          amount: Amounts.stringify(residualAmount),
         });
       }
       await tx.coins.put(revokedCoin);
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index ea0fae8bb..c2f0f0360 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -110,7 +110,7 @@ export function getTotalRefreshCost(
   const denomMap = Object.fromEntries(denoms.map((x) => [x.denomPubHash, x]));
   const withdrawDenoms = selectWithdrawalDenominations(withdrawAmount, denoms);
   const resultingAmount = Amounts.add(
-    Amounts.getZero(withdrawAmount.currency),
+    Amounts.zeroOfCurrency(withdrawAmount.currency),
     ...withdrawDenoms.selectedDenoms.map(
       (d) =>
         Amounts.mult(
@@ -273,7 +273,7 @@ async function refreshCreateSession(
           count: x.count,
           denomPubHash: x.denomPubHash,
         })),
-        amountRefreshOutput: newCoinDenoms.totalCoinValue,
+        amountRefreshOutput: Amounts.stringify(newCoinDenoms.totalCoinValue),
       };
       await tx.refreshGroups.put(rg);
     });
@@ -340,7 +340,7 @@ async function refreshMelt(
           denomPub: newDenom.denomPub,
           denomPubHash: newDenom.denomPubHash,
           feeWithdraw: newDenom.feeWithdraw,
-          value: newDenom.value,
+          value: Amounts.stringify(newDenom.value),
         });
       }
       return { newCoinDenoms, oldCoin, oldDenom, refreshGroup, refreshSession 
};
@@ -368,7 +368,7 @@ async function refreshMelt(
     meltCoinDenomPubHash: oldCoin.denomPubHash,
     meltCoinPriv: oldCoin.coinPriv,
     meltCoinPub: oldCoin.coinPub,
-    feeRefresh: oldDenom.feeRefresh,
+    feeRefresh: Amounts.parseOrThrow(oldDenom.feeRefresh),
     meltCoinMaxAge: oldCoin.maxAge,
     meltCoinAgeCommitmentProof: oldCoin.ageCommitmentProof,
     newCoinDenoms,
@@ -584,7 +584,7 @@ async function refreshReveal(
           denomPub: newDenom.denomPub,
           denomPubHash: newDenom.denomPubHash,
           feeWithdraw: newDenom.feeWithdraw,
-          value: newDenom.value,
+          value: Amounts.stringify(newDenom.value),
         });
       }
       return {
@@ -626,7 +626,7 @@ async function refreshReveal(
     meltCoinDenomPubHash: oldCoin.denomPubHash,
     meltCoinPriv: oldCoin.coinPriv,
     meltCoinPub: oldCoin.coinPub,
-    feeRefresh: oldDenom.feeRefresh,
+    feeRefresh: Amounts.parseOrThrow(oldDenom.feeRefresh),
     newCoinDenoms,
     meltCoinMaxAge: oldCoin.maxAge,
     meltCoinAgeCommitmentProof: oldCoin.ageCommitmentProof,
@@ -922,10 +922,14 @@ export async function createRefreshGroup(
         assertUnreachable(coin.status);
     }
     const refreshAmount = ocp.amount;
-    inputPerCoin.push(refreshAmount);
+    inputPerCoin.push(Amounts.parseOrThrow(refreshAmount));
     await tx.coins.put(coin);
     const denoms = await getDenoms(coin.exchangeBaseUrl);
-    const cost = getTotalRefreshCost(denoms, denom, refreshAmount);
+    const cost = getTotalRefreshCost(
+      denoms,
+      denom,
+      Amounts.parseOrThrow(refreshAmount),
+    );
     const output = Amounts.sub(refreshAmount, cost).amount;
     estimatedOutputPerCoin.push(output);
   }
@@ -934,13 +938,15 @@ export async function createRefreshGroup(
     operationStatus: RefreshOperationStatus.Pending,
     timestampFinished: undefined,
     statusPerCoin: oldCoinPubs.map(() => RefreshCoinStatus.Pending),
-    lastErrorPerCoin: {},
     oldCoinPubs: oldCoinPubs.map((x) => x.coinPub),
+    lastErrorPerCoin: {},
     reason,
     refreshGroupId,
     refreshSessionPerCoin: oldCoinPubs.map(() => undefined),
-    inputPerCoin,
-    estimatedOutputPerCoin,
+    inputPerCoin: inputPerCoin.map((x) => Amounts.stringify(x)),
+    estimatedOutputPerCoin: estimatedOutputPerCoin.map((x) =>
+      Amounts.stringify(x),
+    ),
     timestampCreated: TalerProtocolTimestamp.now(),
   };
 
@@ -1037,11 +1043,11 @@ export async function autoRefresh(
         if (AbsoluteTime.isExpired(executeThreshold)) {
           refreshCoins.push({
             coinPub: coin.coinPub,
-            amount: {
+            amount: Amounts.stringify({
               value: denom.amountVal,
               fraction: denom.amountFrac,
               currency: denom.currency,
-            },
+            }),
           });
         } else {
           const checkThreshold = getAutoRefreshCheckThreshold(denom);
diff --git a/packages/taler-wallet-core/src/operations/tip.ts 
b/packages/taler-wallet-core/src/operations/tip.ts
index f98d69e26..f9d20fa03 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -127,13 +127,13 @@ export async function prepareTip(
     const newTipRecord: TipRecord = {
       walletTipId: walletTipId,
       acceptedTimestamp: undefined,
-      tipAmountRaw: amount,
+      tipAmountRaw: Amounts.stringify(amount),
       tipExpiration: tipPickupStatus.expiration,
       exchangeBaseUrl: tipPickupStatus.exchange_url,
       merchantBaseUrl: res.merchantBaseUrl,
       createdTimestamp: TalerProtocolTimestamp.now(),
       merchantTipId: res.merchantTipId,
-      tipAmountEffective: selectedDenoms.totalCoinValue,
+      tipAmountEffective: Amounts.stringify(selectedDenoms.totalCoinValue),
       denomsSel: selectedDenoms,
       pickedUpTimestamp: undefined,
       secretSeed,
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index 54cb84926..fd0a343e5 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -26,6 +26,7 @@ import {
   Logger,
   OrderShortInfo,
   PaymentStatus,
+  PeerContractTerms,
   RefundInfoShort,
   TalerProtocolTimestamp,
   Transaction,
@@ -49,6 +50,8 @@ import {
   WithdrawalGroupRecord,
   WithdrawalRecordType,
   WalletContractData,
+  PeerPushPaymentInitiationStatus,
+  PeerPullPaymentIncomingStatus,
 } from "../db.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import { assertUnreachable } from "../util/assertUnreachable.js";
@@ -222,7 +225,7 @@ export async function getTransactionById(
         const contractData = download.contractData;
         const refunds = mergeRefundByExecutionTime(
           cleanRefunds,
-          Amounts.getZero(contractData.amount.currency),
+          Amounts.zeroOfAmount(contractData.amount),
         );
 
         const payOpId = RetryTags.forPay(purchase);
@@ -296,7 +299,7 @@ export async function getTransactionById(
         const contractData = download.contractData;
         const refunds = mergeRefundByExecutionTime(
           [theRefund],
-          Amounts.getZero(contractData.amount.currency),
+          Amounts.zeroOfAmount(contractData.amount),
         );
 
         return buildTransactionForRefund(
@@ -320,11 +323,13 @@ export async function getTransactionById(
   } else if (type === TransactionType.PeerPushDebit) {
     const pursePub = rest[0];
     return await ws.db
-      .mktx((x) => [x.peerPushPaymentInitiations])
+      .mktx((x) => [x.peerPushPaymentInitiations, x.contractTerms])
       .runReadWrite(async (tx) => {
         const debit = await tx.peerPushPaymentInitiations.get(pursePub);
         if (!debit) throw Error("not found");
-        return buildTransactionForPushPaymentDebit(debit);
+        const ct = await tx.contractTerms.get(debit.contractTermsHash);
+        checkDbInvariant(!!ct);
+        return buildTransactionForPushPaymentDebit(debit, ct.contractTermsRaw);
       });
   } else {
     const unknownTxType: never = type;
@@ -334,6 +339,7 @@ export async function getTransactionById(
 
 function buildTransactionForPushPaymentDebit(
   pi: PeerPushPaymentInitiationRecord,
+  contractTerms: PeerContractTerms,
   ort?: OperationRetryRecord,
 ): Transaction {
   return {
@@ -342,11 +348,11 @@ function buildTransactionForPushPaymentDebit(
     amountRaw: pi.amount,
     exchangeBaseUrl: pi.exchangeBaseUrl,
     info: {
-      expiration: pi.contractTerms.purse_expiration,
-      summary: pi.contractTerms.summary,
+      expiration: contractTerms.purse_expiration,
+      summary: contractTerms.summary,
     },
     frozen: false,
-    pending: !pi.purseCreated,
+    pending: pi.status != PeerPushPaymentInitiationStatus.PurseCreated,
     timestamp: pi.timestampCreated,
     talerUri: constructPayPushUri({
       exchangeBaseUrl: pi.exchangeBaseUrl,
@@ -586,7 +592,7 @@ function mergeRefundByExecutionTime(
       prev.set(key, {
         executionTime: refund.executionTime,
         amountAppliedEffective: effective,
-        amountAppliedRaw: raw,
+        amountAppliedRaw: Amounts.parseOrThrow(raw),
         firstTimestamp: refund.obtainedTime,
       });
     } else {
@@ -659,7 +665,7 @@ async function buildTransactionForPurchase(
   refundsInfo: MergedRefundInfo[],
   ort?: OperationRetryRecord,
 ): Promise<Transaction> {
-  const zero = Amounts.getZero(contractData.amount.currency);
+  const zero = Amounts.zeroOfAmount(contractData.amount);
 
   const info: OrderShortInfo = {
     merchant: contractData.merchant,
@@ -769,7 +775,11 @@ export async function getTransactions(
         if (shouldSkipSearch(transactionsRequest, [])) {
           return;
         }
-        transactions.push(buildTransactionForPushPaymentDebit(pi));
+        const ct = await tx.contractTerms.get(pi.contractTermsHash);
+        checkDbInvariant(!!ct);
+        transactions.push(
+          buildTransactionForPushPaymentDebit(pi, ct.contractTermsRaw),
+        );
       });
 
       tx.peerPullPaymentIncoming.iter().forEachAsync(async (pi) => {
@@ -780,7 +790,10 @@ export async function getTransactions(
         if (shouldSkipSearch(transactionsRequest, [])) {
           return;
         }
-        if (!pi.accepted) {
+        if (
+          pi.status !== PeerPullPaymentIncomingStatus.Accepted &&
+          pi.status !== PeerPullPaymentIncomingStatus.Paid
+        ) {
           return;
         }
 
@@ -791,7 +804,7 @@ export async function getTransactions(
         if (
           shouldSkipCurrency(
             transactionsRequest,
-            wsr.rawWithdrawalAmount.currency,
+            Amounts.currencyOf(wsr.rawWithdrawalAmount),
           )
         ) {
           return;
@@ -899,7 +912,7 @@ export async function getTransactions(
 
         const refunds = mergeRefundByExecutionTime(
           cleanRefunds,
-          Amounts.getZero(download.currency),
+          Amounts.zeroOfCurrency(download.currency),
         );
 
         refunds.forEach(async (refundInfo) => {
@@ -929,7 +942,7 @@ export async function getTransactions(
         if (
           shouldSkipCurrency(
             transactionsRequest,
-            tipRecord.tipAmountRaw.currency,
+            Amounts.parseOrThrow(tipRecord.tipAmountRaw).currency,
           )
         ) {
           return;
diff --git a/packages/taler-wallet-core/src/operations/withdraw.test.ts 
b/packages/taler-wallet-core/src/operations/withdraw.test.ts
index 70b4f73c0..c77f75b9d 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.test.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.test.ts
@@ -39,26 +39,26 @@ test("withdrawal selection bug repro", (t) => {
       exchangeBaseUrl: "https://exchange.demo.taler.net/";,
       exchangeMasterPub: "",
       fees: {
-        feeDeposit: {
+        feeDeposit: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefresh: {
+        }),
+        feeRefresh: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefund: {
+        }),
+        feeRefund: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeWithdraw: {
+        }),
+        feeWithdraw: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
+        }),
       },
       isOffered: true,
       isRevoked: false,
@@ -95,26 +95,26 @@ test("withdrawal selection bug repro", (t) => {
       exchangeBaseUrl: "https://exchange.demo.taler.net/";,
       exchangeMasterPub: "",
       fees: {
-        feeDeposit: {
+        feeDeposit: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefresh: {
+        }),
+        feeRefresh: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefund: {
+        }),
+        feeRefund: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeWithdraw: {
+        }),
+        feeWithdraw: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
+        }),
       },
       isOffered: true,
       isRevoked: false,
@@ -150,26 +150,26 @@ test("withdrawal selection bug repro", (t) => {
       exchangeBaseUrl: "https://exchange.demo.taler.net/";,
       exchangeMasterPub: "",
       fees: {
-        feeDeposit: {
+        feeDeposit: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefresh: {
+        }),
+        feeRefresh: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefund: {
+        }),
+        feeRefund: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeWithdraw: {
+        }),
+        feeWithdraw: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
+        }),
       },
       isOffered: true,
       isRevoked: false,
@@ -206,26 +206,26 @@ test("withdrawal selection bug repro", (t) => {
       exchangeBaseUrl: "https://exchange.demo.taler.net/";,
       exchangeMasterPub: "",
       fees: {
-        feeDeposit: {
+        feeDeposit: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefresh: {
+        }),
+        feeRefresh: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefund: {
+        }),
+        feeRefund: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeWithdraw: {
+        }),
+        feeWithdraw: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
+        }),
       },
       isOffered: true,
       isRevoked: false,
@@ -261,26 +261,26 @@ test("withdrawal selection bug repro", (t) => {
       exchangeBaseUrl: "https://exchange.demo.taler.net/";,
       exchangeMasterPub: "",
       fees: {
-        feeDeposit: {
+        feeDeposit: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefresh: {
+        }),
+        feeRefresh: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefund: {
+        }),
+        feeRefund: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeWithdraw: {
+        }),
+        feeWithdraw: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
+        }),
       },
       isOffered: true,
       isRevoked: false,
@@ -316,26 +316,26 @@ test("withdrawal selection bug repro", (t) => {
       exchangeBaseUrl: "https://exchange.demo.taler.net/";,
       exchangeMasterPub: "",
       fees: {
-        feeDeposit: {
+        feeDeposit: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefresh: {
+        }),
+        feeRefresh: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeRefund: {
+        }),
+        feeRefund: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
-        feeWithdraw: {
+        }),
+        feeWithdraw: Amounts.stringify({
           currency: "KUDOS",
           fraction: 1000000,
           value: 0,
-        },
+        }),
       },
       isOffered: true,
       isRevoked: false,
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index a9ecdf369..76bbec416 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -167,8 +167,8 @@ export function selectWithdrawalDenominations(
     denomPubHash: string;
   }[] = [];
 
-  let totalCoinValue = Amounts.getZero(amountAvailable.currency);
-  let totalWithdrawCost = Amounts.getZero(amountAvailable.currency);
+  let totalCoinValue = Amounts.zeroOfCurrency(amountAvailable.currency);
+  let totalWithdrawCost = Amounts.zeroOfCurrency(amountAvailable.currency);
 
   denoms = denoms.filter(isWithdrawableDenom);
   denoms.sort((d1, d2) =>
@@ -223,8 +223,8 @@ export function selectWithdrawalDenominations(
 
   return {
     selectedDenoms,
-    totalCoinValue,
-    totalWithdrawCost,
+    totalCoinValue: Amounts.stringify(totalCoinValue),
+    totalWithdrawCost: Amounts.stringify(totalCoinValue),
   };
 }
 
@@ -238,8 +238,8 @@ export function selectForcedWithdrawalDenominations(
     denomPubHash: string;
   }[] = [];
 
-  let totalCoinValue = Amounts.getZero(amountAvailable.currency);
-  let totalWithdrawCost = Amounts.getZero(amountAvailable.currency);
+  let totalCoinValue = Amounts.zeroOfCurrency(amountAvailable.currency);
+  let totalWithdrawCost = Amounts.zeroOfCurrency(amountAvailable.currency);
 
   denoms = denoms.filter(isWithdrawableDenom);
   denoms.sort((d1, d2) =>
@@ -279,8 +279,8 @@ export function selectForcedWithdrawalDenominations(
 
   return {
     selectedDenoms,
-    totalCoinValue,
-    totalWithdrawCost,
+    totalCoinValue: Amounts.stringify(totalCoinValue),
+    totalWithdrawCost: Amounts.stringify(totalWithdrawCost),
   };
 }
 
@@ -416,10 +416,10 @@ async function processPlanchetGenerate(
   checkDbInvariant(!!denom);
   const r = await ws.cryptoApi.createPlanchet({
     denomPub: denom.denomPub,
-    feeWithdraw: denom.feeWithdraw,
+    feeWithdraw: Amounts.parseOrThrow(denom.feeWithdraw),
     reservePriv: withdrawalGroup.reservePriv,
     reservePub: withdrawalGroup.reservePub,
-    value: denom.value,
+    value: Amounts.parseOrThrow(denom.value),
     coinIndex: coinIdx,
     secretSeed: withdrawalGroup.secretSeed,
     restrictAge: withdrawalGroup.restrictAge,
@@ -950,7 +950,7 @@ async function queryReserve(
         return;
       }
       wg.status = WithdrawalGroupStatus.Ready;
-      wg.reserveBalanceAmount = Amounts.parse(result.response.balance);
+      wg.reserveBalanceAmount = Amounts.stringify(result.response.balance);
       await tx.withdrawalGroups.put(wg);
     });
 
@@ -1427,7 +1427,7 @@ export async function getFundingPaytoUrisTx(
 export function augmentPaytoUrisForWithdrawal(
   plainPaytoUris: string[],
   reservePub: string,
-  instructedAmount: AmountJson,
+  instructedAmount: AmountLike,
 ): string[] {
   return plainPaytoUris.map((x) =>
     addPaytoQueryParams(x, {
@@ -1732,7 +1732,7 @@ export async function internalCreateWithdrawalGroup(
     denomSelUid,
     denomsSel: initialDenomSel,
     exchangeBaseUrl: canonExchange,
-    instructedAmount: amount,
+    instructedAmount: Amounts.stringify(amount),
     timestampStart: now,
     rawWithdrawalAmount: initialDenomSel.totalWithdrawCost,
     effectiveWithdrawalAmount: initialDenomSel.totalCoinValue,
diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts 
b/packages/taler-wallet-core/src/util/coinSelection.ts
index 12f87a920..cadf8d829 100644
--- a/packages/taler-wallet-core/src/util/coinSelection.ts
+++ b/packages/taler-wallet-core/src/util/coinSelection.ts
@@ -139,7 +139,7 @@ export function tallyFees(
 
   if (!tally.wireFeeCoveredForExchange.has(exchangeBaseUrl)) {
     const wf =
-      wireFeesPerExchange[exchangeBaseUrl] ?? Amounts.getZero(currency);
+      wireFeesPerExchange[exchangeBaseUrl] ?? Amounts.zeroOfCurrency(currency);
     const wfForgiven = Amounts.min(amountWireFeeLimitRemaining, wf);
     amountWireFeeLimitRemaining = Amounts.sub(
       amountWireFeeLimitRemaining,
diff --git a/packages/taler-wallet-core/src/util/denominations.test.ts 
b/packages/taler-wallet-core/src/util/denominations.test.ts
index 9c93331a3..551e06a33 100644
--- a/packages/taler-wallet-core/src/util/denominations.test.ts
+++ b/packages/taler-wallet-core/src/util/denominations.test.ts
@@ -25,6 +25,7 @@ import {
   FeeDescriptionPair,
   Amounts,
   DenominationInfo,
+  AmountString,
 } from "@gnu-taler/taler-util";
 // import { expect } from "chai";
 import {
@@ -37,8 +38,8 @@ import test, { ExecutionContext } from "ava";
 /**
  * Create some constants to be used as reference in the tests
  */
-const VALUES = Array.from({ length: 10 }).map((undef, t) =>
-  Amounts.parseOrThrow(`USD:${t}`),
+const VALUES: AmountString[] = Array.from({ length: 10 }).map(
+  (undef, t) => `USD:${t}`,
 );
 const TIMESTAMPS = Array.from({ length: 20 }).map((undef, t_s) => ({ t_s }));
 const ABS_TIME = TIMESTAMPS.map((m) => AbsoluteTime.fromTimestamp(m));
diff --git a/packages/taler-wallet-core/src/util/denominations.ts 
b/packages/taler-wallet-core/src/util/denominations.ts
index 9cd931acd..c05df6c6e 100644
--- a/packages/taler-wallet-core/src/util/denominations.ts
+++ b/packages/taler-wallet-core/src/util/denominations.ts
@@ -18,6 +18,7 @@ import {
   AbsoluteTime,
   AmountJson,
   Amounts,
+  AmountString,
   DenominationInfo,
   FeeDescription,
   FeeDescriptionPair,
@@ -51,7 +52,7 @@ export function selectBestForOverlappingDenominations<
   return minDeposit;
 }
 
-export function selectMinimumFee<T extends { fee: AmountJson }>(
+export function selectMinimumFee<T extends { fee: AmountString }>(
   list: T[],
 ): T | undefined {
   let minFee: T | undefined = undefined;
@@ -285,7 +286,7 @@ export function createTimeline<Type extends object>(
   idProp: PropsWithReturnType<Type, string>,
   periodStartProp: PropsWithReturnType<Type, TalerProtocolTimestamp>,
   periodEndProp: PropsWithReturnType<Type, TalerProtocolTimestamp>,
-  feeProp: PropsWithReturnType<Type, AmountJson>,
+  feeProp: PropsWithReturnType<Type, AmountString>,
   groupProp: PropsWithReturnType<Type, string> | undefined,
   selectBestForOverlapping: (l: Type[]) => Type | undefined,
 ): FeeDescription[] {
@@ -312,7 +313,7 @@ export function createTimeline<Type extends object>(
       }
       ps.push({
         type: "start",
-        fee,
+        fee: Amounts.stringify(fee),
         group,
         id,
         moment: AbsoluteTime.fromTimestamp(stampStart),
@@ -320,7 +321,7 @@ export function createTimeline<Type extends object>(
       });
       ps.push({
         type: "end",
-        fee,
+        fee: Amounts.stringify(fee),
         group,
         id,
         moment: AbsoluteTime.fromTimestamp(stampEnd),
@@ -416,7 +417,7 @@ export function createTimeline<Type extends object>(
           group: cursor.group,
           from: cursor.moment,
           until: AbsoluteTime.never(), //not yet known
-          fee: currentFee,
+          fee: Amounts.stringify(currentFee),
         });
       } else {
         prev.until = cursor.moment;
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index f800b68f8..9339b2f8e 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -771,7 +771,7 @@ async function getExchangeDetailedInfo(
     const feesByGroup = [
       ...infoForType.map((w) => ({
         ...w,
-        fee: w.closingFee,
+        fee: Amounts.stringify(w.closingFee),
         group: "closing",
       })),
       ...infoForType.map((w) => ({ ...w, fee: w.wireFee, group: "wire" })),
diff --git 
a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx
 
b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx
index 6f71b9d2e..1396d8707 100644
--- 
a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx
+++ 
b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx
@@ -33,11 +33,7 @@ export default {
 };
 
 const cd: WalletContractData = {
-  amount: {
-    currency: "ARS",
-    fraction: 0,
-    value: 2,
-  },
+  amount: "ARS:2",
   contractTermsHash:
     
"92X0KSJPZ8XS2XECCGFWTCGW8XMFCXTT2S6WHZDP6H9Y3TSKMTHY94WXEWDERTNN5XWCYGW4VN5CF2D4846HXTW7P06J4CZMHCWKC9G",
   fulfillmentUrl: "",
@@ -47,11 +43,7 @@ const cd: WalletContractData = {
     
"0YA1WETV15R6K8QKS79QA3QMT16010F42Q49VSKYQ71HVQKAG0A4ZJCA4YTKHE9EA5SP156TJSKZEJJJ87305N6PS80PC48RNKYZE08",
   orderId: "2022.220-0281XKKB8W7YE",
   summary: "w",
-  maxWireFee: {
-    currency: "ARS",
-    fraction: 0,
-    value: 1,
-  },
+  maxWireFee: "ARS:1",
   payDeadline: {
     t_s: 1660002673,
   },
@@ -77,11 +69,7 @@ const cd: WalletContractData = {
   wireMethod: "x-taler-bank",
   wireInfoHash:
     
"QDT28374ZHYJ59WQFZ3TW1D5WKJVDYHQT86VHED3TNMB15ANJSKXDYPPNX01348KDYCX6T4WXA5A8FJJ8YWNEB1JW726C1JPKHM89DR",
-  maxDepositFee: {
-    currency: "ARS",
-    fraction: 0,
-    value: 1,
-  },
+  maxDepositFee: "ARS:1",
   merchant: {
     name: "Default",
     address: {
diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts 
b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
index a70682d89..2693db79e 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts
@@ -99,7 +99,7 @@ export function useComponentState(
   const balance =
     bs.length > 0
       ? Amounts.parseOrThrow(bs[0].available)
-      : Amounts.getZero(currency);
+      : Amounts.zeroOfCurrency(currency);
 
   if (Amounts.isZero(balance)) {
     return {
@@ -157,12 +157,12 @@ export function useComponentState(
   const totalFee =
     fee !== undefined
       ? Amounts.sum([fee.wire, fee.coin, fee.refresh]).amount
-      : Amounts.getZero(currency);
+      : Amounts.zeroOfCurrency(currency);
 
   const totalToDeposit =
     parsedAmount && fee !== undefined
       ? Amounts.sub(parsedAmount, totalFee).amount
-      : Amounts.getZero(currency);
+      : Amounts.zeroOfCurrency(currency);
 
   const isDirty = amount !== initialValue;
   const amountError = !isDirty
diff --git 
a/packages/taler-wallet-webextension/src/wallet/DepositPage/stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/DepositPage/stories.tsx
index 64b2c91a7..af9c620cb 100644
--- a/packages/taler-wallet-webextension/src/wallet/DepositPage/stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/stories.tsx
@@ -76,7 +76,7 @@ export const WithNoAccountForIBAN = createExample(ReadyView, {
       return;
     },
   },
-  totalFee: Amounts.getZero("USD"),
+  totalFee: Amounts.zeroOfCurrency("USD"),
   totalToDeposit: Amounts.parseOrThrow("USD:10"),
   // onCalculateFee: alwaysReturnFeeToOne,
 });
@@ -111,7 +111,7 @@ export const WithIBANAccountTypeSelected = 
createExample(ReadyView, {
       return;
     },
   },
-  totalFee: Amounts.getZero("USD"),
+  totalFee: Amounts.zeroOfCurrency("USD"),
   totalToDeposit: Amounts.parseOrThrow("USD:10"),
   // onCalculateFee: alwaysReturnFeeToOne,
 });
@@ -146,7 +146,7 @@ export const NewBitcoinAccountTypeSelected = 
createExample(ReadyView, {
       return;
     },
   },
-  totalFee: Amounts.getZero("USD"),
+  totalFee: Amounts.zeroOfCurrency("USD"),
   totalToDeposit: Amounts.parseOrThrow("USD:10"),
   // onCalculateFee: alwaysReturnFeeToOne,
 });
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index d7b6e3b1c..9fff76442 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -1132,7 +1132,7 @@ export function PurchaseDetails({
   const partialFee = Amounts.sub(price.effective, price.raw).amount;
 
   const refundFee = !refund
-    ? Amounts.getZero(price.effective.currency)
+    ? Amounts.zeroOfCurrency(price.effective.currency)
     : Amounts.sub(refund.raw, refund.effective).amount;
 
   const fee = Amounts.sum([partialFee, refundFee]).amount;

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]