gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (14456bb94 -> d50294f76)


From: gnunet
Subject: [taler-wallet-core] branch master updated (14456bb94 -> d50294f76)
Date: Wed, 02 Nov 2022 18:23:20 +0100

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

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

    from 14456bb94 wallet-core: address DB FIXMEs
     new 6c3ef31d9 wallet-core: DB FIXMEs
     new d50294f76 wallet-core: DB FIXMEs (amount format)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 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               |  60 ++++++------
 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, 401 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 f7cb6e177..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,
     };
   }
@@ -526,7 +527,8 @@ export interface ExchangeRecord {
    * exchange advertises a different master public key and/or
    * currency.
    *
-   * FIXME: Use a rowId here?
+   * We could use a rowID here, but having the currency in the
+   * details pointer lets us do fewer DB queries
    */
   detailsPointer: ExchangeDetailsPointer | undefined;
 
@@ -751,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.
@@ -853,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.
@@ -887,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.
@@ -926,9 +928,9 @@ export interface WalletRefundItemCommon {
    */
   obtainedTime: TalerProtocolTimestamp;
 
-  refundAmount: AmountJson;
+  refundAmount: AmountString;
 
-  refundFee: AmountJson;
+  refundFee: AmountString;
 
   /**
    * Upper bound on the refresh cost incurred by
@@ -937,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;
 
@@ -1002,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;
@@ -1016,7 +1018,7 @@ export interface WalletContractData {
   timestamp: TalerProtocolTimestamp;
   wireMethod: string;
   wireInfoHash: string;
-  maxDepositFee: AmountJson;
+  maxDepositFee: AmountString;
   minimumAge?: number;
   deliveryDate: TalerProtocolTimestamp | undefined;
   deliveryLocation: Location | undefined;
@@ -1098,7 +1100,7 @@ export interface ProposalDownloadInfo {
 
 export interface PurchasePayInfo {
   payCoinSelection: PayCoinSelection;
-  totalPayCost: AmountJson;
+  totalPayCost: AmountString;
   payCoinSelectionUid: string;
 }
 
@@ -1215,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 {
@@ -1378,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
@@ -1386,7 +1388,7 @@ export interface WithdrawalGroupRecord {
    *
    * Useful for diagnostics.
    */
-  reserveBalanceAmount?: AmountJson;
+  reserveBalanceAmount?: AmountString;
 
   /**
    * Amount including fees (i.e. the amount subtracted from the
@@ -1395,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.
@@ -1403,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.
@@ -1586,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]