gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (32cd54e1 -> 5d23eb36)


From: gnunet
Subject: [taler-wallet-core] branch master updated (32cd54e1 -> 5d23eb36)
Date: Tue, 22 Mar 2022 21:16:46 +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 32cd54e1 fix: handle new optional parameter for settings_exchange_add 
page
     new f8d12f7b wallet: t_s/d_us migration
     new 5d23eb36 wallet: improve error handling and error codes

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:
 contrib/sample-data/history1.json                  | 402 ---------------------
 packages/anastasis-core/src/index.ts               |  28 +-
 packages/anastasis-core/src/reducer-types.ts       |  11 +-
 .../pages/home/BackupFinishedScreen.stories.tsx    |   4 +-
 .../src/pages/home/BackupFinishedScreen.tsx        |   4 +-
 .../src/pages/home/SecretEditorScreen.tsx          |   2 +-
 packages/taler-util/src/RequestThrottler.ts        |  10 +-
 packages/taler-util/src/ReserveTransaction.ts      |  96 ++---
 packages/taler-util/src/backupTypes.ts             |  74 ++--
 packages/taler-util/src/notifications.ts           |  26 +-
 packages/taler-util/src/taler-error-codes.ts       |  70 +++-
 packages/taler-util/src/talerTypes.ts              |  51 ++-
 packages/taler-util/src/time.ts                    | 356 ++++++++++--------
 packages/taler-util/src/transactionsTypes.ts       |   8 +-
 packages/taler-util/src/types-test.ts              |  16 +-
 packages/taler-util/src/walletTypes.ts             |  28 +-
 packages/taler-wallet-cli/src/harness/harness.ts   |  32 +-
 .../src/harness/merchantApiTypes.ts                |  12 +-
 packages/taler-wallet-cli/src/index.ts             |  21 +-
 .../src/integrationtests/test-bank-api.ts          |   5 +-
 .../src/integrationtests/test-denom-unoffered.ts   |  32 +-
 .../integrationtests/test-exchange-management.ts   |  18 +-
 .../integrationtests/test-exchange-timetravel.ts   |  24 +-
 .../src/integrationtests/test-libeufin-basic.ts    |   5 +-
 .../test-merchant-instances-delete.ts              |   2 +-
 .../src/integrationtests/test-pay-abort.ts         |   2 +-
 .../src/integrationtests/test-payment-claim.ts     |  30 +-
 .../src/integrationtests/test-payment-transient.ts |   8 +-
 .../src/integrationtests/test-refund-auto.ts       |  15 +-
 .../src/integrationtests/test-refund-gone.ts       |  10 +-
 .../src/integrationtests/test-wallet-dbless.ts     |   6 +-
 .../integrationtests/test-withdrawal-abort-bank.ts |   4 +-
 packages/taler-wallet-core/src/bank-api-client.ts  |  13 +-
 packages/taler-wallet-core/src/common.ts           |  11 +-
 .../src/crypto/workers/cryptoApi.ts                |   2 +-
 .../src/crypto/workers/cryptoImplementation.ts     |  16 +-
 packages/taler-wallet-core/src/db.ts               | 128 ++++---
 packages/taler-wallet-core/src/dbless.ts           |   9 +-
 packages/taler-wallet-core/src/errors.ts           | 216 +++++++----
 .../taler-wallet-core/src/headless/NodeHttpLib.ts  |  46 ++-
 .../src/operations/backup/export.ts                |  11 +-
 .../src/operations/backup/import.ts                |  10 +-
 .../src/operations/backup/index.ts                 |  50 +--
 .../taler-wallet-core/src/operations/deposits.ts   |  65 ++--
 .../taler-wallet-core/src/operations/exchanges.ts  |  88 +++--
 packages/taler-wallet-core/src/operations/pay.ts   | 133 ++++---
 .../taler-wallet-core/src/operations/pending.ts    |  42 ++-
 .../taler-wallet-core/src/operations/recoup.ts     |  12 +-
 .../taler-wallet-core/src/operations/refresh.ts    |  80 ++--
 .../taler-wallet-core/src/operations/refund.ts     |  47 ++-
 .../taler-wallet-core/src/operations/reserves.ts   |  32 +-
 .../taler-wallet-core/src/operations/testing.ts    |   4 +-
 packages/taler-wallet-core/src/operations/tip.ts   |  27 +-
 .../src/operations/transactions.ts                 |  29 +-
 .../src/operations/withdraw.test.ts                |  60 +--
 .../taler-wallet-core/src/operations/withdraw.ts   |  72 ++--
 packages/taler-wallet-core/src/pending-types.ts    |  31 +-
 packages/taler-wallet-core/src/util/http.ts        | 109 +++---
 packages/taler-wallet-core/src/util/retries.ts     |  26 +-
 packages/taler-wallet-core/src/wallet.ts           |  81 ++---
 packages/taler-wallet-embedded/src/index.ts        |   8 +-
 .../src/browserHttpLib.ts                          |  22 +-
 .../src/components/ErrorTalerOperation.tsx         |   6 +-
 .../src/components/PendingTransactions.stories.tsx |  62 +---
 .../src/components/PendingTransactions.tsx         |  13 +-
 .../src/components/Time.tsx                        |   4 +-
 .../src/components/TransactionItem.tsx             |  16 +-
 .../taler-wallet-webextension/src/cta/Deposit.tsx  |  14 +-
 packages/taler-wallet-webextension/src/cta/Pay.tsx |   4 +-
 .../src/cta/Tip.stories.tsx                        |   9 +-
 .../taler-wallet-webextension/src/cta/Withdraw.tsx |  13 +-
 .../src/hooks/useAsyncAsHook.ts                    |  30 +-
 .../src/popup/DeveloperPage.tsx                    |   2 +-
 .../src/serviceWorkerHttpLib.ts                    |  18 +-
 .../src/wallet/Backup.stories.tsx                  |  16 +-
 .../src/wallet/BackupPage.tsx                      |  18 +-
 .../src/wallet/History.stories.tsx                 |   7 +-
 .../src/wallet/History.tsx                         |   2 +-
 .../src/wallet/ProviderDetail.stories.tsx          |  26 +-
 .../src/wallet/ProviderDetailPage.tsx              |   5 +-
 .../src/wallet/Transaction.stories.tsx             |  13 +-
 .../src/wallet/Transaction.tsx                     |  20 +-
 packages/taler-wallet-webextension/src/wxApi.ts    |  73 +++-
 .../taler-wallet-webextension/src/wxBackend.ts     |  11 +-
 84 files changed, 1552 insertions(+), 1722 deletions(-)
 delete mode 100644 contrib/sample-data/history1.json

diff --git a/contrib/sample-data/history1.json 
b/contrib/sample-data/history1.json
deleted file mode 100644
index 8d0076a3..00000000
--- a/contrib/sample-data/history1.json
+++ /dev/null
@@ -1,402 +0,0 @@
-{
-  "history": [
-    {
-      "type": "exchange-added",
-      "builtIn": false,
-      "eventId": "exchange-added;https%3A%2F%2Fexchange.demo.taler.net%2F",
-      "exchangeBaseUrl": "https://exchange.demo.taler.net/";,
-      "timestamp": {
-        "t_ms": 1578334008633
-      }
-    },
-    {
-      "type": "exchange-updated",
-      "eventId": "exchange-updated;https%3A%2F%2Fexchange.demo.taler.net%2F",
-      "exchangeBaseUrl": "https://exchange.demo.taler.net/";,
-      "timestamp": {
-        "t_ms": 1578334009266
-      }
-    },
-    {
-      "type": "reserve-balance-updated",
-      "eventId": 
"reserve-balance-updated;HHG1KBFSW4PM8J43D14GVJYB8F5J56RDHANY1EQSW6RTYDAQJC6G",
-      "amountExpected": "KUDOS:5",
-      "amountReserveBalance": "KUDOS:5",
-      "timestamp": {
-        "t_ms": 1578334039291
-      },
-      "newHistoryTransactions": [
-        {
-          "amount": "KUDOS:5",
-          "sender_account_url": "payto://x-taler-bank/bank.demo.taler.net/65",
-          "timestamp": {
-            "t_ms": 1578334028000
-          },
-          "wire_reference": "000000000038Y",
-          "type": "DEPOSIT"
-        }
-      ],
-      "reserveShortInfo": {
-        "exchangeBaseUrl": "https://exchange.demo.taler.net/";,
-        "reserveCreationDetail": {
-          "type": "taler-bank-withdraw",
-          "bankUrl": 
"https://bank.demo.taler.net/api/withdraw-operation/6fd6a78f-3d12-4c91-b5e4-c6fc31f44e8d";
-        },
-        "reservePub": "JPE7VR8R985WQ7ZX3EEYRTEGJQ1FAFE7P3JK1J7WFJEP7AGNTJD0"
-      }
-    },
-    {
-      "type": "withdrawn",
-      "withdrawSessionId": 
"SFW3JS0JV0GZQQ1W07TNQEAGBD84X2QMH38PJ2CCTTKSDKQFCBY0",
-      "eventId": 
"withdrawn;SFW3JS0JV0GZQQ1W07TNQEAGBD84X2QMH38PJ2CCTTKSDKQFCBY0",
-      "amountWithdrawnEffective": "KUDOS:4.8",
-      "amountWithdrawnRaw": "KUDOS:5",
-      "exchangeBaseUrl": "https://exchange.demo.taler.net/";,
-      "timestamp": {
-        "t_ms": 1578334039853
-      },
-      "withdrawalSource": {
-        "type": "reserve",
-        "reservePub": "JPE7VR8R985WQ7ZX3EEYRTEGJQ1FAFE7P3JK1J7WFJEP7AGNTJD0"
-      }
-    },
-    {
-      "type": "order-accepted",
-      "eventId": 
"order-accepted;RNFEQ2FHF6NPM5M58HJH635TD4CE9S2SRNK3VN9SCMH3H7H0REBG",
-      "orderShortInfo": {
-        "amount": "KUDOS:0.5",
-        "orderId": "2020.006-G1NT65XRPQ8GP",
-        "merchantBaseUrl": 
"https://backend.demo.taler.net/public/instances/FSF/";,
-        "proposalId": "RNFEQ2FHF6NPM5M58HJH635TD4CE9S2SRNK3VN9SCMH3H7H0REBG",
-        "summary": "Essay: 2. The GNU Project"
-      },
-      "timestamp": {
-        "t_ms": 1578334078823
-      }
-    },
-    {
-      "type": "order-redirected",
-      "eventId": 
"order-redirected;0W4EBHQJ90XX4TSQ9C0M6K9MBFJ1ENKTWH4R3CXFT986A2QHCESG",
-      "alreadyPaidOrderShortInfo": {
-        "amount": "KUDOS:0.5",
-        "orderId": "2020.006-G1NT65XRPQ8GP",
-        "merchantBaseUrl": 
"https://backend.demo.taler.net/public/instances/FSF/";,
-        "proposalId": "RNFEQ2FHF6NPM5M58HJH635TD4CE9S2SRNK3VN9SCMH3H7H0REBG",
-        "summary": "Essay: 2. The GNU Project"
-      },
-      "newOrderShortInfo": {
-        "amount": "KUDOS:0.5",
-        "orderId": "2020.006-00W4ANVVKAHAP",
-        "merchantBaseUrl": 
"https://backend.demo.taler.net/public/instances/FSF/";,
-        "proposalId": "0W4EBHQJ90XX4TSQ9C0M6K9MBFJ1ENKTWH4R3CXFT986A2QHCESG",
-        "summary": "Essay: 2. The GNU Project"
-      },
-      "timestamp": {
-        "t_ms": 1578334108380
-      }
-    },
-    {
-      "type": "payment-sent",
-      "eventId": 
"payment-sent;RNFEQ2FHF6NPM5M58HJH635TD4CE9S2SRNK3VN9SCMH3H7H0REBG",
-      "orderShortInfo": {
-        "amount": "KUDOS:0.5",
-        "orderId": "2020.006-G1NT65XRPQ8GP",
-        "merchantBaseUrl": 
"https://backend.demo.taler.net/public/instances/FSF/";,
-        "proposalId": "RNFEQ2FHF6NPM5M58HJH635TD4CE9S2SRNK3VN9SCMH3H7H0REBG",
-        "summary": "Essay: 2. The GNU Project"
-      },
-      "replay": true,
-      "sessionId": "ab48396f-3aa1-4e1f-bfb5-30852d1e0d5e",
-      "timestamp": {
-        "t_ms": 1578334108677
-      },
-      "numCoins": 6,
-      "amountPaidWithFees": "KUDOS:0.54"
-    },
-    {
-      "type": "exchange-added",
-      "builtIn": false,
-      "eventId": "exchange-added;https%3A%2F%2Fexchange.test.taler.net%2F",
-      "exchangeBaseUrl": "https://exchange.test.taler.net/";,
-      "timestamp": {
-        "t_ms": 1578334134741
-      }
-    },
-    {
-      "type": "exchange-updated",
-      "eventId": "exchange-updated;https%3A%2F%2Fexchange.test.taler.net%2F",
-      "exchangeBaseUrl": "https://exchange.test.taler.net/";,
-      "timestamp": {
-        "t_ms": 1578334135451
-      }
-    },
-    {
-      "type": "reserve-balance-updated",
-      "eventId": 
"reserve-balance-updated;498DDH4ZB41QX45FH38T4Y8JM14WX8Q2J1VKKZTE0CMS6TCPYZAG",
-      "amountExpected": "TESTKUDOS:5",
-      "amountReserveBalance": "TESTKUDOS:5",
-      "timestamp": {
-        "t_ms": 1578334141843
-      },
-      "newHistoryTransactions": [
-        {
-          "amount": "TESTKUDOS:5",
-          "sender_account_url": "payto://x-taler-bank/bank.test.taler.net/9",
-          "timestamp": {
-            "t_ms": 1578334138000
-          },
-          "wire_reference": "0000000000184",
-          "type": "DEPOSIT"
-        }
-      ],
-      "reserveShortInfo": {
-        "exchangeBaseUrl": "https://exchange.test.taler.net/";,
-        "reserveCreationDetail": {
-          "type": "taler-bank-withdraw",
-          "bankUrl": 
"https://bank.test.taler.net/api/withdraw-operation/e6210f62-d27b-4f58-815c-c5160de8804c";
-        },
-        "reservePub": "ZQ2N7V8M035HAD1HTW7ZX22NM9GAXDCGX6GSJECD2KEY9TN3C0V0"
-      }
-    },
-    {
-      "type": "withdrawn",
-      "withdrawSessionId": 
"AAVX0GVZ8GRPYX2RWANQ9J279ABA7KNFYEQ3A0C63TW7NMV0GAT0",
-      "eventId": 
"withdrawn;AAVX0GVZ8GRPYX2RWANQ9J279ABA7KNFYEQ3A0C63TW7NMV0GAT0",
-      "amountWithdrawnEffective": "TESTKUDOS:5",
-      "amountWithdrawnRaw": "TESTKUDOS:5",
-      "exchangeBaseUrl": "https://exchange.test.taler.net/";,
-      "timestamp": {
-        "t_ms": 1578334142432
-      },
-      "withdrawalSource": {
-        "type": "reserve",
-        "reservePub": "ZQ2N7V8M035HAD1HTW7ZX22NM9GAXDCGX6GSJECD2KEY9TN3C0V0"
-      }
-    },
-    {
-      "type": "refreshed",
-      "refreshGroupId": "2TARBASBNCE0X7F0D89Z3TJGPXKRARFSBH3HKZ5JFQRKPV9CA5C0",
-      "eventId": 
"refreshed;2TARBASBNCE0X7F0D89Z3TJGPXKRARFSBH3HKZ5JFQRKPV9CA5C0",
-      "timestamp": {
-        "t_ms": 1578334142528
-      },
-      "refreshReason": "pay",
-      "amountRefreshedEffective": "KUDOS:0",
-      "amountRefreshedRaw": "KUDOS:0.06",
-      "numInputCoins": 6,
-      "numOutputCoins": 0,
-      "numRefreshedInputCoins": 0
-    },
-    {
-      "type": "order-accepted",
-      "eventId": 
"order-accepted;W39MQT31M1ZY3NPF9ZSGXM8Q1K5XS5D5R1J10ZSHBREC6EZ570F0",
-      "orderShortInfo": {
-        "amount": "TESTKUDOS:1",
-        "orderId": "2020.006-00GBW7AD1VFRW",
-        "merchantBaseUrl": 
"https://backend.test.taler.net/public/instances/GNUnet/";,
-        "proposalId": "W39MQT31M1ZY3NPF9ZSGXM8Q1K5XS5D5R1J10ZSHBREC6EZ570F0",
-        "summary": "Donation to GNUnet"
-      },
-      "timestamp": {
-        "t_ms": 1578334230099
-      }
-    },
-    {
-      "type": "payment-sent",
-      "eventId": 
"payment-sent;W39MQT31M1ZY3NPF9ZSGXM8Q1K5XS5D5R1J10ZSHBREC6EZ570F0",
-      "orderShortInfo": {
-        "amount": "TESTKUDOS:1",
-        "orderId": "2020.006-00GBW7AD1VFRW",
-        "merchantBaseUrl": 
"https://backend.test.taler.net/public/instances/GNUnet/";,
-        "proposalId": "W39MQT31M1ZY3NPF9ZSGXM8Q1K5XS5D5R1J10ZSHBREC6EZ570F0",
-        "summary": "Donation to GNUnet"
-      },
-      "replay": false,
-      "timestamp": {
-        "t_ms": 1578334232527
-      },
-      "numCoins": 4,
-      "amountPaidWithFees": "TESTKUDOS:1"
-    },
-    {
-      "type": "order-accepted",
-      "eventId": 
"order-accepted;Y8230SR6DP52J61CHEAPM5NHRVK408YP5KJP6VRBGZ3QZ0TBZQ90",
-      "orderShortInfo": {
-        "amount": "TESTKUDOS:0.1",
-        "orderId": "2020.006-02RFGFSSAZY9Y",
-        "merchantBaseUrl": 
"https://backend.test.taler.net/public/instances/Taler/";,
-        "proposalId": "Y8230SR6DP52J61CHEAPM5NHRVK408YP5KJP6VRBGZ3QZ0TBZQ90",
-        "summary": "Donation to Taler"
-      },
-      "timestamp": {
-        "t_ms": 1578334258703
-      }
-    },
-    {
-      "type": "payment-sent",
-      "eventId": 
"payment-sent;Y8230SR6DP52J61CHEAPM5NHRVK408YP5KJP6VRBGZ3QZ0TBZQ90",
-      "orderShortInfo": {
-        "amount": "TESTKUDOS:0.1",
-        "orderId": "2020.006-02RFGFSSAZY9Y",
-        "merchantBaseUrl": 
"https://backend.test.taler.net/public/instances/Taler/";,
-        "proposalId": "Y8230SR6DP52J61CHEAPM5NHRVK408YP5KJP6VRBGZ3QZ0TBZQ90",
-        "summary": "Donation to Taler"
-      },
-      "replay": false,
-      "timestamp": {
-        "t_ms": 1578334260497
-      },
-      "numCoins": 1,
-      "amountPaidWithFees": "TESTKUDOS:0.1"
-    },
-    {
-      "type": "reserve-balance-updated",
-      "eventId": 
"reserve-balance-updated;NBZX24YB4GEHFXFXD5NJAC84ZZD63DFAD6Q7YFJQGX8WX9YQ7B90",
-      "amountExpected": "TESTKUDOS:15",
-      "amountReserveBalance": "TESTKUDOS:15",
-      "timestamp": {
-        "t_ms": 1578334530519
-      },
-      "newHistoryTransactions": [
-        {
-          "amount": "TESTKUDOS:15",
-          "sender_account_url": "payto://x-taler-bank/bank.test.taler.net/9",
-          "timestamp": {
-            "t_ms": 1578334522000
-          },
-          "wire_reference": "000000000018C",
-          "type": "DEPOSIT"
-        }
-      ],
-      "reserveShortInfo": {
-        "exchangeBaseUrl": "https://exchange.test.taler.net/";,
-        "reserveCreationDetail": {
-          "type": "taler-bank-withdraw",
-          "bankUrl": 
"https://bank.test.taler.net/api/withdraw-operation/6b5fae55-3634-4e6b-a877-2f8bd76dfaf9";
-        },
-        "reservePub": "5XZC4DQMNR3482443727Q2RNKRVEBEW7SFJ8N9TYV5AZ74AZJB4G"
-      }
-    },
-    {
-      "type": "withdrawn",
-      "withdrawSessionId": 
"312AKNY5BGF08PY1ZK0Z2QBEZ3481NFP9BN36Z08FHJQQZW80EZG",
-      "eventId": 
"withdrawn;312AKNY5BGF08PY1ZK0Z2QBEZ3481NFP9BN36Z08FHJQQZW80EZG",
-      "amountWithdrawnEffective": "TESTKUDOS:15",
-      "amountWithdrawnRaw": "TESTKUDOS:15",
-      "exchangeBaseUrl": "https://exchange.test.taler.net/";,
-      "timestamp": {
-        "t_ms": 1578334531085
-      },
-      "withdrawalSource": {
-        "type": "reserve",
-        "reservePub": "5XZC4DQMNR3482443727Q2RNKRVEBEW7SFJ8N9TYV5AZ74AZJB4G"
-      }
-    },
-    {
-      "type": "refreshed",
-      "refreshGroupId": "3FN9PFD2JCPS3FDHZDPRS50VQT4G7SE54JDTG2ZW2HV1R3PJ9KBG",
-      "eventId": 
"refreshed;3FN9PFD2JCPS3FDHZDPRS50VQT4G7SE54JDTG2ZW2HV1R3PJ9KBG",
-      "timestamp": {
-        "t_ms": 1578334532478
-      },
-      "refreshReason": "pay",
-      "amountRefreshedEffective": "TESTKUDOS:2.46",
-      "amountRefreshedRaw": "TESTKUDOS:2.46",
-      "numInputCoins": 1,
-      "numOutputCoins": 6,
-      "numRefreshedInputCoins": 1
-    },
-    {
-      "type": "refreshed",
-      "refreshGroupId": "DF0DQ6MGJ39R0891B0P86MY2QTMPF9FPDJN30PRBMXBZ8XEVHZE0",
-      "eventId": 
"refreshed;DF0DQ6MGJ39R0891B0P86MY2QTMPF9FPDJN30PRBMXBZ8XEVHZE0",
-      "timestamp": {
-        "t_ms": 1578334533177
-      },
-      "refreshReason": "pay",
-      "amountRefreshedEffective": "TESTKUDOS:1.12",
-      "amountRefreshedRaw": "TESTKUDOS:1.12",
-      "numInputCoins": 4,
-      "numOutputCoins": 3,
-      "numRefreshedInputCoins": 1
-    },
-    {
-      "type": "order-accepted",
-      "eventId": 
"order-accepted;KYSVHAENJFQB308KF6ENPM46VJRFAXFRDGW856P7MM90AW60REZ0",
-      "orderShortInfo": {
-        "amount": "TESTKUDOS:0.5",
-        "orderId": "2020.006-03WSPCDEZK6HG",
-        "merchantBaseUrl": 
"https://backend.test.taler.net/public/instances/FSF/";,
-        "proposalId": "KYSVHAENJFQB308KF6ENPM46VJRFAXFRDGW856P7MM90AW60REZ0",
-        "summary": "Essay: 6. Why Software Should Be Free"
-      },
-      "timestamp": {
-        "t_ms": 1578334554161
-      }
-    },
-    {
-      "type": "payment-sent",
-      "eventId": 
"payment-sent;KYSVHAENJFQB308KF6ENPM46VJRFAXFRDGW856P7MM90AW60REZ0",
-      "orderShortInfo": {
-        "amount": "TESTKUDOS:0.5",
-        "orderId": "2020.006-03WSPCDEZK6HG",
-        "merchantBaseUrl": 
"https://backend.test.taler.net/public/instances/FSF/";,
-        "proposalId": "KYSVHAENJFQB308KF6ENPM46VJRFAXFRDGW856P7MM90AW60REZ0",
-        "summary": "Essay: 6. Why Software Should Be Free"
-      },
-      "replay": false,
-      "sessionId": "489c048b-7702-49e8-b66f-2de5e33f0008",
-      "timestamp": {
-        "t_ms": 1578334556292
-      },
-      "numCoins": 5,
-      "amountPaidWithFees": "TESTKUDOS:0.5"
-    },
-    {
-      "type": "refreshed",
-      "refreshGroupId": "NG8Z05Q8CM7KCC98PDNDQR0G920J2SGYM15ACBV0PMBE6XA8Q87G",
-      "eventId": 
"refreshed;NG8Z05Q8CM7KCC98PDNDQR0G920J2SGYM15ACBV0PMBE6XA8Q87G",
-      "timestamp": {
-        "t_ms": 1578334568850
-      },
-      "refreshReason": "pay",
-      "amountRefreshedEffective": "TESTKUDOS:0.06",
-      "amountRefreshedRaw": "TESTKUDOS:0.06",
-      "numInputCoins": 5,
-      "numOutputCoins": 2,
-      "numRefreshedInputCoins": 1
-    },
-    {
-      "type": "refund",
-      "eventId": "refund;FRW9DGXKPFS9HZEGFYMABDF6FRXDYNMFHH23FT71AAKN8FHGV2EG",
-      "refundGroupId": "FRW9DGXKPFS9HZEGFYMABDF6FRXDYNMFHH23FT71AAKN8FHGV2EG",
-      "orderShortInfo": {
-        "amount": "TESTKUDOS:0.5",
-        "orderId": "2020.006-03WSPCDEZK6HG",
-        "merchantBaseUrl": 
"https://backend.test.taler.net/public/instances/FSF/";,
-        "proposalId": "KYSVHAENJFQB308KF6ENPM46VJRFAXFRDGW856P7MM90AW60REZ0",
-        "summary": "Essay: 6. Why Software Should Be Free"
-      },
-      "timestamp": {
-        "t_ms": 1578334581129
-      },
-      "amountRefundedEffective": "TESTKUDOS:0.5",
-      "amountRefundedRaw": "TESTKUDOS:0.5",
-      "amountRefundedInvalid": "TESTKUDOS:0"
-    },
-    {
-      "type": "refreshed",
-      "refreshGroupId": "TY8G0FDE83YJY3CWBYKMV891CADG06X8MTBZHE42XNQV2B2C95RG",
-      "eventId": 
"refreshed;TY8G0FDE83YJY3CWBYKMV891CADG06X8MTBZHE42XNQV2B2C95RG",
-      "timestamp": {
-        "t_ms": 1578334585583
-      },
-      "refreshReason": "refund",
-      "amountRefreshedEffective": "TESTKUDOS:0.5",
-      "amountRefreshedRaw": "TESTKUDOS:0.5",
-      "numInputCoins": 5,
-      "numOutputCoins": 6,
-      "numRefreshedInputCoins": 5
-    }
-  ]
-}
diff --git a/packages/anastasis-core/src/index.ts 
b/packages/anastasis-core/src/index.ts
index 15e1e5d9..d1afc706 100644
--- a/packages/anastasis-core/src/index.ts
+++ b/packages/anastasis-core/src/index.ts
@@ -19,8 +19,9 @@ import {
   parsePayUri,
   stringToBytes,
   TalerErrorCode,
+  TalerProtocolTimestamp,
   TalerSignaturePurpose,
-  Timestamp,
+  AbsoluteTime,
 } from "@gnu-taler/taler-util";
 import { anastasisData } from "./anastasis-data.js";
 import {
@@ -631,15 +632,13 @@ async function uploadSecret(
     logger.info(`got response for policy upload (http status ${resp.status})`);
     if (resp.status === HttpStatusCode.NoContent) {
       let policyVersion = 0;
-      let policyExpiration: Timestamp = { t_ms: 0 };
+      let policyExpiration: TalerProtocolTimestamp = { t_s: 0 };
       try {
         policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0");
       } catch (e) {}
       try {
         policyExpiration = {
-          t_ms:
-            1000 *
-            Number(resp.headers.get("Anastasis-Policy-Expiration") ?? "0"),
+          t_s: Number(resp.headers.get("Anastasis-Policy-Expiration") ?? "0"),
         };
       } catch (e) {}
       successDetails[prov.provider_url] = {
@@ -1187,7 +1186,7 @@ async function addProviderBackup(
   state: ReducerStateBackup,
   args: ActionArgsAddProvider,
 ): Promise<ReducerStateBackup> {
-  const info = await getProviderInfo(args.provider_url)
+  const info = await getProviderInfo(args.provider_url);
   return {
     ...state,
     authentication_providers: {
@@ -1202,8 +1201,10 @@ async function deleteProviderBackup(
   state: ReducerStateBackup,
   args: ActionArgsDeleteProvider,
 ): Promise<ReducerStateBackup> {
-  const authentication_providers = {... state.authentication_providers ?? {} }
-  delete authentication_providers[args.provider_url]
+  const authentication_providers = {
+    ...(state.authentication_providers ?? {}),
+  };
+  delete authentication_providers[args.provider_url];
   return {
     ...state,
     authentication_providers,
@@ -1214,7 +1215,7 @@ async function addProviderRecovery(
   state: ReducerStateRecovery,
   args: ActionArgsAddProvider,
 ): Promise<ReducerStateRecovery> {
-  const info = await getProviderInfo(args.provider_url)
+  const info = await getProviderInfo(args.provider_url);
   return {
     ...state,
     authentication_providers: {
@@ -1228,8 +1229,10 @@ async function deleteProviderRecovery(
   state: ReducerStateRecovery,
   args: ActionArgsDeleteProvider,
 ): Promise<ReducerStateRecovery> {
-  const authentication_providers = {... state.authentication_providers ?? {} }
-  delete authentication_providers[args.provider_url]
+  const authentication_providers = {
+    ...(state.authentication_providers ?? {}),
+  };
+  delete authentication_providers[args.provider_url];
   return {
     ...state,
     authentication_providers,
@@ -1347,7 +1350,8 @@ async function updateUploadFees(
       x,
     ).amount;
   };
-  const years = Duration.toIntegerYears(Duration.getRemaining(expiration));
+  const expirationTime = AbsoluteTime.fromTimestamp(expiration);
+  const years = Duration.toIntegerYears(Duration.getRemaining(expirationTime));
   logger.info(`computing fees for ${years} years`);
   // For now, we compute fees for *all* available providers.
   for (const provUrl in state.authentication_providers ?? {}) {
diff --git a/packages/anastasis-core/src/reducer-types.ts 
b/packages/anastasis-core/src/reducer-types.ts
index 8d375552..2a869fe4 100644
--- a/packages/anastasis-core/src/reducer-types.ts
+++ b/packages/anastasis-core/src/reducer-types.ts
@@ -7,7 +7,8 @@ import {
   codecForString,
   codecForTimestamp,
   Duration,
-  Timestamp,
+  TalerProtocolTimestamp,
+  AbsoluteTime,
 } from "@gnu-taler/taler-util";
 import { ChallengeFeedback } from "./challenge-feedback-types.js";
 import { KeyShare } from "./crypto.js";
@@ -43,7 +44,7 @@ export interface PolicyProvider {
 export interface SuccessDetails {
   [provider_url: string]: {
     policy_version: number;
-    policy_expiration: Timestamp;
+    policy_expiration: TalerProtocolTimestamp;
   };
 }
 
@@ -112,7 +113,7 @@ export interface ReducerStateBackup {
 
   core_secret?: CoreSecret;
 
-  expiration?: Timestamp;
+  expiration?: TalerProtocolTimestamp;
 
   upload_fees?: { fee: AmountString }[];
 
@@ -369,7 +370,7 @@ export interface ActionArgsEnterSecret {
     value: string;
     mime?: string;
   };
-  expiration: Timestamp;
+  expiration: TalerProtocolTimestamp;
 }
 
 export interface ActionArgsSelectContinent {
@@ -438,7 +439,7 @@ export interface ActionArgsAddPolicy {
 }
 
 export interface ActionArgsUpdateExpiration {
-  expiration: Timestamp;
+  expiration: TalerProtocolTimestamp;
 }
 
 export interface ActionArgsChangeVersion {
diff --git 
a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx 
b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
index bb0c3c4b..1c6ae4b2 100644
--- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
@@ -51,13 +51,13 @@ export const WithDetails = createExample(TestedComponent, {
   success_details: {
     "https://anastasis.demo.taler.net/": {
       policy_expiration: {
-        t_ms: "never",
+        t_s: "never",
       },
       policy_version: 0,
     },
     "https://kudos.demo.anastasis.lu/": {
       policy_expiration: {
-        t_ms: new Date().getTime() + 60 * 60 * 24 * 1000,
+        t_s: new Date().getTime() + 60 * 60 * 24,
       },
       policy_version: 1,
     },
diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx 
b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx
index 66473345..d6272d84 100644
--- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx
@@ -35,9 +35,9 @@ export function BackupFinishedScreen(): VNode {
                 </a>
                 <p>
                   version {sd.policy_version}
-                  {sd.policy_expiration.t_ms !== "never"
+                  {sd.policy_expiration.t_s !== "never"
                     ? ` expires at: ${format(
-                        new Date(sd.policy_expiration.t_ms),
+                        new Date(sd.policy_expiration.t_s),
                         "dd-MM-yyyy",
                       )}`
                     : " without expiration date"}
diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx 
b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
index e401c3d7..d9bf084a 100644
--- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx
@@ -52,7 +52,7 @@ export function SecretEditorScreen(): VNode {
       await tx.transition("enter_secret", {
         secret,
         expiration: {
-          t_ms: new Date().getTime() + 1000 * 60 * 60 * 24 * 365 * 5,
+          t_s: new Date().getTime() + 60 * 60 * 24 * 365 * 5,
         },
       });
       await tx.transition("next", {});
diff --git a/packages/taler-util/src/RequestThrottler.ts 
b/packages/taler-util/src/RequestThrottler.ts
index 7689b421..a151cc63 100644
--- a/packages/taler-util/src/RequestThrottler.ts
+++ b/packages/taler-util/src/RequestThrottler.ts
@@ -15,7 +15,7 @@
  */
 
 import { Logger } from "./logging.js";
-import { getTimestampNow, timestampCmp, timestampDifference } from "./time.js";
+import { AbsoluteTime } from "./time.js";
 
 /**
  * Implementation of token bucket throttling.
@@ -46,16 +46,16 @@ class OriginState {
   tokensSecond: number = MAX_PER_SECOND;
   tokensMinute: number = MAX_PER_MINUTE;
   tokensHour: number = MAX_PER_HOUR;
-  private lastUpdate = getTimestampNow();
+  private lastUpdate = AbsoluteTime.now();
 
   private refill(): void {
-    const now = getTimestampNow();
-    if (timestampCmp(now, this.lastUpdate) < 0) {
+    const now = AbsoluteTime.now();
+    if (AbsoluteTime.cmp(now, this.lastUpdate) < 0) {
       // Did the system time change?
       this.lastUpdate = now;
       return;
     }
-    const d = timestampDifference(now, this.lastUpdate);
+    const d = AbsoluteTime.difference(now, this.lastUpdate);
     if (d.d_ms === "forever") {
       throw Error("assertion failed");
     }
diff --git a/packages/taler-util/src/ReserveTransaction.ts 
b/packages/taler-util/src/ReserveTransaction.ts
index b282ef18..50610483 100644
--- a/packages/taler-util/src/ReserveTransaction.ts
+++ b/packages/taler-util/src/ReserveTransaction.ts
@@ -38,7 +38,11 @@ import {
   EddsaPublicKeyString,
   CoinPublicKeyString,
 } from "./talerTypes";
-import { Timestamp, codecForTimestamp } from "./time.js";
+import {
+  AbsoluteTime,
+  codecForTimestamp,
+  TalerProtocolTimestamp,
+} from "./time.js";
 
 export enum ReserveTransactionType {
   Withdraw = "WITHDRAW",
@@ -98,7 +102,7 @@ export interface ReserveCreditTransaction {
   /**
    * Timestamp of the incoming wire transfer.
    */
-  timestamp: Timestamp;
+  timestamp: TalerProtocolTimestamp;
 }
 
 export interface ReserveClosingTransaction {
@@ -139,7 +143,7 @@ export interface ReserveClosingTransaction {
   /**
    * Time when the reserve was closed.
    */
-  timestamp: Timestamp;
+  timestamp: TalerProtocolTimestamp;
 }
 
 export interface ReserveRecoupTransaction {
@@ -165,7 +169,7 @@ export interface ReserveRecoupTransaction {
   /**
    * Time when the funds were paid back into the reserve.
    */
-  timestamp: Timestamp;
+  timestamp: TalerProtocolTimestamp;
 
   /**
    * Public key of the coin that was paid back.
@@ -182,46 +186,50 @@ export type ReserveTransaction =
   | ReserveClosingTransaction
   | ReserveRecoupTransaction;
 
-export const codecForReserveWithdrawTransaction = (): 
Codec<ReserveWithdrawTransaction> =>
-  buildCodecForObject<ReserveWithdrawTransaction>()
-    .property("amount", codecForString())
-    .property("h_coin_envelope", codecForString())
-    .property("h_denom_pub", codecForString())
-    .property("reserve_sig", codecForString())
-    .property("type", codecForConstString(ReserveTransactionType.Withdraw))
-    .property("withdraw_fee", codecForString())
-    .build("ReserveWithdrawTransaction");
-
-export const codecForReserveCreditTransaction = (): 
Codec<ReserveCreditTransaction> =>
-  buildCodecForObject<ReserveCreditTransaction>()
-    .property("amount", codecForString())
-    .property("sender_account_url", codecForString())
-    .property("timestamp", codecForTimestamp)
-    .property("wire_reference", codecForNumber())
-    .property("type", codecForConstString(ReserveTransactionType.Credit))
-    .build("ReserveCreditTransaction");
-
-export const codecForReserveClosingTransaction = (): 
Codec<ReserveClosingTransaction> =>
-  buildCodecForObject<ReserveClosingTransaction>()
-    .property("amount", codecForString())
-    .property("closing_fee", codecForString())
-    .property("exchange_pub", codecForString())
-    .property("exchange_sig", codecForString())
-    .property("h_wire", codecForString())
-    .property("timestamp", codecForTimestamp)
-    .property("type", codecForConstString(ReserveTransactionType.Closing))
-    .property("wtid", codecForString())
-    .build("ReserveClosingTransaction");
-
-export const codecForReserveRecoupTransaction = (): 
Codec<ReserveRecoupTransaction> =>
-  buildCodecForObject<ReserveRecoupTransaction>()
-    .property("amount", codecForString())
-    .property("coin_pub", codecForString())
-    .property("exchange_pub", codecForString())
-    .property("exchange_sig", codecForString())
-    .property("timestamp", codecForTimestamp)
-    .property("type", codecForConstString(ReserveTransactionType.Recoup))
-    .build("ReserveRecoupTransaction");
+export const codecForReserveWithdrawTransaction =
+  (): Codec<ReserveWithdrawTransaction> =>
+    buildCodecForObject<ReserveWithdrawTransaction>()
+      .property("amount", codecForString())
+      .property("h_coin_envelope", codecForString())
+      .property("h_denom_pub", codecForString())
+      .property("reserve_sig", codecForString())
+      .property("type", codecForConstString(ReserveTransactionType.Withdraw))
+      .property("withdraw_fee", codecForString())
+      .build("ReserveWithdrawTransaction");
+
+export const codecForReserveCreditTransaction =
+  (): Codec<ReserveCreditTransaction> =>
+    buildCodecForObject<ReserveCreditTransaction>()
+      .property("amount", codecForString())
+      .property("sender_account_url", codecForString())
+      .property("timestamp", codecForTimestamp)
+      .property("wire_reference", codecForNumber())
+      .property("type", codecForConstString(ReserveTransactionType.Credit))
+      .build("ReserveCreditTransaction");
+
+export const codecForReserveClosingTransaction =
+  (): Codec<ReserveClosingTransaction> =>
+    buildCodecForObject<ReserveClosingTransaction>()
+      .property("amount", codecForString())
+      .property("closing_fee", codecForString())
+      .property("exchange_pub", codecForString())
+      .property("exchange_sig", codecForString())
+      .property("h_wire", codecForString())
+      .property("timestamp", codecForTimestamp)
+      .property("type", codecForConstString(ReserveTransactionType.Closing))
+      .property("wtid", codecForString())
+      .build("ReserveClosingTransaction");
+
+export const codecForReserveRecoupTransaction =
+  (): Codec<ReserveRecoupTransaction> =>
+    buildCodecForObject<ReserveRecoupTransaction>()
+      .property("amount", codecForString())
+      .property("coin_pub", codecForString())
+      .property("exchange_pub", codecForString())
+      .property("exchange_sig", codecForString())
+      .property("timestamp", codecForTimestamp)
+      .property("type", codecForConstString(ReserveTransactionType.Recoup))
+      .build("ReserveRecoupTransaction");
 
 export const codecForReserveTransaction = (): Codec<ReserveTransaction> =>
   buildCodecForUnion<ReserveTransaction>()
diff --git a/packages/taler-util/src/backupTypes.ts 
b/packages/taler-util/src/backupTypes.ts
index 41a9ce98..b31a8383 100644
--- a/packages/taler-util/src/backupTypes.ts
+++ b/packages/taler-util/src/backupTypes.ts
@@ -54,7 +54,7 @@
  * Imports.
  */
 import { DenominationPubKey, UnblindedSignature } from "./talerTypes.js";
-import { Duration, Timestamp } from "./time.js";
+import { TalerProtocolDuration, TalerProtocolTimestamp } from "./time.js";
 
 /**
  * Type alias for strings that are to be treated like amounts.
@@ -120,7 +120,7 @@ export interface WalletBackupContentV1 {
    * This timestamp should only be advanced if the content
    * of the backup changes.
    */
-  timestamp: Timestamp;
+  timestamp: TalerProtocolTimestamp;
 
   /**
    * Per-exchange data sorted by exchange master public key.
@@ -333,10 +333,10 @@ export interface BackupRecoupGroup {
   /**
    * Timestamp when the recoup was started.
    */
-  timestamp_created: Timestamp;
+  timestamp_created: TalerProtocolTimestamp;
 
-  timestamp_finish?: Timestamp;
-  finish_clock?: Timestamp;
+  timestamp_finish?: TalerProtocolTimestamp;
+  finish_clock?: TalerProtocolTimestamp;
   finish_is_failure?: boolean;
 
   /**
@@ -486,14 +486,14 @@ export interface BackupTip {
    * Has the user accepted the tip?  Only after the tip has been accepted coins
    * withdrawn from the tip may be used.
    */
-  timestamp_accepted: Timestamp | undefined;
+  timestamp_accepted: TalerProtocolTimestamp | undefined;
 
   /**
    * When was the tip first scanned by the wallet?
    */
-  timestamp_created: Timestamp;
+  timestamp_created: TalerProtocolTimestamp;
 
-  timestamp_finished?: Timestamp;
+  timestamp_finished?: TalerProtocolTimestamp;
   finish_is_failure?: boolean;
 
   /**
@@ -504,7 +504,7 @@ export interface BackupTip {
   /**
    * Timestamp, the tip can't be picked up anymore after this deadline.
    */
-  timestamp_expiration: Timestamp;
+  timestamp_expiration: TalerProtocolTimestamp;
 
   /**
    * The exchange that will sign our coins, chosen by the merchant.
@@ -613,9 +613,9 @@ export interface BackupRefreshGroup {
    */
   old_coins: BackupRefreshOldCoin[];
 
-  timestamp_created: Timestamp;
+  timestamp_created: TalerProtocolTimestamp;
 
-  timestamp_finish?: Timestamp;
+  timestamp_finish?: TalerProtocolTimestamp;
   finish_is_failure?: boolean;
 }
 
@@ -636,9 +636,9 @@ export interface BackupWithdrawalGroup {
    * When was the withdrawal operation started started?
    * Timestamp in milliseconds.
    */
-  timestamp_created: Timestamp;
+  timestamp_created: TalerProtocolTimestamp;
 
-  timestamp_finish?: Timestamp;
+  timestamp_finish?: TalerProtocolTimestamp;
   finish_is_failure?: boolean;
 
   /**
@@ -672,12 +672,12 @@ export interface BackupRefundItemCommon {
   /**
    * Execution time as claimed by the merchant
    */
-  execution_time: Timestamp;
+  execution_time: TalerProtocolTimestamp;
 
   /**
    * Time when the wallet became aware of the refund.
    */
-  obtained_time: Timestamp;
+  obtained_time: TalerProtocolTimestamp;
 
   /**
    * Amount refunded for the coin.
@@ -788,7 +788,7 @@ export interface BackupPurchase {
    * Timestamp of the first time that sending a payment to the merchant
    * for this purchase was successful.
    */
-  timestamp_first_successful_pay: Timestamp | undefined;
+  timestamp_first_successful_pay: TalerProtocolTimestamp | undefined;
 
   /**
    * Signature by the merchant confirming the payment.
@@ -799,7 +799,7 @@ export interface BackupPurchase {
    * When was the purchase made?
    * Refers to the time that the user accepted.
    */
-  timestamp_accept: Timestamp;
+  timestamp_accept: TalerProtocolTimestamp;
 
   /**
    * Pending refunds for the purchase.  A refund is pending
@@ -815,7 +815,7 @@ export interface BackupPurchase {
   /**
    * Continue querying the refund status until this deadline has expired.
    */
-  auto_refund_deadline: Timestamp | undefined;
+  auto_refund_deadline: TalerProtocolTimestamp | undefined;
 }
 
 /**
@@ -857,22 +857,22 @@ export interface BackupDenomination {
   /**
    * Validity start date of the denomination.
    */
-  stamp_start: Timestamp;
+  stamp_start: TalerProtocolTimestamp;
 
   /**
    * Date after which the currency can't be withdrawn anymore.
    */
-  stamp_expire_withdraw: Timestamp;
+  stamp_expire_withdraw: TalerProtocolTimestamp;
 
   /**
    * Date after the denomination officially doesn't exist anymore.
    */
-  stamp_expire_legal: Timestamp;
+  stamp_expire_legal: TalerProtocolTimestamp;
 
   /**
    * Data after which coins of this denomination can't be deposited anymore.
    */
-  stamp_expire_deposit: Timestamp;
+  stamp_expire_deposit: TalerProtocolTimestamp;
 
   /**
    * Signature by the exchange's master key over the denomination
@@ -903,7 +903,7 @@ export interface BackupDenomination {
    * The list issue date of the exchange "/keys" response
    * that this denomination was last seen in.
    */
-  list_issue_date: Timestamp;
+  list_issue_date: TalerProtocolTimestamp;
 }
 
 /**
@@ -923,14 +923,14 @@ export interface BackupReserve {
   /**
    * Time when the reserve was created.
    */
-  timestamp_created: Timestamp;
+  timestamp_created: TalerProtocolTimestamp;
 
   /**
    * Timestamp of the last observed activity.
    *
    * Used to compute when to give up querying the exchange.
    */
-  timestamp_last_activity: Timestamp;
+  timestamp_last_activity: TalerProtocolTimestamp;
 
   /**
    * Timestamp of when the reserve closed.
@@ -938,7 +938,7 @@ export interface BackupReserve {
    * Note that the last activity can be after the closing time
    * due to recouping.
    */
-  timestamp_closed?: Timestamp;
+  timestamp_closed?: TalerProtocolTimestamp;
 
   /**
    * Wire information (as payto URI) for the bank account that
@@ -977,14 +977,14 @@ export interface BackupReserve {
     /**
      * Time when the information about this reserve was posted to the bank.
      */
-    timestamp_reserve_info_posted: Timestamp | undefined;
+    timestamp_reserve_info_posted: TalerProtocolTimestamp | undefined;
 
     /**
      * Time when the reserve was confirmed by the bank.
      *
      * Set to undefined if not confirmed yet.
      */
-    timestamp_bank_confirmed: Timestamp | undefined;
+    timestamp_bank_confirmed: TalerProtocolTimestamp | undefined;
   };
 
   /**
@@ -1033,12 +1033,12 @@ export interface BackupExchangeWireFee {
   /**
    * Start date of the fee.
    */
-  start_stamp: Timestamp;
+  start_stamp: TalerProtocolTimestamp;
 
   /**
    * End date of the fee.
    */
-  end_stamp: Timestamp;
+  end_stamp: TalerProtocolTimestamp;
 
   /**
    * Signature made by the exchange master key.
@@ -1050,9 +1050,9 @@ export interface BackupExchangeWireFee {
  * Structure of one exchange signing key in the /keys response.
  */
 export class BackupExchangeSignKey {
-  stamp_start: Timestamp;
-  stamp_expire: Timestamp;
-  stamp_end: Timestamp;
+  stamp_start: TalerProtocolTimestamp;
+  stamp_expire: TalerProtocolTimestamp;
+  stamp_end: TalerProtocolTimestamp;
   key: string;
   master_sig: string;
 }
@@ -1112,7 +1112,7 @@ export interface BackupExchange {
    *
    * Used to facilitate automatic merging.
    */
-  update_clock: Timestamp;
+  update_clock: TalerProtocolTimestamp;
 }
 
 /**
@@ -1161,7 +1161,7 @@ export interface BackupExchangeDetails {
   /**
    * Closing delay of reserves.
    */
-  reserve_closing_delay: Duration;
+  reserve_closing_delay: TalerProtocolDuration;
 
   /**
    * Signing keys we got from the exchange, can also contain
@@ -1187,7 +1187,7 @@ export interface BackupExchangeDetails {
   /**
    * Timestamp when the ToS has been accepted.
    */
-  tos_accepted_timestamp: Timestamp | undefined;
+  tos_accepted_timestamp: TalerProtocolTimestamp | undefined;
 }
 
 export enum BackupProposalStatus {
@@ -1248,7 +1248,7 @@ export interface BackupProposal {
    * Timestamp of when the record
    * was created.
    */
-  timestamp: Timestamp;
+  timestamp: TalerProtocolTimestamp;
 
   /**
    * Private key for the nonce.
diff --git a/packages/taler-util/src/notifications.ts 
b/packages/taler-util/src/notifications.ts
index e8f27062..b3d9ad1d 100644
--- a/packages/taler-util/src/notifications.ts
+++ b/packages/taler-util/src/notifications.ts
@@ -22,7 +22,7 @@
 /**
  * Imports.
  */
-import { TalerErrorDetails } from "./walletTypes.js";
+import { TalerErrorDetail } from "./walletTypes.js";
 
 export enum NotificationType {
   CoinWithdrawn = "coin-withdrawn",
@@ -157,62 +157,62 @@ export interface ExchangeAddedNotification {
 
 export interface ExchangeOperationErrorNotification {
   type: NotificationType.ExchangeOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface RefreshOperationErrorNotification {
   type: NotificationType.RefreshOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface BackupOperationErrorNotification {
   type: NotificationType.BackupOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface RefundStatusOperationErrorNotification {
   type: NotificationType.RefundStatusOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface RefundApplyOperationErrorNotification {
   type: NotificationType.RefundApplyOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface PayOperationErrorNotification {
   type: NotificationType.PayOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface ProposalOperationErrorNotification {
   type: NotificationType.ProposalOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface TipOperationErrorNotification {
   type: NotificationType.TipOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface WithdrawOperationErrorNotification {
   type: NotificationType.WithdrawOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface RecoupOperationErrorNotification {
   type: NotificationType.RecoupOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface DepositOperationErrorNotification {
   type: NotificationType.DepositOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface ReserveOperationErrorNotification {
   type: NotificationType.ReserveOperationError;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface ReserveCreatedNotification {
diff --git a/packages/taler-util/src/taler-error-codes.ts 
b/packages/taler-util/src/taler-error-codes.ts
index b22f29a1..8ea97f7e 100644
--- a/packages/taler-util/src/taler-error-codes.ts
+++ b/packages/taler-util/src/taler-error-codes.ts
@@ -22,6 +22,8 @@
  */
 
 export enum TalerErrorCode {
+
+
   /**
    * Special code to indicate success (no error).
    * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
@@ -78,6 +80,13 @@ export enum TalerErrorCode {
    */
   GENERIC_CONFIGURATION_INVALID = 14,
 
+  /**
+   * The client made a request to a service, but received an error response it 
does not know how to handle.
+   * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  GENERIC_UNEXPECTED_REQUEST_ERROR = 15,
+
   /**
    * The HTTP method used is invalid for this endpoint.
    * Returned with an HTTP status code of #MHD_HTTP_METHOD_NOT_ALLOWED (405).
@@ -372,6 +381,20 @@ export enum TalerErrorCode {
    */
   EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE = 1018,
 
+  /**
+   * The reserve public key was malformed.
+   * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  EXCHANGE_GENERIC_RESERVE_PUB_MALFORMED = 1019,
+
+  /**
+   * The time at the server is too far off from the time specified in the 
request. Most likely the client system time is wrong.
+   * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  EXCHANGE_GENERIC_CLOCK_SKEW = 1020,
+
   /**
    * The exchange did not find information about the specified transaction in 
the database.
    * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
@@ -541,11 +564,25 @@ export enum TalerErrorCode {
   EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT = 1222,
 
   /**
-   * The reserve status was requested using a unknown key.
+   * The reserve balance, status or history was requested for a reserve which 
is not known to the exchange.
    * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  EXCHANGE_RESERVES_GET_STATUS_UNKNOWN = 1250,
+  EXCHANGE_RESERVES_STATUS_UNKNOWN = 1250,
+
+  /**
+   * The reserve status was requested with a bad signature.
+   * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  EXCHANGE_RESERVES_STATUS_BAD_SIGNATURE = 1251,
+
+  /**
+   * The reserve history was requested with a bad signature.
+   * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  EXCHANGE_RESERVES_HISTORY_BAD_SIGNATURE = 1252,
 
   /**
    * The exchange encountered melt fees exceeding the melted coin's 
contribution.
@@ -1394,6 +1431,27 @@ export enum TalerErrorCode {
    */
   MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_FAILED = 2170,
 
+  /**
+   * The payment required a minimum age but one of the coins (of a 
denomination with support for age restriction) did not provide any 
age_commitment.
+   * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_MISSING = 2171,
+
+  /**
+   * The payment required a minimum age but one of the coins provided an 
age_commitment that contained a wrong number of public keys compared to the 
number of age groups defined in the denomination of the coin.
+   * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_SIZE_MISMATCH = 2172,
+
+  /**
+   * The payment required a minimum age but one of the coins provided a 
minimum_age_sig that couldn't be verified with the given age_commitment for 
that particular minimum age.
+   * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED = 2173,
+
   /**
    * The contract hash does not match the given order ID.
    * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
@@ -2150,6 +2208,13 @@ export enum TalerErrorCode {
    */
   WALLET_CONTRACT_TERMS_MALFORMED = 7020,
 
+  /**
+   * A pending operation failed, and thus the request can't be completed.
+   * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  WALLET_PENDING_OPERATION_FAILED = 7021,
+
   /**
    * We encountered a timeout with our payment backend.
    * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
@@ -2646,4 +2711,5 @@ export enum TalerErrorCode {
    * (A value of 0 indicates that the error is generated client-side).
    */
   END = 9999,
+
 }
diff --git a/packages/taler-util/src/talerTypes.ts 
b/packages/taler-util/src/talerTypes.ts
index 4ccfffce..b1bf6ab3 100644
--- a/packages/taler-util/src/talerTypes.ts
+++ b/packages/taler-util/src/talerTypes.ts
@@ -38,15 +38,14 @@ import {
   codecForConstNumber,
   buildCodecForUnion,
   codecForConstString,
-  codecForEither,
 } from "./codec.js";
 import {
-  Timestamp,
   codecForTimestamp,
-  Duration,
   codecForDuration,
+  TalerProtocolTimestamp,
+  TalerProtocolDuration,
 } from "./time.js";
-import { Amounts, codecForAmountString } from "./amounts.js";
+import { codecForAmountString } from "./amounts.js";
 import { strcmp } from "./helpers.js";
 
 /**
@@ -86,24 +85,24 @@ export class ExchangeDenomination {
   /**
    * Start date from which withdraw is allowed.
    */
-  stamp_start: Timestamp;
+  stamp_start: TalerProtocolTimestamp;
 
   /**
    * End date for withdrawing.
    */
-  stamp_expire_withdraw: Timestamp;
+  stamp_expire_withdraw: TalerProtocolTimestamp;
 
   /**
    * Expiration date after which the exchange can forget about
    * the currency.
    */
-  stamp_expire_legal: Timestamp;
+  stamp_expire_legal: TalerProtocolTimestamp;
 
   /**
    * Date after which the coins of this denomination can't be
    * deposited anymore.
    */
-  stamp_expire_deposit: Timestamp;
+  stamp_expire_deposit: TalerProtocolTimestamp;
 
   /**
    * Signature over the denomination information by the exchange's master
@@ -394,7 +393,7 @@ export interface Product {
   taxes?: Tax[];
 
   // time indicating when this product should be delivered
-  delivery_date?: Timestamp;
+  delivery_date?: TalerProtocolTimestamp;
 }
 
 export interface InternationalizedString {
@@ -413,7 +412,7 @@ export interface ContractTerms {
   /**
    * Hash of the merchant's wire details.
    */
-  auto_refund?: Duration;
+  auto_refund?: TalerProtocolDuration;
 
   /**
    * Wire method the merchant wants to use.
@@ -445,7 +444,7 @@ export interface ContractTerms {
   /**
    * Deadline to pay for the contract.
    */
-  pay_deadline: Timestamp;
+  pay_deadline: TalerProtocolTimestamp;
 
   /**
    * Maximum deposit fee covered by the merchant.
@@ -466,7 +465,7 @@ export interface ContractTerms {
    * Time indicating when the order should be delivered.
    * May be overwritten by individual products.
    */
-  delivery_date?: Timestamp;
+  delivery_date?: TalerProtocolTimestamp;
 
   /**
    * Delivery location for (all!) products.
@@ -486,17 +485,17 @@ export interface ContractTerms {
   /**
    * Deadline for refunds.
    */
-  refund_deadline: Timestamp;
+  refund_deadline: TalerProtocolTimestamp;
 
   /**
    * Deadline for the wire transfer.
    */
-  wire_transfer_deadline: Timestamp;
+  wire_transfer_deadline: TalerProtocolTimestamp;
 
   /**
    * Time when the contract was generated by the merchant.
    */
-  timestamp: Timestamp;
+  timestamp: TalerProtocolTimestamp;
 
   /**
    * Order id to uniquely identify the purchase within
@@ -700,9 +699,9 @@ export class Recoup {
  * Structure of one exchange signing key in the /keys response.
  */
 export class ExchangeSignKeyJson {
-  stamp_start: Timestamp;
-  stamp_expire: Timestamp;
-  stamp_end: Timestamp;
+  stamp_start: TalerProtocolTimestamp;
+  stamp_expire: TalerProtocolTimestamp;
+  stamp_end: TalerProtocolTimestamp;
   key: EddsaPublicKeyString;
   master_sig: EddsaSignatureString;
 }
@@ -729,7 +728,7 @@ export class ExchangeKeysJson {
   /**
    * Timestamp when this response was issued.
    */
-  list_issue_date: Timestamp;
+  list_issue_date: TalerProtocolTimestamp;
 
   /**
    * List of revoked denominations.
@@ -747,7 +746,7 @@ export class ExchangeKeysJson {
    */
   version: string;
 
-  reserve_closing_delay: Duration;
+  reserve_closing_delay: TalerProtocolDuration;
 }
 
 /**
@@ -774,12 +773,12 @@ export class WireFeesJson {
   /**
    * Date from which the fee applies.
    */
-  start_date: Timestamp;
+  start_date: TalerProtocolTimestamp;
 
   /**
    * Data after which the fee doesn't apply anymore.
    */
-  end_date: Timestamp;
+  end_date: TalerProtocolTimestamp;
 }
 
 export interface AccountInfo {
@@ -850,7 +849,7 @@ export class TipPickupGetResponse {
 
   exchange_url: string;
 
-  expiration: Timestamp;
+  expiration: TalerProtocolTimestamp;
 }
 
 export enum DenomKeyType {
@@ -1067,7 +1066,7 @@ export interface MerchantCoinRefundSuccessStatus {
   // to the customer.
   refund_amount: AmountString;
 
-  execution_time: Timestamp;
+  execution_time: TalerProtocolTimestamp;
 }
 
 export interface MerchantCoinRefundFailureStatus {
@@ -1092,7 +1091,7 @@ export interface MerchantCoinRefundFailureStatus {
   // to the customer.
   refund_amount: AmountString;
 
-  execution_time: Timestamp;
+  execution_time: TalerProtocolTimestamp;
 }
 
 export interface MerchantOrderStatusUnpaid {
@@ -1733,7 +1732,7 @@ export interface DepositSuccess {
   transaction_base_url?: string;
 
   // timestamp when the deposit was received by the exchange.
-  exchange_timestamp: Timestamp;
+  exchange_timestamp: TalerProtocolTimestamp;
 
   // the EdDSA signature of TALER_DepositConfirmationPS using a current
   // signing key of the exchange affirming the successful
diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts
index 9f957101..43cb7ad4 100644
--- a/packages/taler-util/src/time.ts
+++ b/packages/taler-util/src/time.ts
@@ -23,13 +23,41 @@
  */
 import { Codec, renderContext, Context } from "./codec.js";
 
-export interface Timestamp {
+export interface AbsoluteTime {
   /**
    * Timestamp in milliseconds.
    */
   readonly t_ms: number | "never";
 }
 
+export interface TalerProtocolTimestamp {
+  readonly t_s: number | "never";
+}
+
+export namespace TalerProtocolTimestamp {
+  export function now(): TalerProtocolTimestamp {
+    return AbsoluteTime.toTimestamp(AbsoluteTime.now());
+  }
+
+  export function zero(): TalerProtocolTimestamp {
+    return {
+      t_s: 0,
+    };
+  }
+
+  export function never(): TalerProtocolTimestamp {
+    return {
+      t_s: "never",
+    };
+  }
+
+  export function fromSeconds(s: number): TalerProtocolTimestamp {
+    return {
+      t_s: s,
+    };
+  }
+}
+
 export interface Duration {
   /**
    * Duration in milliseconds.
@@ -37,40 +65,32 @@ export interface Duration {
   readonly d_ms: number | "forever";
 }
 
+export interface TalerProtocolDuration {
+  readonly d_us: number | "forever";
+}
+
 let timeshift = 0;
 
 export function setDangerousTimetravel(dt: number): void {
   timeshift = dt;
 }
 
-export function getTimestampNow(): Timestamp {
-  return {
-    t_ms: new Date().getTime() + timeshift,
-  };
-}
-
-export function isTimestampExpired(t: Timestamp) {
-  return timestampCmp(t, getTimestampNow()) <= 0;
-}
-
-export function getDurationRemaining(
-  deadline: Timestamp,
-  now = getTimestampNow(),
-): Duration {
-  if (deadline.t_ms === "never") {
-    return { d_ms: "forever" };
-  }
-  if (now.t_ms === "never") {
-    throw Error("invalid argument for 'now'");
-  }
-  if (deadline.t_ms < now.t_ms) {
-    return { d_ms: 0 };
-  }
-  return { d_ms: deadline.t_ms - now.t_ms };
-}
-
 export namespace Duration {
-  export const getRemaining = getDurationRemaining;
+  export function getRemaining(
+    deadline: AbsoluteTime,
+    now = AbsoluteTime.now(),
+  ): Duration {
+    if (deadline.t_ms === "never") {
+      return { d_ms: "forever" };
+    }
+    if (now.t_ms === "never") {
+      throw Error("invalid argument for 'now'");
+    }
+    if (deadline.t_ms < now.t_ms) {
+      return { d_ms: 0 };
+    }
+    return { d_ms: deadline.t_ms - now.t_ms };
+  }
   export function toIntegerYears(d: Duration): number {
     if (typeof d.d_ms !== "number") {
       throw Error("infinite duration");
@@ -81,33 +101,152 @@ export namespace Duration {
   export function getForever(): Duration {
     return { d_ms: "forever" };
   }
+  export function fromTalerProtocolDuration(
+    d: TalerProtocolDuration,
+  ): Duration {
+    if (d.d_us === "forever") {
+      return {
+        d_ms: "forever",
+      };
+    }
+    return {
+      d_ms: d.d_us / 1000,
+    };
+  }
 }
 
-export namespace Timestamp {
-  export const now = getTimestampNow;
-  export const min = timestampMin;
-  export const isExpired = isTimestampExpired;
-  export const truncateToSecond = timestampTruncateToSecond;
-}
+export namespace AbsoluteTime {
+  export function now(): AbsoluteTime {
+    return {
+      t_ms: new Date().getTime() + timeshift,
+    };
+  }
 
-export function timestampMin(t1: Timestamp, t2: Timestamp): Timestamp {
-  if (t1.t_ms === "never") {
-    return { t_ms: t2.t_ms };
+  export function never(): AbsoluteTime {
+    return {
+      t_ms: "never",
+    };
   }
-  if (t2.t_ms === "never") {
-    return { t_ms: t2.t_ms };
+
+  export function cmp(t1: AbsoluteTime, t2: AbsoluteTime): number {
+    if (t1.t_ms === "never") {
+      if (t2.t_ms === "never") {
+        return 0;
+      }
+      return 1;
+    }
+    if (t2.t_ms === "never") {
+      return -1;
+    }
+    if (t1.t_ms == t2.t_ms) {
+      return 0;
+    }
+    if (t1.t_ms > t2.t_ms) {
+      return 1;
+    }
+    return -1;
+  }
+
+  export function min(t1: AbsoluteTime, t2: AbsoluteTime): AbsoluteTime {
+    if (t1.t_ms === "never") {
+      return { t_ms: t2.t_ms };
+    }
+    if (t2.t_ms === "never") {
+      return { t_ms: t2.t_ms };
+    }
+    return { t_ms: Math.min(t1.t_ms, t2.t_ms) };
   }
-  return { t_ms: Math.min(t1.t_ms, t2.t_ms) };
-}
 
-export function timestampMax(t1: Timestamp, t2: Timestamp): Timestamp {
-  if (t1.t_ms === "never") {
-    return { t_ms: "never" };
+  export function max(t1: AbsoluteTime, t2: AbsoluteTime): AbsoluteTime {
+    if (t1.t_ms === "never") {
+      return { t_ms: "never" };
+    }
+    if (t2.t_ms === "never") {
+      return { t_ms: "never" };
+    }
+    return { t_ms: Math.max(t1.t_ms, t2.t_ms) };
   }
-  if (t2.t_ms === "never") {
-    return { t_ms: "never" };
+
+  export function difference(t1: AbsoluteTime, t2: AbsoluteTime): Duration {
+    if (t1.t_ms === "never") {
+      return { d_ms: "forever" };
+    }
+    if (t2.t_ms === "never") {
+      return { d_ms: "forever" };
+    }
+    return { d_ms: Math.abs(t1.t_ms - t2.t_ms) };
+  }
+
+  export function isExpired(t: AbsoluteTime) {
+    return cmp(t, now()) <= 0;
+  }
+
+  export function fromTimestamp(t: TalerProtocolTimestamp): AbsoluteTime {
+    if (t.t_s === "never") {
+      return { t_ms: "never" };
+    }
+    return {
+      t_ms: t.t_s * 1000,
+    };
+  }
+
+  export function toTimestamp(at: AbsoluteTime): TalerProtocolTimestamp {
+    if (at.t_ms === "never") {
+      return { t_s: "never" };
+    }
+    return {
+      t_s: Math.floor(at.t_ms / 1000),
+    };
+  }
+
+  export function isBetween(
+    t: AbsoluteTime,
+    start: AbsoluteTime,
+    end: AbsoluteTime,
+  ): boolean {
+    if (cmp(t, start) < 0) {
+      return false;
+    }
+    if (cmp(t, end) > 0) {
+      return false;
+    }
+    return true;
+  }
+
+  export function toIsoString(t: AbsoluteTime): string {
+    if (t.t_ms === "never") {
+      return "<never>";
+    } else {
+      return new Date(t.t_ms).toISOString();
+    }
+  }
+
+  export function addDuration(t1: AbsoluteTime, d: Duration): AbsoluteTime {
+    if (t1.t_ms === "never" || d.d_ms === "forever") {
+      return { t_ms: "never" };
+    }
+    return { t_ms: t1.t_ms + d.d_ms };
+  }
+
+  export function subtractDuraction(
+    t1: AbsoluteTime,
+    d: Duration,
+  ): AbsoluteTime {
+    if (t1.t_ms === "never") {
+      return { t_ms: "never" };
+    }
+    if (d.d_ms === "forever") {
+      return { t_ms: 0 };
+    }
+    return { t_ms: Math.max(0, t1.t_ms - d.d_ms) };
+  }
+
+  export function stringify(t: AbsoluteTime): string {
+    if (t.t_ms === "never") {
+      return "never";
+    }
+    return new Date(t.t_ms).toISOString();
   }
-  return { t_ms: Math.max(t1.t_ms, t2.t_ms) };
 }
 
 const SECONDS = 1000;
@@ -135,19 +274,6 @@ export function durationFromSpec(spec: {
   return { d_ms };
 }
 
-/**
- * Truncate a timestamp so that that it represents a multiple
- * of seconds.  The timestamp is always rounded down.
- */
-export function timestampTruncateToSecond(t1: Timestamp): Timestamp {
-  if (t1.t_ms === "never") {
-    return { t_ms: "never" };
-  }
-  return {
-    t_ms: Math.floor(t1.t_ms / 1000) * 1000,
-  };
-}
-
 export function durationMin(d1: Duration, d2: Duration): Duration {
   if (d1.d_ms === "forever") {
     return { d_ms: d2.d_ms };
@@ -182,111 +308,33 @@ export function durationAdd(d1: Duration, d2: Duration): 
Duration {
   return { d_ms: d1.d_ms + d2.d_ms };
 }
 
-export function timestampCmp(t1: Timestamp, t2: Timestamp): number {
-  if (t1.t_ms === "never") {
-    if (t2.t_ms === "never") {
-      return 0;
-    }
-    return 1;
-  }
-  if (t2.t_ms === "never") {
-    return -1;
-  }
-  if (t1.t_ms == t2.t_ms) {
-    return 0;
-  }
-  if (t1.t_ms > t2.t_ms) {
-    return 1;
-  }
-  return -1;
-}
-
-export function timestampAddDuration(t1: Timestamp, d: Duration): Timestamp {
-  if (t1.t_ms === "never" || d.d_ms === "forever") {
-    return { t_ms: "never" };
-  }
-  return { t_ms: t1.t_ms + d.d_ms };
-}
-
-export function timestampSubtractDuraction(
-  t1: Timestamp,
-  d: Duration,
-): Timestamp {
-  if (t1.t_ms === "never") {
-    return { t_ms: "never" };
-  }
-  if (d.d_ms === "forever") {
-    return { t_ms: 0 };
-  }
-  return { t_ms: Math.max(0, t1.t_ms - d.d_ms) };
-}
-
-export function stringifyTimestamp(t: Timestamp): string {
-  if (t.t_ms === "never") {
-    return "never";
-  }
-  return new Date(t.t_ms).toISOString();
-}
-
-export function timestampDifference(t1: Timestamp, t2: Timestamp): Duration {
-  if (t1.t_ms === "never") {
-    return { d_ms: "forever" };
-  }
-  if (t2.t_ms === "never") {
-    return { d_ms: "forever" };
-  }
-  return { d_ms: Math.abs(t1.t_ms - t2.t_ms) };
-}
-
-export function timestampToIsoString(t: Timestamp): string {
-  if (t.t_ms === "never") {
-    return "<never>";
-  } else {
-    return new Date(t.t_ms).toISOString();
-  }
-}
-
-export function timestampIsBetween(
-  t: Timestamp,
-  start: Timestamp,
-  end: Timestamp,
-): boolean {
-  if (timestampCmp(t, start) < 0) {
-    return false;
-  }
-  if (timestampCmp(t, end) > 0) {
-    return false;
-  }
-  return true;
-}
-
-export const codecForTimestamp: Codec<Timestamp> = {
-  decode(x: any, c?: Context): Timestamp {
-    const t_ms = x.t_ms;
-    if (typeof t_ms === "string") {
-      if (t_ms === "never") {
-        return { t_ms: "never" };
+export const codecForTimestamp: Codec<TalerProtocolTimestamp> = {
+  decode(x: any, c?: Context): TalerProtocolTimestamp {
+    const t_s = x.t_s;
+    if (typeof t_s === "string") {
+      if (t_s === "never") {
+        return { t_s: "never" };
       }
       throw Error(`expected timestamp at ${renderContext(c)}`);
     }
-    if (typeof t_ms === "number") {
-      return { t_ms };
+    if (typeof t_s === "number") {
+      return { t_s };
     }
     throw Error(`expected timestamp at ${renderContext(c)}`);
   },
 };
 
-export const codecForDuration: Codec<Duration> = {
-  decode(x: any, c?: Context): Duration {
-    const d_ms = x.d_ms;
-    if (typeof d_ms === "string") {
-      if (d_ms === "forever") {
-        return { d_ms: "forever" };
+export const codecForDuration: Codec<TalerProtocolDuration> = {
+  decode(x: any, c?: Context): TalerProtocolDuration {
+    const d_us = x.d_us;
+    if (typeof d_us === "string") {
+      if (d_us === "forever") {
+        return { d_us: "forever" };
       }
       throw Error(`expected duration at ${renderContext(c)}`);
     }
-    if (typeof d_ms === "number") {
-      return { d_ms };
+    if (typeof d_us === "number") {
+      return { d_us };
     }
     throw Error(`expected duration at ${renderContext(c)}`);
   },
diff --git a/packages/taler-util/src/transactionsTypes.ts 
b/packages/taler-util/src/transactionsTypes.ts
index e780ca41..b9a227b6 100644
--- a/packages/taler-util/src/transactionsTypes.ts
+++ b/packages/taler-util/src/transactionsTypes.ts
@@ -24,7 +24,7 @@
 /**
  * Imports.
  */
-import { Timestamp } from "./time.js";
+import { TalerProtocolTimestamp } from "./time.js";
 import {
   AmountString,
   Product,
@@ -42,7 +42,7 @@ import {
   codecForList,
   codecForAny,
 } from "./codec.js";
-import { TalerErrorDetails } from "./walletTypes.js";
+import { TalerErrorDetail } from "./walletTypes.js";
 
 export interface TransactionsRequest {
   /**
@@ -73,7 +73,7 @@ export interface TransactionCommon {
   type: TransactionType;
 
   // main timestamp of the transaction
-  timestamp: Timestamp;
+  timestamp: TalerProtocolTimestamp;
 
   // true if the transaction is still pending, false otherwise
   // If a transaction is not longer pending, its timestamp will be updated,
@@ -92,7 +92,7 @@ export interface TransactionCommon {
   // Amount added or removed from the wallet's balance (including all fees and 
other costs)
   amountEffective: AmountString;
 
-  error?: TalerErrorDetails;
+  error?: TalerErrorDetail;
 }
 
 export type Transaction =
diff --git a/packages/taler-util/src/types-test.ts 
b/packages/taler-util/src/types-test.ts
index 6998bb5f..e8af1311 100644
--- a/packages/taler-util/src/types-test.ts
+++ b/packages/taler-util/src/types-test.ts
@@ -29,13 +29,13 @@ test("contract terms validation", (t) => {
     merchant_pub: "12345",
     merchant: { name: "Foo" },
     order_id: "test_order",
-    pay_deadline: { t_ms: 42 },
-    wire_transfer_deadline: { t_ms: 42 },
+    pay_deadline: { t_s: 42 },
+    wire_transfer_deadline: { t_s: 42 },
     merchant_base_url: "https://example.com/pay";,
     products: [],
-    refund_deadline: { t_ms: 42 },
+    refund_deadline: { t_s: 42 },
     summary: "hello",
-    timestamp: { t_ms: 42 },
+    timestamp: { t_s: 42 },
     wire_method: "test",
   };
 
@@ -71,13 +71,13 @@ test("contract terms validation (locations)", (t) => {
       },
     },
     order_id: "test_order",
-    pay_deadline: { t_ms: 42 },
-    wire_transfer_deadline: { t_ms: 42 },
+    pay_deadline: { t_s: 42 },
+    wire_transfer_deadline: { t_s: 42 },
     merchant_base_url: "https://example.com/pay";,
     products: [],
-    refund_deadline: { t_ms: 42 },
+    refund_deadline: { t_s: 42 },
     summary: "hello",
-    timestamp: { t_ms: 42 },
+    timestamp: { t_s: 42 },
     wire_method: "test",
     delivery_location: {
       country: "FR",
diff --git a/packages/taler-util/src/walletTypes.ts 
b/packages/taler-util/src/walletTypes.ts
index 9a3f1f8f..1f88c39e 100644
--- a/packages/taler-util/src/walletTypes.ts
+++ b/packages/taler-util/src/walletTypes.ts
@@ -32,7 +32,11 @@ import {
   codecForAmountJson,
   codecForAmountString,
 } from "./amounts.js";
-import { Timestamp, codecForTimestamp } from "./time.js";
+import {
+  AbsoluteTime,
+  codecForTimestamp,
+  TalerProtocolTimestamp,
+} from "./time.js";
 import {
   buildCodecForObject,
   codecForString,
@@ -56,6 +60,7 @@ import {
 import { OrderShortInfo, codecForOrderShortInfo } from 
"./transactionsTypes.js";
 import { BackupRecovery } from "./backupTypes.js";
 import { PaytoUri } from "./payto.js";
+import { TalerErrorCode } from "./taler-error-codes.js";
 
 /**
  * Response for the create reserve request to the wallet.
@@ -132,7 +137,7 @@ export interface ConfirmPayResultDone {
 export interface ConfirmPayResultPending {
   type: ConfirmPayResultType.Pending;
 
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 }
 
 export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending;
@@ -299,7 +304,7 @@ export interface PrepareTipResult {
    * Time when the tip will expire.  After it expired, it can't be picked
    * up anymore.
    */
-  expirationTimestamp: Timestamp;
+  expirationTimestamp: TalerProtocolTimestamp;
 }
 
 export const codecForPrepareTipResult = (): Codec<PrepareTipResult> =>
@@ -451,16 +456,15 @@ export interface WalletDiagnostics {
   dbOutdated: boolean;
 }
 
-export interface TalerErrorDetails {
-  code: number;
-  hint: string;
-  message: string;
-  details: unknown;
+export interface TalerErrorDetail {
+  code: TalerErrorCode;
+  hint?: string;
+  [x: string]: unknown;
 }
 
 /**
  * Minimal information needed about a planchet for unblinding a signature.
- * 
+ *
  * Can be a withdrawal/tipping/refresh planchet.
  */
 export interface PlanchetUnblindInfo {
@@ -527,8 +531,8 @@ export interface DepositInfo {
   coinPub: string;
   coinPriv: string;
   spendAmount: AmountJson;
-  timestamp: Timestamp;
-  refundDeadline: Timestamp;
+  timestamp: TalerProtocolTimestamp;
+  refundDeadline: TalerProtocolTimestamp;
   merchantPub: string;
   feeDeposit: AmountJson;
   wireInfoHash: string;
@@ -846,7 +850,7 @@ export interface CoreApiResponseError {
   type: "error";
   operation: string;
   id: string;
-  error: TalerErrorDetails;
+  error: TalerErrorDetail;
 }
 
 export interface WithdrawTestBalanceRequest {
diff --git a/packages/taler-wallet-cli/src/harness/harness.ts 
b/packages/taler-wallet-cli/src/harness/harness.ts
index 1500d774..46ddd0ed 100644
--- a/packages/taler-wallet-cli/src/harness/harness.ts
+++ b/packages/taler-wallet-cli/src/harness/harness.ts
@@ -49,7 +49,7 @@ import {
   HarnessExchangeBankAccount,
   NodeHttpLib,
   openPromise,
-  OperationFailedError,
+  TalerError,
   WalletCoreApiClient,
 } from "@gnu-taler/taler-wallet-core";
 import {
@@ -227,19 +227,19 @@ export class GlobalTestState {
     this.servers = [];
   }
 
-  async assertThrowsOperationErrorAsync(
+  async assertThrowsTalerErrorAsync(
     block: () => Promise<void>,
-  ): Promise<OperationFailedError> {
+  ): Promise<TalerError> {
     try {
       await block();
     } catch (e) {
-      if (e instanceof OperationFailedError) {
+      if (e instanceof TalerError) {
         return e;
       }
-      throw Error(`expected OperationFailedError to be thrown, but got ${e}`);
+      throw Error(`expected TalerError to be thrown, but got ${e}`);
     }
     throw Error(
-      `expected OperationFailedError to be thrown, but block finished without 
throwing`,
+      `expected TalerError to be thrown, but block finished without throwing`,
     );
   }
 
@@ -478,7 +478,7 @@ class BankServiceBase {
     protected globalTestState: GlobalTestState,
     protected bankConfig: BankConfig,
     protected configFile: string,
-  ) { }
+  ) {}
 }
 
 /**
@@ -920,7 +920,7 @@ export class FakeBankService {
     private globalTestState: GlobalTestState,
     private bankConfig: FakeBankConfig,
     private configFile: string,
-  ) { }
+  ) {}
 
   async start(): Promise<void> {
     this.proc = this.globalTestState.spawnService(
@@ -1175,7 +1175,7 @@ export class ExchangeService implements 
ExchangeServiceInterface {
     private exchangeConfig: ExchangeConfig,
     private configFilename: string,
     private keyPair: EddsaKeyPair,
-  ) { }
+  ) {}
 
   get name() {
     return this.exchangeConfig.name;
@@ -1276,7 +1276,7 @@ export class ExchangeService implements 
ExchangeServiceInterface {
             accTargetType,
             `${this.exchangeConfig.currency}:0.01`,
             `${this.exchangeConfig.currency}:0.01`,
-            // `${this.exchangeConfig.currency}:0.01`,
+            `${this.exchangeConfig.currency}:0.01`,
             "upload",
           ],
         );
@@ -1398,7 +1398,7 @@ export class MerchantApiClient {
   constructor(
     private baseUrl: string,
     public readonly auth: MerchantAuthConfiguration,
-  ) { }
+  ) {}
 
   async changeAuth(auth: MerchantAuthConfiguration): Promise<void> {
     const url = new URL("private/auth", this.baseUrl);
@@ -1591,7 +1591,7 @@ export class MerchantService implements 
MerchantServiceInterface {
     private globalState: GlobalTestState,
     private merchantConfig: MerchantConfig,
     private configFilename: string,
-  ) { }
+  ) {}
 
   private currentTimetravel: Duration | undefined;
 
@@ -1888,8 +1888,10 @@ export class WalletCli {
         const resp = await sh(
           self.globalTestState,
           `wallet-${self.name}`,
-          `taler-wallet-cli ${self.timetravelArg ?? ""
-          } --no-throttle -LTRACE --wallet-db '${self.dbfile
+          `taler-wallet-cli ${
+            self.timetravelArg ?? ""
+          } --no-throttle -LTRACE --wallet-db '${
+            self.dbfile
           }' api '${op}' ${shellWrap(JSON.stringify(payload))}`,
         );
         console.log("--- wallet core response ---");
@@ -1902,7 +1904,7 @@ export class WalletCli {
           throw new Error("wallet CLI did not return a proper JSON response");
         }
         if (ar.type === "error") {
-          throw new OperationFailedError(ar.error);
+          throw TalerError.fromUncheckedDetail(ar.error);
         }
         return ar.result;
       },
diff --git a/packages/taler-wallet-cli/src/harness/merchantApiTypes.ts 
b/packages/taler-wallet-cli/src/harness/merchantApiTypes.ts
index a93a0ed2..dcb1a2b4 100644
--- a/packages/taler-wallet-cli/src/harness/merchantApiTypes.ts
+++ b/packages/taler-wallet-cli/src/harness/merchantApiTypes.ts
@@ -38,7 +38,7 @@ import {
   codecForAny,
   buildCodecForUnion,
   AmountString,
-  Timestamp,
+  AbsoluteTime,
   CoinPublicKeyString,
   EddsaPublicKeyString,
   codecForAmountString,
@@ -195,7 +195,7 @@ export interface RefundDetails {
   reason: string;
 
   // when was the refund approved
-  timestamp: Timestamp;
+  timestamp: AbsoluteTime;
 
   // Total amount that was refunded (minus a refund fee).
   amount: AmountString;
@@ -209,7 +209,7 @@ export interface TransactionWireTransfer {
   wtid: string;
 
   // execution time of the wire transfer
-  execution_time: Timestamp;
+  execution_time: AbsoluteTime;
 
   // Total amount that has been wire transferred
   // to the merchant
@@ -247,10 +247,10 @@ export interface ReserveStatusEntry {
   reserve_pub: string;
 
   // Timestamp when it was established
-  creation_time: Timestamp;
+  creation_time: AbsoluteTime;
 
   // Timestamp when it expires
-  expiration_time: Timestamp;
+  expiration_time: AbsoluteTime;
 
   // Initial amount as per reserve creation call
   merchant_initial_amount: AmountString;
@@ -281,7 +281,7 @@ export interface TipCreateConfirmation {
   tip_status_url: string;
 
   // when does the tip expire
-  tip_expiration: Timestamp;
+  tip_expiration: AbsoluteTime;
 }
 
 export interface TipCreateRequest {
diff --git a/packages/taler-wallet-cli/src/index.ts 
b/packages/taler-wallet-cli/src/index.ts
index f754ca91..e7b76fa9 100644
--- a/packages/taler-wallet-cli/src/index.ts
+++ b/packages/taler-wallet-cli/src/index.ts
@@ -49,14 +49,13 @@ import {
 import {
   NodeHttpLib,
   getDefaultNodeWallet,
-  OperationFailedAndReportedError,
-  OperationFailedError,
   NodeThreadCryptoWorkerFactory,
   CryptoApi,
   walletCoreDebugFlags,
   WalletApiOperation,
   WalletCoreApiClient,
   Wallet,
+  getErrorDetailFromException,
 } from "@gnu-taler/taler-wallet-core";
 import { lintExchangeDeployment } from "./lint.js";
 import { runBench1 } from "./bench1.js";
@@ -206,18 +205,12 @@ async function withWallet<T>(
     const ret = await f(w);
     return ret;
   } catch (e) {
-    if (
-      e instanceof OperationFailedAndReportedError ||
-      e instanceof OperationFailedError
-    ) {
-      console.error("Operation failed: " + e.message);
-      console.error(
-        "Error details:",
-        JSON.stringify(e.operationError, undefined, 2),
-      );
-    } else {
-      console.error("caught unhandled exception (bug?):", e);
-    }
+    const ed = getErrorDetailFromException(e);
+    console.error("Operation failed: " + ed.message);
+    console.error(
+      "Error details:",
+      JSON.stringify(ed.operationError, undefined, 2),
+    );
     process.exit(1);
   } finally {
     logger.info("operation with wallet finished, stopping");
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts
index 8e410975..97dbf369 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts
@@ -103,11 +103,10 @@ export async function runBankApiTest(t: GlobalTestState) {
 
   // Make sure that registering twice results in a 409 Conflict
   {
-    const e = await t.assertThrowsAsync(async () => {
+    const e = await t.assertThrowsTalerErrorAsync(async () => {
       await BankApi.registerAccount(bank, "user1", "pw1");
     });
-    t.assertAxiosError(e);
-    t.assertTrue(e.response?.status === 409);
+    t.assertTrue(e.errorDetail.httpStatusCode === 409);
   }
 
   let balResp = await BankAccessApi.getAccountBalance(bank, bankUser);
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts
index 28cca075..ec1d9f64 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-denom-unoffered.ts
@@ -20,25 +20,21 @@
 import {
   PreparePayResultType,
   TalerErrorCode,
-  TalerErrorDetails,
-  TransactionType,
+  TalerErrorDetail,
 } from "@gnu-taler/taler-util";
-import {
-  WalletApiOperation,
-} from "@gnu-taler/taler-wallet-core";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { makeEventId } from "@gnu-taler/taler-wallet-core";
 import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";
-import { createSimpleTestkudosEnvironment, withdrawViaBank } from 
"../harness/helpers.js";
+import {
+  createSimpleTestkudosEnvironment,
+  withdrawViaBank,
+} from "../harness/helpers.js";
 
 export async function runDenomUnofferedTest(t: GlobalTestState) {
   // Set up test environment
 
-  const {
-    wallet,
-    bank,
-    exchange,
-    merchant,
-  } = await createSimpleTestkudosEnvironment(t);
+  const { wallet, bank, exchange, merchant } =
+    await createSimpleTestkudosEnvironment(t);
 
   // Withdraw digital cash into the wallet.
 
@@ -95,19 +91,23 @@ export async function runDenomUnofferedTest(t: 
GlobalTestState) {
       preparePayResult.status === PreparePayResultType.PaymentPossible,
     );
 
-    const exc = await t.assertThrowsAsync(async () => {
+    const exc = await t.assertThrowsTalerErrorAsync(async () => {
       await wallet.client.call(WalletApiOperation.ConfirmPay, {
         proposalId: preparePayResult.proposalId,
       });
     });
 
-    const errorDetails: TalerErrorDetails = exc.operationError;
+    t.assertTrue(
+      exc.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED),
+    );
+
     // FIXME: We might want a more specific error code here!
     t.assertDeepEqual(
-      errorDetails.code,
+      exc.errorDetail.innerError.code,
       TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
     );
-    const merchantErrorCode = (errorDetails.details as any).errorResponse.code;
+    const merchantErrorCode = (exc.errorDetail.innerError.errorResponse as any)
+      .code;
     t.assertDeepEqual(
       merchantErrorCode,
       TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND,
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts
index f9c7c4b9..dc650830 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts
@@ -181,16 +181,22 @@ export async function runExchangeManagementTest(t: 
GlobalTestState) {
     },
   });
 
-  const err1 = await t.assertThrowsOperationErrorAsync(async () => {
+  const err1 = await t.assertThrowsTalerErrorAsync(async () => {
     await wallet.client.call(WalletApiOperation.AddExchange, {
       exchangeBaseUrl: faultyExchange.baseUrl,
     });
   });
 
+  // Updating the exchange from the base URL is technically a pending operation
+  // and it will be retried later.
+  t.assertTrue(
+    err1.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED),
+  );
+
   // Response is malformed, since it didn't even contain a version code
   // in a format the wallet can understand.
   t.assertTrue(
-    err1.operationError.code ===
+    err1.errorDetail.innerError.code ===
       TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
   );
 
@@ -223,14 +229,18 @@ export async function runExchangeManagementTest(t: 
GlobalTestState) {
     },
   });
 
-  const err2 = await t.assertThrowsOperationErrorAsync(async () => {
+  const err2 = await t.assertThrowsTalerErrorAsync(async () => {
     await wallet.client.call(WalletApiOperation.AddExchange, {
       exchangeBaseUrl: faultyExchange.baseUrl,
     });
   });
 
   t.assertTrue(
-    err2.operationError.code ===
+    err2.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED),
+  );
+
+  t.assertTrue(
+    err2.errorDetail.innerError.code ===
       TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
   );
 
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts
index ed07114e..e2b81ee6 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts
@@ -18,11 +18,11 @@
  * Imports.
  */
 import {
+  AbsoluteTime,
   codecForExchangeKeysJson,
   DenominationPubKey,
   Duration,
   durationFromSpec,
-  stringifyTimestamp,
 } from "@gnu-taler/taler-util";
 import {
   NodeHttpLib,
@@ -174,27 +174,37 @@ export async function runExchangeTimetravelTest(t: 
GlobalTestState) {
   const denomPubs1 = keys1.denoms.map((x) => {
     return {
       denomPub: x.denom_pub,
-      expireDeposit: stringifyTimestamp(x.stamp_expire_deposit),
+      expireDeposit: AbsoluteTime.stringify(
+        AbsoluteTime.fromTimestamp(x.stamp_expire_deposit),
+      ),
     };
   });
 
   const denomPubs2 = keys2.denoms.map((x) => {
     return {
       denomPub: x.denom_pub,
-      expireDeposit: stringifyTimestamp(x.stamp_expire_deposit),
+      expireDeposit: AbsoluteTime.stringify(
+        AbsoluteTime.fromTimestamp(x.stamp_expire_deposit),
+      ),
     };
   });
   const dps2 = new Set(denomPubs2.map((x) => x.denomPub));
 
   console.log("=== KEYS RESPONSE 1 ===");
 
-  console.log("list issue date", stringifyTimestamp(keys1.list_issue_date));
+  console.log(
+    "list issue date",
+    AbsoluteTime.stringify(AbsoluteTime.fromTimestamp(keys1.list_issue_date)),
+  );
   console.log("num denoms", keys1.denoms.length);
   console.log("denoms", JSON.stringify(denomPubs1, undefined, 2));
 
   console.log("=== KEYS RESPONSE 2 ===");
 
-  console.log("list issue date", stringifyTimestamp(keys2.list_issue_date));
+  console.log(
+    "list issue date",
+    AbsoluteTime.stringify(AbsoluteTime.fromTimestamp(keys2.list_issue_date)),
+  );
   console.log("num denoms", keys2.denoms.length);
   console.log("denoms", JSON.stringify(denomPubs2, undefined, 2));
 
@@ -214,8 +224,8 @@ export async function runExchangeTimetravelTest(t: 
GlobalTestState) {
         `denomination with public key ${da.denomPub} is not present in new 
/keys response`,
       );
       console.log(
-        `the new /keys response was issued ${stringifyTimestamp(
-          keys2.list_issue_date,
+        `the new /keys response was issued ${AbsoluteTime.stringify(
+          AbsoluteTime.fromTimestamp(keys2.list_issue_date),
         )}`,
       );
       console.log(
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts
index 33aad80d..8f7d7771 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts
@@ -18,9 +18,8 @@
  * Imports.
  */
 import {
+  AbsoluteTime,
   ContractTerms,
-  getTimestampNow,
-  timestampTruncateToSecond,
 } from "@gnu-taler/taler-util";
 import {
   WalletApiOperation,
@@ -277,7 +276,7 @@ export async function runLibeufinBasicTest(t: 
GlobalTestState) {
     summary: "Buy me!",
     amount: "EUR:5",
     fulfillment_url: "taler://fulfillment-success/thx",
-    wire_transfer_deadline: timestampTruncateToSecond(getTimestampNow()),
+    wire_transfer_deadline: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
   };
 
   await makeTestPayment(t, { wallet, merchant, order });
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-merchant-instances-delete.ts
 
b/packages/taler-wallet-cli/src/integrationtests/test-merchant-instances-delete.ts
index ef926c4a..2bef87b2 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-merchant-instances-delete.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-merchant-instances-delete.ts
@@ -25,7 +25,7 @@ import {
   MerchantApiClient,
   MerchantService,
   setupDb,
-  getPayto
+  getPayto,
 } from "../harness/harness.js";
 
 /**
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts
index 0fa9ec81..09b546f4 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-pay-abort.ts
@@ -123,7 +123,7 @@ export async function runPayAbortTest(t: GlobalTestState) {
     },
   });
 
-  await t.assertThrowsOperationErrorAsync(async () => {
+  await t.assertThrowsTalerErrorAsync(async () => {
     await wallet.client.call(WalletApiOperation.ConfirmPay, {
       proposalId: preparePayResult.proposalId,
     });
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-payment-claim.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-payment-claim.ts
index ba3bd8e0..e878854f 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-payment-claim.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-payment-claim.ts
@@ -17,8 +17,15 @@
 /**
  * Imports.
  */
-import { GlobalTestState, MerchantPrivateApi, WalletCli } from 
"../harness/harness.js";
-import { createSimpleTestkudosEnvironment, withdrawViaBank } from 
"../harness/helpers.js";
+import {
+  GlobalTestState,
+  MerchantPrivateApi,
+  WalletCli,
+} from "../harness/harness.js";
+import {
+  createSimpleTestkudosEnvironment,
+  withdrawViaBank,
+} from "../harness/helpers.js";
 import { PreparePayResultType } from "@gnu-taler/taler-util";
 import { TalerErrorCode } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
@@ -29,12 +36,8 @@ import { WalletApiOperation } from 
"@gnu-taler/taler-wallet-core";
 export async function runPaymentClaimTest(t: GlobalTestState) {
   // Set up test environment
 
-  const {
-    wallet,
-    bank,
-    exchange,
-    merchant,
-  } = await createSimpleTestkudosEnvironment(t);
+  const { wallet, bank, exchange, merchant } =
+    await createSimpleTestkudosEnvironment(t);
 
   const walletTwo = new WalletCli(t, "two");
 
@@ -73,7 +76,7 @@ export async function runPaymentClaimTest(t: GlobalTestState) 
{
     preparePayResult.status === PreparePayResultType.PaymentPossible,
   );
 
-  t.assertThrowsOperationErrorAsync(async () => {
+  t.assertThrowsTalerErrorAsync(async () => {
     await walletTwo.client.call(WalletApiOperation.PreparePayForUri, {
       talerPayUri,
     });
@@ -93,14 +96,19 @@ export async function runPaymentClaimTest(t: 
GlobalTestState) {
 
   walletTwo.deleteDatabase();
 
-  const err = await t.assertThrowsOperationErrorAsync(async () => {
+  const err = await t.assertThrowsTalerErrorAsync(async () => {
     await walletTwo.client.call(WalletApiOperation.PreparePayForUri, {
       talerPayUri,
     });
   });
 
   t.assertTrue(
-    err.operationError.code === TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED,
+    err.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED),
+  );
+
+  t.assertTrue(
+    err.errorDetail.innerError.code ===
+      TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED,
   );
 
   await t.shutdown();
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-payment-transient.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-payment-transient.ts
index 75d44d49..7e178077 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-payment-transient.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-payment-transient.ts
@@ -32,7 +32,7 @@ import {
   ConfirmPayResultType,
   PreparePayResultType,
   TalerErrorCode,
-  TalerErrorDetails,
+  TalerErrorDetail,
   URL,
 } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
@@ -135,11 +135,9 @@ export async function runPaymentTransientTest(t: 
GlobalTestState) {
       }
       faultInjected = true;
       console.log("injecting pay fault");
-      const err: TalerErrorDetails = {
+      const err: TalerErrorDetail = {
         code: TalerErrorCode.GENERIC_DB_COMMIT_FAILED,
-        details: {},
-        hint: "huh",
-        message: "something went wrong",
+        hint: "something went wrong",
       };
       ctx.responseBody = Buffer.from(JSON.stringify(err));
       ctx.statusCode = 500;
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts
index 230fc942..574177c8 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts
@@ -18,7 +18,10 @@
  * Imports.
  */
 import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";
-import { createSimpleTestkudosEnvironment, withdrawViaBank } from 
"../harness/helpers.js";
+import {
+  createSimpleTestkudosEnvironment,
+  withdrawViaBank,
+} from "../harness/helpers.js";
 import { durationFromSpec } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 
@@ -28,12 +31,8 @@ import { WalletApiOperation } from 
"@gnu-taler/taler-wallet-core";
 export async function runRefundAutoTest(t: GlobalTestState) {
   // Set up test environment
 
-  const {
-    wallet,
-    bank,
-    exchange,
-    merchant,
-  } = await createSimpleTestkudosEnvironment(t);
+  const { wallet, bank, exchange, merchant } =
+    await createSimpleTestkudosEnvironment(t);
 
   // Withdraw digital cash into the wallet.
 
@@ -46,7 +45,7 @@ export async function runRefundAutoTest(t: GlobalTestState) {
       amount: "TESTKUDOS:5",
       fulfillment_url: "taler://fulfillment-success/thx",
       auto_refund: {
-        d_ms: 3000,
+        d_us: 3000 * 1000,
       },
     },
     refund_delay: durationFromSpec({ minutes: 5 }),
diff --git a/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts
index c6442a24..24600384 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts
@@ -24,10 +24,8 @@ import {
   applyTimeTravel,
 } from "../harness/helpers.js";
 import {
+  AbsoluteTime,
   durationFromSpec,
-  timestampAddDuration,
-  getTimestampNow,
-  timestampTruncateToSecond,
 } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 
@@ -55,9 +53,9 @@ export async function runRefundGoneTest(t: GlobalTestState) {
       summary: "Buy me!",
       amount: "TESTKUDOS:5",
       fulfillment_url: "taler://fulfillment-success/thx",
-      pay_deadline: timestampTruncateToSecond(
-        timestampAddDuration(
-          getTimestampNow(),
+      pay_deadline: AbsoluteTime.toTimestamp(
+        AbsoluteTime.addDuration(
+          AbsoluteTime.now(),
           durationFromSpec({
             minutes: 10,
           }),
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts
index 93c22af7..146603f3 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts
@@ -26,9 +26,9 @@ import {
   findDenomOrThrow,
   generateReserveKeypair,
   NodeHttpLib,
-  OperationFailedError,
   refreshCoin,
   SynchronousCryptoWorkerFactory,
+  TalerError,
   topupReserveWithDemobank,
   withdrawCoin,
 } from "@gnu-taler/taler-wallet-core";
@@ -95,9 +95,9 @@ export async function runWalletDblessTest(t: GlobalTestState) 
{
       newDenoms: refreshDenoms,
     });
   } catch (e) {
-    if (e instanceof OperationFailedError) {
+    if (e instanceof TalerError) {
       console.log(e);
-      console.log(j2s(e.operationError));
+      console.log(j2s(e.errorDetail));
     } else {
       console.log(e);
     }
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts
index 19668d76..0125b3b4 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts
@@ -63,7 +63,7 @@ export async function runWithdrawalAbortBankTest(t: 
GlobalTestState) {
   //
   // WHY ?!
   //
-  const e = await t.assertThrowsOperationErrorAsync(async () => {
+  const e = await t.assertThrowsTalerErrorAsync(async () => {
     await wallet.client.call(
       WalletApiOperation.AcceptBankIntegratedWithdrawal,
       {
@@ -73,7 +73,7 @@ export async function runWithdrawalAbortBankTest(t: 
GlobalTestState) {
     );
   });
   t.assertDeepEqual(
-    e.operationError.code,
+    e.errorDetail.code,
     TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK,
   );
 
diff --git a/packages/taler-wallet-core/src/bank-api-client.ts 
b/packages/taler-wallet-core/src/bank-api-client.ts
index a61ea2ee..14bf0717 100644
--- a/packages/taler-wallet-core/src/bank-api-client.ts
+++ b/packages/taler-wallet-core/src/bank-api-client.ts
@@ -25,12 +25,15 @@ import {
   AmountString,
   buildCodecForObject,
   Codec,
+  codecForAny,
   codecForString,
   encodeCrock,
   getRandomBytes,
   j2s,
   Logger,
+  TalerErrorCode,
 } from "@gnu-taler/taler-util";
+import { TalerError } from "./errors.js";
 import {
   HttpRequestLibrary,
   readSuccessResponseJsonOrErrorCode,
@@ -102,10 +105,16 @@ export namespace BankApi {
     const resp = await bank.http.postJson(url.href, { username, password });
     let paytoUri = `payto://x-taler-bank/localhost/${username}`;
     if (resp.status !== 200 && resp.status !== 202) {
-      logger.error(`${j2s(await resp.json())}`)
-      throw new Error();
+      logger.error(`${j2s(await resp.json())}`);
+      throw TalerError.fromDetail(
+        TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR,
+        {
+          httpStatusCode: resp.status,
+        },
+      );
     }
     try {
+      // Pybank has no body, thus this might throw.
       const respJson = await resp.json();
       // LibEuFin demobank returns payto URI in response
       if (respJson.paytoUri) {
diff --git a/packages/taler-wallet-core/src/common.ts 
b/packages/taler-wallet-core/src/common.ts
index 957ba1ca..d37bbe6e 100644
--- a/packages/taler-wallet-core/src/common.ts
+++ b/packages/taler-wallet-core/src/common.ts
@@ -34,7 +34,8 @@ import {
   BalancesResponse,
   AmountJson,
   DenominationPubKey,
-  Timestamp,
+  AbsoluteTime,
+  TalerProtocolTimestamp,
 } from "@gnu-taler/taler-util";
 import { CryptoApi } from "./crypto/workers/cryptoApi.js";
 import { ExchangeDetailsRecord, ExchangeRecord, WalletStoresV1 } from 
"./db.js";
@@ -165,22 +166,22 @@ export interface DenomInfo {
   /**
    * Validity start date of the denomination.
    */
-  stampStart: Timestamp;
+  stampStart: TalerProtocolTimestamp;
 
   /**
    * Date after which the currency can't be withdrawn anymore.
    */
-  stampExpireWithdraw: Timestamp;
+  stampExpireWithdraw: TalerProtocolTimestamp;
 
   /**
    * Date after the denomination officially doesn't exist anymore.
    */
-  stampExpireLegal: Timestamp;
+  stampExpireLegal: TalerProtocolTimestamp;
 
   /**
    * Data after which coins of this denomination can't be deposited anymore.
    */
-  stampExpireDeposit: Timestamp;
+  stampExpireDeposit: TalerProtocolTimestamp;
 }
 
 export type NotificationListener = (n: WalletNotification) => void;
diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts 
b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts
index 82039734..d91aa08a 100644
--- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts
@@ -207,7 +207,7 @@ export class CryptoApi {
       }
     };
     ws.terminationTimerHandle = timer.after(15 * 1000, destroy);
-    ws.terminationTimerHandle.unref();
+    //ws.terminationTimerHandle.unref();
   }
 
   handleWorkerError(ws: WorkerState, e: any): void {
diff --git 
a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts 
b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
index b51d499d..b2706788 100644
--- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
@@ -67,13 +67,11 @@ import {
   setupWithdrawPlanchet,
   stringToBytes,
   TalerSignaturePurpose,
-  Timestamp,
-  timestampTruncateToSecond,
-  typedArrayConcat,
+  AbsoluteTime,
   BlindedDenominationSignature,
-  RsaUnblindedSignature,
   UnblindedSignature,
   PlanchetUnblindInfo,
+  TalerProtocolTimestamp,
 } from "@gnu-taler/taler-util";
 import bigint from "big-integer";
 import { DenominationRecord, WireFee } from "../../db.js";
@@ -110,18 +108,16 @@ function amountToBuffer(amount: AmountJson): Uint8Array {
   return u8buf;
 }
 
-function timestampRoundedToBuffer(ts: Timestamp): Uint8Array {
+function timestampRoundedToBuffer(ts: TalerProtocolTimestamp): Uint8Array {
   const b = new ArrayBuffer(8);
   const v = new DataView(b);
-  const tsRounded = timestampTruncateToSecond(ts);
+  // The buffer we sign over represents the timestamp in microseconds.
   if (typeof v.setBigUint64 !== "undefined") {
-    const s = BigInt(tsRounded.t_ms) * BigInt(1000);
+    const s = BigInt(ts.t_s) * BigInt(1000 * 1000);
     v.setBigUint64(0, s);
   } else {
     const s =
-      tsRounded.t_ms === "never"
-        ? bigint.zero
-        : bigint(tsRounded.t_ms).times(1000);
+      ts.t_s === "never" ? bigint.zero : bigint(ts.t_s).multiply(1000 * 1000);
     const arr = s.toArray(2 ** 8).value;
     let offset = 8 - arr.length;
     for (let i = 0; i < arr.length; i++) {
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 2e76ab52..69606b8f 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -35,10 +35,11 @@ import {
   MerchantInfo,
   Product,
   RefreshReason,
-  TalerErrorDetails,
-  Timestamp,
+  TalerErrorDetail,
   UnblindedSignature,
   CoinEnvelope,
+  TalerProtocolTimestamp,
+  TalerProtocolDuration,
 } from "@gnu-taler/taler-util";
 import { RetryInfo } from "./util/retries.js";
 import { PayCoinSelection } from "./util/coinSelection.js";
@@ -152,7 +153,7 @@ export interface ReserveRecord {
   /**
    * Time when the reserve was created.
    */
-  timestampCreated: Timestamp;
+  timestampCreated: TalerProtocolTimestamp;
 
   /**
    * Time when the information about this reserve was posted to the bank.
@@ -161,14 +162,14 @@ export interface ReserveRecord {
    *
    * Set to undefined if that hasn't happened yet.
    */
-  timestampReserveInfoPosted: Timestamp | undefined;
+  timestampReserveInfoPosted: TalerProtocolTimestamp | undefined;
 
   /**
    * Time when the reserve was confirmed by the bank.
    *
    * Set to undefined if not confirmed yet.
    */
-  timestampBankConfirmed: Timestamp | undefined;
+  timestampBankConfirmed: TalerProtocolTimestamp | undefined;
 
   /**
    * Wire information (as payto URI) for the bank account that
@@ -217,11 +218,6 @@ export interface ReserveRecord {
    */
   operationStatus: OperationStatus;
 
-  /**
-   * Time of the last successful status query.
-   */
-  lastSuccessfulStatusQuery: Timestamp | undefined;
-
   /**
    * Retry info, in case the reserve needs to be processed again
    * later, either due to an error or because the wallet needs to
@@ -233,7 +229,7 @@ export interface ReserveRecord {
    * Last error that happened in a reserve operation
    * (either talking to the bank or the exchange).
    */
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 }
 
 /**
@@ -350,22 +346,22 @@ export interface DenominationRecord {
   /**
    * Validity start date of the denomination.
    */
-  stampStart: Timestamp;
+  stampStart: TalerProtocolTimestamp;
 
   /**
    * Date after which the currency can't be withdrawn anymore.
    */
-  stampExpireWithdraw: Timestamp;
+  stampExpireWithdraw: TalerProtocolTimestamp;
 
   /**
    * Date after the denomination officially doesn't exist anymore.
    */
-  stampExpireLegal: Timestamp;
+  stampExpireLegal: TalerProtocolTimestamp;
 
   /**
    * Data after which coins of this denomination can't be deposited anymore.
    */
-  stampExpireDeposit: Timestamp;
+  stampExpireDeposit: TalerProtocolTimestamp;
 
   /**
    * Signature by the exchange's master key over the denomination
@@ -407,7 +403,7 @@ export interface DenominationRecord {
    * Latest list issue date of the "/keys" response
    * that includes this denomination.
    */
-  listIssueDate: Timestamp;
+  listIssueDate: TalerProtocolTimestamp;
 }
 
 /**
@@ -441,7 +437,7 @@ export interface ExchangeDetailsRecord {
    */
   protocolVersion: string;
 
-  reserveClosingDelay: Duration;
+  reserveClosingDelay: TalerProtocolDuration;
 
   /**
    * Signing keys we got from the exchange, can also contain
@@ -478,7 +474,7 @@ export interface ExchangeDetailsRecord {
    *
    * Used during backup merging.
    */
-  termsOfServiceAcceptedTimestamp: Timestamp | undefined;
+  termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp | undefined;
 
   wireInfo: WireInfo;
 }
@@ -503,7 +499,7 @@ export interface ExchangeDetailsPointer {
    * Timestamp when the (masterPublicKey, currency) pointer
    * has been updated.
    */
-  updateClock: Timestamp;
+  updateClock: TalerProtocolTimestamp;
 }
 
 /**
@@ -528,14 +524,14 @@ export interface ExchangeRecord {
   /**
    * Last time when the exchange was updated.
    */
-  lastUpdate: Timestamp | undefined;
+  lastUpdate: TalerProtocolTimestamp | undefined;
 
   /**
    * Next scheduled update for the exchange.
    *
    * (This field must always be present, so we can index on the timestamp.)
    */
-  nextUpdate: Timestamp;
+  nextUpdate: TalerProtocolTimestamp;
 
   /**
    * Next time that we should check if coins need to be refreshed.
@@ -543,13 +539,13 @@ export interface ExchangeRecord {
    * Updated whenever the exchange's denominations are updated or when
    * the refresh check has been done.
    */
-  nextRefreshCheck: Timestamp;
+  nextRefreshCheck: TalerProtocolTimestamp;
 
   /**
    * Last error (if any) for fetching updated information about the
    * exchange.
    */
-  lastError?: TalerErrorDetails;
+  lastError?: TalerErrorDetail;
 
   /**
    * Retry status for fetching updated information about the exchange.
@@ -584,7 +580,7 @@ export interface PlanchetRecord {
 
   withdrawalDone: boolean;
 
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 
   /**
    * Public key of the reserve that this planchet
@@ -793,7 +789,7 @@ export interface ProposalRecord {
    * Timestamp (in ms) of when the record
    * was created.
    */
-  timestamp: Timestamp;
+  timestamp: TalerProtocolTimestamp;
 
   /**
    * Private key for the nonce.
@@ -824,20 +820,20 @@ export interface ProposalRecord {
    */
   retryInfo?: RetryInfo;
 
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 }
 
 /**
  * Status of a tip we got from a merchant.
  */
 export interface TipRecord {
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 
   /**
    * Has the user accepted the tip?  Only after the tip has been accepted coins
    * withdrawn from the tip may be used.
    */
-  acceptedTimestamp: Timestamp | undefined;
+  acceptedTimestamp: TalerProtocolTimestamp | undefined;
 
   /**
    * The tipped amount.
@@ -849,7 +845,7 @@ export interface TipRecord {
   /**
    * Timestamp, the tip can't be picked up anymore after this deadline.
    */
-  tipExpiration: Timestamp;
+  tipExpiration: TalerProtocolTimestamp;
 
   /**
    * The exchange that will sign our coins, chosen by the merchant.
@@ -884,13 +880,13 @@ export interface TipRecord {
    */
   merchantTipId: string;
 
-  createdTimestamp: Timestamp;
+  createdTimestamp: TalerProtocolTimestamp;
 
   /**
    * Timestamp for when the wallet finished picking up the tip
    * from the merchant.
    */
-  pickedUpTimestamp: Timestamp | undefined;
+  pickedUpTimestamp: TalerProtocolTimestamp | undefined;
 
   /**
    * Retry info, even present when the operation isn't active to allow indexing
@@ -926,9 +922,9 @@ export interface RefreshGroupRecord {
    */
   retryInfo: RetryInfo;
 
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 
-  lastErrorPerCoin: { [coinIndex: number]: TalerErrorDetails };
+  lastErrorPerCoin: { [coinIndex: number]: TalerErrorDetail };
 
   /**
    * Unique, randomly generated identifier for this group of
@@ -959,12 +955,12 @@ export interface RefreshGroupRecord {
    */
   statusPerCoin: RefreshCoinStatus[];
 
-  timestampCreated: Timestamp;
+  timestampCreated: TalerProtocolTimestamp;
 
   /**
    * Timestamp when the refresh session finished.
    */
-  timestampFinished: Timestamp | undefined;
+  timestampFinished: TalerProtocolTimestamp | undefined;
 
   /**
    * No coins are pending, but at least one is frozen.
@@ -1025,12 +1021,12 @@ export interface WireFee {
   /**
    * Start date of the fee.
    */
-  startStamp: Timestamp;
+  startStamp: TalerProtocolTimestamp;
 
   /**
    * End date of the fee.
    */
-  endStamp: Timestamp;
+  endStamp: TalerProtocolTimestamp;
 
   /**
    * Signature made by the exchange master key.
@@ -1054,12 +1050,12 @@ export type WalletRefundItem =
 
 export interface WalletRefundItemCommon {
   // Execution time as claimed by the merchant
-  executionTime: Timestamp;
+  executionTime: TalerProtocolTimestamp;
 
   /**
    * Time when the wallet became aware of the refund.
    */
-  obtainedTime: Timestamp;
+  obtainedTime: TalerProtocolTimestamp;
 
   refundAmount: AmountJson;
 
@@ -1141,14 +1137,14 @@ export interface WalletContractData {
   orderId: string;
   merchantBaseUrl: string;
   summary: string;
-  autoRefund: Duration | undefined;
+  autoRefund: TalerProtocolDuration | undefined;
   maxWireFee: AmountJson;
   wireFeeAmortization: number;
-  payDeadline: Timestamp;
-  refundDeadline: Timestamp;
+  payDeadline: TalerProtocolTimestamp;
+  refundDeadline: TalerProtocolTimestamp;
   allowedAuditors: AllowedAuditorInfo[];
   allowedExchanges: AllowedExchangeInfo[];
-  timestamp: Timestamp;
+  timestamp: TalerProtocolTimestamp;
   wireMethod: string;
   wireInfoHash: string;
   maxDepositFee: AmountJson;
@@ -1215,8 +1211,10 @@ export interface PurchaseRecord {
   /**
    * Timestamp of the first time that sending a payment to the merchant
    * for this purchase was successful.
+   *
+   * FIXME: Does this need to be a timestamp, doensn't boolean suffice?
    */
-  timestampFirstSuccessfulPay: Timestamp | undefined;
+  timestampFirstSuccessfulPay: TalerProtocolTimestamp | undefined;
 
   merchantPaySig: string | undefined;
 
@@ -1224,7 +1222,7 @@ export interface PurchaseRecord {
    * When was the purchase made?
    * Refers to the time that the user accepted.
    */
-  timestampAccept: Timestamp;
+  timestampAccept: TalerProtocolTimestamp;
 
   /**
    * Pending refunds for the purchase.  A refund is pending
@@ -1236,7 +1234,7 @@ export interface PurchaseRecord {
    * When was the last refund made?
    * Set to 0 if no refund was made on the purchase.
    */
-  timestampLastRefundStatus: Timestamp | undefined;
+  timestampLastRefundStatus: TalerProtocolTimestamp | undefined;
 
   /**
    * Last session signature that we submitted to /pay (if any).
@@ -1258,7 +1256,7 @@ export interface PurchaseRecord {
 
   payRetryInfo?: RetryInfo;
 
-  lastPayError: TalerErrorDetails | undefined;
+  lastPayError: TalerErrorDetail | undefined;
 
   /**
    * Retry information for querying the refund status with the merchant.
@@ -1268,12 +1266,12 @@ export interface PurchaseRecord {
   /**
    * Last error (or undefined) for querying the refund status with the 
merchant.
    */
-  lastRefundStatusError: TalerErrorDetails | undefined;
+  lastRefundStatusError: TalerErrorDetail | undefined;
 
   /**
    * Continue querying the refund status until this deadline has expired.
    */
-  autoRefundDeadline: Timestamp | undefined;
+  autoRefundDeadline: TalerProtocolTimestamp | undefined;
 
   /**
    * Is the payment frozen?  I.e. did we encounter
@@ -1308,12 +1306,12 @@ export interface WalletBackupConfState {
   /**
    * Timestamp stored in the last backup.
    */
-  lastBackupTimestamp?: Timestamp;
+  lastBackupTimestamp?: TalerProtocolTimestamp;
 
   /**
    * Last time we tried to do a backup.
    */
-  lastBackupCheckTimestamp?: Timestamp;
+  lastBackupCheckTimestamp?: TalerProtocolTimestamp;
   lastBackupNonce?: string;
 }
 
@@ -1362,14 +1360,14 @@ export interface WithdrawalGroupRecord {
    * When was the withdrawal operation started started?
    * Timestamp in milliseconds.
    */
-  timestampStart: Timestamp;
+  timestampStart: TalerProtocolTimestamp;
 
   /**
    * When was the withdrawal operation completed?
    *
    * FIXME: We should probably drop this and introduce an OperationStatus 
field.
    */
-  timestampFinish?: Timestamp;
+  timestampFinish?: TalerProtocolTimestamp;
 
   /**
    * Operation status of the withdrawal group.
@@ -1402,7 +1400,7 @@ export interface WithdrawalGroupRecord {
    */
   retryInfo: RetryInfo;
 
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 }
 
 export interface BankWithdrawUriRecord {
@@ -1429,9 +1427,9 @@ export interface RecoupGroupRecord {
    */
   recoupGroupId: string;
 
-  timestampStarted: Timestamp;
+  timestampStarted: TalerProtocolTimestamp;
 
-  timestampFinished: Timestamp | undefined;
+  timestampFinished: TalerProtocolTimestamp | undefined;
 
   /**
    * Public keys that identify the coins being recouped
@@ -1467,7 +1465,7 @@ export interface RecoupGroupRecord {
   /**
    * Last error that occurred, if any.
    */
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 }
 
 export enum BackupProviderStateTag {
@@ -1482,12 +1480,12 @@ export type BackupProviderState =
     }
   | {
       tag: BackupProviderStateTag.Ready;
-      nextBackupTimestamp: Timestamp;
+      nextBackupTimestamp: TalerProtocolTimestamp;
     }
   | {
       tag: BackupProviderStateTag.Retrying;
       retryInfo: RetryInfo;
-      lastError?: TalerErrorDetails;
+      lastError?: TalerErrorDetail;
     };
 
 export interface BackupProviderTerms {
@@ -1529,7 +1527,7 @@ export interface BackupProviderRecord {
    * Does NOT correspond to the timestamp of the backup,
    * which only changes when the backup content changes.
    */
-  lastBackupCycleTimestamp?: Timestamp;
+  lastBackupCycleTimestamp?: TalerProtocolTimestamp;
 
   /**
    * Proposal that we're currently trying to pay for.
@@ -1594,13 +1592,13 @@ export interface DepositGroupRecord {
 
   depositedPerCoin: boolean[];
 
-  timestampCreated: Timestamp;
+  timestampCreated: TalerProtocolTimestamp;
 
-  timestampFinished: Timestamp | undefined;
+  timestampFinished: TalerProtocolTimestamp | undefined;
 
   operationStatus: OperationStatus;
 
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 
   /**
    * Retry info.
@@ -1618,14 +1616,14 @@ export interface GhostDepositGroupRecord {
    * When multiple deposits for the same contract terms hash
    * have a different timestamp, we choose the earliest one.
    */
-  timestamp: Timestamp;
+  timestamp: TalerProtocolTimestamp;
 
   contractTermsHash: string;
 
   deposits: {
     coinPub: string;
     amount: AmountString;
-    timestamp: Timestamp;
+    timestamp: TalerProtocolTimestamp;
     depositFee: AmountString;
     merchantPub: string;
     coinSig: string;
diff --git a/packages/taler-wallet-core/src/dbless.ts 
b/packages/taler-wallet-core/src/dbless.ts
index 169b6ae0..854a3ea0 100644
--- a/packages/taler-wallet-core/src/dbless.ts
+++ b/packages/taler-wallet-core/src/dbless.ts
@@ -44,7 +44,7 @@ import {
   hashWire,
   Logger,
   parsePaytoUri,
-  Timestamp,
+  AbsoluteTime,
   UnblindedSignature,
 } from "@gnu-taler/taler-util";
 import { DenominationRecord } from "./db.js";
@@ -222,10 +222,11 @@ export async function depositCoin(args: {
   const depositPayto =
     args.depositPayto ?? "payto://x-taler-bank/localhost/foo";
   const wireSalt = encodeCrock(getRandomBytes(16));
+  const timestampNow = AbsoluteTime.toTimestamp(AbsoluteTime.now());
   const contractTermsHash = encodeCrock(getRandomBytes(64));
-  const depositTimestamp = Timestamp.truncateToSecond(Timestamp.now());
-  const refundDeadline = Timestamp.truncateToSecond(Timestamp.now());
-  const wireTransferDeadline = Timestamp.truncateToSecond(Timestamp.now());
+  const depositTimestamp = timestampNow;
+  const refundDeadline = timestampNow;
+  const wireTransferDeadline = timestampNow;
   const merchantPub = encodeCrock(getRandomBytes(32));
   const dp = await cryptoApi.signDepositPermission({
     coinPriv: coin.coinPriv,
diff --git a/packages/taler-wallet-core/src/errors.ts 
b/packages/taler-wallet-core/src/errors.ts
index 3109644a..07a01a76 100644
--- a/packages/taler-wallet-core/src/errors.ts
+++ b/packages/taler-wallet-core/src/errors.ts
@@ -23,63 +23,143 @@
 /**
  * Imports.
  */
-import { TalerErrorCode, TalerErrorDetails } from "@gnu-taler/taler-util";
+import {
+  TalerErrorCode,
+  TalerErrorDetail,
+  TransactionType,
+} from "@gnu-taler/taler-util";
 
-/**
- * This exception is there to let the caller know that an error happened,
- * but the error has already been reported by writing it to the database.
- */
-export class OperationFailedAndReportedError extends Error {
-  static fromCode(
-    ec: TalerErrorCode,
-    message: string,
-    details: Record<string, unknown>,
-  ): OperationFailedAndReportedError {
-    return new OperationFailedAndReportedError(
-      makeErrorDetails(ec, message, details),
-    );
-  }
+export interface DetailsMap {
+  [TalerErrorCode.WALLET_PENDING_OPERATION_FAILED]: {
+    innerError: TalerErrorDetail;
+    transactionId?: string;
+  };
+  [TalerErrorCode.WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT]: {
+    exchangeBaseUrl: string;
+  };
+  [TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE]: {
+    exchangeProtocolVersion: string;
+    walletProtocolVersion: string;
+  };
+  [TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK]: {};
+  [TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID]: {};
+  [TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED]: {
+    orderId: string;
+    claimUrl: string;
+  };
+  [TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED]: {};
+  [TalerErrorCode.WALLET_CONTRACT_TERMS_SIGNATURE_INVALID]: {
+    merchantPub: string;
+    orderId: string;
+  };
+  [TalerErrorCode.WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH]: {
+    baseUrlForDownload: string;
+    baseUrlFromContractTerms: string;
+  };
+  [TalerErrorCode.WALLET_INVALID_TALER_PAY_URI]: {
+    talerPayUri: string;
+  };
+  [TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR]: {};
+  [TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION]: {};
+  [TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE]: {};
+  [TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN]: {};
+  [TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED]: {};
+  [TalerErrorCode.WALLET_NETWORK_ERROR]: {};
+  [TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE]: {};
+  [TalerErrorCode.WALLET_EXCHANGE_COIN_SIGNATURE_INVALID]: {};
+  [TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE]: {};
+  [TalerErrorCode.WALLET_CORE_NOT_AVAILABLE]: {};
+  [TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR]: {};
+}
 
-  constructor(public operationError: TalerErrorDetails) {
-    super(operationError.message);
+type ErrBody<Y> = Y extends keyof DetailsMap ? DetailsMap[Y] : never;
 
-    // Set the prototype explicitly.
-    Object.setPrototypeOf(this, OperationFailedAndReportedError.prototype);
-  }
+export function makeErrorDetail<C extends TalerErrorCode>(
+  code: C,
+  detail: ErrBody<C>,
+  hint?: string,
+): TalerErrorDetail {
+  // FIXME: include default hint?
+  return { code, hint, ...detail };
 }
 
-/**
- * This exception is thrown when an error occurred and the caller is
- * responsible for recording the failure in the database.
- */
-export class OperationFailedError extends Error {
-  static fromCode(
-    ec: TalerErrorCode,
-    message: string,
-    details: Record<string, unknown>,
-  ): OperationFailedError {
-    return new OperationFailedError(makeErrorDetails(ec, message, details));
+export function makePendingOperationFailedError(
+  innerError: TalerErrorDetail,
+  tag: TransactionType,
+  uid: string,
+): TalerError {
+  return TalerError.fromDetail(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED, 
{
+    innerError,
+    transactionId: `${tag}:${uid}`,
+  });
+}
+
+export class TalerError<T = any> extends Error {
+  errorDetail: TalerErrorDetail & T;
+  private constructor(d: TalerErrorDetail & T) {
+    super();
+    this.errorDetail = d;
+    Object.setPrototypeOf(this, TalerError.prototype);
   }
 
-  constructor(public operationError: TalerErrorDetails) {
-    super(operationError.message);
+  static fromDetail<C extends TalerErrorCode>(
+    code: C,
+    detail: ErrBody<C>,
+    hint?: string,
+  ): TalerError {
+    // FIXME: include default hint?
+    return new TalerError<unknown>({ code, hint, ...detail });
+  }
 
-    // Set the prototype explicitly.
-    Object.setPrototypeOf(this, OperationFailedError.prototype);
+  static fromUncheckedDetail(d: TalerErrorDetail): TalerError {
+    return new TalerError<unknown>({ ...d });
+  }
+
+  static fromException(e: any): TalerError {
+    const errDetail = getErrorDetailFromException(e);
+    return new TalerError(errDetail);
+  }
+
+  hasErrorCode<C extends keyof DetailsMap>(
+    code: C,
+  ): this is TalerError<DetailsMap[C]> {
+    return this.errorDetail.code === code;
   }
 }
 
-export function makeErrorDetails(
-  ec: TalerErrorCode,
-  message: string,
-  details: Record<string, unknown>,
-): TalerErrorDetails {
-  return {
-    code: ec,
-    hint: `Error: ${TalerErrorCode[ec]}`,
-    details: details,
-    message,
-  };
+/**
+ * Convert an exception (or anything that was thrown) into
+ * a TalerErrorDetail object.
+ */
+export function getErrorDetailFromException(e: any): TalerErrorDetail {
+  if (e instanceof TalerError) {
+    return e.errorDetail;
+  }
+  if (e instanceof Error) {
+    const err = makeErrorDetail(
+      TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
+      {
+        stack: e.stack,
+      },
+      `unexpected exception (message: ${e.message})`,
+    );
+    return err;
+  }
+  // Something was thrown that is not even an exception!
+  // Try to stringify it.
+  let excString: string;
+  try {
+    excString = e.toString();
+  } catch (e) {
+    // Something went horribly wrong.
+    excString = "can't stringify exception";
+  }
+  const err = makeErrorDetail(
+    TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
+    {},
+    `unexpected exception (not an exception, ${excString})`,
+  );
+  return err;
 }
 
 /**
@@ -89,44 +169,24 @@ export function makeErrorDetails(
  */
 export async function guardOperationException<T>(
   op: () => Promise<T>,
-  onOpError: (e: TalerErrorDetails) => Promise<void>,
+  onOpError: (e: TalerErrorDetail) => Promise<void>,
 ): Promise<T> {
   try {
     return await op();
   } catch (e: any) {
-    if (e instanceof OperationFailedAndReportedError) {
+    if (
+      e instanceof TalerError &&
+      e.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED)
+    ) {
       throw e;
     }
-    if (e instanceof OperationFailedError) {
-      await onOpError(e.operationError);
-      throw new OperationFailedAndReportedError(e.operationError);
-    }
-    if (e instanceof Error) {
-      const opErr = makeErrorDetails(
-        TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
-        `unexpected exception (message: ${e.message})`,
-        {
-          stack: e.stack,
-        },
-      );
-      await onOpError(opErr);
-      throw new OperationFailedAndReportedError(opErr);
-    }
-    // Something was thrown that is not even an exception!
-    // Try to stringify it.
-    let excString: string;
-    try {
-      excString = e.toString();
-    } catch (e) {
-      // Something went horribly wrong.
-      excString = "can't stringify exception";
-    }
-    const opErr = makeErrorDetails(
-      TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
-      `unexpected exception (not an exception, ${excString})`,
-      {},
-    );
+    const opErr = getErrorDetailFromException(e);
     await onOpError(opErr);
-    throw new OperationFailedAndReportedError(opErr);
+    throw TalerError.fromDetail(
+      TalerErrorCode.WALLET_PENDING_OPERATION_FAILED,
+      {
+        innerError: e.errorDetail,
+      },
+    );
   }
 }
diff --git a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts 
b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts
index 2a8c9e36..df25a109 100644
--- a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts
+++ b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts
@@ -27,7 +27,7 @@ import {
 } from "../util/http.js";
 import { RequestThrottler } from "@gnu-taler/taler-util";
 import Axios, { AxiosResponse } from "axios";
-import { OperationFailedError, makeErrorDetails } from "../errors.js";
+import { TalerError } from "../errors.js";
 import { Logger, bytesToString } from "@gnu-taler/taler-util";
 import { TalerErrorCode, URL } from "@gnu-taler/taler-util";
 
@@ -55,14 +55,14 @@ export class NodeHttpLib implements HttpRequestLibrary {
 
     const parsedUrl = new URL(url);
     if (this.throttlingEnabled && this.throttle.applyThrottle(url)) {
-      throw OperationFailedError.fromCode(
+      throw TalerError.fromDetail(
         TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED,
-        `request to origin ${parsedUrl.origin} was throttled`,
         {
           requestMethod: method,
           requestUrl: url,
           throttleStats: this.throttle.getThrottleStats(url),
         },
+        `request to origin ${parsedUrl.origin} was throttled`,
       );
     }
     let timeout: number | undefined;
@@ -83,13 +83,13 @@ export class NodeHttpLib implements HttpRequestLibrary {
         maxRedirects: 0,
       });
     } catch (e: any) {
-      throw OperationFailedError.fromCode(
+      throw TalerError.fromDetail(
         TalerErrorCode.WALLET_NETWORK_ERROR,
-        `${e.message}`,
         {
           requestUrl: url,
           requestMethod: method,
         },
+        `${e.message}`,
       );
     }
 
@@ -105,30 +105,26 @@ export class NodeHttpLib implements HttpRequestLibrary {
         responseJson = JSON.parse(respText);
       } catch (e) {
         logger.trace(`invalid json: '${resp.data}'`);
-        throw new OperationFailedError(
-          makeErrorDetails(
-            TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-            "invalid JSON",
-            {
-              httpStatusCode: resp.status,
-              requestUrl: url,
-              requestMethod: method,
-            },
-          ),
+        throw TalerError.fromDetail(
+          TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
+          {
+            httpStatusCode: resp.status,
+            requestUrl: url,
+            requestMethod: method,
+          },
+          "Could not parse response body as JSON",
         );
       }
       if (responseJson === null || typeof responseJson !== "object") {
         logger.trace(`invalid json (not an object): '${respText}'`);
-        throw new OperationFailedError(
-          makeErrorDetails(
-            TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-            "invalid JSON",
-            {
-              httpStatusCode: resp.status,
-              requestUrl: url,
-              requestMethod: method,
-            },
-          ),
+        throw TalerError.fromDetail(
+          TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
+          {
+            httpStatusCode: resp.status,
+            requestUrl: url,
+            requestMethod: method,
+          },
+          `invalid JSON`,
         );
       }
       return responseJson;
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts 
b/packages/taler-wallet-core/src/operations/backup/export.ts
index 12b30941..35306da6 100644
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ b/packages/taler-wallet-core/src/operations/backup/export.ts
@@ -49,14 +49,13 @@ import {
   BackupWithdrawalGroup,
   canonicalizeBaseUrl,
   canonicalJson,
-  getTimestampNow,
   Logger,
-  timestampToIsoString,
   WalletBackupContentV1,
   hash,
   encodeCrock,
   getRandomBytes,
   stringToBytes,
+  AbsoluteTime,
 } from "@gnu-taler/taler-util";
 import { InternalWalletState } from "../../common.js";
 import {
@@ -455,7 +454,7 @@ export async function exportBackup(
         });
       });
 
-      const ts = getTimestampNow();
+      const ts = AbsoluteTime.toTimestamp(AbsoluteTime.now());
 
       if (!bs.lastBackupTimestamp) {
         bs.lastBackupTimestamp = ts;
@@ -496,9 +495,9 @@ export async function exportBackup(
         );
         bs.lastBackupNonce = encodeCrock(getRandomBytes(32));
         logger.trace(
-          `setting timestamp to ${timestampToIsoString(ts)} and nonce to ${
-            bs.lastBackupNonce
-          }`,
+          `setting timestamp to ${AbsoluteTime.toIsoString(
+            AbsoluteTime.fromTimestamp(ts),
+          )} and nonce to ${bs.lastBackupNonce}`,
         );
         await tx.config.put({
           key: WALLET_BACKUP_STATE_KEY,
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index 35b62c2e..4b17a5f3 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -20,7 +20,6 @@ import {
   Amounts,
   BackupDenomSel,
   WalletBackupContentV1,
-  getTimestampNow,
   BackupCoinSourceType,
   BackupProposalStatus,
   codecForContractTerms,
@@ -28,6 +27,8 @@ import {
   RefreshReason,
   BackupRefreshReason,
   DenomKeyType,
+  AbsoluteTime,
+  TalerProtocolTimestamp,
 } from "@gnu-taler/taler-util";
 import {
   WalletContractData,
@@ -277,8 +278,8 @@ export async function importBackup(
           permanent: true,
           retryInfo: initRetryInfo(),
           lastUpdate: undefined,
-          nextUpdate: getTimestampNow(),
-          nextRefreshCheck: getTimestampNow(),
+          nextUpdate: TalerProtocolTimestamp.now(),
+          nextRefreshCheck: TalerProtocolTimestamp.now(),
         });
       }
 
@@ -465,7 +466,6 @@ export async function importBackup(
               senderWire: backupReserve.sender_wire,
               retryInfo: initRetryInfo(),
               lastError: undefined,
-              lastSuccessfulStatusQuery: { t_ms: "never" },
               initialWithdrawalGroupId:
                 backupReserve.initial_withdrawal_group_id,
               initialWithdrawalStarted:
@@ -752,7 +752,7 @@ export async function importBackup(
             noncePub:
               cryptoComp.proposalNoncePrivToPub[backupPurchase.nonce_priv],
             lastPayError: undefined,
-            autoRefundDeadline: { t_ms: "never" },
+            autoRefundDeadline: TalerProtocolTimestamp.never(),
             refundStatusRetryInfo: initRetryInfo(),
             lastRefundStatusError: undefined,
             timestampAccept: backupPurchase.timestamp_accept,
diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts 
b/packages/taler-wallet-core/src/operations/backup/index.ts
index 2a1a774f..400406ce 100644
--- a/packages/taler-wallet-core/src/operations/backup/index.ts
+++ b/packages/taler-wallet-core/src/operations/backup/index.ts
@@ -40,21 +40,19 @@ import {
   ConfirmPayResultType,
   DenomKeyType,
   durationFromSpec,
-  getTimestampNow,
   hashDenomPub,
   HttpStatusCode,
   j2s,
-  LibtoolVersion,
   Logger,
   notEmpty,
   PreparePayResultType,
   RecoveryLoadRequest,
   RecoveryMergeStrategy,
-  TalerErrorDetails,
-  Timestamp,
-  timestampAddDuration,
+  TalerErrorDetail,
+  AbsoluteTime,
   URL,
   WalletBackupContentV1,
+  TalerProtocolTimestamp,
 } from "@gnu-taler/taler-util";
 import { gunzipSync, gzipSync } from "fflate";
 import { InternalWalletState } from "../../common.js";
@@ -250,11 +248,13 @@ interface BackupForProviderArgs {
   retryAfterPayment: boolean;
 }
 
-function getNextBackupTimestamp(): Timestamp {
+function getNextBackupTimestamp(): TalerProtocolTimestamp {
   // FIXME:  Randomize!
-  return timestampAddDuration(
-    getTimestampNow(),
-    durationFromSpec({ minutes: 5 }),
+  return AbsoluteTime.toTimestamp(
+    AbsoluteTime.addDuration(
+      AbsoluteTime.now(),
+      durationFromSpec({ minutes: 5 }),
+    ),
   );
 }
 
@@ -324,7 +324,7 @@ async function runBackupCycleForProvider(
         if (!prov) {
           return;
         }
-        prov.lastBackupCycleTimestamp = getTimestampNow();
+        prov.lastBackupCycleTimestamp = TalerProtocolTimestamp.now();
         prov.state = {
           tag: BackupProviderStateTag.Ready,
           nextBackupTimestamp: getNextBackupTimestamp(),
@@ -404,7 +404,7 @@ async function runBackupCycleForProvider(
           return;
         }
         prov.lastBackupHash = encodeCrock(currentBackupHash);
-        prov.lastBackupCycleTimestamp = getTimestampNow();
+        prov.lastBackupCycleTimestamp = TalerProtocolTimestamp.now();
         prov.state = {
           tag: BackupProviderStateTag.Ready,
           nextBackupTimestamp: getNextBackupTimestamp(),
@@ -464,7 +464,7 @@ async function incrementBackupRetryInTx(
     backupProviders: typeof WalletStoresV1.backupProviders;
   }>,
   backupProviderBaseUrl: string,
-  err: TalerErrorDetails | undefined,
+  err: TalerErrorDetail | undefined,
 ): Promise<void> {
   const pr = await tx.backupProviders.get(backupProviderBaseUrl);
   if (!pr) {
@@ -487,7 +487,7 @@ async function incrementBackupRetryInTx(
 async function incrementBackupRetry(
   ws: InternalWalletState,
   backupProviderBaseUrl: string,
-  err: TalerErrorDetails | undefined,
+  err: TalerErrorDetail | undefined,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({ backupProviders: x.backupProviders }))
@@ -509,7 +509,7 @@ export async function processBackupForProvider(
     throw Error("unknown backup provider");
   }
 
-  const onOpErr = (err: TalerErrorDetails): Promise<void> =>
+  const onOpErr = (err: TalerErrorDetail): Promise<void> =>
     incrementBackupRetry(ws, backupProviderBaseUrl, err);
 
   const run = async () => {
@@ -641,7 +641,7 @@ export async function addBackupProvider(
         if (req.activate) {
           oldProv.state = {
             tag: BackupProviderStateTag.Ready,
-            nextBackupTimestamp: getTimestampNow(),
+            nextBackupTimestamp: TalerProtocolTimestamp.now(),
           };
           logger.info("setting existing backup provider to active");
           await tx.backupProviders.put(oldProv);
@@ -662,7 +662,7 @@ export async function addBackupProvider(
       if (req.activate) {
         state = {
           tag: BackupProviderStateTag.Ready,
-          nextBackupTimestamp: getTimestampNow(),
+          nextBackupTimestamp: TalerProtocolTimestamp.now(),
         };
       } else {
         state = {
@@ -700,9 +700,9 @@ export interface ProviderInfo {
   /**
    * Last communication issue with the provider.
    */
-  lastError?: TalerErrorDetails;
-  lastSuccessfulBackupTimestamp?: Timestamp;
-  lastAttemptedBackupTimestamp?: Timestamp;
+  lastError?: TalerErrorDetail;
+  lastSuccessfulBackupTimestamp?: TalerProtocolTimestamp;
+  lastAttemptedBackupTimestamp?: TalerProtocolTimestamp;
   paymentProposalIds: string[];
   backupProblem?: BackupProblem;
   paymentStatus: ProviderPaymentStatus;
@@ -724,7 +724,7 @@ export interface BackupConflictingDeviceProblem {
   type: "backup-conflicting-device";
   otherDeviceId: string;
   myDeviceId: string;
-  backupTimestamp: Timestamp;
+  backupTimestamp: AbsoluteTime;
 }
 
 export type ProviderPaymentStatus =
@@ -774,12 +774,12 @@ export interface ProviderPaymentPending {
 
 export interface ProviderPaymentPaid {
   type: ProviderPaymentType.Paid;
-  paidUntil: Timestamp;
+  paidUntil: AbsoluteTime;
 }
 
 export interface ProviderPaymentTermsChanged {
   type: ProviderPaymentType.TermsChanged;
-  paidUntil: Timestamp;
+  paidUntil: AbsoluteTime;
   oldTerms: BackupProviderTerms;
   newTerms: BackupProviderTerms;
 }
@@ -811,8 +811,8 @@ async function getProviderPaymentInfo(
     if (status.paid) {
       return {
         type: ProviderPaymentType.Paid,
-        paidUntil: timestampAddDuration(
-          status.contractTerms.timestamp,
+        paidUntil: AbsoluteTime.addDuration(
+          AbsoluteTime.fromTimestamp(status.contractTerms.timestamp),
           durationFromSpec({ years: 1 }),
         ),
       };
@@ -915,7 +915,7 @@ async function backupRecoveryTheirs(
             paymentProposalIds: [],
             state: {
               tag: BackupProviderStateTag.Ready,
-              nextBackupTimestamp: getTimestampNow(),
+              nextBackupTimestamp: TalerProtocolTimestamp.now(),
             },
             uids: [encodeCrock(getRandomBytes(32))],
           });
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index a5d6c93c..42ce5e7c 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -15,6 +15,7 @@
  */
 
 import {
+  AbsoluteTime,
   AmountJson,
   Amounts,
   buildCodecForObject,
@@ -27,21 +28,16 @@ import {
   ContractTerms,
   CreateDepositGroupRequest,
   CreateDepositGroupResponse,
-  DenomKeyType,
   durationFromSpec,
   encodeCrock,
   GetFeeForDepositRequest,
   getRandomBytes,
-  getTimestampNow,
   hashWire,
   Logger,
   NotificationType,
   parsePaytoUri,
-  TalerErrorDetails,
-  Timestamp,
-  timestampAddDuration,
-  timestampIsBetween,
-  timestampTruncateToSecond,
+  TalerErrorDetail,
+  TalerProtocolTimestamp,
   TrackDepositGroupRequest,
   TrackDepositGroupResponse,
   URL,
@@ -87,7 +83,7 @@ async function resetDepositGroupRetry(
 async function incrementDepositRetry(
   ws: InternalWalletState,
   depositGroupId: string,
-  err: TalerErrorDetails | undefined,
+  err: TalerErrorDetail | undefined,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({ depositGroups: x.depositGroups }))
@@ -115,7 +111,7 @@ export async function processDepositGroup(
   forceNow = false,
 ): Promise<void> {
   await ws.memoProcessDeposit.memo(depositGroupId, async () => {
-    const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+    const onOpErr = (e: TalerErrorDetail): Promise<void> =>
       incrementDepositRetry(ws, depositGroupId, e);
     return await guardOperationException(
       async () => await processDepositGroupImpl(ws, depositGroupId, forceNow),
@@ -212,7 +208,7 @@ async function processDepositGroupImpl(
         }
       }
       if (allDeposited) {
-        dg.timestampFinished = getTimestampNow();
+        dg.timestampFinished = TalerProtocolTimestamp.now();
         dg.operationStatus = OperationStatus.Finished;
         delete dg.lastError;
         delete dg.retryInfo;
@@ -310,13 +306,8 @@ export async function getFeeForDeposit(
       }
     });
 
-  const timestamp = getTimestampNow();
-  const timestampRound = timestampTruncateToSecond(timestamp);
-  // const noncePair = await ws.cryptoApi.createEddsaKeypair();
-  // const merchantPair = await ws.cryptoApi.createEddsaKeypair();
-  // const wireSalt = encodeCrock(getRandomBytes(16));
-  // const wireHash = hashWire(req.depositPaytoUri, wireSalt);
-  // const wireHashLegacy = hashWireLegacy(req.depositPaytoUri, wireSalt);
+  const timestamp = AbsoluteTime.now();
+  const timestampRound = AbsoluteTime.toTimestamp(timestamp);
   const contractTerms: ContractTerms = {
     auditors: [],
     exchanges: exchangeInfos,
@@ -331,15 +322,14 @@ export async function getFeeForDeposit(
     wire_transfer_deadline: timestampRound,
     order_id: "",
     h_wire: "",
-    pay_deadline: timestampAddDuration(
-      timestampRound,
-      durationFromSpec({ hours: 1 }),
+    pay_deadline: AbsoluteTime.toTimestamp(
+      AbsoluteTime.addDuration(timestamp, durationFromSpec({ hours: 1 })),
     ),
     merchant: {
       name: "",
     },
     merchant_pub: "",
-    refund_deadline: { t_ms: 0 },
+    refund_deadline: TalerProtocolTimestamp.zero(),
   };
 
   const contractData = extractContractData(contractTerms, "", "");
@@ -399,8 +389,8 @@ export async function createDepositGroup(
       }
     });
 
-  const timestamp = getTimestampNow();
-  const timestampRound = timestampTruncateToSecond(timestamp);
+  const now = AbsoluteTime.now();
+  const nowRounded = AbsoluteTime.toTimestamp(now);
   const noncePair = await ws.cryptoApi.createEddsaKeypair();
   const merchantPair = await ws.cryptoApi.createEddsaKeypair();
   const wireSalt = encodeCrock(getRandomBytes(16));
@@ -412,24 +402,23 @@ export async function createDepositGroup(
     max_fee: Amounts.stringify(amount),
     max_wire_fee: Amounts.stringify(amount),
     wire_method: p.targetType,
-    timestamp: timestampRound,
+    timestamp: nowRounded,
     merchant_base_url: "",
     summary: "",
     nonce: noncePair.pub,
-    wire_transfer_deadline: timestampRound,
+    wire_transfer_deadline: nowRounded,
     order_id: "",
     // This is always the v2 wire hash, as we're the "merchant" and support v2.
     h_wire: wireHash,
     // Required for older exchanges.
-    pay_deadline: timestampAddDuration(
-      timestampRound,
-      durationFromSpec({ hours: 1 }),
+    pay_deadline: AbsoluteTime.toTimestamp(
+      AbsoluteTime.addDuration(now, durationFromSpec({ hours: 1 })),
     ),
     merchant: {
       name: "",
     },
     merchant_pub: merchantPair.pub,
-    refund_deadline: { t_ms: 0 },
+    refund_deadline: TalerProtocolTimestamp.zero(),
   };
 
   const contractTermsHash = await ws.cryptoApi.hashString(
@@ -482,7 +471,7 @@ export async function createDepositGroup(
     depositGroupId,
     noncePriv: noncePair.priv,
     noncePub: noncePair.pub,
-    timestampCreated: timestamp,
+    timestampCreated: AbsoluteTime.toTimestamp(now),
     timestampFinished: undefined,
     payCoinSelection: payCoinSel,
     payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
@@ -570,10 +559,10 @@ export async function getEffectiveDepositAmount(
         // about "find method not found on undefined" when the wireType
         // is not supported by the Exchange.
         const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => 
{
-          return timestampIsBetween(
-            getTimestampNow(),
-            x.startStamp,
-            x.endStamp,
+          return AbsoluteTime.isBetween(
+            AbsoluteTime.now(),
+            AbsoluteTime.fromTimestamp(x.startStamp),
+            AbsoluteTime.fromTimestamp(x.endStamp),
           );
         })?.wireFee;
         if (fee) {
@@ -656,10 +645,10 @@ export async function getTotalFeeForDepositAmount(
         // about "find method not found on undefined" when the wireType
         // is not supported by the Exchange.
         const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => 
{
-          return timestampIsBetween(
-            getTimestampNow(),
-            x.startStamp,
-            x.endStamp,
+          return AbsoluteTime.isBetween(
+            AbsoluteTime.now(),
+            AbsoluteTime.fromTimestamp(x.startStamp),
+            AbsoluteTime.fromTimestamp(x.endStamp),
           );
         })?.wireFee;
         if (fee) {
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index 536d4e3b..bbed4228 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -28,22 +28,22 @@ import {
   durationFromSpec,
   ExchangeSignKeyJson,
   ExchangeWireJson,
-  getTimestampNow,
-  isTimestampExpired,
   Logger,
   NotificationType,
   parsePaytoUri,
   Recoup,
   TalerErrorCode,
   URL,
-  TalerErrorDetails,
-  Timestamp,
+  TalerErrorDetail,
+  AbsoluteTime,
   hashDenomPub,
   LibtoolVersion,
   codecForAny,
   DenominationPubKey,
   DenomKeyType,
   ExchangeKeysJson,
+  TalerProtocolTimestamp,
+  TalerProtocolDuration,
 } from "@gnu-taler/taler-util";
 import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util";
 import { CryptoApi } from "../crypto/workers/cryptoApi.js";
@@ -57,18 +57,14 @@ import {
   WireInfo,
 } from "../db.js";
 import {
-  getExpiryTimestamp,
+  getExpiry,
   HttpRequestLibrary,
   readSuccessResponseJsonOrThrow,
   readSuccessResponseTextOrThrow,
 } from "../util/http.js";
 import { DbAccess, GetReadOnlyAccess } from "../util/query.js";
 import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
-import {
-  guardOperationException,
-  makeErrorDetails,
-  OperationFailedError,
-} from "../errors.js";
+import { guardOperationException, TalerError } from "../errors.js";
 import { InternalWalletState, TrustInfo } from "../common.js";
 import {
   WALLET_CACHE_BREAKER_CLIENT_VERSION,
@@ -80,7 +76,7 @@ const logger = new Logger("exchanges.ts");
 function denominationRecordFromKeys(
   exchangeBaseUrl: string,
   exchangeMasterPub: string,
-  listIssueDate: Timestamp,
+  listIssueDate: TalerProtocolTimestamp,
   denomIn: ExchangeDenomination,
 ): DenominationRecord {
   let denomPub: DenominationPubKey;
@@ -112,7 +108,7 @@ function denominationRecordFromKeys(
 async function handleExchangeUpdateError(
   ws: InternalWalletState,
   baseUrl: string,
-  err: TalerErrorDetails,
+  err: TalerErrorDetail,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({ exchanges: x.exchanges }))
@@ -132,7 +128,9 @@ async function handleExchangeUpdateError(
 }
 
 export function getExchangeRequestTimeout(): Duration {
-  return { d_ms: 5000 };
+  return Duration.fromSpec({
+    seconds: 5,
+  });
 }
 
 export interface ExchangeTosDownloadResult {
@@ -351,7 +349,7 @@ export async function updateExchangeFromUrl(
   exchange: ExchangeRecord;
   exchangeDetails: ExchangeDetailsRecord;
 }> {
-  const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+  const onOpErr = (e: TalerErrorDetail): Promise<void> =>
     handleExchangeUpdateError(ws, baseUrl, e);
   return await guardOperationException(
     () => updateExchangeFromUrlImpl(ws, baseUrl, acceptedFormat, forceNow),
@@ -362,12 +360,11 @@ export async function updateExchangeFromUrl(
 async function provideExchangeRecord(
   ws: InternalWalletState,
   baseUrl: string,
-  now: Timestamp,
+  now: AbsoluteTime,
 ): Promise<{
   exchange: ExchangeRecord;
   exchangeDetails: ExchangeDetailsRecord | undefined;
 }> {
-
   return await ws.db
     .mktx((x) => ({
       exchanges: x.exchanges,
@@ -376,14 +373,14 @@ async function provideExchangeRecord(
     .runReadWrite(async (tx) => {
       let exchange = await tx.exchanges.get(baseUrl);
       if (!exchange) {
-        const r = {
+        const r: ExchangeRecord = {
           permanent: true,
           baseUrl: baseUrl,
           retryInfo: initRetryInfo(),
           detailsPointer: undefined,
           lastUpdate: undefined,
-          nextUpdate: now,
-          nextRefreshCheck: now,
+          nextUpdate: AbsoluteTime.toTimestamp(now),
+          nextRefreshCheck: AbsoluteTime.toTimestamp(now),
         };
         await tx.exchanges.put(r);
         exchange = r;
@@ -400,10 +397,10 @@ interface ExchangeKeysDownloadResult {
   currentDenominations: DenominationRecord[];
   protocolVersion: string;
   signingKeys: ExchangeSignKeyJson[];
-  reserveClosingDelay: Duration;
-  expiry: Timestamp;
+  reserveClosingDelay: TalerProtocolDuration;
+  expiry: TalerProtocolTimestamp;
   recoup: Recoup[];
-  listIssueDate: Timestamp;
+  listIssueDate: TalerProtocolTimestamp;
 }
 
 /**
@@ -428,14 +425,13 @@ async function downloadExchangeKeysInfo(
   logger.info("received /keys response");
 
   if (exchangeKeysJsonUnchecked.denoms.length === 0) {
-    const opErr = makeErrorDetails(
+    throw TalerError.fromDetail(
       TalerErrorCode.WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT,
-      "exchange doesn't offer any denominations",
       {
         exchangeBaseUrl: baseUrl,
       },
+      "exchange doesn't offer any denominations",
     );
-    throw new OperationFailedError(opErr);
   }
 
   const protocolVersion = exchangeKeysJsonUnchecked.version;
@@ -445,15 +441,14 @@ async function downloadExchangeKeysInfo(
     protocolVersion,
   );
   if (versionRes?.compatible != true) {
-    const opErr = makeErrorDetails(
+    throw TalerError.fromDetail(
       TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
-      "exchange protocol version not compatible with wallet",
       {
         exchangeProtocolVersion: protocolVersion,
         walletProtocolVersion: WALLET_EXCHANGE_PROTOCOL_VERSION,
       },
+      "exchange protocol version not compatible with wallet",
     );
-    throw new OperationFailedError(opErr);
   }
 
   const currency = Amounts.parseOrThrow(
@@ -475,9 +470,11 @@ async function downloadExchangeKeysInfo(
     protocolVersion: exchangeKeysJsonUnchecked.version,
     signingKeys: exchangeKeysJsonUnchecked.signkeys,
     reserveClosingDelay: exchangeKeysJsonUnchecked.reserve_closing_delay,
-    expiry: getExpiryTimestamp(resp, {
-      minDuration: durationFromSpec({ hours: 1 }),
-    }),
+    expiry: AbsoluteTime.toTimestamp(
+      getExpiry(resp, {
+        minDuration: durationFromSpec({ hours: 1 }),
+      }),
+    ),
     recoup: exchangeKeysJsonUnchecked.recoup ?? [],
     listIssueDate: exchangeKeysJsonUnchecked.list_issue_date,
   };
@@ -529,12 +526,20 @@ async function updateExchangeFromUrlImpl(
   exchangeDetails: ExchangeDetailsRecord;
 }> {
   logger.info(`updating exchange info for ${baseUrl}, forced: ${forceNow}`);
-  const now = getTimestampNow();
+  const now = AbsoluteTime.now();
   baseUrl = canonicalizeBaseUrl(baseUrl);
 
-  const { exchange, exchangeDetails } = await provideExchangeRecord(ws, 
baseUrl, now);
+  const { exchange, exchangeDetails } = await provideExchangeRecord(
+    ws,
+    baseUrl,
+    now,
+  );
 
-  if (!forceNow && exchangeDetails !== undefined && 
!isTimestampExpired(exchange.nextUpdate)) {
+  if (
+    !forceNow &&
+    exchangeDetails !== undefined &&
+    !AbsoluteTime.isExpired(AbsoluteTime.fromTimestamp(exchange.nextUpdate))
+  ) {
     logger.info("using existing exchange info");
     return { exchange, exchangeDetails };
   }
@@ -575,7 +580,8 @@ async function updateExchangeFromUrlImpl(
     timeout,
     acceptedFormat,
   );
-  const tosHasBeenAccepted = exchangeDetails?.termsOfServiceAcceptedEtag === 
tosDownload.tosEtag
+  const tosHasBeenAccepted =
+    exchangeDetails?.termsOfServiceAcceptedEtag === tosDownload.tosEtag;
 
   let recoupGroupId: string | undefined;
 
@@ -611,23 +617,25 @@ async function updateExchangeFromUrlImpl(
         exchangeBaseUrl: r.baseUrl,
         wireInfo,
         termsOfServiceText: tosDownload.tosText,
-        termsOfServiceAcceptedEtag: tosHasBeenAccepted ? tosDownload.tosEtag : 
undefined,
+        termsOfServiceAcceptedEtag: tosHasBeenAccepted
+          ? tosDownload.tosEtag
+          : undefined,
         termsOfServiceContentType: tosDownload.tosContentType,
         termsOfServiceLastEtag: tosDownload.tosEtag,
-        termsOfServiceAcceptedTimestamp: getTimestampNow(),
+        termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp.now(),
       };
       // FIXME: only update if pointer got updated
       r.lastError = undefined;
       r.retryInfo = initRetryInfo();
-      r.lastUpdate = getTimestampNow();
+      r.lastUpdate = TalerProtocolTimestamp.now();
       r.nextUpdate = keysInfo.expiry;
       // New denominations might be available.
-      r.nextRefreshCheck = getTimestampNow();
+      r.nextRefreshCheck = TalerProtocolTimestamp.now();
       r.detailsPointer = {
         currency: details.currency,
         masterPublicKey: details.masterPublicKey,
         // FIXME: only change if pointer really changed
-        updateClock: getTimestampNow(),
+        updateClock: TalerProtocolTimestamp.now(),
         protocolVersionRange: keysInfo.protocolVersion,
       };
       await tx.exchanges.put(r);
diff --git a/packages/taler-wallet-core/src/operations/pay.ts 
b/packages/taler-wallet-core/src/operations/pay.ts
index 9844dc52..ce3a26c3 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -25,9 +25,9 @@
  * Imports.
  */
 import {
+  AbsoluteTime,
   AmountJson,
   Amounts,
-  CheckPaymentResponse,
   codecForContractTerms,
   codecForMerchantPayResponse,
   codecForProposal,
@@ -35,30 +35,24 @@ import {
   ConfirmPayResult,
   ConfirmPayResultType,
   ContractTerms,
-  decodeCrock,
   Duration,
   durationMax,
   durationMin,
   durationMul,
   encodeCrock,
-  getDurationRemaining,
   getRandomBytes,
-  getTimestampNow,
   HttpStatusCode,
-  isTimestampExpired,
   j2s,
-  kdf,
   Logger,
   NotificationType,
   parsePayUri,
   PreparePayResult,
   PreparePayResultType,
   RefreshReason,
-  stringToBytes,
   TalerErrorCode,
-  TalerErrorDetails,
-  Timestamp,
-  timestampAddDuration,
+  TalerErrorDetail,
+  TalerProtocolTimestamp,
+  TransactionType,
   URL,
 } from "@gnu-taler/taler-util";
 import { EXCHANGE_COINS_LOCK, InternalWalletState } from "../common.js";
@@ -78,9 +72,9 @@ import {
 } from "../db.js";
 import {
   guardOperationException,
-  makeErrorDetails,
-  OperationFailedAndReportedError,
-  OperationFailedError,
+  makeErrorDetail,
+  makePendingOperationFailedError,
+  TalerError,
 } from "../errors.js";
 import {
   AvailableCoinInfo,
@@ -172,7 +166,9 @@ function isSpendableCoin(coin: CoinRecord, denom: 
DenominationRecord): boolean {
   if (coin.status !== CoinStatus.Fresh) {
     return false;
   }
-  if (isTimestampExpired(denom.stampExpireDeposit)) {
+  if (
+    
AbsoluteTime.isExpired(AbsoluteTime.fromTimestamp(denom.stampExpireDeposit))
+  ) {
     return false;
   }
   return true;
@@ -187,7 +183,7 @@ export interface CoinSelectionRequest {
   /**
    * Timestamp of the contract.
    */
-  timestamp: Timestamp;
+  timestamp: TalerProtocolTimestamp;
 
   wireMethod: string;
 
@@ -422,7 +418,7 @@ async function recordConfirmPay(
     payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
     totalPayCost: payCostInfo,
     coinDepositPermissions,
-    timestampAccept: getTimestampNow(),
+    timestampAccept: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
     timestampLastRefundStatus: undefined,
     proposalId: proposal.proposalId,
     lastPayError: undefined,
@@ -469,7 +465,7 @@ async function recordConfirmPay(
 async function reportProposalError(
   ws: InternalWalletState,
   proposalId: string,
-  err: TalerErrorDetails,
+  err: TalerErrorDetail,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({ proposals: x.proposals }))
@@ -552,7 +548,7 @@ async function incrementPurchasePayRetry(
 async function reportPurchasePayError(
   ws: InternalWalletState,
   proposalId: string,
-  err: TalerErrorDetails,
+  err: TalerErrorDetail,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({ purchases: x.purchases }))
@@ -577,7 +573,7 @@ export async function processDownloadProposal(
   proposalId: string,
   forceNow = false,
 ): Promise<void> {
-  const onOpErr = (err: TalerErrorDetails): Promise<void> =>
+  const onOpErr = (err: TalerErrorDetail): Promise<void> =>
     reportProposalError(ws, proposalId, err);
   await guardOperationException(
     () => processDownloadProposalImpl(ws, proposalId, forceNow),
@@ -604,7 +600,7 @@ async function resetDownloadProposalRetry(
 async function failProposalPermanently(
   ws: InternalWalletState,
   proposalId: string,
-  err: TalerErrorDetails,
+  err: TalerErrorDetail,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({ proposals: x.proposals }))
@@ -729,13 +725,13 @@ async function processDownloadProposalImpl(
   if (r.isError) {
     switch (r.talerErrorResponse.code) {
       case TalerErrorCode.MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED:
-        throw OperationFailedError.fromCode(
+        throw TalerError.fromDetail(
           TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED,
-          "order already claimed (likely by other wallet)",
           {
             orderId: proposal.orderId,
             claimUrl: orderClaimUrl,
           },
+          "order already claimed (likely by other wallet)",
         );
       default:
         throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
@@ -760,13 +756,17 @@ async function processDownloadProposalImpl(
     logger.trace(
       `malformed contract terms: ${j2s(proposalResp.contract_terms)}`,
     );
-    const err = makeErrorDetails(
+    const err = makeErrorDetail(
       TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED,
-      "validation for well-formedness failed",
       {},
+      "validation for well-formedness failed",
     );
     await failProposalPermanently(ws, proposalId, err);
-    throw new OperationFailedAndReportedError(err);
+    throw makePendingOperationFailedError(
+      err,
+      TransactionType.Payment,
+      proposalId,
+    );
   }
 
   const contractTermsHash = ContractTermsUtil.hashContractTerms(
@@ -782,13 +782,17 @@ async function processDownloadProposalImpl(
       proposalResp.contract_terms,
     );
   } catch (e) {
-    const err = makeErrorDetails(
+    const err = makeErrorDetail(
       TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED,
-      "schema validation failed",
       {},
+      `schema validation failed: ${e}`,
     );
     await failProposalPermanently(ws, proposalId, err);
-    throw new OperationFailedAndReportedError(err);
+    throw makePendingOperationFailedError(
+      err,
+      TransactionType.Payment,
+      proposalId,
+    );
   }
 
   const sigValid = await ws.cryptoApi.isValidContractTermsSignature(
@@ -798,16 +802,20 @@ async function processDownloadProposalImpl(
   );
 
   if (!sigValid) {
-    const err = makeErrorDetails(
+    const err = makeErrorDetail(
       TalerErrorCode.WALLET_CONTRACT_TERMS_SIGNATURE_INVALID,
-      "merchant's signature on contract terms is invalid",
       {
         merchantPub: parsedContractTerms.merchant_pub,
         orderId: parsedContractTerms.order_id,
       },
+      "merchant's signature on contract terms is invalid",
     );
     await failProposalPermanently(ws, proposalId, err);
-    throw new OperationFailedAndReportedError(err);
+    throw makePendingOperationFailedError(
+      err,
+      TransactionType.Payment,
+      proposalId,
+    );
   }
 
   const fulfillmentUrl = parsedContractTerms.fulfillment_url;
@@ -816,16 +824,20 @@ async function processDownloadProposalImpl(
   const baseUrlFromContractTerms = parsedContractTerms.merchant_base_url;
 
   if (baseUrlForDownload !== baseUrlFromContractTerms) {
-    const err = makeErrorDetails(
+    const err = makeErrorDetail(
       TalerErrorCode.WALLET_CONTRACT_TERMS_BASE_URL_MISMATCH,
-      "merchant base URL mismatch",
       {
         baseUrlForDownload,
         baseUrlFromContractTerms,
       },
+      "merchant base URL mismatch",
     );
     await failProposalPermanently(ws, proposalId, err);
-    throw new OperationFailedAndReportedError(err);
+    throw makePendingOperationFailedError(
+      err,
+      TransactionType.Payment,
+      proposalId,
+    );
   }
 
   const contractData = extractContractData(
@@ -897,10 +909,8 @@ async function startDownloadProposal(
       ]);
     });
 
-  /**
-   * If we have already claimed this proposal with the same sessionId
-   * nonce and claim token, reuse it.
-   */
+  /* If we have already claimed this proposal with the same sessionId
+   * nonce and claim token, reuse it. */
   if (
     oldProposal &&
     oldProposal.downloadSessionId === sessionId &&
@@ -921,7 +931,7 @@ async function startDownloadProposal(
     noncePriv: priv,
     noncePub: pub,
     claimToken,
-    timestamp: getTimestampNow(),
+    timestamp: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
     merchantBaseUrl,
     orderId,
     proposalId: proposalId,
@@ -956,7 +966,7 @@ async function storeFirstPaySuccess(
   sessionId: string | undefined,
   paySig: string,
 ): Promise<void> {
-  const now = getTimestampNow();
+  const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
   await ws.db
     .mktx((x) => ({ purchases: x.purchases }))
     .runReadWrite(async (tx) => {
@@ -978,13 +988,16 @@ async function storeFirstPaySuccess(
       purchase.payRetryInfo = initRetryInfo();
       purchase.merchantPaySig = paySig;
       if (isFirst) {
-        const ar = purchase.download.contractData.autoRefund;
-        if (ar) {
+        const protoAr = purchase.download.contractData.autoRefund;
+        if (protoAr) {
+          const ar = Duration.fromTalerProtocolDuration(protoAr);
           logger.info("auto_refund present");
           purchase.refundQueryRequested = true;
           purchase.refundStatusRetryInfo = initRetryInfo();
           purchase.lastRefundStatusError = undefined;
-          purchase.autoRefundDeadline = timestampAddDuration(now, ar);
+          purchase.autoRefundDeadline = AbsoluteTime.toTimestamp(
+            AbsoluteTime.addDuration(AbsoluteTime.now(), ar),
+          );
         }
       }
       await tx.purchases.put(purchase);
@@ -1028,7 +1041,7 @@ async function storePayReplaySuccess(
 async function handleInsufficientFunds(
   ws: InternalWalletState,
   proposalId: string,
-  err: TalerErrorDetails,
+  err: TalerErrorDetail,
 ): Promise<void> {
   logger.trace("handling insufficient funds, trying to re-select coins");
 
@@ -1150,7 +1163,7 @@ async function unblockBackup(
           if (bp.state.tag === BackupProviderStateTag.Retrying) {
             bp.state = {
               tag: BackupProviderStateTag.Ready,
-              nextBackupTimestamp: getTimestampNow(),
+              nextBackupTimestamp: TalerProtocolTimestamp.now(),
             };
           }
         });
@@ -1318,12 +1331,12 @@ export async function preparePayForUri(
   const uriResult = parsePayUri(talerPayUri);
 
   if (!uriResult) {
-    throw OperationFailedError.fromCode(
+    throw TalerError.fromDetail(
       TalerErrorCode.WALLET_INVALID_TALER_PAY_URI,
-      `invalid taler://pay URI (${talerPayUri})`,
       {
         talerPayUri,
       },
+      `invalid taler://pay URI (${talerPayUri})`,
     );
   }
 
@@ -1502,7 +1515,7 @@ export async function processPurchasePay(
   proposalId: string,
   forceNow = false,
 ): Promise<ConfirmPayResult> {
-  const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+  const onOpErr = (e: TalerErrorDetail): Promise<void> =>
     reportPurchasePayError(ws, proposalId, e);
   return await guardOperationException(
     () => processPurchasePayImpl(ws, proposalId, forceNow),
@@ -1526,9 +1539,8 @@ async function processPurchasePayImpl(
       lastError: {
         // FIXME: allocate more specific error code
         code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
-        message: `trying to pay for purchase that is not in the database`,
-        hint: `proposal ID is ${proposalId}`,
-        details: {},
+        hint: `trying to pay for purchase that is not in the database`,
+        proposalId: proposalId,
       },
     };
   }
@@ -1593,10 +1605,10 @@ async function processPurchasePayImpl(
       resp.status <= 599
     ) {
       logger.trace("treating /pay error as transient");
-      const err = makeErrorDetails(
+      const err = makeErrorDetail(
         TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
-        "/pay failed",
         getHttpResponseErrorDetails(resp),
+        "/pay failed",
       );
       return {
         type: ConfirmPayResultType.Pending,
@@ -1620,8 +1632,11 @@ async function processPurchasePayImpl(
           delete purch.payRetryInfo;
           await tx.purchases.put(purch);
         });
-      // FIXME: Maybe introduce a new return type for this instead of throwing?
-      throw new OperationFailedAndReportedError(errDetails);
+      throw makePendingOperationFailedError(
+        errDetails,
+        TransactionType.Payment,
+        proposalId,
+      );
     }
 
     if (resp.status === HttpStatusCode.Conflict) {
@@ -1691,10 +1706,10 @@ async function processPurchasePayImpl(
       resp.status >= 500 &&
       resp.status <= 599
     ) {
-      const err = makeErrorDetails(
+      const err = makeErrorDetail(
         TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
-        "/paid failed",
         getHttpResponseErrorDetails(resp),
+        "/paid failed",
       );
       return {
         type: ConfirmPayResultType.Pending,
@@ -1702,10 +1717,10 @@ async function processPurchasePayImpl(
       };
     }
     if (resp.status !== 204) {
-      throw OperationFailedError.fromCode(
+      throw TalerError.fromDetail(
         TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
-        "/paid failed",
         getHttpResponseErrorDetails(resp),
+        "/paid failed",
       );
     }
     await storePayReplaySuccess(ws, proposalId, sessionId);
diff --git a/packages/taler-wallet-core/src/operations/pending.ts 
b/packages/taler-wallet-core/src/operations/pending.ts
index 6d686fb3..fc76eeb1 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -35,7 +35,7 @@ import {
   PendingTaskType,
   ReserveType,
 } from "../pending-types.js";
-import { getTimestampNow, Timestamp } from "@gnu-taler/taler-util";
+import { AbsoluteTime } from "@gnu-taler/taler-util";
 import { InternalWalletState } from "../common.js";
 import { GetReadOnlyAccess } from "../util/query.js";
 
@@ -44,21 +44,25 @@ async function gatherExchangePending(
     exchanges: typeof WalletStoresV1.exchanges;
     exchangeDetails: typeof WalletStoresV1.exchangeDetails;
   }>,
-  now: Timestamp,
+  now: AbsoluteTime,
   resp: PendingOperationsResponse,
 ): Promise<void> {
   await tx.exchanges.iter().forEachAsync(async (e) => {
     resp.pendingOperations.push({
       type: PendingTaskType.ExchangeUpdate,
       givesLifeness: false,
-      timestampDue: e.lastError ? e.retryInfo.nextRetry : e.nextUpdate,
+      timestampDue: e.lastError
+        ? e.retryInfo.nextRetry
+        : AbsoluteTime.fromTimestamp(e.nextUpdate),
       exchangeBaseUrl: e.baseUrl,
       lastError: e.lastError,
     });
 
     resp.pendingOperations.push({
       type: PendingTaskType.ExchangeCheckRefresh,
-      timestampDue: e.lastError ? e.retryInfo.nextRetry : e.nextRefreshCheck,
+      timestampDue: e.lastError
+        ? e.retryInfo.nextRetry
+        : AbsoluteTime.fromTimestamp(e.nextRefreshCheck),
       givesLifeness: false,
       exchangeBaseUrl: e.baseUrl,
     });
@@ -67,7 +71,7 @@ async function gatherExchangePending(
 
 async function gatherReservePending(
   tx: GetReadOnlyAccess<{ reserves: typeof WalletStoresV1.reserves }>,
-  now: Timestamp,
+  now: AbsoluteTime,
   resp: PendingOperationsResponse,
 ): Promise<void> {
   const reserves = await tx.reserves.indexes.byStatus.getAll(
@@ -87,7 +91,7 @@ async function gatherReservePending(
         resp.pendingOperations.push({
           type: PendingTaskType.Reserve,
           givesLifeness: true,
-          timestampDue: reserve.retryInfo?.nextRetry ?? Timestamp.now(),
+          timestampDue: reserve.retryInfo?.nextRetry ?? AbsoluteTime.now(),
           stage: reserve.reserveStatus,
           timestampCreated: reserve.timestampCreated,
           reserveType,
@@ -105,7 +109,7 @@ async function gatherReservePending(
 
 async function gatherRefreshPending(
   tx: GetReadOnlyAccess<{ refreshGroups: typeof WalletStoresV1.refreshGroups 
}>,
-  now: Timestamp,
+  now: AbsoluteTime,
   resp: PendingOperationsResponse,
 ): Promise<void> {
   const refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll(
@@ -136,7 +140,7 @@ async function gatherWithdrawalPending(
     withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
     planchets: typeof WalletStoresV1.planchets;
   }>,
-  now: Timestamp,
+  now: AbsoluteTime,
   resp: PendingOperationsResponse,
 ): Promise<void> {
   const wsrs = await tx.withdrawalGroups.indexes.byStatus.getAll(
@@ -169,14 +173,14 @@ async function gatherWithdrawalPending(
 
 async function gatherProposalPending(
   tx: GetReadOnlyAccess<{ proposals: typeof WalletStoresV1.proposals }>,
-  now: Timestamp,
+  now: AbsoluteTime,
   resp: PendingOperationsResponse,
 ): Promise<void> {
   await tx.proposals.iter().forEach((proposal) => {
     if (proposal.proposalStatus == ProposalStatus.Proposed) {
       // Nothing to do, user needs to choose.
     } else if (proposal.proposalStatus == ProposalStatus.Downloading) {
-      const timestampDue = proposal.retryInfo?.nextRetry ?? getTimestampNow();
+      const timestampDue = proposal.retryInfo?.nextRetry ?? AbsoluteTime.now();
       resp.pendingOperations.push({
         type: PendingTaskType.ProposalDownload,
         givesLifeness: true,
@@ -194,7 +198,7 @@ async function gatherProposalPending(
 
 async function gatherDepositPending(
   tx: GetReadOnlyAccess<{ depositGroups: typeof WalletStoresV1.depositGroups 
}>,
-  now: Timestamp,
+  now: AbsoluteTime,
   resp: PendingOperationsResponse,
 ): Promise<void> {
   const dgs = await tx.depositGroups.indexes.byStatus.getAll(
@@ -204,7 +208,7 @@ async function gatherDepositPending(
     if (dg.timestampFinished) {
       return;
     }
-    const timestampDue = dg.retryInfo?.nextRetry ?? getTimestampNow();
+    const timestampDue = dg.retryInfo?.nextRetry ?? AbsoluteTime.now();
     resp.pendingOperations.push({
       type: PendingTaskType.Deposit,
       givesLifeness: true,
@@ -218,7 +222,7 @@ async function gatherDepositPending(
 
 async function gatherTipPending(
   tx: GetReadOnlyAccess<{ tips: typeof WalletStoresV1.tips }>,
-  now: Timestamp,
+  now: AbsoluteTime,
   resp: PendingOperationsResponse,
 ): Promise<void> {
   await tx.tips.iter().forEach((tip) => {
@@ -240,7 +244,7 @@ async function gatherTipPending(
 
 async function gatherPurchasePending(
   tx: GetReadOnlyAccess<{ purchases: typeof WalletStoresV1.purchases }>,
-  now: Timestamp,
+  now: AbsoluteTime,
   resp: PendingOperationsResponse,
 ): Promise<void> {
   await tx.purchases.iter().forEach((pr) => {
@@ -249,7 +253,7 @@ async function gatherPurchasePending(
       pr.abortStatus === AbortStatus.None &&
       !pr.payFrozen
     ) {
-      const timestampDue = pr.payRetryInfo?.nextRetry ?? getTimestampNow();
+      const timestampDue = pr.payRetryInfo?.nextRetry ?? AbsoluteTime.now();
       resp.pendingOperations.push({
         type: PendingTaskType.Pay,
         givesLifeness: true,
@@ -275,7 +279,7 @@ async function gatherPurchasePending(
 
 async function gatherRecoupPending(
   tx: GetReadOnlyAccess<{ recoupGroups: typeof WalletStoresV1.recoupGroups }>,
-  now: Timestamp,
+  now: AbsoluteTime,
   resp: PendingOperationsResponse,
 ): Promise<void> {
   await tx.recoupGroups.iter().forEach((rg) => {
@@ -297,7 +301,7 @@ async function gatherBackupPending(
   tx: GetReadOnlyAccess<{
     backupProviders: typeof WalletStoresV1.backupProviders;
   }>,
-  now: Timestamp,
+  now: AbsoluteTime,
   resp: PendingOperationsResponse,
 ): Promise<void> {
   await tx.backupProviders.iter().forEach((bp) => {
@@ -305,7 +309,7 @@ async function gatherBackupPending(
       resp.pendingOperations.push({
         type: PendingTaskType.Backup,
         givesLifeness: false,
-        timestampDue: bp.state.nextBackupTimestamp,
+        timestampDue: AbsoluteTime.fromTimestamp(bp.state.nextBackupTimestamp),
         backupProviderBaseUrl: bp.baseUrl,
         lastError: undefined,
       });
@@ -325,7 +329,7 @@ async function gatherBackupPending(
 export async function getPendingOperations(
   ws: InternalWalletState,
 ): Promise<PendingOperationsResponse> {
-  const now = getTimestampNow();
+  const now = AbsoluteTime.now();
   return await ws.db
     .mktx((x) => ({
       backupProviders: x.backupProviders,
diff --git a/packages/taler-wallet-core/src/operations/recoup.ts 
b/packages/taler-wallet-core/src/operations/recoup.ts
index 23d14f21..56c13f1b 100644
--- a/packages/taler-wallet-core/src/operations/recoup.ts
+++ b/packages/taler-wallet-core/src/operations/recoup.ts
@@ -27,11 +27,11 @@
 import {
   Amounts,
   codecForRecoupConfirmation,
-  getTimestampNow,
   j2s,
   NotificationType,
   RefreshReason,
-  TalerErrorDetails,
+  TalerErrorDetail,
+  TalerProtocolTimestamp,
 } from "@gnu-taler/taler-util";
 import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util";
 import {
@@ -60,7 +60,7 @@ const logger = new Logger("operations/recoup.ts");
 async function incrementRecoupRetry(
   ws: InternalWalletState,
   recoupGroupId: string,
-  err: TalerErrorDetails | undefined,
+  err: TalerErrorDetail | undefined,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({
@@ -110,7 +110,7 @@ async function putGroupAsFinished(
   }
   if (allFinished) {
     logger.info("all recoups of recoup group are finished");
-    recoupGroup.timestampFinished = getTimestampNow();
+    recoupGroup.timestampFinished = TalerProtocolTimestamp.now();
     recoupGroup.retryInfo = initRetryInfo();
     recoupGroup.lastError = undefined;
     if (recoupGroup.scheduleRefreshCoins.length > 0) {
@@ -384,7 +384,7 @@ export async function processRecoupGroup(
   forceNow = false,
 ): Promise<void> {
   await ws.memoProcessRecoup.memo(recoupGroupId, async () => {
-    const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+    const onOpErr = (e: TalerErrorDetail): Promise<void> =>
       incrementRecoupRetry(ws, recoupGroupId, e);
     return await guardOperationException(
       async () => await processRecoupGroupImpl(ws, recoupGroupId, forceNow),
@@ -467,7 +467,7 @@ export async function createRecoupGroup(
     coinPubs: coinPubs,
     lastError: undefined,
     timestampFinished: undefined,
-    timestampStarted: getTimestampNow(),
+    timestampStarted: TalerProtocolTimestamp.now(),
     retryInfo: initRetryInfo(),
     recoupFinishedPerCoin: coinPubs.map(() => false),
     // Will be populated later
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index 8b6d8b2e..7753992f 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -23,7 +23,7 @@ import {
   ExchangeRefreshRevealRequest,
   getRandomBytes,
   HttpStatusCode,
-  j2s,
+  TalerProtocolTimestamp,
 } from "@gnu-taler/taler-util";
 import {
   CoinRecord,
@@ -42,11 +42,8 @@ import {
   fnutil,
   NotificationType,
   RefreshGroupId,
-  RefreshPlanchetInfo,
   RefreshReason,
-  stringifyTimestamp,
-  TalerErrorDetails,
-  timestampToIsoString,
+  TalerErrorDetail,
 } from "@gnu-taler/taler-util";
 import { AmountJson, Amounts } from "@gnu-taler/taler-util";
 import { amountToPretty } from "@gnu-taler/taler-util";
@@ -61,12 +58,7 @@ import {
   Duration,
   durationFromSpec,
   durationMul,
-  getTimestampNow,
-  isTimestampExpired,
-  Timestamp,
-  timestampAddDuration,
-  timestampDifference,
-  timestampMin,
+  AbsoluteTime,
   URL,
 } from "@gnu-taler/taler-util";
 import { guardOperationException } from "../errors.js";
@@ -139,7 +131,7 @@ function updateGroupStatus(rg: RefreshGroupRecord): void {
       rg.frozen = true;
       rg.retryInfo = initRetryInfo();
     } else {
-      rg.timestampFinished = getTimestampNow();
+      rg.timestampFinished = AbsoluteTime.toTimestamp(AbsoluteTime.now());
       rg.operationStatus = OperationStatus.Finished;
       rg.retryInfo = initRetryInfo();
     }
@@ -234,19 +226,6 @@ async function refreshCreateSession(
     availableDenoms,
   );
 
-  if (logger.shouldLogTrace()) {
-    logger.trace(`printing selected denominations for refresh`);
-    logger.trace(`current time: ${stringifyTimestamp(getTimestampNow())}`);
-    for (const denom of newCoinDenoms.selectedDenoms) {
-      logger.trace(`denom ${denom.denom}, count ${denom.count}`);
-      logger.trace(
-        `withdrawal expiration ${stringifyTimestamp(
-          denom.denom.stampExpireWithdraw,
-        )}`,
-      );
-    }
-  }
-
   if (newCoinDenoms.selectedDenoms.length === 0) {
     logger.trace(
       `not refreshing, available amount ${amountToPretty(
@@ -306,7 +285,9 @@ async function refreshCreateSession(
 }
 
 function getRefreshRequestTimeout(rg: RefreshGroupRecord): Duration {
-  return { d_ms: 5000 };
+  return Duration.fromSpec({
+    seconds: 5,
+  });
 }
 
 async function refreshMelt(
@@ -733,7 +714,7 @@ async function refreshReveal(
 async function incrementRefreshRetry(
   ws: InternalWalletState,
   refreshGroupId: string,
-  err: TalerErrorDetails | undefined,
+  err: TalerErrorDetail | undefined,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({
@@ -766,7 +747,7 @@ export async function processRefreshGroup(
   forceNow = false,
 ): Promise<void> {
   await ws.memoProcessRefresh.memo(refreshGroupId, async () => {
-    const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+    const onOpErr = (e: TalerErrorDetail): Promise<void> =>
       incrementRefreshRetry(ws, refreshGroupId, e);
     return await guardOperationException(
       async () => await processRefreshGroupImpl(ws, refreshGroupId, forceNow),
@@ -949,12 +930,12 @@ export async function createRefreshGroup(
     retryInfo: initRetryInfo(),
     inputPerCoin,
     estimatedOutputPerCoin,
-    timestampCreated: getTimestampNow(),
+    timestampCreated: TalerProtocolTimestamp.now(),
   };
 
   if (oldCoinPubs.length == 0) {
     logger.warn("created refresh group with zero coins");
-    refreshGroup.timestampFinished = getTimestampNow();
+    refreshGroup.timestampFinished = TalerProtocolTimestamp.now();
     refreshGroup.operationStatus = OperationStatus.Finished;
   }
 
@@ -974,25 +955,23 @@ export async function createRefreshGroup(
 /**
  * Timestamp after which the wallet would do the next check for an 
auto-refresh.
  */
-function getAutoRefreshCheckThreshold(d: DenominationRecord): Timestamp {
-  const delta = timestampDifference(
-    d.stampExpireWithdraw,
-    d.stampExpireDeposit,
-  );
+function getAutoRefreshCheckThreshold(d: DenominationRecord): AbsoluteTime {
+  const expireWithdraw = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw);
+  const expireDeposit = AbsoluteTime.fromTimestamp(d.stampExpireDeposit);
+  const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit);
   const deltaDiv = durationMul(delta, 0.75);
-  return timestampAddDuration(d.stampExpireWithdraw, deltaDiv);
+  return AbsoluteTime.addDuration(expireWithdraw, deltaDiv);
 }
 
 /**
  * Timestamp after which the wallet would do an auto-refresh.
  */
-function getAutoRefreshExecuteThreshold(d: DenominationRecord): Timestamp {
-  const delta = timestampDifference(
-    d.stampExpireWithdraw,
-    d.stampExpireDeposit,
-  );
+function getAutoRefreshExecuteThreshold(d: DenominationRecord): AbsoluteTime {
+  const expireWithdraw = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw);
+  const expireDeposit = AbsoluteTime.fromTimestamp(d.stampExpireDeposit);
+  const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit);
   const deltaDiv = durationMul(delta, 0.5);
-  return timestampAddDuration(d.stampExpireWithdraw, deltaDiv);
+  return AbsoluteTime.addDuration(expireWithdraw, deltaDiv);
 }
 
 export async function autoRefresh(
@@ -1001,8 +980,8 @@ export async function autoRefresh(
 ): Promise<void> {
   logger.info(`doing auto-refresh check for '${exchangeBaseUrl}'`);
   await updateExchangeFromUrl(ws, exchangeBaseUrl, undefined, true);
-  let minCheckThreshold = timestampAddDuration(
-    getTimestampNow(),
+  let minCheckThreshold = AbsoluteTime.addDuration(
+    AbsoluteTime.now(),
     durationFromSpec({ days: 1 }),
   );
   await ws.db
@@ -1037,11 +1016,14 @@ export async function autoRefresh(
           continue;
         }
         const executeThreshold = getAutoRefreshExecuteThreshold(denom);
-        if (isTimestampExpired(executeThreshold)) {
+        if (AbsoluteTime.isExpired(executeThreshold)) {
           refreshCoins.push(coin);
         } else {
           const checkThreshold = getAutoRefreshCheckThreshold(denom);
-          minCheckThreshold = timestampMin(minCheckThreshold, checkThreshold);
+          minCheckThreshold = AbsoluteTime.min(
+            minCheckThreshold,
+            checkThreshold,
+          );
         }
       }
       if (refreshCoins.length > 0) {
@@ -1056,12 +1038,12 @@ export async function autoRefresh(
         );
       }
       logger.info(
-        `current wallet time: ${timestampToIsoString(getTimestampNow())}`,
+        `current wallet time: ${AbsoluteTime.toIsoString(AbsoluteTime.now())}`,
       );
       logger.info(
-        `next refresh check at ${timestampToIsoString(minCheckThreshold)}`,
+        `next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`,
       );
-      exchange.nextRefreshCheck = minCheckThreshold;
+      exchange.nextRefreshCheck = AbsoluteTime.toTimestamp(minCheckThreshold);
       await tx.exchanges.put(exchange);
     });
 }
diff --git a/packages/taler-wallet-core/src/operations/refund.ts 
b/packages/taler-wallet-core/src/operations/refund.ts
index 106c7936..d888ff01 100644
--- a/packages/taler-wallet-core/src/operations/refund.ts
+++ b/packages/taler-wallet-core/src/operations/refund.ts
@@ -32,7 +32,6 @@ import {
   codecForAbortResponse,
   codecForMerchantOrderRefundPickupResponse,
   CoinPublicKey,
-  getTimestampNow,
   Logger,
   MerchantCoinRefundFailureStatus,
   MerchantCoinRefundStatus,
@@ -41,11 +40,12 @@ import {
   parseRefundUri,
   RefreshReason,
   TalerErrorCode,
-  TalerErrorDetails,
+  TalerErrorDetail,
   URL,
-  timestampAddDuration,
   codecForMerchantOrderStatusPaid,
-  isTimestampExpired,
+  AbsoluteTime,
+  TalerProtocolTimestamp,
+  Duration,
 } from "@gnu-taler/taler-util";
 import {
   AbortStatus,
@@ -88,7 +88,7 @@ async function resetPurchaseQueryRefundRetry(
 async function incrementPurchaseQueryRefundRetry(
   ws: InternalWalletState,
   proposalId: string,
-  err: TalerErrorDetails | undefined,
+  err: TalerErrorDetail | undefined,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({
@@ -170,7 +170,7 @@ async function applySuccessfulRefund(
 
   p.refunds[refundKey] = {
     type: RefundState.Applied,
-    obtainedTime: getTimestampNow(),
+    obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
     executionTime: r.execution_time,
     refundAmount: Amounts.parseOrThrow(r.refund_amount),
     refundFee: denom.feeRefund,
@@ -222,7 +222,7 @@ async function storePendingRefund(
 
   p.refunds[refundKey] = {
     type: RefundState.Pending,
-    obtainedTime: getTimestampNow(),
+    obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
     executionTime: r.execution_time,
     refundAmount: Amounts.parseOrThrow(r.refund_amount),
     refundFee: denom.feeRefund,
@@ -275,7 +275,7 @@ async function storeFailedRefund(
 
   p.refunds[refundKey] = {
     type: RefundState.Failed,
-    obtainedTime: getTimestampNow(),
+    obtainedTime: TalerProtocolTimestamp.now(),
     executionTime: r.execution_time,
     refundAmount: Amounts.parseOrThrow(r.refund_amount),
     refundFee: denom.feeRefund,
@@ -327,7 +327,7 @@ async function acceptRefunds(
   reason: RefundReason,
 ): Promise<void> {
   logger.trace("handling refunds", refunds);
-  const now = getTimestampNow();
+  const now = TalerProtocolTimestamp.now();
 
   await ws.db
     .mktx((x) => ({
@@ -401,7 +401,10 @@ async function acceptRefunds(
       if (
         p.timestampFirstSuccessfulPay &&
         p.autoRefundDeadline &&
-        p.autoRefundDeadline.t_ms > now.t_ms
+        AbsoluteTime.cmp(
+          AbsoluteTime.fromTimestamp(p.autoRefundDeadline),
+          AbsoluteTime.fromTimestamp(now),
+        ) > 0
       ) {
         queryDone = false;
       }
@@ -556,8 +559,10 @@ export async function applyRefund(
         ).amount,
       ).amount;
     } else {
-      amountRefundGone = Amounts.add(amountRefundGone, refund.refundAmount)
-        .amount;
+      amountRefundGone = Amounts.add(
+        amountRefundGone,
+        refund.refundAmount,
+      ).amount;
     }
   });
 
@@ -587,7 +592,7 @@ export async function processPurchaseQueryRefund(
   proposalId: string,
   forceNow = false,
 ): Promise<void> {
-  const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+  const onOpErr = (e: TalerErrorDetail): Promise<void> =>
     incrementPurchaseQueryRefundRetry(ws, proposalId, e);
   await guardOperationException(
     () => processPurchaseQueryRefundImpl(ws, proposalId, forceNow, true),
@@ -623,7 +628,9 @@ async function processPurchaseQueryRefundImpl(
     if (
       waitForAutoRefund &&
       purchase.autoRefundDeadline &&
-      !isTimestampExpired(purchase.autoRefundDeadline)
+      !AbsoluteTime.isExpired(
+        AbsoluteTime.fromTimestamp(purchase.autoRefundDeadline),
+      )
     ) {
       const requestUrl = new URL(
         `orders/${purchase.download.contractData.orderId}`,
@@ -731,11 +738,13 @@ async function processPurchaseQueryRefundImpl(
           purchase.payCoinSelection.coinContributions[i],
         ),
         rtransaction_id: 0,
-        execution_time: timestampAddDuration(
-          purchase.download.contractData.timestamp,
-          {
-            d_ms: 1000,
-          },
+        execution_time: AbsoluteTime.toTimestamp(
+          AbsoluteTime.addDuration(
+            AbsoluteTime.fromTimestamp(
+              purchase.download.contractData.timestamp,
+            ),
+            Duration.fromSpec({ seconds: 1 }),
+          ),
         ),
       });
     }
diff --git a/packages/taler-wallet-core/src/operations/reserves.ts 
b/packages/taler-wallet-core/src/operations/reserves.ts
index d91ce89f..baa97703 100644
--- a/packages/taler-wallet-core/src/operations/reserves.ts
+++ b/packages/taler-wallet-core/src/operations/reserves.ts
@@ -29,15 +29,13 @@ import {
   durationMin,
   encodeCrock,
   getRandomBytes,
-  getTimestampNow,
   j2s,
   Logger,
   NotificationType,
   randomBytes,
-  ReserveTransactionType,
   TalerErrorCode,
-  TalerErrorDetails,
-  Timestamp,
+  TalerErrorDetail,
+  AbsoluteTime,
   URL,
 } from "@gnu-taler/taler-util";
 import { InternalWalletState } from "../common.js";
@@ -49,7 +47,7 @@ import {
   WalletStoresV1,
   WithdrawalGroupRecord,
 } from "../db.js";
-import { guardOperationException, OperationFailedError } from "../errors.js";
+import { guardOperationException, TalerError } from "../errors.js";
 import { assertUnreachable } from "../util/assertUnreachable.js";
 import {
   readSuccessResponseJsonOrErrorCode,
@@ -137,7 +135,7 @@ async function incrementReserveRetry(
 async function reportReserveError(
   ws: InternalWalletState,
   reservePub: string,
-  err: TalerErrorDetails,
+  err: TalerErrorDetail,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({
@@ -172,7 +170,7 @@ export async function createReserve(
   req: CreateReserveRequest,
 ): Promise<CreateReserveResponse> {
   const keypair = await ws.cryptoApi.createEddsaKeypair();
-  const now = getTimestampNow();
+  const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
   const canonExchange = canonicalizeBaseUrl(req.exchange);
 
   let reserveStatus;
@@ -217,7 +215,6 @@ export async function createReserve(
     timestampReserveInfoPosted: undefined,
     bankInfo,
     reserveStatus,
-    lastSuccessfulStatusQuery: undefined,
     retryInfo: initRetryInfo(),
     lastError: undefined,
     currency: req.amount.currency,
@@ -341,7 +338,7 @@ export async function processReserve(
   forceNow = false,
 ): Promise<void> {
   return ws.memoProcessReserve.memo(reservePub, async () => {
-    const onOpError = (err: TalerErrorDetails): Promise<void> =>
+    const onOpError = (err: TalerErrorDetail): Promise<void> =>
       reportReserveError(ws, reservePub, err);
     await guardOperationException(
       () => processReserveImpl(ws, reservePub, forceNow),
@@ -403,7 +400,9 @@ async function registerReserveWithBank(
         default:
           return;
       }
-      r.timestampReserveInfoPosted = getTimestampNow();
+      r.timestampReserveInfoPosted = AbsoluteTime.toTimestamp(
+        AbsoluteTime.now(),
+      );
       r.reserveStatus = ReserveRecordStatus.WaitConfirmBank;
       r.operationStatus = OperationStatus.Pending;
       if (!r.bankInfo) {
@@ -472,7 +471,7 @@ async function processReserveBankStatus(
           default:
             return;
         }
-        const now = getTimestampNow();
+        const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
         r.timestampBankConfirmed = now;
         r.reserveStatus = ReserveRecordStatus.BankAborted;
         r.operationStatus = OperationStatus.Finished;
@@ -509,7 +508,7 @@ async function processReserveBankStatus(
           default:
             return;
         }
-        const now = getTimestampNow();
+        const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
         r.timestampBankConfirmed = now;
         r.reserveStatus = ReserveRecordStatus.QueryingStatus;
         r.operationStatus = OperationStatus.Pending;
@@ -572,7 +571,7 @@ async function updateReserve(
     if (
       resp.status === 404 &&
       result.talerErrorResponse.code ===
-        TalerErrorCode.EXCHANGE_RESERVES_GET_STATUS_UNKNOWN
+        TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN
     ) {
       ws.notify({
         type: NotificationType.ReserveNotYetFound,
@@ -683,7 +682,7 @@ async function updateReserve(
         exchangeBaseUrl: reserve.exchangeBaseUrl,
         reservePub: reserve.reservePub,
         rawWithdrawalAmount: remainingAmount,
-        timestampStart: getTimestampNow(),
+        timestampStart: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
         retryInfo: initRetryInfo(),
         lastError: undefined,
         denomsSel: denomSelectionInfoToState(denomSelInfo),
@@ -736,7 +735,7 @@ async function processReserveImpl(
     await resetReserveRetry(ws, reservePub);
   } else if (
     reserve.retryInfo &&
-    !Timestamp.isExpired(reserve.retryInfo.nextRetry)
+    !AbsoluteTime.isExpired(reserve.retryInfo.nextRetry)
   ) {
     logger.trace("processReserve retry not due yet");
     return;
@@ -804,9 +803,8 @@ export async function createTalerWithdrawReserve(
       return tx.reserves.get(reserve.reservePub);
     });
   if (processedReserve?.reserveStatus === ReserveRecordStatus.BankAborted) {
-    throw OperationFailedError.fromCode(
+    throw TalerError.fromDetail(
       TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK,
-      "withdrawal aborted by bank",
       {},
     );
   }
diff --git a/packages/taler-wallet-core/src/operations/testing.ts 
b/packages/taler-wallet-core/src/operations/testing.ts
index 93f48fb8..23fee56c 100644
--- a/packages/taler-wallet-core/src/operations/testing.ts
+++ b/packages/taler-wallet-core/src/operations/testing.ts
@@ -229,8 +229,8 @@ async function createOrder(
       amount,
       summary,
       fulfillment_url: fulfillmentUrl,
-      refund_deadline: { t_ms: t * 1000 },
-      wire_transfer_deadline: { t_ms: t * 1000 },
+      refund_deadline: { t_s: t },
+      wire_transfer_deadline: { t_s: t },
     },
   };
   const resp = await http.postJson(reqUrl, orderReq, {
diff --git a/packages/taler-wallet-core/src/operations/tip.ts 
b/packages/taler-wallet-core/src/operations/tip.ts
index a2a4e6f4..7b3d36a7 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -22,8 +22,7 @@ import {
   parseTipUri,
   codecForTipPickupGetResponse,
   Amounts,
-  getTimestampNow,
-  TalerErrorDetails,
+  TalerErrorDetail,
   NotificationType,
   TipPlanchetDetail,
   TalerErrorCode,
@@ -32,6 +31,7 @@ import {
   DenomKeyType,
   BlindedDenominationSignature,
   codecForMerchantTipResponseV2,
+  TalerProtocolTimestamp,
 } from "@gnu-taler/taler-util";
 import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";
 import {
@@ -39,11 +39,12 @@ import {
   CoinRecord,
   CoinSourceType,
   CoinStatus,
+  TipRecord,
 } from "../db.js";
 import { j2s } from "@gnu-taler/taler-util";
 import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
 import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
-import { guardOperationException, makeErrorDetails } from "../errors.js";
+import { guardOperationException, makeErrorDetail } from "../errors.js";
 import { updateExchangeFromUrl } from "./exchanges.js";
 import { InternalWalletState } from "../common.js";
 import {
@@ -115,14 +116,14 @@ export async function prepareTip(
     const secretSeed = encodeCrock(getRandomBytes(64));
     const denomSelUid = encodeCrock(getRandomBytes(32));
 
-    const newTipRecord = {
+    const newTipRecord: TipRecord = {
       walletTipId: walletTipId,
       acceptedTimestamp: undefined,
       tipAmountRaw: amount,
       tipExpiration: tipPickupStatus.expiration,
       exchangeBaseUrl: tipPickupStatus.exchange_url,
       merchantBaseUrl: res.merchantBaseUrl,
-      createdTimestamp: getTimestampNow(),
+      createdTimestamp: TalerProtocolTimestamp.now(),
       merchantTipId: res.merchantTipId,
       tipAmountEffective: Amounts.sub(
         amount,
@@ -162,7 +163,7 @@ export async function prepareTip(
 async function incrementTipRetry(
   ws: InternalWalletState,
   walletTipId: string,
-  err: TalerErrorDetails | undefined,
+  err: TalerErrorDetail | undefined,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({
@@ -191,7 +192,7 @@ export async function processTip(
   tipId: string,
   forceNow = false,
 ): Promise<void> {
-  const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+  const onOpErr = (e: TalerErrorDetail): Promise<void> =>
     incrementTipRetry(ws, tipId, e);
   await guardOperationException(
     () => processTipImpl(ws, tipId, forceNow),
@@ -295,10 +296,10 @@ async function processTipImpl(
       merchantResp.status === 424)
   ) {
     logger.trace(`got transient tip error`);
-    const err = makeErrorDetails(
+    const err = makeErrorDetail(
       TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
-      "tip pickup failed (transient)",
       getHttpResponseErrorDetails(merchantResp),
+      "tip pickup failed (transient)",
     );
     await incrementTipRetry(ws, tipRecord.walletTipId, err);
     // FIXME: Maybe we want to signal to the caller that the transient error 
happened?
@@ -354,10 +355,10 @@ async function processTipImpl(
           if (!tipRecord) {
             return;
           }
-          tipRecord.lastError = makeErrorDetails(
+          tipRecord.lastError = makeErrorDetail(
             TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID,
-            "invalid signature from the exchange (via merchant tip) after 
unblinding",
             {},
+            "invalid signature from the exchange (via merchant tip) after 
unblinding",
           );
           await tx.tips.put(tipRecord);
         });
@@ -397,7 +398,7 @@ async function processTipImpl(
       if (tr.pickedUpTimestamp) {
         return;
       }
-      tr.pickedUpTimestamp = getTimestampNow();
+      tr.pickedUpTimestamp = TalerProtocolTimestamp.now();
       tr.lastError = undefined;
       tr.retryInfo = initRetryInfo();
       await tx.tips.put(tr);
@@ -421,7 +422,7 @@ export async function acceptTip(
         logger.error("tip not found");
         return false;
       }
-      tipRecord.acceptedTimestamp = getTimestampNow();
+      tipRecord.acceptedTimestamp = TalerProtocolTimestamp.now();
       await tx.tips.put(tipRecord);
       return true;
     });
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index 23ab3905..bc466f5a 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -18,12 +18,12 @@
  * Imports.
  */
 import {
+  AbsoluteTime,
   AmountJson,
   Amounts,
   Logger,
   OrderShortInfo,
   PaymentStatus,
-  timestampCmp,
   Transaction,
   TransactionsRequest,
   TransactionsResponse,
@@ -309,7 +309,7 @@ export async function getTransactions(
 
           for (const rk of Object.keys(pr.refunds)) {
             const refund = pr.refunds[rk];
-            const groupKey = `${refund.executionTime.t_ms}`;
+            const groupKey = `${refund.executionTime.t_s}`;
             refundGroupKeys.add(groupKey);
           }
 
@@ -333,7 +333,7 @@ export async function getTransactions(
             let amountEffective = 
Amounts.getZero(contractData.amount.currency);
             for (const rk of Object.keys(pr.refunds)) {
               const refund = pr.refunds[rk];
-              const myGroupKey = `${refund.executionTime.t_ms}`;
+              const myGroupKey = `${refund.executionTime.t_s}`;
               if (myGroupKey !== groupKey) {
                 continue;
               }
@@ -403,8 +403,18 @@ export async function getTransactions(
   const txPending = transactions.filter((x) => x.pending);
   const txNotPending = transactions.filter((x) => !x.pending);
 
-  txPending.sort((h1, h2) => timestampCmp(h1.timestamp, h2.timestamp));
-  txNotPending.sort((h1, h2) => timestampCmp(h1.timestamp, h2.timestamp));
+  txPending.sort((h1, h2) =>
+    AbsoluteTime.cmp(
+      AbsoluteTime.fromTimestamp(h1.timestamp),
+      AbsoluteTime.fromTimestamp(h2.timestamp),
+    ),
+  );
+  txNotPending.sort((h1, h2) =>
+    AbsoluteTime.cmp(
+      AbsoluteTime.fromTimestamp(h1.timestamp),
+      AbsoluteTime.fromTimestamp(h2.timestamp),
+    ),
+  );
 
   return { transactions: [...txNotPending, ...txPending] };
 }
@@ -485,11 +495,10 @@ export async function deleteTransaction(
           });
           return;
         }
-        const reserveRecord:
-          | ReserveRecord
-          | undefined = await 
tx.reserves.indexes.byInitialWithdrawalGroupId.get(
-          withdrawalGroupId,
-        );
+        const reserveRecord: ReserveRecord | undefined =
+          await tx.reserves.indexes.byInitialWithdrawalGroupId.get(
+            withdrawalGroupId,
+          );
         if (reserveRecord && !reserveRecord.initialWithdrawalStarted) {
           const reservePub = reserveRecord.reservePub;
           await tx.reserves.delete(reservePub);
diff --git a/packages/taler-wallet-core/src/operations/withdraw.test.ts 
b/packages/taler-wallet-core/src/operations/withdraw.test.ts
index 02540848..e5894a3e 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.test.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.test.ts
@@ -62,16 +62,16 @@ test("withdrawal selection bug repro", (t) => {
       masterSig:
         
"4F0P456CNNTTWK8BFJHGM3JTD6FVVNZY8EP077GYAHDJ5Y81S5RQ3SMS925NXMDVG9A88JAAP0E2GDZBC21PP5NHFFVWHAW3AVT8J3R",
       stampExpireDeposit: {
-        t_ms: 1742909388000,
+        t_s: 1742909388,
       },
       stampExpireLegal: {
-        t_ms: 1900589388000,
+        t_s: 1900589388,
       },
       stampExpireWithdraw: {
-        t_ms: 1679837388000,
+        t_s: 1679837388,
       },
       stampStart: {
-        t_ms: 1585229388000,
+        t_s: 1585229388,
       },
       verificationStatus: DenominationVerificationStatus.Unverified,
       value: {
@@ -79,7 +79,7 @@ test("withdrawal selection bug repro", (t) => {
         fraction: 0,
         value: 1000,
       },
-      listIssueDate: { t_ms: 0 },
+      listIssueDate: { t_s: 0 },
     },
     {
       denomPub: {
@@ -117,16 +117,16 @@ test("withdrawal selection bug repro", (t) => {
       masterSig:
         
"P99AW82W46MZ0AKW7Z58VQPXFNTJQM9DVTYPBDF6KVYF38PPVDAZTV7JQ8TY7HGEC7JJJAY4E7AY7J3W1WV10DAZZQHHKTAVTSRAC20",
       stampExpireDeposit: {
-        t_ms: 1742909388000,
+        t_s: 1742909388,
       },
       stampExpireLegal: {
-        t_ms: 1900589388000,
+        t_s: 1900589388,
       },
       stampExpireWithdraw: {
-        t_ms: 1679837388000,
+        t_s: 1679837388,
       },
       stampStart: {
-        t_ms: 1585229388000,
+        t_s: 1585229388,
       },
       verificationStatus: DenominationVerificationStatus.Unverified,
       value: {
@@ -134,7 +134,7 @@ test("withdrawal selection bug repro", (t) => {
         fraction: 0,
         value: 10,
       },
-      listIssueDate: { t_ms: 0 },
+      listIssueDate: { t_s: 0 },
     },
     {
       denomPub: {
@@ -171,16 +171,16 @@ test("withdrawal selection bug repro", (t) => {
       masterSig:
         
"8S4VZGHE5WE0N5ZVCHYW9KZZR4YAKK15S46MV1HR1QB9AAMH3NWPW4DCR4NYGJK33Q8YNFY80SWNS6XKAP5DEVK933TM894FJ2VGE3G",
       stampExpireDeposit: {
-        t_ms: 1742909388000,
+        t_s: 1742909388,
       },
       stampExpireLegal: {
-        t_ms: 1900589388000,
+        t_s: 1900589388,
       },
       stampExpireWithdraw: {
-        t_ms: 1679837388000,
+        t_s: 1679837388,
       },
       stampStart: {
-        t_ms: 1585229388000,
+        t_s: 1585229388,
       },
       verificationStatus: DenominationVerificationStatus.Unverified,
       value: {
@@ -188,7 +188,7 @@ test("withdrawal selection bug repro", (t) => {
         fraction: 0,
         value: 5,
       },
-      listIssueDate: { t_ms: 0 },
+      listIssueDate: { t_s: 0 },
     },
     {
       denomPub: {
@@ -226,16 +226,16 @@ test("withdrawal selection bug repro", (t) => {
       masterSig:
         
"E3AWGAG8VB42P3KXM8B04Z6M483SX59R3Y4T53C3NXCA2NPB6C7HVCMVX05DC6S58E9X40NGEBQNYXKYMYCF3ASY2C4WP1WCZ4ME610",
       stampExpireDeposit: {
-        t_ms: 1742909388000,
+        t_s: 1742909388,
       },
       stampExpireLegal: {
-        t_ms: 1900589388000,
+        t_s: 1900589388,
       },
       stampExpireWithdraw: {
-        t_ms: 1679837388000,
+        t_s: 1679837388,
       },
       stampStart: {
-        t_ms: 1585229388000,
+        t_s: 1585229388,
       },
       verificationStatus: DenominationVerificationStatus.Unverified,
       value: {
@@ -243,7 +243,7 @@ test("withdrawal selection bug repro", (t) => {
         fraction: 0,
         value: 1,
       },
-      listIssueDate: { t_ms: 0 },
+      listIssueDate: { t_s: 0 },
     },
     {
       denomPub: {
@@ -280,16 +280,16 @@ test("withdrawal selection bug repro", (t) => {
       masterSig:
         
"0ES1RKV002XB4YP21SN0QB7RSDHGYT0XAE65JYN8AVJAA6H7JZFN7JADXT521DJS89XMGPZGR8GCXF1516Y0Q9QDV00E6NMFA6CF838",
       stampExpireDeposit: {
-        t_ms: 1742909388000,
+        t_s: 1742909388,
       },
       stampExpireLegal: {
-        t_ms: 1900589388000,
+        t_s: 1900589388,
       },
       stampExpireWithdraw: {
-        t_ms: 1679837388000,
+        t_s: 1679837388,
       },
       stampStart: {
-        t_ms: 1585229388000,
+        t_s: 1585229388,
       },
       verificationStatus: DenominationVerificationStatus.Unverified,
       value: {
@@ -297,7 +297,7 @@ test("withdrawal selection bug repro", (t) => {
         fraction: 10000000,
         value: 0,
       },
-      listIssueDate: { t_ms: 0 },
+      listIssueDate: { t_s: 0 },
     },
     {
       denomPub: {
@@ -334,16 +334,16 @@ test("withdrawal selection bug repro", (t) => {
       masterSig:
         
"58QEB6C6N7602E572E3JYANVVJ9BRW0V9E2ZFDW940N47YVQDK9SAFPWBN5YGT3G1742AFKQ0CYR4DM2VWV0Z0T1XMEKWN6X2EZ9M0R",
       stampExpireDeposit: {
-        t_ms: 1742909388000,
+        t_s: 1742909388,
       },
       stampExpireLegal: {
-        t_ms: 1900589388000,
+        t_s: 1900589388,
       },
       stampExpireWithdraw: {
-        t_ms: 1679837388000,
+        t_s: 1679837388,
       },
       stampStart: {
-        t_ms: 1585229388000,
+        t_s: 1585229388,
       },
       verificationStatus: DenominationVerificationStatus.Unverified,
       value: {
@@ -351,7 +351,7 @@ test("withdrawal selection bug repro", (t) => {
         fraction: 0,
         value: 2,
       },
-      listIssueDate: { t_ms: 0 },
+      listIssueDate: { t_s: 0 },
     },
   ];
 
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 392cecf0..1d7bf930 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -26,16 +26,12 @@ import {
   codecForWithdrawResponse,
   durationFromSpec,
   ExchangeListItem,
-  getDurationRemaining,
-  getTimestampNow,
   Logger,
   NotificationType,
   parseWithdrawUri,
   TalerErrorCode,
-  TalerErrorDetails,
-  Timestamp,
-  timestampCmp,
-  timestampSubtractDuraction,
+  TalerErrorDetail,
+  AbsoluteTime,
   WithdrawResponse,
   URL,
   WithdrawUriInfoResponse,
@@ -44,6 +40,9 @@ import {
   LibtoolVersion,
   UnblindedSignature,
   ExchangeWithdrawRequest,
+  Duration,
+  TalerProtocolTimestamp,
+  TransactionType,
 } from "@gnu-taler/taler-util";
 import {
   CoinRecord,
@@ -65,9 +64,11 @@ import {
 } from "../util/http.js";
 import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
 import {
+  getErrorDetailFromException,
   guardOperationException,
-  makeErrorDetails,
-  OperationFailedError,
+  makeErrorDetail,
+  makePendingOperationFailedError,
+  TalerError,
 } from "../errors.js";
 import { InternalWalletState } from "../common.js";
 import {
@@ -147,7 +148,7 @@ export interface ExchangeWithdrawDetails {
   /**
    * The earliest deposit expiration of the selected coins.
    */
-  earliestDepositExpiration: Timestamp;
+  earliestDepositExpiration: TalerProtocolTimestamp;
 
   /**
    * Number of currently offered denominations.
@@ -184,18 +185,20 @@ export interface ExchangeWithdrawDetails {
  * revocation and offered state.
  */
 export function isWithdrawableDenom(d: DenominationRecord): boolean {
-  const now = getTimestampNow();
-  const started = timestampCmp(now, d.stampStart) >= 0;
-  let lastPossibleWithdraw: Timestamp;
+  const now = AbsoluteTime.now();
+  const start = AbsoluteTime.fromTimestamp(d.stampStart);
+  const withdrawExpire = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw);
+  const started = AbsoluteTime.cmp(now, start) >= 0;
+  let lastPossibleWithdraw: AbsoluteTime;
   if (walletCoreDebugFlags.denomselAllowLate) {
-    lastPossibleWithdraw = d.stampExpireWithdraw;
+    lastPossibleWithdraw = start;
   } else {
-    lastPossibleWithdraw = timestampSubtractDuraction(
-      d.stampExpireWithdraw,
+    lastPossibleWithdraw = AbsoluteTime.subtractDuraction(
+      withdrawExpire,
       durationFromSpec({ minutes: 5 }),
     );
   }
-  const remaining = getDurationRemaining(lastPossibleWithdraw, now);
+  const remaining = Duration.getRemaining(lastPossibleWithdraw, now);
   const stillOkay = remaining.d_ms !== 0;
   return started && stillOkay && !d.isRevoked && d.isOffered;
 }
@@ -274,7 +277,7 @@ export function selectWithdrawalDenominations(
 /**
  * Get information about a withdrawal from
  * a taler://withdraw URI by asking the bank.
- * 
+ *
  * FIXME: Move into bank client.
  */
 export async function getBankWithdrawalInfo(
@@ -299,15 +302,14 @@ export async function getBankWithdrawalInfo(
     config.version,
   );
   if (versionRes?.compatible != true) {
-    const opErr = makeErrorDetails(
+    throw TalerError.fromDetail(
       TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE,
-      "bank integration protocol version not compatible with wallet",
       {
         exchangeProtocolVersion: config.version,
         walletProtocolVersion: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
       },
+      "bank integration protocol version not compatible with wallet",
     );
-    throw new OperationFailedError(opErr);
   }
 
   const reqUrl = new URL(
@@ -526,12 +528,9 @@ async function processPlanchetExchangeRequest(
     );
     return r;
   } catch (e) {
+    const errDetail = getErrorDetailFromException(e);
     logger.trace("withdrawal request failed", e);
     logger.trace(e);
-    if (!(e instanceof OperationFailedError)) {
-      throw e;
-    }
-    const errDetails = e.operationError;
     await ws.db
       .mktx((x) => ({ planchets: x.planchets }))
       .runReadWrite(async (tx) => {
@@ -542,7 +541,7 @@ async function processPlanchetExchangeRequest(
         if (!planchet) {
           return;
         }
-        planchet.lastError = errDetails;
+        planchet.lastError = errDetail;
         await tx.planchets.put(planchet);
       });
     return;
@@ -628,10 +627,10 @@ async function processPlanchetVerifyAndStoreCoin(
         if (!planchet) {
           return;
         }
-        planchet.lastError = makeErrorDetails(
+        planchet.lastError = makeErrorDetail(
           TalerErrorCode.WALLET_EXCHANGE_COIN_SIGNATURE_INVALID,
-          "invalid signature from the exchange after unblinding",
           {},
+          "invalid signature from the exchange after unblinding",
         );
         await tx.planchets.put(planchet);
       });
@@ -797,7 +796,7 @@ export async function updateWithdrawalDenoms(
 async function incrementWithdrawalRetry(
   ws: InternalWalletState,
   withdrawalGroupId: string,
-  err: TalerErrorDetails | undefined,
+  err: TalerErrorDetail | undefined,
 ): Promise<void> {
   await ws.db
     .mktx((x) => ({ withdrawalGroups: x.withdrawalGroups }))
@@ -821,7 +820,7 @@ export async function processWithdrawGroup(
   withdrawalGroupId: string,
   forceNow = false,
 ): Promise<void> {
-  const onOpErr = (e: TalerErrorDetails): Promise<void> =>
+  const onOpErr = (e: TalerErrorDetail): Promise<void> =>
     incrementWithdrawalRetry(ws, withdrawalGroupId, e);
   await guardOperationException(
     () => processWithdrawGroupImpl(ws, withdrawalGroupId, forceNow),
@@ -919,7 +918,7 @@ async function processWithdrawGroupImpl(
 
   let numFinished = 0;
   let finishedForFirstTime = false;
-  let errorsPerCoin: Record<number, TalerErrorDetails> = {};
+  let errorsPerCoin: Record<number, TalerErrorDetail> = {};
 
   await ws.db
     .mktx((x) => ({
@@ -947,7 +946,7 @@ async function processWithdrawGroupImpl(
       logger.trace(`now withdrawn ${numFinished} of ${numTotalCoins} coins`);
       if (wg.timestampFinish === undefined && numFinished === numTotalCoins) {
         finishedForFirstTime = true;
-        wg.timestampFinish = getTimestampNow();
+        wg.timestampFinish = TalerProtocolTimestamp.now();
         wg.operationStatus = OperationStatus.Finished;
         delete wg.lastError;
         wg.retryInfo = initRetryInfo();
@@ -957,12 +956,12 @@ async function processWithdrawGroupImpl(
     });
 
   if (numFinished != numTotalCoins) {
-    throw OperationFailedError.fromCode(
+    throw TalerError.fromDetail(
       TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE,
-      `withdrawal did not finish (${numFinished} / ${numTotalCoins} coins 
withdrawn)`,
       {
         errorsPerCoin,
       },
+      `withdrawal did not finish (${numFinished} / ${numTotalCoins} coins 
withdrawn)`,
     );
   }
 
@@ -999,7 +998,12 @@ export async function getExchangeWithdrawalInfo(
   for (let i = 1; i < selectedDenoms.selectedDenoms.length; i++) {
     const expireDeposit =
       selectedDenoms.selectedDenoms[i].denom.stampExpireDeposit;
-    if (expireDeposit.t_ms < earliestDepositExpiration.t_ms) {
+    if (
+      AbsoluteTime.cmp(
+        AbsoluteTime.fromTimestamp(expireDeposit),
+        AbsoluteTime.fromTimestamp(earliestDepositExpiration),
+      ) < 0
+    ) {
       earliestDepositExpiration = expireDeposit;
     }
   }
diff --git a/packages/taler-wallet-core/src/pending-types.ts 
b/packages/taler-wallet-core/src/pending-types.ts
index 911d0d8b..f4e5216b 100644
--- a/packages/taler-wallet-core/src/pending-types.ts
+++ b/packages/taler-wallet-core/src/pending-types.ts
@@ -25,9 +25,10 @@
  * Imports.
  */
 import {
-  TalerErrorDetails,
+  TalerErrorDetail,
   BalancesResponse,
-  Timestamp,
+  AbsoluteTime,
+  TalerProtocolTimestamp,
 } from "@gnu-taler/taler-util";
 import { ReserveRecordStatus } from "./db.js";
 import { RetryInfo } from "./util/retries.js";
@@ -70,7 +71,7 @@ export type PendingTaskInfo = PendingTaskInfoCommon &
 export interface PendingBackupTask {
   type: PendingTaskType.Backup;
   backupProviderBaseUrl: string;
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 }
 
 /**
@@ -79,7 +80,7 @@ export interface PendingBackupTask {
 export interface PendingExchangeUpdateTask {
   type: PendingTaskType.ExchangeUpdate;
   exchangeBaseUrl: string;
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 }
 
 /**
@@ -112,7 +113,7 @@ export interface PendingReserveTask {
   type: PendingTaskType.Reserve;
   retryInfo: RetryInfo | undefined;
   stage: ReserveRecordStatus;
-  timestampCreated: Timestamp;
+  timestampCreated: TalerProtocolTimestamp;
   reserveType: ReserveType;
   reservePub: string;
   bankWithdrawConfirmUrl?: string;
@@ -123,7 +124,7 @@ export interface PendingReserveTask {
  */
 export interface PendingRefreshTask {
   type: PendingTaskType.Refresh;
-  lastError?: TalerErrorDetails;
+  lastError?: TalerErrorDetail;
   refreshGroupId: string;
   finishedPerCoin: boolean[];
   retryInfo: RetryInfo;
@@ -135,10 +136,10 @@ export interface PendingRefreshTask {
 export interface PendingProposalDownloadTask {
   type: PendingTaskType.ProposalDownload;
   merchantBaseUrl: string;
-  proposalTimestamp: Timestamp;
+  proposalTimestamp: TalerProtocolTimestamp;
   proposalId: string;
   orderId: string;
-  lastError?: TalerErrorDetails;
+  lastError?: TalerErrorDetail;
   retryInfo?: RetryInfo;
 }
 
@@ -149,7 +150,7 @@ export interface PendingProposalDownloadTask {
 export interface PendingProposalChoiceOperation {
   type: PendingTaskType.ProposalChoice;
   merchantBaseUrl: string;
-  proposalTimestamp: Timestamp;
+  proposalTimestamp: AbsoluteTime;
   proposalId: string;
 }
 
@@ -172,7 +173,7 @@ export interface PendingPayTask {
   proposalId: string;
   isReplay: boolean;
   retryInfo?: RetryInfo;
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 }
 
 /**
@@ -183,14 +184,14 @@ export interface PendingRefundQueryTask {
   type: PendingTaskType.RefundQuery;
   proposalId: string;
   retryInfo: RetryInfo;
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 }
 
 export interface PendingRecoupTask {
   type: PendingTaskType.Recoup;
   recoupGroupId: string;
   retryInfo: RetryInfo;
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
 }
 
 /**
@@ -198,7 +199,7 @@ export interface PendingRecoupTask {
  */
 export interface PendingWithdrawTask {
   type: PendingTaskType.Withdraw;
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
   retryInfo: RetryInfo;
   withdrawalGroupId: string;
 }
@@ -208,7 +209,7 @@ export interface PendingWithdrawTask {
  */
 export interface PendingDepositTask {
   type: PendingTaskType.Deposit;
-  lastError: TalerErrorDetails | undefined;
+  lastError: TalerErrorDetail | undefined;
   retryInfo: RetryInfo | undefined;
   depositGroupId: string;
 }
@@ -231,7 +232,7 @@ export interface PendingTaskInfoCommon {
   /**
    * Timestamp when the pending operation should be executed next.
    */
-  timestampDue: Timestamp;
+  timestampDue: AbsoluteTime;
 
   /**
    * Retry info.  Currently used to stop the wallet after any operation
diff --git a/packages/taler-wallet-core/src/util/http.ts 
b/packages/taler-wallet-core/src/util/http.ts
index 43fe29bb..31e38b60 100644
--- a/packages/taler-wallet-core/src/util/http.ts
+++ b/packages/taler-wallet-core/src/util/http.ts
@@ -24,19 +24,16 @@
 /**
  * Imports
  */
-import { OperationFailedError, makeErrorDetails } from "../errors.js";
 import {
   Logger,
   Duration,
-  Timestamp,
-  getTimestampNow,
-  timestampAddDuration,
-  timestampMax,
-  TalerErrorDetails,
+  AbsoluteTime,
+  TalerErrorDetail,
   Codec,
   j2s,
 } from "@gnu-taler/taler-util";
 import { TalerErrorCode } from "@gnu-taler/taler-util";
+import { makeErrorDetail, TalerError } from "../errors.js";
 
 const logger = new Logger("http.ts");
 
@@ -128,7 +125,7 @@ type ResponseOrError<T> =
 
 export async function readTalerErrorResponse(
   httpResponse: HttpResponse,
-): Promise<TalerErrorDetails> {
+): Promise<TalerErrorDetail> {
   const errJson = await httpResponse.json();
   const talerErrorCode = errJson.code;
   if (typeof talerErrorCode !== "number") {
@@ -137,16 +134,14 @@ export async function readTalerErrorResponse(
         errJson,
       )}`,
     );
-    throw new OperationFailedError(
-      makeErrorDetails(
-        TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-        "Error response did not contain error code",
-        {
-          requestUrl: httpResponse.requestUrl,
-          requestMethod: httpResponse.requestMethod,
-          httpStatusCode: httpResponse.status,
-        },
-      ),
+    throw TalerError.fromDetail(
+      TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
+      {
+        requestUrl: httpResponse.requestUrl,
+        requestMethod: httpResponse.requestMethod,
+        httpStatusCode: httpResponse.status,
+      },
+      "Error response did not contain error code",
     );
   }
   return errJson;
@@ -154,28 +149,28 @@ export async function readTalerErrorResponse(
 
 export async function readUnexpectedResponseDetails(
   httpResponse: HttpResponse,
-): Promise<TalerErrorDetails> {
+): Promise<TalerErrorDetail> {
   const errJson = await httpResponse.json();
   const talerErrorCode = errJson.code;
   if (typeof talerErrorCode !== "number") {
-    return makeErrorDetails(
+    return makeErrorDetail(
       TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-      "Error response did not contain error code",
       {
         requestUrl: httpResponse.requestUrl,
         requestMethod: httpResponse.requestMethod,
         httpStatusCode: httpResponse.status,
       },
+      "Error response did not contain error code",
     );
   }
-  return makeErrorDetails(
+  return makeErrorDetail(
     TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
-    `Unexpected HTTP status (${httpResponse.status}) in response`,
     {
       requestUrl: httpResponse.requestUrl,
       httpStatusCode: httpResponse.status,
       errorResponse: errJson,
     },
+    `Unexpected HTTP status (${httpResponse.status}) in response`,
   );
 }
 
@@ -194,14 +189,14 @@ export async function 
readSuccessResponseJsonOrErrorCode<T>(
   try {
     parsedResponse = codec.decode(respJson);
   } catch (e: any) {
-    throw OperationFailedError.fromCode(
+    throw TalerError.fromDetail(
       TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-      "Response invalid",
       {
         requestUrl: httpResponse.requestUrl,
         httpStatusCode: httpResponse.status,
         validationError: e.toString(),
       },
+      "Response invalid",
     );
   }
   return {
@@ -223,16 +218,14 @@ export function throwUnexpectedRequestError(
   httpResponse: HttpResponse,
   talerErrorResponse: TalerErrorResponse,
 ): never {
-  throw new OperationFailedError(
-    makeErrorDetails(
-      TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
-      `Unexpected HTTP status ${httpResponse.status} in response`,
-      {
-        requestUrl: httpResponse.requestUrl,
-        httpStatusCode: httpResponse.status,
-        errorResponse: talerErrorResponse,
-      },
-    ),
+  throw TalerError.fromDetail(
+    TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
+    {
+      requestUrl: httpResponse.requestUrl,
+      httpStatusCode: httpResponse.status,
+      errorResponse: talerErrorResponse,
+    },
+    `Unexpected HTTP status ${httpResponse.status} in response`,
   );
 }
 
@@ -254,16 +247,14 @@ export async function 
readSuccessResponseTextOrErrorCode<T>(
     const errJson = await httpResponse.json();
     const talerErrorCode = errJson.code;
     if (typeof talerErrorCode !== "number") {
-      throw new OperationFailedError(
-        makeErrorDetails(
-          TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-          "Error response did not contain error code",
-          {
-            httpStatusCode: httpResponse.status,
-            requestUrl: httpResponse.requestUrl,
-            requestMethod: httpResponse.requestMethod,
-          },
-        ),
+      throw TalerError.fromDetail(
+        TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
+        {
+          httpStatusCode: httpResponse.status,
+          requestUrl: httpResponse.requestUrl,
+          requestMethod: httpResponse.requestMethod,
+        },
+        "Error response did not contain error code",
       );
     }
     return {
@@ -285,16 +276,14 @@ export async function checkSuccessResponseOrThrow(
     const errJson = await httpResponse.json();
     const talerErrorCode = errJson.code;
     if (typeof talerErrorCode !== "number") {
-      throw new OperationFailedError(
-        makeErrorDetails(
-          TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-          "Error response did not contain error code",
-          {
-            httpStatusCode: httpResponse.status,
-            requestUrl: httpResponse.requestUrl,
-            requestMethod: httpResponse.requestMethod,
-          },
-        ),
+      throw TalerError.fromDetail(
+        TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
+        {
+          httpStatusCode: httpResponse.status,
+          requestUrl: httpResponse.requestUrl,
+          requestMethod: httpResponse.requestMethod,
+        },
+        "Error response did not contain error code",
       );
     }
     throwUnexpectedRequestError(httpResponse, errJson);
@@ -314,24 +303,24 @@ export async function readSuccessResponseTextOrThrow<T>(
 /**
  * Get the timestamp at which the response's content is considered expired.
  */
-export function getExpiryTimestamp(
+export function getExpiry(
   httpResponse: HttpResponse,
   opt: { minDuration?: Duration },
-): Timestamp {
+): AbsoluteTime {
   const expiryDateMs = new Date(
     httpResponse.headers.get("expiry") ?? "",
   ).getTime();
-  let t: Timestamp;
+  let t: AbsoluteTime;
   if (Number.isNaN(expiryDateMs)) {
-    t = getTimestampNow();
+    t = AbsoluteTime.now();
   } else {
     t = {
       t_ms: expiryDateMs,
     };
   }
   if (opt.minDuration) {
-    const t2 = timestampAddDuration(getTimestampNow(), opt.minDuration);
-    return timestampMax(t, t2);
+    const t2 = AbsoluteTime.addDuration(AbsoluteTime.now(), opt.minDuration);
+    return AbsoluteTime.max(t, t2);
   }
   return t;
 }
diff --git a/packages/taler-wallet-core/src/util/retries.ts 
b/packages/taler-wallet-core/src/util/retries.ts
index 8dec22be..4b78d38e 100644
--- a/packages/taler-wallet-core/src/util/retries.ts
+++ b/packages/taler-wallet-core/src/util/retries.ts
@@ -21,11 +21,11 @@
 /**
  * Imports.
  */
-import { Timestamp, Duration, getTimestampNow } from "@gnu-taler/taler-util";
+import { AbsoluteTime, Duration } from "@gnu-taler/taler-util";
 
 export interface RetryInfo {
-  firstTry: Timestamp;
-  nextRetry: Timestamp;
+  firstTry: AbsoluteTime;
+  nextRetry: AbsoluteTime;
   retryCounter: number;
 }
 
@@ -45,7 +45,7 @@ export function updateRetryInfoTimeout(
   r: RetryInfo,
   p: RetryPolicy = defaultRetryPolicy,
 ): void {
-  const now = getTimestampNow();
+  const now = AbsoluteTime.now();
   if (now.t_ms === "never") {
     throw Error("assertion failed");
   }
@@ -54,10 +54,14 @@ export function updateRetryInfoTimeout(
     return;
   }
 
-  const nextIncrement = p.backoffDelta.d_ms * Math.pow(p.backoffBase, 
r.retryCounter)
+  const nextIncrement =
+    p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter);
 
   const t =
-    now.t_ms + (p.maxTimeout.d_ms === "forever" ? nextIncrement : 
Math.min(p.maxTimeout.d_ms, nextIncrement));
+    now.t_ms +
+    (p.maxTimeout.d_ms === "forever"
+      ? nextIncrement
+      : Math.min(p.maxTimeout.d_ms, nextIncrement));
   r.nextRetry = { t_ms: t };
 }
 
@@ -73,13 +77,13 @@ export function getRetryDuration(
     return { d_ms: "forever" };
   }
   const t = p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter);
-  return { d_ms: p.maxTimeout.d_ms === "forever" ? t : 
Math.min(p.maxTimeout.d_ms, t) };
+  return {
+    d_ms: p.maxTimeout.d_ms === "forever" ? t : Math.min(p.maxTimeout.d_ms, t),
+  };
 }
 
-export function initRetryInfo(
-  p: RetryPolicy = defaultRetryPolicy,
-): RetryInfo {
-  const now = getTimestampNow();
+export function initRetryInfo(p: RetryPolicy = defaultRetryPolicy): RetryInfo {
+  const now = AbsoluteTime.now();
   const info = {
     firstTry: now,
     nextRetry: now,
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 32941756..cb8b53ad 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -64,9 +64,7 @@ import {
   durationMin,
   ExchangeListItem,
   ExchangesListRespose,
-  getDurationRemaining,
   GetExchangeTosResult,
-  isTimestampExpired,
   j2s,
   KnownBankAccounts,
   Logger,
@@ -76,11 +74,12 @@ import {
   PaytoUri,
   RefreshReason,
   TalerErrorCode,
-  Timestamp,
-  timestampMin,
+  AbsoluteTime,
   URL,
   WalletNotification,
+  Duration,
 } from "@gnu-taler/taler-util";
+import { timeStamp } from "console";
 import {
   DenomInfo,
   ExchangeOperations,
@@ -101,9 +100,8 @@ import {
   WalletStoresV1,
 } from "./db.js";
 import {
-  makeErrorDetails,
-  OperationFailedAndReportedError,
-  OperationFailedError,
+  getErrorDetailFromException,
+  TalerError,
 } from "./errors.js";
 import { exportBackup } from "./operations/backup/export.js";
 import {
@@ -292,16 +290,16 @@ export async function runPending(
 ): Promise<void> {
   const pendingOpsResponse = await getPendingOperations(ws);
   for (const p of pendingOpsResponse.pendingOperations) {
-    if (!forceNow && !isTimestampExpired(p.timestampDue)) {
+    if (!forceNow && !AbsoluteTime.isExpired(p.timestampDue)) {
       continue;
     }
     try {
       await processOnePendingOperation(ws, p, forceNow);
     } catch (e) {
-      if (e instanceof OperationFailedAndReportedError) {
+      if (e instanceof TalerError) {
         console.error(
           "Operation failed:",
-          JSON.stringify(e.operationError, undefined, 2),
+          JSON.stringify(e.errorDetail, undefined, 2),
         );
       } else {
         console.error(e);
@@ -340,10 +338,10 @@ async function runTaskLoop(
     logger.trace(`pending operations: ${j2s(pending)}`);
     let numGivingLiveness = 0;
     let numDue = 0;
-    let minDue: Timestamp = { t_ms: "never" };
+    let minDue: AbsoluteTime = AbsoluteTime.never();
     for (const p of pending.pendingOperations) {
-      minDue = timestampMin(minDue, p.timestampDue);
-      if (isTimestampExpired(p.timestampDue)) {
+      minDue = AbsoluteTime.min(minDue, p.timestampDue);
+      if (AbsoluteTime.isExpired(p.timestampDue)) {
         numDue++;
       }
       if (p.givesLifeness) {
@@ -377,7 +375,7 @@ async function runTaskLoop(
         durationFromSpec({
           seconds: 5,
         }),
-        getDurationRemaining(minDue),
+        Duration.getRemaining(minDue),
       );
       logger.trace(`waiting for at most ${dt.d_ms} ms`);
       const timeout = ws.timerGroup.resolveAfter(dt);
@@ -394,16 +392,22 @@ async function runTaskLoop(
         `running ${pending.pendingOperations.length} pending operations`,
       );
       for (const p of pending.pendingOperations) {
-        if (!isTimestampExpired(p.timestampDue)) {
+        if (!AbsoluteTime.isExpired(p.timestampDue)) {
           continue;
         }
         try {
           await processOnePendingOperation(ws, p);
         } catch (e) {
-          if (e instanceof OperationFailedAndReportedError) {
-            logger.warn("operation processed resulted in reported error");
-            logger.warn(`reported error was: ${j2s(e.operationError)}`);
+          if (
+            e instanceof TalerError &&
+            e.hasErrorCode(TalerErrorCode.WALLET_PENDING_OPERATION_FAILED)
+          ) {
+            logger.warn("operation processed resulted in error");
+            logger.warn(`error was: ${j2s(e.errorDetail)}`);
           } else {
+            // This is a bug, as we expect pending operations to always
+            // do their own error handling and only throw 
WALLET_PENDING_OPERATION_FAILED
+            // or return something.
             logger.error("Uncaught exception", e);
             ws.notify({
               type: NotificationType.InternalError,
@@ -723,7 +727,7 @@ export async function getClientFromWalletState(
       const res = await handleCoreApiRequest(ws, op, `${id++}`, payload);
       switch (res.type) {
         case "error":
-          throw new OperationFailedError(res.error);
+          throw TalerError.fromUncheckedDetail(res.error);
         case "response":
           return res.result;
       }
@@ -1041,12 +1045,12 @@ async function dispatchRequestInternal(
       return [];
     }
   }
-  throw OperationFailedError.fromCode(
+  throw TalerError.fromDetail(
     TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,
-    "unknown operation",
     {
       operation,
     },
+    "unknown operation",
   );
 }
 
@@ -1068,34 +1072,13 @@ export async function handleCoreApiRequest(
       result,
     };
   } catch (e: any) {
-    if (
-      e instanceof OperationFailedError ||
-      e instanceof OperationFailedAndReportedError
-    ) {
-      logger.error("Caught operation failed error");
-      logger.trace((e as any).stack);
-      return {
-        type: "error",
-        operation,
-        id,
-        error: e.operationError,
-      };
-    } else {
-      try {
-        logger.error("Caught unexpected exception:");
-        logger.error(e.stack);
-      } catch (e) {}
-      return {
-        type: "error",
-        operation,
-        id,
-        error: makeErrorDetails(
-          TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
-          `unexpected exception: ${e}`,
-          {},
-        ),
-      };
-    }
+    const err = getErrorDetailFromException(e);
+    return {
+      type: "error",
+      operation,
+      id,
+      error: err,
+    };
   }
 }
 
diff --git a/packages/taler-wallet-embedded/src/index.ts 
b/packages/taler-wallet-embedded/src/index.ts
index e01281bc..64b12f63 100644
--- a/packages/taler-wallet-embedded/src/index.ts
+++ b/packages/taler-wallet-embedded/src/index.ts
@@ -21,7 +21,6 @@ import {
   getDefaultNodeWallet,
   DefaultNodeWalletArgs,
   NodeHttpLib,
-  makeErrorDetails,
   handleWorkerError,
   handleWorkerMessage,
   HttpRequestLibrary,
@@ -33,6 +32,7 @@ import {
   WALLET_EXCHANGE_PROTOCOL_VERSION,
   WALLET_MERCHANT_PROTOCOL_VERSION,
   Wallet,
+  getErrorDetailFromException,
 } from "@gnu-taler/taler-wallet-core";
 
 import fs from "fs";
@@ -270,11 +270,7 @@ export function installNativeWalletListener(): void {
         type: "error",
         id,
         operation,
-        error: makeErrorDetails(
-          TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
-          "unexpected exception",
-          {},
-        ),
+        error: getErrorDetailFromException(e),
       };
       sendNativeMessage(respMsg);
       return;
diff --git a/packages/taler-wallet-webextension/src/browserHttpLib.ts 
b/packages/taler-wallet-webextension/src/browserHttpLib.ts
index 8877edfc..53ab8559 100644
--- a/packages/taler-wallet-webextension/src/browserHttpLib.ts
+++ b/packages/taler-wallet-webextension/src/browserHttpLib.ts
@@ -18,11 +18,11 @@
  * Imports.
  */
 import {
-  OperationFailedError,
   HttpRequestLibrary,
   HttpRequestOptions,
   HttpResponse,
   Headers,
+  TalerError,
 } from "@gnu-taler/taler-wallet-core";
 import {
   Logger,
@@ -49,14 +49,14 @@ export class BrowserHttpLib implements HttpRequestLibrary {
 
     if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) {
       const parsedUrl = new URL(requestUrl);
-      throw OperationFailedError.fromCode(
+      throw TalerError.fromDetail(
         TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED,
-        `request to origin ${parsedUrl.origin} was throttled`,
         {
           requestMethod,
           requestUrl,
           throttleStats: this.throttle.getThrottleStats(requestUrl),
         },
+        `request to origin ${parsedUrl.origin} was throttled`,
       );
     }
 
@@ -78,12 +78,12 @@ export class BrowserHttpLib implements HttpRequestLibrary {
       myRequest.onerror = (e) => {
         logger.error("http request error");
         reject(
-          OperationFailedError.fromCode(
+          TalerError.fromDetail(
             TalerErrorCode.WALLET_NETWORK_ERROR,
-            "Could not make request",
             {
               requestUrl: requestUrl,
             },
+            "Could not make request",
           ),
         );
       };
@@ -91,12 +91,12 @@ export class BrowserHttpLib implements HttpRequestLibrary {
       myRequest.addEventListener("readystatechange", (e) => {
         if (myRequest.readyState === XMLHttpRequest.DONE) {
           if (myRequest.status === 0) {
-            const exc = OperationFailedError.fromCode(
+            const exc = TalerError.fromDetail(
               TalerErrorCode.WALLET_NETWORK_ERROR,
-              "HTTP request failed (status 0, maybe URI scheme was wrong?)",
               {
                 requestUrl: requestUrl,
               },
+              "HTTP request failed (status 0, maybe URI scheme was wrong?)",
             );
             reject(exc);
             return;
@@ -112,23 +112,23 @@ export class BrowserHttpLib implements HttpRequestLibrary 
{
               const responseString = td.decode(myRequest.response);
               responseJson = JSON.parse(responseString);
             } catch (e) {
-              throw OperationFailedError.fromCode(
+              throw TalerError.fromDetail(
                 TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-                "Invalid JSON from HTTP response",
                 {
                   requestUrl: requestUrl,
                   httpStatusCode: myRequest.status,
                 },
+                "Invalid JSON from HTTP response",
               );
             }
             if (responseJson === null || typeof responseJson !== "object") {
-              throw OperationFailedError.fromCode(
+              throw TalerError.fromDetail(
                 TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-                "Invalid JSON from HTTP response",
                 {
                   requestUrl: requestUrl,
                   httpStatusCode: myRequest.status,
                 },
+                "Invalid JSON from HTTP response",
               );
             }
             return responseJson;
diff --git 
a/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx 
b/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx
index 35670909..38d6ec56 100644
--- a/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx
+++ b/packages/taler-wallet-webextension/src/components/ErrorTalerOperation.tsx
@@ -13,7 +13,7 @@
  You should have received a copy of the GNU General Public License along with
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
-import { TalerErrorDetails } from "@gnu-taler/taler-util";
+import { TalerErrorDetail } from "@gnu-taler/taler-util";
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import arrowDown from "../../static/img/chevron-down.svg";
@@ -25,7 +25,7 @@ export function ErrorTalerOperation({
   error,
 }: {
   title?: VNode;
-  error?: TalerErrorDetails;
+  error?: TalerErrorDetail;
 }): VNode | null {
   const { devMode } = useDevContext();
   const [showErrorDetail, setShowErrorDetail] = useState(false);
@@ -59,7 +59,7 @@ export function ErrorTalerOperation({
         <Fragment>
           <div style={{ padding: 5, textAlign: "left" }}>
             <div>
-              <b>{error.message}</b> {!errorHint ? "" : `: ${errorHint}`}{" "}
+              <b>{error.hint}</b> {!errorHint ? "" : `: ${errorHint}`}{" "}
             </div>
           </div>
           {devMode && (
diff --git 
a/packages/taler-wallet-webextension/src/components/PendingTransactions.stories.tsx
 
b/packages/taler-wallet-webextension/src/components/PendingTransactions.stories.tsx
index 658a41aa..ea29d4a7 100644
--- 
a/packages/taler-wallet-webextension/src/components/PendingTransactions.stories.tsx
+++ 
b/packages/taler-wallet-webextension/src/components/PendingTransactions.stories.tsx
@@ -22,7 +22,11 @@
 import { PendingTransactionsView as TestedComponent } from 
"./PendingTransactions";
 import { Fragment, h, VNode } from "preact";
 import { createExample } from "../test-utils";
-import { Transaction, TransactionType } from "@gnu-taler/taler-util";
+import {
+  TalerProtocolTimestamp,
+  Transaction,
+  TransactionType,
+} from "@gnu-taler/taler-util";
 
 export default {
   title: "component/PendingTransactions",
@@ -34,9 +38,7 @@ export const OnePendingTransaction = 
createExample(TestedComponent, {
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
   ],
 });
@@ -46,23 +48,17 @@ export const ThreePendingTransactions = 
createExample(TestedComponent, {
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
   ],
 });
@@ -72,72 +68,52 @@ export const TenPendingTransactions = 
createExample(TestedComponent, {
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1)
     } as Transaction,
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
     {
       amountEffective: "USD:10",
       type: TransactionType.Withdrawal,
-      timestamp: {
-        t_ms: 1,
-      },
+      timestamp: TalerProtocolTimestamp.fromSeconds(1),
     } as Transaction,
   ],
 });
diff --git 
a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx 
b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx
index eed31beb..7923eb6a 100644
--- a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx
+++ b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx
@@ -1,4 +1,9 @@
-import { Amounts, NotificationType, Transaction } from "@gnu-taler/taler-util";
+import {
+  AbsoluteTime,
+  Amounts,
+  NotificationType,
+  Transaction,
+} from "@gnu-taler/taler-util";
 import { PendingTaskInfo } from "@gnu-taler/taler-wallet-core";
 import { Fragment, h, JSX } from "preact";
 import { useAsyncAsHook } from "../hooks/useAsyncAsHook";
@@ -70,7 +75,11 @@ export function PendingTransactionsView({
               <b>
                 {amount.currency} {Amounts.stringifyValue(amount)}
               </b>{" "}
-              - <Time timestamp={t.timestamp} format="dd MMMM yyyy" />
+              -{" "}
+              <Time
+                timestamp={AbsoluteTime.fromTimestamp(t.timestamp)}
+                format="dd MMMM yyyy"
+              />
             </Typography>
           ),
         };
diff --git a/packages/taler-wallet-webextension/src/components/Time.tsx 
b/packages/taler-wallet-webextension/src/components/Time.tsx
index 452b0833..902187aa 100644
--- a/packages/taler-wallet-webextension/src/components/Time.tsx
+++ b/packages/taler-wallet-webextension/src/components/Time.tsx
@@ -14,7 +14,7 @@
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { Timestamp } from "@gnu-taler/taler-util";
+import { AbsoluteTime } from "@gnu-taler/taler-util";
 import { formatISO, format } from "date-fns";
 import { h, VNode } from "preact";
 
@@ -22,7 +22,7 @@ export function Time({
   timestamp,
   format: formatString,
 }: {
-  timestamp: Timestamp | undefined;
+  timestamp: AbsoluteTime | undefined;
   format: string;
 }): VNode {
   return (
diff --git 
a/packages/taler-wallet-webextension/src/components/TransactionItem.tsx 
b/packages/taler-wallet-webextension/src/components/TransactionItem.tsx
index 12ed4139..d7eae7bb 100644
--- a/packages/taler-wallet-webextension/src/components/TransactionItem.tsx
+++ b/packages/taler-wallet-webextension/src/components/TransactionItem.tsx
@@ -18,7 +18,7 @@ import {
   AmountJson,
   Amounts,
   AmountString,
-  Timestamp,
+  AbsoluteTime,
   Transaction,
   TransactionType,
 } from "@gnu-taler/taler-util";
@@ -46,7 +46,7 @@ export function TransactionItem(props: { tx: Transaction }): 
VNode {
           amount={tx.amountEffective}
           debitCreditIndicator={"credit"}
           title={new URL(tx.exchangeBaseUrl).hostname}
-          timestamp={tx.timestamp}
+          timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
           iconPath={"W"}
           pending={tx.pending}
         />
@@ -59,7 +59,7 @@ export function TransactionItem(props: { tx: Transaction }): 
VNode {
           debitCreditIndicator={"debit"}
           title={tx.info.merchant.name}
           subtitle={tx.info.summary}
-          timestamp={tx.timestamp}
+          timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
           iconPath={"P"}
           pending={tx.pending}
         />
@@ -72,7 +72,7 @@ export function TransactionItem(props: { tx: Transaction }): 
VNode {
           debitCreditIndicator={"credit"}
           subtitle={tx.info.summary}
           title={tx.info.merchant.name}
-          timestamp={tx.timestamp}
+          timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
           iconPath={"R"}
           pending={tx.pending}
         />
@@ -84,7 +84,7 @@ export function TransactionItem(props: { tx: Transaction }): 
VNode {
           amount={tx.amountEffective}
           debitCreditIndicator={"credit"}
           title={new URL(tx.merchantBaseUrl).hostname}
-          timestamp={tx.timestamp}
+          timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
           iconPath={"T"}
           pending={tx.pending}
         />
@@ -96,7 +96,7 @@ export function TransactionItem(props: { tx: Transaction }): 
VNode {
           amount={tx.amountEffective}
           debitCreditIndicator={"credit"}
           title={new URL(tx.exchangeBaseUrl).hostname}
-          timestamp={tx.timestamp}
+          timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
           iconPath={"R"}
           pending={tx.pending}
         />
@@ -108,7 +108,7 @@ export function TransactionItem(props: { tx: Transaction 
}): VNode {
           amount={tx.amountEffective}
           debitCreditIndicator={"debit"}
           title={tx.targetPaytoUri}
-          timestamp={tx.timestamp}
+          timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
           iconPath={"D"}
           pending={tx.pending}
         />
@@ -165,7 +165,7 @@ function TransactionLayout(props: TransactionLayoutProps): 
VNode {
 interface TransactionLayoutProps {
   debitCreditIndicator: "debit" | "credit" | "unknown";
   amount: AmountString | "unknown";
-  timestamp: Timestamp;
+  timestamp: AbsoluteTime;
   title: string;
   subtitle?: string;
   id: string;
diff --git a/packages/taler-wallet-webextension/src/cta/Deposit.tsx 
b/packages/taler-wallet-webextension/src/cta/Deposit.tsx
index ac6c1fd6..933195a9 100644
--- a/packages/taler-wallet-webextension/src/cta/Deposit.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Deposit.tsx
@@ -35,7 +35,7 @@ import {
   PreparePayResultType,
   Translate,
 } from "@gnu-taler/taler-util";
-import { OperationFailedError } from "@gnu-taler/taler-wallet-core";
+import { TalerError } from "@gnu-taler/taler-wallet-core";
 import { Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { ErrorTalerOperation } from "../components/ErrorTalerOperation";
@@ -64,9 +64,9 @@ export function DepositPage({ talerPayUri, goBack }: Props): 
VNode {
   const [payResult, setPayResult] = useState<ConfirmPayResult | undefined>(
     undefined,
   );
-  const [payErrMsg, setPayErrMsg] = useState<
-    OperationFailedError | string | undefined
-  >(undefined);
+  const [payErrMsg, setPayErrMsg] = useState<TalerError | string | undefined>(
+    undefined,
+  );
 
   const balance = useAsyncAsHook(wxApi.getBalance, [
     NotificationType.CoinWithdrawn,
@@ -97,7 +97,7 @@ export function DepositPage({ talerPayUri, goBack }: Props): 
VNode {
         setPayStatus(p);
       } catch (e) {
         console.log("Got error while trying to pay", e);
-        if (e instanceof OperationFailedError) {
+        if (e instanceof TalerError) {
           setPayErrMsg(e);
         }
         if (e instanceof Error) {
@@ -117,7 +117,7 @@ export function DepositPage({ talerPayUri, goBack }: 
Props): VNode {
   }
 
   if (!payStatus) {
-    if (payErrMsg instanceof OperationFailedError) {
+    if (payErrMsg instanceof TalerError) {
       return (
         <WalletAction>
           <LogoHeader />
@@ -131,7 +131,7 @@ export function DepositPage({ talerPayUri, goBack }: 
Props): VNode {
                   Could not get the payment information for this order
                 </i18n.Translate>
               }
-              error={payErrMsg?.operationError}
+              error={payErrMsg?.errorDetail}
             />
           </section>
         </WalletAction>
diff --git a/packages/taler-wallet-webextension/src/cta/Pay.tsx 
b/packages/taler-wallet-webextension/src/cta/Pay.tsx
index 2a0d1b46..f6ae02f7 100644
--- a/packages/taler-wallet-webextension/src/cta/Pay.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Pay.tsx
@@ -36,7 +36,7 @@ import {
   PreparePayResultType,
   Product,
 } from "@gnu-taler/taler-util";
-import { OperationFailedError } from "@gnu-taler/taler-wallet-core";
+import { TalerError } from "@gnu-taler/taler-wallet-core";
 import { Fragment, h, VNode } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import { ErrorMessage } from "../components/ErrorMessage";
@@ -93,7 +93,7 @@ export function PayPage({
     undefined,
   );
   const [payErrMsg, setPayErrMsg] = useState<
-    OperationFailedError | string | undefined
+    TalerError | string | undefined
   >(undefined);
 
   const hook = useAsyncAsHook(async () => {
diff --git a/packages/taler-wallet-webextension/src/cta/Tip.stories.tsx 
b/packages/taler-wallet-webextension/src/cta/Tip.stories.tsx
index 8da59951..e475a961 100644
--- a/packages/taler-wallet-webextension/src/cta/Tip.stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Tip.stories.tsx
@@ -19,6 +19,7 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
+import { AbsoluteTime, TalerProtocolTimestamp } from "@gnu-taler/taler-util";
 import { createExample } from "../test-utils";
 import { View as TestedComponent } from "./Tip";
 
@@ -33,9 +34,7 @@ export const Accepted = createExample(TestedComponent, {
     accepted: true,
     merchantBaseUrl: "",
     exchangeBaseUrl: "",
-    expirationTimestamp: {
-      t_ms: 0,
-    },
+    expirationTimestamp: TalerProtocolTimestamp.fromSeconds(1),
     tipAmountEffective: "USD:10",
     tipAmountRaw: "USD:5",
     walletTipId: "id",
@@ -47,9 +46,7 @@ export const NotYetAccepted = createExample(TestedComponent, {
     accepted: false,
     merchantBaseUrl: "http://merchant.url/";,
     exchangeBaseUrl: "http://exchange.url/";,
-    expirationTimestamp: {
-      t_ms: 0,
-    },
+    expirationTimestamp: TalerProtocolTimestamp.fromSeconds(1),
     tipAmountEffective: "USD:10",
     tipAmountRaw: "USD:5",
     walletTipId: "id",
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx 
b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx
index 66c83cf1..d58e2ec8 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw.tsx
@@ -25,10 +25,8 @@ import {
   AmountJson,
   Amounts,
   ExchangeListItem,
-  Translate,
   WithdrawUriInfoResponse,
 } from "@gnu-taler/taler-util";
-import { OperationFailedError } from "@gnu-taler/taler-wallet-core";
 import { Fragment, h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { Loading } from "../components/Loading";
@@ -52,6 +50,7 @@ import {
 import * as wxApi from "../wxApi";
 import { TermsOfServiceSection } from "./TermsOfServiceSection";
 import { useTranslationContext } from "../context/translation";
+import { TalerError } from "@gnu-taler/taler-wallet-core";
 
 interface Props {
   talerWithdrawUri?: string;
@@ -85,9 +84,9 @@ export function View({
   reviewed,
 }: ViewProps): VNode {
   const { i18n } = useTranslationContext();
-  const [withdrawError, setWithdrawError] = useState<
-    OperationFailedError | undefined
-  >(undefined);
+  const [withdrawError, setWithdrawError] = useState<TalerError | undefined>(
+    undefined,
+  );
   const [confirmDisabled, setConfirmDisabled] = useState<boolean>(false);
 
   const needsReview = terms.status === "changed" || terms.status === "new";
@@ -109,7 +108,7 @@ export function View({
       setConfirmDisabled(true);
       await onWithdraw();
     } catch (e) {
-      if (e instanceof OperationFailedError) {
+      if (e instanceof TalerError) {
         setWithdrawError(e);
       }
       setConfirmDisabled(false);
@@ -130,7 +129,7 @@ export function View({
               Could not finish the withdrawal operation
             </i18n.Translate>
           }
-          error={withdrawError.operationError}
+          error={withdrawError.errorDetail}
         />
       )}
 
diff --git a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts 
b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
index 6efa1d18..8d31de94 100644
--- a/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useAsyncAsHook.ts
@@ -13,28 +13,32 @@
  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
-import { NotificationType, TalerErrorDetails } from "@gnu-taler/taler-util";
+import {
+  NotificationType,
+  TalerErrorCode,
+  TalerErrorDetail,
+} from "@gnu-taler/taler-util";
+import { TalerError } from "@gnu-taler/taler-wallet-core";
 import { useEffect, useState } from "preact/hooks";
 import * as wxApi from "../wxApi";
-import { OperationFailedError } from "@gnu-taler/taler-wallet-core";
 
 interface HookOk<T> {
   hasError: false;
   response: T;
 }
 
-export type HookError = HookGenericError | HookOperationalError
+export type HookError = HookGenericError | HookOperationalError;
 
 export interface HookGenericError {
   hasError: true;
-  operational: false,
+  operational: false;
   message: string;
 }
 
 export interface HookOperationalError {
   hasError: true;
-  operational: true,
-  details: TalerErrorDetails;
+  operational: true;
+  details: TalerErrorDetail;
 }
 
 export type HookResponse<T> = HookOk<T> | HookError | undefined;
@@ -51,10 +55,18 @@ export function useAsyncAsHook<T>(
         const response = await fn();
         setHookResponse({ hasError: false, response });
       } catch (e) {
-        if (e instanceof OperationFailedError) {
-          setHookResponse({ hasError: true, operational: true, details: 
e.operationError });
+        if (e instanceof TalerError) {
+          setHookResponse({
+            hasError: true,
+            operational: true,
+            details: e.errorDetail,
+          });
         } else if (e instanceof Error) {
-          setHookResponse({ hasError: true, operational: false, message: 
e.message });
+          setHookResponse({
+            hasError: true,
+            operational: false,
+            message: e.message,
+          });
         }
       }
     }
diff --git a/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx 
b/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx
index 738ece2f..3deea032 100644
--- a/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx
+++ b/packages/taler-wallet-webextension/src/popup/DeveloperPage.tsx
@@ -20,7 +20,7 @@ import {
   CoinDumpJson,
   ExchangeListItem,
   NotificationType,
-  Timestamp,
+  AbsoluteTime,
   Translate,
 } from "@gnu-taler/taler-util";
 import { PendingTaskInfo } from "@gnu-taler/taler-wallet-core";
diff --git a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts 
b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts
index 6f2585c1..4dee28a6 100644
--- a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts
+++ b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts
@@ -23,7 +23,7 @@ import {
   HttpRequestLibrary,
   HttpRequestOptions,
   HttpResponse,
-  OperationFailedError,
+  TalerError,
 } from "@gnu-taler/taler-wallet-core";
 
 /**
@@ -44,14 +44,14 @@ export class ServiceWorkerHttpLib implements 
HttpRequestLibrary {
 
     if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) {
       const parsedUrl = new URL(requestUrl);
-      throw OperationFailedError.fromCode(
+      throw TalerError.fromDetail(
         TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED,
-        `request to origin ${parsedUrl.origin} was throttled`,
         {
           requestMethod,
           requestUrl,
           throttleStats: this.throttle.getThrottleStats(requestUrl),
         },
+        `request to origin ${parsedUrl.origin} was throttled`,
       );
     }
 
@@ -107,13 +107,13 @@ function makeTextHandler(response: Response, requestUrl: 
string) {
     try {
       respText = await response.text();
     } catch (e) {
-      throw OperationFailedError.fromCode(
+      throw TalerError.fromDetail(
         TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-        "Invalid JSON from HTTP response",
         {
           requestUrl,
           httpStatusCode: response.status,
         },
+        "Invalid JSON from HTTP response",
       );
     }
     return respText;
@@ -126,23 +126,23 @@ function makeJsonHandler(response: Response, requestUrl: 
string) {
     try {
       responseJson = await response.json();
     } catch (e) {
-      throw OperationFailedError.fromCode(
+      throw TalerError.fromDetail(
         TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-        "Invalid JSON from HTTP response",
         {
           requestUrl,
           httpStatusCode: response.status,
         },
+        "Invalid JSON from HTTP response",
       );
     }
     if (responseJson === null || typeof responseJson !== "object") {
-      throw OperationFailedError.fromCode(
+      throw TalerError.fromDetail(
         TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
-        "Invalid JSON from HTTP response",
         {
           requestUrl,
           httpStatusCode: response.status,
         },
+        "Invalid JSON from HTTP response",
       );
     }
     return responseJson;
diff --git a/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx
index b2771bc2..92536db8 100644
--- a/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Backup.stories.tsx
@@ -23,6 +23,7 @@ import { ProviderPaymentType } from 
"@gnu-taler/taler-wallet-core";
 import { addDays } from "date-fns";
 import { BackupView as TestedComponent } from "./BackupPage";
 import { createExample } from "../test-utils";
+import { TalerProtocolTimestamp } from "@gnu-taler/taler-util";
 
 export default {
   title: "wallet/backup/list",
@@ -40,9 +41,8 @@ export const LotOfProviders = createExample(TestedComponent, {
       active: true,
       name: "sync.demo",
       syncProviderBaseUrl: "http://sync.taler:9967/";,
-      lastSuccessfulBackupTimestamp: {
-        t_ms: 1625063925078,
-      },
+      lastSuccessfulBackupTimestamp:
+        TalerProtocolTimestamp.fromSeconds(1625063925),
       paymentProposalIds: [
         "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
       ],
@@ -62,9 +62,8 @@ export const LotOfProviders = createExample(TestedComponent, {
       active: true,
       name: "sync.demo",
       syncProviderBaseUrl: "http://sync.taler:9967/";,
-      lastSuccessfulBackupTimestamp: {
-        t_ms: 1625063925078,
-      },
+      lastSuccessfulBackupTimestamp:
+        TalerProtocolTimestamp.fromSeconds(1625063925),
       paymentProposalIds: [
         "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
       ],
@@ -172,9 +171,8 @@ export const OneProvider = createExample(TestedComponent, {
       active: true,
       name: "sync.demo",
       syncProviderBaseUrl: "http://sync.taler:9967/";,
-      lastSuccessfulBackupTimestamp: {
-        t_ms: 1625063925078,
-      },
+      lastSuccessfulBackupTimestamp:
+        TalerProtocolTimestamp.fromSeconds(1625063925),
       paymentProposalIds: [
         "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
       ],
diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
index e1d34748..8c12201b 100644
--- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx
@@ -14,7 +14,7 @@
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 
-import { Timestamp, Translate } from "@gnu-taler/taler-util";
+import { AbsoluteTime, Translate } from "@gnu-taler/taler-util";
 import {
   ProviderInfo,
   ProviderPaymentPaid,
@@ -104,7 +104,13 @@ export function BackupView({
           <BackupLayout
             key={idx}
             status={provider.paymentStatus}
-            timestamp={provider.lastSuccessfulBackupTimestamp}
+            timestamp={
+              provider.lastSuccessfulBackupTimestamp
+                ? AbsoluteTime.fromTimestamp(
+                    provider.lastSuccessfulBackupTimestamp,
+                  )
+                : undefined
+            }
             id={provider.syncProviderBaseUrl}
             active={provider.active}
             title={provider.name}
@@ -144,7 +150,7 @@ export function BackupView({
 
 interface TransactionLayoutProps {
   status: ProviderPaymentStatus;
-  timestamp?: Timestamp;
+  timestamp?: AbsoluteTime;
   title: string;
   id: string;
   active: boolean;
@@ -192,7 +198,7 @@ function BackupLayout(props: TransactionLayoutProps): VNode 
{
   );
 }
 
-function ExpirationText({ until }: { until: Timestamp }): VNode {
+function ExpirationText({ until }: { until: AbsoluteTime }): VNode {
   const { i18n } = useTranslationContext();
   return (
     <Fragment>
@@ -207,13 +213,13 @@ function ExpirationText({ until }: { until: Timestamp }): 
VNode {
   );
 }
 
-function colorByTimeToExpire(d: Timestamp): string {
+function colorByTimeToExpire(d: AbsoluteTime): string {
   if (d.t_ms === "never") return "rgb(28, 184, 65)";
   const months = differenceInMonths(d.t_ms, new Date());
   return months > 1 ? "rgb(28, 184, 65)" : "rgb(223, 117, 20)";
 }
 
-function daysUntil(d: Timestamp): string {
+function daysUntil(d: AbsoluteTime): string {
   if (d.t_ms === "never") return "";
   const duration = intervalToDuration({
     start: d.t_ms,
diff --git a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
index bd52995d..8138e63d 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.stories.tsx
@@ -21,6 +21,7 @@
 
 import {
   PaymentStatus,
+  TalerProtocolTimestamp,
   TransactionCommon,
   TransactionDeposit,
   TransactionPayment,
@@ -45,9 +46,9 @@ const commonTransaction = () =>
     amountRaw: "USD:10",
     amountEffective: "USD:9",
     pending: false,
-    timestamp: {
-      t_ms: new Date().getTime() - count++ * 1000 * 60 * 60 * 7,
-    },
+    timestamp: TalerProtocolTimestamp.fromSeconds(
+      new Date().getTime() - count++ * 60 * 60 * 7,
+    ),
     transactionId: "12",
   } as TransactionCommon);
 
diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx 
b/packages/taler-wallet-webextension/src/wallet/History.tsx
index 02fc0a76..5d00e2e0 100644
--- a/packages/taler-wallet-webextension/src/wallet/History.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/History.tsx
@@ -126,7 +126,7 @@ export function HistoryView({
     .filter((t) => t.amountRaw.split(":")[0] === selectedCurrency)
     .reduce((rv, x) => {
       const theDate =
-        x.timestamp.t_ms === "never" ? 0 : normalizeToDay(x.timestamp.t_ms);
+        x.timestamp.t_s === "never" ? 0 : normalizeToDay(x.timestamp.t_s * 
1000);
       if (theDate) {
         (rv[theDate] = rv[theDate] || []).push(x);
       }
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx
index a170620a..c4c070fa 100644
--- a/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetail.stories.tsx
@@ -19,6 +19,7 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
+import { TalerProtocolTimestamp } from "@gnu-taler/taler-util";
 import { ProviderPaymentType } from "@gnu-taler/taler-wallet-core";
 import { createExample } from "../test-utils";
 import { ProviderView as TestedComponent } from "./ProviderDetailPage";
@@ -38,9 +39,8 @@ export const Active = createExample(TestedComponent, {
     active: true,
     name: "sync.demo",
     syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
+    lastSuccessfulBackupTimestamp:
+      TalerProtocolTimestamp.fromSeconds(1625063925),
     paymentProposalIds: [
       "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
     ],
@@ -63,12 +63,10 @@ export const ActiveErrorSync = 
createExample(TestedComponent, {
     active: true,
     name: "sync.demo",
     syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
-    lastAttemptedBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
+    lastSuccessfulBackupTimestamp:
+      TalerProtocolTimestamp.fromSeconds(1625063925),
+    lastAttemptedBackupTimestamp:
+      TalerProtocolTimestamp.fromSeconds(1625063925078),
     paymentProposalIds: [
       "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
     ],
@@ -97,9 +95,8 @@ export const ActiveBackupProblemUnreadable = 
createExample(TestedComponent, {
     active: true,
     name: "sync.demo",
     syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
+    lastSuccessfulBackupTimestamp:
+      TalerProtocolTimestamp.fromSeconds(1625063925),
     paymentProposalIds: [
       "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
     ],
@@ -125,9 +122,8 @@ export const ActiveBackupProblemDevice = 
createExample(TestedComponent, {
     active: true,
     name: "sync.demo",
     syncProviderBaseUrl: "http://sync.taler:9967/";,
-    lastSuccessfulBackupTimestamp: {
-      t_ms: 1625063925078,
-    },
+    lastSuccessfulBackupTimestamp:
+      TalerProtocolTimestamp.fromSeconds(1625063925078),
     paymentProposalIds: [
       "43Q5WWRJPNS4SE9YKS54H9THDS94089EDGXW9EHBPN6E7M184XEG",
     ],
diff --git 
a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx 
b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
index 765f34a0..afd9612e 100644
--- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx
@@ -15,6 +15,7 @@
 */
 
 import * as utils from "@gnu-taler/taler-util";
+import { AbsoluteTime } from "@gnu-taler/taler-util";
 import {
   ProviderInfo,
   ProviderPaymentStatus,
@@ -122,7 +123,9 @@ export function ProviderView({
       </Fragment>
     );
   }
-  const lb = info.lastSuccessfulBackupTimestamp;
+  const lb = info.lastSuccessfulBackupTimestamp
+    ? AbsoluteTime.fromTimestamp(info.lastSuccessfulBackupTimestamp)
+    : undefined;
   const isPaid =
     info.paymentStatus.type === ProviderPaymentType.Paid ||
     info.paymentStatus.type === ProviderPaymentType.TermsChanged;
diff --git 
a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
index 7b6ac1fd..f0293842 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.stories.tsx
@@ -21,6 +21,7 @@
 
 import {
   PaymentStatus,
+  TalerProtocolTimestamp,
   TransactionCommon,
   TransactionDeposit,
   TransactionPayment,
@@ -53,9 +54,7 @@ const commonTransaction = {
   amountRaw: "KUDOS:11",
   amountEffective: "KUDOS:9.2",
   pending: false,
-  timestamp: {
-    t_ms: new Date().getTime(),
-  },
+  timestamp: TalerProtocolTimestamp.now(),
   transactionId: "12",
 } as TransactionCommon;
 
@@ -144,18 +143,14 @@ export const Withdraw = createExample(TestedComponent, {
 export const WithdrawOneMinuteAgo = createExample(TestedComponent, {
   transaction: {
     ...exampleData.withdraw,
-    timestamp: {
-      t_ms: new Date().getTime() - 60 * 1000,
-    },
+    timestamp: TalerProtocolTimestamp.fromSeconds(new Date().getTime() - 60),
   },
 });
 
 export const WithdrawOneMinuteAgoAndPending = createExample(TestedComponent, {
   transaction: {
     ...exampleData.withdraw,
-    timestamp: {
-      t_ms: new Date().getTime() - 60 * 1000,
-    },
+    timestamp: TalerProtocolTimestamp.fromSeconds(new Date().getTime() - 60),
     pending: true,
   },
 });
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx 
b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index b367416f..0e58aaf6 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -15,6 +15,7 @@
  */
 
 import {
+  AbsoluteTime,
   AmountLike,
   Amounts,
   NotificationType,
@@ -137,9 +138,9 @@ export function TransactionView({
   }): VNode {
     const showRetry =
       transaction.error !== undefined ||
-      transaction.timestamp.t_ms === "never" ||
+      transaction.timestamp.t_s === "never" ||
       (transaction.pending &&
-        differenceInSeconds(new Date(), transaction.timestamp.t_ms) > 10);
+        differenceInSeconds(new Date(), transaction.timestamp.t_s * 1000) > 
10);
 
     return (
       <Fragment>
@@ -218,7 +219,7 @@ export function TransactionView({
         <h2>
           <i18n.Translate>Withdrawal</i18n.Translate>
         </h2>
-        <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
+        <Time timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)} 
format="dd MMMM yyyy, HH:mm" />
         {transaction.pending ? (
           transaction.withdrawalDetails.type ===
           WithdrawalType.ManualTransfer ? (
@@ -342,7 +343,7 @@ export function TransactionView({
         <h2>
           <i18n.Translate>Payment</i18n.Translate>
         </h2>
-        <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
+        <Time timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)} 
format="dd MMMM yyyy, HH:mm" />
         <br />
         <Part
           big
@@ -425,7 +426,7 @@ export function TransactionView({
         <h2>
           <i18n.Translate>Deposit</i18n.Translate>
         </h2>
-        <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
+        <Time timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)} 
format="dd MMMM yyyy, HH:mm" />
         <br />
         <Part
           big
@@ -459,7 +460,7 @@ export function TransactionView({
         <h2>
           <i18n.Translate>Refresh</i18n.Translate>
         </h2>
-        <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
+        <Time timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)} 
format="dd MMMM yyyy, HH:mm" />
         <br />
         <Part
           big
@@ -493,7 +494,7 @@ export function TransactionView({
         <h2>
           <i18n.Translate>Tip</i18n.Translate>
         </h2>
-        <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
+        <Time timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)} 
format="dd MMMM yyyy, HH:mm" />
         <br />
         <Part
           big
@@ -527,7 +528,10 @@ export function TransactionView({
         <h2>
           <i18n.Translate>Refund</i18n.Translate>
         </h2>
-        <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" />
+        <Time
+          timestamp={AbsoluteTime.fromTimestamp(transaction.timestamp)}
+          format="dd MMMM yyyy, HH:mm"
+        />
         <br />
         <Part
           big
diff --git a/packages/taler-wallet-webextension/src/wxApi.ts 
b/packages/taler-wallet-webextension/src/wxApi.ts
index 31b46d88..2071f85e 100644
--- a/packages/taler-wallet-webextension/src/wxApi.ts
+++ b/packages/taler-wallet-webextension/src/wxApi.ts
@@ -23,26 +23,48 @@
  */
 import {
   AcceptExchangeTosRequest,
-  AcceptManualWithdrawalResult, AcceptTipRequest, AcceptWithdrawalResponse,
-  AddExchangeRequest, AmountString, ApplyRefundResponse, BalancesResponse, 
CoinDumpJson, ConfirmPayResult,
-  CoreApiResponse, CreateDepositGroupRequest, CreateDepositGroupResponse, 
DeleteTransactionRequest, ExchangesListRespose,
-  GetExchangeTosResult, GetExchangeWithdrawalInfo,
+  AcceptManualWithdrawalResult,
+  AcceptTipRequest,
+  AcceptWithdrawalResponse,
+  AddExchangeRequest,
+  AmountString,
+  ApplyRefundResponse,
+  BalancesResponse,
+  CoinDumpJson,
+  ConfirmPayResult,
+  CoreApiResponse,
+  CreateDepositGroupRequest,
+  CreateDepositGroupResponse,
+  DeleteTransactionRequest,
+  ExchangesListRespose,
+  GetExchangeTosResult,
+  GetExchangeWithdrawalInfo,
   GetFeeForDepositRequest,
-  GetWithdrawalDetailsForUriRequest, KnownBankAccounts, NotificationType, 
PreparePayResult, PrepareTipRequest,
-  PrepareTipResult, RetryTransactionRequest,
-  SetWalletDeviceIdRequest, TransactionsResponse, WalletDiagnostics, 
WithdrawUriInfoResponse
+  GetWithdrawalDetailsForUriRequest,
+  KnownBankAccounts,
+  NotificationType,
+  PreparePayResult,
+  PrepareTipRequest,
+  PrepareTipResult,
+  RetryTransactionRequest,
+  SetWalletDeviceIdRequest,
+  TransactionsResponse,
+  WalletDiagnostics,
+  WithdrawUriInfoResponse,
 } from "@gnu-taler/taler-util";
 import {
-  AddBackupProviderRequest, BackupInfo, OperationFailedError,
+  AddBackupProviderRequest,
+  BackupInfo,
   PendingOperationsResponse,
-  RemoveBackupProviderRequest
+  RemoveBackupProviderRequest,
+  TalerError,
 } from "@gnu-taler/taler-wallet-core";
 import { DepositFee } from 
"@gnu-taler/taler-wallet-core/src/operations/deposits";
 import type { ExchangeWithdrawDetails } from 
"@gnu-taler/taler-wallet-core/src/operations/withdraw";
 import { MessageFromBackend } from "./wxBackend";
 
 /**
- * 
+ *
  * @autor Florian Dold
  * @autor sebasjm
  */
@@ -88,7 +110,7 @@ async function callBackend(operation: string, payload: any): 
Promise<any> {
       console.log("got response", resp);
       const r = resp as CoreApiResponse;
       if (r.type === "error") {
-        reject(new OperationFailedError(r.error));
+        reject(TalerError.fromUncheckedDetail(r.error));
         return;
       }
       resolve(r.result);
@@ -127,15 +149,23 @@ export function resetDb(): Promise<void> {
   return callBackend("reset-db", {});
 }
 
-export function getFeeForDeposit(depositPaytoUri: string, amount: 
AmountString): Promise<DepositFee> {
+export function getFeeForDeposit(
+  depositPaytoUri: string,
+  amount: AmountString,
+): Promise<DepositFee> {
   return callBackend("getFeeForDeposit", {
-    depositPaytoUri, amount
+    depositPaytoUri,
+    amount,
   } as GetFeeForDepositRequest);
 }
 
-export function createDepositGroup(depositPaytoUri: string, amount: 
AmountString): Promise<CreateDepositGroupResponse> {
+export function createDepositGroup(
+  depositPaytoUri: string,
+  amount: AmountString,
+): Promise<CreateDepositGroupResponse> {
   return callBackend("createDepositGroup", {
-    depositPaytoUri, amount
+    depositPaytoUri,
+    amount,
   } as CreateDepositGroupRequest);
 }
 
@@ -190,7 +220,9 @@ export function listKnownCurrencies(): 
Promise<ListOfKnownCurrencies> {
 export function listExchanges(): Promise<ExchangesListRespose> {
   return callBackend("listExchanges", {});
 }
-export function listKnownBankAccounts(currency?: string): 
Promise<KnownBankAccounts> {
+export function listKnownBankAccounts(
+  currency?: string,
+): Promise<KnownBankAccounts> {
   return callBackend("listKnownBankAccounts", { currency });
 }
 
@@ -387,14 +419,17 @@ export function exportDB(): Promise<any> {
 }
 
 export function importDB(dump: any): Promise<void> {
-  return callBackend("importDb", { dump })
+  return callBackend("importDb", { dump });
 }
 
-export function onUpdateNotification(messageTypes: Array<NotificationType>, 
doCallback: () => void): () => void {
+export function onUpdateNotification(
+  messageTypes: Array<NotificationType>,
+  doCallback: () => void,
+): () => void {
   // eslint-disable-next-line no-undef
   const port = chrome.runtime.connect({ name: "notifications" });
   const listener = (message: MessageFromBackend): void => {
-    const shouldNotify = messageTypes.includes(message.type)
+    const shouldNotify = messageTypes.includes(message.type);
     if (shouldNotify) {
       doCallback();
     }
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index e158d294..b7a0cdc5 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -35,7 +35,7 @@ import {
 import {
   DbAccess,
   deleteTalerDatabase,
-  makeErrorDetails,
+  makeErrorDetail,
   OpenedPromise,
   openPromise,
   openTalerDatabase,
@@ -167,10 +167,10 @@ async function dispatch(
           type: "error",
           id: req.id,
           operation: req.operation,
-          error: makeErrorDetails(
+          error: makeErrorDetail(
             TalerErrorCode.WALLET_CORE_NOT_AVAILABLE,
-            "wallet core not available",
             {},
+            "wallet core not available",
           ),
         };
         break;
@@ -233,7 +233,10 @@ function makeSyncWalletRedirect(
     const tab = await getTab(tabId);
     if (tab.url === oldUrl) {
       console.log("redirecting to", innerUrl.href);
-      chrome.tabs.update(tabId, { url: innerUrl.href, loadReplace: true } as 
any);
+      chrome.tabs.update(tabId, {
+        url: innerUrl.href,
+        loadReplace: true,
+      } as any);
     }
   };
   doit();

-- 
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]