gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-wallet-webex] branch master updated: change protocol


From: gnunet
Subject: [GNUnet-SVN] [taler-wallet-webex] branch master updated: change protocol to string amount network format
Date: Mon, 29 Jan 2018 22:59:06 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 97f6e68c change protocol to string amount network format
97f6e68c is described below

commit 97f6e68ce3a515938228b9a4d3e41b5f4b25a015
Author: Florian Dold <address@hidden>
AuthorDate: Mon Jan 29 22:58:47 2018 +0100

    change protocol to string amount network format
---
 src/amounts.ts                             |  36 +++++-
 src/checkable.ts                           |  67 ++++++----
 src/crypto/cryptoWorker.ts                 |   4 +-
 src/dbTypes.ts                             |  34 ++---
 src/helpers.ts                             |   9 +-
 src/talerTypes.ts                          | 201 ++++++++++++++++-------------
 src/types-test.ts                          |  13 +-
 src/wallet.ts                              |  97 +++++++++-----
 src/walletTypes.ts                         |  14 +-
 src/webex/pages/confirm-contract.tsx       |   4 +-
 src/webex/pages/confirm-create-reserve.tsx |   2 +-
 src/webex/pages/refund.tsx                 |  21 ++-
 tsconfig.json                              |   1 +
 13 files changed, 320 insertions(+), 183 deletions(-)

diff --git a/src/amounts.ts b/src/amounts.ts
index a31bec3d..fafbcb3e 100644
--- a/src/amounts.ts
+++ b/src/amounts.ts
@@ -38,19 +38,19 @@ export class AmountJson {
   /**
    * Value, must be an integer.
    */
-  @Checkable.Number
+  @Checkable.Number()
   readonly value: number;
 
   /**
    * Fraction, must be an integer.  Represent 1/1e8 of a unit.
    */
-  @Checkable.Number
+  @Checkable.Number()
   readonly fraction: number;
 
   /**
    * Currency of the amount.
    */
-  @Checkable.String
+  @Checkable.String()
   readonly currency: string;
 
   /**
@@ -226,7 +226,7 @@ export function isNonZero(a: AmountJson): boolean {
  * Parse an amount like 'EUR:20.5' for 20 Euros and 50 ct.
  */
 export function parse(s: string): AmountJson|undefined {
-  const res = s.match(/([a-zA-Z0-9_*-]+):([0-9])+([.][0-9]+)?/);
+  const res = s.match(/([a-zA-Z0-9_*-]+):([0-9]+)([.][0-9]+)?/);
   if (!res) {
     return undefined;
   }
@@ -237,6 +237,14 @@ export function parse(s: string): AmountJson|undefined {
   };
 }
 
+export function parseOrThrow(s: string): AmountJson {
+  const res = parse(s);
+  if (!res) {
+    throw Error(`Can't parse amount: "${s}"`);
+  }
+  return res;
+}
+
 /**
  * Convert the amount to a float.
  */
@@ -255,3 +263,23 @@ export function fromFloat(floatVal: number, currency: 
string) {
     value: Math.floor(floatVal),
   };
 }
+
+/**
+ * Convert to standard human-readable string representation that's
+ * also used in JSON formats.
+ */
+export function toString(a: AmountJson) {
+  return `${a.currency}:${a.value + (a.fraction / fractionalBase)}`;
+}
+
+export function check(a: any) {
+  if (typeof a !== "string") {
+    return false;
+  }
+  try {
+    const parsedAmount = parse(a);
+    return !!parsedAmount;
+  } catch {
+    return false;
+  }
+}
diff --git a/src/checkable.ts b/src/checkable.ts
index 159e5a85..52eb5412 100644
--- a/src/checkable.ts
+++ b/src/checkable.ts
@@ -57,6 +57,7 @@ export namespace Checkable {
     elementChecker?: any;
     elementProp?: any;
     keyProp?: any;
+    stringChecker?: (s: string) => boolean;
     valueProp?: any;
     optional?: boolean;
     extraAllowed?: boolean;
@@ -109,6 +110,9 @@ export namespace Checkable {
     if (typeof target !== "string") {
       throw new SchemaError(`expected string for ${path}, got ${typeof target} 
instead`);
     }
+    if (prop.stringChecker && !prop.stringChecker(target)) {
+      throw new SchemaError(`string property ${path} malformed`);
+    }
     return target;
   }
 
@@ -316,7 +320,7 @@ export namespace Checkable {
   /**
    * Makes another annotation optional, for example 
address@hidden(Checkable.Number)`.
    */
-  export function Optional(type: any) {
+  export function Optional(type: (target: object, propertyKey: string | 
symbol) => void | any) {
     const stub = {};
     type(stub, "(optional-element)");
     const elementProp = getCheckableInfo(stub).props[0];
@@ -342,21 +346,27 @@ export namespace Checkable {
   /**
    * Target property must be a number.
    */
-  export function Number(target: object, propertyKey: string | symbol): void {
-    const chk = getCheckableInfo(target);
-    chk.props.push({checker: checkNumber, propertyKey});
+  export function Number(): (target: object, propertyKey: string | symbol) => 
void {
+    const deco = (target: object, propertyKey: string | symbol) => {
+      const chk = getCheckableInfo(target);
+      chk.props.push({checker: checkNumber, propertyKey});
+    };
+    return deco;
   }
 
 
   /**
    * Target property must be an arbitary object.
    */
-  export function AnyObject(target: object, propertyKey: string | symbol): 
void {
-    const chk = getCheckableInfo(target);
-    chk.props.push({
-      checker: checkAnyObject,
-      propertyKey,
-    });
+  export function AnyObject(): (target: object, propertyKey: string | symbol) 
=> void {
+    const deco = (target: object, propertyKey: string | symbol) => {
+      const chk = getCheckableInfo(target);
+      chk.props.push({
+        checker: checkAnyObject,
+        propertyKey,
+      });
+    };
+    return deco;
   }
 
 
@@ -366,29 +376,40 @@ export namespace Checkable {
    * Not useful by itself, but in combination with higher-order annotations
    * such as List or Map.
    */
-  export function Any(target: object, propertyKey: string | symbol): void {
-    const chk = getCheckableInfo(target);
-    chk.props.push({
-      checker: checkAny,
-      optional: true,
-      propertyKey,
-    });
+  export function Any(): (target: object, propertyKey: string | symbol) => 
void {
+    const deco = (target: object, propertyKey: string | symbol) => {
+      const chk = getCheckableInfo(target);
+      chk.props.push({
+        checker: checkAny,
+        optional: true,
+        propertyKey,
+      });
+    };
+    return deco;
   }
 
 
   /**
    * Target property must be a string.
    */
-  export function String(target: object, propertyKey: string | symbol): void {
-    const chk = getCheckableInfo(target);
-    chk.props.push({ checker: checkString, propertyKey });
+  export function String(
+    stringChecker?: (s: string) => boolean): (target: object, propertyKey: 
string | symbol,
+  ) => void {
+    const deco = (target: object, propertyKey: string | symbol) => {
+      const chk = getCheckableInfo(target);
+      chk.props.push({ checker: checkString, propertyKey, stringChecker });
+    };
+    return deco;
   }
 
   /**
    * Target property must be a boolean value.
    */
-  export function Boolean(target: object, propertyKey: string | symbol): void {
-    const chk = getCheckableInfo(target);
-    chk.props.push({ checker: checkBoolean, propertyKey });
+  export function Boolean(): (target: object, propertyKey: string | symbol) => 
void {
+    const deco = (target: object, propertyKey: string | symbol) => {
+      const chk = getCheckableInfo(target);
+      chk.props.push({ checker: checkBoolean, propertyKey });
+    };
+    return deco;
   }
 }
diff --git a/src/crypto/cryptoWorker.ts b/src/crypto/cryptoWorker.ts
index 6b82f6bc..88e30e55 100644
--- a/src/crypto/cryptoWorker.ts
+++ b/src/crypto/cryptoWorker.ts
@@ -282,7 +282,7 @@ namespace RpcFunctions {
     const feeList: AmountJson[] = cds.map((x) => x.denom.feeDeposit);
     let fees = Amounts.add(Amounts.getZero(feeList[0].currency), 
...feeList).amount;
     // okay if saturates
-    fees = Amounts.sub(fees, contractTerms.max_fee).amount;
+    fees = Amounts.sub(fees, 
Amounts.parseOrThrow(contractTerms.max_fee)).amount;
     const total = Amounts.add(fees, totalAmount).amount;
 
     const amountSpent = 
native.Amount.getZero(cds[0].coin.currentAmount.currency);
@@ -335,7 +335,7 @@ namespace RpcFunctions {
       const s: CoinPaySig = {
         coin_pub: cd.coin.coinPub,
         coin_sig: coinSig,
-        contribution: coinSpend.toJson(),
+        contribution: Amounts.toString(coinSpend.toJson()),
         denom_pub: cd.coin.denomPub,
         exchange_url: cd.denom.exchangeBaseUrl,
         ub_sig: cd.coin.denomSig,
diff --git a/src/dbTypes.ts b/src/dbTypes.ts
index 6c467ce7..6369cd92 100644
--- a/src/dbTypes.ts
+++ b/src/dbTypes.ts
@@ -49,7 +49,7 @@ import {
  * In the future we might consider adding migration functions for
  * each version increment.
  */
-export const WALLET_DB_VERSION = 25;
+export const WALLET_DB_VERSION = 26;
 
 
 /**
@@ -212,14 +212,14 @@ export class DenominationRecord {
   /**
    * The denomination public key.
    */
-  @Checkable.String
+  @Checkable.String()
   denomPub: string;
 
   /**
    * Hash of the denomination public key.
    * Stored in the database for faster lookups.
    */
-  @Checkable.String
+  @Checkable.String()
   denomPubHash: string;
 
   /**
@@ -249,38 +249,38 @@ export class DenominationRecord {
   /**
    * Validity start date of the denomination.
    */
-  @Checkable.String
+  @Checkable.String()
   stampStart: string;
 
   /**
    * Date after which the currency can't be withdrawn anymore.
    */
-  @Checkable.String
+  @Checkable.String()
   stampExpireWithdraw: string;
 
   /**
    * Date after the denomination officially doesn't exist anymore.
    */
-  @Checkable.String
+  @Checkable.String()
   stampExpireLegal: string;
 
   /**
    * Data after which coins of this denomination can't be deposited anymore.
    */
-  @Checkable.String
+  @Checkable.String()
   stampExpireDeposit: string;
 
   /**
    * Signature by the exchange's master key over the denomination
    * information.
    */
-  @Checkable.String
+  @Checkable.String()
   masterSig: string;
 
   /**
    * Did we verify the signature on the denomination?
    */
-  @Checkable.Number
+  @Checkable.Number()
   status: DenominationStatus;
 
   /**
@@ -288,13 +288,13 @@ export class DenominationRecord {
    * we checked?
    * Only false when the exchange redacts a previously published denomination.
    */
-  @Checkable.Boolean
+  @Checkable.Boolean()
   isOffered: boolean;
 
   /**
    * Base URL of the exchange.
    */
-  @Checkable.String
+  @Checkable.String()
   exchangeBaseUrl: string;
 
   /**
@@ -500,7 +500,7 @@ export class ProposalDownloadRecord {
   /**
    * URL where the proposal was downloaded.
    */
-  @Checkable.String
+  @Checkable.String()
   url: string;
 
   /**
@@ -512,32 +512,32 @@ export class ProposalDownloadRecord {
   /**
    * Signature by the merchant over the contract details.
    */
-  @Checkable.String
+  @Checkable.String()
   merchantSig: string;
 
   /**
    * Hash of the contract terms.
    */
-  @Checkable.String
+  @Checkable.String()
   contractTermsHash: string;
 
   /**
    * Serial ID when the offer is stored in the wallet DB.
    */
-  @Checkable.Optional(Checkable.Number)
+  @Checkable.Optional(Checkable.Number())
   id?: number;
 
   /**
    * Timestamp (in ms) of when the record
    * was created.
    */
-  @Checkable.Number
+  @Checkable.Number()
   timestamp: number;
 
   /**
    * Private key for the nonce.
    */
-  @Checkable.String
+  @Checkable.String()
   noncePriv: string;
 
   /**
diff --git a/src/helpers.ts b/src/helpers.ts
index 3b7cd36f..7cd17649 100644
--- a/src/helpers.ts
+++ b/src/helpers.ts
@@ -119,12 +119,19 @@ export function flatMap<T, U>(xs: T[], f: (x: T) => U[]): 
U[] {
  */
 export function getTalerStampSec(stamp: string): number | null {
   const m = stamp.match(/\/?Date\(([0-9]*)\)\/?/);
-  if (!m) {
+  if (!m || !m[1]) {
     return null;
   }
   return parseInt(m[1], 10);
 }
 
+/**
+ * Check if a timestamp is in the right format.
+ */
+export function timestampCheck(stamp: string): boolean {
+  return getTalerStampSec(stamp) !== null;
+}
+
 
 /**
  * Get a JavaScript Date object from a Taler date string.
diff --git a/src/talerTypes.ts b/src/talerTypes.ts
index 62e001a9..15e0009f 100644
--- a/src/talerTypes.ts
+++ b/src/talerTypes.ts
@@ -26,9 +26,12 @@
 /**
  * Imports.
  */
-import { AmountJson } from "./amounts";
 import { Checkable } from "./checkable";
 
+import * as Amounts from "./amounts";
+
+import { timestampCheck } from "./helpers";
+
 
 /**
  * Denomination as found in the /keys response from the exchange.
@@ -38,70 +41,70 @@ export class Denomination {
   /**
    * Value of one coin of the denomination.
    */
-  @Checkable.Value(() => AmountJson)
-  value: AmountJson;
+  @Checkable.String(Amounts.check)
+  value: string;
 
   /**
    * Public signing key of the denomination.
    */
-  @Checkable.String
+  @Checkable.String()
   denom_pub: string;
 
   /**
    * Fee for withdrawing.
    */
-  @Checkable.Value(() => AmountJson)
-  fee_withdraw: AmountJson;
+  @Checkable.String(Amounts.check)
+  fee_withdraw: string;
 
   /**
    * Fee for depositing.
    */
-  @Checkable.Value(() => AmountJson)
-  fee_deposit: AmountJson;
+  @Checkable.String(Amounts.check)
+  fee_deposit: string;
 
   /**
    * Fee for refreshing.
    */
-  @Checkable.Value(() => AmountJson)
-  fee_refresh: AmountJson;
+  @Checkable.String(Amounts.check)
+  fee_refresh: string;
 
   /**
    * Fee for refunding.
    */
-  @Checkable.Value(() => AmountJson)
-  fee_refund: AmountJson;
+  @Checkable.String(Amounts.check)
+  fee_refund: string;
 
   /**
    * Start date from which withdraw is allowed.
    */
-  @Checkable.String
+  @Checkable.String(timestampCheck)
   stamp_start: string;
 
   /**
    * End date for withdrawing.
    */
-  @Checkable.String
+  @Checkable.String(timestampCheck)
   stamp_expire_withdraw: string;
 
   /**
    * Expiration date after which the exchange can forget about
    * the currency.
    */
-  @Checkable.String
+  @Checkable.String(timestampCheck)
   stamp_expire_legal: string;
 
   /**
    * Date after which the coins of this denomination can't be
    * deposited anymore.
    */
-  @Checkable.String
+  @Checkable.String(timestampCheck)
   stamp_expire_deposit: string;
 
   /**
    * Signature over the denomination information by the exchange's master
    * signing key.
    */
-  @Checkable.String
+  @Checkable.String()
   master_sig: string;
 
   /**
@@ -120,13 +123,13 @@ export class AuditorDenomSig {
   /**
    * Denomination public key's hash.
    */
-  @Checkable.String
+  @Checkable.String()
   denom_pub_h: string;
 
   /**
    * The signature.
    */
-  @Checkable.String
+  @Checkable.String()
   auditor_sig: string;
 }
 
@@ -139,13 +142,13 @@ export class Auditor {
   /**
    * Auditor's public key.
    */
-  @Checkable.String
+  @Checkable.String()
   auditor_pub: string;
 
   /**
    * Base URL of the auditor.
    */
-  @Checkable.String
+  @Checkable.String()
   auditor_url: string;
 
   /**
@@ -197,20 +200,20 @@ export class PaybackConfirmation {
   /**
    * public key of the reserve that will receive the payback.
    */
-  @Checkable.String
+  @Checkable.String()
   reserve_pub: string;
 
   /**
    * How much will the exchange pay back (needed by wallet in
    * case coin was partially spent and wallet got restored from backup)
    */
-  @Checkable.Value(() => AmountJson)
-  amount: AmountJson;
+  @Checkable.String()
+  amount: string;
 
   /**
    * Time by which the exchange received the /payback request.
    */
-  @Checkable.String
+  @Checkable.String()
   timestamp: string;
 
   /**
@@ -220,7 +223,7 @@ export class PaybackConfirmation {
    * by the date specified (this allows the exchange delaying the transfer
    * a bit to aggregate additional payback requests into a larger one).
    */
-  @Checkable.String
+  @Checkable.String()
   exchange_sig: string;
 
   /**
@@ -229,7 +232,7 @@ export class PaybackConfirmation {
    * explicitly as the client might otherwise be confused by clock skew as to
    * which signing key was used.
    */
-  @Checkable.String
+  @Checkable.String()
   exchange_pub: string;
 
   /**
@@ -263,7 +266,7 @@ export interface CoinPaySig {
   /**
    * The amount that is subtracted from this coin with this payment.
    */
-  contribution: AmountJson;
+  contribution: string;
 
   /**
    * URL of the exchange this coin was withdrawn from.
@@ -281,13 +284,13 @@ export class ExchangeHandle {
   /**
    * Master public signing key of the exchange.
    */
-  @Checkable.String
+  @Checkable.String()
   master_pub: string;
 
   /**
    * Base URL of the exchange.
    */
-  @Checkable.String
+  @Checkable.String()
   url: string;
 
   /**
@@ -312,67 +315,67 @@ export class ContractTerms {
   /**
    * Hash of the merchant's wire details.
    */
-  @Checkable.String
+  @Checkable.String()
   H_wire: string;
 
   /**
    * Wire method the merchant wants to use.
    */
-  @Checkable.String
+  @Checkable.String()
   wire_method: string;
 
   /**
    * Human-readable short summary of the contract.
    */
-  @Checkable.Optional(Checkable.String)
+  @Checkable.Optional(Checkable.String())
   summary?: string;
 
   /**
    * Nonce used to ensure freshness.
    */
-  @Checkable.Optional(Checkable.String)
+  @Checkable.Optional(Checkable.String())
   nonce?: string;
 
   /**
    * Total amount payable.
    */
-  @Checkable.Value(() => AmountJson)
-  amount: AmountJson;
+  @Checkable.String(Amounts.check)
+  amount: string;
 
   /**
    * Auditors accepted by the merchant.
    */
-  @Checkable.List(Checkable.AnyObject)
+  @Checkable.List(Checkable.AnyObject())
   auditors: any[];
 
   /**
    * Deadline to pay for the contract.
    */
-  @Checkable.Optional(Checkable.String)
+  @Checkable.Optional(Checkable.String())
   pay_deadline: string;
 
   /**
    * Delivery locations.
    */
-  @Checkable.Any
+  @Checkable.Any()
   locations: any;
 
   /**
    * Maximum deposit fee covered by the merchant.
    */
-  @Checkable.Value(() => AmountJson)
-  max_fee: AmountJson;
+  @Checkable.String(Amounts.check)
+  max_fee: string;
 
   /**
    * Information about the merchant.
    */
-  @Checkable.Any
+  @Checkable.Any()
   merchant: any;
 
   /**
    * Public key of the merchant.
    */
-  @Checkable.String
+  @Checkable.String()
   merchant_pub: string;
 
   /**
@@ -384,57 +387,57 @@ export class ContractTerms {
   /**
    * Products that are sold in this contract.
    */
-  @Checkable.List(Checkable.AnyObject)
+  @Checkable.List(Checkable.AnyObject())
   products: any[];
 
   /**
    * Deadline for refunds.
    */
-  @Checkable.String
+  @Checkable.String(timestampCheck)
   refund_deadline: string;
 
   /**
    * Time when the contract was generated by the merchant.
    */
-  @Checkable.String
+  @Checkable.String(timestampCheck)
   timestamp: string;
 
   /**
    * Order id to uniquely identify the purchase within
    * one merchant instance.
    */
-  @Checkable.String
+  @Checkable.String()
   order_id: string;
 
   /**
    * URL to post the payment to.
    */
-  @Checkable.String
+  @Checkable.String()
   pay_url: string;
 
   /**
    * Fulfillment URL to view the product or
    * delivery status.
    */
-  @Checkable.String
+  @Checkable.String()
   fulfillment_url: string;
 
   /**
    * Share of the wire fee that must be settled with one payment.
    */
-  @Checkable.Optional(Checkable.Number)
+  @Checkable.Optional(Checkable.Number())
   wire_fee_amortization?: number;
 
   /**
    * Maximum wire fee that the merchant agrees to pay for.
    */
-  @Checkable.Optional(Checkable.Value(() => AmountJson))
-  max_wire_fee?: AmountJson;
+  @Checkable.Optional(Checkable.String())
+  max_wire_fee?: string;
 
   /**
    * Extra data, interpreted by the mechant only.
    */
-  @Checkable.Any
+  @Checkable.Any()
   extra: any;
 
   /**
@@ -480,31 +483,31 @@ export class MerchantRefundPermission {
   /**
    * Amount to be refunded.
    */
-  @Checkable.Value(() => AmountJson)
-  refund_amount: AmountJson;
+  @Checkable.String(Amounts.check)
+  refund_amount: string;
 
   /**
    * Fee for the refund.
    */
-  @Checkable.Value(() => AmountJson)
-  refund_fee: AmountJson;
+  @Checkable.String(Amounts.check)
+  refund_fee: string;
 
   /**
    * Public key of the coin being refunded.
    */
-  @Checkable.String
+  @Checkable.String()
   coin_pub: string;
 
   /**
    * Refund transaction ID between merchant and exchange.
    */
-  @Checkable.Number
+  @Checkable.Number()
   rtransaction_id: number;
 
   /**
    * Signature made by the merchant over the refund permission.
    */
-  @Checkable.String
+  @Checkable.String()
   merchant_sig: string;
 
   /**
@@ -523,13 +526,13 @@ export interface RefundRequest {
    * coin's total deposit value (including deposit fee);
    * must be larger than the refund fee.
    */
-  refund_amount: AmountJson;
+  refund_amount: string;
 
   /**
    * Refund fee associated with the given coin.
    * must be smaller than the refund amount.
    */
-  refund_fee: AmountJson;
+  refund_fee: string;
 
   /**
    * SHA-512 hash of the contact of the merchant with the customer.
@@ -566,14 +569,14 @@ export class MerchantRefundResponse {
   /**
    * Public key of the merchant
    */
-  @Checkable.String
+  @Checkable.String()
   merchant_pub: string;
 
   /**
    * Contract terms hash of the contract that
    * is being refunded.
    */
-  @Checkable.String
+  @Checkable.String()
   h_contract_terms: string;
 
   /**
@@ -629,7 +632,7 @@ export class ReserveSigSingleton {
   /**
    * Reserve signature.
    */
-  @Checkable.String
+  @Checkable.String()
   reserve_sig: string;
 
   /**
@@ -638,6 +641,30 @@ export class ReserveSigSingleton {
   static checked: (obj: any) => ReserveSigSingleton;
 }
 
+
+/**
+ * Response to /reserve/status
+ */
address@hidden()
+export class ReserveStatus {
+  /**
+   * Reserve signature.
+   */
+  @Checkable.String()
+  balance: string;
+
+  /**
+   * Reserve history, currently not used by the wallet.
+   */
+  @Checkable.Any()
+  history: any;
+
+  /**
+   * Create a ReserveSigSingleton from untyped JSON.
+   */
+  static checked: (obj: any) => ReserveStatus;
+}
+
 /**
  * Response of the merchant
  * to the TipPickupRequest.
@@ -647,7 +674,7 @@ export class TipResponse {
   /**
    * Public key of the reserve
    */
-  @Checkable.String
+  @Checkable.String()
   reserve_pub: string;
 
   /**
@@ -671,37 +698,37 @@ export class TipToken {
   /**
    * Expiration for the tip.
    */
-  @Checkable.String
+  @Checkable.String(timestampCheck)
   expiration: string;
 
   /**
    * URL of the exchange that the tip can be withdrawn from.
    */
-  @Checkable.String
+  @Checkable.String()
   exchange_url: string;
 
   /**
    * Merchant's URL to pick up the tip.
    */
-  @Checkable.String
+  @Checkable.String()
   pickup_url: string;
 
   /**
    * Merchant-chosen tip identifier.
    */
-  @Checkable.String
+  @Checkable.String()
   tip_id: string;
 
   /**
    * Amount of tip.
    */
-  @Checkable.Value(() => AmountJson)
-  amount: AmountJson;
+  @Checkable.String()
+  amount: string;
 
   /**
    * URL to navigate after finishing tip processing.
    */
-  @Checkable.String
+  @Checkable.String()
   next_url: string;
 
   /**
@@ -721,7 +748,7 @@ export class Payback {
   /**
    * The hash of the denomination public key for which the payback is offered.
    */
-  @Checkable.String
+  @Checkable.String()
   h_denom_pub: string;
 }
 
@@ -740,7 +767,7 @@ export class KeysJson {
   /**
    * The exchange's master public key.
    */
-  @Checkable.String
+  @Checkable.String()
   master_public_key: string;
 
   /**
@@ -752,7 +779,7 @@ export class KeysJson {
   /**
    * Timestamp when this response was issued.
    */
-  @Checkable.String
+  @Checkable.String(timestampCheck)
   list_issue_date: string;
 
   /**
@@ -765,13 +792,13 @@ export class KeysJson {
    * Short-lived signing keys used to sign online
    * responses.
    */
-  @Checkable.Any
+  @Checkable.Any()
   signkeys: any;
 
   /**
    * Protocol version.
    */
-  @Checkable.Optional(Checkable.String)
+  @Checkable.Optional(Checkable.String())
   version?: string;
 
   /**
@@ -790,31 +817,31 @@ export class WireFeesJson {
   /**
    * Cost of a wire transfer.
    */
-  @Checkable.Value(() => AmountJson)
-  wire_fee: AmountJson;
+  @Checkable.String(Amounts.check)
+  wire_fee: string;
 
   /**
    * Cost of clising a reserve.
    */
-  @Checkable.Value(() => AmountJson)
-  closing_fee: AmountJson;
+  @Checkable.String(Amounts.check)
+  closing_fee: string;
 
   /**
    * Signature made with the exchange's master key.
    */
-  @Checkable.String
+  @Checkable.String()
   sig: string;
 
   /**
    * Date from which the fee applies.
    */
-  @Checkable.String
+  @Checkable.String(timestampCheck)
   start_date: string;
 
   /**
    * Data after which the fee doesn't apply anymore.
    */
-  @Checkable.String
+  @Checkable.String(timestampCheck)
   end_date: string;
 
   /**
@@ -834,7 +861,7 @@ export class WireDetailJson {
   /**
    * Name of the wire transfer method.
    */
-  @Checkable.String
+  @Checkable.String()
   type: string;
 
   /**
@@ -872,7 +899,7 @@ export class Proposal {
   @Checkable.Value(() => ContractTerms)
   contract_terms: ContractTerms;
 
-  @Checkable.String
+  @Checkable.String()
   sig: string;
 
   /**
diff --git a/src/types-test.ts b/src/types-test.ts
index 097235a7..d65daead 100644
--- a/src/types-test.ts
+++ b/src/types-test.ts
@@ -54,14 +54,23 @@ test("amount subtraction (saturation)", (t) => {
 });
 
 
+test("amount parsing", (t) => {
+  const a1 = Amounts.parseOrThrow("TESTKUDOS:10");
+  t.is(a1.currency, "TESTKUDOS");
+  t.is(a1.value, 10);
+  t.is(a1.fraction, 0);
+  t.pass();
+});
+
+
 test("contract terms validation", (t) => {
   const c = {
     H_wire: "123",
-    amount: amt(1, 2, "EUR"),
+    amount: "EUR:1.5",
     auditors: [],
     exchanges: [{master_pub: "foo", url: "foo"}],
     fulfillment_url: "foo",
-    max_fee: amt(1, 2, "EUR"),
+    max_fee: "EUR:1.5",
     merchant_pub: "12345",
     order_id: "test_order",
     pay_deadline: "Date(12346)",
diff --git a/src/wallet.ts b/src/wallet.ts
index 34b2388e..c4308b8d 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -82,6 +82,7 @@ import {
   PaybackConfirmation,
   Proposal,
   RefundRequest,
+  ReserveStatus,
   TipPlanchetDetail,
   TipResponse,
   TipToken,
@@ -825,13 +826,22 @@ export class Wallet {
       return this.submitPay(purchase.contractTermsHash, sessionId);
     }
 
+    const contractAmount = Amounts.parseOrThrow(proposal.contractTerms.amount);
+
+    let wireFeeLimit;
+    if (!proposal.contractTerms.max_wire_fee) {
+      wireFeeLimit = Amounts.getZero(contractAmount.currency);
+    } else {
+      wireFeeLimit = Amounts.parseOrThrow(proposal.contractTerms.max_wire_fee);
+    }
+
     const res = await this.getCoinsForPayment({
       allowedAuditors: proposal.contractTerms.auditors,
       allowedExchanges: proposal.contractTerms.exchanges,
-      depositFeeLimit: proposal.contractTerms.max_fee,
-      paymentAmount: proposal.contractTerms.amount,
+      depositFeeLimit: Amounts.parseOrThrow(proposal.contractTerms.max_fee),
+      paymentAmount: Amounts.parseOrThrow(proposal.contractTerms.amount),
       wireFeeAmortization: proposal.contractTerms.wire_fee_amortization || 1,
-      wireFeeLimit: proposal.contractTerms.max_wire_fee || 
Amounts.getZero(proposal.contractTerms.amount.currency),
+      wireFeeLimit,
       wireFeeTime: getTalerStampSec(proposal.contractTerms.timestamp) || 0,
       wireMethod: proposal.contractTerms.wire_method,
     });
@@ -907,14 +917,23 @@ export class Wallet {
       return { status: "paid" };
     }
 
+    const paymentAmount = Amounts.parseOrThrow(proposal.contractTerms.amount);
+
+    let wireFeeLimit;
+    if (proposal.contractTerms.max_wire_fee) {
+      wireFeeLimit = Amounts.parseOrThrow(proposal.contractTerms.max_wire_fee);
+    } else {
+      wireFeeLimit = Amounts.getZero(paymentAmount.currency);
+    }
+
     // If not already payed, check if we could pay for it.
     const res = await this.getCoinsForPayment({
       allowedAuditors: proposal.contractTerms.auditors,
       allowedExchanges: proposal.contractTerms.exchanges,
-      depositFeeLimit: proposal.contractTerms.max_fee,
-      paymentAmount: proposal.contractTerms.amount,
+      depositFeeLimit: Amounts.parseOrThrow(proposal.contractTerms.max_fee),
+      paymentAmount,
       wireFeeAmortization: proposal.contractTerms.wire_fee_amortization || 1,
-      wireFeeLimit: proposal.contractTerms.max_wire_fee || 
Amounts.getZero(proposal.contractTerms.amount.currency),
+      wireFeeLimit,
       wireFeeTime: getTalerStampSec(proposal.contractTerms.timestamp) || 0,
       wireMethod: proposal.contractTerms.wire_method,
     });
@@ -1243,8 +1262,8 @@ export class Wallet {
                                        denom.value,
                                        denom.feeWithdraw).amount;
         const result = Amounts.sub(currentAmount,
-                                 denom.value,
-                                 denom.feeWithdraw);
+                                   denom.value,
+                                   denom.feeWithdraw);
         if (result.saturated) {
           console.error("can't create precoin, saturated");
           throw AbortTransaction;
@@ -1290,11 +1309,11 @@ export class Wallet {
     if (resp.status !== 200) {
       throw Error();
     }
-    const reserveInfo = JSON.parse(resp.responseText);
+    const reserveInfo = ReserveStatus.checked(JSON.parse(resp.responseText));
     if (!reserveInfo) {
       throw Error();
     }
-    reserve.current_amount = reserveInfo.balance;
+    reserve.current_amount = Amounts.parseOrThrow(reserveInfo.balance);
     await this.q()
               .put(Stores.reserves, reserve)
               .finish();
@@ -1613,7 +1632,7 @@ export class Wallet {
       exchangeInfo = {
         auditors: exchangeKeysJson.auditors,
         baseUrl,
-        currency: exchangeKeysJson.denoms[0].value.currency,
+        currency: 
Amounts.parseOrThrow(exchangeKeysJson.denoms[0].value).currency,
         lastUpdateTime: updateTimeSec,
         lastUsedTime: 0,
         masterPublicKey: exchangeKeysJson.master_public_key,
@@ -1670,11 +1689,11 @@ export class Wallet {
           continue;
         }
         const wf: WireFee = {
-          closingFee: fee.closing_fee,
+          closingFee: Amounts.parseOrThrow(fee.closing_fee),
           endStamp: end,
           sig: fee.sig,
           startStamp: start,
-          wireFee: fee.wire_fee,
+          wireFee: Amounts.parseOrThrow(fee.wire_fee),
         };
         const valid: boolean = await 
this.cryptoApi.isValidWireFee(detail.type, wf, exchangeInfo.masterPublicKey);
         if (!valid) {
@@ -1829,7 +1848,7 @@ export class Wallet {
         return balance;
       }
       for (const c of t.payReq.coins) {
-        addTo(balance, "pendingIncoming", c.contribution, c.exchange_url);
+        addTo(balance, "pendingIncoming", 
Amounts.parseOrThrow(c.contribution), c.exchange_url);
       }
       return balance;
     }
@@ -2180,10 +2199,17 @@ export class Wallet {
         type: "pay",
       });
       if (p.timestamp_refund) {
-        const amountsPending = Object.keys(p.refundsPending).map((x) => 
p.refundsPending[x].refund_amount);
-        const amountsDone = Object.keys(p.refundsDone).map((x) => 
p.refundsDone[x].refund_amount);
+        const contractAmount = Amounts.parseOrThrow(p.contractTerms.amount);
+        const amountsPending = (
+          Object.keys(p.refundsPending)
+                .map((x) => 
Amounts.parseOrThrow(p.refundsPending[x].refund_amount))
+        );
+        const amountsDone = (
+          Object.keys(p.refundsDone)
+                .map((x) => 
Amounts.parseOrThrow(p.refundsDone[x].refund_amount))
+        );
         const amounts: AmountJson[] = amountsPending.concat(amountsDone);
-        const amount = 
Amounts.add(Amounts.getZero(p.contractTerms.amount.currency), 
...amounts).amount;
+        const amount = Amounts.add(Amounts.getZero(contractAmount.currency), 
...amounts).amount;
 
         history.push({
           detail: {
@@ -2357,10 +2383,10 @@ export class Wallet {
       denomPub: denomIn.denom_pub,
       denomPubHash,
       exchangeBaseUrl,
-      feeDeposit: denomIn.fee_deposit,
-      feeRefresh: denomIn.fee_refresh,
-      feeRefund: denomIn.fee_refund,
-      feeWithdraw: denomIn.fee_withdraw,
+      feeDeposit: Amounts.parseOrThrow(denomIn.fee_deposit),
+      feeRefresh: Amounts.parseOrThrow(denomIn.fee_refresh),
+      feeRefund: Amounts.parseOrThrow(denomIn.fee_refund),
+      feeWithdraw: Amounts.parseOrThrow(denomIn.fee_withdraw),
       isOffered: true,
       masterSig: denomIn.master_sig,
       stampExpireDeposit: denomIn.stamp_expire_deposit,
@@ -2368,7 +2394,7 @@ export class Wallet {
       stampExpireWithdraw: denomIn.stamp_expire_withdraw,
       stampStart: denomIn.stamp_start,
       status: DenominationStatus.Unverified,
-      value: denomIn.value,
+      value: Amounts.parseOrThrow(denomIn.value),
     };
     return d;
   }
@@ -2449,13 +2475,13 @@ export class Wallet {
 
     const contractTerms: ContractTerms = {
       H_wire: wireHash,
-      amount: req.amount,
+      amount: Amounts.toString(req.amount),
       auditors: [],
       exchanges: [ { master_pub: exchange.masterPublicKey, url: 
exchange.baseUrl } ],
       extra: {},
       fulfillment_url: "",
       locations: [],
-      max_fee: req.amount,
+      max_fee: Amounts.toString(req.amount),
       merchant: {},
       merchant_pub: pub,
       order_id: "none",
@@ -2469,7 +2495,9 @@ export class Wallet {
 
     const contractTermsHash = await 
this.cryptoApi.hashString(canonicalJson(contractTerms));
 
-    const payCoinInfo = await this.cryptoApi.signDeposit(contractTerms, cds, 
contractTerms.amount);
+    const payCoinInfo = await (
+      this.cryptoApi.signDeposit(contractTerms, cds, 
Amounts.parseOrThrow(contractTerms.amount))
+    );
 
     console.log("pci", payCoinInfo);
 
@@ -2660,9 +2688,11 @@ export class Wallet {
           console.warn("coin not found, can't apply refund");
           return;
         }
+        const refundAmount = Amounts.parseOrThrow(perm.refund_amount);
+        const refundFee = Amounts.parseOrThrow(perm.refund_fee);
         c.status = CoinStatus.Dirty;
-        c.currentAmount = Amounts.add(c.currentAmount, 
perm.refund_amount).amount;
-        c.currentAmount = Amounts.sub(c.currentAmount, perm.refund_fee).amount;
+        c.currentAmount = Amounts.add(c.currentAmount, refundAmount).amount;
+        c.currentAmount = Amounts.sub(c.currentAmount, refundFee).amount;
 
         return c;
       };
@@ -2690,7 +2720,7 @@ export class Wallet {
     if (!coin0) {
       throw Error("coin not found");
     }
-    let feeAcc = Amounts.getZero(refundPermissions[0].refund_amount.currency);
+    let feeAcc = 
Amounts.getZero(Amounts.parseOrThrow(refundPermissions[0].refund_amount).currency);
 
     const denoms = await 
this.q().iterIndex(Stores.denominations.exchangeBaseUrlIndex, 
coin0.exchangeBaseUrl).toArray();
     for (const rp of refundPermissions) {
@@ -2706,8 +2736,10 @@ export class Wallet {
       // When it hasn't, the refresh cost is inaccurate.  To fix this,
       // we need introduce a flag to tell if a coin was refunded or
       // refreshed normally (and what about incremental refunds?)
-      const refreshCost = getTotalRefreshCost(denoms, denom, 
Amounts.sub(rp.refund_amount, rp.refund_fee).amount);
-      feeAcc = Amounts.add(feeAcc, refreshCost, rp.refund_fee).amount;
+      const refundAmount = Amounts.parseOrThrow(rp.refund_amount);
+      const refundFee = Amounts.parseOrThrow(rp.refund_fee);
+      const refreshCost = getTotalRefreshCost(denoms, denom, 
Amounts.sub(refundAmount, refundFee).amount);
+      feeAcc = Amounts.add(feeAcc, refreshCost, refundFee).amount;
     }
     return feeAcc;
   }
@@ -2731,14 +2763,15 @@ export class Wallet {
     if (tipRecord && tipRecord.pickedUp) {
       return tipRecord;
     }
+    const tipAmount = Amounts.parseOrThrow(tipToken.amount);
     await this.updateExchangeFromUrl(tipToken.exchange_url);
-    const denomsForWithdraw = await 
this.getVerifiedWithdrawDenomList(tipToken.exchange_url, tipToken.amount);
+    const denomsForWithdraw = await 
this.getVerifiedWithdrawDenomList(tipToken.exchange_url, tipAmount);
     const planchets = await Promise.all(denomsForWithdraw.map(d => 
this.cryptoApi.createTipPlanchet(d)));
     const coinPubs: string[] = planchets.map(x => x.coinPub);
     const now = (new Date()).getTime();
     tipRecord = {
       accepted: false,
-      amount: tipToken.amount,
+      amount: Amounts.parseOrThrow(tipToken.amount),
       coinPubs,
       deadline: deadlineSec,
       exchangeUrl: tipToken.exchange_url,
diff --git a/src/walletTypes.ts b/src/walletTypes.ts
index aba7dbfb..edcf6583 100644
--- a/src/walletTypes.ts
+++ b/src/walletTypes.ts
@@ -53,13 +53,13 @@ export class CreateReserveResponse {
    * Exchange URL where the bank should create the reserve.
    * The URL is canonicalized in the response.
    */
-  @Checkable.String
+  @Checkable.String()
   exchange: string;
 
   /**
    * Reserve public key of the newly created reserve.
    */
-  @Checkable.String
+  @Checkable.String()
   reservePub: string;
 
   /**
@@ -333,13 +333,13 @@ export class CreateReserveRequest {
   /**
    * Exchange URL where the bank should create the reserve.
    */
-  @Checkable.String
+  @Checkable.String()
   exchange: string;
 
   /**
    * Wire details for the bank account that sent the funds to the exchange.
    */
-  @Checkable.Optional(Checkable.Any)
+  @Checkable.Optional(Checkable.Any())
   senderWire?: object;
 
   /**
@@ -359,7 +359,7 @@ export class ConfirmReserveRequest {
    * Public key of then reserve that should be marked
    * as confirmed.
    */
-  @Checkable.String
+  @Checkable.String()
   reservePub: string;
 
   /**
@@ -384,14 +384,14 @@ export class ReturnCoinsRequest {
   /**
    * The exchange to take the coins from.
    */
-  @Checkable.String
+  @Checkable.String()
   exchange: string;
 
   /**
    * Wire details for the bank account of the customer that will
    * receive the funds.
    */
-  @Checkable.Any
+  @Checkable.Any()
   senderWire?: object;
 
   /**
diff --git a/src/webex/pages/confirm-contract.tsx 
b/src/webex/pages/confirm-contract.tsx
index 21f05d5d..78e90ee0 100644
--- a/src/webex/pages/confirm-contract.tsx
+++ b/src/webex/pages/confirm-contract.tsx
@@ -42,6 +42,8 @@ import * as ReactDOM from "react-dom";
 import URI = require("urijs");
 import { WalletApiError } from "../wxApi";
 
+import * as Amounts from "../../amounts";
+
 
 interface DetailState {
   collapsed: boolean;
@@ -294,7 +296,7 @@ class ContractPrompt extends 
React.Component<ContractPromptProps, ContractPrompt
     } else {
       merchantName = <strong>(pub: {c.merchant_pub})</strong>;
     }
-    const amount = <strong>{renderAmount(c.amount)}</strong>;
+    const amount = 
<strong>{renderAmount(Amounts.parseOrThrow(c.amount))}</strong>;
     console.log("payStatus", this.state.payStatus);
 
     let products = null;
diff --git a/src/webex/pages/confirm-create-reserve.tsx 
b/src/webex/pages/confirm-create-reserve.tsx
index bd21280c..d83c5666 100644
--- a/src/webex/pages/confirm-create-reserve.tsx
+++ b/src/webex/pages/confirm-create-reserve.tsx
@@ -38,11 +38,11 @@ import {
 
 import { ImplicitStateComponent, StateHolder } from "../components";
 import {
+  WalletApiError,
   createReserve,
   getCurrency,
   getExchangeInfo,
   getReserveCreationInfo,
-  WalletApiError,
 } from "../wxApi";
 
 import {
diff --git a/src/webex/pages/refund.tsx b/src/webex/pages/refund.tsx
index 8164eb66..b2f5948d 100644
--- a/src/webex/pages/refund.tsx
+++ b/src/webex/pages/refund.tsx
@@ -59,18 +59,24 @@ const RefundDetail = ({purchase, fullRefundFees}: 
RefundDetailProps) => {
   }
 
   const firstRefundKey = [...pendingKeys, ...doneKeys][0];
-  const currency = { ...purchase.refundsDone, ...purchase.refundsPending 
}[firstRefundKey].refund_amount.currency;
+  if (!firstRefundKey) {
+    return <p>Waiting for refunds ...</p>;
+  }
+  const allRefunds = { ...purchase.refundsDone, ...purchase.refundsPending };
+  const currency = 
Amounts.parseOrThrow(allRefunds[firstRefundKey].refund_amount).currency;
   if (!currency) {
     throw Error("invariant");
   }
 
   let amountPending = Amounts.getZero(currency);
   for (const k of pendingKeys) {
-    amountPending = Amounts.add(amountPending, 
purchase.refundsPending[k].refund_amount).amount;
+    const refundAmount = 
Amounts.parseOrThrow(purchase.refundsPending[k].refund_amount);
+    amountPending = Amounts.add(amountPending, refundAmount).amount;
   }
   let amountDone = Amounts.getZero(currency);
   for (const k of doneKeys) {
-    amountDone = Amounts.add(amountDone, 
purchase.refundsDone[k].refund_amount).amount;
+    const refundAmount = 
Amounts.parseOrThrow(purchase.refundsDone[k].refund_amount);
+    amountDone = Amounts.add(amountDone, refundAmount).amount;
   }
 
   const hasPending = amountPending.fraction !== 0 || amountPending.value !== 0;
@@ -130,7 +136,7 @@ class RefundStatusView extends 
React.Component<RefundStatusViewProps, RefundStat
           Status of purchase <strong>{summary}</strong> from merchant 
<strong>{merchantName}</strong>{" "}
           (order id {purchase.contractTerms.order_id}).
         </p>
-        <p>Total amount: <AmountDisplay amount={purchase.contractTerms.amount} 
/></p>
+        <p>Total amount: <AmountDisplay 
amount={Amounts.parseOrThrow(purchase.contractTerms.amount)} /></p>
         {purchase.finished
           ? <RefundDetail purchase={purchase} 
fullRefundFees={this.state.refundFees!} />
           : <p>Purchase not completed.</p>}
@@ -147,12 +153,15 @@ class RefundStatusView extends 
React.Component<RefundStatusViewProps, RefundStat
         return;
       }
       contractTermsHash = await wxApi.acceptRefund(refundUrl);
+      this.setState({ contractTermsHash });
     }
     const purchase = await wxApi.getPurchase(contractTermsHash);
     console.log("got purchase", purchase);
     const refundsDone = Object.keys(purchase.refundsDone).map((x) => 
purchase.refundsDone[x]);
-    const refundFees = await wxApi.getFullRefundFees( {refundPermissions: 
refundsDone });
-    this.setState({ purchase, gotResult: true, refundFees });
+    if (refundsDone.length) {
+      const refundFees = await wxApi.getFullRefundFees({ refundPermissions: 
refundsDone });
+      this.setState({ purchase, gotResult: true, refundFees });
+    }
   }
 }
 
diff --git a/tsconfig.json b/tsconfig.json
index ae77fb27..819c5427 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -54,6 +54,7 @@
     "src/walletTypes.ts",
     "src/webex/background.ts",
     "src/webex/chromeBadge.ts",
+    "src/webex/compat.ts",
     "src/webex/components.ts",
     "src/webex/messages.ts",
     "src/webex/notify.ts",

-- 
To stop receiving notification emails like this one, please contact
address@hidden



reply via email to

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